1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Keyboard backlight LED driver for the Wilco Embedded Controller 4 * 5 * Copyright 2019 Google LLC 6 * 7 * Since the EC will never change the backlight level of its own accord, 8 * we don't need to implement a brightness_get() method. 9 */ 10 11 #include <linux/device.h> 12 #include <linux/kernel.h> 13 #include <linux/leds.h> 14 #include <linux/platform_data/wilco-ec.h> 15 #include <linux/slab.h> 16 17 #define WILCO_EC_COMMAND_KBBL 0x75 18 #define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */ 19 #define WILCO_KBBL_DEFAULT_BRIGHTNESS 0 20 21 struct wilco_keyboard_leds { 22 struct wilco_ec_device *ec; 23 struct led_classdev keyboard; 24 }; 25 26 enum wilco_kbbl_subcommand { 27 WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00, 28 WILCO_KBBL_SUBCMD_GET_STATE = 0x01, 29 WILCO_KBBL_SUBCMD_SET_STATE = 0x02, 30 }; 31 32 /** 33 * struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control. 34 * @command: Always WILCO_EC_COMMAND_KBBL. 35 * @status: Set by EC to 0 on success, 0xFF on failure. 36 * @subcmd: One of enum wilco_kbbl_subcommand. 37 * @reserved3: Should be 0. 38 * @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM. 39 * @reserved5to8: Should be 0. 40 * @percent: Brightness in 0-100. Only meaningful in PWM mode. 41 * @reserved10to15: Should be 0. 42 */ 43 struct wilco_keyboard_leds_msg { 44 u8 command; 45 u8 status; 46 u8 subcmd; 47 u8 reserved3; 48 u8 mode; 49 u8 reserved5to8[4]; 50 u8 percent; 51 u8 reserved10to15[6]; 52 } __packed; 53 54 /* Send a request, get a response, and check that the response is good. */ 55 static int send_kbbl_msg(struct wilco_ec_device *ec, 56 struct wilco_keyboard_leds_msg *request, 57 struct wilco_keyboard_leds_msg *response) 58 { 59 struct wilco_ec_message msg; 60 int ret; 61 62 memset(&msg, 0, sizeof(msg)); 63 msg.type = WILCO_EC_MSG_LEGACY; 64 msg.request_data = request; 65 msg.request_size = sizeof(*request); 66 msg.response_data = response; 67 msg.response_size = sizeof(*response); 68 69 ret = wilco_ec_mailbox(ec, &msg); 70 if (ret < 0) { 71 dev_err(ec->dev, 72 "Failed sending keyboard LEDs command: %d", ret); 73 return ret; 74 } 75 76 if (response->status) { 77 dev_err(ec->dev, 78 "EC reported failure sending keyboard LEDs command: %d", 79 response->status); 80 return -EIO; 81 } 82 83 return 0; 84 } 85 86 static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness) 87 { 88 struct wilco_keyboard_leds_msg request; 89 struct wilco_keyboard_leds_msg response; 90 91 memset(&request, 0, sizeof(request)); 92 request.command = WILCO_EC_COMMAND_KBBL; 93 request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE; 94 request.mode = WILCO_KBBL_MODE_FLAG_PWM; 95 request.percent = brightness; 96 97 return send_kbbl_msg(ec, &request, &response); 98 } 99 100 static int kbbl_exist(struct wilco_ec_device *ec, bool *exists) 101 { 102 struct wilco_keyboard_leds_msg request; 103 struct wilco_keyboard_leds_msg response; 104 int ret; 105 106 memset(&request, 0, sizeof(request)); 107 request.command = WILCO_EC_COMMAND_KBBL; 108 request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES; 109 110 ret = send_kbbl_msg(ec, &request, &response); 111 if (ret < 0) 112 return ret; 113 114 *exists = response.status != 0xFF; 115 116 return 0; 117 } 118 119 /** 120 * kbbl_init() - Initialize the state of the keyboard backlight. 121 * @ec: EC device to talk to. 122 * 123 * Gets the current brightness, ensuring that the BIOS already initialized the 124 * backlight to PWM mode. If not in PWM mode, then the current brightness is 125 * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS. 126 * 127 * Return: Final brightness of the keyboard, or negative error code on failure. 128 */ 129 static int kbbl_init(struct wilco_ec_device *ec) 130 { 131 struct wilco_keyboard_leds_msg request; 132 struct wilco_keyboard_leds_msg response; 133 int ret; 134 135 memset(&request, 0, sizeof(request)); 136 request.command = WILCO_EC_COMMAND_KBBL; 137 request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE; 138 139 ret = send_kbbl_msg(ec, &request, &response); 140 if (ret < 0) 141 return ret; 142 143 if (response.mode & WILCO_KBBL_MODE_FLAG_PWM) 144 return response.percent; 145 146 ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS); 147 if (ret < 0) 148 return ret; 149 150 return WILCO_KBBL_DEFAULT_BRIGHTNESS; 151 } 152 153 static int wilco_keyboard_leds_set(struct led_classdev *cdev, 154 enum led_brightness brightness) 155 { 156 struct wilco_keyboard_leds *wkl = 157 container_of(cdev, struct wilco_keyboard_leds, keyboard); 158 return set_kbbl(wkl->ec, brightness); 159 } 160 161 int wilco_keyboard_leds_init(struct wilco_ec_device *ec) 162 { 163 struct wilco_keyboard_leds *wkl; 164 bool leds_exist; 165 int ret; 166 167 ret = kbbl_exist(ec, &leds_exist); 168 if (ret < 0) { 169 dev_err(ec->dev, 170 "Failed checking keyboard LEDs support: %d", ret); 171 return ret; 172 } 173 if (!leds_exist) 174 return 0; 175 176 wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL); 177 if (!wkl) 178 return -ENOMEM; 179 180 wkl->ec = ec; 181 wkl->keyboard.name = "platform::kbd_backlight"; 182 wkl->keyboard.max_brightness = 100; 183 wkl->keyboard.flags = LED_CORE_SUSPENDRESUME; 184 wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set; 185 ret = kbbl_init(ec); 186 if (ret < 0) 187 return ret; 188 wkl->keyboard.brightness = ret; 189 190 return devm_led_classdev_register(ec->dev, &wkl->keyboard); 191 } 192