Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bb28f3d5 authored by Pali Rohár's avatar Pali Rohár Committed by Darren Hart
Browse files

thinkpad_acpi: Add support for keyboard backlight



This patch adds support for controlling keyboard backlight via standard
linux led class interface (::kbd_backlight). It uses ACPI HKEY device with
MLCG and MLCS methods.

Signed-off-by: default avatarPali Rohár <pali.rohar@gmail.com>
Tested-by: default avatarFabio D'Urso <fabiodurso@hotmail.it>
Acked-by: default avatarHenrique de Moraes Holschuh <hmh@hmh.eng.br>
Signed-off-by: default avatarDarren Hart <dvhart@linux.intel.com>
parent 481fe5be
Loading
Loading
Loading
Loading
+206 −0
Original line number Original line Diff line number Diff line
@@ -303,6 +303,7 @@ static struct {
	u32 hotkey_mask:1;
	u32 hotkey_mask:1;
	u32 hotkey_wlsw:1;
	u32 hotkey_wlsw:1;
	u32 hotkey_tablet:1;
	u32 hotkey_tablet:1;
	u32 kbdlight:1;
	u32 light:1;
	u32 light:1;
	u32 light_status:1;
	u32 light_status:1;
	u32 bright_acpimode:1;
	u32 bright_acpimode:1;
@@ -4985,6 +4986,207 @@ static struct ibm_struct video_driver_data = {


#endif /* CONFIG_THINKPAD_ACPI_VIDEO */
#endif /* CONFIG_THINKPAD_ACPI_VIDEO */


/*************************************************************************
 * Keyboard backlight subdriver
 */

static int kbdlight_set_level(int level)
{
	if (!hkey_handle)
		return -ENXIO;

	if (!acpi_evalf(hkey_handle, NULL, "MLCS", "dd", level))
		return -EIO;

	return 0;
}

static int kbdlight_get_level(void)
{
	int status = 0;

	if (!hkey_handle)
		return -ENXIO;

	if (!acpi_evalf(hkey_handle, &status, "MLCG", "dd", 0))
		return -EIO;

	if (status < 0)
		return status;

	return status & 0x3;
}

static bool kbdlight_is_supported(void)
{
	int status = 0;

	if (!hkey_handle)
		return false;

	if (!acpi_has_method(hkey_handle, "MLCG")) {
		vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG is unavailable\n");
		return false;
	}

	if (!acpi_evalf(hkey_handle, &status, "MLCG", "qdd", 0)) {
		vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG failed\n");
		return false;
	}

	if (status < 0) {
		vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG err: %d\n", status);
		return false;
	}

	vdbg_printk(TPACPI_DBG_INIT, "kbdlight MLCG returned 0x%x\n", status);
	/*
	 * Guessed test for keyboard backlight:
	 *
	 * Machines with backlight keyboard return:
	 *   b010100000010000000XX - ThinkPad X1 Carbon 3rd
	 *   b110100010010000000XX - ThinkPad x230
	 *   b010100000010000000XX - ThinkPad x240
	 *   b010100000010000000XX - ThinkPad W541
	 * (XX is current backlight level)
	 *
	 * Machines without backlight keyboard return:
	 *   b10100001000000000000 - ThinkPad x230
	 *   b10110001000000000000 - ThinkPad E430
	 *   b00000000000000000000 - ThinkPad E450
	 *
	 * Candidate BITs for detection test (XOR):
	 *   b01000000001000000000
	 *              ^
	 */
	return status & BIT(9);
}

static void kbdlight_set_worker(struct work_struct *work)
{
	struct tpacpi_led_classdev *data =
			container_of(work, struct tpacpi_led_classdev, work);

	if (likely(tpacpi_lifecycle == TPACPI_LIFE_RUNNING))
		kbdlight_set_level(data->new_state);
}

static void kbdlight_sysfs_set(struct led_classdev *led_cdev,
			enum led_brightness brightness)
{
	struct tpacpi_led_classdev *data =
			container_of(led_cdev,
				     struct tpacpi_led_classdev,
				     led_classdev);
	data->new_state = brightness;
	queue_work(tpacpi_wq, &data->work);
}

static enum led_brightness kbdlight_sysfs_get(struct led_classdev *led_cdev)
{
	int level;

	level = kbdlight_get_level();
	if (level < 0)
		return 0;

	return level;
}

static struct tpacpi_led_classdev tpacpi_led_kbdlight = {
	.led_classdev = {
		.name		= "tpacpi::kbd_backlight",
		.max_brightness	= 2,
		.brightness_set	= &kbdlight_sysfs_set,
		.brightness_get	= &kbdlight_sysfs_get,
		.flags		= LED_CORE_SUSPENDRESUME,
	}
};

static int __init kbdlight_init(struct ibm_init_struct *iibm)
{
	int rc;

	vdbg_printk(TPACPI_DBG_INIT, "initializing kbdlight subdriver\n");

	TPACPI_ACPIHANDLE_INIT(hkey);
	INIT_WORK(&tpacpi_led_kbdlight.work, kbdlight_set_worker);

	if (!kbdlight_is_supported()) {
		tp_features.kbdlight = 0;
		vdbg_printk(TPACPI_DBG_INIT, "kbdlight is unsupported\n");
		return 1;
	}

	tp_features.kbdlight = 1;

	rc = led_classdev_register(&tpacpi_pdev->dev,
				   &tpacpi_led_kbdlight.led_classdev);
	if (rc < 0) {
		tp_features.kbdlight = 0;
		return rc;
	}

	return 0;
}

static void kbdlight_exit(void)
{
	if (tp_features.kbdlight)
		led_classdev_unregister(&tpacpi_led_kbdlight.led_classdev);
	flush_workqueue(tpacpi_wq);
}

static int kbdlight_read(struct seq_file *m)
{
	int level;

	if (!tp_features.kbdlight) {
		seq_printf(m, "status:\t\tnot supported\n");
	} else {
		level = kbdlight_get_level();
		if (level < 0)
			seq_printf(m, "status:\t\terror %d\n", level);
		else
			seq_printf(m, "status:\t\t%d\n", level);
		seq_printf(m, "commands:\t0, 1, 2\n");
	}

	return 0;
}

static int kbdlight_write(char *buf)
{
	char *cmd;
	int level = -1;

	if (!tp_features.kbdlight)
		return -ENODEV;

	while ((cmd = next_cmd(&buf))) {
		if (strlencmp(cmd, "0") == 0)
			level = 0;
		else if (strlencmp(cmd, "1") == 0)
			level = 1;
		else if (strlencmp(cmd, "2") == 0)
			level = 2;
		else
			return -EINVAL;
	}

	if (level == -1)
		return -EINVAL;

	return kbdlight_set_level(level);
}

static struct ibm_struct kbdlight_driver_data = {
	.name = "kbdlight",
	.read = kbdlight_read,
	.write = kbdlight_write,
	.exit = kbdlight_exit,
};

/*************************************************************************
/*************************************************************************
 * Light (thinklight) subdriver
 * Light (thinklight) subdriver
 */
 */
@@ -9206,6 +9408,10 @@ static struct ibm_init_struct ibms_init[] __initdata = {
		.data = &video_driver_data,
		.data = &video_driver_data,
	},
	},
#endif
#endif
	{
		.init = kbdlight_init,
		.data = &kbdlight_driver_data,
	},
	{
	{
		.init = light_init,
		.init = light_init,
		.data = &light_driver_data,
		.data = &light_driver_data,