Merge tag 'leds-next-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds

Pull LED updates from Lee Jones:
 "New Drivers:
   - Add support for Allwinner A100 RGB LED controller
   - Add support for Maxim 5970 Dual Hot-swap controller

  New Device Support:
   - Add support for AW20108 to Awinic LED driver

  New Functionality:
   - Extend support for Net speeds to include; 2.5G, 5G and 10G
   - Allow tx/rx and cts/dsr/dcd/rng TTY LEDS to be turned on and off
     via sysfs if required
   - Add support for hardware control in AW200xx

  Fix-ups:
   - Use safer methods for string handling
   - Improve error handling; return proper error values, simplify,
     avoid duplicates, etc
   - Replace Mutex use with the Completion mechanism
   - Fix include lists; alphabetise, remove unused, explicitly add used
   - Use generic platform device properties
   - Use/convert to new/better APIs/helpers/MACROs instead of
     hand-rolling implementations
   - Device Tree binding adaptions/conversions/creation
   - Continue work to remove superfluous platform .remove() call-backs
   - Remove superfluous/defunct code
   - Trivial; whitespace, unused variables, spelling, clean-ups, etc
   - Avoid unnecessary duplicate locks

  Bug Fixes:
   - Repair Kconfig based dependency lists
   - Ensure unused dynamically allocated data is freed after use
   - Fix support for brightness control
   - Add missing sufficient delays during reset to ensure correct
     operation
   - Avoid division-by-zero issues"

* tag 'leds-next-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (45 commits)
  leds: trigger: netdev: Add core support for hw not supporting fallback to LED sw control
  leds: trigger: panic: Don't register panic notifier if creating the trigger failed
  leds: sun50i-a100: Convert to be agnostic to property provider
  leds: max5970: Add missing headers
  leds: max5970: Make use of dev_err_probe()
  leds: max5970: Make use of device properties
  leds: max5970: Remove unused variable
  leds: rgb: Drop obsolete dependency on COMPILE_TEST
  leds: sun50i-a100: Avoid division-by-zero warning
  leds: trigger: Remove unused function led_trigger_rename_static()
  leds: qcom-lpg: Introduce a wrapper for getting driver data from a pwm chip
  leds: gpio: Add kernel log if devm_fwnode_gpiod_get() fails
  dt-bindings: leds: qcom,spmi-flash-led: Fix example node name
  dt-bindings: leds: aw200xx: Fix led pattern and add reg constraints
  dt-bindings: leds: awinic,aw200xx: Add AW20108 device
  leds: aw200xx: Add support for aw20108 device
  leds: aw200xx: Improve autodim calculation method
  leds: aw200xx: Enable disable_locking flag in regmap config
  leds: aw200xx: Add delay after software reset
  dt-bindings: leds: aw200xx: Remove property "awinic,display-rows"
  ...
This commit is contained in:
Linus Torvalds
2024-01-17 15:25:27 -08:00
24 changed files with 1449 additions and 201 deletions
+11 -15
View File
@@ -41,33 +41,30 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led)
return IRQ_HANDLED;
}
static ssize_t gpio_trig_brightness_show(struct device *dev,
static ssize_t desired_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", gpio_data->desired_brightness);
return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness);
}
static ssize_t gpio_trig_brightness_store(struct device *dev,
static ssize_t desired_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
unsigned desired_brightness;
u8 desired_brightness;
int ret;
ret = sscanf(buf, "%u", &desired_brightness);
if (ret < 1 || desired_brightness > 255) {
dev_err(dev, "invalid value\n");
return -EINVAL;
}
ret = kstrtou8(buf, 10, &desired_brightness);
if (ret)
return ret;
gpio_data->desired_brightness = desired_brightness;
return n;
}
static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
gpio_trig_brightness_store);
static DEVICE_ATTR_RW(desired_brightness);
static struct attribute *gpio_trig_attrs[] = {
&dev_attr_desired_brightness.attr,
@@ -89,10 +86,7 @@ static int gpio_trig_activate(struct led_classdev *led)
* The generic property "trigger-sources" is followed,
* and we hope that this is a GPIO.
*/
gpio_data->gpiod = fwnode_gpiod_get_index(dev->fwnode,
"trigger-sources",
0, GPIOD_IN,
"led-trigger");
gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN);
if (IS_ERR(gpio_data->gpiod)) {
ret = PTR_ERR(gpio_data->gpiod);
kfree(gpio_data);
@@ -104,6 +98,8 @@ static int gpio_trig_activate(struct led_classdev *led)
return -EINVAL;
}
gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger");
gpio_data->led = led;
led_set_trigger_data(led, gpio_data);
+46 -1
View File
@@ -38,6 +38,16 @@
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
*
* Note: If the user selects a mode that is not supported by hw, default
* behavior is to fall back to software control of the LED. However not every
* hw supports software control. LED callbacks brightness_set() and
* brightness_set_blocking() are NULL in this case. hw_control_is_supported()
* should use available means supported by hw to inform the user that selected
* mode isn't supported by hw. This could be switching off the LED or any
* hw blink mode. If software control fallback isn't possible, we return
* -EOPNOTSUPP to the user, but still store the selected mode. This is needed
* in case an intermediate unsupported mode is necessary to switch from one
* supported mode to another.
*/
struct led_netdev_data {
@@ -99,6 +109,18 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
trigger_data->link_speed == SPEED_1000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_2500)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_5000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
trigger_data->link_speed == SPEED_10000)
blink_on = true;
if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
trigger_data->duplex == DUPLEX_HALF)
blink_on = true;
@@ -289,6 +311,9 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
case TRIGGER_NETDEV_LINK_10:
case TRIGGER_NETDEV_LINK_100:
case TRIGGER_NETDEV_LINK_1000:
case TRIGGER_NETDEV_LINK_2500:
case TRIGGER_NETDEV_LINK_5000:
case TRIGGER_NETDEV_LINK_10000:
case TRIGGER_NETDEV_HALF_DUPLEX:
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
@@ -306,6 +331,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
size_t size, enum led_trigger_netdev_modes attr)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
struct led_classdev *led_cdev = trigger_data->led_cdev;
unsigned long state, mode = trigger_data->mode;
int ret;
int bit;
@@ -319,6 +345,9 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
case TRIGGER_NETDEV_LINK_10:
case TRIGGER_NETDEV_LINK_100:
case TRIGGER_NETDEV_LINK_1000:
case TRIGGER_NETDEV_LINK_2500:
case TRIGGER_NETDEV_LINK_5000:
case TRIGGER_NETDEV_LINK_10000:
case TRIGGER_NETDEV_HALF_DUPLEX:
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
@@ -337,7 +366,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
(test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_1000, &mode)))
test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
return -EINVAL;
cancel_delayed_work_sync(&trigger_data->work);
@@ -345,6 +377,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
trigger_data->mode = mode;
trigger_data->hw_control = can_hw_control(trigger_data);
if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
!trigger_data->hw_control)
return -EOPNOTSUPP;
set_baseline_state(trigger_data);
return size;
@@ -367,6 +403,9 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
@@ -425,6 +464,9 @@ static struct attribute *netdev_trig_attrs[] = {
&dev_attr_link_10.attr,
&dev_attr_link_100.attr,
&dev_attr_link_1000.attr,
&dev_attr_link_2500.attr,
&dev_attr_link_5000.attr,
&dev_attr_link_10000.attr,
&dev_attr_full_duplex.attr,
&dev_attr_half_duplex.attr,
&dev_attr_rx.attr,
@@ -522,6 +564,9 @@ static void netdev_trig_work(struct work_struct *work)
test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
interval = jiffies_to_msecs(
+4 -1
View File
@@ -64,10 +64,13 @@ static long led_panic_blink(int state)
static int __init ledtrig_panic_init(void)
{
led_trigger_register_simple("panic", &trigger);
if (!trigger)
return -ENOMEM;
atomic_notifier_chain_register(&panic_notifier_list,
&led_trigger_panic_nb);
led_trigger_register_simple("panic", &trigger);
panic_blink = led_panic_blink;
return 0;
}
+210 -37
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/module.h>
@@ -12,15 +13,45 @@
struct ledtrig_tty_data {
struct led_classdev *led_cdev;
struct delayed_work dwork;
struct mutex mutex;
struct completion sysfs;
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
bool mode_rx;
bool mode_tx;
bool mode_cts;
bool mode_dsr;
bool mode_dcd;
bool mode_rng;
};
static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
/* Indicates which state the LED should now display */
enum led_trigger_tty_state {
TTY_LED_BLINK,
TTY_LED_ENABLE,
TTY_LED_DISABLE,
};
enum led_trigger_tty_modes {
TRIGGER_TTY_RX = 0,
TRIGGER_TTY_TX,
TRIGGER_TTY_CTS,
TRIGGER_TTY_DSR,
TRIGGER_TTY_DCD,
TRIGGER_TTY_RNG,
};
static int ledtrig_tty_wait_for_completion(struct device *dev)
{
schedule_delayed_work(&trigger_data->dwork, 0);
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
int ret;
ret = wait_for_completion_timeout(&trigger_data->sysfs,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));
if (ret == 0)
return -ETIMEDOUT;
return ret;
}
static ssize_t ttyname_show(struct device *dev,
@@ -28,14 +59,16 @@ static ssize_t ttyname_show(struct device *dev,
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
ssize_t len = 0;
int completion;
mutex_lock(&trigger_data->mutex);
reinit_completion(&trigger_data->sysfs);
completion = ledtrig_tty_wait_for_completion(dev);
if (completion < 0)
return completion;
if (trigger_data->ttyname)
len = sprintf(buf, "%s\n", trigger_data->ttyname);
mutex_unlock(&trigger_data->mutex);
return len;
}
@@ -46,7 +79,7 @@ static ssize_t ttyname_store(struct device *dev,
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
char *ttyname;
ssize_t ret = size;
bool running;
int completion;
if (size > 0 && buf[size - 1] == '\n')
size -= 1;
@@ -59,9 +92,10 @@ static ssize_t ttyname_store(struct device *dev,
ttyname = NULL;
}
mutex_lock(&trigger_data->mutex);
running = trigger_data->ttyname != NULL;
reinit_completion(&trigger_data->sysfs);
completion = ledtrig_tty_wait_for_completion(dev);
if (completion < 0)
return completion;
kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
@@ -69,29 +103,107 @@ static ssize_t ttyname_store(struct device *dev,
trigger_data->ttyname = ttyname;
mutex_unlock(&trigger_data->mutex);
if (ttyname && !running)
ledtrig_tty_restart(trigger_data);
return ret;
}
static DEVICE_ATTR_RW(ttyname);
static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
enum led_trigger_tty_modes attr)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
bool state;
switch (attr) {
case TRIGGER_TTY_RX:
state = trigger_data->mode_rx;
break;
case TRIGGER_TTY_TX:
state = trigger_data->mode_tx;
break;
case TRIGGER_TTY_CTS:
state = trigger_data->mode_cts;
break;
case TRIGGER_TTY_DSR:
state = trigger_data->mode_dsr;
break;
case TRIGGER_TTY_DCD:
state = trigger_data->mode_dcd;
break;
case TRIGGER_TTY_RNG:
state = trigger_data->mode_rng;
break;
}
return sysfs_emit(buf, "%u\n", state);
}
static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
size_t size, enum led_trigger_tty_modes attr)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
bool state;
int ret;
ret = kstrtobool(buf, &state);
if (ret)
return ret;
switch (attr) {
case TRIGGER_TTY_RX:
trigger_data->mode_rx = state;
break;
case TRIGGER_TTY_TX:
trigger_data->mode_tx = state;
break;
case TRIGGER_TTY_CTS:
trigger_data->mode_cts = state;
break;
case TRIGGER_TTY_DSR:
trigger_data->mode_dsr = state;
break;
case TRIGGER_TTY_DCD:
trigger_data->mode_dcd = state;
break;
case TRIGGER_TTY_RNG:
trigger_data->mode_rng = state;
break;
}
return size;
}
#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \
static ssize_t trigger_name##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
return ledtrig_tty_attr_show(dev, buf, trigger); \
} \
static ssize_t trigger_name##_store(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t size) \
{ \
return ledtrig_tty_attr_store(dev, buf, size, trigger); \
} \
static DEVICE_ATTR_RW(trigger_name)
DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);
static void ledtrig_tty_work(struct work_struct *work)
{
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
struct serial_icounter_struct icount;
enum led_trigger_tty_state state = TTY_LED_DISABLE;
unsigned long interval = LEDTRIG_TTY_INTERVAL;
bool invert = false;
int status;
int ret;
mutex_lock(&trigger_data->mutex);
if (!trigger_data->ttyname) {
/* exit without rescheduling */
mutex_unlock(&trigger_data->mutex);
return;
}
if (!trigger_data->ttyname)
goto out;
/* try to get the tty corresponding to $ttyname */
if (!trigger_data->tty) {
@@ -115,32 +227,83 @@ static void ledtrig_tty_work(struct work_struct *work)
trigger_data->tty = tty;
}
ret = tty_get_icount(trigger_data->tty, &icount);
if (ret) {
dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
mutex_unlock(&trigger_data->mutex);
return;
status = tty_get_tiocm(trigger_data->tty);
if (status > 0) {
if (trigger_data->mode_cts) {
if (status & TIOCM_CTS)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_dsr) {
if (status & TIOCM_DSR)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_dcd) {
if (status & TIOCM_CAR)
state = TTY_LED_ENABLE;
}
if (trigger_data->mode_rng) {
if (status & TIOCM_RNG)
state = TTY_LED_ENABLE;
}
}
if (icount.rx != trigger_data->rx ||
icount.tx != trigger_data->tx) {
unsigned long interval = LEDTRIG_TTY_INTERVAL;
/*
* The evaluation of rx/tx must be done after the evaluation
* of TIOCM_*, because rx/tx has priority.
*/
if (trigger_data->mode_rx || trigger_data->mode_tx) {
struct serial_icounter_struct icount;
led_blink_set_oneshot(trigger_data->led_cdev, &interval,
&interval, 0);
ret = tty_get_icount(trigger_data->tty, &icount);
if (ret)
goto out;
trigger_data->rx = icount.rx;
trigger_data->tx = icount.tx;
if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
trigger_data->tx = icount.tx;
invert = state == TTY_LED_ENABLE;
state = TTY_LED_BLINK;
}
if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
trigger_data->rx = icount.rx;
invert = state == TTY_LED_ENABLE;
state = TTY_LED_BLINK;
}
}
out:
mutex_unlock(&trigger_data->mutex);
switch (state) {
case TTY_LED_BLINK:
led_blink_set_oneshot(trigger_data->led_cdev, &interval,
&interval, invert);
break;
case TTY_LED_ENABLE:
led_set_brightness(trigger_data->led_cdev,
trigger_data->led_cdev->blink_brightness);
break;
case TTY_LED_DISABLE:
fallthrough;
default:
led_set_brightness(trigger_data->led_cdev, LED_OFF);
break;
}
complete_all(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork,
msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
}
static struct attribute *ledtrig_tty_attrs[] = {
&dev_attr_ttyname.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
&dev_attr_cts.attr,
&dev_attr_dsr.attr,
&dev_attr_dcd.attr,
&dev_attr_rng.attr,
NULL
};
ATTRIBUTE_GROUPS(ledtrig_tty);
@@ -153,11 +316,17 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev)
if (!trigger_data)
return -ENOMEM;
/* Enable default rx/tx mode */
trigger_data->mode_rx = true;
trigger_data->mode_tx = true;
led_set_trigger_data(led_cdev, trigger_data);
INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
trigger_data->led_cdev = led_cdev;
mutex_init(&trigger_data->mutex);
init_completion(&trigger_data->sysfs);
schedule_delayed_work(&trigger_data->dwork, 0);
return 0;
}
@@ -168,6 +337,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->dwork);
kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
trigger_data->tty = NULL;
kfree(trigger_data);
}