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_MFD_CROS_EC_DEV)
125 static int keyboard_led_init_ec_pwm_mfd(struct platform_device
*pdev
)
127 struct cros_ec_dev
*ec_dev
= dev_get_drvdata(pdev
->dev
.parent
);
128 struct cros_ec_device
*cros_ec
= ec_dev
->ec_dev
;
129 struct keyboard_led
*keyboard_led
= platform_get_drvdata(pdev
);
131 keyboard_led
->ec
= cros_ec
;
137 keyboard_led_set_brightness_ec_pwm(struct led_classdev
*cdev
,
138 enum led_brightness brightness
)
141 struct cros_ec_command msg
;
142 struct ec_params_pwm_set_keyboard_backlight params
;
144 struct ec_params_pwm_set_keyboard_backlight
*params
= &buf
.params
;
145 struct cros_ec_command
*msg
= &buf
.msg
;
146 struct keyboard_led
*keyboard_led
= container_of(cdev
, struct keyboard_led
, cdev
);
148 memset(&buf
, 0, sizeof(buf
));
150 msg
->command
= EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT
;
151 msg
->outsize
= sizeof(*params
);
153 params
->percent
= brightness
;
155 return cros_ec_cmd_xfer_status(keyboard_led
->ec
, msg
);
158 static enum led_brightness
159 keyboard_led_get_brightness_ec_pwm(struct led_classdev
*cdev
)
162 struct cros_ec_command msg
;
163 struct ec_response_pwm_get_keyboard_backlight resp
;
165 struct ec_response_pwm_get_keyboard_backlight
*resp
= &buf
.resp
;
166 struct cros_ec_command
*msg
= &buf
.msg
;
167 struct keyboard_led
*keyboard_led
= container_of(cdev
, struct keyboard_led
, cdev
);
170 memset(&buf
, 0, sizeof(buf
));
172 msg
->command
= EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT
;
173 msg
->insize
= sizeof(*resp
);
175 ret
= cros_ec_cmd_xfer_status(keyboard_led
->ec
, msg
);
179 return resp
->percent
;
182 static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd
= {
183 .init
= keyboard_led_init_ec_pwm_mfd
,
184 .brightness_set_blocking
= keyboard_led_set_brightness_ec_pwm
,
185 .brightness_get
= keyboard_led_get_brightness_ec_pwm
,
186 .max_brightness
= KEYBOARD_BACKLIGHT_MAX
,
189 #else /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */
191 static const struct keyboard_led_drvdata keyboard_led_drvdata_ec_pwm_mfd
= {};
193 #endif /* IS_ENABLED(CONFIG_MFD_CROS_EC_DEV) */
195 static int keyboard_led_is_mfd_device(struct platform_device
*pdev
)
197 return IS_ENABLED(CONFIG_MFD_CROS_EC_DEV
) && mfd_get_cell(pdev
);
200 static int keyboard_led_probe(struct platform_device
*pdev
)
202 const struct keyboard_led_drvdata
*drvdata
;
203 struct keyboard_led
*keyboard_led
;
206 if (keyboard_led_is_mfd_device(pdev
))
207 drvdata
= &keyboard_led_drvdata_ec_pwm_mfd
;
209 drvdata
= device_get_match_data(&pdev
->dev
);
213 keyboard_led
= devm_kzalloc(&pdev
->dev
, sizeof(*keyboard_led
), GFP_KERNEL
);
216 platform_set_drvdata(pdev
, keyboard_led
);
219 err
= drvdata
->init(pdev
);
224 keyboard_led
->cdev
.name
= "chromeos::kbd_backlight";
225 keyboard_led
->cdev
.flags
|= LED_CORE_SUSPENDRESUME
| LED_REJECT_NAME_CONFLICT
;
226 keyboard_led
->cdev
.max_brightness
= drvdata
->max_brightness
;
227 keyboard_led
->cdev
.brightness_set
= drvdata
->brightness_set
;
228 keyboard_led
->cdev
.brightness_set_blocking
= drvdata
->brightness_set_blocking
;
229 keyboard_led
->cdev
.brightness_get
= drvdata
->brightness_get
;
231 err
= devm_led_classdev_register(&pdev
->dev
, &keyboard_led
->cdev
);
232 if (err
== -EEXIST
) /* Already bound via other mechanism */
238 static const struct acpi_device_id keyboard_led_acpi_match
[] = {
239 { "GOOG0002", (kernel_ulong_t
)&keyboard_led_drvdata_acpi
},
242 MODULE_DEVICE_TABLE(acpi
, keyboard_led_acpi_match
);
245 static const struct platform_device_id keyboard_led_id
[] = {
246 { "cros-keyboard-leds", 0 },
249 MODULE_DEVICE_TABLE(platform
, keyboard_led_id
);
251 static struct platform_driver keyboard_led_driver
= {
253 .name
= "cros-keyboard-leds",
254 .acpi_match_table
= ACPI_PTR(keyboard_led_acpi_match
),
256 .probe
= keyboard_led_probe
,
257 .id_table
= keyboard_led_id
,
259 module_platform_driver(keyboard_led_driver
);
261 MODULE_AUTHOR("Simon Que <sque@chromium.org>");
262 MODULE_DESCRIPTION("ChromeOS Keyboard backlight LED Driver");
263 MODULE_LICENSE("GPL");