summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/.kunitconfig4
-rw-r--r--drivers/leds/Kconfig27
-rw-r--r--drivers/leds/Makefile6
-rw-r--r--drivers/leds/blink/leds-lgm-sso.c6
-rw-r--r--drivers/leds/flash/Kconfig12
-rw-r--r--drivers/leds/flash/Makefile1
-rw-r--r--drivers/leds/flash/leds-qcom-flash.c15
-rw-r--r--drivers/leds/flash/leds-rt8515.c6
-rw-r--r--drivers/leds/flash/leds-sgm3140.c8
-rw-r--r--drivers/leds/flash/leds-tps6131x.c815
-rw-r--r--drivers/leds/led-class-flash.c15
-rw-r--r--drivers/leds/led-class-multicolor.c3
-rw-r--r--drivers/leds/led-core.c50
-rw-r--r--drivers/leds/led-test.c132
-rw-r--r--drivers/leds/led-triggers.c13
-rw-r--r--drivers/leds/leds-aw200xx.c2
-rw-r--r--drivers/leds/leds-cros_ec.c21
-rw-r--r--drivers/leds/leds-lp50xx.c11
-rw-r--r--drivers/leds/leds-lp8860.c218
-rw-r--r--drivers/leds/leds-max77705.c275
-rw-r--r--drivers/leds/leds-mlxcpld.c1
-rw-r--r--drivers/leds/leds-nic78bx.c16
-rw-r--r--drivers/leds/leds-pca9532.c11
-rw-r--r--drivers/leds/leds-pca955x.c381
-rw-r--r--drivers/leds/leds-pca995x.c2
-rw-r--r--drivers/leds/leds-st1202.c17
-rw-r--r--drivers/leds/leds-tca6507.c11
-rw-r--r--drivers/leds/leds-turris-omnia.c4
-rw-r--r--drivers/leds/rgb/leds-mt6370-rgb.c16
-rw-r--r--drivers/leds/rgb/leds-ncp5623.c5
-rw-r--r--drivers/leds/rgb/leds-pwm-multicolor.c12
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c15
-rw-r--r--drivers/leds/simatic/Kconfig (renamed from drivers/leds/simple/Kconfig)0
-rw-r--r--drivers/leds/simatic/Makefile (renamed from drivers/leds/simple/Makefile)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-core.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-core.c)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio.h (renamed from drivers/leds/simple/simatic-ipc-leds-gpio.h)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds.c (renamed from drivers/leds/simple/simatic-ipc-leds.c)0
-rw-r--r--drivers/leds/trigger/ledtrig-activity.c4
-rw-r--r--drivers/leds/trigger/ledtrig-backlight.c48
-rw-r--r--drivers/leds/trigger/ledtrig-heartbeat.c2
-rw-r--r--drivers/leds/trigger/ledtrig-pattern.c8
-rw-r--r--drivers/leds/trigger/ledtrig-transient.c4
45 files changed, 1761 insertions, 436 deletions
diff --git a/drivers/leds/.kunitconfig b/drivers/leds/.kunitconfig
new file mode 100644
index 000000000000..5180f77910a1
--- /dev/null
+++ b/drivers/leds/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_KUNIT_TEST=y
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 2b27d043921c..6e3dce7e35a4 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -55,6 +55,13 @@ config LEDS_BRIGHTNESS_HW_CHANGED
See Documentation/ABI/testing/sysfs-class-led for details.
+config LEDS_KUNIT_TEST
+ tristate "KUnit tests for LEDs"
+ depends on KUNIT && LEDS_CLASS
+ default KUNIT_ALL_TESTS
+ help
+ Say Y here to enable KUnit testing for the LEDs framework.
+
comment "LED drivers"
config LEDS_88PM860X
@@ -735,7 +742,7 @@ config LEDS_NS2
tristate "LED support for Network Space v2 GPIO LEDs"
depends on LEDS_CLASS
depends on MACH_KIRKWOOD || MACH_ARMADA_370 || COMPILE_TEST
- default y
+ default y if MACH_KIRKWOOD || MACH_ARMADA_370
help
This option enables support for the dual-GPIO LEDs found on the
following LaCie/Seagate boards:
@@ -750,7 +757,7 @@ config LEDS_NETXBIG
depends on LEDS_CLASS
depends on MACH_KIRKWOOD || COMPILE_TEST
depends on OF_GPIO
- default y
+ default MACH_KIRKWOOD
help
This option enables support for LEDs found on the LaCie 2Big
and 5Big Network v2 boards. The LEDs are wired to a CPLD and are
@@ -778,6 +785,14 @@ config LEDS_MAX77650
help
LEDs driver for MAX77650 family of PMICs from Maxim Integrated.
+config LEDS_MAX77705
+ tristate "LED support for Maxim MAX77705 PMIC"
+ depends on MFD_MAX77705
+ depends on LEDS_CLASS
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ LED driver for MAX77705 PMIC from Maxim Integrated.
+
config LEDS_MAX8997
tristate "LED support for MAX8997 PMIC"
depends on LEDS_CLASS && MFD_MAX8997
@@ -924,7 +939,8 @@ config LEDS_USER
config LEDS_NIC78BX
tristate "LED support for NI PXI NIC78bx devices"
depends on LEDS_CLASS
- depends on X86 && ACPI
+ depends on HAS_IOPORT
+ depends on X86 || COMPILE_TEST
help
This option enables support for the User1 and User2 LEDs on NI
PXI NIC78bx devices.
@@ -971,6 +987,7 @@ config LEDS_ST1202
depends on I2C
depends on OF
select LEDS_TRIGGERS
+ select LEDS_TRIGGER_PATTERN
help
Say Y to enable support for LEDs connected to LED1202
LED driver chips accessed via the I2C bus.
@@ -1014,7 +1031,7 @@ source "drivers/leds/rgb/Kconfig"
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
-comment "Simple LED drivers"
-source "drivers/leds/simple/Kconfig"
+comment "Simatic LED drivers"
+source "drivers/leds/simatic/Kconfig"
endif # NEW_LEDS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 6ad52e219ec6..9a0333ec1a86 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
+obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o
# LED Platform Drivers (keep this sorted, M-| sort)
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
@@ -61,6 +62,7 @@ obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
+obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
@@ -121,5 +123,5 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
# LED Blink
obj-y += blink/
-# Simple LED drivers
-obj-y += simple/
+# Simatic LED drivers
+obj-y += simatic/
diff --git a/drivers/leds/blink/leds-lgm-sso.c b/drivers/leds/blink/leds-lgm-sso.c
index effaaaf302b5..c9027f9c4bb7 100644
--- a/drivers/leds/blink/leds-lgm-sso.c
+++ b/drivers/leds/blink/leds-lgm-sso.c
@@ -450,7 +450,7 @@ static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset)
return !!(reg_val & BIT(offset));
}
-static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+static int sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct sso_led_priv *priv = gpiochip_get_data(chip);
@@ -458,6 +458,8 @@ static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
if (!priv->gpio.freq)
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
SSO_CON0_SWU);
+
+ return 0;
}
static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
@@ -469,7 +471,7 @@ static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
gc->get_direction = sso_gpio_get_dir;
gc->direction_output = sso_gpio_dir_out;
gc->get = sso_gpio_get;
- gc->set = sso_gpio_set;
+ gc->set_rv = sso_gpio_set;
gc->label = "lgm-sso";
gc->base = -1;
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
index f39f0bfe6eef..5e08102a6784 100644
--- a/drivers/leds/flash/Kconfig
+++ b/drivers/leds/flash/Kconfig
@@ -132,4 +132,16 @@ config LEDS_SY7802
This driver can be built as a module, it will be called "leds-sy7802".
+config LEDS_TPS6131X
+ tristate "LED support for TI TPS6131x flash LED driver"
+ depends on I2C && OF
+ depends on GPIOLIB
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP_I2C
+ help
+ This option enables support for Texas Instruments TPS61310/TPS61311
+ flash LED driver.
+
+ This driver can be built as a module, it will be called "leds-tps6131x".
+
endif # LEDS_CLASS_FLASH
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
index 48860eeced79..712fb737a428 100644
--- a/drivers/leds/flash/Makefile
+++ b/drivers/leds/flash/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
+obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o
diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c
index b4c19be51c4d..89cf5120f5d5 100644
--- a/drivers/leds/flash/leds-qcom-flash.c
+++ b/drivers/leds/flash/leds-qcom-flash.c
@@ -117,7 +117,7 @@ enum {
REG_MAX_COUNT,
};
-static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
+static const struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
REG_FIELD(0x08, 0, 7), /* status1 */
REG_FIELD(0x09, 0, 7), /* status2 */
REG_FIELD(0x0a, 0, 7), /* status3 */
@@ -132,7 +132,7 @@ static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
REG_FIELD(0x58, 0, 2), /* therm_thrsh3 */
};
-static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
+static const struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
REG_FIELD(0x06, 0, 7), /* status1 */
REG_FIELD(0x07, 0, 6), /* status2 */
REG_FIELD(0x09, 0, 7), /* status3 */
@@ -854,11 +854,17 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) {
flash_data->hw_type = QCOM_MVFLASH_3CH;
flash_data->max_channels = 3;
- regs = mvflash_3ch_regs;
+ regs = devm_kmemdup(dev, mvflash_3ch_regs, sizeof(mvflash_3ch_regs),
+ GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
} else if (val == FLASH_SUBTYPE_4CH_VAL) {
flash_data->hw_type = QCOM_MVFLASH_4CH;
flash_data->max_channels = 4;
- regs = mvflash_4ch_regs;
+ regs = devm_kmemdup(dev, mvflash_4ch_regs, sizeof(mvflash_4ch_regs),
+ GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val);
if (rc < 0) {
@@ -880,6 +886,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc);
return rc;
}
+ devm_kfree(dev, regs); /* devm_regmap_field_bulk_alloc() makes copies */
platform_set_drvdata(pdev, flash_data);
mutex_init(&flash_data->lock);
diff --git a/drivers/leds/flash/leds-rt8515.c b/drivers/leds/flash/leds-rt8515.c
index 6b051f182b72..6af0d2c7fc56 100644
--- a/drivers/leds/flash/leds-rt8515.c
+++ b/drivers/leds/flash/leds-rt8515.c
@@ -127,7 +127,7 @@ static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,
mod_timer(&rt->powerdown_timer,
jiffies + usecs_to_jiffies(timeout->val));
} else {
- del_timer_sync(&rt->powerdown_timer);
+ timer_delete_sync(&rt->powerdown_timer);
/* Turn the LED off */
rt8515_gpio_led_off(rt);
}
@@ -165,7 +165,7 @@ static const struct led_flash_ops rt8515_flash_ops = {
static void rt8515_powerdown_timer(struct timer_list *t)
{
- struct rt8515 *rt = from_timer(rt, t, powerdown_timer);
+ struct rt8515 *rt = timer_container_of(rt, t, powerdown_timer);
/* Turn the LED off */
rt8515_gpio_led_off(rt);
@@ -372,7 +372,7 @@ static void rt8515_remove(struct platform_device *pdev)
struct rt8515 *rt = platform_get_drvdata(pdev);
rt8515_v4l2_flash_release(rt);
- del_timer_sync(&rt->powerdown_timer);
+ timer_delete_sync(&rt->powerdown_timer);
mutex_destroy(&rt->lock);
}
diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c
index 3c01739c0b46..3e83200675f2 100644
--- a/drivers/leds/flash/leds-sgm3140.c
+++ b/drivers/leds/flash/leds-sgm3140.c
@@ -55,7 +55,7 @@ static int sgm3140_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
mod_timer(&priv->powerdown_timer,
jiffies + usecs_to_jiffies(priv->timeout));
} else {
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
gpiod_set_value_cansleep(priv->enable_gpio, 0);
gpiod_set_value_cansleep(priv->flash_gpio, 0);
ret = regulator_disable(priv->vin_regulator);
@@ -117,7 +117,7 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev,
gpiod_set_value_cansleep(priv->flash_gpio, 0);
gpiod_set_value_cansleep(priv->enable_gpio, 1);
} else {
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
gpiod_set_value_cansleep(priv->flash_gpio, 0);
gpiod_set_value_cansleep(priv->enable_gpio, 0);
ret = regulator_disable(priv->vin_regulator);
@@ -135,7 +135,7 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev,
static void sgm3140_powerdown_timer(struct timer_list *t)
{
- struct sgm3140 *priv = from_timer(priv, t, powerdown_timer);
+ struct sgm3140 *priv = timer_container_of(priv, t, powerdown_timer);
gpiod_set_value(priv->enable_gpio, 0);
gpiod_set_value(priv->flash_gpio, 0);
@@ -285,7 +285,7 @@ static void sgm3140_remove(struct platform_device *pdev)
{
struct sgm3140 *priv = platform_get_drvdata(pdev);
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
v4l2_flash_release(priv->v4l2_flash);
}
diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c
new file mode 100644
index 000000000000..6f4d4fd55361
--- /dev/null
+++ b/drivers/leds/flash/leds-tps6131x.c
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface
+ *
+ * Copyright 2025 Matthias Fend <matthias.fend@emfend.at>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define TPS6131X_REG_0 0x00
+#define TPS6131X_REG_0_RESET BIT(7)
+#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3)
+#define TPS6131X_REG_0_DCLC13_SHIFT 3
+#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0)
+#define TPS6131X_REG_0_DCLC2_SHIFT 0
+
+#define TPS6131X_REG_1 0x01
+#define TPS6131X_REG_1_MODE GENMASK(7, 6)
+#define TPS6131X_REG_1_MODE_SHIFT 6
+#define TPS6131X_REG_1_FC2 GENMASK(5, 0)
+#define TPS6131X_REG_1_FC2_SHIFT 0
+
+#define TPS6131X_REG_2 0x02
+#define TPS6131X_REG_2_MODE GENMASK(7, 6)
+#define TPS6131X_REG_2_MODE_SHIFT 6
+#define TPS6131X_REG_2_ENVM BIT(5)
+#define TPS6131X_REG_2_FC13 GENMASK(4, 0)
+#define TPS6131X_REG_2_FC13_SHIFT 0
+
+#define TPS6131X_REG_3 0x03
+#define TPS6131X_REG_3_STIM GENMASK(7, 5)
+#define TPS6131X_REG_3_STIM_SHIFT 5
+#define TPS6131X_REG_3_HPFL BIT(4)
+#define TPS6131X_REG_3_SELSTIM_TO BIT(3)
+#define TPS6131X_REG_3_STT BIT(2)
+#define TPS6131X_REG_3_SFT BIT(1)
+#define TPS6131X_REG_3_TXMASK BIT(0)
+
+#define TPS6131X_REG_4 0x04
+#define TPS6131X_REG_4_PG BIT(7)
+#define TPS6131X_REG_4_HOTDIE_HI BIT(6)
+#define TPS6131X_REG_4_HOTDIE_LO BIT(5)
+#define TPS6131X_REG_4_ILIM BIT(4)
+#define TPS6131X_REG_4_INDC GENMASK(3, 0)
+#define TPS6131X_REG_4_INDC_SHIFT 0
+
+#define TPS6131X_REG_5 0x05
+#define TPS6131X_REG_5_SELFCAL BIT(7)
+#define TPS6131X_REG_5_ENPSM BIT(6)
+#define TPS6131X_REG_5_STSTRB1_DIR BIT(5)
+#define TPS6131X_REG_5_GPIO BIT(4)
+#define TPS6131X_REG_5_GPIOTYPE BIT(3)
+#define TPS6131X_REG_5_ENLED3 BIT(2)
+#define TPS6131X_REG_5_ENLED2 BIT(1)
+#define TPS6131X_REG_5_ENLED1 BIT(0)
+
+#define TPS6131X_REG_6 0x06
+#define TPS6131X_REG_6_ENTS BIT(7)
+#define TPS6131X_REG_6_LEDHOT BIT(6)
+#define TPS6131X_REG_6_LEDWARN BIT(5)
+#define TPS6131X_REG_6_LEDHDR BIT(4)
+#define TPS6131X_REG_6_OV GENMASK(3, 0)
+#define TPS6131X_REG_6_OV_SHIFT 0
+
+#define TPS6131X_REG_7 0x07
+#define TPS6131X_REG_7_ENBATMON BIT(7)
+#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4)
+#define TPS6131X_REG_7_BATDROOP_SHIFT 4
+#define TPS6131X_REG_7_REVID GENMASK(2, 0)
+#define TPS6131X_REG_7_REVID_SHIFT 0
+
+#define TPS6131X_MAX_CHANNELS 3
+
+#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400
+#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800
+#define TPS6131X_FLASH_STEP_I_MA 25
+
+#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175
+#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175
+#define TPS6131X_TORCH_STEP_I_MA 25
+
+/* The torch watchdog timer must be refreshed within an interval of 13 seconds. */
+#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000)
+
+#define UA_TO_MA(UA) ((UA) / 1000)
+
+enum tps6131x_mode {
+ TPS6131X_MODE_SHUTDOWN = 0x0,
+ TPS6131X_MODE_TORCH = 0x1,
+ TPS6131X_MODE_FLASH = 0x2,
+};
+
+struct tps6131x {
+ struct device *dev;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ /*
+ * Registers 0, 1, 2, and 3 control parts of the controller that are not completely
+ * independent of each other. Since some operations require the registers to be written in
+ * a specific order to avoid unwanted side effects, they are synchronized with a lock.
+ */
+ struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */
+ struct delayed_work torch_refresh_work;
+ bool valley_current_limit;
+ bool chan1_en;
+ bool chan2_en;
+ bool chan3_en;
+ struct fwnode_handle *led_node;
+ u32 max_flash_current_ma;
+ u32 step_flash_current_ma;
+ u32 max_torch_current_ma;
+ u32 step_torch_current_ma;
+ u32 max_timeout_us;
+ struct led_classdev_flash fled_cdev;
+ struct v4l2_flash *v4l2_flash;
+};
+
+static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct tps6131x, fled_cdev);
+}
+
+/*
+ * Register contents after a power on/reset. These values cannot be changed.
+ */
+
+#define TPS6131X_DCLC2_50MA 2
+#define TPS6131X_DCLC13_25MA 1
+#define TPS6131X_FC2_400MA 16
+#define TPS6131X_FC13_200MA 8
+#define TPS6131X_STIM_0_579MS_1_37MS 6
+#define TPS6131X_SELSTIM_RANGE0 0
+#define TPS6131X_INDC_OFF 0
+#define TPS6131X_OV_4950MV 9
+#define TPS6131X_BATDROOP_150MV 4
+
+static const struct reg_default tps6131x_regmap_defaults[] = {
+ { TPS6131X_REG_0, (TPS6131X_DCLC13_25MA << TPS6131X_REG_0_DCLC13_SHIFT) |
+ (TPS6131X_DCLC2_50MA << TPS6131X_REG_0_DCLC2_SHIFT) },
+ { TPS6131X_REG_1, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_1_MODE_SHIFT) |
+ (TPS6131X_FC2_400MA << TPS6131X_REG_1_FC2_SHIFT) },
+ { TPS6131X_REG_2, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_2_MODE_SHIFT) |
+ (TPS6131X_FC13_200MA << TPS6131X_REG_2_FC13_SHIFT) },
+ { TPS6131X_REG_3, (TPS6131X_STIM_0_579MS_1_37MS << TPS6131X_REG_3_STIM_SHIFT) |
+ (TPS6131X_SELSTIM_RANGE0 << TPS6131X_REG_3_SELSTIM_TO) |
+ TPS6131X_REG_3_TXMASK },
+ { TPS6131X_REG_4, (TPS6131X_INDC_OFF << TPS6131X_REG_4_INDC_SHIFT) },
+ { TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR |
+ TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 },
+ { TPS6131X_REG_6, (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT) },
+ { TPS6131X_REG_7, (TPS6131X_BATDROOP_150MV << TPS6131X_REG_7_BATDROOP_SHIFT) },
+};
+
+/*
+ * These registers contain flags that are reset when read.
+ */
+static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TPS6131X_REG_3:
+ case TPS6131X_REG_4:
+ case TPS6131X_REG_6:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tps6131x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = TPS6131X_REG_7,
+ .reg_defaults = tps6131x_regmap_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults),
+ .cache_type = REGCACHE_FLAT,
+ .precious_reg = &tps6131x_regmap_precious,
+};
+
+struct tps6131x_timer_config {
+ u8 val;
+ u8 range;
+ u32 time_us;
+};
+
+static const struct tps6131x_timer_config tps6131x_timer_configs[] = {
+ { .val = 0, .range = 1, .time_us = 5300 },
+ { .val = 1, .range = 1, .time_us = 10700 },
+ { .val = 2, .range = 1, .time_us = 16000 },
+ { .val = 3, .range = 1, .time_us = 21300 },
+ { .val = 4, .range = 1, .time_us = 26600 },
+ { .val = 5, .range = 1, .time_us = 32000 },
+ { .val = 6, .range = 1, .time_us = 37300 },
+ { .val = 0, .range = 0, .time_us = 68200 },
+ { .val = 7, .range = 1, .time_us = 71500 },
+ { .val = 1, .range = 0, .time_us = 102200 },
+ { .val = 2, .range = 0, .time_us = 136300 },
+ { .val = 3, .range = 0, .time_us = 170400 },
+ { .val = 4, .range = 0, .time_us = 204500 },
+ { .val = 5, .range = 0, .time_us = 340800 },
+ { .val = 6, .range = 0, .time_us = 579300 },
+ { .val = 7, .range = 0, .time_us = 852000 },
+};
+
+static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us)
+{
+ const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0];
+ u32 diff, min_diff = U32_MAX;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) {
+ diff = abs(tps6131x_timer_configs[i].time_us - timeout_us);
+ if (diff < min_diff) {
+ timer_config = &tps6131x_timer_configs[i];
+ min_diff = diff;
+ if (!min_diff)
+ break;
+ }
+ }
+
+ return timer_config;
+}
+
+static int tps6131x_reset_chip(struct tps6131x *tps6131x)
+{
+ int ret;
+
+ if (tps6131x->reset_gpio) {
+ gpiod_set_value_cansleep(tps6131x->reset_gpio, 1);
+ fsleep(10);
+ gpiod_set_value_cansleep(tps6131x->reset_gpio, 0);
+ fsleep(100);
+ } else {
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET,
+ TPS6131X_REG_0_RESET);
+ if (ret)
+ return ret;
+
+ fsleep(100);
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps6131x_init_chip(struct tps6131x *tps6131x)
+{
+ u32 val;
+ int ret;
+
+ val = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, val);
+ if (ret)
+ return ret;
+
+ val = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE;
+
+ if (tps6131x->chan1_en)
+ val |= TPS6131X_REG_5_ENLED1;
+
+ if (tps6131x->chan2_en)
+ val |= TPS6131X_REG_5_ENLED2;
+
+ if (tps6131x->chan3_en)
+ val |= TPS6131X_REG_5_ENLED3;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, val);
+ if (ret)
+ return ret;
+
+ val = TPS6131X_REG_6_ENTS;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force)
+{
+ u8 val = mode << TPS6131X_REG_1_MODE_SHIFT;
+
+ return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val,
+ NULL, false, force);
+}
+
+static void tps6131x_torch_refresh_handler(struct work_struct *work)
+{
+ struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work);
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true);
+ if (ret < 0) {
+ dev_err(tps6131x->dev, "Failed to refresh torch watchdog timer\n");
+ return;
+ }
+
+ schedule_delayed_work(&tps6131x->torch_refresh_work,
+ TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
+}
+
+static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u32 num_chans, steps_chan13, steps_chan2, steps_remaining;
+ u8 reg0;
+ int ret;
+
+ cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
+
+ /*
+ * The brightness parameter uses the number of current steps as the unit (not the current
+ * value itself). Since the reported step size can vary depending on the configuration,
+ * this value must be converted into actual register steps.
+ */
+ steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA;
+
+ num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
+
+ /*
+ * The currents are distributed as evenly as possible across the activated channels.
+ * Since channels 1 and 3 share the same register setting, they always use the same current
+ * value. Channel 2 supports higher currents and thus takes over the remaining additional
+ * portion that cannot be covered by the other channels.
+ */
+ steps_chan13 = min_t(u32, steps_remaining / num_chans,
+ TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA);
+ if (tps6131x->chan1_en)
+ steps_remaining -= steps_chan13;
+ if (tps6131x->chan3_en)
+ steps_remaining -= steps_chan13;
+
+ steps_chan2 = min_t(u32, steps_remaining,
+ TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA);
+
+ guard(mutex)(&tps6131x->lock);
+
+ reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) |
+ (steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT);
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0,
+ TPS6131X_REG_0_DCLC13 | TPS6131X_REG_0_DCLC2, reg0);
+ if (ret < 0)
+ return ret;
+
+ ret = tps6131x_set_mode(tps6131x, brightness ? TPS6131X_MODE_TORCH : TPS6131X_MODE_SHUTDOWN,
+ true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * In order to use both the flash and the video light functions purely via the I2C
+ * interface, STRB1 must be low. If STRB1 is low, then the video light watchdog timer
+ * is also active, which puts the device into the shutdown state after around 13 seconds.
+ * To prevent this, the mode must be refreshed within the watchdog timeout.
+ */
+ if (brightness)
+ schedule_delayed_work(&tps6131x->torch_refresh_work,
+ TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
+
+ return 0;
+}
+
+static int tps6131x_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = tps6131x_set_mode(tps6131x, state ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
+ true);
+ if (ret < 0)
+ return ret;
+
+ if (state) {
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT,
+ TPS6131X_REG_3_SFT, NULL, false, true);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, 0, NULL,
+ false, true);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u32 num_chans;
+ u32 steps_chan13, steps_chan2;
+ u32 steps_remaining;
+ int ret;
+
+ steps_remaining = brightness / TPS6131X_FLASH_STEP_I_MA;
+ num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
+ steps_chan13 = min_t(u32, steps_remaining / num_chans,
+ TPS6131X_FLASH_MAX_I_CHAN13_MA / TPS6131X_FLASH_STEP_I_MA);
+ if (tps6131x->chan1_en)
+ steps_remaining -= steps_chan13;
+ if (tps6131x->chan3_en)
+ steps_remaining -= steps_chan13;
+ steps_chan2 = min_t(u32, steps_remaining,
+ TPS6131X_FLASH_MAX_I_CHAN2_MA / TPS6131X_FLASH_STEP_I_MA);
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_2, TPS6131X_REG_2_FC13,
+ steps_chan13 << TPS6131X_REG_2_FC13_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_FC2,
+ steps_chan2 << TPS6131X_REG_1_FC2_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ fled_cdev->brightness.val = brightness;
+
+ return 0;
+}
+
+static int tps6131x_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout_us)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u8 reg3;
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ timer_config = tps6131x_find_closest_timer_config(timeout_us);
+
+ reg3 = timer_config->val << TPS6131X_REG_3_STIM_SHIFT;
+ if (timer_config->range)
+ reg3 |= TPS6131X_REG_3_SELSTIM_TO;
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_3,
+ TPS6131X_REG_3_STIM | TPS6131X_REG_3_SELSTIM_TO, reg3);
+ if (ret < 0)
+ return ret;
+
+ fled_cdev->timeout.val = timer_config->time_us;
+
+ return 0;
+}
+
+static int tps6131x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ unsigned int reg3;
+ int ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, &reg3);
+ if (ret)
+ return ret;
+
+ *state = !!(reg3 & TPS6131X_REG_3_SFT);
+
+ return 0;
+}
+
+static int tps6131x_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ unsigned int reg3, reg4, reg6;
+ int ret;
+
+ *fault = 0;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, &reg3);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_4, &reg4);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_6, &reg6);
+ if (ret < 0)
+ return ret;
+
+ if (reg3 & TPS6131X_REG_3_HPFL)
+ *fault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (reg3 & TPS6131X_REG_3_SELSTIM_TO)
+ *fault |= LED_FAULT_TIMEOUT;
+
+ if (reg4 & TPS6131X_REG_4_HOTDIE_HI)
+ *fault |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (reg6 & (TPS6131X_REG_6_LEDHOT | TPS6131X_REG_6_LEDWARN))
+ *fault |= LED_FAULT_LED_OVER_TEMPERATURE;
+
+ if (!(reg6 & TPS6131X_REG_6_LEDHDR))
+ *fault |= LED_FAULT_UNDER_VOLTAGE;
+
+ if (reg6 & TPS6131X_REG_6_LEDHOT) {
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_6,
+ TPS6131X_REG_6_LEDHOT, 0, NULL, false, true);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = tps6131x_flash_brightness_set,
+ .strobe_set = tps6131x_strobe_set,
+ .strobe_get = tps6131x_strobe_get,
+ .timeout_set = tps6131x_flash_timeout_set,
+ .fault_get = tps6131x_flash_fault_get,
+};
+
+static int tps6131x_parse_node(struct tps6131x *tps6131x)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct device *dev = tps6131x->dev;
+ u32 channels[TPS6131X_MAX_CHANNELS];
+ u32 current_step_multiplier;
+ u32 current_ua;
+ u32 max_current_flash_ma, max_current_torch_ma;
+ u32 timeout_us;
+ int num_channels;
+ int i;
+ int ret;
+
+ tps6131x->valley_current_limit = device_property_read_bool(dev, "ti,valley-current-limit");
+
+ tps6131x->led_node = fwnode_get_next_available_child_node(dev->fwnode, NULL);
+ if (!tps6131x->led_node) {
+ dev_err(dev, "Missing LED node\n");
+ return -EINVAL;
+ }
+
+ num_channels = fwnode_property_count_u32(tps6131x->led_node, "led-sources");
+ if (num_channels <= 0) {
+ dev_err(dev, "Failed to read led-sources property\n");
+ return -EINVAL;
+ }
+
+ if (num_channels > TPS6131X_MAX_CHANNELS) {
+ dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
+ num_channels, TPS6131X_MAX_CHANNELS);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32_array(tps6131x->led_node, "led-sources", channels,
+ num_channels);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read led-sources property\n");
+ return ret;
+ }
+
+ max_current_flash_ma = 0;
+ max_current_torch_ma = 0;
+ for (i = 0; i < num_channels; i++) {
+ switch (channels[i]) {
+ case 1:
+ tps6131x->chan1_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
+ break;
+ case 2:
+ tps6131x->chan2_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN2_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN2_MA;
+ break;
+ case 3:
+ tps6131x->chan3_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
+ break;
+ default:
+ dev_err(dev, "led-source out of range [1-3]\n");
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * If only channels 1 and 3 are used, the step size is doubled because the two channels
+ * share the same current control register.
+ */
+ current_step_multiplier =
+ (tps6131x->chan1_en && tps6131x->chan3_en && !tps6131x->chan2_en) ? 2 : 1;
+ tps6131x->step_flash_current_ma = current_step_multiplier * TPS6131X_FLASH_STEP_I_MA;
+ tps6131x->step_torch_current_ma = current_step_multiplier * TPS6131X_TORCH_STEP_I_MA;
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "led-max-microamp", &current_ua);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read led-max-microamp property\n");
+ return ret;
+ }
+
+ tps6131x->max_torch_current_ma = UA_TO_MA(current_ua);
+
+ if (!tps6131x->max_torch_current_ma ||
+ tps6131x->max_torch_current_ma > max_current_torch_ma ||
+ (tps6131x->max_torch_current_ma % tps6131x->step_torch_current_ma)) {
+ dev_err(dev, "led-max-microamp out of range or not a multiple of %u\n",
+ tps6131x->step_torch_current_ma);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-microamp", &current_ua);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read flash-max-microamp property\n");
+ return ret;
+ }
+
+ tps6131x->max_flash_current_ma = UA_TO_MA(current_ua);
+
+ if (!tps6131x->max_flash_current_ma ||
+ tps6131x->max_flash_current_ma > max_current_flash_ma ||
+ (tps6131x->max_flash_current_ma % tps6131x->step_flash_current_ma)) {
+ dev_err(dev, "flash-max-microamp out of range or not a multiple of %u\n",
+ tps6131x->step_flash_current_ma);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-timeout-us", &timeout_us);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read flash-max-timeout-us property\n");
+ return ret;
+ }
+
+ timer_config = tps6131x_find_closest_timer_config(timeout_us);
+ tps6131x->max_timeout_us = timer_config->time_us;
+
+ if (tps6131x->max_timeout_us != timeout_us)
+ dev_warn(dev, "flash-max-timeout-us %u not supported (using %u)\n", timeout_us,
+ tps6131x->max_timeout_us);
+
+ return 0;
+}
+
+static int tps6131x_led_class_setup(struct tps6131x *tps6131x)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct led_classdev *led_cdev;
+ struct led_flash_setting *setting;
+ struct led_init_data init_data = {};
+ int ret;
+
+ tps6131x->fled_cdev.ops = &flash_ops;
+
+ setting = &tps6131x->fled_cdev.timeout;
+ timer_config = tps6131x_find_closest_timer_config(0);
+ setting->min = timer_config->time_us;
+ setting->max = tps6131x->max_timeout_us;
+ setting->step = 1; /* Only some specific time periods are supported. No fixed step size. */
+ setting->val = setting->min;
+
+ setting = &tps6131x->fled_cdev.brightness;
+ setting->min = tps6131x->step_flash_current_ma;
+ setting->max = tps6131x->max_flash_current_ma;
+ setting->step = tps6131x->step_flash_current_ma;
+ setting->val = setting->min;
+
+ led_cdev = &tps6131x->fled_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = tps6131x_brightness_set;
+ led_cdev->max_brightness = tps6131x->max_torch_current_ma;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ init_data.fwnode = tps6131x->led_node;
+ init_data.devicename = NULL;
+ init_data.default_label = NULL;
+ init_data.devname_mandatory = false;
+
+ ret = devm_led_classdev_flash_register_ext(tps6131x->dev, &tps6131x->fled_cdev,
+ &init_data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+
+ guard(mutex)(&tps6131x->lock);
+
+ return tps6131x_set_mode(tps6131x, enable ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
+ false);
+}
+
+static const struct v4l2_flash_ops tps6131x_v4l2_flash_ops = {
+ .external_strobe_set = tps6131x_flash_external_strobe_set,
+};
+
+static int tps6131x_v4l2_setup(struct tps6131x *tps6131x)
+{
+ struct v4l2_flash_config v4l2_cfg = { 0 };
+ struct led_flash_setting *intensity = &v4l2_cfg.intensity;
+
+ intensity->min = tps6131x->step_torch_current_ma;
+ intensity->max = tps6131x->max_torch_current_ma;
+ intensity->step = tps6131x->step_torch_current_ma;
+ intensity->val = intensity->min;
+
+ strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name,
+ sizeof(v4l2_cfg.dev_name));
+
+ v4l2_cfg.has_external_strobe = true;
+ v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE |
+ LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE |
+ LED_FAULT_LED_OVER_TEMPERATURE;
+
+ tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node,
+ &tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops,
+ &v4l2_cfg);
+ if (IS_ERR(tps6131x->v4l2_flash)) {
+ dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n");
+ return PTR_ERR(tps6131x->v4l2_flash);
+ }
+
+ return 0;
+}
+
+static int tps6131x_probe(struct i2c_client *client)
+{
+ struct tps6131x *tps6131x;
+ int ret;
+
+ tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL);
+ if (!tps6131x)
+ return -ENOMEM;
+
+ tps6131x->dev = &client->dev;
+ i2c_set_clientdata(client, tps6131x);
+ mutex_init(&tps6131x->lock);
+ INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler);
+
+ ret = tps6131x_parse_node(tps6131x);
+ if (ret)
+ return ret;
+
+ tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap);
+ if (IS_ERR(tps6131x->regmap)) {
+ ret = PTR_ERR(tps6131x->regmap);
+ return dev_err_probe(&client->dev, ret, "Failed to allocate register map\n");
+ }
+
+ tps6131x->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(tps6131x->reset_gpio)) {
+ ret = PTR_ERR(tps6131x->reset_gpio);
+ return dev_err_probe(&client->dev, ret, "Failed to get reset GPIO\n");
+ }
+
+ ret = tps6131x_reset_chip(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to reset LED controller\n");
+
+ ret = tps6131x_init_chip(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to initialize LED controller\n");
+
+ ret = tps6131x_led_class_setup(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to setup LED class\n");
+
+ ret = tps6131x_v4l2_setup(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to setup v4l2 flash\n");
+
+ return 0;
+}
+
+static void tps6131x_remove(struct i2c_client *client)
+{
+ struct tps6131x *tps6131x = i2c_get_clientdata(client);
+
+ v4l2_flash_release(tps6131x->v4l2_flash);
+
+ cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
+}
+
+static const struct of_device_id of_tps6131x_leds_match[] = {
+ { .compatible = "ti,tps61310" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_tps6131x_leds_match);
+
+static struct i2c_driver tps6131x_i2c_driver = {
+ .driver = {
+ .name = "tps6131x",
+ .of_match_table = of_tps6131x_leds_match,
+ },
+ .probe = tps6131x_probe,
+ .remove = tps6131x_remove,
+};
+module_i2c_driver(tps6131x_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments TPS6131X flash LED driver");
+MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
index f4e26ce84862..165035a8826c 100644
--- a/drivers/leds/led-class-flash.c
+++ b/drivers/leds/led-class-flash.c
@@ -440,6 +440,21 @@ int led_update_flash_brightness(struct led_classdev_flash *fled_cdev)
}
EXPORT_SYMBOL_GPL(led_update_flash_brightness);
+int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration)
+{
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *s = &fled_cdev->duration;
+
+ s->val = duration;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(fled_cdev, duration_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_duration);
+
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
MODULE_DESCRIPTION("LED Flash class interface");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
index b2a87c994816..fd66d2bdeace 100644
--- a/drivers/leds/led-class-multicolor.c
+++ b/drivers/leds/led-class-multicolor.c
@@ -59,7 +59,8 @@ static ssize_t multi_intensity_store(struct device *dev,
for (i = 0; i < mcled_cdev->num_colors; i++)
mcled_cdev->subled_info[i].intensity = intensity_value[i];
- led_set_brightness(led_cdev, led_cdev->brightness);
+ if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
+ led_set_brightness(led_cdev, led_cdev->brightness);
ret = size;
err_out:
mutex_unlock(&led_cdev->led_access);
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index e3d8ddcff567..59473f286b31 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -64,7 +64,8 @@ static int __led_set_brightness_blocking(struct led_classdev *led_cdev, unsigned
static void led_timer_function(struct timer_list *t)
{
- struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer);
+ struct led_classdev *led_cdev = timer_container_of(led_cdev, t,
+ blink_timer);
unsigned long brightness;
unsigned long delay;
@@ -245,7 +246,7 @@ void led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
- del_timer_sync(&led_cdev->blink_timer);
+ timer_delete_sync(&led_cdev->blink_timer);
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
@@ -294,7 +295,7 @@ EXPORT_SYMBOL_GPL(led_blink_set_nosleep);
void led_stop_software_blink(struct led_classdev *led_cdev)
{
- del_timer_sync(&led_cdev->blink_timer);
+ timer_delete_sync(&led_cdev->blink_timer);
led_cdev->blink_delay_on = 0;
led_cdev->blink_delay_off = 0;
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
@@ -529,6 +530,7 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
struct led_properties props = {};
struct fwnode_handle *fwnode = init_data->fwnode;
const char *devicename = init_data->devicename;
+ int n;
if (!led_classdev_name)
return -EINVAL;
@@ -542,45 +544,49 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
* Otherwise the label is prepended with devicename to compose
* the final LED class device name.
*/
- if (!devicename) {
- strscpy(led_classdev_name, props.label,
- LED_MAX_NAME_SIZE);
+ if (devicename) {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, props.label);
} else {
- snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
- devicename, props.label);
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", props.label);
}
} else if (props.function || props.color_present) {
char tmp_buf[LED_MAX_NAME_SIZE];
if (props.func_enum_present) {
- snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
- props.color_present ? led_colors[props.color] : "",
- props.function ?: "", props.func_enum);
+ n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
+ props.color_present ? led_colors[props.color] : "",
+ props.function ?: "", props.func_enum);
} else {
- snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
- props.color_present ? led_colors[props.color] : "",
- props.function ?: "");
+ n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
+ props.color_present ? led_colors[props.color] : "",
+ props.function ?: "");
}
+ if (n >= LED_MAX_NAME_SIZE)
+ return -E2BIG;
+
if (init_data->devname_mandatory) {
- snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
- devicename, tmp_buf);
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, tmp_buf);
} else {
- strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE);
-
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", tmp_buf);
}
} else if (init_data->default_label) {
if (!devicename) {
dev_err(dev, "Legacy LED naming requires devicename segment");
return -EINVAL;
}
- snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
- devicename, init_data->default_label);
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, init_data->default_label);
} else if (is_of_node(fwnode)) {
- strscpy(led_classdev_name, to_of_node(fwnode)->name,
- LED_MAX_NAME_SIZE);
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s",
+ to_of_node(fwnode)->name);
} else
return -EINVAL;
+ if (n >= LED_MAX_NAME_SIZE)
+ return -E2BIG;
+
return 0;
}
EXPORT_SYMBOL_GPL(led_compose_name);
diff --git a/drivers/leds/led-test.c b/drivers/leds/led-test.c
new file mode 100644
index 000000000000..ddf9aa967a6a
--- /dev/null
+++ b/drivers/leds/led-test.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Google LLC
+ *
+ * Author: Lee Jones <lee@kernel.org>
+ */
+
+#include <kunit/device.h>
+#include <kunit/test.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
+#define LED_TEST_POST_REG_BRIGHTNESS 10
+
+struct led_test_ddata {
+ struct led_classdev cdev;
+ struct device *dev;
+};
+
+static enum led_brightness led_test_brightness_get(struct led_classdev *cdev)
+{
+ return LED_TEST_POST_REG_BRIGHTNESS;
+}
+
+static void led_test_class_register(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+ struct led_classdev *cdev_clash, *cdev = &ddata->cdev;
+ struct device *dev = ddata->dev;
+ int ret;
+
+ /* Register a LED class device */
+ cdev->name = "led-test";
+ cdev->brightness_get = led_test_brightness_get;
+ cdev->brightness = 0;
+
+ ret = devm_led_classdev_register(dev, cdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, cdev->max_brightness, LED_FULL);
+ KUNIT_EXPECT_EQ(test, cdev->brightness, LED_TEST_POST_REG_BRIGHTNESS);
+ KUNIT_EXPECT_STREQ(test, cdev->dev->kobj.name, "led-test");
+
+ /* Register again with the same name - expect it to pass with the LED renamed */
+ cdev_clash = devm_kmemdup(dev, cdev, sizeof(*cdev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cdev_clash);
+
+ ret = devm_led_classdev_register(dev, cdev_clash);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_STREQ(test, cdev_clash->dev->kobj.name, "led-test_1");
+ KUNIT_EXPECT_STREQ(test, cdev_clash->name, "led-test");
+
+ /* Enable name conflict rejection and register with the same name again - expect failure */
+ cdev_clash->flags |= LED_REJECT_NAME_CONFLICT;
+ ret = devm_led_classdev_register(dev, cdev_clash);
+ KUNIT_EXPECT_EQ(test, ret, -EEXIST);
+}
+
+static void led_test_class_add_lookup_and_get(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+ struct led_classdev *cdev = &ddata->cdev, *cdev_get;
+ struct device *dev = ddata->dev;
+ struct led_lookup_data lookup;
+ int ret;
+
+ /* First, register a LED class device */
+ cdev->name = "led-test";
+ ret = devm_led_classdev_register(dev, cdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* Then make the LED available for lookup */
+ lookup.provider = cdev->name;
+ lookup.dev_id = dev_name(dev);
+ lookup.con_id = "led-test-1";
+ led_add_lookup(&lookup);
+
+ /* Finally, attempt to look it up via the API - imagine this was an orthogonal driver */
+ cdev_get = devm_led_get(dev, "led-test-1");
+ KUNIT_ASSERT_FALSE(test, IS_ERR(cdev_get));
+
+ KUNIT_EXPECT_STREQ(test, cdev_get->name, cdev->name);
+
+ led_remove_lookup(&lookup);
+}
+
+static struct kunit_case led_test_cases[] = {
+ KUNIT_CASE(led_test_class_register),
+ KUNIT_CASE(led_test_class_add_lookup_and_get),
+ { }
+};
+
+static int led_test_init(struct kunit *test)
+{
+ struct led_test_ddata *ddata;
+ struct device *dev;
+
+ ddata = kunit_kzalloc(test, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ test->priv = ddata;
+
+ dev = kunit_device_register(test, "led_test");
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ ddata->dev = get_device(dev);
+
+ return 0;
+}
+
+static void led_test_exit(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+
+ if (ddata && ddata->dev)
+ put_device(ddata->dev);
+}
+
+static struct kunit_suite led_test_suite = {
+ .name = "led",
+ .init = led_test_init,
+ .exit = led_test_exit,
+ .test_cases = led_test_cases,
+};
+kunit_test_suite(led_test_suite);
+
+MODULE_AUTHOR("Lee Jones <lee@kernel.org>");
+MODULE_DESCRIPTION("KUnit tests for the LED framework");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index b2d40f87a5ff..3799dcc1cf07 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -54,6 +54,11 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
goto unlock;
}
+ if (sysfs_streq(buf, "default")) {
+ led_trigger_set_default(led_cdev);
+ goto unlock;
+ }
+
down_read(&triggers_list_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) {
@@ -98,6 +103,9 @@ static int led_trigger_format(char *buf, size_t size,
int len = led_trigger_snprintf(buf, size, "%s",
led_cdev->trigger ? "none" : "[none]");
+ if (led_cdev->default_trigger)
+ len += led_trigger_snprintf(buf + len, size - len, " default");
+
list_for_each_entry(trig, &trigger_list, next_trig) {
bool hit;
@@ -281,6 +289,11 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
if (!led_cdev->default_trigger)
return;
+ if (!strcmp(led_cdev->default_trigger, "none")) {
+ led_trigger_remove(led_cdev);
+ return;
+ }
+
down_read(&triggers_list_lock);
down_write(&led_cdev->trigger_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c
index 08cca128458c..fe223d363a5d 100644
--- a/drivers/leds/leds-aw200xx.c
+++ b/drivers/leds/leds-aw200xx.c
@@ -379,7 +379,7 @@ static void aw200xx_enable(const struct aw200xx *const chip)
static void aw200xx_disable(const struct aw200xx *const chip)
{
- return gpiod_set_value_cansleep(chip->hwen, 0);
+ gpiod_set_value_cansleep(chip->hwen, 0);
}
static int aw200xx_probe_get_display_rows(struct device *dev,
diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
index 275522b81ea5..377cf04e202a 100644
--- a/drivers/leds/leds-cros_ec.c
+++ b/drivers/leds/leds-cros_ec.c
@@ -60,31 +60,18 @@ static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_class
union cros_ec_led_cmd_data {
struct ec_params_led_control req;
struct ec_response_led_control resp;
-} __packed;
+};
static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec,
union cros_ec_led_cmd_data *arg)
{
int ret;
- struct {
- struct cros_ec_command msg;
- union cros_ec_led_cmd_data data;
- } __packed buf = {
- .msg = {
- .version = 1,
- .command = EC_CMD_LED_CONTROL,
- .insize = sizeof(arg->resp),
- .outsize = sizeof(arg->req),
- },
- .data.req = arg->req
- };
-
- ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
+
+ ret = cros_ec_cmd(cros_ec, 1, EC_CMD_LED_CONTROL, &arg->req,
+ sizeof(arg->req), &arg->resp, sizeof(arg->resp));
if (ret < 0)
return ret;
- arg->resp = buf.data.resp;
-
return 0;
}
diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
index 02cb1565a9fb..94f8ef6b482c 100644
--- a/drivers/leds/leds-lp50xx.c
+++ b/drivers/leds/leds-lp50xx.c
@@ -476,6 +476,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
return -ENOMEM;
fwnode_for_each_child_node(child, led_node) {
+ int multi_index;
ret = fwnode_property_read_u32(led_node, "color",
&color_id);
if (ret) {
@@ -483,8 +484,16 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
dev_err(priv->dev, "Cannot read color\n");
return ret;
}
+ ret = fwnode_property_read_u32(led_node, "reg", &multi_index);
+ if (ret != 0) {
+ dev_err(priv->dev, "reg must be set\n");
+ return -EINVAL;
+ } else if (multi_index >= LP50XX_LEDS_PER_MODULE) {
+ dev_err(priv->dev, "reg %i out of range\n", multi_index);
+ return -EINVAL;
+ }
- mc_led_info[num_colors].color_index = color_id;
+ mc_led_info[multi_index].color_index = color_id;
num_colors++;
}
diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c
index 06196d851ade..0962c00c215a 100644
--- a/drivers/leds/leds-lp8860.c
+++ b/drivers/leds/leds-lp8860.c
@@ -90,8 +90,6 @@
* @led_dev: led class device pointer
* @regmap: Devices register map
* @eeprom_regmap: EEPROM register map
- * @enable_gpio: VDDIO/EN gpio to enable communication interface
- * @regulator: LED supply regulator pointer
*/
struct lp8860_led {
struct mutex lock;
@@ -99,16 +97,9 @@ struct lp8860_led {
struct led_classdev led_dev;
struct regmap *regmap;
struct regmap *eeprom_regmap;
- struct gpio_desc *enable_gpio;
- struct regulator *regulator;
-};
-
-struct lp8860_eeprom_reg {
- uint8_t reg;
- uint8_t value;
};
-static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = {
+static const struct reg_sequence lp8860_eeprom_disp_regs[] = {
{ LP8860_EEPROM_REG_0, 0xed },
{ LP8860_EEPROM_REG_1, 0xdf },
{ LP8860_EEPROM_REG_2, 0xdc },
@@ -136,43 +127,29 @@ static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = {
{ LP8860_EEPROM_REG_24, 0x3E },
};
-static int lp8860_unlock_eeprom(struct lp8860_led *led, int lock)
+static int lp8860_unlock_eeprom(struct lp8860_led *led)
{
int ret;
- mutex_lock(&led->lock);
-
- if (lock == LP8860_UNLOCK_EEPROM) {
- ret = regmap_write(led->regmap,
- LP8860_EEPROM_UNLOCK,
- LP8860_EEPROM_CODE_1);
- if (ret) {
- dev_err(&led->client->dev, "EEPROM Unlock failed\n");
- goto out;
- }
-
- ret = regmap_write(led->regmap,
- LP8860_EEPROM_UNLOCK,
- LP8860_EEPROM_CODE_2);
- if (ret) {
- dev_err(&led->client->dev, "EEPROM Unlock failed\n");
- goto out;
- }
- ret = regmap_write(led->regmap,
- LP8860_EEPROM_UNLOCK,
- LP8860_EEPROM_CODE_3);
- if (ret) {
- dev_err(&led->client->dev, "EEPROM Unlock failed\n");
- goto out;
- }
- } else {
- ret = regmap_write(led->regmap,
- LP8860_EEPROM_UNLOCK,
- LP8860_LOCK_EEPROM);
+ guard(mutex)(&led->lock);
+
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_1);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
+ }
+
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_2);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
+ }
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_3);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
}
-out:
- mutex_unlock(&led->lock);
return ret;
}
@@ -209,47 +186,35 @@ static int lp8860_brightness_set(struct led_classdev *led_cdev,
int disp_brightness = brt_val * 255;
int ret;
- mutex_lock(&led->lock);
+ guard(mutex)(&led->lock);
ret = lp8860_fault_check(led);
if (ret) {
dev_err(&led->client->dev, "Cannot read/clear faults\n");
- goto out;
+ return ret;
}
ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_MSB,
(disp_brightness & 0xff00) >> 8);
if (ret) {
dev_err(&led->client->dev, "Cannot write CL1 MSB\n");
- goto out;
+ return ret;
}
ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_LSB,
disp_brightness & 0xff);
if (ret) {
dev_err(&led->client->dev, "Cannot write CL1 LSB\n");
- goto out;
+ return ret;
}
-out:
- mutex_unlock(&led->lock);
- return ret;
+
+ return 0;
}
static int lp8860_init(struct lp8860_led *led)
{
unsigned int read_buf;
- int ret, i, reg_count;
-
- if (led->regulator) {
- ret = regulator_enable(led->regulator);
- if (ret) {
- dev_err(&led->client->dev,
- "Failed to enable regulator\n");
- return ret;
- }
- }
-
- gpiod_direction_output(led->enable_gpio, 1);
+ int ret, reg_count;
ret = lp8860_fault_check(led);
if (ret)
@@ -259,24 +224,20 @@ static int lp8860_init(struct lp8860_led *led)
if (ret)
goto out;
- ret = lp8860_unlock_eeprom(led, LP8860_UNLOCK_EEPROM);
+ ret = lp8860_unlock_eeprom(led);
if (ret) {
dev_err(&led->client->dev, "Failed unlocking EEPROM\n");
goto out;
}
reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs);
- for (i = 0; i < reg_count; i++) {
- ret = regmap_write(led->eeprom_regmap,
- lp8860_eeprom_disp_regs[i].reg,
- lp8860_eeprom_disp_regs[i].value);
- if (ret) {
- dev_err(&led->client->dev, "Failed writing EEPROM\n");
- goto out;
- }
+ ret = regmap_multi_reg_write(led->eeprom_regmap, lp8860_eeprom_disp_regs, reg_count);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed writing EEPROM\n");
+ goto out;
}
- ret = lp8860_unlock_eeprom(led, LP8860_LOCK_EEPROM);
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_LOCK_EEPROM);
if (ret)
goto out;
@@ -291,75 +252,14 @@ static int lp8860_init(struct lp8860_led *led)
return ret;
out:
- if (ret)
- gpiod_direction_output(led->enable_gpio, 0);
-
- if (led->regulator) {
- ret = regulator_disable(led->regulator);
- if (ret)
- dev_err(&led->client->dev,
- "Failed to disable regulator\n");
- }
-
return ret;
}
-static const struct reg_default lp8860_reg_defs[] = {
- { LP8860_DISP_CL1_BRT_MSB, 0x00},
- { LP8860_DISP_CL1_BRT_LSB, 0x00},
- { LP8860_DISP_CL1_CURR_MSB, 0x00},
- { LP8860_DISP_CL1_CURR_LSB, 0x00},
- { LP8860_CL2_BRT_MSB, 0x00},
- { LP8860_CL2_BRT_LSB, 0x00},
- { LP8860_CL2_CURRENT, 0x00},
- { LP8860_CL3_BRT_MSB, 0x00},
- { LP8860_CL3_BRT_LSB, 0x00},
- { LP8860_CL3_CURRENT, 0x00},
- { LP8860_CL4_BRT_MSB, 0x00},
- { LP8860_CL4_BRT_LSB, 0x00},
- { LP8860_CL4_CURRENT, 0x00},
- { LP8860_CONFIG, 0x00},
- { LP8860_FAULT_CLEAR, 0x00},
- { LP8860_EEPROM_CNTRL, 0x80},
- { LP8860_EEPROM_UNLOCK, 0x00},
-};
-
static const struct regmap_config lp8860_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = LP8860_EEPROM_UNLOCK,
- .reg_defaults = lp8860_reg_defs,
- .num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs),
- .cache_type = REGCACHE_NONE,
-};
-
-static const struct reg_default lp8860_eeprom_defs[] = {
- { LP8860_EEPROM_REG_0, 0x00 },
- { LP8860_EEPROM_REG_1, 0x00 },
- { LP8860_EEPROM_REG_2, 0x00 },
- { LP8860_EEPROM_REG_3, 0x00 },
- { LP8860_EEPROM_REG_4, 0x00 },
- { LP8860_EEPROM_REG_5, 0x00 },
- { LP8860_EEPROM_REG_6, 0x00 },
- { LP8860_EEPROM_REG_7, 0x00 },
- { LP8860_EEPROM_REG_8, 0x00 },
- { LP8860_EEPROM_REG_9, 0x00 },
- { LP8860_EEPROM_REG_10, 0x00 },
- { LP8860_EEPROM_REG_11, 0x00 },
- { LP8860_EEPROM_REG_12, 0x00 },
- { LP8860_EEPROM_REG_13, 0x00 },
- { LP8860_EEPROM_REG_14, 0x00 },
- { LP8860_EEPROM_REG_15, 0x00 },
- { LP8860_EEPROM_REG_16, 0x00 },
- { LP8860_EEPROM_REG_17, 0x00 },
- { LP8860_EEPROM_REG_18, 0x00 },
- { LP8860_EEPROM_REG_19, 0x00 },
- { LP8860_EEPROM_REG_20, 0x00 },
- { LP8860_EEPROM_REG_21, 0x00 },
- { LP8860_EEPROM_REG_22, 0x00 },
- { LP8860_EEPROM_REG_23, 0x00 },
- { LP8860_EEPROM_REG_24, 0x00 },
};
static const struct regmap_config lp8860_eeprom_regmap_config = {
@@ -367,11 +267,15 @@ static const struct regmap_config lp8860_eeprom_regmap_config = {
.val_bits = 8,
.max_register = LP8860_EEPROM_REG_24,
- .reg_defaults = lp8860_eeprom_defs,
- .num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs),
- .cache_type = REGCACHE_NONE,
};
+static void lp8860_disable_gpio(void *data)
+{
+ struct gpio_desc *gpio = data;
+
+ gpiod_set_value(gpio, 0);
+}
+
static int lp8860_probe(struct i2c_client *client)
{
int ret;
@@ -379,6 +283,7 @@ static int lp8860_probe(struct i2c_client *client)
struct device_node *np = dev_of_node(&client->dev);
struct device_node *child_node;
struct led_init_data init_data = {};
+ struct gpio_desc *enable_gpio;
led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
if (!led)
@@ -388,24 +293,23 @@ static int lp8860_probe(struct i2c_client *client)
if (!child_node)
return -EINVAL;
- led->enable_gpio = devm_gpiod_get_optional(&client->dev,
- "enable", GPIOD_OUT_LOW);
- if (IS_ERR(led->enable_gpio)) {
- ret = PTR_ERR(led->enable_gpio);
- dev_err(&client->dev, "Failed to get enable gpio: %d\n", ret);
- return ret;
- }
+ enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(enable_gpio))
+ return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
+ "Failed to get enable GPIO\n");
+ devm_add_action_or_reset(&client->dev, lp8860_disable_gpio, enable_gpio);
- led->regulator = devm_regulator_get(&client->dev, "vled");
- if (IS_ERR(led->regulator))
- led->regulator = NULL;
+ ret = devm_regulator_get_enable_optional(&client->dev, "vled");
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to enable vled regulator\n");
led->client = client;
led->led_dev.brightness_set_blocking = lp8860_brightness_set;
- mutex_init(&led->lock);
-
- i2c_set_clientdata(client, led);
+ ret = devm_mutex_init(&client->dev, &led->lock);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to initialize lock\n");
led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config);
if (IS_ERR(led->regmap)) {
@@ -441,23 +345,6 @@ static int lp8860_probe(struct i2c_client *client)
return 0;
}
-static void lp8860_remove(struct i2c_client *client)
-{
- struct lp8860_led *led = i2c_get_clientdata(client);
- int ret;
-
- gpiod_direction_output(led->enable_gpio, 0);
-
- if (led->regulator) {
- ret = regulator_disable(led->regulator);
- if (ret)
- dev_err(&led->client->dev,
- "Failed to disable regulator\n");
- }
-
- mutex_destroy(&led->lock);
-}
-
static const struct i2c_device_id lp8860_id[] = {
{ "lp8860" },
{ }
@@ -476,7 +363,6 @@ static struct i2c_driver lp8860_driver = {
.of_match_table = of_lp8860_leds_match,
},
.probe = lp8860_probe,
- .remove = lp8860_remove,
.id_table = lp8860_id,
};
module_i2c_driver(lp8860_driver);
diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c
new file mode 100644
index 000000000000..933cb4f19be9
--- /dev/null
+++ b/drivers/leds/leds-max77705.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on leds-max77650 driver
+ *
+ * LED driver for MAXIM 77705 PMIC.
+ * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77705_LED_NUM_LEDS 4
+#define MAX77705_LED_EN_MASK GENMASK(1, 0)
+#define MAX77705_LED_MAX_BRIGHTNESS 0xff
+#define MAX77705_LED_EN_SHIFT(reg) (reg * MAX77705_RGBLED_EN_WIDTH)
+#define MAX77705_LED_REG_BRIGHTNESS(reg) (reg + MAX77705_RGBLED_REG_LED0BRT)
+
+struct max77705_led {
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+ struct regmap *regmap;
+
+ struct mc_subled *subled_info;
+};
+
+static const struct regmap_config max77705_leds_regmap_config = {
+ .reg_base = MAX77705_RGBLED_REG_BASE,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX77705_LED_REG_END,
+};
+
+static int max77705_rgb_blink(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+ int value, on_value, off_value;
+
+ if (*delay_on < MAX77705_RGB_DELAY_100_STEP)
+ on_value = 0;
+ else if (*delay_on < MAX77705_RGB_DELAY_100_STEP_LIM)
+ on_value = *delay_on / MAX77705_RGB_DELAY_100_STEP - 1;
+ else if (*delay_on < MAX77705_RGB_DELAY_250_STEP_LIM)
+ on_value = (*delay_on - MAX77705_RGB_DELAY_100_STEP_LIM) /
+ MAX77705_RGB_DELAY_250_STEP +
+ MAX77705_RGB_DELAY_100_STEP_COUNT;
+ else
+ on_value = 15;
+
+ on_value <<= 4;
+
+ if (*delay_off < 1)
+ off_value = 0;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP)
+ off_value = 1;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP_LIM)
+ off_value = *delay_off / MAX77705_RGB_DELAY_500_STEP;
+ else if (*delay_off < MAX77705_RGB_DELAY_1000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_1000_STEP_LIM) /
+ MAX77705_RGB_DELAY_1000_STEP +
+ MAX77705_RGB_DELAY_500_STEP_COUNT;
+ else if (*delay_off < MAX77705_RGB_DELAY_2000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_2000_STEP_LIM) /
+ MAX77705_RGB_DELAY_2000_STEP +
+ MAX77705_RGB_DELAY_1000_STEP_COUNT;
+ else
+ off_value = 15;
+
+ value = on_value | off_value;
+ return regmap_write(led->regmap, MAX77705_RGBLED_REG_LEDBLNK, value);
+}
+
+static int max77705_led_brightness_set(struct regmap *regmap, struct mc_subled *subled,
+ int num_colors)
+{
+ int ret;
+
+ for (int i = 0; i < num_colors; i++) {
+ unsigned int channel, brightness;
+
+ channel = subled[i].channel;
+ brightness = subled[i].brightness;
+
+ if (brightness == LED_OFF) {
+ /* Flash OFF */
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel), 0);
+ } else {
+ /* Set current */
+ ret = regmap_write(regmap, MAX77705_LED_REG_BRIGHTNESS(channel),
+ brightness);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ LED_ON << MAX77705_LED_EN_SHIFT(channel),
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel));
+ }
+ }
+
+ return ret;
+}
+
+static int max77705_led_brightness_set_single(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+
+ led->subled_info->brightness = brightness;
+
+ return max77705_led_brightness_set(led->regmap, led->subled_info, 1);
+}
+
+static int max77705_led_brightness_set_multi(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev);
+ struct max77705_led *led = container_of(mcdev, struct max77705_led, mcdev);
+
+ led_mc_calc_color_components(mcdev, brightness);
+
+ return max77705_led_brightness_set(led->regmap, led->mcdev.subled_info, mcdev->num_colors);
+}
+
+static int max77705_parse_subled(struct device *dev, struct fwnode_handle *np,
+ struct mc_subled *info)
+{
+ u32 color = LED_COLOR_ID_GREEN;
+ u32 reg;
+ int ret;
+
+ ret = fwnode_property_read_u32(np, "reg", &reg);
+ if (ret || !reg || reg >= MAX77705_LED_NUM_LEDS)
+ return dev_err_probe(dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
+
+ info->channel = reg;
+
+ ret = fwnode_property_read_u32(np, "color", &color);
+ if (ret < 0 && ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "failed to parse \"color\" of %pOF\n", np);
+
+ info->color_index = color;
+
+ return 0;
+}
+
+static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fwnode_handle *np)
+{
+ int ret, i = 0;
+ unsigned int color, reg;
+ struct max77705_led *led;
+ struct led_classdev *cdev;
+ struct mc_subled *info;
+ struct fwnode_handle *child;
+ struct led_init_data init_data = {};
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ ret = fwnode_property_read_u32(np, "color", &color);
+ if (ret < 0 && ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "failed to parse \"color\" of %pOF\n", np);
+
+ led->regmap = regmap;
+ init_data.fwnode = np;
+
+ if (color == LED_COLOR_ID_RGB) {
+ int num_channels = of_get_available_child_count(to_of_node(np));
+
+ ret = fwnode_property_read_u32(np, "reg", &reg);
+ if (ret || reg >= MAX77705_LED_NUM_LEDS)
+ ret = -EINVAL;
+
+ info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ cdev = &led->mcdev.led_cdev;
+ cdev->max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+ cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
+ cdev->blink_set = max77705_rgb_blink;
+
+ fwnode_for_each_available_child_node(np, child) {
+ ret = max77705_parse_subled(dev, child, &info[i]);
+ if (ret < 0)
+ return ret;
+
+ info[i].intensity = 0;
+ i++;
+ }
+
+ led->mcdev.subled_info = info;
+ led->mcdev.num_colors = num_channels;
+ led->cdev = *cdev;
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mcdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_multi(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ } else {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ max77705_parse_subled(dev, np, info);
+
+ led->subled_info = info;
+ led->cdev.brightness_set_blocking = max77705_led_brightness_set_single;
+ led->cdev.blink_set = max77705_rgb_blink;
+ led->cdev.max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_single(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max77705_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct i2c_client *i2c = to_i2c_client(pdev->dev.parent);
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(i2c, &max77705_leds_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register LEDs regmap\n");
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = max77705_add_led(dev, regmap, child);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id max77705_led_of_match[] = {
+ { .compatible = "maxim,max77705-rgb" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77705_led_of_match);
+
+static struct platform_driver max77705_led_driver = {
+ .driver = {
+ .name = "max77705-led",
+ .of_match_table = max77705_led_of_match,
+ },
+ .probe = max77705_led_probe,
+};
+module_platform_driver(max77705_led_driver);
+
+MODULE_DESCRIPTION("Maxim MAX77705 LED driver");
+MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c
index 718f55096e90..f25f68789281 100644
--- a/drivers/leds/leds-mlxcpld.c
+++ b/drivers/leds/leds-mlxcpld.c
@@ -32,7 +32,6 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c
index 282d9e4cf116..f3161266b8ad 100644
--- a/drivers/leds/leds-nic78bx.c
+++ b/drivers/leds/leds-nic78bx.c
@@ -3,11 +3,19 @@
* Copyright (C) 2016 National Instruments Corp.
*/
-#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
+#include <linux/types.h>
#define NIC78BX_USER1_LED_MASK 0x3
#define NIC78BX_USER1_GREEN_LED BIT(0)
@@ -181,8 +189,8 @@ static int nic78bx_probe(struct platform_device *pdev)
}
static const struct acpi_device_id led_device_ids[] = {
- {"NIC78B3", 0},
- {"", 0},
+ { "NIC78B3" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, led_device_ids);
@@ -190,7 +198,7 @@ static struct platform_driver led_driver = {
.probe = nic78bx_probe,
.driver = {
.name = KBUILD_MODNAME,
- .acpi_match_table = ACPI_PTR(led_device_ids),
+ .acpi_match_table = led_device_ids,
},
};
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index 1b47acf54720..7d4c071a6cd0 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -318,7 +318,8 @@ static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset)
return -EBUSY;
}
-static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val)
+static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
struct pca9532_data *data = gpiochip_get_data(gc);
struct pca9532_led *led = &data->leds[offset];
@@ -329,6 +330,8 @@ static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int va
led->state = PCA9532_OFF;
pca9532_setled(led);
+
+ return 0;
}
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
@@ -351,9 +354,7 @@ static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
{
- pca9532_gpio_set_value(gc, offset, val);
-
- return 0;
+ return pca9532_gpio_set_value(gc, offset, val);
}
#endif /* CONFIG_LEDS_PCA9532_GPIO */
@@ -472,7 +473,7 @@ static int pca9532_configure(struct i2c_client *client,
data->gpio.label = "gpio-pca9532";
data->gpio.direction_input = pca9532_gpio_direction_input;
data->gpio.direction_output = pca9532_gpio_direction_output;
- data->gpio.set = pca9532_gpio_set_value;
+ data->gpio.set_rv = pca9532_gpio_set_value;
data->gpio.get = pca9532_gpio_get_value;
data->gpio.request = pca9532_gpio_request_pin;
data->gpio.can_sleep = 1;
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c
index 94a9f8a54b35..70d109246088 100644
--- a/drivers/leds/leds-pca955x.c
+++ b/drivers/leds/leds-pca955x.c
@@ -62,6 +62,8 @@
#define PCA955X_GPIO_HIGH LED_OFF
#define PCA955X_GPIO_LOW LED_FULL
+#define PCA955X_BLINK_DEFAULT_MS 1000
+
enum pca955x_type {
pca9550,
pca9551,
@@ -71,9 +73,10 @@ enum pca955x_type {
};
struct pca955x_chipdef {
- int bits;
+ u8 bits;
u8 slv_addr; /* 7-bit slave address mask */
int slv_addr_shift; /* Number of bits to ignore */
+ int blink_div; /* PSC divider */
};
static const struct pca955x_chipdef pca955x_chipdefs[] = {
@@ -81,26 +84,31 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = {
.bits = 2,
.slv_addr = /* 110000x */ 0x60,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
[pca9551] = {
.bits = 8,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 38,
},
[pca9552] = {
.bits = 16,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 44,
},
[ibm_pca9552] = {
.bits = 16,
.slv_addr = /* 0110xxx */ 0x30,
.slv_addr_shift = 3,
+ .blink_div = 44,
},
[pca9553] = {
.bits = 4,
.slv_addr = /* 110001x */ 0x62,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
};
@@ -109,7 +117,9 @@ struct pca955x {
struct pca955x_led *leds;
const struct pca955x_chipdef *chipdef;
struct i2c_client *client;
+ unsigned long active_blink;
unsigned long active_pins;
+ unsigned long blink_period;
#ifdef CONFIG_LEDS_PCA955X_GPIO
struct gpio_chip gpio;
#endif
@@ -124,17 +134,25 @@ struct pca955x_led {
struct fwnode_handle *fwnode;
};
+#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
+
struct pca955x_platform_data {
struct pca955x_led *leds;
int num_leds;
};
/* 8 bits per input register */
-static inline int pca95xx_num_input_regs(int bits)
+static inline u8 pca955x_num_input_regs(u8 bits)
{
return (bits + 7) / 8;
}
+/* 4 bits per LED selector register */
+static inline u8 pca955x_num_led_regs(u8 bits)
+{
+ return (bits + 3) / 4;
+}
+
/*
* Return an LED selector register value based on an existing one, with
* the appropriate 2-bit state value set for the given LED number (0-3).
@@ -145,20 +163,25 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
((state & 0x3) << (led_num << 1));
}
+static inline int pca955x_ledstate(u8 ls, int led_num)
+{
+ return (ls >> (led_num << 1)) & 0x3;
+}
+
/*
* Write to frequency prescaler register, used to program the
- * period of the PWM output. period = (PSCx + 1) / 38
+ * period of the PWM output. period = (PSCx + 1) / coeff
+ * Where for pca9551 chips coeff = 38 and for all other chips coeff = 44
*/
-static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_psc(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -169,16 +192,15 @@ static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
*
* Duty cycle is (256 - PWMx) / 256
*/
-static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_pwm(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -186,16 +208,15 @@ static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
* Write to LED selector register, which determines the source that
* drives the LED output.
*/
-static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_ls(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -203,32 +224,43 @@ static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
* Read the LED selector register, which determines the source that
* drives the LED output.
*/
-static int pca955x_read_ls(struct i2c_client *client, int n, u8 *val)
+static int pca955x_read_ls(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
int ret;
- ret = i2c_smbus_read_byte_data(client, cmd);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
if (ret < 0) {
- dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
- __func__, n, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
return ret;
}
*val = (u8)ret;
return 0;
}
-static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
+static int pca955x_read_pwm(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
int ret;
- ret = i2c_smbus_read_byte_data(client, cmd);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
if (ret < 0) {
- dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
- __func__, n, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
+ return ret;
+ }
+ *val = (u8)ret;
+ return 0;
+}
+
+static int pca955x_read_psc(struct pca955x *pca955x, int n, u8 *val)
+{
+ int ret;
+ u8 cmd;
+
+ cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
+ if (ret < 0) {
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
return ret;
}
*val = (u8)ret;
@@ -237,30 +269,25 @@ static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
{
- struct pca955x_led *pca955x_led = container_of(led_cdev,
- struct pca955x_led,
- led_cdev);
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
struct pca955x *pca955x = pca955x_led->pca955x;
u8 ls, pwm;
int ret;
- ret = pca955x_read_ls(pca955x->client, pca955x_led->led_num / 4, &ls);
+ ret = pca955x_read_ls(pca955x, pca955x_led->led_num / 4, &ls);
if (ret)
return ret;
- ls = (ls >> ((pca955x_led->led_num % 4) << 1)) & 0x3;
- switch (ls) {
+ switch (pca955x_ledstate(ls, pca955x_led->led_num % 4)) {
case PCA955X_LS_LED_ON:
+ case PCA955X_LS_BLINK0:
ret = LED_FULL;
break;
case PCA955X_LS_LED_OFF:
ret = LED_OFF;
break;
- case PCA955X_LS_BLINK0:
- ret = LED_HALF;
- break;
case PCA955X_LS_BLINK1:
- ret = pca955x_read_pwm(pca955x->client, 1, &pwm);
+ ret = pca955x_read_pwm(pca955x, 1, &pwm);
if (ret)
return ret;
ret = 255 - pwm;
@@ -273,51 +300,150 @@ static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
static int pca955x_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- struct pca955x_led *pca955x_led;
- struct pca955x *pca955x;
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
+ struct pca955x *pca955x = pca955x_led->pca955x;
+ int reg = pca955x_led->led_num / 4;
+ int bit = pca955x_led->led_num % 4;
u8 ls;
- int chip_ls; /* which LSx to use (0-3 potentially) */
- int ls_led; /* which set of bits within LSx to use (0-3) */
int ret;
- pca955x_led = container_of(led_cdev, struct pca955x_led, led_cdev);
- pca955x = pca955x_led->pca955x;
-
- chip_ls = pca955x_led->led_num / 4;
- ls_led = pca955x_led->led_num % 4;
-
mutex_lock(&pca955x->lock);
- ret = pca955x_read_ls(pca955x->client, chip_ls, &ls);
+ ret = pca955x_read_ls(pca955x, reg, &ls);
if (ret)
goto out;
- switch (value) {
- case LED_FULL:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
- break;
- case LED_OFF:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF);
- break;
- case LED_HALF:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0);
- break;
- default:
- /*
- * Use PWM1 for all other values. This has the unwanted
- * side effect of making all LEDs on the chip share the
- * same brightness level if set to a value other than
- * OFF, HALF, or FULL. But, this is probably better than
- * just turning off for all other values.
- */
- ret = pca955x_write_pwm(pca955x->client, 1, 255 - value);
- if (ret)
+ if (test_bit(pca955x_led->led_num, &pca955x->active_blink)) {
+ if (value == LED_OFF) {
+ clear_bit(pca955x_led->led_num, &pca955x->active_blink);
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
+ } else {
+ /* No variable brightness for blinking LEDs */
goto out;
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
- break;
+ }
+ } else {
+ switch (value) {
+ case LED_FULL:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_ON);
+ break;
+ case LED_OFF:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
+ break;
+ default:
+ /*
+ * Use PWM1 for all other values. This has the unwanted
+ * side effect of making all LEDs on the chip share the
+ * same brightness level if set to a value other than
+ * OFF or FULL. But, this is probably better than just
+ * turning off for all other values.
+ */
+ ret = pca955x_write_pwm(pca955x, 1, 255 - value);
+ if (ret)
+ goto out;
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK1);
+ break;
+ }
+ }
+
+ ret = pca955x_write_ls(pca955x, reg, ls);
+
+out:
+ mutex_unlock(&pca955x->lock);
+
+ return ret;
+}
+
+static u8 pca955x_period_to_psc(struct pca955x *pca955x, unsigned long period)
+{
+ /* psc register value = (blink period * coeff) - 1 */
+ period *= pca955x->chipdef->blink_div;
+ period /= MSEC_PER_SEC;
+ period -= 1;
+
+ return period;
+}
+
+static unsigned long pca955x_psc_to_period(struct pca955x *pca955x, u8 psc)
+{
+ unsigned long period = psc;
+
+ /* blink period = (psc register value + 1) / coeff */
+ period += 1;
+ period *= MSEC_PER_SEC;
+ period /= pca955x->chipdef->blink_div;
+
+ return period;
+}
+
+static int pca955x_led_blink(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
+ struct pca955x *pca955x = pca955x_led->pca955x;
+ unsigned long period = *delay_on + *delay_off;
+ int ret = 0;
+
+ mutex_lock(&pca955x->lock);
+
+ if (period) {
+ if (*delay_on != *delay_off) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (period < pca955x_psc_to_period(pca955x, 0) ||
+ period > pca955x_psc_to_period(pca955x, 0xff)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ period = pca955x->active_blink ? pca955x->blink_period :
+ PCA955X_BLINK_DEFAULT_MS;
}
- ret = pca955x_write_ls(pca955x->client, chip_ls, ls);
+ if (!pca955x->active_blink ||
+ pca955x->active_blink == BIT(pca955x_led->led_num) ||
+ pca955x->blink_period == period) {
+ u8 psc = pca955x_period_to_psc(pca955x, period);
+
+ if (!test_and_set_bit(pca955x_led->led_num,
+ &pca955x->active_blink)) {
+ u8 ls;
+ int reg = pca955x_led->led_num / 4;
+ int bit = pca955x_led->led_num % 4;
+
+ ret = pca955x_read_ls(pca955x, reg, &ls);
+ if (ret)
+ goto out;
+
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK0);
+ ret = pca955x_write_ls(pca955x, reg, ls);
+ if (ret)
+ goto out;
+
+ /*
+ * Force 50% duty cycle to maintain the specified
+ * blink rate.
+ */
+ ret = pca955x_write_pwm(pca955x, 0, 128);
+ if (ret)
+ goto out;
+ }
+
+ if (pca955x->blink_period != period) {
+ pca955x->blink_period = period;
+ ret = pca955x_write_psc(pca955x, 0, psc);
+ if (ret)
+ goto out;
+ }
+
+ period = pca955x_psc_to_period(pca955x, psc);
+ period /= 2;
+ *delay_on = period;
+ *delay_off = period;
+ } else {
+ ret = -EBUSY;
+ }
out:
mutex_unlock(&pca955x->lock);
@@ -369,10 +495,10 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset,
return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW);
}
-static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
- int val)
+static int pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
- pca955x_set_value(gc, offset, val);
+ return pca955x_set_value(gc, offset, val);
}
static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
@@ -455,11 +581,14 @@ static int pca955x_probe(struct i2c_client *client)
struct led_classdev *led;
struct led_init_data init_data;
struct i2c_adapter *adapter;
- int i, err;
+ u8 i, nls, psc0;
+ u8 ls1[4];
+ u8 ls2[4];
struct pca955x_platform_data *pdata;
+ bool keep_psc0 = false;
bool set_default_label = false;
- bool keep_pwm = false;
- char default_label[8];
+ char default_label[4];
+ int bit, err, reg;
chip = i2c_get_match_data(client);
if (!chip)
@@ -481,16 +610,15 @@ static int pca955x_probe(struct i2c_client *client)
return -ENODEV;
}
- dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
- "slave address 0x%02x\n", client->name, chip->bits,
- client->addr);
+ dev_info(&client->dev, "Using %s %u-bit LED driver at slave address 0x%02x\n",
+ client->name, chip->bits, client->addr);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
if (pdata->num_leds != chip->bits) {
dev_err(&client->dev,
- "board info claims %d LEDs on a %d-bit chip\n",
+ "board info claims %d LEDs on a %u-bit chip\n",
pdata->num_leds, chip->bits);
return -ENODEV;
}
@@ -509,10 +637,22 @@ static int pca955x_probe(struct i2c_client *client)
mutex_init(&pca955x->lock);
pca955x->client = client;
pca955x->chipdef = chip;
+ pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS;
init_data.devname_mandatory = false;
init_data.devicename = "pca955x";
+ nls = pca955x_num_led_regs(chip->bits);
+ /* Use auto-increment feature to read all the LED selectors at once. */
+ err = i2c_smbus_read_i2c_block_data(client,
+ 0x10 | (pca955x_num_input_regs(chip->bits) + 4), nls,
+ ls1);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < nls; i++)
+ ls2[i] = ls1[i];
+
for (i = 0; i < chip->bits; i++) {
pca955x_led = &pca955x->leds[i];
pca955x_led->led_num = i;
@@ -524,18 +664,20 @@ static int pca955x_probe(struct i2c_client *client)
case PCA955X_TYPE_GPIO:
break;
case PCA955X_TYPE_LED:
+ bit = i % 4;
+ reg = i / 4;
led = &pca955x_led->led_cdev;
led->brightness_set_blocking = pca955x_led_set;
led->brightness_get = pca955x_led_get;
-
- if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) {
- err = pca955x_led_set(led, LED_OFF);
- if (err)
- return err;
- } else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) {
- err = pca955x_led_set(led, LED_FULL);
- if (err)
- return err;
+ led->blink_set = pca955x_led_blink;
+
+ if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF)
+ ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF);
+ else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON)
+ ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON);
+ else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) {
+ keep_psc0 = true;
+ set_bit(i, &pca955x->active_blink);
}
init_data.fwnode = pdata->leds[i].fwnode;
@@ -551,8 +693,7 @@ static int pca955x_probe(struct i2c_client *client)
}
if (set_default_label) {
- snprintf(default_label, sizeof(default_label),
- "%d", i);
+ snprintf(default_label, sizeof(default_label), "%hhu", i);
init_data.default_label = default_label;
} else {
init_data.default_label = NULL;
@@ -564,39 +705,31 @@ static int pca955x_probe(struct i2c_client *client)
return err;
set_bit(i, &pca955x->active_pins);
-
- /*
- * For default-state == "keep", let the core update the
- * brightness from the hardware, then check the
- * brightness to see if it's using PWM1. If so, PWM1
- * should not be written below.
- */
- if (pdata->leds[i].default_state == LEDS_DEFSTATE_KEEP) {
- if (led->brightness != LED_FULL &&
- led->brightness != LED_OFF &&
- led->brightness != LED_HALF)
- keep_pwm = true;
- }
}
}
- /* PWM0 is used for half brightness or 50% duty cycle */
- err = pca955x_write_pwm(client, 0, 255 - LED_HALF);
- if (err)
- return err;
+ for (i = 0; i < nls; i++) {
+ if (ls1[i] != ls2[i]) {
+ err = pca955x_write_ls(pca955x, i, ls2[i]);
+ if (err)
+ return err;
+ }
+ }
- if (!keep_pwm) {
- /* PWM1 is used for variable brightness, default to OFF */
- err = pca955x_write_pwm(client, 1, 0);
- if (err)
- return err;
+ if (keep_psc0) {
+ err = pca955x_read_psc(pca955x, 0, &psc0);
+ } else {
+ psc0 = pca955x_period_to_psc(pca955x, pca955x->blink_period);
+ err = pca955x_write_psc(pca955x, 0, psc0);
}
- /* Set to fast frequency so we do not see flashing */
- err = pca955x_write_psc(client, 0, 0);
if (err)
return err;
- err = pca955x_write_psc(client, 1, 0);
+
+ pca955x->blink_period = pca955x_psc_to_period(pca955x, psc0);
+
+ /* Set PWM1 to fast frequency so we do not see flashing */
+ err = pca955x_write_psc(pca955x, 1, 0);
if (err)
return err;
@@ -604,7 +737,7 @@ static int pca955x_probe(struct i2c_client *client)
pca955x->gpio.label = "gpio-pca955x";
pca955x->gpio.direction_input = pca955x_gpio_direction_input;
pca955x->gpio.direction_output = pca955x_gpio_direction_output;
- pca955x->gpio.set = pca955x_gpio_set_value;
+ pca955x->gpio.set_rv = pca955x_gpio_set_value;
pca955x->gpio.get = pca955x_gpio_get_value;
pca955x->gpio.request = pca955x_gpio_request_pin;
pca955x->gpio.free = pca955x_gpio_free_pin;
diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c
index 11c7bb69573e..6ad06ce2bf64 100644
--- a/drivers/leds/leds-pca995x.c
+++ b/drivers/leds/leds-pca995x.c
@@ -197,7 +197,7 @@ MODULE_DEVICE_TABLE(i2c, pca995x_id);
static const struct of_device_id pca995x_of_match[] = {
{ .compatible = "nxp,pca9952", .data = &pca9952_chipdef },
- { .compatible = "nxp,pca9955b", . data = &pca9955b_chipdef },
+ { .compatible = "nxp,pca9955b", .data = &pca9955b_chipdef },
{ .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef },
{},
};
diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c
index 4cebc0203c22..4e5dd76d714d 100644
--- a/drivers/leds/leds-st1202.c
+++ b/drivers/leds/leds-st1202.c
@@ -99,9 +99,9 @@ static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
value_h = (u8)(value >> 8);
/*
- * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
- * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
- * and y is the pattern number in hexadecimal (y = 00h .. 07h)
+ * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
+ * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
+ * and y is the pattern number in hexadecimal (y = 00h .. 07h)
*/
ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
value_l);
@@ -189,9 +189,8 @@ static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
{
struct st1202_led *led = cdev_to_st1202_led(ldev);
- struct st1202_chip *chip = led->chip;
- return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true);
+ return st1202_channel_set(led->chip, led->led_num, !!value);
}
static int st1202_led_pattern_clear(struct led_classdev *ldev)
@@ -288,8 +287,8 @@ static int st1202_setup(struct st1202_chip *chip)
guard(mutex)(&chip->lock);
/*
- * Once the supply voltage is applied, the LED1202 executes some internal checks,
- * afterwords it stops the oscillator and puts the internal LDO in quiescent mode.
+ * Once the supply voltage is applied, the LED1202 executes some internal checks.
+ * Afterwards, it stops the oscillator and puts the internal LDO in quiescent mode.
* To start the device, EN bit must be set inside the “Device Enable” register at
* address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
* from the internal non-volatile memory and performs an auto-calibration procedure
@@ -350,11 +349,11 @@ static int st1202_probe(struct i2c_client *client)
return ret;
chip->client = client;
- ret = st1202_dt_init(chip);
+ ret = st1202_setup(chip);
if (ret < 0)
return ret;
- ret = st1202_setup(chip);
+ ret = st1202_dt_init(chip);
if (ret < 0)
return ret;
diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index acbd8169723c..89c165c8ee9c 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -588,8 +588,8 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
}
#ifdef CONFIG_GPIOLIB
-static void tca6507_gpio_set_value(struct gpio_chip *gc,
- unsigned offset, int val)
+static int tca6507_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
struct tca6507_chip *tca = gpiochip_get_data(gc);
unsigned long flags;
@@ -604,13 +604,14 @@ static void tca6507_gpio_set_value(struct gpio_chip *gc,
spin_unlock_irqrestore(&tca->lock, flags);
if (tca->reg_set)
schedule_work(&tca->work);
+
+ return 0;
}
static int tca6507_gpio_direction_output(struct gpio_chip *gc,
unsigned offset, int val)
{
- tca6507_gpio_set_value(gc, offset, val);
- return 0;
+ return tca6507_gpio_set_value(gc, offset, val);
}
static int tca6507_probe_gpios(struct device *dev,
@@ -636,7 +637,7 @@ static int tca6507_probe_gpios(struct device *dev,
tca->gpio.base = -1;
tca->gpio.owner = THIS_MODULE;
tca->gpio.direction_output = tca6507_gpio_direction_output;
- tca->gpio.set = tca6507_gpio_set_value;
+ tca->gpio.set_rv = tca6507_gpio_set_value;
tca->gpio.parent = dev;
err = devm_gpiochip_add_data(dev, &tca->gpio, tca);
if (err) {
diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
index 4fe1a9c0bc1b..25ee5c1eb820 100644
--- a/drivers/leds/leds-turris-omnia.c
+++ b/drivers/leds/leds-turris-omnia.c
@@ -361,7 +361,7 @@ static DEVICE_ATTR_RW(gamma_correction);
static struct attribute *omnia_led_controller_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_gamma_correction.attr,
- NULL,
+ NULL
};
ATTRIBUTE_GROUPS(omnia_led_controller);
@@ -527,7 +527,7 @@ static void omnia_leds_remove(struct i2c_client *client)
static const struct of_device_id of_omnia_leds_match[] = {
{ .compatible = "cznic,turris-omnia-leds", },
- {},
+ { }
};
MODULE_DEVICE_TABLE(of, of_omnia_leds_match);
diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c b/drivers/leds/rgb/leds-mt6370-rgb.c
index ebd3ba878dd5..c5927d0eb830 100644
--- a/drivers/leds/rgb/leds-mt6370-rgb.c
+++ b/drivers/leds/rgb/leds-mt6370-rgb.c
@@ -199,17 +199,17 @@ static const struct reg_field mt6372_reg_fields[F_MAX_FIELDS] = {
/* Current unit: microamp, time unit: millisecond */
static const struct linear_range common_led_ranges[R_MAX_RANGES] = {
- [R_LED123_CURR] = { 4000, 1, 6, 4000 },
- [R_LED4_CURR] = { 2000, 1, 3, 2000 },
- [R_LED_TRFON] = { 125, 0, 15, 200 },
- [R_LED_TOFF] = { 250, 0, 15, 400 },
+ [R_LED123_CURR] = LINEAR_RANGE(4000, 1, 6, 4000),
+ [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 3, 2000),
+ [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 200),
+ [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 400),
};
static const struct linear_range mt6372_led_ranges[R_MAX_RANGES] = {
- [R_LED123_CURR] = { 2000, 1, 14, 2000 },
- [R_LED4_CURR] = { 2000, 1, 14, 2000 },
- [R_LED_TRFON] = { 125, 0, 15, 250 },
- [R_LED_TOFF] = { 250, 0, 15, 500 },
+ [R_LED123_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
+ [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
+ [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 250),
+ [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 500),
};
static const unsigned int common_tfreqs[] = {
diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c
index f18156683375..7c7d44623a9e 100644
--- a/drivers/leds/rgb/leds-ncp5623.c
+++ b/drivers/leds/rgb/leds-ncp5623.c
@@ -155,9 +155,9 @@ static int ncp5623_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct fwnode_handle *mc_node, *led_node;
struct led_init_data init_data = { };
- int num_subleds = 0;
struct ncp5623 *ncp;
struct mc_subled *subled_info;
+ unsigned int num_subleds;
u32 color_index;
u32 reg;
int ret;
@@ -172,8 +172,7 @@ static int ncp5623_probe(struct i2c_client *client)
if (!mc_node)
return -EINVAL;
- fwnode_for_each_child_node(mc_node, led_node)
- num_subleds++;
+ num_subleds = fwnode_get_child_node_count(mc_node);
subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL);
if (!subled_info) {
diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c
index f80a06cc31f8..e0d7d3c9215c 100644
--- a/drivers/leds/rgb/leds-pwm-multicolor.c
+++ b/drivers/leds/rgb/leds-pwm-multicolor.c
@@ -107,12 +107,12 @@ release_fwnode:
static int led_pwm_mc_probe(struct platform_device *pdev)
{
- struct fwnode_handle *mcnode, *fwnode;
+ struct fwnode_handle *mcnode;
struct led_init_data init_data = {};
struct led_classdev *cdev;
struct mc_subled *subled;
struct pwm_mc_led *priv;
- int count = 0;
+ unsigned int count;
int ret = 0;
mcnode = device_get_named_child_node(&pdev->dev, "multi-led");
@@ -121,8 +121,7 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
"expected multi-led node\n");
/* count the nodes inside the multi-led node */
- fwnode_for_each_child_node(mcnode, fwnode)
- count++;
+ count = fwnode_get_child_node_count(mcnode);
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
GFP_KERNEL);
@@ -141,8 +140,11 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
/* init the multicolor's LED class device */
cdev = &priv->mc_cdev.led_cdev;
- fwnode_property_read_u32(mcnode, "max-brightness",
+ ret = fwnode_property_read_u32(mcnode, "max-brightness",
&cdev->max_brightness);
+ if (ret)
+ goto release_mcnode;
+
cdev->flags = LED_CORE_SUSPENDRESUME;
cdev->brightness_set_blocking = led_pwm_mc_set;
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index 5d8e27e2e7ae..4f2a178e3d26 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -24,6 +24,7 @@
#define LPG_PATTERN_CONFIG_REG 0x40
#define LPG_SIZE_CLK_REG 0x41
#define PWM_CLK_SELECT_MASK GENMASK(1, 0)
+#define PWM_SIZE_SELECT_MASK BIT(2)
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
#define LPG_PREDIV_CLK_REG 0x42
@@ -412,8 +413,8 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
-static const unsigned int lpg_pwm_resolution[] = {9};
-static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
+static const unsigned int lpg_pwm_resolution[] = {6, 9};
+static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
{
@@ -436,12 +437,12 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
* period = --------------------------
* refclk
*
- * Resolution = 2^9 bits for PWM or
+ * Resolution = 2^{6 or 9} bits for PWM or
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
* pre_div = {1, 3, 5, 6} and
* M = [0..7].
*
- * This allows for periods between 27uS and 384s for PWM channels and periods between
+ * This allows for periods between 3uS and 384s for PWM channels and periods between
* 3uS and 24576s for high resolution PWMs.
* The PWM framework wants a period of equal or lower length than requested,
* reject anything below minimum period.
@@ -532,7 +533,7 @@ static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
max = BIT(lpg_pwm_resolution_hi_res[chan->pwm_resolution_sel]) - 1;
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
} else {
- max = LPG_RESOLUTION_9BIT - 1;
+ max = BIT(lpg_pwm_resolution[chan->pwm_resolution_sel]) - 1;
clk_rate = lpg_clk_rates[chan->clk_sel];
}
@@ -558,7 +559,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
val |= GENMASK(5, 4);
break;
case LPG_SUBTYPE_PWM:
- val |= BIT(2);
+ val |= FIELD_PREP(PWM_SIZE_SELECT_MASK, chan->pwm_resolution_sel);
break;
case LPG_SUBTYPE_HI_RES_PWM:
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
@@ -1276,7 +1277,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
} else {
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
- resolution = 9;
+ resolution = lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)];
}
if (refclk) {
diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simatic/Kconfig
index e616cc6d6051..e616cc6d6051 100644
--- a/drivers/leds/simple/Kconfig
+++ b/drivers/leds/simatic/Kconfig
diff --git a/drivers/leds/simple/Makefile b/drivers/leds/simatic/Makefile
index 783578f11bb0..783578f11bb0 100644
--- a/drivers/leds/simple/Makefile
+++ b/drivers/leds/simatic/Makefile
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
index c98c370687c2..c98c370687c2 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
index 9bc5f361a06b..9bc5f361a06b 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
index 7f7cff275448..7f7cff275448 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
index bc23d701bcb7..bc23d701bcb7 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio.h b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
index 6b2519809cee..6b2519809cee 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio.h
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
diff --git a/drivers/leds/simple/simatic-ipc-leds.c b/drivers/leds/simatic/simatic-ipc-leds.c
index 348679f0d1b7..348679f0d1b7 100644
--- a/drivers/leds/simple/simatic-ipc-leds.c
+++ b/drivers/leds/simatic/simatic-ipc-leds.c
diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c
index b3ee33aed36e..c973246a57f9 100644
--- a/drivers/leds/trigger/ledtrig-activity.c
+++ b/drivers/leds/trigger/ledtrig-activity.c
@@ -32,8 +32,8 @@ struct activity_data {
static void led_activity_function(struct timer_list *t)
{
- struct activity_data *activity_data = from_timer(activity_data, t,
- timer);
+ struct activity_data *activity_data = timer_container_of(activity_data,
+ t, timer);
struct led_classdev *led_cdev = activity_data->led_cdev;
unsigned int target;
unsigned int usage;
diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c
index 487577d22cfc..c1f0f5becaee 100644
--- a/drivers/leds/trigger/ledtrig-backlight.c
+++ b/drivers/leds/trigger/ledtrig-backlight.c
@@ -10,7 +10,6 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
-#include <linux/fb.h>
#include <linux/leds.h>
#include "../leds.h"
@@ -21,29 +20,20 @@ struct bl_trig_notifier {
struct led_classdev *led;
int brightness;
int old_status;
- struct notifier_block notifier;
unsigned invert;
+
+ struct list_head entry;
};
-static int fb_notifier_callback(struct notifier_block *p,
- unsigned long event, void *data)
+static DEFINE_MUTEX(ledtrig_backlight_list_mutex);
+static LIST_HEAD(ledtrig_backlight_list);
+
+static void ledtrig_backlight_notify_blank(struct bl_trig_notifier *n, int new_status)
{
- struct bl_trig_notifier *n = container_of(p,
- struct bl_trig_notifier, notifier);
struct led_classdev *led = n->led;
- struct fb_event *fb_event = data;
- int *blank;
- int new_status;
-
- /* If we aren't interested in this event, skip it immediately ... */
- if (event != FB_EVENT_BLANK)
- return 0;
-
- blank = fb_event->data;
- new_status = *blank ? BLANK : UNBLANK;
if (new_status == n->old_status)
- return 0;
+ return;
if ((n->old_status == UNBLANK) ^ n->invert) {
n->brightness = led->brightness;
@@ -53,9 +43,19 @@ static int fb_notifier_callback(struct notifier_block *p,
}
n->old_status = new_status;
+}
- return 0;
+void ledtrig_backlight_blank(bool blank)
+{
+ struct bl_trig_notifier *n;
+ int new_status = blank ? BLANK : UNBLANK;
+
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+
+ list_for_each_entry(n, &ledtrig_backlight_list, entry)
+ ledtrig_backlight_notify_blank(n, new_status);
}
+EXPORT_SYMBOL(ledtrig_backlight_blank);
static ssize_t bl_trig_invert_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -100,8 +100,6 @@ ATTRIBUTE_GROUPS(bl_trig);
static int bl_trig_activate(struct led_classdev *led)
{
- int ret;
-
struct bl_trig_notifier *n;
n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL);
@@ -112,11 +110,9 @@ static int bl_trig_activate(struct led_classdev *led)
n->led = led;
n->brightness = led->brightness;
n->old_status = UNBLANK;
- n->notifier.notifier_call = fb_notifier_callback;
- ret = fb_register_client(&n->notifier);
- if (ret)
- dev_err(led->dev, "unable to register backlight trigger\n");
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+ list_add(&n->entry, &ledtrig_backlight_list);
return 0;
}
@@ -125,7 +121,9 @@ static void bl_trig_deactivate(struct led_classdev *led)
{
struct bl_trig_notifier *n = led_get_trigger_data(led);
- fb_unregister_client(&n->notifier);
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+ list_del(&n->entry);
+
kfree(n);
}
diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c
index 393b3ae832f4..40eb61b6d54e 100644
--- a/drivers/leds/trigger/ledtrig-heartbeat.c
+++ b/drivers/leds/trigger/ledtrig-heartbeat.c
@@ -33,7 +33,7 @@ struct heartbeat_trig_data {
static void led_heartbeat_function(struct timer_list *t)
{
struct heartbeat_trig_data *heartbeat_data =
- from_timer(heartbeat_data, t, timer);
+ timer_container_of(heartbeat_data, t, timer);
struct led_classdev *led_cdev;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c
index aad48c2540fc..9af3c18f14f4 100644
--- a/drivers/leds/trigger/ledtrig-pattern.c
+++ b/drivers/leds/trigger/ledtrig-pattern.c
@@ -94,7 +94,7 @@ static void pattern_trig_timer_cancel(struct pattern_trig_data *data)
if (data->type == PATTERN_TYPE_HR)
hrtimer_cancel(&data->hrtimer);
else
- del_timer_sync(&data->timer);
+ timer_delete_sync(&data->timer);
}
static void pattern_trig_timer_restart(struct pattern_trig_data *data,
@@ -150,7 +150,7 @@ static void pattern_trig_timer_common_function(struct pattern_trig_data *data)
static void pattern_trig_timer_function(struct timer_list *t)
{
- struct pattern_trig_data *data = from_timer(data, t, timer);
+ struct pattern_trig_data *data = timer_container_of(data, t, timer);
return pattern_trig_timer_common_function(data);
}
@@ -483,8 +483,8 @@ static int pattern_trig_activate(struct led_classdev *led_cdev)
data->led_cdev = led_cdev;
led_set_trigger_data(led_cdev, data);
timer_setup(&data->timer, pattern_trig_timer_function, 0);
- hrtimer_init(&data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
- data->hrtimer.function = pattern_trig_hrtimer_function;
+ hrtimer_setup(&data->hrtimer, pattern_trig_hrtimer_function, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
led_cdev->activated = true;
if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c
index f111fa7635e5..20f1351464b1 100644
--- a/drivers/leds/trigger/ledtrig-transient.c
+++ b/drivers/leds/trigger/ledtrig-transient.c
@@ -32,7 +32,7 @@ struct transient_trig_data {
static void transient_timer_function(struct timer_list *t)
{
struct transient_trig_data *transient_data =
- from_timer(transient_data, t, timer);
+ timer_container_of(transient_data, t, timer);
struct led_classdev *led_cdev = transient_data->led_cdev;
transient_data->activate = 0;
@@ -66,7 +66,7 @@ static ssize_t transient_activate_store(struct device *dev,
/* cancel the running timer */
if (state == 0 && transient_data->activate == 1) {
- del_timer(&transient_data->timer);
+ timer_delete(&transient_data->timer);
transient_data->activate = state;
led_set_brightness_nosleep(led_cdev,
transient_data->restore_state);