mirror of
https://github.com/torvalds/linux.git
synced 2026-01-12 00:42:35 +08:00
Merge tag 'leds-next-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones:
"LED Triggers:
- Allow writing "default" to the sysfs 'trigger' attribute to set an
LED to its default trigger
- If the default trigger is "none", writing "default" will remove the
current trigger
- Updated sysfs ABI documentation for the new "default" trigger
functionality
LED KUnit Testing:
- Provide a skeleton KUnit test suite for the LEDs framework
- Expand the LED class device registration KUnit test to cover more
scenarios, including 'brightness_get' behavior
- Add KUnit tests for the LED lookup and get API ('led_add_lookup',
'devm_led_get')
LED Flash Class:
- Add support for setting flash/strobe duration through a new
'duration_set' op and 'led_set_flash_duration()' function, aligning
with 'V4L2_CID_FLASH_DURATION'
Texas Instruments TPS6131x:
- Add a new driver for the TPS61310/TPS61311 flash LED controllers
- The driver supports the device's three constant-current sinks for
flash and torch modes
LED Core:
- Prevent potential 'snprintf()' truncations in LED names by checking
for buffer overflows
ChromeOS EC LEDs:
- Avoid a -Wflex-array-member-not-at-end GCC warning by replacing an
on-stack flexible structure definition with a utility function call
Multicolor LEDs:
- Fix issue where setting multi_intensity while software blinking is
active could stop blinking
PCA955x LEDs:
- Avoid potential buffer overflow when creating default labels by
changing a field's type to 'u8' and updating format specifiers
PCA995x LEDs:
- Fix a typo (stray space) in an 'of_device_id' entry in the
'pca995x_of_match' table
Kconfig:
- Prevent LED drivers from being enabled by default when
'COMPILE_TEST' is set
Device Property API:
- Split 'device_get_child_node_count()' into a new helper
'fwnode_get_child_node_count()' that doesn't require a device
struct, making the API more symmetrical
Driver Modernization (using 'fwnode_get_child_node_count()'):
- Update 'leds-pwm-multicolor', 'leds-ncp5623' and 'leds-ncp5623' to
use the new 'fwnode_get_child_node_count()' helper, removing their
custom implementation
- As above in the USB Type-C TCPM driver
Driver Modernization (using new GPIO setter callbacks):
- Convert 'leds-lgm-sso' to use new GPIO line value setter callbacks
which return an integer for error handling
- Convert 'leds-pca955x', 'leds-pca9532' and 'leds-tca6507' to use
new GPIO setter callbacks
Documentation:
- Remove the '.rst' extension for 'leds-st1202' in the documentation
index for consistency
LP8860 LEDs:
- Use 'regmap_multi_reg_write()' for EEPROM writes instead of manual
looping
- Use scoped mutex guards and 'devm_mutex_init()' to simplify
function exits and ensure automatic cleanup
- Remove default register definitions that are unused when regmap
caching is not active
- Use 'devm_regulator_get_enable_optional()' to handle the optional
regulator, simplifying enabling and removing manual disabling
- Refactor 'lp8860_unlock_eeprom()' to only perform the unlock
operation, removing the lock part and an unnecessary parameter
- Use a 'devm' action to disable the enable-GPIO, simplifying cleanup
and error paths, and remove the now-empty '.remove()' function
Turris Omnia LEDs:
- Drop unnecessary commas in terminator entries of 'struct attribute'
and 'struct of_device_id' arrays
MT6370 RGB LEDs:
- Use the 'LINEAR_RANGE()' for defining 'struct linear_range' entries
to improve robustness
Texas Instruments TPS6131x:
- Add new devicetree bindings for the TI TPS61310/TPS61311 flash LED
driver"
* tag 'leds-next-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (31 commits)
leds: tps6131x: Add support for Texas Instruments TPS6131X flash LED driver
dt-bindings: leds: Add Texas Instruments TPS6131x flash LED driver
leds: flash: Add support for flash/strobe duration
leds: rgb: leds-mt6370-rgb: Improve definition of some struct linear_range
leds: led-test: Provide tests for the lookup and get infrastructure
leds: led-test: Fill out the registration test to cover more test cases
leds: led-test: Remove standard error checking after KUNIT_ASSERT_*()
leds: pca995x: Fix typo in pca995x_of_match's of_device_id entry
leds: Provide skeleton KUnit testing for the LEDs framework
leds: tca6507: Use new GPIO line value setter callbacks
leds: pca9532: Use new GPIO line value setter callbacks
leds: pca955x: Use new GPIO line value setter callbacks
leds: lgm-sso: Use new GPIO line value setter callbacks
leds: Do not enable by default during compile testing
leds: turris-omnia: Drop commas in the terminator entries
leds: lp8860: Disable GPIO with devm action
leds: lp8860: Only unlock in lp8860_unlock_eeprom()
leds: lp8860: Enable regulator using enable_optional helper
leds: lp8860: Remove default regs when not caching
leds: lp8860: Use new mutex guards to cleanup function exits
...
This commit is contained in:
@@ -72,6 +72,12 @@ Description:
|
||||
/sys/class/leds/<led> once a given trigger is selected. For
|
||||
their documentation see `sysfs-class-led-trigger-*`.
|
||||
|
||||
Writing "none" removes the trigger for this LED.
|
||||
|
||||
Writing "default" sets the trigger to the LED's default trigger
|
||||
(which would often be configured in the device tree for the
|
||||
hardware).
|
||||
|
||||
What: /sys/class/leds/<led>/inverted
|
||||
Date: January 2011
|
||||
KernelVersion: 2.6.38
|
||||
|
||||
120
Documentation/devicetree/bindings/leds/ti,tps61310.yaml
Normal file
120
Documentation/devicetree/bindings/leds/ti,tps61310.yaml
Normal file
@@ -0,0 +1,120 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/ti,tps61310.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments TPS6131X flash LED driver
|
||||
|
||||
maintainers:
|
||||
- Matthias Fend <matthias.fend@emfend.at>
|
||||
|
||||
description: |
|
||||
The TPS61310/TPS61311 is a flash LED driver with I2C interface.
|
||||
Its power stage is capable of supplying a maximum total current of roughly 1500mA.
|
||||
The TPS6131x provides three constant-current sinks, capable of sinking
|
||||
up to 2 x 400mA (LED1 and LED3) and 800mA (LED2) in flash mode.
|
||||
In torch mode, each sink (LED1, LED2, LED3) supports currents up to 175mA.
|
||||
Since the three current sinks share most of the control components such as
|
||||
flash timer, control logic, safety timer and the operating mode, they cannot
|
||||
be used completely independently of each other. Therefore, only one LED is
|
||||
supported, but the current sinks can be combined accordingly.
|
||||
|
||||
The data sheet can be found at:
|
||||
https://www.ti.com/lit/ds/symlink/tps61310.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- ti,tps61311
|
||||
- const: ti,tps61310
|
||||
- items:
|
||||
- const: ti,tps61310
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
description: GPIO connected to NRESET pin
|
||||
|
||||
ti,valley-current-limit:
|
||||
type: boolean
|
||||
description:
|
||||
Reduce the valley peak current limit from 1750mA to 1250mA (TPS61310) or
|
||||
from 2480mA to 1800mA (TPS61311).
|
||||
|
||||
led:
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
unevaluatedProperties: false
|
||||
|
||||
properties:
|
||||
led-sources:
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
items:
|
||||
enum: [1, 2, 3]
|
||||
|
||||
led-max-microamp:
|
||||
oneOf:
|
||||
- minimum: 50000
|
||||
maximum: 350000
|
||||
multipleOf: 50000
|
||||
- minimum: 25000
|
||||
maximum: 525000
|
||||
multipleOf: 25000
|
||||
|
||||
flash-max-microamp:
|
||||
oneOf:
|
||||
- minimum: 50000
|
||||
maximum: 800000
|
||||
multipleOf: 50000
|
||||
- minimum: 25000
|
||||
maximum: 1500000
|
||||
multipleOf: 25000
|
||||
|
||||
flash-max-timeout-us:
|
||||
enum: [ 5300, 10700, 16000, 21300, 26600, 32000, 37300, 68200, 71500,
|
||||
102200, 136300, 170400, 204500, 340800, 579300, 852000 ]
|
||||
|
||||
required:
|
||||
- led-sources
|
||||
- led-max-microamp
|
||||
- flash-max-microamp
|
||||
- flash-max-timeout-us
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- led
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/leds/common.h>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@33 {
|
||||
compatible = "ti,tps61311", "ti,tps61310";
|
||||
reg = <0x33>;
|
||||
|
||||
reset-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;
|
||||
|
||||
led {
|
||||
function = LED_FUNCTION_FLASH;
|
||||
color = <LED_COLOR_ID_WHITE>;
|
||||
led-sources = <1>, <2>, <3>;
|
||||
led-max-microamp = <525000>;
|
||||
flash-max-microamp = <1500000>;
|
||||
flash-max-timeout-us = <852000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -28,5 +28,5 @@ LEDs
|
||||
leds-mlxcpld
|
||||
leds-mt6370-rgb
|
||||
leds-sc27xx
|
||||
leds-st1202.rst
|
||||
leds-st1202
|
||||
leds-qcom-lpg
|
||||
|
||||
@@ -24447,6 +24447,13 @@ F: Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
|
||||
F: Documentation/hwmon/tps23861.rst
|
||||
F: drivers/hwmon/tps23861.c
|
||||
|
||||
TEXAS INSTRUMENTS TPS6131X FLASH LED DRIVER
|
||||
M: Matthias Fend <matthias.fend@emfend.at>
|
||||
L: linux-leds@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/leds/ti,tps6131x.yaml
|
||||
F: drivers/leds/flash/leds-tps6131x.c
|
||||
|
||||
TEXAS INSTRUMENTS' DAC7612 DAC DRIVER
|
||||
M: Ricardo Ribalda <ribalda@kernel.org>
|
||||
L: linux-iio@vger.kernel.org
|
||||
|
||||
@@ -928,22 +928,22 @@ bool fwnode_device_is_available(const struct fwnode_handle *fwnode)
|
||||
EXPORT_SYMBOL_GPL(fwnode_device_is_available);
|
||||
|
||||
/**
|
||||
* device_get_child_node_count - return the number of child nodes for device
|
||||
* @dev: Device to count the child nodes for
|
||||
* fwnode_get_child_node_count - return the number of child nodes for a given firmware node
|
||||
* @fwnode: Pointer to the parent firmware node
|
||||
*
|
||||
* Return: the number of child nodes for a given device.
|
||||
* Return: the number of child nodes for a given firmware node.
|
||||
*/
|
||||
unsigned int device_get_child_node_count(const struct device *dev)
|
||||
unsigned int fwnode_get_child_node_count(const struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct fwnode_handle *child;
|
||||
unsigned int count = 0;
|
||||
|
||||
device_for_each_child_node(dev, child)
|
||||
fwnode_for_each_child_node(fwnode, child)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_get_child_node_count);
|
||||
EXPORT_SYMBOL_GPL(fwnode_get_child_node_count);
|
||||
|
||||
bool device_dma_supported(const struct device *dev)
|
||||
{
|
||||
|
||||
4
drivers/leds/.kunitconfig
Normal file
4
drivers/leds/.kunitconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_KUNIT=y
|
||||
CONFIG_NEW_LEDS=y
|
||||
CONFIG_LEDS_CLASS=y
|
||||
CONFIG_LEDS_KUNIT_TEST=y
|
||||
@@ -55,6 +55,13 @@ config LEDS_BRIGHTNESS_HW_CHANGED
|
||||
|
||||
See Documentation/ABI/testing/sysfs-class-led for details.
|
||||
|
||||
config LEDS_KUNIT_TEST
|
||||
tristate "KUnit tests for LEDs"
|
||||
depends on KUNIT && LEDS_CLASS
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
Say Y here to enable KUnit testing for the LEDs framework.
|
||||
|
||||
comment "LED drivers"
|
||||
|
||||
config LEDS_88PM860X
|
||||
@@ -735,7 +742,7 @@ config LEDS_NS2
|
||||
tristate "LED support for Network Space v2 GPIO LEDs"
|
||||
depends on LEDS_CLASS
|
||||
depends on MACH_KIRKWOOD || MACH_ARMADA_370 || COMPILE_TEST
|
||||
default y
|
||||
default y if MACH_KIRKWOOD || MACH_ARMADA_370
|
||||
help
|
||||
This option enables support for the dual-GPIO LEDs found on the
|
||||
following LaCie/Seagate boards:
|
||||
@@ -750,7 +757,7 @@ config LEDS_NETXBIG
|
||||
depends on LEDS_CLASS
|
||||
depends on MACH_KIRKWOOD || COMPILE_TEST
|
||||
depends on OF_GPIO
|
||||
default y
|
||||
default MACH_KIRKWOOD
|
||||
help
|
||||
This option enables support for LEDs found on the LaCie 2Big
|
||||
and 5Big Network v2 boards. The LEDs are wired to a CPLD and are
|
||||
|
||||
@@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o
|
||||
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
|
||||
obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o
|
||||
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
|
||||
obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o
|
||||
|
||||
# LED Platform Drivers (keep this sorted, M-| sort)
|
||||
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
|
||||
|
||||
@@ -450,7 +450,7 @@ static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
||||
return !!(reg_val & BIT(offset));
|
||||
}
|
||||
|
||||
static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
|
||||
static int sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
|
||||
{
|
||||
struct sso_led_priv *priv = gpiochip_get_data(chip);
|
||||
|
||||
@@ -458,6 +458,8 @@ static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
|
||||
if (!priv->gpio.freq)
|
||||
regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
|
||||
SSO_CON0_SWU);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
|
||||
@@ -469,7 +471,7 @@ static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
|
||||
gc->get_direction = sso_gpio_get_dir;
|
||||
gc->direction_output = sso_gpio_dir_out;
|
||||
gc->get = sso_gpio_get;
|
||||
gc->set = sso_gpio_set;
|
||||
gc->set_rv = sso_gpio_set;
|
||||
|
||||
gc->label = "lgm-sso";
|
||||
gc->base = -1;
|
||||
|
||||
@@ -132,4 +132,15 @@ config LEDS_SY7802
|
||||
|
||||
This driver can be built as a module, it will be called "leds-sy7802".
|
||||
|
||||
config LEDS_TPS6131X
|
||||
tristate "LED support for TI TPS6131x flash LED driver"
|
||||
depends on I2C && OF
|
||||
depends on GPIOLIB
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This option enables support for Texas Instruments TPS61310/TPS61311
|
||||
flash LED driver.
|
||||
|
||||
This driver can be built as a module, it will be called "leds-tps6131x".
|
||||
|
||||
endif # LEDS_CLASS_FLASH
|
||||
|
||||
@@ -12,3 +12,4 @@ obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
|
||||
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
|
||||
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
|
||||
obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
|
||||
obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o
|
||||
|
||||
815
drivers/leds/flash/leds-tps6131x.c
Normal file
815
drivers/leds/flash/leds-tps6131x.c
Normal file
@@ -0,0 +1,815 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface
|
||||
*
|
||||
* Copyright 2025 Matthias Fend <matthias.fend@emfend.at>
|
||||
*/
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/led-class-flash.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <media/v4l2-flash-led-class.h>
|
||||
|
||||
#define TPS6131X_REG_0 0x00
|
||||
#define TPS6131X_REG_0_RESET BIT(7)
|
||||
#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3)
|
||||
#define TPS6131X_REG_0_DCLC13_SHIFT 3
|
||||
#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0)
|
||||
#define TPS6131X_REG_0_DCLC2_SHIFT 0
|
||||
|
||||
#define TPS6131X_REG_1 0x01
|
||||
#define TPS6131X_REG_1_MODE GENMASK(7, 6)
|
||||
#define TPS6131X_REG_1_MODE_SHIFT 6
|
||||
#define TPS6131X_REG_1_FC2 GENMASK(5, 0)
|
||||
#define TPS6131X_REG_1_FC2_SHIFT 0
|
||||
|
||||
#define TPS6131X_REG_2 0x02
|
||||
#define TPS6131X_REG_2_MODE GENMASK(7, 6)
|
||||
#define TPS6131X_REG_2_MODE_SHIFT 6
|
||||
#define TPS6131X_REG_2_ENVM BIT(5)
|
||||
#define TPS6131X_REG_2_FC13 GENMASK(4, 0)
|
||||
#define TPS6131X_REG_2_FC13_SHIFT 0
|
||||
|
||||
#define TPS6131X_REG_3 0x03
|
||||
#define TPS6131X_REG_3_STIM GENMASK(7, 5)
|
||||
#define TPS6131X_REG_3_STIM_SHIFT 5
|
||||
#define TPS6131X_REG_3_HPFL BIT(4)
|
||||
#define TPS6131X_REG_3_SELSTIM_TO BIT(3)
|
||||
#define TPS6131X_REG_3_STT BIT(2)
|
||||
#define TPS6131X_REG_3_SFT BIT(1)
|
||||
#define TPS6131X_REG_3_TXMASK BIT(0)
|
||||
|
||||
#define TPS6131X_REG_4 0x04
|
||||
#define TPS6131X_REG_4_PG BIT(7)
|
||||
#define TPS6131X_REG_4_HOTDIE_HI BIT(6)
|
||||
#define TPS6131X_REG_4_HOTDIE_LO BIT(5)
|
||||
#define TPS6131X_REG_4_ILIM BIT(4)
|
||||
#define TPS6131X_REG_4_INDC GENMASK(3, 0)
|
||||
#define TPS6131X_REG_4_INDC_SHIFT 0
|
||||
|
||||
#define TPS6131X_REG_5 0x05
|
||||
#define TPS6131X_REG_5_SELFCAL BIT(7)
|
||||
#define TPS6131X_REG_5_ENPSM BIT(6)
|
||||
#define TPS6131X_REG_5_STSTRB1_DIR BIT(5)
|
||||
#define TPS6131X_REG_5_GPIO BIT(4)
|
||||
#define TPS6131X_REG_5_GPIOTYPE BIT(3)
|
||||
#define TPS6131X_REG_5_ENLED3 BIT(2)
|
||||
#define TPS6131X_REG_5_ENLED2 BIT(1)
|
||||
#define TPS6131X_REG_5_ENLED1 BIT(0)
|
||||
|
||||
#define TPS6131X_REG_6 0x06
|
||||
#define TPS6131X_REG_6_ENTS BIT(7)
|
||||
#define TPS6131X_REG_6_LEDHOT BIT(6)
|
||||
#define TPS6131X_REG_6_LEDWARN BIT(5)
|
||||
#define TPS6131X_REG_6_LEDHDR BIT(4)
|
||||
#define TPS6131X_REG_6_OV GENMASK(3, 0)
|
||||
#define TPS6131X_REG_6_OV_SHIFT 0
|
||||
|
||||
#define TPS6131X_REG_7 0x07
|
||||
#define TPS6131X_REG_7_ENBATMON BIT(7)
|
||||
#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4)
|
||||
#define TPS6131X_REG_7_BATDROOP_SHIFT 4
|
||||
#define TPS6131X_REG_7_REVID GENMASK(2, 0)
|
||||
#define TPS6131X_REG_7_REVID_SHIFT 0
|
||||
|
||||
#define TPS6131X_MAX_CHANNELS 3
|
||||
|
||||
#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400
|
||||
#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800
|
||||
#define TPS6131X_FLASH_STEP_I_MA 25
|
||||
|
||||
#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175
|
||||
#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175
|
||||
#define TPS6131X_TORCH_STEP_I_MA 25
|
||||
|
||||
/* The torch watchdog timer must be refreshed within an interval of 13 seconds. */
|
||||
#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000)
|
||||
|
||||
#define UA_TO_MA(UA) ((UA) / 1000)
|
||||
|
||||
enum tps6131x_mode {
|
||||
TPS6131X_MODE_SHUTDOWN = 0x0,
|
||||
TPS6131X_MODE_TORCH = 0x1,
|
||||
TPS6131X_MODE_FLASH = 0x2,
|
||||
};
|
||||
|
||||
struct tps6131x {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
struct gpio_desc *reset_gpio;
|
||||
/*
|
||||
* Registers 0, 1, 2, and 3 control parts of the controller that are not completely
|
||||
* independent of each other. Since some operations require the registers to be written in
|
||||
* a specific order to avoid unwanted side effects, they are synchronized with a lock.
|
||||
*/
|
||||
struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */
|
||||
struct delayed_work torch_refresh_work;
|
||||
bool valley_current_limit;
|
||||
bool chan1_en;
|
||||
bool chan2_en;
|
||||
bool chan3_en;
|
||||
struct fwnode_handle *led_node;
|
||||
u32 max_flash_current_ma;
|
||||
u32 step_flash_current_ma;
|
||||
u32 max_torch_current_ma;
|
||||
u32 step_torch_current_ma;
|
||||
u32 max_timeout_us;
|
||||
struct led_classdev_flash fled_cdev;
|
||||
struct v4l2_flash *v4l2_flash;
|
||||
};
|
||||
|
||||
static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev)
|
||||
{
|
||||
return container_of(fled_cdev, struct tps6131x, fled_cdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Register contents after a power on/reset. These values cannot be changed.
|
||||
*/
|
||||
|
||||
#define TPS6131X_DCLC2_50MA 2
|
||||
#define TPS6131X_DCLC13_25MA 1
|
||||
#define TPS6131X_FC2_400MA 16
|
||||
#define TPS6131X_FC13_200MA 8
|
||||
#define TPS6131X_STIM_0_579MS_1_37MS 6
|
||||
#define TPS6131X_SELSTIM_RANGE0 0
|
||||
#define TPS6131X_INDC_OFF 0
|
||||
#define TPS6131X_OV_4950MV 9
|
||||
#define TPS6131X_BATDROOP_150MV 4
|
||||
|
||||
static const struct reg_default tps6131x_regmap_defaults[] = {
|
||||
{ TPS6131X_REG_0, (TPS6131X_DCLC13_25MA << TPS6131X_REG_0_DCLC13_SHIFT) |
|
||||
(TPS6131X_DCLC2_50MA << TPS6131X_REG_0_DCLC2_SHIFT) },
|
||||
{ TPS6131X_REG_1, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_1_MODE_SHIFT) |
|
||||
(TPS6131X_FC2_400MA << TPS6131X_REG_1_FC2_SHIFT) },
|
||||
{ TPS6131X_REG_2, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_2_MODE_SHIFT) |
|
||||
(TPS6131X_FC13_200MA << TPS6131X_REG_2_FC13_SHIFT) },
|
||||
{ TPS6131X_REG_3, (TPS6131X_STIM_0_579MS_1_37MS << TPS6131X_REG_3_STIM_SHIFT) |
|
||||
(TPS6131X_SELSTIM_RANGE0 << TPS6131X_REG_3_SELSTIM_TO) |
|
||||
TPS6131X_REG_3_TXMASK },
|
||||
{ TPS6131X_REG_4, (TPS6131X_INDC_OFF << TPS6131X_REG_4_INDC_SHIFT) },
|
||||
{ TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR |
|
||||
TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 },
|
||||
{ TPS6131X_REG_6, (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT) },
|
||||
{ TPS6131X_REG_7, (TPS6131X_BATDROOP_150MV << TPS6131X_REG_7_BATDROOP_SHIFT) },
|
||||
};
|
||||
|
||||
/*
|
||||
* These registers contain flags that are reset when read.
|
||||
*/
|
||||
static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case TPS6131X_REG_3:
|
||||
case TPS6131X_REG_4:
|
||||
case TPS6131X_REG_6:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config tps6131x_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = TPS6131X_REG_7,
|
||||
.reg_defaults = tps6131x_regmap_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults),
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
.precious_reg = &tps6131x_regmap_precious,
|
||||
};
|
||||
|
||||
struct tps6131x_timer_config {
|
||||
u8 val;
|
||||
u8 range;
|
||||
u32 time_us;
|
||||
};
|
||||
|
||||
static const struct tps6131x_timer_config tps6131x_timer_configs[] = {
|
||||
{ .val = 0, .range = 1, .time_us = 5300 },
|
||||
{ .val = 1, .range = 1, .time_us = 10700 },
|
||||
{ .val = 2, .range = 1, .time_us = 16000 },
|
||||
{ .val = 3, .range = 1, .time_us = 21300 },
|
||||
{ .val = 4, .range = 1, .time_us = 26600 },
|
||||
{ .val = 5, .range = 1, .time_us = 32000 },
|
||||
{ .val = 6, .range = 1, .time_us = 37300 },
|
||||
{ .val = 0, .range = 0, .time_us = 68200 },
|
||||
{ .val = 7, .range = 1, .time_us = 71500 },
|
||||
{ .val = 1, .range = 0, .time_us = 102200 },
|
||||
{ .val = 2, .range = 0, .time_us = 136300 },
|
||||
{ .val = 3, .range = 0, .time_us = 170400 },
|
||||
{ .val = 4, .range = 0, .time_us = 204500 },
|
||||
{ .val = 5, .range = 0, .time_us = 340800 },
|
||||
{ .val = 6, .range = 0, .time_us = 579300 },
|
||||
{ .val = 7, .range = 0, .time_us = 852000 },
|
||||
};
|
||||
|
||||
static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us)
|
||||
{
|
||||
const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0];
|
||||
u32 diff, min_diff = U32_MAX;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) {
|
||||
diff = abs(tps6131x_timer_configs[i].time_us - timeout_us);
|
||||
if (diff < min_diff) {
|
||||
timer_config = &tps6131x_timer_configs[i];
|
||||
min_diff = diff;
|
||||
if (!min_diff)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return timer_config;
|
||||
}
|
||||
|
||||
static int tps6131x_reset_chip(struct tps6131x *tps6131x)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (tps6131x->reset_gpio) {
|
||||
gpiod_set_value_cansleep(tps6131x->reset_gpio, 1);
|
||||
fsleep(10);
|
||||
gpiod_set_value_cansleep(tps6131x->reset_gpio, 0);
|
||||
fsleep(100);
|
||||
} else {
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET,
|
||||
TPS6131X_REG_0_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fsleep(100);
|
||||
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_init_chip(struct tps6131x *tps6131x)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0;
|
||||
|
||||
ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE;
|
||||
|
||||
if (tps6131x->chan1_en)
|
||||
val |= TPS6131X_REG_5_ENLED1;
|
||||
|
||||
if (tps6131x->chan2_en)
|
||||
val |= TPS6131X_REG_5_ENLED2;
|
||||
|
||||
if (tps6131x->chan3_en)
|
||||
val |= TPS6131X_REG_5_ENLED3;
|
||||
|
||||
ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = TPS6131X_REG_6_ENTS;
|
||||
|
||||
ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force)
|
||||
{
|
||||
u8 val = mode << TPS6131X_REG_1_MODE_SHIFT;
|
||||
|
||||
return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val,
|
||||
NULL, false, force);
|
||||
}
|
||||
|
||||
static void tps6131x_torch_refresh_handler(struct work_struct *work)
|
||||
{
|
||||
struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work);
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
ret = tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true);
|
||||
if (ret < 0) {
|
||||
dev_err(tps6131x->dev, "Failed to refresh torch watchdog timer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
schedule_delayed_work(&tps6131x->torch_refresh_work,
|
||||
TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
|
||||
}
|
||||
|
||||
static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
u32 num_chans, steps_chan13, steps_chan2, steps_remaining;
|
||||
u8 reg0;
|
||||
int ret;
|
||||
|
||||
cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
|
||||
|
||||
/*
|
||||
* The brightness parameter uses the number of current steps as the unit (not the current
|
||||
* value itself). Since the reported step size can vary depending on the configuration,
|
||||
* this value must be converted into actual register steps.
|
||||
*/
|
||||
steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA;
|
||||
|
||||
num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
|
||||
|
||||
/*
|
||||
* The currents are distributed as evenly as possible across the activated channels.
|
||||
* Since channels 1 and 3 share the same register setting, they always use the same current
|
||||
* value. Channel 2 supports higher currents and thus takes over the remaining additional
|
||||
* portion that cannot be covered by the other channels.
|
||||
*/
|
||||
steps_chan13 = min_t(u32, steps_remaining / num_chans,
|
||||
TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA);
|
||||
if (tps6131x->chan1_en)
|
||||
steps_remaining -= steps_chan13;
|
||||
if (tps6131x->chan3_en)
|
||||
steps_remaining -= steps_chan13;
|
||||
|
||||
steps_chan2 = min_t(u32, steps_remaining,
|
||||
TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA);
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) |
|
||||
(steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT);
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0,
|
||||
TPS6131X_REG_0_DCLC13 | TPS6131X_REG_0_DCLC2, reg0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = tps6131x_set_mode(tps6131x, brightness ? TPS6131X_MODE_TORCH : TPS6131X_MODE_SHUTDOWN,
|
||||
true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* In order to use both the flash and the video light functions purely via the I2C
|
||||
* interface, STRB1 must be low. If STRB1 is low, then the video light watchdog timer
|
||||
* is also active, which puts the device into the shutdown state after around 13 seconds.
|
||||
* To prevent this, the mode must be refreshed within the watchdog timeout.
|
||||
*/
|
||||
if (brightness)
|
||||
schedule_delayed_work(&tps6131x->torch_refresh_work,
|
||||
TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
|
||||
{
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
ret = tps6131x_set_mode(tps6131x, state ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
|
||||
true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (state) {
|
||||
ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT,
|
||||
TPS6131X_REG_3_SFT, NULL, false, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, 0, NULL,
|
||||
false, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
|
||||
{
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
u32 num_chans;
|
||||
u32 steps_chan13, steps_chan2;
|
||||
u32 steps_remaining;
|
||||
int ret;
|
||||
|
||||
steps_remaining = brightness / TPS6131X_FLASH_STEP_I_MA;
|
||||
num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
|
||||
steps_chan13 = min_t(u32, steps_remaining / num_chans,
|
||||
TPS6131X_FLASH_MAX_I_CHAN13_MA / TPS6131X_FLASH_STEP_I_MA);
|
||||
if (tps6131x->chan1_en)
|
||||
steps_remaining -= steps_chan13;
|
||||
if (tps6131x->chan3_en)
|
||||
steps_remaining -= steps_chan13;
|
||||
steps_chan2 = min_t(u32, steps_remaining,
|
||||
TPS6131X_FLASH_MAX_I_CHAN2_MA / TPS6131X_FLASH_STEP_I_MA);
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_2, TPS6131X_REG_2_FC13,
|
||||
steps_chan13 << TPS6131X_REG_2_FC13_SHIFT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_FC2,
|
||||
steps_chan2 << TPS6131X_REG_1_FC2_SHIFT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fled_cdev->brightness.val = brightness;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout_us)
|
||||
{
|
||||
const struct tps6131x_timer_config *timer_config;
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
u8 reg3;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
timer_config = tps6131x_find_closest_timer_config(timeout_us);
|
||||
|
||||
reg3 = timer_config->val << TPS6131X_REG_3_STIM_SHIFT;
|
||||
if (timer_config->range)
|
||||
reg3 |= TPS6131X_REG_3_SELSTIM_TO;
|
||||
|
||||
ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_3,
|
||||
TPS6131X_REG_3_STIM | TPS6131X_REG_3_SELSTIM_TO, reg3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fled_cdev->timeout.val = timer_config->time_us;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
|
||||
{
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
unsigned int reg3;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*state = !!(reg3 & TPS6131X_REG_3_SFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
|
||||
{
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
unsigned int reg3, reg4, reg6;
|
||||
int ret;
|
||||
|
||||
*fault = 0;
|
||||
|
||||
ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_4, ®4);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_6, ®6);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (reg3 & TPS6131X_REG_3_HPFL)
|
||||
*fault |= LED_FAULT_SHORT_CIRCUIT;
|
||||
|
||||
if (reg3 & TPS6131X_REG_3_SELSTIM_TO)
|
||||
*fault |= LED_FAULT_TIMEOUT;
|
||||
|
||||
if (reg4 & TPS6131X_REG_4_HOTDIE_HI)
|
||||
*fault |= LED_FAULT_OVER_TEMPERATURE;
|
||||
|
||||
if (reg6 & (TPS6131X_REG_6_LEDHOT | TPS6131X_REG_6_LEDWARN))
|
||||
*fault |= LED_FAULT_LED_OVER_TEMPERATURE;
|
||||
|
||||
if (!(reg6 & TPS6131X_REG_6_LEDHDR))
|
||||
*fault |= LED_FAULT_UNDER_VOLTAGE;
|
||||
|
||||
if (reg6 & TPS6131X_REG_6_LEDHOT) {
|
||||
ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_6,
|
||||
TPS6131X_REG_6_LEDHOT, 0, NULL, false, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct led_flash_ops flash_ops = {
|
||||
.flash_brightness_set = tps6131x_flash_brightness_set,
|
||||
.strobe_set = tps6131x_strobe_set,
|
||||
.strobe_get = tps6131x_strobe_get,
|
||||
.timeout_set = tps6131x_flash_timeout_set,
|
||||
.fault_get = tps6131x_flash_fault_get,
|
||||
};
|
||||
|
||||
static int tps6131x_parse_node(struct tps6131x *tps6131x)
|
||||
{
|
||||
const struct tps6131x_timer_config *timer_config;
|
||||
struct device *dev = tps6131x->dev;
|
||||
u32 channels[TPS6131X_MAX_CHANNELS];
|
||||
u32 current_step_multiplier;
|
||||
u32 current_ua;
|
||||
u32 max_current_flash_ma, max_current_torch_ma;
|
||||
u32 timeout_us;
|
||||
int num_channels;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
tps6131x->valley_current_limit = device_property_read_bool(dev, "ti,valley-current-limit");
|
||||
|
||||
tps6131x->led_node = fwnode_get_next_available_child_node(dev->fwnode, NULL);
|
||||
if (!tps6131x->led_node) {
|
||||
dev_err(dev, "Missing LED node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
num_channels = fwnode_property_count_u32(tps6131x->led_node, "led-sources");
|
||||
if (num_channels <= 0) {
|
||||
dev_err(dev, "Failed to read led-sources property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (num_channels > TPS6131X_MAX_CHANNELS) {
|
||||
dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
|
||||
num_channels, TPS6131X_MAX_CHANNELS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fwnode_property_read_u32_array(tps6131x->led_node, "led-sources", channels,
|
||||
num_channels);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read led-sources property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
max_current_flash_ma = 0;
|
||||
max_current_torch_ma = 0;
|
||||
for (i = 0; i < num_channels; i++) {
|
||||
switch (channels[i]) {
|
||||
case 1:
|
||||
tps6131x->chan1_en = true;
|
||||
max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
|
||||
max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
|
||||
break;
|
||||
case 2:
|
||||
tps6131x->chan2_en = true;
|
||||
max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN2_MA;
|
||||
max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN2_MA;
|
||||
break;
|
||||
case 3:
|
||||
tps6131x->chan3_en = true;
|
||||
max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
|
||||
max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "led-source out of range [1-3]\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If only channels 1 and 3 are used, the step size is doubled because the two channels
|
||||
* share the same current control register.
|
||||
*/
|
||||
current_step_multiplier =
|
||||
(tps6131x->chan1_en && tps6131x->chan3_en && !tps6131x->chan2_en) ? 2 : 1;
|
||||
tps6131x->step_flash_current_ma = current_step_multiplier * TPS6131X_FLASH_STEP_I_MA;
|
||||
tps6131x->step_torch_current_ma = current_step_multiplier * TPS6131X_TORCH_STEP_I_MA;
|
||||
|
||||
ret = fwnode_property_read_u32(tps6131x->led_node, "led-max-microamp", ¤t_ua);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read led-max-microamp property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
tps6131x->max_torch_current_ma = UA_TO_MA(current_ua);
|
||||
|
||||
if (!tps6131x->max_torch_current_ma ||
|
||||
tps6131x->max_torch_current_ma > max_current_torch_ma ||
|
||||
(tps6131x->max_torch_current_ma % tps6131x->step_torch_current_ma)) {
|
||||
dev_err(dev, "led-max-microamp out of range or not a multiple of %u\n",
|
||||
tps6131x->step_torch_current_ma);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-microamp", ¤t_ua);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read flash-max-microamp property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
tps6131x->max_flash_current_ma = UA_TO_MA(current_ua);
|
||||
|
||||
if (!tps6131x->max_flash_current_ma ||
|
||||
tps6131x->max_flash_current_ma > max_current_flash_ma ||
|
||||
(tps6131x->max_flash_current_ma % tps6131x->step_flash_current_ma)) {
|
||||
dev_err(dev, "flash-max-microamp out of range or not a multiple of %u\n",
|
||||
tps6131x->step_flash_current_ma);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-timeout-us", &timeout_us);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to read flash-max-timeout-us property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
timer_config = tps6131x_find_closest_timer_config(timeout_us);
|
||||
tps6131x->max_timeout_us = timer_config->time_us;
|
||||
|
||||
if (tps6131x->max_timeout_us != timeout_us)
|
||||
dev_warn(dev, "flash-max-timeout-us %u not supported (using %u)\n", timeout_us,
|
||||
tps6131x->max_timeout_us);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_led_class_setup(struct tps6131x *tps6131x)
|
||||
{
|
||||
const struct tps6131x_timer_config *timer_config;
|
||||
struct led_classdev *led_cdev;
|
||||
struct led_flash_setting *setting;
|
||||
struct led_init_data init_data = {};
|
||||
int ret;
|
||||
|
||||
tps6131x->fled_cdev.ops = &flash_ops;
|
||||
|
||||
setting = &tps6131x->fled_cdev.timeout;
|
||||
timer_config = tps6131x_find_closest_timer_config(0);
|
||||
setting->min = timer_config->time_us;
|
||||
setting->max = tps6131x->max_timeout_us;
|
||||
setting->step = 1; /* Only some specific time periods are supported. No fixed step size. */
|
||||
setting->val = setting->min;
|
||||
|
||||
setting = &tps6131x->fled_cdev.brightness;
|
||||
setting->min = tps6131x->step_flash_current_ma;
|
||||
setting->max = tps6131x->max_flash_current_ma;
|
||||
setting->step = tps6131x->step_flash_current_ma;
|
||||
setting->val = setting->min;
|
||||
|
||||
led_cdev = &tps6131x->fled_cdev.led_cdev;
|
||||
led_cdev->brightness_set_blocking = tps6131x_brightness_set;
|
||||
led_cdev->max_brightness = tps6131x->max_torch_current_ma;
|
||||
led_cdev->flags |= LED_DEV_CAP_FLASH;
|
||||
|
||||
init_data.fwnode = tps6131x->led_node;
|
||||
init_data.devicename = NULL;
|
||||
init_data.default_label = NULL;
|
||||
init_data.devname_mandatory = false;
|
||||
|
||||
ret = devm_led_classdev_flash_register_ext(tps6131x->dev, &tps6131x->fled_cdev,
|
||||
&init_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
|
||||
{
|
||||
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
||||
struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
|
||||
|
||||
guard(mutex)(&tps6131x->lock);
|
||||
|
||||
return tps6131x_set_mode(tps6131x, enable ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
|
||||
false);
|
||||
}
|
||||
|
||||
static const struct v4l2_flash_ops tps6131x_v4l2_flash_ops = {
|
||||
.external_strobe_set = tps6131x_flash_external_strobe_set,
|
||||
};
|
||||
|
||||
static int tps6131x_v4l2_setup(struct tps6131x *tps6131x)
|
||||
{
|
||||
struct v4l2_flash_config v4l2_cfg = { 0 };
|
||||
struct led_flash_setting *intensity = &v4l2_cfg.intensity;
|
||||
|
||||
intensity->min = tps6131x->step_torch_current_ma;
|
||||
intensity->max = tps6131x->max_torch_current_ma;
|
||||
intensity->step = tps6131x->step_torch_current_ma;
|
||||
intensity->val = intensity->min;
|
||||
|
||||
strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name,
|
||||
sizeof(v4l2_cfg.dev_name));
|
||||
|
||||
v4l2_cfg.has_external_strobe = true;
|
||||
v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE |
|
||||
LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE |
|
||||
LED_FAULT_LED_OVER_TEMPERATURE;
|
||||
|
||||
tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node,
|
||||
&tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops,
|
||||
&v4l2_cfg);
|
||||
if (IS_ERR(tps6131x->v4l2_flash)) {
|
||||
dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n");
|
||||
return PTR_ERR(tps6131x->v4l2_flash);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps6131x_probe(struct i2c_client *client)
|
||||
{
|
||||
struct tps6131x *tps6131x;
|
||||
int ret;
|
||||
|
||||
tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL);
|
||||
if (!tps6131x)
|
||||
return -ENOMEM;
|
||||
|
||||
tps6131x->dev = &client->dev;
|
||||
i2c_set_clientdata(client, tps6131x);
|
||||
mutex_init(&tps6131x->lock);
|
||||
INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler);
|
||||
|
||||
ret = tps6131x_parse_node(tps6131x);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap);
|
||||
if (IS_ERR(tps6131x->regmap)) {
|
||||
ret = PTR_ERR(tps6131x->regmap);
|
||||
return dev_err_probe(&client->dev, ret, "Failed to allocate register map\n");
|
||||
}
|
||||
|
||||
tps6131x->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(tps6131x->reset_gpio)) {
|
||||
ret = PTR_ERR(tps6131x->reset_gpio);
|
||||
return dev_err_probe(&client->dev, ret, "Failed to get reset GPIO\n");
|
||||
}
|
||||
|
||||
ret = tps6131x_reset_chip(tps6131x);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to reset LED controller\n");
|
||||
|
||||
ret = tps6131x_init_chip(tps6131x);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to initialize LED controller\n");
|
||||
|
||||
ret = tps6131x_led_class_setup(tps6131x);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to setup LED class\n");
|
||||
|
||||
ret = tps6131x_v4l2_setup(tps6131x);
|
||||
if (ret)
|
||||
return dev_err_probe(&client->dev, ret, "Failed to setup v4l2 flash\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tps6131x_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tps6131x *tps6131x = i2c_get_clientdata(client);
|
||||
|
||||
v4l2_flash_release(tps6131x->v4l2_flash);
|
||||
|
||||
cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
|
||||
}
|
||||
|
||||
static const struct of_device_id of_tps6131x_leds_match[] = {
|
||||
{ .compatible = "ti,tps61310" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_tps6131x_leds_match);
|
||||
|
||||
static struct i2c_driver tps6131x_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "tps6131x",
|
||||
.of_match_table = of_tps6131x_leds_match,
|
||||
},
|
||||
.probe = tps6131x_probe,
|
||||
.remove = tps6131x_remove,
|
||||
};
|
||||
module_i2c_driver(tps6131x_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Texas Instruments TPS6131X flash LED driver");
|
||||
MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -440,6 +440,21 @@ int led_update_flash_brightness(struct led_classdev_flash *fled_cdev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_update_flash_brightness);
|
||||
|
||||
int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration)
|
||||
{
|
||||
struct led_classdev *led_cdev = &fled_cdev->led_cdev;
|
||||
struct led_flash_setting *s = &fled_cdev->duration;
|
||||
|
||||
s->val = duration;
|
||||
led_clamp_align(s);
|
||||
|
||||
if (!(led_cdev->flags & LED_SUSPENDED))
|
||||
return call_flash_op(fled_cdev, duration_set, s->val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_set_flash_duration);
|
||||
|
||||
MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
|
||||
MODULE_DESCRIPTION("LED Flash class interface");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -59,7 +59,8 @@ static ssize_t multi_intensity_store(struct device *dev,
|
||||
for (i = 0; i < mcled_cdev->num_colors; i++)
|
||||
mcled_cdev->subled_info[i].intensity = intensity_value[i];
|
||||
|
||||
led_set_brightness(led_cdev, led_cdev->brightness);
|
||||
if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
|
||||
led_set_brightness(led_cdev, led_cdev->brightness);
|
||||
ret = size;
|
||||
err_out:
|
||||
mutex_unlock(&led_cdev->led_access);
|
||||
|
||||
@@ -529,6 +529,7 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
|
||||
struct led_properties props = {};
|
||||
struct fwnode_handle *fwnode = init_data->fwnode;
|
||||
const char *devicename = init_data->devicename;
|
||||
int n;
|
||||
|
||||
if (!led_classdev_name)
|
||||
return -EINVAL;
|
||||
@@ -542,45 +543,49 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
|
||||
* Otherwise the label is prepended with devicename to compose
|
||||
* the final LED class device name.
|
||||
*/
|
||||
if (!devicename) {
|
||||
strscpy(led_classdev_name, props.label,
|
||||
LED_MAX_NAME_SIZE);
|
||||
if (devicename) {
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, props.label);
|
||||
} else {
|
||||
snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, props.label);
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", props.label);
|
||||
}
|
||||
} else if (props.function || props.color_present) {
|
||||
char tmp_buf[LED_MAX_NAME_SIZE];
|
||||
|
||||
if (props.func_enum_present) {
|
||||
snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
|
||||
props.color_present ? led_colors[props.color] : "",
|
||||
props.function ?: "", props.func_enum);
|
||||
n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
|
||||
props.color_present ? led_colors[props.color] : "",
|
||||
props.function ?: "", props.func_enum);
|
||||
} else {
|
||||
snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
props.color_present ? led_colors[props.color] : "",
|
||||
props.function ?: "");
|
||||
n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
props.color_present ? led_colors[props.color] : "",
|
||||
props.function ?: "");
|
||||
}
|
||||
if (init_data->devname_mandatory) {
|
||||
snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, tmp_buf);
|
||||
} else {
|
||||
strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE);
|
||||
if (n >= LED_MAX_NAME_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
if (init_data->devname_mandatory) {
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, tmp_buf);
|
||||
} else {
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", tmp_buf);
|
||||
}
|
||||
} else if (init_data->default_label) {
|
||||
if (!devicename) {
|
||||
dev_err(dev, "Legacy LED naming requires devicename segment");
|
||||
return -EINVAL;
|
||||
}
|
||||
snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, init_data->default_label);
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
|
||||
devicename, init_data->default_label);
|
||||
} else if (is_of_node(fwnode)) {
|
||||
strscpy(led_classdev_name, to_of_node(fwnode)->name,
|
||||
LED_MAX_NAME_SIZE);
|
||||
n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s",
|
||||
to_of_node(fwnode)->name);
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
if (n >= LED_MAX_NAME_SIZE)
|
||||
return -E2BIG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_compose_name);
|
||||
|
||||
132
drivers/leds/led-test.c
Normal file
132
drivers/leds/led-test.c
Normal file
@@ -0,0 +1,132 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2025 Google LLC
|
||||
*
|
||||
* Author: Lee Jones <lee@kernel.org>
|
||||
*/
|
||||
|
||||
#include <kunit/device.h>
|
||||
#include <kunit/test.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#define LED_TEST_POST_REG_BRIGHTNESS 10
|
||||
|
||||
struct led_test_ddata {
|
||||
struct led_classdev cdev;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static enum led_brightness led_test_brightness_get(struct led_classdev *cdev)
|
||||
{
|
||||
return LED_TEST_POST_REG_BRIGHTNESS;
|
||||
}
|
||||
|
||||
static void led_test_class_register(struct kunit *test)
|
||||
{
|
||||
struct led_test_ddata *ddata = test->priv;
|
||||
struct led_classdev *cdev_clash, *cdev = &ddata->cdev;
|
||||
struct device *dev = ddata->dev;
|
||||
int ret;
|
||||
|
||||
/* Register a LED class device */
|
||||
cdev->name = "led-test";
|
||||
cdev->brightness_get = led_test_brightness_get;
|
||||
cdev->brightness = 0;
|
||||
|
||||
ret = devm_led_classdev_register(dev, cdev);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, cdev->max_brightness, LED_FULL);
|
||||
KUNIT_EXPECT_EQ(test, cdev->brightness, LED_TEST_POST_REG_BRIGHTNESS);
|
||||
KUNIT_EXPECT_STREQ(test, cdev->dev->kobj.name, "led-test");
|
||||
|
||||
/* Register again with the same name - expect it to pass with the LED renamed */
|
||||
cdev_clash = devm_kmemdup(dev, cdev, sizeof(*cdev), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cdev_clash);
|
||||
|
||||
ret = devm_led_classdev_register(dev, cdev_clash);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_STREQ(test, cdev_clash->dev->kobj.name, "led-test_1");
|
||||
KUNIT_EXPECT_STREQ(test, cdev_clash->name, "led-test");
|
||||
|
||||
/* Enable name conflict rejection and register with the same name again - expect failure */
|
||||
cdev_clash->flags |= LED_REJECT_NAME_CONFLICT;
|
||||
ret = devm_led_classdev_register(dev, cdev_clash);
|
||||
KUNIT_EXPECT_EQ(test, ret, -EEXIST);
|
||||
}
|
||||
|
||||
static void led_test_class_add_lookup_and_get(struct kunit *test)
|
||||
{
|
||||
struct led_test_ddata *ddata = test->priv;
|
||||
struct led_classdev *cdev = &ddata->cdev, *cdev_get;
|
||||
struct device *dev = ddata->dev;
|
||||
struct led_lookup_data lookup;
|
||||
int ret;
|
||||
|
||||
/* First, register a LED class device */
|
||||
cdev->name = "led-test";
|
||||
ret = devm_led_classdev_register(dev, cdev);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
/* Then make the LED available for lookup */
|
||||
lookup.provider = cdev->name;
|
||||
lookup.dev_id = dev_name(dev);
|
||||
lookup.con_id = "led-test-1";
|
||||
led_add_lookup(&lookup);
|
||||
|
||||
/* Finally, attempt to look it up via the API - imagine this was an orthogonal driver */
|
||||
cdev_get = devm_led_get(dev, "led-test-1");
|
||||
KUNIT_ASSERT_FALSE(test, IS_ERR(cdev_get));
|
||||
|
||||
KUNIT_EXPECT_STREQ(test, cdev_get->name, cdev->name);
|
||||
|
||||
led_remove_lookup(&lookup);
|
||||
}
|
||||
|
||||
static struct kunit_case led_test_cases[] = {
|
||||
KUNIT_CASE(led_test_class_register),
|
||||
KUNIT_CASE(led_test_class_add_lookup_and_get),
|
||||
{ }
|
||||
};
|
||||
|
||||
static int led_test_init(struct kunit *test)
|
||||
{
|
||||
struct led_test_ddata *ddata;
|
||||
struct device *dev;
|
||||
|
||||
ddata = kunit_kzalloc(test, sizeof(*ddata), GFP_KERNEL);
|
||||
if (!ddata)
|
||||
return -ENOMEM;
|
||||
|
||||
test->priv = ddata;
|
||||
|
||||
dev = kunit_device_register(test, "led_test");
|
||||
if (IS_ERR(dev))
|
||||
return PTR_ERR(dev);
|
||||
|
||||
ddata->dev = get_device(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void led_test_exit(struct kunit *test)
|
||||
{
|
||||
struct led_test_ddata *ddata = test->priv;
|
||||
|
||||
if (ddata && ddata->dev)
|
||||
put_device(ddata->dev);
|
||||
}
|
||||
|
||||
static struct kunit_suite led_test_suite = {
|
||||
.name = "led",
|
||||
.init = led_test_init,
|
||||
.exit = led_test_exit,
|
||||
.test_cases = led_test_cases,
|
||||
};
|
||||
kunit_test_suite(led_test_suite);
|
||||
|
||||
MODULE_AUTHOR("Lee Jones <lee@kernel.org>");
|
||||
MODULE_DESCRIPTION("KUnit tests for the LED framework");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -54,6 +54,11 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (sysfs_streq(buf, "default")) {
|
||||
led_trigger_set_default(led_cdev);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
down_read(&triggers_list_lock);
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) {
|
||||
@@ -98,6 +103,9 @@ static int led_trigger_format(char *buf, size_t size,
|
||||
int len = led_trigger_snprintf(buf, size, "%s",
|
||||
led_cdev->trigger ? "none" : "[none]");
|
||||
|
||||
if (led_cdev->default_trigger)
|
||||
len += led_trigger_snprintf(buf + len, size - len, " default");
|
||||
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
bool hit;
|
||||
|
||||
@@ -281,6 +289,11 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
|
||||
if (!led_cdev->default_trigger)
|
||||
return;
|
||||
|
||||
if (!strcmp(led_cdev->default_trigger, "none")) {
|
||||
led_trigger_remove(led_cdev);
|
||||
return;
|
||||
}
|
||||
|
||||
down_read(&triggers_list_lock);
|
||||
down_write(&led_cdev->trigger_lock);
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
|
||||
@@ -60,31 +60,18 @@ static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_class
|
||||
union cros_ec_led_cmd_data {
|
||||
struct ec_params_led_control req;
|
||||
struct ec_response_led_control resp;
|
||||
} __packed;
|
||||
};
|
||||
|
||||
static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec,
|
||||
union cros_ec_led_cmd_data *arg)
|
||||
{
|
||||
int ret;
|
||||
struct {
|
||||
struct cros_ec_command msg;
|
||||
union cros_ec_led_cmd_data data;
|
||||
} __packed buf = {
|
||||
.msg = {
|
||||
.version = 1,
|
||||
.command = EC_CMD_LED_CONTROL,
|
||||
.insize = sizeof(arg->resp),
|
||||
.outsize = sizeof(arg->req),
|
||||
},
|
||||
.data.req = arg->req
|
||||
};
|
||||
|
||||
ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
|
||||
ret = cros_ec_cmd(cros_ec, 1, EC_CMD_LED_CONTROL, &arg->req,
|
||||
sizeof(arg->req), &arg->resp, sizeof(arg->resp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
arg->resp = buf.data.resp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +90,6 @@
|
||||
* @led_dev: led class device pointer
|
||||
* @regmap: Devices register map
|
||||
* @eeprom_regmap: EEPROM register map
|
||||
* @enable_gpio: VDDIO/EN gpio to enable communication interface
|
||||
* @regulator: LED supply regulator pointer
|
||||
*/
|
||||
struct lp8860_led {
|
||||
struct mutex lock;
|
||||
@@ -99,16 +97,9 @@ struct lp8860_led {
|
||||
struct led_classdev led_dev;
|
||||
struct regmap *regmap;
|
||||
struct regmap *eeprom_regmap;
|
||||
struct gpio_desc *enable_gpio;
|
||||
struct regulator *regulator;
|
||||
};
|
||||
|
||||
struct lp8860_eeprom_reg {
|
||||
uint8_t reg;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = {
|
||||
static const struct reg_sequence lp8860_eeprom_disp_regs[] = {
|
||||
{ LP8860_EEPROM_REG_0, 0xed },
|
||||
{ LP8860_EEPROM_REG_1, 0xdf },
|
||||
{ LP8860_EEPROM_REG_2, 0xdc },
|
||||
@@ -136,43 +127,29 @@ static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = {
|
||||
{ LP8860_EEPROM_REG_24, 0x3E },
|
||||
};
|
||||
|
||||
static int lp8860_unlock_eeprom(struct lp8860_led *led, int lock)
|
||||
static int lp8860_unlock_eeprom(struct lp8860_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
guard(mutex)(&led->lock);
|
||||
|
||||
if (lock == LP8860_UNLOCK_EEPROM) {
|
||||
ret = regmap_write(led->regmap,
|
||||
LP8860_EEPROM_UNLOCK,
|
||||
LP8860_EEPROM_CODE_1);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = regmap_write(led->regmap,
|
||||
LP8860_EEPROM_UNLOCK,
|
||||
LP8860_EEPROM_CODE_2);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
goto out;
|
||||
}
|
||||
ret = regmap_write(led->regmap,
|
||||
LP8860_EEPROM_UNLOCK,
|
||||
LP8860_EEPROM_CODE_3);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
ret = regmap_write(led->regmap,
|
||||
LP8860_EEPROM_UNLOCK,
|
||||
LP8860_LOCK_EEPROM);
|
||||
ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_1);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_2);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_3);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "EEPROM Unlock failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&led->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -209,47 +186,35 @@ static int lp8860_brightness_set(struct led_classdev *led_cdev,
|
||||
int disp_brightness = brt_val * 255;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&led->lock);
|
||||
guard(mutex)(&led->lock);
|
||||
|
||||
ret = lp8860_fault_check(led);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Cannot read/clear faults\n");
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_MSB,
|
||||
(disp_brightness & 0xff00) >> 8);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Cannot write CL1 MSB\n");
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_LSB,
|
||||
disp_brightness & 0xff);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Cannot write CL1 LSB\n");
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&led->lock);
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lp8860_init(struct lp8860_led *led)
|
||||
{
|
||||
unsigned int read_buf;
|
||||
int ret, i, reg_count;
|
||||
|
||||
if (led->regulator) {
|
||||
ret = regulator_enable(led->regulator);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev,
|
||||
"Failed to enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
gpiod_direction_output(led->enable_gpio, 1);
|
||||
int ret, reg_count;
|
||||
|
||||
ret = lp8860_fault_check(led);
|
||||
if (ret)
|
||||
@@ -259,24 +224,20 @@ static int lp8860_init(struct lp8860_led *led)
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = lp8860_unlock_eeprom(led, LP8860_UNLOCK_EEPROM);
|
||||
ret = lp8860_unlock_eeprom(led);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Failed unlocking EEPROM\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs);
|
||||
for (i = 0; i < reg_count; i++) {
|
||||
ret = regmap_write(led->eeprom_regmap,
|
||||
lp8860_eeprom_disp_regs[i].reg,
|
||||
lp8860_eeprom_disp_regs[i].value);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Failed writing EEPROM\n");
|
||||
goto out;
|
||||
}
|
||||
ret = regmap_multi_reg_write(led->eeprom_regmap, lp8860_eeprom_disp_regs, reg_count);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "Failed writing EEPROM\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = lp8860_unlock_eeprom(led, LP8860_LOCK_EEPROM);
|
||||
ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_LOCK_EEPROM);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
@@ -291,74 +252,14 @@ static int lp8860_init(struct lp8860_led *led)
|
||||
return ret;
|
||||
|
||||
out:
|
||||
if (ret)
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
|
||||
if (led->regulator) {
|
||||
ret = regulator_disable(led->regulator);
|
||||
if (ret)
|
||||
dev_err(&led->client->dev,
|
||||
"Failed to disable regulator\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct reg_default lp8860_reg_defs[] = {
|
||||
{ LP8860_DISP_CL1_BRT_MSB, 0x00},
|
||||
{ LP8860_DISP_CL1_BRT_LSB, 0x00},
|
||||
{ LP8860_DISP_CL1_CURR_MSB, 0x00},
|
||||
{ LP8860_DISP_CL1_CURR_LSB, 0x00},
|
||||
{ LP8860_CL2_BRT_MSB, 0x00},
|
||||
{ LP8860_CL2_BRT_LSB, 0x00},
|
||||
{ LP8860_CL2_CURRENT, 0x00},
|
||||
{ LP8860_CL3_BRT_MSB, 0x00},
|
||||
{ LP8860_CL3_BRT_LSB, 0x00},
|
||||
{ LP8860_CL3_CURRENT, 0x00},
|
||||
{ LP8860_CL4_BRT_MSB, 0x00},
|
||||
{ LP8860_CL4_BRT_LSB, 0x00},
|
||||
{ LP8860_CL4_CURRENT, 0x00},
|
||||
{ LP8860_CONFIG, 0x00},
|
||||
{ LP8860_FAULT_CLEAR, 0x00},
|
||||
{ LP8860_EEPROM_CNTRL, 0x80},
|
||||
{ LP8860_EEPROM_UNLOCK, 0x00},
|
||||
};
|
||||
|
||||
static const struct regmap_config lp8860_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = LP8860_EEPROM_UNLOCK,
|
||||
.reg_defaults = lp8860_reg_defs,
|
||||
.num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs),
|
||||
};
|
||||
|
||||
static const struct reg_default lp8860_eeprom_defs[] = {
|
||||
{ LP8860_EEPROM_REG_0, 0x00 },
|
||||
{ LP8860_EEPROM_REG_1, 0x00 },
|
||||
{ LP8860_EEPROM_REG_2, 0x00 },
|
||||
{ LP8860_EEPROM_REG_3, 0x00 },
|
||||
{ LP8860_EEPROM_REG_4, 0x00 },
|
||||
{ LP8860_EEPROM_REG_5, 0x00 },
|
||||
{ LP8860_EEPROM_REG_6, 0x00 },
|
||||
{ LP8860_EEPROM_REG_7, 0x00 },
|
||||
{ LP8860_EEPROM_REG_8, 0x00 },
|
||||
{ LP8860_EEPROM_REG_9, 0x00 },
|
||||
{ LP8860_EEPROM_REG_10, 0x00 },
|
||||
{ LP8860_EEPROM_REG_11, 0x00 },
|
||||
{ LP8860_EEPROM_REG_12, 0x00 },
|
||||
{ LP8860_EEPROM_REG_13, 0x00 },
|
||||
{ LP8860_EEPROM_REG_14, 0x00 },
|
||||
{ LP8860_EEPROM_REG_15, 0x00 },
|
||||
{ LP8860_EEPROM_REG_16, 0x00 },
|
||||
{ LP8860_EEPROM_REG_17, 0x00 },
|
||||
{ LP8860_EEPROM_REG_18, 0x00 },
|
||||
{ LP8860_EEPROM_REG_19, 0x00 },
|
||||
{ LP8860_EEPROM_REG_20, 0x00 },
|
||||
{ LP8860_EEPROM_REG_21, 0x00 },
|
||||
{ LP8860_EEPROM_REG_22, 0x00 },
|
||||
{ LP8860_EEPROM_REG_23, 0x00 },
|
||||
{ LP8860_EEPROM_REG_24, 0x00 },
|
||||
};
|
||||
|
||||
static const struct regmap_config lp8860_eeprom_regmap_config = {
|
||||
@@ -366,10 +267,15 @@ static const struct regmap_config lp8860_eeprom_regmap_config = {
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = LP8860_EEPROM_REG_24,
|
||||
.reg_defaults = lp8860_eeprom_defs,
|
||||
.num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs),
|
||||
};
|
||||
|
||||
static void lp8860_disable_gpio(void *data)
|
||||
{
|
||||
struct gpio_desc *gpio = data;
|
||||
|
||||
gpiod_set_value(gpio, 0);
|
||||
}
|
||||
|
||||
static int lp8860_probe(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
@@ -377,6 +283,7 @@ static int lp8860_probe(struct i2c_client *client)
|
||||
struct device_node *np = dev_of_node(&client->dev);
|
||||
struct device_node *child_node;
|
||||
struct led_init_data init_data = {};
|
||||
struct gpio_desc *enable_gpio;
|
||||
|
||||
led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
|
||||
if (!led)
|
||||
@@ -386,24 +293,21 @@ static int lp8860_probe(struct i2c_client *client)
|
||||
if (!child_node)
|
||||
return -EINVAL;
|
||||
|
||||
led->enable_gpio = devm_gpiod_get_optional(&client->dev,
|
||||
"enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(led->enable_gpio)) {
|
||||
ret = PTR_ERR(led->enable_gpio);
|
||||
dev_err(&client->dev, "Failed to get enable gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(enable_gpio))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
|
||||
"Failed to get enable GPIO\n");
|
||||
devm_add_action_or_reset(&client->dev, lp8860_disable_gpio, enable_gpio);
|
||||
|
||||
led->regulator = devm_regulator_get(&client->dev, "vled");
|
||||
if (IS_ERR(led->regulator))
|
||||
led->regulator = NULL;
|
||||
ret = devm_regulator_get_enable_optional(&client->dev, "vled");
|
||||
if (ret && ret != -ENODEV)
|
||||
return dev_err_probe(&client->dev, ret,
|
||||
"Failed to enable vled regulator\n");
|
||||
|
||||
led->client = client;
|
||||
led->led_dev.brightness_set_blocking = lp8860_brightness_set;
|
||||
|
||||
mutex_init(&led->lock);
|
||||
|
||||
i2c_set_clientdata(client, led);
|
||||
devm_mutex_init(&client->dev, &led->lock);
|
||||
|
||||
led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config);
|
||||
if (IS_ERR(led->regmap)) {
|
||||
@@ -439,23 +343,6 @@ static int lp8860_probe(struct i2c_client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lp8860_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lp8860_led *led = i2c_get_clientdata(client);
|
||||
int ret;
|
||||
|
||||
gpiod_direction_output(led->enable_gpio, 0);
|
||||
|
||||
if (led->regulator) {
|
||||
ret = regulator_disable(led->regulator);
|
||||
if (ret)
|
||||
dev_err(&led->client->dev,
|
||||
"Failed to disable regulator\n");
|
||||
}
|
||||
|
||||
mutex_destroy(&led->lock);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lp8860_id[] = {
|
||||
{ "lp8860" },
|
||||
{ }
|
||||
@@ -474,7 +361,6 @@ static struct i2c_driver lp8860_driver = {
|
||||
.of_match_table = of_lp8860_leds_match,
|
||||
},
|
||||
.probe = lp8860_probe,
|
||||
.remove = lp8860_remove,
|
||||
.id_table = lp8860_id,
|
||||
};
|
||||
module_i2c_driver(lp8860_driver);
|
||||
|
||||
@@ -318,7 +318,8 @@ static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val)
|
||||
static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
int val)
|
||||
{
|
||||
struct pca9532_data *data = gpiochip_get_data(gc);
|
||||
struct pca9532_led *led = &data->leds[offset];
|
||||
@@ -329,6 +330,8 @@ static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int va
|
||||
led->state = PCA9532_OFF;
|
||||
|
||||
pca9532_setled(led);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
|
||||
@@ -351,9 +354,7 @@ static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
|
||||
|
||||
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
|
||||
{
|
||||
pca9532_gpio_set_value(gc, offset, val);
|
||||
|
||||
return 0;
|
||||
return pca9532_gpio_set_value(gc, offset, val);
|
||||
}
|
||||
#endif /* CONFIG_LEDS_PCA9532_GPIO */
|
||||
|
||||
@@ -472,7 +473,7 @@ static int pca9532_configure(struct i2c_client *client,
|
||||
data->gpio.label = "gpio-pca9532";
|
||||
data->gpio.direction_input = pca9532_gpio_direction_input;
|
||||
data->gpio.direction_output = pca9532_gpio_direction_output;
|
||||
data->gpio.set = pca9532_gpio_set_value;
|
||||
data->gpio.set_rv = pca9532_gpio_set_value;
|
||||
data->gpio.get = pca9532_gpio_get_value;
|
||||
data->gpio.request = pca9532_gpio_request_pin;
|
||||
data->gpio.can_sleep = 1;
|
||||
|
||||
@@ -73,7 +73,7 @@ enum pca955x_type {
|
||||
};
|
||||
|
||||
struct pca955x_chipdef {
|
||||
int bits;
|
||||
u8 bits;
|
||||
u8 slv_addr; /* 7-bit slave address mask */
|
||||
int slv_addr_shift; /* Number of bits to ignore */
|
||||
int blink_div; /* PSC divider */
|
||||
@@ -142,13 +142,13 @@ struct pca955x_platform_data {
|
||||
};
|
||||
|
||||
/* 8 bits per input register */
|
||||
static inline int pca955x_num_input_regs(int bits)
|
||||
static inline u8 pca955x_num_input_regs(u8 bits)
|
||||
{
|
||||
return (bits + 7) / 8;
|
||||
}
|
||||
|
||||
/* 4 bits per LED selector register */
|
||||
static inline int pca955x_num_led_regs(int bits)
|
||||
static inline u8 pca955x_num_led_regs(u8 bits)
|
||||
{
|
||||
return (bits + 3) / 4;
|
||||
}
|
||||
@@ -495,10 +495,10 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW);
|
||||
}
|
||||
|
||||
static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
int val)
|
||||
static int pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
int val)
|
||||
{
|
||||
pca955x_set_value(gc, offset, val);
|
||||
return pca955x_set_value(gc, offset, val);
|
||||
}
|
||||
|
||||
static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
|
||||
@@ -581,14 +581,14 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
struct led_classdev *led;
|
||||
struct led_init_data init_data;
|
||||
struct i2c_adapter *adapter;
|
||||
int i, bit, err, nls, reg;
|
||||
u8 i, nls, psc0;
|
||||
u8 ls1[4];
|
||||
u8 ls2[4];
|
||||
struct pca955x_platform_data *pdata;
|
||||
u8 psc0;
|
||||
bool keep_psc0 = false;
|
||||
bool set_default_label = false;
|
||||
char default_label[8];
|
||||
int bit, err, reg;
|
||||
|
||||
chip = i2c_get_match_data(client);
|
||||
if (!chip)
|
||||
@@ -610,16 +610,15 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
|
||||
"slave address 0x%02x\n", client->name, chip->bits,
|
||||
client->addr);
|
||||
dev_info(&client->dev, "Using %s %u-bit LED driver at slave address 0x%02x\n",
|
||||
client->name, chip->bits, client->addr);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -EIO;
|
||||
|
||||
if (pdata->num_leds != chip->bits) {
|
||||
dev_err(&client->dev,
|
||||
"board info claims %d LEDs on a %d-bit chip\n",
|
||||
"board info claims %d LEDs on a %u-bit chip\n",
|
||||
pdata->num_leds, chip->bits);
|
||||
return -ENODEV;
|
||||
}
|
||||
@@ -694,8 +693,7 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
if (set_default_label) {
|
||||
snprintf(default_label, sizeof(default_label),
|
||||
"%d", i);
|
||||
snprintf(default_label, sizeof(default_label), "%u", i);
|
||||
init_data.default_label = default_label;
|
||||
} else {
|
||||
init_data.default_label = NULL;
|
||||
@@ -739,7 +737,7 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
pca955x->gpio.label = "gpio-pca955x";
|
||||
pca955x->gpio.direction_input = pca955x_gpio_direction_input;
|
||||
pca955x->gpio.direction_output = pca955x_gpio_direction_output;
|
||||
pca955x->gpio.set = pca955x_gpio_set_value;
|
||||
pca955x->gpio.set_rv = pca955x_gpio_set_value;
|
||||
pca955x->gpio.get = pca955x_gpio_get_value;
|
||||
pca955x->gpio.request = pca955x_gpio_request_pin;
|
||||
pca955x->gpio.free = pca955x_gpio_free_pin;
|
||||
|
||||
@@ -197,7 +197,7 @@ MODULE_DEVICE_TABLE(i2c, pca995x_id);
|
||||
|
||||
static const struct of_device_id pca995x_of_match[] = {
|
||||
{ .compatible = "nxp,pca9952", .data = &pca9952_chipdef },
|
||||
{ .compatible = "nxp,pca9955b", . data = &pca9955b_chipdef },
|
||||
{ .compatible = "nxp,pca9955b", .data = &pca9955b_chipdef },
|
||||
{ .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -588,8 +588,8 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GPIOLIB
|
||||
static void tca6507_gpio_set_value(struct gpio_chip *gc,
|
||||
unsigned offset, int val)
|
||||
static int tca6507_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
|
||||
int val)
|
||||
{
|
||||
struct tca6507_chip *tca = gpiochip_get_data(gc);
|
||||
unsigned long flags;
|
||||
@@ -604,13 +604,14 @@ static void tca6507_gpio_set_value(struct gpio_chip *gc,
|
||||
spin_unlock_irqrestore(&tca->lock, flags);
|
||||
if (tca->reg_set)
|
||||
schedule_work(&tca->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tca6507_gpio_direction_output(struct gpio_chip *gc,
|
||||
unsigned offset, int val)
|
||||
{
|
||||
tca6507_gpio_set_value(gc, offset, val);
|
||||
return 0;
|
||||
return tca6507_gpio_set_value(gc, offset, val);
|
||||
}
|
||||
|
||||
static int tca6507_probe_gpios(struct device *dev,
|
||||
@@ -636,7 +637,7 @@ static int tca6507_probe_gpios(struct device *dev,
|
||||
tca->gpio.base = -1;
|
||||
tca->gpio.owner = THIS_MODULE;
|
||||
tca->gpio.direction_output = tca6507_gpio_direction_output;
|
||||
tca->gpio.set = tca6507_gpio_set_value;
|
||||
tca->gpio.set_rv = tca6507_gpio_set_value;
|
||||
tca->gpio.parent = dev;
|
||||
err = devm_gpiochip_add_data(dev, &tca->gpio, tca);
|
||||
if (err) {
|
||||
|
||||
@@ -361,7 +361,7 @@ static DEVICE_ATTR_RW(gamma_correction);
|
||||
static struct attribute *omnia_led_controller_attrs[] = {
|
||||
&dev_attr_brightness.attr,
|
||||
&dev_attr_gamma_correction.attr,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(omnia_led_controller);
|
||||
|
||||
@@ -527,7 +527,7 @@ static void omnia_leds_remove(struct i2c_client *client)
|
||||
|
||||
static const struct of_device_id of_omnia_leds_match[] = {
|
||||
{ .compatible = "cznic,turris-omnia-leds", },
|
||||
{},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_omnia_leds_match);
|
||||
|
||||
|
||||
@@ -199,17 +199,17 @@ static const struct reg_field mt6372_reg_fields[F_MAX_FIELDS] = {
|
||||
|
||||
/* Current unit: microamp, time unit: millisecond */
|
||||
static const struct linear_range common_led_ranges[R_MAX_RANGES] = {
|
||||
[R_LED123_CURR] = { 4000, 1, 6, 4000 },
|
||||
[R_LED4_CURR] = { 2000, 1, 3, 2000 },
|
||||
[R_LED_TRFON] = { 125, 0, 15, 200 },
|
||||
[R_LED_TOFF] = { 250, 0, 15, 400 },
|
||||
[R_LED123_CURR] = LINEAR_RANGE(4000, 1, 6, 4000),
|
||||
[R_LED4_CURR] = LINEAR_RANGE(2000, 1, 3, 2000),
|
||||
[R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 200),
|
||||
[R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 400),
|
||||
};
|
||||
|
||||
static const struct linear_range mt6372_led_ranges[R_MAX_RANGES] = {
|
||||
[R_LED123_CURR] = { 2000, 1, 14, 2000 },
|
||||
[R_LED4_CURR] = { 2000, 1, 14, 2000 },
|
||||
[R_LED_TRFON] = { 125, 0, 15, 250 },
|
||||
[R_LED_TOFF] = { 250, 0, 15, 500 },
|
||||
[R_LED123_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
|
||||
[R_LED4_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
|
||||
[R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 250),
|
||||
[R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 500),
|
||||
};
|
||||
|
||||
static const unsigned int common_tfreqs[] = {
|
||||
|
||||
@@ -155,9 +155,9 @@ static int ncp5623_probe(struct i2c_client *client)
|
||||
struct device *dev = &client->dev;
|
||||
struct fwnode_handle *mc_node, *led_node;
|
||||
struct led_init_data init_data = { };
|
||||
int num_subleds = 0;
|
||||
struct ncp5623 *ncp;
|
||||
struct mc_subled *subled_info;
|
||||
unsigned int num_subleds;
|
||||
u32 color_index;
|
||||
u32 reg;
|
||||
int ret;
|
||||
@@ -172,8 +172,7 @@ static int ncp5623_probe(struct i2c_client *client)
|
||||
if (!mc_node)
|
||||
return -EINVAL;
|
||||
|
||||
fwnode_for_each_child_node(mc_node, led_node)
|
||||
num_subleds++;
|
||||
num_subleds = fwnode_get_child_node_count(mc_node);
|
||||
|
||||
subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL);
|
||||
if (!subled_info) {
|
||||
|
||||
@@ -107,12 +107,12 @@ release_fwnode:
|
||||
|
||||
static int led_pwm_mc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct fwnode_handle *mcnode, *fwnode;
|
||||
struct fwnode_handle *mcnode;
|
||||
struct led_init_data init_data = {};
|
||||
struct led_classdev *cdev;
|
||||
struct mc_subled *subled;
|
||||
struct pwm_mc_led *priv;
|
||||
int count = 0;
|
||||
unsigned int count;
|
||||
int ret = 0;
|
||||
|
||||
mcnode = device_get_named_child_node(&pdev->dev, "multi-led");
|
||||
@@ -121,8 +121,7 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
|
||||
"expected multi-led node\n");
|
||||
|
||||
/* count the nodes inside the multi-led node */
|
||||
fwnode_for_each_child_node(mcnode, fwnode)
|
||||
count++;
|
||||
count = fwnode_get_child_node_count(mcnode);
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
|
||||
GFP_KERNEL);
|
||||
|
||||
@@ -7166,7 +7166,7 @@ static void tcpm_fw_get_timings(struct tcpm_port *port, struct fwnode_handle *fw
|
||||
|
||||
static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode)
|
||||
{
|
||||
struct fwnode_handle *capabilities, *child, *caps = NULL;
|
||||
struct fwnode_handle *capabilities, *caps = NULL;
|
||||
unsigned int nr_src_pdo, nr_snk_pdo;
|
||||
const char *opmode_str;
|
||||
u32 *src_pdo, *snk_pdo;
|
||||
@@ -7232,9 +7232,7 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode
|
||||
if (!capabilities) {
|
||||
port->pd_count = 1;
|
||||
} else {
|
||||
fwnode_for_each_child_node(capabilities, child)
|
||||
port->pd_count++;
|
||||
|
||||
port->pd_count = fwnode_get_child_node_count(capabilities);
|
||||
if (!port->pd_count) {
|
||||
ret = -ENODATA;
|
||||
goto put_capabilities;
|
||||
|
||||
@@ -45,6 +45,8 @@ struct led_flash_ops {
|
||||
int (*timeout_set)(struct led_classdev_flash *fled_cdev, u32 timeout);
|
||||
/* get the flash LED fault */
|
||||
int (*fault_get)(struct led_classdev_flash *fled_cdev, u32 *fault);
|
||||
/* set flash duration */
|
||||
int (*duration_set)(struct led_classdev_flash *fled_cdev, u32 duration);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -75,6 +77,9 @@ struct led_classdev_flash {
|
||||
/* flash timeout value in microseconds along with its constraints */
|
||||
struct led_flash_setting timeout;
|
||||
|
||||
/* flash timeout value in microseconds along with its constraints */
|
||||
struct led_flash_setting duration;
|
||||
|
||||
/* LED Flash class sysfs groups */
|
||||
const struct attribute_group *sysfs_groups[LED_FLASH_SYSFS_GROUPS_SIZE];
|
||||
};
|
||||
@@ -209,4 +214,15 @@ int led_set_flash_timeout(struct led_classdev_flash *fled_cdev, u32 timeout);
|
||||
*/
|
||||
int led_get_flash_fault(struct led_classdev_flash *fled_cdev, u32 *fault);
|
||||
|
||||
/**
|
||||
* led_set_flash_duration - set flash LED duration
|
||||
* @fled_cdev: the flash LED to set
|
||||
* @timeout: the flash duration to set it to
|
||||
*
|
||||
* Set the flash strobe duration.
|
||||
*
|
||||
* Returns: 0 on success or negative error value on failure
|
||||
*/
|
||||
int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration);
|
||||
|
||||
#endif /* __LINUX_FLASH_LEDS_H_INCLUDED */
|
||||
|
||||
@@ -208,7 +208,12 @@ DEFINE_FREE(fwnode_handle, struct fwnode_handle *, fwnode_handle_put(_T))
|
||||
int fwnode_irq_get(const struct fwnode_handle *fwnode, unsigned int index);
|
||||
int fwnode_irq_get_byname(const struct fwnode_handle *fwnode, const char *name);
|
||||
|
||||
unsigned int device_get_child_node_count(const struct device *dev);
|
||||
unsigned int fwnode_get_child_node_count(const struct fwnode_handle *fwnode);
|
||||
|
||||
static inline unsigned int device_get_child_node_count(const struct device *dev)
|
||||
{
|
||||
return fwnode_get_child_node_count(dev_fwnode(dev));
|
||||
}
|
||||
|
||||
static inline int device_property_read_u8(const struct device *dev,
|
||||
const char *propname, u8 *val)
|
||||
|
||||
Reference in New Issue
Block a user