diff options
Diffstat (limited to 'drivers/platform/x86')
25 files changed, 4199 insertions, 536 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 3e1b8a288719..79baa6368f79 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -5,6 +5,7 @@ menuconfig X86_PLATFORM_DEVICES bool "X86 Platform Specific Device Drivers" default y + depends on X86 ---help--- Say Y here to get to see options for device drivers for various x86 platforms, including vendor-specific laptop extension drivers. @@ -151,6 +152,7 @@ config MSI_LAPTOP depends on ACPI depends on BACKLIGHT_CLASS_DEVICE depends on RFKILL + depends on SERIO_I8042 ---help--- This is a driver for laptops built by MSI (MICRO-STAR INTERNATIONAL): @@ -181,6 +183,8 @@ config COMPAL_LAPTOP depends on ACPI depends on BACKLIGHT_CLASS_DEVICE depends on RFKILL + depends on HWMON + depends on POWER_SUPPLY ---help--- This is a driver for laptops built by Compal: @@ -520,6 +524,7 @@ config TOSHIBA_BT_RFKILL config ACPI_CMPC tristate "CMPC Laptop Extras" depends on X86 && ACPI + depends on RFKILL || RFKILL=n select INPUT select BACKLIGHT_CLASS_DEVICE default n @@ -537,4 +542,43 @@ config INTEL_SCU_IPC some embedded Intel x86 platforms. This is not needed for PC-type machines. +config GPIO_INTEL_PMIC + bool "Intel PMIC GPIO support" + depends on INTEL_SCU_IPC && GPIOLIB + ---help--- + Say Y here to support GPIO via the SCU IPC interface + on Intel MID platforms. + +config RAR_REGISTER + bool "Restricted Access Region Register Driver" + depends on PCI && X86_MRST + default n + ---help--- + This driver allows other kernel drivers access to the + contents of the restricted access region control registers. + + The restricted access region control registers + (rar_registers) are used to pass address and + locking information on restricted access regions + to other drivers that use restricted access regions. + + The restricted access regions are regions of memory + on the Intel MID Platform that are not accessible to + the x86 processor, but are accessible to dedicated + processors on board peripheral devices. + + The purpose of the restricted access regions is to + protect sensitive data from compromise by unauthorized + programs running on the x86 processor. + +config INTEL_IPS + tristate "Intel Intelligent Power Sharing" + depends on ACPI + ---help--- + Intel Calpella platforms support dynamic power sharing between the + CPU and GPU, maximizing performance in a given TDP. This driver, + along with the CPU frequency and i915 drivers, provides that + functionality. If in doubt, say Y here; it will only load on + supported platforms. + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 8770bfe71431..4744c7744ffa 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o +obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.o +obj-$(CONFIG_INTEL_IPS) += intel_ips.o +obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o + diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 1ea6c434d330..2badee2fdeed 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -50,17 +50,6 @@ MODULE_LICENSE("GPL"); #define ACER_INFO KERN_INFO ACER_LOGPREFIX /* - * The following defines quirks to get some specific functions to work - * which are known to not be supported over ACPI-WMI (such as the mail LED - * on WMID based Acer's) - */ -struct acer_quirks { - const char *vendor; - const char *model; - u16 quirks; -}; - -/* * Magic Number * Meaning is unknown - this number is required for writing to ACPI for AMW0 * (it's also used in acerhk when directly accessing the BIOS) @@ -200,7 +189,7 @@ static void set_quirks(void) static int dmi_matched(const struct dmi_system_id *dmi) { quirks = dmi->driver_data; - return 0; + return 1; } static struct quirk_entry quirk_unknown = { @@ -555,6 +544,7 @@ static acpi_status AMW0_find_mailled(void) obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { + kfree(out.pointer); return AE_ERROR; } @@ -570,7 +560,7 @@ static acpi_status AMW0_set_capabilities(void) { struct wmab_args args; struct wmab_ret ret; - acpi_status status = AE_OK; + acpi_status status; struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; @@ -593,12 +583,13 @@ static acpi_status AMW0_set_capabilities(void) if (ACPI_FAILURE(status)) return status; - obj = (union acpi_object *) out.pointer; + obj = out.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { - return AE_ERROR; + status = AE_ERROR; + goto out; } if (ret.eax & 0x1) @@ -607,23 +598,26 @@ static acpi_status AMW0_set_capabilities(void) args.ebx = 2 << 8; args.ebx |= ACER_AMW0_BLUETOOTH_MASK; + /* + * It's ok to use existing buffer for next wmab_execute call. + * But we need to kfree(out.pointer) if next wmab_execute fail. + */ status = wmab_execute(&args, &out); if (ACPI_FAILURE(status)) - return status; + goto out; obj = (union acpi_object *) out.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == sizeof(struct wmab_ret)) { ret = *((struct wmab_ret *) obj->buffer.pointer); } else { - return AE_ERROR; + status = AE_ERROR; + goto out; } if (ret.eax & 0x1) interface->capability |= ACER_CAP_BLUETOOTH; - kfree(out.pointer); - /* * This appears to be safe to enable, since all Wistron based laptops * appear to use the same EC register for brightness, even if they @@ -632,7 +626,10 @@ static acpi_status AMW0_set_capabilities(void) if (quirks->brightness >= 0) interface->capability |= ACER_CAP_BRIGHTNESS; - return AE_OK; + status = AE_OK; +out: + kfree(out.pointer); + return status; } static struct wmi_interface AMW0_interface = { @@ -772,6 +769,7 @@ static acpi_status WMID_set_capabilities(void) obj->buffer.length == sizeof(u32)) { devices = *((u32 *) obj->buffer.pointer); } else { + kfree(out.pointer); return AE_ERROR; } @@ -788,6 +786,7 @@ static acpi_status WMID_set_capabilities(void) if (!(devices & 0x20)) max_brightness = 0x9; + kfree(out.pointer); return status; } @@ -1084,8 +1083,7 @@ static ssize_t show_interface(struct device *dev, struct device_attribute *attr, } } -static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR, - show_interface, NULL); +static DEVICE_ATTR(interface, S_IRUGO, show_interface, NULL); /* * debugfs functions @@ -1095,6 +1093,7 @@ static u32 get_wmid_devices(void) struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; acpi_status status; + u32 devices = 0; status = wmi_query_block(WMID_GUID2, 1, &out); if (ACPI_FAILURE(status)) @@ -1103,10 +1102,11 @@ static u32 get_wmid_devices(void) obj = (union acpi_object *) out.pointer; if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == sizeof(u32)) { - return *((u32 *) obj->buffer.pointer); - } else { - return 0; + devices = *((u32 *) obj->buffer.pointer); } + + kfree(out.pointer); + return devices; } /* @@ -1327,22 +1327,31 @@ static int __init acer_wmi_init(void) "generic video driver\n"); } - if (platform_driver_register(&acer_platform_driver)) { + err = platform_driver_register(&acer_platform_driver); + if (err) { printk(ACER_ERR "Unable to register platform driver.\n"); goto error_platform_register; } + acer_platform_device = platform_device_alloc("acer-wmi", -1); - platform_device_add(acer_platform_device); + if (!acer_platform_device) { + err = -ENOMEM; + goto error_device_alloc; + } + + err = platform_device_add(acer_platform_device); + if (err) + goto error_device_add; err = create_sysfs(); if (err) - return err; + goto error_create_sys; if (wmi_has_guid(WMID_GUID2)) { interface->debug.wmid_devices = get_wmid_devices(); err = create_debugfs(); if (err) - return err; + goto error_create_debugfs; } /* Override any initial settings with values from the commandline */ @@ -1350,15 +1359,23 @@ static int __init acer_wmi_init(void) return 0; +error_create_debugfs: + remove_sysfs(acer_platform_device); +error_create_sys: + platform_device_del(acer_platform_device); +error_device_add: + platform_device_put(acer_platform_device); +error_device_alloc: + platform_driver_unregister(&acer_platform_driver); error_platform_register: - return -ENODEV; + return err; } static void __exit acer_wmi_exit(void) { remove_sysfs(acer_platform_device); remove_debugfs(); - platform_device_del(acer_platform_device); + platform_device_unregister(acer_platform_device); platform_driver_unregister(&acer_platform_driver); printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n"); diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 7b2384d674d0..60f9cfcac93f 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -52,7 +52,7 @@ */ #undef START_IN_KERNEL_MODE -#define DRV_VER "0.5.22" +#define DRV_VER "0.5.24" /* * According to the Atom N270 datasheet, @@ -92,9 +92,9 @@ static unsigned int fanstate = ACERHDF_FAN_AUTO; static char force_bios[16]; static char force_product[16]; static unsigned int prev_interval; -struct thermal_zone_device *thz_dev; -struct thermal_cooling_device *cl_dev; -struct platform_device *acerhdf_dev; +static struct thermal_zone_device *thz_dev; +static struct thermal_cooling_device *cl_dev; +static struct platform_device *acerhdf_dev; module_param(kernelmode, uint, 0); MODULE_PARM_DESC(kernelmode, "Kernel mode fan control on / off"); @@ -112,14 +112,12 @@ module_param_string(force_product, force_product, 16, 0); MODULE_PARM_DESC(force_product, "Force BIOS product and omit BIOS check"); /* - * cmd_off: to switch the fan completely off - * chk_off: to check if the fan is off + * cmd_off: to switch the fan completely off and check if the fan is off * cmd_auto: to set the BIOS in control of the fan. The BIOS regulates then * the fan speed depending on the temperature */ struct fancmd { u8 cmd_off; - u8 chk_off; u8 cmd_auto; }; @@ -136,47 +134,81 @@ struct bios_settings_t { /* Register addresses and values for different BIOS versions */ static const struct bios_settings_t bios_tbl[] = { /* AOA110 */ - {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x1f, 0x00} }, - {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0xaf, 0x00} }, - {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x21, 0x00} }, + {"Acer", "AOA110", "v0.3109", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA110", "v0.3301", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3304", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3305", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3307", 0x55, 0x58, {0xaf, 0x00} }, + {"Acer", "AOA110", "v0.3308", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3309", 0x55, 0x58, {0x21, 0x00} }, + {"Acer", "AOA110", "v0.3310", 0x55, 0x58, {0x21, 0x00} }, /* AOA150 */ - {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x20, 0x00} }, + {"Acer", "AOA150", "v0.3114", 0x55, 0x58, {0x1f, 0x00} }, + {"Acer", "AOA150", "v0.3301", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3304", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3305", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3307", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3308", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3309", 0x55, 0x58, {0x20, 0x00} }, + {"Acer", "AOA150", "v0.3310", 0x55, 0x58, {0x20, 0x00} }, /* Acer 1410 */ - {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1410", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, /* Acer 1810xx */ - {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3308", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810TZ", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, + {"Acer", "Aspire 1810T", "v1.3310", 0x55, 0x58, {0x9e, 0x00} }, + /* Acer 531 */ + {"Acer", "AO531h", "v0.3201", 0x55, 0x58, {0x20, 0x00} }, /* Gateway */ - {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x10, 0x0f, 0x00} }, - {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x10, 0x0f, 0x00} }, - {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x10, 0x0f, 0x00} }, + {"Gateway", "AOA110", "v0.3103", 0x55, 0x58, {0x21, 0x00} }, + {"Gateway", "AOA150", "v0.3103", 0x55, 0x58, {0x20, 0x00} }, + {"Gateway", "LT31", "v1.3103", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "LT31", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, + {"Gateway", "LT31", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, /* Packard Bell */ - {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x21, 0x00} }, - {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x20, 0x00} }, - {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, - {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x9e, 0x00} }, + {"Packard Bell", "DOA150", "v0.3104", 0x55, 0x58, {0x21, 0x00} }, + {"Packard Bell", "DOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, + {"Packard Bell", "AOA110", "v0.3105", 0x55, 0x58, {0x21, 0x00} }, + {"Packard Bell", "AOA150", "v0.3105", 0x55, 0x58, {0x20, 0x00} }, + {"Packard Bell", "DOTMU", "v1.3303", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3120", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3108", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3113", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3115", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3117", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v0.3119", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMU", "v1.3204", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMA", "v1.3201", 0x55, 0x58, {0x9e, 0x00} }, + {"Packard Bell", "DOTMA", "v1.3302", 0x55, 0x58, {0x9e, 0x00} }, /* pewpew-terminator */ - {"", "", "", 0, 0, {0, 0, 0} } + {"", "", "", 0, 0, {0, 0} } }; static const struct bios_settings_t *bios_cfg __read_mostly; @@ -200,7 +232,7 @@ static int acerhdf_get_fanstate(int *state) if (ec_read(bios_cfg->fanreg, &fan)) return -EINVAL; - if (fan != bios_cfg->cmd.chk_off) + if (fan != bios_cfg->cmd.cmd_off) *state = ACERHDF_FAN_AUTO; else *state = ACERHDF_FAN_OFF; @@ -374,7 +406,7 @@ static int acerhdf_get_crit_temp(struct thermal_zone_device *thermal, } /* bind callback functions to thermalzone */ -struct thermal_zone_device_ops acerhdf_dev_ops = { +static struct thermal_zone_device_ops acerhdf_dev_ops = { .bind = acerhdf_bind, .unbind = acerhdf_unbind, .get_temp = acerhdf_get_ec_temp, @@ -449,7 +481,7 @@ err_out: } /* bind fan callbacks to fan device */ -struct thermal_cooling_device_ops acerhdf_cooling_ops = { +static struct thermal_cooling_device_ops acerhdf_cooling_ops = { .get_max_state = acerhdf_get_max_state, .get_cur_state = acerhdf_get_cur_state, .set_cur_state = acerhdf_set_cur_state, @@ -518,6 +550,10 @@ static int acerhdf_check_hardware(void) version = dmi_get_system_info(DMI_BIOS_VERSION); product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!vendor || !version || !product) { + pr_err("error getting hardware information\n"); + return -EINVAL; + } pr_info("Acer Aspire One Fan driver, v.%s\n", DRV_VER); @@ -579,17 +615,26 @@ static int acerhdf_register_platform(void) return err; acerhdf_dev = platform_device_alloc("acerhdf", -1); - platform_device_add(acerhdf_dev); + if (!acerhdf_dev) { + err = -ENOMEM; + goto err_device_alloc; + } + err = platform_device_add(acerhdf_dev); + if (err) + goto err_device_add; return 0; + +err_device_add: + platform_device_put(acerhdf_dev); +err_device_alloc: + platform_driver_unregister(&acerhdf_driver); + return err; } static void acerhdf_unregister_platform(void) { - if (!acerhdf_dev) - return; - - platform_device_del(acerhdf_dev); + platform_device_unregister(acerhdf_dev); platform_driver_unregister(&acerhdf_driver); } @@ -633,7 +678,7 @@ static int __init acerhdf_init(void) err = acerhdf_register_platform(); if (err) - goto err_unreg; + goto out_err; err = acerhdf_register_thermal(); if (err) @@ -646,7 +691,7 @@ err_unreg: acerhdf_unregister_platform(); out_err: - return -ENODEV; + return err; } static void __exit acerhdf_exit(void) @@ -662,11 +707,13 @@ MODULE_DESCRIPTION("Aspire One temperature and fan driver"); MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire 1410*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire 1810*:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAO531*:"); MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:"); MODULE_ALIAS("dmi:*:*Packard Bell*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Packard Bell*:pnDOA*:"); MODULE_ALIAS("dmi:*:*Packard Bell*:pnDOTMU*:"); +MODULE_ALIAS("dmi:*:*Packard Bell*:pnDOTMA*:"); module_init(acerhdf_init); module_exit(acerhdf_exit); diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index efe8f6388906..b756e07d41b4 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -76,18 +76,18 @@ MODULE_LICENSE("GPL"); * So, if something doesn't work as you want, just try other values =) */ static uint wapf = 1; -module_param(wapf, uint, 0644); +module_param(wapf, uint, 0444); MODULE_PARM_DESC(wapf, "WAPF value"); static int wlan_status = 1; static int bluetooth_status = 1; -module_param(wlan_status, int, 0644); +module_param(wlan_status, int, 0444); MODULE_PARM_DESC(wlan_status, "Set the wireless status on boot " "(0 = disabled, 1 = enabled, -1 = don't do anything). " "default is 1"); -module_param(bluetooth_status, int, 0644); +module_param(bluetooth_status, int, 0444); MODULE_PARM_DESC(bluetooth_status, "Set the wireless status on boot " "(0 = disabled, 1 = enabled, -1 = don't do anything). " "default is 1"); @@ -297,7 +297,7 @@ static int write_acpi_int_ret(acpi_handle handle, const char *method, int val, acpi_status status; if (!handle) - return 0; + return -1; params.count = 1; params.pointer = &in_obj; @@ -796,10 +796,11 @@ static ssize_t store_ledd(struct device *dev, struct device_attribute *attr, rv = parse_arg(buf, count, &value); if (rv > 0) { - if (write_acpi_int(asus->handle, METHOD_LEDD, value)) + if (write_acpi_int(asus->handle, METHOD_LEDD, value)) { pr_warning("LED display write failed\n"); - else - asus->ledd_status = (u32) value; + return -ENODEV; + } + asus->ledd_status = (u32) value; } return rv; } @@ -1123,7 +1124,7 @@ static int asus_input_init(struct asus_laptop *asus) input = input_allocate_device(); if (!input) { pr_info("Unable to allocate input device\n"); - return 0; + return -ENOMEM; } input->name = "Asus Laptop extra buttons"; input->phys = ASUS_LAPTOP_FILE "/input0"; @@ -1134,20 +1135,20 @@ static int asus_input_init(struct asus_laptop *asus) error = sparse_keymap_setup(input, asus_keymap, NULL); if (error) { pr_err("Unable to setup input device keymap\n"); - goto err_keymap; + goto err_free_dev; } error = input_register_device(input); if (error) { pr_info("Unable to register input device\n"); - goto err_device; + goto err_free_keymap; } asus->inputdev = input; return 0; -err_keymap: +err_free_keymap: sparse_keymap_free(input); -err_device: +err_free_dev: input_free_device(input); return error; } @@ -1397,8 +1398,10 @@ static int asus_laptop_get_info(struct asus_laptop *asus) } } asus->name = kstrdup(string, GFP_KERNEL); - if (!asus->name) + if (!asus->name) { + kfree(buffer.pointer); return -ENOMEM; + } if (*string) pr_notice(" %s model detected\n", string); diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c index 92fd30c9379c..e058c2ba2a15 100644 --- a/drivers/platform/x86/asus_acpi.c +++ b/drivers/platform/x86/asus_acpi.c @@ -1330,6 +1330,9 @@ static int asus_hotk_get_info(void) hotk->model = P30; printk(KERN_NOTICE " Samsung P30 detected, supported\n"); + hotk->methods = &model_conf[hotk->model]; + kfree(model); + return 0; } else { hotk->model = M2E; printk(KERN_NOTICE " unsupported model %s, trying " @@ -1339,8 +1342,6 @@ static int asus_hotk_get_info(void) kfree(model); return -ENODEV; } - hotk->methods = &model_conf[hotk->model]; - return AE_OK; } hotk->methods = &model_conf[hotk->model]; printk(KERN_NOTICE " %s model detected, supported\n", string); @@ -1374,7 +1375,7 @@ static int asus_hotk_get_info(void) kfree(model); - return AE_OK; + return 0; } static int asus_hotk_check(void) diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 3bf399fe2bbc..341cbfef93ee 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -208,7 +208,7 @@ static ssize_t cmpc_accel_sensitivity_store(struct device *dev, return strnlen(buf, count); } -struct device_attribute cmpc_accel_sensitivity_attr = { +static struct device_attribute cmpc_accel_sensitivity_attr = { .attr = { .name = "sensitivity", .mode = 0660 }, .show = cmpc_accel_sensitivity_show, .store = cmpc_accel_sensitivity_store @@ -573,16 +573,17 @@ static int cmpc_ipml_add(struct acpi_device *acpi) ipml->rf = rfkill_alloc("cmpc_rfkill", &acpi->dev, RFKILL_TYPE_WLAN, &cmpc_rfkill_ops, acpi->handle); - /* rfkill_alloc may fail if RFKILL is disabled. We should still work - * anyway. */ - if (!IS_ERR(ipml->rf)) { + /* + * If RFKILL is disabled, rfkill_alloc will return ERR_PTR(-ENODEV). + * This is OK, however, since all other uses of the device will not + * derefence it. + */ + if (ipml->rf) { retval = rfkill_register(ipml->rf); if (retval) { rfkill_destroy(ipml->rf); ipml->rf = NULL; } - } else { - ipml->rf = NULL; } dev_set_drvdata(&acpi->dev, ipml); diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index 71ff1545a93e..d071ce056322 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -24,17 +24,50 @@ */ /* - * comapl-laptop.c - Compal laptop support. + * compal-laptop.c - Compal laptop support. + * + * This driver exports a few files in /sys/devices/platform/compal-laptop/: + * wake_up_XXX Whether or not we listen to such wake up events (rw) + * + * In addition to these platform device attributes the driver + * registers itself in the Linux backlight control, power_supply, rfkill + * and hwmon subsystem and is available to userspace under: + * + * /sys/class/backlight/compal-laptop/ + * /sys/class/power_supply/compal-laptop/ + * /sys/class/rfkill/rfkillX/ + * /sys/class/hwmon/hwmonX/ + * + * Notes on the power_supply battery interface: + * - the "minimum" design voltage is *the* design voltage + * - the ambient temperature is the average battery temperature + * and the value is an educated guess (see commented code below) * - * The driver registers itself with the rfkill subsystem and - * the Linux backlight control subsystem. * * This driver might work on other laptops produced by Compal. If you * want to try it you can pass force=1 as argument to the module which * will force it to load even when the DMI data doesn't identify the - * laptop as FL9x. + * laptop as compatible. + * + * Lots of data available at: + * http://service1.marasst.com/Compal/JHL90_91/Service%20Manual/ + * JHL90%20service%20manual-Final-0725.pdf + * + * + * + * Support for the Compal JHL90 added by Roald Frederickx + * (roald.frederickx@gmail.com): + * Driver got large revision. Added functionalities: backlight + * power, wake_on_XXX, a hwmon and power_supply interface. + * + * In case this gets merged into the kernel source: I want to dedicate this + * to Kasper Meerts, the awesome guy who showed me Linux and C! */ +/* NOTE: currently the wake_on_XXX, hwmon and power_supply interfaces are + * only enabled on a JHL90 board until it is verified that they work on the + * other boards too. See the extra_features variable. */ + #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> @@ -43,71 +76,296 @@ #include <linux/backlight.h> #include <linux/platform_device.h> #include <linux/rfkill.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/power_supply.h> +#include <linux/fb.h> + + +/* ======= */ +/* Defines */ +/* ======= */ +#define DRIVER_NAME "compal-laptop" +#define DRIVER_VERSION "0.2.7" + +#define BACKLIGHT_LEVEL_ADDR 0xB9 +#define BACKLIGHT_LEVEL_MAX 7 +#define BACKLIGHT_STATE_ADDR 0x59 +#define BACKLIGHT_STATE_ON_DATA 0xE1 +#define BACKLIGHT_STATE_OFF_DATA 0xE2 + +#define WAKE_UP_ADDR 0xA4 +#define WAKE_UP_PME (1 << 0) +#define WAKE_UP_MODEM (1 << 1) +#define WAKE_UP_LAN (1 << 2) +#define WAKE_UP_WLAN (1 << 4) +#define WAKE_UP_KEY (1 << 6) +#define WAKE_UP_MOUSE (1 << 7) + +#define WIRELESS_ADDR 0xBB +#define WIRELESS_WLAN (1 << 0) +#define WIRELESS_BT (1 << 1) +#define WIRELESS_WLAN_EXISTS (1 << 2) +#define WIRELESS_BT_EXISTS (1 << 3) +#define WIRELESS_KILLSWITCH (1 << 4) + +#define PWM_ADDRESS 0x46 +#define PWM_DISABLE_ADDR 0x59 +#define PWM_DISABLE_DATA 0xA5 +#define PWM_ENABLE_ADDR 0x59 +#define PWM_ENABLE_DATA 0xA8 + +#define FAN_ADDRESS 0x46 +#define FAN_DATA 0x81 +#define FAN_FULL_ON_CMD 0x59 /* Doesn't seem to work. Just */ +#define FAN_FULL_ON_ENABLE 0x76 /* force the pwm signal to its */ +#define FAN_FULL_ON_DISABLE 0x77 /* maximum value instead */ + +#define TEMP_CPU 0xB0 +#define TEMP_CPU_LOCAL 0xB1 +#define TEMP_CPU_DTS 0xB5 +#define TEMP_NORTHBRIDGE 0xB6 +#define TEMP_VGA 0xB4 +#define TEMP_SKIN 0xB2 + +#define BAT_MANUFACTURER_NAME_ADDR 0x10 +#define BAT_MANUFACTURER_NAME_LEN 9 +#define BAT_MODEL_NAME_ADDR 0x19 +#define BAT_MODEL_NAME_LEN 6 +#define BAT_SERIAL_NUMBER_ADDR 0xC4 +#define BAT_SERIAL_NUMBER_LEN 5 +#define BAT_CHARGE_NOW 0xC2 +#define BAT_CHARGE_DESIGN 0xCA +#define BAT_VOLTAGE_NOW 0xC6 +#define BAT_VOLTAGE_DESIGN 0xC8 +#define BAT_CURRENT_NOW 0xD0 +#define BAT_CURRENT_AVG 0xD2 +#define BAT_POWER 0xD4 +#define BAT_CAPACITY 0xCE +#define BAT_TEMP 0xD6 +#define BAT_TEMP_AVG 0xD7 +#define BAT_STATUS0 0xC1 +#define BAT_STATUS1 0xF0 +#define BAT_STATUS2 0xF1 +#define BAT_STOP_CHARGE1 0xF2 +#define BAT_STOP_CHARGE2 0xF3 + +#define BAT_S0_DISCHARGE (1 << 0) +#define BAT_S0_DISCHRG_CRITICAL (1 << 2) +#define BAT_S0_LOW (1 << 3) +#define BAT_S0_CHARGING (1 << 1) +#define BAT_S0_AC (1 << 7) +#define BAT_S1_EXISTS (1 << 0) +#define BAT_S1_FULL (1 << 1) +#define BAT_S1_EMPTY (1 << 2) +#define BAT_S1_LiION_OR_NiMH (1 << 7) +#define BAT_S2_LOW_LOW (1 << 0) +#define BAT_STOP_CHRG1_BAD_CELL (1 << 1) +#define BAT_STOP_CHRG1_COMM_FAIL (1 << 2) +#define BAT_STOP_CHRG1_OVERVOLTAGE (1 << 6) +#define BAT_STOP_CHRG1_OVERTEMPERATURE (1 << 7) + + +/* ======= */ +/* Structs */ +/* ======= */ +struct compal_data{ + /* Fan control */ + struct device *hwmon_dev; + int pwm_enable; /* 0:full on, 1:set by pwm1, 2:control by moterboard */ + unsigned char curr_pwm; + + /* Power supply */ + struct power_supply psy; + struct power_supply_info psy_info; + char bat_model_name[BAT_MODEL_NAME_LEN + 1]; + char bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN + 1]; + char bat_serial_number[BAT_SERIAL_NUMBER_LEN + 1]; +}; -#define COMPAL_DRIVER_VERSION "0.2.6" -#define COMPAL_LCD_LEVEL_MAX 8 +/* =============== */ +/* General globals */ +/* =============== */ +static int force; +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); -#define COMPAL_EC_COMMAND_WIRELESS 0xBB -#define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 +/* Support for the wake_on_XXX, hwmon and power_supply interface. Currently + * only gets enabled on a JHL90 board. Might work with the others too */ +static bool extra_features; + +/* Nasty stuff. For some reason the fan control is very un-linear. I've + * come up with these values by looping through the possible inputs and + * watching the output of address 0x4F (do an ec_transaction writing 0x33 + * into 0x4F and read a few bytes from the output, like so: + * u8 writeData = 0x33; + * ec_transaction(0x4F, &writeData, 1, buffer, 32, 0); + * That address is labled "fan1 table information" in the service manual. + * It should be clear which value in 'buffer' changes). This seems to be + * related to fan speed. It isn't a proper 'realtime' fan speed value + * though, because physically stopping or speeding up the fan doesn't + * change it. It might be the average voltage or current of the pwm output. + * Nevertheless, it is more fine-grained than the actual RPM reading */ +static const unsigned char pwm_lookup_table[256] = { + 0, 0, 0, 1, 1, 1, 2, 253, 254, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, + 7, 7, 7, 8, 86, 86, 9, 9, 9, 10, 10, 10, 11, 92, 92, 12, 12, 95, + 13, 66, 66, 14, 14, 98, 15, 15, 15, 16, 16, 67, 17, 17, 72, 18, 70, + 75, 19, 90, 90, 73, 73, 73, 21, 21, 91, 91, 91, 96, 23, 94, 94, 94, + 94, 94, 94, 94, 94, 94, 94, 141, 141, 238, 223, 192, 139, 139, 139, + 139, 139, 142, 142, 142, 142, 142, 78, 78, 78, 78, 78, 76, 76, 76, + 76, 76, 79, 79, 79, 79, 79, 79, 79, 20, 20, 20, 20, 20, 22, 22, 22, + 22, 22, 24, 24, 24, 24, 24, 24, 219, 219, 219, 219, 219, 219, 219, + 219, 27, 27, 188, 188, 28, 28, 28, 29, 186, 186, 186, 186, 186, + 186, 186, 186, 186, 186, 31, 31, 31, 31, 31, 32, 32, 32, 41, 33, + 33, 33, 33, 33, 252, 252, 34, 34, 34, 43, 35, 35, 35, 36, 36, 38, + 206, 206, 206, 206, 206, 206, 206, 206, 206, 37, 37, 37, 46, 46, + 47, 47, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 48, 48, + 48, 48, 48, 40, 40, 40, 49, 42, 42, 42, 42, 42, 42, 42, 42, 44, + 189, 189, 189, 189, 54, 54, 45, 45, 45, 45, 45, 45, 45, 45, 251, + 191, 199, 199, 199, 199, 199, 215, 215, 215, 215, 187, 187, 187, + 187, 187, 193, 50 +}; -#define KILLSWITCH_MASK 0x10 -#define WLAN_MASK 0x01 -#define BT_MASK 0x02 -static struct rfkill *wifi_rfkill; -static struct rfkill *bt_rfkill; -static struct platform_device *compal_device; -static int force; -module_param(force, bool, 0); -MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); -/* Hardware access */ +/* ========================= */ +/* Hardware access functions */ +/* ========================= */ +/* General access */ +static u8 ec_read_u8(u8 addr) +{ + u8 value; + ec_read(addr, &value); + return value; +} + +static s8 ec_read_s8(u8 addr) +{ + return (s8)ec_read_u8(addr); +} + +static u16 ec_read_u16(u8 addr) +{ + int hi, lo; + lo = ec_read_u8(addr); + hi = ec_read_u8(addr + 1); + return (hi << 8) + lo; +} + +static s16 ec_read_s16(u8 addr) +{ + return (s16) ec_read_u16(addr); +} -static int set_lcd_level(int level) +static void ec_read_sequence(u8 addr, u8 *buf, int len) { - if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) + int i; + for (i = 0; i < len; i++) + ec_read(addr + i, buf + i); +} + + +/* Backlight access */ +static int set_backlight_level(int level) +{ + if (level < 0 || level > BACKLIGHT_LEVEL_MAX) return -EINVAL; - ec_write(COMPAL_EC_COMMAND_LCD_LEVEL, level); + ec_write(BACKLIGHT_LEVEL_ADDR, level); - return 0; + return 1; +} + +static int get_backlight_level(void) +{ + return (int) ec_read_u8(BACKLIGHT_LEVEL_ADDR); } -static int get_lcd_level(void) +static void set_backlight_state(bool on) { - u8 result; + u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA; + ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0, 0); +} + + +/* Fan control access */ +static void pwm_enable_control(void) +{ + unsigned char writeData = PWM_ENABLE_DATA; + ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0, 0); +} + +static void pwm_disable_control(void) +{ + unsigned char writeData = PWM_DISABLE_DATA; + ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0, 0); +} - ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); +static void set_pwm(int pwm) +{ + ec_transaction(PWM_ADDRESS, &pwm_lookup_table[pwm], 1, NULL, 0, 0); +} + +static int get_fan_rpm(void) +{ + u8 value, data = FAN_DATA; + ec_transaction(FAN_ADDRESS, &data, 1, &value, 1, 0); + return 100 * (int)value; +} + + + + +/* =================== */ +/* Interface functions */ +/* =================== */ + +/* Backlight interface */ +static int bl_get_brightness(struct backlight_device *b) +{ + return get_backlight_level(); +} + +static int bl_update_status(struct backlight_device *b) +{ + int ret = set_backlight_level(b->props.brightness); + if (ret) + return ret; - return (int) result; + set_backlight_state((b->props.power == FB_BLANK_UNBLANK) + && !(b->props.state & BL_CORE_SUSPENDED) + && !(b->props.state & BL_CORE_FBBLANK)); + return 0; } +static const struct backlight_ops compalbl_ops = { + .get_brightness = bl_get_brightness, + .update_status = bl_update_status, +}; + + +/* Wireless interface */ static int compal_rfkill_set(void *data, bool blocked) { unsigned long radio = (unsigned long) data; - u8 result, value; - - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); + u8 result = ec_read_u8(WIRELESS_ADDR); + u8 value; if (!blocked) value = (u8) (result | radio); else value = (u8) (result & ~radio); - ec_write(COMPAL_EC_COMMAND_WIRELESS, value); + ec_write(WIRELESS_ADDR, value); return 0; } static void compal_rfkill_poll(struct rfkill *rfkill, void *data) { - u8 result; - bool hw_blocked; - - ec_read(COMPAL_EC_COMMAND_WIRELESS, &result); - - hw_blocked = !(result & KILLSWITCH_MASK); + u8 result = ec_read_u8(WIRELESS_ADDR); + bool hw_blocked = !(result & WIRELESS_KILLSWITCH); rfkill_set_hw_state(rfkill, hw_blocked); } @@ -116,80 +374,404 @@ static const struct rfkill_ops compal_rfkill_ops = { .set_block = compal_rfkill_set, }; -static int setup_rfkill(void) + +/* Wake_up interface */ +#define SIMPLE_MASKED_STORE_SHOW(NAME, ADDR, MASK) \ +static ssize_t NAME##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", ((ec_read_u8(ADDR) & MASK) != 0)); \ +} \ +static ssize_t NAME##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + int state; \ + u8 old_val = ec_read_u8(ADDR); \ + if (sscanf(buf, "%d", &state) != 1 || (state < 0 || state > 1)) \ + return -EINVAL; \ + ec_write(ADDR, state ? (old_val | MASK) : (old_val & ~MASK)); \ + return count; \ +} + +SIMPLE_MASKED_STORE_SHOW(wake_up_pme, WAKE_UP_ADDR, WAKE_UP_PME) +SIMPLE_MASKED_STORE_SHOW(wake_up_modem, WAKE_UP_ADDR, WAKE_UP_MODEM) +SIMPLE_MASKED_STORE_SHOW(wake_up_lan, WAKE_UP_ADDR, WAKE_UP_LAN) +SIMPLE_MASKED_STORE_SHOW(wake_up_wlan, WAKE_UP_ADDR, WAKE_UP_WLAN) +SIMPLE_MASKED_STORE_SHOW(wake_up_key, WAKE_UP_ADDR, WAKE_UP_KEY) +SIMPLE_MASKED_STORE_SHOW(wake_up_mouse, WAKE_UP_ADDR, WAKE_UP_MOUSE) + + +/* General hwmon interface */ +static ssize_t hwmon_name_show(struct device *dev, + struct device_attribute *attr, char *buf) { - int ret; + return sprintf(buf, "%s\n", DRIVER_NAME); +} - wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev, - RFKILL_TYPE_WLAN, &compal_rfkill_ops, - (void *) WLAN_MASK); - if (!wifi_rfkill) - return -ENOMEM; - ret = rfkill_register(wifi_rfkill); - if (ret) - goto err_wifi; +/* Fan control interface */ +static ssize_t pwm_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct compal_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", data->pwm_enable); +} - bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev, - RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops, - (void *) BT_MASK); - if (!bt_rfkill) { - ret = -ENOMEM; - goto err_allocate_bt; +static ssize_t pwm_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct compal_data *data = dev_get_drvdata(dev); + long val; + int err; + err = strict_strtol(buf, 10, &val); + if (err) + return err; + if (val < 0) + return -EINVAL; + + data->pwm_enable = val; + + switch (val) { + case 0: /* Full speed */ + pwm_enable_control(); + set_pwm(255); + break; + case 1: /* As set by pwm1 */ + pwm_enable_control(); + set_pwm(data->curr_pwm); + break; + default: /* Control by motherboard */ + pwm_disable_control(); + break; } - ret = rfkill_register(bt_rfkill); - if (ret) - goto err_register_bt; - return 0; + return count; +} -err_register_bt: - rfkill_destroy(bt_rfkill); +static ssize_t pwm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct compal_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%hhu\n", data->curr_pwm); +} -err_allocate_bt: - rfkill_unregister(wifi_rfkill); +static ssize_t pwm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct compal_data *data = dev_get_drvdata(dev); + long val; + int err; + err = strict_strtol(buf, 10, &val); + if (err) + return err; + if (val < 0 || val > 255) + return -EINVAL; -err_wifi: - rfkill_destroy(wifi_rfkill); + data->curr_pwm = val; - return ret; + if (data->pwm_enable != 1) + return count; + set_pwm(val); + + return count; } -/* Backlight device stuff */ +static ssize_t fan_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", get_fan_rpm()); +} -static int bl_get_brightness(struct backlight_device *b) + +/* Temperature interface */ +#define TEMPERATURE_SHOW_TEMP_AND_LABEL(POSTFIX, ADDRESS, LABEL) \ +static ssize_t temp_##POSTFIX(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", 1000 * (int)ec_read_s8(ADDRESS)); \ +} \ +static ssize_t label_##POSTFIX(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%s\n", LABEL); \ +} + +/* Labels as in service guide */ +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu, TEMP_CPU, "CPU_TEMP"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_local, TEMP_CPU_LOCAL, "CPU_TEMP_LOCAL"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(cpu_DTS, TEMP_CPU_DTS, "CPU_DTS"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(northbridge,TEMP_NORTHBRIDGE,"NorthBridge"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(vga, TEMP_VGA, "VGA_TEMP"); +TEMPERATURE_SHOW_TEMP_AND_LABEL(SKIN, TEMP_SKIN, "SKIN_TEMP90"); + + +/* Power supply interface */ +static int bat_status(void) +{ + u8 status0 = ec_read_u8(BAT_STATUS0); + u8 status1 = ec_read_u8(BAT_STATUS1); + + if (status0 & BAT_S0_CHARGING) + return POWER_SUPPLY_STATUS_CHARGING; + if (status0 & BAT_S0_DISCHARGE) + return POWER_SUPPLY_STATUS_DISCHARGING; + if (status1 & BAT_S1_FULL) + return POWER_SUPPLY_STATUS_FULL; + return POWER_SUPPLY_STATUS_NOT_CHARGING; +} + +static int bat_health(void) { - return get_lcd_level(); + u8 status = ec_read_u8(BAT_STOP_CHARGE1); + + if (status & BAT_STOP_CHRG1_OVERTEMPERATURE) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (status & BAT_STOP_CHRG1_OVERVOLTAGE) + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + if (status & BAT_STOP_CHRG1_BAD_CELL) + return POWER_SUPPLY_HEALTH_DEAD; + if (status & BAT_STOP_CHRG1_COMM_FAIL) + return POWER_SUPPLY_HEALTH_UNKNOWN; + return POWER_SUPPLY_HEALTH_GOOD; } +static int bat_is_present(void) +{ + u8 status = ec_read_u8(BAT_STATUS2); + return ((status & BAT_S1_EXISTS) != 0); +} -static int bl_update_status(struct backlight_device *b) +static int bat_technology(void) { - return set_lcd_level(b->props.brightness); + u8 status = ec_read_u8(BAT_STATUS1); + + if (status & BAT_S1_LiION_OR_NiMH) + return POWER_SUPPLY_TECHNOLOGY_LION; + return POWER_SUPPLY_TECHNOLOGY_NiMH; } -static struct backlight_ops compalbl_ops = { - .get_brightness = bl_get_brightness, - .update_status = bl_update_status, -}; +static int bat_capacity_level(void) +{ + u8 status0 = ec_read_u8(BAT_STATUS0); + u8 status1 = ec_read_u8(BAT_STATUS1); + u8 status2 = ec_read_u8(BAT_STATUS2); + + if (status0 & BAT_S0_DISCHRG_CRITICAL + || status1 & BAT_S1_EMPTY + || status2 & BAT_S2_LOW_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + if (status0 & BAT_S0_LOW) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + if (status1 & BAT_S1_FULL) + return POWER_SUPPLY_CAPACITY_LEVEL_FULL; + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; +} + +static int bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct compal_data *data; + data = container_of(psy, struct compal_data, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = bat_status(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = bat_health(); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = bat_is_present(); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = bat_technology(); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: /* THE design voltage... */ + val->intval = ec_read_u16(BAT_VOLTAGE_DESIGN) * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = ec_read_u16(BAT_VOLTAGE_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ec_read_s16(BAT_CURRENT_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = ec_read_s16(BAT_CURRENT_AVG) * 1000; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = ec_read_u8(BAT_POWER) * 1000000; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = ec_read_u16(BAT_CHARGE_DESIGN) * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = ec_read_u16(BAT_CHARGE_NOW) * 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = ec_read_u8(BAT_CAPACITY); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval = bat_capacity_level(); + break; + /* It smees that BAT_TEMP_AVG is a (2's complement?) value showing + * the number of degrees, whereas BAT_TEMP is somewhat more + * complicated. It looks like this is a negative nember with a + * 100/256 divider and an offset of 222. Both were determined + * experimentally by comparing BAT_TEMP and BAT_TEMP_AVG. */ + case POWER_SUPPLY_PROP_TEMP: + val->intval = ((222 - (int)ec_read_u8(BAT_TEMP)) * 1000) >> 8; + break; + case POWER_SUPPLY_PROP_TEMP_AMBIENT: /* Ambient, Avg, ... same thing */ + val->intval = ec_read_s8(BAT_TEMP_AVG) * 10; + break; + /* Neither the model name nor manufacturer name work for me. */ + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = data->bat_model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = data->bat_manufacturer_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = data->bat_serial_number; + break; + default: + break; + } + return 0; +} -static struct backlight_device *compalbl_device; + + +/* ============== */ +/* Driver Globals */ +/* ============== */ +static DEVICE_ATTR(wake_up_pme, + 0644, wake_up_pme_show, wake_up_pme_store); +static DEVICE_ATTR(wake_up_modem, + 0644, wake_up_modem_show, wake_up_modem_store); +static DEVICE_ATTR(wake_up_lan, + 0644, wake_up_lan_show, wake_up_lan_store); +static DEVICE_ATTR(wake_up_wlan, + 0644, wake_up_wlan_show, wake_up_wlan_store); +static DEVICE_ATTR(wake_up_key, + 0644, wake_up_key_show, wake_up_key_store); +static DEVICE_ATTR(wake_up_mouse, + 0644, wake_up_mouse_show, wake_up_mouse_store); + +static SENSOR_DEVICE_ATTR(name, S_IRUGO, hwmon_name_show, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, fan_show, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp_cpu, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, temp_cpu_local, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, temp_cpu_DTS, NULL, 1); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, temp_northbridge, NULL, 1); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, temp_vga, NULL, 1); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, temp_SKIN, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, label_cpu, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, label_cpu_local, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, label_cpu_DTS, NULL, 1); +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, label_northbridge, NULL, 1); +static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, label_vga, NULL, 1); +static SENSOR_DEVICE_ATTR(temp6_label, S_IRUGO, label_SKIN, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, pwm_show, pwm_store, 1); +static SENSOR_DEVICE_ATTR(pwm1_enable, + S_IRUGO | S_IWUSR, pwm_enable_show, pwm_enable_store, 0); + +static struct attribute *compal_attributes[] = { + &dev_attr_wake_up_pme.attr, + &dev_attr_wake_up_modem.attr, + &dev_attr_wake_up_lan.attr, + &dev_attr_wake_up_wlan.attr, + &dev_attr_wake_up_key.attr, + &dev_attr_wake_up_mouse.attr, + /* Maybe put the sensor-stuff in a separate hwmon-driver? That way, + * the hwmon sysfs won't be cluttered with the above files. */ + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_temp6_label.dev_attr.attr, + NULL +}; + +static struct attribute_group compal_attribute_group = { + .attrs = compal_attributes +}; + +static int __devinit compal_probe(struct platform_device *); +static int __devexit compal_remove(struct platform_device *); static struct platform_driver compal_driver = { .driver = { - .name = "compal-laptop", + .name = DRIVER_NAME, .owner = THIS_MODULE, - } + }, + .probe = compal_probe, + .remove = __devexit_p(compal_remove) }; -/* Initialization */ +static enum power_supply_property compal_bat_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static struct backlight_device *compalbl_device; + +static struct platform_device *compal_device; + +static struct rfkill *wifi_rfkill; +static struct rfkill *bt_rfkill; + + + + + +/* =================================== */ +/* Initialization & clean-up functions */ +/* =================================== */ static int dmi_check_cb(const struct dmi_system_id *id) { - printk(KERN_INFO "compal-laptop: Identified laptop model '%s'.\n", + printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s'\n", id->ident); + extra_features = false; + return 0; +} +static int dmi_check_cb_extra(const struct dmi_system_id *id) +{ + printk(KERN_INFO DRIVER_NAME": Identified laptop model '%s', " + "enabling extra features\n", + id->ident); + extra_features = true; return 0; } @@ -274,27 +856,106 @@ static struct dmi_system_id __initdata compal_dmi_table[] = { }, .callback = dmi_check_cb }, - + { + .ident = "JHL90", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "JHL90"), + DMI_MATCH(DMI_BOARD_VERSION, "REFERENCE"), + }, + .callback = dmi_check_cb_extra + }, { } }; +static void initialize_power_supply_data(struct compal_data *data) +{ + data->psy.name = DRIVER_NAME; + data->psy.type = POWER_SUPPLY_TYPE_BATTERY; + data->psy.properties = compal_bat_properties; + data->psy.num_properties = ARRAY_SIZE(compal_bat_properties); + data->psy.get_property = bat_get_property; + + ec_read_sequence(BAT_MANUFACTURER_NAME_ADDR, + data->bat_manufacturer_name, + BAT_MANUFACTURER_NAME_LEN); + data->bat_manufacturer_name[BAT_MANUFACTURER_NAME_LEN] = 0; + + ec_read_sequence(BAT_MODEL_NAME_ADDR, + data->bat_model_name, + BAT_MODEL_NAME_LEN); + data->bat_model_name[BAT_MODEL_NAME_LEN] = 0; + + scnprintf(data->bat_serial_number, BAT_SERIAL_NUMBER_LEN + 1, "%d", + ec_read_u16(BAT_SERIAL_NUMBER_ADDR)); +} + +static void initialize_fan_control_data(struct compal_data *data) +{ + data->pwm_enable = 2; /* Keep motherboard in control for now */ + data->curr_pwm = 255; /* Try not to cause a CPU_on_fire exception + if we take over... */ +} + +static int setup_rfkill(void) +{ + int ret; + + wifi_rfkill = rfkill_alloc("compal-wifi", &compal_device->dev, + RFKILL_TYPE_WLAN, &compal_rfkill_ops, + (void *) WIRELESS_WLAN); + if (!wifi_rfkill) + return -ENOMEM; + + ret = rfkill_register(wifi_rfkill); + if (ret) + goto err_wifi; + + bt_rfkill = rfkill_alloc("compal-bluetooth", &compal_device->dev, + RFKILL_TYPE_BLUETOOTH, &compal_rfkill_ops, + (void *) WIRELESS_BT); + if (!bt_rfkill) { + ret = -ENOMEM; + goto err_allocate_bt; + } + ret = rfkill_register(bt_rfkill); + if (ret) + goto err_register_bt; + + return 0; + +err_register_bt: + rfkill_destroy(bt_rfkill); + +err_allocate_bt: + rfkill_unregister(wifi_rfkill); + +err_wifi: + rfkill_destroy(wifi_rfkill); + + return ret; +} + static int __init compal_init(void) { int ret; - if (acpi_disabled) + if (acpi_disabled) { + printk(KERN_ERR DRIVER_NAME": ACPI needs to be enabled for " + "this driver to work!\n"); return -ENODEV; + } - if (!force && !dmi_check_system(compal_dmi_table)) + if (!force && !dmi_check_system(compal_dmi_table)) { + printk(KERN_ERR DRIVER_NAME": Motherboard not recognized (You " + "could try the module's force-parameter)"); return -ENODEV; - - /* Register backlight stuff */ + } if (!acpi_video_backlight_support()) { struct backlight_properties props; memset(&props, 0, sizeof(struct backlight_properties)); - props.max_brightness = COMPAL_LCD_LEVEL_MAX - 1; - compalbl_device = backlight_device_register("compal-laptop", + props.max_brightness = BACKLIGHT_LEVEL_MAX; + compalbl_device = backlight_device_register(DRIVER_NAME, NULL, NULL, &compalbl_ops, &props); @@ -304,67 +965,122 @@ static int __init compal_init(void) ret = platform_driver_register(&compal_driver); if (ret) - goto fail_backlight; + goto err_backlight; - /* Register platform stuff */ - - compal_device = platform_device_alloc("compal-laptop", -1); + compal_device = platform_device_alloc(DRIVER_NAME, -1); if (!compal_device) { ret = -ENOMEM; - goto fail_platform_driver; + goto err_platform_driver; } - ret = platform_device_add(compal_device); + ret = platform_device_add(compal_device); /* This calls compal_probe */ if (ret) - goto fail_platform_device; + goto err_platform_device; ret = setup_rfkill(); if (ret) - goto fail_rfkill; - - printk(KERN_INFO "compal-laptop: driver "COMPAL_DRIVER_VERSION - " successfully loaded.\n"); + goto err_rfkill; + printk(KERN_INFO DRIVER_NAME": Driver "DRIVER_VERSION + " successfully loaded\n"); return 0; -fail_rfkill: +err_rfkill: platform_device_del(compal_device); -fail_platform_device: - +err_platform_device: platform_device_put(compal_device); -fail_platform_driver: - +err_platform_driver: platform_driver_unregister(&compal_driver); -fail_backlight: - +err_backlight: backlight_device_unregister(compalbl_device); return ret; } -static void __exit compal_cleanup(void) +static int __devinit compal_probe(struct platform_device *pdev) { + int err; + struct compal_data *data; + + if (!extra_features) + return 0; + + /* Fan control */ + data = kzalloc(sizeof(struct compal_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + initialize_fan_control_data(data); + + err = sysfs_create_group(&pdev->dev.kobj, &compal_attribute_group); + if (err) + return err; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, + &compal_attribute_group); + kfree(data); + return err; + } + + /* Power supply */ + initialize_power_supply_data(data); + power_supply_register(&compal_device->dev, &data->psy); + + platform_set_drvdata(pdev, data); + + return 0; +} +static void __exit compal_cleanup(void) +{ platform_device_unregister(compal_device); platform_driver_unregister(&compal_driver); backlight_device_unregister(compalbl_device); rfkill_unregister(wifi_rfkill); - rfkill_destroy(wifi_rfkill); rfkill_unregister(bt_rfkill); + rfkill_destroy(wifi_rfkill); rfkill_destroy(bt_rfkill); - printk(KERN_INFO "compal-laptop: driver unloaded.\n"); + printk(KERN_INFO DRIVER_NAME": Driver unloaded\n"); } +static int __devexit compal_remove(struct platform_device *pdev) +{ + struct compal_data *data; + + if (!extra_features) + return 0; + + printk(KERN_INFO DRIVER_NAME": Unloading: resetting fan control " + "to motherboard\n"); + pwm_disable_control(); + + data = platform_get_drvdata(pdev); + hwmon_device_unregister(data->hwmon_dev); + power_supply_unregister(&data->psy); + + platform_set_drvdata(pdev, NULL); + kfree(data); + + sysfs_remove_group(&pdev->dev.kobj, &compal_attribute_group); + + return 0; +} + + module_init(compal_init); module_exit(compal_cleanup); MODULE_AUTHOR("Cezary Jackiewicz"); +MODULE_AUTHOR("Roald Frederickx (roald.frederickx@gmail.com)"); MODULE_DESCRIPTION("Compal Laptop Support"); -MODULE_VERSION(COMPAL_DRIVER_VERSION); +MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); MODULE_ALIAS("dmi:*:rnIFL90:rvrIFT00:*"); @@ -372,6 +1088,7 @@ MODULE_ALIAS("dmi:*:rnIFL90:rvrREFERENCE:*"); MODULE_ALIAS("dmi:*:rnIFL91:rvrIFT00:*"); MODULE_ALIAS("dmi:*:rnJFL92:rvrIFT00:*"); MODULE_ALIAS("dmi:*:rnIFT00:rvrIFT00:*"); +MODULE_ALIAS("dmi:*:rnJHL90:rvrREFERENCE:*"); MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron910:*"); MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1010:*"); MODULE_ALIAS("dmi:*:svnDellInc.:pnInspiron1011:*"); diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 661e3ac4d5b1..b41ed5cab3e7 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -83,6 +83,12 @@ static const struct dmi_system_id __initdata dell_device_table[] = { }, }, { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { .ident = "Dell Computer Corporation", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), @@ -467,7 +473,7 @@ static struct backlight_ops dell_ops = { .update_status = dell_send_intensity, }; -bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, +static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, struct serio *port) { static bool extended; @@ -621,4 +627,5 @@ MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); MODULE_DESCRIPTION("Dell laptop driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("dmi:*svnDellInc.:*:ct8:*"); +MODULE_ALIAS("dmi:*svnDellInc.:*:ct9:*"); MODULE_ALIAS("dmi:*svnDellComputerCorporation.:*:ct8:*"); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index 66f53c3c35e8..08fb70f6d9bf 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -221,7 +221,7 @@ static void dell_wmi_notify(u32 value, void *context) return; } - if (dell_new_hk_type) + if (dell_new_hk_type || buffer_entry[1] == 0x0) reported_key = (int)buffer_entry[2]; else reported_key = (int)buffer_entry[1] & 0xffff; @@ -339,13 +339,18 @@ static int __init dell_wmi_init(void) acpi_video = acpi_video_backlight_support(); err = dell_wmi_input_setup(); - if (err) + if (err) { + if (dell_new_hk_type) + kfree(dell_wmi_keymap); return err; + } status = wmi_install_notify_handler(DELL_EVENT_GUID, dell_wmi_notify, NULL); if (ACPI_FAILURE(status)) { input_unregister_device(dell_wmi_input_dev); + if (dell_new_hk_type) + kfree(dell_wmi_keymap); printk(KERN_ERR "dell-wmi: Unable to register notify handler - %d\n", status); @@ -359,6 +364,8 @@ static void __exit dell_wmi_exit(void) { wmi_remove_notify_handler(DELL_EVENT_GUID); input_unregister_device(dell_wmi_input_dev); + if (dell_new_hk_type) + kfree(dell_wmi_keymap); } module_init(dell_wmi_init); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 0306174ba875..6b8e06206c46 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -53,7 +53,7 @@ MODULE_LICENSE("GPL"); static bool hotplug_disabled; -module_param(hotplug_disabled, bool, 0644); +module_param(hotplug_disabled, bool, 0444); MODULE_PARM_DESC(hotplug_disabled, "Disable hotplug for wireless device. " "If your laptop need that, please report to " diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index e325aeb37d2e..f44cd2620ff9 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -182,7 +182,7 @@ static enum led_brightness logolamp_get(struct led_classdev *cdev); static void logolamp_set(struct led_classdev *cdev, enum led_brightness brightness); -struct led_classdev logolamp_led = { +static struct led_classdev logolamp_led = { .name = "fujitsu::logolamp", .brightness_get = logolamp_get, .brightness_set = logolamp_set @@ -192,7 +192,7 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev); static void kblamps_set(struct led_classdev *cdev, enum led_brightness brightness); -struct led_classdev kblamps_led = { +static struct led_classdev kblamps_led = { .name = "fujitsu::kblamps", .brightness_get = kblamps_get, .brightness_set = kblamps_set @@ -603,7 +603,7 @@ static int dmi_check_cb_s6410(const struct dmi_system_id *id) dmi_check_cb_common(id); fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 0; + return 1; } static int dmi_check_cb_s6420(const struct dmi_system_id *id) @@ -611,7 +611,7 @@ static int dmi_check_cb_s6420(const struct dmi_system_id *id) dmi_check_cb_common(id); fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */ fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */ - return 0; + return 1; } static int dmi_check_cb_p8010(const struct dmi_system_id *id) @@ -620,7 +620,7 @@ static int dmi_check_cb_p8010(const struct dmi_system_id *id) fujitsu->keycode1 = KEY_HELP; /* "Support" */ fujitsu->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */ fujitsu->keycode4 = KEY_WWW; /* "Internet" */ - return 0; + return 1; } static struct dmi_system_id fujitsu_dmi_table[] = { @@ -725,6 +725,7 @@ static int acpi_fujitsu_add(struct acpi_device *device) err_unregister_input_dev: input_unregister_device(input); + input = NULL; err_free_input_dev: input_free_device(input); err_stop: @@ -738,8 +739,6 @@ static int acpi_fujitsu_remove(struct acpi_device *device, int type) input_unregister_device(input); - input_free_device(input); - fujitsu->acpi_handle = NULL; return 0; @@ -930,6 +929,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) err_unregister_input_dev: input_unregister_device(input); + input = NULL; err_free_input_dev: input_free_device(input); err_free_fifo: @@ -953,8 +953,6 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device, int type) input_unregister_device(input); - input_free_device(input); - kfifo_free(&fujitsu_hotkey->fifo); fujitsu_hotkey->acpi_handle = NULL; diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 51c07a05a7bc..f15516374987 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -29,7 +29,6 @@ #include <linux/slab.h> #include <linux/types.h> #include <linux/input.h> -#include <acpi/acpi_drivers.h> #include <linux/platform_device.h> #include <linux/acpi.h> #include <linux/rfkill.h> @@ -52,12 +51,25 @@ MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); #define HPWMI_WIRELESS_QUERY 0x5 #define HPWMI_HOTKEY_QUERY 0xc +#define PREFIX "HP WMI: " +#define UNIMP "Unimplemented " + enum hp_wmi_radio { HPWMI_WIFI = 0, HPWMI_BLUETOOTH = 1, HPWMI_WWAN = 2, }; +enum hp_wmi_event_ids { + HPWMI_DOCK_EVENT = 1, + HPWMI_PARK_HDD = 2, + HPWMI_SMART_ADAPTER = 3, + HPWMI_BEZEL_BUTTON = 4, + HPWMI_WIRELESS = 5, + HPWMI_CPU_BATTERY_THROTTLE = 6, + HPWMI_LOCK_SWITCH = 7, +}; + static int __devinit hp_wmi_bios_setup(struct platform_device *device); static int __exit hp_wmi_bios_remove(struct platform_device *device); static int hp_wmi_resume_handler(struct device *device); @@ -67,13 +79,12 @@ struct bios_args { u32 command; u32 commandtype; u32 datasize; - u32 data; + char *data; }; struct bios_return { u32 sigpass; u32 return_code; - u32 value; }; struct key_entry { @@ -88,6 +99,7 @@ static struct key_entry hp_wmi_keymap[] = { {KE_KEY, 0x02, KEY_BRIGHTNESSUP}, {KE_KEY, 0x03, KEY_BRIGHTNESSDOWN}, {KE_KEY, 0x20e6, KEY_PROG1}, + {KE_KEY, 0x20e8, KEY_MEDIA}, {KE_KEY, 0x2142, KEY_MEDIA}, {KE_KEY, 0x213b, KEY_INFO}, {KE_KEY, 0x2169, KEY_DIRECTION}, @@ -117,7 +129,27 @@ static struct platform_driver hp_wmi_driver = { .remove = hp_wmi_bios_remove, }; -static int hp_wmi_perform_query(int query, int write, int value) +/* + * hp_wmi_perform_query + * + * query: The commandtype -> What should be queried + * write: The command -> 0 read, 1 write, 3 ODM specific + * buffer: Buffer used as input and/or output + * buffersize: Size of buffer + * + * returns zero on success + * an HP WMI query specific error code (which is positive) + * -EINVAL if the query was not successful at all + * -EINVAL if the output buffer size exceeds buffersize + * + * Note: The buffersize must at least be the maximum of the input and output + * size. E.g. Battery info query (0x7) is defined to have 1 byte input + * and 128 byte output. The caller would do: + * buffer = kzalloc(128, GFP_KERNEL); + * ret = hp_wmi_perform_query(0x7, 0, buffer, 128) + */ +static int hp_wmi_perform_query(int query, int write, char *buffer, + int buffersize) { struct bios_return bios_return; acpi_status status; @@ -126,8 +158,8 @@ static int hp_wmi_perform_query(int query, int write, int value) .signature = 0x55434553, .command = write ? 0x2 : 0x1, .commandtype = query, - .datasize = write ? 0x4 : 0, - .data = value, + .datasize = buffersize, + .data = buffer, }; struct acpi_buffer input = { sizeof(struct bios_args), &args }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -144,54 +176,90 @@ static int hp_wmi_perform_query(int query, int write, int value) } bios_return = *((struct bios_return *)obj->buffer.pointer); + + if (bios_return.return_code) { + printk(KERN_WARNING PREFIX "Query %d returned %d\n", query, + bios_return.return_code); + kfree(obj); + return bios_return.return_code; + } + if (obj->buffer.length - sizeof(bios_return) > buffersize) { + kfree(obj); + return -EINVAL; + } + + memset(buffer, 0, buffersize); + memcpy(buffer, + ((char *)obj->buffer.pointer) + sizeof(struct bios_return), + obj->buffer.length - sizeof(bios_return)); kfree(obj); - if (bios_return.return_code > 0) - return bios_return.return_code * -1; - else - return bios_return.value; + return 0; } static int hp_wmi_display_state(void) { - return hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, 0); + int state; + int ret = hp_wmi_perform_query(HPWMI_DISPLAY_QUERY, 0, (char *)&state, + sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_hddtemp_state(void) { - return hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, 0); + int state; + int ret = hp_wmi_perform_query(HPWMI_HDDTEMP_QUERY, 0, (char *)&state, + sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_als_state(void) { - return hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, 0); + int state; + int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 0, (char *)&state, + sizeof(state)); + if (ret) + return -EINVAL; + return state; } static int hp_wmi_dock_state(void) { - int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); + int state; + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, (char *)&state, + sizeof(state)); - if (ret < 0) - return ret; + if (ret) + return -EINVAL; - return ret & 0x1; + return state & 0x1; } static int hp_wmi_tablet_state(void) { - int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, 0); - - if (ret < 0) + int state; + int ret = hp_wmi_perform_query(HPWMI_HARDWARE_QUERY, 0, (char *)&state, + sizeof(state)); + if (ret) return ret; - return (ret & 0x4) ? 1 : 0; + return (state & 0x4) ? 1 : 0; } static int hp_wmi_set_block(void *data, bool blocked) { enum hp_wmi_radio r = (enum hp_wmi_radio) data; int query = BIT(r + 8) | ((!blocked) << r); + int ret; - return hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, query); + ret = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 1, + (char *)&query, sizeof(query)); + if (ret) + return -EINVAL; + return 0; } static const struct rfkill_ops hp_wmi_rfkill_ops = { @@ -200,8 +268,13 @@ static const struct rfkill_ops hp_wmi_rfkill_ops = { static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) { - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); - int mask = 0x200 << (r * 8); + int wireless; + int mask; + hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, + (char *)&wireless, sizeof(wireless)); + /* TBD: Pass error */ + + mask = 0x200 << (r * 8); if (wireless & mask) return false; @@ -211,8 +284,13 @@ static bool hp_wmi_get_sw_state(enum hp_wmi_radio r) static bool hp_wmi_get_hw_state(enum hp_wmi_radio r) { - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); - int mask = 0x800 << (r * 8); + int wireless; + int mask; + hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, + (char *)&wireless, sizeof(wireless)); + /* TBD: Pass error */ + + mask = 0x800 << (r * 8); if (wireless & mask) return false; @@ -269,7 +347,11 @@ static ssize_t set_als(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u32 tmp = simple_strtoul(buf, NULL, 10); - hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, tmp); + int ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, 1, (char *)&tmp, + sizeof(tmp)); + if (ret) + return -EINVAL; + return count; } @@ -338,47 +420,82 @@ static void hp_wmi_notify(u32 value, void *context) struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; static struct key_entry *key; union acpi_object *obj; - int eventcode; + u32 event_id, event_data; + int key_code, ret; + u32 *location; acpi_status status; status = wmi_get_event_data(value, &response); if (status != AE_OK) { - printk(KERN_INFO "hp-wmi: bad event status 0x%x\n", status); + printk(KERN_INFO PREFIX "bad event status 0x%x\n", status); return; } obj = (union acpi_object *)response.pointer; - if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length != 8) { - printk(KERN_INFO "HP WMI: Unknown response received\n"); + if (!obj) + return; + if (obj->type != ACPI_TYPE_BUFFER) { + printk(KERN_INFO "hp-wmi: Unknown response received %d\n", + obj->type); kfree(obj); return; } - eventcode = *((u8 *) obj->buffer.pointer); + /* + * Depending on ACPI version the concatenation of id and event data + * inside _WED function will result in a 8 or 16 byte buffer. + */ + location = (u32 *)obj->buffer.pointer; + if (obj->buffer.length == 8) { + event_id = *location; + event_data = *(location + 1); + } else if (obj->buffer.length == 16) { + event_id = *location; + event_data = *(location + 2); + } else { + printk(KERN_INFO "hp-wmi: Unknown buffer length %d\n", + obj->buffer.length); + kfree(obj); + return; + } kfree(obj); - if (eventcode == 0x4) - eventcode = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, - 0); - key = hp_wmi_get_entry_by_scancode(eventcode); - if (key) { - switch (key->type) { - case KE_KEY: - input_report_key(hp_wmi_input_dev, - key->keycode, 1); - input_sync(hp_wmi_input_dev); - input_report_key(hp_wmi_input_dev, - key->keycode, 0); - input_sync(hp_wmi_input_dev); - break; - } - } else if (eventcode == 0x1) { + + switch (event_id) { + case HPWMI_DOCK_EVENT: input_report_switch(hp_wmi_input_dev, SW_DOCK, hp_wmi_dock_state()); input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, hp_wmi_tablet_state()); input_sync(hp_wmi_input_dev); - } else if (eventcode == 0x5) { + break; + case HPWMI_PARK_HDD: + break; + case HPWMI_SMART_ADAPTER: + break; + case HPWMI_BEZEL_BUTTON: + ret = hp_wmi_perform_query(HPWMI_HOTKEY_QUERY, 0, + (char *)&key_code, + sizeof(key_code)); + if (ret) + break; + key = hp_wmi_get_entry_by_scancode(key_code); + if (key) { + switch (key->type) { + case KE_KEY: + input_report_key(hp_wmi_input_dev, + key->keycode, 1); + input_sync(hp_wmi_input_dev); + input_report_key(hp_wmi_input_dev, + key->keycode, 0); + input_sync(hp_wmi_input_dev); + break; + } + } else + printk(KERN_INFO PREFIX "Unknown key code - 0x%x\n", + key_code); + break; + case HPWMI_WIRELESS: if (wifi_rfkill) rfkill_set_states(wifi_rfkill, hp_wmi_get_sw_state(HPWMI_WIFI), @@ -391,9 +508,18 @@ static void hp_wmi_notify(u32 value, void *context) rfkill_set_states(wwan_rfkill, hp_wmi_get_sw_state(HPWMI_WWAN), hp_wmi_get_hw_state(HPWMI_WWAN)); - } else - printk(KERN_INFO "HP WMI: Unknown key pressed - %x\n", - eventcode); + break; + case HPWMI_CPU_BATTERY_THROTTLE: + printk(KERN_INFO PREFIX UNIMP "CPU throttle because of 3 Cell" + " battery event detected\n"); + break; + case HPWMI_LOCK_SWITCH: + break; + default: + printk(KERN_INFO PREFIX "Unknown event_id - %d - 0x%x\n", + event_id, event_data); + break; + } } static int __init hp_wmi_input_setup(void) @@ -402,6 +528,8 @@ static int __init hp_wmi_input_setup(void) int err; hp_wmi_input_dev = input_allocate_device(); + if (!hp_wmi_input_dev) + return -ENOMEM; hp_wmi_input_dev->name = "HP WMI hotkeys"; hp_wmi_input_dev->phys = "wmi/input0"; @@ -450,7 +578,12 @@ static void cleanup_sysfs(struct platform_device *device) static int __devinit hp_wmi_bios_setup(struct platform_device *device) { int err; - int wireless = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, 0); + int wireless; + + err = hp_wmi_perform_query(HPWMI_WIRELESS_QUERY, 0, (char *)&wireless, + sizeof(wireless)); + if (err) + return err; err = device_create_file(&device->dev, &dev_attr_display); if (err) @@ -581,27 +714,51 @@ static int hp_wmi_resume_handler(struct device *device) static int __init hp_wmi_init(void) { int err; + int event_capable = wmi_has_guid(HPWMI_EVENT_GUID); + int bios_capable = wmi_has_guid(HPWMI_BIOS_GUID); - if (wmi_has_guid(HPWMI_EVENT_GUID)) { + if (event_capable) { err = wmi_install_notify_handler(HPWMI_EVENT_GUID, hp_wmi_notify, NULL); - if (ACPI_SUCCESS(err)) - hp_wmi_input_setup(); + if (ACPI_FAILURE(err)) + return -EINVAL; + err = hp_wmi_input_setup(); + if (err) { + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + return err; + } } - if (wmi_has_guid(HPWMI_BIOS_GUID)) { + if (bios_capable) { err = platform_driver_register(&hp_wmi_driver); if (err) - return 0; + goto err_driver_reg; hp_wmi_platform_dev = platform_device_alloc("hp-wmi", -1); if (!hp_wmi_platform_dev) { - platform_driver_unregister(&hp_wmi_driver); - return 0; + err = -ENOMEM; + goto err_device_alloc; } - platform_device_add(hp_wmi_platform_dev); + err = platform_device_add(hp_wmi_platform_dev); + if (err) + goto err_device_add; } + if (!bios_capable && !event_capable) + return -ENODEV; + return 0; + +err_device_add: + platform_device_put(hp_wmi_platform_dev); +err_device_alloc: + platform_driver_unregister(&hp_wmi_driver); +err_driver_reg: + if (wmi_has_guid(HPWMI_EVENT_GUID)) { + input_unregister_device(hp_wmi_input_dev); + wmi_remove_notify_handler(HPWMI_EVENT_GUID); + } + + return err; } static void __exit hp_wmi_exit(void) @@ -611,7 +768,7 @@ static void __exit hp_wmi_exit(void) input_unregister_device(hp_wmi_input_dev); } if (hp_wmi_platform_dev) { - platform_device_del(hp_wmi_platform_dev); + platform_device_unregister(hp_wmi_platform_dev); platform_driver_unregister(&hp_wmi_driver); } } diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c new file mode 100644 index 000000000000..afe82e50dfea --- /dev/null +++ b/drivers/platform/x86/intel_ips.c @@ -0,0 +1,1660 @@ +/* + * Copyright (c) 2009-2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Authors: + * Jesse Barnes <jbarnes@virtuousgeek.org> + */ + +/* + * Some Intel Ibex Peak based platforms support so-called "intelligent + * power sharing", which allows the CPU and GPU to cooperate to maximize + * performance within a given TDP (thermal design point). This driver + * performs the coordination between the CPU and GPU, monitors thermal and + * power statistics in the platform, and initializes power monitoring + * hardware. It also provides a few tunables to control behavior. Its + * primary purpose is to safely allow CPU and GPU turbo modes to be enabled + * by tracking power and thermal budget; secondarily it can boost turbo + * performance by allocating more power or thermal budget to the CPU or GPU + * based on available headroom and activity. + * + * The basic algorithm is driven by a 5s moving average of tempurature. If + * thermal headroom is available, the CPU and/or GPU power clamps may be + * adjusted upwards. If we hit the thermal ceiling or a thermal trigger, + * we scale back the clamp. Aside from trigger events (when we're critically + * close or over our TDP) we don't adjust the clamps more than once every + * five seconds. + * + * The thermal device (device 31, function 6) has a set of registers that + * are updated by the ME firmware. The ME should also take the clamp values + * written to those registers and write them to the CPU, but we currently + * bypass that functionality and write the CPU MSR directly. + * + * UNSUPPORTED: + * - dual MCP configs + * + * TODO: + * - handle CPU hotplug + * - provide turbo enable/disable api + * - make sure we can write turbo enable/disable reg based on MISC_EN + * + * Related documents: + * - CDI 403777, 403778 - Auburndale EDS vol 1 & 2 + * - CDI 401376 - Ibex Peak EDS + * - ref 26037, 26641 - IPS BIOS spec + * - ref 26489 - Nehalem BIOS writer's guide + * - ref 26921 - Ibex Peak BIOS Specification + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/string.h> +#include <linux/tick.h> +#include <linux/timer.h> +#include <drm/i915_drm.h> +#include <asm/msr.h> +#include <asm/processor.h> + +#define PCI_DEVICE_ID_INTEL_THERMAL_SENSOR 0x3b32 + +/* + * Package level MSRs for monitor/control + */ +#define PLATFORM_INFO 0xce +#define PLATFORM_TDP (1<<29) +#define PLATFORM_RATIO (1<<28) + +#define IA32_MISC_ENABLE 0x1a0 +#define IA32_MISC_TURBO_EN (1ULL<<38) + +#define TURBO_POWER_CURRENT_LIMIT 0x1ac +#define TURBO_TDC_OVR_EN (1UL<<31) +#define TURBO_TDC_MASK (0x000000007fff0000UL) +#define TURBO_TDC_SHIFT (16) +#define TURBO_TDP_OVR_EN (1UL<<15) +#define TURBO_TDP_MASK (0x0000000000003fffUL) + +/* + * Core/thread MSRs for monitoring + */ +#define IA32_PERF_CTL 0x199 +#define IA32_PERF_TURBO_DIS (1ULL<<32) + +/* + * Thermal PCI device regs + */ +#define THM_CFG_TBAR 0x10 +#define THM_CFG_TBAR_HI 0x14 + +#define THM_TSIU 0x00 +#define THM_TSE 0x01 +#define TSE_EN 0xb8 +#define THM_TSS 0x02 +#define THM_TSTR 0x03 +#define THM_TSTTP 0x04 +#define THM_TSCO 0x08 +#define THM_TSES 0x0c +#define THM_TSGPEN 0x0d +#define TSGPEN_HOT_LOHI (1<<1) +#define TSGPEN_CRIT_LOHI (1<<2) +#define THM_TSPC 0x0e +#define THM_PPEC 0x10 +#define THM_CTA 0x12 +#define THM_PTA 0x14 +#define PTA_SLOPE_MASK (0xff00) +#define PTA_SLOPE_SHIFT 8 +#define PTA_OFFSET_MASK (0x00ff) +#define THM_MGTA 0x16 +#define MGTA_SLOPE_MASK (0xff00) +#define MGTA_SLOPE_SHIFT 8 +#define MGTA_OFFSET_MASK (0x00ff) +#define THM_TRC 0x1a +#define TRC_CORE2_EN (1<<15) +#define TRC_THM_EN (1<<12) +#define TRC_C6_WAR (1<<8) +#define TRC_CORE1_EN (1<<7) +#define TRC_CORE_PWR (1<<6) +#define TRC_PCH_EN (1<<5) +#define TRC_MCH_EN (1<<4) +#define TRC_DIMM4 (1<<3) +#define TRC_DIMM3 (1<<2) +#define TRC_DIMM2 (1<<1) +#define TRC_DIMM1 (1<<0) +#define THM_TES 0x20 +#define THM_TEN 0x21 +#define TEN_UPDATE_EN 1 +#define THM_PSC 0x24 +#define PSC_NTG (1<<0) /* No GFX turbo support */ +#define PSC_NTPC (1<<1) /* No CPU turbo support */ +#define PSC_PP_DEF (0<<2) /* Perf policy up to driver */ +#define PSP_PP_PC (1<<2) /* BIOS prefers CPU perf */ +#define PSP_PP_BAL (2<<2) /* BIOS wants balanced perf */ +#define PSP_PP_GFX (3<<2) /* BIOS prefers GFX perf */ +#define PSP_PBRT (1<<4) /* BIOS run time support */ +#define THM_CTV1 0x30 +#define CTV_TEMP_ERROR (1<<15) +#define CTV_TEMP_MASK 0x3f +#define CTV_ +#define THM_CTV2 0x32 +#define THM_CEC 0x34 /* undocumented power accumulator in joules */ +#define THM_AE 0x3f +#define THM_HTS 0x50 /* 32 bits */ +#define HTS_PCPL_MASK (0x7fe00000) +#define HTS_PCPL_SHIFT 21 +#define HTS_GPL_MASK (0x001ff000) +#define HTS_GPL_SHIFT 12 +#define HTS_PP_MASK (0x00000c00) +#define HTS_PP_SHIFT 10 +#define HTS_PP_DEF 0 +#define HTS_PP_PROC 1 +#define HTS_PP_BAL 2 +#define HTS_PP_GFX 3 +#define HTS_PCTD_DIS (1<<9) +#define HTS_GTD_DIS (1<<8) +#define HTS_PTL_MASK (0x000000fe) +#define HTS_PTL_SHIFT 1 +#define HTS_NVV (1<<0) +#define THM_HTSHI 0x54 /* 16 bits */ +#define HTS2_PPL_MASK (0x03ff) +#define HTS2_PRST_MASK (0x3c00) +#define HTS2_PRST_SHIFT 10 +#define HTS2_PRST_UNLOADED 0 +#define HTS2_PRST_RUNNING 1 +#define HTS2_PRST_TDISOP 2 /* turbo disabled due to power */ +#define HTS2_PRST_TDISHT 3 /* turbo disabled due to high temp */ +#define HTS2_PRST_TDISUSR 4 /* user disabled turbo */ +#define HTS2_PRST_TDISPLAT 5 /* platform disabled turbo */ +#define HTS2_PRST_TDISPM 6 /* power management disabled turbo */ +#define HTS2_PRST_TDISERR 7 /* some kind of error disabled turbo */ +#define THM_PTL 0x56 +#define THM_MGTV 0x58 +#define TV_MASK 0x000000000000ff00 +#define TV_SHIFT 8 +#define THM_PTV 0x60 +#define PTV_MASK 0x00ff +#define THM_MMGPC 0x64 +#define THM_MPPC 0x66 +#define THM_MPCPC 0x68 +#define THM_TSPIEN 0x82 +#define TSPIEN_AUX_LOHI (1<<0) +#define TSPIEN_HOT_LOHI (1<<1) +#define TSPIEN_CRIT_LOHI (1<<2) +#define TSPIEN_AUX2_LOHI (1<<3) +#define THM_TSLOCK 0x83 +#define THM_ATR 0x84 +#define THM_TOF 0x87 +#define THM_STS 0x98 +#define STS_PCPL_MASK (0x7fe00000) +#define STS_PCPL_SHIFT 21 +#define STS_GPL_MASK (0x001ff000) +#define STS_GPL_SHIFT 12 +#define STS_PP_MASK (0x00000c00) +#define STS_PP_SHIFT 10 +#define STS_PP_DEF 0 +#define STS_PP_PROC 1 +#define STS_PP_BAL 2 +#define STS_PP_GFX 3 +#define STS_PCTD_DIS (1<<9) +#define STS_GTD_DIS (1<<8) +#define STS_PTL_MASK (0x000000fe) +#define STS_PTL_SHIFT 1 +#define STS_NVV (1<<0) +#define THM_SEC 0x9c +#define SEC_ACK (1<<0) +#define THM_TC3 0xa4 +#define THM_TC1 0xa8 +#define STS_PPL_MASK (0x0003ff00) +#define STS_PPL_SHIFT 16 +#define THM_TC2 0xac +#define THM_DTV 0xb0 +#define THM_ITV 0xd8 +#define ITV_ME_SEQNO_MASK 0x000f0000 /* ME should update every ~200ms */ +#define ITV_ME_SEQNO_SHIFT (16) +#define ITV_MCH_TEMP_MASK 0x0000ff00 +#define ITV_MCH_TEMP_SHIFT (8) +#define ITV_PCH_TEMP_MASK 0x000000ff + +#define thm_readb(off) readb(ips->regmap + (off)) +#define thm_readw(off) readw(ips->regmap + (off)) +#define thm_readl(off) readl(ips->regmap + (off)) +#define thm_readq(off) readq(ips->regmap + (off)) + +#define thm_writeb(off, val) writeb((val), ips->regmap + (off)) +#define thm_writew(off, val) writew((val), ips->regmap + (off)) +#define thm_writel(off, val) writel((val), ips->regmap + (off)) + +static const int IPS_ADJUST_PERIOD = 5000; /* ms */ + +/* For initial average collection */ +static const int IPS_SAMPLE_PERIOD = 200; /* ms */ +static const int IPS_SAMPLE_WINDOW = 5000; /* 5s moving window of samples */ +#define IPS_SAMPLE_COUNT (IPS_SAMPLE_WINDOW / IPS_SAMPLE_PERIOD) + +/* Per-SKU limits */ +struct ips_mcp_limits { + int cpu_family; + int cpu_model; /* includes extended model... */ + int mcp_power_limit; /* mW units */ + int core_power_limit; + int mch_power_limit; + int core_temp_limit; /* degrees C */ + int mch_temp_limit; +}; + +/* Max temps are -10 degrees C to avoid PROCHOT# */ + +struct ips_mcp_limits ips_sv_limits = { + .mcp_power_limit = 35000, + .core_power_limit = 29000, + .mch_power_limit = 20000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_mcp_limits ips_lv_limits = { + .mcp_power_limit = 25000, + .core_power_limit = 21000, + .mch_power_limit = 13000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_mcp_limits ips_ulv_limits = { + .mcp_power_limit = 18000, + .core_power_limit = 14000, + .mch_power_limit = 11000, + .core_temp_limit = 95, + .mch_temp_limit = 90 +}; + +struct ips_driver { + struct pci_dev *dev; + void *regmap; + struct task_struct *monitor; + struct task_struct *adjust; + struct dentry *debug_root; + + /* Average CPU core temps (all averages in .01 degrees C for precision) */ + u16 ctv1_avg_temp; + u16 ctv2_avg_temp; + /* GMCH average */ + u16 mch_avg_temp; + /* Average for the CPU (both cores?) */ + u16 mcp_avg_temp; + /* Average power consumption (in mW) */ + u32 cpu_avg_power; + u32 mch_avg_power; + + /* Offset values */ + u16 cta_val; + u16 pta_val; + u16 mgta_val; + + /* Maximums & prefs, protected by turbo status lock */ + spinlock_t turbo_status_lock; + u16 mcp_temp_limit; + u16 mcp_power_limit; + u16 core_power_limit; + u16 mch_power_limit; + bool cpu_turbo_enabled; + bool __cpu_turbo_on; + bool gpu_turbo_enabled; + bool __gpu_turbo_on; + bool gpu_preferred; + bool poll_turbo_status; + bool second_cpu; + struct ips_mcp_limits *limits; + + /* Optional MCH interfaces for if i915 is in use */ + unsigned long (*read_mch_val)(void); + bool (*gpu_raise)(void); + bool (*gpu_lower)(void); + bool (*gpu_busy)(void); + bool (*gpu_turbo_disable)(void); + + /* For restoration at unload */ + u64 orig_turbo_limit; + u64 orig_turbo_ratios; +}; + +/** + * ips_cpu_busy - is CPU busy? + * @ips: IPS driver struct + * + * Check CPU for load to see whether we should increase its thermal budget. + * + * RETURNS: + * True if the CPU could use more power, false otherwise. + */ +static bool ips_cpu_busy(struct ips_driver *ips) +{ + if ((avenrun[0] >> FSHIFT) > 1) + return true; + + return false; +} + +/** + * ips_cpu_raise - raise CPU power clamp + * @ips: IPS driver struct + * + * Raise the CPU power clamp by %IPS_CPU_STEP, in accordance with TDP for + * this platform. + * + * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR upwards (as + * long as we haven't hit the TDP limit for the SKU). + */ +static void ips_cpu_raise(struct ips_driver *ips) +{ + u64 turbo_override; + u16 cur_tdp_limit, new_tdp_limit; + + if (!ips->cpu_turbo_enabled) + return; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + cur_tdp_limit = turbo_override & TURBO_TDP_MASK; + new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */ + + /* Clamp to SKU TDP limit */ + if (((new_tdp_limit * 10) / 8) > ips->core_power_limit) + new_tdp_limit = cur_tdp_limit; + + thm_writew(THM_MPCPC, (new_tdp_limit * 10) / 8); + + turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDC_OVR_EN; + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + turbo_override &= ~TURBO_TDP_MASK; + turbo_override |= new_tdp_limit; + + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); +} + +/** + * ips_cpu_lower - lower CPU power clamp + * @ips: IPS driver struct + * + * Lower CPU power clamp b %IPS_CPU_STEP if possible. + * + * We do this by adjusting the TURBO_POWER_CURRENT_LIMIT MSR down, going + * as low as the platform limits will allow (though we could go lower there + * wouldn't be much point). + */ +static void ips_cpu_lower(struct ips_driver *ips) +{ + u64 turbo_override; + u16 cur_limit, new_limit; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + cur_limit = turbo_override & TURBO_TDP_MASK; + new_limit = cur_limit - 8; /* 1W decrease */ + + /* Clamp to SKU TDP limit */ + if (((new_limit * 10) / 8) < (ips->orig_turbo_limit & TURBO_TDP_MASK)) + new_limit = ips->orig_turbo_limit & TURBO_TDP_MASK; + + thm_writew(THM_MPCPC, (new_limit * 10) / 8); + + turbo_override |= TURBO_TDC_OVR_EN | TURBO_TDC_OVR_EN; + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + turbo_override &= ~TURBO_TDP_MASK; + turbo_override |= new_limit; + + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); +} + +/** + * do_enable_cpu_turbo - internal turbo enable function + * @data: unused + * + * Internal function for actually updating MSRs. When we enable/disable + * turbo, we need to do it on each CPU; this function is the one called + * by on_each_cpu() when needed. + */ +static void do_enable_cpu_turbo(void *data) +{ + u64 perf_ctl; + + rdmsrl(IA32_PERF_CTL, perf_ctl); + if (perf_ctl & IA32_PERF_TURBO_DIS) { + perf_ctl &= ~IA32_PERF_TURBO_DIS; + wrmsrl(IA32_PERF_CTL, perf_ctl); + } +} + +/** + * ips_enable_cpu_turbo - enable turbo mode on all CPUs + * @ips: IPS driver struct + * + * Enable turbo mode by clearing the disable bit in IA32_PERF_CTL on + * all logical threads. + */ +static void ips_enable_cpu_turbo(struct ips_driver *ips) +{ + /* Already on, no need to mess with MSRs */ + if (ips->__cpu_turbo_on) + return; + + on_each_cpu(do_enable_cpu_turbo, ips, 1); + + ips->__cpu_turbo_on = true; +} + +/** + * do_disable_cpu_turbo - internal turbo disable function + * @data: unused + * + * Internal function for actually updating MSRs. When we enable/disable + * turbo, we need to do it on each CPU; this function is the one called + * by on_each_cpu() when needed. + */ +static void do_disable_cpu_turbo(void *data) +{ + u64 perf_ctl; + + rdmsrl(IA32_PERF_CTL, perf_ctl); + if (!(perf_ctl & IA32_PERF_TURBO_DIS)) { + perf_ctl |= IA32_PERF_TURBO_DIS; + wrmsrl(IA32_PERF_CTL, perf_ctl); + } +} + +/** + * ips_disable_cpu_turbo - disable turbo mode on all CPUs + * @ips: IPS driver struct + * + * Disable turbo mode by setting the disable bit in IA32_PERF_CTL on + * all logical threads. + */ +static void ips_disable_cpu_turbo(struct ips_driver *ips) +{ + /* Already off, leave it */ + if (!ips->__cpu_turbo_on) + return; + + on_each_cpu(do_disable_cpu_turbo, ips, 1); + + ips->__cpu_turbo_on = false; +} + +/** + * ips_gpu_busy - is GPU busy? + * @ips: IPS driver struct + * + * Check GPU for load to see whether we should increase its thermal budget. + * We need to call into the i915 driver in this case. + * + * RETURNS: + * True if the GPU could use more power, false otherwise. + */ +static bool ips_gpu_busy(struct ips_driver *ips) +{ + if (!ips->gpu_turbo_enabled) + return false; + + return ips->gpu_busy(); +} + +/** + * ips_gpu_raise - raise GPU power clamp + * @ips: IPS driver struct + * + * Raise the GPU frequency/power if possible. We need to call into the + * i915 driver in this case. + */ +static void ips_gpu_raise(struct ips_driver *ips) +{ + if (!ips->gpu_turbo_enabled) + return; + + if (!ips->gpu_raise()) + ips->gpu_turbo_enabled = false; + + return; +} + +/** + * ips_gpu_lower - lower GPU power clamp + * @ips: IPS driver struct + * + * Lower GPU frequency/power if possible. Need to call i915. + */ +static void ips_gpu_lower(struct ips_driver *ips) +{ + if (!ips->gpu_turbo_enabled) + return; + + if (!ips->gpu_lower()) + ips->gpu_turbo_enabled = false; + + return; +} + +/** + * ips_enable_gpu_turbo - notify the gfx driver turbo is available + * @ips: IPS driver struct + * + * Call into the graphics driver indicating that it can safely use + * turbo mode. + */ +static void ips_enable_gpu_turbo(struct ips_driver *ips) +{ + if (ips->__gpu_turbo_on) + return; + ips->__gpu_turbo_on = true; +} + +/** + * ips_disable_gpu_turbo - notify the gfx driver to disable turbo mode + * @ips: IPS driver struct + * + * Request that the graphics driver disable turbo mode. + */ +static void ips_disable_gpu_turbo(struct ips_driver *ips) +{ + /* Avoid calling i915 if turbo is already disabled */ + if (!ips->__gpu_turbo_on) + return; + + if (!ips->gpu_turbo_disable()) + dev_err(&ips->dev->dev, "failed to disable graphis turbo\n"); + else + ips->__gpu_turbo_on = false; +} + +/** + * mcp_exceeded - check whether we're outside our thermal & power limits + * @ips: IPS driver struct + * + * Check whether the MCP is over its thermal or power budget. + */ +static bool mcp_exceeded(struct ips_driver *ips) +{ + unsigned long flags; + bool ret = false; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + if (ips->mcp_avg_temp > (ips->mcp_temp_limit * 100)) + ret = true; + if (ips->cpu_avg_power + ips->mch_avg_power > ips->mcp_power_limit) + ret = true; + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + if (ret) + dev_info(&ips->dev->dev, + "MCP power or thermal limit exceeded\n"); + + return ret; +} + +/** + * cpu_exceeded - check whether a CPU core is outside its limits + * @ips: IPS driver struct + * @cpu: CPU number to check + * + * Check a given CPU's average temp or power is over its limit. + */ +static bool cpu_exceeded(struct ips_driver *ips, int cpu) +{ + unsigned long flags; + int avg; + bool ret = false; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + avg = cpu ? ips->ctv2_avg_temp : ips->ctv1_avg_temp; + if (avg > (ips->limits->core_temp_limit * 100)) + ret = true; + if (ips->cpu_avg_power > ips->core_power_limit * 100) + ret = true; + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + if (ret) + dev_info(&ips->dev->dev, + "CPU power or thermal limit exceeded\n"); + + return ret; +} + +/** + * mch_exceeded - check whether the GPU is over budget + * @ips: IPS driver struct + * + * Check the MCH temp & power against their maximums. + */ +static bool mch_exceeded(struct ips_driver *ips) +{ + unsigned long flags; + bool ret = false; + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + if (ips->mch_avg_temp > (ips->limits->mch_temp_limit * 100)) + ret = true; + if (ips->mch_avg_power > ips->mch_power_limit) + ret = true; + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + return ret; +} + +/** + * update_turbo_limits - get various limits & settings from regs + * @ips: IPS driver struct + * + * Update the IPS power & temp limits, along with turbo enable flags, + * based on latest register contents. + * + * Used at init time and for runtime BIOS support, which requires polling + * the regs for updates (as a result of AC->DC transition for example). + * + * LOCKING: + * Caller must hold turbo_status_lock (outside of init) + */ +static void update_turbo_limits(struct ips_driver *ips) +{ + u32 hts = thm_readl(THM_HTS); + + ips->cpu_turbo_enabled = !(hts & HTS_PCTD_DIS); + ips->gpu_turbo_enabled = !(hts & HTS_GTD_DIS); + ips->core_power_limit = thm_readw(THM_MPCPC); + ips->mch_power_limit = thm_readw(THM_MMGPC); + ips->mcp_temp_limit = thm_readw(THM_PTL); + ips->mcp_power_limit = thm_readw(THM_MPPC); + + /* Ignore BIOS CPU vs GPU pref */ +} + +/** + * ips_adjust - adjust power clamp based on thermal state + * @data: ips driver structure + * + * Wake up every 5s or so and check whether we should adjust the power clamp. + * Check CPU and GPU load to determine which needs adjustment. There are + * several things to consider here: + * - do we need to adjust up or down? + * - is CPU busy? + * - is GPU busy? + * - is CPU in turbo? + * - is GPU in turbo? + * - is CPU or GPU preferred? (CPU is default) + * + * So, given the above, we do the following: + * - up (TDP available) + * - CPU not busy, GPU not busy - nothing + * - CPU busy, GPU not busy - adjust CPU up + * - CPU not busy, GPU busy - adjust GPU up + * - CPU busy, GPU busy - adjust preferred unit up, taking headroom from + * non-preferred unit if necessary + * - down (at TDP limit) + * - adjust both CPU and GPU down if possible + * + cpu+ gpu+ cpu+gpu- cpu-gpu+ cpu-gpu- +cpu < gpu < cpu+gpu+ cpu+ gpu+ nothing +cpu < gpu >= cpu+gpu-(mcp<) cpu+gpu-(mcp<) gpu- gpu- +cpu >= gpu < cpu-gpu+(mcp<) cpu- cpu-gpu+(mcp<) cpu- +cpu >= gpu >= cpu-gpu- cpu-gpu- cpu-gpu- cpu-gpu- + * + */ +static int ips_adjust(void *data) +{ + struct ips_driver *ips = data; + unsigned long flags; + + dev_dbg(&ips->dev->dev, "starting ips-adjust thread\n"); + + /* + * Adjust CPU and GPU clamps every 5s if needed. Doing it more + * often isn't recommended due to ME interaction. + */ + do { + bool cpu_busy = ips_cpu_busy(ips); + bool gpu_busy = ips_gpu_busy(ips); + + spin_lock_irqsave(&ips->turbo_status_lock, flags); + if (ips->poll_turbo_status) + update_turbo_limits(ips); + spin_unlock_irqrestore(&ips->turbo_status_lock, flags); + + /* Update turbo status if necessary */ + if (ips->cpu_turbo_enabled) + ips_enable_cpu_turbo(ips); + else + ips_disable_cpu_turbo(ips); + + if (ips->gpu_turbo_enabled) + ips_enable_gpu_turbo(ips); + else + ips_disable_gpu_turbo(ips); + + /* We're outside our comfort zone, crank them down */ + if (mcp_exceeded(ips)) { + ips_cpu_lower(ips); + ips_gpu_lower(ips); + goto sleep; + } + + if (!cpu_exceeded(ips, 0) && cpu_busy) + ips_cpu_raise(ips); + else + ips_cpu_lower(ips); + + if (!mch_exceeded(ips) && gpu_busy) + ips_gpu_raise(ips); + else + ips_gpu_lower(ips); + +sleep: + schedule_timeout_interruptible(msecs_to_jiffies(IPS_ADJUST_PERIOD)); + } while (!kthread_should_stop()); + + dev_dbg(&ips->dev->dev, "ips-adjust thread stopped\n"); + + return 0; +} + +/* + * Helpers for reading out temp/power values and calculating their + * averages for the decision making and monitoring functions. + */ + +static u16 calc_avg_temp(struct ips_driver *ips, u16 *array) +{ + u64 total = 0; + int i; + u16 avg; + + for (i = 0; i < IPS_SAMPLE_COUNT; i++) + total += (u64)(array[i] * 100); + + do_div(total, IPS_SAMPLE_COUNT); + + avg = (u16)total; + + return avg; +} + +static u16 read_mgtv(struct ips_driver *ips) +{ + u16 ret; + u64 slope, offset; + u64 val; + + val = thm_readq(THM_MGTV); + val = (val & TV_MASK) >> TV_SHIFT; + + slope = offset = thm_readw(THM_MGTA); + slope = (slope & MGTA_SLOPE_MASK) >> MGTA_SLOPE_SHIFT; + offset = offset & MGTA_OFFSET_MASK; + + ret = ((val * slope + 0x40) >> 7) + offset; + + return 0; /* MCH temp reporting buggy */ +} + +static u16 read_ptv(struct ips_driver *ips) +{ + u16 val, slope, offset; + + slope = (ips->pta_val & PTA_SLOPE_MASK) >> PTA_SLOPE_SHIFT; + offset = ips->pta_val & PTA_OFFSET_MASK; + + val = thm_readw(THM_PTV) & PTV_MASK; + + return val; +} + +static u16 read_ctv(struct ips_driver *ips, int cpu) +{ + int reg = cpu ? THM_CTV2 : THM_CTV1; + u16 val; + + val = thm_readw(reg); + if (!(val & CTV_TEMP_ERROR)) + val = (val) >> 6; /* discard fractional component */ + else + val = 0; + + return val; +} + +static u32 get_cpu_power(struct ips_driver *ips, u32 *last, int period) +{ + u32 val; + u32 ret; + + /* + * CEC is in joules/65535. Take difference over time to + * get watts. + */ + val = thm_readl(THM_CEC); + + /* period is in ms and we want mW */ + ret = (((val - *last) * 1000) / period); + ret = (ret * 1000) / 65535; + *last = val; + + return ret; +} + +static const u16 temp_decay_factor = 2; +static u16 update_average_temp(u16 avg, u16 val) +{ + u16 ret; + + /* Multiply by 100 for extra precision */ + ret = (val * 100 / temp_decay_factor) + + (((temp_decay_factor - 1) * avg) / temp_decay_factor); + return ret; +} + +static const u16 power_decay_factor = 2; +static u16 update_average_power(u32 avg, u32 val) +{ + u32 ret; + + ret = (val / power_decay_factor) + + (((power_decay_factor - 1) * avg) / power_decay_factor); + + return ret; +} + +static u32 calc_avg_power(struct ips_driver *ips, u32 *array) +{ + u64 total = 0; + u32 avg; + int i; + + for (i = 0; i < IPS_SAMPLE_COUNT; i++) + total += array[i]; + + do_div(total, IPS_SAMPLE_COUNT); + avg = (u32)total; + + return avg; +} + +static void monitor_timeout(unsigned long arg) +{ + wake_up_process((struct task_struct *)arg); +} + +/** + * ips_monitor - temp/power monitoring thread + * @data: ips driver structure + * + * This is the main function for the IPS driver. It monitors power and + * tempurature in the MCP and adjusts CPU and GPU power clams accordingly. + * + * We keep a 5s moving average of power consumption and tempurature. Using + * that data, along with CPU vs GPU preference, we adjust the power clamps + * up or down. + */ +static int ips_monitor(void *data) +{ + struct ips_driver *ips = data; + struct timer_list timer; + unsigned long seqno_timestamp, expire, last_msecs, last_sample_period; + int i; + u32 *cpu_samples, *mchp_samples, old_cpu_power; + u16 *mcp_samples, *ctv1_samples, *ctv2_samples, *mch_samples; + u8 cur_seqno, last_seqno; + + mcp_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + ctv1_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + ctv2_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + mch_samples = kzalloc(sizeof(u16) * IPS_SAMPLE_COUNT, GFP_KERNEL); + cpu_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL); + mchp_samples = kzalloc(sizeof(u32) * IPS_SAMPLE_COUNT, GFP_KERNEL); + if (!mcp_samples || !ctv1_samples || !ctv2_samples || !mch_samples || + !cpu_samples || !mchp_samples) { + dev_err(&ips->dev->dev, + "failed to allocate sample array, ips disabled\n"); + kfree(mcp_samples); + kfree(ctv1_samples); + kfree(ctv2_samples); + kfree(mch_samples); + kfree(cpu_samples); + kfree(mchp_samples); + kthread_stop(ips->adjust); + return -ENOMEM; + } + + last_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >> + ITV_ME_SEQNO_SHIFT; + seqno_timestamp = get_jiffies_64(); + + old_cpu_power = thm_readl(THM_CEC) / 65535; + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + + /* Collect an initial average */ + for (i = 0; i < IPS_SAMPLE_COUNT; i++) { + u32 mchp, cpu_power; + u16 val; + + mcp_samples[i] = read_ptv(ips); + + val = read_ctv(ips, 0); + ctv1_samples[i] = val; + + val = read_ctv(ips, 1); + ctv2_samples[i] = val; + + val = read_mgtv(ips); + mch_samples[i] = val; + + cpu_power = get_cpu_power(ips, &old_cpu_power, + IPS_SAMPLE_PERIOD); + cpu_samples[i] = cpu_power; + + if (ips->read_mch_val) { + mchp = ips->read_mch_val(); + mchp_samples[i] = mchp; + } + + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + if (kthread_should_stop()) + break; + } + + ips->mcp_avg_temp = calc_avg_temp(ips, mcp_samples); + ips->ctv1_avg_temp = calc_avg_temp(ips, ctv1_samples); + ips->ctv2_avg_temp = calc_avg_temp(ips, ctv2_samples); + ips->mch_avg_temp = calc_avg_temp(ips, mch_samples); + ips->cpu_avg_power = calc_avg_power(ips, cpu_samples); + ips->mch_avg_power = calc_avg_power(ips, mchp_samples); + kfree(mcp_samples); + kfree(ctv1_samples); + kfree(ctv2_samples); + kfree(mch_samples); + kfree(cpu_samples); + kfree(mchp_samples); + + /* Start the adjustment thread now that we have data */ + wake_up_process(ips->adjust); + + /* + * Ok, now we have an initial avg. From here on out, we track the + * running avg using a decaying average calculation. This allows + * us to reduce the sample frequency if the CPU and GPU are idle. + */ + old_cpu_power = thm_readl(THM_CEC); + schedule_timeout_interruptible(msecs_to_jiffies(IPS_SAMPLE_PERIOD)); + last_sample_period = IPS_SAMPLE_PERIOD; + + setup_deferrable_timer_on_stack(&timer, monitor_timeout, + (unsigned long)current); + do { + u32 cpu_val, mch_val; + u16 val; + + /* MCP itself */ + val = read_ptv(ips); + ips->mcp_avg_temp = update_average_temp(ips->mcp_avg_temp, val); + + /* Processor 0 */ + val = read_ctv(ips, 0); + ips->ctv1_avg_temp = + update_average_temp(ips->ctv1_avg_temp, val); + /* Power */ + cpu_val = get_cpu_power(ips, &old_cpu_power, + last_sample_period); + ips->cpu_avg_power = + update_average_power(ips->cpu_avg_power, cpu_val); + + if (ips->second_cpu) { + /* Processor 1 */ + val = read_ctv(ips, 1); + ips->ctv2_avg_temp = + update_average_temp(ips->ctv2_avg_temp, val); + } + + /* MCH */ + val = read_mgtv(ips); + ips->mch_avg_temp = update_average_temp(ips->mch_avg_temp, val); + /* Power */ + if (ips->read_mch_val) { + mch_val = ips->read_mch_val(); + ips->mch_avg_power = + update_average_power(ips->mch_avg_power, + mch_val); + } + + /* + * Make sure ME is updating thermal regs. + * Note: + * If it's been more than a second since the last update, + * the ME is probably hung. + */ + cur_seqno = (thm_readl(THM_ITV) & ITV_ME_SEQNO_MASK) >> + ITV_ME_SEQNO_SHIFT; + if (cur_seqno == last_seqno && + time_after(jiffies, seqno_timestamp + HZ)) { + dev_warn(&ips->dev->dev, "ME failed to update for more than 1s, likely hung\n"); + } else { + seqno_timestamp = get_jiffies_64(); + last_seqno = cur_seqno; + } + + last_msecs = jiffies_to_msecs(jiffies); + expire = jiffies + msecs_to_jiffies(IPS_SAMPLE_PERIOD); + + __set_current_state(TASK_UNINTERRUPTIBLE); + mod_timer(&timer, expire); + schedule(); + + /* Calculate actual sample period for power averaging */ + last_sample_period = jiffies_to_msecs(jiffies) - last_msecs; + if (!last_sample_period) + last_sample_period = 1; + } while (!kthread_should_stop()); + + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + + dev_dbg(&ips->dev->dev, "ips-monitor thread stopped\n"); + + return 0; +} + +#if 0 +#define THM_DUMPW(reg) \ + { \ + u16 val = thm_readw(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%04x\n", val); \ + } +#define THM_DUMPL(reg) \ + { \ + u32 val = thm_readl(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%08x\n", val); \ + } +#define THM_DUMPQ(reg) \ + { \ + u64 val = thm_readq(reg); \ + dev_dbg(&ips->dev->dev, #reg ": 0x%016x\n", val); \ + } + +static void dump_thermal_info(struct ips_driver *ips) +{ + u16 ptl; + + ptl = thm_readw(THM_PTL); + dev_dbg(&ips->dev->dev, "Processor temp limit: %d\n", ptl); + + THM_DUMPW(THM_CTA); + THM_DUMPW(THM_TRC); + THM_DUMPW(THM_CTV1); + THM_DUMPL(THM_STS); + THM_DUMPW(THM_PTV); + THM_DUMPQ(THM_MGTV); +} +#endif + +/** + * ips_irq_handler - handle temperature triggers and other IPS events + * @irq: irq number + * @arg: unused + * + * Handle temperature limit trigger events, generally by lowering the clamps. + * If we're at a critical limit, we clamp back to the lowest possible value + * to prevent emergency shutdown. + */ +static irqreturn_t ips_irq_handler(int irq, void *arg) +{ + struct ips_driver *ips = arg; + u8 tses = thm_readb(THM_TSES); + u8 tes = thm_readb(THM_TES); + + if (!tses && !tes) + return IRQ_NONE; + + dev_info(&ips->dev->dev, "TSES: 0x%02x\n", tses); + dev_info(&ips->dev->dev, "TES: 0x%02x\n", tes); + + /* STS update from EC? */ + if (tes & 1) { + u32 sts, tc1; + + sts = thm_readl(THM_STS); + tc1 = thm_readl(THM_TC1); + + if (sts & STS_NVV) { + spin_lock(&ips->turbo_status_lock); + ips->core_power_limit = (sts & STS_PCPL_MASK) >> + STS_PCPL_SHIFT; + ips->mch_power_limit = (sts & STS_GPL_MASK) >> + STS_GPL_SHIFT; + /* ignore EC CPU vs GPU pref */ + ips->cpu_turbo_enabled = !(sts & STS_PCTD_DIS); + ips->gpu_turbo_enabled = !(sts & STS_GTD_DIS); + ips->mcp_temp_limit = (sts & STS_PTL_MASK) >> + STS_PTL_SHIFT; + ips->mcp_power_limit = (tc1 & STS_PPL_MASK) >> + STS_PPL_SHIFT; + spin_unlock(&ips->turbo_status_lock); + + thm_writeb(THM_SEC, SEC_ACK); + } + thm_writeb(THM_TES, tes); + } + + /* Thermal trip */ + if (tses) { + dev_warn(&ips->dev->dev, + "thermal trip occurred, tses: 0x%04x\n", tses); + thm_writeb(THM_TSES, tses); + } + + return IRQ_HANDLED; +} + +#ifndef CONFIG_DEBUG_FS +static void ips_debugfs_init(struct ips_driver *ips) { return; } +static void ips_debugfs_cleanup(struct ips_driver *ips) { return; } +#else + +/* Expose current state and limits in debugfs if possible */ + +struct ips_debugfs_node { + struct ips_driver *ips; + char *name; + int (*show)(struct seq_file *m, void *data); +}; + +static int show_cpu_temp(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%d.%02d\n", ips->ctv1_avg_temp / 100, + ips->ctv1_avg_temp % 100); + + return 0; +} + +static int show_cpu_power(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%dmW\n", ips->cpu_avg_power); + + return 0; +} + +static int show_cpu_clamp(struct seq_file *m, void *data) +{ + u64 turbo_override; + int tdp, tdc; + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + + tdp = (int)(turbo_override & TURBO_TDP_MASK); + tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT); + + /* Convert to .1W/A units */ + tdp = tdp * 10 / 8; + tdc = tdc * 10 / 8; + + /* Watts Amperes */ + seq_printf(m, "%d.%dW %d.%dA\n", tdp / 10, tdp % 10, + tdc / 10, tdc % 10); + + return 0; +} + +static int show_mch_temp(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%d.%02d\n", ips->mch_avg_temp / 100, + ips->mch_avg_temp % 100); + + return 0; +} + +static int show_mch_power(struct seq_file *m, void *data) +{ + struct ips_driver *ips = m->private; + + seq_printf(m, "%dmW\n", ips->mch_avg_power); + + return 0; +} + +static struct ips_debugfs_node ips_debug_files[] = { + { NULL, "cpu_temp", show_cpu_temp }, + { NULL, "cpu_power", show_cpu_power }, + { NULL, "cpu_clamp", show_cpu_clamp }, + { NULL, "mch_temp", show_mch_temp }, + { NULL, "mch_power", show_mch_power }, +}; + +static int ips_debugfs_open(struct inode *inode, struct file *file) +{ + struct ips_debugfs_node *node = inode->i_private; + + return single_open(file, node->show, node->ips); +} + +static const struct file_operations ips_debugfs_ops = { + .owner = THIS_MODULE, + .open = ips_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void ips_debugfs_cleanup(struct ips_driver *ips) +{ + if (ips->debug_root) + debugfs_remove_recursive(ips->debug_root); + return; +} + +static void ips_debugfs_init(struct ips_driver *ips) +{ + int i; + + ips->debug_root = debugfs_create_dir("ips", NULL); + if (!ips->debug_root) { + dev_err(&ips->dev->dev, + "failed to create debugfs entries: %ld\n", + PTR_ERR(ips->debug_root)); + return; + } + + for (i = 0; i < ARRAY_SIZE(ips_debug_files); i++) { + struct dentry *ent; + struct ips_debugfs_node *node = &ips_debug_files[i]; + + node->ips = ips; + ent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, + ips->debug_root, node, + &ips_debugfs_ops); + if (!ent) { + dev_err(&ips->dev->dev, + "failed to create debug file: %ld\n", + PTR_ERR(ent)); + goto err_cleanup; + } + } + + return; + +err_cleanup: + ips_debugfs_cleanup(ips); + return; +} +#endif /* CONFIG_DEBUG_FS */ + +/** + * ips_detect_cpu - detect whether CPU supports IPS + * + * Walk our list and see if we're on a supported CPU. If we find one, + * return the limits for it. + */ +static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips) +{ + u64 turbo_power, misc_en; + struct ips_mcp_limits *limits = NULL; + u16 tdp; + + if (!(boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 37)) { + dev_info(&ips->dev->dev, "Non-IPS CPU detected.\n"); + goto out; + } + + rdmsrl(IA32_MISC_ENABLE, misc_en); + /* + * If the turbo enable bit isn't set, we shouldn't try to enable/disable + * turbo manually or we'll get an illegal MSR access, even though + * turbo will still be available. + */ + if (!(misc_en & IA32_MISC_TURBO_EN)) + ; /* add turbo MSR write allowed flag if necessary */ + + if (strstr(boot_cpu_data.x86_model_id, "CPU M")) + limits = &ips_sv_limits; + else if (strstr(boot_cpu_data.x86_model_id, "CPU L")) + limits = &ips_lv_limits; + else if (strstr(boot_cpu_data.x86_model_id, "CPU U")) + limits = &ips_ulv_limits; + else + dev_info(&ips->dev->dev, "No CPUID match found.\n"); + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_power); + tdp = turbo_power & TURBO_TDP_MASK; + + /* Sanity check TDP against CPU */ + if (limits->mcp_power_limit != (tdp / 8) * 1000) { + dev_warn(&ips->dev->dev, "Warning: CPU TDP doesn't match expected value (found %d, expected %d)\n", + tdp / 8, limits->mcp_power_limit / 1000); + } + +out: + return limits; +} + +/** + * ips_get_i915_syms - try to get GPU control methods from i915 driver + * @ips: IPS driver + * + * The i915 driver exports several interfaces to allow the IPS driver to + * monitor and control graphics turbo mode. If we can find them, we can + * enable graphics turbo, otherwise we must disable it to avoid exceeding + * thermal and power limits in the MCP. + */ +static bool ips_get_i915_syms(struct ips_driver *ips) +{ + ips->read_mch_val = symbol_get(i915_read_mch_val); + if (!ips->read_mch_val) + goto out_err; + ips->gpu_raise = symbol_get(i915_gpu_raise); + if (!ips->gpu_raise) + goto out_put_mch; + ips->gpu_lower = symbol_get(i915_gpu_lower); + if (!ips->gpu_lower) + goto out_put_raise; + ips->gpu_busy = symbol_get(i915_gpu_busy); + if (!ips->gpu_busy) + goto out_put_lower; + ips->gpu_turbo_disable = symbol_get(i915_gpu_turbo_disable); + if (!ips->gpu_turbo_disable) + goto out_put_busy; + + return true; + +out_put_busy: + symbol_put(i915_gpu_turbo_disable); +out_put_lower: + symbol_put(i915_gpu_lower); +out_put_raise: + symbol_put(i915_gpu_raise); +out_put_mch: + symbol_put(i915_read_mch_val); +out_err: + return false; +} + +static DEFINE_PCI_DEVICE_TABLE(ips_id_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_THERMAL_SENSOR), }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, ips_id_table); + +static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + u64 platform_info; + struct ips_driver *ips; + u32 hts; + int ret = 0; + u16 htshi, trc, trc_required_mask; + u8 tse; + + ips = kzalloc(sizeof(struct ips_driver), GFP_KERNEL); + if (!ips) + return -ENOMEM; + + pci_set_drvdata(dev, ips); + ips->dev = dev; + + ips->limits = ips_detect_cpu(ips); + if (!ips->limits) { + dev_info(&dev->dev, "IPS not supported on this CPU\n"); + ret = -ENXIO; + goto error_free; + } + + spin_lock_init(&ips->turbo_status_lock); + + if (!pci_resource_start(dev, 0)) { + dev_err(&dev->dev, "TBAR not assigned, aborting\n"); + ret = -ENXIO; + goto error_free; + } + + ret = pci_request_regions(dev, "ips thermal sensor"); + if (ret) { + dev_err(&dev->dev, "thermal resource busy, aborting\n"); + goto error_free; + } + + ret = pci_enable_device(dev); + if (ret) { + dev_err(&dev->dev, "can't enable PCI device, aborting\n"); + goto error_free; + } + + ips->regmap = ioremap(pci_resource_start(dev, 0), + pci_resource_len(dev, 0)); + if (!ips->regmap) { + dev_err(&dev->dev, "failed to map thermal regs, aborting\n"); + ret = -EBUSY; + goto error_release; + } + + tse = thm_readb(THM_TSE); + if (tse != TSE_EN) { + dev_err(&dev->dev, "thermal device not enabled (0x%02x), aborting\n", tse); + ret = -ENXIO; + goto error_unmap; + } + + trc = thm_readw(THM_TRC); + trc_required_mask = TRC_CORE1_EN | TRC_CORE_PWR | TRC_MCH_EN; + if ((trc & trc_required_mask) != trc_required_mask) { + dev_err(&dev->dev, "thermal reporting for required devices not enabled, aborting\n"); + ret = -ENXIO; + goto error_unmap; + } + + if (trc & TRC_CORE2_EN) + ips->second_cpu = true; + + update_turbo_limits(ips); + dev_dbg(&dev->dev, "max cpu power clamp: %dW\n", + ips->mcp_power_limit / 10); + dev_dbg(&dev->dev, "max core power clamp: %dW\n", + ips->core_power_limit / 10); + /* BIOS may update limits at runtime */ + if (thm_readl(THM_PSC) & PSP_PBRT) + ips->poll_turbo_status = true; + + if (!ips_get_i915_syms(ips)) { + dev_err(&dev->dev, "failed to get i915 symbols, graphics turbo disabled\n"); + ips->gpu_turbo_enabled = false; + } else { + dev_dbg(&dev->dev, "graphics turbo enabled\n"); + ips->gpu_turbo_enabled = true; + } + + /* + * Check PLATFORM_INFO MSR to make sure this chip is + * turbo capable. + */ + rdmsrl(PLATFORM_INFO, platform_info); + if (!(platform_info & PLATFORM_TDP)) { + dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n"); + ret = -ENODEV; + goto error_unmap; + } + + /* + * IRQ handler for ME interaction + * Note: don't use MSI here as the PCH has bugs. + */ + pci_disable_msi(dev); + ret = request_irq(dev->irq, ips_irq_handler, IRQF_SHARED, "ips", + ips); + if (ret) { + dev_err(&dev->dev, "request irq failed, aborting\n"); + goto error_unmap; + } + + /* Enable aux, hot & critical interrupts */ + thm_writeb(THM_TSPIEN, TSPIEN_AUX2_LOHI | TSPIEN_CRIT_LOHI | + TSPIEN_HOT_LOHI | TSPIEN_AUX_LOHI); + thm_writeb(THM_TEN, TEN_UPDATE_EN); + + /* Collect adjustment values */ + ips->cta_val = thm_readw(THM_CTA); + ips->pta_val = thm_readw(THM_PTA); + ips->mgta_val = thm_readw(THM_MGTA); + + /* Save turbo limits & ratios */ + rdmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit); + + ips_enable_cpu_turbo(ips); + ips->cpu_turbo_enabled = true; + + /* Set up the work queue and monitor/adjust threads */ + ips->monitor = kthread_run(ips_monitor, ips, "ips-monitor"); + if (IS_ERR(ips->monitor)) { + dev_err(&dev->dev, + "failed to create thermal monitor thread, aborting\n"); + ret = -ENOMEM; + goto error_free_irq; + } + + ips->adjust = kthread_create(ips_adjust, ips, "ips-adjust"); + if (IS_ERR(ips->adjust)) { + dev_err(&dev->dev, + "failed to create thermal adjust thread, aborting\n"); + ret = -ENOMEM; + goto error_thread_cleanup; + } + + hts = (ips->core_power_limit << HTS_PCPL_SHIFT) | + (ips->mcp_temp_limit << HTS_PTL_SHIFT) | HTS_NVV; + htshi = HTS2_PRST_RUNNING << HTS2_PRST_SHIFT; + + thm_writew(THM_HTSHI, htshi); + thm_writel(THM_HTS, hts); + + ips_debugfs_init(ips); + + dev_info(&dev->dev, "IPS driver initialized, MCP temp limit %d\n", + ips->mcp_temp_limit); + return ret; + +error_thread_cleanup: + kthread_stop(ips->monitor); +error_free_irq: + free_irq(ips->dev->irq, ips); +error_unmap: + iounmap(ips->regmap); +error_release: + pci_release_regions(dev); +error_free: + kfree(ips); + return ret; +} + +static void ips_remove(struct pci_dev *dev) +{ + struct ips_driver *ips = pci_get_drvdata(dev); + u64 turbo_override; + + if (!ips) + return; + + ips_debugfs_cleanup(ips); + + /* Release i915 driver */ + if (ips->read_mch_val) + symbol_put(i915_read_mch_val); + if (ips->gpu_raise) + symbol_put(i915_gpu_raise); + if (ips->gpu_lower) + symbol_put(i915_gpu_lower); + if (ips->gpu_busy) + symbol_put(i915_gpu_busy); + if (ips->gpu_turbo_disable) + symbol_put(i915_gpu_turbo_disable); + + rdmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN); + wrmsrl(TURBO_POWER_CURRENT_LIMIT, turbo_override); + wrmsrl(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit); + + free_irq(ips->dev->irq, ips); + if (ips->adjust) + kthread_stop(ips->adjust); + if (ips->monitor) + kthread_stop(ips->monitor); + iounmap(ips->regmap); + pci_release_regions(dev); + kfree(ips); + dev_dbg(&dev->dev, "IPS driver removed\n"); +} + +#ifdef CONFIG_PM +static int ips_suspend(struct pci_dev *dev, pm_message_t state) +{ + return 0; +} + +static int ips_resume(struct pci_dev *dev) +{ + return 0; +} +#else +#define ips_suspend NULL +#define ips_resume NULL +#endif /* CONFIG_PM */ + +static void ips_shutdown(struct pci_dev *dev) +{ +} + +static struct pci_driver ips_pci_driver = { + .name = "intel ips", + .id_table = ips_id_table, + .probe = ips_probe, + .remove = ips_remove, + .suspend = ips_suspend, + .resume = ips_resume, + .shutdown = ips_shutdown, +}; + +static int __init ips_init(void) +{ + return pci_register_driver(&ips_pci_driver); +} +module_init(ips_init); + +static void ips_exit(void) +{ + pci_unregister_driver(&ips_pci_driver); + return; +} +module_exit(ips_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jesse Barnes <jbarnes@virtuousgeek.org>"); +MODULE_DESCRIPTION("Intelligent Power Sharing Driver"); diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c index 2f795ce2b939..eacd5da7dd24 100644 --- a/drivers/platform/x86/intel_menlow.c +++ b/drivers/platform/x86/intel_menlow.c @@ -53,6 +53,8 @@ MODULE_LICENSE("GPL"); #define MEMORY_ARG_CUR_BANDWIDTH 1 #define MEMORY_ARG_MAX_BANDWIDTH 0 +static void intel_menlow_unregister_sensor(void); + /* * GTHS returning 'n' would mean that [0,n-1] states are supported * In that case max_cstate would be n-1 @@ -406,8 +408,10 @@ static int intel_menlow_add_one_attribute(char *name, int mode, void *show, attr->handle = handle; result = device_create_file(dev, &attr->attr); - if (result) + if (result) { + kfree(attr); return result; + } mutex_lock(&intel_menlow_attr_lock); list_add_tail(&attr->node, &intel_menlow_attr_list); @@ -431,11 +435,11 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, /* _TZ must have the AUX0/1 methods */ status = acpi_get_handle(handle, GET_AUX0, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + return (status == AE_NOT_FOUND) ? AE_OK : status; status = acpi_get_handle(handle, SET_AUX0, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + return (status == AE_NOT_FOUND) ? AE_OK : status; result = intel_menlow_add_one_attribute("aux0", 0644, aux0_show, aux0_store, @@ -445,17 +449,19 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, status = acpi_get_handle(handle, GET_AUX1, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + goto aux1_not_found; status = acpi_get_handle(handle, SET_AUX1, &dummy); if (ACPI_FAILURE(status)) - goto not_found; + goto aux1_not_found; result = intel_menlow_add_one_attribute("aux1", 0644, aux1_show, aux1_store, &thermal->device, handle); - if (result) + if (result) { + intel_menlow_unregister_sensor(); return AE_ERROR; + } /* * create the "dabney_enabled" attribute which means the user app @@ -465,14 +471,17 @@ static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl, result = intel_menlow_add_one_attribute("bios_enabled", 0444, bios_enabled_show, NULL, &thermal->device, handle); - if (result) + if (result) { + intel_menlow_unregister_sensor(); return AE_ERROR; + } - not_found: + aux1_not_found: if (status == AE_NOT_FOUND) return AE_OK; - else - return status; + + intel_menlow_unregister_sensor(); + return status; } static void intel_menlow_unregister_sensor(void) @@ -513,8 +522,10 @@ static int __init intel_menlow_module_init(void) status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, intel_menlow_register_sensor, NULL, NULL, NULL); - if (ACPI_FAILURE(status)) + if (ACPI_FAILURE(status)) { + acpi_bus_unregister_driver(&intel_menlow_memory_driver); return -ENODEV; + } return 0; } diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c new file mode 100644 index 000000000000..5cdcff653918 --- /dev/null +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -0,0 +1,340 @@ +/* Moorestown PMIC GPIO (access through IPC) driver + * Copyright (c) 2008 - 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * Moorestown platform PMIC chip + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/stddef.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <asm/intel_scu_ipc.h> +#include <linux/device.h> +#include <linux/intel_pmic_gpio.h> +#include <linux/platform_device.h> + +#define DRIVER_NAME "pmic_gpio" + +/* register offset that IPC driver should use + * 8 GPIO + 8 GPOSW (6 controllable) + 8GPO + */ +enum pmic_gpio_register { + GPIO0 = 0xE0, + GPIO7 = 0xE7, + GPIOINT = 0xE8, + GPOSWCTL0 = 0xEC, + GPOSWCTL5 = 0xF1, + GPO = 0xF4, +}; + +/* bits definition for GPIO & GPOSW */ +#define GPIO_DRV 0x01 +#define GPIO_DIR 0x02 +#define GPIO_DIN 0x04 +#define GPIO_DOU 0x08 +#define GPIO_INTCTL 0x30 +#define GPIO_DBC 0xc0 + +#define GPOSW_DRV 0x01 +#define GPOSW_DOU 0x08 +#define GPOSW_RDRV 0x30 + + +#define NUM_GPIO 24 + +struct pmic_gpio_irq { + spinlock_t lock; + u32 trigger[NUM_GPIO]; + u32 dirty; + struct work_struct work; +}; + + +struct pmic_gpio { + struct gpio_chip chip; + struct pmic_gpio_irq irqtypes; + void *gpiointr; + int irq; + unsigned irq_base; +}; + +static void pmic_program_irqtype(int gpio, int type) +{ + if (type & IRQ_TYPE_EDGE_RISING) + intel_scu_ipc_update_register(GPIO0 + gpio, 0x20, 0x20); + else + intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x20); + + if (type & IRQ_TYPE_EDGE_FALLING) + intel_scu_ipc_update_register(GPIO0 + gpio, 0x10, 0x10); + else + intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10); +}; + +static void pmic_irqtype_work(struct work_struct *work) +{ + struct pmic_gpio_irq *t = + container_of(work, struct pmic_gpio_irq, work); + unsigned long flags; + int i; + u16 type; + + spin_lock_irqsave(&t->lock, flags); + /* As we drop the lock, we may need multiple scans if we race the + pmic_irq_type function */ + while (t->dirty) { + /* + * For each pin that has the dirty bit set send an IPC + * message to configure the hardware via the PMIC + */ + for (i = 0; i < NUM_GPIO; i++) { + if (!(t->dirty & (1 << i))) + continue; + t->dirty &= ~(1 << i); + /* We can't trust the array entry or dirty + once the lock is dropped */ + type = t->trigger[i]; + spin_unlock_irqrestore(&t->lock, flags); + pmic_program_irqtype(i, type); + spin_lock_irqsave(&t->lock, flags); + } + } + spin_unlock_irqrestore(&t->lock, flags); +} + +static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + if (offset > 8) { + printk(KERN_ERR + "%s: only pin 0-7 support input\n", __func__); + return -1;/* we only have 8 GPIO can use as input */ + } + return intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DIR, GPIO_DIR); +} + +static int pmic_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + int rc = 0; + + if (offset < 8)/* it is GPIO */ + rc = intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DRV | GPIO_DOU | GPIO_DIR, + GPIO_DRV | (value ? GPIO_DOU : 0)); + else if (offset < 16)/* it is GPOSW */ + rc = intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8, + GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV, + GPOSW_DRV | (value ? GPOSW_DOU : 0)); + else if (offset > 15 && offset < 24)/* it is GPO */ + rc = intel_scu_ipc_update_register(GPO, + 1 << (offset - 16), + value ? 1 << (offset - 16) : 0); + else { + printk(KERN_ERR + "%s: invalid PMIC GPIO pin %d!\n", __func__, offset); + WARN_ON(1); + } + + return rc; +} + +static int pmic_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + u8 r; + int ret; + + /* we only have 8 GPIO pins we can use as input */ + if (offset > 8) + return -EOPNOTSUPP; + ret = intel_scu_ipc_ioread8(GPIO0 + offset, &r); + if (ret < 0) + return ret; + return r & GPIO_DIN; +} + +static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + if (offset < 8)/* it is GPIO */ + intel_scu_ipc_update_register(GPIO0 + offset, + GPIO_DRV | GPIO_DOU, + GPIO_DRV | (value ? GPIO_DOU : 0)); + else if (offset < 16)/* it is GPOSW */ + intel_scu_ipc_update_register(GPOSWCTL0 + offset - 8, + GPOSW_DRV | GPOSW_DOU | GPOSW_RDRV, + GPOSW_DRV | (value ? GPOSW_DOU : 0)); + else if (offset > 15 && offset < 24) /* it is GPO */ + intel_scu_ipc_update_register(GPO, + 1 << (offset - 16), + value ? 1 << (offset - 16) : 0); +} + +static int pmic_irq_type(unsigned irq, unsigned type) +{ + struct pmic_gpio *pg = get_irq_chip_data(irq); + u32 gpio = irq - pg->irq_base; + unsigned long flags; + + if (gpio > pg->chip.ngpio) + return -EINVAL; + + spin_lock_irqsave(&pg->irqtypes.lock, flags); + pg->irqtypes.trigger[gpio] = type; + pg->irqtypes.dirty |= (1 << gpio); + spin_unlock_irqrestore(&pg->irqtypes.lock, flags); + schedule_work(&pg->irqtypes.work); + return 0; +} + + + +static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pmic_gpio *pg = container_of(chip, struct pmic_gpio, chip); + + return pg->irq_base + offset; +} + +/* the gpiointr register is read-clear, so just do nothing. */ +static void pmic_irq_unmask(unsigned irq) +{ +}; + +static void pmic_irq_mask(unsigned irq) +{ +}; + +static struct irq_chip pmic_irqchip = { + .name = "PMIC-GPIO", + .mask = pmic_irq_mask, + .unmask = pmic_irq_unmask, + .set_type = pmic_irq_type, +}; + +static void pmic_irq_handler(unsigned irq, struct irq_desc *desc) +{ + struct pmic_gpio *pg = (struct pmic_gpio *)get_irq_data(irq); + u8 intsts = *((u8 *)pg->gpiointr + 4); + int gpio; + + for (gpio = 0; gpio < 8; gpio++) { + if (intsts & (1 << gpio)) { + pr_debug("pmic pin %d triggered\n", gpio); + generic_handle_irq(pg->irq_base + gpio); + } + } + desc->chip->eoi(irq); +} + +static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq = platform_get_irq(pdev, 0); + struct intel_pmic_gpio_platform_data *pdata = dev->platform_data; + + struct pmic_gpio *pg; + int retval; + int i; + + if (irq < 0) { + dev_dbg(dev, "no IRQ line\n"); + return -EINVAL; + } + + if (!pdata || !pdata->gpio_base || !pdata->irq_base) { + dev_dbg(dev, "incorrect or missing platform data\n"); + return -EINVAL; + } + + pg = kzalloc(sizeof(*pg), GFP_KERNEL); + if (!pg) + return -ENOMEM; + + dev_set_drvdata(dev, pg); + + pg->irq = irq; + /* setting up SRAM mapping for GPIOINT register */ + pg->gpiointr = ioremap_nocache(pdata->gpiointr, 8); + if (!pg->gpiointr) { + printk(KERN_ERR "%s: Can not map GPIOINT.\n", __func__); + retval = -EINVAL; + goto err2; + } + pg->irq_base = pdata->irq_base; + pg->chip.label = "intel_pmic"; + pg->chip.direction_input = pmic_gpio_direction_input; + pg->chip.direction_output = pmic_gpio_direction_output; + pg->chip.get = pmic_gpio_get; + pg->chip.set = pmic_gpio_set; + pg->chip.to_irq = pmic_gpio_to_irq; + pg->chip.base = pdata->gpio_base; + pg->chip.ngpio = NUM_GPIO; + pg->chip.can_sleep = 1; + pg->chip.dev = dev; + + INIT_WORK(&pg->irqtypes.work, pmic_irqtype_work); + spin_lock_init(&pg->irqtypes.lock); + + pg->chip.dev = dev; + retval = gpiochip_add(&pg->chip); + if (retval) { + printk(KERN_ERR "%s: Can not add pmic gpio chip.\n", __func__); + goto err; + } + set_irq_data(pg->irq, pg); + set_irq_chained_handler(pg->irq, pmic_irq_handler); + for (i = 0; i < 8; i++) { + set_irq_chip_and_handler_name(i + pg->irq_base, &pmic_irqchip, + handle_simple_irq, "demux"); + set_irq_chip_data(i + pg->irq_base, pg); + } + return 0; +err: + iounmap(pg->gpiointr); +err2: + kfree(pg); + return retval; +} + +/* at the same time, register a platform driver + * this supports the sfi 0.81 fw */ +static struct platform_driver platform_pmic_gpio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = platform_pmic_gpio_probe, +}; + +static int __init platform_pmic_gpio_init(void) +{ + return platform_driver_register(&platform_pmic_gpio_driver); +} + +subsys_initcall(platform_pmic_gpio_init); + +MODULE_AUTHOR("Alek Du <alek.du@intel.com>"); +MODULE_DESCRIPTION("Intel Moorestown PMIC GPIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_rar_register.c b/drivers/platform/x86/intel_rar_register.c new file mode 100644 index 000000000000..73f8e6d72669 --- /dev/null +++ b/drivers/platform/x86/intel_rar_register.c @@ -0,0 +1,671 @@ +/* + * rar_register.c - An Intel Restricted Access Region register driver + * + * Copyright(c) 2009 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ------------------------------------------------------------------- + * 20091204 Mark Allyn <mark.a.allyn@intel.com> + * Ossama Othman <ossama.othman@intel.com> + * Cleanup per feedback from Alan Cox and Arjan Van De Ven + * + * 20090806 Ossama Othman <ossama.othman@intel.com> + * Return zero high address if upper 22 bits is zero. + * Cleaned up checkpatch errors. + * Clarified that driver is dealing with bus addresses. + * + * 20090702 Ossama Othman <ossama.othman@intel.com> + * Removed unnecessary include directives + * Cleaned up spinlocks. + * Cleaned up logging. + * Improved invalid parameter checks. + * Fixed and simplified RAR address retrieval and RAR locking + * code. + * + * 20090626 Mark Allyn <mark.a.allyn@intel.com> + * Initial publish + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/rar_register.h> + +/* === Lincroft Message Bus Interface === */ +#define LNC_MCR_OFFSET 0xD0 /* Message Control Register */ +#define LNC_MDR_OFFSET 0xD4 /* Message Data Register */ + +/* Message Opcodes */ +#define LNC_MESSAGE_READ_OPCODE 0xD0 +#define LNC_MESSAGE_WRITE_OPCODE 0xE0 + +/* Message Write Byte Enables */ +#define LNC_MESSAGE_BYTE_WRITE_ENABLES 0xF + +/* B-unit Port */ +#define LNC_BUNIT_PORT 0x3 + +/* === Lincroft B-Unit Registers - Programmed by IA32 firmware === */ +#define LNC_BRAR0L 0x10 +#define LNC_BRAR0H 0x11 +#define LNC_BRAR1L 0x12 +#define LNC_BRAR1H 0x13 +/* Reserved for SeP */ +#define LNC_BRAR2L 0x14 +#define LNC_BRAR2H 0x15 + +/* Moorestown supports three restricted access regions. */ +#define MRST_NUM_RAR 3 + +/* RAR Bus Address Range */ +struct rar_addr { + dma_addr_t low; + dma_addr_t high; +}; + +/* + * We create one of these for each RAR + */ +struct client { + int (*callback)(unsigned long data); + unsigned long driver_priv; + bool busy; +}; + +static DEFINE_MUTEX(rar_mutex); +static DEFINE_MUTEX(lnc_reg_mutex); + +/* + * One per RAR device (currently only one device) + */ +struct rar_device { + struct rar_addr rar_addr[MRST_NUM_RAR]; + struct pci_dev *rar_dev; + bool registered; + bool allocated; + struct client client[MRST_NUM_RAR]; +}; + +/* Current platforms have only one rar_device for 3 rar regions */ +static struct rar_device my_rar_device; + +/* + * Abstract out multiple device support. Current platforms only + * have a single RAR device. + */ + +/** + * alloc_rar_device - return a new RAR structure + * + * Return a new (but not yet ready) RAR device object + */ +static struct rar_device *alloc_rar_device(void) +{ + if (my_rar_device.allocated) + return NULL; + my_rar_device.allocated = 1; + return &my_rar_device; +} + +/** + * free_rar_device - free a RAR object + * @rar: the RAR device being freed + * + * Release a RAR object and any attached resources + */ +static void free_rar_device(struct rar_device *rar) +{ + pci_dev_put(rar->rar_dev); + rar->allocated = 0; +} + +/** + * _rar_to_device - return the device handling this RAR + * @rar: RAR number + * @off: returned offset + * + * Internal helper for looking up RAR devices. This and alloc are the + * two functions that need touching to go to multiple RAR devices. + */ +static struct rar_device *_rar_to_device(int rar, int *off) +{ + if (rar >= 0 && rar <= 3) { + *off = rar; + return &my_rar_device; + } + return NULL; +} + +/** + * rar_to_device - return the device handling this RAR + * @rar: RAR number + * @off: returned offset + * + * Return the device this RAR maps to if one is present, otherwise + * returns NULL. Reports the offset relative to the base of this + * RAR device in off. + */ +static struct rar_device *rar_to_device(int rar, int *off) +{ + struct rar_device *rar_dev = _rar_to_device(rar, off); + if (rar_dev == NULL || !rar_dev->registered) + return NULL; + return rar_dev; +} + +/** + * rar_to_client - return the client handling this RAR + * @rar: RAR number + * + * Return the client this RAR maps to if a mapping is known, otherwise + * returns NULL. + */ +static struct client *rar_to_client(int rar) +{ + int idx; + struct rar_device *r = _rar_to_device(rar, &idx); + if (r != NULL) + return &r->client[idx]; + return NULL; +} + +/** + * rar_read_addr - retrieve a RAR mapping + * @pdev: PCI device for the RAR + * @offset: offset for message + * @addr: returned address + * + * Reads the address of a given RAR register. Returns 0 on success + * or an error code on failure. + */ +static int rar_read_addr(struct pci_dev *pdev, int offset, dma_addr_t *addr) +{ + /* + * ======== The Lincroft Message Bus Interface ======== + * Lincroft registers may be obtained via PCI from + * the host bridge using the Lincroft Message Bus + * Interface. That message bus interface is generally + * comprised of two registers: a control register (MCR, 0xDO) + * and a data register (MDR, 0xD4). + * + * The MCR (message control register) format is the following: + * 1. [31:24]: Opcode + * 2. [23:16]: Port + * 3. [15:8]: Register Offset + * 4. [7:4]: Byte Enables (use 0xF to set all of these bits + * to 1) + * 5. [3:0]: reserved + * + * Read (0xD0) and write (0xE0) opcodes are written to the + * control register when reading and writing to Lincroft + * registers, respectively. + * + * We're interested in registers found in the Lincroft + * B-unit. The B-unit port is 0x3. + * + * The six B-unit RAR register offsets we use are listed + * earlier in this file. + * + * Lastly writing to the MCR register requires the "Byte + * enables" bits to be set to 1. This may be achieved by + * writing 0xF at bit 4. + * + * The MDR (message data register) format is the following: + * 1. [31:0]: Read/Write Data + * + * Data being read from this register is only available after + * writing the appropriate control message to the MCR + * register. + * + * Data being written to this register must be written before + * writing the appropriate control message to the MCR + * register. + */ + + int result; + u32 addr32; + + /* Construct control message */ + u32 const message = + (LNC_MESSAGE_READ_OPCODE << 24) + | (LNC_BUNIT_PORT << 16) + | (offset << 8) + | (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); + + dev_dbg(&pdev->dev, "Offset for 'get' LNC MSG is %x\n", offset); + + /* + * We synchronize access to the Lincroft MCR and MDR registers + * until BOTH the command is issued through the MCR register + * and the corresponding data is read from the MDR register. + * Otherwise a race condition would exist between accesses to + * both registers. + */ + + mutex_lock(&lnc_reg_mutex); + + /* Send the control message */ + result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message); + if (!result) { + /* Read back the address as a 32bit value */ + result = pci_read_config_dword(pdev, LNC_MDR_OFFSET, &addr32); + *addr = (dma_addr_t)addr32; + } + mutex_unlock(&lnc_reg_mutex); + return result; +} + +/** + * rar_set_addr - Set a RAR mapping + * @pdev: PCI device for the RAR + * @offset: offset for message + * @addr: address to set + * + * Sets the address of a given RAR register. Returns 0 on success + * or an error code on failure. + */ +static int rar_set_addr(struct pci_dev *pdev, + int offset, + dma_addr_t addr) +{ + /* + * Data being written to this register must be written before + * writing the appropriate control message to the MCR + * register. + * See rar_get_addrs() for a description of the + * message bus interface being used here. + */ + + int result; + + /* Construct control message */ + u32 const message = (LNC_MESSAGE_WRITE_OPCODE << 24) + | (LNC_BUNIT_PORT << 16) + | (offset << 8) + | (LNC_MESSAGE_BYTE_WRITE_ENABLES << 4); + + /* + * We synchronize access to the Lincroft MCR and MDR registers + * until BOTH the command is issued through the MCR register + * and the corresponding data is read from the MDR register. + * Otherwise a race condition would exist between accesses to + * both registers. + */ + + mutex_lock(&lnc_reg_mutex); + + /* Send the control message */ + result = pci_write_config_dword(pdev, LNC_MDR_OFFSET, addr); + if (!result) + /* And address */ + result = pci_write_config_dword(pdev, LNC_MCR_OFFSET, message); + + mutex_unlock(&lnc_reg_mutex); + return result; +} + +/* + * rar_init_params - Initialize RAR parameters + * @rar: RAR device to initialise + * + * Initialize RAR parameters, such as bus addresses, etc. Returns 0 + * on success, or an error code on failure. + */ +static int init_rar_params(struct rar_device *rar) +{ + struct pci_dev *pdev = rar->rar_dev; + unsigned int i; + int result = 0; + int offset = 0x10; /* RAR 0 to 2 in order low/high/low/high/... */ + + /* Retrieve RAR start and end bus addresses. + * Access the RAR registers through the Lincroft Message Bus + * Interface on PCI device: 00:00.0 Host bridge. + */ + + for (i = 0; i < MRST_NUM_RAR; ++i) { + struct rar_addr *addr = &rar->rar_addr[i]; + + result = rar_read_addr(pdev, offset++, &addr->low); + if (result != 0) + return result; + + result = rar_read_addr(pdev, offset++, &addr->high); + if (result != 0) + return result; + + + /* + * Only the upper 22 bits of the RAR addresses are + * stored in their corresponding RAR registers so we + * must set the lower 10 bits accordingly. + + * The low address has its lower 10 bits cleared, and + * the high address has all its lower 10 bits set, + * e.g.: + * low = 0x2ffffc00 + */ + + addr->low &= (dma_addr_t)0xfffffc00u; + + /* + * Set bits 9:0 on uppser address if bits 31:10 are non + * zero; otherwize clear all bits + */ + + if ((addr->high & 0xfffffc00u) == 0) + addr->high = 0; + else + addr->high |= 0x3ffu; + } + /* Done accessing the device. */ + + if (result == 0) { + for (i = 0; i != MRST_NUM_RAR; ++i) { + /* + * "BRAR" refers to the RAR registers in the + * Lincroft B-unit. + */ + dev_info(&pdev->dev, "BRAR[%u] bus address range = " + "[%lx, %lx]\n", i, + (unsigned long)rar->rar_addr[i].low, + (unsigned long)rar->rar_addr[i].high); + } + } + return result; +} + +/** + * rar_get_address - get the bus address in a RAR + * @start: return value of start address of block + * @end: return value of end address of block + * + * The rar_get_address function is used by other device drivers + * to obtain RAR address information on a RAR. It takes three + * parameters: + * + * The function returns a 0 upon success or an error if there is no RAR + * facility on this system. + */ +int rar_get_address(int rar_index, dma_addr_t *start, dma_addr_t *end) +{ + int idx; + struct rar_device *rar = rar_to_device(rar_index, &idx); + + if (rar == NULL) { + WARN_ON(1); + return -ENODEV; + } + + *start = rar->rar_addr[idx].low; + *end = rar->rar_addr[idx].high; + return 0; +} +EXPORT_SYMBOL(rar_get_address); + +/** + * rar_lock - lock a RAR register + * @rar_index: RAR to lock (0-2) + * + * The rar_lock function is ued by other device drivers to lock an RAR. + * once a RAR is locked, it stays locked until the next system reboot. + * + * The function returns a 0 upon success or an error if there is no RAR + * facility on this system, or the locking fails + */ +int rar_lock(int rar_index) +{ + struct rar_device *rar; + int result; + int idx; + dma_addr_t low, high; + + rar = rar_to_device(rar_index, &idx); + + if (rar == NULL) { + WARN_ON(1); + return -EINVAL; + } + + low = rar->rar_addr[idx].low & 0xfffffc00u; + high = rar->rar_addr[idx].high & 0xfffffc00u; + + /* + * Only allow I/O from the graphics and Langwell; + * not from the x86 processor + */ + + if (rar_index == RAR_TYPE_VIDEO) { + low |= 0x00000009; + high |= 0x00000015; + } else if (rar_index == RAR_TYPE_AUDIO) { + /* Only allow I/O from Langwell; nothing from x86 */ + low |= 0x00000008; + high |= 0x00000018; + } else + /* Read-only from all agents */ + high |= 0x00000018; + + /* + * Now program the register using the Lincroft message + * bus interface. + */ + result = rar_set_addr(rar->rar_dev, + 2 * idx, low); + + if (result == 0) + result = rar_set_addr(rar->rar_dev, + 2 * idx + 1, high); + + return result; +} +EXPORT_SYMBOL(rar_lock); + +/** + * register_rar - register a RAR handler + * @num: RAR we wish to register for + * @callback: function to call when RAR support is available + * @data: data to pass to this function + * + * The register_rar function is to used by other device drivers + * to ensure that this driver is ready. As we cannot be sure of + * the compile/execute order of drivers in ther kernel, it is + * best to give this driver a callback function to call when + * it is ready to give out addresses. The callback function + * would have those steps that continue the initialization of + * a driver that do require a valid RAR address. One of those + * steps would be to call rar_get_address() + * + * This function return 0 on success or an error code on failure. + */ +int register_rar(int num, int (*callback)(unsigned long data), + unsigned long data) +{ + /* For now we hardcode a single RAR device */ + struct rar_device *rar; + struct client *c; + int idx; + int retval = 0; + + mutex_lock(&rar_mutex); + + /* Do we have a client mapping for this RAR number ? */ + c = rar_to_client(num); + if (c == NULL) { + retval = -ERANGE; + goto done; + } + /* Is it claimed ? */ + if (c->busy) { + retval = -EBUSY; + goto done; + } + c->busy = 1; + + /* See if we have a handler for this RAR yet, if we do then fire it */ + rar = rar_to_device(num, &idx); + + if (rar) { + /* + * if the driver already registered, then we can simply + * call the callback right now + */ + (*callback)(data); + goto done; + } + + /* Arrange to be called back when the hardware is found */ + c->callback = callback; + c->driver_priv = data; +done: + mutex_unlock(&rar_mutex); + return retval; +} +EXPORT_SYMBOL(register_rar); + +/** + * unregister_rar - release a RAR allocation + * @num: RAR number + * + * Releases a RAR allocation, or pending allocation. If a callback is + * pending then this function will either complete before the unregister + * returns or not at all. + */ + +void unregister_rar(int num) +{ + struct client *c; + + mutex_lock(&rar_mutex); + c = rar_to_client(num); + if (c == NULL || !c->busy) + WARN_ON(1); + else + c->busy = 0; + mutex_unlock(&rar_mutex); +} +EXPORT_SYMBOL(unregister_rar); + +/** + * rar_callback - Process callbacks + * @rar: new RAR device + * + * Process the callbacks for a newly found RAR device. + */ + +static void rar_callback(struct rar_device *rar) +{ + struct client *c = &rar->client[0]; + int i; + + mutex_lock(&rar_mutex); + + rar->registered = 1; /* Ensure no more callbacks queue */ + + for (i = 0; i < MRST_NUM_RAR; i++) { + if (c->callback && c->busy) { + c->callback(c->driver_priv); + c->callback = NULL; + } + c++; + } + mutex_unlock(&rar_mutex); +} + +/** + * rar_probe - PCI probe callback + * @dev: PCI device + * @id: matching entry in the match table + * + * A RAR device has been discovered. Initialise it and if successful + * process any pending callbacks that can now be completed. + */ +static int rar_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int error; + struct rar_device *rar; + + dev_dbg(&dev->dev, "PCI probe starting\n"); + + rar = alloc_rar_device(); + if (rar == NULL) + return -EBUSY; + + /* Enable the device */ + error = pci_enable_device(dev); + if (error) { + dev_err(&dev->dev, + "Error enabling RAR register PCI device\n"); + goto end_function; + } + + /* Fill in the rar_device structure */ + rar->rar_dev = pci_dev_get(dev); + pci_set_drvdata(dev, rar); + + /* + * Initialize the RAR parameters, which have to be retrieved + * via the message bus interface. + */ + error = init_rar_params(rar); + if (error) { + pci_disable_device(dev); + dev_err(&dev->dev, "Error retrieving RAR addresses\n"); + goto end_function; + } + /* now call anyone who has registered (using callbacks) */ + rar_callback(rar); + return 0; +end_function: + free_rar_device(rar); + return error; +} + +const struct pci_device_id rar_pci_id_tbl[] = { + { PCI_VDEVICE(INTEL, 0x4110) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, rar_pci_id_tbl); + +const struct pci_device_id *my_id_table = rar_pci_id_tbl; + +/* field for registering driver to PCI device */ +static struct pci_driver rar_pci_driver = { + .name = "rar_register_driver", + .id_table = rar_pci_id_tbl, + .probe = rar_probe, + /* Cannot be unplugged - no remove */ +}; + +static int __init rar_init_handler(void) +{ + return pci_register_driver(&rar_pci_driver); +} + +static void __exit rar_exit_handler(void) +{ + pci_unregister_driver(&rar_pci_driver); +} + +module_init(rar_init_handler); +module_exit(rar_exit_handler); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel Restricted Access Region Register Driver"); diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index 40658e3385b4..943f9084dcb1 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -23,7 +23,7 @@ #include <linux/pm.h> #include <linux/pci.h> #include <linux/interrupt.h> -#include <asm/setup.h> +#include <asm/mrst.h> #include <asm/intel_scu_ipc.h> /* IPC defines the following message types */ @@ -38,10 +38,6 @@ #define IPC_CMD_PCNTRL_R 1 /* Register read */ #define IPC_CMD_PCNTRL_M 2 /* Register read-modify-write */ -/* Miscelaneous Command ids */ -#define IPC_CMD_INDIRECT_RD 2 /* 32bit indirect read */ -#define IPC_CMD_INDIRECT_WR 5 /* 32bit indirect write */ - /* * IPC register summary * @@ -62,8 +58,8 @@ #define IPC_BASE_ADDR 0xFF11C000 /* IPC1 base register address */ #define IPC_MAX_ADDR 0x100 /* Maximum IPC regisers */ -#define IPC_WWBUF_SIZE 16 /* IPC Write buffer Size */ -#define IPC_RWBUF_SIZE 16 /* IPC Read buffer Size */ +#define IPC_WWBUF_SIZE 20 /* IPC Write buffer Size */ +#define IPC_RWBUF_SIZE 20 /* IPC Read buffer Size */ #define IPC_I2C_BASE 0xFF12B000 /* I2C control register base address */ #define IPC_I2C_MAX_ADDR 0x10 /* Maximum I2C regisers */ @@ -78,12 +74,7 @@ struct intel_scu_ipc_dev { static struct intel_scu_ipc_dev ipcdev; /* Only one for now */ -static int platform = 1; -module_param(platform, int, 0); -MODULE_PARM_DESC(platform, "1 for moorestown platform"); - - - +static int platform; /* Platform type */ /* * IPC Read Buffer (Read Only): @@ -119,24 +110,6 @@ static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */ } /* - * IPC destination Pointer (Write Only): - * Use content as pointer for destination write - */ -static inline void ipc_write_dptr(u32 data) /* Write dptr data */ -{ - writel(data, ipcdev.ipc_base + 0x0C); -} - -/* - * IPC Source Pointer (Write Only): - * Use content as pointer for read location -*/ -static inline void ipc_write_sptr(u32 data) /* Write dptr data */ -{ - writel(data, ipcdev.ipc_base + 0x08); -} - -/* * Status Register (Read Only): * Driver will read this register to get the ready/busy status of the IPC * block and error status of the IPC command that was just processed by SCU @@ -154,7 +127,7 @@ static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */ return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset); } -static inline u8 ipc_data_readl(u32 offset) /* Read ipc u32 data */ +static inline u32 ipc_data_readl(u32 offset) /* Read ipc u32 data */ { return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset); } @@ -175,62 +148,73 @@ static inline int busy_loop(void) /* Wait till scu status is busy */ return -ETIMEDOUT; } } - return (status >> 1) & 1; + if ((status >> 1) & 1) + return -EIO; + + return 0; } /* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */ static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id) { - int nc; + int i, nc, bytes, d; u32 offset = 0; u32 err = 0; - u8 cbuf[IPC_WWBUF_SIZE] = { '\0' }; + u8 cbuf[IPC_WWBUF_SIZE] = { }; u32 *wbuf = (u32 *)&cbuf; mutex_lock(&ipclock); + + memset(cbuf, 0, sizeof(cbuf)); + if (ipcdev.pdev == NULL) { mutex_unlock(&ipclock); return -ENODEV; } - if (platform == 1) { - /* Entry is 4 bytes for read/write, 5 bytes for read modify */ - for (nc = 0; nc < count; nc++) { + if (platform != MRST_CPU_CHIP_PENWELL) { + bytes = 0; + d = 0; + for (i = 0; i < count; i++) { + cbuf[bytes++] = addr[i]; + cbuf[bytes++] = addr[i] >> 8; + if (id != IPC_CMD_PCNTRL_R) + cbuf[bytes++] = data[d++]; + if (id == IPC_CMD_PCNTRL_M) + cbuf[bytes++] = data[d++]; + } + for (i = 0; i < bytes; i += 4) + ipc_data_writel(wbuf[i/4], i); + ipc_command(bytes << 16 | id << 12 | 0 << 8 | op); + } else { + for (nc = 0; nc < count; nc++, offset += 2) { cbuf[offset] = addr[nc]; cbuf[offset + 1] = addr[nc] >> 8; - if (id != IPC_CMD_PCNTRL_R) - cbuf[offset + 2] = data[nc]; - if (id == IPC_CMD_PCNTRL_M) { - cbuf[offset + 3] = data[nc + 1]; - offset += 1; - } - offset += 3; } - for (nc = 0, offset = 0; nc < count; nc++, offset += 4) - ipc_data_writel(wbuf[nc], offset); /* Write wbuff */ - } else { - for (nc = 0, offset = 0; nc < count; nc++, offset += 2) - ipc_data_writel(addr[nc], offset); /* Write addresses */ - if (id != IPC_CMD_PCNTRL_R) { - for (nc = 0; nc < count; nc++, offset++) - ipc_data_writel(data[nc], offset); /* Write data */ - if (id == IPC_CMD_PCNTRL_M) - ipc_data_writel(data[nc + 1], offset); /* Mask value*/ + if (id == IPC_CMD_PCNTRL_R) { + for (nc = 0, offset = 0; nc < count; nc++, offset += 4) + ipc_data_writel(wbuf[nc], offset); + ipc_command((count*2) << 16 | id << 12 | 0 << 8 | op); + } else if (id == IPC_CMD_PCNTRL_W) { + for (nc = 0; nc < count; nc++, offset += 1) + cbuf[offset] = data[nc]; + for (nc = 0, offset = 0; nc < count; nc++, offset += 4) + ipc_data_writel(wbuf[nc], offset); + ipc_command((count*3) << 16 | id << 12 | 0 << 8 | op); + } else if (id == IPC_CMD_PCNTRL_M) { + cbuf[offset] = data[0]; + cbuf[offset + 1] = data[1]; + ipc_data_writel(wbuf[0], 0); /* Write wbuff */ + ipc_command(4 << 16 | id << 12 | 0 << 8 | op); } } - if (id != IPC_CMD_PCNTRL_M) - ipc_command((count * 3) << 16 | id << 12 | 0 << 8 | op); - else - ipc_command((count * 4) << 16 | id << 12 | 0 << 8 | op); - err = busy_loop(); - if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */ /* Workaround: values are read as 0 without memcpy_fromio */ - memcpy_fromio(cbuf, ipcdev.ipc_base + IPC_READ_BUFFER, 16); - if (platform == 1) { + memcpy_fromio(cbuf, ipcdev.ipc_base + 0x90, 16); + if (platform != MRST_CPU_CHIP_PENWELL) { for (nc = 0, offset = 2; nc < count; nc++, offset += 3) data[nc] = ipc_data_readb(offset); } else { @@ -405,70 +389,6 @@ int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask) EXPORT_SYMBOL(intel_scu_ipc_update_register); /** - * intel_scu_ipc_register_read - 32bit indirect read - * @addr: register address - * @value: 32bit value return - * - * Performs IA 32 bit indirect read, returns 0 on success, or an - * error code. - * - * Can be used when SCCB(System Controller Configuration Block) register - * HRIM(Honor Restricted IPC Messages) is set (bit 23) - * - * This function may sleep. Locking for SCU accesses is handled for - * the caller. - */ -int intel_scu_ipc_register_read(u32 addr, u32 *value) -{ - u32 err = 0; - - mutex_lock(&ipclock); - if (ipcdev.pdev == NULL) { - mutex_unlock(&ipclock); - return -ENODEV; - } - ipc_write_sptr(addr); - ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD); - err = busy_loop(); - *value = ipc_data_readl(0); - mutex_unlock(&ipclock); - return err; -} -EXPORT_SYMBOL(intel_scu_ipc_register_read); - -/** - * intel_scu_ipc_register_write - 32bit indirect write - * @addr: register address - * @value: 32bit value to write - * - * Performs IA 32 bit indirect write, returns 0 on success, or an - * error code. - * - * Can be used when SCCB(System Controller Configuration Block) register - * HRIM(Honor Restricted IPC Messages) is set (bit 23) - * - * This function may sleep. Locking for SCU accesses is handled for - * the caller. - */ -int intel_scu_ipc_register_write(u32 addr, u32 value) -{ - u32 err = 0; - - mutex_lock(&ipclock); - if (ipcdev.pdev == NULL) { - mutex_unlock(&ipclock); - return -ENODEV; - } - ipc_write_dptr(addr); - ipc_data_writel(value, 0); - ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR); - err = busy_loop(); - mutex_unlock(&ipclock); - return err; -} -EXPORT_SYMBOL(intel_scu_ipc_register_write); - -/** * intel_scu_ipc_simple_command - send a simple command * @cmd: command * @sub: sub type @@ -489,7 +409,7 @@ int intel_scu_ipc_simple_command(int cmd, int sub) mutex_unlock(&ipclock); return -ENODEV; } - ipc_command(cmd << 12 | sub); + ipc_command(sub << 12 | cmd); err = busy_loop(); mutex_unlock(&ipclock); return err; @@ -501,9 +421,9 @@ EXPORT_SYMBOL(intel_scu_ipc_simple_command); * @cmd: command * @sub: sub type * @in: input data - * @inlen: input length + * @inlen: input length in dwords * @out: output data - * @outlein: output length + * @outlein: output length in dwords * * Issue a command to the SCU which involves data transfers. Do the * data copies under the lock but leave it for the caller to interpret @@ -524,7 +444,7 @@ int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen, for (i = 0; i < inlen; i++) ipc_data_writel(*in++, 4 * i); - ipc_command((cmd << 12) | sub | (inlen << 18)); + ipc_command((inlen << 16) | (sub << 12) | cmd); err = busy_loop(); for (i = 0; i < outlen; i++) @@ -556,6 +476,10 @@ int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data) u32 cmd = 0; mutex_lock(&ipclock); + if (ipcdev.pdev == NULL) { + mutex_unlock(&ipclock); + return -ENODEV; + } cmd = (addr >> 24) & 0xFF; if (cmd == IPC_I2C_READ) { writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR); @@ -799,6 +723,7 @@ static void ipc_remove(struct pci_dev *pdev) static const struct pci_device_id pci_ids[] = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x082a)}, { 0,} }; MODULE_DEVICE_TABLE(pci, pci_ids); @@ -813,6 +738,9 @@ static struct pci_driver ipc_driver = { static int __init intel_scu_ipc_init(void) { + platform = mrst_identify_cpu(); + if (platform == 0) + return -ENODEV; return pci_register_driver(&ipc_driver); } diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index afd762b58ad9..7e9bb6df9d39 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -434,7 +434,7 @@ static int dmi_check_cb(const struct dmi_system_id *id) { printk(KERN_INFO "msi-laptop: Identified laptop model '%s'.\n", id->ident); - return 0; + return 1; } static struct dmi_system_id __initdata msi_dmi_table[] = { @@ -562,15 +562,15 @@ static int rfkill_threeg_set(void *data, bool blocked) return 0; } -static struct rfkill_ops rfkill_bluetooth_ops = { +static const struct rfkill_ops rfkill_bluetooth_ops = { .set_block = rfkill_bluetooth_set }; -static struct rfkill_ops rfkill_wlan_ops = { +static const struct rfkill_ops rfkill_wlan_ops = { .set_block = rfkill_wlan_set }; -static struct rfkill_ops rfkill_threeg_ops = { +static const struct rfkill_ops rfkill_threeg_ops = { .set_block = rfkill_threeg_set }; diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index d1736009636f..42a5469a2459 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -57,7 +57,7 @@ static struct key_entry msi_wmi_keymap[] = { }; static ktime_t last_pressed[ARRAY_SIZE(msi_wmi_keymap) - 1]; -struct backlight_device *backlight; +static struct backlight_device *backlight; static int backlight_map[] = { 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF }; diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 2fb9a32926f8..ec01c3d8fc5a 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -248,7 +248,7 @@ static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val) status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET, ¶ms, NULL); - return status == AE_OK; + return (status == AE_OK) ? 0 : -EIO; } static inline int acpi_pcc_get_sqty(struct acpi_device *device) @@ -586,7 +586,6 @@ static int acpi_pcc_init_input(struct pcc_acpi *pcc) static int acpi_pcc_hotkey_resume(struct acpi_device *device) { struct pcc_acpi *pcc = acpi_driver_data(device); - acpi_status status = AE_OK; if (device == NULL || pcc == NULL) return -EINVAL; @@ -594,9 +593,7 @@ static int acpi_pcc_hotkey_resume(struct acpi_device *device) ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n", pcc->sticky_mode)); - status = acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); - - return status == AE_OK ? 0 : -EINVAL; + return acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); } static int acpi_pcc_hotkey_add(struct acpi_device *device) diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 1387c5f9c24d..e3154ff7a39f 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -561,8 +561,7 @@ static void sony_pf_remove(void) if (!atomic_dec_and_test(&sony_pf_users)) return; - platform_device_del(sony_pf_device); - platform_device_put(sony_pf_device); + platform_device_unregister(sony_pf_device); platform_driver_unregister(&sony_pf_driver); } @@ -1196,9 +1195,13 @@ static void sony_nc_rfkill_setup(struct acpi_device *device) } device_enum = (union acpi_object *) buffer.pointer; - if (!device_enum || device_enum->type != ACPI_TYPE_BUFFER) { - printk(KERN_ERR "Invalid SN06 return object 0x%.2x\n", - device_enum->type); + if (!device_enum) { + pr_err("Invalid SN06 return object\n"); + goto out_no_enum; + } + if (device_enum->type != ACPI_TYPE_BUFFER) { + pr_err("Invalid SN06 return object type 0x%.2x\n", + device_enum->type); goto out_no_enum; } diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4bdb13796e24..5d6119bed00c 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -5838,75 +5838,6 @@ static struct ibm_struct thermal_driver_data = { }; /************************************************************************* - * EC Dump subdriver - */ - -static u8 ecdump_regs[256]; - -static int ecdump_read(struct seq_file *m) -{ - int i, j; - u8 v; - - seq_printf(m, "EC " - " +00 +01 +02 +03 +04 +05 +06 +07" - " +08 +09 +0a +0b +0c +0d +0e +0f\n"); - for (i = 0; i < 256; i += 16) { - seq_printf(m, "EC 0x%02x:", i); - for (j = 0; j < 16; j++) { - if (!acpi_ec_read(i + j, &v)) - break; - if (v != ecdump_regs[i + j]) - seq_printf(m, " *%02x", v); - else - seq_printf(m, " %02x", v); - ecdump_regs[i + j] = v; - } - seq_putc(m, '\n'); - if (j != 16) - break; - } - - /* These are way too dangerous to advertise openly... */ -#if 0 - seq_printf(m, "commands:\t0x<offset> 0x<value>" - " (<offset> is 00-ff, <value> is 00-ff)\n"); - seq_printf(m, "commands:\t0x<offset> <value> " - " (<offset> is 00-ff, <value> is 0-255)\n"); -#endif - return 0; -} - -static int ecdump_write(char *buf) -{ - char *cmd; - int i, v; - - while ((cmd = next_cmd(&buf))) { - if (sscanf(cmd, "0x%x 0x%x", &i, &v) == 2) { - /* i and v set */ - } else if (sscanf(cmd, "0x%x %u", &i, &v) == 2) { - /* i and v set */ - } else - return -EINVAL; - if (i >= 0 && i < 256 && v >= 0 && v < 256) { - if (!acpi_ec_write(i, v)) - return -EIO; - } else - return -EINVAL; - } - - return 0; -} - -static struct ibm_struct ecdump_driver_data = { - .name = "ecdump", - .read = ecdump_read, - .write = ecdump_write, - .flags.experimental = 1, -}; - -/************************************************************************* * Backlight/brightness subdriver */ @@ -8883,9 +8814,6 @@ static struct ibm_init_struct ibms_init[] __initdata = { .data = &thermal_driver_data, }, { - .data = &ecdump_driver_data, - }, - { .init = brightness_init, .data = &brightness_driver_data, }, @@ -8993,7 +8921,6 @@ TPACPI_PARAM(light); TPACPI_PARAM(cmos); TPACPI_PARAM(led); TPACPI_PARAM(beep); -TPACPI_PARAM(ecdump); TPACPI_PARAM(brightness); TPACPI_PARAM(volume); TPACPI_PARAM(fan); diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index 37aa14798551..7d67a45bb2b0 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -4,6 +4,7 @@ * * Copyright (C) 2002-2004 John Belmonte * Copyright (C) 2008 Philip Langdale + * Copyright (C) 2010 Pierre Ducroquet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,6 +48,7 @@ #include <linux/platform_device.h> #include <linux/rfkill.h> #include <linux/input.h> +#include <linux/leds.h> #include <linux/slab.h> #include <asm/uaccess.h> @@ -129,6 +131,8 @@ enum {KE_KEY, KE_END}; static struct key_entry toshiba_acpi_keymap[] = { {KE_KEY, 0x101, KEY_MUTE}, + {KE_KEY, 0x102, KEY_ZOOMOUT}, + {KE_KEY, 0x103, KEY_ZOOMIN}, {KE_KEY, 0x13b, KEY_COFFEE}, {KE_KEY, 0x13c, KEY_BATTERY}, {KE_KEY, 0x13d, KEY_SLEEP}, @@ -285,6 +289,7 @@ struct toshiba_acpi_dev { struct platform_device *p_dev; struct rfkill *bt_rfk; struct input_dev *hotkey_dev; + int illumination_installed; acpi_handle handle; const char *bt_name; @@ -292,6 +297,110 @@ struct toshiba_acpi_dev { struct mutex mutex; }; +/* Illumination support */ +static int toshiba_illumination_available(void) +{ + u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + in[0] = 0xf100; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "Illumination device not available\n"); + return 0; + } + in[0] = 0xf400; + status = hci_raw(in, out); + return 1; +} + +static void toshiba_illumination_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + + /* First request : initialize communication. */ + in[0] = 0xf100; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "Illumination device not available\n"); + return; + } + + if (brightness) { + /* Switch the illumination on */ + in[0] = 0xf400; + in[1] = 0x14e; + in[2] = 1; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "ACPI call for illumination failed.\n"); + return; + } + } else { + /* Switch the illumination off */ + in[0] = 0xf400; + in[1] = 0x14e; + in[2] = 0; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "ACPI call for illumination failed.\n"); + return; + } + } + + /* Last request : close communication. */ + in[0] = 0xf200; + in[1] = 0; + in[2] = 0; + hci_raw(in, out); +} + +static enum led_brightness toshiba_illumination_get(struct led_classdev *cdev) +{ + u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 }; + u32 out[HCI_WORDS]; + acpi_status status; + enum led_brightness result; + + /* First request : initialize communication. */ + in[0] = 0xf100; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "Illumination device not available\n"); + return LED_OFF; + } + + /* Check the illumination */ + in[0] = 0xf300; + in[1] = 0x14e; + status = hci_raw(in, out); + if (ACPI_FAILURE(status)) { + printk(MY_INFO "ACPI call for illumination failed.\n"); + return LED_OFF; + } + + result = out[2] ? LED_FULL : LED_OFF; + + /* Last request : close communication. */ + in[0] = 0xf200; + in[1] = 0; + in[2] = 0; + hci_raw(in, out); + + return result; +} + +static struct led_classdev toshiba_led = { + .name = "toshiba::illumination", + .max_brightness = 1, + .brightness_set = toshiba_illumination_set, + .brightness_get = toshiba_illumination_get, +}; + static struct toshiba_acpi_dev toshiba_acpi = { .bt_name = "Toshiba Bluetooth", }; @@ -720,25 +829,22 @@ static const struct file_operations version_proc_fops = { #define PROC_TOSHIBA "toshiba" -static acpi_status __init add_device(void) +static void __init create_toshiba_proc_entries(void) { proc_create("lcd", S_IRUGO | S_IWUSR, toshiba_proc_dir, &lcd_proc_fops); proc_create("video", S_IRUGO | S_IWUSR, toshiba_proc_dir, &video_proc_fops); proc_create("fan", S_IRUGO | S_IWUSR, toshiba_proc_dir, &fan_proc_fops); proc_create("keys", S_IRUGO | S_IWUSR, toshiba_proc_dir, &keys_proc_fops); proc_create("version", S_IRUGO, toshiba_proc_dir, &version_proc_fops); - - return AE_OK; } -static acpi_status remove_device(void) +static void remove_toshiba_proc_entries(void) { remove_proc_entry("lcd", toshiba_proc_dir); remove_proc_entry("video", toshiba_proc_dir); remove_proc_entry("fan", toshiba_proc_dir); remove_proc_entry("keys", toshiba_proc_dir); remove_proc_entry("version", toshiba_proc_dir); - return AE_OK; } static struct backlight_ops toshiba_backlight_data = { @@ -906,7 +1012,7 @@ static void toshiba_acpi_exit(void) if (toshiba_backlight_device) backlight_device_unregister(toshiba_backlight_device); - remove_device(); + remove_toshiba_proc_entries(); if (toshiba_proc_dir) remove_proc_entry(PROC_TOSHIBA, acpi_root_dir); @@ -914,6 +1020,9 @@ static void toshiba_acpi_exit(void) acpi_remove_notify_handler(toshiba_acpi.handle, ACPI_DEVICE_NOTIFY, toshiba_acpi_notify); + if (toshiba_acpi.illumination_installed) + led_classdev_unregister(&toshiba_led); + platform_device_unregister(toshiba_acpi.p_dev); return; @@ -921,7 +1030,6 @@ static void toshiba_acpi_exit(void) static int __init toshiba_acpi_init(void) { - acpi_status status = AE_OK; u32 hci_result; bool bt_present; int ret = 0; @@ -969,11 +1077,7 @@ static int __init toshiba_acpi_init(void) toshiba_acpi_exit(); return -ENODEV; } else { - status = add_device(); - if (ACPI_FAILURE(status)) { - toshiba_acpi_exit(); - return -ENODEV; - } + create_toshiba_proc_entries(); } props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; @@ -1013,6 +1117,13 @@ static int __init toshiba_acpi_init(void) } } + toshiba_acpi.illumination_installed = 0; + if (toshiba_illumination_available()) { + if (!led_classdev_register(&(toshiba_acpi.p_dev->dev), + &toshiba_led)) + toshiba_acpi.illumination_installed = 1; + } + return 0; } diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index e4eaa14ed987..b2978a04317f 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -518,8 +518,13 @@ static void wmi_notify_debug(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; + acpi_status status; - wmi_get_event_data(value, &response); + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + printk(KERN_INFO "wmi: bad event status 0x%x\n", status); + return; + } obj = (union acpi_object *)response.pointer; @@ -543,6 +548,7 @@ static void wmi_notify_debug(u32 value, void *context) default: printk("object type 0x%X\n", obj->type); } + kfree(obj); } /** @@ -804,7 +810,7 @@ static bool guid_already_parsed(const char *guid_string) /* * Parse the _WDG method for the GUID data blocks */ -static __init acpi_status parse_wdg(acpi_handle handle) +static acpi_status parse_wdg(acpi_handle handle) { struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; @@ -827,8 +833,10 @@ static __init acpi_status parse_wdg(acpi_handle handle) total = obj->buffer.length / sizeof(struct guid_block); gblock = kmemdup(obj->buffer.pointer, obj->buffer.length, GFP_KERNEL); - if (!gblock) - return AE_NO_MEMORY; + if (!gblock) { + status = AE_NO_MEMORY; + goto out_free_pointer; + } for (i = 0; i < total; i++) { /* @@ -848,8 +856,10 @@ static __init acpi_status parse_wdg(acpi_handle handle) wmi_dump_wdg(&gblock[i]); wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL); - if (!wblock) - return AE_NO_MEMORY; + if (!wblock) { + status = AE_NO_MEMORY; + goto out_free_gblock; + } wblock->gblock = gblock[i]; wblock->handle = handle; @@ -860,8 +870,10 @@ static __init acpi_status parse_wdg(acpi_handle handle) list_add_tail(&wblock->list, &wmi_blocks.list); } - kfree(out.pointer); +out_free_gblock: kfree(gblock); +out_free_pointer: + kfree(out.pointer); return status; } @@ -947,7 +959,7 @@ static int acpi_wmi_remove(struct acpi_device *device, int type) return 0; } -static int __init acpi_wmi_add(struct acpi_device *device) +static int acpi_wmi_add(struct acpi_device *device) { acpi_status status; int result = 0; |