- New Drivers

- Add support for Intel Cherry Trail Whiskey Cove PMIC LEDs
    - Add support for Awinic AW20036/AW20054/AW20072 LEDs
 
  - New Device Support
    - Add support for PMI632 LPG to QCom LPG
    - Add support for PMI8998 to QCom Flash
    - Add support for MT6331, WLEDs and MT6332 to Mediatek MT6323 PMIC
 
  - New Functionality
    - Implement the LP55xx Charge Pump
    - Add support for suspend / resume to Intel Cherry Trail Whiskey Cove PMIC
    - Add support for breathing mode to Intel Cherry Trail Whiskey Cove PMIC
    - Enable per-pin resolution Pinctrl in LEDs GPIO
 
  - Fix-ups
    - Allow thread to sleep by switching from spinlock to mutex
    - Add lots of Device Tree bindings / support
    - Adapt relationships / dependencies driven by Kconfig
    - Switch I2C drivers from .probe_new() to .probe()
    - Remove superfluous / duplicate code
    - Replace strlcpy() with strscpy() for efficiency and overflow prevention
    - Staticify various functions
    - Trivial: Fixing coding style
    - Simplify / reduce code
 
  - Bug Fixes
    - Prevent NETDEV_LED_MODE_LINKUP from being cleared on rename
    - Repair race between led_set_brightness(LED_{OFF,FULL})
    - Fix Oops relating to sleeping in critical sections
    - Clear LED_INIT_DEFAULT_TRIGGER flag when clearing the current trigger
    - Do not leak resources in error handling paths
    - Fix unsigned comparison which can never be negative
    - Provide missing NULL terminating entries in tables
    - Fix misnaming issues
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmSinb0ACgkQUa+KL4f8
 d2FYfg//WWLVfXRuRpY9ueOxvWj65WVPQSQ+wzF/vRTweogR+lN0qxNPH6yT943z
 ap2EBxpWf84zCifYG4yhTEYDHQT+nH1fIz6xaK29DK8sCQi4WdRpHuE2pE30R/tf
 Q7SyZi9DlWyoqNiqgNNCl7vkTaHpO3trxoxfEfN2YIB0npLf8yyWRz4feVXXsYtg
 41S4Mo7oTxphd7OLvw9PKogdTbT29vBMXen8jzv5g8FObj1Gheg0frq2t2W+bfAl
 27cJJJS7he4/WLCDzXVQfB46Nva5NpqHiANbgOAApDGx3hFCzZFTCg6K7+VucpjY
 bNz3pqmslT5uJxMjqNz8fCSzwWTjyKLHBeGsIT/4HBXD4DnfFbWz9HYkorfNgsu2
 lKEp0SYhSmmuS8IVzJvqDqXg6k21hGpuR9P+dI7teoClh0qLTMCz2L2c9p2zNfth
 0W+WeLYQ67QTRH9EcHo3dlZH/mP/J1jGmUDbF+DFI6bHsg2iahZUA6ixD18E7sWE
 RwtCnb3xyn7eoDe3LwJdKtJMyrX59MbFWqozij2NNhvduXc+m1kH/DX5CSaBUVwf
 RtfDZwWHf4qK4CipuuqOLd5fiUArJ3TSHBxXkoo0Wz7NYXK9k86eIZgWrgdEbvuA
 oHmSousS19Eiscjtzxl7OjvDJMRc0rTJfD7LzYoHQBL4Vpnd8VI=
 =9kd5
 -----END PGP SIGNATURE-----

Merge tag 'leds-next-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds

Pull LED updates from Lee Jones:
 "New Drivers:
   - Add support for Intel Cherry Trail Whiskey Cove PMIC LEDs
   - Add support for Awinic AW20036/AW20054/AW20072 LEDs

  New Device Support:
   - Add support for PMI632 LPG to QCom LPG
   - Add support for PMI8998 to QCom Flash
   - Add support for MT6331, WLEDs and MT6332 to Mediatek MT6323 PMIC

  New Functionality:
   - Implement the LP55xx Charge Pump
   - Add support for suspend / resume to Intel Cherry Trail Whiskey Cove PMIC
   - Add support for breathing mode to Intel Cherry Trail Whiskey Cove PMIC
   - Enable per-pin resolution Pinctrl in LEDs GPIO

  Fix-ups:
   - Allow thread to sleep by switching from spinlock to mutex
   - Add lots of Device Tree bindings / support
   - Adapt relationships / dependencies driven by Kconfig
   - Switch I2C drivers from .probe_new() to .probe()
   - Remove superfluous / duplicate code
   - Replace strlcpy() with strscpy() for efficiency and overflow prevention
   - Staticify various functions
   - Trivial: Fixing coding style
   - Simplify / reduce code

  Bug Fixes:
   - Prevent NETDEV_LED_MODE_LINKUP from being cleared on rename
   - Repair race between led_set_brightness(LED_{OFF,FULL})
   - Fix Oops relating to sleeping in critical sections
   - Clear LED_INIT_DEFAULT_TRIGGER flag when clearing the current trigger
   - Do not leak resources in error handling paths
   - Fix unsigned comparison which can never be negative
   - Provide missing NULL terminating entries in tables
   - Fix misnaming issues"

* tag 'leds-next-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (53 commits)
  leds: leds-mt6323: Adjust return/parameter types in wled get/set callbacks
  leds: sgm3140: Add richtek,rt5033-led compatible
  dt-bindings: leds: sgm3140: Document richtek,rt5033 compatible
  dt-bindings: backlight: kinetic,ktz8866: Add missing type for "current-num-sinks"
  dt-bindings: leds: Drop unneeded quotes
  leds: Fix config reference for AW200xx driver
  leds: leds-mt6323: Add support for WLEDs and MT6332
  leds: leds-mt6323: Add support for MT6331 leds
  leds: leds-mt6323: Open code and drop MT6323_CAL_HW_DUTY macro
  leds: leds-mt6323: Drop MT6323_ prefix from macros and defines
  leds: leds-mt6323: Specify registers and specs in platform data
  dt-bindings: leds: leds-mt6323: Document mt6332 compatible
  dt-bindings: leds: leds-mt6323: Document mt6331 compatible
  leds: simatic-ipc-leds-gpio: Introduce more Kconfig switches
  leds: simatic-ipc-leds-gpio: Split up into multiple drivers
  leds: simatic-ipc-leds-gpio: Move two extra gpio pins into another table
  leds: simatic-ipc-leds-gpio: Add terminating entries to gpio tables
  leds: flash: leds-qcom-flash: Fix an unsigned comparison which can never be negative
  leds: cht-wcove: Remove unneeded semicolon
  leds: cht-wcove: Fix an unsigned comparison which can never be negative
  ...
This commit is contained in:
Linus Torvalds 2023-07-03 11:26:05 -07:00
commit c156d4af43
81 changed files with 2206 additions and 393 deletions

View File

@ -0,0 +1,5 @@
What: /sys/class/leds/<led>/dim
Date: May 2023
Description: 64-level DIM current. If you write a negative value or
"auto", the dim will be calculated according to the
brightness.

View File

@ -0,0 +1,126 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/awinic,aw200xx.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: AWINIC AW200XX LED
maintainers:
- Martin Kurbanov <mmkurbanov@sberdevices.ru>
description: |
This controller is present on AW20036/AW20054/AW20072.
It is a 3x12/6x9/6x12 matrix LED programmed via
an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
3 pattern controllers for auto breathing or group dimming control.
For more product information please see the link below:
aw20036 - https://www.awinic.com/en/productDetail/AW20036QNR#tech-docs
aw20054 - https://www.awinic.com/en/productDetail/AW20054QNR#tech-docs
aw20072 - https://www.awinic.com/en/productDetail/AW20072QNR#tech-docs
properties:
compatible:
enum:
- awinic,aw20036
- awinic,aw20054
- awinic,aw20072
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
awinic,display-rows:
$ref: /schemas/types.yaml#/definitions/uint32
description:
Leds matrix size
patternProperties:
"^led@[0-9a-f]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
description:
LED number
maxItems: 1
led-max-microamp:
default: 9780
description: |
Note that a driver will take the minimum of all LED limits
since the chip has a single global setting.
The maximum output current of each LED is calculated by the
following formula:
IMAXled = 160000 * (592 / 600.5) * (1 / display-rows)
And the minimum output current formula:
IMINled = 3300 * (592 / 600.5) * (1 / display-rows)
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- awinic,display-rows
allOf:
- if:
properties:
compatible:
contains:
const: awinic,aw20036
then:
properties:
awinic,display-rows:
enum: [1, 2, 3]
else:
properties:
awinic,display-rows:
enum: [1, 2, 3, 4, 5, 6, 7]
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@3a {
compatible = "awinic,aw20036";
reg = <0x3a>;
#address-cells = <1>;
#size-cells = <0>;
awinic,display-rows = <3>;
led@0 {
reg = <0x0>;
color = <LED_COLOR_ID_RED>;
led-max-microamp = <9780>;
};
led@1 {
reg = <0x1>;
color = <LED_COLOR_ID_GREEN>;
led-max-microamp = <9780>;
};
led@2 {
reg = <0x2>;
color = <LED_COLOR_ID_BLUE>;
led-max-microamp = <9780>;
};
};
};
...

View File

@ -33,6 +33,7 @@ properties:
current-num-sinks:
description: number of the LED current sinks' channels.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 3, 4, 5, 6]
kinetic,current-ramp-delay-ms:

View File

@ -105,8 +105,6 @@ properties:
- audio-mute
# LED indicates bluetooth power state
- bluetooth-power
# LED indicates activity of all CPUs
- cpu
# LED indicates camera flash state
- flash
# LED indicated keyboard capslock

View File

@ -34,7 +34,7 @@ required:
- color
allOf:
- $ref: "common.yaml#"
- $ref: common.yaml#
additionalProperties: true

View File

@ -66,6 +66,14 @@ properties:
'#size-cells':
const: 0
ti,charge-pump-mode:
description:
Set the operating mode of the internal charge pump as defined in
<dt-bindings/leds/leds-lp55xx.h>.
$ref: /schemas/types.yaml#/definitions/uint32
default: 3 # auto
maximum: 3
patternProperties:
'^multi-led@[0-8]$':
type: object
@ -152,6 +160,7 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
#include <dt-bindings/leds/leds-lp55xx.h>
i2c {
#address-cells = <1>;
@ -164,6 +173,7 @@ examples:
reg = <0x32>;
clock-mode = /bits/ 8 <2>;
pwr-sel = /bits/ 8 <3>; /* D1~9 connected to VOUT */
ti,charge-pump-mode = <LP55XX_CP_BYPASS>;
led@0 {
reg = <0>;

View File

@ -12,7 +12,10 @@ For MediaTek PMIC wrapper bindings see:
Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml
Required properties:
- compatible : Must be "mediatek,mt6323-led"
- compatible : Must be one of
- "mediatek,mt6323-led"
- "mediatek,mt6331-led"
- "mediatek,mt6332-led"
- address-cells : Must be 1
- size-cells : Must be 0

View File

@ -16,18 +16,24 @@ description: >
properties:
compatible:
enum:
- qcom,pm660l-lpg
- qcom,pm8150b-lpg
- qcom,pm8150l-lpg
- qcom,pm8350c-pwm
- qcom,pm8916-pwm
- qcom,pm8941-lpg
- qcom,pm8994-lpg
- qcom,pmc8180c-lpg
- qcom,pmi8994-lpg
- qcom,pmi8998-lpg
- qcom,pmk8550-pwm
oneOf:
- enum:
- qcom,pm660l-lpg
- qcom,pm8150b-lpg
- qcom,pm8150l-lpg
- qcom,pm8350c-pwm
- qcom,pm8916-pwm
- qcom,pm8941-lpg
- qcom,pm8994-lpg
- qcom,pmc8180c-lpg
- qcom,pmi632-lpg
- qcom,pmi8994-lpg
- qcom,pmi8998-lpg
- qcom,pmk8550-pwm
- items:
- enum:
- qcom,pm8550-pwm
- const: qcom,pm8350c-pwm
"#pwm-cells":
const: 2

View File

@ -20,6 +20,7 @@ properties:
compatible:
enum:
- ocs,ocp8110
- richtek,rt5033-led
- sgmicro,sgm3140
enable-gpios:

View File

@ -26,6 +26,8 @@ properties:
- qcom,pm8150c-flash-led
- qcom,pm8150l-flash-led
- qcom,pm8350c-flash-led
- qcom,pm8550-flash-led
- qcom,pmi8998-flash-led
- const: qcom,spmi-flash-led
reg:

View File

@ -32,7 +32,7 @@ patternProperties:
properties:
rohm,led-compatible:
description: LED identification string
$ref: "/schemas/types.yaml#/definitions/string"
$ref: /schemas/types.yaml#/definitions/string
enum:
- bd71828-ambled
- bd71828-grnled

View File

@ -71,6 +71,7 @@ properties:
- qcom,pm8998
- qcom,pma8084
- qcom,pmd9635
- qcom,pmi632
- qcom,pmi8950
- qcom,pmi8962
- qcom,pmi8994

View File

@ -17,6 +17,7 @@ LEDs
uleds
leds-blinkm
leds-cht-wcove
leds-el15203000
leds-lm3556
leds-lp3944

View File

@ -0,0 +1,38 @@
.. SPDX-License-Identifier: GPL-2.0
===========================================================
Kernel driver for Intel Cherry Trail Whiskey Cove PMIC LEDs
===========================================================
/sys/class/leds/<led>/hw_pattern
--------------------------------
Specify a hardware pattern for the Whiskey Cove PMIC LEDs.
The only supported pattern is hardware breathing mode::
"0 2000 1 2000"
^
|
Max-| ---
| / \
| / \
| / \ /
| / \ /
Min-|- ---
|
0------2------4--> time (sec)
The rise and fall times must be the same value.
Supported values are 2000, 1000, 500 and 250 for
breathing frequencies of 1/4, 1/2, 1 and 2 Hz.
The set pattern only controls the timing. For max brightness the last
set brightness is used and the max brightness can be changed
while breathing by writing the brightness attribute.
This is just like how blinking works in the LED subsystem,
for both sw and hw blinking the brightness can also be changed
while blinking. Breathing on this hw really is just a variant
mode of blinking.

View File

@ -58,6 +58,7 @@ LEDs on notebook body, indicating that sound input / output is muted.
* System notification
Good: "rgb:status"
Legacy: "status-led:{red,green,blue}" (Motorola Droid 4)
Legacy: "lp5523:{r,g,b}" (Nokia N900)
@ -65,7 +66,7 @@ Phones usually have multi-color status LED.
* Power management
Good: "platform:*:charging" (allwinner sun50i)
Good: "platform:*:charging" (allwinner sun50i, leds-cht-wcove)
* Screen

View File

@ -94,6 +94,19 @@ config LEDS_ARIEL
Say Y to if your machine is a Dell Wyse 3020 thin client.
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072"
depends on LEDS_CLASS
depends on I2C
help
This option enables support for the AW20036/AW20054/AW20072 LED driver.
It is a 3x12/6x9/6x12 matrix LED driver programmed via
an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
3 pattern controllers for auto breathing or group dimming control.
To compile this driver as a module, choose M here: the module
will be called leds-aw200xx.
config LEDS_AW2013
tristate "LED support for Awinic AW2013"
depends on LEDS_CLASS && I2C && OF
@ -122,6 +135,17 @@ config LEDS_BCM6358
This option enables support for LEDs connected to the BCM6358
LED HW controller accessed via MMIO registers.
config LEDS_CHT_WCOVE
tristate "LED support for Intel Cherry Trail Whiskey Cove PMIC"
depends on LEDS_CLASS
depends on INTEL_SOC_PMIC_CHTWC
help
This option enables support for charger and general purpose LEDs
connected to the Intel Cherrytrail Whiskey Cove PMIC.
To compile this driver as a module, choose M here: the module
will be called leds-cht-wcove.
config LEDS_CPCAP
tristate "LED Support for Motorola CPCAP"
depends on LEDS_CLASS
@ -676,7 +700,7 @@ config LEDS_LM355x
config LEDS_OT200
tristate "LED support for the Bachmann OT200"
depends on LEDS_CLASS && HAS_IOMEM && (X86_32 || COMPILE_TEST)
depends on LEDS_CLASS && HAS_IOPORT && (X86_32 || COMPILE_TEST)
help
This option enables support for the LEDs on the Bachmann OT200.
Say Y to enable LEDs on the Bachmann OT200.
@ -807,8 +831,7 @@ config LEDS_SPI_BYTE
supported: Ubiquiti airCube ISP microcontroller based LED controller.
config LEDS_TI_LMU_COMMON
tristate "LED driver for TI LMU"
depends on LEDS_CLASS
tristate "LED driver for TI LMU" if COMPILE_TEST
select REGMAP
help
Say Y to enable the LED driver for TI LMU devices.
@ -817,16 +840,16 @@ config LEDS_TI_LMU_COMMON
config LEDS_LM3697
tristate "LED driver for LM3697"
depends on LEDS_TI_LMU_COMMON
depends on I2C && OF
depends on LEDS_CLASS && I2C && OF
select LEDS_TI_LMU_COMMON
help
Say Y to enable the LM3697 LED driver for TI LMU devices.
This supports the LED device LM3697.
config LEDS_LM36274
tristate "LED driver for LM36274"
depends on LEDS_TI_LMU_COMMON
depends on MFD_TI_LMU
depends on LEDS_CLASS && MFD_TI_LMU
select LEDS_TI_LMU_COMMON
help
Say Y to enable the LM36274 LED driver for TI LMU devices.
This supports the LED device LM36274.

View File

@ -14,12 +14,14 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
obj-$(CONFIG_LEDS_BD2606MVV) += leds-bd2606mvv.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_CHT_WCOVE) += leds-cht-wcove.o
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o
obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o

View File

@ -425,7 +425,7 @@ static void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
struct led_flash_setting *s;
strlcpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
strscpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
sizeof(v4l2_sd_cfg->dev_name));
s = &v4l2_sd_cfg->intensity;

View File

@ -651,8 +651,8 @@ static int as3645a_v4l2_setup(struct as3645a *flash)
},
};
strlcpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name));
strlcpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name,
strscpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name));
strscpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name,
sizeof(cfgind.dev_name));
flash->vf = v4l2_flash_init(
@ -759,7 +759,7 @@ static struct i2c_driver as3645a_i2c_driver = {
.of_match_table = as3645a_of_table,
.name = AS_NAME,
},
.probe_new = as3645a_probe,
.probe = as3645a_probe,
.remove = as3645a_remove,
.id_table = as3645a_id_table,
};

View File

@ -471,7 +471,7 @@ static struct i2c_driver lm3601x_i2c_driver = {
.name = "lm3601x",
.of_match_table = of_lm3601x_leds_match,
},
.probe_new = lm3601x_probe,
.probe = lm3601x_probe,
.remove = lm3601x_remove,
.id_table = lm3601x_id,
};

View File

@ -18,7 +18,8 @@
#define FLASH_TYPE_VAL 0x18
#define FLASH_SUBTYPE_REG 0x05
#define FLASH_SUBTYPE_3CH_VAL 0x04
#define FLASH_SUBTYPE_3CH_PM8150_VAL 0x04
#define FLASH_SUBTYPE_3CH_PMI8998_VAL 0x03
#define FLASH_SUBTYPE_4CH_VAL 0x07
#define FLASH_STS_3CH_OTST1 BIT(0)
@ -416,6 +417,14 @@ static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
bool enable = !!brightness;
int rc;
rc = set_flash_strobe(led, SW_STROBE, false);
if (rc)
return rc;
rc = set_flash_module_en(led, false);
if (rc)
return rc;
rc = set_flash_current(led, current_ma, TORCH_MODE);
if (rc)
return rc;
@ -529,9 +538,9 @@ static int qcom_flash_register_led_device(struct device *dev,
struct led_init_data init_data;
struct led_classdev_flash *flash = &led->flash;
struct led_flash_setting *brightness, *timeout;
u32 count, current_ua, timeout_us;
u32 current_ua, timeout_us;
u32 channels[4];
int i, rc;
int i, rc, count;
count = fwnode_property_count_u32(node, "led-sources");
if (count <= 0) {
@ -682,7 +691,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
return rc;
}
if (val == FLASH_SUBTYPE_3CH_VAL) {
if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) {
flash_data->hw_type = QCOM_MVFLASH_3CH;
flash_data->max_channels = 3;
regs = mvflash_3ch_regs;

View File

@ -419,7 +419,7 @@ static struct i2c_driver rt4505_driver = {
.name = "rt4505",
.of_match_table = of_match_ptr(rt4505_leds_match),
},
.probe_new = rt4505_probe,
.probe = rt4505_probe,
.remove = rt4505_remove,
.shutdown = rt4505_shutdown,
};

View File

@ -291,6 +291,7 @@ static int sgm3140_remove(struct platform_device *pdev)
static const struct of_device_id sgm3140_dt_match[] = {
{ .compatible = "ocs,ocp8110" },
{ .compatible = "richtek,rt5033-led" },
{ .compatible = "sgmicro,sgm3140" },
{ /* sentinel */ }
};

View File

@ -409,7 +409,7 @@ static int led_classdev_next_name(const char *init_name, char *name,
int ret = 0;
struct device *dev;
strlcpy(name, init_name, len);
strscpy(name, init_name, len);
while ((ret < len) &&
(dev = class_find_device_by_name(leds_class, name))) {

View File

@ -114,21 +114,14 @@ static void led_timer_function(struct timer_list *t)
mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}
static void set_brightness_delayed(struct work_struct *ws)
static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev,
unsigned int value)
{
struct led_classdev *led_cdev =
container_of(ws, struct led_classdev, set_brightness_work);
int ret = 0;
if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
led_cdev->delayed_set_value = LED_OFF;
led_stop_software_blink(led_cdev);
}
ret = __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
ret = __led_set_brightness(led_cdev, value);
if (ret == -ENOTSUPP)
ret = __led_set_brightness_blocking(led_cdev,
led_cdev->delayed_set_value);
ret = __led_set_brightness_blocking(led_cdev, value);
if (ret < 0 &&
/* LED HW might have been unplugged, therefore don't warn */
!(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&
@ -137,6 +130,37 @@ static void set_brightness_delayed(struct work_struct *ws)
"Setting an LED's brightness failed (%d)\n", ret);
}
static void set_brightness_delayed(struct work_struct *ws)
{
struct led_classdev *led_cdev =
container_of(ws, struct led_classdev, set_brightness_work);
if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
led_stop_software_blink(led_cdev);
set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags);
}
/*
* Triggers may call led_set_brightness(LED_OFF),
* led_set_brightness(LED_FULL) in quick succession to disable blinking
* and turn the LED on. Both actions may have been scheduled to run
* 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))
set_brightness_delayed_set_brightness(led_cdev, LED_OFF);
if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags))
set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value);
if (test_and_clear_bit(LED_SET_BLINK, &led_cdev->work_flags)) {
unsigned long delay_on = led_cdev->delayed_delay_on;
unsigned long delay_off = led_cdev->delayed_delay_off;
led_blink_set(led_cdev, &delay_on, &delay_off);
}
}
static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
@ -229,6 +253,22 @@ void led_blink_set_oneshot(struct led_classdev *led_cdev,
}
EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on,
unsigned long delay_off)
{
/* If necessary delegate to a work queue task. */
if (led_cdev->blink_set && led_cdev->brightness_set_blocking) {
led_cdev->delayed_delay_on = delay_on;
led_cdev->delayed_delay_off = delay_off;
set_bit(LED_SET_BLINK, &led_cdev->work_flags);
schedule_work(&led_cdev->set_brightness_work);
return;
}
led_blink_set(led_cdev, &delay_on, &delay_off);
}
EXPORT_SYMBOL_GPL(led_blink_set_nosleep);
void led_stop_software_blink(struct led_classdev *led_cdev)
{
del_timer_sync(&led_cdev->blink_timer);
@ -271,8 +311,23 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value)
if (!__led_set_brightness(led_cdev, value))
return;
/* If brightness setting can sleep, delegate it to a work queue task */
led_cdev->delayed_set_value = value;
/*
* Brightness setting can sleep, delegate it to a work queue task.
* value 0 / LED_OFF is special, since it also disables hw-blinking
* (sw-blink disable is handled in led_set_brightness()).
* To avoid a hw-blink-disable getting lost when a second brightness
* change is done immediately afterwards (before the work runs),
* it uses a separate work_flag.
*/
if (value) {
led_cdev->delayed_set_value = value;
set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
} 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);
}
schedule_work(&led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nopm);

View File

@ -185,6 +185,7 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
led_cdev->trigger = NULL;
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
led_set_brightness(led_cdev, LED_OFF);
}
if (trig) {
@ -393,8 +394,8 @@ void led_trigger_event(struct led_trigger *trig,
EXPORT_SYMBOL_GPL(led_trigger_event);
static void led_trigger_blink_setup(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off,
unsigned long delay_on,
unsigned long delay_off,
int oneshot,
int invert)
{
@ -406,25 +407,25 @@ static void led_trigger_blink_setup(struct led_trigger *trig,
rcu_read_lock();
list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) {
if (oneshot)
led_blink_set_oneshot(led_cdev, delay_on, delay_off,
led_blink_set_oneshot(led_cdev, &delay_on, &delay_off,
invert);
else
led_blink_set(led_cdev, delay_on, delay_off);
led_blink_set_nosleep(led_cdev, delay_on, delay_off);
}
rcu_read_unlock();
}
void led_trigger_blink(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off)
unsigned long delay_on,
unsigned long delay_off)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0);
}
EXPORT_SYMBOL_GPL(led_trigger_blink);
void led_trigger_blink_oneshot(struct led_trigger *trig,
unsigned long *delay_on,
unsigned long *delay_off,
unsigned long delay_on,
unsigned long delay_off,
int invert)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert);

View File

@ -346,7 +346,7 @@ static struct i2c_driver an30259a_driver = {
.name = "leds-an30259a",
.of_match_table = of_match_ptr(an30259a_match_table),
},
.probe_new = an30259a_probe,
.probe = an30259a_probe,
.remove = an30259a_remove,
.id_table = an30259a_id,
};

594
drivers/leds/leds-aw200xx.c Normal file
View File

@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Awinic AW20036/AW20054/AW20072 LED driver
*
* Copyright (c) 2023, SberDevices. All Rights Reserved.
*
* Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/units.h>
#define AW200XX_DIM_MAX (BIT(6) - 1)
#define AW200XX_FADE_MAX (BIT(8) - 1)
#define AW200XX_IMAX_DEFAULT_uA 60000
#define AW200XX_IMAX_MAX_uA 160000
#define AW200XX_IMAX_MIN_uA 3300
/* Page 0 */
#define AW200XX_REG_PAGE0_BASE 0xc000
/* Select page register */
#define AW200XX_REG_PAGE 0xF0
#define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0))
#define AW200XX_PAGE_SHIFT 0
#define AW200XX_NUM_PAGES 6
#define AW200XX_PAGE_SIZE 256
#define AW200XX_REG(page, reg) \
(AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg))
#define AW200XX_REG_MAX \
AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1)
#define AW200XX_PAGE0 0
#define AW200XX_PAGE1 1
#define AW200XX_PAGE2 2
#define AW200XX_PAGE3 3
#define AW200XX_PAGE4 4
#define AW200XX_PAGE5 5
/* Chip ID register */
#define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00)
#define AW200XX_IDR_CHIPID 0x18
/* Sleep mode register */
#define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01)
#define AW200XX_SLPCR_ACTIVE 0x00
/* Reset register */
#define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02)
#define AW200XX_RSTR_RESET 0x01
/* Global current configuration register */
#define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03)
#define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4)
#define AW200XX_GCCR_IMAX(x) ((x) << 4)
#define AW200XX_GCCR_ALLON BIT(3)
/* Fast clear display control register */
#define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04)
#define AW200XX_FCD_CLEAR 0x01
/* Display size configuration */
#define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80)
#define AW200XX_DSIZE_COLUMNS_MAX 12
#define AW200XX_LED2REG(x, columns) \
((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))
/*
* DIM current configuration register (page 4).
* The even address for current DIM configuration.
* The odd address for current FADE configuration
*/
#define AW200XX_REG_DIM(x, columns) \
AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
/*
* Duty ratio of display scan (see p.15 of datasheet for formula):
* duty = (592us / 600.5us) * (1 / (display_rows + 1))
*
* Multiply to 1000 (MILLI) to improve the accuracy of calculations.
*/
#define AW200XX_DUTY_RATIO(rows) \
(((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI)
struct aw200xx_chipdef {
u32 channels;
u32 display_size_rows_max;
u32 display_size_columns;
};
struct aw200xx_led {
struct led_classdev cdev;
struct aw200xx *chip;
int dim;
u32 num;
};
struct aw200xx {
const struct aw200xx_chipdef *cdef;
struct i2c_client *client;
struct regmap *regmap;
struct mutex mutex;
u32 num_leds;
u32 display_rows;
struct aw200xx_led leds[];
};
static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct led_classdev *cdev = dev_get_drvdata(dev);
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
int dim = led->dim;
if (dim < 0)
return sysfs_emit(buf, "auto\n");
return sysfs_emit(buf, "%d\n", dim);
}
static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct led_classdev *cdev = dev_get_drvdata(dev);
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
struct aw200xx *chip = led->chip;
u32 columns = chip->cdef->display_size_columns;
int dim;
ssize_t ret;
if (sysfs_streq(buf, "auto")) {
dim = -1;
} else {
ret = kstrtoint(buf, 0, &dim);
if (ret)
return ret;
if (dim > AW200XX_DIM_MAX)
return -EINVAL;
}
mutex_lock(&chip->mutex);
if (dim >= 0) {
ret = regmap_write(chip->regmap,
AW200XX_REG_DIM(led->num, columns), dim);
if (ret)
goto out_unlock;
}
led->dim = dim;
ret = count;
out_unlock:
mutex_unlock(&chip->mutex);
return ret;
}
static DEVICE_ATTR_RW(dim);
static struct attribute *dim_attrs[] = {
&dev_attr_dim.attr,
NULL
};
ATTRIBUTE_GROUPS(dim);
static int aw200xx_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
struct aw200xx *chip = led->chip;
int dim;
u32 reg;
int ret;
mutex_lock(&chip->mutex);
reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns);
dim = led->dim;
if (dim < 0)
dim = max_t(int,
brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX),
1);
ret = regmap_write(chip->regmap, reg, dim);
if (ret)
goto out_unlock;
ret = regmap_write(chip->regmap,
AW200XX_REG_DIM2FADE(reg), brightness);
out_unlock:
mutex_unlock(&chip->mutex);
return ret;
}
static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
u32 global_imax_uA)
{
u64 led_imax_uA;
/*
* The output current of each LED (see p.14 of datasheet for formula):
* Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty
*
* The value of duty is determined by the following formula:
* duty = (592us / 600.5us) * (1 / (display_rows + 1))
*
* Calculated for the maximum values of fade and dim.
* We divide by 1000 because we earlier multiplied by 1000 to improve
* accuracy when calculating the duty.
*/
led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows);
do_div(led_imax_uA, MILLI);
return led_imax_uA;
}
static u32 aw200xx_imax_to_global(const struct aw200xx *const chip,
u32 led_imax_uA)
{
u32 duty = AW200XX_DUTY_RATIO(chip->display_rows);
/* The output current of each LED (see p.14 of datasheet for formula) */
return (led_imax_uA * 1000U) / duty;
}
#define AW200XX_IMAX_MULTIPLIER1 10000
#define AW200XX_IMAX_MULTIPLIER2 3333
#define AW200XX_IMAX_BASE_VAL1 0
#define AW200XX_IMAX_BASE_VAL2 8
/*
* The AW200XX has a 4-bit register (GCCR) to configure the global current,
* which ranges from 3.3mA to 160mA. The following table indicates the values
* of the global current, divided into two parts:
*
* +-----------+-----------------+-----------+-----------------+
* | reg value | global max (mA) | reg value | global max (mA) |
* +-----------+-----------------+-----------+-----------------+
* | 0 | 10 | 8 | 3.3 |
* | 1 | 20 | 9 | 6.7 |
* | 2 | 30 | 10 | 10 |
* | 3 | 40 | 11 | 13.3 |
* | 4 | 60 | 12 | 20 |
* | 5 | 80 | 13 | 26.7 |
* | 6 | 120 | 14 | 40 |
* | 7 | 160 | 15 | 53.3 |
* +-----------+-----------------+-----------+-----------------+
*
* The left part with a multiplier of 10, and the right part with a multiplier
* of 3.3.
* So we have two formulas to calculate the global current:
* for the left part of the table:
* imax = coefficient * 10
*
* for the right part of the table:
* imax = coefficient * 3.3
*
* The coefficient table consists of the following values:
* 1, 2, 3, 4, 6, 8, 12, 16.
*/
static int aw200xx_set_imax(const struct aw200xx *const chip,
u32 led_imax_uA)
{
u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA);
u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16};
u32 gccr_imax = UINT_MAX;
u32 cur_imax = 0;
int i;
for (i = 0; i < ARRAY_SIZE(coeff_table); i++) {
u32 imax;
/* select closest ones */
imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1;
if (g_imax_uA >= imax && imax > cur_imax) {
cur_imax = imax;
gccr_imax = i + AW200XX_IMAX_BASE_VAL1;
}
imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2;
imax = DIV_ROUND_CLOSEST(imax, 100) * 100;
if (g_imax_uA >= imax && imax > cur_imax) {
cur_imax = imax;
gccr_imax = i + AW200XX_IMAX_BASE_VAL2;
}
}
if (gccr_imax == UINT_MAX)
return -EINVAL;
return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
AW200XX_GCCR_IMAX_MASK,
AW200XX_GCCR_IMAX(gccr_imax));
}
static int aw200xx_chip_reset(const struct aw200xx *const chip)
{
int ret;
ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET);
if (ret)
return ret;
regcache_mark_dirty(chip->regmap);
return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
}
static int aw200xx_chip_init(const struct aw200xx *const chip)
{
int ret;
ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE,
chip->display_rows - 1);
if (ret)
return ret;
ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR,
AW200XX_SLPCR_ACTIVE);
if (ret)
return ret;
return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
}
static int aw200xx_chip_check(const struct aw200xx *const chip)
{
struct device *dev = &chip->client->dev;
u32 chipid;
int ret;
ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid);
if (ret)
return dev_err_probe(dev, ret, "Failed to read chip ID\n");
if (chipid != AW200XX_IDR_CHIPID)
return dev_err_probe(dev, -ENODEV,
"Chip reported wrong ID: %x\n", chipid);
return 0;
}
static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
{
struct fwnode_handle *child;
u32 current_min, current_max, min_uA;
int ret;
int i;
ret = device_property_read_u32(dev, "awinic,display-rows",
&chip->display_rows);
if (ret)
return dev_err_probe(dev, ret,
"Failed to read 'display-rows' property\n");
if (!chip->display_rows ||
chip->display_rows > chip->cdef->display_size_rows_max) {
return dev_err_probe(dev, ret,
"Invalid leds display size %u\n",
chip->display_rows);
}
current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
min_uA = UINT_MAX;
i = 0;
device_for_each_child_node(dev, child) {
struct led_init_data init_data = {};
struct aw200xx_led *led;
u32 source, imax;
ret = fwnode_property_read_u32(child, "reg", &source);
if (ret) {
dev_err(dev, "Missing reg property\n");
chip->num_leds--;
continue;
}
if (source >= chip->cdef->channels) {
dev_err(dev, "LED reg %u out of range (max %u)\n",
source, chip->cdef->channels);
chip->num_leds--;
continue;
}
ret = fwnode_property_read_u32(child, "led-max-microamp",
&imax);
if (ret) {
dev_info(&chip->client->dev,
"DT property led-max-microamp is missing\n");
} else if (imax < current_min || imax > current_max) {
dev_err(dev, "Invalid value %u for led-max-microamp\n",
imax);
chip->num_leds--;
continue;
} else {
min_uA = min(min_uA, imax);
}
led = &chip->leds[i];
led->dim = -1;
led->num = source;
led->chip = chip;
led->cdev.brightness_set_blocking = aw200xx_brightness_set;
led->cdev.groups = dim_groups;
init_data.fwnode = child;
ret = devm_led_classdev_register_ext(dev, &led->cdev,
&init_data);
if (ret) {
fwnode_handle_put(child);
break;
}
i++;
}
if (!chip->num_leds)
return -EINVAL;
if (min_uA == UINT_MAX) {
min_uA = aw200xx_imax_from_global(chip,
AW200XX_IMAX_DEFAULT_uA);
}
return aw200xx_set_imax(chip, min_uA);
}
static const struct regmap_range_cfg aw200xx_ranges[] = {
{
.name = "aw200xx",
.range_min = 0,
.range_max = AW200XX_REG_MAX,
.selector_reg = AW200XX_REG_PAGE,
.selector_mask = AW200XX_PAGE_MASK,
.selector_shift = AW200XX_PAGE_SHIFT,
.window_start = 0,
.window_len = AW200XX_PAGE_SIZE,
},
};
static const struct regmap_range aw200xx_writeonly_ranges[] = {
regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX),
};
static const struct regmap_access_table aw200xx_readable_table = {
.no_ranges = aw200xx_writeonly_ranges,
.n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges),
};
static const struct regmap_range aw200xx_readonly_ranges[] = {
regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR),
};
static const struct regmap_access_table aw200xx_writeable_table = {
.no_ranges = aw200xx_readonly_ranges,
.n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges),
};
static const struct regmap_config aw200xx_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = AW200XX_REG_MAX,
.ranges = aw200xx_ranges,
.num_ranges = ARRAY_SIZE(aw200xx_ranges),
.rd_table = &aw200xx_readable_table,
.wr_table = &aw200xx_writeable_table,
.cache_type = REGCACHE_RBTREE,
};
static int aw200xx_probe(struct i2c_client *client)
{
const struct aw200xx_chipdef *cdef;
struct aw200xx *chip;
int count;
int ret;
cdef = device_get_match_data(&client->dev);
if (!cdef)
return -ENODEV;
count = device_get_child_node_count(&client->dev);
if (!count || count > cdef->channels)
return dev_err_probe(&client->dev, -EINVAL,
"Incorrect number of leds (%d)", count);
chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count),
GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cdef = cdef;
chip->num_leds = count;
chip->client = client;
i2c_set_clientdata(client, chip);
chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config);
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
ret = aw200xx_chip_check(chip);
if (ret)
return ret;
mutex_init(&chip->mutex);
/* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
mutex_lock(&chip->mutex);
ret = aw200xx_chip_reset(chip);
if (ret)
goto out_unlock;
ret = aw200xx_probe_fw(&client->dev, chip);
if (ret)
goto out_unlock;
ret = aw200xx_chip_init(chip);
out_unlock:
mutex_unlock(&chip->mutex);
return ret;
}
static void aw200xx_remove(struct i2c_client *client)
{
struct aw200xx *chip = i2c_get_clientdata(client);
aw200xx_chip_reset(chip);
mutex_destroy(&chip->mutex);
}
static const struct aw200xx_chipdef aw20036_cdef = {
.channels = 36,
.display_size_rows_max = 3,
.display_size_columns = 12,
};
static const struct aw200xx_chipdef aw20054_cdef = {
.channels = 54,
.display_size_rows_max = 6,
.display_size_columns = 9,
};
static const struct aw200xx_chipdef aw20072_cdef = {
.channels = 72,
.display_size_rows_max = 6,
.display_size_columns = 12,
};
static const struct i2c_device_id aw200xx_id[] = {
{ "aw20036" },
{ "aw20054" },
{ "aw20072" },
{}
};
MODULE_DEVICE_TABLE(i2c, aw200xx_id);
static const struct of_device_id aw200xx_match_table[] = {
{ .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
{ .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
{ .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
{}
};
MODULE_DEVICE_TABLE(of, aw200xx_match_table);
static struct i2c_driver aw200xx_driver = {
.driver = {
.name = "aw200xx",
.of_match_table = aw200xx_match_table,
},
.probe_new = aw200xx_probe,
.remove = aw200xx_remove,
.id_table = aw200xx_id,
};
module_i2c_driver(aw200xx_driver);
MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>");
MODULE_DESCRIPTION("AW200XX LED driver");
MODULE_LICENSE("GPL");

View File

@ -422,7 +422,7 @@ static struct i2c_driver aw2013_driver = {
.name = "leds-aw2013",
.of_match_table = of_match_ptr(aw2013_match_table),
},
.probe_new = aw2013_probe,
.probe = aw2013_probe,
.remove = aw2013_remove,
};

View File

@ -150,7 +150,7 @@ static struct i2c_driver bd2606mvv_driver = {
.name = "leds-bd2606mvv",
.of_match_table = of_match_ptr(of_bd2606mvv_leds_match),
},
.probe_new = bd2606mvv_probe,
.probe = bd2606mvv_probe,
};
module_i2c_driver(bd2606mvv_driver);

View File

@ -786,7 +786,7 @@ static struct i2c_driver bd2802_i2c_driver = {
.name = "BD2802",
.pm = &bd2802_pm,
},
.probe_new = bd2802_probe,
.probe = bd2802_probe,
.remove = bd2802_remove,
.id_table = bd2802_id,
};

View File

@ -561,7 +561,7 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
return -ENODEV;
}
strlcpy(info->type, "blinkm", I2C_NAME_SIZE);
strscpy(info->type, "blinkm", I2C_NAME_SIZE);
return 0;
}
@ -730,7 +730,7 @@ static struct i2c_driver blinkm_driver = {
.driver = {
.name = "blinkm",
},
.probe_new = blinkm_probe,
.probe = blinkm_probe,
.remove = blinkm_remove,
.id_table = blinkm_id,
.detect = blinkm_detect,

View File

@ -0,0 +1,476 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC
*
* Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com>
* Copyright 2023 Hans de Goede <hansg@kernel.org>
*
* Register info comes from the Lenovo Yoga Book Android opensource code
* available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z:
* YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c
*/
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/suspend.h>
#define CHT_WC_LED1_CTRL 0x5e1f
#define CHT_WC_LED1_FSM 0x5e20
#define CHT_WC_LED1_PWM 0x5e21
#define CHT_WC_LED2_CTRL 0x4fdf
#define CHT_WC_LED2_FSM 0x4fe0
#define CHT_WC_LED2_PWM 0x4fe1
#define CHT_WC_LED1_SWCTL BIT(0) /* HW or SW control of charging led */
#define CHT_WC_LED1_ON BIT(1)
#define CHT_WC_LED2_ON BIT(0)
#define CHT_WC_LED_I_MA2_5 (2 << 2) /* LED current limit */
#define CHT_WC_LED_I_MASK GENMASK(3, 2) /* LED current limit mask */
#define CHT_WC_LED_F_1_4_HZ (0 << 4)
#define CHT_WC_LED_F_1_2_HZ (1 << 4)
#define CHT_WC_LED_F_1_HZ (2 << 4)
#define CHT_WC_LED_F_2_HZ (3 << 4)
#define CHT_WC_LED_F_MASK GENMASK(5, 4)
#define CHT_WC_LED_EFF_OFF (0 << 1)
#define CHT_WC_LED_EFF_ON (1 << 1)
#define CHT_WC_LED_EFF_BLINKING (2 << 1)
#define CHT_WC_LED_EFF_BREATHING (3 << 1)
#define CHT_WC_LED_EFF_MASK GENMASK(2, 1)
#define CHT_WC_LED_COUNT 2
struct cht_wc_led_regs {
/* Register addresses */
u16 ctrl;
u16 fsm;
u16 pwm;
/* Mask + values for turning the LED on/off */
u8 on_off_mask;
u8 on_val;
u8 off_val;
};
struct cht_wc_led_saved_regs {
unsigned int ctrl;
unsigned int fsm;
unsigned int pwm;
};
struct cht_wc_led {
struct led_classdev cdev;
const struct cht_wc_led_regs *regs;
struct regmap *regmap;
struct mutex mutex;
struct cht_wc_led_saved_regs saved_regs;
};
struct cht_wc_leds {
struct cht_wc_led leds[CHT_WC_LED_COUNT];
/* Saved LED1 initial register values */
struct cht_wc_led_saved_regs led1_initial_regs;
};
static const struct cht_wc_led_regs cht_wc_led_regs[CHT_WC_LED_COUNT] = {
{
.ctrl = CHT_WC_LED1_CTRL,
.fsm = CHT_WC_LED1_FSM,
.pwm = CHT_WC_LED1_PWM,
.on_off_mask = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
.on_val = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
.off_val = CHT_WC_LED1_SWCTL,
},
{
.ctrl = CHT_WC_LED2_CTRL,
.fsm = CHT_WC_LED2_FSM,
.pwm = CHT_WC_LED2_PWM,
.on_off_mask = CHT_WC_LED2_ON,
.on_val = CHT_WC_LED2_ON,
.off_val = 0,
},
};
static const char * const cht_wc_leds_names[CHT_WC_LED_COUNT] = {
"platform::" LED_FUNCTION_CHARGING,
"platform::" LED_FUNCTION_INDICATOR,
};
static int cht_wc_leds_brightness_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
int ret;
mutex_lock(&led->mutex);
if (!value) {
ret = regmap_update_bits(led->regmap, led->regs->ctrl,
led->regs->on_off_mask, led->regs->off_val);
if (ret < 0) {
dev_err(cdev->dev, "Failed to turn off: %d\n", ret);
goto out;
}
/* Disable HW blinking */
ret = regmap_update_bits(led->regmap, led->regs->fsm,
CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
if (ret < 0)
dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
} else {
ret = regmap_write(led->regmap, led->regs->pwm, value);
if (ret < 0) {
dev_err(cdev->dev, "Failed to set brightness: %d\n", ret);
goto out;
}
ret = regmap_update_bits(led->regmap, led->regs->ctrl,
led->regs->on_off_mask, led->regs->on_val);
if (ret < 0)
dev_err(cdev->dev, "Failed to turn on: %d\n", ret);
}
out:
mutex_unlock(&led->mutex);
return ret;
}
static enum led_brightness cht_wc_leds_brightness_get(struct led_classdev *cdev)
{
struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
unsigned int val;
int ret;
mutex_lock(&led->mutex);
ret = regmap_read(led->regmap, led->regs->ctrl, &val);
if (ret < 0) {
dev_err(cdev->dev, "Failed to read LED CTRL reg: %d\n", ret);
ret = 0;
goto done;
}
val &= led->regs->on_off_mask;
if (val != led->regs->on_val) {
ret = 0;
goto done;
}
ret = regmap_read(led->regmap, led->regs->pwm, &val);
if (ret < 0) {
dev_err(cdev->dev, "Failed to read LED PWM reg: %d\n", ret);
ret = 0;
goto done;
}
ret = val;
done:
mutex_unlock(&led->mutex);
return ret;
}
/* Return blinking period for given CTRL reg value */
static unsigned long cht_wc_leds_get_period(int ctrl)
{
ctrl &= CHT_WC_LED_F_MASK;
switch (ctrl) {
case CHT_WC_LED_F_1_4_HZ:
return 1000 * 4;
case CHT_WC_LED_F_1_2_HZ:
return 1000 * 2;
case CHT_WC_LED_F_1_HZ:
return 1000;
case CHT_WC_LED_F_2_HZ:
return 1000 / 2;
}
return 0;
}
/*
* Find suitable hardware blink mode for given period.
* period < 750 ms - select 2 HZ
* 750 ms <= period < 1500 ms - select 1 HZ
* 1500 ms <= period < 3000 ms - select 1/2 HZ
* 3000 ms <= period < 5000 ms - select 1/4 HZ
* 5000 ms <= period - return -1
*/
static int cht_wc_leds_find_freq(unsigned long period)
{
if (period < 750)
return CHT_WC_LED_F_2_HZ;
else if (period < 1500)
return CHT_WC_LED_F_1_HZ;
else if (period < 3000)
return CHT_WC_LED_F_1_2_HZ;
else if (period < 5000)
return CHT_WC_LED_F_1_4_HZ;
else
return -1;
}
static int cht_wc_leds_set_effect(struct led_classdev *cdev,
unsigned long *delay_on,
unsigned long *delay_off,
u8 effect)
{
struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
int ctrl, ret;
mutex_lock(&led->mutex);
/* Blink with 1 Hz as default if nothing specified */
if (!*delay_on && !*delay_off)
*delay_on = *delay_off = 500;
ctrl = cht_wc_leds_find_freq(*delay_on + *delay_off);
if (ctrl < 0) {
/* Disable HW blinking */
ret = regmap_update_bits(led->regmap, led->regs->fsm,
CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
if (ret < 0)
dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
/* Fallback to software timer */
*delay_on = *delay_off = 0;
ret = -EINVAL;
goto done;
}
ret = regmap_update_bits(led->regmap, led->regs->fsm,
CHT_WC_LED_EFF_MASK, effect);
if (ret < 0)
dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
/* Set the frequency and make sure the LED is on */
ret = regmap_update_bits(led->regmap, led->regs->ctrl,
CHT_WC_LED_F_MASK | led->regs->on_off_mask,
ctrl | led->regs->on_val);
if (ret < 0)
dev_err(cdev->dev, "Failed to update LED CTRL reg: %d\n", ret);
*delay_off = *delay_on = cht_wc_leds_get_period(ctrl) / 2;
done:
mutex_unlock(&led->mutex);
return ret;
}
static int cht_wc_leds_blink_set(struct led_classdev *cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
u8 effect = CHT_WC_LED_EFF_BLINKING;
/*
* The desired default behavior of LED1 / the charge LED is breathing
* while charging and on/solid when full. Since triggers cannot select
* breathing, blink_set() gets called when charging. Use slow breathing
* when the default "charging-blink-full-solid" trigger is used to
* achieve the desired default behavior.
*/
if (cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
*delay_on = *delay_off = 1000;
effect = CHT_WC_LED_EFF_BREATHING;
}
return cht_wc_leds_set_effect(cdev, delay_on, delay_off, effect);
}
static int cht_wc_leds_pattern_set(struct led_classdev *cdev,
struct led_pattern *pattern,
u32 len, int repeat)
{
unsigned long delay_off, delay_on;
if (repeat > 0 || len != 2 ||
pattern[0].brightness != 0 || pattern[1].brightness != 1 ||
pattern[0].delta_t != pattern[1].delta_t ||
(pattern[0].delta_t != 250 && pattern[0].delta_t != 500 &&
pattern[0].delta_t != 1000 && pattern[0].delta_t != 2000))
return -EINVAL;
delay_off = pattern[0].delta_t;
delay_on = pattern[1].delta_t;
return cht_wc_leds_set_effect(cdev, &delay_on, &delay_off, CHT_WC_LED_EFF_BREATHING);
}
static int cht_wc_leds_pattern_clear(struct led_classdev *cdev)
{
return cht_wc_leds_brightness_set(cdev, 0);
}
static int cht_wc_led_save_regs(struct cht_wc_led *led,
struct cht_wc_led_saved_regs *saved_regs)
{
int ret;
ret = regmap_read(led->regmap, led->regs->ctrl, &saved_regs->ctrl);
if (ret < 0)
return ret;
ret = regmap_read(led->regmap, led->regs->fsm, &saved_regs->fsm);
if (ret < 0)
return ret;
return regmap_read(led->regmap, led->regs->pwm, &saved_regs->pwm);
}
static void cht_wc_led_restore_regs(struct cht_wc_led *led,
const struct cht_wc_led_saved_regs *saved_regs)
{
regmap_write(led->regmap, led->regs->ctrl, saved_regs->ctrl);
regmap_write(led->regmap, led->regs->fsm, saved_regs->fsm);
regmap_write(led->regmap, led->regs->pwm, saved_regs->pwm);
}
static int cht_wc_leds_probe(struct platform_device *pdev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
struct cht_wc_leds *leds;
int ret;
int i;
/*
* On the Lenovo Yoga Tab 3 the LED1 driver output is actually
* connected to a haptic feedback motor rather then a LED.
* So do not register a LED classdev there (LED2 is unused).
*/
if (pmic->cht_wc_model == INTEL_CHT_WC_LENOVO_YT3_X90)
return -ENODEV;
leds = devm_kzalloc(&pdev->dev, sizeof(*leds), GFP_KERNEL);
if (!leds)
return -ENOMEM;
/*
* LED1 might be in hw-controlled mode when this driver gets loaded; and
* since the PMIC is always powered by the battery any changes made are
* permanent. Save LED1 regs to restore them on remove() or shutdown().
*/
leds->leds[0].regs = &cht_wc_led_regs[0];
leds->leds[0].regmap = pmic->regmap;
ret = cht_wc_led_save_regs(&leds->leds[0], &leds->led1_initial_regs);
if (ret < 0)
return ret;
/* Set LED1 default trigger based on machine model */
switch (pmic->cht_wc_model) {
case INTEL_CHT_WC_GPD_WIN_POCKET:
leds->leds[0].cdev.default_trigger = "max170xx_battery-charging-blink-full-solid";
break;
case INTEL_CHT_WC_XIAOMI_MIPAD2:
leds->leds[0].cdev.default_trigger = "bq27520-0-charging-blink-full-solid";
break;
case INTEL_CHT_WC_LENOVO_YOGABOOK1:
leds->leds[0].cdev.default_trigger = "bq27542-0-charging-blink-full-solid";
break;
default:
dev_warn(&pdev->dev, "Unknown model, no default charging trigger\n");
break;
}
for (i = 0; i < CHT_WC_LED_COUNT; i++) {
struct cht_wc_led *led = &leds->leds[i];
led->regs = &cht_wc_led_regs[i];
led->regmap = pmic->regmap;
mutex_init(&led->mutex);
led->cdev.name = cht_wc_leds_names[i];
led->cdev.brightness_set_blocking = cht_wc_leds_brightness_set;
led->cdev.brightness_get = cht_wc_leds_brightness_get;
led->cdev.blink_set = cht_wc_leds_blink_set;
led->cdev.pattern_set = cht_wc_leds_pattern_set;
led->cdev.pattern_clear = cht_wc_leds_pattern_clear;
led->cdev.max_brightness = 255;
ret = led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0)
return ret;
}
platform_set_drvdata(pdev, leds);
return 0;
}
static void cht_wc_leds_remove(struct platform_device *pdev)
{
struct cht_wc_leds *leds = platform_get_drvdata(pdev);
int i;
for (i = 0; i < CHT_WC_LED_COUNT; i++)
led_classdev_unregister(&leds->leds[i].cdev);
/* Restore LED1 regs if hw-control was active else leave LED1 off */
if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
}
static void cht_wc_leds_disable(struct platform_device *pdev)
{
struct cht_wc_leds *leds = platform_get_drvdata(pdev);
int i;
for (i = 0; i < CHT_WC_LED_COUNT; i++)
cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0);
/* Restore LED1 regs if hw-control was active else leave LED1 off */
if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
}
/* On suspend save current settings and turn LEDs off */
static int cht_wc_leds_suspend(struct device *dev)
{
struct cht_wc_leds *leds = dev_get_drvdata(dev);
int i, ret;
for (i = 0; i < CHT_WC_LED_COUNT; i++) {
ret = cht_wc_led_save_regs(&leds->leds[i], &leds->leds[i].saved_regs);
if (ret < 0)
return ret;
}
cht_wc_leds_disable(to_platform_device(dev));
return 0;
}
/* On resume restore the saved settings */
static int cht_wc_leds_resume(struct device *dev)
{
struct cht_wc_leds *leds = dev_get_drvdata(dev);
int i;
for (i = 0; i < CHT_WC_LED_COUNT; i++)
cht_wc_led_restore_regs(&leds->leds[i], &leds->leds[i].saved_regs);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds_resume);
static struct platform_driver cht_wc_leds_driver = {
.probe = cht_wc_leds_probe,
.remove_new = cht_wc_leds_remove,
.shutdown = cht_wc_leds_disable,
.driver = {
.name = "cht_wcove_leds",
.pm = pm_sleep_ptr(&cht_wc_leds_pm),
},
};
module_platform_driver(cht_wc_leds_driver);
MODULE_ALIAS("platform:cht_wcove_leds");
MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
MODULE_LICENSE("GPL");

View File

@ -13,6 +13,7 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
@ -77,6 +78,7 @@ static int create_gpio_led(const struct gpio_led *template,
struct fwnode_handle *fwnode, gpio_blink_set_t blink_set)
{
struct led_init_data init_data = {};
struct pinctrl *pinctrl;
int ret, state;
led_dat->cdev.default_trigger = template->default_trigger;
@ -119,6 +121,22 @@ static int create_gpio_led(const struct gpio_led *template,
&init_data);
}
if (ret)
return ret;
pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev);
if (IS_ERR(pinctrl)) {
ret = PTR_ERR(pinctrl);
if (ret != -ENODEV) {
dev_warn(led_dat->cdev.dev,
"Failed to select %pOF pinctrl: %d\n",
to_of_node(fwnode), ret);
} else {
/* pinctrl-%d not present, not an error */
ret = 0;
}
}
return ret;
}

View File

@ -602,7 +602,7 @@ static struct i2c_driver is31fl319x_driver = {
.name = "leds-is31fl319x",
.of_match_table = of_is31fl319x_match,
},
.probe_new = is31fl319x_probe,
.probe = is31fl319x_probe,
.id_table = is31fl319x_id,
};

View File

@ -488,7 +488,7 @@ static struct i2c_driver is31fl32xx_driver = {
.name = "is31fl32xx",
.of_match_table = of_is31fl32xx_match,
},
.probe_new = is31fl32xx_probe,
.probe = is31fl32xx_probe,
.remove = is31fl32xx_remove,
.id_table = is31fl32xx_id,
};

View File

@ -484,7 +484,7 @@ static const struct i2c_device_id lm3530_id[] = {
MODULE_DEVICE_TABLE(i2c, lm3530_id);
static struct i2c_driver lm3530_i2c_driver = {
.probe_new = lm3530_probe,
.probe = lm3530_probe,
.remove = lm3530_remove,
.id_table = lm3530_id,
.driver = {

View File

@ -726,7 +726,7 @@ static const struct i2c_device_id lm3532_id[] = {
MODULE_DEVICE_TABLE(i2c, lm3532_id);
static struct i2c_driver lm3532_i2c_driver = {
.probe_new = lm3532_probe,
.probe = lm3532_probe,
.remove = lm3532_remove,
.id_table = lm3532_id,
.driver = {

View File

@ -516,7 +516,7 @@ static struct i2c_driver lm355x_i2c_driver = {
.name = LM355x_NAME,
.pm = NULL,
},
.probe_new = lm355x_probe,
.probe = lm355x_probe,
.remove = lm355x_remove,
.id_table = lm355x_id,
};

View File

@ -401,7 +401,7 @@ static struct i2c_driver lm3642_i2c_driver = {
.name = LM3642_NAME,
.pm = NULL,
},
.probe_new = lm3642_probe,
.probe = lm3642_probe,
.remove = lm3642_remove,
.id_table = lm3642_id,
};

View File

@ -518,7 +518,7 @@ static struct i2c_driver lm3692x_driver = {
.name = "lm3692x",
.of_match_table = of_lm3692x_leds_match,
},
.probe_new = lm3692x_probe,
.probe = lm3692x_probe,
.remove = lm3692x_remove,
.id_table = lm3692x_id,
};

View File

@ -376,7 +376,7 @@ static struct i2c_driver lm3697_driver = {
.name = "lm3697",
.of_match_table = of_lm3697_leds_match,
},
.probe_new = lm3697_probe,
.probe = lm3697_probe,
.remove = lm3697_remove,
.id_table = lm3697_id,
};

View File

@ -427,7 +427,7 @@ static struct i2c_driver lp3944_driver = {
.driver = {
.name = "lp3944",
},
.probe_new = lp3944_probe,
.probe = lp3944_probe,
.remove = lp3944_remove,
.id_table = lp3944_id,
};

View File

@ -273,7 +273,7 @@ static struct i2c_driver lp3952_i2c_driver = {
.driver = {
.name = LP3952_NAME,
},
.probe_new = lp3952_probe,
.probe = lp3952_probe,
.remove = lp3952_remove,
.id_table = lp3952_id,
};

View File

@ -608,7 +608,7 @@ static struct i2c_driver lp50xx_driver = {
.name = "lp50xx",
.of_match_table = of_lp50xx_leds_match,
},
.probe_new = lp50xx_probe,
.probe = lp50xx_probe,
.remove = lp50xx_remove,
.id_table = lp50xx_id,
};

View File

@ -58,14 +58,11 @@
/* CONFIG register */
#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */
#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */
#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */
#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */
#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */
#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */
#define LP5521_CP_MODE_MASK 0x18 /* Charge pump mode */
#define LP5521_CP_MODE_SHIFT 3
#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */
#define LP5521_CLK_INT 0x01 /* Internal clock */
#define LP5521_DEFAULT_CFG \
(LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO)
#define LP5521_DEFAULT_CFG (LP5521_PWM_HF | LP5521_PWRSAVE_EN)
/* Status */
#define LP5521_EXT_CLK_USED 0x08
@ -310,6 +307,8 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip)
if (!lp55xx_is_extclk_used(chip))
val |= LP5521_CLK_INT;
val |= (chip->pdata->charge_pump_mode << LP5521_CP_MODE_SHIFT) & LP5521_CP_MODE_MASK;
ret = lp55xx_write(chip, LP5521_REG_CONFIG, val);
if (ret)
return ret;
@ -608,7 +607,7 @@ static struct i2c_driver lp5521_driver = {
.name = "lp5521",
.of_match_table = of_match_ptr(of_lp5521_leds_match),
},
.probe_new = lp5521_probe,
.probe = lp5521_probe,
.remove = lp5521_remove,
.id_table = lp5521_id,
};

View File

@ -57,8 +57,11 @@
#define LP5523_AUTO_INC 0x40
#define LP5523_PWR_SAVE 0x20
#define LP5523_PWM_PWR_SAVE 0x04
#define LP5523_CP_AUTO 0x18
#define LP5523_CP_MODE_MASK 0x18
#define LP5523_CP_MODE_SHIFT 3
#define LP5523_AUTO_CLK 0x02
#define LP5523_DEFAULT_CONFIG \
(LP5523_AUTO_INC | LP5523_PWR_SAVE | LP5523_AUTO_CLK | LP5523_PWM_PWR_SAVE)
#define LP5523_EN_LEDTEST 0x80
#define LP5523_LEDTEST_DONE 0x80
@ -125,6 +128,7 @@ static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current)
static int lp5523_post_init_device(struct lp55xx_chip *chip)
{
int ret;
int val;
ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE);
if (ret)
@ -133,10 +137,10 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip)
/* Chip startup time is 500 us, 1 - 2 ms gives some margin */
usleep_range(1000, 2000);
ret = lp55xx_write(chip, LP5523_REG_CONFIG,
LP5523_AUTO_INC | LP5523_PWR_SAVE |
LP5523_CP_AUTO | LP5523_AUTO_CLK |
LP5523_PWM_PWR_SAVE);
val = LP5523_DEFAULT_CONFIG;
val |= (chip->pdata->charge_pump_mode << LP5523_CP_MODE_SHIFT) & LP5523_CP_MODE_MASK;
ret = lp55xx_write(chip, LP5523_REG_CONFIG, val);
if (ret)
return ret;
@ -983,7 +987,7 @@ static struct i2c_driver lp5523_driver = {
.name = "lp5523x",
.of_match_table = of_match_ptr(of_lp5523_leds_match),
},
.probe_new = lp5523_probe,
.probe = lp5523_probe,
.remove = lp5523_remove,
.id_table = lp5523_id,
};

View File

@ -603,7 +603,7 @@ static struct i2c_driver lp5562_driver = {
.name = "lp5562",
.of_match_table = of_match_ptr(of_lp5562_leds_match),
},
.probe_new = lp5562_probe,
.probe = lp5562_probe,
.remove = lp5562_remove,
.id_table = lp5562_id,
};

View File

@ -18,6 +18,7 @@
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <dt-bindings/leds/leds-lp55xx.h>
#include "leds-lp55xx-common.h"
@ -691,6 +692,14 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
i++;
}
if (of_property_read_u32(np, "ti,charge-pump-mode", &pdata->charge_pump_mode))
pdata->charge_pump_mode = LP55XX_CP_AUTO;
if (pdata->charge_pump_mode > LP55XX_CP_AUTO) {
dev_err(dev, "invalid charge pump mode %d\n", pdata->charge_pump_mode);
return ERR_PTR(-EINVAL);
}
of_property_read_string(np, "label", &pdata->label);
of_property_read_u8(np, "clock-mode", &pdata->clock_mode);

View File

@ -53,10 +53,10 @@
#define LP8501_PWM_PSAVE BIT(7)
#define LP8501_AUTO_INC BIT(6)
#define LP8501_PWR_SAVE BIT(5)
#define LP8501_CP_AUTO 0x18
#define LP8501_CP_MODE_MASK 0x18
#define LP8501_CP_MODE_SHIFT 3
#define LP8501_INT_CLK BIT(0)
#define LP8501_DEFAULT_CFG \
(LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO)
#define LP8501_DEFAULT_CFG (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE)
#define LP8501_REG_RESET 0x3D
#define LP8501_RESET 0xFF
@ -102,6 +102,8 @@ static int lp8501_post_init_device(struct lp55xx_chip *chip)
if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT)
val |= LP8501_INT_CLK;
val |= (chip->pdata->charge_pump_mode << LP8501_CP_MODE_SHIFT) & LP8501_CP_MODE_MASK;
ret = lp55xx_write(chip, LP8501_REG_CONFIG, val);
if (ret)
return ret;
@ -392,7 +394,7 @@ static struct i2c_driver lp8501_driver = {
.name = "lp8501",
.of_match_table = of_match_ptr(of_lp8501_leds_match),
},
.probe_new = lp8501_probe,
.probe = lp8501_probe,
.remove = lp8501_remove,
.id_table = lp8501_id,
};

View File

@ -475,7 +475,7 @@ static struct i2c_driver lp8860_driver = {
.name = "lp8860",
.of_match_table = of_lp8860_leds_match,
},
.probe_new = lp8860_probe,
.probe = lp8860_probe,
.remove = lp8860_remove,
.id_table = lp8860_id,
};

View File

@ -14,61 +14,54 @@
#include <linux/regmap.h>
/*
* Register field for MT6323_TOP_CKPDN0 to enable
* Register field for TOP_CKPDN0 to enable
* 32K clock common for LED device.
*/
#define MT6323_RG_DRV_32K_CK_PDN BIT(11)
#define MT6323_RG_DRV_32K_CK_PDN_MASK BIT(11)
#define RG_DRV_32K_CK_PDN BIT(11)
#define RG_DRV_32K_CK_PDN_MASK BIT(11)
/* 32K/1M/6M clock common for WLED device */
#define RG_VWLED_1M_CK_PDN BIT(0)
#define RG_VWLED_32K_CK_PDN BIT(12)
#define RG_VWLED_6M_CK_PDN BIT(13)
/*
* Register field for MT6323_TOP_CKPDN2 to enable
* Register field for TOP_CKPDN2 to enable
* individual clock for LED device.
*/
#define MT6323_RG_ISINK_CK_PDN(i) BIT(i)
#define MT6323_RG_ISINK_CK_PDN_MASK(i) BIT(i)
#define RG_ISINK_CK_PDN(i) BIT(i)
#define RG_ISINK_CK_PDN_MASK(i) BIT(i)
/*
* Register field for MT6323_TOP_CKCON1 to select
* Register field for TOP_CKCON1 to select
* clock source.
*/
#define MT6323_RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))
#define RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))
/*
* Register for MT6323_ISINK_CON0 to setup the
* duty cycle of the blink.
*/
#define MT6323_ISINK_CON0(i) (MT6323_ISINK0_CON0 + 0x8 * (i))
#define MT6323_ISINK_DIM_DUTY_MASK (0x1f << 8)
#define MT6323_ISINK_DIM_DUTY(i) (((i) << 8) & \
MT6323_ISINK_DIM_DUTY_MASK)
#define ISINK_CON(r, i) (r + 0x8 * (i))
/* Register to setup the period of the blink. */
#define MT6323_ISINK_CON1(i) (MT6323_ISINK0_CON1 + 0x8 * (i))
#define MT6323_ISINK_DIM_FSEL_MASK (0xffff)
#define MT6323_ISINK_DIM_FSEL(i) ((i) & MT6323_ISINK_DIM_FSEL_MASK)
/* ISINK_CON0: Register to setup the duty cycle of the blink. */
#define ISINK_DIM_DUTY_MASK (0x1f << 8)
#define ISINK_DIM_DUTY(i) (((i) << 8) & ISINK_DIM_DUTY_MASK)
/* Register to control the brightness. */
#define MT6323_ISINK_CON2(i) (MT6323_ISINK0_CON2 + 0x8 * (i))
#define MT6323_ISINK_CH_STEP_SHIFT 12
#define MT6323_ISINK_CH_STEP_MASK (0x7 << 12)
#define MT6323_ISINK_CH_STEP(i) (((i) << 12) & \
MT6323_ISINK_CH_STEP_MASK)
#define MT6323_ISINK_SFSTR0_TC_MASK (0x3 << 1)
#define MT6323_ISINK_SFSTR0_TC(i) (((i) << 1) & \
MT6323_ISINK_SFSTR0_TC_MASK)
#define MT6323_ISINK_SFSTR0_EN_MASK BIT(0)
#define MT6323_ISINK_SFSTR0_EN BIT(0)
/* ISINK_CON1: Register to setup the period of the blink. */
#define ISINK_DIM_FSEL_MASK (0xffff)
#define ISINK_DIM_FSEL(i) ((i) & ISINK_DIM_FSEL_MASK)
/* ISINK_CON2: Register to control the brightness. */
#define ISINK_CH_STEP_SHIFT 12
#define ISINK_CH_STEP_MASK (0x7 << 12)
#define ISINK_CH_STEP(i) (((i) << 12) & ISINK_CH_STEP_MASK)
#define ISINK_SFSTR0_TC_MASK (0x3 << 1)
#define ISINK_SFSTR0_TC(i) (((i) << 1) & ISINK_SFSTR0_TC_MASK)
#define ISINK_SFSTR0_EN_MASK BIT(0)
#define ISINK_SFSTR0_EN BIT(0)
/* Register to LED channel enablement. */
#define MT6323_ISINK_CH_EN_MASK(i) BIT(i)
#define MT6323_ISINK_CH_EN(i) BIT(i)
#define ISINK_CH_EN_MASK(i) BIT(i)
#define ISINK_CH_EN(i) BIT(i)
#define MT6323_MAX_PERIOD 10000
#define MT6323_MAX_LEDS 4
#define MT6323_MAX_BRIGHTNESS 6
#define MT6323_UNIT_DUTY 3125
#define MT6323_CAL_HW_DUTY(o, p) DIV_ROUND_CLOSEST((o) * 100000ul,\
(p) * MT6323_UNIT_DUTY)
#define MAX_SUPPORTED_LEDS 8
struct mt6323_leds;
@ -86,12 +79,63 @@ struct mt6323_led {
enum led_brightness current_brightness;
};
/**
* struct mt6323_regs - register spec for the LED device
* @top_ckpdn: Offset to ISINK_CKPDN[0..x] registers
* @num_top_ckpdn: Number of ISINK_CKPDN registers
* @top_ckcon: Offset to ISINK_CKCON[0..x] registers
* @num_top_ckcon: Number of ISINK_CKCON registers
* @isink_con: Offset to ISINKx_CON[0..x] registers
* @num_isink_con: Number of ISINKx_CON registers
* @isink_max_regs: Number of ISINK[0..x] registers
* @isink_en_ctrl: Offset to ISINK_EN_CTRL register
* @iwled_en_ctrl: Offset to IWLED_EN_CTRL register
*/
struct mt6323_regs {
const u16 *top_ckpdn;
u8 num_top_ckpdn;
const u16 *top_ckcon;
u8 num_top_ckcon;
const u16 *isink_con;
u8 num_isink_con;
u8 isink_max_regs;
u16 isink_en_ctrl;
u16 iwled_en_ctrl;
};
/**
* struct mt6323_hwspec - hardware specific parameters
* @max_period: Maximum period for all LEDs
* @max_leds: Maximum number of supported LEDs
* @max_wleds: Maximum number of WLEDs
* @max_brightness: Maximum brightness for all LEDs
* @unit_duty: Steps of duty per period
*/
struct mt6323_hwspec {
u16 max_period;
u8 max_leds;
u8 max_wleds;
u16 max_brightness;
u16 unit_duty;
};
/**
* struct mt6323_data - device specific data
* @regs: Register spec for this device
* @spec: Hardware specific parameters
*/
struct mt6323_data {
const struct mt6323_regs *regs;
const struct mt6323_hwspec *spec;
};
/**
* struct mt6323_leds - state container for holding LED controller
* of the driver
* @dev: the device pointer
* @hw: the underlying hardware providing shared
* bus for the register operations
* @pdata: device specific data
* @lock: the lock among process context
* @led: the array that contains the state of individual
* LED device
@ -99,9 +143,10 @@ struct mt6323_led {
struct mt6323_leds {
struct device *dev;
struct mt6397_chip *hw;
const struct mt6323_data *pdata;
/* protect among process context */
struct mutex lock;
struct mt6323_led *led[MT6323_MAX_LEDS];
struct mt6323_led *led[MAX_SUPPORTED_LEDS];
};
static int mt6323_led_hw_brightness(struct led_classdev *cdev,
@ -109,6 +154,7 @@ static int mt6323_led_hw_brightness(struct led_classdev *cdev,
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
u32 con2_mask = 0, con2_val = 0;
int ret;
@ -117,14 +163,14 @@ static int mt6323_led_hw_brightness(struct led_classdev *cdev,
* Setup current output for the corresponding
* brightness level.
*/
con2_mask |= MT6323_ISINK_CH_STEP_MASK |
MT6323_ISINK_SFSTR0_TC_MASK |
MT6323_ISINK_SFSTR0_EN_MASK;
con2_val |= MT6323_ISINK_CH_STEP(brightness - 1) |
MT6323_ISINK_SFSTR0_TC(2) |
MT6323_ISINK_SFSTR0_EN;
con2_mask |= ISINK_CH_STEP_MASK |
ISINK_SFSTR0_TC_MASK |
ISINK_SFSTR0_EN_MASK;
con2_val |= ISINK_CH_STEP(brightness - 1) |
ISINK_SFSTR0_TC(2) |
ISINK_SFSTR0_EN;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON2(led->id),
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[2], led->id),
con2_mask, con2_val);
return ret;
}
@ -133,20 +179,21 @@ static int mt6323_led_hw_off(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
status = MT6323_ISINK_CH_EN(led->id);
ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
MT6323_ISINK_CH_EN_MASK(led->id), ~status);
status = ISINK_CH_EN(led->id);
ret = regmap_update_bits(regmap, regs->isink_en_ctrl,
ISINK_CH_EN_MASK(led->id), ~status);
if (ret < 0)
return ret;
usleep_range(100, 300);
ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
MT6323_RG_ISINK_CK_PDN_MASK(led->id),
MT6323_RG_ISINK_CK_PDN(led->id));
ret = regmap_update_bits(regmap, regs->top_ckpdn[2],
RG_ISINK_CK_PDN_MASK(led->id),
RG_ISINK_CK_PDN(led->id));
if (ret < 0)
return ret;
@ -158,30 +205,31 @@ mt6323_get_led_hw_brightness(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
ret = regmap_read(regmap, MT6323_TOP_CKPDN2, &status);
ret = regmap_read(regmap, regs->top_ckpdn[2], &status);
if (ret < 0)
return ret;
if (status & MT6323_RG_ISINK_CK_PDN_MASK(led->id))
if (status & RG_ISINK_CK_PDN_MASK(led->id))
return 0;
ret = regmap_read(regmap, MT6323_ISINK_EN_CTRL, &status);
ret = regmap_read(regmap, regs->isink_en_ctrl, &status);
if (ret < 0)
return ret;
if (!(status & MT6323_ISINK_CH_EN(led->id)))
if (!(status & ISINK_CH_EN(led->id)))
return 0;
ret = regmap_read(regmap, MT6323_ISINK_CON2(led->id), &status);
ret = regmap_read(regmap, ISINK_CON(regs->isink_con[2], led->id), &status);
if (ret < 0)
return ret;
return ((status & MT6323_ISINK_CH_STEP_MASK)
>> MT6323_ISINK_CH_STEP_SHIFT) + 1;
return ((status & ISINK_CH_STEP_MASK)
>> ISINK_CH_STEP_SHIFT) + 1;
}
static int mt6323_led_hw_on(struct led_classdev *cdev,
@ -189,6 +237,7 @@ static int mt6323_led_hw_on(struct led_classdev *cdev,
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
@ -198,23 +247,23 @@ static int mt6323_led_hw_on(struct led_classdev *cdev,
* clock and channel and let work with continuous blink as
* the default.
*/
ret = regmap_update_bits(regmap, MT6323_TOP_CKCON1,
MT6323_RG_ISINK_CK_SEL_MASK(led->id), 0);
ret = regmap_update_bits(regmap, regs->top_ckcon[1],
RG_ISINK_CK_SEL_MASK(led->id), 0);
if (ret < 0)
return ret;
status = MT6323_RG_ISINK_CK_PDN(led->id);
ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
MT6323_RG_ISINK_CK_PDN_MASK(led->id),
status = RG_ISINK_CK_PDN(led->id);
ret = regmap_update_bits(regmap, regs->top_ckpdn[2],
RG_ISINK_CK_PDN_MASK(led->id),
~status);
if (ret < 0)
return ret;
usleep_range(100, 300);
ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
MT6323_ISINK_CH_EN_MASK(led->id),
MT6323_ISINK_CH_EN(led->id));
ret = regmap_update_bits(regmap, regs->isink_en_ctrl,
ISINK_CH_EN_MASK(led->id),
ISINK_CH_EN(led->id));
if (ret < 0)
return ret;
@ -222,15 +271,15 @@ static int mt6323_led_hw_on(struct led_classdev *cdev,
if (ret < 0)
return ret;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
MT6323_ISINK_DIM_DUTY_MASK,
MT6323_ISINK_DIM_DUTY(31));
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
ISINK_DIM_DUTY_MASK,
ISINK_DIM_DUTY(31));
if (ret < 0)
return ret;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
MT6323_ISINK_DIM_FSEL_MASK,
MT6323_ISINK_DIM_FSEL(1000));
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),
ISINK_DIM_FSEL_MASK,
ISINK_DIM_FSEL(1000));
if (ret < 0)
return ret;
@ -243,6 +292,8 @@ static int mt6323_led_set_blink(struct led_classdev *cdev,
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
const struct mt6323_hwspec *spec = leds->pdata->spec;
struct regmap *regmap = leds->hw->regmap;
unsigned long period;
u8 duty_hw;
@ -265,14 +316,14 @@ static int mt6323_led_set_blink(struct led_classdev *cdev,
*/
period = *delay_on + *delay_off;
if (period > MT6323_MAX_PERIOD)
if (period > spec->max_period)
return -EINVAL;
/*
* Calculate duty_hw based on the percentage of period during
* which the led is ON.
*/
duty_hw = MT6323_CAL_HW_DUTY(*delay_on, period);
duty_hw = DIV_ROUND_CLOSEST(*delay_on * 100000ul, period * spec->unit_duty);
/* hardware doesn't support zero duty cycle. */
if (!duty_hw)
@ -290,15 +341,15 @@ static int mt6323_led_set_blink(struct led_classdev *cdev,
led->current_brightness = cdev->max_brightness;
}
ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
MT6323_ISINK_DIM_DUTY_MASK,
MT6323_ISINK_DIM_DUTY(duty_hw - 1));
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
ISINK_DIM_DUTY_MASK,
ISINK_DIM_DUTY(duty_hw - 1));
if (ret < 0)
goto out;
ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
MT6323_ISINK_DIM_FSEL_MASK,
MT6323_ISINK_DIM_FSEL(period - 1));
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),
ISINK_DIM_FSEL_MASK,
ISINK_DIM_FSEL(period - 1));
out:
mutex_unlock(&leds->lock);
@ -335,6 +386,117 @@ out:
return ret;
}
static int mtk_wled_hw_on(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
int ret;
ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);
if (ret)
return ret;
ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);
if (ret)
return ret;
ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);
if (ret)
return ret;
usleep_range(5000, 6000);
/* Enable WLED channel pair */
ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));
if (ret)
return ret;
ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));
if (ret)
return ret;
return 0;
}
static int mtk_wled_hw_off(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
int ret;
ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));
if (ret)
return ret;
ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));
if (ret)
return ret;
ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);
if (ret)
return ret;
ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);
if (ret)
return ret;
ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);
if (ret)
return ret;
return 0;
}
static enum led_brightness mt6323_get_wled_brightness(struct led_classdev *cdev)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
const struct mt6323_regs *regs = leds->pdata->regs;
struct regmap *regmap = leds->hw->regmap;
unsigned int status;
int ret;
ret = regmap_read(regmap, regs->iwled_en_ctrl, &status);
if (ret)
return 0;
/* Always two channels per WLED */
status &= BIT(led->id) | BIT(led->id + 1);
return status ? led->current_brightness : 0;
}
static int mt6323_wled_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
struct mt6323_leds *leds = led->parent;
int ret = 0;
mutex_lock(&leds->lock);
if (brightness) {
if (!led->current_brightness)
ret = mtk_wled_hw_on(cdev);
if (ret)
goto out;
} else {
ret = mtk_wled_hw_off(cdev);
if (ret)
goto out;
}
led->current_brightness = brightness;
out:
mutex_unlock(&leds->lock);
return ret;
}
static int mt6323_led_set_dt_default(struct led_classdev *cdev,
struct device_node *np)
{
@ -369,9 +531,12 @@ static int mt6323_led_probe(struct platform_device *pdev)
struct mt6397_chip *hw = dev_get_drvdata(dev->parent);
struct mt6323_leds *leds;
struct mt6323_led *led;
const struct mt6323_regs *regs;
const struct mt6323_hwspec *spec;
int ret;
unsigned int status;
u32 reg;
u8 max_leds;
leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
if (!leds)
@ -379,6 +544,10 @@ static int mt6323_led_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, leds);
leds->dev = dev;
leds->pdata = device_get_match_data(dev);
regs = leds->pdata->regs;
spec = leds->pdata->spec;
max_leds = spec->max_leds + spec->max_wleds;
/*
* leds->hw points to the underlying bus for the register
@ -387,17 +556,18 @@ static int mt6323_led_probe(struct platform_device *pdev)
leds->hw = hw;
mutex_init(&leds->lock);
status = MT6323_RG_DRV_32K_CK_PDN;
ret = regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
MT6323_RG_DRV_32K_CK_PDN_MASK, ~status);
status = RG_DRV_32K_CK_PDN;
ret = regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],
RG_DRV_32K_CK_PDN_MASK, ~status);
if (ret < 0) {
dev_err(leds->dev,
"Failed to update MT6323_TOP_CKPDN0 Register\n");
"Failed to update TOP_CKPDN0 Register\n");
return ret;
}
for_each_available_child_of_node(np, child) {
struct led_init_data init_data = {};
bool is_wled;
ret = of_property_read_u32(child, "reg", &reg);
if (ret) {
@ -405,7 +575,8 @@ static int mt6323_led_probe(struct platform_device *pdev)
goto put_child_node;
}
if (reg >= MT6323_MAX_LEDS || leds->led[reg]) {
if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS ||
leds->led[reg]) {
dev_err(dev, "Invalid led reg %u\n", reg);
ret = -EINVAL;
goto put_child_node;
@ -417,14 +588,24 @@ static int mt6323_led_probe(struct platform_device *pdev)
goto put_child_node;
}
is_wled = of_property_read_bool(child, "mediatek,is-wled");
leds->led[reg] = led;
leds->led[reg]->id = reg;
leds->led[reg]->cdev.max_brightness = MT6323_MAX_BRIGHTNESS;
leds->led[reg]->cdev.brightness_set_blocking =
mt6323_led_set_brightness;
leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;
leds->led[reg]->cdev.brightness_get =
mt6323_get_led_hw_brightness;
leds->led[reg]->cdev.max_brightness = spec->max_brightness;
if (is_wled) {
leds->led[reg]->cdev.brightness_set_blocking =
mt6323_wled_set_brightness;
leds->led[reg]->cdev.brightness_get =
mt6323_get_wled_brightness;
} else {
leds->led[reg]->cdev.brightness_set_blocking =
mt6323_led_set_brightness;
leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;
leds->led[reg]->cdev.brightness_get =
mt6323_get_led_hw_brightness;
}
leds->led[reg]->parent = leds;
ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child);
@ -454,23 +635,88 @@ put_child_node:
static int mt6323_led_remove(struct platform_device *pdev)
{
struct mt6323_leds *leds = platform_get_drvdata(pdev);
const struct mt6323_regs *regs = leds->pdata->regs;
int i;
/* Turn the LEDs off on driver removal. */
for (i = 0 ; leds->led[i] ; i++)
mt6323_led_hw_off(&leds->led[i]->cdev);
regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
MT6323_RG_DRV_32K_CK_PDN_MASK,
MT6323_RG_DRV_32K_CK_PDN);
regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],
RG_DRV_32K_CK_PDN_MASK,
RG_DRV_32K_CK_PDN);
mutex_destroy(&leds->lock);
return 0;
}
static const struct mt6323_regs mt6323_registers = {
.top_ckpdn = (const u16[]){ 0x102, 0x106, 0x10e },
.num_top_ckpdn = 3,
.top_ckcon = (const u16[]){ 0x120, 0x126 },
.num_top_ckcon = 2,
.isink_con = (const u16[]){ 0x330, 0x332, 0x334 },
.num_isink_con = 3,
.isink_max_regs = 4, /* ISINK[0..3] */
.isink_en_ctrl = 0x356,
};
static const struct mt6323_regs mt6331_registers = {
.top_ckpdn = (const u16[]){ 0x138, 0x13e, 0x144 },
.num_top_ckpdn = 3,
.top_ckcon = (const u16[]){ 0x14c, 0x14a },
.num_top_ckcon = 2,
.isink_con = (const u16[]){ 0x40c, 0x40e, 0x410, 0x412, 0x414 },
.num_isink_con = 5,
.isink_max_regs = 4, /* ISINK[0..3] */
.isink_en_ctrl = 0x43a,
};
static const struct mt6323_regs mt6332_registers = {
.top_ckpdn = (const u16[]){ 0x8094, 0x809a, 0x80a0 },
.num_top_ckpdn = 3,
.top_ckcon = (const u16[]){ 0x80a6, 0x80ac },
.num_top_ckcon = 2,
.isink_con = (const u16[]){ 0x8cd4 },
.num_isink_con = 1,
.isink_max_regs = 12, /* IWLED[0..2, 3..9] */
.iwled_en_ctrl = 0x8cda,
};
static const struct mt6323_hwspec mt6323_spec = {
.max_period = 10000,
.max_leds = 4,
.max_brightness = 6,
.unit_duty = 3125,
};
static const struct mt6323_hwspec mt6332_spec = {
/* There are no LEDs in MT6332. Only WLEDs are present. */
.max_leds = 0,
.max_wleds = 1,
.max_brightness = 1024,
};
static const struct mt6323_data mt6323_pdata = {
.regs = &mt6323_registers,
.spec = &mt6323_spec,
};
static const struct mt6323_data mt6331_pdata = {
.regs = &mt6331_registers,
.spec = &mt6323_spec,
};
static const struct mt6323_data mt6332_pdata = {
.regs = &mt6332_registers,
.spec = &mt6332_spec,
};
static const struct of_device_id mt6323_led_dt_match[] = {
{ .compatible = "mediatek,mt6323-led" },
{ .compatible = "mediatek,mt6323-led", .data = &mt6323_pdata},
{ .compatible = "mediatek,mt6331-led", .data = &mt6331_pdata },
{ .compatible = "mediatek,mt6332-led", .data = &mt6332_pdata },
{},
};
MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);

View File

@ -102,7 +102,7 @@ static struct i2c_driver pca9532_driver = {
.name = "leds-pca953x",
.of_match_table = of_match_ptr(of_pca9532_leds_match),
},
.probe_new = pca9532_probe,
.probe = pca9532_probe,
.remove = pca9532_remove,
.id_table = pca9532_id,
};

View File

@ -668,7 +668,7 @@ static struct i2c_driver pca955x_driver = {
.name = "leds-pca955x",
.of_match_table = of_pca955x_match,
},
.probe_new = pca955x_probe,
.probe = pca955x_probe,
.id_table = pca955x_id,
};

View File

@ -431,7 +431,7 @@ static struct i2c_driver pca963x_driver = {
.name = "leds-pca963x",
.of_match_table = of_pca963x_match,
},
.probe_new = pca963x_probe,
.probe = pca963x_probe,
.id_table = pca963x_id,
};

View File

@ -98,7 +98,7 @@ static int spi_byte_probe(struct spi_device *spi)
return -ENOMEM;
of_property_read_string(child, "label", &name);
strlcpy(led->name, name, sizeof(led->name));
strscpy(led->name, name, sizeof(led->name));
led->spi = spi;
mutex_init(&led->mutex);
led->cdef = device_get_match_data(dev);

View File

@ -808,7 +808,7 @@ static struct i2c_driver tca6507_driver = {
.name = "leds-tca6507",
.of_match_table = of_match_ptr(of_tca6507_leds_match),
},
.probe_new = tca6507_probe,
.probe = tca6507_probe,
.remove = tca6507_remove,
.id_table = tca6507_id,
};

View File

@ -230,7 +230,7 @@ static struct i2c_driver tlc591xx_driver = {
.name = "tlc591xx",
.of_match_table = of_match_ptr(of_tlc591xx_leds_match),
},
.probe_new = tlc591xx_probe,
.probe = tlc591xx_probe,
.id_table = tlc591xx_id,
};

View File

@ -271,7 +271,7 @@ static const struct i2c_device_id omnia_id[] = {
MODULE_DEVICE_TABLE(i2c, omnia_id);
static struct i2c_driver omnia_leds_driver = {
.probe_new = omnia_leds_probe,
.probe = omnia_leds_probe,
.remove = omnia_leds_remove,
.id_table = omnia_id,
.driver = {

View File

@ -1173,8 +1173,10 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
i = 0;
for_each_available_child_of_node(np, child) {
ret = lpg_parse_channel(lpg, child, &led->channels[i]);
if (ret < 0)
if (ret < 0) {
of_node_put(child);
return ret;
}
info[i].color_index = led->channels[i]->color;
info[i].intensity = 0;
@ -1352,8 +1354,10 @@ static int lpg_probe(struct platform_device *pdev)
for_each_available_child_of_node(pdev->dev.of_node, np) {
ret = lpg_add_led(lpg, np);
if (ret)
if (ret) {
of_node_put(np);
return ret;
}
}
for (i = 0; i < lpg->num_channels; i++)
@ -1414,6 +1418,20 @@ static const struct lpg_data pm8994_lpg_data = {
},
};
/* PMI632 uses SDAM instead of LUT for pattern */
static const struct lpg_data pmi632_lpg_data = {
.triled_base = 0xd000,
.num_channels = 5,
.channels = (const struct lpg_channel_data[]) {
{ .base = 0xb300, .triled_mask = BIT(7) },
{ .base = 0xb400, .triled_mask = BIT(6) },
{ .base = 0xb500, .triled_mask = BIT(5) },
{ .base = 0xb600 },
{ .base = 0xb700 },
},
};
static const struct lpg_data pmi8994_lpg_data = {
.lut_base = 0xb000,
.lut_size = 24,
@ -1505,6 +1523,7 @@ static const struct of_device_id lpg_of_table[] = {
{ .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data },
{ .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data },
{ .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data },
{ .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data },
{ .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
{ .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
{ .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },

View File

@ -1,11 +1,36 @@
# SPDX-License-Identifier: GPL-2.0-only
config LEDS_SIEMENS_SIMATIC_IPC
tristate "LED driver for Siemens Simatic IPCs"
depends on LEDS_GPIO
depends on SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens.
To compile this driver as a module, choose M here: the modules
will be called simatic-ipc-leds and simatic-ipc-leds-gpio.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds.
config LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE
tristate "LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO"
depends on LEDS_GPIO
depends on PINCTRL_BROXTON
depends on SIEMENS_SIMATIC_IPC
default LEDS_SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens based on Apollo Lake GPIO i.e. IPC127E.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds-gpio-apollolake.
config LEDS_SIEMENS_SIMATIC_IPC_F7188X
tristate "LED driver for Siemens Simatic IPCs based on Nuvoton GPIO"
depends on LEDS_GPIO
depends on GPIO_F7188X
depends on SIEMENS_SIMATIC_IPC
default LEDS_SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens based on Nuvoton GPIO i.e. IPC227G.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds-gpio-f7188x.

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds-gpio.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2023
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include "simatic-ipc-leds-gpio.h"
static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 52, NULL, 0, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 1, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 2, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 3, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 4, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, NULL, 5, GPIO_ACTIVE_LOW),
{} /* Terminating entry */
},
};
static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = {
.dev_id = NULL, /* Filled during initialization */
.table = {
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH),
{} /* Terminating entry */
},
};
static int simatic_ipc_leds_gpio_apollolake_probe(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra);
}
static int simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra);
}
static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = {
.probe = simatic_ipc_leds_gpio_apollolake_probe,
.remove = simatic_ipc_leds_gpio_apollolake_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_led_gpio_apollolake_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:apollolake-pinctrl");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2023
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include "simatic-ipc-leds-gpio.h"
static struct platform_device *simatic_leds_pdev;
static const struct gpio_led simatic_ipc_gpio_leds[] = {
{ .name = "red:" LED_FUNCTION_STATUS "-1" },
{ .name = "green:" LED_FUNCTION_STATUS "-1" },
{ .name = "red:" LED_FUNCTION_STATUS "-2" },
{ .name = "green:" LED_FUNCTION_STATUS "-2" },
{ .name = "red:" LED_FUNCTION_STATUS "-3" },
{ .name = "green:" LED_FUNCTION_STATUS "-3" },
};
static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = {
.num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds),
.leds = simatic_ipc_gpio_leds,
};
int simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra)
{
gpiod_remove_lookup_table(table);
gpiod_remove_lookup_table(table_extra);
platform_device_unregister(simatic_leds_pdev);
return 0;
}
EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove);
int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra)
{
const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct gpio_desc *gpiod;
int err;
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_127E:
case SIMATIC_IPC_DEVICE_227G:
break;
default:
return -ENODEV;
}
gpiod_add_lookup_table(table);
simatic_leds_pdev = platform_device_register_resndata(NULL,
"leds-gpio", PLATFORM_DEVID_NONE, NULL, 0,
&simatic_ipc_gpio_leds_pdata,
sizeof(simatic_ipc_gpio_leds_pdata));
if (IS_ERR(simatic_leds_pdev)) {
err = PTR_ERR(simatic_leds_pdev);
goto out;
}
table_extra->dev_id = dev_name(dev);
gpiod_add_lookup_table(table_extra);
/* PM_BIOS_BOOT_N */
gpiod = gpiod_get_index(dev, NULL, 6, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
/* PM_WDT_OUT */
gpiod = gpiod_get_index(dev, NULL, 7, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
return 0;
out:
simatic_ipc_leds_gpio_remove(pdev, table, table_extra);
return err;
}
EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe);
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: platform:leds-gpio");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2023
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
#include "simatic-ipc-leds-gpio.h"
static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW),
{} /* Terminating entry */
},
};
static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = {
.dev_id = NULL, /* Filled during initialization */
.table = {
GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH),
{} /* Terminating entry */
},
};
static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra);
}
static int simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev)
{
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra);
}
static struct platform_driver simatic_ipc_led_gpio_driver = {
.probe = simatic_ipc_leds_gpio_f7188x_probe,
.remove = simatic_ipc_leds_gpio_f7188x_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_led_gpio_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -1,139 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2022
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/x86/simatic-ipc-base.h>
static struct gpiod_lookup_table *simatic_ipc_led_gpio_table;
static struct gpiod_lookup_table simatic_ipc_led_gpio_table_127e = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 52, NULL, 0, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 1, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 2, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 3, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 4, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, NULL, 5, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH),
},
};
static struct gpiod_lookup_table simatic_ipc_led_gpio_table_227g = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH),
GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH),
}
};
static const struct gpio_led simatic_ipc_gpio_leds[] = {
{ .name = "red:" LED_FUNCTION_STATUS "-1" },
{ .name = "green:" LED_FUNCTION_STATUS "-1" },
{ .name = "red:" LED_FUNCTION_STATUS "-2" },
{ .name = "green:" LED_FUNCTION_STATUS "-2" },
{ .name = "red:" LED_FUNCTION_STATUS "-3" },
{ .name = "green:" LED_FUNCTION_STATUS "-3" },
};
static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = {
.num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds),
.leds = simatic_ipc_gpio_leds,
};
static struct platform_device *simatic_leds_pdev;
static int simatic_ipc_leds_gpio_remove(struct platform_device *pdev)
{
gpiod_remove_lookup_table(simatic_ipc_led_gpio_table);
platform_device_unregister(simatic_leds_pdev);
return 0;
}
static int simatic_ipc_leds_gpio_probe(struct platform_device *pdev)
{
const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
struct gpio_desc *gpiod;
int err;
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_127E:
if (!IS_ENABLED(CONFIG_PINCTRL_BROXTON))
return -ENODEV;
simatic_ipc_led_gpio_table = &simatic_ipc_led_gpio_table_127e;
break;
case SIMATIC_IPC_DEVICE_227G:
if (!IS_ENABLED(CONFIG_GPIO_F7188X))
return -ENODEV;
request_module("gpio-f7188x");
simatic_ipc_led_gpio_table = &simatic_ipc_led_gpio_table_227g;
break;
default:
return -ENODEV;
}
gpiod_add_lookup_table(simatic_ipc_led_gpio_table);
simatic_leds_pdev = platform_device_register_resndata(NULL,
"leds-gpio", PLATFORM_DEVID_NONE, NULL, 0,
&simatic_ipc_gpio_leds_pdata,
sizeof(simatic_ipc_gpio_leds_pdata));
if (IS_ERR(simatic_leds_pdev)) {
err = PTR_ERR(simatic_leds_pdev);
goto out;
}
/* PM_BIOS_BOOT_N */
gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 6, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
/* PM_WDT_OUT */
gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 7, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
return 0;
out:
simatic_ipc_leds_gpio_remove(pdev);
return err;
}
static struct platform_driver simatic_ipc_led_gpio_driver = {
.probe = simatic_ipc_leds_gpio_probe,
.remove = simatic_ipc_leds_gpio_remove,
.driver = {
.name = KBUILD_MODNAME,
}
};
module_platform_driver(simatic_ipc_led_gpio_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: platform:leds-gpio");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2023
*
* Author:
* Henning Schild <henning.schild@siemens.com>
*/
#ifndef _SIMATIC_IPC_LEDS_GPIO_H
#define _SIMATIC_IPC_LEDS_GPIO_H
int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra);
int simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra);
#endif /* _SIMATIC_IPC_LEDS_GPIO_H */

View File

@ -126,7 +126,6 @@ static struct platform_driver simatic_ipc_led_driver = {
.name = KBUILD_MODNAME,
}
};
module_platform_driver(simatic_ipc_led_driver);
MODULE_LICENSE("GPL v2");

View File

@ -19,16 +19,13 @@ DEFINE_LED_TRIGGER(ledtrig_disk_write);
void ledtrig_disk_activity(bool write)
{
unsigned long blink_delay = BLINK_DELAY;
led_trigger_blink_oneshot(ledtrig_disk,
&blink_delay, &blink_delay, 0);
led_trigger_blink_oneshot(ledtrig_disk, BLINK_DELAY, BLINK_DELAY, 0);
if (write)
led_trigger_blink_oneshot(ledtrig_disk_write,
&blink_delay, &blink_delay, 0);
BLINK_DELAY, BLINK_DELAY, 0);
else
led_trigger_blink_oneshot(ledtrig_disk_read,
&blink_delay, &blink_delay, 0);
BLINK_DELAY, BLINK_DELAY, 0);
}
EXPORT_SYMBOL(ledtrig_disk_activity);

View File

@ -22,12 +22,8 @@ DEFINE_LED_TRIGGER(ledtrig_nand);
void ledtrig_mtd_activity(void)
{
unsigned long blink_delay = BLINK_DELAY;
led_trigger_blink_oneshot(ledtrig_mtd,
&blink_delay, &blink_delay, 0);
led_trigger_blink_oneshot(ledtrig_nand,
&blink_delay, &blink_delay, 0);
led_trigger_blink_oneshot(ledtrig_mtd, BLINK_DELAY, BLINK_DELAY, 0);
led_trigger_blink_oneshot(ledtrig_nand, BLINK_DELAY, BLINK_DELAY, 0);
}
EXPORT_SYMBOL(ledtrig_mtd_activity);

View File

@ -462,8 +462,7 @@ static int netdev_trig_notify(struct notifier_block *nb,
get_device_state(trigger_data);
fallthrough;
case NETDEV_REGISTER:
if (trigger_data->net_dev)
dev_put(trigger_data->net_dev);
dev_put(trigger_data->net_dev);
dev_hold(dev);
trigger_data->net_dev = dev;
break;
@ -594,8 +593,7 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->work);
if (trigger_data->net_dev)
dev_put(trigger_data->net_dev);
dev_put(trigger_data->net_dev);
kfree(trigger_data);
}

View File

@ -196,13 +196,10 @@ static void arcnet_dump_packet(struct net_device *dev, int bufnum,
void arcnet_led_event(struct net_device *dev, enum arcnet_led_event event)
{
struct arcnet_local *lp = netdev_priv(dev);
unsigned long led_delay = 350;
unsigned long tx_delay = 50;
switch (event) {
case ARCNET_LED_EVENT_RECON:
led_trigger_blink_oneshot(lp->recon_led_trig,
&led_delay, &led_delay, 0);
led_trigger_blink_oneshot(lp->recon_led_trig, 350, 350, 0);
break;
case ARCNET_LED_EVENT_OPEN:
led_trigger_event(lp->tx_led_trig, LED_OFF);
@ -213,8 +210,7 @@ void arcnet_led_event(struct net_device *dev, enum arcnet_led_event event)
led_trigger_event(lp->recon_led_trig, LED_OFF);
break;
case ARCNET_LED_EVENT_TX:
led_trigger_blink_oneshot(lp->tx_led_trig,
&tx_delay, &tx_delay, 0);
led_trigger_blink_oneshot(lp->tx_led_trig, 50, 50, 0);
break;
}
}

View File

@ -68,9 +68,10 @@ static int register_platform_devices(u32 station_id)
}
if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
if (ledmode == SIMATIC_IPC_DEVICE_127E ||
ledmode == SIMATIC_IPC_DEVICE_227G)
pdevname = KBUILD_MODNAME "_leds_gpio";
if (ledmode == SIMATIC_IPC_DEVICE_127E)
pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
if (ledmode == SIMATIC_IPC_DEVICE_227G)
pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
platform_data.devmode = ledmode;
ipc_led_platform_device =
platform_device_register_data(NULL,

View File

@ -22,8 +22,6 @@
static void power_supply_update_bat_leds(struct power_supply *psy)
{
union power_supply_propval status;
unsigned long delay_on = 0;
unsigned long delay_off = 0;
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
return;
@ -43,8 +41,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
led_trigger_event(psy->charging_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_FULL);
led_trigger_event(psy->full_trig, LED_OFF);
led_trigger_blink(psy->charging_blink_full_solid_trig,
&delay_on, &delay_off);
led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0);
break;
default:
led_trigger_event(psy->charging_full_trig, LED_OFF);

View File

@ -14,8 +14,6 @@
#define BLINK_DELAY 30
static unsigned long usb_blink_delay = BLINK_DELAY;
DEFINE_LED_TRIGGER(ledtrig_usb_gadget);
DEFINE_LED_TRIGGER(ledtrig_usb_host);
@ -32,7 +30,7 @@ void usb_led_activity(enum usb_led_event ev)
break;
}
/* led_trigger_blink_oneshot() handles trig == NULL gracefully */
led_trigger_blink_oneshot(trig, &usb_blink_delay, &usb_blink_delay, 0);
led_trigger_blink_oneshot(trig, BLINK_DELAY, BLINK_DELAY, 0);
}
EXPORT_SYMBOL_GPL(usb_led_activity);

View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
#ifndef _DT_BINDINGS_LEDS_LP55XX_H
#define _DT_BINDINGS_LEDS_LP55XX_H
#define LP55XX_CP_OFF 0
#define LP55XX_CP_BYPASS 1
#define LP55XX_CP_BOOST 2
#define LP55XX_CP_AUTO 3
#endif /* _DT_BINDINGS_LEDS_LP55XX_H */

View File

@ -124,6 +124,10 @@ struct led_classdev {
#define LED_BLINK_INVERT 3
#define LED_BLINK_BRIGHTNESS_CHANGE 4
#define LED_BLINK_DISABLE 5
/* Brightness off also disables hw-blinking so it is a separate action */
#define LED_SET_BRIGHTNESS_OFF 6
#define LED_SET_BRIGHTNESS 7
#define LED_SET_BLINK 8
/* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers
@ -147,6 +151,10 @@ struct led_classdev {
* match the values specified exactly.
* Deactivate blinking again when the brightness is set to LED_OFF
* via the brightness_set() callback.
* For led_blink_set_nosleep() the LED core assumes that blink_set
* implementations, of drivers which do not use brightness_set_blocking,
* will not sleep. Therefor if brightness_set_blocking is not set
* this function must not sleep!
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
@ -170,6 +178,8 @@ struct led_classdev {
struct work_struct set_brightness_work;
int delayed_set_value;
unsigned long delayed_delay_on;
unsigned long delayed_delay_off;
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
@ -315,12 +325,27 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev,
* software blinking if there is no hardware blinking or if
* the LED refuses the passed values.
*
* This function may sleep!
*
* Note that if software blinking is active, simply calling
* led_cdev->brightness_set() will not stop the blinking,
* use led_set_brightness() instead.
*/
void led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
unsigned long *delay_off);
/**
* led_blink_set_nosleep - set blinking, guaranteed to not sleep
* @led_cdev: the LED to start blinking
* @delay_on: the time it should be on (in ms)
* @delay_off: the time it should ble off (in ms)
*
* This function makes the LED blink and is guaranteed to not sleep. Otherwise
* this is the same as led_blink_set(), see led_blink_set() for details.
*/
void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on,
unsigned long delay_off);
/**
* led_blink_set_oneshot - do a oneshot software blink
* @led_cdev: the LED to start blinking
@ -334,6 +359,8 @@ void led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
*
* If invert is set, led blinks for delay_off first, then for
* delay_on and leave the led on after the on-off cycle.
*
* This function is guaranteed not to sleep.
*/
void led_blink_set_oneshot(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off,
@ -476,11 +503,11 @@ void led_trigger_register_simple(const char *name,
struct led_trigger **trigger);
void led_trigger_unregister_simple(struct led_trigger *trigger);
void led_trigger_event(struct led_trigger *trigger, enum led_brightness event);
void led_trigger_blink(struct led_trigger *trigger, unsigned long *delay_on,
unsigned long *delay_off);
void led_trigger_blink(struct led_trigger *trigger, unsigned long delay_on,
unsigned long delay_off);
void led_trigger_blink_oneshot(struct led_trigger *trigger,
unsigned long *delay_on,
unsigned long *delay_off,
unsigned long delay_on,
unsigned long delay_off,
int invert);
void led_trigger_set_default(struct led_classdev *led_cdev);
int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger);
@ -530,11 +557,11 @@ static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {}
static inline void led_trigger_event(struct led_trigger *trigger,
enum led_brightness event) {}
static inline void led_trigger_blink(struct led_trigger *trigger,
unsigned long *delay_on,
unsigned long *delay_off) {}
unsigned long delay_on,
unsigned long delay_off) {}
static inline void led_trigger_blink_oneshot(struct led_trigger *trigger,
unsigned long *delay_on,
unsigned long *delay_off,
unsigned long delay_on,
unsigned long delay_off,
int invert) {}
static inline void led_trigger_set_default(struct led_classdev *led_cdev) {}
static inline int led_trigger_set(struct led_classdev *led_cdev,

View File

@ -73,6 +73,9 @@ struct lp55xx_platform_data {
/* Clock configuration */
u8 clock_mode;
/* Charge pump mode */
u32 charge_pump_mode;
/* optional enable GPIO */
struct gpio_desc *enable_gpiod;

View File

@ -282,7 +282,7 @@ static void tpt_trig_timer(struct timer_list *t)
}
}
led_trigger_blink(&local->tpt_led, &on, &off);
led_trigger_blink(&local->tpt_led, on, off);
}
const char *

View File

@ -13,22 +13,18 @@
static inline void ieee80211_led_rx(struct ieee80211_local *local)
{
#ifdef CONFIG_MAC80211_LEDS
unsigned long led_delay = MAC80211_BLINK_DELAY;
if (!atomic_read(&local->rx_led_active))
return;
led_trigger_blink_oneshot(&local->rx_led, &led_delay, &led_delay, 0);
led_trigger_blink_oneshot(&local->rx_led, MAC80211_BLINK_DELAY, MAC80211_BLINK_DELAY, 0);
#endif
}
static inline void ieee80211_led_tx(struct ieee80211_local *local)
{
#ifdef CONFIG_MAC80211_LEDS
unsigned long led_delay = MAC80211_BLINK_DELAY;
if (!atomic_read(&local->tx_led_active))
return;
led_trigger_blink_oneshot(&local->tx_led, &led_delay, &led_delay, 0);
led_trigger_blink_oneshot(&local->tx_led, MAC80211_BLINK_DELAY, MAC80211_BLINK_DELAY, 0);
#endif
}

View File

@ -43,7 +43,6 @@ led_tg(struct sk_buff *skb, const struct xt_action_param *par)
{
const struct xt_led_info *ledinfo = par->targinfo;
struct xt_led_info_internal *ledinternal = ledinfo->internal_data;
unsigned long led_delay = XT_LED_BLINK_DELAY;
/*
* If "always blink" is enabled, and there's still some time until the
@ -52,7 +51,7 @@ led_tg(struct sk_buff *skb, const struct xt_action_param *par)
if ((ledinfo->delay > 0) && ledinfo->always_blink &&
timer_pending(&ledinternal->timer))
led_trigger_blink_oneshot(&ledinternal->netfilter_led_trigger,
&led_delay, &led_delay, 1);
XT_LED_BLINK_DELAY, XT_LED_BLINK_DELAY, 1);
else
led_trigger_event(&ledinternal->netfilter_led_trigger, LED_FULL);