diff options
Diffstat (limited to 'drivers/platform/x86/thinkpad_acpi.c')
| -rw-r--r-- | drivers/platform/x86/thinkpad_acpi.c | 260 | 
1 files changed, 215 insertions, 45 deletions
| diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index ad460417f901..c4895e9bc714 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -50,6 +50,7 @@  #include <linux/kthread.h>  #include <linux/leds.h>  #include <linux/list.h> +#include <linux/lockdep.h>  #include <linux/module.h>  #include <linux/mutex.h>  #include <linux/nvram.h> @@ -511,10 +512,10 @@ struct tpacpi_quirk {   * Iterates over a quirks list until one is found that matches the   * ThinkPad's vendor, BIOS and EC model.   * - * Returns 0 if nothing matches, otherwise returns the quirks field of + * Returns: %0 if nothing matches, otherwise returns the quirks field of   * the matching &struct tpacpi_quirk entry.   * - * The match criteria is: vendor, ec and bios much match. + * The match criteria is: vendor, ec and bios must match.   */  static unsigned long __init tpacpi_check_quirks(  			const struct tpacpi_quirk *qlist, @@ -907,16 +908,9 @@ static ssize_t dispatch_proc_write(struct file *file,  	if (count > PAGE_SIZE - 1)  		return -EINVAL; -	kernbuf = kmalloc(count + 1, GFP_KERNEL); -	if (!kernbuf) -		return -ENOMEM; - -	if (copy_from_user(kernbuf, userbuf, count)) { -		kfree(kernbuf); -		return -EFAULT; -	} - -	kernbuf[count] = 0; +	kernbuf = memdup_user_nul(userbuf, count); +	if (IS_ERR(kernbuf)) +		return PTR_ERR(kernbuf);  	ret = ibm->write(kernbuf);  	if (ret == 0)  		ret = count; @@ -2066,11 +2060,11 @@ static int hotkey_get_tablet_mode(int *status)   * hotkey_acpi_mask accordingly.  Also resets any bits   * from hotkey_user_mask that are unavailable to be   * delivered (shadow requirement of the userspace ABI). - * - * Call with hotkey_mutex held   */  static int hotkey_mask_get(void)  { +	lockdep_assert_held(&hotkey_mutex); +  	if (tp_features.hotkey_mask) {  		u32 m = 0; @@ -2106,8 +2100,6 @@ static void hotkey_mask_warn_incomplete_mask(void)   * Also calls hotkey_mask_get to update hotkey_acpi_mask.   *   * NOTE: does not set bits in hotkey_user_mask, but may reset them. - * - * Call with hotkey_mutex held   */  static int hotkey_mask_set(u32 mask)  { @@ -2116,6 +2108,8 @@ static int hotkey_mask_set(u32 mask)  	const u32 fwmask = mask & ~hotkey_source_mask; +	lockdep_assert_held(&hotkey_mutex); +  	if (tp_features.hotkey_mask) {  		for (i = 0; i < 32; i++) {  			if (!acpi_evalf(hkey_handle, @@ -2147,13 +2141,13 @@ static int hotkey_mask_set(u32 mask)  /*   * Sets hotkey_user_mask and tries to set the firmware mask - * - * Call with hotkey_mutex held   */  static int hotkey_user_mask_set(const u32 mask)  {  	int rc; +	lockdep_assert_held(&hotkey_mutex); +  	/* Give people a chance to notice they are doing something that  	 * is bound to go boom on their users sooner or later */  	if (!tp_warned.hotkey_mask_ff && @@ -2514,21 +2508,23 @@ exit:  	return 0;  } -/* call with hotkey_mutex held */  static void hotkey_poll_stop_sync(void)  { +	lockdep_assert_held(&hotkey_mutex); +  	if (tpacpi_hotkey_task) {  		kthread_stop(tpacpi_hotkey_task);  		tpacpi_hotkey_task = NULL;  	}  } -/* call with hotkey_mutex held */  static void hotkey_poll_setup(const bool may_warn)  {  	const u32 poll_driver_mask = hotkey_driver_mask & hotkey_source_mask;  	const u32 poll_user_mask = hotkey_user_mask & hotkey_source_mask; +	lockdep_assert_held(&hotkey_mutex); +  	if (hotkey_poll_freq > 0 &&  	    (poll_driver_mask ||  	     (poll_user_mask && tpacpi_inputdev->users > 0))) { @@ -2557,9 +2553,10 @@ static void hotkey_poll_setup_safe(const bool may_warn)  	mutex_unlock(&hotkey_mutex);  } -/* call with hotkey_mutex held */  static void hotkey_poll_set_freq(unsigned int freq)  { +	lockdep_assert_held(&hotkey_mutex); +  	if (!freq)  		hotkey_poll_stop_sync(); @@ -3473,7 +3470,9 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  	if (tp_features.hotkey_mask) {  		/* hotkey_source_mask *must* be zero for  		 * the first hotkey_mask_get to return hotkey_orig_mask */ +		mutex_lock(&hotkey_mutex);  		res = hotkey_mask_get(); +		mutex_unlock(&hotkey_mutex);  		if (res)  			return res; @@ -3572,9 +3571,11 @@ static int __init hotkey_init(struct ibm_init_struct *iibm)  		hotkey_exit();  		return res;  	} +	mutex_lock(&hotkey_mutex);  	res = hotkey_mask_set(((hotkey_all_mask & ~hotkey_reserved_mask)  			       | hotkey_driver_mask)  			      & ~hotkey_source_mask); +	mutex_unlock(&hotkey_mutex);  	if (res < 0 && res != -ENXIO) {  		hotkey_exit();  		return res; @@ -4115,9 +4116,11 @@ static void hotkey_resume(void)  {  	tpacpi_disable_brightness_delay(); +	mutex_lock(&hotkey_mutex);  	if (hotkey_status_set(true) < 0 ||  	    hotkey_mask_set(hotkey_acpi_mask) < 0)  		pr_err("error while attempting to reset the event firmware interface\n"); +	mutex_unlock(&hotkey_mutex);  	tpacpi_send_radiosw_update();  	tpacpi_input_send_tabletsw(); @@ -6528,12 +6531,13 @@ static unsigned int brightness_enable = 2; /* 2 = auto, 0 = no, 1 = yes */  static struct mutex brightness_mutex; -/* NVRAM brightness access, - * call with brightness_mutex held! */ +/* NVRAM brightness access */  static unsigned int tpacpi_brightness_nvram_get(void)  {  	u8 lnvram; +	lockdep_assert_held(&brightness_mutex); +  	lnvram = (nvram_read_byte(TP_NVRAM_ADDR_BRIGHTNESS)  		  & TP_NVRAM_MASK_LEVEL_BRIGHTNESS)  		  >> TP_NVRAM_POS_LEVEL_BRIGHTNESS; @@ -6581,11 +6585,12 @@ unlock:  } -/* call with brightness_mutex held! */  static int tpacpi_brightness_get_raw(int *status)  {  	u8 lec = 0; +	lockdep_assert_held(&brightness_mutex); +  	switch (brightness_mode) {  	case TPACPI_BRGHT_MODE_UCMS_STEP:  		*status = tpacpi_brightness_nvram_get(); @@ -6601,12 +6606,13 @@ static int tpacpi_brightness_get_raw(int *status)  	}  } -/* call with brightness_mutex held! */  /* do NOT call with illegal backlight level value */  static int tpacpi_brightness_set_ec(unsigned int value)  {  	u8 lec = 0; +	lockdep_assert_held(&brightness_mutex); +  	if (unlikely(!acpi_ec_read(TP_EC_BACKLIGHT, &lec)))  		return -EIO; @@ -6618,12 +6624,13 @@ static int tpacpi_brightness_set_ec(unsigned int value)  	return 0;  } -/* call with brightness_mutex held! */  static int tpacpi_brightness_set_ucmsstep(unsigned int value)  {  	int cmos_cmd, inc;  	unsigned int current_value, i; +	lockdep_assert_held(&brightness_mutex); +  	current_value = tpacpi_brightness_nvram_get();  	if (value == current_value) @@ -7941,8 +7948,19 @@ static struct ibm_struct volume_driver_data = {   * 	TPACPI_FAN_WR_TPEC is also available and should be used to   * 	command the fan.  The X31/X40/X41 seems to have 8 fan levels,   * 	but the ACPI tables just mention level 7. + * + * TPACPI_FAN_RD_TPEC_NS: + *	This mode is used for a few ThinkPads (L13 Yoga Gen2, X13 Yoga Gen2 etc.) + *	that are using non-standard EC locations for reporting fan speeds. + *	Currently these platforms only provide fan rpm reporting. + *   */ +#define FAN_RPM_CAL_CONST 491520	/* FAN RPM calculation offset for some non-standard ECFW */ + +#define FAN_NS_CTRL_STATUS	BIT(2)		/* Bit which determines control is enabled or not */ +#define FAN_NS_CTRL		BIT(4)		/* Bit which determines control is by host or EC */ +  enum {					/* Fan control constants */  	fan_status_offset = 0x2f,	/* EC register 0x2f */  	fan_rpm_offset = 0x84,		/* EC register 0x84: LSB, 0x85 MSB (RPM) @@ -7950,6 +7968,11 @@ enum {					/* Fan control constants */  	fan_select_offset = 0x31,	/* EC register 0x31 (Firmware 7M)  					   bit 0 selects which fan is active */ +	fan_status_offset_ns = 0x93,	/* Special status/control offset for non-standard EC Fan1 */ +	fan2_status_offset_ns = 0x96,	/* Special status/control offset for non-standard EC Fan2 */ +	fan_rpm_status_ns = 0x95,	/* Special offset for Fan1 RPM status for non-standard EC */ +	fan2_rpm_status_ns = 0x98,	/* Special offset for Fan2 RPM status for non-standard EC */ +  	TP_EC_FAN_FULLSPEED = 0x40,	/* EC fan mode: full speed */  	TP_EC_FAN_AUTO	    = 0x80,	/* EC fan mode: auto fan control */ @@ -7960,6 +7983,7 @@ enum fan_status_access_mode {  	TPACPI_FAN_NONE = 0,		/* No fan status or control */  	TPACPI_FAN_RD_ACPI_GFAN,	/* Use ACPI GFAN */  	TPACPI_FAN_RD_TPEC,		/* Use ACPI EC regs 0x2f, 0x84-0x85 */ +	TPACPI_FAN_RD_TPEC_NS,		/* Use non-standard ACPI EC regs (eg: L13 Yoga gen2 etc.) */  };  enum fan_control_access_mode { @@ -7987,6 +8011,8 @@ static u8 fan_control_desired_level;  static u8 fan_control_resume_level;  static int fan_watchdog_maxinterval; +static bool fan_with_ns_addr; +  static struct mutex fan_mutex;  static void fan_watchdog_fire(struct work_struct *ignored); @@ -8072,11 +8098,10 @@ static bool fan_select_fan2(void)  	return true;  } -/* - * Call with fan_mutex held - */  static void fan_update_desired_level(u8 status)  { +	lockdep_assert_held(&fan_mutex); +  	if ((status &  	     (TP_EC_FAN_AUTO | TP_EC_FAN_FULLSPEED)) == 0) {  		if (status > 7) @@ -8117,6 +8142,15 @@ static int fan_get_status(u8 *status)  		}  		break; +	case TPACPI_FAN_RD_TPEC_NS: +		/* Default mode is AUTO which means controlled by EC */ +		if (!acpi_ec_read(fan_status_offset_ns, &s)) +			return -EIO; + +		if (status) +			*status = s; + +		break;  	default:  		return -ENXIO; @@ -8133,7 +8167,8 @@ static int fan_get_status_safe(u8 *status)  	if (mutex_lock_killable(&fan_mutex))  		return -ERESTARTSYS;  	rc = fan_get_status(&s); -	if (!rc) +	/* NS EC doesn't have register with level settings */ +	if (!rc && !fan_with_ns_addr)  		fan_update_desired_level(s);  	mutex_unlock(&fan_mutex); @@ -8160,7 +8195,13 @@ static int fan_get_speed(unsigned int *speed)  		if (likely(speed))  			*speed = (hi << 8) | lo; +		break; +	case TPACPI_FAN_RD_TPEC_NS: +		if (!acpi_ec_read(fan_rpm_status_ns, &lo)) +			return -EIO; +		if (speed) +			*speed = lo ? FAN_RPM_CAL_CONST / lo : 0;  		break;  	default: @@ -8172,7 +8213,7 @@ static int fan_get_speed(unsigned int *speed)  static int fan2_get_speed(unsigned int *speed)  { -	u8 hi, lo; +	u8 hi, lo, status;  	bool rc;  	switch (fan_status_access_mode) { @@ -8188,7 +8229,21 @@ static int fan2_get_speed(unsigned int *speed)  		if (likely(speed))  			*speed = (hi << 8) | lo; +		break; +	case TPACPI_FAN_RD_TPEC_NS: +		rc = !acpi_ec_read(fan2_status_offset_ns, &status); +		if (rc) +			return -EIO; +		if (!(status & FAN_NS_CTRL_STATUS)) { +			pr_info("secondary fan control not supported\n"); +			return -EIO; +		} +		rc = !acpi_ec_read(fan2_rpm_status_ns, &lo); +		if (rc) +			return -EIO; +		if (speed) +			*speed = lo ? FAN_RPM_CAL_CONST / lo : 0;  		break;  	default: @@ -8691,6 +8746,7 @@ static const struct attribute_group fan_driver_attr_group = {  #define TPACPI_FAN_2FAN		0x0002		/* EC 0x31 bit 0 selects fan2 */  #define TPACPI_FAN_2CTL		0x0004		/* selects fan2 control */  #define TPACPI_FAN_NOFAN	0x0008		/* no fan available */ +#define TPACPI_FAN_NS		0x0010		/* For EC with non-Standard register addresses */  static const struct tpacpi_quirk fan_quirk_table[] __initconst = {  	TPACPI_QEC_IBM('1', 'Y', TPACPI_FAN_Q1), @@ -8709,6 +8765,8 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = {  	TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL),	/* P1 / X1 Extreme (2nd gen) */  	TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL),	/* P15 (1st gen) / P15v (1st gen) */  	TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL),  /* T15g (2nd gen) */ +	TPACPI_Q_LNV3('R', '1', 'F', TPACPI_FAN_NS),	/* L13 Yoga Gen 2 */ +	TPACPI_Q_LNV3('N', '2', 'U', TPACPI_FAN_NS),	/* X13 Yoga Gen 2*/  	TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN),	/* X1 Tablet (2nd gen) */  }; @@ -8743,18 +8801,27 @@ static int __init fan_init(struct ibm_init_struct *iibm)  		return -ENODEV;  	} +	if (quirks & TPACPI_FAN_NS) { +		pr_info("ECFW with non-standard fan reg control found\n"); +		fan_with_ns_addr = 1; +		/* Fan ctrl support from host is undefined for now */ +		tp_features.fan_ctrl_status_undef = 1; +	} +  	if (gfan_handle) {  		/* 570, 600e/x, 770e, 770x */  		fan_status_access_mode = TPACPI_FAN_RD_ACPI_GFAN;  	} else {  		/* all other ThinkPads: note that even old-style  		 * ThinkPad ECs supports the fan control register */ -		if (likely(acpi_ec_read(fan_status_offset, -					&fan_control_initial_status))) { +		if (fan_with_ns_addr || +		    likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) {  			int res;  			unsigned int speed; -			fan_status_access_mode = TPACPI_FAN_RD_TPEC; +			fan_status_access_mode = fan_with_ns_addr ? +				TPACPI_FAN_RD_TPEC_NS : TPACPI_FAN_RD_TPEC; +  			if (quirks & TPACPI_FAN_Q1)  				fan_quirk1_setup();  			/* Try and probe the 2nd fan */ @@ -8763,7 +8830,8 @@ static int __init fan_init(struct ibm_init_struct *iibm)  			if (res >= 0 && speed != FAN_NOT_PRESENT) {  				/* It responded - so let's assume it's there */  				tp_features.second_fan = 1; -				tp_features.second_fan_ctl = 1; +				/* fan control not currently available for ns ECFW */ +				tp_features.second_fan_ctl = !fan_with_ns_addr;  				pr_info("secondary fan control detected & enabled\n");  			} else {  				/* Fan not auto-detected */ @@ -8938,6 +9006,7 @@ static int fan_read(struct seq_file *m)  			       str_enabled_disabled(status), status);  		break; +	case TPACPI_FAN_RD_TPEC_NS:  	case TPACPI_FAN_RD_TPEC:  		/* all except 570, 600e/x, 770e, 770x */  		rc = fan_get_status_safe(&status); @@ -8952,13 +9021,22 @@ static int fan_read(struct seq_file *m)  		seq_printf(m, "speed:\t\t%d\n", speed); -		if (status & TP_EC_FAN_FULLSPEED) -			/* Disengaged mode takes precedence */ -			seq_printf(m, "level:\t\tdisengaged\n"); -		else if (status & TP_EC_FAN_AUTO) -			seq_printf(m, "level:\t\tauto\n"); -		else -			seq_printf(m, "level:\t\t%d\n", status); +		if (fan_status_access_mode == TPACPI_FAN_RD_TPEC_NS) { +			/* +			 * No full speed bit in NS EC +			 * EC Auto mode is set by default. +			 * No other levels settings available +			 */ +			seq_printf(m, "level:\t\t%s\n", status & FAN_NS_CTRL ? "unknown" : "auto"); +		} else { +			if (status & TP_EC_FAN_FULLSPEED) +				/* Disengaged mode takes precedence */ +				seq_printf(m, "level:\t\tdisengaged\n"); +			else if (status & TP_EC_FAN_AUTO) +				seq_printf(m, "level:\t\tauto\n"); +			else +				seq_printf(m, "level:\t\t%d\n", status); +		}  		break;  	case TPACPI_FAN_NONE: @@ -9297,7 +9375,7 @@ static struct tpacpi_battery_driver_data battery_info;  /* ACPI helpers/functions/probes */ -/** +/*   * This evaluates a ACPI method call specific to the battery   * ACPI extension. The specifics are that an error is marked   * in the 32rd bit of the response, so we just check that here. @@ -9810,6 +9888,7 @@ static const struct tpacpi_quirk battery_quirk_table[] __initconst = {  	 * Individual addressing is broken on models that expose the  	 * primary battery as BAT1.  	 */ +	TPACPI_Q_LNV('8', 'F', true),       /* Thinkpad X120e */  	TPACPI_Q_LNV('J', '7', true),       /* B5400 */  	TPACPI_Q_LNV('J', 'I', true),       /* Thinkpad 11e */  	TPACPI_Q_LNV3('R', '0', 'B', true), /* Thinkpad 11e gen 3 */ @@ -10781,6 +10860,89 @@ static struct ibm_struct dprc_driver_data = {  	.name = "dprc",  }; +/* + * Auxmac + * + * This auxiliary mac address is enabled in the bios through the + * MAC Address Pass-through feature. In most cases, there are three + * possibilities: Internal Mac, Second Mac, and disabled. + * + */ + +#define AUXMAC_LEN 12 +#define AUXMAC_START 9 +#define AUXMAC_STRLEN 22 +#define AUXMAC_BEGIN_MARKER 8 +#define AUXMAC_END_MARKER 21 + +static char auxmac[AUXMAC_LEN + 1]; + +static int auxmac_init(struct ibm_init_struct *iibm) +{ +	acpi_status status; +	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; +	union acpi_object *obj; + +	status = acpi_evaluate_object(NULL, "\\MACA", NULL, &buffer); + +	if (ACPI_FAILURE(status)) +		return -ENODEV; + +	obj = buffer.pointer; + +	if (obj->type != ACPI_TYPE_STRING || obj->string.length != AUXMAC_STRLEN) { +		pr_info("Invalid buffer for MAC address pass-through.\n"); +		goto auxmacinvalid; +	} + +	if (obj->string.pointer[AUXMAC_BEGIN_MARKER] != '#' || +	    obj->string.pointer[AUXMAC_END_MARKER] != '#') { +		pr_info("Invalid header for MAC address pass-through.\n"); +		goto auxmacinvalid; +	} + +	if (strncmp(obj->string.pointer + AUXMAC_START, "XXXXXXXXXXXX", AUXMAC_LEN) != 0) +		strscpy(auxmac, obj->string.pointer + AUXMAC_START, sizeof(auxmac)); +	else +		strscpy(auxmac, "disabled", sizeof(auxmac)); + +free: +	kfree(obj); +	return 0; + +auxmacinvalid: +	strscpy(auxmac, "unavailable", sizeof(auxmac)); +	goto free; +} + +static struct ibm_struct auxmac_data = { +	.name = "auxmac", +}; + +static ssize_t auxmac_show(struct device *dev, +			   struct device_attribute *attr, +			   char *buf) +{ +	return sysfs_emit(buf, "%s\n", auxmac); +} +static DEVICE_ATTR_RO(auxmac); + +static umode_t auxmac_attr_is_visible(struct kobject *kobj, +				      struct attribute *attr, int n) +{ +	return auxmac[0] == 0 ? 0 : attr->mode; +} + +static struct attribute *auxmac_attributes[] = { +	&dev_attr_auxmac.attr, +	NULL +}; + +static const struct attribute_group auxmac_attr_group = { +	.is_visible = auxmac_attr_is_visible, +	.attrs = auxmac_attributes, +}; +  /* --------------------------------------------------------------------- */  static struct attribute *tpacpi_driver_attributes[] = { @@ -10839,6 +11001,7 @@ static const struct attribute_group *tpacpi_groups[] = {  	&proxsensor_attr_group,  	&kbdlang_attr_group,  	&dprc_attr_group, +	&auxmac_attr_group,  	NULL,  }; @@ -11138,6 +11301,8 @@ invalid:  	return '\0';  } +#define EC_FW_STRING_LEN 18 +  static void find_new_ec_fwstr(const struct dmi_header *dm, void *private)  {  	char *ec_fw_string = (char *) private; @@ -11166,7 +11331,8 @@ static void find_new_ec_fwstr(const struct dmi_header *dm, void *private)  		return;  	/* fwstr is the first 8byte string  */ -	strncpy(ec_fw_string, dmi_data + 0x0F, 8); +	BUILD_BUG_ON(EC_FW_STRING_LEN <= 8); +	memcpy(ec_fw_string, dmi_data + 0x0F, 8);  }  /* returns 0 - probe ok, or < 0 - probe error. @@ -11176,7 +11342,7 @@ static int __must_check __init get_thinkpad_model_data(  						struct thinkpad_id_data *tp)  {  	const struct dmi_device *dev = NULL; -	char ec_fw_string[18] = {0}; +	char ec_fw_string[EC_FW_STRING_LEN] = {0};  	char const *s;  	char t; @@ -11410,6 +11576,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {  		.init = tpacpi_dprc_init,  		.data = &dprc_driver_data,  	}, +	{ +		.init = auxmac_init, +		.data = &auxmac_data, +	},  };  static int __init set_ibm_param(const char *val, const struct kernel_param *kp) |