keyboard_leds.c (5202B)
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 21struct wilco_keyboard_leds { 22 struct wilco_ec_device *ec; 23 struct led_classdev keyboard; 24}; 25 26enum 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 */ 43struct 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. */ 55static 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\n", ret); 73 return ret; 74 } 75 76 return 0; 77} 78 79static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness) 80{ 81 struct wilco_keyboard_leds_msg request; 82 struct wilco_keyboard_leds_msg response; 83 int ret; 84 85 memset(&request, 0, sizeof(request)); 86 request.command = WILCO_EC_COMMAND_KBBL; 87 request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE; 88 request.mode = WILCO_KBBL_MODE_FLAG_PWM; 89 request.percent = brightness; 90 91 ret = send_kbbl_msg(ec, &request, &response); 92 if (ret < 0) 93 return ret; 94 95 if (response.status) { 96 dev_err(ec->dev, 97 "EC reported failure sending keyboard LEDs command: %d\n", 98 response.status); 99 return -EIO; 100 } 101 102 return 0; 103} 104 105static int kbbl_exist(struct wilco_ec_device *ec, bool *exists) 106{ 107 struct wilco_keyboard_leds_msg request; 108 struct wilco_keyboard_leds_msg response; 109 int ret; 110 111 memset(&request, 0, sizeof(request)); 112 request.command = WILCO_EC_COMMAND_KBBL; 113 request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES; 114 115 ret = send_kbbl_msg(ec, &request, &response); 116 if (ret < 0) 117 return ret; 118 119 *exists = response.status != 0xFF; 120 121 return 0; 122} 123 124/** 125 * kbbl_init() - Initialize the state of the keyboard backlight. 126 * @ec: EC device to talk to. 127 * 128 * Gets the current brightness, ensuring that the BIOS already initialized the 129 * backlight to PWM mode. If not in PWM mode, then the current brightness is 130 * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS. 131 * 132 * Return: Final brightness of the keyboard, or negative error code on failure. 133 */ 134static int kbbl_init(struct wilco_ec_device *ec) 135{ 136 struct wilco_keyboard_leds_msg request; 137 struct wilco_keyboard_leds_msg response; 138 int ret; 139 140 memset(&request, 0, sizeof(request)); 141 request.command = WILCO_EC_COMMAND_KBBL; 142 request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE; 143 144 ret = send_kbbl_msg(ec, &request, &response); 145 if (ret < 0) 146 return ret; 147 148 if (response.status) { 149 dev_err(ec->dev, 150 "EC reported failure sending keyboard LEDs command: %d\n", 151 response.status); 152 return -EIO; 153 } 154 155 if (response.mode & WILCO_KBBL_MODE_FLAG_PWM) 156 return response.percent; 157 158 ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS); 159 if (ret < 0) 160 return ret; 161 162 return WILCO_KBBL_DEFAULT_BRIGHTNESS; 163} 164 165static int wilco_keyboard_leds_set(struct led_classdev *cdev, 166 enum led_brightness brightness) 167{ 168 struct wilco_keyboard_leds *wkl = 169 container_of(cdev, struct wilco_keyboard_leds, keyboard); 170 return set_kbbl(wkl->ec, brightness); 171} 172 173int wilco_keyboard_leds_init(struct wilco_ec_device *ec) 174{ 175 struct wilco_keyboard_leds *wkl; 176 bool leds_exist; 177 int ret; 178 179 ret = kbbl_exist(ec, &leds_exist); 180 if (ret < 0) { 181 dev_err(ec->dev, 182 "Failed checking keyboard LEDs support: %d\n", ret); 183 return ret; 184 } 185 if (!leds_exist) 186 return 0; 187 188 wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL); 189 if (!wkl) 190 return -ENOMEM; 191 192 wkl->ec = ec; 193 wkl->keyboard.name = "platform::kbd_backlight"; 194 wkl->keyboard.max_brightness = 100; 195 wkl->keyboard.flags = LED_CORE_SUSPENDRESUME; 196 wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set; 197 ret = kbbl_init(ec); 198 if (ret < 0) 199 return ret; 200 wkl->keyboard.brightness = ret; 201 202 return devm_led_classdev_register(ec->dev, &wkl->keyboard); 203}