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: &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 | |
79 | static 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: &request, response: &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 | |
105 | static 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: &request, response: &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 | */ |
134 | static 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: &request, response: &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 | |
165 | static 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(ec: wkl->ec, brightness); |
171 | } |
172 | |
173 | int 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, exists: &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(dev: ec->dev, size: 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(parent: ec->dev, led_cdev: &wkl->keyboard); |
203 | } |
204 | |