summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drivers/leds')
-rw-r--r--kernel/drivers/leds/Kconfig105
-rw-r--r--kernel/drivers/leds/Makefile9
-rw-r--r--kernel/drivers/leds/led-class.c71
-rw-r--r--kernel/drivers/leds/led-core.c78
-rw-r--r--kernel/drivers/leds/leds-88pm860x.c1
-rw-r--r--kernel/drivers/leds/leds-aat1290.c577
-rw-r--r--kernel/drivers/leds/leds-bcm6328.c435
-rw-r--r--kernel/drivers/leds/leds-bcm6358.c246
-rw-r--r--kernel/drivers/leds/leds-cobalt-qube.c23
-rw-r--r--kernel/drivers/leds/leds-cobalt-raq.c15
-rw-r--r--kernel/drivers/leds/leds-dac124s085.c1
-rw-r--r--kernel/drivers/leds/leds-fsg.c52
-rw-r--r--kernel/drivers/leds/leds-gpio.c27
-rw-r--r--kernel/drivers/leds/leds-hp6xx.c17
-rw-r--r--kernel/drivers/leds/leds-ipaq-micro.c27
-rw-r--r--kernel/drivers/leds/leds-ktd2692.c444
-rw-r--r--kernel/drivers/leds/leds-lm3530.c1
-rw-r--r--kernel/drivers/leds/leds-lm355x.c1
-rw-r--r--kernel/drivers/leds/leds-lm3642.c1
-rw-r--r--kernel/drivers/leds/leds-locomo.c15
-rw-r--r--kernel/drivers/leds/leds-lp5521.c11
-rw-r--r--kernel/drivers/leds/leds-lp5523.c159
-rw-r--r--kernel/drivers/leds/leds-lp5562.c11
-rw-r--r--kernel/drivers/leds/leds-lp55xx-common.c15
-rw-r--r--kernel/drivers/leds/leds-lp55xx-common.h4
-rw-r--r--kernel/drivers/leds/leds-lp8501.c11
-rw-r--r--kernel/drivers/leds/leds-lp8860.c4
-rw-r--r--kernel/drivers/leds/leds-max77693.c1099
-rw-r--r--kernel/drivers/leds/leds-menf21bmc.c26
-rw-r--r--kernel/drivers/leds/leds-net48xx.c9
-rw-r--r--kernel/drivers/leds/leds-netxbig.c336
-rw-r--r--kernel/drivers/leds/leds-ns2.c170
-rw-r--r--kernel/drivers/leds/leds-ot200.c21
-rw-r--r--kernel/drivers/leds/leds-pca955x.c1
-rw-r--r--kernel/drivers/leds/leds-pca963x.c2
-rw-r--r--kernel/drivers/leds/leds-pm8941-wled.c435
-rw-r--r--kernel/drivers/leds/leds-powernv.c349
-rw-r--r--kernel/drivers/leds/leds-sead3.c78
-rw-r--r--kernel/drivers/leds/leds-syscon.c170
-rw-r--r--kernel/drivers/leds/leds-tca6507.c2
-rw-r--r--kernel/drivers/leds/leds-tlc591xx.c296
-rw-r--r--kernel/drivers/leds/leds-wrap.c28
-rw-r--r--kernel/drivers/leds/leds.h25
-rw-r--r--kernel/drivers/leds/trigger/Kconfig2
-rw-r--r--kernel/drivers/leds/trigger/ledtrig-heartbeat.c47
45 files changed, 4438 insertions, 1019 deletions
diff --git a/kernel/drivers/leds/Kconfig b/kernel/drivers/leds/Kconfig
index 966b9605f..b1ab8bdf8 100644
--- a/kernel/drivers/leds/Kconfig
+++ b/kernel/drivers/leds/Kconfig
@@ -11,9 +11,6 @@ menuconfig NEW_LEDS
Say Y to enable Linux LED support. This allows control of supported
LEDs from both userspace and optionally, by kernel events (triggers).
- This is not related to standard keyboard LEDs which are controlled
- via the input system.
-
if NEW_LEDS
config LEDS_CLASS
@@ -42,6 +39,32 @@ config LEDS_88PM860X
This option enables support for on-chip LED drivers found on Marvell
Semiconductor 88PM8606 PMIC.
+config LEDS_AAT1290
+ tristate "LED support for the AAT1290"
+ depends on LEDS_CLASS_FLASH
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on GPIOLIB || COMPILE_TEST
+ depends on OF
+ depends on PINCTRL
+ help
+ This option enables support for the LEDs on the AAT1290.
+
+config LEDS_BCM6328
+ tristate "LED Support for Broadcom BCM6328"
+ depends on LEDS_CLASS
+ depends on OF
+ help
+ This option enables support for LEDs connected to the BCM6328
+ LED HW controller accessed via MMIO registers.
+
+config LEDS_BCM6358
+ tristate "LED Support for Broadcom BCM6358"
+ depends on LEDS_CLASS
+ depends on OF
+ help
+ This option enables support for LEDs connected to the BCM6358
+ LED HW controller accessed via MMIO registers.
+
config LEDS_LM3530
tristate "LCD Backlight driver for LM3530"
depends on LEDS_CLASS
@@ -147,6 +170,7 @@ config LEDS_SUNFIRE
config LEDS_IPAQ_MICRO
tristate "LED Support for the Compaq iPAQ h3xxx"
+ depends on LEDS_CLASS
depends on MFD_IPAQ_MICRO
help
Choose this option if you want to use the notification LED on
@@ -182,7 +206,7 @@ config LEDS_PCA9532_GPIO
config LEDS_GPIO
tristate "LED Support for GPIO connected LEDs"
depends on LEDS_CLASS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This option enables support for the LEDs connected to GPIO
outputs. To be useful the particular board must have LEDs
@@ -206,6 +230,7 @@ config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
select FW_LOADER
+ select FW_LOADER_USER_HELPER
help
This option supports common operations for LP5521/5523/55231/5562/8501
devices.
@@ -395,7 +420,7 @@ config LEDS_INTEL_SS4200
config LEDS_LT3593
tristate "LED driver for LT3593 controllers"
depends on LEDS_CLASS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This option enables support for LEDs driven by a Linear Technology
LT3593 controller. This controller uses a special one-wire pulse
@@ -431,12 +456,16 @@ config LEDS_MC13783
config LEDS_NS2
tristate "LED support for Network Space v2 GPIO LEDs"
depends on LEDS_CLASS
- depends on MACH_KIRKWOOD
+ depends on MACH_KIRKWOOD || MACH_ARMADA_370
default y
help
- This option enable support for the dual-GPIO LED found on the
- Network Space v2 board (and parents). This include Internet Space v2,
- Network Space (Max) v2 and d2 Network v2 boards.
+ This option enables support for the dual-GPIO LEDs found on the
+ following LaCie/Seagate boards:
+
+ Network Space v2 (and parents: Max, Mini)
+ Internet Space v2
+ d2 Network v2
+ n090401 (Seagate NAS 4-Bay)
config LEDS_NETXBIG
tristate "LED support for Big Network series LEDs"
@@ -467,6 +496,25 @@ config LEDS_TCA6507
LED driver chips accessed via the I2C bus.
Driver support brightness control and hardware-assisted blinking.
+config LEDS_TLC591XX
+ tristate "LED driver for TLC59108 and TLC59116 controllers"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for Texas Instruments TLC59108
+ and TLC59116 LED controllers.
+
+config LEDS_MAX77693
+ tristate "LED support for MAX77693 Flash"
+ depends on LEDS_CLASS_FLASH
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MAX77693
+ depends on OF
+ help
+ This option enables support for the flash part of the MAX77693
+ multifunction device. It has build in control for two leds in flash
+ and torch mode.
+
config LEDS_MAX8997
tristate "LED support for MAX8997 PMIC"
depends on LEDS_CLASS && MFD_MAX8997
@@ -498,6 +546,26 @@ config LEDS_MENF21BMC
This driver can also be built as a module. If so the module
will be called leds-menf21bmc.
+config LEDS_KTD2692
+ tristate "LED support for KTD2692 flash LED controller"
+ depends on LEDS_CLASS_FLASH && OF
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ This option enables support for KTD2692 LED flash connected
+ through ExpressWire interface.
+
+ Say Y to enable this driver.
+
+config LEDS_SEAD3
+ tristate "LED support for the MIPS SEAD 3 board"
+ depends on LEDS_CLASS && MIPS_SEAD3
+ help
+ Say Y here to include support for the FLED and PLED LEDs on SEAD3 eval
+ boards.
+
+ This driver can also be built as a module. If so the module
+ will be called leds-sead3.
+
comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
config LEDS_BLINKM
@@ -508,6 +576,17 @@ config LEDS_BLINKM
This option enables support for the BlinkM RGB LED connected
through I2C. Say Y to enable support for the BlinkM LED.
+config LEDS_POWERNV
+ tristate "LED support for PowerNV Platform"
+ depends on LEDS_CLASS
+ depends on PPC_POWERNV
+ depends on OF
+ help
+ This option enables support for the system LEDs present on
+ PowerNV platforms. Say 'y' to enable this support in kernel.
+ To compile this driver as a module, choose 'm' here: the module
+ will be called leds-powernv.
+
config LEDS_SYSCON
bool "LED support for LEDs on system controllers"
depends on LEDS_CLASS=y
@@ -526,14 +605,6 @@ config LEDS_VERSATILE
This option enabled support for the LEDs on the ARM Versatile
and RealView boards. Say Y to enabled these.
-config LEDS_PM8941_WLED
- tristate "LED support for the Qualcomm PM8941 WLED block"
- depends on LEDS_CLASS
- select REGMAP
- help
- This option enables support for the 'White' LED block
- on Qualcomm PM8941 PMICs.
-
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
diff --git a/kernel/drivers/leds/Makefile b/kernel/drivers/leds/Makefile
index bf4609338..e9d530927 100644
--- a/kernel/drivers/leds/Makefile
+++ b/kernel/drivers/leds/Makefile
@@ -7,6 +7,9 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
+obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
+obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
+obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
@@ -31,6 +34,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
+obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
@@ -52,13 +56,16 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
-obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o
+obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
+obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
+obj-$(CONFIG_LEDS_SEAD3) += leds-sead3.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/kernel/drivers/leds/led-class.c b/kernel/drivers/leds/led-class.c
index 7fb2a19ac..7385f98dd 100644
--- a/kernel/drivers/leds/led-class.c
+++ b/kernel/drivers/leds/led-class.c
@@ -102,65 +102,6 @@ static const struct attribute_group *led_groups[] = {
NULL,
};
-static void led_timer_function(unsigned long data)
-{
- struct led_classdev *led_cdev = (void *)data;
- unsigned long brightness;
- unsigned long delay;
-
- if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
- led_set_brightness_async(led_cdev, LED_OFF);
- return;
- }
-
- if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
- led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
- return;
- }
-
- brightness = led_get_brightness(led_cdev);
- if (!brightness) {
- /* Time to switch the LED on. */
- 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.
- */
- led_cdev->blink_brightness = brightness;
- brightness = LED_OFF;
- delay = led_cdev->blink_delay_off;
- }
-
- led_set_brightness_async(led_cdev, brightness);
-
- /* Return in next iteration if led is in one-shot mode and we are in
- * 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 (brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- } else {
- if (!brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- }
- }
-
- mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
-}
-
-static void set_brightness_delayed(struct work_struct *ws)
-{
- struct led_classdev *led_cdev =
- container_of(ws, struct led_classdev, set_brightness_work);
-
- led_stop_software_blink(led_cdev);
-
- led_set_brightness_async(led_cdev, led_cdev->delayed_set_value);
-}
-
/**
* led_classdev_suspend - suspend an led_classdev.
* @led_cdev: the led_classdev to suspend.
@@ -223,12 +164,15 @@ static int led_classdev_next_name(const char *init_name, char *name,
{
unsigned int i = 0;
int ret = 0;
+ struct device *dev;
strlcpy(name, init_name, len);
- while (class_find_device(leds_class, NULL, name, match_name) &&
- (ret < len))
+ while ((ret < len) &&
+ (dev = class_find_device(leds_class, NULL, name, match_name))) {
+ put_device(dev);
ret = snprintf(name, len, "%s_%u", init_name, ++i);
+ }
if (ret >= len)
return -ENOMEM;
@@ -275,10 +219,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
led_update_brightness(led_cdev);
- INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
-
- setup_timer(&led_cdev->blink_timer, led_timer_function,
- (unsigned long)led_cdev);
+ led_init_core(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
diff --git a/kernel/drivers/leds/led-core.c b/kernel/drivers/leds/led-core.c
index 9886dace5..c1c3af089 100644
--- a/kernel/drivers/leds/led-core.c
+++ b/kernel/drivers/leds/led-core.c
@@ -25,6 +25,70 @@ EXPORT_SYMBOL_GPL(leds_list_lock);
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
+static void led_timer_function(unsigned long data)
+{
+ struct led_classdev *led_cdev = (void *)data;
+ unsigned long brightness;
+ unsigned long delay;
+
+ if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
+ led_set_brightness_async(led_cdev, LED_OFF);
+ return;
+ }
+
+ if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
+ led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
+ return;
+ }
+
+ brightness = led_get_brightness(led_cdev);
+ if (!brightness) {
+ /* Time to switch the LED on. */
+ if (led_cdev->delayed_set_value) {
+ led_cdev->blink_brightness =
+ led_cdev->delayed_set_value;
+ led_cdev->delayed_set_value = 0;
+ }
+ 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.
+ */
+ led_cdev->blink_brightness = brightness;
+ brightness = LED_OFF;
+ delay = led_cdev->blink_delay_off;
+ }
+
+ led_set_brightness_async(led_cdev, brightness);
+
+ /* Return in next iteration if led is in one-shot mode and we are in
+ * 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 (brightness)
+ led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
+ } else {
+ if (!brightness)
+ led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
+ }
+ }
+
+ mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
+}
+
+static void set_brightness_delayed(struct work_struct *ws)
+{
+ struct led_classdev *led_cdev =
+ container_of(ws, struct led_classdev, set_brightness_work);
+
+ led_stop_software_blink(led_cdev);
+
+ led_set_brightness_async(led_cdev, led_cdev->delayed_set_value);
+}
+
static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
@@ -72,6 +136,15 @@ static void led_blink_setup(struct led_classdev *led_cdev,
led_set_software_blink(led_cdev, *delay_on, *delay_off);
}
+void led_init_core(struct led_classdev *led_cdev)
+{
+ INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
+
+ setup_timer(&led_cdev->blink_timer, led_timer_function,
+ (unsigned long)led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_init_core);
+
void led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
@@ -119,10 +192,11 @@ void led_set_brightness(struct led_classdev *led_cdev,
{
int ret = 0;
- /* delay brightness setting if need to stop soft-blink timer */
+ /* delay brightness if soft-blink is active */
if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
led_cdev->delayed_set_value = brightness;
- schedule_work(&led_cdev->set_brightness_work);
+ if (brightness == LED_OFF)
+ schedule_work(&led_cdev->set_brightness_work);
return;
}
diff --git a/kernel/drivers/leds/leds-88pm860x.c b/kernel/drivers/leds/leds-88pm860x.c
index 1497a0916..7870840e7 100644
--- a/kernel/drivers/leds/leds-88pm860x.c
+++ b/kernel/drivers/leds/leds-88pm860x.c
@@ -142,6 +142,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev,
of_property_read_u32(np, "marvell,88pm860x-iset",
&iset);
data->iset = PM8606_LED_CURRENT(iset);
+ of_node_put(np);
break;
}
}
diff --git a/kernel/drivers/leds/leds-aat1290.c b/kernel/drivers/leds/leds-aat1290.c
new file mode 100644
index 000000000..ac77d36b6
--- /dev/null
+++ b/kernel/drivers/leds/leds-aat1290.c
@@ -0,0 +1,577 @@
+/*
+ * LED Flash class driver for the AAT1290
+ * 1.5A Step-Up Current Regulator for Flash LEDs
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
+#define AAT1290_MAX_MM_CURR_PERCENT_0 16
+#define AAT1290_MAX_MM_CURR_PERCENT_100 1
+
+#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
+
+#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
+#define AAT1290_MOVIE_MODE_OFF 1
+#define AAT1290_MOVIE_MODE_ON 3
+
+#define AAT1290_MM_CURRENT_RATIO_ADDR 20
+#define AAT1290_MM_TO_FL_1_92 1
+
+#define AAT1290_MM_TO_FL_RATIO 1000 / 1920
+#define AAT1290_MAX_MM_CURRENT(fl_max) (fl_max * AAT1290_MM_TO_FL_RATIO)
+
+#define AAT1290_LATCH_TIME_MIN_US 500
+#define AAT1290_LATCH_TIME_MAX_US 1000
+#define AAT1290_EN_SET_TICK_TIME_US 1
+#define AAT1290_FLEN_OFF_DELAY_TIME_US 10
+#define AAT1290_FLASH_TM_NUM_LEVELS 16
+#define AAT1290_MM_CURRENT_SCALE_SIZE 15
+
+
+struct aat1290_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 max_mm_current;
+ /* maximum LED current in flash mode */
+ u32 max_flash_current;
+ /* maximum flash timeout */
+ u32 max_flash_tm;
+ /* external strobe capability */
+ bool has_external_strobe;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+struct aat1290_led {
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* FLEN pin */
+ struct gpio_desc *gpio_fl_en;
+ /* EN|SET pin */
+ struct gpio_desc *gpio_en_set;
+ /* movie mode current scale */
+ int *mm_current_scale;
+ /* device mode */
+ bool movie_mode;
+
+ /* brightness cache */
+ unsigned int torch_brightness;
+ /* assures led-triggers compatibility */
+ struct work_struct work_brightness_set;
+};
+
+static struct aat1290_led *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct aat1290_led, fled_cdev);
+}
+
+static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
+{
+ int i;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ udelay(AAT1290_FLEN_OFF_DELAY_TIME_US);
+
+ /* write address */
+ for (i = 0; i < addr; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+
+ /* write data */
+ for (i = 0; i < value; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+}
+
+static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
+ unsigned int micro_sec)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *flash_tm = &fled_cdev->timeout;
+ int flash_tm_reg = AAT1290_FLASH_TM_NUM_LEVELS -
+ (micro_sec / flash_tm->step) + 1;
+
+ aat1290_as2cwire_write(led, AAT1290_FLASH_SAFETY_TIMER_ADDR,
+ flash_tm_reg);
+}
+
+static void aat1290_brightness_set(struct aat1290_led *led,
+ enum led_brightness brightness)
+{
+ mutex_lock(&led->lock);
+
+ if (brightness == 0) {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ led->movie_mode = false;
+ } else {
+ if (!led->movie_mode) {
+ aat1290_as2cwire_write(led,
+ AAT1290_MM_CURRENT_RATIO_ADDR,
+ AAT1290_MM_TO_FL_1_92);
+ led->movie_mode = true;
+ }
+
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CURRENT_ADDR,
+ AAT1290_MAX_MM_CURR_PERCENT_0 - brightness);
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CONFIG_ADDR,
+ AAT1290_MOVIE_MODE_ON);
+ }
+
+ mutex_unlock(&led->lock);
+}
+
+/* LED subsystem callbacks */
+
+static void aat1290_brightness_set_work(struct work_struct *work)
+{
+ struct aat1290_led *led =
+ container_of(work, struct aat1290_led, work_brightness_set);
+
+ aat1290_brightness_set(led, led->torch_brightness);
+}
+
+static void aat1290_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ led->torch_brightness = brightness;
+ schedule_work(&led->work_brightness_set);
+}
+
+static int aat1290_led_brightness_set_sync(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ aat1290_brightness_set(led, brightness);
+
+ return 0;
+}
+
+static int aat1290_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+
+{
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ aat1290_set_flash_safety_timer(led, timeout->val);
+ gpiod_direction_output(led->gpio_fl_en, 1);
+ } else {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ }
+
+ /*
+ * To reenter movie mode after a flash event the part must be cycled
+ * off and back on to reset the movie mode and reprogrammed via the
+ * AS2Cwire. Therefore the brightness and movie_mode properties needs
+ * to be updated here to reflect the actual state.
+ */
+ led_cdev->brightness = 0;
+ led->movie_mode = false;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ /*
+ * Don't do anything - flash timeout is cached in the led-class-flash
+ * core and will be applied in the strobe_set op, as writing the
+ * safety timer register spuriously turns the torch mode on.
+ */
+
+ return 0;
+}
+
+static int aat1290_led_parse_dt(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+ struct device *dev = &led->pdev->dev;
+ struct device_node *child_node;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct pinctrl *pinctrl;
+#endif
+ int ret = 0;
+
+ led->gpio_fl_en = devm_gpiod_get(dev, "flen", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_fl_en)) {
+ ret = PTR_ERR(led->gpio_fl_en);
+ dev_err(dev, "Unable to claim gpio \"flen\".\n");
+ return ret;
+ }
+
+ led->gpio_en_set = devm_gpiod_get(dev, "enset", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_en_set)) {
+ ret = PTR_ERR(led->gpio_en_set);
+ dev_err(dev, "Unable to claim gpio \"enset\".\n");
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ pinctrl = devm_pinctrl_get_select_default(&led->pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ cfg->has_external_strobe = false;
+ dev_info(dev,
+ "No support for external strobe detected.\n");
+ } else {
+ cfg->has_external_strobe = true;
+ }
+#endif
+
+ child_node = of_get_next_available_child(dev->of_node, NULL);
+ if (!child_node) {
+ dev_err(dev, "No DT child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ led_cdev->name = of_get_property(child_node, "label", NULL) ? :
+ child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->max_mm_current);
+ /*
+ * led-max-microamp will default to 1/20 of flash-max-microamp
+ * in case it is missing.
+ */
+ if (ret < 0)
+ dev_warn(dev,
+ "led-max-microamp DT property missing\n");
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->max_flash_current);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-microamp DT property missing\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->max_flash_tm);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-timeout-us DT property missing\n");
+ return ret;
+ }
+
+ of_node_put(child_node);
+
+ *sub_node = child_node;
+
+ return ret;
+}
+
+static void aat1290_led_validate_mm_current(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ int i, b = 0, e = AAT1290_MM_CURRENT_SCALE_SIZE;
+
+ while (e - b > 1) {
+ i = b + (e - b) / 2;
+ if (cfg->max_mm_current < led->mm_current_scale[i])
+ e = i;
+ else
+ b = i;
+ }
+
+ cfg->max_mm_current = led->mm_current_scale[b];
+ cfg->max_brightness = b + 1;
+}
+
+static int init_mm_current_scale(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ int max_mm_current_percent[] = { 20, 22, 25, 28, 32, 36, 40, 45, 50, 56,
+ 63, 71, 79, 89, 100 };
+ int i, max_mm_current =
+ AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
+
+ led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
+ sizeof(max_mm_current_percent),
+ GFP_KERNEL);
+ if (!led->mm_current_scale)
+ return -ENOMEM;
+
+ for (i = 0; i < AAT1290_MM_CURRENT_SCALE_SIZE; ++i)
+ led->mm_current_scale[i] = max_mm_current *
+ max_mm_current_percent[i] / 100;
+
+ return 0;
+}
+
+static int aat1290_led_get_configuration(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ int ret;
+
+ ret = aat1290_led_parse_dt(led, cfg, sub_node);
+ if (ret < 0)
+ return ret;
+ /*
+ * Init non-linear movie mode current scale basing
+ * on the max flash current from led configuration.
+ */
+ ret = init_mm_current_scale(led, cfg);
+ if (ret < 0)
+ return ret;
+
+ aat1290_led_validate_mm_current(led, cfg);
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#else
+ devm_kfree(&led->pdev->dev, led->mm_current_scale);
+#endif
+
+ return 0;
+}
+
+static void aat1290_init_flash_timeout(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *setting;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = cfg->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
+ setting->max = cfg->max_flash_tm;
+ setting->step = setting->min;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static enum led_brightness aat1290_intensity_to_brightness(
+ struct v4l2_flash *v4l2_flash,
+ s32 intensity)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ int i;
+
+ for (i = AAT1290_MM_CURRENT_SCALE_SIZE - 1; i >= 0; --i)
+ if (intensity >= led->mm_current_scale[i])
+ return i + 1;
+
+ return 1;
+}
+
+static s32 aat1290_brightness_to_intensity(struct v4l2_flash *v4l2_flash,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ return led->mm_current_scale[brightness - 1];
+}
+
+static int aat1290_led_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct aat1290_led *led = fled_cdev_to_led(v4l2_flash->fled_cdev);
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct pinctrl *pinctrl;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ led->movie_mode = false;
+ led_cdev->brightness = 0;
+
+ pinctrl = devm_pinctrl_get_select(&led->pdev->dev,
+ enable ? "isp" : "host");
+ if (IS_ERR(pinctrl)) {
+ dev_warn(&led->pdev->dev, "Unable to switch strobe source.\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ return 0;
+}
+
+static void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+ struct led_flash_setting *s;
+
+ strlcpy(v4l2_sd_cfg->dev_name, led_cdev->name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ s = &v4l2_sd_cfg->torch_intensity;
+ s->min = led->mm_current_scale[0];
+ s->max = led_cfg->max_mm_current;
+ s->step = 1;
+ s->val = s->max;
+
+ v4l2_sd_cfg->has_external_strobe = led_cfg->has_external_strobe;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = aat1290_led_external_strobe_set,
+ .intensity_to_led_brightness = aat1290_intensity_to_brightness,
+ .led_brightness_to_intensity = aat1290_brightness_to_intensity,
+};
+#else
+static inline void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = aat1290_led_flash_strobe_set,
+ .timeout_set = aat1290_led_flash_timeout_set,
+};
+
+static int aat1290_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *sub_node = NULL;
+ struct aat1290_led *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct aat1290_led_config_data led_cfg = {};
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ platform_set_drvdata(pdev, led);
+
+ fled_cdev = &led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ ret = aat1290_led_get_configuration(led, &led_cfg, &sub_node);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&led->lock);
+
+ /* Initialize LED Flash class device */
+ led_cdev->brightness_set = aat1290_led_brightness_set;
+ led_cdev->brightness_set_sync = aat1290_led_brightness_set_sync;
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+ INIT_WORK(&led->work_brightness_set, aat1290_brightness_set_work);
+
+ aat1290_init_flash_timeout(led, &led_cfg);
+
+ /* Register LED Flash class device */
+ ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+ if (ret < 0)
+ goto err_flash_register;
+
+ aat1290_init_v4l2_flash_config(led, &led_cfg, &v4l2_sd_cfg);
+
+ /* Create V4L2 Flash subdev. */
+ led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL,
+ &v4l2_flash_ops, &v4l2_sd_cfg);
+ if (IS_ERR(led->v4l2_flash)) {
+ ret = PTR_ERR(led->v4l2_flash);
+ goto error_v4l2_flash_init;
+ }
+
+ return 0;
+
+error_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+err_flash_register:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int aat1290_led_remove(struct platform_device *pdev)
+{
+ struct aat1290_led *led = platform_get_drvdata(pdev);
+
+ v4l2_flash_release(led->v4l2_flash);
+ led_classdev_flash_unregister(&led->fled_cdev);
+ cancel_work_sync(&led->work_brightness_set);
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id aat1290_led_dt_match[] = {
+ { .compatible = "skyworks,aat1290" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aat1290_led_dt_match);
+
+static struct platform_driver aat1290_led_driver = {
+ .probe = aat1290_led_probe,
+ .remove = aat1290_led_remove,
+ .driver = {
+ .name = "aat1290",
+ .of_match_table = aat1290_led_dt_match,
+ },
+};
+
+module_platform_driver(aat1290_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/leds/leds-bcm6328.c b/kernel/drivers/leds/leds-bcm6328.c
new file mode 100644
index 000000000..c7ea5c626
--- /dev/null
+++ b/kernel/drivers/leds/leds-bcm6328.c
@@ -0,0 +1,435 @@
+/*
+ * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ * Copyright 2015 Jonas Gorski <jogo@openwrt.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.
+ */
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6328_REG_INIT 0x00
+#define BCM6328_REG_MODE_HI 0x04
+#define BCM6328_REG_MODE_LO 0x08
+#define BCM6328_REG_HWDIS 0x0c
+#define BCM6328_REG_STROBE 0x10
+#define BCM6328_REG_LNKACTSEL_HI 0x14
+#define BCM6328_REG_LNKACTSEL_LO 0x18
+#define BCM6328_REG_RBACK 0x1c
+#define BCM6328_REG_SERMUX 0x20
+
+#define BCM6328_LED_MAX_COUNT 24
+#define BCM6328_LED_DEF_DELAY 500
+#define BCM6328_LED_INTERVAL_MS 20
+
+#define BCM6328_LED_INTV_MASK 0x3f
+#define BCM6328_LED_FAST_INTV_SHIFT 6
+#define BCM6328_LED_FAST_INTV_MASK (BCM6328_LED_INTV_MASK << \
+ BCM6328_LED_FAST_INTV_SHIFT)
+#define BCM6328_SERIAL_LED_EN BIT(12)
+#define BCM6328_SERIAL_LED_MUX BIT(13)
+#define BCM6328_SERIAL_LED_CLK_NPOL BIT(14)
+#define BCM6328_SERIAL_LED_DATA_PPOL BIT(15)
+#define BCM6328_SERIAL_LED_SHIFT_DIR BIT(16)
+#define BCM6328_LED_SHIFT_TEST BIT(30)
+#define BCM6328_LED_TEST BIT(31)
+#define BCM6328_INIT_MASK (BCM6328_SERIAL_LED_EN | \
+ BCM6328_SERIAL_LED_MUX | \
+ BCM6328_SERIAL_LED_CLK_NPOL | \
+ BCM6328_SERIAL_LED_DATA_PPOL | \
+ BCM6328_SERIAL_LED_SHIFT_DIR)
+
+#define BCM6328_LED_MODE_MASK 3
+#define BCM6328_LED_MODE_OFF 0
+#define BCM6328_LED_MODE_FAST 1
+#define BCM6328_LED_MODE_BLINK 2
+#define BCM6328_LED_MODE_ON 3
+#define BCM6328_LED_SHIFT(X) ((X) << 1)
+
+/**
+ * struct bcm6328_led - state container for bcm6328 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @blink_leds: blinking LEDs
+ * @blink_delay: blinking delay
+ * @active_low: LED is active low
+ */
+struct bcm6328_led {
+ struct led_classdev cdev;
+ void __iomem *mem;
+ spinlock_t *lock;
+ unsigned long pin;
+ unsigned long *blink_leds;
+ unsigned long *blink_delay;
+ bool active_low;
+};
+
+static void bcm6328_led_write(void __iomem *reg, unsigned long data)
+{
+ iowrite32be(data, reg);
+}
+
+static unsigned long bcm6328_led_read(void __iomem *reg)
+{
+ return ioread32be(reg);
+}
+
+/**
+ * LEDMode 64 bits / 24 LEDs
+ * bits [31:0] -> LEDs 8-23
+ * bits [47:32] -> LEDs 0-7
+ * bits [63:48] -> unused
+ */
+static unsigned long bcm6328_pin2shift(unsigned long pin)
+{
+ if (pin < 8)
+ return pin + 16; /* LEDs 0-7 (bits 47:32) */
+ else
+ return pin - 8; /* LEDs 8-23 (bits 31:0) */
+}
+
+static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
+{
+ void __iomem *mode;
+ unsigned long val, shift;
+
+ shift = bcm6328_pin2shift(led->pin);
+ if (shift / 16)
+ mode = led->mem + BCM6328_REG_MODE_HI;
+ else
+ mode = led->mem + BCM6328_REG_MODE_LO;
+
+ val = bcm6328_led_read(mode);
+ val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16));
+ val |= (value << BCM6328_LED_SHIFT(shift % 16));
+ bcm6328_led_write(mode, val);
+}
+
+static void bcm6328_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm6328_led *led =
+ container_of(led_cdev, struct bcm6328_led, cdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(led->lock, flags);
+ *(led->blink_leds) &= ~BIT(led->pin);
+ if ((led->active_low && value == LED_OFF) ||
+ (!led->active_low && value != LED_OFF))
+ bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+ else
+ bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
+ spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6328_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct bcm6328_led *led =
+ container_of(led_cdev, struct bcm6328_led, cdev);
+ unsigned long delay, flags;
+
+ if (!*delay_on)
+ *delay_on = BCM6328_LED_DEF_DELAY;
+ if (!*delay_off)
+ *delay_off = BCM6328_LED_DEF_DELAY;
+
+ if (*delay_on != *delay_off) {
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay_on != delay_off)\n");
+ return -EINVAL;
+ }
+
+ delay = *delay_on / BCM6328_LED_INTERVAL_MS;
+ if (delay == 0)
+ delay = 1;
+ else if (delay > BCM6328_LED_INTV_MASK) {
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay > %ums)\n",
+ BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(led->lock, flags);
+ if (*(led->blink_leds) == 0 ||
+ *(led->blink_leds) == BIT(led->pin) ||
+ *(led->blink_delay) == delay) {
+ unsigned long val;
+
+ *(led->blink_leds) |= BIT(led->pin);
+ *(led->blink_delay) = delay;
+
+ val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
+ val &= ~BCM6328_LED_FAST_INTV_MASK;
+ val |= (delay << BCM6328_LED_FAST_INTV_SHIFT);
+ bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
+
+ bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK);
+
+ spin_unlock_irqrestore(led->lock, flags);
+ } else {
+ spin_unlock_irqrestore(led->lock, flags);
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay already set)\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock)
+{
+ int i, cnt;
+ unsigned long flags, val;
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(mem + BCM6328_REG_HWDIS);
+ val &= ~BIT(reg);
+ bcm6328_led_write(mem + BCM6328_REG_HWDIS, val);
+ spin_unlock_irqrestore(lock, flags);
+
+ /* Only LEDs 0-7 can be activity/link controlled */
+ if (reg >= 8)
+ return 0;
+
+ cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
+ sizeof(u32));
+ for (i = 0; i < cnt; i++) {
+ u32 sel;
+ void __iomem *addr;
+
+ if (reg < 4)
+ addr = mem + BCM6328_REG_LNKACTSEL_LO;
+ else
+ addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+ of_property_read_u32_index(nc, "brcm,link-signal-sources", i,
+ &sel);
+
+ if (reg / 4 != sel / 4) {
+ dev_warn(dev, "invalid link signal source\n");
+ continue;
+ }
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(addr);
+ val |= (BIT(reg) << (((sel % 4) * 4) + 16));
+ bcm6328_led_write(addr, val);
+ spin_unlock_irqrestore(lock, flags);
+ }
+
+ cnt = of_property_count_elems_of_size(nc,
+ "brcm,activity-signal-sources",
+ sizeof(u32));
+ for (i = 0; i < cnt; i++) {
+ u32 sel;
+ void __iomem *addr;
+
+ if (reg < 4)
+ addr = mem + BCM6328_REG_LNKACTSEL_LO;
+ else
+ addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+ of_property_read_u32_index(nc, "brcm,activity-signal-sources",
+ i, &sel);
+
+ if (reg / 4 != sel / 4) {
+ dev_warn(dev, "invalid activity signal source\n");
+ continue;
+ }
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(addr);
+ val |= (BIT(reg) << ((sel % 4) * 4));
+ bcm6328_led_write(addr, val);
+ spin_unlock_irqrestore(lock, flags);
+ }
+
+ return 0;
+}
+
+static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock,
+ unsigned long *blink_leds, unsigned long *blink_delay)
+{
+ struct bcm6328_led *led;
+ unsigned long flags;
+ const char *state;
+ int rc;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pin = reg;
+ led->mem = mem;
+ led->lock = lock;
+ led->blink_leds = blink_leds;
+ led->blink_delay = blink_delay;
+
+ if (of_property_read_bool(nc, "active-low"))
+ led->active_low = true;
+
+ led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+ led->cdev.default_trigger = of_get_property(nc,
+ "linux,default-trigger",
+ NULL);
+
+ spin_lock_irqsave(lock, flags);
+ if (!of_property_read_string(nc, "default-state", &state)) {
+ if (!strcmp(state, "on")) {
+ led->cdev.brightness = LED_FULL;
+ } else if (!strcmp(state, "keep")) {
+ void __iomem *mode;
+ unsigned long val, shift;
+
+ shift = bcm6328_pin2shift(led->pin);
+ if (shift / 16)
+ mode = mem + BCM6328_REG_MODE_HI;
+ else
+ mode = mem + BCM6328_REG_MODE_LO;
+
+ val = bcm6328_led_read(mode) >>
+ BCM6328_LED_SHIFT(shift % 16);
+ val &= BCM6328_LED_MODE_MASK;
+ if ((led->active_low && val == BCM6328_LED_MODE_ON) ||
+ (!led->active_low && val == BCM6328_LED_MODE_OFF))
+ led->cdev.brightness = LED_FULL;
+ else
+ led->cdev.brightness = LED_OFF;
+ } else {
+ led->cdev.brightness = LED_OFF;
+ }
+ } else {
+ led->cdev.brightness = LED_OFF;
+ }
+
+ if ((led->active_low && led->cdev.brightness == LED_FULL) ||
+ (!led->active_low && led->cdev.brightness == LED_OFF))
+ bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
+ else
+ bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+ spin_unlock_irqrestore(lock, flags);
+
+ led->cdev.brightness_set = bcm6328_led_set;
+ led->cdev.blink_set = bcm6328_blink_set;
+
+ rc = led_classdev_register(dev, &led->cdev);
+ if (rc < 0)
+ return rc;
+
+ dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+ return 0;
+}
+
+static int bcm6328_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *child;
+ struct resource *mem_r;
+ void __iomem *mem;
+ spinlock_t *lock;
+ unsigned long val, *blink_leds, *blink_delay;
+
+ mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_r)
+ return -EINVAL;
+
+ mem = devm_ioremap_resource(dev, mem_r);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL);
+ if (!blink_leds)
+ return -ENOMEM;
+
+ blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL);
+ if (!blink_delay)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0);
+ bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0);
+ bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0);
+
+ val = bcm6328_led_read(mem + BCM6328_REG_INIT);
+ val &= ~(BCM6328_INIT_MASK);
+ if (of_property_read_bool(np, "brcm,serial-leds"))
+ val |= BCM6328_SERIAL_LED_EN;
+ if (of_property_read_bool(np, "brcm,serial-mux"))
+ val |= BCM6328_SERIAL_LED_MUX;
+ if (of_property_read_bool(np, "brcm,serial-clk-low"))
+ val |= BCM6328_SERIAL_LED_CLK_NPOL;
+ if (!of_property_read_bool(np, "brcm,serial-dat-low"))
+ val |= BCM6328_SERIAL_LED_DATA_PPOL;
+ if (!of_property_read_bool(np, "brcm,serial-shift-inv"))
+ val |= BCM6328_SERIAL_LED_SHIFT_DIR;
+ bcm6328_led_write(mem + BCM6328_REG_INIT, val);
+
+ for_each_available_child_of_node(np, child) {
+ int rc;
+ u32 reg;
+
+ if (of_property_read_u32(child, "reg", &reg))
+ continue;
+
+ if (reg >= BCM6328_LED_MAX_COUNT) {
+ dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+ BCM6328_LED_MAX_COUNT);
+ continue;
+ }
+
+ if (of_property_read_bool(child, "brcm,hardware-controlled"))
+ rc = bcm6328_hwled(dev, child, reg, mem, lock);
+ else
+ rc = bcm6328_led(dev, child, reg, mem, lock,
+ blink_leds, blink_delay);
+
+ if (rc < 0) {
+ of_node_put(child);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm6328_leds_of_match[] = {
+ { .compatible = "brcm,bcm6328-leds", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match);
+
+static struct platform_driver bcm6328_leds_driver = {
+ .probe = bcm6328_leds_probe,
+ .driver = {
+ .name = "leds-bcm6328",
+ .of_match_table = bcm6328_leds_of_match,
+ },
+};
+
+module_platform_driver(bcm6328_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6328");
diff --git a/kernel/drivers/leds/leds-bcm6358.c b/kernel/drivers/leds/leds-bcm6358.c
new file mode 100644
index 000000000..82b4ee1bc
--- /dev/null
+++ b/kernel/drivers/leds/leds-bcm6358.c
@@ -0,0 +1,246 @@
+/*
+ * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ *
+ * 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.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6358_REG_MODE 0x0
+#define BCM6358_REG_CTRL 0x4
+
+#define BCM6358_SLED_CLKDIV_MASK 3
+#define BCM6358_SLED_CLKDIV_1 0
+#define BCM6358_SLED_CLKDIV_2 1
+#define BCM6358_SLED_CLKDIV_4 2
+#define BCM6358_SLED_CLKDIV_8 3
+
+#define BCM6358_SLED_POLARITY BIT(2)
+#define BCM6358_SLED_BUSY BIT(3)
+
+#define BCM6358_SLED_MAX_COUNT 32
+#define BCM6358_SLED_WAIT 100
+
+/**
+ * struct bcm6358_led - state container for bcm6358 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @active_low: LED is active low
+ */
+struct bcm6358_led {
+ struct led_classdev cdev;
+ void __iomem *mem;
+ spinlock_t *lock;
+ unsigned long pin;
+ bool active_low;
+};
+
+static void bcm6358_led_write(void __iomem *reg, unsigned long data)
+{
+ iowrite32be(data, reg);
+}
+
+static unsigned long bcm6358_led_read(void __iomem *reg)
+{
+ return ioread32be(reg);
+}
+
+static unsigned long bcm6358_led_busy(void __iomem *mem)
+{
+ unsigned long val;
+
+ while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
+ BCM6358_SLED_BUSY)
+ udelay(BCM6358_SLED_WAIT);
+
+ return val;
+}
+
+static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value)
+{
+ unsigned long val;
+
+ bcm6358_led_busy(led->mem);
+
+ val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+ if ((led->active_low && value == LED_OFF) ||
+ (!led->active_low && value != LED_OFF))
+ val |= BIT(led->pin);
+ else
+ val &= ~(BIT(led->pin));
+ bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
+}
+
+static void bcm6358_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm6358_led *led =
+ container_of(led_cdev, struct bcm6358_led, cdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(led->lock, flags);
+ bcm6358_led_mode(led, value);
+ spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock)
+{
+ struct bcm6358_led *led;
+ unsigned long flags;
+ const char *state;
+ int rc;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pin = reg;
+ led->mem = mem;
+ led->lock = lock;
+
+ if (of_property_read_bool(nc, "active-low"))
+ led->active_low = true;
+
+ led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
+ led->cdev.default_trigger = of_get_property(nc,
+ "linux,default-trigger",
+ NULL);
+
+ spin_lock_irqsave(lock, flags);
+ if (!of_property_read_string(nc, "default-state", &state)) {
+ if (!strcmp(state, "on")) {
+ led->cdev.brightness = LED_FULL;
+ } else if (!strcmp(state, "keep")) {
+ unsigned long val;
+
+ bcm6358_led_busy(led->mem);
+
+ val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+ val &= BIT(led->pin);
+ if ((led->active_low && !val) ||
+ (!led->active_low && val))
+ led->cdev.brightness = LED_FULL;
+ else
+ led->cdev.brightness = LED_OFF;
+ } else {
+ led->cdev.brightness = LED_OFF;
+ }
+ } else {
+ led->cdev.brightness = LED_OFF;
+ }
+ bcm6358_led_mode(led, led->cdev.brightness);
+ spin_unlock_irqrestore(lock, flags);
+
+ led->cdev.brightness_set = bcm6358_led_set;
+
+ rc = led_classdev_register(dev, &led->cdev);
+ if (rc < 0)
+ return rc;
+
+ dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+ return 0;
+}
+
+static int bcm6358_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *child;
+ struct resource *mem_r;
+ void __iomem *mem;
+ spinlock_t *lock; /* memory lock */
+ unsigned long val;
+ u32 clk_div;
+
+ mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_r)
+ return -EINVAL;
+
+ mem = devm_ioremap_resource(dev, mem_r);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ val = bcm6358_led_busy(mem);
+ val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
+ if (of_property_read_bool(np, "brcm,clk-dat-low"))
+ val |= BCM6358_SLED_POLARITY;
+ of_property_read_u32(np, "brcm,clk-div", &clk_div);
+ switch (clk_div) {
+ case 8:
+ val |= BCM6358_SLED_CLKDIV_8;
+ break;
+ case 4:
+ val |= BCM6358_SLED_CLKDIV_4;
+ break;
+ case 2:
+ val |= BCM6358_SLED_CLKDIV_2;
+ break;
+ default:
+ val |= BCM6358_SLED_CLKDIV_1;
+ break;
+ }
+ bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
+
+ for_each_available_child_of_node(np, child) {
+ int rc;
+ u32 reg;
+
+ if (of_property_read_u32(child, "reg", &reg))
+ continue;
+
+ if (reg >= BCM6358_SLED_MAX_COUNT) {
+ dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+ BCM6358_SLED_MAX_COUNT);
+ continue;
+ }
+
+ rc = bcm6358_led(dev, child, reg, mem, lock);
+ if (rc < 0) {
+ of_node_put(child);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm6358_leds_of_match[] = {
+ { .compatible = "brcm,bcm6358-leds", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);
+
+static struct platform_driver bcm6358_leds_driver = {
+ .probe = bcm6358_leds_probe,
+ .driver = {
+ .name = "leds-bcm6358",
+ .of_match_table = bcm6358_leds_of_match,
+ },
+};
+
+module_platform_driver(bcm6358_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6358");
diff --git a/kernel/drivers/leds/leds-cobalt-qube.c b/kernel/drivers/leds/leds-cobalt-qube.c
index d97522080..9be195707 100644
--- a/kernel/drivers/leds/leds-cobalt-qube.c
+++ b/kernel/drivers/leds/leds-cobalt-qube.c
@@ -36,7 +36,6 @@ static struct led_classdev qube_front_led = {
static int cobalt_qube_led_probe(struct platform_device *pdev)
{
struct resource *res;
- int retval;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
@@ -49,31 +48,11 @@ static int cobalt_qube_led_probe(struct platform_device *pdev)
led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT;
writeb(led_value, led_port);
- retval = led_classdev_register(&pdev->dev, &qube_front_led);
- if (retval)
- goto err_null;
-
- return 0;
-
-err_null:
- led_port = NULL;
-
- return retval;
-}
-
-static int cobalt_qube_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&qube_front_led);
-
- if (led_port)
- led_port = NULL;
-
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &qube_front_led);
}
static struct platform_driver cobalt_qube_led_driver = {
.probe = cobalt_qube_led_probe,
- .remove = cobalt_qube_led_remove,
.driver = {
.name = "cobalt-qube-leds",
},
diff --git a/kernel/drivers/leds/leds-cobalt-raq.c b/kernel/drivers/leds/leds-cobalt-raq.c
index 06dbe18a2..b316df4a8 100644
--- a/kernel/drivers/leds/leds-cobalt-raq.c
+++ b/kernel/drivers/leds/leds-cobalt-raq.c
@@ -108,20 +108,8 @@ err_null:
return retval;
}
-static int cobalt_raq_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&raq_power_off_led);
- led_classdev_unregister(&raq_web_led);
-
- if (led_port)
- led_port = NULL;
-
- return 0;
-}
-
static struct platform_driver cobalt_raq_led_driver = {
.probe = cobalt_raq_led_probe,
- .remove = cobalt_raq_led_remove,
.driver = {
.name = "cobalt-raq-leds",
},
@@ -131,5 +119,4 @@ static int __init cobalt_raq_led_init(void)
{
return platform_driver_register(&cobalt_raq_led_driver);
}
-
-module_init(cobalt_raq_led_init);
+device_initcall(cobalt_raq_led_init);
diff --git a/kernel/drivers/leds/leds-dac124s085.c b/kernel/drivers/leds/leds-dac124s085.c
index db3ba8b42..314159610 100644
--- a/kernel/drivers/leds/leds-dac124s085.c
+++ b/kernel/drivers/leds/leds-dac124s085.c
@@ -122,7 +122,6 @@ static struct spi_driver dac124s085_driver = {
.remove = dac124s085_remove,
.driver = {
.name = "dac124s085",
- .owner = THIS_MODULE,
},
};
diff --git a/kernel/drivers/leds/leds-fsg.c b/kernel/drivers/leds/leds-fsg.c
index 2b4dc738d..257a813c7 100644
--- a/kernel/drivers/leds/leds-fsg.c
+++ b/kernel/drivers/leds/leds-fsg.c
@@ -156,63 +156,35 @@ static int fsg_led_probe(struct platform_device *pdev)
latch_value = 0xffff;
*latch_address = latch_value;
- ret = led_classdev_register(&pdev->dev, &fsg_wlan_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_wlan_led);
if (ret < 0)
- goto failwlan;
+ return ret;
- ret = led_classdev_register(&pdev->dev, &fsg_wan_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_wan_led);
if (ret < 0)
- goto failwan;
+ return ret;
- ret = led_classdev_register(&pdev->dev, &fsg_sata_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_sata_led);
if (ret < 0)
- goto failsata;
+ return ret;
- ret = led_classdev_register(&pdev->dev, &fsg_usb_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_usb_led);
if (ret < 0)
- goto failusb;
+ return ret;
- ret = led_classdev_register(&pdev->dev, &fsg_sync_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_sync_led);
if (ret < 0)
- goto failsync;
+ return ret;
- ret = led_classdev_register(&pdev->dev, &fsg_ring_led);
+ ret = devm_led_classdev_register(&pdev->dev, &fsg_ring_led);
if (ret < 0)
- goto failring;
-
- return ret;
-
- failring:
- led_classdev_unregister(&fsg_sync_led);
- failsync:
- led_classdev_unregister(&fsg_usb_led);
- failusb:
- led_classdev_unregister(&fsg_sata_led);
- failsata:
- led_classdev_unregister(&fsg_wan_led);
- failwan:
- led_classdev_unregister(&fsg_wlan_led);
- failwlan:
+ return ret;
return ret;
}
-static int fsg_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&fsg_wlan_led);
- led_classdev_unregister(&fsg_wan_led);
- led_classdev_unregister(&fsg_sata_led);
- led_classdev_unregister(&fsg_usb_led);
- led_classdev_unregister(&fsg_sync_led);
- led_classdev_unregister(&fsg_ring_led);
-
- return 0;
-}
-
-
static struct platform_driver fsg_led_driver = {
.probe = fsg_led_probe,
- .remove = fsg_led_remove,
.driver = {
.name = "fsg-led",
},
diff --git a/kernel/drivers/leds/leds-gpio.c b/kernel/drivers/leds/leds-gpio.c
index 15eb3f86f..5db4515a4 100644
--- a/kernel/drivers/leds/leds-gpio.c
+++ b/kernel/drivers/leds/leds-gpio.c
@@ -16,6 +16,7 @@
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
@@ -191,15 +192,17 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
goto err;
}
- np = of_node(child);
+ np = to_of_node(child);
if (fwnode_property_present(child, "label")) {
fwnode_property_read_string(child, "label", &led.name);
} else {
if (IS_ENABLED(CONFIG_OF) && !led.name && np)
led.name = np->name;
- if (!led.name)
- return ERR_PTR(-EINVAL);
+ if (!led.name) {
+ ret = -EINVAL;
+ goto err;
+ }
}
fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger);
@@ -217,18 +220,19 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
if (fwnode_property_present(child, "retain-state-suspended"))
led.retain_state_suspended = 1;
- ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
+ ret = create_gpio_led(&led, &priv->leds[priv->num_leds],
dev, NULL);
if (ret < 0) {
fwnode_handle_put(child);
goto err;
}
+ priv->num_leds++;
}
return priv;
err:
- for (count = priv->num_leds - 2; count >= 0; count--)
+ for (count = priv->num_leds - 1; count >= 0; count--)
delete_gpio_led(&priv->leds[count]);
return ERR_PTR(ret);
}
@@ -287,9 +291,22 @@ static int gpio_led_remove(struct platform_device *pdev)
return 0;
}
+static void gpio_led_shutdown(struct platform_device *pdev)
+{
+ struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < priv->num_leds; i++) {
+ struct gpio_led_data *led = &priv->leds[i];
+
+ gpio_led_set(&led->cdev, LED_OFF);
+ }
+}
+
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
.remove = gpio_led_remove,
+ .shutdown = gpio_led_shutdown,
.driver = {
.name = "leds-gpio",
.of_match_table = of_gpio_leds_match,
diff --git a/kernel/drivers/leds/leds-hp6xx.c b/kernel/drivers/leds/leds-hp6xx.c
index 0b84c0113..a6b8db0e2 100644
--- a/kernel/drivers/leds/leds-hp6xx.c
+++ b/kernel/drivers/leds/leds-hp6xx.c
@@ -59,28 +59,15 @@ static int hp6xxled_probe(struct platform_device *pdev)
{
int ret;
- ret = led_classdev_register(&pdev->dev, &hp6xx_red_led);
+ ret = devm_led_classdev_register(&pdev->dev, &hp6xx_red_led);
if (ret < 0)
return ret;
- ret = led_classdev_register(&pdev->dev, &hp6xx_green_led);
- if (ret < 0)
- led_classdev_unregister(&hp6xx_red_led);
-
- return ret;
-}
-
-static int hp6xxled_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&hp6xx_red_led);
- led_classdev_unregister(&hp6xx_green_led);
-
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &hp6xx_green_led);
}
static struct platform_driver hp6xxled_driver = {
.probe = hp6xxled_probe,
- .remove = hp6xxled_remove,
.driver = {
.name = "hp6xx-led",
},
diff --git a/kernel/drivers/leds/leds-ipaq-micro.c b/kernel/drivers/leds/leds-ipaq-micro.c
index 3776f516c..fa262b6b2 100644
--- a/kernel/drivers/leds/leds-ipaq-micro.c
+++ b/kernel/drivers/leds/leds-ipaq-micro.c
@@ -16,9 +16,9 @@
#define LED_YELLOW 0x00
#define LED_GREEN 0x01
-#define LED_EN (1 << 4) /* LED ON/OFF 0:off, 1:on */
-#define LED_AUTOSTOP (1 << 5) /* LED ON/OFF auto stop set 0:disable, 1:enable */
-#define LED_ALWAYS (1 << 6) /* LED Interrupt Mask 0:No mask, 1:mask */
+#define LED_EN (1 << 4) /* LED ON/OFF 0:off, 1:on */
+#define LED_AUTOSTOP (1 << 5) /* LED ON/OFF auto stop set 0:disable, 1:enable */
+#define LED_ALWAYS (1 << 6) /* LED Interrupt Mask 0:No mask, 1:mask */
static void micro_leds_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
@@ -79,14 +79,14 @@ static int micro_leds_blink_set(struct led_classdev *led_cdev,
};
msg.tx_data[0] = LED_GREEN;
- if (*delay_on > IPAQ_LED_MAX_DUTY ||
+ if (*delay_on > IPAQ_LED_MAX_DUTY ||
*delay_off > IPAQ_LED_MAX_DUTY)
- return -EINVAL;
+ return -EINVAL;
- if (*delay_on == 0 && *delay_off == 0) {
- *delay_on = 100;
- *delay_off = 100;
- }
+ if (*delay_on == 0 && *delay_off == 0) {
+ *delay_on = 100;
+ *delay_off = 100;
+ }
msg.tx_data[1] = 0;
if (*delay_on >= IPAQ_LED_MAX_DUTY)
@@ -111,7 +111,7 @@ static int micro_leds_probe(struct platform_device *pdev)
{
int ret;
- ret = led_classdev_register(&pdev->dev, &micro_led);
+ ret = devm_led_classdev_register(&pdev->dev, &micro_led);
if (ret) {
dev_err(&pdev->dev, "registering led failed: %d\n", ret);
return ret;
@@ -121,18 +121,11 @@ static int micro_leds_probe(struct platform_device *pdev)
return 0;
}
-static int micro_leds_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&micro_led);
- return 0;
-}
-
static struct platform_driver micro_leds_device_driver = {
.driver = {
.name = "ipaq-micro-leds",
},
.probe = micro_leds_probe,
- .remove = micro_leds_remove,
};
module_platform_driver(micro_leds_device_driver);
diff --git a/kernel/drivers/leds/leds-ktd2692.c b/kernel/drivers/leds/leds-ktd2692.c
new file mode 100644
index 000000000..feca07be8
--- /dev/null
+++ b/kernel/drivers/leds/leds-ktd2692.c
@@ -0,0 +1,444 @@
+/*
+ * LED driver : leds-ktd2692.c
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Ingi Kim <ingi2.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+
+/* Value related the movie mode */
+#define KTD2692_MOVIE_MODE_CURRENT_LEVELS 16
+#define KTD2692_MM_TO_FL_RATIO(x) ((x) / 3)
+#define KTD2962_MM_MIN_CURR_THRESHOLD_SCALE 8
+
+/* Value related the flash mode */
+#define KTD2692_FLASH_MODE_TIMEOUT_LEVELS 8
+#define KTD2692_FLASH_MODE_TIMEOUT_DISABLE 0
+#define KTD2692_FLASH_MODE_CURR_PERCENT(x) (((x) * 16) / 100)
+
+/* Macro for getting offset of flash timeout */
+#define GET_TIMEOUT_OFFSET(timeout, step) ((timeout) / (step))
+
+/* Base register address */
+#define KTD2692_REG_LVP_BASE 0x00
+#define KTD2692_REG_FLASH_TIMEOUT_BASE 0x20
+#define KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE 0x40
+#define KTD2692_REG_MOVIE_CURRENT_BASE 0x60
+#define KTD2692_REG_FLASH_CURRENT_BASE 0x80
+#define KTD2692_REG_MODE_BASE 0xA0
+
+/* Set bit coding time for expresswire interface */
+#define KTD2692_TIME_RESET_US 700
+#define KTD2692_TIME_DATA_START_TIME_US 10
+#define KTD2692_TIME_HIGH_END_OF_DATA_US 350
+#define KTD2692_TIME_LOW_END_OF_DATA_US 10
+#define KTD2692_TIME_SHORT_BITSET_US 4
+#define KTD2692_TIME_LONG_BITSET_US 12
+
+/* KTD2692 default length of name */
+#define KTD2692_NAME_LENGTH 20
+
+enum ktd2692_bitset {
+ KTD2692_LOW = 0,
+ KTD2692_HIGH,
+};
+
+/* Movie / Flash Mode Control */
+enum ktd2692_led_mode {
+ KTD2692_MODE_DISABLE = 0, /* default */
+ KTD2692_MODE_MOVIE,
+ KTD2692_MODE_FLASH,
+};
+
+struct ktd2692_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 movie_max_microamp;
+ /* maximum LED current in flash mode */
+ u32 flash_max_microamp;
+ /* maximum flash timeout */
+ u32 flash_max_timeout;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+struct ktd2692_context {
+ /* Related LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+
+ /* secures access to the device */
+ struct mutex lock;
+ struct regulator *regulator;
+ struct work_struct work_brightness_set;
+
+ struct gpio_desc *aux_gpio;
+ struct gpio_desc *ctrl_gpio;
+
+ enum ktd2692_led_mode mode;
+ enum led_brightness torch_brightness;
+};
+
+static struct ktd2692_context *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct ktd2692_context, fled_cdev);
+}
+
+static void ktd2692_expresswire_start(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_DATA_START_TIME_US);
+}
+
+static void ktd2692_expresswire_reset(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_RESET_US);
+}
+
+static void ktd2692_expresswire_end(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_LOW_END_OF_DATA_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_HIGH_END_OF_DATA_US);
+}
+
+static void ktd2692_expresswire_set_bit(struct ktd2692_context *led, bool bit)
+{
+ /*
+ * The Low Bit(0) and High Bit(1) is based on a time detection
+ * algorithm between time low and time high
+ * Time_(L_LB) : Low time of the Low Bit(0)
+ * Time_(H_LB) : High time of the LOW Bit(0)
+ * Time_(L_HB) : Low time of the High Bit(1)
+ * Time_(H_HB) : High time of the High Bit(1)
+ *
+ * It can be simplified to:
+ * Low Bit(0) : 2 * Time_(H_LB) < Time_(L_LB)
+ * High Bit(1) : 2 * Time_(L_HB) < Time_(H_HB)
+ * HIGH ___ ____ _.. _________ ___
+ * |_________| |_.. |____| |__|
+ * LOW <L_LB> <H_LB> <L_HB> <H_HB>
+ * [ Low Bit (0) ] [ High Bit(1) ]
+ */
+ if (bit) {
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_SHORT_BITSET_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_LONG_BITSET_US);
+ } else {
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_LONG_BITSET_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_SHORT_BITSET_US);
+ }
+}
+
+static void ktd2692_expresswire_write(struct ktd2692_context *led, u8 value)
+{
+ int i;
+
+ ktd2692_expresswire_start(led);
+ for (i = 7; i >= 0; i--)
+ ktd2692_expresswire_set_bit(led, value & BIT(i));
+ ktd2692_expresswire_end(led);
+}
+
+static void ktd2692_brightness_set(struct ktd2692_context *led,
+ enum led_brightness brightness)
+{
+ mutex_lock(&led->lock);
+
+ if (brightness == LED_OFF) {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+ } else {
+ ktd2692_expresswire_write(led, brightness |
+ KTD2692_REG_MOVIE_CURRENT_BASE);
+ led->mode = KTD2692_MODE_MOVIE;
+ }
+
+ ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+ mutex_unlock(&led->lock);
+}
+
+static void ktd2692_brightness_set_work(struct work_struct *work)
+{
+ struct ktd2692_context *led =
+ container_of(work, struct ktd2692_context, work_brightness_set);
+
+ ktd2692_brightness_set(led, led->torch_brightness);
+}
+
+static void ktd2692_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+ led->torch_brightness = brightness;
+ schedule_work(&led->work_brightness_set);
+}
+
+static int ktd2692_led_brightness_set_sync(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+ ktd2692_brightness_set(led, brightness);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+ u32 flash_tm_reg;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step);
+ ktd2692_expresswire_write(led, flash_tm_reg
+ | KTD2692_REG_FLASH_TIMEOUT_BASE);
+
+ led->mode = KTD2692_MODE_FLASH;
+ gpiod_direction_output(led->aux_gpio, KTD2692_HIGH);
+ } else {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+ }
+
+ ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+
+ fled_cdev->led_cdev.brightness = LED_OFF;
+ led->mode = KTD2692_MODE_DISABLE;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ return 0;
+}
+
+static void ktd2692_init_movie_current_max(struct ktd2692_led_config_data *cfg)
+{
+ u32 offset, step;
+ u32 movie_current_microamp;
+
+ offset = KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+ step = KTD2692_MM_TO_FL_RATIO(cfg->flash_max_microamp)
+ / KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+
+ do {
+ movie_current_microamp = step * offset;
+ offset--;
+ } while ((movie_current_microamp > cfg->movie_max_microamp) &&
+ (offset > 0));
+
+ cfg->max_brightness = offset;
+}
+
+static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev,
+ struct ktd2692_led_config_data *cfg)
+{
+ struct led_flash_setting *setting;
+
+ setting = &fled_cdev->timeout;
+ setting->min = KTD2692_FLASH_MODE_TIMEOUT_DISABLE;
+ setting->max = cfg->flash_max_timeout;
+ setting->step = cfg->flash_max_timeout
+ / (KTD2692_FLASH_MODE_TIMEOUT_LEVELS - 1);
+ setting->val = cfg->flash_max_timeout;
+}
+
+static void ktd2692_setup(struct ktd2692_context *led)
+{
+ led->mode = KTD2692_MODE_DISABLE;
+ ktd2692_expresswire_reset(led);
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+
+ ktd2692_expresswire_write(led, (KTD2962_MM_MIN_CURR_THRESHOLD_SCALE - 1)
+ | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE);
+ ktd2692_expresswire_write(led, KTD2692_FLASH_MODE_CURR_PERCENT(45)
+ | KTD2692_REG_FLASH_CURRENT_BASE);
+}
+
+static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
+ struct ktd2692_led_config_data *cfg)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *child_node;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENXIO;
+
+ led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
+ if (IS_ERR(led->ctrl_gpio)) {
+ ret = PTR_ERR(led->ctrl_gpio);
+ dev_err(dev, "cannot get ctrl-gpios %d\n", ret);
+ return ret;
+ }
+
+ led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS);
+ if (IS_ERR(led->aux_gpio)) {
+ ret = PTR_ERR(led->aux_gpio);
+ dev_err(dev, "cannot get aux-gpios %d\n", ret);
+ return ret;
+ }
+
+ led->regulator = devm_regulator_get(dev, "vin");
+ if (IS_ERR(led->regulator))
+ led->regulator = NULL;
+
+ if (led->regulator) {
+ ret = regulator_enable(led->regulator);
+ if (ret)
+ dev_err(dev, "Failed to enable supply: %d\n", ret);
+ }
+
+ child_node = of_get_next_available_child(np, NULL);
+ if (!child_node) {
+ dev_err(dev, "No DT child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ led->fled_cdev.led_cdev.name =
+ of_get_property(child_node, "label", NULL) ? : child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->movie_max_microamp);
+ if (ret) {
+ dev_err(dev, "failed to parse led-max-microamp\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->flash_max_microamp);
+ if (ret) {
+ dev_err(dev, "failed to parse flash-max-microamp\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->flash_max_timeout);
+ if (ret)
+ dev_err(dev, "failed to parse flash-max-timeout-us\n");
+
+ of_node_put(child_node);
+ return ret;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = ktd2692_led_flash_strobe_set,
+ .timeout_set = ktd2692_led_flash_timeout_set,
+};
+
+static int ktd2692_probe(struct platform_device *pdev)
+{
+ struct ktd2692_context *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct ktd2692_led_config_data led_cfg;
+ int ret;
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ fled_cdev = &led->fled_cdev;
+ led_cdev = &fled_cdev->led_cdev;
+
+ ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg);
+ if (ret)
+ return ret;
+
+ ktd2692_init_flash_timeout(fled_cdev, &led_cfg);
+ ktd2692_init_movie_current_max(&led_cfg);
+
+ fled_cdev->ops = &flash_ops;
+
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->brightness_set = ktd2692_led_brightness_set;
+ led_cdev->brightness_set_sync = ktd2692_led_brightness_set_sync;
+ led_cdev->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+ mutex_init(&led->lock);
+ INIT_WORK(&led->work_brightness_set, ktd2692_brightness_set_work);
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "can't register LED %s\n", led_cdev->name);
+ mutex_destroy(&led->lock);
+ return ret;
+ }
+
+ ktd2692_setup(led);
+
+ return 0;
+}
+
+static int ktd2692_remove(struct platform_device *pdev)
+{
+ struct ktd2692_context *led = platform_get_drvdata(pdev);
+ int ret;
+
+ led_classdev_flash_unregister(&led->fled_cdev);
+ cancel_work_sync(&led->work_brightness_set);
+
+ if (led->regulator) {
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(&pdev->dev,
+ "Failed to disable supply: %d\n", ret);
+ }
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id ktd2692_match[] = {
+ { .compatible = "kinetic,ktd2692", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ktd2692_match);
+
+static struct platform_driver ktd2692_driver = {
+ .driver = {
+ .name = "ktd2692",
+ .of_match_table = ktd2692_match,
+ },
+ .probe = ktd2692_probe,
+ .remove = ktd2692_remove,
+};
+
+module_platform_driver(ktd2692_driver);
+
+MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>");
+MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/leds/leds-lm3530.c b/kernel/drivers/leds/leds-lm3530.c
index 91325de3c..b38430cb1 100644
--- a/kernel/drivers/leds/leds-lm3530.c
+++ b/kernel/drivers/leds/leds-lm3530.c
@@ -492,7 +492,6 @@ static struct i2c_driver lm3530_i2c_driver = {
.id_table = lm3530_id,
.driver = {
.name = LM3530_NAME,
- .owner = THIS_MODULE,
},
};
diff --git a/kernel/drivers/leds/leds-lm355x.c b/kernel/drivers/leds/leds-lm355x.c
index f5112cb2d..48872997d 100644
--- a/kernel/drivers/leds/leds-lm355x.c
+++ b/kernel/drivers/leds/leds-lm355x.c
@@ -555,7 +555,6 @@ MODULE_DEVICE_TABLE(i2c, lm355x_id);
static struct i2c_driver lm355x_i2c_driver = {
.driver = {
.name = LM355x_NAME,
- .owner = THIS_MODULE,
.pm = NULL,
},
.probe = lm355x_probe,
diff --git a/kernel/drivers/leds/leds-lm3642.c b/kernel/drivers/leds/leds-lm3642.c
index d3dec0132..02ebe342f 100644
--- a/kernel/drivers/leds/leds-lm3642.c
+++ b/kernel/drivers/leds/leds-lm3642.c
@@ -446,7 +446,6 @@ MODULE_DEVICE_TABLE(i2c, lm3642_id);
static struct i2c_driver lm3642_i2c_driver = {
.driver = {
.name = LM3642_NAME,
- .owner = THIS_MODULE,
.pm = NULL,
},
.probe = lm3642_probe,
diff --git a/kernel/drivers/leds/leds-locomo.c b/kernel/drivers/leds/leds-locomo.c
index 80ba04888..24c4b53a6 100644
--- a/kernel/drivers/leds/leds-locomo.c
+++ b/kernel/drivers/leds/leds-locomo.c
@@ -59,23 +59,13 @@ static int locomoled_probe(struct locomo_dev *ldev)
{
int ret;
- ret = led_classdev_register(&ldev->dev, &locomo_led0);
+ ret = devm_led_classdev_register(&ldev->dev, &locomo_led0);
if (ret < 0)
return ret;
- ret = led_classdev_register(&ldev->dev, &locomo_led1);
- if (ret < 0)
- led_classdev_unregister(&locomo_led0);
-
- return ret;
+ return devm_led_classdev_register(&ldev->dev, &locomo_led1);
}
-static int locomoled_remove(struct locomo_dev *dev)
-{
- led_classdev_unregister(&locomo_led0);
- led_classdev_unregister(&locomo_led1);
- return 0;
-}
static struct locomo_driver locomoled_driver = {
.drv = {
@@ -83,7 +73,6 @@ static struct locomo_driver locomoled_driver = {
},
.devid = LOCOMO_DEVID_LED,
.probe = locomoled_probe,
- .remove = locomoled_remove,
};
static int __init locomoled_init(void)
diff --git a/kernel/drivers/leds/leds-lp5521.c b/kernel/drivers/leds/leds-lp5521.c
index 8ca197af2..63a92542c 100644
--- a/kernel/drivers/leds/leds-lp5521.c
+++ b/kernel/drivers/leds/leds-lp5521.c
@@ -514,20 +514,19 @@ static int lp5521_probe(struct i2c_client *client,
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
- if (!dev_get_platdata(&client->dev)) {
+ if (!pdata) {
if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
+ pdata = lp55xx_of_populate_pdata(&client->dev, np);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
- pdata = dev_get_platdata(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
diff --git a/kernel/drivers/leds/leds-lp5523.c b/kernel/drivers/leds/leds-lp5523.c
index 9e1716f80..1d0187f42 100644
--- a/kernel/drivers/leds/leds-lp5523.c
+++ b/kernel/drivers/leds/leds-lp5523.c
@@ -50,6 +50,7 @@
#define LP5523_REG_OP_MODE 0x01
#define LP5523_REG_ENABLE_LEDS_MSB 0x04
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
+#define LP5523_REG_LED_CTRL_BASE 0x06
#define LP5523_REG_LED_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36
@@ -57,6 +58,7 @@
#define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42
+#define LP5523_REG_MASTER_FADER_BASE 0x48
#define LP5523_REG_CH1_PROG_START 0x4C
#define LP5523_REG_CH2_PROG_START 0x4D
#define LP5523_REG_CH3_PROG_START 0x4E
@@ -78,6 +80,9 @@
#define LP5523_EXT_CLK_USED 0x08
#define LP5523_ENG_STATUS_MASK 0x07
+#define LP5523_FADER_MAPPING_MASK 0xC0
+#define LP5523_FADER_MAPPING_SHIFT 6
+
/* Memory Page Selection */
#define LP5523_PAGE_ENG1 0
#define LP5523_PAGE_ENG2 1
@@ -666,6 +671,137 @@ release_lock:
return pos;
}
+#define show_fader(nr) \
+static ssize_t show_master_fader##nr(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return show_master_fader(dev, attr, buf, nr); \
+}
+
+#define store_fader(nr) \
+static ssize_t store_master_fader##nr(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return store_master_fader(dev, attr, buf, len, nr); \
+}
+
+static ssize_t show_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+ u8 val;
+
+ mutex_lock(&chip->lock);
+ ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val);
+ mutex_unlock(&chip->lock);
+
+ if (ret == 0)
+ ret = sprintf(buf, "%u\n", val);
+
+ return ret;
+}
+show_fader(1)
+show_fader(2)
+show_fader(3)
+
+static ssize_t store_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ if (val > 0xff)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1,
+ (u8)val);
+ mutex_unlock(&chip->lock);
+
+ if (ret == 0)
+ ret = len;
+
+ return ret;
+}
+store_fader(1)
+store_fader(2)
+store_fader(3)
+
+static ssize_t show_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int i, ret, pos = 0;
+ u8 val;
+
+ mutex_lock(&chip->lock);
+
+ for (i = 0; i < LP5523_MAX_LEDS; i++) {
+ ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val);
+ if (ret)
+ goto leave;
+
+ val = (val & LP5523_FADER_MAPPING_MASK)
+ >> LP5523_FADER_MAPPING_SHIFT;
+ if (val > 3) {
+ ret = -EINVAL;
+ goto leave;
+ }
+ buf[pos++] = val + '0';
+ }
+ buf[pos++] = '\n';
+ ret = pos;
+leave:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static ssize_t store_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int i, n, ret;
+ u8 val;
+
+ n = min_t(int, len, LP5523_MAX_LEDS);
+
+ mutex_lock(&chip->lock);
+
+ for (i = 0; i < n; i++) {
+ if (buf[i] >= '0' && buf[i] <= '3') {
+ val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT;
+ ret = lp55xx_update_bits(chip,
+ LP5523_REG_LED_CTRL_BASE + i,
+ LP5523_FADER_MAPPING_MASK,
+ val);
+ if (ret)
+ goto leave;
+ } else {
+ ret = -EINVAL;
+ goto leave;
+ }
+ }
+ ret = len;
+leave:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
static void lp5523_led_brightness_work(struct work_struct *work)
{
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
@@ -688,6 +824,14 @@ static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
+static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
+ store_master_fader1);
+static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
+ store_master_fader2);
+static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
+ store_master_fader3);
+static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
+ store_master_fader_leds);
static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr,
@@ -700,6 +844,10 @@ static struct attribute *lp5523_attributes[] = {
&dev_attr_engine2_leds.attr,
&dev_attr_engine3_leds.attr,
&dev_attr_selftest.attr,
+ &dev_attr_master_fader1.attr,
+ &dev_attr_master_fader2.attr,
+ &dev_attr_master_fader3.attr,
+ &dev_attr_master_fader_leds.attr,
NULL,
};
@@ -732,20 +880,19 @@ static int lp5523_probe(struct i2c_client *client,
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
- if (!dev_get_platdata(&client->dev)) {
+ if (!pdata) {
if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
+ pdata = lp55xx_of_populate_pdata(&client->dev, np);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
- pdata = dev_get_platdata(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
diff --git a/kernel/drivers/leds/leds-lp5562.c b/kernel/drivers/leds/leds-lp5562.c
index ca85724ab..0360c59db 100644
--- a/kernel/drivers/leds/leds-lp5562.c
+++ b/kernel/drivers/leds/leds-lp5562.c
@@ -515,20 +515,19 @@ static int lp5562_probe(struct i2c_client *client,
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
- if (!dev_get_platdata(&client->dev)) {
+ if (!pdata) {
if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
+ pdata = lp55xx_of_populate_pdata(&client->dev, np);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
- pdata = dev_get_platdata(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
diff --git a/kernel/drivers/leds/leds-lp55xx-common.c b/kernel/drivers/leds/leds-lp55xx-common.c
index 77c26bc32..59b76833f 100644
--- a/kernel/drivers/leds/leds-lp55xx-common.c
+++ b/kernel/drivers/leds/leds-lp55xx-common.c
@@ -223,7 +223,7 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
const char *name = chip->cl->name;
struct device *dev = &chip->cl->dev;
- return request_firmware_nowait(THIS_MODULE, true, name, dev,
+ return request_firmware_nowait(THIS_MODULE, false, name, dev,
GFP_KERNEL, chip, lp55xx_firmware_loaded);
}
@@ -543,7 +543,8 @@ void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
}
EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
-int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np)
+struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
+ struct device_node *np)
{
struct device_node *child;
struct lp55xx_platform_data *pdata;
@@ -553,17 +554,17 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np)
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
num_channels = of_get_child_count(np);
if (num_channels == 0) {
dev_err(dev, "no LED channels\n");
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
}
cfg = devm_kzalloc(dev, sizeof(*cfg) * num_channels, GFP_KERNEL);
if (!cfg)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
pdata->led_config = &cfg[0];
pdata->num_channels = num_channels;
@@ -588,9 +589,7 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np)
/* LP8501 specific */
of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel);
- dev->platform_data = pdata;
-
- return 0;
+ return pdata;
}
EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata);
diff --git a/kernel/drivers/leds/leds-lp55xx-common.h b/kernel/drivers/leds/leds-lp55xx-common.h
index cceab483e..c7f1e6155 100644
--- a/kernel/drivers/leds/leds-lp55xx-common.h
+++ b/kernel/drivers/leds/leds-lp55xx-common.h
@@ -202,7 +202,7 @@ extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
/* common device tree population function */
-extern int lp55xx_of_populate_pdata(struct device *dev,
- struct device_node *np);
+extern struct lp55xx_platform_data
+*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np);
#endif /* _LEDS_LP55XX_COMMON_H */
diff --git a/kernel/drivers/leds/leds-lp8501.c b/kernel/drivers/leds/leds-lp8501.c
index d3098e395..3f54f6f2b 100644
--- a/kernel/drivers/leds/leds-lp8501.c
+++ b/kernel/drivers/leds/leds-lp8501.c
@@ -308,20 +308,19 @@ static int lp8501_probe(struct i2c_client *client,
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = client->dev.of_node;
- if (!dev_get_platdata(&client->dev)) {
+ if (!pdata) {
if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
+ pdata = lp55xx_of_populate_pdata(&client->dev, np);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
- pdata = dev_get_platdata(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
diff --git a/kernel/drivers/leds/leds-lp8860.c b/kernel/drivers/leds/leds-lp8860.c
index 8c2b7fbe2..79f084354 100644
--- a/kernel/drivers/leds/leds-lp8860.c
+++ b/kernel/drivers/leds/leds-lp8860.c
@@ -302,7 +302,7 @@ out:
return ret;
}
-static struct reg_default lp8860_reg_defs[] = {
+static const struct reg_default lp8860_reg_defs[] = {
{ LP8860_DISP_CL1_BRT_MSB, 0x00},
{ LP8860_DISP_CL1_BRT_LSB, 0x00},
{ LP8860_DISP_CL1_CURR_MSB, 0x00},
@@ -332,7 +332,7 @@ static const struct regmap_config lp8860_regmap_config = {
.cache_type = REGCACHE_NONE,
};
-static struct reg_default lp8860_eeprom_defs[] = {
+static const struct reg_default lp8860_eeprom_defs[] = {
{ LP8860_EEPROM_REG_0, 0x00 },
{ LP8860_EEPROM_REG_1, 0x00 },
{ LP8860_EEPROM_REG_2, 0x00 },
diff --git a/kernel/drivers/leds/leds-max77693.c b/kernel/drivers/leds/leds-max77693.c
new file mode 100644
index 000000000..afbb1409b
--- /dev/null
+++ b/kernel/drivers/leds/leds-max77693.c
@@ -0,0 +1,1099 @@
+/*
+ * LED Flash class driver for the flash cell of max77693 mfd.
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *
+ * Authors: Jacek Anaszewski <j.anaszewski@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ */
+
+#include <linux/led-class-flash.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define MODE_OFF 0
+#define MODE_FLASH(a) (1 << (a))
+#define MODE_TORCH(a) (1 << (2 + (a)))
+#define MODE_FLASH_EXTERNAL(a) (1 << (4 + (a)))
+
+#define MODE_FLASH_MASK (MODE_FLASH(FLED1) | MODE_FLASH(FLED2) | \
+ MODE_FLASH_EXTERNAL(FLED1) | \
+ MODE_FLASH_EXTERNAL(FLED2))
+#define MODE_TORCH_MASK (MODE_TORCH(FLED1) | MODE_TORCH(FLED2))
+
+#define FLED1_IOUT (1 << 0)
+#define FLED2_IOUT (1 << 1)
+
+enum max77693_fled {
+ FLED1,
+ FLED2,
+};
+
+enum max77693_led_mode {
+ FLASH,
+ TORCH,
+};
+
+struct max77693_led_config_data {
+ const char *label[2];
+ u32 iout_torch_max[2];
+ u32 iout_flash_max[2];
+ u32 flash_timeout_max[2];
+ u32 num_leds;
+ u32 boost_mode;
+ u32 boost_vout;
+ u32 low_vsys;
+};
+
+struct max77693_sub_led {
+ /* corresponding FLED output identifier */
+ int fled_id;
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* assures led-triggers compatibility */
+ struct work_struct work_brightness_set;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* brightness cache */
+ unsigned int torch_brightness;
+ /* flash timeout cache */
+ unsigned int flash_timeout;
+ /* flash faults that may have occurred */
+ u32 flash_faults;
+};
+
+struct max77693_led_device {
+ /* parent mfd regmap */
+ struct regmap *regmap;
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* sub led data */
+ struct max77693_sub_led sub_leds[2];
+
+ /* maximum torch current values for FLED outputs */
+ u32 iout_torch_max[2];
+ /* maximum flash current values for FLED outputs */
+ u32 iout_flash_max[2];
+
+ /* current flash timeout cache */
+ unsigned int current_flash_timeout;
+ /* ITORCH register cache */
+ u8 torch_iout_reg;
+ /* mode of fled outputs */
+ unsigned int mode_flags;
+ /* recently strobed fled */
+ int strobing_sub_led_id;
+ /* bitmask of FLED outputs use state (bit 0. - FLED1, bit 1. - FLED2) */
+ u8 fled_mask;
+ /* FLED modes that can be set */
+ u8 allowed_modes;
+
+ /* arrangement of current outputs */
+ bool iout_joint;
+};
+
+static u8 max77693_led_iout_to_reg(u32 ua)
+{
+ if (ua < FLASH_IOUT_MIN)
+ ua = FLASH_IOUT_MIN;
+ return (ua - FLASH_IOUT_MIN) / FLASH_IOUT_STEP;
+}
+
+static u8 max77693_flash_timeout_to_reg(u32 us)
+{
+ return (us - FLASH_TIMEOUT_MIN) / FLASH_TIMEOUT_STEP;
+}
+
+static inline struct max77693_sub_led *flcdev_to_sub_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct max77693_sub_led, fled_cdev);
+}
+
+static inline struct max77693_led_device *sub_led_to_led(
+ struct max77693_sub_led *sub_led)
+{
+ return container_of(sub_led, struct max77693_led_device,
+ sub_leds[sub_led->fled_id]);
+}
+
+static inline u8 max77693_led_vsys_to_reg(u32 mv)
+{
+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
+}
+
+static inline u8 max77693_led_vout_to_reg(u32 mv)
+{
+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
+}
+
+static inline bool max77693_fled_used(struct max77693_led_device *led,
+ int fled_id)
+{
+ u8 fled_bit = (fled_id == FLED1) ? FLED1_IOUT : FLED2_IOUT;
+
+ return led->fled_mask & fled_bit;
+}
+
+static int max77693_set_mode_reg(struct max77693_led_device *led, u8 mode)
+{
+ struct regmap *rmap = led->regmap;
+ int ret, v = 0, i;
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (mode & MODE_TORCH(i))
+ v |= FLASH_EN_ON << TORCH_EN_SHIFT(i);
+
+ if (mode & MODE_FLASH(i)) {
+ v |= FLASH_EN_ON << FLASH_EN_SHIFT(i);
+ } else if (mode & MODE_FLASH_EXTERNAL(i)) {
+ v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(i);
+ /*
+ * Enable hw triggering also for torch mode, as some
+ * camera sensors use torch led to fathom ambient light
+ * conditions before strobing the flash.
+ */
+ v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(i);
+ }
+ }
+
+ /* Reset the register only prior setting flash modes */
+ if (mode & ~(MODE_TORCH(FLED1) | MODE_TORCH(FLED2))) {
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, v);
+}
+
+static int max77693_add_mode(struct max77693_led_device *led, u8 mode)
+{
+ u8 new_mode_flags;
+ int i, ret;
+
+ if (led->iout_joint)
+ /* Span the mode on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ /*
+ * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins in the device.
+ * Corresponding register bit fields interfere with SW triggered modes,
+ * thus clear them to ensure proper device configuration.
+ */
+ for (i = FLED1; i <= FLED2; ++i)
+ if (mode & MODE_FLASH_EXTERNAL(i))
+ led->mode_flags &= (~MODE_TORCH(i) & ~MODE_FLASH(i));
+
+ new_mode_flags = mode | led->mode_flags;
+ new_mode_flags &= led->allowed_modes;
+
+ if (new_mode_flags ^ led->mode_flags)
+ led->mode_flags = new_mode_flags;
+ else
+ return 0;
+
+ ret = max77693_set_mode_reg(led, led->mode_flags);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Clear flash mode flag after setting the mode to avoid spurious flash
+ * strobing on each subsequent torch mode setting.
+ */
+ if (mode & MODE_FLASH_MASK)
+ led->mode_flags &= ~mode;
+
+ return ret;
+}
+
+static int max77693_clear_mode(struct max77693_led_device *led,
+ u8 mode)
+{
+ if (led->iout_joint)
+ /* Clear mode also on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ led->mode_flags &= ~mode;
+
+ return max77693_set_mode_reg(led, led->mode_flags);
+}
+
+static void max77693_add_allowed_modes(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode)
+{
+ if (mode == FLASH)
+ led->allowed_modes |= (MODE_FLASH(fled_id) |
+ MODE_FLASH_EXTERNAL(fled_id));
+ else
+ led->allowed_modes |= MODE_TORCH(fled_id);
+}
+
+static void max77693_distribute_currents(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode,
+ u32 micro_amp, u32 iout_max[2], u32 iout[2])
+{
+ if (!led->iout_joint) {
+ iout[fled_id] = micro_amp;
+ max77693_add_allowed_modes(led, fled_id, mode);
+ return;
+ }
+
+ iout[FLED1] = min(micro_amp, iout_max[FLED1]);
+ iout[FLED2] = micro_amp - iout[FLED1];
+
+ if (mode == FLASH)
+ led->allowed_modes &= ~MODE_FLASH_MASK;
+ else
+ led->allowed_modes &= ~MODE_TORCH_MASK;
+
+ max77693_add_allowed_modes(led, FLED1, mode);
+
+ if (iout[FLED2])
+ max77693_add_allowed_modes(led, FLED2, mode);
+}
+
+static int max77693_set_torch_current(struct max77693_led_device *led,
+ int fled_id, u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg = 0, iout2_reg = 0;
+ u32 iout[2];
+
+ max77693_distribute_currents(led, fled_id, TORCH, micro_amp,
+ led->iout_torch_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT2_SHIFT);
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT1_SHIFT);
+ }
+
+ led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
+ (iout2_reg << TORCH_IOUT2_SHIFT));
+
+ return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
+ led->torch_iout_reg);
+}
+
+static int max77693_set_flash_current(struct max77693_led_device *led,
+ int fled_id,
+ u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg, iout2_reg;
+ u32 iout[2];
+ int ret = -EINVAL;
+
+ max77693_distribute_currents(led, fled_id, FLASH, micro_amp,
+ led->iout_flash_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
+ iout1_reg);
+ if (ret < 0)
+ return ret;
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
+ iout2_reg);
+ }
+
+ return ret;
+}
+
+static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
+{
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ v = max77693_flash_timeout_to_reg(microsec) | FLASH_TMR_LEVEL;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ led->current_flash_timeout = microsec;
+
+ return 0;
+}
+
+static int max77693_get_strobe_status(struct max77693_led_device *led,
+ bool *state)
+{
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ int ret;
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
+ if (ret < 0)
+ return ret;
+
+ *state = v & FLASH_STATUS_FLASH_ON;
+
+ return ret;
+}
+
+static int max77693_get_flash_faults(struct max77693_sub_led *sub_led)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ u8 fault_open_mask, fault_short_mask;
+ int ret;
+
+ sub_led->flash_faults = 0;
+
+ if (led->iout_joint) {
+ fault_open_mask = FLASH_INT_FLED1_OPEN | FLASH_INT_FLED2_OPEN;
+ fault_short_mask = FLASH_INT_FLED1_SHORT |
+ FLASH_INT_FLED2_SHORT;
+ } else {
+ fault_open_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_OPEN :
+ FLASH_INT_FLED2_OPEN;
+ fault_short_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_SHORT :
+ FLASH_INT_FLED2_SHORT;
+ }
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, &v);
+ if (ret < 0)
+ return ret;
+
+ if (v & fault_open_mask)
+ sub_led->flash_faults |= LED_FAULT_OVER_VOLTAGE;
+ if (v & fault_short_mask)
+ sub_led->flash_faults |= LED_FAULT_SHORT_CIRCUIT;
+ if (v & FLASH_INT_OVER_CURRENT)
+ sub_led->flash_faults |= LED_FAULT_OVER_CURRENT;
+
+ return 0;
+}
+
+static int max77693_setup(struct max77693_led_device *led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct regmap *rmap = led->regmap;
+ int i, first_led, last_led, ret;
+ u32 max_flash_curr[2];
+ u8 v;
+
+ /*
+ * Initialize only flash current. Torch current doesn't
+ * require initialization as ITORCH register is written with
+ * new value each time brightness_set op is called.
+ */
+ if (led->iout_joint) {
+ first_led = FLED1;
+ last_led = FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2];
+ } else {
+ first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
+ last_led = max77693_fled_used(led, FLED2) ? FLED2 : FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1];
+ max_flash_curr[FLED2] = led_cfg->iout_flash_max[FLED2];
+ }
+
+ for (i = first_led; i <= last_led; ++i) {
+ ret = max77693_set_flash_current(led, i,
+ max_flash_curr[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
+ ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(led_cfg->low_vsys) |
+ MAX_FLASH1_MAX_FL_EN;
+ else
+ v = 0;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
+ v = FLASH_BOOST_FIXED;
+ else
+ v = led_cfg->boost_mode | led_cfg->boost_mode << 1;
+
+ if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ v |= FLASH_BOOST_LEDNUM_2;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_led_vout_to_reg(led_cfg->boost_vout);
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ return max77693_set_mode_reg(led, MODE_OFF);
+}
+
+static int __max77693_led_brightness_set(struct max77693_led_device *led,
+ int fled_id, enum led_brightness value)
+{
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (value == 0) {
+ ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to clear torch mode (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
+ if (ret < 0) {
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch current (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_add_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static void max77693_led_brightness_set_work(
+ struct work_struct *work)
+{
+ struct max77693_sub_led *sub_led =
+ container_of(work, struct max77693_sub_led,
+ work_brightness_set);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ __max77693_led_brightness_set(led, sub_led->fled_id,
+ sub_led->torch_brightness);
+}
+
+/* LED subsystem callbacks */
+
+static int max77693_led_brightness_set_sync(
+ struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ return __max77693_led_brightness_set(led, sub_led->fled_id, value);
+}
+
+static void max77693_led_brightness_set(
+ struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+ sub_led->torch_brightness = value;
+ schedule_work(&sub_led->work_brightness_set);
+}
+
+static int max77693_led_flash_brightness_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_strobe_set(
+ struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (!state) {
+ ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
+ goto unlock;
+ }
+
+ if (sub_led->flash_timeout != led->current_flash_timeout) {
+ ret = max77693_set_timeout(led, sub_led->flash_timeout);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ led->strobing_sub_led_id = fled_id;
+
+ ret = max77693_add_mode(led, MODE_FLASH(fled_id));
+ if (ret < 0)
+ goto unlock;
+
+ ret = max77693_get_flash_faults(sub_led);
+
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_fault_get(
+ struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+ *fault = sub_led->flash_faults;
+
+ return 0;
+}
+
+static int max77693_led_flash_strobe_get(
+ struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ if (!state)
+ return -EINVAL;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_get_strobe_status(led, state);
+
+ *state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_timeout_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ mutex_lock(&led->lock);
+ sub_led->flash_timeout = timeout;
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int max77693_led_parse_dt(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ struct device *dev = &led->pdev->dev;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct device_node *node = dev->of_node, *child_node;
+ struct property *prop;
+ u32 led_sources[2];
+ int i, ret, fled_id;
+
+ of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
+ of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout);
+ of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys);
+
+ for_each_available_child_of_node(node, child_node) {
+ prop = of_find_property(child_node, "led-sources", NULL);
+ if (prop) {
+ const __be32 *srcs = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(led_sources); ++i) {
+ srcs = of_prop_next_u32(prop, srcs,
+ &led_sources[i]);
+ if (!srcs)
+ break;
+ }
+ } else {
+ dev_err(dev,
+ "led-sources DT property missing\n");
+ of_node_put(child_node);
+ return -EINVAL;
+ }
+
+ if (i == 2) {
+ fled_id = FLED1;
+ led->fled_mask = FLED1_IOUT | FLED2_IOUT;
+ } else if (led_sources[0] == FLED1) {
+ fled_id = FLED1;
+ led->fled_mask |= FLED1_IOUT;
+ } else if (led_sources[0] == FLED2) {
+ fled_id = FLED2;
+ led->fled_mask |= FLED2_IOUT;
+ } else {
+ dev_err(dev,
+ "Wrong led-sources DT property value.\n");
+ of_node_put(child_node);
+ return -EINVAL;
+ }
+
+ if (sub_nodes[fled_id]) {
+ dev_err(dev,
+ "Conflicting \"led-sources\" DT properties\n");
+ return -EINVAL;
+ }
+
+ sub_nodes[fled_id] = child_node;
+ sub_leds[fled_id].fled_id = fled_id;
+
+ cfg->label[fled_id] =
+ of_get_property(child_node, "label", NULL) ? :
+ child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->iout_torch_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_torch_max[fled_id] = TORCH_IOUT_MIN;
+ dev_warn(dev, "led-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->iout_flash_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_flash_max[fled_id] = FLASH_IOUT_MIN;
+ dev_warn(dev,
+ "flash-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->flash_timeout_max[fled_id]);
+ if (ret < 0) {
+ cfg->flash_timeout_max[fled_id] = FLASH_TIMEOUT_MIN;
+ dev_warn(dev,
+ "flash-max-timeout-us DT property missing\n");
+ }
+
+ if (++cfg->num_leds == 2 ||
+ (max77693_fled_used(led, FLED1) &&
+ max77693_fled_used(led, FLED2))) {
+ of_node_put(child_node);
+ break;
+ }
+ }
+
+ if (cfg->num_leds == 0) {
+ dev_err(dev, "No DT child node found for connected LED(s).\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
+{
+ *v = clamp_val(*v, min, max);
+ if (step > 1)
+ *v = (*v - min) / step * step + min;
+}
+
+static void max77693_align_iout_current(struct max77693_led_device *led,
+ u32 *iout, u32 min, u32 max, u32 step)
+{
+ int i;
+
+ if (led->iout_joint) {
+ if (iout[FLED1] > min) {
+ iout[FLED1] /= 2;
+ iout[FLED2] = iout[FLED1];
+ } else {
+ iout[FLED1] = min;
+ iout[FLED2] = 0;
+ return;
+ }
+ }
+
+ for (i = FLED1; i <= FLED2; ++i)
+ if (max77693_fled_used(led, i))
+ clamp_align(&iout[i], min, max, step);
+ else
+ iout[i] = 0;
+}
+
+static void max77693_led_validate_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg)
+{
+ u32 flash_iout_max = cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
+ FLASH_IOUT_MAX_1LED;
+ int i;
+
+ if (cfg->num_leds == 1 &&
+ max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ led->iout_joint = true;
+
+ cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED);
+
+ /* Boost must be enabled if both current outputs are used */
+ if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
+ cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+ max77693_align_iout_current(led, cfg->iout_torch_max,
+ TORCH_IOUT_MIN, TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+
+ max77693_align_iout_current(led, cfg->iout_flash_max,
+ FLASH_IOUT_MIN, flash_iout_max, FLASH_IOUT_STEP);
+
+ for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout_max); ++i)
+ clamp_align(&cfg->flash_timeout_max[i], FLASH_TIMEOUT_MIN,
+ FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+ clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+ FLASH_VOUT_STEP);
+
+ if (cfg->low_vsys)
+ clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
+ MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ int ret;
+
+ ret = max77693_led_parse_dt(led, cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ max77693_led_validate_configuration(led, cfg);
+
+ memcpy(led->iout_torch_max, cfg->iout_torch_max,
+ sizeof(led->iout_torch_max));
+ memcpy(led->iout_flash_max, cfg->iout_flash_max,
+ sizeof(led->iout_flash_max));
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = max77693_led_flash_brightness_set,
+ .strobe_set = max77693_led_flash_strobe_set,
+ .strobe_get = max77693_led_flash_strobe_get,
+ .timeout_set = max77693_led_flash_timeout_set,
+ .fault_get = max77693_led_flash_fault_get,
+};
+
+static void max77693_init_flash_settings(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_flash_setting *setting;
+
+ /* Init flash intensity setting */
+ setting = &fled_cdev->brightness;
+ setting->min = FLASH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2] :
+ led_cfg->iout_flash_max[fled_id];
+ setting->step = FLASH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = FLASH_TIMEOUT_MIN;
+ setting->max = led_cfg->flash_timeout_max[fled_id];
+ setting->step = FLASH_TIMEOUT_STEP;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+
+static int max77693_led_external_strobe_set(
+ struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct max77693_sub_led *sub_led =
+ flcdev_to_sub_led(v4l2_flash->fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (enable)
+ ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+ else
+ ret = max77693_clear_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static void max77693_init_v4l2_flash_config(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct device *dev = &led->pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct i2c_client *i2c = iodev->i2c;
+ struct led_flash_setting *s;
+
+ snprintf(v4l2_sd_cfg->dev_name, sizeof(v4l2_sd_cfg->dev_name),
+ "%s %d-%04x", sub_led->fled_cdev.led_cdev.name,
+ i2c_adapter_id(i2c->adapter), i2c->addr);
+
+ s = &v4l2_sd_cfg->torch_intensity;
+ s->min = TORCH_IOUT_MIN;
+ s->max = sub_led->fled_cdev.led_cdev.max_brightness * TORCH_IOUT_STEP;
+ s->step = TORCH_IOUT_STEP;
+ s->val = s->max;
+
+ /* Init flash faults config */
+ v4l2_sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_OVER_CURRENT;
+
+ v4l2_sd_cfg->has_external_strobe = true;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = max77693_led_external_strobe_set,
+};
+#else
+static inline void max77693_init_v4l2_flash_config(
+ struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static void max77693_init_fled_cdev(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_classdev_flash *fled_cdev;
+ struct led_classdev *led_cdev;
+
+ /* Initialize LED Flash class device */
+ fled_cdev = &sub_led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ led_cdev->name = led_cfg->label[fled_id];
+
+ led_cdev->brightness_set = max77693_led_brightness_set;
+ led_cdev->brightness_set_sync = max77693_led_brightness_set_sync;
+ led_cdev->max_brightness = (led->iout_joint ?
+ led_cfg->iout_torch_max[FLED1] +
+ led_cfg->iout_torch_max[FLED2] :
+ led_cfg->iout_torch_max[fled_id]) /
+ TORCH_IOUT_STEP;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+ INIT_WORK(&sub_led->work_brightness_set,
+ max77693_led_brightness_set_work);
+
+ max77693_init_flash_settings(sub_led, led_cfg);
+
+ /* Init flash timeout cache */
+ sub_led->flash_timeout = fled_cdev->timeout.val;
+}
+
+static int max77693_register_led(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct device_node *sub_node)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct device *dev = &led->pdev->dev;
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ /* Register in the LED subsystem */
+ ret = led_classdev_flash_register(dev, fled_cdev);
+ if (ret < 0)
+ return ret;
+
+ max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg);
+
+ /* Register in the V4L2 subsystem. */
+ sub_led->v4l2_flash = v4l2_flash_init(dev, sub_node, fled_cdev, NULL,
+ &v4l2_flash_ops, &v4l2_sd_cfg);
+ if (IS_ERR(sub_led->v4l2_flash)) {
+ ret = PTR_ERR(sub_led->v4l2_flash);
+ goto err_v4l2_flash_init;
+ }
+
+ return 0;
+
+err_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+ return ret;
+}
+
+static int max77693_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct max77693_led_device *led;
+ struct max77693_sub_led *sub_leds;
+ struct device_node *sub_nodes[2] = {};
+ struct max77693_led_config_data led_cfg = {};
+ int init_fled_cdev[2], i, ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led->regmap = iodev->regmap;
+ led->allowed_modes = MODE_FLASH_MASK;
+ sub_leds = led->sub_leds;
+
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_configuration(led, &led_cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ ret = max77693_setup(led, &led_cfg);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&led->lock);
+
+ init_fled_cdev[FLED1] =
+ led->iout_joint || max77693_fled_used(led, FLED1);
+ init_fled_cdev[FLED2] =
+ !led->iout_joint && max77693_fled_used(led, FLED2);
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (!init_fled_cdev[i])
+ continue;
+
+ /* Initialize LED Flash class device */
+ max77693_init_fled_cdev(&sub_leds[i], &led_cfg);
+
+ /*
+ * Register LED Flash class device and corresponding
+ * V4L2 Flash device.
+ */
+ ret = max77693_register_led(&sub_leds[i], &led_cfg,
+ sub_nodes[i]);
+ if (ret < 0) {
+ /*
+ * At this moment FLED1 might have been already
+ * registered and it needs to be released.
+ */
+ if (i == FLED2)
+ goto err_register_led2;
+ else
+ goto err_register_led1;
+ }
+ }
+
+ return 0;
+
+err_register_led2:
+ /* It is possible than only FLED2 was to be registered */
+ if (!init_fled_cdev[FLED1])
+ goto err_register_led1;
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+err_register_led1:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_remove(struct platform_device *pdev)
+{
+ struct max77693_led_device *led = platform_get_drvdata(pdev);
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+
+ if (led->iout_joint || max77693_fled_used(led, FLED1)) {
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+ cancel_work_sync(&sub_leds[FLED1].work_brightness_set);
+ }
+
+ if (!led->iout_joint && max77693_fled_used(led, FLED2)) {
+ v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED2].fled_cdev);
+ cancel_work_sync(&sub_leds[FLED2].work_brightness_set);
+ }
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id max77693_led_dt_match[] = {
+ { .compatible = "maxim,max77693-led" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77693_led_dt_match);
+
+static struct platform_driver max77693_led_driver = {
+ .probe = max77693_led_probe,
+ .remove = max77693_led_remove,
+ .driver = {
+ .name = "max77693-led",
+ .of_match_table = max77693_led_dt_match,
+ },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL v2");
diff --git a/kernel/drivers/leds/leds-menf21bmc.c b/kernel/drivers/leds/leds-menf21bmc.c
index 4b9eea815..dec2a6e59 100644
--- a/kernel/drivers/leds/leds-menf21bmc.c
+++ b/kernel/drivers/leds/leds-menf21bmc.c
@@ -87,36 +87,20 @@ static int menf21bmc_led_probe(struct platform_device *pdev)
leds[i].cdev.name = leds[i].name;
leds[i].cdev.brightness_set = menf21bmc_led_set;
leds[i].i2c_client = i2c_client;
- ret = led_classdev_register(&pdev->dev, &leds[i].cdev);
- if (ret < 0)
- goto err_free_leds;
+ ret = devm_led_classdev_register(&pdev->dev, &leds[i].cdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register LED device\n");
+ return ret;
+ }
}
dev_info(&pdev->dev, "MEN 140F21P00 BMC LED device enabled\n");
return 0;
-err_free_leds:
- dev_err(&pdev->dev, "failed to register LED device\n");
-
- for (i = i - 1; i >= 0; i--)
- led_classdev_unregister(&leds[i].cdev);
-
- return ret;
-}
-
-static int menf21bmc_led_remove(struct platform_device *pdev)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(leds); i++)
- led_classdev_unregister(&leds[i].cdev);
-
- return 0;
}
static struct platform_driver menf21bmc_led = {
.probe = menf21bmc_led_probe,
- .remove = menf21bmc_led_remove,
.driver = {
.name = "menf21bmc_led",
},
diff --git a/kernel/drivers/leds/leds-net48xx.c b/kernel/drivers/leds/leds-net48xx.c
index ec3a2e8ad..0d214c2e4 100644
--- a/kernel/drivers/leds/leds-net48xx.c
+++ b/kernel/drivers/leds/leds-net48xx.c
@@ -39,18 +39,11 @@ static struct led_classdev net48xx_error_led = {
static int net48xx_led_probe(struct platform_device *pdev)
{
- return led_classdev_register(&pdev->dev, &net48xx_error_led);
-}
-
-static int net48xx_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&net48xx_error_led);
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &net48xx_error_led);
}
static struct platform_driver net48xx_led_driver = {
.probe = net48xx_led_probe,
- .remove = net48xx_led_remove,
.driver = {
.name = DRVNAME,
},
diff --git a/kernel/drivers/leds/leds-netxbig.c b/kernel/drivers/leds/leds-netxbig.c
index 25e419752..4b88b9324 100644
--- a/kernel/drivers/leds/leds-netxbig.c
+++ b/kernel/drivers/leds/leds-netxbig.c
@@ -26,6 +26,7 @@
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
+#include <linux/of_gpio.h>
#include <linux/leds.h>
#include <linux/platform_data/leds-kirkwood-netxbig.h>
@@ -70,7 +71,8 @@ static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext,
spin_unlock_irqrestore(&gpio_ext_lock, flags);
}
-static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
+static int gpio_ext_init(struct platform_device *pdev,
+ struct netxbig_gpio_ext *gpio_ext)
{
int err;
int i;
@@ -80,46 +82,28 @@ static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
/* Configure address GPIOs. */
for (i = 0; i < gpio_ext->num_addr; i++) {
- err = gpio_request_one(gpio_ext->addr[i], GPIOF_OUT_INIT_LOW,
- "GPIO extension addr");
+ err = devm_gpio_request_one(&pdev->dev, gpio_ext->addr[i],
+ GPIOF_OUT_INIT_LOW,
+ "GPIO extension addr");
if (err)
- goto err_free_addr;
+ return err;
}
/* Configure data GPIOs. */
for (i = 0; i < gpio_ext->num_data; i++) {
- err = gpio_request_one(gpio_ext->data[i], GPIOF_OUT_INIT_LOW,
- "GPIO extension data");
+ err = devm_gpio_request_one(&pdev->dev, gpio_ext->data[i],
+ GPIOF_OUT_INIT_LOW,
+ "GPIO extension data");
if (err)
- goto err_free_data;
+ return err;
}
/* Configure "enable select" GPIO. */
- err = gpio_request_one(gpio_ext->enable, GPIOF_OUT_INIT_LOW,
- "GPIO extension enable");
+ err = devm_gpio_request_one(&pdev->dev, gpio_ext->enable,
+ GPIOF_OUT_INIT_LOW,
+ "GPIO extension enable");
if (err)
- goto err_free_data;
+ return err;
return 0;
-
-err_free_data:
- for (i = i - 1; i >= 0; i--)
- gpio_free(gpio_ext->data[i]);
- i = gpio_ext->num_addr;
-err_free_addr:
- for (i = i - 1; i >= 0; i--)
- gpio_free(gpio_ext->addr[i]);
-
- return err;
-}
-
-static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext)
-{
- int i;
-
- gpio_free(gpio_ext->enable);
- for (i = gpio_ext->num_addr - 1; i >= 0; i--)
- gpio_free(gpio_ext->addr[i]);
- for (i = gpio_ext->num_data - 1; i >= 0; i--)
- gpio_free(gpio_ext->data[i]);
}
/*
@@ -132,7 +116,6 @@ struct netxbig_led_data {
int mode_addr;
int *mode_val;
int bright_addr;
- int bright_max;
struct netxbig_led_timer *timer;
int num_timer;
enum netxbig_led_mode mode;
@@ -194,7 +177,7 @@ static void netxbig_led_set(struct led_classdev *led_cdev,
struct netxbig_led_data *led_dat =
container_of(led_cdev, struct netxbig_led_data, cdev);
enum netxbig_led_mode mode;
- int mode_val, bright_val;
+ int mode_val;
int set_brightness = 1;
unsigned long flags;
@@ -220,12 +203,9 @@ static void netxbig_led_set(struct led_classdev *led_cdev,
* SATA LEDs. So, change the brightness setting for a single
* SATA LED will affect all the others.
*/
- if (set_brightness) {
- bright_val = DIV_ROUND_UP(value * led_dat->bright_max,
- LED_FULL);
+ if (set_brightness)
gpio_ext_set_value(led_dat->gpio_ext,
- led_dat->bright_addr, bright_val);
- }
+ led_dat->bright_addr, value);
spin_unlock_irqrestore(&led_dat->lock, flags);
}
@@ -299,18 +279,11 @@ static struct attribute *netxbig_led_attrs[] = {
};
ATTRIBUTE_GROUPS(netxbig_led);
-static void delete_netxbig_led(struct netxbig_led_data *led_dat)
+static int create_netxbig_led(struct platform_device *pdev,
+ struct netxbig_led_platform_data *pdata,
+ struct netxbig_led_data *led_dat,
+ const struct netxbig_led *template)
{
- led_classdev_unregister(&led_dat->cdev);
-}
-
-static int
-create_netxbig_led(struct platform_device *pdev,
- struct netxbig_led_data *led_dat,
- const struct netxbig_led *template)
-{
- struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
-
spin_lock_init(&led_dat->lock);
led_dat->gpio_ext = pdata->gpio_ext;
led_dat->cdev.name = template->name;
@@ -329,11 +302,11 @@ create_netxbig_led(struct platform_device *pdev,
*/
led_dat->sata = 0;
led_dat->cdev.brightness = LED_OFF;
+ led_dat->cdev.max_brightness = template->bright_max;
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
led_dat->mode_addr = template->mode_addr;
led_dat->mode_val = template->mode_val;
led_dat->bright_addr = template->bright_addr;
- led_dat->bright_max = (1 << pdata->gpio_ext->num_data) - 1;
led_dat->timer = pdata->timer;
led_dat->num_timer = pdata->num_timer;
/*
@@ -343,67 +316,274 @@ create_netxbig_led(struct platform_device *pdev,
if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE)
led_dat->cdev.groups = netxbig_led_groups;
- return led_classdev_register(&pdev->dev, &led_dat->cdev);
+ return devm_led_classdev_register(&pdev->dev, &led_dat->cdev);
}
-static int netxbig_led_probe(struct platform_device *pdev)
+#ifdef CONFIG_OF_GPIO
+static int gpio_ext_get_of_pdata(struct device *dev, struct device_node *np,
+ struct netxbig_gpio_ext *gpio_ext)
{
- struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct netxbig_led_data *leds_data;
- int i;
+ int *addr, *data;
+ int num_addr, num_data;
int ret;
+ int i;
- if (!pdata)
- return -EINVAL;
-
- leds_data = devm_kzalloc(&pdev->dev,
- sizeof(struct netxbig_led_data) * pdata->num_leds, GFP_KERNEL);
- if (!leds_data)
+ ret = of_gpio_named_count(np, "addr-gpios");
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to count GPIOs in DT property addr-gpios\n");
+ return ret;
+ }
+ num_addr = ret;
+ addr = devm_kzalloc(dev, num_addr * sizeof(*addr), GFP_KERNEL);
+ if (!addr)
return -ENOMEM;
- ret = gpio_ext_init(pdata->gpio_ext);
- if (ret < 0)
+ for (i = 0; i < num_addr; i++) {
+ ret = of_get_named_gpio(np, "addr-gpios", i);
+ if (ret < 0)
+ return ret;
+ addr[i] = ret;
+ }
+ gpio_ext->addr = addr;
+ gpio_ext->num_addr = num_addr;
+
+ ret = of_gpio_named_count(np, "data-gpios");
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to count GPIOs in DT property data-gpios\n");
return ret;
+ }
+ num_data = ret;
+ data = devm_kzalloc(dev, num_data * sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++) {
- ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]);
+ for (i = 0; i < num_data; i++) {
+ ret = of_get_named_gpio(np, "data-gpios", i);
if (ret < 0)
- goto err_free_leds;
+ return ret;
+ data[i] = ret;
}
+ gpio_ext->data = data;
+ gpio_ext->num_data = num_data;
- platform_set_drvdata(pdev, leds_data);
+ ret = of_get_named_gpio(np, "enable-gpio", 0);
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to get GPIO from DT property enable-gpio\n");
+ return ret;
+ }
+ gpio_ext->enable = ret;
return 0;
+}
+
+static int netxbig_leds_get_of_pdata(struct device *dev,
+ struct netxbig_led_platform_data *pdata)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *gpio_ext_np;
+ struct device_node *child;
+ struct netxbig_gpio_ext *gpio_ext;
+ struct netxbig_led_timer *timers;
+ struct netxbig_led *leds, *led;
+ int num_timers;
+ int num_leds = 0;
+ int ret;
+ int i;
-err_free_leds:
- for (i = i - 1; i >= 0; i--)
- delete_netxbig_led(&leds_data[i]);
+ /* GPIO extension */
+ gpio_ext_np = of_parse_phandle(np, "gpio-ext", 0);
+ if (!gpio_ext_np) {
+ dev_err(dev, "Failed to get DT handle gpio-ext\n");
+ return -EINVAL;
+ }
- gpio_ext_free(pdata->gpio_ext);
+ gpio_ext = devm_kzalloc(dev, sizeof(*gpio_ext), GFP_KERNEL);
+ if (!gpio_ext)
+ return -ENOMEM;
+ ret = gpio_ext_get_of_pdata(dev, gpio_ext_np, gpio_ext);
+ if (ret)
+ return ret;
+ of_node_put(gpio_ext_np);
+ pdata->gpio_ext = gpio_ext;
+
+ /* Timers (optional) */
+ ret = of_property_count_u32_elems(np, "timers");
+ if (ret > 0) {
+ if (ret % 3)
+ return -EINVAL;
+ num_timers = ret / 3;
+ timers = devm_kzalloc(dev, num_timers * sizeof(*timers),
+ GFP_KERNEL);
+ if (!timers)
+ return -ENOMEM;
+ for (i = 0; i < num_timers; i++) {
+ u32 tmp;
+
+ of_property_read_u32_index(np, "timers", 3 * i,
+ &timers[i].mode);
+ if (timers[i].mode >= NETXBIG_LED_MODE_NUM)
+ return -EINVAL;
+ of_property_read_u32_index(np, "timers",
+ 3 * i + 1, &tmp);
+ timers[i].delay_on = tmp;
+ of_property_read_u32_index(np, "timers",
+ 3 * i + 2, &tmp);
+ timers[i].delay_off = tmp;
+ }
+ pdata->timer = timers;
+ pdata->num_timer = num_timers;
+ }
+
+ /* LEDs */
+ num_leds = of_get_child_count(np);
+ if (!num_leds) {
+ dev_err(dev, "No LED subnodes found in DT\n");
+ return -ENODEV;
+ }
+
+ leds = devm_kzalloc(dev, num_leds * sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ led = leds;
+ for_each_child_of_node(np, child) {
+ const char *string;
+ int *mode_val;
+ int num_modes;
+
+ ret = of_property_read_u32(child, "mode-addr",
+ &led->mode_addr);
+ if (ret)
+ goto err_node_put;
+
+ ret = of_property_read_u32(child, "bright-addr",
+ &led->bright_addr);
+ if (ret)
+ goto err_node_put;
+
+ ret = of_property_read_u32(child, "max-brightness",
+ &led->bright_max);
+ if (ret)
+ goto err_node_put;
+
+ mode_val =
+ devm_kzalloc(dev,
+ NETXBIG_LED_MODE_NUM * sizeof(*mode_val),
+ GFP_KERNEL);
+ if (!mode_val) {
+ ret = -ENOMEM;
+ goto err_node_put;
+ }
+
+ for (i = 0; i < NETXBIG_LED_MODE_NUM; i++)
+ mode_val[i] = NETXBIG_LED_INVALID_MODE;
+
+ ret = of_property_count_u32_elems(child, "mode-val");
+ if (ret < 0 || ret % 2) {
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+ num_modes = ret / 2;
+ if (num_modes > NETXBIG_LED_MODE_NUM) {
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+
+ for (i = 0; i < num_modes; i++) {
+ int mode;
+ int val;
+
+ of_property_read_u32_index(child,
+ "mode-val", 2 * i, &mode);
+ of_property_read_u32_index(child,
+ "mode-val", 2 * i + 1, &val);
+ if (mode >= NETXBIG_LED_MODE_NUM) {
+ ret = -EINVAL;
+ goto err_node_put;
+ }
+ mode_val[mode] = val;
+ }
+ led->mode_val = mode_val;
+
+ if (!of_property_read_string(child, "label", &string))
+ led->name = string;
+ else
+ led->name = child->name;
+
+ if (!of_property_read_string(child,
+ "linux,default-trigger", &string))
+ led->default_trigger = string;
+
+ led++;
+ }
+
+ pdata->leds = leds;
+ pdata->num_leds = num_leds;
+
+ return 0;
+
+err_node_put:
+ of_node_put(child);
return ret;
}
-static int netxbig_led_remove(struct platform_device *pdev)
+static const struct of_device_id of_netxbig_leds_match[] = {
+ { .compatible = "lacie,netxbig-leds", },
+ {},
+};
+#else
+static inline int
+netxbig_leds_get_of_pdata(struct device *dev,
+ struct netxbig_led_platform_data *pdata)
+{
+ return -ENODEV;
+}
+#endif /* CONFIG_OF_GPIO */
+
+static int netxbig_led_probe(struct platform_device *pdev)
{
struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct netxbig_led_data *leds_data;
int i;
+ int ret;
- leds_data = platform_get_drvdata(pdev);
+ if (!pdata) {
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ ret = netxbig_leds_get_of_pdata(&pdev->dev, pdata);
+ if (ret)
+ return ret;
+ }
+
+ leds_data = devm_kzalloc(&pdev->dev,
+ pdata->num_leds * sizeof(*leds_data),
+ GFP_KERNEL);
+ if (!leds_data)
+ return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++)
- delete_netxbig_led(&leds_data[i]);
+ ret = gpio_ext_init(pdev, pdata->gpio_ext);
+ if (ret < 0)
+ return ret;
- gpio_ext_free(pdata->gpio_ext);
+ for (i = 0; i < pdata->num_leds; i++) {
+ ret = create_netxbig_led(pdev, pdata,
+ &leds_data[i], &pdata->leds[i]);
+ if (ret < 0)
+ return ret;
+ }
return 0;
}
static struct platform_driver netxbig_led_driver = {
.probe = netxbig_led_probe,
- .remove = netxbig_led_remove,
.driver = {
- .name = "leds-netxbig",
+ .name = "leds-netxbig",
+ .of_match_table = of_match_ptr(of_netxbig_leds_match),
},
};
diff --git a/kernel/drivers/leds/leds-ns2.c b/kernel/drivers/leds/leds-ns2.c
index 1fd6adbb4..a95a61220 100644
--- a/kernel/drivers/leds/leds-ns2.c
+++ b/kernel/drivers/leds/leds-ns2.c
@@ -31,50 +31,38 @@
#include <linux/platform_data/leds-kirkwood-ns2.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
+#include "leds.h"
/*
- * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
- * relation with the SATA activity. This capability is exposed through the
- * "sata" sysfs attribute.
- *
- * The following array detail the different LED registers and the combination
- * of their possible values:
- *
- * cmd_led | slow_led | /SATA active | LED state
- * | | |
- * 1 | 0 | x | off
- * - | 1 | x | on
- * 0 | 0 | 1 | on
- * 0 | 0 | 0 | blink (rate 300ms)
+ * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
+ * modes are available: off, on and SATA activity blinking. The LED modes are
+ * controlled through two GPIOs (command and slow): each combination of values
+ * for the command/slow GPIOs corresponds to a LED mode.
*/
-enum ns2_led_modes {
- NS_V2_LED_OFF,
- NS_V2_LED_ON,
- NS_V2_LED_SATA,
-};
-
-struct ns2_led_mode_value {
- enum ns2_led_modes mode;
- int cmd_level;
- int slow_level;
-};
-
-static struct ns2_led_mode_value ns2_led_modval[] = {
- { NS_V2_LED_OFF , 1, 0 },
- { NS_V2_LED_ON , 0, 1 },
- { NS_V2_LED_ON , 1, 1 },
- { NS_V2_LED_SATA, 0, 0 },
-};
-
struct ns2_led_data {
struct led_classdev cdev;
unsigned cmd;
unsigned slow;
+ bool can_sleep;
+ int mode_index;
unsigned char sata; /* True when SATA mode active. */
rwlock_t rw_lock; /* Lock GPIOs. */
+ struct work_struct work;
+ int num_modes;
+ struct ns2_led_modval *modval;
};
+static void ns2_led_work(struct work_struct *work)
+{
+ struct ns2_led_data *led_dat =
+ container_of(work, struct ns2_led_data, work);
+ int i = led_dat->mode_index;
+
+ gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
+ gpio_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level);
+}
+
static int ns2_led_get_mode(struct ns2_led_data *led_dat,
enum ns2_led_modes *mode)
{
@@ -83,22 +71,18 @@ static int ns2_led_get_mode(struct ns2_led_data *led_dat,
int cmd_level;
int slow_level;
- read_lock_irq(&led_dat->rw_lock);
+ cmd_level = gpio_get_value_cansleep(led_dat->cmd);
+ slow_level = gpio_get_value_cansleep(led_dat->slow);
- cmd_level = gpio_get_value(led_dat->cmd);
- slow_level = gpio_get_value(led_dat->slow);
-
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (cmd_level == ns2_led_modval[i].cmd_level &&
- slow_level == ns2_led_modval[i].slow_level) {
- *mode = ns2_led_modval[i].mode;
+ for (i = 0; i < led_dat->num_modes; i++) {
+ if (cmd_level == led_dat->modval[i].cmd_level &&
+ slow_level == led_dat->modval[i].slow_level) {
+ *mode = led_dat->modval[i].mode;
ret = 0;
break;
}
}
- read_unlock_irq(&led_dat->rw_lock);
-
return ret;
}
@@ -106,19 +90,32 @@ static void ns2_led_set_mode(struct ns2_led_data *led_dat,
enum ns2_led_modes mode)
{
int i;
+ bool found = false;
unsigned long flags;
+ for (i = 0; i < led_dat->num_modes; i++)
+ if (mode == led_dat->modval[i].mode) {
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return;
+
write_lock_irqsave(&led_dat->rw_lock, flags);
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (mode == ns2_led_modval[i].mode) {
- gpio_set_value(led_dat->cmd,
- ns2_led_modval[i].cmd_level);
- gpio_set_value(led_dat->slow,
- ns2_led_modval[i].slow_level);
- }
+ if (!led_dat->can_sleep) {
+ gpio_set_value(led_dat->cmd,
+ led_dat->modval[i].cmd_level);
+ gpio_set_value(led_dat->slow,
+ led_dat->modval[i].slow_level);
+ goto exit_unlock;
}
+ led_dat->mode_index = i;
+ schedule_work(&led_dat->work);
+
+exit_unlock:
write_unlock_irqrestore(&led_dat->rw_lock, flags);
}
@@ -148,7 +145,6 @@ static ssize_t ns2_led_sata_store(struct device *dev,
container_of(led_cdev, struct ns2_led_data, cdev);
int ret;
unsigned long enable;
- enum ns2_led_modes mode;
ret = kstrtoul(buff, 10, &enable);
if (ret < 0)
@@ -157,19 +153,19 @@ static ssize_t ns2_led_sata_store(struct device *dev,
enable = !!enable;
if (led_dat->sata == enable)
- return count;
+ goto exit;
- ret = ns2_led_get_mode(led_dat, &mode);
- if (ret < 0)
- return ret;
+ led_dat->sata = enable;
+
+ if (!led_get_brightness(led_cdev))
+ goto exit;
- if (enable && mode == NS_V2_LED_ON)
+ if (enable)
ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
- if (!enable && mode == NS_V2_LED_SATA)
+ else
ns2_led_set_mode(led_dat, NS_V2_LED_ON);
- led_dat->sata = enable;
-
+exit:
return count;
}
@@ -199,7 +195,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
enum ns2_led_modes mode;
ret = devm_gpio_request_one(&pdev->dev, template->cmd,
- gpio_get_value(template->cmd) ?
+ gpio_get_value_cansleep(template->cmd) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
template->name);
if (ret) {
@@ -209,7 +205,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
}
ret = devm_gpio_request_one(&pdev->dev, template->slow,
- gpio_get_value(template->slow) ?
+ gpio_get_value_cansleep(template->slow) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
template->name);
if (ret) {
@@ -228,6 +224,10 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
led_dat->cdev.groups = ns2_led_groups;
led_dat->cmd = template->cmd;
led_dat->slow = template->slow;
+ led_dat->can_sleep = gpio_cansleep(led_dat->cmd) |
+ gpio_cansleep(led_dat->slow);
+ led_dat->modval = template->modval;
+ led_dat->num_modes = template->num_modes;
ret = ns2_led_get_mode(led_dat, &mode);
if (ret < 0)
@@ -238,6 +238,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
led_dat->cdev.brightness =
(mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
+ INIT_WORK(&led_dat->work, ns2_led_work);
+
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0)
return ret;
@@ -248,6 +250,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
static void delete_ns2_led(struct ns2_led_data *led_dat)
{
led_classdev_unregister(&led_dat->cdev);
+ cancel_work_sync(&led_dat->work);
}
#ifdef CONFIG_OF_GPIO
@@ -259,9 +262,8 @@ ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
{
struct device_node *np = dev->of_node;
struct device_node *child;
- struct ns2_led *leds;
+ struct ns2_led *led, *leds;
int num_leds = 0;
- int i = 0;
num_leds = of_get_child_count(np);
if (!num_leds)
@@ -272,26 +274,57 @@ ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
if (!leds)
return -ENOMEM;
+ led = leds;
for_each_child_of_node(np, child) {
const char *string;
- int ret;
+ int ret, i, num_modes;
+ struct ns2_led_modval *modval;
ret = of_get_named_gpio(child, "cmd-gpio", 0);
if (ret < 0)
return ret;
- leds[i].cmd = ret;
+ led->cmd = ret;
ret = of_get_named_gpio(child, "slow-gpio", 0);
if (ret < 0)
return ret;
- leds[i].slow = ret;
+ led->slow = ret;
ret = of_property_read_string(child, "label", &string);
- leds[i].name = (ret == 0) ? string : child->name;
+ led->name = (ret == 0) ? string : child->name;
ret = of_property_read_string(child, "linux,default-trigger",
&string);
if (ret == 0)
- leds[i].default_trigger = string;
+ led->default_trigger = string;
+
+ ret = of_property_count_u32_elems(child, "modes-map");
+ if (ret < 0 || ret % 3) {
+ dev_err(dev,
+ "Missing or malformed modes-map property\n");
+ return -EINVAL;
+ }
+
+ num_modes = ret / 3;
+ modval = devm_kzalloc(dev,
+ num_modes * sizeof(struct ns2_led_modval),
+ GFP_KERNEL);
+ if (!modval)
+ return -ENOMEM;
+
+ for (i = 0; i < num_modes; i++) {
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i,
+ (u32 *) &modval[i].mode);
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i + 1,
+ (u32 *) &modval[i].cmd_level);
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i + 2,
+ (u32 *) &modval[i].slow_level);
+ }
+
+ led->num_modes = num_modes;
+ led->modval = modval;
- i++;
+ led++;
}
pdata->leds = leds;
@@ -304,6 +337,7 @@ static const struct of_device_id of_ns2_leds_match[] = {
{ .compatible = "lacie,ns2-leds", },
{},
};
+MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
#endif /* CONFIG_OF_GPIO */
struct ns2_led_priv {
diff --git a/kernel/drivers/leds/leds-ot200.c b/kernel/drivers/leds/leds-ot200.c
index 39870de20..12af1127d 100644
--- a/kernel/drivers/leds/leds-ot200.c
+++ b/kernel/drivers/leds/leds-ot200.c
@@ -124,9 +124,9 @@ static int ot200_led_probe(struct platform_device *pdev)
leds[i].cdev.name = leds[i].name;
leds[i].cdev.brightness_set = ot200_led_brightness_set;
- ret = led_classdev_register(&pdev->dev, &leds[i].cdev);
+ ret = devm_led_classdev_register(&pdev->dev, &leds[i].cdev);
if (ret < 0)
- goto err;
+ return ret;
}
leds_front = 0; /* turn off all front leds */
@@ -135,27 +135,10 @@ static int ot200_led_probe(struct platform_device *pdev)
outb(leds_back, 0x5a);
return 0;
-
-err:
- for (i = i - 1; i >= 0; i--)
- led_classdev_unregister(&leds[i].cdev);
-
- return ret;
-}
-
-static int ot200_led_remove(struct platform_device *pdev)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(leds); i++)
- led_classdev_unregister(&leds[i].cdev);
-
- return 0;
}
static struct platform_driver ot200_led_driver = {
.probe = ot200_led_probe,
- .remove = ot200_led_remove,
.driver = {
.name = "leds-ot200",
},
diff --git a/kernel/drivers/leds/leds-pca955x.c b/kernel/drivers/leds/leds-pca955x.c
index c3a08b605..b775e1efe 100644
--- a/kernel/drivers/leds/leds-pca955x.c
+++ b/kernel/drivers/leds/leds-pca955x.c
@@ -379,7 +379,6 @@ static int pca955x_remove(struct i2c_client *client)
static struct i2c_driver pca955x_driver = {
.driver = {
.name = "leds-pca955x",
- .owner = THIS_MODULE,
},
.probe = pca955x_probe,
.remove = pca955x_remove,
diff --git a/kernel/drivers/leds/leds-pca963x.c b/kernel/drivers/leds/leds-pca963x.c
index bee3e1ab2..41f269fe0 100644
--- a/kernel/drivers/leds/leds-pca963x.c
+++ b/kernel/drivers/leds/leds-pca963x.c
@@ -332,6 +332,7 @@ static const struct of_device_id of_pca963x_match[] = {
{ .compatible = "nxp,pca9635", },
{},
};
+MODULE_DEVICE_TABLE(of, of_pca963x_match);
#else
static struct pca963x_platform_data *
pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip)
@@ -458,7 +459,6 @@ static int pca963x_remove(struct i2c_client *client)
static struct i2c_driver pca963x_driver = {
.driver = {
.name = "leds-pca963x",
- .owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_pca963x_match),
},
.probe = pca963x_probe,
diff --git a/kernel/drivers/leds/leds-pm8941-wled.c b/kernel/drivers/leds/leds-pm8941-wled.c
deleted file mode 100644
index bf64a593f..000000000
--- a/kernel/drivers/leds/leds-pm8941-wled.c
+++ /dev/null
@@ -1,435 +0,0 @@
-/* Copyright (c) 2015, Sony Mobile Communications, AB.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 and
- * only version 2 as published by the Free Software Foundation.
- *
- * 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/kernel.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/regmap.h>
-
-#define PM8941_WLED_REG_VAL_BASE 0x40
-#define PM8941_WLED_REG_VAL_MAX 0xFFF
-
-#define PM8941_WLED_REG_MOD_EN 0x46
-#define PM8941_WLED_REG_MOD_EN_BIT BIT(7)
-#define PM8941_WLED_REG_MOD_EN_MASK BIT(7)
-
-#define PM8941_WLED_REG_SYNC 0x47
-#define PM8941_WLED_REG_SYNC_MASK 0x07
-#define PM8941_WLED_REG_SYNC_LED1 BIT(0)
-#define PM8941_WLED_REG_SYNC_LED2 BIT(1)
-#define PM8941_WLED_REG_SYNC_LED3 BIT(2)
-#define PM8941_WLED_REG_SYNC_ALL 0x07
-#define PM8941_WLED_REG_SYNC_CLEAR 0x00
-
-#define PM8941_WLED_REG_FREQ 0x4c
-#define PM8941_WLED_REG_FREQ_MASK 0x0f
-
-#define PM8941_WLED_REG_OVP 0x4d
-#define PM8941_WLED_REG_OVP_MASK 0x03
-
-#define PM8941_WLED_REG_BOOST 0x4e
-#define PM8941_WLED_REG_BOOST_MASK 0x07
-
-#define PM8941_WLED_REG_SINK 0x4f
-#define PM8941_WLED_REG_SINK_MASK 0xe0
-#define PM8941_WLED_REG_SINK_SHFT 0x05
-
-/* Per-'string' registers below */
-#define PM8941_WLED_REG_STR_OFFSET 0x10
-
-#define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60
-#define PM8941_WLED_REG_STR_MOD_MASK BIT(7)
-#define PM8941_WLED_REG_STR_MOD_EN BIT(7)
-
-#define PM8941_WLED_REG_STR_SCALE_BASE 0x62
-#define PM8941_WLED_REG_STR_SCALE_MASK 0x1f
-
-#define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63
-#define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01
-#define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00
-#define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01
-
-#define PM8941_WLED_REG_STR_CABC_BASE 0x66
-#define PM8941_WLED_REG_STR_CABC_MASK BIT(7)
-#define PM8941_WLED_REG_STR_CABC_EN BIT(7)
-
-struct pm8941_wled_config {
- u32 i_boost_limit;
- u32 ovp;
- u32 switch_freq;
- u32 num_strings;
- u32 i_limit;
- bool cs_out_en;
- bool ext_gen;
- bool cabc_en;
-};
-
-struct pm8941_wled {
- struct regmap *regmap;
- u16 addr;
-
- struct led_classdev cdev;
-
- struct pm8941_wled_config cfg;
-};
-
-static int pm8941_wled_set(struct led_classdev *cdev,
- enum led_brightness value)
-{
- struct pm8941_wled *wled;
- u8 ctrl = 0;
- u16 val;
- int rc;
- int i;
-
- wled = container_of(cdev, struct pm8941_wled, cdev);
-
- if (value != 0)
- ctrl = PM8941_WLED_REG_MOD_EN_BIT;
-
- val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_MOD_EN,
- PM8941_WLED_REG_MOD_EN_MASK, ctrl);
- if (rc)
- return rc;
-
- for (i = 0; i < wled->cfg.num_strings; ++i) {
- u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
-
- rc = regmap_bulk_write(wled->regmap,
- wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
- v, 2);
- if (rc)
- return rc;
- }
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SYNC,
- PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SYNC,
- PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
- return rc;
-}
-
-static void pm8941_wled_set_brightness(struct led_classdev *cdev,
- enum led_brightness value)
-{
- if (pm8941_wled_set(cdev, value)) {
- dev_err(cdev->dev, "Unable to set brightness\n");
- return;
- }
- cdev->brightness = value;
-}
-
-static int pm8941_wled_setup(struct pm8941_wled *wled)
-{
- int rc;
- int i;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_OVP,
- PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_BOOST,
- PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_FREQ,
- PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
- if (rc)
- return rc;
-
- if (wled->cfg.cs_out_en) {
- u8 all = (BIT(wled->cfg.num_strings) - 1)
- << PM8941_WLED_REG_SINK_SHFT;
-
- rc = regmap_update_bits(wled->regmap,
- wled->addr + PM8941_WLED_REG_SINK,
- PM8941_WLED_REG_SINK_MASK, all);
- if (rc)
- return rc;
- }
-
- for (i = 0; i < wled->cfg.num_strings; ++i) {
- u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
- PM8941_WLED_REG_STR_MOD_MASK,
- PM8941_WLED_REG_STR_MOD_EN);
- if (rc)
- return rc;
-
- if (wled->cfg.ext_gen) {
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
- PM8941_WLED_REG_STR_MOD_SRC_MASK,
- PM8941_WLED_REG_STR_MOD_SRC_EXT);
- if (rc)
- return rc;
- }
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_SCALE_BASE,
- PM8941_WLED_REG_STR_SCALE_MASK,
- wled->cfg.i_limit);
- if (rc)
- return rc;
-
- rc = regmap_update_bits(wled->regmap,
- addr + PM8941_WLED_REG_STR_CABC_BASE,
- PM8941_WLED_REG_STR_CABC_MASK,
- wled->cfg.cabc_en ?
- PM8941_WLED_REG_STR_CABC_EN : 0);
- if (rc)
- return rc;
- }
-
- return 0;
-}
-
-static const struct pm8941_wled_config pm8941_wled_config_defaults = {
- .i_boost_limit = 3,
- .i_limit = 20,
- .ovp = 2,
- .switch_freq = 5,
- .num_strings = 0,
- .cs_out_en = false,
- .ext_gen = false,
- .cabc_en = false,
-};
-
-struct pm8941_wled_var_cfg {
- const u32 *values;
- u32 (*fn)(u32);
- int size;
-};
-
-static const u32 pm8941_wled_i_boost_limit_values[] = {
- 105, 385, 525, 805, 980, 1260, 1400, 1680,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
- .values = pm8941_wled_i_boost_limit_values,
- .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
-};
-
-static const u32 pm8941_wled_ovp_values[] = {
- 35, 32, 29, 27,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
- .values = pm8941_wled_ovp_values,
- .size = ARRAY_SIZE(pm8941_wled_ovp_values),
-};
-
-static u32 pm8941_wled_num_strings_values_fn(u32 idx)
-{
- return idx + 1;
-}
-
-static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
- .fn = pm8941_wled_num_strings_values_fn,
- .size = 3,
-};
-
-static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
-{
- return 19200 / (2 * (1 + idx));
-}
-
-static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
- .fn = pm8941_wled_switch_freq_values_fn,
- .size = 16,
-};
-
-static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
- .size = 26,
-};
-
-static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
-{
- if (idx >= cfg->size)
- return UINT_MAX;
- if (cfg->fn)
- return cfg->fn(idx);
- if (cfg->values)
- return cfg->values[idx];
- return idx;
-}
-
-static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
-{
- struct pm8941_wled_config *cfg = &wled->cfg;
- u32 val;
- int rc;
- u32 c;
- int i;
- int j;
-
- const struct {
- const char *name;
- u32 *val_ptr;
- const struct pm8941_wled_var_cfg *cfg;
- } u32_opts[] = {
- {
- "qcom,current-boost-limit",
- &cfg->i_boost_limit,
- .cfg = &pm8941_wled_i_boost_limit_cfg,
- },
- {
- "qcom,current-limit",
- &cfg->i_limit,
- .cfg = &pm8941_wled_i_limit_cfg,
- },
- {
- "qcom,ovp",
- &cfg->ovp,
- .cfg = &pm8941_wled_ovp_cfg,
- },
- {
- "qcom,switching-freq",
- &cfg->switch_freq,
- .cfg = &pm8941_wled_switch_freq_cfg,
- },
- {
- "qcom,num-strings",
- &cfg->num_strings,
- .cfg = &pm8941_wled_num_strings_cfg,
- },
- };
- const struct {
- const char *name;
- bool *val_ptr;
- } bool_opts[] = {
- { "qcom,cs-out", &cfg->cs_out_en, },
- { "qcom,ext-gen", &cfg->ext_gen, },
- { "qcom,cabc", &cfg->cabc_en, },
- };
-
- rc = of_property_read_u32(dev->of_node, "reg", &val);
- if (rc || val > 0xffff) {
- dev_err(dev, "invalid IO resources\n");
- return rc ? rc : -EINVAL;
- }
- wled->addr = val;
-
- rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name);
- if (rc)
- wled->cdev.name = dev->of_node->name;
-
- wled->cdev.default_trigger = of_get_property(dev->of_node,
- "linux,default-trigger", NULL);
-
- *cfg = pm8941_wled_config_defaults;
- for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
- rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
- if (rc == -EINVAL) {
- continue;
- } else if (rc) {
- dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
- return rc;
- }
-
- c = UINT_MAX;
- for (j = 0; c != val; j++) {
- c = pm8941_wled_values(u32_opts[i].cfg, j);
- if (c == UINT_MAX) {
- dev_err(dev, "invalid value for '%s'\n",
- u32_opts[i].name);
- return -EINVAL;
- }
- }
-
- dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
- *u32_opts[i].val_ptr = j;
- }
-
- for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
- if (of_property_read_bool(dev->of_node, bool_opts[i].name))
- *bool_opts[i].val_ptr = true;
- }
-
- cfg->num_strings = cfg->num_strings + 1;
-
- return 0;
-}
-
-static int pm8941_wled_probe(struct platform_device *pdev)
-{
- struct pm8941_wled *wled;
- struct regmap *regmap;
- int rc;
-
- regmap = dev_get_regmap(pdev->dev.parent, NULL);
- if (!regmap) {
- dev_err(&pdev->dev, "Unable to get regmap\n");
- return -EINVAL;
- }
-
- wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
- if (!wled)
- return -ENOMEM;
-
- wled->regmap = regmap;
-
- rc = pm8941_wled_configure(wled, &pdev->dev);
- if (rc)
- return rc;
-
- rc = pm8941_wled_setup(wled);
- if (rc)
- return rc;
-
- wled->cdev.brightness_set = pm8941_wled_set_brightness;
-
- rc = devm_led_classdev_register(&pdev->dev, &wled->cdev);
- if (rc)
- return rc;
-
- platform_set_drvdata(pdev, wled);
-
- return 0;
-};
-
-static const struct of_device_id pm8941_wled_match_table[] = {
- { .compatible = "qcom,pm8941-wled" },
- {}
-};
-MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
-
-static struct platform_driver pm8941_wled_driver = {
- .probe = pm8941_wled_probe,
- .driver = {
- .name = "pm8941-wled",
- .of_match_table = pm8941_wled_match_table,
- },
-};
-
-module_platform_driver(pm8941_wled_driver);
-
-MODULE_DESCRIPTION("pm8941 wled driver");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:pm8941-wled");
diff --git a/kernel/drivers/leds/leds-powernv.c b/kernel/drivers/leds/leds-powernv.c
new file mode 100644
index 000000000..1e75e1fe9
--- /dev/null
+++ b/kernel/drivers/leds/leds-powernv.c
@@ -0,0 +1,349 @@
+/*
+ * PowerNV LED Driver
+ *
+ * Copyright IBM Corp. 2015
+ *
+ * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
+ * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
+ *
+ * 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.
+ */
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/opal.h>
+
+/* Map LED type to description. */
+struct led_type_map {
+ const int type;
+ const char *desc;
+};
+static const struct led_type_map led_type_map[] = {
+ {OPAL_SLOT_LED_TYPE_ID, "identify"},
+ {OPAL_SLOT_LED_TYPE_FAULT, "fault"},
+ {OPAL_SLOT_LED_TYPE_ATTN, "attention"},
+ {-1, NULL},
+};
+
+struct powernv_led_common {
+ /*
+ * By default unload path resets all the LEDs. But on PowerNV
+ * platform we want to retain LED state across reboot as these
+ * are controlled by firmware. Also service processor can modify
+ * the LEDs independent of OS. Hence avoid resetting LEDs in
+ * unload path.
+ */
+ bool led_disabled;
+
+ /* Max supported LED type */
+ __be64 max_led_type;
+
+ /* glabal lock */
+ struct mutex lock;
+};
+
+/* PowerNV LED data */
+struct powernv_led_data {
+ struct led_classdev cdev;
+ char *loc_code; /* LED location code */
+ int led_type; /* OPAL_SLOT_LED_TYPE_* */
+
+ struct powernv_led_common *common;
+};
+
+
+/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
+static int powernv_get_led_type(const char *led_type_desc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(led_type_map); i++)
+ if (!strcmp(led_type_map[i].desc, led_type_desc))
+ return led_type_map[i].type;
+
+ return -1;
+}
+
+/*
+ * This commits the state change of the requested LED through an OPAL call.
+ * This function is called from work queue task context when ever it gets
+ * scheduled. This function can sleep at opal_async_wait_response call.
+ */
+static void powernv_led_set(struct powernv_led_data *powernv_led,
+ enum led_brightness value)
+{
+ int rc, token;
+ u64 led_mask, led_value = 0;
+ __be64 max_type;
+ struct opal_msg msg;
+ struct device *dev = powernv_led->cdev.dev;
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+
+ /* Prepare for the OPAL call */
+ max_type = powernv_led_common->max_led_type;
+ led_mask = OPAL_SLOT_LED_STATE_ON << powernv_led->led_type;
+ if (value)
+ led_value = led_mask;
+
+ /* OPAL async call */
+ token = opal_async_get_token_interruptible();
+ if (token < 0) {
+ if (token != -ERESTARTSYS)
+ dev_err(dev, "%s: Couldn't get OPAL async token\n",
+ __func__);
+ return;
+ }
+
+ rc = opal_leds_set_ind(token, powernv_led->loc_code,
+ led_mask, led_value, &max_type);
+ if (rc != OPAL_ASYNC_COMPLETION) {
+ dev_err(dev, "%s: OPAL set LED call failed for %s [rc=%d]\n",
+ __func__, powernv_led->loc_code, rc);
+ goto out_token;
+ }
+
+ rc = opal_async_wait_response(token, &msg);
+ if (rc) {
+ dev_err(dev,
+ "%s: Failed to wait for the async response [rc=%d]\n",
+ __func__, rc);
+ goto out_token;
+ }
+
+ rc = be64_to_cpu(msg.params[1]);
+ if (rc != OPAL_SUCCESS)
+ dev_err(dev, "%s : OAPL async call returned failed [rc=%d]\n",
+ __func__, rc);
+
+out_token:
+ opal_async_release_token(token);
+}
+
+/*
+ * This function fetches the LED state for a given LED type for
+ * mentioned LED classdev structure.
+ */
+static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led)
+{
+ int rc;
+ __be64 mask, value, max_type;
+ u64 led_mask, led_value;
+ struct device *dev = powernv_led->cdev.dev;
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+
+ /* Fetch all LED status */
+ mask = cpu_to_be64(0);
+ value = cpu_to_be64(0);
+ max_type = powernv_led_common->max_led_type;
+
+ rc = opal_leds_get_ind(powernv_led->loc_code,
+ &mask, &value, &max_type);
+ if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) {
+ dev_err(dev, "%s: OPAL get led call failed [rc=%d]\n",
+ __func__, rc);
+ return LED_OFF;
+ }
+
+ led_mask = be64_to_cpu(mask);
+ led_value = be64_to_cpu(value);
+
+ /* LED status available */
+ if (!((led_mask >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)) {
+ dev_err(dev, "%s: LED status not available for %s\n",
+ __func__, powernv_led->cdev.name);
+ return LED_OFF;
+ }
+
+ /* LED status value */
+ if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)
+ return LED_FULL;
+
+ return LED_OFF;
+}
+
+/*
+ * LED classdev 'brightness_get' function. This schedules work
+ * to update LED state.
+ */
+static void powernv_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct powernv_led_data *powernv_led =
+ container_of(led_cdev, struct powernv_led_data, cdev);
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+
+ /* Do not modify LED in unload path */
+ if (powernv_led_common->led_disabled)
+ return;
+
+ mutex_lock(&powernv_led_common->lock);
+ powernv_led_set(powernv_led, value);
+ mutex_unlock(&powernv_led_common->lock);
+}
+
+/* LED classdev 'brightness_get' function */
+static enum led_brightness powernv_brightness_get(struct led_classdev *led_cdev)
+{
+ struct powernv_led_data *powernv_led =
+ container_of(led_cdev, struct powernv_led_data, cdev);
+
+ return powernv_led_get(powernv_led);
+}
+
+/*
+ * This function registers classdev structure for any given type of LED on
+ * a given child LED device node.
+ */
+static int powernv_led_create(struct device *dev,
+ struct powernv_led_data *powernv_led,
+ const char *led_type_desc)
+{
+ int rc;
+
+ /* Make sure LED type is supported */
+ powernv_led->led_type = powernv_get_led_type(led_type_desc);
+ if (powernv_led->led_type == -1) {
+ dev_warn(dev, "%s: No support for led type : %s\n",
+ __func__, led_type_desc);
+ return -EINVAL;
+ }
+
+ /* Create the name for classdev */
+ powernv_led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
+ powernv_led->loc_code,
+ led_type_desc);
+ if (!powernv_led->cdev.name) {
+ dev_err(dev,
+ "%s: Memory allocation failed for classdev name\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ powernv_led->cdev.brightness_set = powernv_brightness_set;
+ powernv_led->cdev.brightness_get = powernv_brightness_get;
+ powernv_led->cdev.brightness = LED_OFF;
+ powernv_led->cdev.max_brightness = LED_FULL;
+
+ /* Register the classdev */
+ rc = devm_led_classdev_register(dev, &powernv_led->cdev);
+ if (rc) {
+ dev_err(dev, "%s: Classdev registration failed for %s\n",
+ __func__, powernv_led->cdev.name);
+ }
+
+ return rc;
+}
+
+/* Go through LED device tree node and register LED classdev structure */
+static int powernv_led_classdev(struct platform_device *pdev,
+ struct device_node *led_node,
+ struct powernv_led_common *powernv_led_common)
+{
+ const char *cur = NULL;
+ int rc = -1;
+ struct property *p;
+ struct device_node *np;
+ struct powernv_led_data *powernv_led;
+ struct device *dev = &pdev->dev;
+
+ for_each_child_of_node(led_node, np) {
+ p = of_find_property(np, "led-types", NULL);
+ if (!p)
+ continue;
+
+ while ((cur = of_prop_next_string(p, cur)) != NULL) {
+ powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),
+ GFP_KERNEL);
+ if (!powernv_led) {
+ of_node_put(np);
+ return -ENOMEM;
+ }
+
+ powernv_led->common = powernv_led_common;
+ powernv_led->loc_code = (char *)np->name;
+
+ rc = powernv_led_create(dev, powernv_led, cur);
+ if (rc) {
+ of_node_put(np);
+ return rc;
+ }
+ } /* while end */
+ }
+
+ return rc;
+}
+
+/* Platform driver probe */
+static int powernv_led_probe(struct platform_device *pdev)
+{
+ struct device_node *led_node;
+ struct powernv_led_common *powernv_led_common;
+ struct device *dev = &pdev->dev;
+
+ led_node = of_find_node_by_path("/ibm,opal/leds");
+ if (!led_node) {
+ dev_err(dev, "%s: LED parent device node not found\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
+ GFP_KERNEL);
+ if (!powernv_led_common)
+ return -ENOMEM;
+
+ mutex_init(&powernv_led_common->lock);
+ powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);
+
+ platform_set_drvdata(pdev, powernv_led_common);
+
+ return powernv_led_classdev(pdev, led_node, powernv_led_common);
+}
+
+/* Platform driver remove */
+static int powernv_led_remove(struct platform_device *pdev)
+{
+ struct powernv_led_common *powernv_led_common;
+
+ /* Disable LED operation */
+ powernv_led_common = platform_get_drvdata(pdev);
+ powernv_led_common->led_disabled = true;
+
+ /* Destroy lock */
+ mutex_destroy(&powernv_led_common->lock);
+
+ dev_info(&pdev->dev, "PowerNV led module unregistered\n");
+ return 0;
+}
+
+/* Platform driver property match */
+static const struct of_device_id powernv_led_match[] = {
+ {
+ .compatible = "ibm,opal-v3-led",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, powernv_led_match);
+
+static struct platform_driver powernv_led_driver = {
+ .probe = powernv_led_probe,
+ .remove = powernv_led_remove,
+ .driver = {
+ .name = "powernv-led-driver",
+ .of_match_table = powernv_led_match,
+ },
+};
+
+module_platform_driver(powernv_led_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PowerNV LED driver");
+MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");
diff --git a/kernel/drivers/leds/leds-sead3.c b/kernel/drivers/leds/leds-sead3.c
new file mode 100644
index 000000000..eb97a3271
--- /dev/null
+++ b/kernel/drivers/leds/leds-sead3.c
@@ -0,0 +1,78 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
+ * Copyright (C) 2015 Imagination Technologies, Inc.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include <asm/mips-boards/sead3-addr.h>
+
+static void sead3_pled_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ writel(value, (void __iomem *)SEAD3_CPLD_P_LED);
+}
+
+static void sead3_fled_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ writel(value, (void __iomem *)SEAD3_CPLD_F_LED);
+}
+
+static struct led_classdev sead3_pled = {
+ .name = "sead3::pled",
+ .brightness_set = sead3_pled_set,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static struct led_classdev sead3_fled = {
+ .name = "sead3::fled",
+ .brightness_set = sead3_fled_set,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static int sead3_led_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = led_classdev_register(&pdev->dev, &sead3_pled);
+ if (ret < 0)
+ return ret;
+
+ ret = led_classdev_register(&pdev->dev, &sead3_fled);
+ if (ret < 0)
+ led_classdev_unregister(&sead3_pled);
+
+ return ret;
+}
+
+static int sead3_led_remove(struct platform_device *pdev)
+{
+ led_classdev_unregister(&sead3_pled);
+ led_classdev_unregister(&sead3_fled);
+
+ return 0;
+}
+
+static struct platform_driver sead3_led_driver = {
+ .probe = sead3_led_probe,
+ .remove = sead3_led_remove,
+ .driver = {
+ .name = "sead3-led",
+ },
+};
+
+module_platform_driver(sead3_led_driver);
+
+MODULE_AUTHOR("Kristian Kielhofner <kris@krisk.org>");
+MODULE_DESCRIPTION("SEAD3 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/kernel/drivers/leds/leds-syscon.c b/kernel/drivers/leds/leds-syscon.c
index 6896e2d9b..b88900d72 100644
--- a/kernel/drivers/leds/leds-syscon.c
+++ b/kernel/drivers/leds/leds-syscon.c
@@ -20,6 +20,7 @@
* MA 02111-1307 USA
*/
#include <linux/io.h>
+#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -66,102 +67,101 @@ static void syscon_led_set(struct led_classdev *led_cdev,
dev_err(sled->cdev.dev, "error updating LED status\n");
}
-static int __init syscon_leds_spawn(struct device_node *np,
- struct device *dev,
- struct regmap *map)
+static int syscon_led_probe(struct platform_device *pdev)
{
- struct device_node *child;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device *parent;
+ struct regmap *map;
+ struct syscon_led *sled;
+ const char *state;
int ret;
- for_each_available_child_of_node(np, child) {
- struct syscon_led *sled;
- const char *state;
-
- /* Only check for register-bit-leds */
- if (of_property_match_string(child, "compatible",
- "register-bit-led") < 0)
- continue;
-
- sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
- if (!sled)
- return -ENOMEM;
-
- sled->map = map;
-
- if (of_property_read_u32(child, "offset", &sled->offset))
- return -EINVAL;
- if (of_property_read_u32(child, "mask", &sled->mask))
- return -EINVAL;
- sled->cdev.name =
- of_get_property(child, "label", NULL) ? : child->name;
- sled->cdev.default_trigger =
- of_get_property(child, "linux,default-trigger", NULL);
-
- state = of_get_property(child, "default-state", NULL);
- if (state) {
- if (!strcmp(state, "keep")) {
- u32 val;
-
- ret = regmap_read(map, sled->offset, &val);
- if (ret < 0)
- return ret;
- sled->state = !!(val & sled->mask);
- } else if (!strcmp(state, "on")) {
- sled->state = true;
- ret = regmap_update_bits(map, sled->offset,
- sled->mask,
- sled->mask);
- if (ret < 0)
- return ret;
- } else {
- sled->state = false;
- ret = regmap_update_bits(map, sled->offset,
- sled->mask, 0);
- if (ret < 0)
- return ret;
- }
+ parent = dev->parent;
+ if (!parent) {
+ dev_err(dev, "no parent for syscon LED\n");
+ return -ENODEV;
+ }
+ map = syscon_node_to_regmap(parent->of_node);
+ if (IS_ERR(map)) {
+ dev_err(dev, "no regmap for syscon LED parent\n");
+ return PTR_ERR(map);
+ }
+
+ sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
+ if (!sled)
+ return -ENOMEM;
+
+ sled->map = map;
+
+ if (of_property_read_u32(np, "offset", &sled->offset))
+ return -EINVAL;
+ if (of_property_read_u32(np, "mask", &sled->mask))
+ return -EINVAL;
+ sled->cdev.name =
+ of_get_property(np, "label", NULL) ? : np->name;
+ sled->cdev.default_trigger =
+ of_get_property(np, "linux,default-trigger", NULL);
+
+ state = of_get_property(np, "default-state", NULL);
+ if (state) {
+ if (!strcmp(state, "keep")) {
+ u32 val;
+
+ ret = regmap_read(map, sled->offset, &val);
+ if (ret < 0)
+ return ret;
+ sled->state = !!(val & sled->mask);
+ } else if (!strcmp(state, "on")) {
+ sled->state = true;
+ ret = regmap_update_bits(map, sled->offset,
+ sled->mask,
+ sled->mask);
+ if (ret < 0)
+ return ret;
+ } else {
+ sled->state = false;
+ ret = regmap_update_bits(map, sled->offset,
+ sled->mask, 0);
+ if (ret < 0)
+ return ret;
}
- sled->cdev.brightness_set = syscon_led_set;
+ }
+ sled->cdev.brightness_set = syscon_led_set;
- ret = led_classdev_register(dev, &sled->cdev);
- if (ret < 0)
- return ret;
+ ret = led_classdev_register(dev, &sled->cdev);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, sled);
+ dev_info(dev, "registered LED %s\n", sled->cdev.name);
- dev_info(dev, "registered LED %s\n", sled->cdev.name);
- }
return 0;
}
-static int __init syscon_leds_init(void)
+static int syscon_led_remove(struct platform_device *pdev)
{
- struct device_node *np;
-
- for_each_of_allnodes(np) {
- struct platform_device *pdev;
- struct regmap *map;
- int ret;
+ struct syscon_led *sled = platform_get_drvdata(pdev);
- if (!of_device_is_compatible(np, "syscon"))
- continue;
+ led_classdev_unregister(&sled->cdev);
+ /* Turn it off */
+ regmap_update_bits(sled->map, sled->offset, sled->mask, 0);
+ return 0;
+}
- map = syscon_node_to_regmap(np);
- if (IS_ERR(map)) {
- pr_err("error getting regmap for syscon LEDs\n");
- continue;
- }
+static const struct of_device_id of_syscon_leds_match[] = {
+ { .compatible = "register-bit-led", },
+ {},
+};
- /*
- * If the map is there, the device should be there, we allocate
- * memory on the syscon device's behalf here.
- */
- pdev = of_find_device_by_node(np);
- if (!pdev)
- return -ENODEV;
- ret = syscon_leds_spawn(np, &pdev->dev, map);
- if (ret)
- dev_err(&pdev->dev, "could not spawn syscon LEDs\n");
- }
+MODULE_DEVICE_TABLE(of, of_syscon_leds_match);
- return 0;
-}
-device_initcall(syscon_leds_init);
+static struct platform_driver syscon_led_driver = {
+ .probe = syscon_led_probe,
+ .remove = syscon_led_remove,
+ .driver = {
+ .name = "leds-syscon",
+ .of_match_table = of_syscon_leds_match,
+ },
+};
+module_platform_driver(syscon_led_driver);
diff --git a/kernel/drivers/leds/leds-tca6507.c b/kernel/drivers/leds/leds-tca6507.c
index 20fa8e77f..edbecc4ca 100644
--- a/kernel/drivers/leds/leds-tca6507.c
+++ b/kernel/drivers/leds/leds-tca6507.c
@@ -735,6 +735,7 @@ static const struct of_device_id of_tca6507_leds_match[] = {
{ .compatible = "ti,tca6507", },
{},
};
+MODULE_DEVICE_TABLE(of, of_tca6507_leds_match);
#else
static struct tca6507_platform_data *
@@ -830,7 +831,6 @@ static int tca6507_remove(struct i2c_client *client)
static struct i2c_driver tca6507_driver = {
.driver = {
.name = "leds-tca6507",
- .owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_tca6507_leds_match),
},
.probe = tca6507_probe,
diff --git a/kernel/drivers/leds/leds-tlc591xx.c b/kernel/drivers/leds/leds-tlc591xx.c
new file mode 100644
index 000000000..b806eca83
--- /dev/null
+++ b/kernel/drivers/leds/leds-tlc591xx.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2014 Belkin Inc.
+ * Copyright 2015 Andrew Lunn <andrew@lunn.ch>
+ *
+ * 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; version 2 of the License.
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define TLC591XX_MAX_LEDS 16
+
+#define TLC591XX_REG_MODE1 0x00
+#define MODE1_RESPON_ADDR_MASK 0xF0
+#define MODE1_NORMAL_MODE (0 << 4)
+#define MODE1_SPEED_MODE (1 << 4)
+
+#define TLC591XX_REG_MODE2 0x01
+#define MODE2_DIM (0 << 5)
+#define MODE2_BLINK (1 << 5)
+#define MODE2_OCH_STOP (0 << 3)
+#define MODE2_OCH_ACK (1 << 3)
+
+#define TLC591XX_REG_PWM(x) (0x02 + (x))
+
+#define TLC591XX_REG_GRPPWM 0x12
+#define TLC591XX_REG_GRPFREQ 0x13
+
+/* LED Driver Output State, determine the source that drives LED outputs */
+#define LEDOUT_OFF 0x0 /* Output LOW */
+#define LEDOUT_ON 0x1 /* Output HI-Z */
+#define LEDOUT_DIM 0x2 /* Dimming */
+#define LEDOUT_BLINK 0x3 /* Blinking */
+#define LEDOUT_MASK 0x3
+
+#define ldev_to_led(c) container_of(c, struct tlc591xx_led, ldev)
+#define work_to_led(work) container_of(work, struct tlc591xx_led, work)
+
+struct tlc591xx_led {
+ bool active;
+ unsigned int led_no;
+ struct led_classdev ldev;
+ struct work_struct work;
+ struct tlc591xx_priv *priv;
+};
+
+struct tlc591xx_priv {
+ struct tlc591xx_led leds[TLC591XX_MAX_LEDS];
+ struct regmap *regmap;
+ unsigned int reg_ledout_offset;
+};
+
+struct tlc591xx {
+ unsigned int max_leds;
+ unsigned int reg_ledout_offset;
+};
+
+static const struct tlc591xx tlc59116 = {
+ .max_leds = 16,
+ .reg_ledout_offset = 0x14,
+};
+
+static const struct tlc591xx tlc59108 = {
+ .max_leds = 8,
+ .reg_ledout_offset = 0x0c,
+};
+
+static int
+tlc591xx_set_mode(struct regmap *regmap, u8 mode)
+{
+ int err;
+ u8 val;
+
+ err = regmap_write(regmap, TLC591XX_REG_MODE1, MODE1_NORMAL_MODE);
+ if (err)
+ return err;
+
+ val = MODE2_OCH_STOP | mode;
+
+ return regmap_write(regmap, TLC591XX_REG_MODE2, val);
+}
+
+static int
+tlc591xx_set_ledout(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+ u8 val)
+{
+ unsigned int i = (led->led_no % 4) * 2;
+ unsigned int mask = LEDOUT_MASK << i;
+ unsigned int addr = priv->reg_ledout_offset + (led->led_no >> 2);
+
+ val = val << i;
+
+ return regmap_update_bits(priv->regmap, addr, mask, val);
+}
+
+static int
+tlc591xx_set_pwm(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+ u8 brightness)
+{
+ u8 pwm = TLC591XX_REG_PWM(led->led_no);
+
+ return regmap_write(priv->regmap, pwm, brightness);
+}
+
+static void
+tlc591xx_led_work(struct work_struct *work)
+{
+ struct tlc591xx_led *led = work_to_led(work);
+ struct tlc591xx_priv *priv = led->priv;
+ enum led_brightness brightness = led->ldev.brightness;
+ int err;
+
+ switch (brightness) {
+ case 0:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_OFF);
+ break;
+ case LED_FULL:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_ON);
+ break;
+ default:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_DIM);
+ if (!err)
+ err = tlc591xx_set_pwm(priv, led, brightness);
+ }
+
+ if (err)
+ dev_err(led->ldev.dev, "Failed setting brightness\n");
+}
+
+static void
+tlc591xx_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct tlc591xx_led *led = ldev_to_led(led_cdev);
+
+ led->ldev.brightness = brightness;
+ schedule_work(&led->work);
+}
+
+static void
+tlc591xx_destroy_devices(struct tlc591xx_priv *priv, unsigned int j)
+{
+ int i = j;
+
+ while (--i >= 0) {
+ if (priv->leds[i].active) {
+ led_classdev_unregister(&priv->leds[i].ldev);
+ cancel_work_sync(&priv->leds[i].work);
+ }
+ }
+}
+
+static int
+tlc591xx_configure(struct device *dev,
+ struct tlc591xx_priv *priv,
+ const struct tlc591xx *tlc591xx)
+{
+ unsigned int i;
+ int err = 0;
+
+ tlc591xx_set_mode(priv->regmap, MODE2_DIM);
+ for (i = 0; i < TLC591XX_MAX_LEDS; i++) {
+ struct tlc591xx_led *led = &priv->leds[i];
+
+ if (!led->active)
+ continue;
+
+ led->priv = priv;
+ led->led_no = i;
+ led->ldev.brightness_set = tlc591xx_brightness_set;
+ led->ldev.max_brightness = LED_FULL;
+ INIT_WORK(&led->work, tlc591xx_led_work);
+ err = led_classdev_register(dev, &led->ldev);
+ if (err < 0) {
+ dev_err(dev, "couldn't register LED %s\n",
+ led->ldev.name);
+ goto exit;
+ }
+ }
+
+ return 0;
+
+exit:
+ tlc591xx_destroy_devices(priv, i);
+ return err;
+}
+
+static const struct regmap_config tlc591xx_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x1e,
+};
+
+static const struct of_device_id of_tlc591xx_leds_match[] = {
+ { .compatible = "ti,tlc59116",
+ .data = &tlc59116 },
+ { .compatible = "ti,tlc59108",
+ .data = &tlc59108 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
+
+static int
+tlc591xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device_node *np = client->dev.of_node, *child;
+ struct device *dev = &client->dev;
+ const struct of_device_id *match;
+ const struct tlc591xx *tlc591xx;
+ struct tlc591xx_priv *priv;
+ int err, count, reg;
+
+ match = of_match_device(of_tlc591xx_leds_match, dev);
+ if (!match)
+ return -ENODEV;
+
+ tlc591xx = match->data;
+ if (!np)
+ return -ENODEV;
+
+ count = of_get_child_count(np);
+ if (!count || count > tlc591xx->max_leds)
+ return -EINVAL;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(client, &tlc591xx_regmap);
+ if (IS_ERR(priv->regmap)) {
+ err = PTR_ERR(priv->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", err);
+ return err;
+ }
+ priv->reg_ledout_offset = tlc591xx->reg_ledout_offset;
+
+ i2c_set_clientdata(client, priv);
+
+ for_each_child_of_node(np, child) {
+ err = of_property_read_u32(child, "reg", &reg);
+ if (err)
+ return err;
+ if (reg < 0 || reg >= tlc591xx->max_leds)
+ return -EINVAL;
+ if (priv->leds[reg].active)
+ return -EINVAL;
+ priv->leds[reg].active = true;
+ priv->leds[reg].ldev.name =
+ of_get_property(child, "label", NULL) ? : child->name;
+ priv->leds[reg].ldev.default_trigger =
+ of_get_property(child, "linux,default-trigger", NULL);
+ }
+ return tlc591xx_configure(dev, priv, tlc591xx);
+}
+
+static int
+tlc591xx_remove(struct i2c_client *client)
+{
+ struct tlc591xx_priv *priv = i2c_get_clientdata(client);
+
+ tlc591xx_destroy_devices(priv, TLC591XX_MAX_LEDS);
+
+ return 0;
+}
+
+static const struct i2c_device_id tlc591xx_id[] = {
+ { "tlc59116" },
+ { "tlc59108" },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, tlc591xx_id);
+
+static struct i2c_driver tlc591xx_driver = {
+ .driver = {
+ .name = "tlc591xx",
+ .of_match_table = of_match_ptr(of_tlc591xx_leds_match),
+ },
+ .probe = tlc591xx_probe,
+ .remove = tlc591xx_remove,
+ .id_table = tlc591xx_id,
+};
+
+module_i2c_driver(tlc591xx_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TLC591XX LED driver");
diff --git a/kernel/drivers/leds/leds-wrap.c b/kernel/drivers/leds/leds-wrap.c
index 1ba3defdd..473fb6b97 100644
--- a/kernel/drivers/leds/leds-wrap.c
+++ b/kernel/drivers/leds/leds-wrap.c
@@ -76,39 +76,19 @@ static int wrap_led_probe(struct platform_device *pdev)
{
int ret;
- ret = led_classdev_register(&pdev->dev, &wrap_power_led);
+ ret = devm_led_classdev_register(&pdev->dev, &wrap_power_led);
if (ret < 0)
return ret;
- ret = led_classdev_register(&pdev->dev, &wrap_error_led);
+ ret = devm_led_classdev_register(&pdev->dev, &wrap_error_led);
if (ret < 0)
- goto err1;
-
- ret = led_classdev_register(&pdev->dev, &wrap_extra_led);
- if (ret < 0)
- goto err2;
-
- return ret;
-
-err2:
- led_classdev_unregister(&wrap_error_led);
-err1:
- led_classdev_unregister(&wrap_power_led);
-
- return ret;
-}
+ return ret;
-static int wrap_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&wrap_power_led);
- led_classdev_unregister(&wrap_error_led);
- led_classdev_unregister(&wrap_extra_led);
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &wrap_extra_led);
}
static struct platform_driver wrap_led_driver = {
.probe = wrap_led_probe,
- .remove = wrap_led_remove,
.driver = {
.name = DRVNAME,
},
diff --git a/kernel/drivers/leds/leds.h b/kernel/drivers/leds/leds.h
index 79efe57c7..4238fbc31 100644
--- a/kernel/drivers/leds/leds.h
+++ b/kernel/drivers/leds/leds.h
@@ -13,7 +13,6 @@
#ifndef __LEDS_H_INCLUDED
#define __LEDS_H_INCLUDED
-#include <linux/device.h>
#include <linux/rwsem.h>
#include <linux/leds.h>
@@ -45,32 +44,10 @@ static inline int led_get_brightness(struct led_classdev *led_cdev)
return led_cdev->brightness;
}
+void led_init_core(struct led_classdev *led_cdev);
void led_stop_software_blink(struct led_classdev *led_cdev);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
-#ifdef CONFIG_LEDS_TRIGGERS
-void led_trigger_set_default(struct led_classdev *led_cdev);
-void led_trigger_set(struct led_classdev *led_cdev,
- struct led_trigger *trigger);
-void led_trigger_remove(struct led_classdev *led_cdev);
-
-static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
-{
- return led_cdev->trigger_data;
-}
-
-#else
-#define led_trigger_set_default(x) do {} while (0)
-#define led_trigger_set(x, y) do {} while (0)
-#define led_trigger_remove(x) do {} while (0)
-#define led_get_trigger_data(x) (NULL)
-#endif
-
-ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count);
-ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
- char *buf);
-
#endif /* __LEDS_H_INCLUDED */
diff --git a/kernel/drivers/leds/trigger/Kconfig b/kernel/drivers/leds/trigger/Kconfig
index 3d7245d6b..d6286584c 100644
--- a/kernel/drivers/leds/trigger/Kconfig
+++ b/kernel/drivers/leds/trigger/Kconfig
@@ -72,7 +72,7 @@ config LEDS_TRIGGER_CPU
config LEDS_TRIGGER_GPIO
tristate "LED GPIO Trigger"
depends on LEDS_TRIGGERS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This allows LEDs to be controlled by gpio events. It's good
when using gpios as switches and triggering the needed LEDs
diff --git a/kernel/drivers/leds/trigger/ledtrig-heartbeat.c b/kernel/drivers/leds/trigger/ledtrig-heartbeat.c
index fea6871d2..8622ce651 100644
--- a/kernel/drivers/leds/trigger/ledtrig-heartbeat.c
+++ b/kernel/drivers/leds/trigger/ledtrig-heartbeat.c
@@ -27,6 +27,7 @@ struct heartbeat_trig_data {
unsigned int phase;
unsigned int period;
struct timer_list timer;
+ unsigned int invert;
};
static void led_heartbeat_function(unsigned long data)
@@ -56,21 +57,27 @@ static void led_heartbeat_function(unsigned long data)
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
- brightness = led_cdev->max_brightness;
+ if (!heartbeat_data->invert)
+ brightness = led_cdev->max_brightness;
break;
case 1:
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
heartbeat_data->phase++;
+ if (heartbeat_data->invert)
+ brightness = led_cdev->max_brightness;
break;
case 2:
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
- brightness = led_cdev->max_brightness;
+ if (!heartbeat_data->invert)
+ brightness = led_cdev->max_brightness;
break;
default:
delay = heartbeat_data->period - heartbeat_data->period / 4 -
msecs_to_jiffies(70);
heartbeat_data->phase = 0;
+ if (heartbeat_data->invert)
+ brightness = led_cdev->max_brightness;
break;
}
@@ -78,15 +85,50 @@ static void led_heartbeat_function(unsigned long data)
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
+static ssize_t led_invert_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
+
+ return sprintf(buf, "%u\n", heartbeat_data->invert);
+}
+
+static ssize_t led_invert_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ heartbeat_data->invert = !!state;
+
+ return size;
+}
+
+static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
+
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data;
+ int rc;
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
if (!heartbeat_data)
return;
led_cdev->trigger_data = heartbeat_data;
+ rc = device_create_file(led_cdev->dev, &dev_attr_invert);
+ if (rc) {
+ kfree(led_cdev->trigger_data);
+ return;
+ }
+
setup_timer(&heartbeat_data->timer,
led_heartbeat_function, (unsigned long) led_cdev);
heartbeat_data->phase = 0;
@@ -100,6 +142,7 @@ static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
if (led_cdev->activated) {
del_timer_sync(&heartbeat_data->timer);
+ device_remove_file(led_cdev->dev, &dev_attr_invert);
kfree(heartbeat_data);
led_cdev->activated = false;
}