From 40a3a0f2ba633fa219f69aef0fd0dcdd6e5756b0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 19 Nov 2016 16:15:18 +0100 Subject: [PATCH 01/20] mfd: qcom-pm8xxx: Clean up PM8XXX namespace The Kconfig and file naming for the PM8xxx driver is totally confusing: - Kconfig options MFD_PM8XXX and MFD_PM8921_CORE, some in-kernel users depending on or selecting either at random. - A driver file named pm8921-core.c even if it is indeed used by the whole PM8xxx family of chips. - An irqchip named pm8xxx since it was (I guess) realized that the driver was generic for all pm8xxx PMICs. As I may want to add support for PM8901 this is starting to get really messy. Fix this situation by: - Remove the MFD_PM8921_CORE symbol and rely solely on MFD_PM8XXX and convert all users, including LEDs Kconfig and ARM defconfigs for qcom and multi_v7 to use that single symbol. - Renaming the driver to qcom-pm8xxx.c to fit along the two other qcom* prefixed drivers. - Rename functions withing the driver from 8921 to 8xxx to indicate it is generic. - Just drop the =m config from the pxa_defconfig, I have no clue why it is even there, it is not a Qualcomm platform. (Possibly older Kconfig noise from saveconfig.) Cc: Stephen Boyd Cc: Neil Armstrong Cc: Abhijeet Dharmapurikar Signed-off-by: Linus Walleij Reviewed-by: Andy Gross Acked-by: Bjorn Andersson Acked-by: Jacek Anaszewski Acked-by: Arnd Bergmann Signed-off-by: Lee Jones --- arch/arm/configs/multi_v7_defconfig | 2 +- arch/arm/configs/pxa_defconfig | 1 - arch/arm/configs/qcom_defconfig | 1 - drivers/leds/Kconfig | 2 +- drivers/mfd/Kconfig | 14 +++---- drivers/mfd/Makefile | 2 +- drivers/mfd/{pm8921-core.c => qcom-pm8xxx.c} | 42 ++++++++++---------- 7 files changed, 29 insertions(+), 35 deletions(-) rename drivers/mfd/{pm8921-core.c => qcom-pm8xxx.c} (92%) diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index 437d0740dec6..4280de7b9b4e 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -489,7 +489,7 @@ CONFIG_MFD_MAX8907=y CONFIG_MFD_MAX8997=y CONFIG_MFD_MAX8998=y CONFIG_MFD_RK808=y -CONFIG_MFD_PM8921_CORE=y +CONFIG_MFD_PM8XXX=y CONFIG_MFD_QCOM_RPM=y CONFIG_MFD_SPMI_PMIC=y CONFIG_MFD_SEC_CORE=y diff --git a/arch/arm/configs/pxa_defconfig b/arch/arm/configs/pxa_defconfig index a016ecc0084b..e4314b1227a3 100644 --- a/arch/arm/configs/pxa_defconfig +++ b/arch/arm/configs/pxa_defconfig @@ -411,7 +411,6 @@ CONFIG_MFD_MAX77693=y CONFIG_MFD_MAX8907=m CONFIG_EZX_PCAP=y CONFIG_UCB1400_CORE=m -CONFIG_MFD_PM8921_CORE=m CONFIG_MFD_SEC_CORE=y CONFIG_MFD_PALMAS=y CONFIG_MFD_TPS65090=y diff --git a/arch/arm/configs/qcom_defconfig b/arch/arm/configs/qcom_defconfig index c2dff4fd5fc4..74e9cd759b99 100644 --- a/arch/arm/configs/qcom_defconfig +++ b/arch/arm/configs/qcom_defconfig @@ -119,7 +119,6 @@ CONFIG_POWER_RESET=y CONFIG_POWER_RESET_MSM=y CONFIG_THERMAL=y CONFIG_MFD_PM8XXX=y -CONFIG_MFD_PM8921_CORE=y CONFIG_MFD_QCOM_RPM=y CONFIG_MFD_SPMI_PMIC=y CONFIG_REGULATOR=y diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..86bb1515a00e 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 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 similarity index 92% rename from drivers/mfd/pm8921-core.c rename to 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"); From e381322b0190c1253d347de3f28b5c37756fb651 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 16 Sep 2016 14:16:48 -0500 Subject: [PATCH 02/20] leds: Introduce userspace LED class driver This driver creates a userspace leds driver similar to uinput. New LEDs are created by opening /dev/uleds and writing a uleds_user_dev struct. A new LED class device is registered with the name given in the struct. Reading will return a single byte that is the current brightness. The poll() syscall is also supported. It will be triggered whenever the brightness changes. Closing the file handle to /dev/uleds will remove the leds class device. Signed-off-by: David Lechner Signed-off-by: Jacek Anaszewski --- Documentation/leds/uleds.txt | 36 ++++++ drivers/leds/Kconfig | 8 ++ drivers/leds/Makefile | 3 + drivers/leds/uleds.c | 235 +++++++++++++++++++++++++++++++++++ include/uapi/linux/Kbuild | 1 + include/uapi/linux/uleds.h | 24 ++++ 6 files changed, 307 insertions(+) create mode 100644 Documentation/leds/uleds.txt create mode 100644 drivers/leds/uleds.c create mode 100644 include/uapi/linux/uleds.h diff --git a/Documentation/leds/uleds.txt b/Documentation/leds/uleds.txt new file mode 100644 index 000000000000..13e375a580f9 --- /dev/null +++ b/Documentation/leds/uleds.txt @@ -0,0 +1,36 @@ +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. diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..5fd3f4ca0c3c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -659,6 +659,14 @@ 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. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3965070190f5..d5331ff5b563 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -75,5 +75,8 @@ obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.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/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 + * + * Based on uinput.c: Aristeu Sergio Rozanski Filho + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 6965d0909554..f196c539021e 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -425,6 +425,7 @@ header-y += udp.h header-y += uhid.h header-y += uinput.h header-y += uio.h +header-y += uleds.h header-y += ultrasound.h header-y += un.h header-y += unistd.h diff --git a/include/uapi/linux/uleds.h b/include/uapi/linux/uleds.h new file mode 100644 index 000000000000..95186578c46e --- /dev/null +++ b/include/uapi/linux/uleds.h @@ -0,0 +1,24 @@ +/* + * 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_ */ From eb1ce7469940b4eb8a466d9ae4d032fb2c9969dc Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 16 Sep 2016 14:16:49 -0500 Subject: [PATCH 03/20] leds: Use macro for max device node name size Use a macro instead of hard-coding the max device node name size. The uleds driver introduced a macro for this value, so using it. Signed-off-by: David Lechner Signed-off-by: Jacek Anaszewski --- drivers/leds/led-class.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index aa84e5b37593..d946eb166781 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -20,6 +20,7 @@ #include #include #include +#include #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)); From fa7f32422ea1ac276b45b96a540ed5981caaa61f Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 16 Sep 2016 14:16:50 -0500 Subject: [PATCH 04/20] tools/leds: Add uledmon program for monitoring userspace LEDs The uleds driver provides userspace LED devices. This tool is used to create one of these devices and monitor the changes in brighness for testing purposes. Signed-off-by: David Lechner Signed-off-by: Jacek Anaszewski --- tools/Makefile | 7 ++--- tools/leds/.gitignore | 1 + tools/leds/Makefile | 13 +++++++++ tools/leds/uledmon.c | 63 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tools/leds/.gitignore create mode 100644 tools/leds/Makefile create mode 100644 tools/leds/uledmon.c diff --git a/tools/Makefile b/tools/Makefile index daa8fb3e4363..00caacd3ed92 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -17,6 +17,7 @@ help: @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' @echo ' kvm_stat - top-like utility for displaying kvm statistics' + @echo ' leds - LEDs tools' @echo ' lguest - a minimal 32-bit x86 hypervisor' @echo ' net - misc networking tools' @echo ' perf - Linux performance measurement and analysis tool' @@ -56,7 +57,7 @@ acpi: FORCE cpupower: FORCE $(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,$@) liblockdep: FORCE @@ -126,7 +127,7 @@ acpi_clean: 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) liblockdep_clean: @@ -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 \ vm_clean net_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ - gpio_clean objtool_clean + gpio_clean objtool_clean leds_clean .PHONY: FORCE diff --git a/tools/leds/.gitignore b/tools/leds/.gitignore new file mode 100644 index 000000000000..ac96d9f53dfc --- /dev/null +++ b/tools/leds/.gitignore @@ -0,0 +1 @@ +uledmon diff --git a/tools/leds/Makefile b/tools/leds/Makefile new file mode 100644 index 000000000000..c03a79ebf9c8 --- /dev/null +++ b/tools/leds/Makefile @@ -0,0 +1,13 @@ +# 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 diff --git a/tools/leds/uledmon.c b/tools/leds/uledmon.c new file mode 100644 index 000000000000..25cbc7acf50a --- /dev/null +++ b/tools/leds/uledmon.c @@ -0,0 +1,63 @@ +/* + * 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 + * + * is the name of the LED class device to be created. Pressing + * CTRL+C will exit. + */ + +#include +#include +#include +#include +#include + +#include + +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 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; +} From e28aaeabeed7744bc88ccb9e67d47358e313178f Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 3 Oct 2016 10:42:01 +0200 Subject: [PATCH 05/20] leds/leds-lp5523.txt: make documentation match reality Files are visible all the time, so remove incorrect notes. Signed-off-by: Pavel Machek Signed-off-by: Jacek Anaszewski --- Documentation/leds/leds-lp5523.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/leds/leds-lp5523.txt b/Documentation/leds/leds-lp5523.txt index 0dbbd279c9b9..0961a060fc4d 100644 --- a/Documentation/leds/leds-lp5523.txt +++ b/Documentation/leds/leds-lp5523.txt @@ -34,8 +34,8 @@ There are two ways to run LED patterns. Control interface for the engines: x is 1 .. 3 enginex_mode : disabled, load, run - enginex_load : microcode load (visible only in load mode) - enginex_leds : led mux control (visible only in load mode) + enginex_load : microcode load + enginex_leds : led mux control cd /sys/class/leds/lp5523:channel2/device echo "load" > engine3_mode From e602fda1a358a0672ef358c3ab39fb0f3fe15d91 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 3 Oct 2016 10:13:14 +0200 Subject: [PATCH 06/20] ledtrig-cpu.c: fix english Fix english spelling. Signed-off-by: Pavel Machek Signed-off-by: Jacek Anaszewski --- drivers/leds/trigger/ledtrig-cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From cce35f357fb4d3269dc401ee8af257d63faea8c4 Mon Sep 17 00:00:00 2001 From: Alexander Kurz Date: Sun, 9 Oct 2016 15:37:47 +0200 Subject: [PATCH 07/20] leds: mc13783: Fix MC13892 keypad led access Fix the register access shift argument calculation introduced with commit a59ce6584d56 ("leds: leds-mc13783: Add MC34708 LED support") and re-enable access to the "keypad" led for MC13892 MFC devices. Signed-off-by: Alexander Kurz Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-mc13783.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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: From aa73684cdeb3a8a9f38e6a240b177c1fe04178b5 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 14 Oct 2016 09:23:34 -0300 Subject: [PATCH 08/20] leds: lp3952: Export I2C module alias information for module autoload If the driver is built as a module, I2C module alias information is not filled so the module won't be autoloaded if the device isn't registered over ACPI. Export the I2C device table alias with MODULE_DEVICE_TABLE() macro so the information is exported in the module. Before this patch: $ modinfo drivers/leds/leds-lp3952.ko | grep alias alias: acpi*:TXNW3952:* After this patch: $ modinfo drivers/leds/leds-lp3952.ko | grep alias alias: i2c:lp3952 alias: acpi*:TXNW3952:* Signed-off-by: Javier Martinez Canillas Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-lp3952.c | 1 + 1 file changed, 1 insertion(+) 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[] = { From ed25e9cae1ab8e84556c9413fd5de0a9da8f3657 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 3 Oct 2016 10:10:50 +0200 Subject: [PATCH 09/20] cleanup LED documentation and make it match reality sysfs-class-led fails to mention some important details. Also fix led vs LED and english. Signed-off-by: Pavel Machek Signed-off-by: Jacek Anaszewski --- Documentation/ABI/testing/sysfs-class-led | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index 86ace287d48b..491cdeedc195 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -4,16 +4,24 @@ KernelVersion: 2.6.17 Contact: Richard Purdie Description: 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 /sys/class/leds//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//max_brightness Date: March 2006 KernelVersion: 2.6.17 Contact: Richard Purdie 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//trigger Date: March 2006 @@ -21,7 +29,7 @@ KernelVersion: 2.6.17 Contact: Richard Purdie Description: 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 scheduler is chosen. Trigger specific parameters can appear in /sys/class/leds/ once a given trigger is selected. For From 35c7d30179fbc17af7281ccea18e6d74c003e448 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Thu, 13 Oct 2016 06:16:12 -0700 Subject: [PATCH 10/20] leds: pca963x: workaround group blink scaling issue PCA9632TK part seems to incorrectly blink at ~1.3x of the programmed rate. This patchset add a nxp,period-scale devicetree property to adjust for this misconfiguration. Signed-off-by: Matt Ranostay Cc: Tony Lindgren Signed-off-by: Jacek Anaszewski --- .../devicetree/bindings/leds/pca963x.txt | 3 +++ drivers/leds/leds-pca963x.c | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/pca963x.txt b/Documentation/devicetree/bindings/leds/pca963x.txt index dafbe9931c2b..dfbdb123a9bf 100644 --- a/Documentation/devicetree/bindings/leds/pca963x.txt +++ b/Documentation/devicetree/bindings/leds/pca963x.txt @@ -7,6 +7,9 @@ Optional properties: - nxp,totem-pole : use totem pole (push-pull) instead of open-drain (pca9632 defaults to open-drain, newer chips to totem pole) - 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. diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 407eba11e187..b6ce1f2ec33e 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -59,6 +59,7 @@ struct pca963x_chipdef { u8 grpfreq; u8 ledout_base; int n_leds; + unsigned int scaling; }; static struct pca963x_chipdef pca963x_chipdefs[] = { @@ -189,6 +190,14 @@ static int pca963x_led_set(struct led_classdev *led_cdev, return pca963x_brightness(pca963x, value); } +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, unsigned long *delay_on, unsigned long *delay_off) { @@ -207,14 +216,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 +231,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 +303,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; } From 90a5537bd83b461180120c926b7795c7dd44093a Mon Sep 17 00:00:00 2001 From: Felix Brack Date: Wed, 26 Oct 2016 16:08:02 +0200 Subject: [PATCH 11/20] leds: pca9532: Use default trigger value from platform data The value for a led's default_trigger should come from platform data instead of data (which is always 0). Signed-off-by: Felix Brack Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-pca9532.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From a8c170b015ff6695641bdfbdb74729411ba1e9e5 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Fri, 28 Oct 2016 18:20:42 -0700 Subject: [PATCH 12/20] leds: pca963x: enable low-power state Allow chip to enter low power state when no LEDs are being lit or in blink mode. Cc: Peter Meerwald , Cc: Ricardo Ribalda Cc: Tony Lindgren Signed-off-by: Matt Ranostay Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-pca963x.c | 40 ++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index b6ce1f2ec33e..46fbf935944a 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -103,6 +103,7 @@ struct pca963x { struct mutex mutex; struct i2c_client *client; struct pca963x_led *leds; + unsigned long leds_on; }; struct pca963x_led { @@ -124,7 +125,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: @@ -141,14 +141,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; } @@ -180,14 +179,41 @@ 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, @@ -403,8 +429,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) */ From dca897e28f364724e42f65cf0262fa367512498d Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 3 Nov 2016 19:17:19 +0000 Subject: [PATCH 13/20] leds: verify vendor and change license in mlxcpld driver Verify that vendor is Mellanox as the first step of initialization. If it is not - return ENODEV. Change module license from "GPL v2" to "Dual BSD/GPL". Signed-off-by: Vadim Pasternak Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-mlxcpld.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 "); MODULE_DESCRIPTION("Mellanox board LED driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:leds_mlxcpld"); From 8338eab50ffb3399a938d723f9605383ed9f8473 Mon Sep 17 00:00:00 2001 From: Hui Chun Ong Date: Fri, 4 Nov 2016 13:31:47 +0800 Subject: [PATCH 14/20] leds: Add user LED driver for NIC78bx device Add the driver to support User LEDs on PXI Embedded Controller. Signed-off-by: Hui Chun Ong Signed-off-by: Brad Mouring Acked-by: Mika Westerberg Signed-off-by: Jacek Anaszewski --- drivers/leds/Kconfig | 11 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-nic78bx.c | 209 ++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 drivers/leds/leds-nic78bx.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 5fd3f4ca0c3c..fb6304e97422 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -667,6 +667,17 @@ config LEDS_USER 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 d5331ff5b563..6b8273736478 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -71,6 +71,7 @@ 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 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 +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); From a9c6ce57ec2f136d08141e8220a0ffaca216f7b0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Nov 2016 14:38:46 +0100 Subject: [PATCH 15/20] led: core: Use atomic bit-field for the blink-flags All the LED_BLINK* flags are accessed read-modify-write from e.g. led_set_brightness and led_blink_set_oneshot while both set_brightness_work and the blink_timer may be running. If these race then the modify step done by one of them may be lost, switch the LED_BLINK* flags to a new atomic work_flags bit-field to avoid this race. Signed-off-by: Hans de Goede Signed-off-by: Jacek Anaszewski --- drivers/leds/led-class.c | 1 + drivers/leds/led-core.c | 52 +++++++++++++++++++++------------------- include/linux/leds.h | 24 +++++++++++-------- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index d946eb166781..326ee6e925a2 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -204,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..bd6bb4d44f05 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -53,12 +53,13 @@ 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; } @@ -73,10 +74,9 @@ static void led_timer_function(unsigned long data) * 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)) + if (!test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, + &led_cdev->work_flags)) led_cdev->blink_brightness = brightness; - else - led_cdev->flags &= ~LED_BLINK_BRIGHTNESS_CHANGE; 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,17 +233,18 @@ 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; + set_bit(LED_BLINK_BRIGHTNESS_CHANGE, + &led_cdev->work_flags); led_cdev->blink_brightness = brightness; } return; diff --git a/include/linux/leds.h b/include/linux/leds.h index ddfcb2df3656..21c598b366f8 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -42,16 +42,20 @@ struct led_classdev { #define LED_UNREGISTERING (1 << 1) /* Upper 16 bits reflect control information */ #define LED_CORE_SUSPENDRESUME (1 << 16) -#define LED_BLINK_SW (1 << 17) -#define LED_BLINK_ONESHOT (1 << 18) -#define LED_BLINK_ONESHOT_STOP (1 << 19) -#define LED_BLINK_INVERT (1 << 20) -#define LED_BLINK_BRIGHTNESS_CHANGE (1 << 21) -#define LED_BLINK_DISABLE (1 << 22) -#define LED_SYSFS_DISABLE (1 << 23) -#define LED_DEV_CAP_FLASH (1 << 24) -#define LED_HW_PLUGGABLE (1 << 25) -#define LED_PANIC_INDICATOR (1 << 26) +#define LED_SYSFS_DISABLE (1 << 17) +#define LED_DEV_CAP_FLASH (1 << 18) +#define LED_HW_PLUGGABLE (1 << 19) +#define LED_PANIC_INDICATOR (1 << 20) + + /* set_brightness_work / blink_timer flags, atomic, private. */ + unsigned long work_flags; + +#define LED_BLINK_SW 0 +#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 * Must not sleep. Use brightness_set_blocking for drivers From eb1610b4c273370f491c5e194e5a56e3470d81e8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 23 Oct 2016 21:47:26 +0200 Subject: [PATCH 16/20] led: core: Fix blink_brightness setting race All 3 of led_timer_func, led_set_brightness and led_set_software_blink set blink_brightness. If led_timer_func or led_set_software_blink race with led_set_brightness they may end up overwriting the new blink_brightness. The new atomic work_flags does not protect against this as it just protects the flags and not blink_brightness. This commit introduces a new new_blink_brightness value which gets set by led_set_brightness and read by led_timer_func on LED on, fixing this. Dealing with the new brightness at LED on time, makes the new brightness apply sooner, which also fixes a led_set_brightness which happens while a oneshot blink which ends in LED on is running not getting applied. Signed-off-by: Hans de Goede Signed-off-by: Jacek Anaszewski --- drivers/leds/led-core.c | 14 +++++++------- include/linux/leds.h | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index bd6bb4d44f05..ef1360445413 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -66,17 +66,17 @@ static void led_timer_function(unsigned long data) 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 (!test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, - &led_cdev->work_flags)) - led_cdev->blink_brightness = brightness; + led_cdev->blink_brightness = brightness; brightness = LED_OFF; delay = led_cdev->blink_delay_off; } @@ -245,7 +245,7 @@ void led_set_brightness(struct led_classdev *led_cdev, } else { set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); - led_cdev->blink_brightness = brightness; + led_cdev->new_blink_brightness = brightness; } return; } diff --git a/include/linux/leds.h b/include/linux/leds.h index 21c598b366f8..569cb531094c 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -93,6 +93,7 @@ struct led_classdev { unsigned long blink_delay_on, blink_delay_off; struct timer_list blink_timer; int blink_brightness; + int new_blink_brightness; void (*flash_resume)(struct led_classdev *led_cdev); struct work_struct set_brightness_work; From cee0122de3a850d3d66d8346a20e3d2a0146b4a0 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Wed, 23 Nov 2016 22:50:05 +0800 Subject: [PATCH 17/20] leds: leds-cobalt-raq: use builtin_platform_driver Use builtin_platform_driver() helper to simplify the code. Signed-off-by: Geliang Tang Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-cobalt-raq.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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); From f26dab9fc745540f7e30aa8840be6e49b9671080 Mon Sep 17 00:00:00 2001 From: Tin Huynh Date: Tue, 29 Nov 2016 11:18:20 +0700 Subject: [PATCH 18/20] leds: pca963x: Add ACPI support This patch enables ACPI support for leds-pca963x driver. Signed-off-by: Tin Huynh Acked-by: Mika Westerberg Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-pca963x.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 46fbf935944a..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 #include #include #include @@ -96,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 { @@ -360,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) { @@ -464,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, From 825fe38a0976756fba907952d5ac9d4046068fe0 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 21 Oct 2016 12:52:05 -0300 Subject: [PATCH 19/20] leds: netxbig: fix module autoload for OF registration If the driver is built as a module, autoload won't work because the module alias information is not filled. So user-space can't match the registered device with the corresponding module. Export the module alias information using the MODULE_DEVICE_TABLE() macro. Before this patch: $ modinfo drivers/leds//leds-netxbig.ko | grep alias alias: platform:leds-netxbig After this patch: $ modinfo drivers/leds//leds-netxbig.ko | grep alias alias: platform:leds-netxbig alias: of:N*T*Clacie,netxbig-ledsC* alias: of:N*T*Clacie,netxbig-leds Signed-off-by: Javier Martinez Canillas Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-netxbig.c | 1 + 1 file changed, 1 insertion(+) 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, From 44b3e31d540e917a4d2292b902ade63fa1748d9a Mon Sep 17 00:00:00 2001 From: Tin Huynh Date: Fri, 2 Dec 2016 11:39:13 +0700 Subject: [PATCH 20/20] leds: pca955x: Add ACPI support This patch enables ACPI support for leds-pca955x driver. Signed-off-by: Tin Huynh Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-pca955x.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) 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 #include #include #include @@ -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,