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 <ncrews@xxxxxxxxxxxx> --- 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 <ncrews@xxxxxxxxxxxx>"); +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