1 // SPDX-License-Identifier: GPL-2.0+
2 // Keyboard backlight LED driver for ChromeOS
4 // Copyright (C) 2012 Google, Inc.
6 #include <linux/acpi.h>
7 #include <linux/delay.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>
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>
23 struct led_classdev cdev
;
24 struct cros_ec_device
*ec
;
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
35 * @max_brightness: Maximum brightness.
37 * See struct led_classdev in include/linux/leds.h for more details.
39 struct keyboard_led_drvdata
{
40 int (*init
)(struct platform_device
*pdev
);
42 enum led_brightness (*brightness_get
)(struct led_classdev
*led_cdev
);
44 void (*brightness_set
)(struct led_classdev
*led_cdev
,
45 enum led_brightness brightness
);
46 int (*brightness_set_blocking
)(struct led_classdev
*led_cdev
,
47 enum led_brightness brightness
);
49 enum led_brightness max_brightness
;
52 #define KEYBOARD_BACKLIGHT_MAX 100
56 /* Keyboard LED ACPI Device must be defined in firmware */
57 #define ACPI_KEYBOARD_BACKLIGHT_DEVICE "\\_SB.KBLT"
58 #define ACPI_KEYBOARD_BACKLIGHT_READ ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBQC"
59 #define ACPI_KEYBOARD_BACKLIGHT_WRITE ACPI_KEYBOARD_BACKLIGHT_DEVICE ".KBCM"
61 static void keyboard_led_set_brightness_acpi(struct led_classdev
*cdev
,
62 enum led_brightness brightness
)
64 union acpi_object param
;
65 struct acpi_object_list input
;
68 param
.type
= ACPI_TYPE_INTEGER
;
69 param
.integer
.value
= brightness
;
71 input
.pointer
= ¶m
;
73 status
= acpi_evaluate_object(NULL
, ACPI_KEYBOARD_BACKLIGHT_WRITE
,
75 if (ACPI_FAILURE(status
))
76 dev_err(cdev
->dev
, "Error setting keyboard LED value: %d\n",
80 static enum led_brightness
81 keyboard_led_get_brightness_acpi(struct led_classdev
*cdev
)
83 unsigned long long brightness
;
86 status
= acpi_evaluate_integer(NULL
, ACPI_KEYBOARD_BACKLIGHT_READ
,
88 if (ACPI_FAILURE(status
)) {
89 dev_err(cdev
->dev
, "Error getting keyboard LED value: %d\n",
97 static int keyboard_led_init_acpi(struct platform_device
*pdev
)
102 /* Look for the keyboard LED ACPI Device */
103 status
= acpi_get_handle(ACPI_ROOT_OBJECT
,
104 ACPI_KEYBOARD_BACKLIGHT_DEVICE
,
106 if (ACPI_FAILURE(status
)) {
107 dev_err(&pdev
->dev
, "Unable to find ACPI device %s: %d\n",
108 ACPI_KEYBOARD_BACKLIGHT_DEVICE
, status
);
115 static const struct keyboard_led_drvdata keyboard_led_drvdata_acpi
= {
116 .init
= keyboard_led_init_acpi
,
117 .brightness_set
= keyboard_led_set_brightness_acpi
,
118 .brightness_get
= keyboard_led_get_brightness_acpi
,
119 .max_brightness
= KEYBOARD_BACKLIGHT_MAX
,
122 #endif /* CONFIG_ACPI */
124 #if IS_ENABLED(CONFIG_CROS_EC)
127 keyboard_led_set_brightness_ec_pwm(struct led_classdev
*cdev
,
128 enum led_brightness brightness
)
131 struct cros_ec_command msg
;
132 struct ec_params_pwm_set_keyboard_backlight params
;
134 struct ec_params_pwm_set_keyboard_backlight
*params
= &buf
.params
;
135 struct cros_ec_command
*msg
= &buf
.msg
;
136 struct keyboard_led
*keyboard_led
= container_of(cdev
, struct keyboard_led
, cdev
);
138 memset(&buf
, 0, sizeof(buf
));
140 msg
->command
= EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT
;
141 msg
->outsize
= sizeof(*params
);
143 params
->percent
= brightness
;
145 return cros_ec_cmd_xfer_status(keyboard_led
->ec
, msg
);
148 static enum led_brightness
149 keyboard_led_get_brightness_ec_pwm(struct led_classdev
*cdev
)
152 struct cros_ec_command msg
;
153 struct ec_response_pwm_get_keyboard_backlight resp
;
155 struct ec_response_pwm_get_keyboard_backlight
*resp
= &buf
.resp
;
156 struct cros_ec_command
*msg
= &buf
.msg
;
157 struct keyboard_led
*keyboard_led
= container_of(cdev
, struct keyboard_led
, cdev
);
160 memset(&buf
, 0, sizeof(buf
));
162 msg
->command
= EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT
;
163 msg
->insize
= sizeof(*resp
);
165 ret
= cros_ec_cmd_xfer_status(keyboard_led
->ec
, msg
);
169 return resp
->percent
;
172 static int keyboard_led_init_ec_pwm(struct platform_device
*pdev
)
174 struct keyboard_led
*keyboard_led
= platform_get_drvdata(pdev
);
176 keyboard_led
->ec
= dev_get_drvdata(pdev
->dev
.parent
);
177 if (!keyboard_led
->ec
) {
178 dev_err(&pdev
->dev
, "no parent EC device\n");
185 static const __maybe_unused
struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm
= {
186 .init
= keyboard_led_init_ec_pwm
,
187 .brightness_set_blocking
= keyboard_led_set_brightness_ec_pwm
,
188 .brightness_get
= keyboard_led_get_brightness_ec_pwm
,
189 .max_brightness
= KEYBOARD_BACKLIGHT_MAX
,
192 #else /* IS_ENABLED(CONFIG_CROS_EC) */
194 static const __maybe_unused
struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm
= {};
196 #endif /* IS_ENABLED(CONFIG_CROS_EC) */
198 #if IS_ENABLED(CONFIG_MFD_CROS_EC_DEV)
199 static int keyboard_led_init_ec_pwm_mfd(struct platform_device
*pdev
)
201 struct cros_ec_dev
*ec_dev
= dev_get_drvdata(pdev
->dev
.parent
);
202 struct cros_ec_device
*cros_ec
= ec_dev
->ec_dev
;
203 struct keyboard_led
*keyboard_led
= platform_get_drvdata(pdev
);
205 keyboard_led
->ec
= cros_ec
;
210 static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd
= {
211 .init
= keyboard_led_init_ec_pwm_mfd
,
212 .brightness_set_blocking
= keyboard_led_set_brightness_ec_pwm
,
213 .brightness_get
= keyboard_led_get_brightness_ec_pwm
,
214 .max_brightness
= KEYBOARD_BACKLIGHT_MAX
,
217 #else /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */
219 static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd
= {};
221 #endif /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */
223 static int keyboard_led_is_mfd_device(struct platform_device
*pdev
)
225 return IS_ENABLED(CONFIG_MFD_CROS_EC_DEV
) && mfd_get_cell(pdev
);
228 static int keyboard_led_probe(struct platform_device
*pdev
)
230 const struct keyboard_led_drvdata
*drvdata
;
231 struct keyboard_led
*keyboard_led
;
234 if (keyboard_led_is_mfd_device(pdev
))
235 drvdata
= &keyboard_led_drvdata_ec_pwm_mfd
;
237 drvdata
= device_get_match_data(&pdev
->dev
);
241 keyboard_led
= devm_kzalloc(&pdev
->dev
, sizeof(*keyboard_led
), GFP_KERNEL
);
244 platform_set_drvdata(pdev
, keyboard_led
);
247 error
= drvdata
->init(pdev
);
252 keyboard_led
->cdev
.name
= "chromeos::kbd_backlight";
253 keyboard_led
->cdev
.flags
|= LED_CORE_SUSPENDRESUME
| LED_REJECT_NAME_CONFLICT
;
254 keyboard_led
->cdev
.max_brightness
= drvdata
->max_brightness
;
255 keyboard_led
->cdev
.brightness_set
= drvdata
->brightness_set
;
256 keyboard_led
->cdev
.brightness_set_blocking
= drvdata
->brightness_set_blocking
;
257 keyboard_led
->cdev
.brightness_get
= drvdata
->brightness_get
;
259 error
= devm_led_classdev_register(&pdev
->dev
, &keyboard_led
->cdev
);
260 if (error
== -EEXIST
) /* Already bound via other mechanism */
269 static const struct acpi_device_id keyboard_led_acpi_match
[] = {
270 { "GOOG0002", (kernel_ulong_t
)&keyboard_led_drvdata_acpi
},
273 MODULE_DEVICE_TABLE(acpi
, keyboard_led_acpi_match
);
277 static const struct of_device_id keyboard_led_of_match
[] = {
279 .compatible
= "google,cros-kbd-led-backlight",
280 .data
= &keyboard_led_drvdata_ec_pwm
,
284 MODULE_DEVICE_TABLE(of
, keyboard_led_of_match
);
287 static const struct platform_device_id keyboard_led_id
[] = {
288 { "cros-keyboard-leds", 0 },
291 MODULE_DEVICE_TABLE(platform
, keyboard_led_id
);
293 static struct platform_driver keyboard_led_driver
= {
295 .name
= "cros-keyboard-leds",
296 .acpi_match_table
= ACPI_PTR(keyboard_led_acpi_match
),
297 .of_match_table
= of_match_ptr(keyboard_led_of_match
),
299 .probe
= keyboard_led_probe
,
300 .id_table
= keyboard_led_id
,
302 module_platform_driver(keyboard_led_driver
);
304 MODULE_AUTHOR("Simon Que <sque@chromium.org>");
305 MODULE_DESCRIPTION("ChromeOS Keyboard backlight LED Driver");
306 MODULE_LICENSE("GPL");