Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Documentation/devicetree/bindings/leds/wirenboard,wbec-led.yaml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>

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>;
};
...
1 change: 1 addition & 0 deletions Documentation/driver-api/wbec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
5 changes: 5 additions & 0 deletions arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,11 @@
compatible = "wirenboard,wbec-power";
reg = <0>;
};

wbec_led: wbec-led@0 {
compatible = "wirenboard,wbec-led";
reg = <0>;
};
};
};

Expand Down
5 changes: 5 additions & 0 deletions arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,11 @@
reg = <0>;
#pwm-cells = <3>;
};

wbec_led: wbec-led@0 {
compatible = "wirenboard,wbec-led";
reg = <0>;
};
};
};

Expand Down
1 change: 1 addition & 0 deletions arch/arm64/configs/wb8-bootlet.config
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions arch/arm64/configs/wb8.config
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions drivers/leds/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions drivers/leds/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
216 changes: 216 additions & 0 deletions drivers/leds/leds-wbec.c
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/

#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/mfd/wbec.h>

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 <[email protected]>");
MODULE_DESCRIPTION("Wiren Board Embedded Controller System LED driver");
MODULE_LICENSE("GPL");
5 changes: 5 additions & 0 deletions drivers/mfd/wbec.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
};

/* ----------------------------------------------------------------------- */
Expand Down
5 changes: 5 additions & 0 deletions include/linux/mfd/wbec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down