The EC is in charge of controlling the keyboard backlight on
the Wilco platform. We expose a standard LED class device at
/sys/class/leds/wilco::kbd_backlight. This driver is modeled
after the standard Chrome OS keyboard backlight driver at
drivers/platform/chrome/cros_kbd_led_backlight.c

Some Wilco devices do not support a keyboard backlight. This
is checked via wilco_ec_keyboard_leds_exist() in the core driver,
and a platform_device will only be registered by the core if
a backlight is supported.

After an EC reset the backlight could be in a non-PWM mode.
Earlier in the boot sequence the BIOS should send a command to
the EC to set the brightness, so things **should** be set up,
but we double check in probe() as we query the initial brightness.
If not set up, then set the brightness to KBBL_DEFAULT_BRIGHTNESS.

Since the EC will never change the backlight level of its own accord,
we don't need to implement a brightness_get() method.

v2 changes:
-Remove and fix uses of led vs LED in kconfig
-Assume BIOS initializes brightness, but double check in probe()
-Remove get_brightness() callback, as EC never changes brightness
 by itself.
-Use a __packed struct as message instead of opaque array
-Add exported wilco_ec_keyboard_leds_exist() so the core driver
 now only creates a platform _device if relevant
-Fix use of keyboard_led_set_brightness() since it can sleep

Signed-off-by: Nick Crews <[email protected]>
---
 drivers/platform/chrome/wilco_ec/Kconfig      |   9 +
 drivers/platform/chrome/wilco_ec/Makefile     |   2 +
 drivers/platform/chrome/wilco_ec/core.c       |  16 ++
 .../chrome/wilco_ec/kbd_led_backlight.c       | 216 ++++++++++++++++++
 include/linux/platform_data/wilco-ec.h        |  10 +
 5 files changed, 253 insertions(+)
 create mode 100644 drivers/platform/chrome/wilco_ec/kbd_led_backlight.c

diff --git a/drivers/platform/chrome/wilco_ec/Kconfig 
b/drivers/platform/chrome/wilco_ec/Kconfig
index e09e4cebe9b4..82abd3377a67 100644
--- a/drivers/platform/chrome/wilco_ec/Kconfig
+++ b/drivers/platform/chrome/wilco_ec/Kconfig
@@ -18,3 +18,12 @@ config WILCO_EC_DEBUGFS
          manipulation and allow for testing arbitrary commands.  This
          interface is intended for debug only and will not be present
          on production devices.
+
+config WILCO_EC_KBD_BACKLIGHT
+       tristate "Enable keyboard backlight control"
+       depends on WILCO_EC
+       help
+         If you say Y here, you get support to set the keyboard backlight
+         brightness. This happens via a standard LED driver that uses the
+         Wilco EC mailbox interface. A standard LED class device will
+         appear under /sys/class/leds/wilco::kbd_backlight
diff --git a/drivers/platform/chrome/wilco_ec/Makefile 
b/drivers/platform/chrome/wilco_ec/Makefile
index 9706aeb20ccb..114940aa9d53 100644
--- a/drivers/platform/chrome/wilco_ec/Makefile
+++ b/drivers/platform/chrome/wilco_ec/Makefile
@@ -6,3 +6,5 @@ wilco_ec_core-objs                      := core.o
 obj-$(CONFIG_WILCO_EC)                 += wilco_ec_core.o
 wilco_ec_debugfs-objs                  := debugfs.o
 obj-$(CONFIG_WILCO_EC_DEBUGFS)         += wilco_ec_debugfs.o
+wilco_kbd_backlight-objs               := kbd_led_backlight.o
+obj-$(CONFIG_WILCO_EC_KBD_BACKLIGHT)   += wilco_kbd_backlight.o
diff --git a/drivers/platform/chrome/wilco_ec/core.c 
b/drivers/platform/chrome/wilco_ec/core.c
index ece30874f35f..c62990e4fa02 100644
--- a/drivers/platform/chrome/wilco_ec/core.c
+++ b/drivers/platform/chrome/wilco_ec/core.c
@@ -89,8 +89,23 @@ static int wilco_ec_probe(struct platform_device *pdev)
                goto unregister_debugfs;
        }
 
+       /* Register child dev to be found by the keyboard backlight driver. */
+       if (wilco_ec_keyboard_leds_exist(ec)) {
+               ec->kbbl_pdev = platform_device_register_data(dev,
+                                               "wilco-kbd-backlight",
+                                               PLATFORM_DEVID_AUTO, NULL, 0);
+               if (IS_ERR(ec->kbbl_pdev)) {
+                       dev_err(dev,
+                               "Failed to create keyboard backlight pdev\n");
+                       ret = PTR_ERR(ec->kbbl_pdev);
+                       goto unregister_rtc;
+               }
+       }
+
        return 0;
 
+unregister_rtc:
+       platform_device_unregister(ec->rtc_pdev);
 unregister_debugfs:
        if (ec->debugfs_pdev)
                platform_device_unregister(ec->debugfs_pdev);
@@ -102,6 +117,7 @@ static int wilco_ec_remove(struct platform_device *pdev)
 {
        struct wilco_ec_device *ec = platform_get_drvdata(pdev);
 
+       platform_device_unregister(ec->kbbl_pdev);
        platform_device_unregister(ec->rtc_pdev);
        if (ec->debugfs_pdev)
                platform_device_unregister(ec->debugfs_pdev);
diff --git a/drivers/platform/chrome/wilco_ec/kbd_led_backlight.c 
b/drivers/platform/chrome/wilco_ec/kbd_led_backlight.c
new file mode 100644
index 000000000000..14cbc54cd8db
--- /dev/null
+++ b/drivers/platform/chrome/wilco_ec/kbd_led_backlight.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Keyboard backlight LED driver for the Wilco Embedded Controller
+ *
+ * Copyright 2019 Google LLC
+ *
+ * The EC is in charge of controlling the keyboard backlight on
+ * the Wilco platform. We expose a standard LED class device at
+ * /sys/class/leds/wilco::kbd_backlight. Power Manager normally
+ * controls the backlight by writing a percentage in range [0, 100]
+ * to the brightness property. This driver is modeled after the
+ * standard Chrome OS keyboard backlight driver at
+ * drivers/platform/chrome/cros_kbd_led_backlight.c
+ *
+ * Some Wilco devices do not support a keyboard backlight. This
+ * is checked via wilco_ec_keyboard_leds_exist() in the core driver,
+ * and a platform_device will only be registered by the core if
+ * a backlight is supported.
+ *
+ * After an EC reset the backlight could be in a non-PWM mode.
+ * Earlier in the boot sequence the BIOS should send a command to
+ * the EC to set the brightness, so things **should** be set up,
+ * but we double check in probe() as we query the initial brightness.
+ * If not set up, then we set the brightness to KBBL_DEFAULT_BRIGHTNESS.
+ *
+ * Since the EC will never change the backlight level of its own accord,
+ * we don't need to implement a brightness_get() method.
+ */
+
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define DRV_NAME               "wilco-kbd-backlight"
+
+#define EC_COMMAND_KB_BKLIGHT  0x75
+#define KBBL_MODE_PWM          BIT(1)  /* Flag to set brightness by percent */
+/* What action do we want the EC to perform? */
+enum kbbl_subcommand {
+       KBBL_SUBCMD_GET_FEATURES = 0x00,
+       KBBL_SUBCMD_GET_STATE = 0x01,
+       KBBL_SUBCMD_SET_STATE = 0x02,
+};
+
+#define KBBL_DEFAULT_BRIGHTNESS        0
+
+/* The message sent to and received by the EC */
+struct wilco_ec_kbbl_msg {
+       u8 command;             /* Always EC_COMMAND_KB_BKLIGHT */
+       u8 status;              /* Will be set to 0xFF by EC on failure */
+       u8 subcmd;              /* One of enum kbbl_subcommand */
+       u8 reserved3;
+       u8 mode;                /* Always KBBL_MODE_PWM */
+       u8 reserved5to8[4];
+       u8 percent;
+       u8 reserved10to15[6];
+} __packed;
+
+struct wilco_keyboard_led_data {
+       struct wilco_ec_device *ec;
+       struct led_classdev keyboard;
+};
+
+/**
+ * wilco_ec_keyboard_leds_exist() - Is the keyboad backlight supported?
+ * @ec: EC device to query.
+ *
+ * Return: true if backlight is supported, false if not or if error occurred.
+ */
+bool wilco_ec_keyboard_leds_exist(struct wilco_ec_device *ec)
+{
+       struct wilco_ec_kbbl_msg request;
+       struct wilco_ec_kbbl_msg response;
+       struct wilco_ec_message msg;
+       int ret;
+
+       memset(&request, 0, sizeof(request));
+       request.command = EC_COMMAND_KB_BKLIGHT;
+       request.subcmd = KBBL_SUBCMD_GET_FEATURES;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.type = WILCO_EC_MSG_LEGACY;
+       msg.request_data = &request;
+       msg.request_size = sizeof(request);
+       msg.response_data = &response;
+       msg.response_size = sizeof(response);
+
+       ret = wilco_ec_mailbox(ec, &msg);
+       if (ret < 0) {
+               dev_err(ec->dev,
+                       "Failed checking keyboard backlight support: %d", ret);
+               return false;
+       }
+
+       return response.status != 0xFF;
+}
+EXPORT_SYMBOL_GPL(wilco_ec_keyboard_leds_exist);
+
+/* This may sleep because it uses wilco_ec_mailbox() */
+static int keyboard_led_set_brightness(struct led_classdev *cdev,
+                                      enum led_brightness brightness)
+{
+       struct wilco_ec_kbbl_msg request;
+       struct wilco_ec_message msg;
+       struct wilco_keyboard_led_data *data;
+       int ret;
+
+       memset(&request, 0, sizeof(request));
+       request.command = EC_COMMAND_KB_BKLIGHT;
+       request.subcmd = KBBL_SUBCMD_SET_STATE;
+       request.mode = KBBL_MODE_PWM;
+       request.percent = brightness;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.type = WILCO_EC_MSG_LEGACY;
+       msg.request_data = &request;
+       msg.request_size = sizeof(request);
+       msg.response_size = 0;
+
+       data = container_of(cdev, struct wilco_keyboard_led_data, keyboard);
+       ret = wilco_ec_mailbox(data->ec, &msg);
+       if (ret < 0)
+               dev_err(cdev->dev, "Failed setting brightness: %d", ret);
+
+       return 0;
+}
+
+/*
+ * Get the current brightness, ensuring that we are in PWM mode. If not
+ * in PWM mode, then the current brightness is meaningless, so set the
+ * brightness to KBBL_DEFAULT_BRIGHTNESS.
+ *
+ * Return: Final brightness of the keyboard, or negative error code on failure.
+ */
+static int initialize_brightness(struct wilco_keyboard_led_data *data)
+{
+       struct wilco_ec_kbbl_msg request;
+       struct wilco_ec_kbbl_msg response;
+       struct wilco_ec_message msg;
+       int ret;
+
+       memset(&request, 0, sizeof(request));
+       request.command = EC_COMMAND_KB_BKLIGHT;
+       request.subcmd = KBBL_SUBCMD_GET_STATE;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.type = WILCO_EC_MSG_LEGACY;
+       msg.request_data = &request;
+       msg.request_size = sizeof(request);
+       msg.response_data = &response;
+       msg.response_size = sizeof(response);
+
+       ret = wilco_ec_mailbox(data->ec, &msg);
+       if (ret < 0) {
+               dev_err(data->ec->dev, "Failed getting brightness: %d", ret);
+               return ret;
+       }
+
+       if (response.mode & KBBL_MODE_PWM)
+               return response.percent;
+
+       ret = led_set_brightness_sync(&data->keyboard, KBBL_DEFAULT_BRIGHTNESS);
+       if (ret < 0)
+               return ret;
+
+       return KBBL_DEFAULT_BRIGHTNESS;
+}
+
+static int keyboard_led_probe(struct platform_device *pdev)
+{
+       struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+       struct wilco_keyboard_led_data *data;
+       int ret;
+
+       if (!wilco_ec_keyboard_leds_exist(ec))
+               return -ENXIO;
+
+       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->ec = ec;
+       /* To be found by Power Manager needs to end in ":kbd_backlight" */
+       data->keyboard.name = "wilco::kbd_backlight";
+       data->keyboard.max_brightness = 100;
+       data->keyboard.flags = LED_CORE_SUSPENDRESUME;
+       data->keyboard.brightness_set_blocking = keyboard_led_set_brightness;
+       ret = initialize_brightness(data);
+       if (ret < 0)
+               return ret;
+       data->keyboard.brightness = ret;
+
+       ret = devm_led_classdev_register(&pdev->dev, &data->keyboard);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static struct platform_driver keyboard_led_driver = {
+       .driver = {
+               .name = DRV_NAME,
+       },
+       .probe = keyboard_led_probe,
+};
+module_platform_driver(keyboard_led_driver);
+
+MODULE_AUTHOR("Nick Crews <[email protected]>");
+MODULE_DESCRIPTION("Wilco keyboard backlight LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/include/linux/platform_data/wilco-ec.h 
b/include/linux/platform_data/wilco-ec.h
index 1ff224793c99..785aa50513c2 100644
--- a/include/linux/platform_data/wilco-ec.h
+++ b/include/linux/platform_data/wilco-ec.h
@@ -32,6 +32,7 @@
  * @data_size: Size of the data buffer used for EC communication.
  * @debugfs_pdev: The child platform_device used by the debugfs sub-driver.
  * @rtc_pdev: The child platform_device used by the RTC sub-driver.
+ * @kbbl_pdev: The child pdev used by the keyboard backlight sub-driver.
  */
 struct wilco_ec_device {
        struct device *dev;
@@ -43,6 +44,7 @@ struct wilco_ec_device {
        size_t data_size;
        struct platform_device *debugfs_pdev;
        struct platform_device *rtc_pdev;
+       struct platform_device *kbbl_pdev;
 };
 
 /**
@@ -114,6 +116,14 @@ struct wilco_ec_message {
        void *response_data;
 };
 
+/**
+ * wilco_ec_keyboard_leds_exist() - Is the keyboad backlight supported?
+ * @ec: EC device to query.
+ *
+ * Return: true if backlight is supported, false if not or if error occurred.
+ */
+bool wilco_ec_keyboard_leds_exist(struct wilco_ec_device *ec);
+
 /**
  * wilco_ec_mailbox() - Send request to the EC and receive the response.
  * @ec: Wilco EC device.
-- 
2.20.1

Reply via email to