diff options
Diffstat (limited to 'drivers/platform/x86')
25 files changed, 4189 insertions, 530 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 3e1b8a28871..79baa6368f7 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 8770bfe7143..4744c7744ff 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 1ea6c434d33..2badee2fdee 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 7b2384d674d..60f9cfcac93 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 efe8f638890..b756e07d41b 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 92fd30c9379..e058c2ba2a1 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 3bf399fe2bb..341cbfef93e 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 71ff1545a93..d071ce05632 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> -#define COMPAL_DRIVER_VERSION "0.2.6" -#define COMPAL_LCD_LEVEL_MAX 8 +/* ======= */ +/* Defines */ +/* ======= */ +#define DRIVER_NAME "compal-laptop" +#define DRIVER_VERSION	"0.2.7" -#define COMPAL_EC_COMMAND_WIRELESS 0xBB -#define COMPAL_EC_COMMAND_LCD_LEVEL 0xB9 +#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 KILLSWITCH_MASK 0x10 -#define WLAN_MASK	0x01 -#define BT_MASK 	0x02 +#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) -static struct rfkill *wifi_rfkill; -static struct rfkill *bt_rfkill; -static struct platform_device *compal_device; +#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]; +}; + + +/* =============== */ +/* General globals */ +/* =============== */  static int force;  module_param(force, bool, 0);  MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); -/* Hardware access */ +/* 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 +}; + + + + +/* ========================= */ +/* 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 int set_lcd_level(int level) +static s16 ec_read_s16(u8 addr)  { -	if (level < 0 || level >= COMPAL_LCD_LEVEL_MAX) +	return (s16) ec_read_u16(addr); +} + +static void ec_read_sequence(u8 addr, u8 *buf, int len) +{ +	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 void set_backlight_state(bool on) +{ +	u8 data = on ? BACKLIGHT_STATE_ON_DATA : BACKLIGHT_STATE_OFF_DATA; +	ec_transaction(BACKLIGHT_STATE_ADDR, &data, 1, NULL, 0, 0);  } -static int get_lcd_level(void) + +/* Fan control access */ +static void pwm_enable_control(void)  { -	u8 result; +	unsigned char writeData = PWM_ENABLE_DATA; +	ec_transaction(PWM_ENABLE_ADDR, &writeData, 1, NULL, 0, 0); +} -	ec_read(COMPAL_EC_COMMAND_LCD_LEVEL, &result); +static void pwm_disable_control(void) +{ +	unsigned char writeData = PWM_DISABLE_DATA; +	ec_transaction(PWM_DISABLE_ADDR, &writeData, 1, NULL, 0, 0); +} -	return (int) 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; + +	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; +} + +static ssize_t fan_show(struct device *dev, struct device_attribute *attr, +		char *buf) +{ +	return sprintf(buf, "%d\n", get_fan_rpm());  } -/* Backlight device stuff */ -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 661e3ac4d5b..b41ed5cab3e 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 66f53c3c35e..08fb70f6d9b 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 0306174ba87..6b8e06206c4 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 e325aeb37d2..f44cd2620ff 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 51c07a05a7b..f1551637498 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 00000000000..afe82e50dfe --- /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 2f795ce2b93..eacd5da7dd2 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 00000000000..5cdcff65391 --- /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 00000000000..73f8e6d7266 --- /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 bb2f1fba637..943f9084dcb 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 @@ -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((sub << 12) | cmd | (inlen << 18)); +	ipc_command((inlen << 16) | (sub << 12) | cmd);  	err = busy_loop();  	for (i = 0; i < outlen; i++) @@ -803,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); @@ -817,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 afd762b58ad..7e9bb6df9d3 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 d1736009636..42a5469a245 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 2fb9a32926f..ec01c3d8fc5 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 1387c5f9c24..e3154ff7a39 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 4bdb13796e2..5d6119bed00 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 37aa1479855..7d67a45bb2b 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 e4eaa14ed98..b2978a04317 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;  |