提交 1f0a53f6 编写于 作者: L Linus Torvalds

Merge tag 'leds_for_4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds

Pull LED updates from Jacek Anaszewski:

 - userspace LED class driver - it can be useful for testing triggers
   and can also be used to implement virtual LEDs

 - LED class driver for NIC78bx device

 - LED core fixes for preventing potential races while setting
   brightness when software blinking is enabled

 - improvements in LED documentation to mention semantics on changing
   brightness while trigger is active

* tag 'leds_for_4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds:
  leds: pca955x: Add ACPI support
  leds: netxbig: fix module autoload for OF registration
  leds: pca963x: Add ACPI support
  leds: leds-cobalt-raq: use builtin_platform_driver
  led: core: Fix blink_brightness setting race
  led: core: Use atomic bit-field for the blink-flags
  leds: Add user LED driver for NIC78bx device
  leds: verify vendor and change license in mlxcpld driver
  leds: pca963x: enable low-power state
  leds: pca9532: Use default trigger value from platform data
  leds: pca963x: workaround group blink scaling issue
  cleanup LED documentation and make it match reality
  leds: lp3952: Export I2C module alias information for module autoload
  leds: mc13783: Fix MC13892 keypad led access
  ledtrig-cpu.c: fix english
  leds/leds-lp5523.txt: make documentation match reality
  tools/leds: Add uledmon program for monitoring userspace LEDs
  leds: Use macro for max device node name size
  leds: Introduce userspace LED class driver
  mfd: qcom-pm8xxx: Clean up PM8XXX namespace
...@@ -4,16 +4,24 @@ KernelVersion: 2.6.17 ...@@ -4,16 +4,24 @@ KernelVersion: 2.6.17
Contact: Richard Purdie <rpurdie@rpsys.net> Contact: Richard Purdie <rpurdie@rpsys.net>
Description: Description:
Set the brightness of the LED. Most LEDs don't Set the brightness of the LED. Most LEDs don't
have hardware brightness support so will just be turned on for have hardware brightness support, so will just be turned on for
non-zero brightness settings. The value is between 0 and non-zero brightness settings. The value is between 0 and
/sys/class/leds/<led>/max_brightness. /sys/class/leds/<led>/max_brightness.
Writing 0 to this file clears active trigger.
Writing non-zero to this file while trigger is active changes the
top brightness trigger is going to use.
What: /sys/class/leds/<led>/max_brightness What: /sys/class/leds/<led>/max_brightness
Date: March 2006 Date: March 2006
KernelVersion: 2.6.17 KernelVersion: 2.6.17
Contact: Richard Purdie <rpurdie@rpsys.net> Contact: Richard Purdie <rpurdie@rpsys.net>
Description: Description:
Maximum brightness level for this led, default is 255 (LED_FULL). Maximum brightness level for this LED, default is 255 (LED_FULL).
If the LED does not support different brightness levels, this
should be 1.
What: /sys/class/leds/<led>/trigger What: /sys/class/leds/<led>/trigger
Date: March 2006 Date: March 2006
...@@ -21,7 +29,7 @@ KernelVersion: 2.6.17 ...@@ -21,7 +29,7 @@ KernelVersion: 2.6.17
Contact: Richard Purdie <rpurdie@rpsys.net> Contact: Richard Purdie <rpurdie@rpsys.net>
Description: Description:
Set the trigger for this LED. A trigger is a kernel based source Set the trigger for this LED. A trigger is a kernel based source
of led events. of LED events.
You can change triggers in a similar manner to the way an IO You can change triggers in a similar manner to the way an IO
scheduler is chosen. Trigger specific parameters can appear in scheduler is chosen. Trigger specific parameters can appear in
/sys/class/leds/<led> once a given trigger is selected. For /sys/class/leds/<led> once a given trigger is selected. For
......
...@@ -7,6 +7,9 @@ Optional properties: ...@@ -7,6 +7,9 @@ Optional properties:
- nxp,totem-pole : use totem pole (push-pull) instead of open-drain (pca9632 defaults - nxp,totem-pole : use totem pole (push-pull) instead of open-drain (pca9632 defaults
to open-drain, newer chips to totem pole) to open-drain, newer chips to totem pole)
- nxp,hw-blink : use hardware blinking instead of software blinking - nxp,hw-blink : use hardware blinking instead of software blinking
- nxp,period-scale : In some configurations, the chip blinks faster than expected.
This parameter provides a scaling ratio (fixed point, decimal divided
by 1000) to compensate, e.g. 1300=1.3x and 750=0.75x.
Each led is represented as a sub-node of the nxp,pca963x device. Each led is represented as a sub-node of the nxp,pca963x device.
......
...@@ -34,8 +34,8 @@ There are two ways to run LED patterns. ...@@ -34,8 +34,8 @@ There are two ways to run LED patterns.
Control interface for the engines: Control interface for the engines:
x is 1 .. 3 x is 1 .. 3
enginex_mode : disabled, load, run enginex_mode : disabled, load, run
enginex_load : microcode load (visible only in load mode) enginex_load : microcode load
enginex_leds : led mux control (visible only in load mode) enginex_leds : led mux control
cd /sys/class/leds/lp5523:channel2/device cd /sys/class/leds/lp5523:channel2/device
echo "load" > engine3_mode echo "load" > engine3_mode
......
Userspace LEDs
==============
The uleds driver supports userspace LEDs. This can be useful for testing
triggers and can also be used to implement virtual LEDs.
Usage
=====
When the driver is loaded, a character device is created at /dev/uleds. To
create a new LED class device, open /dev/uleds and write a uleds_user_dev
structure to it (found in kernel public header file linux/uleds.h).
#define LED_MAX_NAME_SIZE 64
struct uleds_user_dev {
char name[LED_MAX_NAME_SIZE];
};
A new LED class device will be created with the name given. The name can be
any valid sysfs device node name, but consider using the LED class naming
convention of "devicename:color:function".
The current brightness is found by reading a single byte from the character
device. Values are unsigned: 0 to 255. Reading will block until the brightness
changes. The device node can also be polled to notify when the brightness value
changes.
The LED class device will be removed when the open file handle to /dev/uleds
is closed.
Multiple LED class devices are created by opening additional file handles to
/dev/uleds.
See tools/leds/uledmon.c for an example userspace program.
...@@ -489,7 +489,7 @@ CONFIG_MFD_MAX8907=y ...@@ -489,7 +489,7 @@ CONFIG_MFD_MAX8907=y
CONFIG_MFD_MAX8997=y CONFIG_MFD_MAX8997=y
CONFIG_MFD_MAX8998=y CONFIG_MFD_MAX8998=y
CONFIG_MFD_RK808=y CONFIG_MFD_RK808=y
CONFIG_MFD_PM8921_CORE=y CONFIG_MFD_PM8XXX=y
CONFIG_MFD_QCOM_RPM=y CONFIG_MFD_QCOM_RPM=y
CONFIG_MFD_SPMI_PMIC=y CONFIG_MFD_SPMI_PMIC=y
CONFIG_MFD_SEC_CORE=y CONFIG_MFD_SEC_CORE=y
......
...@@ -411,7 +411,6 @@ CONFIG_MFD_MAX77693=y ...@@ -411,7 +411,6 @@ CONFIG_MFD_MAX77693=y
CONFIG_MFD_MAX8907=m CONFIG_MFD_MAX8907=m
CONFIG_EZX_PCAP=y CONFIG_EZX_PCAP=y
CONFIG_UCB1400_CORE=m CONFIG_UCB1400_CORE=m
CONFIG_MFD_PM8921_CORE=m
CONFIG_MFD_SEC_CORE=y CONFIG_MFD_SEC_CORE=y
CONFIG_MFD_PALMAS=y CONFIG_MFD_PALMAS=y
CONFIG_MFD_TPS65090=y CONFIG_MFD_TPS65090=y
......
...@@ -119,7 +119,6 @@ CONFIG_POWER_RESET=y ...@@ -119,7 +119,6 @@ CONFIG_POWER_RESET=y
CONFIG_POWER_RESET_MSM=y CONFIG_POWER_RESET_MSM=y
CONFIG_THERMAL=y CONFIG_THERMAL=y
CONFIG_MFD_PM8XXX=y CONFIG_MFD_PM8XXX=y
CONFIG_MFD_PM8921_CORE=y
CONFIG_MFD_QCOM_RPM=y CONFIG_MFD_QCOM_RPM=y
CONFIG_MFD_SPMI_PMIC=y CONFIG_MFD_SPMI_PMIC=y
CONFIG_REGULATOR=y CONFIG_REGULATOR=y
......
...@@ -645,7 +645,7 @@ config LEDS_VERSATILE ...@@ -645,7 +645,7 @@ config LEDS_VERSATILE
config LEDS_PM8058 config LEDS_PM8058
tristate "LED Support for the Qualcomm PM8058 PMIC" tristate "LED Support for the Qualcomm PM8058 PMIC"
depends on MFD_PM8921_CORE depends on MFD_PM8XXX
depends on LEDS_CLASS depends on LEDS_CLASS
help help
Choose this option if you want to use the LED drivers in Choose this option if you want to use the LED drivers in
...@@ -659,6 +659,25 @@ config LEDS_MLXCPLD ...@@ -659,6 +659,25 @@ config LEDS_MLXCPLD
This option enabled support for the LEDs on the Mellanox This option enabled support for the LEDs on the Mellanox
boards. Say Y to enabled these. boards. Say Y to enabled these.
config LEDS_USER
tristate "Userspace LED support"
depends on LEDS_CLASS
help
This option enables support for userspace LEDs. Say 'y' to enable this
support in kernel. To compile this driver as a module, choose 'm' here:
the module will be called uleds.
config LEDS_NIC78BX
tristate "LED support for NI PXI NIC78bx devices"
depends on LEDS_CLASS
depends on X86 && ACPI
help
This option enables support for the User1 and User2 LEDs on NI
PXI NIC78bx devices.
To compile this driver as a module, choose M here: the module
will be called leds-nic78bx.
comment "LED Triggers" comment "LED Triggers"
source "drivers/leds/trigger/Kconfig" source "drivers/leds/trigger/Kconfig"
......
...@@ -71,9 +71,13 @@ obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o ...@@ -71,9 +71,13 @@ obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o
obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o
# LED SPI Drivers # LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
# LED Userspace Drivers
obj-$(CONFIG_LEDS_USER) += uleds.o
# LED Triggers # LED Triggers
obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <uapi/linux/uleds.h>
#include "leds.h" #include "leds.h"
static struct class *leds_class; static struct class *leds_class;
...@@ -187,7 +188,7 @@ static int led_classdev_next_name(const char *init_name, char *name, ...@@ -187,7 +188,7 @@ static int led_classdev_next_name(const char *init_name, char *name,
*/ */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{ {
char name[64]; char name[LED_MAX_NAME_SIZE];
int ret; int ret;
ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
...@@ -203,6 +204,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) ...@@ -203,6 +204,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
dev_warn(parent, "Led %s renamed to %s due to name collision", dev_warn(parent, "Led %s renamed to %s due to name collision",
led_cdev->name, dev_name(led_cdev->dev)); led_cdev->name, dev_name(led_cdev->dev));
led_cdev->work_flags = 0;
#ifdef CONFIG_LEDS_TRIGGERS #ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock); init_rwsem(&led_cdev->trigger_lock);
#endif #endif
......
...@@ -53,30 +53,30 @@ static void led_timer_function(unsigned long data) ...@@ -53,30 +53,30 @@ static void led_timer_function(unsigned long data)
if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
led_set_brightness_nosleep(led_cdev, LED_OFF); led_set_brightness_nosleep(led_cdev, LED_OFF);
led_cdev->flags &= ~LED_BLINK_SW; clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
return; return;
} }
if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP,
led_cdev->flags &= ~(LED_BLINK_ONESHOT_STOP | LED_BLINK_SW); &led_cdev->work_flags)) {
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
return; return;
} }
brightness = led_get_brightness(led_cdev); brightness = led_get_brightness(led_cdev);
if (!brightness) { if (!brightness) {
/* Time to switch the LED on. */ /* Time to switch the LED on. */
if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,
&led_cdev->work_flags))
brightness = led_cdev->new_blink_brightness;
else
brightness = led_cdev->blink_brightness; brightness = led_cdev->blink_brightness;
delay = led_cdev->blink_delay_on; delay = led_cdev->blink_delay_on;
} else { } else {
/* Store the current brightness value to be able /* Store the current brightness value to be able
* to restore it when the delay_off period is over. * to restore it when the delay_off period is over.
* Do it only if there is no pending blink brightness
* change, to avoid overwriting the new value.
*/ */
if (!(led_cdev->flags & LED_BLINK_BRIGHTNESS_CHANGE))
led_cdev->blink_brightness = brightness; led_cdev->blink_brightness = brightness;
else
led_cdev->flags &= ~LED_BLINK_BRIGHTNESS_CHANGE;
brightness = LED_OFF; brightness = LED_OFF;
delay = led_cdev->blink_delay_off; delay = led_cdev->blink_delay_off;
} }
...@@ -87,13 +87,15 @@ static void led_timer_function(unsigned long data) ...@@ -87,13 +87,15 @@ static void led_timer_function(unsigned long data)
* the final blink state so that the led is toggled each delay_on + * the final blink state so that the led is toggled each delay_on +
* delay_off milliseconds in worst case. * delay_off milliseconds in worst case.
*/ */
if (led_cdev->flags & LED_BLINK_ONESHOT) { if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) {
if (led_cdev->flags & LED_BLINK_INVERT) { if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) {
if (brightness) if (brightness)
led_cdev->flags |= LED_BLINK_ONESHOT_STOP; set_bit(LED_BLINK_ONESHOT_STOP,
&led_cdev->work_flags);
} else { } else {
if (!brightness) if (!brightness)
led_cdev->flags |= LED_BLINK_ONESHOT_STOP; set_bit(LED_BLINK_ONESHOT_STOP,
&led_cdev->work_flags);
} }
} }
...@@ -106,10 +108,9 @@ static void set_brightness_delayed(struct work_struct *ws) ...@@ -106,10 +108,9 @@ static void set_brightness_delayed(struct work_struct *ws)
container_of(ws, struct led_classdev, set_brightness_work); container_of(ws, struct led_classdev, set_brightness_work);
int ret = 0; int ret = 0;
if (led_cdev->flags & LED_BLINK_DISABLE) { if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
led_cdev->delayed_set_value = LED_OFF; led_cdev->delayed_set_value = LED_OFF;
led_stop_software_blink(led_cdev); led_stop_software_blink(led_cdev);
led_cdev->flags &= ~LED_BLINK_DISABLE;
} }
ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value); ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
...@@ -152,7 +153,7 @@ static void led_set_software_blink(struct led_classdev *led_cdev, ...@@ -152,7 +153,7 @@ static void led_set_software_blink(struct led_classdev *led_cdev,
return; return;
} }
led_cdev->flags |= LED_BLINK_SW; set_bit(LED_BLINK_SW, &led_cdev->work_flags);
mod_timer(&led_cdev->blink_timer, jiffies + 1); mod_timer(&led_cdev->blink_timer, jiffies + 1);
} }
...@@ -161,7 +162,7 @@ static void led_blink_setup(struct led_classdev *led_cdev, ...@@ -161,7 +162,7 @@ static void led_blink_setup(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_on,
unsigned long *delay_off) unsigned long *delay_off)
{ {
if (!(led_cdev->flags & LED_BLINK_ONESHOT) && if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
led_cdev->blink_set && led_cdev->blink_set &&
!led_cdev->blink_set(led_cdev, delay_on, delay_off)) !led_cdev->blink_set(led_cdev, delay_on, delay_off))
return; return;
...@@ -188,8 +189,8 @@ void led_blink_set(struct led_classdev *led_cdev, ...@@ -188,8 +189,8 @@ void led_blink_set(struct led_classdev *led_cdev,
{ {
del_timer_sync(&led_cdev->blink_timer); del_timer_sync(&led_cdev->blink_timer);
led_cdev->flags &= ~LED_BLINK_ONESHOT; clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off); led_blink_setup(led_cdev, delay_on, delay_off);
} }
...@@ -200,17 +201,17 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev, ...@@ -200,17 +201,17 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
unsigned long *delay_off, unsigned long *delay_off,
int invert) int invert)
{ {
if ((led_cdev->flags & LED_BLINK_ONESHOT) && if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
timer_pending(&led_cdev->blink_timer)) timer_pending(&led_cdev->blink_timer))
return; return;
led_cdev->flags |= LED_BLINK_ONESHOT; set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
if (invert) if (invert)
led_cdev->flags |= LED_BLINK_INVERT; set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
else else
led_cdev->flags &= ~LED_BLINK_INVERT; clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off); led_blink_setup(led_cdev, delay_on, delay_off);
} }
...@@ -221,7 +222,7 @@ void led_stop_software_blink(struct led_classdev *led_cdev) ...@@ -221,7 +222,7 @@ void led_stop_software_blink(struct led_classdev *led_cdev)
del_timer_sync(&led_cdev->blink_timer); del_timer_sync(&led_cdev->blink_timer);
led_cdev->blink_delay_on = 0; led_cdev->blink_delay_on = 0;
led_cdev->blink_delay_off = 0; led_cdev->blink_delay_off = 0;
led_cdev->flags &= ~LED_BLINK_SW; clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
} }
EXPORT_SYMBOL_GPL(led_stop_software_blink); EXPORT_SYMBOL_GPL(led_stop_software_blink);
...@@ -232,18 +233,19 @@ void led_set_brightness(struct led_classdev *led_cdev, ...@@ -232,18 +233,19 @@ void led_set_brightness(struct led_classdev *led_cdev,
* If software blink is active, delay brightness setting * If software blink is active, delay brightness setting
* until the next timer tick. * until the next timer tick.
*/ */
if (led_cdev->flags & LED_BLINK_SW) { if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
/* /*
* If we need to disable soft blinking delegate this to the * If we need to disable soft blinking delegate this to the
* work queue task to avoid problems in case we are called * work queue task to avoid problems in case we are called
* from hard irq context. * from hard irq context.
*/ */
if (brightness == LED_OFF) { if (brightness == LED_OFF) {
led_cdev->flags |= LED_BLINK_DISABLE; set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
schedule_work(&led_cdev->set_brightness_work); schedule_work(&led_cdev->set_brightness_work);
} else { } else {
led_cdev->flags |= LED_BLINK_BRIGHTNESS_CHANGE; set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
led_cdev->blink_brightness = brightness; &led_cdev->work_flags);
led_cdev->new_blink_brightness = brightness;
} }
return; return;
} }
......
...@@ -115,8 +115,4 @@ static struct platform_driver cobalt_raq_led_driver = { ...@@ -115,8 +115,4 @@ static struct platform_driver cobalt_raq_led_driver = {
}, },
}; };
static int __init cobalt_raq_led_init(void) builtin_platform_driver(cobalt_raq_led_driver);
{
return platform_driver_register(&cobalt_raq_led_driver);
}
device_initcall(cobalt_raq_led_init);
...@@ -274,6 +274,7 @@ static const struct i2c_device_id lp3952_id[] = { ...@@ -274,6 +274,7 @@ static const struct i2c_device_id lp3952_id[] = {
{LP3952_NAME, 0}, {LP3952_NAME, 0},
{} {}
}; };
MODULE_DEVICE_TABLE(i2c, lp3952_id);
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
static const struct acpi_device_id lp3952_acpi_match[] = { static const struct acpi_device_id lp3952_acpi_match[] = {
......
...@@ -84,8 +84,9 @@ static int mc13xxx_led_set(struct led_classdev *led_cdev, ...@@ -84,8 +84,9 @@ static int mc13xxx_led_set(struct led_classdev *led_cdev,
case MC13892_LED_MD: case MC13892_LED_MD:
case MC13892_LED_AD: case MC13892_LED_AD:
case MC13892_LED_KP: case MC13892_LED_KP:
reg = (led->id - MC13892_LED_MD) / 2; off = led->id - MC13892_LED_MD;
shift = 3 + (led->id - MC13892_LED_MD) * 12; reg = off / 2;
shift = 3 + (off - reg * 2) * 12;
break; break;
case MC13892_LED_R: case MC13892_LED_R:
case MC13892_LED_G: case MC13892_LED_G:
......
...@@ -400,6 +400,9 @@ static int __init mlxcpld_led_init(void) ...@@ -400,6 +400,9 @@ static int __init mlxcpld_led_init(void)
struct platform_device *pdev; struct platform_device *pdev;
int err; int err;
if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd."))
return -ENODEV;
pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
if (IS_ERR(pdev)) { if (IS_ERR(pdev)) {
pr_err("Device allocation failed\n"); pr_err("Device allocation failed\n");
...@@ -426,5 +429,5 @@ module_exit(mlxcpld_led_exit); ...@@ -426,5 +429,5 @@ module_exit(mlxcpld_led_exit);
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
MODULE_DESCRIPTION("Mellanox board LED driver"); MODULE_DESCRIPTION("Mellanox board LED driver");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("platform:leds_mlxcpld"); MODULE_ALIAS("platform:leds_mlxcpld");
...@@ -534,6 +534,7 @@ static const struct of_device_id of_netxbig_leds_match[] = { ...@@ -534,6 +534,7 @@ static const struct of_device_id of_netxbig_leds_match[] = {
{ .compatible = "lacie,netxbig-leds", }, { .compatible = "lacie,netxbig-leds", },
{}, {},
}; };
MODULE_DEVICE_TABLE(of, of_netxbig_leds_match);
#else #else
static inline int static inline int
netxbig_leds_get_of_pdata(struct device *dev, netxbig_leds_get_of_pdata(struct device *dev,
......
/*
* Copyright (C) 2016 National Instruments Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/acpi.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#define NIC78BX_USER1_LED_MASK 0x3
#define NIC78BX_USER1_GREEN_LED BIT(0)
#define NIC78BX_USER1_YELLOW_LED BIT(1)
#define NIC78BX_USER2_LED_MASK 0xC
#define NIC78BX_USER2_GREEN_LED BIT(2)
#define NIC78BX_USER2_YELLOW_LED BIT(3)
#define NIC78BX_LOCK_REG_OFFSET 1
#define NIC78BX_LOCK_VALUE 0xA5
#define NIC78BX_UNLOCK_VALUE 0x5A
#define NIC78BX_USER_LED_IO_SIZE 2
struct nic78bx_led_data {
u16 io_base;
spinlock_t lock;
struct platform_device *pdev;
};
struct nic78bx_led {
u8 bit;
u8 mask;
struct nic78bx_led_data *data;
struct led_classdev cdev;
};
static inline struct nic78bx_led *to_nic78bx_led(struct led_classdev *cdev)
{
return container_of(cdev, struct nic78bx_led, cdev);
}
static void nic78bx_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct nic78bx_led *nled = to_nic78bx_led(cdev);
unsigned long flags;
u8 value;
spin_lock_irqsave(&nled->data->lock, flags);
value = inb(nled->data->io_base);
if (brightness) {
value &= ~nled->mask;
value |= nled->bit;
} else {
value &= ~nled->bit;
}
outb(value, nled->data->io_base);
spin_unlock_irqrestore(&nled->data->lock, flags);
}
static enum led_brightness nic78bx_brightness_get(struct led_classdev *cdev)
{
struct nic78bx_led *nled = to_nic78bx_led(cdev);
unsigned long flags;
u8 value;
spin_lock_irqsave(&nled->data->lock, flags);
value = inb(nled->data->io_base);
spin_unlock_irqrestore(&nled->data->lock, flags);
return (value & nled->bit) ? 1 : LED_OFF;
}
static struct nic78bx_led nic78bx_leds[] = {
{
.bit = NIC78BX_USER1_GREEN_LED,
.mask = NIC78BX_USER1_LED_MASK,
.cdev = {
.name = "nilrt:green:user1",
.max_brightness = 1,
.brightness_set = nic78bx_brightness_set,
.brightness_get = nic78bx_brightness_get,
}
},
{
.bit = NIC78BX_USER1_YELLOW_LED,
.mask = NIC78BX_USER1_LED_MASK,
.cdev = {
.name = "nilrt:yellow:user1",
.max_brightness = 1,
.brightness_set = nic78bx_brightness_set,
.brightness_get = nic78bx_brightness_get,
}
},
{
.bit = NIC78BX_USER2_GREEN_LED,
.mask = NIC78BX_USER2_LED_MASK,
.cdev = {
.name = "nilrt:green:user2",
.max_brightness = 1,
.brightness_set = nic78bx_brightness_set,
.brightness_get = nic78bx_brightness_get,
}
},
{
.bit = NIC78BX_USER2_YELLOW_LED,
.mask = NIC78BX_USER2_LED_MASK,
.cdev = {
.name = "nilrt:yellow:user2",
.max_brightness = 1,
.brightness_set = nic78bx_brightness_set,
.brightness_get = nic78bx_brightness_get,
}
}
};
static int nic78bx_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct nic78bx_led_data *led_data;
struct resource *io_rc;
int ret, i;
led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
if (!led_data)
return -ENOMEM;
led_data->pdev = pdev;
platform_set_drvdata(pdev, led_data);
io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!io_rc) {
dev_err(dev, "missing IO resources\n");
return -EINVAL;
}
if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) {
dev_err(dev, "IO region too small\n");
return -EINVAL;
}
if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
KBUILD_MODNAME)) {
dev_err(dev, "failed to get IO region\n");
return -EBUSY;
}
led_data->io_base = io_rc->start;
spin_lock_init(&led_data->lock);
for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) {
nic78bx_leds[i].data = led_data;
ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev);
if (ret)
return ret;
}
/* Unlock LED register */
outb(NIC78BX_UNLOCK_VALUE,
led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
return ret;
}
static int nic78bx_remove(struct platform_device *pdev)
{
struct nic78bx_led_data *led_data = platform_get_drvdata(pdev);
/* Lock LED register */
outb(NIC78BX_LOCK_VALUE,
led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
return 0;
}
static const struct acpi_device_id led_device_ids[] = {
{"NIC78B3", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, led_device_ids);
static struct platform_driver led_driver = {
.probe = nic78bx_probe,
.remove = nic78bx_remove,
.driver = {
.name = KBUILD_MODNAME,
.acpi_match_table = ACPI_PTR(led_device_ids),
},
};
module_platform_driver(led_driver);
MODULE_DESCRIPTION("National Instruments PXI User LEDs driver");
MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
MODULE_LICENSE("GPL");
...@@ -369,7 +369,7 @@ static int pca9532_configure(struct i2c_client *client, ...@@ -369,7 +369,7 @@ static int pca9532_configure(struct i2c_client *client,
led->state = pled->state; led->state = pled->state;
led->name = pled->name; led->name = pled->name;
led->ldev.name = led->name; led->ldev.name = led->name;
led->ldev.default_trigger = led->default_trigger; led->ldev.default_trigger = pled->default_trigger;
led->ldev.brightness = LED_OFF; led->ldev.brightness = LED_OFF;
led->ldev.brightness_set_blocking = led->ldev.brightness_set_blocking =
pca9532_set_brightness; pca9532_set_brightness;
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
* bits the chip supports. * bits the chip supports.
*/ */
#include <linux/acpi.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/string.h> #include <linux/string.h>
...@@ -100,6 +101,15 @@ static const struct i2c_device_id pca955x_id[] = { ...@@ -100,6 +101,15 @@ static const struct i2c_device_id pca955x_id[] = {
}; };
MODULE_DEVICE_TABLE(i2c, pca955x_id); MODULE_DEVICE_TABLE(i2c, pca955x_id);
static const struct acpi_device_id pca955x_acpi_ids[] = {
{ "PCA9550", pca9550 },
{ "PCA9551", pca9551 },
{ "PCA9552", pca9552 },
{ "PCA9553", pca9553 },
{ }
};
MODULE_DEVICE_TABLE(acpi, pca955x_acpi_ids);
struct pca955x { struct pca955x {
struct mutex lock; struct mutex lock;
struct pca955x_led *leds; struct pca955x_led *leds;
...@@ -250,7 +260,16 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -250,7 +260,16 @@ static int pca955x_probe(struct i2c_client *client,
struct led_platform_data *pdata; struct led_platform_data *pdata;
int i, err; int i, err;
if (id) {
chip = &pca955x_chipdefs[id->driver_data]; chip = &pca955x_chipdefs[id->driver_data];
} else {
const struct acpi_device_id *acpi_id;
acpi_id = acpi_match_device(pca955x_acpi_ids, &client->dev);
if (!acpi_id)
return -ENODEV;
chip = &pca955x_chipdefs[acpi_id->driver_data];
}
adapter = to_i2c_adapter(client->dev.parent); adapter = to_i2c_adapter(client->dev.parent);
pdata = dev_get_platdata(&client->dev); pdata = dev_get_platdata(&client->dev);
...@@ -264,7 +283,7 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -264,7 +283,7 @@ static int pca955x_probe(struct i2c_client *client,
dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at " dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
"slave address 0x%02x\n", "slave address 0x%02x\n",
id->name, chip->bits, client->addr); client->name, chip->bits, client->addr);
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
return -EIO; return -EIO;
...@@ -358,6 +377,7 @@ static int pca955x_remove(struct i2c_client *client) ...@@ -358,6 +377,7 @@ static int pca955x_remove(struct i2c_client *client)
static struct i2c_driver pca955x_driver = { static struct i2c_driver pca955x_driver = {
.driver = { .driver = {
.name = "leds-pca955x", .name = "leds-pca955x",
.acpi_match_table = ACPI_PTR(pca955x_acpi_ids),
}, },
.probe = pca955x_probe, .probe = pca955x_probe,
.remove = pca955x_remove, .remove = pca955x_remove,
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
* or by adding the 'nxp,hw-blink' property to the DTS. * or by adding the 'nxp,hw-blink' property to the DTS.
*/ */
#include <linux/acpi.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/string.h> #include <linux/string.h>
...@@ -59,6 +60,7 @@ struct pca963x_chipdef { ...@@ -59,6 +60,7 @@ struct pca963x_chipdef {
u8 grpfreq; u8 grpfreq;
u8 ledout_base; u8 ledout_base;
int n_leds; int n_leds;
unsigned int scaling;
}; };
static struct pca963x_chipdef pca963x_chipdefs[] = { static struct pca963x_chipdef pca963x_chipdefs[] = {
...@@ -95,6 +97,15 @@ static const struct i2c_device_id pca963x_id[] = { ...@@ -95,6 +97,15 @@ static const struct i2c_device_id pca963x_id[] = {
}; };
MODULE_DEVICE_TABLE(i2c, pca963x_id); MODULE_DEVICE_TABLE(i2c, pca963x_id);
static const struct acpi_device_id pca963x_acpi_ids[] = {
{ "PCA9632", pca9633 },
{ "PCA9633", pca9633 },
{ "PCA9634", pca9634 },
{ "PCA9635", pca9635 },
{ }
};
MODULE_DEVICE_TABLE(acpi, pca963x_acpi_ids);
struct pca963x_led; struct pca963x_led;
struct pca963x { struct pca963x {
...@@ -102,6 +113,7 @@ struct pca963x { ...@@ -102,6 +113,7 @@ struct pca963x {
struct mutex mutex; struct mutex mutex;
struct i2c_client *client; struct i2c_client *client;
struct pca963x_led *leds; struct pca963x_led *leds;
unsigned long leds_on;
}; };
struct pca963x_led { struct pca963x_led {
...@@ -123,7 +135,6 @@ static int pca963x_brightness(struct pca963x_led *pca963x, ...@@ -123,7 +135,6 @@ static int pca963x_brightness(struct pca963x_led *pca963x,
u8 mask = 0x3 << shift; u8 mask = 0x3 << shift;
int ret; int ret;
mutex_lock(&pca963x->chip->mutex);
ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr);
switch (brightness) { switch (brightness) {
case LED_FULL: case LED_FULL:
...@@ -140,14 +151,13 @@ static int pca963x_brightness(struct pca963x_led *pca963x, ...@@ -140,14 +151,13 @@ static int pca963x_brightness(struct pca963x_led *pca963x,
PCA963X_PWM_BASE + pca963x->led_num, PCA963X_PWM_BASE + pca963x->led_num,
brightness); brightness);
if (ret < 0) if (ret < 0)
goto unlock; return ret;
ret = i2c_smbus_write_byte_data(pca963x->chip->client, ret = i2c_smbus_write_byte_data(pca963x->chip->client,
ledout_addr, ledout_addr,
(ledout & ~mask) | (PCA963X_LED_PWM << shift)); (ledout & ~mask) | (PCA963X_LED_PWM << shift));
break; break;
} }
unlock:
mutex_unlock(&pca963x->chip->mutex);
return ret; return ret;
} }
...@@ -179,14 +189,49 @@ static void pca963x_blink(struct pca963x_led *pca963x) ...@@ -179,14 +189,49 @@ static void pca963x_blink(struct pca963x_led *pca963x)
mutex_unlock(&pca963x->chip->mutex); mutex_unlock(&pca963x->chip->mutex);
} }
static int pca963x_power_state(struct pca963x_led *pca963x)
{
unsigned long *leds_on = &pca963x->chip->leds_on;
unsigned long cached_leds = pca963x->chip->leds_on;
if (pca963x->led_cdev.brightness)
set_bit(pca963x->led_num, leds_on);
else
clear_bit(pca963x->led_num, leds_on);
if (!(*leds_on) != !cached_leds)
return i2c_smbus_write_byte_data(pca963x->chip->client,
PCA963X_MODE1, *leds_on ? 0 : BIT(4));
return 0;
}
static int pca963x_led_set(struct led_classdev *led_cdev, static int pca963x_led_set(struct led_classdev *led_cdev,
enum led_brightness value) enum led_brightness value)
{ {
struct pca963x_led *pca963x; struct pca963x_led *pca963x;
int ret;
pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); pca963x = container_of(led_cdev, struct pca963x_led, led_cdev);
return pca963x_brightness(pca963x, value); mutex_lock(&pca963x->chip->mutex);
ret = pca963x_brightness(pca963x, value);
if (ret < 0)
goto unlock;
ret = pca963x_power_state(pca963x);
unlock:
mutex_unlock(&pca963x->chip->mutex);
return ret;
}
static unsigned int pca963x_period_scale(struct pca963x_led *pca963x,
unsigned int val)
{
unsigned int scaling = pca963x->chip->chipdef->scaling;
return scaling ? DIV_ROUND_CLOSEST(val * scaling, 1000) : val;
} }
static int pca963x_blink_set(struct led_classdev *led_cdev, static int pca963x_blink_set(struct led_classdev *led_cdev,
...@@ -207,14 +252,14 @@ static int pca963x_blink_set(struct led_classdev *led_cdev, ...@@ -207,14 +252,14 @@ static int pca963x_blink_set(struct led_classdev *led_cdev,
time_off = 500; time_off = 500;
} }
period = time_on + time_off; period = pca963x_period_scale(pca963x, time_on + time_off);
/* If period not supported by hardware, default to someting sane. */ /* If period not supported by hardware, default to someting sane. */
if ((period < PCA963X_BLINK_PERIOD_MIN) || if ((period < PCA963X_BLINK_PERIOD_MIN) ||
(period > PCA963X_BLINK_PERIOD_MAX)) { (period > PCA963X_BLINK_PERIOD_MAX)) {
time_on = 500; time_on = 500;
time_off = 500; time_off = 500;
period = time_on + time_off; period = pca963x_period_scale(pca963x, 1000);
} }
/* /*
...@@ -222,7 +267,7 @@ static int pca963x_blink_set(struct led_classdev *led_cdev, ...@@ -222,7 +267,7 @@ static int pca963x_blink_set(struct led_classdev *led_cdev,
* (time_on / period) = (GDC / 256) -> * (time_on / period) = (GDC / 256) ->
* GDC = ((time_on * 256) / period) * GDC = ((time_on * 256) / period)
*/ */
gdc = (time_on * 256) / period; gdc = (pca963x_period_scale(pca963x, time_on) * 256) / period;
/* /*
* From manual: period = ((GFRQ + 1) / 24) in seconds. * From manual: period = ((GFRQ + 1) / 24) in seconds.
...@@ -294,6 +339,9 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) ...@@ -294,6 +339,9 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip)
else else
pdata->blink_type = PCA963X_SW_BLINK; pdata->blink_type = PCA963X_SW_BLINK;
if (of_property_read_u32(np, "nxp,period-scale", &chip->scaling))
chip->scaling = 1000;
return pdata; return pdata;
} }
...@@ -322,7 +370,16 @@ static int pca963x_probe(struct i2c_client *client, ...@@ -322,7 +370,16 @@ static int pca963x_probe(struct i2c_client *client,
struct pca963x_chipdef *chip; struct pca963x_chipdef *chip;
int i, err; int i, err;
if (id) {
chip = &pca963x_chipdefs[id->driver_data]; chip = &pca963x_chipdefs[id->driver_data];
} else {
const struct acpi_device_id *acpi_id;
acpi_id = acpi_match_device(pca963x_acpi_ids, &client->dev);
if (!acpi_id)
return -ENODEV;
chip = &pca963x_chipdefs[acpi_id->driver_data];
}
pdata = dev_get_platdata(&client->dev); pdata = dev_get_platdata(&client->dev);
if (!pdata) { if (!pdata) {
...@@ -391,8 +448,8 @@ static int pca963x_probe(struct i2c_client *client, ...@@ -391,8 +448,8 @@ static int pca963x_probe(struct i2c_client *client,
goto exit; goto exit;
} }
/* Disable LED all-call address and set normal mode */ /* Disable LED all-call address, and power down initially */
i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00); i2c_smbus_write_byte_data(client, PCA963X_MODE1, BIT(4));
if (pdata) { if (pdata) {
/* Configure output: open-drain or totem pole (push-pull) */ /* Configure output: open-drain or totem pole (push-pull) */
...@@ -426,6 +483,7 @@ static struct i2c_driver pca963x_driver = { ...@@ -426,6 +483,7 @@ static struct i2c_driver pca963x_driver = {
.driver = { .driver = {
.name = "leds-pca963x", .name = "leds-pca963x",
.of_match_table = of_match_ptr(of_pca963x_match), .of_match_table = of_match_ptr(of_pca963x_match),
.acpi_match_table = ACPI_PTR(pca963x_acpi_ids),
}, },
.probe = pca963x_probe, .probe = pca963x_probe,
.remove = pca963x_remove, .remove = pca963x_remove,
......
...@@ -42,7 +42,7 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); ...@@ -42,7 +42,7 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig);
* @evt: CPU event to be emitted * @evt: CPU event to be emitted
* *
* Emit a CPU event on a CPU core, which will trigger a * Emit a CPU event on a CPU core, which will trigger a
* binded LED to turn on or turn off. * bound LED to turn on or turn off.
*/ */
void ledtrig_cpu(enum cpu_led_event ledevt) void ledtrig_cpu(enum cpu_led_event ledevt)
{ {
......
/*
* Userspace driver for the LED subsystem
*
* Copyright (C) 2016 David Lechner <david@lechnology.com>
*
* Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <uapi/linux/uleds.h>
#define ULEDS_NAME "uleds"
enum uleds_state {
ULEDS_STATE_UNKNOWN,
ULEDS_STATE_REGISTERED,
};
struct uleds_device {
struct uleds_user_dev user_dev;
struct led_classdev led_cdev;
struct mutex mutex;
enum uleds_state state;
wait_queue_head_t waitq;
int brightness;
bool new_data;
};
static struct miscdevice uleds_misc;
static void uleds_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct uleds_device *udev = container_of(led_cdev, struct uleds_device,
led_cdev);
if (udev->brightness != brightness) {
udev->brightness = brightness;
udev->new_data = true;
wake_up_interruptible(&udev->waitq);
}
}
static int uleds_open(struct inode *inode, struct file *file)
{
struct uleds_device *udev;
udev = kzalloc(sizeof(*udev), GFP_KERNEL);
if (!udev)
return -ENOMEM;
udev->led_cdev.name = udev->user_dev.name;
udev->led_cdev.brightness_set = uleds_brightness_set;
mutex_init(&udev->mutex);
init_waitqueue_head(&udev->waitq);
udev->state = ULEDS_STATE_UNKNOWN;
file->private_data = udev;
nonseekable_open(inode, file);
return 0;
}
static ssize_t uleds_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct uleds_device *udev = file->private_data;
const char *name;
int ret;
if (count == 0)
return 0;
ret = mutex_lock_interruptible(&udev->mutex);
if (ret)
return ret;
if (udev->state == ULEDS_STATE_REGISTERED) {
ret = -EBUSY;
goto out;
}
if (count != sizeof(struct uleds_user_dev)) {
ret = -EINVAL;
goto out;
}
if (copy_from_user(&udev->user_dev, buffer,
sizeof(struct uleds_user_dev))) {
ret = -EFAULT;
goto out;
}
name = udev->user_dev.name;
if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") ||
strchr(name, '/')) {
ret = -EINVAL;
goto out;
}
if (udev->user_dev.max_brightness <= 0) {
ret = -EINVAL;
goto out;
}
udev->led_cdev.max_brightness = udev->user_dev.max_brightness;
ret = devm_led_classdev_register(uleds_misc.this_device,
&udev->led_cdev);
if (ret < 0)
goto out;
udev->new_data = true;
udev->state = ULEDS_STATE_REGISTERED;
ret = count;
out:
mutex_unlock(&udev->mutex);
return ret;
}
static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count,
loff_t *ppos)
{
struct uleds_device *udev = file->private_data;
ssize_t retval;
if (count < sizeof(udev->brightness))
return 0;
do {
retval = mutex_lock_interruptible(&udev->mutex);
if (retval)
return retval;
if (udev->state != ULEDS_STATE_REGISTERED) {
retval = -ENODEV;
} else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) {
retval = -EAGAIN;
} else if (udev->new_data) {
retval = copy_to_user(buffer, &udev->brightness,
sizeof(udev->brightness));
udev->new_data = false;
retval = sizeof(udev->brightness);
}
mutex_unlock(&udev->mutex);
if (retval)
break;
if (!(file->f_flags & O_NONBLOCK))
retval = wait_event_interruptible(udev->waitq,
udev->new_data ||
udev->state != ULEDS_STATE_REGISTERED);
} while (retval == 0);
return retval;
}
static unsigned int uleds_poll(struct file *file, poll_table *wait)
{
struct uleds_device *udev = file->private_data;
poll_wait(file, &udev->waitq, wait);
if (udev->new_data)
return POLLIN | POLLRDNORM;
return 0;
}
static int uleds_release(struct inode *inode, struct file *file)
{
struct uleds_device *udev = file->private_data;
if (udev->state == ULEDS_STATE_REGISTERED) {
udev->state = ULEDS_STATE_UNKNOWN;
devm_led_classdev_unregister(uleds_misc.this_device,
&udev->led_cdev);
}
kfree(udev);
return 0;
}
static const struct file_operations uleds_fops = {
.owner = THIS_MODULE,
.open = uleds_open,
.release = uleds_release,
.read = uleds_read,
.write = uleds_write,
.poll = uleds_poll,
.llseek = no_llseek,
};
static struct miscdevice uleds_misc = {
.fops = &uleds_fops,
.minor = MISC_DYNAMIC_MINOR,
.name = ULEDS_NAME,
};
static int __init uleds_init(void)
{
return misc_register(&uleds_misc);
}
module_init(uleds_init);
static void __exit uleds_exit(void)
{
misc_deregister(&uleds_misc);
}
module_exit(uleds_exit);
MODULE_AUTHOR("David Lechner <david@lechnology.com>");
MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
MODULE_LICENSE("GPL");
...@@ -756,24 +756,20 @@ config UCB1400_CORE ...@@ -756,24 +756,20 @@ config UCB1400_CORE
module will be called ucb1400_core. module will be called ucb1400_core.
config MFD_PM8XXX config MFD_PM8XXX
tristate tristate "Qualcomm PM8xxx PMIC chips driver"
config MFD_PM8921_CORE
tristate "Qualcomm PM8921 PMIC chip"
depends on (ARM || HEXAGON) depends on (ARM || HEXAGON)
select IRQ_DOMAIN select IRQ_DOMAIN
select MFD_CORE select MFD_CORE
select MFD_PM8XXX
select REGMAP select REGMAP
help help
If you say yes to this option, support will be included for the If you say yes to this option, support will be included for the
built-in PM8921 PMIC chip. built-in PM8xxx PMIC chips.
This is required if your board has a PM8921 and uses its features, This is required if your board has a PM8xxx and uses its features,
such as: MPPs, GPIOs, regulators, interrupts, and PWM. such as: MPPs, GPIOs, regulators, interrupts, and PWM.
Say M here if you want to include support for PM8921 chip as a module. Say M here if you want to include support for PM8xxx chips as a
This will build a module called "pm8921-core". module. This will build a module called "pm8xxx-core".
config MFD_QCOM_RPM config MFD_QCOM_RPM
tristate "Qualcomm Resource Power Manager (RPM)" tristate "Qualcomm Resource Power Manager (RPM)"
......
...@@ -172,7 +172,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o ...@@ -172,7 +172,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o
obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o
obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o obj-$(CONFIG_MFD_PM8XXX) += qcom-pm8xxx.o ssbi.o
obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o
obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o
obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
#define REG_HWREV 0x002 /* PMIC4 revision */ #define REG_HWREV 0x002 /* PMIC4 revision */
#define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ #define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */
#define PM8921_NR_IRQS 256 #define PM8XXX_NR_IRQS 256
struct pm_irq_chip { struct pm_irq_chip {
struct regmap *regmap; struct regmap *regmap;
...@@ -308,22 +308,22 @@ static const struct regmap_config ssbi_regmap_config = { ...@@ -308,22 +308,22 @@ static const struct regmap_config ssbi_regmap_config = {
.reg_write = ssbi_reg_write .reg_write = ssbi_reg_write
}; };
static const struct of_device_id pm8921_id_table[] = { static const struct of_device_id pm8xxx_id_table[] = {
{ .compatible = "qcom,pm8018", }, { .compatible = "qcom,pm8018", },
{ .compatible = "qcom,pm8058", }, { .compatible = "qcom,pm8058", },
{ .compatible = "qcom,pm8921", }, { .compatible = "qcom,pm8921", },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, pm8921_id_table); MODULE_DEVICE_TABLE(of, pm8xxx_id_table);
static int pm8921_probe(struct platform_device *pdev) static int pm8xxx_probe(struct platform_device *pdev)
{ {
struct regmap *regmap; struct regmap *regmap;
int irq, rc; int irq, rc;
unsigned int val; unsigned int val;
u32 rev; u32 rev;
struct pm_irq_chip *chip; struct pm_irq_chip *chip;
unsigned int nirqs = PM8921_NR_IRQS; unsigned int nirqs = PM8XXX_NR_IRQS;
irq = platform_get_irq(pdev, 0); irq = platform_get_irq(pdev, 0);
if (irq < 0) if (irq < 0)
...@@ -384,46 +384,46 @@ static int pm8921_probe(struct platform_device *pdev) ...@@ -384,46 +384,46 @@ static int pm8921_probe(struct platform_device *pdev)
return rc; return rc;
} }
static int pm8921_remove_child(struct device *dev, void *unused) static int pm8xxx_remove_child(struct device *dev, void *unused)
{ {
platform_device_unregister(to_platform_device(dev)); platform_device_unregister(to_platform_device(dev));
return 0; return 0;
} }
static int pm8921_remove(struct platform_device *pdev) static int pm8xxx_remove(struct platform_device *pdev)
{ {
int irq = platform_get_irq(pdev, 0); int irq = platform_get_irq(pdev, 0);
struct pm_irq_chip *chip = platform_get_drvdata(pdev); struct pm_irq_chip *chip = platform_get_drvdata(pdev);
device_for_each_child(&pdev->dev, NULL, pm8921_remove_child); device_for_each_child(&pdev->dev, NULL, pm8xxx_remove_child);
irq_set_chained_handler_and_data(irq, NULL, NULL); irq_set_chained_handler_and_data(irq, NULL, NULL);
irq_domain_remove(chip->irqdomain); irq_domain_remove(chip->irqdomain);
return 0; return 0;
} }
static struct platform_driver pm8921_driver = { static struct platform_driver pm8xxx_driver = {
.probe = pm8921_probe, .probe = pm8xxx_probe,
.remove = pm8921_remove, .remove = pm8xxx_remove,
.driver = { .driver = {
.name = "pm8921-core", .name = "pm8xxx-core",
.of_match_table = pm8921_id_table, .of_match_table = pm8xxx_id_table,
}, },
}; };
static int __init pm8921_init(void) static int __init pm8xxx_init(void)
{ {
return platform_driver_register(&pm8921_driver); return platform_driver_register(&pm8xxx_driver);
} }
subsys_initcall(pm8921_init); subsys_initcall(pm8xxx_init);
static void __exit pm8921_exit(void) static void __exit pm8xxx_exit(void)
{ {
platform_driver_unregister(&pm8921_driver); platform_driver_unregister(&pm8xxx_driver);
} }
module_exit(pm8921_exit); module_exit(pm8xxx_exit);
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("PMIC 8921 core driver"); MODULE_DESCRIPTION("PMIC 8xxx core driver");
MODULE_VERSION("1.0"); MODULE_VERSION("1.0");
MODULE_ALIAS("platform:pm8921-core"); MODULE_ALIAS("platform:pm8xxx-core");
...@@ -42,16 +42,20 @@ struct led_classdev { ...@@ -42,16 +42,20 @@ struct led_classdev {
#define LED_UNREGISTERING (1 << 1) #define LED_UNREGISTERING (1 << 1)
/* Upper 16 bits reflect control information */ /* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16) #define LED_CORE_SUSPENDRESUME (1 << 16)
#define LED_BLINK_SW (1 << 17) #define LED_SYSFS_DISABLE (1 << 17)
#define LED_BLINK_ONESHOT (1 << 18) #define LED_DEV_CAP_FLASH (1 << 18)
#define LED_BLINK_ONESHOT_STOP (1 << 19) #define LED_HW_PLUGGABLE (1 << 19)
#define LED_BLINK_INVERT (1 << 20) #define LED_PANIC_INDICATOR (1 << 20)
#define LED_BLINK_BRIGHTNESS_CHANGE (1 << 21)
#define LED_BLINK_DISABLE (1 << 22) /* set_brightness_work / blink_timer flags, atomic, private. */
#define LED_SYSFS_DISABLE (1 << 23) unsigned long work_flags;
#define LED_DEV_CAP_FLASH (1 << 24)
#define LED_HW_PLUGGABLE (1 << 25) #define LED_BLINK_SW 0
#define LED_PANIC_INDICATOR (1 << 26) #define LED_BLINK_ONESHOT 1
#define LED_BLINK_ONESHOT_STOP 2
#define LED_BLINK_INVERT 3
#define LED_BLINK_BRIGHTNESS_CHANGE 4
#define LED_BLINK_DISABLE 5
/* Set LED brightness level /* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers * Must not sleep. Use brightness_set_blocking for drivers
...@@ -89,6 +93,7 @@ struct led_classdev { ...@@ -89,6 +93,7 @@ struct led_classdev {
unsigned long blink_delay_on, blink_delay_off; unsigned long blink_delay_on, blink_delay_off;
struct timer_list blink_timer; struct timer_list blink_timer;
int blink_brightness; int blink_brightness;
int new_blink_brightness;
void (*flash_resume)(struct led_classdev *led_cdev); void (*flash_resume)(struct led_classdev *led_cdev);
struct work_struct set_brightness_work; struct work_struct set_brightness_work;
......
...@@ -426,6 +426,7 @@ header-y += udp.h ...@@ -426,6 +426,7 @@ header-y += udp.h
header-y += uhid.h header-y += uhid.h
header-y += uinput.h header-y += uinput.h
header-y += uio.h header-y += uio.h
header-y += uleds.h
header-y += ultrasound.h header-y += ultrasound.h
header-y += un.h header-y += un.h
header-y += unistd.h header-y += unistd.h
......
/*
* Userspace driver support for the LED subsystem
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _UAPI__ULEDS_H_
#define _UAPI__ULEDS_H_
#define LED_MAX_NAME_SIZE 64
struct uleds_user_dev {
char name[LED_MAX_NAME_SIZE];
int max_brightness;
};
#endif /* _UAPI__ULEDS_H_ */
...@@ -17,6 +17,7 @@ help: ...@@ -17,6 +17,7 @@ help:
@echo ' hv - tools used when in Hyper-V clients' @echo ' hv - tools used when in Hyper-V clients'
@echo ' iio - IIO tools' @echo ' iio - IIO tools'
@echo ' kvm_stat - top-like utility for displaying kvm statistics' @echo ' kvm_stat - top-like utility for displaying kvm statistics'
@echo ' leds - LEDs tools'
@echo ' lguest - a minimal 32-bit x86 hypervisor' @echo ' lguest - a minimal 32-bit x86 hypervisor'
@echo ' net - misc networking tools' @echo ' net - misc networking tools'
@echo ' perf - Linux performance measurement and analysis tool' @echo ' perf - Linux performance measurement and analysis tool'
...@@ -56,7 +57,7 @@ acpi: FORCE ...@@ -56,7 +57,7 @@ acpi: FORCE
cpupower: FORCE cpupower: FORCE
$(call descend,power/$@) $(call descend,power/$@)
cgroup firewire hv guest spi usb virtio vm net iio gpio objtool: FORCE cgroup firewire hv guest spi usb virtio vm net iio gpio objtool leds: FORCE
$(call descend,$@) $(call descend,$@)
liblockdep: FORCE liblockdep: FORCE
...@@ -126,7 +127,7 @@ acpi_clean: ...@@ -126,7 +127,7 @@ acpi_clean:
cpupower_clean: cpupower_clean:
$(call descend,power/cpupower,clean) $(call descend,power/cpupower,clean)
cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean gpio_clean objtool_clean: cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean gpio_clean objtool_clean leds_clean:
$(call descend,$(@:_clean=),clean) $(call descend,$(@:_clean=),clean)
liblockdep_clean: liblockdep_clean:
...@@ -164,6 +165,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean lguest_cle ...@@ -164,6 +165,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean lguest_cle
perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \
vm_clean net_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ vm_clean net_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
gpio_clean objtool_clean gpio_clean objtool_clean leds_clean
.PHONY: FORCE .PHONY: FORCE
# Makefile for LEDs tools
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -Wextra -g -I../../include/uapi
all: uledmon
%: %.c
$(CC) $(CFLAGS) -o $@ $^
clean:
$(RM) uledmon
.PHONY: all clean
/*
* uledmon.c
*
* This program creates a new userspace LED class device and monitors it. A
* timestamp and brightness value is printed each time the brightness changes.
*
* Usage: uledmon <device-name>
*
* <device-name> is the name of the LED class device to be created. Pressing
* CTRL+C will exit.
*/
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/uleds.h>
int main(int argc, char const *argv[])
{
struct uleds_user_dev uleds_dev;
int fd, ret;
int brightness;
struct timespec ts;
if (argc != 2) {
fprintf(stderr, "Requires <device-name> argument\n");
return 1;
}
strncpy(uleds_dev.name, argv[1], LED_MAX_NAME_SIZE);
uleds_dev.max_brightness = 100;
fd = open("/dev/uleds", O_RDWR);
if (fd == -1) {
perror("Failed to open /dev/uleds");
return 1;
}
ret = write(fd, &uleds_dev, sizeof(uleds_dev));
if (ret == -1) {
perror("Failed to write to /dev/uleds");
close(fd);
return 1;
}
while (1) {
ret = read(fd, &brightness, sizeof(brightness));
if (ret == -1) {
perror("Failed to read from /dev/uleds");
close(fd);
return 1;
}
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("[%ld.%09ld] %u\n", ts.tv_sec, ts.tv_nsec, brightness);
}
close(fd);
return 0;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册