mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 11:11:45 -04:00
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:
commit
13d45f79a2
31 changed files with 4939 additions and 58 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
576
drivers/leds/leds-aat1290.c
Normal 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
413
drivers/leds/leds-bcm6328.c
Normal 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", ®))
|
||||
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
243
drivers/leds/leds-bcm6358.c
Normal 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", ®))
|
||||
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");
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
443
drivers/leds/leds-ktd2692.c
Normal 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");
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
1097
drivers/leds/leds-max77693.c
Normal file
File diff suppressed because it is too large
Load diff
300
drivers/leds/leds-tlc591xx.c
Normal file
300
drivers/leds/leds-tlc591xx.c
Normal 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", ®);
|
||||
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");
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Reference in a new issue