From 467df9b11cb55456f0365f40dd11c9e666623bf3 Mon Sep 17 00:00:00 2001 From: Redecorating <69827514+Redecorating@users.noreply.github.com> Date: Sat, 16 Oct 2021 15:14:56 +1100 Subject: [PATCH] ib-tb: support magic keyboard backlight MacBookPro16,1/2/3/4 have the magic keyboard, with the keyboard backlight controlled via the Touch Bar Backlight USB device. We check that the internal keyboard's USB product ID matches that of models with the magic keyboard, and then we create a sysfs led class dev that controls the backlight. --- apple-ib-tb.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/apple-ib-tb.c b/apple-ib-tb.c index a1c4693..e3eecdf 100644 --- a/apple-ib-tb.c +++ b/apple-ib-tb.c @@ -76,6 +76,8 @@ #define APPLETB_MAX_DIM_TIME 30 +#define APPLE_MAGIC_KBD_BL_MAX 60 + static int appletb_tb_def_idle_timeout = 5 * 60; module_param_named(idle_timeout, appletb_tb_def_idle_timeout, int, 0444); MODULE_PARM_DESC(idle_timeout, "Default touch bar idle timeout:\n" @@ -138,6 +140,12 @@ static const struct attribute_group appletb_attr_group = { .attrs = appletb_attrs, }; +struct apple_magic_backlight { + struct led_classdev cdev; + struct usb_device *dev; + bool powered; +}; + struct appletb_device { bool active; struct device *log_dev; @@ -177,6 +185,8 @@ struct appletb_device { int fn_mode; bool is_t2; + + struct apple_magic_backlight kbd_backlight; }; struct appletb_key_translation { @@ -199,6 +209,30 @@ static const struct appletb_key_translation appletb_fn_codes[] = { { KEY_F12, KEY_VOLUMEUP }, }; +struct apple_magic_keyboard_backlight_brightness_report { + u8 report_id; /* 0x01 */ + u8 mode; /* If 0x00, brightness can turn off backlight */ + u8 brightness; + u8 override_1; /* If these are non-zero, backlight is overridden to max brightness */ + u8 override_2; + u8 max; /* Lower is brighter, only takes effect when turning backlight + * on from off, can be unreliable + */ + u8 rate; + u8 magic_1; /* If these are non-zero, we are ignored. */ + u8 magic_2; +}; + +struct apple_magic_keyboard_backlight_power_report { + u8 report_id; /* 0x03 */ + u8 power; + u8 max; /* Lower is brighter, only takes effect when turning backlight + * on from off, can be unreliable + */ + u8 rate; + u8 magic_1; /* If these are non-zero, we are ignored. */ + u8 magic_2; +}; static struct appletb_device *appletb_dev; static int appletb_send_usb_ctrl(struct appletb_iface_info *iface_info, @@ -319,6 +353,132 @@ static int appletb_send_hid_report(struct appletb_iface_info *iface_info, return rc; } +static int apple_magic_keyboard_backlight_power_set(struct apple_magic_backlight *backlight, + char power, char rate) +{ + int tries = 0; + int rc; + struct apple_magic_keyboard_backlight_power_report *rep; + + rep = kmalloc(sizeof(*rep), GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + backlight->powered = power ? true : false; + + rep->report_id = 0x03; + rep->power = power; + rep->max = 0x5e; /* Windows uses 0x5e when turning on, and 0xf4 when + * turning off. When it's off it doesn't matter, so + * use 0x5e + */ + rep->rate = rate; + + do { + /* + * FIXME: use appletb_send_hid_report, don't hard code all of this + * Need to get apple_tb_send_hid_report to use wIndex=0x01 + */ + rc = usb_control_msg(backlight->dev, + usb_sndctrlpipe(backlight->dev, 0), + HID_REQ_SET_REPORT, USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0303, 0x01, rep, sizeof(*rep), + 2000); + if (rc != -EPIPE) + break; + + usleep_range(1000 << tries, 3000 << tries); + } while (++tries < 5); + + kfree(rep); + return (rc > 0) ? 0 : rc; +} + +static int apple_magic_keyboard_backlight_brightness_set(struct apple_magic_backlight *backlight, + char brightness, char rate) +{ + int tries = 0; + int rc; + struct apple_magic_keyboard_backlight_brightness_report *rep; + + rep = kmalloc(sizeof(*rep), GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + rep->report_id = 0x01; + rep->mode = brightness; + rep->brightness = brightness; + rep->max = 0x5e; + rep->rate = rate; + + do { + /* + * FIXME: use appletb_send_hid_report, don't hard code all of this + * Need to get apple_tb_send_hid_report to use wIndex=0x01 + */ + rc = usb_control_msg(backlight->dev, + usb_sndctrlpipe(backlight->dev, 0), + HID_REQ_SET_REPORT, USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0301, 0x01, rep, sizeof(*rep), + 2000); + if (rc != -EPIPE) + break; + + usleep_range(1000 << tries, 3000 << tries); + } while (++tries < 5); + + kfree(rep); + return (rc > 0) ? 0 : rc; +} + +static int apple_magic_keyboard_backlight_set(struct apple_magic_backlight *backlight, + char brightness, char rate) +{ + int rc; + + if (!brightness) + return apple_magic_keyboard_backlight_power_set(backlight, 0, rate); + + rc = apple_magic_keyboard_backlight_brightness_set(backlight, brightness, rate); + if (rc) + return rc; + + if (!backlight->powered && brightness) + rc = apple_magic_keyboard_backlight_power_set(backlight, 1, rate); + + return (rc > 0) ? 0 : rc; +} + +static int apple_magic_keyboard_backlight_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct apple_magic_backlight *backlight = container_of(led_cdev, + struct apple_magic_backlight, cdev); + + return apple_magic_keyboard_backlight_set(backlight, brightness, 1); +} + +static int apple_magic_keyboard_backlight_init(struct appletb_device *tb_dev) +{ + int ret; + + tb_dev->kbd_backlight.dev = interface_to_usbdev(tb_dev->disp_iface.usb_iface); + tb_dev->kbd_backlight.cdev.name = "apple::kbd_backlight"; + tb_dev->kbd_backlight.cdev.max_brightness = APPLE_MAGIC_KBD_BL_MAX; + tb_dev->kbd_backlight.cdev.brightness_set_blocking = apple_magic_keyboard_backlight_led_set; + + ret = apple_magic_keyboard_backlight_set(&tb_dev->kbd_backlight, 0, 0); + if (ret) { + dev_err(tb_dev->log_dev, + "Failed to initialise Magic Keyboard Backlight (%d)\n", ret); + return ret; + } + + return devm_led_classdev_register(tb_dev->log_dev, &tb_dev->kbd_backlight.cdev); +} + static int appletb_set_tb_disp(struct appletb_device *tb_dev, unsigned char disp) { @@ -884,6 +1044,13 @@ static int appletb_inp_connect(struct input_handler *handler, if (id->driver_info == APPLETB_DEVID_KEYBOARD) { handle = &tb_dev->kbd_handle; handle->name = "tbkbd"; + switch (dev->id.product) { + case 0x0340u: /* MacBookPro16,1/4 */ + case 0x027eu: /* MacBookPro16,2 */ + case 0x027fu: /* MacBookPro16,3 */ + apple_magic_keyboard_backlight_init(tb_dev); + break; + } } else if (id->driver_info == APPLETB_DEVID_TOUCHPAD) { handle = &tb_dev->tpd_handle; handle->name = "tbtpad";