diff options
Diffstat (limited to 'kernel/drivers/leds')
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", ®)) + 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", ®)) + 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, µ_led); + ret = devm_led_classdev_register(&pdev->dev, µ_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(µ_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", ®); + 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; } |