Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Keyboard backlight LED driver for the Wilco Embedded Controller
0004  *
0005  * Copyright 2019 Google LLC
0006  *
0007  * Since the EC will never change the backlight level of its own accord,
0008  * we don't need to implement a brightness_get() method.
0009  */
0010 
0011 #include <linux/device.h>
0012 #include <linux/kernel.h>
0013 #include <linux/leds.h>
0014 #include <linux/platform_data/wilco-ec.h>
0015 #include <linux/slab.h>
0016 
0017 #define WILCO_EC_COMMAND_KBBL       0x75
0018 #define WILCO_KBBL_MODE_FLAG_PWM    BIT(1)  /* Set brightness by percent. */
0019 #define WILCO_KBBL_DEFAULT_BRIGHTNESS   0
0020 
0021 struct wilco_keyboard_leds {
0022     struct wilco_ec_device *ec;
0023     struct led_classdev keyboard;
0024 };
0025 
0026 enum wilco_kbbl_subcommand {
0027     WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00,
0028     WILCO_KBBL_SUBCMD_GET_STATE    = 0x01,
0029     WILCO_KBBL_SUBCMD_SET_STATE    = 0x02,
0030 };
0031 
0032 /**
0033  * struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control.
0034  * @command: Always WILCO_EC_COMMAND_KBBL.
0035  * @status: Set by EC to 0 on success, 0xFF on failure.
0036  * @subcmd: One of enum wilco_kbbl_subcommand.
0037  * @reserved3: Should be 0.
0038  * @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM.
0039  * @reserved5to8: Should be 0.
0040  * @percent: Brightness in 0-100. Only meaningful in PWM mode.
0041  * @reserved10to15: Should be 0.
0042  */
0043 struct wilco_keyboard_leds_msg {
0044     u8 command;
0045     u8 status;
0046     u8 subcmd;
0047     u8 reserved3;
0048     u8 mode;
0049     u8 reserved5to8[4];
0050     u8 percent;
0051     u8 reserved10to15[6];
0052 } __packed;
0053 
0054 /* Send a request, get a response, and check that the response is good. */
0055 static int send_kbbl_msg(struct wilco_ec_device *ec,
0056              struct wilco_keyboard_leds_msg *request,
0057              struct wilco_keyboard_leds_msg *response)
0058 {
0059     struct wilco_ec_message msg;
0060     int ret;
0061 
0062     memset(&msg, 0, sizeof(msg));
0063     msg.type = WILCO_EC_MSG_LEGACY;
0064     msg.request_data = request;
0065     msg.request_size = sizeof(*request);
0066     msg.response_data = response;
0067     msg.response_size = sizeof(*response);
0068 
0069     ret = wilco_ec_mailbox(ec, &msg);
0070     if (ret < 0) {
0071         dev_err(ec->dev,
0072             "Failed sending keyboard LEDs command: %d\n", ret);
0073         return ret;
0074     }
0075 
0076     return 0;
0077 }
0078 
0079 static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness)
0080 {
0081     struct wilco_keyboard_leds_msg request;
0082     struct wilco_keyboard_leds_msg response;
0083     int ret;
0084 
0085     memset(&request, 0, sizeof(request));
0086     request.command = WILCO_EC_COMMAND_KBBL;
0087     request.subcmd  = WILCO_KBBL_SUBCMD_SET_STATE;
0088     request.mode    = WILCO_KBBL_MODE_FLAG_PWM;
0089     request.percent = brightness;
0090 
0091     ret = send_kbbl_msg(ec, &request, &response);
0092     if (ret < 0)
0093         return ret;
0094 
0095     if (response.status) {
0096         dev_err(ec->dev,
0097             "EC reported failure sending keyboard LEDs command: %d\n",
0098             response.status);
0099         return -EIO;
0100     }
0101 
0102     return 0;
0103 }
0104 
0105 static int kbbl_exist(struct wilco_ec_device *ec, bool *exists)
0106 {
0107     struct wilco_keyboard_leds_msg request;
0108     struct wilco_keyboard_leds_msg response;
0109     int ret;
0110 
0111     memset(&request, 0, sizeof(request));
0112     request.command = WILCO_EC_COMMAND_KBBL;
0113     request.subcmd  = WILCO_KBBL_SUBCMD_GET_FEATURES;
0114 
0115     ret = send_kbbl_msg(ec, &request, &response);
0116     if (ret < 0)
0117         return ret;
0118 
0119     *exists = response.status != 0xFF;
0120 
0121     return 0;
0122 }
0123 
0124 /**
0125  * kbbl_init() - Initialize the state of the keyboard backlight.
0126  * @ec: EC device to talk to.
0127  *
0128  * Gets the current brightness, ensuring that the BIOS already initialized the
0129  * backlight to PWM mode. If not in PWM mode, then the current brightness is
0130  * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS.
0131  *
0132  * Return: Final brightness of the keyboard, or negative error code on failure.
0133  */
0134 static int kbbl_init(struct wilco_ec_device *ec)
0135 {
0136     struct wilco_keyboard_leds_msg request;
0137     struct wilco_keyboard_leds_msg response;
0138     int ret;
0139 
0140     memset(&request, 0, sizeof(request));
0141     request.command = WILCO_EC_COMMAND_KBBL;
0142     request.subcmd  = WILCO_KBBL_SUBCMD_GET_STATE;
0143 
0144     ret = send_kbbl_msg(ec, &request, &response);
0145     if (ret < 0)
0146         return ret;
0147 
0148     if (response.status) {
0149         dev_err(ec->dev,
0150             "EC reported failure sending keyboard LEDs command: %d\n",
0151             response.status);
0152         return -EIO;
0153     }
0154 
0155     if (response.mode & WILCO_KBBL_MODE_FLAG_PWM)
0156         return response.percent;
0157 
0158     ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS);
0159     if (ret < 0)
0160         return ret;
0161 
0162     return WILCO_KBBL_DEFAULT_BRIGHTNESS;
0163 }
0164 
0165 static int wilco_keyboard_leds_set(struct led_classdev *cdev,
0166                    enum led_brightness brightness)
0167 {
0168     struct wilco_keyboard_leds *wkl =
0169         container_of(cdev, struct wilco_keyboard_leds, keyboard);
0170     return set_kbbl(wkl->ec, brightness);
0171 }
0172 
0173 int wilco_keyboard_leds_init(struct wilco_ec_device *ec)
0174 {
0175     struct wilco_keyboard_leds *wkl;
0176     bool leds_exist;
0177     int ret;
0178 
0179     ret = kbbl_exist(ec, &leds_exist);
0180     if (ret < 0) {
0181         dev_err(ec->dev,
0182             "Failed checking keyboard LEDs support: %d\n", ret);
0183         return ret;
0184     }
0185     if (!leds_exist)
0186         return 0;
0187 
0188     wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL);
0189     if (!wkl)
0190         return -ENOMEM;
0191 
0192     wkl->ec = ec;
0193     wkl->keyboard.name = "platform::kbd_backlight";
0194     wkl->keyboard.max_brightness = 100;
0195     wkl->keyboard.flags = LED_CORE_SUSPENDRESUME;
0196     wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set;
0197     ret = kbbl_init(ec);
0198     if (ret < 0)
0199         return ret;
0200     wkl->keyboard.brightness = ret;
0201 
0202     return devm_led_classdev_register(ec->dev, &wkl->keyboard);
0203 }