1 // SPDX-License-Identifier: GPL-2.0+ 2 // Keyboard backlight LED driver for ChromeOS 3 // 4 // Copyright (C) 2012 Google, Inc. 5 6 #include <linux/acpi.h> 7 #include <linux/delay.h> 8 #include <linux/err.h> 9 #include <linux/init.h> 10 #include <linux/kernel.h> 11 #include <linux/leds.h> 12 #include <linux/mfd/core.h> 13 #include <linux/mod_devicetable.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/platform_data/cros_ec_commands.h> 17 #include <linux/platform_data/cros_ec_proto.h> 18 #include <linux/platform_device.h> 19 #include <linux/property.h> 20 #include <linux/slab.h> 21 22 struct keyboard_led { 23 struct led_classdev cdev; 24 struct cros_ec_device *ec; 25 }; 26 27 /** 28 * struct keyboard_led_drvdata - keyboard LED driver data. 29 * @init: Init function. 30 * @brightness_get: Get LED brightness level. 31 * @brightness_set: Set LED brightness level. Must not sleep. 32 * @brightness_set_blocking: Set LED brightness level. It can block the 33 * caller for the time required for accessing a 34 * LED device register 35 * 36 * See struct led_classdev in include/linux/leds.h for more details. 37 */ 38 struct keyboard_led_drvdata { 39 int (*init)(struct platform_device *pdev, struct keyboard_led *keyboard_led); 40 41 enum led_brightness (*brightness_get)(struct led_classdev *led_cdev); 42 43 void (*brightness_set)(struct led_classdev *led_cdev, 44 enum led_brightness brightness); 45 int (*brightness_set_blocking)(struct led_classdev *led_cdev, 46 enum led_brightness brightness); 47 }; 48 49 #ifdef CONFIG_ACPI 50 51 /* Keyboard LED ACPI Device must be defined in firmware */ 52 #define ACPI_KEYBOARD_BACKLIGHT_DEVICE "\\_SB.KBLT" 53 #define ACPI_KEYBOARD_BACKLIGHT_READ ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBQC" 54 #define ACPI_KEYBOARD_BACKLIGHT_WRITE ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBCM" 55 56 static void keyboard_led_set_brightness_acpi(struct led_classdev *cdev, 57 enum led_brightness brightness) 58 { 59 union acpi_object param; 60 struct acpi_object_list input; 61 acpi_status status; 62 63 param.type = ACPI_TYPE_INTEGER; 64 param.integer.value = brightness; 65 input.count = 1; 66 input.pointer = ¶m; 67 68 status = acpi_evaluate_object(NULL, ACPI_KEYBOARD_BACKLIGHT_WRITE, 69 &input, NULL); 70 if (ACPI_FAILURE(status)) 71 dev_err(cdev->dev, "Error setting keyboard LED value: %d\n", 72 status); 73 } 74 75 static enum led_brightness 76 keyboard_led_get_brightness_acpi(struct led_classdev *cdev) 77 { 78 unsigned long long brightness; 79 acpi_status status; 80 81 status = acpi_evaluate_integer(NULL, ACPI_KEYBOARD_BACKLIGHT_READ, 82 NULL, &brightness); 83 if (ACPI_FAILURE(status)) { 84 dev_err(cdev->dev, "Error getting keyboard LED value: %d\n", 85 status); 86 return -EIO; 87 } 88 89 return brightness; 90 } 91 92 static int keyboard_led_init_acpi(struct platform_device *pdev, 93 struct keyboard_led *keyboard_led) 94 { 95 acpi_handle handle; 96 acpi_status status; 97 98 /* Look for the keyboard LED ACPI Device */ 99 status = acpi_get_handle(ACPI_ROOT_OBJECT, 100 ACPI_KEYBOARD_BACKLIGHT_DEVICE, 101 &handle); 102 if (ACPI_FAILURE(status)) { 103 dev_err(&pdev->dev, "Unable to find ACPI device %s: %d\n", 104 ACPI_KEYBOARD_BACKLIGHT_DEVICE, status); 105 return -ENXIO; 106 } 107 108 return 0; 109 } 110 111 static const struct keyboard_led_drvdata keyboard_led_drvdata_acpi = { 112 .init = keyboard_led_init_acpi, 113 .brightness_set = keyboard_led_set_brightness_acpi, 114 .brightness_get = keyboard_led_get_brightness_acpi, 115 }; 116 117 #endif /* CONFIG_ACPI */ 118 119 static int keyboard_led_init_ec_pwm_mfd(struct platform_device *pdev, 120 struct keyboard_led *keyboard_led) 121 { 122 struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); 123 struct cros_ec_device *cros_ec = ec_dev->ec_dev; 124 125 keyboard_led->ec = cros_ec; 126 127 return 0; 128 } 129 130 static int 131 keyboard_led_set_brightness_ec_pwm(struct led_classdev *cdev, 132 enum led_brightness brightness) 133 { 134 DEFINE_RAW_FLEX(struct cros_ec_command, msg, data, 135 sizeof(struct ec_params_pwm_set_keyboard_backlight)); 136 struct ec_params_pwm_set_keyboard_backlight *params = 137 (struct ec_params_pwm_set_keyboard_backlight *)msg->data; 138 struct keyboard_led *keyboard_led = container_of(cdev, struct keyboard_led, cdev); 139 140 msg->command = EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT; 141 msg->outsize = sizeof(*params); 142 143 params->percent = brightness; 144 145 return cros_ec_cmd_xfer_status(keyboard_led->ec, msg); 146 } 147 148 static enum led_brightness 149 keyboard_led_get_brightness_ec_pwm(struct led_classdev *cdev) 150 { 151 DEFINE_RAW_FLEX(struct cros_ec_command, msg, data, 152 sizeof(struct ec_response_pwm_get_keyboard_backlight)); 153 struct ec_response_pwm_get_keyboard_backlight *resp = 154 (struct ec_response_pwm_get_keyboard_backlight *)msg->data; 155 struct keyboard_led *keyboard_led = container_of(cdev, struct keyboard_led, cdev); 156 int ret; 157 158 msg->command = EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT; 159 msg->insize = sizeof(*resp); 160 161 ret = cros_ec_cmd_xfer_status(keyboard_led->ec, msg); 162 if (ret < 0) 163 return ret; 164 165 return resp->percent; 166 } 167 168 static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd = { 169 .init = keyboard_led_init_ec_pwm_mfd, 170 .brightness_set_blocking = keyboard_led_set_brightness_ec_pwm, 171 .brightness_get = keyboard_led_get_brightness_ec_pwm, 172 }; 173 174 static int keyboard_led_is_mfd_device(struct platform_device *pdev) 175 { 176 return IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) && mfd_get_cell(pdev); 177 } 178 179 static int keyboard_led_probe(struct platform_device *pdev) 180 { 181 const struct keyboard_led_drvdata *drvdata; 182 struct keyboard_led *keyboard_led; 183 int err; 184 185 if (keyboard_led_is_mfd_device(pdev)) 186 drvdata = &keyboard_led_drvdata_ec_pwm_mfd; 187 else 188 drvdata = device_get_match_data(&pdev->dev); 189 if (!drvdata) 190 return -EINVAL; 191 192 keyboard_led = devm_kzalloc(&pdev->dev, sizeof(*keyboard_led), GFP_KERNEL); 193 if (!keyboard_led) 194 return -ENOMEM; 195 196 if (drvdata->init) { 197 err = drvdata->init(pdev, keyboard_led); 198 if (err) 199 return err; 200 } 201 202 keyboard_led->cdev.name = "chromeos::kbd_backlight"; 203 keyboard_led->cdev.flags |= LED_CORE_SUSPENDRESUME | LED_REJECT_NAME_CONFLICT; 204 keyboard_led->cdev.max_brightness = 100; 205 keyboard_led->cdev.brightness_set = drvdata->brightness_set; 206 keyboard_led->cdev.brightness_set_blocking = drvdata->brightness_set_blocking; 207 keyboard_led->cdev.brightness_get = drvdata->brightness_get; 208 209 err = devm_led_classdev_register(&pdev->dev, &keyboard_led->cdev); 210 if (err == -EEXIST) /* Already bound via other mechanism */ 211 return -ENODEV; 212 return err; 213 } 214 215 #ifdef CONFIG_ACPI 216 static const struct acpi_device_id keyboard_led_acpi_match[] = { 217 { "GOOG0002", (kernel_ulong_t)&keyboard_led_drvdata_acpi }, 218 { } 219 }; 220 MODULE_DEVICE_TABLE(acpi, keyboard_led_acpi_match); 221 #endif 222 223 static const struct platform_device_id keyboard_led_id[] = { 224 { "cros-keyboard-leds", 0 }, 225 {} 226 }; 227 MODULE_DEVICE_TABLE(platform, keyboard_led_id); 228 229 static struct platform_driver keyboard_led_driver = { 230 .driver = { 231 .name = "cros-keyboard-leds", 232 .acpi_match_table = ACPI_PTR(keyboard_led_acpi_match), 233 }, 234 .probe = keyboard_led_probe, 235 .id_table = keyboard_led_id, 236 }; 237 module_platform_driver(keyboard_led_driver); 238 239 MODULE_AUTHOR("Simon Que <sque@chromium.org>"); 240 MODULE_DESCRIPTION("ChromeOS Keyboard backlight LED Driver"); 241 MODULE_LICENSE("GPL"); 242