diff --git a/Documentation/devicetree/bindings/leds/wirenboard,wbec-led.yaml b/Documentation/devicetree/bindings/leds/wirenboard,wbec-led.yaml new file mode 100644 index 0000000000000..2997fbc775466 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/wirenboard,wbec-led.yaml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/wirenboard,wbec-led.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wiren Board Embedded Controller System LED Driver + +maintainers: + - Ilia Skochilov + +description: | + Enables access to the System LED on the Wiren Board Embedded Controller + +properties: + compatible: + const: wirenboard,wbec-led + + reg: + const: 0 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + wbec-led@0 { + compatible = "wirenboard,wbec-led"; + reg = <0>; + }; +... diff --git a/Documentation/driver-api/wbec.rst b/Documentation/driver-api/wbec.rst index 3779419a87018..30b6f67161ff3 100644 --- a/Documentation/driver-api/wbec.rst +++ b/Documentation/driver-api/wbec.rst @@ -23,6 +23,7 @@ sub-drivers for each device connected to WBEC: * rtc-wbec - RTC driver * pwrkey-wbec - Power key driver * wbec-power - Power driver + * wbec-led - System LED driver The wbec driver also provides a set of sysfs attributes for WBEC: diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi index b48d2dd56ef75..7ff77da3d5240 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi @@ -651,6 +651,11 @@ compatible = "wirenboard,wbec-power"; reg = <0>; }; + + wbec_led: wbec-led@0 { + compatible = "wirenboard,wbec-led"; + reg = <0>; + }; }; }; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index 7f05ce927b4db..7d8b079310779 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -612,6 +612,11 @@ reg = <0>; #pwm-cells = <3>; }; + + wbec_led: wbec-led@0 { + compatible = "wirenboard,wbec-led"; + reg = <0>; + }; }; }; diff --git a/arch/arm64/configs/wb8-bootlet.config b/arch/arm64/configs/wb8-bootlet.config index e03ed42629299..917c185e3fc9b 100644 --- a/arch/arm64/configs/wb8-bootlet.config +++ b/arch/arm64/configs/wb8-bootlet.config @@ -77,6 +77,7 @@ CONFIG_RTC_DRV_WBEC=y CONFIG_WBEC_ADC=y CONFIG_WBEC_BATTERY=y CONFIG_PWM_WBEC=y +CONFIG_LEDS_WBEC=y # SD3078 RTC (optional on boards without WBEC) CONFIG_RTC_DRV_SD3078=y diff --git a/arch/arm64/configs/wb8.config b/arch/arm64/configs/wb8.config index e38ef28620859..bb34d33f7cb20 100644 --- a/arch/arm64/configs/wb8.config +++ b/arch/arm64/configs/wb8.config @@ -119,6 +119,7 @@ CONFIG_RTC_DRV_WBEC=y CONFIG_WBEC_ADC=y CONFIG_WBEC_BATTERY=y CONFIG_PWM_WBEC=y +CONFIG_LEDS_WBEC=y # SD3078 RTC (optional on boards without WBEC) CONFIG_RTC_DRV_SD3078=y diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index d721b254e1e45..e9cf46411c0db 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -917,6 +917,13 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_WBEC + tristate "Wiren Board Embedded Controller System LED Driver" + depends on MFD_WBEC + help + This option enables support for the System LED of + the Wiren Board Embedded Controller. + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ce07dc295ff00..81c64ae214bd0 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o +obj-$(CONFIG_LEDS_WBEC) += leds-wbec.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o diff --git a/drivers/leds/leds-wbec.c b/drivers/leds/leds-wbec.c new file mode 100644 index 0000000000000..cdc4b76c38d97 --- /dev/null +++ b/drivers/leds/leds-wbec.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * led-wbec.c - Wiren Board Embedded Controller System LED driver + * + * Copyright (c) 2024 Wiren Board LLC + * + * Author: Ilia Skochilov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum led_mode { + WBEC_LED_OFF, + WBEC_LED_ON, + WBEC_LED_BLINK, +}; + +struct wbec_led { + struct regmap *regmap; + struct led_classdev cdev; + unsigned long blink_delay_on; + unsigned long blink_delay_off; +}; + +static void wbec_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + int value; + if (brightness > 0) { + value = WBEC_LED_ON; + } else { + value = WBEC_LED_OFF; + } + regmap_write(wbec_led->regmap, WBEC_REG_EC_SYSTEM_LED, value); +} + +static int wbec_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + int err; + + err = regmap_write(wbec_led->regmap, WBEC_REG_EC_SYSTEM_LED, WBEC_LED_BLINK); + if (err) { + return err; + } + + err = regmap_write(wbec_led->regmap, WBEC_REG_EC_SYSTEM_LED_ON_MS, *delay_on); + if (err) { + return err; + } + + err = regmap_write(wbec_led->regmap, WBEC_REG_EC_SYSTEM_LED_OFF_MS, *delay_off); + if (err) { + return err; + } + + wbec_led->blink_delay_on = *delay_on; + wbec_led->blink_delay_off = *delay_off; + + return 0; +} + +static ssize_t blink_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + + return sprintf(buf, "%lu\n", wbec_led->blink_delay_on); +} + +static ssize_t blink_on_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + unsigned long value; + int ret; + + ret = kstrtoul(buf, 10, &value); + if (ret) { + return ret; + } + + wbec_led->blink_delay_on = value; + wbec_blink_set(led_cdev, &wbec_led->blink_delay_on, &wbec_led->blink_delay_off); + + return size; +} + +static ssize_t blink_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + + return sprintf(buf, "%lu\n", wbec_led->blink_delay_off); +} + +static ssize_t blink_off_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct wbec_led *wbec_led = container_of(led_cdev, struct wbec_led, cdev); + unsigned long value; + int ret; + + ret = kstrtoul(buf, 10, &value); + if (ret) { + return ret; + } + + wbec_led->blink_delay_off = value; + wbec_blink_set(led_cdev, &wbec_led->blink_delay_on, &wbec_led->blink_delay_off); + + return size; +} + +static DEVICE_ATTR_RW(blink_on); +static DEVICE_ATTR_RW(blink_off); + +static struct attribute *wbec_led_attrs[] = { + &dev_attr_blink_on.attr, + &dev_attr_blink_off.attr, + NULL +}; + +ATTRIBUTE_GROUPS(wbec_led); + +static int wbec_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct wbec *wbec = dev_get_drvdata(pdev->dev.parent); + struct wbec_led *wbec_led; + int err; + + if (!pdev->dev.parent) { + return -ENODEV; + } + + wbec_led = devm_kzalloc(dev, sizeof(struct wbec_led), GFP_KERNEL); + if (!wbec_led) { + return -ENOMEM; + } + + wbec_led->regmap = wbec->regmap; + if (!wbec_led->regmap) { + return -ENODEV; + } + + if (!of_device_is_compatible(dev->of_node, "wirenboard,wbec-led")) { + dev_err(dev, "Incompatible device\n"); + return -EINVAL; + } + + wbec_led->cdev.name = "wbec_led"; + wbec_led->cdev.brightness_set = wbec_led_set; + wbec_led->cdev.blink_set = wbec_blink_set; + wbec_led->cdev.groups = wbec_led_groups; + + err = led_classdev_register(dev, &wbec_led->cdev); + if (err) { + dev_err(dev, "Failed to register System LED device\n"); + return err; + } + + platform_set_drvdata(pdev, wbec_led); + + dev_info(dev, "System LED device probed successfully\n"); + return 0; +} + +static int wbec_led_remove(struct platform_device *pdev) +{ + struct wbec_led *wbec_led = platform_get_drvdata(pdev); + + led_classdev_unregister(&wbec_led->cdev); + + dev_info(&pdev->dev, "System LED device removed\n"); + + return 0; +} + +static const struct of_device_id wbec_led_of_match[] = { + { .compatible = "wirenboard,wbec-led" }, + { } +}; +MODULE_DEVICE_TABLE(of, wbec_led_of_match); + +static struct platform_driver wbec_led_driver = { + .driver = { + .name = "wbec-led", + .of_match_table = wbec_led_of_match, + }, + .probe = wbec_led_probe, + .remove = wbec_led_remove, +}; +module_platform_driver(wbec_led_driver); + +MODULE_ALIAS("platform:wbec-led"); +MODULE_AUTHOR("Ilia Skochilov "); +MODULE_DESCRIPTION("Wiren Board Embedded Controller System LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/wbec.c b/drivers/mfd/wbec.c index a64a1e3422435..6cda419a53f70 100644 --- a/drivers/mfd/wbec.c +++ b/drivers/mfd/wbec.c @@ -82,6 +82,11 @@ static const struct mfd_cell wbec_cells[] = { .id = PLATFORM_DEVID_NONE, .of_compatible = "wirenboard,wbec-battery" }, + { + .name = "wbec-led", + .id = PLATFORM_DEVID_NONE, + .of_compatible = "wirenboard,wbec-led" + }, }; /* ----------------------------------------------------------------------- */ diff --git a/include/linux/mfd/wbec.h b/include/linux/mfd/wbec.h index 9b362c99ecf19..09f960bcadc9f 100644 --- a/include/linux/mfd/wbec.h +++ b/include/linux/mfd/wbec.h @@ -57,6 +57,11 @@ #define WBEC_REG_ADC_DATA_ADC5 0x4A #define WBEC_REG_ADC_DATA_ADC6 0x4B +/* Region EC_SYSTEM_LED: RW */ +#define WBEC_REG_EC_SYSTEM_LED 0x60 +#define WBEC_REG_EC_SYSTEM_LED_ON_MS 0x61 +#define WBEC_REG_EC_SYSTEM_LED_OFF_MS 0x62 + /* Region GPIO: RW */ #define WBEC_REG_GPIO 0x80 #define WBEC_REG_GPIO_A1_MSK BIT(0)