diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-12-13 19:01:19 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-12-13 19:01:19 +0300 |
commit | 1f0a53f623b675e856554f2bb1d6b630ea78125d (patch) | |
tree | 7c0fdaac3a433ec812413bd5c1149aaae66c2595 /drivers | |
parent | 20d5ba4928ceb79b919092c939ae4ef4d88807bd (diff) | |
parent | 44b3e31d540e917a4d2292b902ade63fa1748d9a (diff) | |
download | linux-1f0a53f623b675e856554f2bb1d6b630ea78125d.tar.xz |
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
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/leds/Kconfig | 21 | ||||
-rw-r--r-- | drivers/leds/Makefile | 4 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 4 | ||||
-rw-r--r-- | drivers/leds/led-core.c | 62 | ||||
-rw-r--r-- | drivers/leds/leds-cobalt-raq.c | 6 | ||||
-rw-r--r-- | drivers/leds/leds-lp3952.c | 1 | ||||
-rw-r--r-- | drivers/leds/leds-mc13783.c | 5 | ||||
-rw-r--r-- | drivers/leds/leds-mlxcpld.c | 5 | ||||
-rw-r--r-- | drivers/leds/leds-netxbig.c | 1 | ||||
-rw-r--r-- | drivers/leds/leds-nic78bx.c | 209 | ||||
-rw-r--r-- | drivers/leds/leds-pca9532.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-pca955x.c | 24 | ||||
-rw-r--r-- | drivers/leds/leds-pca963x.c | 80 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-cpu.c | 2 | ||||
-rw-r--r-- | drivers/leds/uleds.c | 235 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 14 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 2 | ||||
-rw-r--r-- | drivers/mfd/qcom-pm8xxx.c (renamed from drivers/mfd/pm8921-core.c) | 42 |
18 files changed, 633 insertions, 86 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..c621cbbb5768 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -645,7 +645,7 @@ config LEDS_VERSATILE config LEDS_PM8058 tristate "LED Support for the Qualcomm PM8058 PMIC" - depends on MFD_PM8921_CORE + depends on MFD_PM8XXX depends on LEDS_CLASS help Choose this option if you want to use the LED drivers in @@ -659,6 +659,25 @@ config LEDS_MLXCPLD This option enabled support for the LEDs on the Mellanox 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" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3965070190f5..6b8273736478 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -71,9 +71,13 @@ obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o +obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +# LED Userspace Drivers +obj-$(CONFIG_LEDS_USER) += uleds.o + # LED Triggers obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index aa84e5b37593..326ee6e925a2 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -20,6 +20,7 @@ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/timer.h> +#include <uapi/linux/uleds.h> #include "leds.h" static struct class *leds_class; @@ -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) { - char name[64]; + char name[LED_MAX_NAME_SIZE]; int ret; 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) dev_warn(parent, "Led %s renamed to %s due to name collision", led_cdev->name, dev_name(led_cdev->dev)); + led_cdev->work_flags = 0; #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 3bce44893021..ef1360445413 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -53,30 +53,30 @@ static void led_timer_function(unsigned long data) if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_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; } - if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { - led_cdev->flags &= ~(LED_BLINK_ONESHOT_STOP | LED_BLINK_SW); + if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP, + &led_cdev->work_flags)) { + clear_bit(LED_BLINK_SW, &led_cdev->work_flags); return; } brightness = led_get_brightness(led_cdev); if (!brightness) { /* Time to switch the LED on. */ - brightness = led_cdev->blink_brightness; + 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; delay = led_cdev->blink_delay_on; } else { /* Store the current brightness value to be able * 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; - else - led_cdev->flags &= ~LED_BLINK_BRIGHTNESS_CHANGE; + led_cdev->blink_brightness = brightness; brightness = LED_OFF; delay = led_cdev->blink_delay_off; } @@ -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 + * delay_off milliseconds in worst case. */ - if (led_cdev->flags & LED_BLINK_ONESHOT) { - if (led_cdev->flags & LED_BLINK_INVERT) { + if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) { + if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) { if (brightness) - led_cdev->flags |= LED_BLINK_ONESHOT_STOP; + set_bit(LED_BLINK_ONESHOT_STOP, + &led_cdev->work_flags); } else { 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) container_of(ws, struct led_classdev, set_brightness_work); 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_stop_software_blink(led_cdev); - led_cdev->flags &= ~LED_BLINK_DISABLE; } 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, return; } - led_cdev->flags |= LED_BLINK_SW; + set_bit(LED_BLINK_SW, &led_cdev->work_flags); mod_timer(&led_cdev->blink_timer, jiffies + 1); } @@ -161,7 +162,7 @@ static void led_blink_setup(struct led_classdev *led_cdev, unsigned long *delay_on, 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, delay_on, delay_off)) return; @@ -188,8 +189,8 @@ void led_blink_set(struct led_classdev *led_cdev, { del_timer_sync(&led_cdev->blink_timer); - led_cdev->flags &= ~LED_BLINK_ONESHOT; - led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; + clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); + clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); led_blink_setup(led_cdev, delay_on, delay_off); } @@ -200,17 +201,17 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev, unsigned long *delay_off, 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)) return; - led_cdev->flags |= LED_BLINK_ONESHOT; - led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; + set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); + clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags); if (invert) - led_cdev->flags |= LED_BLINK_INVERT; + set_bit(LED_BLINK_INVERT, &led_cdev->work_flags); 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); } @@ -221,7 +222,7 @@ void led_stop_software_blink(struct led_classdev *led_cdev) del_timer_sync(&led_cdev->blink_timer); led_cdev->blink_delay_on = 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); @@ -232,18 +233,19 @@ void led_set_brightness(struct led_classdev *led_cdev, * If software blink is active, delay brightness setting * 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 * work queue task to avoid problems in case we are called * from hard irq context. */ 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); } else { - led_cdev->flags |= LED_BLINK_BRIGHTNESS_CHANGE; - led_cdev->blink_brightness = brightness; + set_bit(LED_BLINK_BRIGHTNESS_CHANGE, + &led_cdev->work_flags); + led_cdev->new_blink_brightness = brightness; } return; } diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c index b316df4a8c1e..8d066facdc73 100644 --- a/drivers/leds/leds-cobalt-raq.c +++ b/drivers/leds/leds-cobalt-raq.c @@ -115,8 +115,4 @@ static struct platform_driver cobalt_raq_led_driver = { }, }; -static int __init cobalt_raq_led_init(void) -{ - return platform_driver_register(&cobalt_raq_led_driver); -} -device_initcall(cobalt_raq_led_init); +builtin_platform_driver(cobalt_raq_led_driver); diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c index a73c8ff08530..4847e89883a7 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -274,6 +274,7 @@ static const struct i2c_device_id lp3952_id[] = { {LP3952_NAME, 0}, {} }; +MODULE_DEVICE_TABLE(i2c, lp3952_id); #ifdef CONFIG_ACPI static const struct acpi_device_id lp3952_acpi_match[] = { diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index a2e4c1792e17..2421cf104991 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -84,8 +84,9 @@ static int mc13xxx_led_set(struct led_classdev *led_cdev, case MC13892_LED_MD: case MC13892_LED_AD: case MC13892_LED_KP: - reg = (led->id - MC13892_LED_MD) / 2; - shift = 3 + (led->id - MC13892_LED_MD) * 12; + off = led->id - MC13892_LED_MD; + reg = off / 2; + shift = 3 + (off - reg * 2) * 12; break; case MC13892_LED_R: case MC13892_LED_G: diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c index 197ab9b29a9c..281482e1d50f 100644 --- a/drivers/leds/leds-mlxcpld.c +++ b/drivers/leds/leds-mlxcpld.c @@ -400,6 +400,9 @@ static int __init mlxcpld_led_init(void) struct platform_device *pdev; int err; + if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) + return -ENODEV; + pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); if (IS_ERR(pdev)) { pr_err("Device allocation failed\n"); @@ -426,5 +429,5 @@ module_exit(mlxcpld_led_exit); MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); MODULE_DESCRIPTION("Mellanox board LED driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:leds_mlxcpld"); diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index 4b88b93244be..f48b1aed9b4e 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -534,6 +534,7 @@ static const struct of_device_id of_netxbig_leds_match[] = { { .compatible = "lacie,netxbig-leds", }, {}, }; +MODULE_DEVICE_TABLE(of, of_netxbig_leds_match); #else static inline int netxbig_leds_get_of_pdata(struct device *dev, diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c new file mode 100644 index 000000000000..8d69e2b74a27 --- /dev/null +++ b/drivers/leds/leds-nic78bx.c @@ -0,0 +1,209 @@ +/* + * 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"); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 09a7cffbc46f..06e63106ae1e 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -369,7 +369,7 @@ static int pca9532_configure(struct i2c_client *client, led->state = pled->state; led->name = pled->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_set_blocking = pca9532_set_brightness; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 840401ae9a4e..78a7ce816a47 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -40,6 +40,7 @@ * bits the chip supports. */ +#include <linux/acpi.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/string.h> @@ -100,6 +101,15 @@ static const struct i2c_device_id 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 mutex lock; struct pca955x_led *leds; @@ -250,7 +260,16 @@ static int pca955x_probe(struct i2c_client *client, struct led_platform_data *pdata; int i, err; - chip = &pca955x_chipdefs[id->driver_data]; + if (id) { + 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); pdata = dev_get_platdata(&client->dev); @@ -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 " "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)) return -EIO; @@ -358,6 +377,7 @@ static int pca955x_remove(struct i2c_client *client) static struct i2c_driver pca955x_driver = { .driver = { .name = "leds-pca955x", + .acpi_match_table = ACPI_PTR(pca955x_acpi_ids), }, .probe = pca955x_probe, .remove = pca955x_remove, diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 407eba11e187..ded1e4dac36a 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -25,6 +25,7 @@ * or by adding the 'nxp,hw-blink' property to the DTS. */ +#include <linux/acpi.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/string.h> @@ -59,6 +60,7 @@ struct pca963x_chipdef { u8 grpfreq; u8 ledout_base; int n_leds; + unsigned int scaling; }; static struct pca963x_chipdef pca963x_chipdefs[] = { @@ -95,6 +97,15 @@ static const struct i2c_device_id 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 { @@ -102,6 +113,7 @@ struct pca963x { struct mutex mutex; struct i2c_client *client; struct pca963x_led *leds; + unsigned long leds_on; }; struct pca963x_led { @@ -123,7 +135,6 @@ static int pca963x_brightness(struct pca963x_led *pca963x, u8 mask = 0x3 << shift; int ret; - mutex_lock(&pca963x->chip->mutex); ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); switch (brightness) { case LED_FULL: @@ -140,14 +151,13 @@ static int pca963x_brightness(struct pca963x_led *pca963x, PCA963X_PWM_BASE + pca963x->led_num, brightness); if (ret < 0) - goto unlock; + return ret; ret = i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, (ledout & ~mask) | (PCA963X_LED_PWM << shift)); break; } -unlock: - mutex_unlock(&pca963x->chip->mutex); + return ret; } @@ -179,14 +189,49 @@ static void pca963x_blink(struct pca963x_led *pca963x) 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, enum led_brightness value) { struct pca963x_led *pca963x; + int ret; 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, @@ -207,14 +252,14 @@ static int pca963x_blink_set(struct led_classdev *led_cdev, 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 < PCA963X_BLINK_PERIOD_MIN) || (period > PCA963X_BLINK_PERIOD_MAX)) { time_on = 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, * (time_on / period) = (GDC / 256) -> * 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. @@ -294,6 +339,9 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) else pdata->blink_type = PCA963X_SW_BLINK; + if (of_property_read_u32(np, "nxp,period-scale", &chip->scaling)) + chip->scaling = 1000; + return pdata; } @@ -322,7 +370,16 @@ static int pca963x_probe(struct i2c_client *client, struct pca963x_chipdef *chip; int i, err; - chip = &pca963x_chipdefs[id->driver_data]; + if (id) { + 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); if (!pdata) { @@ -391,8 +448,8 @@ static int pca963x_probe(struct i2c_client *client, goto exit; } - /* Disable LED all-call address and set normal mode */ - i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00); + /* Disable LED all-call address, and power down initially */ + i2c_smbus_write_byte_data(client, PCA963X_MODE1, BIT(4)); if (pdata) { /* Configure output: open-drain or totem pole (push-pull) */ @@ -426,6 +483,7 @@ static struct i2c_driver pca963x_driver = { .driver = { .name = "leds-pca963x", .of_match_table = of_match_ptr(of_pca963x_match), + .acpi_match_table = ACPI_PTR(pca963x_acpi_ids), }, .probe = pca963x_probe, .remove = pca963x_remove, diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c index 22f0634dd3fa..9719caf7437c 100644 --- a/drivers/leds/trigger/ledtrig-cpu.c +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -42,7 +42,7 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); * @evt: CPU event to be emitted * * 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) { diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c new file mode 100644 index 000000000000..5e9e8a1fdefb --- /dev/null +++ b/drivers/leds/uleds.c @@ -0,0 +1,235 @@ +/* + * 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"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c6df6442ba2b..1ed0584f494e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -756,24 +756,20 @@ config UCB1400_CORE module will be called ucb1400_core. config MFD_PM8XXX - tristate - -config MFD_PM8921_CORE - tristate "Qualcomm PM8921 PMIC chip" + tristate "Qualcomm PM8xxx PMIC chips driver" depends on (ARM || HEXAGON) select IRQ_DOMAIN select MFD_CORE - select MFD_PM8XXX select REGMAP help 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. - Say M here if you want to include support for PM8921 chip as a module. - This will build a module called "pm8921-core". + Say M here if you want to include support for PM8xxx chips as a + module. This will build a module called "pm8xxx-core". config MFD_QCOM_RPM tristate "Qualcomm Resource Power Manager (RPM)" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 9834e669d985..7bb5a50127cb 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -172,7 +172,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.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_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_SPMI_PMIC) += qcom-spmi-pmic.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/qcom-pm8xxx.c index 0e3a2ea25942..7f9620ec61e8 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/qcom-pm8xxx.c @@ -53,7 +53,7 @@ #define REG_HWREV 0x002 /* PMIC4 revision */ #define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ -#define PM8921_NR_IRQS 256 +#define PM8XXX_NR_IRQS 256 struct pm_irq_chip { struct regmap *regmap; @@ -308,22 +308,22 @@ static const struct regmap_config ssbi_regmap_config = { .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,pm8058", }, { .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; int irq, rc; unsigned int val; u32 rev; struct pm_irq_chip *chip; - unsigned int nirqs = PM8921_NR_IRQS; + unsigned int nirqs = PM8XXX_NR_IRQS; irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -384,46 +384,46 @@ static int pm8921_probe(struct platform_device *pdev) 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)); 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); 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_domain_remove(chip->irqdomain); return 0; } -static struct platform_driver pm8921_driver = { - .probe = pm8921_probe, - .remove = pm8921_remove, +static struct platform_driver pm8xxx_driver = { + .probe = pm8xxx_probe, + .remove = pm8xxx_remove, .driver = { - .name = "pm8921-core", - .of_match_table = pm8921_id_table, + .name = "pm8xxx-core", + .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_DESCRIPTION("PMIC 8921 core driver"); +MODULE_DESCRIPTION("PMIC 8xxx core driver"); MODULE_VERSION("1.0"); -MODULE_ALIAS("platform:pm8921-core"); +MODULE_ALIAS("platform:pm8xxx-core"); |