- Core Frameworks

- Add support for a bunch more colours
 
  - New Drivers
    - Add support for Kinetic KTD2026/7 RGB/White LEDs
 
  - New Functionality
    - Add support for device to enter HW Controlled Mode to Turris Omnia LEDs
    - Add support for HW Gamma Correction to Turris Omnia LEDs
 
  - Fix-ups
    - Apply new __counted_by() annotation to several data structures containing flexible arrays
    - Rid the return value from Platform's .remove() operation
    - Use *_cansleep() variants for instances were threads can sleep
    - Improve the semantics when setting the brightness
    - Generic clean-ups; code reduction, coding style, standard patterns
    - Replace strncpy() with strscpy()
    - Fix-up / add various documentation
    - Re-author the GPIO associated Trigger to use trigger-sources
    - Move to using standard APIs and helpers
    - Improve error checking
    - Stop using static GPIO bases
 
  - Bug Fixes
    - Fix Pointer to Enum casing warnings
    - Do not pretend that I2C backed device supports SMBUS
    - Ensure PWM LEDs are extinguished when disabled, rather than held in a state
    - Fix 'output may be truncated' warnings
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmVDsTgACgkQUa+KL4f8
 d2EQsg/+Jy2zYIpFXaj/sSZCB9gDToIpHNer2XUEgKMXCAF0Ar8SCHvdmIeb9exG
 rCLb86AxvE2trIcZBRITtRgU4YCAr6yiyNZHCYyLxN+WZLind3lL7mKB1x9gVFbw
 MKFmQjl9cWmJjI1Mk10NFzhgYJkMotKWZrQqAeggD5gYinieSVXE2RkrcV/zfQxh
 NqtijzDL0C9pWIXQtqpjic7PSgyC8mIGmD0Q9gyEOWOVbWzmR/vklwEsbirT4nEp
 7QIz/b5nwXVEEJ69WCTNeDBmXYeEKw4DMLJxJ6aTYHf62gramtfTH+d6izY3Hnrs
 EszgWmqmwSqXrwadtg7Zn6xrgec+yDuwj3rwQEQcveOsUJMENtVN6i2ZYO90Hpor
 XP6jzz+KBT/qLF4syfkO7Z9vV7em1zm/AuoiDieFVBy6vMQJmmCvIb+GE1l5CXUv
 myEYVu0P9Ir9/2W+z9rqzor2wtZ9OVXT1XCnV6UPU7mNhJIH8PXr9/cjJgsY5vYC
 H8kZcfaCRlwqPiVA0yucvfEeveptObVojZdkhCH/BnhRtIAWf1jlj5PglfSaOQaU
 jGy6s/8auS5ua0vX2UYFv0cXxSTZpX+7jDzpQcaIKqOPe/xqUSAeL/N4btY+TlfR
 zkQxWxksOtVdD2xaIZvGutcAMXRBEIjgDS61SZBLf/jh7N/iXlc=
 =XYXV
 -----END PGP SIGNATURE-----

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

Pull LED updates from Lee Jones:
 "Core Frameworks:
   - Add support for a bunch more colours

  New Drivers:
   - Add support for Kinetic KTD2026/7 RGB/White LEDs

  New Functionality:
   - Add support for device to enter HW Controlled Mode to Turris Omnia
     LEDs
   - Add support for HW Gamma Correction to Turris Omnia LEDs

  Fix-ups:
   - Apply new __counted_by() annotation to several data structures
     containing flexible arrays
   - Rid the return value from Platform's .remove() operation
   - Use *_cansleep() variants for instances were threads can sleep
   - Improve the semantics when setting the brightness
   - Generic clean-ups; code reduction, coding style, standard patterns
   - Replace strncpy() with strscpy()
   - Fix-up / add various documentation
   - Re-author the GPIO associated Trigger to use trigger-sources
   - Move to using standard APIs and helpers
   - Improve error checking
   - Stop using static GPIO bases

  Bug Fixes:
   - Fix Pointer to Enum casing warnings
   - Do not pretend that I2C backed device supports SMBUS
   - Ensure PWM LEDs are extinguished when disabled, rather than held in
     a state
   - Fix 'output may be truncated' warnings"

* tag 'leds-next-6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (43 commits)
  leds: lp5521: Add an error check in lp5521_post_init_device
  leds: gpio: Update headers
  leds: gpio: Remove unneeded assignment
  leds: gpio: Move temporary variable for struct device to gpio_led_probe()
  leds: gpio: Refactor code to use devm_gpiod_get_index_optional()
  leds: gpio: Utilise PTR_ERR_OR_ZERO()
  leds: gpio: Keep driver firmware interface agnostic
  leds: core: Refactor led_update_brightness() to use standard pattern
  leds: turris-omnia: Fix brightness setting and trigger activating
  leds: sc27xx: Move mutex_init() down
  leds: trigger: netdev: Move size check in set_device_name
  leds: Add ktd202x driver
  dt-bindings: leds: Add Kinetic KTD2026/2027 LED
  leds: core: Add more colors from DT bindings to led_colors
  dt-bindings: leds: Last color ID is now 14 (LED_COLOR_ID_LIME)
  leds: tca6507: Don't use fixed GPIO base
  leds: lp3952: Convert to use maple tree register cache
  leds: lm392x: Convert to use maple tree register cache
  leds: aw200xx: Convert to use maple tree register cache
  leds: lm3601x: Convert to use maple tree register cache
  ...
This commit is contained in:
Linus Torvalds 2023-11-02 14:53:19 -10:00
commit 431f105188
59 changed files with 1348 additions and 365 deletions

View File

@ -12,3 +12,17 @@ Description: (RW) On the front panel of the Turris Omnia router there is also
able to change this setting from software. able to change this setting from software.
Format: %i Format: %i
What: /sys/class/leds/<led>/device/gamma_correction
Date: August 2023
KernelVersion: 6.6
Contact: Marek Behún <kabel@kernel.org>
Description: (RW) Newer versions of the microcontroller firmware of the
Turris Omnia router support gamma correction for the RGB LEDs.
This feature can be enabled/disabled by writing to this file.
If the feature is not supported because the MCU firmware is too
old, the file always reads as 0, and writing to the file results
in the EOPNOTSUPP error.
Format: %i

View File

@ -43,7 +43,7 @@ properties:
LED_COLOR_ID available, add a new one. LED_COLOR_ID available, add a new one.
$ref: /schemas/types.yaml#/definitions/uint32 $ref: /schemas/types.yaml#/definitions/uint32
minimum: 0 minimum: 0
maximum: 9 maximum: 14
function-enumerator: function-enumerator:
description: description:
@ -191,6 +191,8 @@ properties:
each of them having its own LED assigned (assuming they are not each of them having its own LED assigned (assuming they are not
hardwired). In such cases this property should contain phandle(s) of hardwired). In such cases this property should contain phandle(s) of
related source device(s). related source device(s).
Another example is a GPIO line that will be monitored and mirror the
state of the line (with or without inversion flags) to the LED.
In many cases LED can be related to more than one device (e.g. one USB LED In many cases LED can be related to more than one device (e.g. one USB LED
vs. multiple USB ports). Each source should be represented by a node in vs. multiple USB ports). Each source should be represented by a node in
the device tree and be referenced by a phandle and a set of phandle the device tree and be referenced by a phandle and a set of phandle

View File

@ -0,0 +1,171 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/kinetic,ktd202x.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Kinetic KTD2026/7 RGB/White LED Driver
maintainers:
- André Apitzsch <git@apitzsch.eu>
description: |
The KTD2026/7 is a RGB/White LED driver with I2C interface.
The data sheet can be found at:
https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf
properties:
compatible:
enum:
- kinetic,ktd2026
- kinetic,ktd2027
reg:
maxItems: 1
vin-supply:
description: Regulator providing power to the "VIN" pin.
vio-supply:
description: Regulator providing power for pull-up of the I/O lines.
Note that this regulator does not directly connect to KTD2026, but is
needed for the correct operation of the status ("ST") and I2C lines.
"#address-cells":
const: 1
"#size-cells":
const: 0
multi-led:
type: object
$ref: leds-class-multicolor.yaml#
unevaluatedProperties: false
properties:
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^led@[0-3]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
description: Index of the LED.
minimum: 0
maximum: 3
required:
- reg
- color
required:
- "#address-cells"
- "#size-cells"
patternProperties:
"^led@[0-3]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
description: Index of the LED.
minimum: 0
maximum: 3
required:
- reg
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@30 {
compatible = "kinetic,ktd2026";
reg = <0x30>;
#address-cells = <1>;
#size-cells = <0>;
vin-supply = <&pm8916_l17>;
vio-supply = <&pm8916_l6>;
led@0 {
reg = <0>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>;
};
led@1 {
reg = <1>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>;
};
led@2 {
reg = <2>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_BLUE>;
};
};
};
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@30 {
compatible = "kinetic,ktd2026";
reg = <0x30>;
#address-cells = <1>;
#size-cells = <0>;
vin-supply = <&pm8916_l17>;
vio-supply = <&pm8916_l6>;
multi-led {
color = <LED_COLOR_ID_RGB>;
function = LED_FUNCTION_STATUS;
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_RED>;
};
led@1 {
reg = <1>;
color = <LED_COLOR_ID_GREEN>;
};
led@2 {
reg = <2>;
color = <LED_COLOR_ID_BLUE>;
};
};
};
};

View File

@ -187,6 +187,7 @@ config LEDS_TURRIS_OMNIA
depends on I2C depends on I2C
depends on MACH_ARMADA_38X || COMPILE_TEST depends on MACH_ARMADA_38X || COMPILE_TEST
depends on OF depends on OF
select LEDS_TRIGGERS
help help
This option enables basic support for the LEDs found on the front This option enables basic support for the LEDs found on the front
side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the

View File

@ -837,7 +837,7 @@ static int intel_sso_led_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int intel_sso_led_remove(struct platform_device *pdev) static void intel_sso_led_remove(struct platform_device *pdev)
{ {
struct sso_led_priv *priv; struct sso_led_priv *priv;
struct sso_led *led, *n; struct sso_led *led, *n;
@ -850,8 +850,6 @@ static int intel_sso_led_remove(struct platform_device *pdev)
} }
regmap_exit(priv->mmap); regmap_exit(priv->mmap);
return 0;
} }
static const struct of_device_id of_sso_led_match[] = { static const struct of_device_id of_sso_led_match[] = {
@ -863,7 +861,7 @@ MODULE_DEVICE_TABLE(of, of_sso_led_match);
static struct platform_driver intel_sso_led_driver = { static struct platform_driver intel_sso_led_driver = {
.probe = intel_sso_led_probe, .probe = intel_sso_led_probe,
.remove = intel_sso_led_remove, .remove_new = intel_sso_led_remove,
.driver = { .driver = {
.name = "lgm-ssoled", .name = "lgm-ssoled",
.of_match_table = of_sso_led_match, .of_match_table = of_sso_led_match,

View File

@ -522,7 +522,7 @@ err_flash_register:
return ret; return ret;
} }
static int aat1290_led_remove(struct platform_device *pdev) static void aat1290_led_remove(struct platform_device *pdev)
{ {
struct aat1290_led *led = platform_get_drvdata(pdev); struct aat1290_led *led = platform_get_drvdata(pdev);
@ -530,8 +530,6 @@ static int aat1290_led_remove(struct platform_device *pdev)
led_classdev_flash_unregister(&led->fled_cdev); led_classdev_flash_unregister(&led->fled_cdev);
mutex_destroy(&led->lock); mutex_destroy(&led->lock);
return 0;
} }
static const struct of_device_id aat1290_led_dt_match[] = { static const struct of_device_id aat1290_led_dt_match[] = {
@ -542,7 +540,7 @@ MODULE_DEVICE_TABLE(of, aat1290_led_dt_match);
static struct platform_driver aat1290_led_driver = { static struct platform_driver aat1290_led_driver = {
.probe = aat1290_led_probe, .probe = aat1290_led_probe,
.remove = aat1290_led_remove, .remove_new = aat1290_led_remove,
.driver = { .driver = {
.name = "aat1290", .name = "aat1290",
.of_match_table = aat1290_led_dt_match, .of_match_table = aat1290_led_dt_match,

View File

@ -386,15 +386,13 @@ static int ktd2692_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int ktd2692_remove(struct platform_device *pdev) static void ktd2692_remove(struct platform_device *pdev)
{ {
struct ktd2692_context *led = platform_get_drvdata(pdev); struct ktd2692_context *led = platform_get_drvdata(pdev);
led_classdev_flash_unregister(&led->fled_cdev); led_classdev_flash_unregister(&led->fled_cdev);
mutex_destroy(&led->lock); mutex_destroy(&led->lock);
return 0;
} }
static const struct of_device_id ktd2692_match[] = { static const struct of_device_id ktd2692_match[] = {
@ -409,7 +407,7 @@ static struct platform_driver ktd2692_driver = {
.of_match_table = ktd2692_match, .of_match_table = ktd2692_match,
}, },
.probe = ktd2692_probe, .probe = ktd2692_probe,
.remove = ktd2692_remove, .remove_new = ktd2692_remove,
}; };
module_platform_driver(ktd2692_driver); module_platform_driver(ktd2692_driver);

View File

@ -123,7 +123,7 @@ static const struct regmap_config lm3601x_regmap = {
.max_register = LM3601X_DEV_ID_REG, .max_register = LM3601X_DEV_ID_REG,
.reg_defaults = lm3601x_regmap_defs, .reg_defaults = lm3601x_regmap_defs,
.num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs), .num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs),
.cache_type = REGCACHE_RBTREE, .cache_type = REGCACHE_MAPLE,
.volatile_reg = lm3601x_volatile_reg, .volatile_reg = lm3601x_volatile_reg,
}; };

View File

@ -1016,7 +1016,7 @@ err_register_led1:
return ret; return ret;
} }
static int max77693_led_remove(struct platform_device *pdev) static void max77693_led_remove(struct platform_device *pdev)
{ {
struct max77693_led_device *led = platform_get_drvdata(pdev); struct max77693_led_device *led = platform_get_drvdata(pdev);
struct max77693_sub_led *sub_leds = led->sub_leds; struct max77693_sub_led *sub_leds = led->sub_leds;
@ -1032,8 +1032,6 @@ static int max77693_led_remove(struct platform_device *pdev)
} }
mutex_destroy(&led->lock); mutex_destroy(&led->lock);
return 0;
} }
static const struct of_device_id max77693_led_dt_match[] = { static const struct of_device_id max77693_led_dt_match[] = {
@ -1044,7 +1042,7 @@ MODULE_DEVICE_TABLE(of, max77693_led_dt_match);
static struct platform_driver max77693_led_driver = { static struct platform_driver max77693_led_driver = {
.probe = max77693_led_probe, .probe = max77693_led_probe,
.remove = max77693_led_remove, .remove_new = max77693_led_remove,
.driver = { .driver = {
.name = "max77693-led", .name = "max77693-led",
.of_match_table = max77693_led_dt_match, .of_match_table = max77693_led_dt_match,

View File

@ -91,7 +91,7 @@ struct mt6360_priv {
unsigned int fled_torch_used; unsigned int fled_torch_used;
unsigned int leds_active; unsigned int leds_active;
unsigned int leds_count; unsigned int leds_count;
struct mt6360_led leds[]; struct mt6360_led leds[] __counted_by(leds_count);
}; };
static int mt6360_mc_brightness_set(struct led_classdev *lcdev, static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
@ -855,12 +855,11 @@ out_flash_release:
return ret; return ret;
} }
static int mt6360_led_remove(struct platform_device *pdev) static void mt6360_led_remove(struct platform_device *pdev)
{ {
struct mt6360_priv *priv = platform_get_drvdata(pdev); struct mt6360_priv *priv = platform_get_drvdata(pdev);
mt6360_v4l2_flash_release(priv); mt6360_v4l2_flash_release(priv);
return 0;
} }
static const struct of_device_id __maybe_unused mt6360_led_of_id[] = { static const struct of_device_id __maybe_unused mt6360_led_of_id[] = {
@ -875,7 +874,7 @@ static struct platform_driver mt6360_led_driver = {
.of_match_table = mt6360_led_of_id, .of_match_table = mt6360_led_of_id,
}, },
.probe = mt6360_led_probe, .probe = mt6360_led_probe,
.remove = mt6360_led_remove, .remove_new = mt6360_led_remove,
}; };
module_platform_driver(mt6360_led_driver); module_platform_driver(mt6360_led_driver);

View File

@ -81,7 +81,7 @@ struct mt6370_priv {
unsigned int fled_torch_used; unsigned int fled_torch_used;
unsigned int leds_active; unsigned int leds_active;
unsigned int leds_count; unsigned int leds_count;
struct mt6370_led leds[]; struct mt6370_led leds[] __counted_by(leds_count);
}; };
static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level) static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level)

View File

@ -755,7 +755,7 @@ release:
return rc; return rc;
} }
static int qcom_flash_led_remove(struct platform_device *pdev) static void qcom_flash_led_remove(struct platform_device *pdev)
{ {
struct qcom_flash_data *flash_data = platform_get_drvdata(pdev); struct qcom_flash_data *flash_data = platform_get_drvdata(pdev);
@ -763,7 +763,6 @@ static int qcom_flash_led_remove(struct platform_device *pdev)
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
mutex_destroy(&flash_data->lock); mutex_destroy(&flash_data->lock);
return 0;
} }
static const struct of_device_id qcom_flash_led_match_table[] = { static const struct of_device_id qcom_flash_led_match_table[] = {
@ -778,7 +777,7 @@ static struct platform_driver qcom_flash_led_driver = {
.of_match_table = qcom_flash_led_match_table, .of_match_table = qcom_flash_led_match_table,
}, },
.probe = qcom_flash_led_probe, .probe = qcom_flash_led_probe,
.remove = qcom_flash_led_remove, .remove_new = qcom_flash_led_remove,
}; };
module_platform_driver(qcom_flash_led_driver); module_platform_driver(qcom_flash_led_driver);

View File

@ -367,15 +367,13 @@ static int rt8515_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int rt8515_remove(struct platform_device *pdev) static void rt8515_remove(struct platform_device *pdev)
{ {
struct rt8515 *rt = platform_get_drvdata(pdev); struct rt8515 *rt = platform_get_drvdata(pdev);
rt8515_v4l2_flash_release(rt); rt8515_v4l2_flash_release(rt);
del_timer_sync(&rt->powerdown_timer); del_timer_sync(&rt->powerdown_timer);
mutex_destroy(&rt->lock); mutex_destroy(&rt->lock);
return 0;
} }
static const struct of_device_id rt8515_match[] = { static const struct of_device_id rt8515_match[] = {
@ -390,7 +388,7 @@ static struct platform_driver rt8515_driver = {
.of_match_table = rt8515_match, .of_match_table = rt8515_match,
}, },
.probe = rt8515_probe, .probe = rt8515_probe,
.remove = rt8515_remove, .remove_new = rt8515_remove,
}; };
module_platform_driver(rt8515_driver); module_platform_driver(rt8515_driver);

View File

@ -278,15 +278,13 @@ err:
return ret; return ret;
} }
static int sgm3140_remove(struct platform_device *pdev) static void sgm3140_remove(struct platform_device *pdev)
{ {
struct sgm3140 *priv = platform_get_drvdata(pdev); struct sgm3140 *priv = platform_get_drvdata(pdev);
del_timer_sync(&priv->powerdown_timer); del_timer_sync(&priv->powerdown_timer);
v4l2_flash_release(priv->v4l2_flash); v4l2_flash_release(priv->v4l2_flash);
return 0;
} }
static const struct of_device_id sgm3140_dt_match[] = { static const struct of_device_id sgm3140_dt_match[] = {
@ -299,7 +297,7 @@ MODULE_DEVICE_TABLE(of, sgm3140_dt_match);
static struct platform_driver sgm3140_driver = { static struct platform_driver sgm3140_driver = {
.probe = sgm3140_probe, .probe = sgm3140_probe,
.remove = sgm3140_remove, .remove_new = sgm3140_remove,
.driver = { .driver = {
.name = "sgm3140", .name = "sgm3140",
.of_match_table = sgm3140_dt_match, .of_match_table = sgm3140_dt_match,

View File

@ -36,6 +36,11 @@ const char * const led_colors[LED_COLOR_ID_MAX] = {
[LED_COLOR_ID_IR] = "ir", [LED_COLOR_ID_IR] = "ir",
[LED_COLOR_ID_MULTI] = "multicolor", [LED_COLOR_ID_MULTI] = "multicolor",
[LED_COLOR_ID_RGB] = "rgb", [LED_COLOR_ID_RGB] = "rgb",
[LED_COLOR_ID_PURPLE] = "purple",
[LED_COLOR_ID_ORANGE] = "orange",
[LED_COLOR_ID_PINK] = "pink",
[LED_COLOR_ID_CYAN] = "cyan",
[LED_COLOR_ID_LIME] = "lime",
}; };
EXPORT_SYMBOL_GPL(led_colors); EXPORT_SYMBOL_GPL(led_colors);
@ -359,17 +364,17 @@ EXPORT_SYMBOL_GPL(led_set_brightness_sync);
int led_update_brightness(struct led_classdev *led_cdev) int led_update_brightness(struct led_classdev *led_cdev)
{ {
int ret = 0; int ret;
if (led_cdev->brightness_get) { if (led_cdev->brightness_get) {
ret = led_cdev->brightness_get(led_cdev); ret = led_cdev->brightness_get(led_cdev);
if (ret >= 0) { if (ret < 0)
led_cdev->brightness = ret; return ret;
return 0;
} led_cdev->brightness = ret;
} }
return ret; return 0;
} }
EXPORT_SYMBOL_GPL(led_update_brightness); EXPORT_SYMBOL_GPL(led_update_brightness);

View File

@ -215,13 +215,11 @@ static int pm860x_led_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int pm860x_led_remove(struct platform_device *pdev) static void pm860x_led_remove(struct platform_device *pdev)
{ {
struct pm860x_led *data = platform_get_drvdata(pdev); struct pm860x_led *data = platform_get_drvdata(pdev);
led_classdev_unregister(&data->cdev); led_classdev_unregister(&data->cdev);
return 0;
} }
static struct platform_driver pm860x_led_driver = { static struct platform_driver pm860x_led_driver = {
@ -229,7 +227,7 @@ static struct platform_driver pm860x_led_driver = {
.name = "88pm860x-led", .name = "88pm860x-led",
}, },
.probe = pm860x_led_probe, .probe = pm860x_led_probe,
.remove = pm860x_led_remove, .remove_new = pm860x_led_remove,
}; };
module_platform_driver(pm860x_led_driver); module_platform_driver(pm860x_led_driver);

View File

@ -163,7 +163,7 @@ err:
return ret; return ret;
} }
static int adp5520_led_remove(struct platform_device *pdev) static void adp5520_led_remove(struct platform_device *pdev)
{ {
struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct adp5520_led *led; struct adp5520_led *led;
@ -177,8 +177,6 @@ static int adp5520_led_remove(struct platform_device *pdev)
for (i = 0; i < pdata->num_leds; i++) { for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&led[i].cdev); led_classdev_unregister(&led[i].cdev);
} }
return 0;
} }
static struct platform_driver adp5520_led_driver = { static struct platform_driver adp5520_led_driver = {
@ -186,7 +184,7 @@ static struct platform_driver adp5520_led_driver = {
.name = "adp5520-led", .name = "adp5520-led",
}, },
.probe = adp5520_led_probe, .probe = adp5520_led_probe,
.remove = adp5520_led_remove, .remove_new = adp5520_led_remove,
}; };
module_platform_driver(adp5520_led_driver); module_platform_driver(adp5520_led_driver);

View File

@ -112,7 +112,7 @@ struct aw200xx {
struct mutex mutex; struct mutex mutex;
u32 num_leds; u32 num_leds;
u32 display_rows; u32 display_rows;
struct aw200xx_led leds[]; struct aw200xx_led leds[] __counted_by(num_leds);
}; };
static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
@ -479,7 +479,7 @@ static const struct regmap_config aw200xx_regmap_config = {
.num_ranges = ARRAY_SIZE(aw200xx_ranges), .num_ranges = ARRAY_SIZE(aw200xx_ranges),
.rd_table = &aw200xx_readable_table, .rd_table = &aw200xx_readable_table,
.wr_table = &aw200xx_writeable_table, .wr_table = &aw200xx_writeable_table,
.cache_type = REGCACHE_RBTREE, .cache_type = REGCACHE_MAPLE,
}; };
static int aw200xx_probe(struct i2c_client *client) static int aw200xx_probe(struct i2c_client *client)

View File

@ -159,14 +159,13 @@ static int __init clevo_mail_led_probe(struct platform_device *pdev)
return led_classdev_register(&pdev->dev, &clevo_mail_led); return led_classdev_register(&pdev->dev, &clevo_mail_led);
} }
static int clevo_mail_led_remove(struct platform_device *pdev) static void clevo_mail_led_remove(struct platform_device *pdev)
{ {
led_classdev_unregister(&clevo_mail_led); led_classdev_unregister(&clevo_mail_led);
return 0;
} }
static struct platform_driver clevo_mail_led_driver = { static struct platform_driver clevo_mail_led_driver = {
.remove = clevo_mail_led_remove, .remove_new = clevo_mail_led_remove,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
}, },

View File

@ -56,7 +56,7 @@ struct cr0014114 {
struct spi_device *spi; struct spi_device *spi;
u8 *buf; u8 *buf;
unsigned long delay; unsigned long delay;
struct cr0014114_led leds[]; struct cr0014114_led leds[] __counted_by(count);
}; };
static void cr0014114_calc_crc(u8 *buf, const size_t len) static void cr0014114_calc_crc(u8 *buf, const size_t len)

View File

@ -121,13 +121,11 @@ static int da903x_led_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int da903x_led_remove(struct platform_device *pdev) static void da903x_led_remove(struct platform_device *pdev)
{ {
struct da903x_led *led = platform_get_drvdata(pdev); struct da903x_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev); led_classdev_unregister(&led->cdev);
return 0;
} }
static struct platform_driver da903x_led_driver = { static struct platform_driver da903x_led_driver = {
@ -135,7 +133,7 @@ static struct platform_driver da903x_led_driver = {
.name = "da903x-led", .name = "da903x-led",
}, },
.probe = da903x_led_probe, .probe = da903x_led_probe,
.remove = da903x_led_remove, .remove_new = da903x_led_remove,
}; };
module_platform_driver(da903x_led_driver); module_platform_driver(da903x_led_driver);

View File

@ -156,7 +156,7 @@ err:
return error; return error;
} }
static int da9052_led_remove(struct platform_device *pdev) static void da9052_led_remove(struct platform_device *pdev)
{ {
struct da9052_led *led = platform_get_drvdata(pdev); struct da9052_led *led = platform_get_drvdata(pdev);
struct da9052_pdata *pdata; struct da9052_pdata *pdata;
@ -172,8 +172,6 @@ static int da9052_led_remove(struct platform_device *pdev)
da9052_set_led_brightness(&led[i], LED_OFF); da9052_set_led_brightness(&led[i], LED_OFF);
led_classdev_unregister(&led[i].cdev); led_classdev_unregister(&led[i].cdev);
} }
return 0;
} }
static struct platform_driver da9052_led_driver = { static struct platform_driver da9052_led_driver = {
@ -181,7 +179,7 @@ static struct platform_driver da9052_led_driver = {
.name = "da9052-leds", .name = "da9052-leds",
}, },
.probe = da9052_led_probe, .probe = da9052_led_probe,
.remove = da9052_led_remove, .remove_new = da9052_led_remove,
}; };
module_platform_driver(da9052_led_driver); module_platform_driver(da9052_led_driver);

View File

@ -80,7 +80,7 @@ struct el15203000 {
struct spi_device *spi; struct spi_device *spi;
unsigned long delay; unsigned long delay;
size_t count; size_t count;
struct el15203000_led leds[]; struct el15203000_led leds[] __counted_by(count);
}; };
#define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev) #define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev)

View File

@ -6,17 +6,21 @@
* Raphael Assenat <raph@8d.com> * Raphael Assenat <raph@8d.com>
* Copyright (C) 2008 Freescale Semiconductor, Inc. * Copyright (C) 2008 Freescale Semiconductor, Inc.
*/ */
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/overflow.h>
#include <linux/pinctrl/consumer.h> #include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/property.h> #include <linux/property.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/types.h>
#include "leds.h" #include "leds.h"
struct gpio_led_data { struct gpio_led_data {
@ -125,16 +129,13 @@ static int create_gpio_led(const struct gpio_led *template,
return ret; return ret;
pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev); pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev);
if (IS_ERR(pinctrl)) { ret = PTR_ERR_OR_ZERO(pinctrl);
ret = PTR_ERR(pinctrl); /* pinctrl-%d not present, not an error */
if (ret != -ENODEV) { if (ret == -ENODEV)
dev_warn(led_dat->cdev.dev, ret = 0;
"Failed to select %pOF pinctrl: %d\n", if (ret) {
to_of_node(fwnode), ret); dev_warn(led_dat->cdev.dev, "Failed to select %pfw pinctrl: %d\n",
} else { fwnode, ret);
/* pinctrl-%d not present, not an error */
ret = 0;
}
} }
return ret; return ret;
@ -142,12 +143,11 @@ static int create_gpio_led(const struct gpio_led *template,
struct gpio_leds_priv { struct gpio_leds_priv {
int num_leds; int num_leds;
struct gpio_led_data leds[]; struct gpio_led_data leds[] __counted_by(num_leds);
}; };
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
{ {
struct device *dev = &pdev->dev;
struct fwnode_handle *child; struct fwnode_handle *child;
struct gpio_leds_priv *priv; struct gpio_leds_priv *priv;
int count, ret; int count, ret;
@ -221,13 +221,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
* device, this will hit the board file, if any and get * device, this will hit the board file, if any and get
* the GPIO from there. * the GPIO from there.
*/ */
gpiod = devm_gpiod_get_index(dev, NULL, idx, GPIOD_OUT_LOW); gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW);
if (!IS_ERR(gpiod)) { if (IS_ERR(gpiod))
return gpiod;
if (gpiod) {
gpiod_set_consumer_name(gpiod, template->name); gpiod_set_consumer_name(gpiod, template->name);
return gpiod; return gpiod;
} }
if (PTR_ERR(gpiod) != -ENOENT)
return gpiod;
/* /*
* This is the legacy code path for platform code that * This is the legacy code path for platform code that
@ -256,13 +256,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
static int gpio_led_probe(struct platform_device *pdev) static int gpio_led_probe(struct platform_device *pdev)
{ {
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = &pdev->dev;
struct gpio_led_platform_data *pdata = dev_get_platdata(dev);
struct gpio_leds_priv *priv; struct gpio_leds_priv *priv;
int i, ret = 0; int i, ret;
if (pdata && pdata->num_leds) { if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, pdata->num_leds), priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL);
GFP_KERNEL);
if (!priv) if (!priv)
return -ENOMEM; return -ENOMEM;
@ -275,22 +275,20 @@ static int gpio_led_probe(struct platform_device *pdev)
led_dat->gpiod = template->gpiod; led_dat->gpiod = template->gpiod;
else else
led_dat->gpiod = led_dat->gpiod =
gpio_led_get_gpiod(&pdev->dev, gpio_led_get_gpiod(dev, i, template);
i, template);
if (IS_ERR(led_dat->gpiod)) { if (IS_ERR(led_dat->gpiod)) {
dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n", dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n",
template->gpio, template->name); template->gpio, template->name);
continue; continue;
} }
ret = create_gpio_led(template, led_dat, ret = create_gpio_led(template, led_dat, dev, NULL,
&pdev->dev, NULL,
pdata->gpio_blink_set); pdata->gpio_blink_set);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
} else { } else {
priv = gpio_leds_create(pdev); priv = gpio_leds_create(dev);
if (IS_ERR(priv)) if (IS_ERR(priv))
return PTR_ERR(priv); return PTR_ERR(priv);
} }

View File

@ -718,7 +718,7 @@ err_deregister:
return ret; return ret;
} }
static int lm3533_led_remove(struct platform_device *pdev) static void lm3533_led_remove(struct platform_device *pdev)
{ {
struct lm3533_led *led = platform_get_drvdata(pdev); struct lm3533_led *led = platform_get_drvdata(pdev);
@ -726,8 +726,6 @@ static int lm3533_led_remove(struct platform_device *pdev)
lm3533_ctrlbank_disable(&led->cb); lm3533_ctrlbank_disable(&led->cb);
led_classdev_unregister(&led->cdev); led_classdev_unregister(&led->cdev);
return 0;
} }
static void lm3533_led_shutdown(struct platform_device *pdev) static void lm3533_led_shutdown(struct platform_device *pdev)
@ -746,7 +744,7 @@ static struct platform_driver lm3533_led_driver = {
.name = "lm3533-leds", .name = "lm3533-leds",
}, },
.probe = lm3533_led_probe, .probe = lm3533_led_probe,
.remove = lm3533_led_remove, .remove_new = lm3533_led_remove,
.shutdown = lm3533_led_shutdown, .shutdown = lm3533_led_shutdown,
}; };
module_platform_driver(lm3533_led_driver); module_platform_driver(lm3533_led_driver);

View File

@ -139,7 +139,7 @@ static const struct regmap_config lm3692x_regmap_config = {
.max_register = LM3692X_FAULT_FLAGS, .max_register = LM3692X_FAULT_FLAGS,
.reg_defaults = lm3692x_reg_defs, .reg_defaults = lm3692x_reg_defs,
.num_reg_defaults = ARRAY_SIZE(lm3692x_reg_defs), .num_reg_defaults = ARRAY_SIZE(lm3692x_reg_defs),
.cache_type = REGCACHE_RBTREE, .cache_type = REGCACHE_MAPLE,
}; };
static int lm3692x_fault_check(struct lm3692x_led *led) static int lm3692x_fault_check(struct lm3692x_led *led)

View File

@ -89,7 +89,7 @@ struct lm3697 {
int bank_cfg; int bank_cfg;
int num_banks; int num_banks;
struct lm3697_led leds[]; struct lm3697_led leds[] __counted_by(num_banks);
}; };
static const struct reg_default lm3697_reg_defs[] = { static const struct reg_default lm3697_reg_defs[] = {

View File

@ -101,7 +101,7 @@ static int lp3952_get_label(struct device *dev, const char *label, char *dest)
if (ret) if (ret)
return ret; return ret;
strncpy(dest, str, LP3952_LABEL_MAX_LEN); strscpy(dest, str, LP3952_LABEL_MAX_LEN);
return 0; return 0;
} }
@ -204,7 +204,7 @@ static const struct regmap_config lp3952_regmap = {
.reg_bits = 8, .reg_bits = 8,
.val_bits = 8, .val_bits = 8,
.max_register = REG_MAX, .max_register = REG_MAX,
.cache_type = REGCACHE_RBTREE, .cache_type = REGCACHE_MAPLE,
}; };
static int lp3952_probe(struct i2c_client *client) static int lp3952_probe(struct i2c_client *client)

View File

@ -301,6 +301,8 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip)
/* Set all PWMs to direct control mode */ /* Set all PWMs to direct control mode */
ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
if (ret)
return ret;
/* Update configuration for the clock setting */ /* Update configuration for the clock setting */
val = LP5521_DEFAULT_CFG; val = LP5521_DEFAULT_CFG;

View File

@ -442,9 +442,9 @@ int lp55xx_init_device(struct lp55xx_chip *chip)
gpiod_direction_output(pdata->enable_gpiod, 0); gpiod_direction_output(pdata->enable_gpiod, 0);
gpiod_set_consumer_name(pdata->enable_gpiod, "LP55xx enable"); gpiod_set_consumer_name(pdata->enable_gpiod, "LP55xx enable");
gpiod_set_value(pdata->enable_gpiod, 0); gpiod_set_value_cansleep(pdata->enable_gpiod, 0);
usleep_range(1000, 2000); /* Keep enable down at least 1ms */ usleep_range(1000, 2000); /* Keep enable down at least 1ms */
gpiod_set_value(pdata->enable_gpiod, 1); gpiod_set_value_cansleep(pdata->enable_gpiod, 1);
usleep_range(1000, 2000); /* 500us abs min. */ usleep_range(1000, 2000); /* 500us abs min. */
} }

View File

@ -261,15 +261,13 @@ static int __init mc13xxx_led_probe(struct platform_device *pdev)
return ret; return ret;
} }
static int mc13xxx_led_remove(struct platform_device *pdev) static void mc13xxx_led_remove(struct platform_device *pdev)
{ {
struct mc13xxx_leds *leds = platform_get_drvdata(pdev); struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
int i; int i;
for (i = 0; i < leds->num_leds; i++) for (i = 0; i < leds->num_leds; i++)
led_classdev_unregister(&leds->led[i].cdev); led_classdev_unregister(&leds->led[i].cdev);
return 0;
} }
static const struct mc13xxx_led_devtype mc13783_led_devtype = { static const struct mc13xxx_led_devtype mc13783_led_devtype = {
@ -305,7 +303,7 @@ static struct platform_driver mc13xxx_led_driver = {
.driver = { .driver = {
.name = "mc13xxx-led", .name = "mc13xxx-led",
}, },
.remove = mc13xxx_led_remove, .remove_new = mc13xxx_led_remove,
.id_table = mc13xxx_led_id_table, .id_table = mc13xxx_led_id_table,
}; };
module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);

View File

@ -275,13 +275,11 @@ static int mlxreg_led_probe(struct platform_device *pdev)
return mlxreg_led_config(priv); return mlxreg_led_config(priv);
} }
static int mlxreg_led_remove(struct platform_device *pdev) static void mlxreg_led_remove(struct platform_device *pdev)
{ {
struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev);
mutex_destroy(&priv->access_lock); mutex_destroy(&priv->access_lock);
return 0;
} }
static struct platform_driver mlxreg_led_driver = { static struct platform_driver mlxreg_led_driver = {
@ -289,7 +287,7 @@ static struct platform_driver mlxreg_led_driver = {
.name = "leds-mlxreg", .name = "leds-mlxreg",
}, },
.probe = mlxreg_led_probe, .probe = mlxreg_led_probe,
.remove = mlxreg_led_remove, .remove_new = mlxreg_led_remove,
}; };
module_platform_driver(mlxreg_led_driver); module_platform_driver(mlxreg_led_driver);

View File

@ -632,7 +632,7 @@ put_child_node:
return ret; return ret;
} }
static int mt6323_led_remove(struct platform_device *pdev) static void mt6323_led_remove(struct platform_device *pdev)
{ {
struct mt6323_leds *leds = platform_get_drvdata(pdev); struct mt6323_leds *leds = platform_get_drvdata(pdev);
const struct mt6323_regs *regs = leds->pdata->regs; const struct mt6323_regs *regs = leds->pdata->regs;
@ -647,8 +647,6 @@ static int mt6323_led_remove(struct platform_device *pdev)
RG_DRV_32K_CK_PDN); RG_DRV_32K_CK_PDN);
mutex_destroy(&leds->lock); mutex_destroy(&leds->lock);
return 0;
} }
static const struct mt6323_regs mt6323_registers = { static const struct mt6323_regs mt6323_registers = {
@ -723,7 +721,7 @@ MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);
static struct platform_driver mt6323_led_driver = { static struct platform_driver mt6323_led_driver = {
.probe = mt6323_led_probe, .probe = mt6323_led_probe,
.remove = mt6323_led_remove, .remove_new = mt6323_led_remove,
.driver = { .driver = {
.name = "mt6323-led", .name = "mt6323-led",
.of_match_table = mt6323_led_dt_match, .of_match_table = mt6323_led_dt_match,

View File

@ -167,15 +167,13 @@ static int nic78bx_probe(struct platform_device *pdev)
return ret; return ret;
} }
static int nic78bx_remove(struct platform_device *pdev) static void nic78bx_remove(struct platform_device *pdev)
{ {
struct nic78bx_led_data *led_data = platform_get_drvdata(pdev); struct nic78bx_led_data *led_data = platform_get_drvdata(pdev);
/* Lock LED register */ /* Lock LED register */
outb(NIC78BX_LOCK_VALUE, outb(NIC78BX_LOCK_VALUE,
led_data->io_base + NIC78BX_LOCK_REG_OFFSET); led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
return 0;
} }
static const struct acpi_device_id led_device_ids[] = { static const struct acpi_device_id led_device_ids[] = {
@ -186,7 +184,7 @@ MODULE_DEVICE_TABLE(acpi, led_device_ids);
static struct platform_driver led_driver = { static struct platform_driver led_driver = {
.probe = nic78bx_probe, .probe = nic78bx_probe,
.remove = nic78bx_remove, .remove_new = nic78bx_remove,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
.acpi_match_table = ACPI_PTR(led_device_ids), .acpi_match_table = ACPI_PTR(led_device_ids),

View File

@ -76,7 +76,7 @@ struct pca955x_chipdef {
int slv_addr_shift; /* Number of bits to ignore */ int slv_addr_shift; /* Number of bits to ignore */
}; };
static struct pca955x_chipdef pca955x_chipdefs[] = { static const struct pca955x_chipdef pca955x_chipdefs[] = {
[pca9550] = { [pca9550] = {
.bits = 2, .bits = 2,
.slv_addr = /* 110000x */ 0x60, .slv_addr = /* 110000x */ 0x60,
@ -104,20 +104,10 @@ static struct pca955x_chipdef pca955x_chipdefs[] = {
}, },
}; };
static const struct i2c_device_id pca955x_id[] = {
{ "pca9550", pca9550 },
{ "pca9551", pca9551 },
{ "pca9552", pca9552 },
{ "ibm-pca9552", ibm_pca9552 },
{ "pca9553", pca9553 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pca955x_id);
struct pca955x { struct pca955x {
struct mutex lock; struct mutex lock;
struct pca955x_led *leds; struct pca955x_led *leds;
struct pca955x_chipdef *chipdef; const struct pca955x_chipdef *chipdef;
struct i2c_client *client; struct i2c_client *client;
unsigned long active_pins; unsigned long active_pins;
#ifdef CONFIG_LEDS_PCA955X_GPIO #ifdef CONFIG_LEDS_PCA955X_GPIO
@ -415,7 +405,7 @@ static int pca955x_gpio_direction_output(struct gpio_chip *gc,
#endif /* CONFIG_LEDS_PCA955X_GPIO */ #endif /* CONFIG_LEDS_PCA955X_GPIO */
static struct pca955x_platform_data * static struct pca955x_platform_data *
pca955x_get_pdata(struct i2c_client *client, struct pca955x_chipdef *chip) pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
{ {
struct pca955x_platform_data *pdata; struct pca955x_platform_data *pdata;
struct pca955x_led *led; struct pca955x_led *led;
@ -457,21 +447,11 @@ pca955x_get_pdata(struct i2c_client *client, struct pca955x_chipdef *chip)
return pdata; return pdata;
} }
static const struct of_device_id of_pca955x_match[] = {
{ .compatible = "nxp,pca9550", .data = (void *)pca9550 },
{ .compatible = "nxp,pca9551", .data = (void *)pca9551 },
{ .compatible = "nxp,pca9552", .data = (void *)pca9552 },
{ .compatible = "ibm,pca9552", .data = (void *)ibm_pca9552 },
{ .compatible = "nxp,pca9553", .data = (void *)pca9553 },
{},
};
MODULE_DEVICE_TABLE(of, of_pca955x_match);
static int pca955x_probe(struct i2c_client *client) static int pca955x_probe(struct i2c_client *client)
{ {
struct pca955x *pca955x; struct pca955x *pca955x;
struct pca955x_led *pca955x_led; struct pca955x_led *pca955x_led;
struct pca955x_chipdef *chip; const struct pca955x_chipdef *chip;
struct led_classdev *led; struct led_classdev *led;
struct led_init_data init_data; struct led_init_data init_data;
struct i2c_adapter *adapter; struct i2c_adapter *adapter;
@ -480,24 +460,11 @@ static int pca955x_probe(struct i2c_client *client)
bool set_default_label = false; bool set_default_label = false;
bool keep_pwm = false; bool keep_pwm = false;
char default_label[8]; char default_label[8];
enum pca955x_type chip_type;
const void *md = device_get_match_data(&client->dev);
if (md) { chip = i2c_get_match_data(client);
chip_type = (enum pca955x_type)md; if (!chip)
} else { return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n");
const struct i2c_device_id *id = i2c_match_id(pca955x_id,
client);
if (id) {
chip_type = (enum pca955x_type)id->driver_data;
} else {
dev_err(&client->dev, "unknown chip\n");
return -ENODEV;
}
}
chip = &pca955x_chipdefs[chip_type];
adapter = client->adapter; adapter = client->adapter;
pdata = dev_get_platdata(&client->dev); pdata = dev_get_platdata(&client->dev);
if (!pdata) { if (!pdata) {
@ -663,6 +630,26 @@ static int pca955x_probe(struct i2c_client *client)
return 0; return 0;
} }
static const struct i2c_device_id pca955x_id[] = {
{ "pca9550", (kernel_ulong_t)&pca955x_chipdefs[pca9550] },
{ "pca9551", (kernel_ulong_t)&pca955x_chipdefs[pca9551] },
{ "pca9552", (kernel_ulong_t)&pca955x_chipdefs[pca9552] },
{ "ibm-pca9552", (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] },
{ "pca9553", (kernel_ulong_t)&pca955x_chipdefs[pca9553] },
{}
};
MODULE_DEVICE_TABLE(i2c, pca955x_id);
static const struct of_device_id of_pca955x_match[] = {
{ .compatible = "nxp,pca9550", .data = &pca955x_chipdefs[pca9550] },
{ .compatible = "nxp,pca9551", .data = &pca955x_chipdefs[pca9551] },
{ .compatible = "nxp,pca9552", .data = &pca955x_chipdefs[pca9552] },
{ .compatible = "ibm,pca9552", .data = &pca955x_chipdefs[ibm_pca9552] },
{ .compatible = "nxp,pca9553", .data = &pca955x_chipdefs[pca9553] },
{}
};
MODULE_DEVICE_TABLE(of, of_pca955x_match);
static struct i2c_driver pca955x_driver = { static struct i2c_driver pca955x_driver = {
.driver = { .driver = {
.name = "leds-pca955x", .name = "leds-pca955x",

View File

@ -309,7 +309,7 @@ out:
} }
/* Platform driver remove */ /* Platform driver remove */
static int powernv_led_remove(struct platform_device *pdev) static void powernv_led_remove(struct platform_device *pdev)
{ {
struct powernv_led_common *powernv_led_common; struct powernv_led_common *powernv_led_common;
@ -321,7 +321,6 @@ static int powernv_led_remove(struct platform_device *pdev)
mutex_destroy(&powernv_led_common->lock); mutex_destroy(&powernv_led_common->lock);
dev_info(&pdev->dev, "PowerNV led module unregistered\n"); dev_info(&pdev->dev, "PowerNV led module unregistered\n");
return 0;
} }
/* Platform driver property match */ /* Platform driver property match */
@ -335,7 +334,7 @@ MODULE_DEVICE_TABLE(of, powernv_led_match);
static struct platform_driver powernv_led_driver = { static struct platform_driver powernv_led_driver = {
.probe = powernv_led_probe, .probe = powernv_led_probe,
.remove = powernv_led_remove, .remove_new = powernv_led_remove,
.driver = { .driver = {
.name = "powernv-led-driver", .name = "powernv-led-driver",
.of_match_table = powernv_led_match, .of_match_table = powernv_led_match,

View File

@ -53,7 +53,7 @@ static int led_pwm_set(struct led_classdev *led_cdev,
duty = led_dat->pwmstate.period - duty; duty = led_dat->pwmstate.period - duty;
led_dat->pwmstate.duty_cycle = duty; led_dat->pwmstate.duty_cycle = duty;
led_dat->pwmstate.enabled = duty > 0; led_dat->pwmstate.enabled = true;
return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate); return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate);
} }

View File

@ -42,15 +42,14 @@ static int rb532_led_probe(struct platform_device *pdev)
return led_classdev_register(&pdev->dev, &rb532_uled); return led_classdev_register(&pdev->dev, &rb532_uled);
} }
static int rb532_led_remove(struct platform_device *pdev) static void rb532_led_remove(struct platform_device *pdev)
{ {
led_classdev_unregister(&rb532_uled); led_classdev_unregister(&rb532_uled);
return 0;
} }
static struct platform_driver rb532_led_driver = { static struct platform_driver rb532_led_driver = {
.probe = rb532_led_probe, .probe = rb532_led_probe,
.remove = rb532_led_remove, .remove_new = rb532_led_remove,
.driver = { .driver = {
.name = "rb532-led", .name = "rb532-led",
}, },

View File

@ -173,13 +173,12 @@ static int regulator_led_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int regulator_led_remove(struct platform_device *pdev) static void regulator_led_remove(struct platform_device *pdev)
{ {
struct regulator_led *led = platform_get_drvdata(pdev); struct regulator_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev); led_classdev_unregister(&led->cdev);
regulator_led_disable(led); regulator_led_disable(led);
return 0;
} }
static const struct of_device_id regulator_led_of_match[] = { static const struct of_device_id regulator_led_of_match[] = {
@ -194,7 +193,7 @@ static struct platform_driver regulator_led_driver = {
.of_match_table = regulator_led_of_match, .of_match_table = regulator_led_of_match,
}, },
.probe = regulator_led_probe, .probe = regulator_led_probe,
.remove = regulator_led_remove, .remove_new = regulator_led_remove,
}; };
module_platform_driver(regulator_led_driver); module_platform_driver(regulator_led_driver);

View File

@ -296,7 +296,6 @@ static int sc27xx_led_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
platform_set_drvdata(pdev, priv); platform_set_drvdata(pdev, priv);
mutex_init(&priv->lock);
priv->base = base; priv->base = base;
priv->regmap = dev_get_regmap(dev->parent, NULL); priv->regmap = dev_get_regmap(dev->parent, NULL);
if (!priv->regmap) { if (!priv->regmap) {
@ -309,13 +308,11 @@ static int sc27xx_led_probe(struct platform_device *pdev)
err = of_property_read_u32(child, "reg", &reg); err = of_property_read_u32(child, "reg", &reg);
if (err) { if (err) {
of_node_put(child); of_node_put(child);
mutex_destroy(&priv->lock);
return err; return err;
} }
if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) {
of_node_put(child); of_node_put(child);
mutex_destroy(&priv->lock);
return -EINVAL; return -EINVAL;
} }
@ -323,6 +320,8 @@ static int sc27xx_led_probe(struct platform_device *pdev)
priv->leds[reg].active = true; priv->leds[reg].active = true;
} }
mutex_init(&priv->lock);
err = sc27xx_led_register(dev, priv); err = sc27xx_led_register(dev, priv);
if (err) if (err)
mutex_destroy(&priv->lock); mutex_destroy(&priv->lock);
@ -330,12 +329,11 @@ static int sc27xx_led_probe(struct platform_device *pdev)
return err; return err;
} }
static int sc27xx_led_remove(struct platform_device *pdev) static void sc27xx_led_remove(struct platform_device *pdev)
{ {
struct sc27xx_led_priv *priv = platform_get_drvdata(pdev); struct sc27xx_led_priv *priv = platform_get_drvdata(pdev);
mutex_destroy(&priv->lock); mutex_destroy(&priv->lock);
return 0;
} }
static const struct of_device_id sc27xx_led_of_match[] = { static const struct of_device_id sc27xx_led_of_match[] = {
@ -350,7 +348,7 @@ static struct platform_driver sc27xx_led_driver = {
.of_match_table = sc27xx_led_of_match, .of_match_table = sc27xx_led_of_match,
}, },
.probe = sc27xx_led_probe, .probe = sc27xx_led_probe,
.remove = sc27xx_led_remove, .remove_new = sc27xx_led_remove,
}; };
module_platform_driver(sc27xx_led_driver); module_platform_driver(sc27xx_led_driver);

View File

@ -163,15 +163,13 @@ static int sunfire_led_generic_probe(struct platform_device *pdev,
return 0; return 0;
} }
static int sunfire_led_generic_remove(struct platform_device *pdev) static void sunfire_led_generic_remove(struct platform_device *pdev)
{ {
struct sunfire_drvdata *p = platform_get_drvdata(pdev); struct sunfire_drvdata *p = platform_get_drvdata(pdev);
int i; int i;
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) for (i = 0; i < NUM_LEDS_PER_BOARD; i++)
led_classdev_unregister(&p->leds[i].led_cdev); led_classdev_unregister(&p->leds[i].led_cdev);
return 0;
} }
static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = { static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = {
@ -221,7 +219,7 @@ MODULE_ALIAS("platform:sunfire-fhc-leds");
static struct platform_driver sunfire_clockboard_led_driver = { static struct platform_driver sunfire_clockboard_led_driver = {
.probe = sunfire_clockboard_led_probe, .probe = sunfire_clockboard_led_probe,
.remove = sunfire_led_generic_remove, .remove_new = sunfire_led_generic_remove,
.driver = { .driver = {
.name = "sunfire-clockboard-leds", .name = "sunfire-clockboard-leds",
}, },
@ -229,7 +227,7 @@ static struct platform_driver sunfire_clockboard_led_driver = {
static struct platform_driver sunfire_fhc_led_driver = { static struct platform_driver sunfire_fhc_led_driver = {
.probe = sunfire_fhc_led_probe, .probe = sunfire_fhc_led_probe,
.remove = sunfire_led_generic_remove, .remove_new = sunfire_led_generic_remove,
.driver = { .driver = {
.name = "sunfire-fhc-leds", .name = "sunfire-fhc-leds",
}, },

View File

@ -92,9 +92,6 @@
struct tca6507_platform_data { struct tca6507_platform_data {
struct led_platform_data leds; struct led_platform_data leds;
#ifdef CONFIG_GPIOLIB
int gpio_base;
#endif
}; };
#define TCA6507_MAKE_GPIO 1 #define TCA6507_MAKE_GPIO 1
@ -636,7 +633,7 @@ static int tca6507_probe_gpios(struct device *dev,
tca->gpio.label = "gpio-tca6507"; tca->gpio.label = "gpio-tca6507";
tca->gpio.ngpio = gpios; tca->gpio.ngpio = gpios;
tca->gpio.base = pdata->gpio_base; tca->gpio.base = -1;
tca->gpio.owner = THIS_MODULE; tca->gpio.owner = THIS_MODULE;
tca->gpio.direction_output = tca6507_gpio_direction_output; tca->gpio.direction_output = tca6507_gpio_direction_output;
tca->gpio.set = tca6507_gpio_set_value; tca->gpio.set = tca6507_gpio_set_value;
@ -715,9 +712,6 @@ tca6507_led_dt_init(struct device *dev)
pdata->leds.leds = tca_leds; pdata->leds.leds = tca_leds;
pdata->leds.num_leds = NUM_LEDS; pdata->leds.num_leds = NUM_LEDS;
#ifdef CONFIG_GPIOLIB
pdata->gpio_base = -1;
#endif
return pdata; return pdata;
} }

View File

@ -2,7 +2,7 @@
/* /*
* CZ.NIC's Turris Omnia LEDs driver * CZ.NIC's Turris Omnia LEDs driver
* *
* 2020 by Marek Behún <kabel@kernel.org> * 2020, 2023 by Marek Behún <kabel@kernel.org>
*/ */
#include <linux/i2c.h> #include <linux/i2c.h>
@ -15,21 +15,36 @@
#define OMNIA_BOARD_LEDS 12 #define OMNIA_BOARD_LEDS 12
#define OMNIA_LED_NUM_CHANNELS 3 #define OMNIA_LED_NUM_CHANNELS 3
#define CMD_LED_MODE 3 /* MCU controller commands at I2C address 0x2a */
#define CMD_LED_MODE_LED(l) ((l) & 0x0f) #define OMNIA_MCU_I2C_ADDR 0x2a
#define CMD_LED_MODE_USER 0x10
#define CMD_LED_STATE 4 #define CMD_GET_STATUS_WORD 0x01
#define CMD_LED_STATE_LED(l) ((l) & 0x0f) #define STS_FEATURES_SUPPORTED BIT(2)
#define CMD_LED_STATE_ON 0x10
#define CMD_LED_COLOR 5 #define CMD_GET_FEATURES 0x10
#define CMD_LED_SET_BRIGHTNESS 7 #define FEAT_LED_GAMMA_CORRECTION BIT(5)
#define CMD_LED_GET_BRIGHTNESS 8
/* LED controller commands at I2C address 0x2b */
#define CMD_LED_MODE 0x03
#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
#define CMD_LED_MODE_USER 0x10
#define CMD_LED_STATE 0x04
#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
#define CMD_LED_STATE_ON 0x10
#define CMD_LED_COLOR 0x05
#define CMD_LED_SET_BRIGHTNESS 0x07
#define CMD_LED_GET_BRIGHTNESS 0x08
#define CMD_SET_GAMMA_CORRECTION 0x30
#define CMD_GET_GAMMA_CORRECTION 0x31
struct omnia_led { struct omnia_led {
struct led_classdev_mc mc_cdev; struct led_classdev_mc mc_cdev;
struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
bool on, hwtrig;
int reg; int reg;
}; };
@ -38,41 +53,204 @@ struct omnia_led {
struct omnia_leds { struct omnia_leds {
struct i2c_client *client; struct i2c_client *client;
struct mutex lock; struct mutex lock;
bool has_gamma_correction;
struct omnia_led leds[]; struct omnia_led leds[];
}; };
static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val)
{
u8 buf[2] = { cmd, val };
int ret;
ret = i2c_master_send(client, buf, sizeof(buf));
return ret < 0 ? ret : 0;
}
static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd,
void *reply, size_t len)
{
struct i2c_msg msgs[2];
int ret;
msgs[0].addr = addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &cmd;
msgs[1].addr = addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = reply;
ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
if (likely(ret == ARRAY_SIZE(msgs)))
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
{
u8 reply;
int err;
err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
if (err)
return err;
return reply;
}
static int omnia_led_send_color_cmd(const struct i2c_client *client,
struct omnia_led *led)
{
char cmd[5];
int ret;
cmd[0] = CMD_LED_COLOR;
cmd[1] = led->reg;
cmd[2] = led->subled_info[0].brightness;
cmd[3] = led->subled_info[1].brightness;
cmd[4] = led->subled_info[2].brightness;
/* Send the color change command */
ret = i2c_master_send(client, cmd, 5);
if (ret < 0)
return ret;
/* Cache the RGB channel brightnesses */
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
led->cached_channels[i] = led->subled_info[i].brightness;
return 0;
}
/* Determine if the computed RGB channels are different from the cached ones */
static bool omnia_led_channels_changed(struct omnia_led *led)
{
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
if (led->subled_info[i].brightness != led->cached_channels[i])
return true;
return false;
}
static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
enum led_brightness brightness) enum led_brightness brightness)
{ {
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
struct omnia_led *led = to_omnia_led(mc_cdev); struct omnia_led *led = to_omnia_led(mc_cdev);
u8 buf[5], state; int err = 0;
int ret;
mutex_lock(&leds->lock); mutex_lock(&leds->lock);
led_mc_calc_color_components(&led->mc_cdev, brightness); /*
* Only recalculate RGB brightnesses from intensities if brightness is
* non-zero (if it is zero and the LED is in HW blinking mode, we use
* max_brightness as brightness). Otherwise we won't be using them and
* we can save ourselves some software divisions (Omnia's CPU does not
* implement the division instruction).
*/
if (brightness || led->hwtrig) {
led_mc_calc_color_components(mc_cdev, brightness ?:
cdev->max_brightness);
buf[0] = CMD_LED_COLOR; /*
buf[1] = led->reg; * Send color command only if brightness is non-zero and the RGB
buf[2] = mc_cdev->subled_info[0].brightness; * channel brightnesses changed.
buf[3] = mc_cdev->subled_info[1].brightness; */
buf[4] = mc_cdev->subled_info[2].brightness; if (omnia_led_channels_changed(led))
err = omnia_led_send_color_cmd(leds->client, led);
}
state = CMD_LED_STATE_LED(led->reg); /*
if (buf[2] || buf[3] || buf[4]) * Send on/off state change only if (bool)brightness changed and the LED
state |= CMD_LED_STATE_ON; * is not being blinked by HW.
*/
if (!err && !led->hwtrig && !brightness != !led->on) {
u8 state = CMD_LED_STATE_LED(led->reg);
ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state); if (brightness)
if (ret >= 0 && (state & CMD_LED_STATE_ON)) state |= CMD_LED_STATE_ON;
ret = i2c_master_send(leds->client, buf, 5);
err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
if (!err)
led->on = !!brightness;
}
mutex_unlock(&leds->lock); mutex_unlock(&leds->lock);
return ret; return err;
} }
static struct led_hw_trigger_type omnia_hw_trigger_type;
static int omnia_hwtrig_activate(struct led_classdev *cdev)
{
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
struct omnia_led *led = to_omnia_led(mc_cdev);
int err = 0;
mutex_lock(&leds->lock);
if (!led->on) {
/*
* If the LED is off (brightness was set to 0), the last
* configured color was not necessarily sent to the MCU.
* Recompute with max_brightness and send if needed.
*/
led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
if (omnia_led_channels_changed(led))
err = omnia_led_send_color_cmd(leds->client, led);
}
if (!err) {
/* Put the LED into MCU controlled mode */
err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
CMD_LED_MODE_LED(led->reg));
if (!err)
led->hwtrig = true;
}
mutex_unlock(&leds->lock);
return err;
}
static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
{
struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
int err;
mutex_lock(&leds->lock);
led->hwtrig = false;
/* Put the LED into software mode */
err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
CMD_LED_MODE_LED(led->reg) |
CMD_LED_MODE_USER);
mutex_unlock(&leds->lock);
if (err)
dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
err);
}
static struct led_trigger omnia_hw_trigger = {
.name = "omnia-mcu",
.activate = omnia_hwtrig_activate,
.deactivate = omnia_hwtrig_deactivate,
.trigger_type = &omnia_hw_trigger_type,
};
static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
struct device_node *np) struct device_node *np)
{ {
@ -98,11 +276,15 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
} }
led->subled_info[0].color_index = LED_COLOR_ID_RED; led->subled_info[0].color_index = LED_COLOR_ID_RED;
led->subled_info[0].channel = 0;
led->subled_info[1].color_index = LED_COLOR_ID_GREEN; led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
led->subled_info[1].channel = 1;
led->subled_info[2].color_index = LED_COLOR_ID_BLUE; led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
led->subled_info[2].channel = 2;
/* Initial color is white */
for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
led->subled_info[i].intensity = 255;
led->subled_info[i].brightness = 255;
led->subled_info[i].channel = i;
}
led->mc_cdev.subled_info = led->subled_info; led->mc_cdev.subled_info = led->subled_info;
led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
@ -112,25 +294,39 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
cdev = &led->mc_cdev.led_cdev; cdev = &led->mc_cdev.led_cdev;
cdev->max_brightness = 255; cdev->max_brightness = 255;
cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
cdev->trigger_type = &omnia_hw_trigger_type;
/*
* Use the omnia-mcu trigger as the default trigger. It may be rewritten
* by LED class from the linux,default-trigger property.
*/
cdev->default_trigger = omnia_hw_trigger.name;
/* put the LED into software mode */ /* put the LED into software mode */
ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
CMD_LED_MODE_LED(led->reg) | CMD_LED_MODE_LED(led->reg) |
CMD_LED_MODE_USER); CMD_LED_MODE_USER);
if (ret < 0) { if (ret) {
dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np,
ret); ret);
return ret; return ret;
} }
/* disable the LED */ /* disable the LED */
ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, ret = omnia_cmd_write_u8(client, CMD_LED_STATE,
CMD_LED_STATE_LED(led->reg)); CMD_LED_STATE_LED(led->reg));
if (ret < 0) { if (ret) {
dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
return ret; return ret;
} }
/* Set initial color and cache it */
ret = omnia_led_send_color_cmd(client, led);
if (ret < 0) {
dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np,
ret);
return ret;
}
ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
&init_data); &init_data);
if (ret < 0) { if (ret < 0) {
@ -158,7 +354,7 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a,
struct i2c_client *client = to_i2c_client(dev); struct i2c_client *client = to_i2c_client(dev);
int ret; int ret;
ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -171,7 +367,7 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
{ {
struct i2c_client *client = to_i2c_client(dev); struct i2c_client *client = to_i2c_client(dev);
unsigned long brightness; unsigned long brightness;
int ret; int err;
if (kstrtoul(buf, 10, &brightness)) if (kstrtoul(buf, 10, &brightness))
return -EINVAL; return -EINVAL;
@ -179,19 +375,80 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
if (brightness > 100) if (brightness > 100)
return -EINVAL; return -EINVAL;
ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness);
(u8)brightness);
return ret < 0 ? ret : count; return err ?: count;
} }
static DEVICE_ATTR_RW(brightness); static DEVICE_ATTR_RW(brightness);
static ssize_t gamma_correction_show(struct device *dev,
struct device_attribute *a, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct omnia_leds *leds = i2c_get_clientdata(client);
int ret;
if (leds->has_gamma_correction) {
ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION);
if (ret < 0)
return ret;
} else {
ret = 0;
}
return sysfs_emit(buf, "%d\n", !!ret);
}
static ssize_t gamma_correction_store(struct device *dev,
struct device_attribute *a,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct omnia_leds *leds = i2c_get_clientdata(client);
bool val;
int err;
if (!leds->has_gamma_correction)
return -EOPNOTSUPP;
if (kstrtobool(buf, &val) < 0)
return -EINVAL;
err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
return err ?: count;
}
static DEVICE_ATTR_RW(gamma_correction);
static struct attribute *omnia_led_controller_attrs[] = { static struct attribute *omnia_led_controller_attrs[] = {
&dev_attr_brightness.attr, &dev_attr_brightness.attr,
&dev_attr_gamma_correction.attr,
NULL, NULL,
}; };
ATTRIBUTE_GROUPS(omnia_led_controller); ATTRIBUTE_GROUPS(omnia_led_controller);
static int omnia_mcu_get_features(const struct i2c_client *client)
{
u16 reply;
int err;
err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
CMD_GET_STATUS_WORD, &reply, sizeof(reply));
if (err)
return err;
/* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */
if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED))
return 0;
err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
CMD_GET_FEATURES, &reply, sizeof(reply));
if (err)
return err;
return le16_to_cpu(reply);
}
static int omnia_leds_probe(struct i2c_client *client) static int omnia_leds_probe(struct i2c_client *client)
{ {
struct device *dev = &client->dev; struct device *dev = &client->dev;
@ -216,8 +473,29 @@ static int omnia_leds_probe(struct i2c_client *client)
leds->client = client; leds->client = client;
i2c_set_clientdata(client, leds); i2c_set_clientdata(client, leds);
ret = omnia_mcu_get_features(client);
if (ret < 0) {
dev_err(dev, "Cannot determine MCU supported features: %d\n",
ret);
return ret;
}
leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION;
if (!leds->has_gamma_correction) {
dev_info(dev,
"Your board's MCU firmware does not support the LED gamma correction feature.\n");
dev_info(dev,
"Consider upgrading MCU firmware with the omnia-mcutool utility.\n");
}
mutex_init(&leds->lock); mutex_init(&leds->lock);
ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
if (ret < 0) {
dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
return ret;
}
led = &leds->leds[0]; led = &leds->leds[0];
for_each_available_child_of_node(np, child) { for_each_available_child_of_node(np, child) {
ret = omnia_led_register(client, led, child); ret = omnia_led_register(client, led, child);
@ -237,8 +515,8 @@ static void omnia_leds_remove(struct i2c_client *client)
u8 buf[5]; u8 buf[5];
/* put all LEDs into default (HW triggered) mode */ /* put all LEDs into default (HW triggered) mode */
i2c_smbus_write_byte_data(client, CMD_LED_MODE, omnia_cmd_write_u8(client, CMD_LED_MODE,
CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
/* set all LEDs color to [255, 255, 255] */ /* set all LEDs color to [255, 255, 255] */
buf[0] = CMD_LED_COLOR; buf[0] = CMD_LED_COLOR;

View File

@ -280,13 +280,11 @@ static int wm831x_status_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int wm831x_status_remove(struct platform_device *pdev) static void wm831x_status_remove(struct platform_device *pdev)
{ {
struct wm831x_status *drvdata = platform_get_drvdata(pdev); struct wm831x_status *drvdata = platform_get_drvdata(pdev);
led_classdev_unregister(&drvdata->cdev); led_classdev_unregister(&drvdata->cdev);
return 0;
} }
static struct platform_driver wm831x_status_driver = { static struct platform_driver wm831x_status_driver = {
@ -294,7 +292,7 @@ static struct platform_driver wm831x_status_driver = {
.name = "wm831x-status", .name = "wm831x-status",
}, },
.probe = wm831x_status_probe, .probe = wm831x_status_probe,
.remove = wm831x_status_remove, .remove_new = wm831x_status_remove,
}; };
module_platform_driver(wm831x_status_driver); module_platform_driver(wm831x_status_driver);

View File

@ -242,13 +242,12 @@ static int wm8350_led_probe(struct platform_device *pdev)
return led_classdev_register(&pdev->dev, &led->cdev); return led_classdev_register(&pdev->dev, &led->cdev);
} }
static int wm8350_led_remove(struct platform_device *pdev) static void wm8350_led_remove(struct platform_device *pdev)
{ {
struct wm8350_led *led = platform_get_drvdata(pdev); struct wm8350_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev); led_classdev_unregister(&led->cdev);
wm8350_led_disable(led); wm8350_led_disable(led);
return 0;
} }
static struct platform_driver wm8350_led_driver = { static struct platform_driver wm8350_led_driver = {
@ -256,7 +255,7 @@ static struct platform_driver wm8350_led_driver = {
.name = "wm8350-led", .name = "wm8350-led",
}, },
.probe = wm8350_led_probe, .probe = wm8350_led_probe,
.remove = wm8350_led_remove, .remove_new = wm8350_led_remove,
.shutdown = wm8350_led_shutdown, .shutdown = wm8350_led_shutdown,
}; };

View File

@ -14,6 +14,19 @@ config LEDS_GROUP_MULTICOLOR
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called leds-group-multicolor. will be called leds-group-multicolor.
config LEDS_KTD202X
tristate "LED support for KTD202x Chips"
depends on I2C
depends on OF
select REGMAP_I2C
help
This option enables support for the Kinetic KTD2026/KTD2027
RGB/White LED driver found in different BQ mobile phones.
It is a 3 or 4 channel LED driver programmed via an I2C interface.
To compile this driver as a module, choose M here: the module
will be called leds-ktd202x.
config LEDS_PWM_MULTICOLOR config LEDS_PWM_MULTICOLOR
tristate "PWM driven multi-color LED Support" tristate "PWM driven multi-color LED Support"
depends on PWM depends on PWM

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o

View File

@ -0,0 +1,625 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Kinetic KTD2026/7 RGB/White LED driver with I2C interface
*
* Copyright 2023 André Apitzsch <git@apitzsch.eu>
*
* Datasheet: https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf
*/
#include <linux/i2c.h>
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#define KTD2026_NUM_LEDS 3
#define KTD2027_NUM_LEDS 4
#define KTD202X_MAX_LEDS 4
/* Register bank */
#define KTD202X_REG_RESET_CONTROL 0x00
#define KTD202X_REG_FLASH_PERIOD 0x01
#define KTD202X_REG_PWM1_TIMER 0x02
#define KTD202X_REG_PWM2_TIMER 0x03
#define KTD202X_REG_CHANNEL_CTRL 0x04
#define KTD202X_REG_TRISE_FALL 0x05
#define KTD202X_REG_LED_IOUT(x) (0x06 + (x))
/* Register 0 */
#define KTD202X_TIMER_SLOT_CONTROL_TSLOT1 0x00
#define KTD202X_TIMER_SLOT_CONTROL_TSLOT2 0x01
#define KTD202X_TIMER_SLOT_CONTROL_TSLOT3 0x02
#define KTD202X_TIMER_SLOT_CONTROL_TSLOT4 0x03
#define KTD202X_RSTR_RESET 0x07
#define KTD202X_ENABLE_CTRL_WAKE 0x00 /* SCL High & SDA High */
#define KTD202X_ENABLE_CTRL_SLEEP 0x08 /* SCL High & SDA Toggling */
#define KTD202X_TRISE_FALL_SCALE_NORMAL 0x00
#define KTD202X_TRISE_FALL_SCALE_SLOW_X2 0x20
#define KTD202X_TRISE_FALL_SCALE_SLOW_X4 0x40
#define KTD202X_TRISE_FALL_SCALE_FAST_X8 0x60
/* Register 1 */
#define KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP 0x00
/* Register 2-3 */
#define KTD202X_FLASH_ON_TIME_0_4_PERCENT 0x01
/* Register 4 */
#define KTD202X_CHANNEL_CTRL_MASK(x) (BIT(2 * (x)) | BIT(2 * (x) + 1))
#define KTD202X_CHANNEL_CTRL_OFF 0x00
#define KTD202X_CHANNEL_CTRL_ON(x) BIT(2 * (x))
#define KTD202X_CHANNEL_CTRL_PWM1(x) BIT(2 * (x) + 1)
#define KTD202X_CHANNEL_CTRL_PWM2(x) (BIT(2 * (x)) | BIT(2 * (x) + 1))
/* Register 5 */
#define KTD202X_RAMP_TIMES_2_MS 0x00
/* Register 6-9 */
#define KTD202X_LED_CURRENT_10_mA 0x4f
#define KTD202X_FLASH_PERIOD_MIN_MS 256
#define KTD202X_FLASH_PERIOD_STEP_MS 128
#define KTD202X_FLASH_PERIOD_MAX_STEPS 126
#define KTD202X_FLASH_ON_MAX 256
#define KTD202X_MAX_BRIGHTNESS 192
static const struct reg_default ktd202x_reg_defaults[] = {
{ KTD202X_REG_RESET_CONTROL, KTD202X_TIMER_SLOT_CONTROL_TSLOT1 |
KTD202X_ENABLE_CTRL_WAKE | KTD202X_TRISE_FALL_SCALE_NORMAL },
{ KTD202X_REG_FLASH_PERIOD, KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP },
{ KTD202X_REG_PWM1_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT },
{ KTD202X_REG_PWM2_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT },
{ KTD202X_REG_CHANNEL_CTRL, KTD202X_CHANNEL_CTRL_OFF },
{ KTD202X_REG_TRISE_FALL, KTD202X_RAMP_TIMES_2_MS },
{ KTD202X_REG_LED_IOUT(0), KTD202X_LED_CURRENT_10_mA },
{ KTD202X_REG_LED_IOUT(1), KTD202X_LED_CURRENT_10_mA },
{ KTD202X_REG_LED_IOUT(2), KTD202X_LED_CURRENT_10_mA },
{ KTD202X_REG_LED_IOUT(3), KTD202X_LED_CURRENT_10_mA },
};
struct ktd202x_led {
struct ktd202x *chip;
union {
struct led_classdev cdev;
struct led_classdev_mc mcdev;
};
u32 index;
};
struct ktd202x {
struct mutex mutex;
struct regulator_bulk_data regulators[2];
struct device *dev;
struct regmap *regmap;
bool enabled;
int num_leds;
struct ktd202x_led leds[] __counted_by(num_leds);
};
static int ktd202x_chip_disable(struct ktd202x *chip)
{
int ret;
if (!chip->enabled)
return 0;
regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_SLEEP);
ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
if (ret) {
dev_err(chip->dev, "Failed to disable regulators: %d\n", ret);
return ret;
}
chip->enabled = false;
return 0;
}
static int ktd202x_chip_enable(struct ktd202x *chip)
{
int ret;
if (chip->enabled)
return 0;
ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators);
if (ret) {
dev_err(chip->dev, "Failed to enable regulators: %d\n", ret);
return ret;
}
chip->enabled = true;
ret = regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_WAKE);
if (ret) {
dev_err(chip->dev, "Failed to enable the chip: %d\n", ret);
ktd202x_chip_disable(chip);
}
return ret;
}
static bool ktd202x_chip_in_use(struct ktd202x *chip)
{
int i;
for (i = 0; i < chip->num_leds; i++) {
if (chip->leds[i].cdev.brightness)
return true;
}
return false;
}
static int ktd202x_brightness_set(struct ktd202x_led *led,
struct mc_subled *subleds,
unsigned int num_channels)
{
bool mode_blink = false;
int channel;
int state;
int ret;
int i;
if (ktd202x_chip_in_use(led->chip)) {
ret = ktd202x_chip_enable(led->chip);
if (ret)
return ret;
}
ret = regmap_read(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, &state);
if (ret)
return ret;
/*
* In multicolor case, assume blink mode if PWM is set for at least one
* channel because another channel cannot be in state ON at the same time
*/
for (i = 0; i < num_channels; i++) {
int channel_state;
channel = subleds[i].channel;
channel_state = (state >> 2 * channel) & KTD202X_CHANNEL_CTRL_MASK(0);
if (channel_state == KTD202X_CHANNEL_CTRL_OFF)
continue;
mode_blink = channel_state == KTD202X_CHANNEL_CTRL_PWM1(0);
break;
}
for (i = 0; i < num_channels; i++) {
enum led_brightness brightness;
int mode;
brightness = subleds[i].brightness;
channel = subleds[i].channel;
if (brightness) {
/* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */
ret = regmap_write(led->chip->regmap, KTD202X_REG_LED_IOUT(channel),
brightness - 1);
if (ret)
return ret;
if (mode_blink)
mode = KTD202X_CHANNEL_CTRL_PWM1(channel);
else
mode = KTD202X_CHANNEL_CTRL_ON(channel);
} else {
mode = KTD202X_CHANNEL_CTRL_OFF;
}
ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
KTD202X_CHANNEL_CTRL_MASK(channel), mode);
if (ret)
return ret;
}
if (!ktd202x_chip_in_use(led->chip))
return ktd202x_chip_disable(led->chip);
return 0;
}
static int ktd202x_brightness_single_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev);
struct mc_subled info;
int ret;
cdev->brightness = value;
mutex_lock(&led->chip->mutex);
info.brightness = value;
info.channel = led->index;
ret = ktd202x_brightness_set(led, &info, 1);
mutex_unlock(&led->chip->mutex);
return ret;
}
static int ktd202x_brightness_mc_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev);
int ret;
cdev->brightness = value;
mutex_lock(&led->chip->mutex);
led_mc_calc_color_components(mc, value);
ret = ktd202x_brightness_set(led, mc->subled_info, mc->num_colors);
mutex_unlock(&led->chip->mutex);
return ret;
}
static int ktd202x_blink_set(struct ktd202x_led *led, unsigned long *delay_on,
unsigned long *delay_off, struct mc_subled *subleds,
unsigned int num_channels)
{
unsigned long delay_total_ms;
int ret, num_steps, on;
u8 ctrl_mask = 0;
u8 ctrl_pwm1 = 0;
u8 ctrl_on = 0;
int i;
mutex_lock(&led->chip->mutex);
for (i = 0; i < num_channels; i++) {
int channel = subleds[i].channel;
ctrl_mask |= KTD202X_CHANNEL_CTRL_MASK(channel);
ctrl_on |= KTD202X_CHANNEL_CTRL_ON(channel);
ctrl_pwm1 |= KTD202X_CHANNEL_CTRL_PWM1(channel);
}
/* Never off - brightness is already set, disable blinking */
if (!*delay_off) {
ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
ctrl_mask, ctrl_on);
goto out;
}
/* Convert into values the HW will understand. */
/* Integer representation of time of flash period */
num_steps = (*delay_on + *delay_off - KTD202X_FLASH_PERIOD_MIN_MS) /
KTD202X_FLASH_PERIOD_STEP_MS;
num_steps = clamp(num_steps, 0, KTD202X_FLASH_PERIOD_MAX_STEPS);
/* Integer representation of percentage of LED ON time */
on = (*delay_on * KTD202X_FLASH_ON_MAX) / (*delay_on + *delay_off);
/* Actually used delay_{on,off} values */
delay_total_ms = num_steps * KTD202X_FLASH_PERIOD_STEP_MS + KTD202X_FLASH_PERIOD_MIN_MS;
*delay_on = (delay_total_ms * on) / KTD202X_FLASH_ON_MAX;
*delay_off = delay_total_ms - *delay_on;
/* Set timings */
ret = regmap_write(led->chip->regmap, KTD202X_REG_FLASH_PERIOD, num_steps);
if (ret)
goto out;
ret = regmap_write(led->chip->regmap, KTD202X_REG_PWM1_TIMER, on);
if (ret)
goto out;
ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
ctrl_mask, ctrl_pwm1);
out:
mutex_unlock(&led->chip->mutex);
return ret;
}
static int ktd202x_blink_single_set(struct led_classdev *cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev);
struct mc_subled info;
int ret;
if (!cdev->brightness) {
ret = ktd202x_brightness_single_set(cdev, KTD202X_MAX_BRIGHTNESS);
if (ret)
return ret;
}
/* If no blink specified, default to 1 Hz. */
if (!*delay_off && !*delay_on) {
*delay_off = 500;
*delay_on = 500;
}
/* Never on - just set to off */
if (!*delay_on)
return ktd202x_brightness_single_set(cdev, LED_OFF);
info.channel = led->index;
return ktd202x_blink_set(led, delay_on, delay_off, &info, 1);
}
static int ktd202x_blink_mc_set(struct led_classdev *cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev);
int ret;
if (!cdev->brightness) {
ret = ktd202x_brightness_mc_set(cdev, KTD202X_MAX_BRIGHTNESS);
if (ret)
return ret;
}
/* If no blink specified, default to 1 Hz. */
if (!*delay_off && !*delay_on) {
*delay_off = 500;
*delay_on = 500;
}
/* Never on - just set to off */
if (!*delay_on)
return ktd202x_brightness_mc_set(cdev, LED_OFF);
return ktd202x_blink_set(led, delay_on, delay_off, mc->subled_info,
mc->num_colors);
}
static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
struct ktd202x_led *led, struct led_init_data *init_data)
{
struct led_classdev *cdev;
struct device_node *child;
struct mc_subled *info;
int num_channels;
int i = 0;
num_channels = of_get_available_child_count(np);
if (!num_channels || num_channels > chip->num_leds)
return -EINVAL;
info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
for_each_available_child_of_node(np, child) {
u32 mono_color;
u32 reg;
int ret;
ret = of_property_read_u32(child, "reg", &reg);
if (ret != 0 || reg >= chip->num_leds) {
dev_err(chip->dev, "invalid 'reg' of %pOFn\n", child);
of_node_put(child);
return -EINVAL;
}
ret = of_property_read_u32(child, "color", &mono_color);
if (ret < 0 && ret != -EINVAL) {
dev_err(chip->dev, "failed to parse 'color' of %pOF\n", child);
of_node_put(child);
return ret;
}
info[i].color_index = mono_color;
info[i].channel = reg;
info[i].intensity = KTD202X_MAX_BRIGHTNESS;
i++;
}
led->mcdev.subled_info = info;
led->mcdev.num_colors = num_channels;
cdev = &led->mcdev.led_cdev;
cdev->brightness_set_blocking = ktd202x_brightness_mc_set;
cdev->blink_set = ktd202x_blink_mc_set;
return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data);
}
static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np,
struct ktd202x_led *led, struct led_init_data *init_data)
{
struct led_classdev *cdev;
u32 reg;
int ret;
ret = of_property_read_u32(np, "reg", &reg);
if (ret != 0 || reg >= chip->num_leds) {
dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np);
return -EINVAL;
}
led->index = reg;
cdev = &led->cdev;
cdev->brightness_set_blocking = ktd202x_brightness_single_set;
cdev->blink_set = ktd202x_blink_single_set;
return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data);
}
static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigned int index)
{
struct ktd202x_led *led = &chip->leds[index];
struct led_init_data init_data = {};
struct led_classdev *cdev;
u32 color;
int ret;
/* Color property is optional in single color case */
ret = of_property_read_u32(np, "color", &color);
if (ret < 0 && ret != -EINVAL) {
dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np);
return ret;
}
led->chip = chip;
init_data.fwnode = of_fwnode_handle(np);
if (color == LED_COLOR_ID_RGB) {
cdev = &led->mcdev.led_cdev;
ret = ktd202x_setup_led_rgb(chip, np, led, &init_data);
} else {
cdev = &led->cdev;
ret = ktd202x_setup_led_single(chip, np, led, &init_data);
}
if (ret) {
dev_err(chip->dev, "unable to register %s\n", cdev->name);
return ret;
}
cdev->max_brightness = KTD202X_MAX_BRIGHTNESS;
return 0;
}
static int ktd202x_probe_dt(struct ktd202x *chip)
{
struct device_node *np = dev_of_node(chip->dev), *child;
int count;
int i = 0;
chip->num_leds = (int)(unsigned long)of_device_get_match_data(chip->dev);
count = of_get_available_child_count(np);
if (!count || count > chip->num_leds)
return -EINVAL;
regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
/* Allow the device to execute the complete reset */
usleep_range(200, 300);
for_each_available_child_of_node(np, child) {
int ret = ktd202x_add_led(chip, child, i);
if (ret) {
of_node_put(child);
return ret;
}
i++;
}
return 0;
}
static const struct regmap_config ktd202x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x09,
.cache_type = REGCACHE_FLAT,
.reg_defaults = ktd202x_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(ktd202x_reg_defaults),
};
static int ktd202x_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ktd202x *chip;
int count;
int ret;
count = device_get_child_node_count(dev);
if (!count || count > KTD202X_MAX_LEDS)
return dev_err_probe(dev, -EINVAL, "Incorrect number of leds (%d)", count);
chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = dev;
i2c_set_clientdata(client, chip);
chip->regmap = devm_regmap_init_i2c(client, &ktd202x_regmap_config);
if (IS_ERR(chip->regmap)) {
ret = dev_err_probe(dev, PTR_ERR(chip->regmap),
"Failed to allocate register map.\n");
return ret;
}
chip->regulators[0].supply = "vin";
chip->regulators[1].supply = "vio";
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators);
if (ret < 0) {
dev_err_probe(dev, ret, "Failed to request regulators.\n");
return ret;
}
ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators);
if (ret) {
dev_err_probe(dev, ret, "Failed to enable regulators.\n");
return ret;
}
ret = ktd202x_probe_dt(chip);
if (ret < 0) {
regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
return ret;
}
ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
if (ret) {
dev_err_probe(dev, ret, "Failed to disable regulators.\n");
return ret;
}
mutex_init(&chip->mutex);
return 0;
}
static void ktd202x_remove(struct i2c_client *client)
{
struct ktd202x *chip = i2c_get_clientdata(client);
ktd202x_chip_disable(chip);
mutex_destroy(&chip->mutex);
}
static void ktd202x_shutdown(struct i2c_client *client)
{
struct ktd202x *chip = i2c_get_clientdata(client);
/* Reset registers to make sure all LEDs are off before shutdown */
regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
}
static const struct of_device_id ktd202x_match_table[] = {
{ .compatible = "kinetic,ktd2026", .data = (void *)KTD2026_NUM_LEDS },
{ .compatible = "kinetic,ktd2027", .data = (void *)KTD2027_NUM_LEDS },
{},
};
MODULE_DEVICE_TABLE(of, ktd202x_match_table);
static struct i2c_driver ktd202x_driver = {
.driver = {
.name = "leds-ktd202x",
.of_match_table = ktd202x_match_table,
},
.probe = ktd202x_probe,
.remove = ktd202x_remove,
.shutdown = ktd202x_shutdown,
};
module_i2c_driver(ktd202x_driver);
MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>");
MODULE_DESCRIPTION("Kinetic KTD2026/7 LED driver");
MODULE_LICENSE("GPL");

View File

@ -153,7 +153,7 @@ struct mt6370_priv {
const struct mt6370_pdata *pdata; const struct mt6370_pdata *pdata;
unsigned int leds_count; unsigned int leds_count;
unsigned int leds_active; unsigned int leds_active;
struct mt6370_led leds[]; struct mt6370_led leds[] __counted_by(leds_count);
}; };
static const struct reg_field common_reg_fields[F_MAX_FIELDS] = { static const struct reg_field common_reg_fields[F_MAX_FIELDS] = {

View File

@ -173,7 +173,7 @@ struct lpg_led {
struct led_classdev_mc mcdev; struct led_classdev_mc mcdev;
unsigned int num_channels; unsigned int num_channels;
struct lpg_channel *channels[]; struct lpg_channel *channels[] __counted_by(num_channels);
}; };
/** /**
@ -1364,13 +1364,11 @@ static int lpg_probe(struct platform_device *pdev)
return lpg_add_pwm(lpg); return lpg_add_pwm(lpg);
} }
static int lpg_remove(struct platform_device *pdev) static void lpg_remove(struct platform_device *pdev)
{ {
struct lpg *lpg = platform_get_drvdata(pdev); struct lpg *lpg = platform_get_drvdata(pdev);
pwmchip_remove(&lpg->pwm); pwmchip_remove(&lpg->pwm);
return 0;
} }
static const struct lpg_data pm8916_pwm_data = { static const struct lpg_data pm8916_pwm_data = {
@ -1532,7 +1530,7 @@ MODULE_DEVICE_TABLE(of, lpg_of_table);
static struct platform_driver lpg_driver = { static struct platform_driver lpg_driver = {
.probe = lpg_probe, .probe = lpg_probe,
.remove = lpg_remove, .remove_new = lpg_remove,
.driver = { .driver = {
.name = "qcom-spmi-lpg", .name = "qcom-spmi-lpg",
.of_match_table = lpg_of_table, .of_match_table = lpg_of_table,

View File

@ -45,15 +45,15 @@ static int simatic_ipc_leds_gpio_apollolake_probe(struct platform_device *pdev)
&simatic_ipc_led_gpio_table_extra); &simatic_ipc_led_gpio_table_extra);
} }
static int simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev) static void simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev)
{ {
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra); &simatic_ipc_led_gpio_table_extra);
} }
static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = { static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = {
.probe = simatic_ipc_leds_gpio_apollolake_probe, .probe = simatic_ipc_leds_gpio_apollolake_probe,
.remove = simatic_ipc_leds_gpio_apollolake_remove, .remove_new = simatic_ipc_leds_gpio_apollolake_remove,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
}, },

View File

@ -33,15 +33,13 @@ static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = {
.leds = simatic_ipc_gpio_leds, .leds = simatic_ipc_gpio_leds,
}; };
int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, void simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
struct gpiod_lookup_table *table, struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra) struct gpiod_lookup_table *table_extra)
{ {
gpiod_remove_lookup_table(table); gpiod_remove_lookup_table(table);
gpiod_remove_lookup_table(table_extra); gpiod_remove_lookup_table(table_extra);
platform_device_unregister(simatic_leds_pdev); platform_device_unregister(simatic_leds_pdev);
return 0;
} }
EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove); EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove);

View File

@ -36,15 +36,14 @@ static int simatic_ipc_leds_gpio_elkhartlake_probe(struct platform_device *pdev)
NULL); NULL);
} }
static int simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev) static void simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev)
{ {
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, NULL);
NULL);
} }
static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = { static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
.probe = simatic_ipc_leds_gpio_elkhartlake_probe, .probe = simatic_ipc_leds_gpio_elkhartlake_probe,
.remove = simatic_ipc_leds_gpio_elkhartlake_remove, .remove_new = simatic_ipc_leds_gpio_elkhartlake_remove,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
}, },

View File

@ -45,15 +45,15 @@ static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev)
&simatic_ipc_led_gpio_table_extra); &simatic_ipc_led_gpio_table_extra);
} }
static int simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev) static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev)
{ {
return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
&simatic_ipc_led_gpio_table_extra); &simatic_ipc_led_gpio_table_extra);
} }
static struct platform_driver simatic_ipc_led_gpio_driver = { static struct platform_driver simatic_ipc_led_gpio_driver = {
.probe = simatic_ipc_leds_gpio_f7188x_probe, .probe = simatic_ipc_leds_gpio_f7188x_probe,
.remove = simatic_ipc_leds_gpio_f7188x_remove, .remove_new = simatic_ipc_leds_gpio_f7188x_remove,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
}, },

View File

@ -15,8 +15,8 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
struct gpiod_lookup_table *table, struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra); struct gpiod_lookup_table *table_extra);
int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, void simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
struct gpiod_lookup_table *table, struct gpiod_lookup_table *table,
struct gpiod_lookup_table *table_extra); struct gpiod_lookup_table *table_extra);
#endif /* _SIMATIC_IPC_LEDS_GPIO_H */ #endif /* _SIMATIC_IPC_LEDS_GPIO_H */

View File

@ -83,13 +83,10 @@ config LEDS_TRIGGER_ACTIVITY
config LEDS_TRIGGER_GPIO config LEDS_TRIGGER_GPIO
tristate "LED GPIO Trigger" tristate "LED GPIO Trigger"
depends on GPIOLIB || COMPILE_TEST depends on GPIOLIB || COMPILE_TEST
depends on BROKEN
help help
This allows LEDs to be controlled by gpio events. It's good This allows LEDs to be controlled by gpio events. It's good
when using gpios as switches and triggering the needed LEDs when using gpios as switches and triggering the needed LEDs
from there. One use case is n810's keypad LEDs that could from there. Triggers are defined as device properties.
be triggered by this trigger when user slides up to show
keypad.
If unsure, say N. If unsure, say N.

View File

@ -130,7 +130,7 @@ static int ledtrig_prepare_down_cpu(unsigned int cpu)
static int __init ledtrig_cpu_init(void) static int __init ledtrig_cpu_init(void)
{ {
int cpu; unsigned int cpu;
int ret; int ret;
/* Supports up to 9999 cpu cores */ /* Supports up to 9999 cpu cores */
@ -152,7 +152,7 @@ static int __init ledtrig_cpu_init(void)
if (cpu >= 8) if (cpu >= 8)
continue; continue;
snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu);
led_trigger_register_simple(trig->name, &trig->_trig); led_trigger_register_simple(trig->name, &trig->_trig);
} }

View File

@ -3,12 +3,13 @@
* ledtrig-gio.c - LED Trigger Based on GPIO events * ledtrig-gio.c - LED Trigger Based on GPIO events
* *
* Copyright 2009 Felipe Balbi <me@felipebalbi.com> * Copyright 2009 Felipe Balbi <me@felipebalbi.com>
* Copyright 2023 Linus Walleij <linus.walleij@linaro.org>
*/ */
#include <linux/module.h> #include <linux/module.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/gpio.h> #include <linux/gpio/consumer.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/slab.h> #include <linux/slab.h>
@ -16,10 +17,8 @@
struct gpio_trig_data { struct gpio_trig_data {
struct led_classdev *led; struct led_classdev *led;
unsigned desired_brightness; /* desired brightness when led is on */ unsigned desired_brightness; /* desired brightness when led is on */
unsigned inverted; /* true when gpio is inverted */ struct gpio_desc *gpiod; /* gpio that triggers the led */
unsigned gpio; /* gpio that triggers the leds */
}; };
static irqreturn_t gpio_trig_irq(int irq, void *_led) static irqreturn_t gpio_trig_irq(int irq, void *_led)
@ -28,10 +27,7 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led)
struct gpio_trig_data *gpio_data = led_get_trigger_data(led); struct gpio_trig_data *gpio_data = led_get_trigger_data(led);
int tmp; int tmp;
tmp = gpio_get_value_cansleep(gpio_data->gpio); tmp = gpiod_get_value_cansleep(gpio_data->gpiod);
if (gpio_data->inverted)
tmp = !tmp;
if (tmp) { if (tmp) {
if (gpio_data->desired_brightness) if (gpio_data->desired_brightness)
led_set_brightness_nosleep(gpio_data->led, led_set_brightness_nosleep(gpio_data->led,
@ -73,93 +69,8 @@ static ssize_t gpio_trig_brightness_store(struct device *dev,
static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
gpio_trig_brightness_store); gpio_trig_brightness_store);
static ssize_t gpio_trig_inverted_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", gpio_data->inverted);
}
static ssize_t gpio_trig_inverted_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
struct led_classdev *led = led_trigger_get_led(dev);
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
unsigned long inverted;
int ret;
ret = kstrtoul(buf, 10, &inverted);
if (ret < 0)
return ret;
if (inverted > 1)
return -EINVAL;
gpio_data->inverted = inverted;
/* After inverting, we need to update the LED. */
if (gpio_is_valid(gpio_data->gpio))
gpio_trig_irq(0, led);
return n;
}
static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show,
gpio_trig_inverted_store);
static ssize_t gpio_trig_gpio_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", gpio_data->gpio);
}
static ssize_t gpio_trig_gpio_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
struct led_classdev *led = led_trigger_get_led(dev);
struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
unsigned gpio;
int ret;
ret = sscanf(buf, "%u", &gpio);
if (ret < 1) {
dev_err(dev, "couldn't read gpio number\n");
return -EINVAL;
}
if (gpio_data->gpio == gpio)
return n;
if (!gpio_is_valid(gpio)) {
if (gpio_is_valid(gpio_data->gpio))
free_irq(gpio_to_irq(gpio_data->gpio), led);
gpio_data->gpio = gpio;
return n;
}
ret = request_threaded_irq(gpio_to_irq(gpio), NULL, gpio_trig_irq,
IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
if (ret) {
dev_err(dev, "request_irq failed with error %d\n", ret);
} else {
if (gpio_is_valid(gpio_data->gpio))
free_irq(gpio_to_irq(gpio_data->gpio), led);
gpio_data->gpio = gpio;
/* After changing the GPIO, we need to update the LED. */
gpio_trig_irq(0, led);
}
return ret ? ret : n;
}
static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store);
static struct attribute *gpio_trig_attrs[] = { static struct attribute *gpio_trig_attrs[] = {
&dev_attr_desired_brightness.attr, &dev_attr_desired_brightness.attr,
&dev_attr_inverted.attr,
&dev_attr_gpio.attr,
NULL NULL
}; };
ATTRIBUTE_GROUPS(gpio_trig); ATTRIBUTE_GROUPS(gpio_trig);
@ -167,16 +78,48 @@ ATTRIBUTE_GROUPS(gpio_trig);
static int gpio_trig_activate(struct led_classdev *led) static int gpio_trig_activate(struct led_classdev *led)
{ {
struct gpio_trig_data *gpio_data; struct gpio_trig_data *gpio_data;
struct device *dev = led->dev;
int ret;
gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL);
if (!gpio_data) if (!gpio_data)
return -ENOMEM; return -ENOMEM;
gpio_data->led = led; /*
gpio_data->gpio = -ENOENT; * The generic property "trigger-sources" is followed,
* and we hope that this is a GPIO.
*/
gpio_data->gpiod = fwnode_gpiod_get_index(dev->fwnode,
"trigger-sources",
0, GPIOD_IN,
"led-trigger");
if (IS_ERR(gpio_data->gpiod)) {
ret = PTR_ERR(gpio_data->gpiod);
kfree(gpio_data);
return ret;
}
if (!gpio_data->gpiod) {
dev_err(dev, "no valid GPIO for the trigger\n");
kfree(gpio_data);
return -EINVAL;
}
gpio_data->led = led;
led_set_trigger_data(led, gpio_data); led_set_trigger_data(led, gpio_data);
ret = request_threaded_irq(gpiod_to_irq(gpio_data->gpiod), NULL, gpio_trig_irq,
IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
if (ret) {
dev_err(dev, "request_irq failed with error %d\n", ret);
gpiod_put(gpio_data->gpiod);
kfree(gpio_data);
return ret;
}
/* Finally update the LED to initial status */
gpio_trig_irq(0, led);
return 0; return 0;
} }
@ -184,8 +127,8 @@ static void gpio_trig_deactivate(struct led_classdev *led)
{ {
struct gpio_trig_data *gpio_data = led_get_trigger_data(led); struct gpio_trig_data *gpio_data = led_get_trigger_data(led);
if (gpio_is_valid(gpio_data->gpio)) free_irq(gpiod_to_irq(gpio_data->gpiod), led);
free_irq(gpio_to_irq(gpio_data->gpio), led); gpiod_put(gpio_data->gpiod);
kfree(gpio_data); kfree(gpio_data);
} }

View File

@ -221,6 +221,9 @@ static ssize_t device_name_show(struct device *dev,
static int set_device_name(struct led_netdev_data *trigger_data, static int set_device_name(struct led_netdev_data *trigger_data,
const char *name, size_t size) const char *name, size_t size)
{ {
if (size >= IFNAMSIZ)
return -EINVAL;
cancel_delayed_work_sync(&trigger_data->work); cancel_delayed_work_sync(&trigger_data->work);
mutex_lock(&trigger_data->lock); mutex_lock(&trigger_data->lock);
@ -263,9 +266,6 @@ static ssize_t device_name_store(struct device *dev,
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
int ret; int ret;
if (size >= IFNAMSIZ)
return -EINVAL;
ret = set_device_name(trigger_data, buf, size); ret = set_device_name(trigger_data, buf, size);
if (ret < 0) if (ret < 0)