mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00
* pca955x: Add HW blink support, utilizing PWM0. It supports one frequency
across all blinking LEDs and falls back to software blink if different frequencies are requested. * trigger: netdev: Allow configuring LED blink interval via .blink_set even when HW offload (.hw_control) is enabled. * led-core: Fix a race condition where a quick LED_OFF followed by another brightness set could leave the LED off incorrectly, especially noticeable after the introduction of the ordered workqueue. * qcom-lpg: Add support for 6-bit PWM resolution alongside the existing 9-bit support. * qcom-lpg: Fix PWM value capping to respect the selected resolution (6-bit or 9-bit) for normal PWMs. * qcom-lpg: Fix PWM value capping to respect the selected resolution for Hi-Res PWMs. * qcom-lpg: Fix calculation of the best period for Hi-Res PWMs to prevent requested duty cycles from exceeding the maximum allowed by the selected resolution. * st1202: Add a check for the error code returned by devm_mutex_init(). * pwm-multicolor: Add a check for the return value of fwnode_property_read_u32(). * st1202: Ensure hardware initialization (st1202_setup) happens before DT node processing (st1202_dt_init). * Kconfig: leds-st1202: Add select LEDS_TRIGGER_PATTERN as it's required by the driver. * lp8860: Drop unneeded explicit assignment to REGCACHE_NONE. * pca955x: Refactor code with helper functions and rename some functions/variables for clarity. * pca955x: Pass driver data pointers instead of the I2C client to helper functions. * pca955x: Optimize probe LED selection logic to reduce I2C operations. * pca955x: Revert the removal of pca95xx_num_led_regs() (renaming it to pca955x_num_led_regs) as it's needed for HW blink support. * st1202: Refactor st1202_led_set() to use the !! operator for boolean conversion. * st1202: Minor spacing and proofreading edits in comments. * Directory Rename: Rename the drivers/leds/simple directory to drivers/leds/simatic as the drivers within are not simple. * mlxcpld: Remove unused include of acpi.h. * nic78bx: Tidy up the ACPI ID table (remove ACPI_PTR, use mod_devicetable.h, remove explicit driver_data initializer). * tlc591xx: Convert text binding to YAML format, add child node constraints, and fix typos/formatting in the example. * qcom-lpg: Document the qcom,pm8937-pwm compatible string as a fallback for qcom,pm8916-pwm. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmfmqRcACgkQUa+KL4f8 d2EIjg//cmG4xqmhs5u38pAVL30ZYphGL3AlSVnAtMbBgP3u6AzzppjrDHQuPbZC Iw8VGRbl1JTu1kazlQ4B4VR1b0KBAScz0oIrLqb/qJUWFw9DsIeKxCsTjukRr+/O FaQJRwhSPd4x+RW6+yGrug0ciR+f6ZfaQl7SLP7Puo2TqQukaA28aVG+KPeVxUzi CdVNDyMLSpDBNvWq1zRzlGEHDUprFO+lwV5kVy9V5qT7t5WkayMkOE4qsopJVQqh jkbxpKfxfWh7Mi3BxACqiVVTZRlzPu8hCmm+9OwT08m+coXGkSNzhXni3dSNugOL XMFQatfmsRgqSt68icHA993xhNytkLN8yj98mzUcpky8VfwUMNXKN2JhTJT+QG19 +W4/Xt1WROMr/FTi4JBLzQe3dmyXPiVpONtUuO4vVtPeXyUMOUxKU9opVF6KHu/v +9xF4qDYVbxvV0NysB6unsqyEL+su+//wBhR+7gSpc7Gg8gquE9kFeP+1jkK5K4d mKhijFr1BGP1f6nJA5wivnKJ9EIie7wnJcrLFCDDYV2uACJDwmaBIx+VLb2yf/FY usd1bj/4mMobtSGrYnOZf4IK8erDt/+ozm0t7pqshmI/SM54xMvF3L7CttOPPh6K j69dTkKJ9FzfSqy2RZ9gvbga+WoCD3++lhw5ivGJjF9lRaQQo7I= =cEhV -----END PGP SIGNATURE----- Merge tag 'leds-next-6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds Pull LED updates from Lee Jones: - pca955x: Add HW blink support, utilizing PWM0. It supports one frequency across all blinking LEDs and falls back to software blink if different frequencies are requested. - trigger: netdev: Allow configuring LED blink interval via .blink_set even when HW offload (.hw_control) is enabled. - led-core: Fix a race condition where a quick LED_OFF followed by another brightness set could leave the LED off incorrectly, especially noticeable after the introduction of the ordered workqueue. - qcom-lpg: Add support for 6-bit PWM resolution alongside the existing 9-bit support. - qcom-lpg: Fix PWM value capping to respect the selected resolution (6-bit or 9-bit) for normal PWMs. - qcom-lpg: Fix PWM value capping to respect the selected resolution for Hi-Res PWMs. - qcom-lpg: Fix calculation of the best period for Hi-Res PWMs to prevent requested duty cycles from exceeding the maximum allowed by the selected resolution. - st1202: Add a check for the error code returned by devm_mutex_init(). - pwm-multicolor: Add a check for the return value of fwnode_property_read_u32(). - st1202: Ensure hardware initialization (st1202_setup) happens before DT node processing (st1202_dt_init). - Kconfig: leds-st1202: Add select LEDS_TRIGGER_PATTERN as it's required by the driver. - lp8860: Drop unneeded explicit assignment to REGCACHE_NONE. - pca955x: Refactor code with helper functions and rename some functions/variables for clarity. - pca955x: Pass driver data pointers instead of the I2C client to helper functions. - pca955x: Optimize probe LED selection logic to reduce I2C operations. - pca955x: Revert the removal of pca95xx_num_led_regs() (renaming it to pca955x_num_led_regs) as it's needed for HW blink support. - st1202: Refactor st1202_led_set() to use the !! operator for boolean conversion. - st1202: Minor spacing and proofreading edits in comments. - Directory Rename: Rename the drivers/leds/simple directory to drivers/leds/simatic as the drivers within are not simple. - mlxcpld: Remove unused include of acpi.h. - nic78bx: Tidy up the ACPI ID table (remove ACPI_PTR, use mod_devicetable.h, remove explicit driver_data initializer). - tlc591xx: Convert text binding to YAML format, add child node constraints, and fix typos/formatting in the example. - qcom-lpg: Document the qcom,pm8937-pwm compatible string as a fallback for qcom,pm8916-pwm. * tag 'leds-next-6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (23 commits) leds: nic78bx: Tidy up ACPI ID table leds: mlxcpld: Remove unused ACPI header inclusion leds: rgb: leds-qcom-lpg: Fix calculation of best period Hi-Res PWMs leds: rgb: leds-qcom-lpg: Fix pwm resolution max for Hi-Res PWMs leds: rgb: leds-qcom-lpg: Fix pwm resolution max for normal PWMs leds: Rename simple directory to simatic leds: Kconfig: leds-st1202: Add select for required LEDS_TRIGGER_PATTERN leds: leds-st1202: Spacing and proofreading editing leds: leds-st1202: Initialize hardware before DT node child operations leds: pwm-multicolor: Add check for fwnode_property_read_u32 leds: rgb: leds-qcom-lpg: Add support for 6-bit PWM resolution leds: Fix LED_OFF brightness race Revert "leds-pca955x: Remove the unused function pca95xx_num_led_regs()" leds: st1202: Refactor st1202_led_set() to use !! operator for boolean conversion dt-bindings: leds: qcom-lpg: Document PM8937 PWM compatible leds: pca955x: Add HW blink support leds: pca955x: Optimize probe LED selection leds: pca955x: Use pointers to driver data rather than I2C client leds: pca955x: Refactor with helper functions and renaming dt-bindings: leds: Convert leds-tlc591xx.txt to yaml format ...
This commit is contained in:
commit
cb9b4c3403
@ -39,6 +39,10 @@ properties:
|
||||
- enum:
|
||||
- qcom,pm8550-pwm
|
||||
- const: qcom,pm8350c-pwm
|
||||
- items:
|
||||
- enum:
|
||||
- qcom,pm8937-pwm
|
||||
- const: qcom,pm8916-pwm
|
||||
|
||||
"#pwm-cells":
|
||||
const: 2
|
||||
|
@ -1,40 +0,0 @@
|
||||
LEDs connected to tlc59116 or tlc59108
|
||||
|
||||
Required properties
|
||||
- compatible: should be "ti,tlc59116" or "ti,tlc59108"
|
||||
- #address-cells: must be 1
|
||||
- #size-cells: must be 0
|
||||
- reg: typically 0x68
|
||||
|
||||
Each led is represented as a sub-node of the ti,tlc59116.
|
||||
See Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
LED sub-node properties:
|
||||
- reg: number of LED line, 0 to 15 or 0 to 7
|
||||
- label: (optional) name of LED
|
||||
- linux,default-trigger : (optional)
|
||||
|
||||
Examples:
|
||||
|
||||
tlc59116@68 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,tlc59116";
|
||||
reg = <0x68>;
|
||||
|
||||
wan@0 {
|
||||
label = "wrt1900ac:amber:wan";
|
||||
reg = <0x0>;
|
||||
};
|
||||
|
||||
2g@2 {
|
||||
label = "wrt1900ac:white:2g";
|
||||
reg = <0x2>;
|
||||
};
|
||||
|
||||
alive@9 {
|
||||
label = "wrt1900ac:green:alive";
|
||||
reg = <0x9>;
|
||||
linux,default_trigger = "heartbeat";
|
||||
};
|
||||
};
|
90
Documentation/devicetree/bindings/leds/ti,tlc59116.yaml
Normal file
90
Documentation/devicetree/bindings/leds/ti,tlc59116.yaml
Normal file
@ -0,0 +1,90 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/leds/ti,tlc59116.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: LEDs connected to tlc59116 or tlc59108
|
||||
|
||||
maintainers:
|
||||
- Andrew Lunn <andrew@lunn.ch>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,tlc59108
|
||||
- ti,tlc59116
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#address-cells":
|
||||
const: 1
|
||||
|
||||
"#size-cells":
|
||||
const: 0
|
||||
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
type: object
|
||||
$ref: common.yaml#
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
minimum: 0
|
||||
maximum: 15
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- "#address-cells"
|
||||
- "#size-cells"
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: ti,tlc59108
|
||||
then:
|
||||
patternProperties:
|
||||
"^led@[0-9a-f]$":
|
||||
properties:
|
||||
reg:
|
||||
items:
|
||||
maximum: 7
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led-controller@68 {
|
||||
compatible = "ti,tlc59116";
|
||||
reg = <0x68>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
led@0 {
|
||||
reg = <0x0>;
|
||||
label = "wrt1900ac:amber:wan";
|
||||
};
|
||||
|
||||
led@2 {
|
||||
reg = <0x2>;
|
||||
label = "wrt1900ac:white:2g";
|
||||
};
|
||||
|
||||
led@9 {
|
||||
reg = <0x9>;
|
||||
label = "wrt1900ac:green:alive";
|
||||
linux,default-trigger = "heartbeat";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -932,7 +932,8 @@ config LEDS_USER
|
||||
config LEDS_NIC78BX
|
||||
tristate "LED support for NI PXI NIC78bx devices"
|
||||
depends on LEDS_CLASS
|
||||
depends on X86 && ACPI
|
||||
depends on HAS_IOPORT
|
||||
depends on X86 || COMPILE_TEST
|
||||
help
|
||||
This option enables support for the User1 and User2 LEDs on NI
|
||||
PXI NIC78bx devices.
|
||||
@ -979,6 +980,7 @@ config LEDS_ST1202
|
||||
depends on I2C
|
||||
depends on OF
|
||||
select LEDS_TRIGGERS
|
||||
select LEDS_TRIGGER_PATTERN
|
||||
help
|
||||
Say Y to enable support for LEDs connected to LED1202
|
||||
LED driver chips accessed via the I2C bus.
|
||||
@ -1022,7 +1024,7 @@ source "drivers/leds/rgb/Kconfig"
|
||||
comment "LED Triggers"
|
||||
source "drivers/leds/trigger/Kconfig"
|
||||
|
||||
comment "Simple LED drivers"
|
||||
source "drivers/leds/simple/Kconfig"
|
||||
comment "Simatic LED drivers"
|
||||
source "drivers/leds/simatic/Kconfig"
|
||||
|
||||
endif # NEW_LEDS
|
||||
|
@ -122,5 +122,5 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
|
||||
# LED Blink
|
||||
obj-y += blink/
|
||||
|
||||
# Simple LED drivers
|
||||
obj-y += simple/
|
||||
# Simatic LED drivers
|
||||
obj-y += simatic/
|
||||
|
@ -159,8 +159,19 @@ static void set_brightness_delayed(struct work_struct *ws)
|
||||
* before this work item runs once. To make sure this works properly
|
||||
* handle LED_SET_BRIGHTNESS_OFF first.
|
||||
*/
|
||||
if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags))
|
||||
if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) {
|
||||
set_brightness_delayed_set_brightness(led_cdev, LED_OFF);
|
||||
/*
|
||||
* The consecutives led_set_brightness(LED_OFF),
|
||||
* led_set_brightness(LED_FULL) could have been executed out of
|
||||
* order (LED_FULL first), if the work_flags has been set
|
||||
* between LED_SET_BRIGHTNESS_OFF and LED_SET_BRIGHTNESS of this
|
||||
* work. To avoid ending with the LED turned off, turn the LED
|
||||
* on again.
|
||||
*/
|
||||
if (led_cdev->delayed_set_value != LED_OFF)
|
||||
set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags))
|
||||
set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value);
|
||||
@ -331,10 +342,13 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value)
|
||||
* change is done immediately afterwards (before the work runs),
|
||||
* it uses a separate work_flag.
|
||||
*/
|
||||
if (value) {
|
||||
led_cdev->delayed_set_value = value;
|
||||
led_cdev->delayed_set_value = value;
|
||||
/* Ensure delayed_set_value is seen before work_flags modification */
|
||||
smp_mb__before_atomic();
|
||||
|
||||
if (value)
|
||||
set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
|
||||
} else {
|
||||
else {
|
||||
clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
|
||||
clear_bit(LED_SET_BLINK, &led_cdev->work_flags);
|
||||
set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags);
|
||||
|
@ -331,7 +331,6 @@ static const struct regmap_config lp8860_regmap_config = {
|
||||
.max_register = LP8860_EEPROM_UNLOCK,
|
||||
.reg_defaults = lp8860_reg_defs,
|
||||
.num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs),
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static const struct reg_default lp8860_eeprom_defs[] = {
|
||||
@ -369,7 +368,6 @@ static const struct regmap_config lp8860_eeprom_regmap_config = {
|
||||
.max_register = LP8860_EEPROM_REG_24,
|
||||
.reg_defaults = lp8860_eeprom_defs,
|
||||
.num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs),
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static int lp8860_probe(struct i2c_client *client)
|
||||
|
@ -32,7 +32,6 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
|
@ -3,11 +3,19 @@
|
||||
* Copyright (C) 2016 National Instruments Corp.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define NIC78BX_USER1_LED_MASK 0x3
|
||||
#define NIC78BX_USER1_GREEN_LED BIT(0)
|
||||
@ -181,8 +189,8 @@ static int nic78bx_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct acpi_device_id led_device_ids[] = {
|
||||
{"NIC78B3", 0},
|
||||
{"", 0},
|
||||
{ "NIC78B3" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, led_device_ids);
|
||||
|
||||
@ -190,7 +198,7 @@ static struct platform_driver led_driver = {
|
||||
.probe = nic78bx_probe,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.acpi_match_table = ACPI_PTR(led_device_ids),
|
||||
.acpi_match_table = led_device_ids,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -62,6 +62,8 @@
|
||||
#define PCA955X_GPIO_HIGH LED_OFF
|
||||
#define PCA955X_GPIO_LOW LED_FULL
|
||||
|
||||
#define PCA955X_BLINK_DEFAULT_MS 1000
|
||||
|
||||
enum pca955x_type {
|
||||
pca9550,
|
||||
pca9551,
|
||||
@ -74,6 +76,7 @@ struct pca955x_chipdef {
|
||||
int bits;
|
||||
u8 slv_addr; /* 7-bit slave address mask */
|
||||
int slv_addr_shift; /* Number of bits to ignore */
|
||||
int blink_div; /* PSC divider */
|
||||
};
|
||||
|
||||
static const struct pca955x_chipdef pca955x_chipdefs[] = {
|
||||
@ -81,26 +84,31 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = {
|
||||
.bits = 2,
|
||||
.slv_addr = /* 110000x */ 0x60,
|
||||
.slv_addr_shift = 1,
|
||||
.blink_div = 44,
|
||||
},
|
||||
[pca9551] = {
|
||||
.bits = 8,
|
||||
.slv_addr = /* 1100xxx */ 0x60,
|
||||
.slv_addr_shift = 3,
|
||||
.blink_div = 38,
|
||||
},
|
||||
[pca9552] = {
|
||||
.bits = 16,
|
||||
.slv_addr = /* 1100xxx */ 0x60,
|
||||
.slv_addr_shift = 3,
|
||||
.blink_div = 44,
|
||||
},
|
||||
[ibm_pca9552] = {
|
||||
.bits = 16,
|
||||
.slv_addr = /* 0110xxx */ 0x30,
|
||||
.slv_addr_shift = 3,
|
||||
.blink_div = 44,
|
||||
},
|
||||
[pca9553] = {
|
||||
.bits = 4,
|
||||
.slv_addr = /* 110001x */ 0x62,
|
||||
.slv_addr_shift = 1,
|
||||
.blink_div = 44,
|
||||
},
|
||||
};
|
||||
|
||||
@ -109,7 +117,9 @@ struct pca955x {
|
||||
struct pca955x_led *leds;
|
||||
const struct pca955x_chipdef *chipdef;
|
||||
struct i2c_client *client;
|
||||
unsigned long active_blink;
|
||||
unsigned long active_pins;
|
||||
unsigned long blink_period;
|
||||
#ifdef CONFIG_LEDS_PCA955X_GPIO
|
||||
struct gpio_chip gpio;
|
||||
#endif
|
||||
@ -124,17 +134,25 @@ struct pca955x_led {
|
||||
struct fwnode_handle *fwnode;
|
||||
};
|
||||
|
||||
#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
|
||||
|
||||
struct pca955x_platform_data {
|
||||
struct pca955x_led *leds;
|
||||
int num_leds;
|
||||
};
|
||||
|
||||
/* 8 bits per input register */
|
||||
static inline int pca95xx_num_input_regs(int bits)
|
||||
static inline int pca955x_num_input_regs(int bits)
|
||||
{
|
||||
return (bits + 7) / 8;
|
||||
}
|
||||
|
||||
/* 4 bits per LED selector register */
|
||||
static inline int pca955x_num_led_regs(int bits)
|
||||
{
|
||||
return (bits + 3) / 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an LED selector register value based on an existing one, with
|
||||
* the appropriate 2-bit state value set for the given LED number (0-3).
|
||||
@ -145,20 +163,25 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
|
||||
((state & 0x3) << (led_num << 1));
|
||||
}
|
||||
|
||||
static inline int pca955x_ledstate(u8 ls, int led_num)
|
||||
{
|
||||
return (ls >> (led_num << 1)) & 0x3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to frequency prescaler register, used to program the
|
||||
* period of the PWM output. period = (PSCx + 1) / 38
|
||||
* period of the PWM output. period = (PSCx + 1) / coeff
|
||||
* Where for pca9551 chips coeff = 38 and for all other chips coeff = 44
|
||||
*/
|
||||
static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
|
||||
static int pca955x_write_psc(struct pca955x *pca955x, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + (2 * n);
|
||||
u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, cmd, val);
|
||||
ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
|
||||
__func__, n, val, ret);
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
|
||||
val, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -169,16 +192,15 @@ static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
|
||||
*
|
||||
* Duty cycle is (256 - PWMx) / 256
|
||||
*/
|
||||
static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
|
||||
static int pca955x_write_pwm(struct pca955x *pca955x, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
|
||||
u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, cmd, val);
|
||||
ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
|
||||
__func__, n, val, ret);
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
|
||||
val, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -186,16 +208,15 @@ static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
|
||||
* Write to LED selector register, which determines the source that
|
||||
* drives the LED output.
|
||||
*/
|
||||
static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
|
||||
static int pca955x_write_ls(struct pca955x *pca955x, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
|
||||
u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, cmd, val);
|
||||
ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
|
||||
__func__, n, val, ret);
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
|
||||
val, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -203,32 +224,43 @@ static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
|
||||
* Read the LED selector register, which determines the source that
|
||||
* drives the LED output.
|
||||
*/
|
||||
static int pca955x_read_ls(struct i2c_client *client, int n, u8 *val)
|
||||
static int pca955x_read_ls(struct pca955x *pca955x, int n, u8 *val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
|
||||
u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, cmd);
|
||||
ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
|
||||
__func__, n, ret);
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
|
||||
return ret;
|
||||
}
|
||||
*val = (u8)ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
|
||||
static int pca955x_read_pwm(struct pca955x *pca955x, int n, u8 *val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
|
||||
u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, cmd);
|
||||
ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
|
||||
__func__, n, ret);
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
|
||||
return ret;
|
||||
}
|
||||
*val = (u8)ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca955x_read_psc(struct pca955x *pca955x, int n, u8 *val)
|
||||
{
|
||||
int ret;
|
||||
u8 cmd;
|
||||
|
||||
cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
|
||||
ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
|
||||
return ret;
|
||||
}
|
||||
*val = (u8)ret;
|
||||
@ -237,30 +269,25 @@ static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
|
||||
|
||||
static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct pca955x_led *pca955x_led = container_of(led_cdev,
|
||||
struct pca955x_led,
|
||||
led_cdev);
|
||||
struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
|
||||
struct pca955x *pca955x = pca955x_led->pca955x;
|
||||
u8 ls, pwm;
|
||||
int ret;
|
||||
|
||||
ret = pca955x_read_ls(pca955x->client, pca955x_led->led_num / 4, &ls);
|
||||
ret = pca955x_read_ls(pca955x, pca955x_led->led_num / 4, &ls);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ls = (ls >> ((pca955x_led->led_num % 4) << 1)) & 0x3;
|
||||
switch (ls) {
|
||||
switch (pca955x_ledstate(ls, pca955x_led->led_num % 4)) {
|
||||
case PCA955X_LS_LED_ON:
|
||||
case PCA955X_LS_BLINK0:
|
||||
ret = LED_FULL;
|
||||
break;
|
||||
case PCA955X_LS_LED_OFF:
|
||||
ret = LED_OFF;
|
||||
break;
|
||||
case PCA955X_LS_BLINK0:
|
||||
ret = LED_HALF;
|
||||
break;
|
||||
case PCA955X_LS_BLINK1:
|
||||
ret = pca955x_read_pwm(pca955x->client, 1, &pwm);
|
||||
ret = pca955x_read_pwm(pca955x, 1, &pwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = 255 - pwm;
|
||||
@ -273,51 +300,150 @@ static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
|
||||
static int pca955x_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pca955x_led *pca955x_led;
|
||||
struct pca955x *pca955x;
|
||||
struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
|
||||
struct pca955x *pca955x = pca955x_led->pca955x;
|
||||
int reg = pca955x_led->led_num / 4;
|
||||
int bit = pca955x_led->led_num % 4;
|
||||
u8 ls;
|
||||
int chip_ls; /* which LSx to use (0-3 potentially) */
|
||||
int ls_led; /* which set of bits within LSx to use (0-3) */
|
||||
int ret;
|
||||
|
||||
pca955x_led = container_of(led_cdev, struct pca955x_led, led_cdev);
|
||||
pca955x = pca955x_led->pca955x;
|
||||
|
||||
chip_ls = pca955x_led->led_num / 4;
|
||||
ls_led = pca955x_led->led_num % 4;
|
||||
|
||||
mutex_lock(&pca955x->lock);
|
||||
|
||||
ret = pca955x_read_ls(pca955x->client, chip_ls, &ls);
|
||||
ret = pca955x_read_ls(pca955x, reg, &ls);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
switch (value) {
|
||||
case LED_FULL:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
|
||||
break;
|
||||
case LED_OFF:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF);
|
||||
break;
|
||||
case LED_HALF:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Use PWM1 for all other values. This has the unwanted
|
||||
* side effect of making all LEDs on the chip share the
|
||||
* same brightness level if set to a value other than
|
||||
* OFF, HALF, or FULL. But, this is probably better than
|
||||
* just turning off for all other values.
|
||||
*/
|
||||
ret = pca955x_write_pwm(pca955x->client, 1, 255 - value);
|
||||
if (ret)
|
||||
if (test_bit(pca955x_led->led_num, &pca955x->active_blink)) {
|
||||
if (value == LED_OFF) {
|
||||
clear_bit(pca955x_led->led_num, &pca955x->active_blink);
|
||||
ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
|
||||
} else {
|
||||
/* No variable brightness for blinking LEDs */
|
||||
goto out;
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (value) {
|
||||
case LED_FULL:
|
||||
ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_ON);
|
||||
break;
|
||||
case LED_OFF:
|
||||
ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Use PWM1 for all other values. This has the unwanted
|
||||
* side effect of making all LEDs on the chip share the
|
||||
* same brightness level if set to a value other than
|
||||
* OFF or FULL. But, this is probably better than just
|
||||
* turning off for all other values.
|
||||
*/
|
||||
ret = pca955x_write_pwm(pca955x, 1, 255 - value);
|
||||
if (ret)
|
||||
goto out;
|
||||
ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = pca955x_write_ls(pca955x->client, chip_ls, ls);
|
||||
ret = pca955x_write_ls(pca955x, reg, ls);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pca955x->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u8 pca955x_period_to_psc(struct pca955x *pca955x, unsigned long period)
|
||||
{
|
||||
/* psc register value = (blink period * coeff) - 1 */
|
||||
period *= pca955x->chipdef->blink_div;
|
||||
period /= MSEC_PER_SEC;
|
||||
period -= 1;
|
||||
|
||||
return period;
|
||||
}
|
||||
|
||||
static unsigned long pca955x_psc_to_period(struct pca955x *pca955x, u8 psc)
|
||||
{
|
||||
unsigned long period = psc;
|
||||
|
||||
/* blink period = (psc register value + 1) / coeff */
|
||||
period += 1;
|
||||
period *= MSEC_PER_SEC;
|
||||
period /= pca955x->chipdef->blink_div;
|
||||
|
||||
return period;
|
||||
}
|
||||
|
||||
static int pca955x_led_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on, unsigned long *delay_off)
|
||||
{
|
||||
struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
|
||||
struct pca955x *pca955x = pca955x_led->pca955x;
|
||||
unsigned long period = *delay_on + *delay_off;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pca955x->lock);
|
||||
|
||||
if (period) {
|
||||
if (*delay_on != *delay_off) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (period < pca955x_psc_to_period(pca955x, 0) ||
|
||||
period > pca955x_psc_to_period(pca955x, 0xff)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
period = pca955x->active_blink ? pca955x->blink_period :
|
||||
PCA955X_BLINK_DEFAULT_MS;
|
||||
}
|
||||
|
||||
if (!pca955x->active_blink ||
|
||||
pca955x->active_blink == BIT(pca955x_led->led_num) ||
|
||||
pca955x->blink_period == period) {
|
||||
u8 psc = pca955x_period_to_psc(pca955x, period);
|
||||
|
||||
if (!test_and_set_bit(pca955x_led->led_num,
|
||||
&pca955x->active_blink)) {
|
||||
u8 ls;
|
||||
int reg = pca955x_led->led_num / 4;
|
||||
int bit = pca955x_led->led_num % 4;
|
||||
|
||||
ret = pca955x_read_ls(pca955x, reg, &ls);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK0);
|
||||
ret = pca955x_write_ls(pca955x, reg, ls);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Force 50% duty cycle to maintain the specified
|
||||
* blink rate.
|
||||
*/
|
||||
ret = pca955x_write_pwm(pca955x, 0, 128);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pca955x->blink_period != period) {
|
||||
pca955x->blink_period = period;
|
||||
ret = pca955x_write_psc(pca955x, 0, psc);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
period = pca955x_psc_to_period(pca955x, psc);
|
||||
period /= 2;
|
||||
*delay_on = period;
|
||||
*delay_off = period;
|
||||
} else {
|
||||
ret = -EBUSY;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&pca955x->lock);
|
||||
@ -455,10 +581,13 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
struct led_classdev *led;
|
||||
struct led_init_data init_data;
|
||||
struct i2c_adapter *adapter;
|
||||
int i, err;
|
||||
int i, bit, err, nls, reg;
|
||||
u8 ls1[4];
|
||||
u8 ls2[4];
|
||||
struct pca955x_platform_data *pdata;
|
||||
u8 psc0;
|
||||
bool keep_psc0 = false;
|
||||
bool set_default_label = false;
|
||||
bool keep_pwm = false;
|
||||
char default_label[8];
|
||||
|
||||
chip = i2c_get_match_data(client);
|
||||
@ -509,10 +638,22 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
mutex_init(&pca955x->lock);
|
||||
pca955x->client = client;
|
||||
pca955x->chipdef = chip;
|
||||
pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS;
|
||||
|
||||
init_data.devname_mandatory = false;
|
||||
init_data.devicename = "pca955x";
|
||||
|
||||
nls = pca955x_num_led_regs(chip->bits);
|
||||
/* Use auto-increment feature to read all the LED selectors at once. */
|
||||
err = i2c_smbus_read_i2c_block_data(client,
|
||||
0x10 | (pca955x_num_input_regs(chip->bits) + 4), nls,
|
||||
ls1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < nls; i++)
|
||||
ls2[i] = ls1[i];
|
||||
|
||||
for (i = 0; i < chip->bits; i++) {
|
||||
pca955x_led = &pca955x->leds[i];
|
||||
pca955x_led->led_num = i;
|
||||
@ -524,18 +665,20 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
case PCA955X_TYPE_GPIO:
|
||||
break;
|
||||
case PCA955X_TYPE_LED:
|
||||
bit = i % 4;
|
||||
reg = i / 4;
|
||||
led = &pca955x_led->led_cdev;
|
||||
led->brightness_set_blocking = pca955x_led_set;
|
||||
led->brightness_get = pca955x_led_get;
|
||||
led->blink_set = pca955x_led_blink;
|
||||
|
||||
if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) {
|
||||
err = pca955x_led_set(led, LED_OFF);
|
||||
if (err)
|
||||
return err;
|
||||
} else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) {
|
||||
err = pca955x_led_set(led, LED_FULL);
|
||||
if (err)
|
||||
return err;
|
||||
if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF)
|
||||
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF);
|
||||
else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON)
|
||||
ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON);
|
||||
else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) {
|
||||
keep_psc0 = true;
|
||||
set_bit(i, &pca955x->active_blink);
|
||||
}
|
||||
|
||||
init_data.fwnode = pdata->leds[i].fwnode;
|
||||
@ -564,39 +707,31 @@ static int pca955x_probe(struct i2c_client *client)
|
||||
return err;
|
||||
|
||||
set_bit(i, &pca955x->active_pins);
|
||||
|
||||
/*
|
||||
* For default-state == "keep", let the core update the
|
||||
* brightness from the hardware, then check the
|
||||
* brightness to see if it's using PWM1. If so, PWM1
|
||||
* should not be written below.
|
||||
*/
|
||||
if (pdata->leds[i].default_state == LEDS_DEFSTATE_KEEP) {
|
||||
if (led->brightness != LED_FULL &&
|
||||
led->brightness != LED_OFF &&
|
||||
led->brightness != LED_HALF)
|
||||
keep_pwm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* PWM0 is used for half brightness or 50% duty cycle */
|
||||
err = pca955x_write_pwm(client, 0, 255 - LED_HALF);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!keep_pwm) {
|
||||
/* PWM1 is used for variable brightness, default to OFF */
|
||||
err = pca955x_write_pwm(client, 1, 0);
|
||||
if (err)
|
||||
return err;
|
||||
for (i = 0; i < nls; i++) {
|
||||
if (ls1[i] != ls2[i]) {
|
||||
err = pca955x_write_ls(pca955x, i, ls2[i]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (keep_psc0) {
|
||||
err = pca955x_read_psc(pca955x, 0, &psc0);
|
||||
} else {
|
||||
psc0 = pca955x_period_to_psc(pca955x, pca955x->blink_period);
|
||||
err = pca955x_write_psc(pca955x, 0, psc0);
|
||||
}
|
||||
|
||||
/* Set to fast frequency so we do not see flashing */
|
||||
err = pca955x_write_psc(client, 0, 0);
|
||||
if (err)
|
||||
return err;
|
||||
err = pca955x_write_psc(client, 1, 0);
|
||||
|
||||
pca955x->blink_period = pca955x_psc_to_period(pca955x, psc0);
|
||||
|
||||
/* Set PWM1 to fast frequency so we do not see flashing */
|
||||
err = pca955x_write_psc(pca955x, 1, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -99,9 +99,9 @@ static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
|
||||
value_h = (u8)(value >> 8);
|
||||
|
||||
/*
|
||||
* Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
|
||||
* where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
|
||||
* and y is the pattern number in hexadecimal (y = 00h .. 07h)
|
||||
* Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
|
||||
* where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
|
||||
* and y is the pattern number in hexadecimal (y = 00h .. 07h)
|
||||
*/
|
||||
ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
|
||||
value_l);
|
||||
@ -189,9 +189,8 @@ static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active
|
||||
static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
|
||||
{
|
||||
struct st1202_led *led = cdev_to_st1202_led(ldev);
|
||||
struct st1202_chip *chip = led->chip;
|
||||
|
||||
return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true);
|
||||
return st1202_channel_set(led->chip, led->led_num, !!value);
|
||||
}
|
||||
|
||||
static int st1202_led_pattern_clear(struct led_classdev *ldev)
|
||||
@ -288,8 +287,8 @@ static int st1202_setup(struct st1202_chip *chip)
|
||||
guard(mutex)(&chip->lock);
|
||||
|
||||
/*
|
||||
* Once the supply voltage is applied, the LED1202 executes some internal checks,
|
||||
* afterwords it stops the oscillator and puts the internal LDO in quiescent mode.
|
||||
* Once the supply voltage is applied, the LED1202 executes some internal checks.
|
||||
* Afterwards, it stops the oscillator and puts the internal LDO in quiescent mode.
|
||||
* To start the device, EN bit must be set inside the “Device Enable” register at
|
||||
* address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
|
||||
* from the internal non-volatile memory and performs an auto-calibration procedure
|
||||
@ -345,14 +344,16 @@ static int st1202_probe(struct i2c_client *client)
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
devm_mutex_init(&client->dev, &chip->lock);
|
||||
ret = devm_mutex_init(&client->dev, &chip->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
chip->client = client;
|
||||
|
||||
ret = st1202_dt_init(chip);
|
||||
ret = st1202_setup(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = st1202_setup(chip);
|
||||
ret = st1202_dt_init(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -141,8 +141,11 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
|
||||
|
||||
/* init the multicolor's LED class device */
|
||||
cdev = &priv->mc_cdev.led_cdev;
|
||||
fwnode_property_read_u32(mcnode, "max-brightness",
|
||||
ret = fwnode_property_read_u32(mcnode, "max-brightness",
|
||||
&cdev->max_brightness);
|
||||
if (ret)
|
||||
goto release_mcnode;
|
||||
|
||||
cdev->flags = LED_CORE_SUSPENDRESUME;
|
||||
cdev->brightness_set_blocking = led_pwm_mc_set;
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#define LPG_PATTERN_CONFIG_REG 0x40
|
||||
#define LPG_SIZE_CLK_REG 0x41
|
||||
#define PWM_CLK_SELECT_MASK GENMASK(1, 0)
|
||||
#define PWM_SIZE_SELECT_MASK BIT(2)
|
||||
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
|
||||
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
|
||||
#define LPG_PREDIV_CLK_REG 0x42
|
||||
@ -412,8 +413,8 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
|
||||
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
|
||||
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
|
||||
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
|
||||
static const unsigned int lpg_pwm_resolution[] = {9};
|
||||
static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
static const unsigned int lpg_pwm_resolution[] = {6, 9};
|
||||
static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
||||
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
{
|
||||
@ -436,12 +437,12 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
* period = --------------------------
|
||||
* refclk
|
||||
*
|
||||
* Resolution = 2^9 bits for PWM or
|
||||
* Resolution = 2^{6 or 9} bits for PWM or
|
||||
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
|
||||
* pre_div = {1, 3, 5, 6} and
|
||||
* M = [0..7].
|
||||
*
|
||||
* This allows for periods between 27uS and 384s for PWM channels and periods between
|
||||
* This allows for periods between 3uS and 384s for PWM channels and periods between
|
||||
* 3uS and 24576s for high resolution PWMs.
|
||||
* The PWM framework wants a period of equal or lower length than requested,
|
||||
* reject anything below minimum period.
|
||||
@ -461,7 +462,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
max_res = LPG_RESOLUTION_9BIT;
|
||||
}
|
||||
|
||||
min_period = div64_u64((u64)NSEC_PER_SEC * (1 << pwm_resolution_arr[0]),
|
||||
min_period = div64_u64((u64)NSEC_PER_SEC * ((1 << pwm_resolution_arr[0]) - 1),
|
||||
clk_rate_arr[clk_len - 1]);
|
||||
if (period <= min_period)
|
||||
return -EINVAL;
|
||||
@ -482,7 +483,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
|
||||
*/
|
||||
|
||||
for (i = 0; i < pwm_resolution_count; i++) {
|
||||
resolution = 1 << pwm_resolution_arr[i];
|
||||
resolution = (1 << pwm_resolution_arr[i]) - 1;
|
||||
for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
|
||||
u64 numerator = period * clk_rate_arr[clk_sel];
|
||||
|
||||
@ -529,10 +530,10 @@ static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
|
||||
unsigned int clk_rate;
|
||||
|
||||
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
|
||||
max = LPG_RESOLUTION_15BIT - 1;
|
||||
max = BIT(lpg_pwm_resolution_hi_res[chan->pwm_resolution_sel]) - 1;
|
||||
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
|
||||
} else {
|
||||
max = LPG_RESOLUTION_9BIT - 1;
|
||||
max = BIT(lpg_pwm_resolution[chan->pwm_resolution_sel]) - 1;
|
||||
clk_rate = lpg_clk_rates[chan->clk_sel];
|
||||
}
|
||||
|
||||
@ -558,7 +559,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
|
||||
val |= GENMASK(5, 4);
|
||||
break;
|
||||
case LPG_SUBTYPE_PWM:
|
||||
val |= BIT(2);
|
||||
val |= FIELD_PREP(PWM_SIZE_SELECT_MASK, chan->pwm_resolution_sel);
|
||||
break;
|
||||
case LPG_SUBTYPE_HI_RES_PWM:
|
||||
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
|
||||
@ -1276,7 +1277,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
|
||||
} else {
|
||||
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
|
||||
resolution = 9;
|
||||
resolution = lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)];
|
||||
}
|
||||
|
||||
if (refclk) {
|
||||
@ -1291,7 +1292,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
|
||||
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * ((1 << resolution) - 1) *
|
||||
pre_div * (1 << m), refclk);
|
||||
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
|
||||
} else {
|
||||
|
@ -68,6 +68,7 @@ struct led_netdev_data {
|
||||
unsigned int last_activity;
|
||||
|
||||
unsigned long mode;
|
||||
unsigned long blink_delay;
|
||||
int link_speed;
|
||||
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes);
|
||||
u8 duplex;
|
||||
@ -86,6 +87,10 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
|
||||
/* Already validated, hw control is possible with the requested mode */
|
||||
if (trigger_data->hw_control) {
|
||||
led_cdev->hw_control_set(led_cdev, trigger_data->mode);
|
||||
if (led_cdev->blink_set) {
|
||||
led_cdev->blink_set(led_cdev, &trigger_data->blink_delay,
|
||||
&trigger_data->blink_delay);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -454,10 +459,11 @@ static ssize_t interval_store(struct device *dev,
|
||||
size_t size)
|
||||
{
|
||||
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
|
||||
struct led_classdev *led_cdev = trigger_data->led_cdev;
|
||||
unsigned long value;
|
||||
int ret;
|
||||
|
||||
if (trigger_data->hw_control)
|
||||
if (trigger_data->hw_control && !led_cdev->blink_set)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 0, &value);
|
||||
@ -466,9 +472,13 @@ static ssize_t interval_store(struct device *dev,
|
||||
|
||||
/* impose some basic bounds on the timer interval */
|
||||
if (value >= 5 && value <= 10000) {
|
||||
cancel_delayed_work_sync(&trigger_data->work);
|
||||
if (trigger_data->hw_control) {
|
||||
trigger_data->blink_delay = value;
|
||||
} else {
|
||||
cancel_delayed_work_sync(&trigger_data->work);
|
||||
|
||||
atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
|
||||
atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
|
||||
}
|
||||
set_baseline_state(trigger_data); /* resets timer */
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user