diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-08-08 11:36:21 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-08-08 11:36:21 -0700 |
commit | c8a684e2e110376c58f0bfa30fb3855d1e319670 (patch) | |
tree | 190ad1c64a84029bd5d151ba88050c9f5410af37 /drivers/leds | |
parent | 607ca0f742b7d990b6efb3c3e7a52830f7e96419 (diff) | |
parent | 38ba0bb291aacd92d8eaa4a1aa8b63ce4286e797 (diff) | |
download | linux-c8a684e2e110376c58f0bfa30fb3855d1e319670.tar.bz2 |
Merge tag 'leds-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds
Pull LED updates from Pavel Machek:
"A new driver for bcm63138, is31fl319x updates, fixups for multicolor.
The clevo-mail driver got disabled, it needs an API fix"
* tag 'leds-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds: (23 commits)
leds: is31fl319x: use simple i2c probe function
leds: is31fl319x: Fix devm vs. non-devm ordering
leds: is31fl319x: Make use of dev_err_probe()
leds: is31fl319x: Make use of device properties
leds: is31fl319x: Cleanup formatting and dev_dbg calls
leds: is31fl319x: Add support for is31fl319{0,1,3} chips
leds: is31fl319x: Move chipset-specific values in chipdef struct
leds: is31fl319x: Use non-wildcard names for vars, structs and defines
leds: is31fl319x: Add missing si-en compatibles
dt-bindings: leds: pwm-multicolor: document max-brigthness
leds: turris-omnia: convert to use dev_groups
leds: leds-bcm63138: get rid of LED_OFF
leds: add help info about BCM63138 module name
dt-bindings: leds: leds-bcm63138: unify full stops in descriptions
dt-bindings: leds: lp50xx: fix LED children names
dt-bindings: leds: class-multicolor: reference class directly in multi-led node
leds: bcm63138: add support for BCM63138 controller
dt-bindings: leds: add Broadcom's BCM63138 controller
leds: clevo-mail: Mark as broken pending interface fix
leds: pwm-multicolor: Support active-low LEDs
...
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 10 | ||||
-rw-r--r-- | drivers/leds/blink/Kconfig | 14 | ||||
-rw-r--r-- | drivers/leds/blink/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/blink/leds-bcm63138.c | 307 | ||||
-rw-r--r-- | drivers/leds/leds-is31fl319x.c | 529 | ||||
-rw-r--r-- | drivers/leds/leds-turris-omnia.c | 4 | ||||
-rw-r--r-- | drivers/leds/rgb/leds-pwm-multicolor.c | 8 |
7 files changed, 675 insertions, 198 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index a49979f41eee..499d0f215a8b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -447,16 +447,16 @@ config LEDS_LP8860 config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" - depends on LEDS_CLASS + depends on LEDS_CLASS && BROKEN depends on X86 && SERIO_I8042 && DMI help This driver makes the mail LED accessible from userspace - programs through the leds subsystem. This LED have three - known mode: off, blink at 0.5Hz and blink at 1Hz. + programs through the LEDs subsystem. This LED has three + known modes: off, blink at 0.5Hz and blink at 1Hz. The driver supports two kinds of interface: using ledtrig-timer or through /sys/class/leds/clevo::mail/brightness. As this LED - cannot change it's brightness it blinks instead. The brightness + cannot change its brightness it blinks instead. The brightness value 0 means off, 1..127 means blink at 0.5Hz and 128..255 means blink at 1Hz. @@ -697,7 +697,7 @@ config LEDS_MENF21BMC config LEDS_IS31FL319X tristate "LED Support for ISSI IS31FL319x I2C LED controller family" - depends on LEDS_CLASS && I2C && OF + depends on LEDS_CLASS && I2C select REGMAP_I2C help This option enables support for LEDs connected to ISSI IS31FL319x diff --git a/drivers/leds/blink/Kconfig b/drivers/leds/blink/Kconfig index 59ba81e40e85..945c84286a4e 100644 --- a/drivers/leds/blink/Kconfig +++ b/drivers/leds/blink/Kconfig @@ -1,3 +1,17 @@ +config LEDS_BCM63138 + tristate "LED Support for Broadcom BCM63138 SoC" + depends on LEDS_CLASS + depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + default ARCH_BCM4908 + help + This option enables support for LED controller that is part of + BCM63138 SoC. The same hardware block is known to be also used + in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360. + + If compiled as module it will be called leds-bcm63138. + config LEDS_LGM tristate "LED support for LGM SoC series" depends on X86 || COMPILE_TEST diff --git a/drivers/leds/blink/Makefile b/drivers/leds/blink/Makefile index fa5d04dccf13..447029f4153a 100644 --- a/drivers/leds/blink/Makefile +++ b/drivers/leds/blink/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_LEDS_BCM63138) += leds-bcm63138.o obj-$(CONFIG_LEDS_LGM) += leds-lgm-sso.o diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c new file mode 100644 index 000000000000..2cf2761e4914 --- /dev/null +++ b/drivers/leds/blink/leds-bcm63138.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl> + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define BCM63138_MAX_LEDS 32 +#define BCM63138_MAX_BRIGHTNESS 9 + +#define BCM63138_LED_BITS 4 /* how many bits control a single LED */ +#define BCM63138_LED_MASK ((1 << BCM63138_LED_BITS) - 1) /* 0xf */ +#define BCM63138_LEDS_PER_REG (32 / BCM63138_LED_BITS) /* 8 */ + +#define BCM63138_GLB_CTRL 0x00 +#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL 0x00000002 +#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL 0x00000008 +#define BCM63138_MASK 0x04 +#define BCM63138_HW_LED_EN 0x08 +#define BCM63138_SERIAL_LED_SHIFT_SEL 0x0c +#define BCM63138_FLASH_RATE_CTRL1 0x10 +#define BCM63138_FLASH_RATE_CTRL2 0x14 +#define BCM63138_FLASH_RATE_CTRL3 0x18 +#define BCM63138_FLASH_RATE_CTRL4 0x1c +#define BCM63138_BRIGHT_CTRL1 0x20 +#define BCM63138_BRIGHT_CTRL2 0x24 +#define BCM63138_BRIGHT_CTRL3 0x28 +#define BCM63138_BRIGHT_CTRL4 0x2c +#define BCM63138_POWER_LED_CFG 0x30 +#define BCM63138_HW_POLARITY 0xb4 +#define BCM63138_SW_DATA 0xb8 +#define BCM63138_SW_POLARITY 0xbc +#define BCM63138_PARALLEL_LED_POLARITY 0xc0 +#define BCM63138_SERIAL_LED_POLARITY 0xc4 +#define BCM63138_HW_LED_STATUS 0xc8 +#define BCM63138_FLASH_CTRL_STATUS 0xcc +#define BCM63138_FLASH_BRT_CTRL 0xd0 +#define BCM63138_FLASH_P_LED_OUT_STATUS 0xd4 +#define BCM63138_FLASH_S_LED_OUT_STATUS 0xd8 + +struct bcm63138_leds { + struct device *dev; + void __iomem *base; + spinlock_t lock; +}; + +struct bcm63138_led { + struct bcm63138_leds *leds; + struct led_classdev cdev; + u32 pin; + bool active_low; +}; + +/* + * I/O access + */ + +static void bcm63138_leds_write(struct bcm63138_leds *leds, unsigned int reg, + u32 data) +{ + writel(data, leds->base + reg); +} + +static unsigned long bcm63138_leds_read(struct bcm63138_leds *leds, + unsigned int reg) +{ + return readl(leds->base + reg); +} + +static void bcm63138_leds_update_bits(struct bcm63138_leds *leds, + unsigned int reg, u32 mask, u32 val) +{ + WARN_ON(val & ~mask); + + bcm63138_leds_write(leds, reg, (bcm63138_leds_read(leds, reg) & ~mask) | (val & mask)); +} + +/* + * Helpers + */ + +static void bcm63138_leds_set_flash_rate(struct bcm63138_leds *leds, + struct bcm63138_led *led, + u8 value) +{ + int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4; + int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS; + + bcm63138_leds_update_bits(leds, BCM63138_FLASH_RATE_CTRL1 + reg_offset, + BCM63138_LED_MASK << shift, value << shift); +} + +static void bcm63138_leds_set_bright(struct bcm63138_leds *leds, + struct bcm63138_led *led, + u8 value) +{ + int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4; + int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS; + + bcm63138_leds_update_bits(leds, BCM63138_BRIGHT_CTRL1 + reg_offset, + BCM63138_LED_MASK << shift, value << shift); +} + +static void bcm63138_leds_enable_led(struct bcm63138_leds *leds, + struct bcm63138_led *led, + enum led_brightness value) +{ + u32 bit = BIT(led->pin); + + bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit, value ? bit : 0); +} + +/* + * API callbacks + */ + +static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); + struct bcm63138_leds *leds = led->leds; + unsigned long flags; + + spin_lock_irqsave(&leds->lock, flags); + + bcm63138_leds_enable_led(leds, led, value); + if (!value) + bcm63138_leds_set_flash_rate(leds, led, 0); + else + bcm63138_leds_set_bright(leds, led, value); + + spin_unlock_irqrestore(&leds->lock, flags); +} + +static int bcm63138_leds_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); + struct bcm63138_leds *leds = led->leds; + unsigned long flags; + u8 value; + + if (!*delay_on && !*delay_off) { + *delay_on = 640; + *delay_off = 640; + } + + if (*delay_on != *delay_off) { + dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n"); + return -EINVAL; + } + + switch (*delay_on) { + case 1152 ... 1408: /* 1280 ms ± 10% */ + value = 0x7; + break; + case 576 ... 704: /* 640 ms ± 10% */ + value = 0x6; + break; + case 288 ... 352: /* 320 ms ± 10% */ + value = 0x5; + break; + case 126 ... 154: /* 140 ms ± 10% */ + value = 0x4; + break; + case 59 ... 72: /* 65 ms ± 10% */ + value = 0x3; + break; + default: + dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n", + *delay_on); + return -EINVAL; + } + + spin_lock_irqsave(&leds->lock, flags); + + bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS); + bcm63138_leds_set_flash_rate(leds, led, value); + + spin_unlock_irqrestore(&leds->lock, flags); + + return 0; +} + +/* + * LED driver + */ + +static void bcm63138_leds_create_led(struct bcm63138_leds *leds, + struct device_node *np) +{ + struct led_init_data init_data = { + .fwnode = of_fwnode_handle(np), + }; + struct device *dev = leds->dev; + struct bcm63138_led *led; + struct pinctrl *pinctrl; + u32 bit; + int err; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) { + dev_err(dev, "Failed to alloc LED\n"); + return; + } + + led->leds = leds; + + if (of_property_read_u32(np, "reg", &led->pin)) { + dev_err(dev, "Missing \"reg\" property in %pOF\n", np); + goto err_free; + } + + if (led->pin >= BCM63138_MAX_LEDS) { + dev_err(dev, "Invalid \"reg\" value %d\n", led->pin); + goto err_free; + } + + led->active_low = of_property_read_bool(np, "active-low"); + + led->cdev.max_brightness = BCM63138_MAX_BRIGHTNESS; + led->cdev.brightness_set = bcm63138_leds_brightness_set; + led->cdev.blink_set = bcm63138_leds_blink_set; + + err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); + if (err) { + dev_err(dev, "Failed to register LED %pOF: %d\n", np, err); + goto err_free; + } + + pinctrl = devm_pinctrl_get_select_default(led->cdev.dev); + if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) { + dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n", + np, PTR_ERR(pinctrl)); + } + + bit = BIT(led->pin); + bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit, + led->active_low ? 0 : bit); + bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0); + bcm63138_leds_set_flash_rate(leds, led, 0); + bcm63138_leds_enable_led(leds, led, led->cdev.brightness); + + return; + +err_free: + devm_kfree(dev, led); +} + +static int bcm63138_leds_probe(struct platform_device *pdev) +{ + struct device_node *np = dev_of_node(&pdev->dev); + struct device *dev = &pdev->dev; + struct bcm63138_leds *leds; + struct device_node *child; + + leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); + if (!leds) + return -ENOMEM; + + leds->dev = dev; + + leds->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(leds->base)) + return PTR_ERR(leds->base); + + spin_lock_init(&leds->lock); + + bcm63138_leds_write(leds, BCM63138_GLB_CTRL, + BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL | + BCM63138_GLB_CTRL_SERIAL_LED_EN_POL); + bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0); + bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0); + bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0); + + for_each_available_child_of_node(np, child) { + bcm63138_leds_create_led(leds, child); + } + + return 0; +} + +static const struct of_device_id bcm63138_leds_of_match_table[] = { + { .compatible = "brcm,bcm63138-leds", }, + { }, +}; + +static struct platform_driver bcm63138_leds_driver = { + .probe = bcm63138_leds_probe, + .driver = { + .name = "leds-bcm63xxx", + .of_match_table = bcm63138_leds_of_match_table, + }, +}; + +module_platform_driver(bcm63138_leds_driver); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 4161b9dd7e48..52b59b62f437 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -11,9 +11,9 @@ #include <linux/err.h> #include <linux/i2c.h> #include <linux/leds.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/of_device.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/delay.h> @@ -21,39 +21,64 @@ /* register numbers */ #define IS31FL319X_SHUTDOWN 0x00 -#define IS31FL319X_CTRL1 0x01 -#define IS31FL319X_CTRL2 0x02 -#define IS31FL319X_CONFIG1 0x03 -#define IS31FL319X_CONFIG2 0x04 -#define IS31FL319X_RAMP_MODE 0x05 -#define IS31FL319X_BREATH_MASK 0x06 -#define IS31FL319X_PWM(channel) (0x07 + channel) -#define IS31FL319X_DATA_UPDATE 0x10 -#define IS31FL319X_T0(channel) (0x11 + channel) -#define IS31FL319X_T123_1 0x1a -#define IS31FL319X_T123_2 0x1b -#define IS31FL319X_T123_3 0x1c -#define IS31FL319X_T4(channel) (0x1d + channel) -#define IS31FL319X_TIME_UPDATE 0x26 -#define IS31FL319X_RESET 0xff - -#define IS31FL319X_REG_CNT (IS31FL319X_RESET + 1) + +/* registers for 3190, 3191 and 3193 */ +#define IS31FL3190_BREATHING 0x01 +#define IS31FL3190_LEDMODE 0x02 +#define IS31FL3190_CURRENT 0x03 +#define IS31FL3190_PWM(channel) (0x04 + channel) +#define IS31FL3190_DATA_UPDATE 0x07 +#define IS31FL3190_T0(channel) (0x0a + channel) +#define IS31FL3190_T1T2(channel) (0x10 + channel) +#define IS31FL3190_T3T4(channel) (0x16 + channel) +#define IS31FL3190_TIME_UPDATE 0x1c +#define IS31FL3190_LEDCONTROL 0x1d +#define IS31FL3190_RESET 0x2f + +#define IS31FL3190_CURRENT_uA_MIN 5000 +#define IS31FL3190_CURRENT_uA_DEFAULT 42000 +#define IS31FL3190_CURRENT_uA_MAX 42000 +#define IS31FL3190_CURRENT_MASK GENMASK(4, 2) +#define IS31FL3190_CURRENT_5_mA 0x02 +#define IS31FL3190_CURRENT_10_mA 0x01 +#define IS31FL3190_CURRENT_17dot5_mA 0x04 +#define IS31FL3190_CURRENT_30_mA 0x03 +#define IS31FL3190_CURRENT_42_mA 0x00 + +/* registers for 3196 and 3199 */ +#define IS31FL3196_CTRL1 0x01 +#define IS31FL3196_CTRL2 0x02 +#define IS31FL3196_CONFIG1 0x03 +#define IS31FL3196_CONFIG2 0x04 +#define IS31FL3196_RAMP_MODE 0x05 +#define IS31FL3196_BREATH_MARK 0x06 +#define IS31FL3196_PWM(channel) (0x07 + channel) +#define IS31FL3196_DATA_UPDATE 0x10 +#define IS31FL3196_T0(channel) (0x11 + channel) +#define IS31FL3196_T123_1 0x1a +#define IS31FL3196_T123_2 0x1b +#define IS31FL3196_T123_3 0x1c +#define IS31FL3196_T4(channel) (0x1d + channel) +#define IS31FL3196_TIME_UPDATE 0x26 +#define IS31FL3196_RESET 0xff + +#define IS31FL3196_REG_CNT (IS31FL3196_RESET + 1) #define IS31FL319X_MAX_LEDS 9 /* CS (Current Setting) in CONFIG2 register */ -#define IS31FL319X_CONFIG2_CS_SHIFT 4 -#define IS31FL319X_CONFIG2_CS_MASK 0x7 -#define IS31FL319X_CONFIG2_CS_STEP_REF 12 +#define IS31FL3196_CONFIG2_CS_SHIFT 4 +#define IS31FL3196_CONFIG2_CS_MASK GENMASK(2, 0) +#define IS31FL3196_CONFIG2_CS_STEP_REF 12 -#define IS31FL319X_CURRENT_MIN ((u32)5000) -#define IS31FL319X_CURRENT_MAX ((u32)40000) -#define IS31FL319X_CURRENT_STEP ((u32)5000) -#define IS31FL319X_CURRENT_DEFAULT ((u32)20000) +#define IS31FL3196_CURRENT_uA_MIN 5000 +#define IS31FL3196_CURRENT_uA_MAX 40000 +#define IS31FL3196_CURRENT_uA_STEP 5000 +#define IS31FL3196_CURRENT_uA_DEFAULT 20000 /* Audio gain in CONFIG2 register */ -#define IS31FL319X_AUDIO_GAIN_DB_MAX ((u32)21) -#define IS31FL319X_AUDIO_GAIN_DB_STEP ((u32)3) +#define IS31FL3196_AUDIO_GAIN_DB_MAX ((u32)21) +#define IS31FL3196_AUDIO_GAIN_DB_STEP 3 /* * regmap is used as a cache of chip's register space, @@ -78,52 +103,161 @@ struct is31fl319x_chip { struct is31fl319x_chipdef { int num_leds; + u8 reset_reg; + const struct regmap_config *is31fl319x_regmap_config; + int (*brightness_set)(struct led_classdev *cdev, enum led_brightness brightness); + u32 current_default; + u32 current_min; + u32 current_max; + bool is_3196or3199; }; -static const struct is31fl319x_chipdef is31fl3190_cdef = { - .num_leds = 1, -}; +static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg) +{ + /* we have no readable registers */ + return false; +} -static const struct is31fl319x_chipdef is31fl3193_cdef = { - .num_leds = 3, +static bool is31fl3190_volatile_reg(struct device *dev, unsigned int reg) +{ + /* volatile registers are not cached */ + switch (reg) { + case IS31FL3190_DATA_UPDATE: + case IS31FL3190_TIME_UPDATE: + case IS31FL3190_RESET: + return true; /* always write-through */ + default: + return false; + } +} + +static const struct reg_default is31fl3190_reg_defaults[] = { + { IS31FL3190_LEDMODE, 0x00 }, + { IS31FL3190_CURRENT, 0x00 }, + { IS31FL3190_PWM(0), 0x00 }, + { IS31FL3190_PWM(1), 0x00 }, + { IS31FL3190_PWM(2), 0x00 }, }; -static const struct is31fl319x_chipdef is31fl3196_cdef = { - .num_leds = 6, +static struct regmap_config is31fl3190_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IS31FL3190_RESET, + .cache_type = REGCACHE_FLAT, + .readable_reg = is31fl319x_readable_reg, + .volatile_reg = is31fl3190_volatile_reg, + .reg_defaults = is31fl3190_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(is31fl3190_reg_defaults), }; -static const struct is31fl319x_chipdef is31fl3199_cdef = { - .num_leds = 9, +static bool is31fl3196_volatile_reg(struct device *dev, unsigned int reg) +{ + /* volatile registers are not cached */ + switch (reg) { + case IS31FL3196_DATA_UPDATE: + case IS31FL3196_TIME_UPDATE: + case IS31FL3196_RESET: + return true; /* always write-through */ + default: + return false; + } +} + +static const struct reg_default is31fl3196_reg_defaults[] = { + { IS31FL3196_CONFIG1, 0x00 }, + { IS31FL3196_CONFIG2, 0x00 }, + { IS31FL3196_PWM(0), 0x00 }, + { IS31FL3196_PWM(1), 0x00 }, + { IS31FL3196_PWM(2), 0x00 }, + { IS31FL3196_PWM(3), 0x00 }, + { IS31FL3196_PWM(4), 0x00 }, + { IS31FL3196_PWM(5), 0x00 }, + { IS31FL3196_PWM(6), 0x00 }, + { IS31FL3196_PWM(7), 0x00 }, + { IS31FL3196_PWM(8), 0x00 }, }; -static const struct of_device_id of_is31fl319x_match[] = { - { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, }, - { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, }, - { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, }, - { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, }, - { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, }, - { .compatible = "si-en,sn3199", .data = &is31fl3199_cdef, }, - { } +static struct regmap_config is31fl3196_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IS31FL3196_REG_CNT, + .cache_type = REGCACHE_FLAT, + .readable_reg = is31fl319x_readable_reg, + .volatile_reg = is31fl3196_volatile_reg, + .reg_defaults = is31fl3196_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(is31fl3196_reg_defaults), }; -MODULE_DEVICE_TABLE(of, of_is31fl319x_match); -static int is31fl319x_brightness_set(struct led_classdev *cdev, +static int is31fl3190_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev); + struct is31fl319x_chip *is31 = led->chip; + int chan = led - is31->leds; + int ret; + int i; + u8 ctrl = 0; + + dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness); + + mutex_lock(&is31->lock); + + /* update PWM register */ + ret = regmap_write(is31->regmap, IS31FL3190_PWM(chan), brightness); + if (ret < 0) + goto out; + + /* read current brightness of all PWM channels */ + for (i = 0; i < is31->cdef->num_leds; i++) { + unsigned int pwm_value; + bool on; + + /* + * since neither cdev nor the chip can provide + * the current setting, we read from the regmap cache + */ + + ret = regmap_read(is31->regmap, IS31FL3190_PWM(i), &pwm_value); + on = ret >= 0 && pwm_value > LED_OFF; + + ctrl |= on << i; + } + + if (ctrl > 0) { + dev_dbg(&is31->client->dev, "power up %02x\n", ctrl); + regmap_write(is31->regmap, IS31FL3190_LEDCONTROL, ctrl); + /* update PWMs */ + regmap_write(is31->regmap, IS31FL3190_DATA_UPDATE, 0x00); + /* enable chip from shut down and enable all channels */ + ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x20); + } else { + dev_dbg(&is31->client->dev, "power down\n"); + /* shut down (no need to clear LEDCONTROL) */ + ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01); + } + +out: + mutex_unlock(&is31->lock); + + return ret; +} + +static int is31fl3196_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) { - struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, - cdev); + struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev); struct is31fl319x_chip *is31 = led->chip; int chan = led - is31->leds; int ret; int i; u8 ctrl1 = 0, ctrl2 = 0; - dev_dbg(&is31->client->dev, "%s %d: %d\n", __func__, chan, brightness); + dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness); mutex_lock(&is31->lock); /* update PWM register */ - ret = regmap_write(is31->regmap, IS31FL319X_PWM(chan), brightness); + ret = regmap_write(is31->regmap, IS31FL3196_PWM(chan), brightness); if (ret < 0) goto out; @@ -137,9 +271,7 @@ static int is31fl319x_brightness_set(struct led_classdev *cdev, * the current setting, we read from the regmap cache */ - ret = regmap_read(is31->regmap, IS31FL319X_PWM(i), &pwm_value); - dev_dbg(&is31->client->dev, "%s read %d: ret=%d: %d\n", - __func__, i, ret, pwm_value); + ret = regmap_read(is31->regmap, IS31FL3196_PWM(i), &pwm_value); on = ret >= 0 && pwm_value > LED_OFF; if (i < 3) @@ -153,10 +285,10 @@ static int is31fl319x_brightness_set(struct led_classdev *cdev, if (ctrl1 > 0 || ctrl2 > 0) { dev_dbg(&is31->client->dev, "power up %02x %02x\n", ctrl1, ctrl2); - regmap_write(is31->regmap, IS31FL319X_CTRL1, ctrl1); - regmap_write(is31->regmap, IS31FL319X_CTRL2, ctrl2); + regmap_write(is31->regmap, IS31FL3196_CTRL1, ctrl1); + regmap_write(is31->regmap, IS31FL3196_CTRL2, ctrl2); /* update PWMs */ - regmap_write(is31->regmap, IS31FL319X_DATA_UPDATE, 0x00); + regmap_write(is31->regmap, IS31FL3196_DATA_UPDATE, 0x00); /* enable chip from shut down */ ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01); } else { @@ -171,92 +303,141 @@ out: return ret; } -static int is31fl319x_parse_child_dt(const struct device *dev, - const struct device_node *child, - struct is31fl319x_led *led) +static const struct is31fl319x_chipdef is31fl3190_cdef = { + .num_leds = 1, + .reset_reg = IS31FL3190_RESET, + .is31fl319x_regmap_config = &is31fl3190_regmap_config, + .brightness_set = is31fl3190_brightness_set, + .current_default = IS31FL3190_CURRENT_uA_DEFAULT, + .current_min = IS31FL3190_CURRENT_uA_MIN, + .current_max = IS31FL3190_CURRENT_uA_MAX, + .is_3196or3199 = false, +}; + +static const struct is31fl319x_chipdef is31fl3193_cdef = { + .num_leds = 3, + .reset_reg = IS31FL3190_RESET, + .is31fl319x_regmap_config = &is31fl3190_regmap_config, + .brightness_set = is31fl3190_brightness_set, + .current_default = IS31FL3190_CURRENT_uA_DEFAULT, + .current_min = IS31FL3190_CURRENT_uA_MIN, + .current_max = IS31FL3190_CURRENT_uA_MAX, + .is_3196or3199 = false, +}; + +static const struct is31fl319x_chipdef is31fl3196_cdef = { + .num_leds = 6, + .reset_reg = IS31FL3196_RESET, + .is31fl319x_regmap_config = &is31fl3196_regmap_config, + .brightness_set = is31fl3196_brightness_set, + .current_default = IS31FL3196_CURRENT_uA_DEFAULT, + .current_min = IS31FL3196_CURRENT_uA_MIN, + .current_max = IS31FL3196_CURRENT_uA_MAX, + .is_3196or3199 = true, +}; + +static const struct is31fl319x_chipdef is31fl3199_cdef = { + .num_leds = 9, + .reset_reg = IS31FL3196_RESET, + .is31fl319x_regmap_config = &is31fl3196_regmap_config, + .brightness_set = is31fl3196_brightness_set, + .current_default = IS31FL3196_CURRENT_uA_DEFAULT, + .current_min = IS31FL3196_CURRENT_uA_MIN, + .current_max = IS31FL3196_CURRENT_uA_MAX, + .is_3196or3199 = true, +}; + +static const struct of_device_id of_is31fl319x_match[] = { + { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, }, + { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, }, + { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, }, + { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, }, + { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, }, + { .compatible = "si-en,sn3190", .data = &is31fl3190_cdef, }, + { .compatible = "si-en,sn3191", .data = &is31fl3190_cdef, }, + { .compatible = "si-en,sn3193", .data = &is31fl3193_cdef, }, + { .compatible = "si-en,sn3196", .data = &is31fl3196_cdef, }, + { .compatible = "si-en,sn3199", .data = &is31fl3199_cdef, }, + { } +}; +MODULE_DEVICE_TABLE(of, of_is31fl319x_match); + +static int is31fl319x_parse_child_fw(const struct device *dev, + const struct fwnode_handle *child, + struct is31fl319x_led *led, + struct is31fl319x_chip *is31) { struct led_classdev *cdev = &led->cdev; int ret; - if (of_property_read_string(child, "label", &cdev->name)) - cdev->name = child->name; + if (fwnode_property_read_string(child, "label", &cdev->name)) + cdev->name = fwnode_get_name(child); - ret = of_property_read_string(child, "linux,default-trigger", - &cdev->default_trigger); + ret = fwnode_property_read_string(child, "linux,default-trigger", &cdev->default_trigger); if (ret < 0 && ret != -EINVAL) /* is optional */ return ret; - led->max_microamp = IS31FL319X_CURRENT_DEFAULT; - ret = of_property_read_u32(child, "led-max-microamp", - &led->max_microamp); + led->max_microamp = is31->cdef->current_default; + ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp); if (!ret) { - if (led->max_microamp < IS31FL319X_CURRENT_MIN) + if (led->max_microamp < is31->cdef->current_min) return -EINVAL; /* not supported */ led->max_microamp = min(led->max_microamp, - IS31FL319X_CURRENT_MAX); + is31->cdef->current_max); } return 0; } -static int is31fl319x_parse_dt(struct device *dev, - struct is31fl319x_chip *is31) +static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) { - struct device_node *np = dev_of_node(dev), *child; + struct fwnode_handle *fwnode = dev_fwnode(dev), *child; int count; int ret; - if (!np) - return -ENODEV; - - is31->shutdown_gpio = devm_gpiod_get_optional(dev, - "shutdown", - GPIOD_OUT_HIGH); - if (IS_ERR(is31->shutdown_gpio)) { - ret = PTR_ERR(is31->shutdown_gpio); - dev_err(dev, "Failed to get shutdown gpio: %d\n", ret); - return ret; - } + is31->shutdown_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + if (IS_ERR(is31->shutdown_gpio)) + return dev_err_probe(dev, PTR_ERR(is31->shutdown_gpio), + "Failed to get shutdown gpio\n"); is31->cdef = device_get_match_data(dev); - count = of_get_available_child_count(np); + count = 0; + fwnode_for_each_available_child_node(fwnode, child) + count++; dev_dbg(dev, "probing with %d leds defined in DT\n", count); - if (!count || count > is31->cdef->num_leds) { - dev_err(dev, "Number of leds defined must be between 1 and %u\n", - is31->cdef->num_leds); - return -ENODEV; - } + if (!count || count > is31->cdef->num_leds) + return dev_err_probe(dev, -ENODEV, + "Number of leds defined must be between 1 and %u\n", + is31->cdef->num_leds); - for_each_available_child_of_node(np, child) { + fwnode_for_each_available_child_node(fwnode, child) { struct is31fl319x_led *led; u32 reg; - ret = of_property_read_u32(child, "reg", ®); + ret = fwnode_property_read_u32(child, "reg", ®); if (ret) { - dev_err(dev, "Failed to read led 'reg' property\n"); + ret = dev_err_probe(dev, ret, "Failed to read led 'reg' property\n"); goto put_child_node; } if (reg < 1 || reg > is31->cdef->num_leds) { - dev_err(dev, "invalid led reg %u\n", reg); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg); goto put_child_node; } led = &is31->leds[reg - 1]; if (led->configured) { - dev_err(dev, "led %u is already configured\n", reg); - ret = -EINVAL; + ret = dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg); goto put_child_node; } - ret = is31fl319x_parse_child_dt(dev, child, led); + ret = is31fl319x_parse_child_fw(dev, child, led, is31); if (ret) { - dev_err(dev, "led %u DT parsing failed\n", reg); + ret = dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg); goto put_child_node; } @@ -264,82 +445,62 @@ static int is31fl319x_parse_dt(struct device *dev, } is31->audio_gain_db = 0; - ret = of_property_read_u32(np, "audio-gain-db", &is31->audio_gain_db); - if (!ret) - is31->audio_gain_db = min(is31->audio_gain_db, - IS31FL319X_AUDIO_GAIN_DB_MAX); + if (is31->cdef->is_3196or3199) { + ret = fwnode_property_read_u32(fwnode, "audio-gain-db", &is31->audio_gain_db); + if (!ret) + is31->audio_gain_db = min(is31->audio_gain_db, + IS31FL3196_AUDIO_GAIN_DB_MAX); + } return 0; put_child_node: - of_node_put(child); + fwnode_handle_put(child); return ret; } -static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg) -{ /* we have no readable registers */ - return false; -} - -static bool is31fl319x_volatile_reg(struct device *dev, unsigned int reg) -{ /* volatile registers are not cached */ - switch (reg) { - case IS31FL319X_DATA_UPDATE: - case IS31FL319X_TIME_UPDATE: - case IS31FL319X_RESET: - return true; /* always write-through */ +static inline int is31fl3190_microamp_to_cs(struct device *dev, u32 microamp) +{ + switch (microamp) { + case 5000: + return IS31FL3190_CURRENT_5_mA; + case 10000: + return IS31FL3190_CURRENT_10_mA; + case 17500: + return IS31FL3190_CURRENT_17dot5_mA; + case 30000: + return IS31FL3190_CURRENT_30_mA; + case 42000: + return IS31FL3190_CURRENT_42_mA; default: - return false; + dev_warn(dev, "Unsupported current value: %d, using 5000 µA!\n", microamp); + return IS31FL3190_CURRENT_5_mA; } } -static const struct reg_default is31fl319x_reg_defaults[] = { - { IS31FL319X_CONFIG1, 0x00}, - { IS31FL319X_CONFIG2, 0x00}, - { IS31FL319X_PWM(0), 0x00}, - { IS31FL319X_PWM(1), 0x00}, - { IS31FL319X_PWM(2), 0x00}, - { IS31FL319X_PWM(3), 0x00}, - { IS31FL319X_PWM(4), 0x00}, - { IS31FL319X_PWM(5), 0x00}, - { IS31FL319X_PWM(6), 0x00}, - { IS31FL319X_PWM(7), 0x00}, - { IS31FL319X_PWM(8), 0x00}, -}; - -static struct regmap_config regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .max_register = IS31FL319X_REG_CNT, - .cache_type = REGCACHE_FLAT, - .readable_reg = is31fl319x_readable_reg, - .volatile_reg = is31fl319x_volatile_reg, - .reg_defaults = is31fl319x_reg_defaults, - .num_reg_defaults = ARRAY_SIZE(is31fl319x_reg_defaults), -}; - -static inline int is31fl319x_microamp_to_cs(struct device *dev, u32 microamp) -{ /* round down to nearest supported value (range check done by caller) */ - u32 step = microamp / IS31FL319X_CURRENT_STEP; +static inline int is31fl3196_microamp_to_cs(struct device *dev, u32 microamp) +{ + /* round down to nearest supported value (range check done by caller) */ + u32 step = microamp / IS31FL3196_CURRENT_uA_STEP; - return ((IS31FL319X_CONFIG2_CS_STEP_REF - step) & - IS31FL319X_CONFIG2_CS_MASK) << - IS31FL319X_CONFIG2_CS_SHIFT; /* CS encoding */ + return ((IS31FL3196_CONFIG2_CS_STEP_REF - step) & + IS31FL3196_CONFIG2_CS_MASK) << + IS31FL3196_CONFIG2_CS_SHIFT; /* CS encoding */ } -static inline int is31fl319x_db_to_gain(u32 dezibel) -{ /* round down to nearest supported value (range check done by caller) */ - return dezibel / IS31FL319X_AUDIO_GAIN_DB_STEP; +static inline int is31fl3196_db_to_gain(u32 dezibel) +{ + /* round down to nearest supported value (range check done by caller) */ + return dezibel / IS31FL3196_AUDIO_GAIN_DB_STEP; } -static int is31fl319x_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int is31fl319x_probe(struct i2c_client *client) { struct is31fl319x_chip *is31; struct device *dev = &client->dev; int err; int i = 0; - u32 aggregated_led_microamp = IS31FL319X_CURRENT_MAX; + u32 aggregated_led_microamp; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -EIO; @@ -349,10 +510,13 @@ static int is31fl319x_probe(struct i2c_client *client, return -ENOMEM; mutex_init(&is31->lock); + err = devm_add_action(dev, (void (*)(void *))mutex_destroy, &is31->lock); + if (err) + return err; - err = is31fl319x_parse_dt(&client->dev, is31); + err = is31fl319x_parse_fw(&client->dev, is31); if (err) - goto free_mutex; + return err; if (is31->shutdown_gpio) { gpiod_direction_output(is31->shutdown_gpio, 0); @@ -361,37 +525,35 @@ static int is31fl319x_probe(struct i2c_client *client, } is31->client = client; - is31->regmap = devm_regmap_init_i2c(client, ®map_config); - if (IS_ERR(is31->regmap)) { - dev_err(&client->dev, "failed to allocate register map\n"); - err = PTR_ERR(is31->regmap); - goto free_mutex; - } + is31->regmap = devm_regmap_init_i2c(client, is31->cdef->is31fl319x_regmap_config); + if (IS_ERR(is31->regmap)) + return dev_err_probe(dev, PTR_ERR(is31->regmap), "failed to allocate register map\n"); i2c_set_clientdata(client, is31); /* check for write-reply from chip (we can't read any registers) */ - err = regmap_write(is31->regmap, IS31FL319X_RESET, 0x00); - if (err < 0) { - dev_err(&client->dev, "no response from chip write: err = %d\n", - err); - err = -EIO; /* does not answer */ - goto free_mutex; - } + err = regmap_write(is31->regmap, is31->cdef->reset_reg, 0x00); + if (err < 0) + return dev_err_probe(dev, err, "no response from chip write\n"); /* * Kernel conventions require per-LED led-max-microamp property. * But the chip does not allow to limit individual LEDs. * So we take minimum from all subnodes for safety of hardware. */ + aggregated_led_microamp = is31->cdef->current_max; for (i = 0; i < is31->cdef->num_leds; i++) if (is31->leds[i].configured && is31->leds[i].max_microamp < aggregated_led_microamp) aggregated_led_microamp = is31->leds[i].max_microamp; - regmap_write(is31->regmap, IS31FL319X_CONFIG2, - is31fl319x_microamp_to_cs(dev, aggregated_led_microamp) | - is31fl319x_db_to_gain(is31->audio_gain_db)); + if (is31->cdef->is_3196or3199) + regmap_write(is31->regmap, IS31FL3196_CONFIG2, + is31fl3196_microamp_to_cs(dev, aggregated_led_microamp) | + is31fl3196_db_to_gain(is31->audio_gain_db)); + else + regmap_update_bits(is31->regmap, IS31FL3190_CURRENT, IS31FL3190_CURRENT_MASK, + is31fl3190_microamp_to_cs(dev, aggregated_led_microamp)); for (i = 0; i < is31->cdef->num_leds; i++) { struct is31fl319x_led *led = &is31->leds[i]; @@ -400,26 +562,14 @@ static int is31fl319x_probe(struct i2c_client *client, continue; led->chip = is31; - led->cdev.brightness_set_blocking = is31fl319x_brightness_set; + led->cdev.brightness_set_blocking = is31->cdef->brightness_set; err = devm_led_classdev_register(&client->dev, &led->cdev); if (err < 0) - goto free_mutex; + return err; } return 0; - -free_mutex: - mutex_destroy(&is31->lock); - return err; -} - -static int is31fl319x_remove(struct i2c_client *client) -{ - struct is31fl319x_chip *is31 = i2c_get_clientdata(client); - - mutex_destroy(&is31->lock); - return 0; } /* @@ -432,6 +582,10 @@ static const struct i2c_device_id is31fl319x_id[] = { { "is31fl3193" }, { "is31fl3196" }, { "is31fl3199" }, + { "sn3190" }, + { "sn3191" }, + { "sn3193" }, + { "sn3196" }, { "sn3199" }, {}, }; @@ -440,10 +594,9 @@ MODULE_DEVICE_TABLE(i2c, is31fl319x_id); static struct i2c_driver is31fl319x_driver = { .driver = { .name = "leds-is31fl319x", - .of_match_table = of_match_ptr(of_is31fl319x_match), + .of_match_table = of_is31fl319x_match, }, - .probe = is31fl319x_probe, - .remove = is31fl319x_remove, + .probe_new = is31fl319x_probe, .id_table = is31fl319x_id, }; diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 1adfed1c0619..eac6f4a573b2 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -239,9 +239,6 @@ static int omnia_leds_probe(struct i2c_client *client, led += ret; } - if (devm_device_add_groups(dev, omnia_led_controller_groups)) - dev_warn(dev, "Could not add attribute group!\n"); - return 0; } @@ -283,6 +280,7 @@ static struct i2c_driver omnia_leds_driver = { .driver = { .name = "leds-turris-omnia", .of_match_table = of_omnia_leds_match, + .dev_groups = omnia_led_controller_groups, }, }; diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index 45e38708ecb1..da9d2218ae18 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -19,6 +19,7 @@ struct pwm_led { struct pwm_device *pwm; struct pwm_state state; + bool active_low; }; struct pwm_mc_led { @@ -45,6 +46,9 @@ static int led_pwm_mc_set(struct led_classdev *cdev, duty *= mc_cdev->subled_info[i].brightness; do_div(duty, cdev->max_brightness); + if (priv->leds[i].active_low) + duty = priv->leds[i].state.period - duty; + priv->leds[i].state.duty_cycle = duty; priv->leds[i].state.enabled = duty > 0; ret = pwm_apply_state(priv->leds[i].pwm, @@ -72,11 +76,11 @@ static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv, pwmled = &priv->leds[priv->mc_cdev.num_colors]; pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); if (IS_ERR(pwmled->pwm)) { - ret = PTR_ERR(pwmled->pwm); - dev_err(dev, "unable to request PWM: %d\n", ret); + ret = dev_err_probe(dev, PTR_ERR(pwmled->pwm), "unable to request PWM\n"); goto release_fwnode; } pwm_init_state(pwmled->pwm, &pwmled->state); + pwmled->active_low = fwnode_property_read_bool(fwnode, "active-low"); ret = fwnode_property_read_u32(fwnode, "color", &color); if (ret) { |