Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds

Pull LED subsystem updates from Bryan Wu:
 "In this cycle, we finished to merge patches for LED Flash class
  driver.

  Other than that we have some bug fixes and new drivers for LED
  controllers"

* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds: (33 commits)
  leds:lp55xx: fix firmware loading error
  leds: fix max77693-led build errors
  leds: fix aat1290 build errors
  leds: aat1290: pass flags parameter to devm_gpiod_get
  leds: ktd2692: pass flags parameter to devm_gpiod_get
  drivers/leds: don't use module_init in non-modular leds-cobalt-raq.c
  leds: aat1290: add support for V4L2 Flash sub-device
  DT: aat1290: Document handling external strobe sources
  leds: max77693: add support for V4L2 Flash sub-device
  media: Add registration helpers for V4L2 flash sub-devices
  v4l: async: Add a pointer to of_node to struct v4l2_subdev, match it
  Documentation: leds: Add description of v4l2-flash sub-device
  leds: add BCM6358 LED driver
  leds: add DT binding for BCM6358 LED controller
  leds: fix brightness changing when software blinking is active
  Documentation: leds-lp5523: describe master fader attributes
  leds: lp5523: add master_fader support
  leds: leds-gpio: Allow compile test if !GPIOLIB
  leds: leds-gpio: Add missing #include <linux/of.h>
  gpiolib: Add missing dummies for the unified device properties interface
  ...
This commit is contained in:
Linus Torvalds 2015-07-01 19:09:11 -07:00
commit 13d45f79a2
31 changed files with 4939 additions and 58 deletions

View file

@ -39,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
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
@ -179,7 +205,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
@ -203,6 +229,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_FALLBACK
help
This option supports common operations for LP5521/5523/55231/5562/8501
devices.
@ -464,6 +491,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
@ -495,6 +541,15 @@ 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 && GPIOLIB && OF
help
This option enables support for KTD2692 LED flash connected
through ExpressWire interface.
Say Y to enable this driver.
comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
config LEDS_BLINKM

View file

@ -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,6 +56,7 @@ 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
@ -59,6 +64,7 @@ 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
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o

View file

@ -121,6 +121,11 @@ static void led_timer_function(unsigned long data)
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 {

View file

@ -119,10 +119,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;
}

576
drivers/leds/leds-aat1290.c Normal file
View file

@ -0,0 +1,576 @@
/*
* 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;
}
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" },
{},
};
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 <[email protected]>");
MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
MODULE_LICENSE("GPL v2");

413
drivers/leds/leds-bcm6328.c Normal file
View file

@ -0,0 +1,413 @@
/*
* 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_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);
if (!of_property_read_string(nc, "default-state", &state)) {
spin_lock_irqsave(lock, flags);
if (!strcmp(state, "on")) {
led->cdev.brightness = LED_FULL;
bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
} 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) >> (shift % 16);
val &= BCM6328_LED_MODE_MASK;
if (val == BCM6328_LED_MODE_ON)
led->cdev.brightness = LED_FULL;
else {
led->cdev.brightness = LED_OFF;
bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
}
} else {
led->cdev.brightness = LED_OFF;
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_SERIAL_LED_EN;
if (of_property_read_bool(np, "brcm,serial-leds"))
val |= BCM6328_SERIAL_LED_EN;
bcm6328_led_write(mem + BCM6328_REG_INIT, val);
for_each_available_child_of_node(np, child) {
int rc;
u32 reg;
if (of_property_read_u32(child, "reg", &reg))
continue;
if (reg >= BCM6328_LED_MAX_COUNT) {
dev_err(dev, "invalid LED (>= %d)\n",
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)
return rc;
}
return 0;
}
static const struct of_device_id bcm6328_leds_of_match[] = {
{ .compatible = "brcm,bcm6328-leds", },
{ },
};
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 <[email protected]>");
MODULE_AUTHOR("Jonas Gorski <[email protected]>");
MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-bcm6328");

243
drivers/leds/leds-bcm6358.c Normal file
View file

@ -0,0 +1,243 @@
/*
* Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
*
* Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#define BCM6358_REG_MODE 0x0
#define BCM6358_REG_CTRL 0x4
#define BCM6358_SLED_CLKDIV_MASK 3
#define BCM6358_SLED_CLKDIV_1 0
#define BCM6358_SLED_CLKDIV_2 1
#define BCM6358_SLED_CLKDIV_4 2
#define BCM6358_SLED_CLKDIV_8 3
#define BCM6358_SLED_POLARITY BIT(2)
#define BCM6358_SLED_BUSY BIT(3)
#define BCM6358_SLED_MAX_COUNT 32
#define BCM6358_SLED_WAIT 100
/**
* struct bcm6358_led - state container for bcm6358 based LEDs
* @cdev: LED class device for this LED
* @mem: memory resource
* @lock: memory lock
* @pin: LED pin number
* @active_low: LED is active low
*/
struct bcm6358_led {
struct led_classdev cdev;
void __iomem *mem;
spinlock_t *lock;
unsigned long pin;
bool active_low;
};
static void bcm6358_led_write(void __iomem *reg, unsigned long data)
{
iowrite32be(data, reg);
}
static unsigned long bcm6358_led_read(void __iomem *reg)
{
return ioread32be(reg);
}
static unsigned long bcm6358_led_busy(void __iomem *mem)
{
unsigned long val;
while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
BCM6358_SLED_BUSY)
udelay(BCM6358_SLED_WAIT);
return val;
}
static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value)
{
unsigned long val;
bcm6358_led_busy(led->mem);
val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
if ((led->active_low && value == LED_OFF) ||
(!led->active_low && value != LED_OFF))
val |= BIT(led->pin);
else
val &= ~(BIT(led->pin));
bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
}
static void bcm6358_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct bcm6358_led *led =
container_of(led_cdev, struct bcm6358_led, cdev);
unsigned long flags;
spin_lock_irqsave(led->lock, flags);
bcm6358_led_mode(led, value);
spin_unlock_irqrestore(led->lock, flags);
}
static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
void __iomem *mem, spinlock_t *lock)
{
struct bcm6358_led *led;
unsigned long flags;
const char *state;
int rc;
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
led->pin = reg;
led->mem = mem;
led->lock = lock;
if (of_property_read_bool(nc, "active-low"))
led->active_low = true;
led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
led->cdev.default_trigger = of_get_property(nc,
"linux,default-trigger",
NULL);
spin_lock_irqsave(lock, flags);
if (!of_property_read_string(nc, "default-state", &state)) {
if (!strcmp(state, "on")) {
led->cdev.brightness = LED_FULL;
} else if (!strcmp(state, "keep")) {
unsigned long val;
bcm6358_led_busy(led->mem);
val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
val &= BIT(led->pin);
if ((led->active_low && !val) ||
(!led->active_low && val))
led->cdev.brightness = LED_FULL;
else
led->cdev.brightness = LED_OFF;
} else {
led->cdev.brightness = LED_OFF;
}
} else {
led->cdev.brightness = LED_OFF;
}
bcm6358_led_mode(led, led->cdev.brightness);
spin_unlock_irqrestore(lock, flags);
led->cdev.brightness_set = bcm6358_led_set;
rc = led_classdev_register(dev, &led->cdev);
if (rc < 0)
return rc;
dev_dbg(dev, "registered LED %s\n", led->cdev.name);
return 0;
}
static int bcm6358_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct device_node *child;
struct resource *mem_r;
void __iomem *mem;
spinlock_t *lock; /* memory lock */
unsigned long val;
u32 clk_div;
mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_r)
return -EINVAL;
mem = devm_ioremap_resource(dev, mem_r);
if (IS_ERR(mem))
return PTR_ERR(mem);
lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
if (!lock)
return -ENOMEM;
spin_lock_init(lock);
val = bcm6358_led_busy(mem);
val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
if (of_property_read_bool(np, "brcm,clk-dat-low"))
val |= BCM6358_SLED_POLARITY;
of_property_read_u32(np, "brcm,clk-div", &clk_div);
switch (clk_div) {
case 8:
val |= BCM6358_SLED_CLKDIV_8;
break;
case 4:
val |= BCM6358_SLED_CLKDIV_4;
break;
case 2:
val |= BCM6358_SLED_CLKDIV_2;
break;
default:
val |= BCM6358_SLED_CLKDIV_1;
break;
}
bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
for_each_available_child_of_node(np, child) {
int rc;
u32 reg;
if (of_property_read_u32(child, "reg", &reg))
continue;
if (reg >= BCM6358_SLED_MAX_COUNT) {
dev_err(dev, "invalid LED (%u >= %d)\n", reg,
BCM6358_SLED_MAX_COUNT);
continue;
}
rc = bcm6358_led(dev, child, reg, mem, lock);
if (rc < 0)
return rc;
}
return 0;
}
static const struct of_device_id bcm6358_leds_of_match[] = {
{ .compatible = "brcm,bcm6358-leds", },
{ },
};
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 <[email protected]>");
MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-bcm6358");

View file

@ -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);

View file

@ -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>
@ -198,8 +199,10 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
} 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);
}

443
drivers/leds/leds-ktd2692.c Normal file
View file

@ -0,0 +1,443 @@
/*
* 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 */ },
};
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 <[email protected]>");
MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
MODULE_LICENSE("GPL v2");

View file

@ -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,
};

View file

@ -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);
}

1097
drivers/leds/leds-max77693.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,300 @@
/*
* 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;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->regmap = devm_regmap_init_i2c(client, &tlc591xx_regmap);
if (IS_ERR(priv->regmap)) {
err = PTR_ERR(priv->regmap);
dev_err(dev, "Failed to allocate register map: %d\n", err);
return err;
}
priv->reg_ledout_offset = tlc591xx->reg_ledout_offset;
i2c_set_clientdata(client, priv);
for_each_child_of_node(np, child) {
err = of_property_read_u32(child, "reg", &reg);
if (err)
return err;
if (reg < 0 || reg >= tlc591xx->max_leds)
return -EINVAL;
if (priv->leds[reg].active)
return -EINVAL;
priv->leds[reg].active = true;
priv->leds[reg].ldev.name =
of_get_property(child, "label", NULL) ? : child->name;
priv->leds[reg].ldev.default_trigger =
of_get_property(child, "linux,default-trigger", NULL);
}
return tlc591xx_configure(dev, priv, tlc591xx);
}
static int
tlc591xx_remove(struct i2c_client *client)
{
struct tlc591xx_priv *priv = i2c_get_clientdata(client);
tlc591xx_destroy_devices(priv, TLC591XX_MAX_LEDS);
return 0;
}
static const struct i2c_device_id tlc591xx_id[] = {
{ "tlc59116" },
{ "tlc59108" },
{},
};
MODULE_DEVICE_TABLE(i2c, tlc591xx_id);
static struct i2c_driver tlc591xx_driver = {
.driver = {
.name = "tlc591xx",
.of_match_table = of_match_ptr(of_tlc591xx_leds_match),
},
.probe = tlc591xx_probe,
.remove = tlc591xx_remove,
.id_table = tlc591xx_id,
};
module_i2c_driver(tlc591xx_driver);
MODULE_AUTHOR("Andrew Lunn <[email protected]>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TLC591XX LED driver");

View file

@ -13,7 +13,6 @@
#ifndef __LEDS_H_INCLUDED
#define __LEDS_H_INCLUDED
#include <linux/device.h>
#include <linux/rwsem.h>
#include <linux/leds.h>
@ -50,27 +49,4 @@ 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 */