- Core Frameworks

- Add new Trigger for Input Events
    - Add new led_mc_set_brightness() call to adapt colour/brightness for mutli-colour LEDs
    - Add new lled_mc_trigger_event() call to call the above based on given trigger conditions
    - Add new led_get_color_name() call, a wrapper around the existing led_colors[] array
    - Add a new flag to avoid automatic renaming of LED devices
 
  - New Drivers
    - Add support for Silergy SY7802 Flash LED Controller
    - Add support for Texas Instruments LP5569 LED Controller
    - Add support for ChromeOS EC LED Controller
 
  - New Device Support
    - Add support for KTD202{6,7} to Kinetic KTD2026/7 LEDs
 
  - Fix-ups
    - Replace ACPI/DT firmware helpers with agnostic variants
    - Make use of resource managed devm_* API calls
    - Device Tree binding adaptions/conversions/creation
    - Constify/staticise applicable data structures
    - Trivial; spelling, whitespace, coding-style adaptions
    - Drop i2c_device_id::driver_data where the value is unused
    - Utilise centrally provided helpers and macros to aid simplicity/duplication
    - Use generic platform device properties instead of OF/ACPI specific ones
    - Consolidate/de-duplicate various functionality
    - Remove superfluous/duplicated/unused sections
    - Make use of the new *_scoped() guard APIs
    - Improve/simplify error handling
 
  - Bug Fixes
    - Flush pending brightness changes before activating the trigger
    - Repair incorrect device naming preventing matches
    - Prevent memory leaks by correctly free resources during error handling routines
    - Repair locking issue causing circular dependency splats and lock-ups
    - Unregister sysfs entries before deactivating triggers to prevent use-after issues
    - Supply a bunch of MODULE_DESCRIPTIONs to silence modpost warnings
    - Use correct return codes expected by the callers
    - Omit set_brightness() error message for a LEDs that support only HW triggers
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmaWgMEACgkQUa+KL4f8
 d2Glng//Sw3+ev7IFn4PmNfMHtKeWlJEerT8kyF0GdK05zn7ImlydkSVW5RvHN8B
 J2A8cfXXkhggYoG2i4zjs1wtfPDXP9iJMhVaRnPPuWgYJpPrhUFSSkJKiauSKyGN
 Tp3Vn6QaHM5Gs/THUidcTV0+USE6j9+DXdLjLASAi2YXqoGCC3KWcq4Hb6NdGM/Q
 ajEuGnrO1HEF0dOaEgkYuNIXcADHzeeQlU0Y96bGv7qnUBw7LAIpH+AC1d49jcXS
 gIJNxmtmn0QqCK0/lS74DmR9r2quofeYer6G5WDJVv72R8KIljFN16AGFLdC8ID/
 3B4JnjQKpEyn344gmvqzx23dp3jqvCupgDcojR6RY7yIZTXSjg0R0OocKYQlDnNY
 LoNZqZ9J2nNDWNlx7r7PXuIAm7Bb9kdY0/PWgXOwWsEb19DcxyAHE3+xGeyQF6f5
 /s1bGZxQyfBnDZm8+XiJ6KTsiZ1t0MljG9y7xil6vtzJIJTynao9qzZipcrx7UuT
 3UyoGa/Z4o95OkQye9n6tKGJJDKLJIZL7x4pGOW3HeHgfvJ1koyHr8WwhmK4Zoh/
 xeHfHABuaMoulTsuGjtnRP4/UWOTEf2hh7FK1mSrpxSCvv+I9W4lTO9QBkXU8xuD
 1oCekcTYWOwKhxaFX4jjaGEHXIWYzXDjE7hqroLzj8Ifhqe9xrc=
 =0Kyu
 -----END PGP SIGNATURE-----

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

Pull LED updates from Lee Jones:
 "Core Frameworks:
   - New trigger for Input Events
   - New led_mc_set_brightness() call to adapt colour/brightness for
     mutli-colour LEDs
   - New lled_mc_trigger_event() call to call the above based on given
     trigger conditions
   - New led_get_color_name() call, a wrapper around the existing
     led_colors[] array
   - A new flag to avoid automatic renaming of LED devices

  New Drivers:
   - Silergy SY7802 Flash LED Controller
   - Texas Instruments LP5569 LED Controller
   - ChromeOS EC LED Controller

  New Device Support:
   - KTD202{6,7} support for Kinetic KTD2026/7 LEDs

  Fix-ups:
   - Replace ACPI/DT firmware helpers with agnostic variants
   - Make use of resource managed devm_* API calls
   - Device Tree binding adaptions/conversions/creation
   - Constify/staticise applicable data structures
   - Trivial; spelling, whitespace, coding-style adaptions
   - Drop i2c_device_id::driver_data where the value is unused
   - Utilise centrally provided helpers and macros to aid simplicity and
     avoid duplication
   - Use generic platform device properties instead of OF/ACPI specific
     ones
   - Consolidate/de-duplicate various functionality
   - Remove superfluous/duplicated/unused sections
   - Make use of the new *_scoped() guard APIs
   - Improve/simplify error handling

  Bug Fixes:
   - Flush pending brightness changes before activating the trigger
   - Repair incorrect device naming preventing matches
   - Prevent memory leaks by correctly free resources during error
     handling routines
   - Repair locking issue causing circular dependency splats and
     lock-ups
   - Unregister sysfs entries before deactivating triggers to prevent
     use-after issues
   - Supply a bunch of MODULE_DESCRIPTIONs to silence modpost warnings
   - Use correct return codes expected by the callers
   - Omit set_brightness() error message for a LEDs that support only HW
     triggers"

* tag 'leds-next-6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: (65 commits)
  leds: leds-lp5569: Enable chip after chip configuration
  leds: leds-lp5569: Better handle enabling clock internal setting
  leds: leds-lp5569: Fix typo in driver name
  leds: flash: leds-qcom-flash: Test the correct variable in init
  leds: leds-lp55xx: Convert mutex lock/unlock to guard API
  leds: leds-lp5523: Convert to sysfs_emit API
  leds: leds-lp5569: Convert to sysfs_emit API
  Revert "leds: led-core: Fix refcount leak in of_led_get()"
  leds: leds-lp5569: Add support for Texas Instruments LP5569
  leds: leds-lp55xx: Drop deprecated defines
  leds: leds-lp55xx: Support ENGINE program up to 128 bytes
  leds: leds-lp55xx: Generalize sysfs master_fader
  leds: leds-lp55xx: Generalize sysfs engine_leds
  leds: leds-lp55xx: Generalize sysfs engine_load and engine_mode
  leds: leds-lp55xx: Generalize stop_engine function
  leds: leds-lp55xx: Generalize turn_off_channels function
  leds: leds-lp55xx: Generalize set_led_current function
  leds: leds-lp55xx: Generalize multicolor_brightness function
  leds: leds-lp55xx: Generalize led_brightness function
  leds: leds-lp55xx: Generalize firmware_loaded function
  ...
This commit is contained in:
Linus Torvalds 2024-07-17 17:51:30 -07:00
commit fea17683c4
58 changed files with 2728 additions and 1864 deletions

View File

@ -28,6 +28,7 @@ properties:
- national,lp5523
- ti,lp55231
- ti,lp5562
- ti,lp5569
- ti,lp8501
reg:
@ -151,6 +152,16 @@ patternProperties:
$ref: /schemas/types.yaml#/definitions/string
description: name of channel
if:
not:
properties:
compatible:
contains:
const: ti,lp8501
then:
properties:
pwr-sel: false
required:
- compatible
- reg

View File

@ -0,0 +1,100 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/silergy,sy7802.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Silergy SY7802 1800mA Boost Charge Pump LED Driver
maintainers:
- André Apitzsch <git@apitzsch.eu>
description: |
The SY7802 is a current-regulated charge pump which can regulate two current
levels for Flash and Torch modes.
The SY7802 is a high-current synchronous boost converter with 2-channel
high side current sources. Each channel is able to deliver 900mA current.
properties:
compatible:
enum:
- silergy,sy7802
reg:
maxItems: 1
enable-gpios:
maxItems: 1
description: A connection to the 'EN' pin.
flash-gpios:
maxItems: 1
description: A connection to the 'FLEN' pin.
vin-supply:
description: Regulator providing power to the 'VIN' pin.
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^led@[0-1]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
description: Index of the LED.
minimum: 0
maximum: 1
led-sources:
minItems: 1
maxItems: 2
items:
minimum: 0
maximum: 1
required:
- reg
- led-sources
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- enable-gpios
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
flash-led-controller@53 {
compatible = "silergy,sy7802";
reg = <0x53>;
#address-cells = <1>;
#size-cells = <0>;
enable-gpios = <&tlmm 16 GPIO_ACTIVE_HIGH>;
led@0 {
reg = <0>;
function = LED_FUNCTION_FLASH;
color = <LED_COLOR_ID_WHITE>;
led-sources = <0>, <1>;
};
};
};

View File

@ -7,7 +7,7 @@ The leds-blinkm driver supports the devices of the BlinkM family.
They are RGB-LED modules driven by a (AT)tiny microcontroller and
communicate through I2C. The default address of these modules is
0x09 but this can be changed through a command. By this you could
dasy-chain up to 127 BlinkMs on an I2C bus.
daisy-chain up to 127 BlinkMs on an I2C bus.
The device accepts RGB and HSB color values through separate commands.
Also you can store blinking sequences as "scripts" in

View File

@ -12598,7 +12598,7 @@ M: Pavel Machek <pavel@ucw.cz>
M: Lee Jones <lee@kernel.org>
L: linux-leds@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds.git
F: Documentation/devicetree/bindings/leds/
F: Documentation/leds/
F: drivers/leds/

View File

@ -429,7 +429,7 @@ config LEDS_LP50XX
module will be called leds-lp50xx.
config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
tristate "Common Driver for TI/National LP5521/5523/55231/5562/5569/8501"
depends on LEDS_CLASS
depends on LEDS_CLASS_MULTICOLOR
depends on OF
@ -437,8 +437,8 @@ config LEDS_LP55XX_COMMON
select FW_LOADER
select FW_LOADER_USER_HELPER
help
This option supports common operations for LP5521/5523/55231/5562/8501
devices.
This option supports common operations for LP5521/5523/55231/5562/5569/
8501 devices.
config LEDS_LP5521
tristate "LED Support for N.S. LP5521 LED driver chip"
@ -471,6 +471,16 @@ config LEDS_LP5562
Driver provides direct control via LED class and interface for
programming the engines.
config LEDS_LP5569
tristate "LED Support for TI LP5569 LED driver chip"
depends on LEDS_CLASS && I2C
depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI LP5569 LED driver.
It is 9 channels chip with programmable engines.
Driver provides direct control via LED class and interface for
programming the engines.
config LEDS_LP8501
tristate "LED Support for TI LP8501 LED driver chip"
depends on LEDS_CLASS && I2C
@ -884,7 +894,6 @@ config LEDS_SPI_BYTE
tristate "LED support for SPI LED controller with a single byte"
depends on LEDS_CLASS
depends on SPI
depends on OF
help
This option enables support for LED controller which use a single byte
for controlling the brightness. Currently the following controller is

View File

@ -52,6 +52,7 @@ obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o
obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o
obj-$(CONFIG_LEDS_LP5569) += leds-lp5569.o
obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o

View File

@ -303,5 +303,6 @@ static struct platform_driver bcm63138_leds_driver = {
module_platform_driver(bcm63138_leds_driver);
MODULE_AUTHOR("Rafał Miłecki");
MODULE_DESCRIPTION("Broadcom BCM63138 SoC LED driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table);

View File

@ -121,4 +121,15 @@ config LEDS_SGM3140
This option enables support for the SGM3140 500mA Buck/Boost Charge
Pump LED Driver.
config LEDS_SY7802
tristate "LED support for the Silergy SY7802"
depends on I2C && OF
depends on GPIOLIB
select REGMAP_I2C
help
This option enables support for the SY7802 flash LED controller.
SY7802 includes torch and flash functions with programmable current.
This driver can be built as a module, it will be called "leds-sy7802".
endif # LEDS_CLASS_FLASH

View File

@ -11,3 +11,4 @@ obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o

View File

@ -743,8 +743,8 @@ static void as3645a_remove(struct i2c_client *client)
}
static const struct i2c_device_id as3645a_id_table[] = {
{ AS_NAME, 0 },
{ },
{ AS_NAME },
{ }
};
MODULE_DEVICE_TABLE(i2c, as3645a_id_table);

View File

@ -643,14 +643,17 @@ static int mt6360_init_isnk_properties(struct mt6360_led *led,
ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret || reg > MT6360_LED_ISNK3 ||
priv->leds_active & BIT(reg))
priv->leds_active & BIT(reg)) {
fwnode_handle_put(child);
return -EINVAL;
}
ret = fwnode_property_read_u32(child, "color", &color);
if (ret) {
dev_err(priv->dev,
"led %d, no color specified\n",
led->led_no);
fwnode_handle_put(child);
return ret;
}

View File

@ -505,6 +505,7 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno
struct qcom_flash_data *flash_data = led->flash_data;
struct v4l2_flash_config v4l2_cfg = { 0 };
struct led_flash_setting *intensity = &v4l2_cfg.intensity;
struct v4l2_flash *v4l2_flash;
if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH))
return 0;
@ -523,9 +524,12 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno
LED_FAULT_OVER_TEMPERATURE |
LED_FAULT_TIMEOUT;
flash_data->v4l2_flash[flash_data->leds_count] =
v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
return PTR_ERR_OR_ZERO(flash_data->v4l2_flash);
v4l2_flash = v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
if (IS_ERR(v4l2_flash))
return PTR_ERR(v4l2_flash);
flash_data->v4l2_flash[flash_data->leds_count] = v4l2_flash;
return 0;
}
# else
static int

View File

@ -426,4 +426,5 @@ static struct i2c_driver rt4505_driver = {
module_i2c_driver(rt4505_driver);
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
MODULE_DESCRIPTION("Richtek RT4505 LED driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Silergy SY7802 flash LED driver with an I2C interface
*
* Copyright 2024 André Apitzsch <git@apitzsch.eu>
*/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/led-class-flash.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#define SY7802_MAX_LEDS 2
#define SY7802_LED_JOINT 2
#define SY7802_REG_ENABLE 0x10
#define SY7802_REG_TORCH_BRIGHTNESS 0xa0
#define SY7802_REG_FLASH_BRIGHTNESS 0xb0
#define SY7802_REG_FLASH_DURATION 0xc0
#define SY7802_REG_FLAGS 0xd0
#define SY7802_REG_CONFIG_1 0xe0
#define SY7802_REG_CONFIG_2 0xf0
#define SY7802_REG_VIN_MONITOR 0x80
#define SY7802_REG_LAST_FLASH 0x81
#define SY7802_REG_VLED_MONITOR 0x30
#define SY7802_REG_ADC_DELAY 0x31
#define SY7802_REG_DEV_ID 0xff
#define SY7802_MODE_OFF 0
#define SY7802_MODE_TORCH 2
#define SY7802_MODE_FLASH 3
#define SY7802_MODE_MASK GENMASK(1, 0)
#define SY7802_LEDS_SHIFT 3
#define SY7802_LEDS_MASK(_id) (BIT(_id) << SY7802_LEDS_SHIFT)
#define SY7802_LEDS_MASK_ALL (SY7802_LEDS_MASK(0) | SY7802_LEDS_MASK(1))
#define SY7802_TORCH_CURRENT_SHIFT 3
#define SY7802_TORCH_CURRENT_MASK(_id) \
(GENMASK(2, 0) << (SY7802_TORCH_CURRENT_SHIFT * (_id)))
#define SY7802_TORCH_CURRENT_MASK_ALL \
(SY7802_TORCH_CURRENT_MASK(0) | SY7802_TORCH_CURRENT_MASK(1))
#define SY7802_FLASH_CURRENT_SHIFT 4
#define SY7802_FLASH_CURRENT_MASK(_id) \
(GENMASK(3, 0) << (SY7802_FLASH_CURRENT_SHIFT * (_id)))
#define SY7802_FLASH_CURRENT_MASK_ALL \
(SY7802_FLASH_CURRENT_MASK(0) | SY7802_FLASH_CURRENT_MASK(1))
#define SY7802_TIMEOUT_DEFAULT_US 512000U
#define SY7802_TIMEOUT_MIN_US 32000U
#define SY7802_TIMEOUT_MAX_US 1024000U
#define SY7802_TIMEOUT_STEPSIZE_US 32000U
#define SY7802_TORCH_BRIGHTNESS_MAX 8
#define SY7802_FLASH_BRIGHTNESS_DEFAULT 14
#define SY7802_FLASH_BRIGHTNESS_MIN 0
#define SY7802_FLASH_BRIGHTNESS_MAX 15
#define SY7802_FLASH_BRIGHTNESS_STEP 1
#define SY7802_FLAG_TIMEOUT BIT(0)
#define SY7802_FLAG_THERMAL_SHUTDOWN BIT(1)
#define SY7802_FLAG_LED_FAULT BIT(2)
#define SY7802_FLAG_TX1_INTERRUPT BIT(3)
#define SY7802_FLAG_TX2_INTERRUPT BIT(4)
#define SY7802_FLAG_LED_THERMAL_FAULT BIT(5)
#define SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW BIT(6)
#define SY7802_FLAG_INPUT_VOLTAGE_LOW BIT(7)
#define SY7802_CHIP_ID 0x51
static const struct reg_default sy7802_regmap_defs[] = {
{ SY7802_REG_ENABLE, SY7802_LEDS_MASK_ALL },
{ SY7802_REG_TORCH_BRIGHTNESS, 0x92 },
{ SY7802_REG_FLASH_BRIGHTNESS, SY7802_FLASH_BRIGHTNESS_DEFAULT |
SY7802_FLASH_BRIGHTNESS_DEFAULT << SY7802_FLASH_CURRENT_SHIFT },
{ SY7802_REG_FLASH_DURATION, 0x6f },
{ SY7802_REG_FLAGS, 0x0 },
{ SY7802_REG_CONFIG_1, 0x68 },
{ SY7802_REG_CONFIG_2, 0xf0 },
};
struct sy7802_led {
struct led_classdev_flash flash;
struct sy7802 *chip;
u8 led_id;
};
struct sy7802 {
struct device *dev;
struct regmap *regmap;
struct mutex mutex;
struct gpio_desc *enable_gpio;
struct regulator *vin_regulator;
unsigned int fled_strobe_used;
unsigned int fled_torch_used;
unsigned int leds_active;
int num_leds;
struct sy7802_led leds[] __counted_by(num_leds);
};
static int sy7802_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness brightness)
{
struct sy7802_led *led = container_of(lcdev, struct sy7802_led, flash.led_cdev);
struct sy7802 *chip = led->chip;
u32 fled_torch_used_tmp;
u32 led_enable_mask;
u32 enable_mask;
u32 torch_mask;
u32 val;
int ret;
mutex_lock(&chip->mutex);
if (chip->fled_strobe_used) {
dev_warn(chip->dev, "Cannot set torch brightness whilst strobe is enabled\n");
ret = -EBUSY;
goto unlock;
}
if (brightness)
fled_torch_used_tmp = chip->fled_torch_used | BIT(led->led_id);
else
fled_torch_used_tmp = chip->fled_torch_used & ~BIT(led->led_id);
led_enable_mask = led->led_id == SY7802_LED_JOINT ?
SY7802_LEDS_MASK_ALL :
SY7802_LEDS_MASK(led->led_id);
val = brightness ? led_enable_mask : SY7802_MODE_OFF;
if (fled_torch_used_tmp)
val |= SY7802_MODE_TORCH;
/* Disable torch to apply brightness */
ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_MASK,
SY7802_MODE_OFF);
if (ret)
goto unlock;
torch_mask = led->led_id == SY7802_LED_JOINT ?
SY7802_TORCH_CURRENT_MASK_ALL :
SY7802_TORCH_CURRENT_MASK(led->led_id);
/* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */
if (brightness)
brightness -= 1;
brightness |= (brightness << SY7802_TORCH_CURRENT_SHIFT);
ret = regmap_update_bits(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, torch_mask, brightness);
if (ret)
goto unlock;
enable_mask = SY7802_MODE_MASK | led_enable_mask;
ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
if (ret)
goto unlock;
chip->fled_torch_used = fled_torch_used_tmp;
unlock:
mutex_unlock(&chip->mutex);
return ret;
}
static int sy7802_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
{
struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
struct led_flash_setting *s = &fl_cdev->brightness;
u32 val = (brightness - s->min) / s->step;
struct sy7802 *chip = led->chip;
u32 flash_mask;
int ret;
val |= (val << SY7802_FLASH_CURRENT_SHIFT);
flash_mask = led->led_id == SY7802_LED_JOINT ?
SY7802_FLASH_CURRENT_MASK_ALL :
SY7802_FLASH_CURRENT_MASK(led->led_id);
mutex_lock(&chip->mutex);
ret = regmap_update_bits(chip->regmap, SY7802_REG_FLASH_BRIGHTNESS, flash_mask, val);
mutex_unlock(&chip->mutex);
return ret;
}
static int sy7802_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
{
struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
struct sy7802 *chip = led->chip;
u32 fled_strobe_used_tmp;
u32 led_enable_mask;
u32 enable_mask;
u32 val;
int ret;
mutex_lock(&chip->mutex);
if (chip->fled_torch_used) {
dev_warn(chip->dev, "Cannot set strobe brightness whilst torch is enabled\n");
ret = -EBUSY;
goto unlock;
}
if (state)
fled_strobe_used_tmp = chip->fled_strobe_used | BIT(led->led_id);
else
fled_strobe_used_tmp = chip->fled_strobe_used & ~BIT(led->led_id);
led_enable_mask = led->led_id == SY7802_LED_JOINT ?
SY7802_LEDS_MASK_ALL :
SY7802_LEDS_MASK(led->led_id);
val = state ? led_enable_mask : SY7802_MODE_OFF;
if (fled_strobe_used_tmp)
val |= SY7802_MODE_FLASH;
enable_mask = SY7802_MODE_MASK | led_enable_mask;
ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
if (ret)
goto unlock;
chip->fled_strobe_used = fled_strobe_used_tmp;
unlock:
mutex_unlock(&chip->mutex);
return ret;
}
static int sy7802_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
{
struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
struct sy7802 *chip = led->chip;
mutex_lock(&chip->mutex);
*state = !!(chip->fled_strobe_used & BIT(led->led_id));
mutex_unlock(&chip->mutex);
return 0;
}
static int sy7802_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
{
struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
struct led_flash_setting *s = &fl_cdev->timeout;
u32 val = (timeout - s->min) / s->step;
struct sy7802 *chip = led->chip;
return regmap_write(chip->regmap, SY7802_REG_FLASH_DURATION, val);
}
static int sy7802_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
{
struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
struct sy7802 *chip = led->chip;
u32 val, led_faults = 0;
int ret;
/* NOTE: reading register clears fault status */
ret = regmap_read(chip->regmap, SY7802_REG_FLAGS, &val);
if (ret)
return ret;
if (val & (SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW | SY7802_FLAG_INPUT_VOLTAGE_LOW))
led_faults |= LED_FAULT_INPUT_VOLTAGE;
if (val & SY7802_FLAG_THERMAL_SHUTDOWN)
led_faults |= LED_FAULT_OVER_TEMPERATURE;
if (val & SY7802_FLAG_TIMEOUT)
led_faults |= LED_FAULT_TIMEOUT;
*fault = led_faults;
return 0;
}
static const struct led_flash_ops sy7802_flash_ops = {
.flash_brightness_set = sy7802_flash_brightness_set,
.strobe_set = sy7802_strobe_set,
.strobe_get = sy7802_strobe_get,
.timeout_set = sy7802_timeout_set,
.fault_get = sy7802_fault_get,
};
static void sy7802_init_flash_brightness(struct led_classdev_flash *fl_cdev)
{
struct led_flash_setting *s;
/* Init flash brightness setting */
s = &fl_cdev->brightness;
s->min = SY7802_FLASH_BRIGHTNESS_MIN;
s->max = SY7802_FLASH_BRIGHTNESS_MAX;
s->step = SY7802_FLASH_BRIGHTNESS_STEP;
s->val = SY7802_FLASH_BRIGHTNESS_DEFAULT;
}
static void sy7802_init_flash_timeout(struct led_classdev_flash *fl_cdev)
{
struct led_flash_setting *s;
/* Init flash timeout setting */
s = &fl_cdev->timeout;
s->min = SY7802_TIMEOUT_MIN_US;
s->max = SY7802_TIMEOUT_MAX_US;
s->step = SY7802_TIMEOUT_STEPSIZE_US;
s->val = SY7802_TIMEOUT_DEFAULT_US;
}
static int sy7802_led_register(struct device *dev, struct sy7802_led *led,
struct device_node *np)
{
struct led_init_data init_data = {};
int ret;
init_data.fwnode = of_fwnode_handle(np);
ret = devm_led_classdev_flash_register_ext(dev, &led->flash, &init_data);
if (ret) {
dev_err(dev, "Couldn't register flash %d\n", led->led_id);
return ret;
}
return 0;
}
static int sy7802_init_flash_properties(struct device *dev, struct sy7802_led *led,
struct device_node *np)
{
struct led_classdev_flash *flash = &led->flash;
struct led_classdev *lcdev = &flash->led_cdev;
u32 sources[SY7802_MAX_LEDS];
int i, num, ret;
num = of_property_count_u32_elems(np, "led-sources");
if (num < 1) {
dev_err(dev, "Not specified or wrong number of led-sources\n");
return -EINVAL;
}
ret = of_property_read_u32_array(np, "led-sources", sources, num);
if (ret)
return ret;
for (i = 0; i < num; i++) {
if (sources[i] >= SY7802_MAX_LEDS)
return -EINVAL;
if (led->chip->leds_active & BIT(sources[i]))
return -EINVAL;
led->chip->leds_active |= BIT(sources[i]);
}
/* If both channels are specified in 'led-sources', joint flash output mode is used */
led->led_id = num == 2 ? SY7802_LED_JOINT : sources[0];
lcdev->max_brightness = SY7802_TORCH_BRIGHTNESS_MAX;
lcdev->brightness_set_blocking = sy7802_torch_brightness_set;
lcdev->flags |= LED_DEV_CAP_FLASH;
flash->ops = &sy7802_flash_ops;
sy7802_init_flash_brightness(flash);
sy7802_init_flash_timeout(flash);
return 0;
}
static int sy7802_chip_check(struct sy7802 *chip)
{
struct device *dev = chip->dev;
u32 chipid;
int ret;
ret = regmap_read(chip->regmap, SY7802_REG_DEV_ID, &chipid);
if (ret)
return dev_err_probe(dev, ret, "Failed to read chip ID\n");
if (chipid != SY7802_CHIP_ID)
return dev_err_probe(dev, -ENODEV, "Unsupported chip detected: %x\n", chipid);
return 0;
}
static void sy7802_enable(struct sy7802 *chip)
{
gpiod_set_value_cansleep(chip->enable_gpio, 1);
usleep_range(200, 300);
}
static void sy7802_disable(struct sy7802 *chip)
{
gpiod_set_value_cansleep(chip->enable_gpio, 0);
}
static int sy7802_probe_dt(struct sy7802 *chip)
{
struct device_node *np = dev_of_node(chip->dev);
int child_num;
int ret;
regmap_write(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_OFF);
regmap_write(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, LED_OFF);
child_num = 0;
for_each_available_child_of_node_scoped(np, child) {
struct sy7802_led *led = chip->leds + child_num;
led->chip = chip;
led->led_id = child_num;
ret = sy7802_init_flash_properties(chip->dev, led, child);
if (ret)
return ret;
ret = sy7802_led_register(chip->dev, led, child);
if (ret)
return ret;
child_num++;
}
return 0;
}
static void sy7802_chip_disable_action(void *data)
{
struct sy7802 *chip = data;
sy7802_disable(chip);
}
static void sy7802_regulator_disable_action(void *data)
{
struct sy7802 *chip = data;
regulator_disable(chip->vin_regulator);
}
static const struct regmap_config sy7802_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
.cache_type = REGCACHE_MAPLE,
.reg_defaults = sy7802_regmap_defs,
.num_reg_defaults = ARRAY_SIZE(sy7802_regmap_defs),
};
static int sy7802_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct sy7802 *chip;
size_t count;
int ret;
count = device_get_child_node_count(dev);
if (!count || count > SY7802_MAX_LEDS)
return dev_err_probe(dev, -EINVAL, "Invalid amount of LED nodes %zu\n", count);
chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->num_leds = count;
chip->dev = dev;
i2c_set_clientdata(client, chip);
chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
ret = PTR_ERR_OR_ZERO(chip->enable_gpio);
if (ret)
return dev_err_probe(dev, ret, "Failed to request enable gpio\n");
chip->vin_regulator = devm_regulator_get(dev, "vin");
ret = PTR_ERR_OR_ZERO(chip->vin_regulator);
if (ret)
return dev_err_probe(dev, ret, "Failed to request regulator\n");
ret = regulator_enable(chip->vin_regulator);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable regulator\n");
ret = devm_add_action_or_reset(dev, sy7802_regulator_disable_action, chip);
if (ret)
return ret;
ret = devm_mutex_init(dev, &chip->mutex);
if (ret)
return ret;
mutex_lock(&chip->mutex);
chip->regmap = devm_regmap_init_i2c(client, &sy7802_regmap_config);
if (IS_ERR(chip->regmap)) {
ret = PTR_ERR(chip->regmap);
dev_err_probe(dev, ret, "Failed to allocate register map\n");
goto error;
}
ret = sy7802_probe_dt(chip);
if (ret < 0)
goto error;
sy7802_enable(chip);
ret = devm_add_action_or_reset(dev, sy7802_chip_disable_action, chip);
if (ret)
goto error;
ret = sy7802_chip_check(chip);
error:
mutex_unlock(&chip->mutex);
return ret;
}
static const struct of_device_id __maybe_unused sy7802_leds_match[] = {
{ .compatible = "silergy,sy7802", },
{}
};
MODULE_DEVICE_TABLE(of, sy7802_leds_match);
static struct i2c_driver sy7802_driver = {
.driver = {
.name = "sy7802",
.of_match_table = of_match_ptr(sy7802_leds_match),
},
.probe = sy7802_probe,
};
module_i2c_driver(sy7802_driver);
MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>");
MODULE_DESCRIPTION("Silergy SY7802 flash LED driver");
MODULE_LICENSE("GPL");

View File

@ -134,6 +134,7 @@ int led_classdev_multicolor_register_ext(struct device *parent,
return -EINVAL;
led_cdev = &mcled_cdev->led_cdev;
led_cdev->flags |= LED_MULTI_COLOR;
mcled_cdev->led_cdev.groups = led_multicolor_groups;
return led_classdev_register_ext(parent, led_cdev, init_data);

View File

@ -258,7 +258,6 @@ struct led_classdev *of_led_get(struct device_node *np, int index)
led_dev = class_find_device_by_of_node(&leds_class, led_node);
of_node_put(led_node);
put_device(led_dev);
return led_module_get(led_dev);
}

View File

@ -8,6 +8,7 @@
*/
#include <linux/kernel.h>
#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/list.h>
#include <linux/module.h>
@ -121,15 +122,22 @@ static void led_timer_function(struct timer_list *t)
static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev,
unsigned int value)
{
int ret = 0;
int ret;
ret = __led_set_brightness(led_cdev, value);
if (ret == -ENOTSUPP)
if (ret == -ENOTSUPP) {
ret = __led_set_brightness_blocking(led_cdev, value);
if (ret < 0 &&
/* LED HW might have been unplugged, therefore don't warn */
!(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&
(led_cdev->flags & LED_HW_PLUGGABLE)))
if (ret == -ENOTSUPP)
/* No back-end support to set a fixed brightness value */
return;
}
/* LED HW might have been unplugged, therefore don't warn */
if (ret == -ENODEV && led_cdev->flags & LED_UNREGISTERING &&
led_cdev->flags & LED_HW_PLUGGABLE)
return;
if (ret < 0)
dev_err(led_cdev->dev,
"Setting an LED's brightness failed (%d)\n", ret);
}
@ -361,6 +369,36 @@ int led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value)
}
EXPORT_SYMBOL_GPL(led_set_brightness_sync);
/*
* This is a led-core function because just like led_set_brightness()
* it is used in the kernel by e.g. triggers.
*/
void led_mc_set_brightness(struct led_classdev *led_cdev,
unsigned int *intensity_value, unsigned int num_colors,
unsigned int brightness)
{
struct led_classdev_mc *mcled_cdev;
unsigned int i;
if (!(led_cdev->flags & LED_MULTI_COLOR)) {
dev_err_once(led_cdev->dev, "error not a multi-color LED\n");
return;
}
mcled_cdev = lcdev_to_mccdev(led_cdev);
if (num_colors != mcled_cdev->num_colors) {
dev_err_once(led_cdev->dev, "error num_colors mismatch %u != %u\n",
num_colors, mcled_cdev->num_colors);
return;
}
for (i = 0; i < mcled_cdev->num_colors; i++)
mcled_cdev->subled_info[i].intensity = intensity_value[i];
led_set_brightness(led_cdev, brightness);
}
EXPORT_SYMBOL_GPL(led_mc_set_brightness);
int led_update_brightness(struct led_classdev *led_cdev)
{
int ret;

View File

@ -179,9 +179,9 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
cancel_work_sync(&led_cdev->set_brightness_work);
led_stop_software_blink(led_cdev);
device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
led_cdev->trigger = NULL;
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
@ -194,6 +194,19 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
spin_unlock(&trig->leddev_list_lock);
led_cdev->trigger = trig;
/*
* Some activate() calls use led_trigger_event() to initialize
* the brightness of the LED for which the trigger is being set.
* Ensure the led_cdev is visible on trig->led_cdevs for this.
*/
synchronize_rcu();
/*
* If "set brightness to 0" is pending in workqueue,
* we don't want that to be reordered after ->activate()
*/
flush_work(&led_cdev->set_brightness_work);
ret = 0;
if (trig->activate)
ret = trig->activate(led_cdev);
@ -396,6 +409,26 @@ void led_trigger_event(struct led_trigger *trig,
}
EXPORT_SYMBOL_GPL(led_trigger_event);
void led_mc_trigger_event(struct led_trigger *trig,
unsigned int *intensity_value, unsigned int num_colors,
enum led_brightness brightness)
{
struct led_classdev *led_cdev;
if (!trig)
return;
rcu_read_lock();
list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) {
if (!(led_cdev->flags & LED_MULTI_COLOR))
continue;
led_mc_set_brightness(led_cdev, intensity_value, num_colors, brightness);
}
rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(led_mc_trigger_event);
static void led_trigger_blink_setup(struct led_trigger *trig,
unsigned long delay_on,
unsigned long delay_off,

View File

@ -331,8 +331,8 @@ static const struct of_device_id an30259a_match_table[] = {
MODULE_DEVICE_TABLE(of, an30259a_match_table);
static const struct i2c_device_id an30259a_id[] = {
{ "an30259a", 0 },
{ /* sentinel */ },
{ "an30259a" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, an30259a_id);

View File

@ -776,7 +776,7 @@ static int bd2802_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume);
static const struct i2c_device_id bd2802_id[] = {
{ "BD2802", 0 },
{ "BD2802" },
{ }
};
MODULE_DEVICE_TABLE(i2c, bd2802_id);

View File

@ -718,7 +718,7 @@ static void blinkm_remove(struct i2c_client *client)
}
static const struct i2c_device_id blinkm_id[] = {
{"blinkm", 0},
{ "blinkm" },
{}
};

View File

@ -140,7 +140,7 @@ static const struct reg_default is31fl3190_reg_defaults[] = {
{ IS31FL3190_PWM(2), 0x00 },
};
static struct regmap_config is31fl3190_regmap_config = {
static const struct regmap_config is31fl3190_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = IS31FL3190_RESET,
@ -178,7 +178,7 @@ static const struct reg_default is31fl3196_reg_defaults[] = {
{ IS31FL3196_PWM(8), 0x00 },
};
static struct regmap_config is31fl3196_regmap_config = {
static const struct regmap_config is31fl3196_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = IS31FL3196_REG_CNT,

View File

@ -478,7 +478,7 @@ static void lm3530_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3530_id[] = {
{LM3530_NAME, 0},
{ LM3530_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lm3530_id);

View File

@ -726,7 +726,7 @@ static const struct of_device_id of_lm3532_leds_match[] = {
MODULE_DEVICE_TABLE(of, of_lm3532_leds_match);
static const struct i2c_device_id lm3532_id[] = {
{LM3532_NAME, 0},
{ LM3532_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lm3532_id);

View File

@ -390,7 +390,7 @@ static void lm3642_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3642_id[] = {
{LM3642_NAME, 0},
{ LM3642_NAME },
{}
};

View File

@ -360,7 +360,7 @@ static void lm3697_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3697_id[] = {
{ "lm3697", 0 },
{ "lm3697" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm3697_id);

View File

@ -417,7 +417,7 @@ static void lp3944_remove(struct i2c_client *client)
/* lp3944 i2c driver struct */
static const struct i2c_device_id lp3944_id[] = {
{"lp3944", 0},
{ "lp3944" },
{}
};

View File

@ -266,7 +266,7 @@ static int lp3952_probe(struct i2c_client *client)
}
static const struct i2c_device_id lp3952_id[] = {
{LP3952_NAME, 0},
{ LP3952_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lp3952_id);

View File

@ -9,6 +9,7 @@
* Milo(Woogyom) Kim <milo.kim@ti.com>
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@ -21,7 +22,6 @@
#include "leds-lp55xx-common.h"
#define LP5521_PROGRAM_LENGTH 32
#define LP5521_MAX_LEDS 3
#define LP5521_CMD_DIRECT 0x3F
@ -73,29 +73,6 @@
/* Reset register value */
#define LP5521_RESET 0xFF
/* Program Memory Operations */
#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */
#define LP5521_MODE_G_M 0x0C
#define LP5521_MODE_B_M 0x03
#define LP5521_LOAD_R 0x10
#define LP5521_LOAD_G 0x04
#define LP5521_LOAD_B 0x01
#define LP5521_R_IS_LOADING(mode) \
((mode & LP5521_MODE_R_M) == LP5521_LOAD_R)
#define LP5521_G_IS_LOADING(mode) \
((mode & LP5521_MODE_G_M) == LP5521_LOAD_G)
#define LP5521_B_IS_LOADING(mode) \
((mode & LP5521_MODE_B_M) == LP5521_LOAD_B)
#define LP5521_EXEC_R_M 0x30 /* Enable Register */
#define LP5521_EXEC_G_M 0x0C
#define LP5521_EXEC_B_M 0x03
#define LP5521_EXEC_M 0x3F
#define LP5521_RUN_R 0x20
#define LP5521_RUN_G 0x08
#define LP5521_RUN_B 0x02
static inline void lp5521_wait_opmode_done(void)
{
/* operation mode change needs to be longer than 153 us */
@ -108,170 +85,21 @@ static inline void lp5521_wait_enable_done(void)
usleep_range(500, 600);
}
static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current)
{
led->led_current = led_current;
lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
led_current);
}
static void lp5521_load_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
};
static const u8 val[] = {
[LP55XX_ENGINE_1] = LP5521_LOAD_R,
[LP55XX_ENGINE_2] = LP5521_LOAD_G,
[LP55XX_ENGINE_3] = LP5521_LOAD_B,
};
lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]);
lp5521_wait_opmode_done();
}
static void lp5521_stop_all_engines(struct lp55xx_chip *chip)
{
lp55xx_write(chip, LP5521_REG_OP_MODE, 0);
lp5521_wait_opmode_done();
}
static void lp5521_stop_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
};
lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0);
lp5521_wait_opmode_done();
}
static void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
u8 mode;
u8 exec;
/* stop engine */
if (!start) {
lp5521_stop_engine(chip);
lp55xx_stop_engine(chip);
lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
lp5521_wait_opmode_done();
return;
}
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode);
if (ret)
return;
ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec);
if (ret)
return;
/* change operation mode to RUN only when each engine is loading */
if (LP5521_R_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R;
exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R;
}
if (LP5521_G_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
}
if (LP5521_B_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B;
exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B;
}
lp55xx_write(chip, LP5521_REG_OP_MODE, mode);
lp5521_wait_opmode_done();
lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec);
lp5521_wait_enable_done();
}
static int lp5521_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
enum lp55xx_engine_index idx = chip->engine_idx;
u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
static const u8 addr[] = {
[LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM,
[LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM,
[LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM,
};
unsigned cmd;
char c[3];
int nrchars;
int ret;
int offset = 0;
int i = 0;
while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) {
ret = lp55xx_write(chip, addr[idx] + i, pattern[i]);
if (ret)
return -EINVAL;
}
return size;
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
static void lp5521_firmware_loaded(struct lp55xx_chip *chip)
{
const struct firmware *fw = chip->fw;
if (fw->size > LP5521_PROGRAM_LENGTH) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
/*
* Program memory sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp5521_load_engine(chip);
lp5521_update_program_memory(chip, fw->data, fw->size);
ret = lp55xx_run_engine_common(chip);
if (!ret)
lp5521_wait_enable_done();
}
static int lp5521_post_init_device(struct lp55xx_chip *chip)
@ -350,114 +178,6 @@ static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
return 0;
}
static int lp5521_multicolor_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
int i;
mutex_lock(&chip->lock);
for (i = 0; i < led->mc_cdev.num_colors; i++) {
ret = lp55xx_write(chip,
LP5521_REG_LED_PWM_BASE +
led->mc_cdev.subled_info[i].channel,
led->mc_cdev.subled_info[i].brightness);
if (ret)
break;
}
mutex_unlock(&chip->lock);
return ret;
}
static int lp5521_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
ret = lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
switch (mode) {
case LP55XX_ENGINE_RUN:
return sprintf(buf, "run\n");
case LP55XX_ENGINE_LOAD:
return sprintf(buf, "load\n");
case LP55XX_ENGINE_DISABLED:
default:
return sprintf(buf, "disabled\n");
}
}
show_mode(1)
show_mode(2)
show_mode(3)
static ssize_t store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_engine *engine = &chip->engines[nr - 1];
mutex_lock(&chip->lock);
chip->engine_idx = nr;
if (!strncmp(buf, "run", 3)) {
lp5521_run_engine(chip, true);
engine->mode = LP55XX_ENGINE_RUN;
} else if (!strncmp(buf, "load", 4)) {
lp5521_stop_engine(chip);
lp5521_load_engine(chip);
engine->mode = LP55XX_ENGINE_LOAD;
} else if (!strncmp(buf, "disabled", 8)) {
lp5521_stop_engine(chip);
engine->mode = LP55XX_ENGINE_DISABLED;
}
mutex_unlock(&chip->lock);
return len;
}
store_mode(1)
store_mode(2)
store_mode(3)
static ssize_t store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
chip->engine_idx = nr;
lp5521_load_engine(chip);
ret = lp5521_update_program_memory(chip, buf, len);
mutex_unlock(&chip->lock);
return ret;
}
store_load(1)
store_load(2)
store_load(3)
static ssize_t lp5521_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -466,20 +186,20 @@ static ssize_t lp5521_selftest(struct device *dev,
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
ret = lp5521_run_selftest(chip, buf);
mutex_unlock(&chip->lock);
return sysfs_emit(buf, "%s\n", ret ? "FAIL" : "OK");
}
/* device attributes */
static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
LP55XX_DEV_ATTR_ENGINE_MODE(1);
LP55XX_DEV_ATTR_ENGINE_MODE(2);
LP55XX_DEV_ATTR_ENGINE_MODE(3);
LP55XX_DEV_ATTR_ENGINE_LOAD(1);
LP55XX_DEV_ATTR_ENGINE_LOAD(2);
LP55XX_DEV_ATTR_ENGINE_LOAD(3);
static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest);
static struct attribute *lp5521_attributes[] = {
@ -499,6 +219,12 @@ static const struct attribute_group lp5521_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5521_cfg = {
.reg_op_mode = {
.addr = LP5521_REG_OP_MODE,
},
.reg_exec = {
.addr = LP5521_REG_ENABLE,
},
.reset = {
.addr = LP5521_REG_RESET,
.val = LP5521_RESET,
@ -507,97 +233,33 @@ static struct lp55xx_device_config lp5521_cfg = {
.addr = LP5521_REG_ENABLE,
.val = LP5521_ENABLE_DEFAULT,
},
.prog_mem_base = {
.addr = LP5521_REG_R_PROG_MEM,
},
.reg_led_pwm_base = {
.addr = LP5521_REG_LED_PWM_BASE,
},
.reg_led_current_base = {
.addr = LP5521_REG_LED_CURRENT_BASE,
},
.max_channel = LP5521_MAX_LEDS,
.post_init_device = lp5521_post_init_device,
.brightness_fn = lp5521_led_brightness,
.multicolor_brightness_fn = lp5521_multicolor_brightness,
.set_led_current = lp5521_set_led_current,
.firmware_cb = lp5521_firmware_loaded,
.brightness_fn = lp55xx_led_brightness,
.multicolor_brightness_fn = lp55xx_multicolor_brightness,
.set_led_current = lp55xx_set_led_current,
.firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp5521_run_engine,
.dev_attr_group = &lp5521_group,
};
static int lp5521_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = dev_of_node(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5521_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
mutex_init(&chip->lock);
i2c_set_clientdata(client, led);
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
dev_info(&client->dev, "%s programmable led chip found\n", id->name);
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_out;
}
return 0;
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static void lp5521_remove(struct i2c_client *client)
{
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp5521_stop_all_engines(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_deinit_device(chip);
}
static const struct i2c_device_id lp5521_id[] = {
{ "lp5521", 0 }, /* Three channel chip */
{ "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5521_id);
static const struct of_device_id of_lp5521_leds_match[] = {
{ .compatible = "national,lp5521", },
{ .compatible = "national,lp5521", .data = &lp5521_cfg, },
{},
};
@ -608,8 +270,8 @@ static struct i2c_driver lp5521_driver = {
.name = "lp5521",
.of_match_table = of_lp5521_leds_match,
},
.probe = lp5521_probe,
.remove = lp5521_remove,
.probe = lp55xx_probe,
.remove = lp55xx_remove,
.id_table = lp5521_id,
};

View File

@ -9,6 +9,7 @@
* Milo(Woogyom) Kim <milo.kim@ti.com>
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@ -21,7 +22,6 @@
#include "leds-lp55xx-common.h"
#define LP5523_PROGRAM_LENGTH 32 /* bytes */
/* Memory is used like this:
* 0x00 engine 1 program
* 0x10 engine 2 program
@ -30,6 +30,7 @@
* 0x40 engine 2 muxing info
* 0x50 engine 3 muxing info
*/
#define LP5523_PAGES_PER_ENGINE 1
#define LP5523_MAX_LEDS 9
/* Registers */
@ -41,7 +42,10 @@
#define LP5523_REG_LED_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36
#define LP5523_REG_STATUS 0x3A
#define LP5523_ENGINE_BUSY BIT(4)
#define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42
@ -70,61 +74,8 @@
#define LP5523_EXT_CLK_USED 0x08
#define LP5523_ENG_STATUS_MASK 0x07
#define LP5523_FADER_MAPPING_MASK 0xC0
#define LP5523_FADER_MAPPING_SHIFT 6
/* Memory Page Selection */
#define LP5523_PAGE_ENG1 0
#define LP5523_PAGE_ENG2 1
#define LP5523_PAGE_ENG3 2
#define LP5523_PAGE_MUX1 3
#define LP5523_PAGE_MUX2 4
#define LP5523_PAGE_MUX3 5
/* Program Memory Operations */
#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */
#define LP5523_MODE_ENG2_M 0x0C
#define LP5523_MODE_ENG3_M 0x03
#define LP5523_LOAD_ENG1 0x10
#define LP5523_LOAD_ENG2 0x04
#define LP5523_LOAD_ENG3 0x01
#define LP5523_ENG1_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1)
#define LP5523_ENG2_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2)
#define LP5523_ENG3_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3)
#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */
#define LP5523_EXEC_ENG2_M 0x0C
#define LP5523_EXEC_ENG3_M 0x03
#define LP5523_EXEC_M 0x3F
#define LP5523_RUN_ENG1 0x20
#define LP5523_RUN_ENG2 0x08
#define LP5523_RUN_ENG3 0x02
#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
enum lp5523_chip_id {
LP5523,
LP55231,
};
static int lp5523_init_program_engine(struct lp55xx_chip *chip);
static inline void lp5523_wait_opmode_done(void)
{
usleep_range(1000, 2000);
}
static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current)
{
led->led_current = led_current;
lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
led_current);
}
static int lp5523_post_init_device(struct lp55xx_chip *chip)
{
int ret;
@ -156,114 +107,16 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip)
return lp5523_init_program_engine(chip);
}
static void lp5523_load_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
[LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
[LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
};
static const u8 val[] = {
[LP55XX_ENGINE_1] = LP5523_LOAD_ENG1,
[LP55XX_ENGINE_2] = LP5523_LOAD_ENG2,
[LP55XX_ENGINE_3] = LP5523_LOAD_ENG3,
};
lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
lp5523_wait_opmode_done();
}
static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 page_sel[] = {
[LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
[LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
[LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
};
lp5523_load_engine(chip);
lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
}
static void lp5523_stop_all_engines(struct lp55xx_chip *chip)
{
lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
lp5523_wait_opmode_done();
}
static void lp5523_stop_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
[LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
[LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
};
lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0);
lp5523_wait_opmode_done();
}
static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
{
int i;
for (i = 0; i < LP5523_MAX_LEDS; i++)
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
}
static void lp5523_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
u8 mode;
u8 exec;
/* stop engine */
if (!start) {
lp5523_stop_engine(chip);
lp5523_turn_off_channels(chip);
lp55xx_stop_engine(chip);
lp55xx_turn_off_channels(chip);
return;
}
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode);
if (ret)
return;
ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec);
if (ret)
return;
/* change operation mode to RUN only when each engine is loading */
if (LP5523_ENG1_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1;
exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1;
}
if (LP5523_ENG2_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2;
exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2;
}
if (LP5523_ENG3_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3;
exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3;
}
lp55xx_write(chip, LP5523_REG_OP_MODE, mode);
lp5523_wait_opmode_done();
lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec);
lp55xx_run_engine_common(chip);
}
static int lp5523_init_program_engine(struct lp55xx_chip *chip)
@ -273,7 +126,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
int ret;
u8 status;
/* one pattern per engine setting LED MUX start and stop addresses */
static const u8 pattern[][LP5523_PROGRAM_LENGTH] = {
static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = {
{ 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
@ -295,9 +148,9 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
/* write LED MUX address space for each engine */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
chip->engine_idx = i;
lp5523_load_engine_and_select_page(chip);
lp55xx_load_engine(chip);
for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) {
for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j,
pattern[i - 1][j]);
if (ret)
@ -322,262 +175,10 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
}
out:
lp5523_stop_all_engines(chip);
lp55xx_stop_all_engine(chip);
return ret;
}
static int lp5523_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
unsigned int cmd;
char c[3];
int nrchars;
int ret;
int offset = 0;
int i = 0;
while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) {
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
if (ret)
return -EINVAL;
}
return size;
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
{
const struct firmware *fw = chip->fw;
if (fw->size > LP5523_PROGRAM_LENGTH) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
/*
* Program memory sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp5523_load_engine_and_select_page(chip);
lp5523_update_program_memory(chip, fw->data, fw->size);
}
static ssize_t show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
switch (mode) {
case LP55XX_ENGINE_RUN:
return sprintf(buf, "run\n");
case LP55XX_ENGINE_LOAD:
return sprintf(buf, "load\n");
case LP55XX_ENGINE_DISABLED:
default:
return sprintf(buf, "disabled\n");
}
}
show_mode(1)
show_mode(2)
show_mode(3)
static ssize_t store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_engine *engine = &chip->engines[nr - 1];
mutex_lock(&chip->lock);
chip->engine_idx = nr;
if (!strncmp(buf, "run", 3)) {
lp5523_run_engine(chip, true);
engine->mode = LP55XX_ENGINE_RUN;
} else if (!strncmp(buf, "load", 4)) {
lp5523_stop_engine(chip);
lp5523_load_engine(chip);
engine->mode = LP55XX_ENGINE_LOAD;
} else if (!strncmp(buf, "disabled", 8)) {
lp5523_stop_engine(chip);
engine->mode = LP55XX_ENGINE_DISABLED;
}
mutex_unlock(&chip->lock);
return len;
}
store_mode(1)
store_mode(2)
store_mode(3)
static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
{
u16 tmp_mux = 0;
int i;
len = min_t(int, len, LP5523_MAX_LEDS);
for (i = 0; i < len; i++) {
switch (buf[i]) {
case '1':
tmp_mux |= (1 << i);
break;
case '0':
break;
case '\n':
i = len;
break;
default:
return -1;
}
}
*mux = tmp_mux;
return 0;
}
static void lp5523_mux_to_array(u16 led_mux, char *array)
{
int i, pos = 0;
for (i = 0; i < LP5523_MAX_LEDS; i++)
pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
array[pos] = '\0';
}
static ssize_t show_engine_leds(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
char mux[LP5523_MAX_LEDS + 1];
lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
return sprintf(buf, "%s\n", mux);
}
show_leds(1)
show_leds(2)
show_leds(3)
static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
{
struct lp55xx_engine *engine = &chip->engines[nr - 1];
int ret;
static const u8 mux_page[] = {
[LP55XX_ENGINE_1] = LP5523_PAGE_MUX1,
[LP55XX_ENGINE_2] = LP5523_PAGE_MUX2,
[LP55XX_ENGINE_3] = LP5523_PAGE_MUX3,
};
lp5523_load_engine(chip);
ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]);
if (ret)
return ret;
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM, (u8)(mux >> 8));
if (ret)
return ret;
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux));
if (ret)
return ret;
engine->led_mux = mux;
return 0;
}
static ssize_t store_engine_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_engine *engine = &chip->engines[nr - 1];
u16 mux = 0;
ssize_t ret;
if (lp5523_mux_parse(buf, &mux, len))
return -EINVAL;
mutex_lock(&chip->lock);
chip->engine_idx = nr;
ret = -EINVAL;
if (engine->mode != LP55XX_ENGINE_LOAD)
goto leave;
if (lp5523_load_mux(chip, mux, nr))
goto leave;
ret = len;
leave:
mutex_unlock(&chip->lock);
return ret;
}
store_leds(1)
store_leds(2)
store_leds(3)
static ssize_t store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
chip->engine_idx = nr;
lp5523_load_engine_and_select_page(chip);
ret = lp5523_update_program_memory(chip, buf, len);
mutex_unlock(&chip->lock);
return ret;
}
store_load(1)
store_load(2)
store_load(3)
static ssize_t lp5523_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
@ -588,16 +189,16 @@ static ssize_t lp5523_selftest(struct device *dev,
int ret, pos = 0;
u8 status, adc, vdd, i;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
/* Check that ext clock is really in use if requested */
if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
if ((status & LP5523_EXT_CLK_USED) == 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
}
/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
@ -605,14 +206,14 @@ static ssize_t lp5523_selftest(struct device *dev,
usleep_range(3000, 6000); /* ADC conversion time is typically 2.7 ms */
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000); /* Was not ready. Wait little bit */
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &vdd);
if (ret < 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
vdd--; /* There may be some fluctuation in measurement */
@ -635,18 +236,18 @@ static ssize_t lp5523_selftest(struct device *dev,
usleep_range(3000, 6000);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000); /* Was not ready. Wait. */
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc);
if (ret < 0)
goto fail;
return sysfs_emit(buf, "FAIL\n");
if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM)
pos += sprintf(buf + pos, "LED %d FAIL\n",
led->chan_nr);
pos += sysfs_emit_at(buf, pos, "LED %d FAIL\n",
led->chan_nr);
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
0x00);
@ -656,198 +257,25 @@ static ssize_t lp5523_selftest(struct device *dev,
led->led_current);
led++;
}
if (pos == 0)
pos = sprintf(buf, "OK\n");
goto release_lock;
fail:
pos = sprintf(buf, "FAIL\n");
release_lock:
mutex_unlock(&chip->lock);
return pos;
return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
}
#define show_fader(nr) \
static ssize_t show_master_fader##nr(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return show_master_fader(dev, attr, buf, nr); \
}
#define store_fader(nr) \
static ssize_t store_master_fader##nr(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_master_fader(dev, attr, buf, len, nr); \
}
static ssize_t show_master_fader(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
u8 val;
mutex_lock(&chip->lock);
ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val);
mutex_unlock(&chip->lock);
if (ret == 0)
ret = sprintf(buf, "%u\n", val);
return ret;
}
show_fader(1)
show_fader(2)
show_fader(3)
static ssize_t store_master_fader(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val > 0xff)
return -EINVAL;
mutex_lock(&chip->lock);
ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1,
(u8)val);
mutex_unlock(&chip->lock);
if (ret == 0)
ret = len;
return ret;
}
store_fader(1)
store_fader(2)
store_fader(3)
static ssize_t show_master_fader_leds(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int i, ret, pos = 0;
u8 val;
mutex_lock(&chip->lock);
for (i = 0; i < LP5523_MAX_LEDS; i++) {
ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val);
if (ret)
goto leave;
val = (val & LP5523_FADER_MAPPING_MASK)
>> LP5523_FADER_MAPPING_SHIFT;
if (val > 3) {
ret = -EINVAL;
goto leave;
}
buf[pos++] = val + '0';
}
buf[pos++] = '\n';
ret = pos;
leave:
mutex_unlock(&chip->lock);
return ret;
}
static ssize_t store_master_fader_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int i, n, ret;
u8 val;
n = min_t(int, len, LP5523_MAX_LEDS);
mutex_lock(&chip->lock);
for (i = 0; i < n; i++) {
if (buf[i] >= '0' && buf[i] <= '3') {
val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT;
ret = lp55xx_update_bits(chip,
LP5523_REG_LED_CTRL_BASE + i,
LP5523_FADER_MAPPING_MASK,
val);
if (ret)
goto leave;
} else {
ret = -EINVAL;
goto leave;
}
}
ret = len;
leave:
mutex_unlock(&chip->lock);
return ret;
}
static int lp5523_multicolor_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
int i;
mutex_lock(&chip->lock);
for (i = 0; i < led->mc_cdev.num_colors; i++) {
ret = lp55xx_write(chip,
LP5523_REG_LED_PWM_BASE +
led->mc_cdev.subled_info[i].channel,
led->mc_cdev.subled_info[i].brightness);
if (ret)
break;
}
mutex_unlock(&chip->lock);
return ret;
}
static int lp5523_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
return ret;
}
static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds);
static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds);
static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds);
static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
LP55XX_DEV_ATTR_ENGINE_MODE(1);
LP55XX_DEV_ATTR_ENGINE_MODE(2);
LP55XX_DEV_ATTR_ENGINE_MODE(3);
LP55XX_DEV_ATTR_ENGINE_LEDS(1);
LP55XX_DEV_ATTR_ENGINE_LEDS(2);
LP55XX_DEV_ATTR_ENGINE_LEDS(3);
LP55XX_DEV_ATTR_ENGINE_LOAD(1);
LP55XX_DEV_ATTR_ENGINE_LOAD(2);
LP55XX_DEV_ATTR_ENGINE_LOAD(3);
static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
store_master_fader1);
static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
store_master_fader2);
static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
store_master_fader3);
static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
store_master_fader_leds);
LP55XX_DEV_ATTR_MASTER_FADER(1);
LP55XX_DEV_ATTR_MASTER_FADER(2);
LP55XX_DEV_ATTR_MASTER_FADER(3);
static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds,
lp55xx_store_master_fader_leds);
static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr,
@ -873,6 +301,16 @@ static const struct attribute_group lp5523_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5523_cfg = {
.reg_op_mode = {
.addr = LP5523_REG_OP_MODE,
},
.reg_exec = {
.addr = LP5523_REG_ENABLE,
},
.engine_busy = {
.addr = LP5523_REG_STATUS,
.mask = LP5523_ENGINE_BUSY,
},
.reset = {
.addr = LP5523_REG_RESET,
.val = LP5523_RESET,
@ -881,100 +319,43 @@ static struct lp55xx_device_config lp5523_cfg = {
.addr = LP5523_REG_ENABLE,
.val = LP5523_ENABLE,
},
.prog_mem_base = {
.addr = LP5523_REG_PROG_MEM,
},
.reg_led_pwm_base = {
.addr = LP5523_REG_LED_PWM_BASE,
},
.reg_led_current_base = {
.addr = LP5523_REG_LED_CURRENT_BASE,
},
.reg_master_fader_base = {
.addr = LP5523_REG_MASTER_FADER_BASE,
},
.reg_led_ctrl_base = {
.addr = LP5523_REG_LED_CTRL_BASE,
},
.pages_per_engine = LP5523_PAGES_PER_ENGINE,
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
.brightness_fn = lp5523_led_brightness,
.multicolor_brightness_fn = lp5523_multicolor_brightness,
.set_led_current = lp5523_set_led_current,
.firmware_cb = lp5523_firmware_loaded,
.brightness_fn = lp55xx_led_brightness,
.multicolor_brightness_fn = lp55xx_multicolor_brightness,
.set_led_current = lp55xx_set_led_current,
.firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp5523_run_engine,
.dev_attr_group = &lp5523_group,
};
static int lp5523_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = dev_of_node(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5523_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
mutex_init(&chip->lock);
i2c_set_clientdata(client, led);
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_out;
}
return 0;
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static void lp5523_remove(struct i2c_client *client)
{
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp5523_stop_all_engines(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_deinit_device(chip);
}
static const struct i2c_device_id lp5523_id[] = {
{ "lp5523", LP5523 },
{ "lp55231", LP55231 },
{ "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
{ "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5523_id);
static const struct of_device_id of_lp5523_leds_match[] = {
{ .compatible = "national,lp5523", },
{ .compatible = "ti,lp55231", },
{ .compatible = "national,lp5523", .data = &lp5523_cfg, },
{ .compatible = "ti,lp55231", .data = &lp5523_cfg, },
{},
};
@ -985,8 +366,8 @@ static struct i2c_driver lp5523_driver = {
.name = "lp5523x",
.of_match_table = of_lp5523_leds_match,
},
.probe = lp5523_probe,
.remove = lp5523_remove,
.probe = lp55xx_probe,
.remove = lp55xx_remove,
.id_table = lp5523_id,
};

View File

@ -7,6 +7,7 @@
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@ -19,7 +20,6 @@
#include "leds-lp55xx-common.h"
#define LP5562_PROGRAM_LENGTH 32
#define LP5562_MAX_LEDS 4
/* ENABLE Register 00h */
@ -38,21 +38,6 @@
/* OPMODE Register 01h */
#define LP5562_REG_OP_MODE 0x01
#define LP5562_MODE_ENG1_M 0x30
#define LP5562_MODE_ENG2_M 0x0C
#define LP5562_MODE_ENG3_M 0x03
#define LP5562_LOAD_ENG1 0x10
#define LP5562_LOAD_ENG2 0x04
#define LP5562_LOAD_ENG3 0x01
#define LP5562_RUN_ENG1 0x20
#define LP5562_RUN_ENG2 0x08
#define LP5562_RUN_ENG3 0x02
#define LP5562_ENG1_IS_LOADING(mode) \
((mode & LP5562_MODE_ENG1_M) == LP5562_LOAD_ENG1)
#define LP5562_ENG2_IS_LOADING(mode) \
((mode & LP5562_MODE_ENG2_M) == LP5562_LOAD_ENG2)
#define LP5562_ENG3_IS_LOADING(mode) \
((mode & LP5562_MODE_ENG3_M) == LP5562_LOAD_ENG3)
/* BRIGHTNESS Registers */
#define LP5562_REG_R_PWM 0x04
@ -124,160 +109,24 @@ static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current)
lp55xx_write(led->chip, addr[led->chan_nr], led_current);
}
static void lp5562_load_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M,
[LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M,
[LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M,
};
static const u8 val[] = {
[LP55XX_ENGINE_1] = LP5562_LOAD_ENG1,
[LP55XX_ENGINE_2] = LP5562_LOAD_ENG2,
[LP55XX_ENGINE_3] = LP5562_LOAD_ENG3,
};
lp55xx_update_bits(chip, LP5562_REG_OP_MODE, mask[idx], val[idx]);
lp5562_wait_opmode_done();
}
static void lp5562_stop_engine(struct lp55xx_chip *chip)
{
lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DISABLE);
lp5562_wait_opmode_done();
}
static void lp5562_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
u8 mode;
u8 exec;
/* stop engine */
if (!start) {
lp55xx_write(chip, LP5562_REG_ENABLE, LP5562_ENABLE_DEFAULT);
lp5562_wait_enable_done();
lp5562_stop_engine(chip);
lp55xx_stop_all_engine(chip);
lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM);
lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT);
lp5562_wait_opmode_done();
return;
}
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read(chip, LP5562_REG_OP_MODE, &mode);
if (ret)
return;
ret = lp55xx_read(chip, LP5562_REG_ENABLE, &exec);
if (ret)
return;
/* change operation mode to RUN only when each engine is loading */
if (LP5562_ENG1_IS_LOADING(mode)) {
mode = (mode & ~LP5562_MODE_ENG1_M) | LP5562_RUN_ENG1;
exec = (exec & ~LP5562_EXEC_ENG1_M) | LP5562_RUN_ENG1;
}
if (LP5562_ENG2_IS_LOADING(mode)) {
mode = (mode & ~LP5562_MODE_ENG2_M) | LP5562_RUN_ENG2;
exec = (exec & ~LP5562_EXEC_ENG2_M) | LP5562_RUN_ENG2;
}
if (LP5562_ENG3_IS_LOADING(mode)) {
mode = (mode & ~LP5562_MODE_ENG3_M) | LP5562_RUN_ENG3;
exec = (exec & ~LP5562_EXEC_ENG3_M) | LP5562_RUN_ENG3;
}
lp55xx_write(chip, LP5562_REG_OP_MODE, mode);
lp5562_wait_opmode_done();
lp55xx_update_bits(chip, LP5562_REG_ENABLE, LP5562_EXEC_M, exec);
lp5562_wait_enable_done();
}
static int lp5562_update_firmware(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
enum lp55xx_engine_index idx = chip->engine_idx;
u8 pattern[LP5562_PROGRAM_LENGTH] = {0};
static const u8 addr[] = {
[LP55XX_ENGINE_1] = LP5562_REG_PROG_MEM_ENG1,
[LP55XX_ENGINE_2] = LP5562_REG_PROG_MEM_ENG2,
[LP55XX_ENGINE_3] = LP5562_REG_PROG_MEM_ENG3,
};
unsigned cmd;
char c[3];
int program_size;
int nrchars;
int offset = 0;
int ret;
int i;
/* clear program memory before updating */
for (i = 0; i < LP5562_PROGRAM_LENGTH; i++)
lp55xx_write(chip, addr[idx] + i, 0);
i = 0;
while ((offset < size - 1) && (i < LP5562_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
program_size = i;
for (i = 0; i < program_size; i++)
lp55xx_write(chip, addr[idx] + i, pattern[i]);
return 0;
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
static void lp5562_firmware_loaded(struct lp55xx_chip *chip)
{
const struct firmware *fw = chip->fw;
/*
* the firmware is encoded in ascii hex character, with 2 chars
* per byte
*/
if (fw->size > (LP5562_PROGRAM_LENGTH * 2)) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
/*
* Program memory sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp5562_load_engine(chip);
lp5562_update_firmware(chip, fw->data, fw->size);
ret = lp55xx_run_engine_common(chip);
if (!ret)
lp5562_wait_enable_done();
}
static int lp5562_post_init_device(struct lp55xx_chip *chip)
@ -323,9 +172,9 @@ static int lp5562_led_brightness(struct lp55xx_led *led)
};
int ret;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
ret = lp55xx_write(chip, addr[led->chan_nr], led->brightness);
mutex_unlock(&chip->lock);
return ret;
}
@ -348,9 +197,9 @@ static void lp5562_write_program_memory(struct lp55xx_chip *chip,
/* check the size of program count */
static inline bool _is_pc_overflow(struct lp55xx_predef_pattern *ptn)
{
return ptn->size_r >= LP5562_PROGRAM_LENGTH ||
ptn->size_g >= LP5562_PROGRAM_LENGTH ||
ptn->size_b >= LP5562_PROGRAM_LENGTH;
return ptn->size_r >= LP55xx_BYTES_PER_PAGE ||
ptn->size_g >= LP55xx_BYTES_PER_PAGE ||
ptn->size_b >= LP55xx_BYTES_PER_PAGE;
}
static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
@ -369,7 +218,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
return -EINVAL;
}
lp5562_stop_engine(chip);
lp55xx_stop_all_engine(chip);
/* Set LED map as RGB */
lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB);
@ -377,7 +226,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
/* Load engines */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
chip->engine_idx = i;
lp5562_load_engine(chip);
lp55xx_load_engine(chip);
}
/* Clear program registers */
@ -420,9 +269,9 @@ static ssize_t lp5562_store_pattern(struct device *dev,
if (mode > num_patterns || !ptn)
return -EINVAL;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
ret = lp5562_run_predef_led_pattern(chip, mode);
mutex_unlock(&chip->lock);
if (ret)
return ret;
@ -472,9 +321,9 @@ static ssize_t lp5562_store_engine_mux(struct device *dev,
return -EINVAL;
}
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val);
mutex_unlock(&chip->lock);
return len;
}
@ -495,6 +344,12 @@ static const struct attribute_group lp5562_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5562_cfg = {
.max_channel = LP5562_MAX_LEDS,
.reg_op_mode = {
.addr = LP5562_REG_OP_MODE,
},
.reg_exec = {
.addr = LP5562_REG_ENABLE,
},
.reset = {
.addr = LP5562_REG_RESET,
.val = LP5562_RESET,
@ -503,94 +358,25 @@ static struct lp55xx_device_config lp5562_cfg = {
.addr = LP5562_REG_ENABLE,
.val = LP5562_ENABLE_DEFAULT,
},
.prog_mem_base = {
.addr = LP5562_REG_PROG_MEM_ENG1,
},
.post_init_device = lp5562_post_init_device,
.set_led_current = lp5562_set_led_current,
.brightness_fn = lp5562_led_brightness,
.run_engine = lp5562_run_engine,
.firmware_cb = lp5562_firmware_loaded,
.firmware_cb = lp55xx_firmware_loaded_cb,
.dev_attr_group = &lp5562_group,
};
static int lp5562_probe(struct i2c_client *client)
{
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = dev_of_node(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp5562_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
mutex_init(&chip->lock);
i2c_set_clientdata(client, led);
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_out;
}
return 0;
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static void lp5562_remove(struct i2c_client *client)
{
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp5562_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_deinit_device(chip);
}
static const struct i2c_device_id lp5562_id[] = {
{ "lp5562", 0 },
{ "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5562_id);
static const struct of_device_id of_lp5562_leds_match[] = {
{ .compatible = "ti,lp5562", },
{ .compatible = "ti,lp5562", .data = &lp5562_cfg, },
{},
};
@ -601,8 +387,8 @@ static struct i2c_driver lp5562_driver = {
.name = "lp5562",
.of_match_table = of_lp5562_leds_match,
},
.probe = lp5562_probe,
.remove = lp5562_remove,
.probe = lp55xx_probe,
.remove = lp55xx_remove,
.id_table = lp5562_id,
};

544
drivers/leds/leds-lp5569.c Normal file
View File

@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/iopoll.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#include <dt-bindings/leds/leds-lp55xx.h>
#include "leds-lp55xx-common.h"
#define LP5569_MAX_LEDS 9
/* Memory is used like this:
* 0x00 engine 1 program (4 pages)
* 0x40 engine 2 program (4 pages)
* 0x80 engine 3 program (4 pages)
* 0xc0 engine 1 muxing info (1 page)
* 0xd0 engine 2 muxing info (1 page)
* 0xe0 engine 3 muxing info (1 page)
*/
#define LP5569_PAGES_PER_ENGINE 4
#define LP5569_REG_ENABLE 0x00
#define LP5569_ENABLE BIT(6)
#define LP5569_REG_EXEC_CTRL 0x01
#define LP5569_MODE_ENG_SHIFT 2
#define LP5569_REG_OP_MODE 0x02
#define LP5569_EXEC_ENG_SHIFT 2
#define LP5569_REG_ENABLE_LEDS_MSB 0x04
#define LP5569_REG_ENABLE_LEDS_LSB 0x05
#define LP5569_REG_LED_CTRL_BASE 0x07
#define LP5569_FADER_MAPPING_MASK GENMASK(7, 5)
#define LP5569_REG_LED_PWM_BASE 0x16
#define LP5569_REG_LED_CURRENT_BASE 0x22
#define LP5569_REG_MISC 0x2F
#define LP5569_AUTO_INC BIT(6)
#define LP5569_PWR_SAVE BIT(5)
#define LP5569_CP_MODE_MASK GENMASK(4, 3)
#define LP5569_PWM_PWR_SAVE BIT(2)
#define LP5569_INTERNAL_CLK BIT(0)
#define LP5569_REG_MISC2 0x33
#define LP5569_LED_SHORT_TEST BIT(4)
#define LP5569_LED_OPEN_TEST BIT(3)
#define LP5569_REG_STATUS 0x3C
#define LP5569_MASK_BUSY BIT(7)
#define LP5569_STARTUP_BUSY BIT(6)
#define LP5569_ENGINE_BUSY BIT(5)
#define LP5569_ENGINE1_INT BIT(2)
#define LP5569_ENGINE2_INT BIT(1)
#define LP5569_ENGINE3_INT BIT(0)
#define LP5569_ENG_STATUS_MASK (LP5569_ENGINE1_INT | LP5569_ENGINE2_INT | \
LP5569_ENGINE3_INT)
#define LP5569_REG_IO_CONTROL 0x3D
#define LP5569_CLK_OUTPUT BIT(3)
#define LP5569_REG_RESET 0x3F
#define LP5569_RESET 0xFF
#define LP5569_REG_MASTER_FADER_BASE 0x46
#define LP5569_REG_CH1_PROG_START 0x4B
#define LP5569_REG_CH2_PROG_START 0x4C
#define LP5569_REG_CH3_PROG_START 0x4D
#define LP5569_REG_PROG_PAGE_SEL 0x4F
#define LP5569_REG_PROG_MEM 0x50
#define LP5569_REG_LED_FAULT1 0x81
#define LP5569_LED_FAULT8 BIT(0)
#define LP5569_REG_LED_FAULT2 0x82
#define LP5569_LED_FAULT7 BIT(7)
#define LP5569_LED_FAULT6 BIT(6)
#define LP5569_LED_FAULT5 BIT(5)
#define LP5569_LED_FAULT4 BIT(4)
#define LP5569_LED_FAULT3 BIT(3)
#define LP5569_LED_FAULT2 BIT(2)
#define LP5569_LED_FAULT1 BIT(1)
#define LP5569_LED_FAULT0 BIT(0)
#define LP5569_ENG1_PROG_ADDR 0x0
#define LP5569_ENG2_PROG_ADDR 0x40
#define LP5569_ENG3_PROG_ADDR 0x80
#define LP5569_ENG1_MUX_ADDR 0xc0
#define LP5569_ENG2_MUX_ADDR 0xd0
#define LP5569_ENG3_MUX_ADDR 0xe0
#define LP5569_STARTUP_SLEEP 500
#define LEDn_STATUS_FAULT(n, status) ((status) >> (n) & BIT(0))
#define LP5569_DEFAULT_CONFIG \
(LP5569_AUTO_INC | LP5569_PWR_SAVE | LP5569_PWM_PWR_SAVE)
static void lp5569_run_engine(struct lp55xx_chip *chip, bool start)
{
if (!start) {
lp55xx_stop_engine(chip);
lp55xx_turn_off_channels(chip);
return;
}
lp55xx_run_engine_common(chip);
}
static int lp5569_init_program_engine(struct lp55xx_chip *chip)
{
int i;
int j;
int ret;
u8 status;
/* Precompiled pattern per ENGINE setting LED MUX start and stop addresses */
static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = {
{ 0x9c, LP5569_ENG1_MUX_ADDR, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, LP5569_ENG2_MUX_ADDR, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, LP5569_ENG3_MUX_ADDR, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
};
/* Setup each ENGINE program start address */
ret = lp55xx_write(chip, LP5569_REG_CH1_PROG_START, LP5569_ENG1_PROG_ADDR);
if (ret)
return ret;
ret = lp55xx_write(chip, LP5569_REG_CH2_PROG_START, LP5569_ENG2_PROG_ADDR);
if (ret)
return ret;
ret = lp55xx_write(chip, LP5569_REG_CH3_PROG_START, LP5569_ENG3_PROG_ADDR);
if (ret)
return ret;
/* Write precompiled pattern for LED MUX address space for each ENGINE */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
chip->engine_idx = i;
lp55xx_load_engine(chip);
for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + j,
pattern[i - 1][j]);
if (ret)
goto out;
}
}
lp5569_run_engine(chip, true);
/* Let the programs run for couple of ms and check the engine status */
usleep_range(3000, 6000);
lp55xx_read(chip, LP5569_REG_STATUS, &status);
status = FIELD_GET(LP5569_ENG_STATUS_MASK, status);
if (status != LP5569_ENG_STATUS_MASK) {
dev_err(&chip->cl->dev,
"could not configure LED engine, status = 0x%.2x\n",
status);
ret = -EINVAL;
}
out:
lp55xx_stop_all_engine(chip);
return ret;
}
static int lp5569_post_init_device(struct lp55xx_chip *chip)
{
int ret;
u8 val;
val = LP5569_DEFAULT_CONFIG;
val |= FIELD_PREP(LP5569_CP_MODE_MASK, chip->pdata->charge_pump_mode);
ret = lp55xx_write(chip, LP5569_REG_MISC, val);
if (ret)
return ret;
if (chip->pdata->clock_mode == LP55XX_CLOCK_INT) {
/* Internal clock MUST be configured before CLK output */
ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
LP5569_INTERNAL_CLK,
LP5569_INTERNAL_CLK);
if (ret)
return ret;
ret = lp55xx_update_bits(chip, LP5569_REG_IO_CONTROL,
LP5569_CLK_OUTPUT,
LP5569_CLK_OUTPUT);
if (ret)
return ret;
}
ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
if (ret)
return ret;
read_poll_timeout(lp55xx_read, ret, !(val & LP5569_STARTUP_BUSY),
LP5569_STARTUP_SLEEP, LP5569_STARTUP_SLEEP * 10, false,
chip, LP5569_REG_STATUS, &val);
return lp5569_init_program_engine(chip);
}
static ssize_t lp5569_led_open_test(struct lp55xx_led *led, char *buf)
{
struct lp55xx_chip *chip = led->chip;
struct lp55xx_platform_data *pdata = chip->pdata;
bool leds_fault[LP5569_MAX_LEDS];
struct lp55xx_led *led_tmp = led;
int i, ret, pos = 0;
u8 status;
/* Set in STANDBY state */
ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
if (ret)
goto exit;
/* Wait 1ms for device to enter STANDBY state */
usleep_range(1000, 2000);
/* Set Charge Pump to 1.5x */
ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BOOST),
LP5569_CP_MODE_MASK);
if (ret)
goto exit;
/* Enable LED Open Test */
ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
LP5569_LED_OPEN_TEST);
if (ret)
goto exit;
/* Put Device in NORMAL state */
ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
if (ret)
goto exit;
/* Wait 500 us for device to enter NORMAL state */
usleep_range(500, 750);
/* Enable LED and set to 100% brightness */
for (i = 0; i < pdata->num_channels; i++) {
ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
LED_FULL);
if (ret)
goto exit;
led_tmp++;
}
/* Wait 500 us for device to fill status regs */
usleep_range(500, 750);
/* Parse status led fault 1 regs */
ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
if (ret < 0)
goto exit;
for (i = 0; i < 8; i++)
leds_fault[i] = !!((status >> i) & 0x1);
/* Parse status led fault 2 regs */
ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
if (ret < 0)
goto exit;
for (i = 0; i < 1; i++)
leds_fault[i + 8] = !!((status >> i) & 0x1);
/* Report LED fault */
led_tmp = led;
for (i = 0; i < pdata->num_channels; i++) {
if (leds_fault[led_tmp->chan_nr])
pos += sysfs_emit_at(buf, pos, "LED %d OPEN FAIL\n",
led_tmp->chan_nr);
led_tmp++;
}
ret = pos;
exit:
/* Disable LED Open Test */
lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, 0);
led_tmp = led;
for (i = 0; i < pdata->num_channels; i++) {
lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
led_tmp++;
}
return ret;
}
static ssize_t lp5569_led_short_test(struct lp55xx_led *led, char *buf)
{
struct lp55xx_chip *chip = led->chip;
struct lp55xx_platform_data *pdata = chip->pdata;
bool leds_fault[LP5569_MAX_LEDS];
struct lp55xx_led *led_tmp = led;
int i, ret, pos = 0;
u8 status;
/* Set in STANDBY state */
ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
if (ret)
goto exit;
/* Wait 1ms for device to enter STANDBY state */
usleep_range(1000, 2000);
/* Set Charge Pump to 1x */
ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BYPASS),
LP5569_CP_MODE_MASK);
if (ret)
goto exit;
/* Enable LED and set to 100% brightness and current to 100% (25.5mA) */
for (i = 0; i < pdata->num_channels; i++) {
ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
LED_FULL);
if (ret)
goto exit;
ret = lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led_tmp->chan_nr,
LED_FULL);
if (ret)
goto exit;
led_tmp++;
}
/* Put Device in NORMAL state */
ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
if (ret)
goto exit;
/* Wait 500 us for device to enter NORMAL state */
usleep_range(500, 750);
/* Enable LED Shorted Test */
ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
LP5569_LED_SHORT_TEST);
if (ret)
goto exit;
/* Wait 500 us for device to fill status regs */
usleep_range(500, 750);
/* Parse status led fault 1 regs */
ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
if (ret < 0)
goto exit;
for (i = 0; i < 8; i++)
leds_fault[i] = !!LEDn_STATUS_FAULT(i, status);
/* Parse status led fault 2 regs */
ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
if (ret < 0)
goto exit;
for (i = 0; i < 1; i++)
leds_fault[i + 8] = !!LEDn_STATUS_FAULT(i, status);
/* Report LED fault */
led_tmp = led;
for (i = 0; i < pdata->num_channels; i++) {
if (leds_fault[led_tmp->chan_nr])
pos += sysfs_emit_at(buf, pos, "LED %d SHORTED FAIL\n",
led_tmp->chan_nr);
led_tmp++;
}
ret = pos;
exit:
/* Disable LED Shorted Test */
lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_SHORT_TEST, 0);
led_tmp = led;
for (i = 0; i < pdata->num_channels; i++) {
lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
led_tmp++;
}
return ret;
}
static ssize_t lp5569_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int i, pos = 0;
guard(mutex)(&chip->lock);
/* Test LED Open */
pos = lp5569_led_open_test(led, buf);
if (pos < 0)
return sprintf(buf, "FAIL\n");
/* Test LED Shorted */
pos += lp5569_led_short_test(led, buf);
if (pos < 0)
return sprintf(buf, "FAIL\n");
for (i = 0; i < chip->pdata->num_channels; i++) {
/* Restore current */
lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led->chan_nr,
led->led_current);
/* Restore brightness */
lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
led++;
}
return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
}
LP55XX_DEV_ATTR_ENGINE_MODE(1);
LP55XX_DEV_ATTR_ENGINE_MODE(2);
LP55XX_DEV_ATTR_ENGINE_MODE(3);
LP55XX_DEV_ATTR_ENGINE_LEDS(1);
LP55XX_DEV_ATTR_ENGINE_LEDS(2);
LP55XX_DEV_ATTR_ENGINE_LEDS(3);
LP55XX_DEV_ATTR_ENGINE_LOAD(1);
LP55XX_DEV_ATTR_ENGINE_LOAD(2);
LP55XX_DEV_ATTR_ENGINE_LOAD(3);
static LP55XX_DEV_ATTR_RO(selftest, lp5569_selftest);
LP55XX_DEV_ATTR_MASTER_FADER(1);
LP55XX_DEV_ATTR_MASTER_FADER(2);
LP55XX_DEV_ATTR_MASTER_FADER(3);
static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds,
lp55xx_store_master_fader_leds);
static struct attribute *lp5569_attributes[] = {
&dev_attr_engine1_mode.attr,
&dev_attr_engine2_mode.attr,
&dev_attr_engine3_mode.attr,
&dev_attr_engine1_load.attr,
&dev_attr_engine2_load.attr,
&dev_attr_engine3_load.attr,
&dev_attr_engine1_leds.attr,
&dev_attr_engine2_leds.attr,
&dev_attr_engine3_leds.attr,
&dev_attr_selftest.attr,
&dev_attr_master_fader1.attr,
&dev_attr_master_fader2.attr,
&dev_attr_master_fader3.attr,
&dev_attr_master_fader_leds.attr,
NULL,
};
static const struct attribute_group lp5569_group = {
.attrs = lp5569_attributes,
};
/* Chip specific configurations */
static struct lp55xx_device_config lp5569_cfg = {
.reg_op_mode = {
.addr = LP5569_REG_OP_MODE,
.shift = LP5569_MODE_ENG_SHIFT,
},
.reg_exec = {
.addr = LP5569_REG_EXEC_CTRL,
.shift = LP5569_EXEC_ENG_SHIFT,
},
.reset = {
.addr = LP5569_REG_RESET,
.val = LP5569_RESET,
},
.enable = {
.addr = LP5569_REG_ENABLE,
.val = LP5569_ENABLE,
},
.prog_mem_base = {
.addr = LP5569_REG_PROG_MEM,
},
.reg_led_pwm_base = {
.addr = LP5569_REG_LED_PWM_BASE,
},
.reg_led_current_base = {
.addr = LP5569_REG_LED_CURRENT_BASE,
},
.reg_master_fader_base = {
.addr = LP5569_REG_MASTER_FADER_BASE,
},
.reg_led_ctrl_base = {
.addr = LP5569_REG_LED_CTRL_BASE,
},
.pages_per_engine = LP5569_PAGES_PER_ENGINE,
.max_channel = LP5569_MAX_LEDS,
.post_init_device = lp5569_post_init_device,
.brightness_fn = lp55xx_led_brightness,
.multicolor_brightness_fn = lp55xx_multicolor_brightness,
.set_led_current = lp55xx_set_led_current,
.firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp5569_run_engine,
.dev_attr_group = &lp5569_group,
};
static const struct i2c_device_id lp5569_id[] = {
{ "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5569_id);
static const struct of_device_id of_lp5569_leds_match[] = {
{ .compatible = "ti,lp5569", .data = &lp5569_cfg, },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5569_leds_match);
static struct i2c_driver lp5569_driver = {
.driver = {
.name = "lp5569",
.of_match_table = of_lp5569_leds_match,
},
.probe = lp55xx_probe,
.remove = lp55xx_remove,
.id_table = lp5569_id,
};
module_i2c_driver(lp5569_driver);
MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
MODULE_DESCRIPTION("LP5569 LED engine");
MODULE_LICENSE("GPL");

View File

@ -9,10 +9,13 @@
* Derived from leds-lp5521.c, leds-lp5523.c
*/
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/iopoll.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_data/leds-lp55xx.h>
@ -22,6 +25,50 @@
#include "leds-lp55xx-common.h"
/* OP MODE require at least 153 us to clear regs */
#define LP55XX_CMD_SLEEP 200
#define LP55xx_PROGRAM_PAGES 16
#define LP55xx_MAX_PROGRAM_LENGTH (LP55xx_BYTES_PER_PAGE * 4) /* 128 bytes (4 pages) */
/*
* Program Memory Operations
* Same Mask for each engine for both mode and exec
* ENG1 GENMASK(3, 2)
* ENG2 GENMASK(5, 4)
* ENG3 GENMASK(7, 6)
*/
#define LP55xx_MODE_DISABLE_ALL_ENG 0x0
#define LP55xx_MODE_ENG_MASK GENMASK(1, 0)
#define LP55xx_MODE_DISABLE_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x0)
#define LP55xx_MODE_LOAD_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x1)
#define LP55xx_MODE_RUN_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x2)
#define LP55xx_MODE_HALT_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x3)
#define LP55xx_MODE_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
#define LP55xx_MODE_ENGn_MASK(n, shift) (LP55xx_MODE_ENG_MASK << LP55xx_MODE_ENGn_SHIFT(n, shift))
#define LP55xx_MODE_ENGn_GET(n, mode, shift) \
(((mode) >> LP55xx_MODE_ENGn_SHIFT(n, shift)) & LP55xx_MODE_ENG_MASK)
#define LP55xx_EXEC_ENG_MASK GENMASK(1, 0)
#define LP55xx_EXEC_HOLD_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x0)
#define LP55xx_EXEC_STEP_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x1)
#define LP55xx_EXEC_RUN_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x2)
#define LP55xx_EXEC_ONCE_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x3)
#define LP55xx_EXEC_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
#define LP55xx_EXEC_ENGn_MASK(n, shift) (LP55xx_EXEC_ENG_MASK << LP55xx_EXEC_ENGn_SHIFT(n, shift))
/* Memory Page Selection */
#define LP55xx_REG_PROG_PAGE_SEL 0x4f
/* If supported, each ENGINE have an equal amount of pages offset from page 0 */
#define LP55xx_PAGE_OFFSET(n, pages) (((n) - 1) * (pages))
#define LED_ACTIVE(mux, led) (!!((mux) & (0x0001 << (led))))
/* MASTER FADER common property */
#define LP55xx_FADER_MAPPING_MASK GENMASK(7, 6)
/* External clock rate */
#define LP55XX_CLK_32K 32768
@ -40,9 +87,259 @@ static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
return container_of(mc_cdev, struct lp55xx_led, mc_cdev);
}
static void lp55xx_wait_opmode_done(struct lp55xx_chip *chip)
{
const struct lp55xx_device_config *cfg = chip->cfg;
int __always_unused ret;
u8 val;
/*
* Recent chip supports BUSY bit for engine.
* Check support by checking if val is not 0.
* For legacy device, sleep at least 153 us.
*/
if (cfg->engine_busy.val) {
read_poll_timeout(lp55xx_read, ret, !(val & cfg->engine_busy.mask),
LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 10, false,
chip, cfg->engine_busy.addr, &val);
} else {
usleep_range(LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 2);
}
}
void lp55xx_stop_all_engine(struct lp55xx_chip *chip)
{
const struct lp55xx_device_config *cfg = chip->cfg;
lp55xx_write(chip, cfg->reg_op_mode.addr, LP55xx_MODE_DISABLE_ALL_ENG);
lp55xx_wait_opmode_done(chip);
}
EXPORT_SYMBOL_GPL(lp55xx_stop_all_engine);
void lp55xx_load_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
const struct lp55xx_device_config *cfg = chip->cfg;
u8 mask, val;
mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
val = LP55xx_MODE_LOAD_ENG << LP55xx_MODE_ENGn_SHIFT(idx, cfg->reg_op_mode.shift);
lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, val);
lp55xx_wait_opmode_done(chip);
/* Setup PAGE if supported (pages_per_engine not 0)*/
if (cfg->pages_per_engine)
lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine));
}
EXPORT_SYMBOL_GPL(lp55xx_load_engine);
int lp55xx_run_engine_common(struct lp55xx_chip *chip)
{
const struct lp55xx_device_config *cfg = chip->cfg;
u8 mode, exec;
int i, ret;
/* To run the engine, both OP MODE and EXEC needs to be put in RUN mode */
ret = lp55xx_read(chip, cfg->reg_op_mode.addr, &mode);
if (ret)
return ret;
ret = lp55xx_read(chip, cfg->reg_exec.addr, &exec);
if (ret)
return ret;
/* Switch to RUN only for engine that were put in LOAD previously */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
if (LP55xx_MODE_ENGn_GET(i, mode, cfg->reg_op_mode.shift) != LP55xx_MODE_LOAD_ENG)
continue;
mode &= ~LP55xx_MODE_ENGn_MASK(i, cfg->reg_op_mode.shift);
mode |= LP55xx_MODE_RUN_ENG << LP55xx_MODE_ENGn_SHIFT(i, cfg->reg_op_mode.shift);
exec &= ~LP55xx_EXEC_ENGn_MASK(i, cfg->reg_exec.shift);
exec |= LP55xx_EXEC_RUN_ENG << LP55xx_EXEC_ENGn_SHIFT(i, cfg->reg_exec.shift);
}
lp55xx_write(chip, cfg->reg_op_mode.addr, mode);
lp55xx_wait_opmode_done(chip);
lp55xx_write(chip, cfg->reg_exec.addr, exec);
return 0;
}
EXPORT_SYMBOL_GPL(lp55xx_run_engine_common);
int lp55xx_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
enum lp55xx_engine_index idx = chip->engine_idx;
const struct lp55xx_device_config *cfg = chip->cfg;
u8 pattern[LP55xx_MAX_PROGRAM_LENGTH] = { };
u8 start_addr = cfg->prog_mem_base.addr;
int page, i = 0, offset = 0;
int program_length, ret;
program_length = LP55xx_BYTES_PER_PAGE;
if (cfg->pages_per_engine)
program_length *= cfg->pages_per_engine;
while ((offset < size - 1) && (i < program_length)) {
unsigned int cmd;
int nrchars;
char c[3];
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
/*
* For legacy LED chip with no page support, engine base address are
* one after another at offset of 32.
* For LED chip that support page, PAGE is already set in load_engine.
*/
if (!cfg->pages_per_engine)
start_addr += LP55xx_BYTES_PER_PAGE * idx;
for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) {
/* Write to the next page each 32 bytes (if supported) */
if (cfg->pages_per_engine)
lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine) + page);
for (i = 0; i < LP55xx_BYTES_PER_PAGE; i++) {
ret = lp55xx_write(chip, start_addr + i,
pattern[i + (page * LP55xx_BYTES_PER_PAGE)]);
if (ret)
return -EINVAL;
}
}
return size;
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
EXPORT_SYMBOL_GPL(lp55xx_update_program_memory);
void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip)
{
const struct lp55xx_device_config *cfg = chip->cfg;
const struct firmware *fw = chip->fw;
int program_length;
program_length = LP55xx_BYTES_PER_PAGE;
if (cfg->pages_per_engine)
program_length *= cfg->pages_per_engine;
/*
* the firmware is encoded in ascii hex character, with 2 chars
* per byte
*/
if (fw->size > program_length * 2) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
/*
* Program memory sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp55xx_load_engine(chip);
lp55xx_update_program_memory(chip, fw->data, fw->size);
}
EXPORT_SYMBOL_GPL(lp55xx_firmware_loaded_cb);
int lp55xx_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
guard(mutex)(&chip->lock);
ret = lp55xx_write(chip, cfg->reg_led_pwm_base.addr + led->chan_nr,
led->brightness);
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_led_brightness);
int lp55xx_multicolor_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
int i;
guard(mutex)(&chip->lock);
for (i = 0; i < led->mc_cdev.num_colors; i++) {
ret = lp55xx_write(chip,
cfg->reg_led_pwm_base.addr +
led->mc_cdev.subled_info[i].channel,
led->mc_cdev.subled_info[i].brightness);
if (ret)
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_multicolor_brightness);
void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current)
{
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
led->led_current = led_current;
lp55xx_write(led->chip, cfg->reg_led_current_base.addr + led->chan_nr,
led_current);
}
EXPORT_SYMBOL_GPL(lp55xx_set_led_current);
void lp55xx_turn_off_channels(struct lp55xx_chip *chip)
{
const struct lp55xx_device_config *cfg = chip->cfg;
int i;
for (i = 0; i < cfg->max_channel; i++)
lp55xx_write(chip, cfg->reg_led_pwm_base.addr + i, 0);
}
EXPORT_SYMBOL_GPL(lp55xx_turn_off_channels);
void lp55xx_stop_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
const struct lp55xx_device_config *cfg = chip->cfg;
u8 mask;
mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, 0);
lp55xx_wait_opmode_done(chip);
}
EXPORT_SYMBOL_GPL(lp55xx_stop_engine);
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->reset.addr;
u8 val = cfg->reset.val;
@ -52,7 +349,7 @@ static void lp55xx_reset_device(struct lp55xx_chip *chip)
static int lp55xx_detect_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->enable.addr;
u8 val = cfg->enable.val;
int ret;
@ -75,7 +372,7 @@ static int lp55xx_detect_device(struct lp55xx_chip *chip)
static int lp55xx_post_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
if (!cfg->post_init_device)
return 0;
@ -109,9 +406,9 @@ static ssize_t led_current_store(struct device *dev,
if (!chip->cfg->set_led_current)
return len;
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
chip->cfg->set_led_current(led, (u8)curr);
mutex_unlock(&chip->lock);
return len;
}
@ -140,7 +437,7 @@ static int lp55xx_set_mc_brightness(struct led_classdev *cdev,
{
struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
struct lp55xx_led *led = mcled_cdev_to_led(mc_dev);
struct lp55xx_device_config *cfg = led->chip->cfg;
const struct lp55xx_device_config *cfg = led->chip->cfg;
led_mc_calc_color_components(&led->mc_cdev, brightness);
return cfg->multicolor_brightness_fn(led);
@ -151,7 +448,7 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
struct lp55xx_device_config *cfg = led->chip->cfg;
const struct lp55xx_device_config *cfg = led->chip->cfg;
led->brightness = (u8)brightness;
return cfg->brightness_fn(led);
@ -161,7 +458,7 @@ static int lp55xx_init_led(struct lp55xx_led *led,
struct lp55xx_chip *chip, int chan)
{
struct lp55xx_platform_data *pdata = chip->pdata;
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
struct device *dev = &chip->cl->dev;
int max_channel = cfg->max_channel;
struct mc_subled *mc_led_info;
@ -246,14 +543,12 @@ static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
}
/* handling firmware data is chip dependent */
mutex_lock(&chip->lock);
chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
chip->fw = fw;
if (chip->cfg->firmware_cb)
chip->cfg->firmware_cb(chip);
mutex_unlock(&chip->lock);
scoped_guard(mutex, &chip->lock) {
chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
chip->fw = fw;
if (chip->cfg->firmware_cb)
chip->cfg->firmware_cb(chip);
}
/* firmware should be released for other channel use */
release_firmware(chip->fw);
@ -270,8 +565,8 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
}
static ssize_t select_engine_show(struct device *dev,
struct device_attribute *attr,
char *buf)
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@ -280,8 +575,8 @@ static ssize_t select_engine_show(struct device *dev,
}
static ssize_t select_engine_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@ -297,10 +592,10 @@ static ssize_t select_engine_store(struct device *dev,
case LP55XX_ENGINE_1:
case LP55XX_ENGINE_2:
case LP55XX_ENGINE_3:
mutex_lock(&chip->lock);
chip->engine_idx = val;
ret = lp55xx_request_firmware(chip);
mutex_unlock(&chip->lock);
scoped_guard(mutex, &chip->lock) {
chip->engine_idx = val;
ret = lp55xx_request_firmware(chip);
}
break;
default:
dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
@ -322,8 +617,8 @@ static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
}
static ssize_t run_engine_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@ -339,9 +634,9 @@ static ssize_t run_engine_store(struct device *dev,
return len;
}
mutex_lock(&chip->lock);
guard(mutex)(&chip->lock);
lp55xx_run_engine(chip, true);
mutex_unlock(&chip->lock);
return len;
}
@ -349,6 +644,279 @@ static ssize_t run_engine_store(struct device *dev,
static DEVICE_ATTR_RW(select_engine);
static DEVICE_ATTR_WO(run_engine);
ssize_t lp55xx_show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
switch (mode) {
case LP55XX_ENGINE_RUN:
return sysfs_emit(buf, "run\n");
case LP55XX_ENGINE_LOAD:
return sysfs_emit(buf, "load\n");
case LP55XX_ENGINE_DISABLED:
default:
return sysfs_emit(buf, "disabled\n");
}
}
EXPORT_SYMBOL_GPL(lp55xx_show_engine_mode);
ssize_t lp55xx_store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
struct lp55xx_engine *engine = &chip->engines[nr - 1];
guard(mutex)(&chip->lock);
chip->engine_idx = nr;
if (!strncmp(buf, "run", 3)) {
cfg->run_engine(chip, true);
engine->mode = LP55XX_ENGINE_RUN;
} else if (!strncmp(buf, "load", 4)) {
lp55xx_stop_engine(chip);
lp55xx_load_engine(chip);
engine->mode = LP55XX_ENGINE_LOAD;
} else if (!strncmp(buf, "disabled", 8)) {
lp55xx_stop_engine(chip);
engine->mode = LP55XX_ENGINE_DISABLED;
}
return len;
}
EXPORT_SYMBOL_GPL(lp55xx_store_engine_mode);
ssize_t lp55xx_store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
guard(mutex)(&chip->lock);
chip->engine_idx = nr;
lp55xx_load_engine(chip);
ret = lp55xx_update_program_memory(chip, buf, len);
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_store_engine_load);
static int lp55xx_mux_parse(struct lp55xx_chip *chip, const char *buf,
u16 *mux, size_t len)
{
const struct lp55xx_device_config *cfg = chip->cfg;
u16 tmp_mux = 0;
int i;
len = min_t(int, len, cfg->max_channel);
for (i = 0; i < len; i++) {
switch (buf[i]) {
case '1':
tmp_mux |= (1 << i);
break;
case '0':
break;
case '\n':
i = len;
break;
default:
return -1;
}
}
*mux = tmp_mux;
return 0;
}
ssize_t lp55xx_show_engine_leds(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
unsigned int led_active;
int i, pos = 0;
for (i = 0; i < cfg->max_channel; i++) {
led_active = LED_ACTIVE(chip->engines[nr - 1].led_mux, i);
pos += sysfs_emit_at(buf, pos, "%x", led_active);
}
pos += sysfs_emit_at(buf, pos, "\n");
return pos;
}
EXPORT_SYMBOL_GPL(lp55xx_show_engine_leds);
static int lp55xx_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
{
struct lp55xx_engine *engine = &chip->engines[nr - 1];
const struct lp55xx_device_config *cfg = chip->cfg;
u8 mux_page;
int ret;
lp55xx_load_engine(chip);
/* Derive the MUX page offset by starting at the end of the ENGINE pages */
mux_page = cfg->pages_per_engine * LP55XX_ENGINE_MAX + (nr - 1);
ret = lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, mux_page);
if (ret)
return ret;
ret = lp55xx_write(chip, cfg->prog_mem_base.addr, (u8)(mux >> 8));
if (ret)
return ret;
ret = lp55xx_write(chip, cfg->prog_mem_base.addr + 1, (u8)(mux));
if (ret)
return ret;
engine->led_mux = mux;
return 0;
}
ssize_t lp55xx_store_engine_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_engine *engine = &chip->engines[nr - 1];
u16 mux = 0;
if (lp55xx_mux_parse(chip, buf, &mux, len))
return -EINVAL;
guard(mutex)(&chip->lock);
chip->engine_idx = nr;
if (engine->mode != LP55XX_ENGINE_LOAD)
return -EINVAL;
if (lp55xx_load_mux(chip, mux, nr))
return -EINVAL;
return len;
}
EXPORT_SYMBOL_GPL(lp55xx_store_engine_leds);
ssize_t lp55xx_show_master_fader(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
u8 val;
guard(mutex)(&chip->lock);
ret = lp55xx_read(chip, cfg->reg_master_fader_base.addr + nr - 1, &val);
return ret ? ret : sysfs_emit(buf, "%u\n", val);
}
EXPORT_SYMBOL_GPL(lp55xx_show_master_fader);
ssize_t lp55xx_store_master_fader(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
if (val > 0xff)
return -EINVAL;
guard(mutex)(&chip->lock);
ret = lp55xx_write(chip, cfg->reg_master_fader_base.addr + nr - 1,
(u8)val);
return ret ? ret : len;
}
EXPORT_SYMBOL_GPL(lp55xx_store_master_fader);
ssize_t lp55xx_show_master_fader_leds(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int i, ret, pos = 0;
u8 val;
guard(mutex)(&chip->lock);
for (i = 0; i < cfg->max_channel; i++) {
ret = lp55xx_read(chip, cfg->reg_led_ctrl_base.addr + i, &val);
if (ret)
return ret;
val = FIELD_GET(LP55xx_FADER_MAPPING_MASK, val);
if (val > FIELD_MAX(LP55xx_FADER_MAPPING_MASK)) {
return -EINVAL;
}
buf[pos++] = val + '0';
}
buf[pos++] = '\n';
return pos;
}
EXPORT_SYMBOL_GPL(lp55xx_show_master_fader_leds);
ssize_t lp55xx_store_master_fader_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
const struct lp55xx_device_config *cfg = chip->cfg;
int i, n, ret;
u8 val;
n = min_t(int, len, cfg->max_channel);
guard(mutex)(&chip->lock);
for (i = 0; i < n; i++) {
if (buf[i] >= '0' && buf[i] <= '3') {
val = (buf[i] - '0') << __bf_shf(LP55xx_FADER_MAPPING_MASK);
ret = lp55xx_update_bits(chip,
cfg->reg_led_ctrl_base.addr + i,
LP55xx_FADER_MAPPING_MASK,
val);
if (ret)
return ret;
} else {
return -EINVAL;
}
}
return len;
}
EXPORT_SYMBOL_GPL(lp55xx_store_master_fader_leds);
static struct attribute *lp55xx_engine_attributes[] = {
&dev_attr_select_engine.attr,
&dev_attr_run_engine.attr,
@ -423,10 +991,21 @@ use_internal_clk:
}
EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used);
int lp55xx_init_device(struct lp55xx_chip *chip)
static void lp55xx_deinit_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
if (chip->clk)
clk_disable_unprepare(chip->clk);
if (pdata->enable_gpiod)
gpiod_set_value(pdata->enable_gpiod, 0);
}
static int lp55xx_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata;
struct lp55xx_device_config *cfg;
const struct lp55xx_device_config *cfg;
struct device *dev = &chip->cl->dev;
int ret = 0;
@ -476,24 +1055,11 @@ err_post_init:
err:
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_init_device);
void lp55xx_deinit_device(struct lp55xx_chip *chip)
static int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
if (chip->clk)
clk_disable_unprepare(chip->clk);
if (pdata->enable_gpiod)
gpiod_set_value(pdata->enable_gpiod, 0);
}
EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
int num_channels = pdata->num_channels;
struct lp55xx_led *each;
u8 led_current;
@ -530,12 +1096,11 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
err_init_led:
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_register_leds);
int lp55xx_register_sysfs(struct lp55xx_chip *chip)
static int lp55xx_register_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
if (!cfg->run_engine || !cfg->firmware_cb)
@ -549,19 +1114,17 @@ dev_specific_attrs:
return cfg->dev_attr_group ?
sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0;
}
EXPORT_SYMBOL_GPL(lp55xx_register_sysfs);
void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
static void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
struct lp55xx_device_config *cfg = chip->cfg;
const struct lp55xx_device_config *cfg = chip->cfg;
if (cfg->dev_attr_group)
sysfs_remove_group(&dev->kobj, cfg->dev_attr_group);
sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group);
}
EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
static int lp55xx_parse_common_child(struct device_node *np,
struct lp55xx_led_config *cfg,
@ -654,9 +1217,9 @@ static int lp55xx_parse_logical_led(struct device_node *np,
return ret;
}
struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
struct device_node *np,
struct lp55xx_chip *chip)
static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
struct device_node *np,
struct lp55xx_chip *chip)
{
struct device_node *child;
struct lp55xx_platform_data *pdata;
@ -713,7 +1276,92 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
return pdata;
}
EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata);
int lp55xx_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
int program_length, ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = dev_of_node(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = i2c_get_match_data(client);
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
/* Validate max program page */
program_length = LP55xx_BYTES_PER_PAGE;
if (chip->cfg->pages_per_engine)
program_length *= chip->cfg->pages_per_engine;
/* support a max of 128bytes */
if (program_length > LP55xx_MAX_PROGRAM_LENGTH) {
dev_err(&client->dev, "invalid pages_per_engine configured\n");
return -EINVAL;
}
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
mutex_init(&chip->lock);
i2c_set_clientdata(client, led);
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_out;
}
return 0;
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_probe);
void lp55xx_remove(struct i2c_client *client)
{
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp55xx_stop_all_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_deinit_device(chip);
}
EXPORT_SYMBOL_GPL(lp55xx_remove);
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP55xx Common Driver");

View File

@ -14,6 +14,8 @@
#include <linux/led-class-multicolor.h>
#define LP55xx_BYTES_PER_PAGE 32 /* bytes */
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
@ -35,45 +37,62 @@ enum lp55xx_engine_mode {
#define LP55XX_DEV_ATTR_WO(name, store) \
DEVICE_ATTR(name, S_IWUSR, NULL, store)
#define show_mode(nr) \
#define LP55XX_DEV_ATTR_ENGINE_MODE(nr) \
static ssize_t show_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
struct device_attribute *attr, \
char *buf) \
{ \
return show_engine_mode(dev, attr, buf, nr); \
}
#define store_mode(nr) \
return lp55xx_show_engine_mode(dev, attr, buf, nr); \
} \
static ssize_t store_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_mode(dev, attr, buf, len, nr); \
}
return lp55xx_store_engine_mode(dev, attr, buf, len, nr); \
} \
static LP55XX_DEV_ATTR_RW(engine##nr##_mode, show_engine##nr##_mode, \
store_engine##nr##_mode)
#define show_leds(nr) \
#define LP55XX_DEV_ATTR_ENGINE_LEDS(nr) \
static ssize_t show_engine##nr##_leds(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
struct device_attribute *attr, \
char *buf) \
{ \
return show_engine_leds(dev, attr, buf, nr); \
}
return lp55xx_show_engine_leds(dev, attr, buf, nr); \
} \
static ssize_t store_engine##nr##_leds(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return lp55xx_store_engine_leds(dev, attr, buf, len, nr); \
} \
static LP55XX_DEV_ATTR_RW(engine##nr##_leds, show_engine##nr##_leds, \
store_engine##nr##_leds)
#define store_leds(nr) \
static ssize_t store_engine##nr##_leds(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_leds(dev, attr, buf, len, nr); \
}
#define store_load(nr) \
#define LP55XX_DEV_ATTR_ENGINE_LOAD(nr) \
static ssize_t store_engine##nr##_load(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_load(dev, attr, buf, len, nr); \
}
return lp55xx_store_engine_load(dev, attr, buf, len, nr); \
} \
static LP55XX_DEV_ATTR_WO(engine##nr##_load, store_engine##nr##_load)
#define LP55XX_DEV_ATTR_MASTER_FADER(nr) \
static ssize_t show_master_fader##nr(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return lp55xx_show_master_fader(dev, attr, buf, nr); \
} \
static ssize_t store_master_fader##nr(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return lp55xx_store_master_fader(dev, attr, buf, len, nr); \
} \
static LP55XX_DEV_ATTR_RW(master_fader##nr, show_master_fader##nr, \
store_master_fader##nr)
struct lp55xx_led;
struct lp55xx_chip;
@ -81,17 +100,31 @@ struct lp55xx_chip;
/*
* struct lp55xx_reg
* @addr : Register address
* @val : Register value
* @val : Register value (can also used as mask or shift)
*/
struct lp55xx_reg {
u8 addr;
u8 val;
union {
u8 val;
u8 mask;
u8 shift;
};
};
/*
* struct lp55xx_device_config
* @reg_op_mode : Chip specific OP MODE reg addr
* @engine_busy : Chip specific engine busy
* (if not supported 153 us sleep)
* @reset : Chip specific reset command
* @enable : Chip specific enable command
* @prog_mem_base : Chip specific base reg address for chip SMEM programming
* @reg_led_pwm_base : Chip specific base reg address for LED PWM conf
* @reg_led_current_base : Chip specific base reg address for LED current conf
* @reg_master_fader_base : Chip specific base reg address for master fader base
* @reg_led_ctrl_base : Chip specific base reg address for LED ctrl base
* @pages_per_engine : Assigned pages for each engine
* (if not set chip doesn't support pages)
* @max_channel : Maximum number of channels
* @post_init_device : Chip specific initialization code
* @brightness_fn : Brightness function
@ -102,8 +135,17 @@ struct lp55xx_reg {
* @dev_attr_group : Device specific attributes
*/
struct lp55xx_device_config {
const struct lp55xx_reg reg_op_mode; /* addr, shift */
const struct lp55xx_reg reg_exec; /* addr, shift */
const struct lp55xx_reg engine_busy; /* addr, mask */
const struct lp55xx_reg reset;
const struct lp55xx_reg enable;
const struct lp55xx_reg prog_mem_base;
const struct lp55xx_reg reg_led_pwm_base;
const struct lp55xx_reg reg_led_current_base;
const struct lp55xx_reg reg_master_fader_base;
const struct lp55xx_reg reg_led_ctrl_base;
const int pages_per_engine;
const int max_channel;
/* define if the device has specific initialization process */
@ -155,7 +197,7 @@ struct lp55xx_chip {
struct lp55xx_platform_data *pdata;
struct mutex lock; /* lock for user-space interface */
int num_leds;
struct lp55xx_device_config *cfg;
const struct lp55xx_device_config *cfg;
enum lp55xx_engine_index engine_idx;
struct lp55xx_engine engines[LP55XX_ENGINE_MAX];
const struct firmware *fw;
@ -191,21 +233,50 @@ extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg,
/* external clock detection */
extern bool lp55xx_is_extclk_used(struct lp55xx_chip *chip);
/* common device init/deinit functions */
extern int lp55xx_init_device(struct lp55xx_chip *chip);
extern void lp55xx_deinit_device(struct lp55xx_chip *chip);
/* common chip functions */
extern void lp55xx_stop_all_engine(struct lp55xx_chip *chip);
extern void lp55xx_load_engine(struct lp55xx_chip *chip);
extern int lp55xx_run_engine_common(struct lp55xx_chip *chip);
extern int lp55xx_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size);
extern void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip);
extern int lp55xx_led_brightness(struct lp55xx_led *led);
extern int lp55xx_multicolor_brightness(struct lp55xx_led *led);
extern void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current);
extern void lp55xx_turn_off_channels(struct lp55xx_chip *chip);
extern void lp55xx_stop_engine(struct lp55xx_chip *chip);
/* common LED class device functions */
extern int lp55xx_register_leds(struct lp55xx_led *led,
struct lp55xx_chip *chip);
/* common probe/remove function */
extern int lp55xx_probe(struct i2c_client *client);
extern void lp55xx_remove(struct i2c_client *client);
/* common device attributes functions */
extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
/* common device tree population function */
extern struct lp55xx_platform_data
*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np,
struct lp55xx_chip *chip);
/* common sysfs function */
extern ssize_t lp55xx_show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr);
extern ssize_t lp55xx_store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr);
extern ssize_t lp55xx_store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr);
extern ssize_t lp55xx_show_engine_leds(struct device *dev,
struct device_attribute *attr,
char *buf, int nr);
extern ssize_t lp55xx_store_engine_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr);
extern ssize_t lp55xx_show_master_fader(struct device *dev,
struct device_attribute *attr,
char *buf, int nr);
extern ssize_t lp55xx_store_master_fader(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr);
extern ssize_t lp55xx_show_master_fader_leds(struct device *dev,
struct device_attribute *attr,
char *buf);
extern ssize_t lp55xx_store_master_fader_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len);
#endif /* _LEDS_LP55XX_COMMON_H */

View File

@ -20,27 +20,14 @@
#include "leds-lp55xx-common.h"
#define LP8501_PROGRAM_LENGTH 32
#define LP8501_PAGES_PER_ENGINE 1
#define LP8501_MAX_LEDS 9
/* Registers */
#define LP8501_REG_ENABLE 0x00
#define LP8501_ENABLE BIT(6)
#define LP8501_EXEC_M 0x3F
#define LP8501_EXEC_ENG1_M 0x30
#define LP8501_EXEC_ENG2_M 0x0C
#define LP8501_EXEC_ENG3_M 0x03
#define LP8501_RUN_ENG1 0x20
#define LP8501_RUN_ENG2 0x08
#define LP8501_RUN_ENG3 0x02
#define LP8501_REG_OP_MODE 0x01
#define LP8501_MODE_ENG1_M 0x30
#define LP8501_MODE_ENG2_M 0x0C
#define LP8501_MODE_ENG3_M 0x03
#define LP8501_LOAD_ENG1 0x10
#define LP8501_LOAD_ENG2 0x04
#define LP8501_LOAD_ENG3 0x01
#define LP8501_REG_PWR_CONFIG 0x05
#define LP8501_PWR_CONFIG_M 0x03
@ -58,35 +45,14 @@
#define LP8501_INT_CLK BIT(0)
#define LP8501_DEFAULT_CFG (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE)
#define LP8501_REG_STATUS 0x3A
#define LP8501_ENGINE_BUSY BIT(4)
#define LP8501_REG_RESET 0x3D
#define LP8501_RESET 0xFF
#define LP8501_REG_PROG_PAGE_SEL 0x4F
#define LP8501_PAGE_ENG1 0
#define LP8501_PAGE_ENG2 1
#define LP8501_PAGE_ENG3 2
#define LP8501_REG_PROG_MEM 0x50
#define LP8501_ENG1_IS_LOADING(mode) \
((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1)
#define LP8501_ENG2_IS_LOADING(mode) \
((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2)
#define LP8501_ENG3_IS_LOADING(mode) \
((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3)
static inline void lp8501_wait_opmode_done(void)
{
usleep_range(1000, 2000);
}
static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current)
{
led->led_current = led_current;
lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr,
led_current);
}
static int lp8501_post_init_device(struct lp55xx_chip *chip)
{
int ret;
@ -113,178 +79,30 @@ static int lp8501_post_init_device(struct lp55xx_chip *chip)
LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel);
}
static void lp8501_load_engine(struct lp55xx_chip *chip)
{
enum lp55xx_engine_index idx = chip->engine_idx;
static const u8 mask[] = {
[LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M,
[LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M,
[LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M,
};
static const u8 val[] = {
[LP55XX_ENGINE_1] = LP8501_LOAD_ENG1,
[LP55XX_ENGINE_2] = LP8501_LOAD_ENG2,
[LP55XX_ENGINE_3] = LP8501_LOAD_ENG3,
};
static const u8 page_sel[] = {
[LP55XX_ENGINE_1] = LP8501_PAGE_ENG1,
[LP55XX_ENGINE_2] = LP8501_PAGE_ENG2,
[LP55XX_ENGINE_3] = LP8501_PAGE_ENG3,
};
lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]);
lp8501_wait_opmode_done();
lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]);
}
static void lp8501_stop_engine(struct lp55xx_chip *chip)
{
lp55xx_write(chip, LP8501_REG_OP_MODE, 0);
lp8501_wait_opmode_done();
}
static void lp8501_turn_off_channels(struct lp55xx_chip *chip)
{
int i;
for (i = 0; i < LP8501_MAX_LEDS; i++)
lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0);
}
static void lp8501_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
u8 mode;
u8 exec;
/* stop engine */
if (!start) {
lp8501_stop_engine(chip);
lp8501_turn_off_channels(chip);
lp55xx_stop_all_engine(chip);
lp55xx_turn_off_channels(chip);
return;
}
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode);
if (ret)
return;
ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec);
if (ret)
return;
/* change operation mode to RUN only when each engine is loading */
if (LP8501_ENG1_IS_LOADING(mode)) {
mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1;
exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1;
}
if (LP8501_ENG2_IS_LOADING(mode)) {
mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2;
exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2;
}
if (LP8501_ENG3_IS_LOADING(mode)) {
mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3;
exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3;
}
lp55xx_write(chip, LP8501_REG_OP_MODE, mode);
lp8501_wait_opmode_done();
lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec);
}
static int lp8501_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
u8 pattern[LP8501_PROGRAM_LENGTH] = {0};
unsigned cmd;
char c[3];
int update_size;
int nrchars;
int offset = 0;
int ret;
int i;
/* clear program memory before updating */
for (i = 0; i < LP8501_PROGRAM_LENGTH; i++)
lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0);
i = 0;
while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
update_size = i;
for (i = 0; i < update_size; i++)
lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]);
return 0;
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
static void lp8501_firmware_loaded(struct lp55xx_chip *chip)
{
const struct firmware *fw = chip->fw;
if (fw->size > LP8501_PROGRAM_LENGTH) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
/*
* Program memory sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp8501_load_engine(chip);
lp8501_update_program_memory(chip, fw->data, fw->size);
}
static int lp8501_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
ret = lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
return ret;
lp55xx_run_engine_common(chip);
}
/* Chip specific configurations */
static struct lp55xx_device_config lp8501_cfg = {
.reg_op_mode = {
.addr = LP8501_REG_OP_MODE,
},
.reg_exec = {
.addr = LP8501_REG_ENABLE,
},
.engine_busy = {
.addr = LP8501_REG_STATUS,
.mask = LP8501_ENGINE_BUSY,
},
.reset = {
.addr = LP8501_REG_RESET,
.val = LP8501_RESET,
@ -293,95 +111,32 @@ static struct lp55xx_device_config lp8501_cfg = {
.addr = LP8501_REG_ENABLE,
.val = LP8501_ENABLE,
},
.prog_mem_base = {
.addr = LP8501_REG_PROG_MEM,
},
.reg_led_pwm_base = {
.addr = LP8501_REG_LED_PWM_BASE,
},
.reg_led_current_base = {
.addr = LP8501_REG_LED_CURRENT_BASE,
},
.pages_per_engine = LP8501_PAGES_PER_ENGINE,
.max_channel = LP8501_MAX_LEDS,
.post_init_device = lp8501_post_init_device,
.brightness_fn = lp8501_led_brightness,
.set_led_current = lp8501_set_led_current,
.firmware_cb = lp8501_firmware_loaded,
.brightness_fn = lp55xx_led_brightness,
.set_led_current = lp55xx_set_led_current,
.firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp8501_run_engine,
};
static int lp8501_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
struct device_node *np = dev_of_node(&client->dev);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->cfg = &lp8501_cfg;
if (!pdata) {
if (np) {
pdata = lp55xx_of_populate_pdata(&client->dev, np,
chip);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
}
led = devm_kcalloc(&client->dev,
pdata->num_channels, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
mutex_init(&chip->lock);
i2c_set_clientdata(client, led);
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_out;
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto err_out;
}
return 0;
err_out:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static void lp8501_remove(struct i2c_client *client)
{
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp8501_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_deinit_device(chip);
}
static const struct i2c_device_id lp8501_id[] = {
{ "lp8501", 0 },
{ "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp8501_id);
static const struct of_device_id of_lp8501_leds_match[] = {
{ .compatible = "ti,lp8501", },
{ .compatible = "ti,lp8501", .data = &lp8501_cfg, },
{},
};
@ -392,8 +147,8 @@ static struct i2c_driver lp8501_driver = {
.name = "lp8501",
.of_match_table = of_lp8501_leds_match,
},
.probe = lp8501_probe,
.remove = lp8501_remove,
.probe = lp55xx_probe,
.remove = lp55xx_remove,
.id_table = lp8501_id,
};

View File

@ -459,7 +459,7 @@ static void lp8860_remove(struct i2c_client *client)
}
static const struct i2c_device_id lp8860_id[] = {
{ "lp8860", 0 },
{ "lp8860" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp8860_id);

View File

@ -29,6 +29,9 @@
#define LED_SHIFT(led) (LED_NUM(led) * 2)
#define LED_MASK(led) (0x3 << LED_SHIFT(led))
#define PCA9532_PWM_PERIOD_DIV 152
#define PCA9532_PWM_DUTY_DIV 256
#define ldev_to_led(c) container_of(c, struct pca9532_led, ldev)
struct pca9532_chip_info {
@ -45,8 +48,12 @@ struct pca9532_data {
struct gpio_chip gpio;
#endif
const struct pca9532_chip_info *chip_info;
#define PCA9532_PWM_ID_0 0
#define PCA9532_PWM_ID_1 1
u8 pwm[2];
u8 psc[2];
bool hw_blink;
};
static int pca9532_probe(struct i2c_client *client);
@ -181,39 +188,74 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
led->state = PCA9532_ON;
else {
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
err = pca9532_calcpwm(led->client, 0, 0, value);
err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value);
if (err)
return err;
}
if (led->state == PCA9532_PWM0)
pca9532_setpwm(led->client, 0);
pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
pca9532_setled(led);
return err;
}
static int pca9532_update_hw_blink(struct pca9532_led *led,
unsigned long delay_on, unsigned long delay_off)
{
struct pca9532_data *data = i2c_get_clientdata(led->client);
unsigned int psc;
int i;
/* Look for others LEDs that already use PWM1 */
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *other = &data->leds[i];
if (other == led)
continue;
if (other->state == PCA9532_PWM1) {
if (other->ldev.blink_delay_on != delay_on ||
other->ldev.blink_delay_off != delay_off) {
dev_err(&led->client->dev,
"HW can handle only one blink configuration at a time\n");
return -EINVAL;
}
}
}
psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
if (psc > U8_MAX) {
dev_err(&led->client->dev, "Blink period too long to be handled by hardware\n");
return -EINVAL;
}
led->state = PCA9532_PWM1;
data->psc[PCA9532_PWM_ID_1] = psc;
data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
}
static int pca9532_set_blink(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client;
int psc;
int err = 0;
struct pca9532_data *data = i2c_get_clientdata(client);
int err;
if (!data->hw_blink)
return -EINVAL;
if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */
*delay_on = 1000;
*delay_off = 1000;
*delay_on = 500;
*delay_off = 500;
}
if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6)
return -EINVAL;
/* Thecus specific: only use PSC/PWM 0 */
psc = (*delay_on * 152-1)/1000;
err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness);
err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
if (err)
return err;
if (led->state == PCA9532_PWM0)
pca9532_setpwm(led->client, 0);
pca9532_setled(led);
return 0;
@ -229,9 +271,9 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
/* XXX: allow different kind of beeps with psc/pwm modifications */
if (value > 1 && value < 32767)
data->pwm[1] = 127;
data->pwm[PCA9532_PWM_ID_1] = 127;
else
data->pwm[1] = 0;
data->pwm[PCA9532_PWM_ID_1] = 0;
schedule_work(&data->work);
@ -246,7 +288,7 @@ static void pca9532_input_work(struct work_struct *work)
mutex_lock(&data->update_lock);
i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
data->pwm[1]);
data->pwm[PCA9532_PWM_ID_1]);
mutex_unlock(&data->update_lock);
}
@ -359,6 +401,7 @@ static int pca9532_configure(struct i2c_client *client,
data->psc[i]);
}
data->hw_blink = true;
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
@ -393,6 +436,8 @@ static int pca9532_configure(struct i2c_client *client,
pca9532_setled(led);
break;
case PCA9532_TYPE_N2100_BEEP:
/* PWM1 is reserved for beeper so blink will not use hardware */
data->hw_blink = false;
BUG_ON(data->idev);
led->state = PCA9532_PWM1;
pca9532_setled(led);
@ -475,9 +520,9 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
pdata->gpio_base = -1;
of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[0],
of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[PCA9532_PWM_ID_0],
ARRAY_SIZE(pdata->pwm));
of_property_read_u8_array(np, "nxp,psc", &pdata->psc[0],
of_property_read_u8_array(np, "nxp,psc", &pdata->psc[PCA9532_PWM_ID_0],
ARRAY_SIZE(pdata->psc));
for_each_available_child_of_node(np, child) {

View File

@ -246,29 +246,25 @@ static int powernv_led_classdev(struct platform_device *pdev,
const char *cur = NULL;
int rc = -1;
struct property *p;
struct device_node *np;
struct powernv_led_data *powernv_led;
struct device *dev = &pdev->dev;
for_each_available_child_of_node(led_node, np) {
for_each_available_child_of_node_scoped(led_node, np) {
p = of_find_property(np, "led-types", NULL);
while ((cur = of_prop_next_string(p, cur)) != NULL) {
powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),
GFP_KERNEL);
if (!powernv_led) {
of_node_put(np);
if (!powernv_led)
return -ENOMEM;
}
powernv_led->common = powernv_led_common;
powernv_led->loc_code = (char *)np->name;
rc = powernv_led_create(dev, powernv_led, cur);
if (rc) {
of_node_put(np);
if (rc)
return rc;
}
} /* while end */
}
@ -278,12 +274,11 @@ static int powernv_led_classdev(struct platform_device *pdev,
/* Platform driver probe */
static int powernv_led_probe(struct platform_device *pdev)
{
struct device_node *led_node;
struct powernv_led_common *powernv_led_common;
struct device *dev = &pdev->dev;
int rc;
struct device_node *led_node
__free(device_node) = of_find_node_by_path("/ibm,opal/leds");
led_node = of_find_node_by_path("/ibm,opal/leds");
if (!led_node) {
dev_err(dev, "%s: LED parent device node not found\n",
__func__);
@ -292,20 +287,15 @@ static int powernv_led_probe(struct platform_device *pdev)
powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
GFP_KERNEL);
if (!powernv_led_common) {
rc = -ENOMEM;
goto out;
}
if (!powernv_led_common)
return -ENOMEM;
mutex_init(&powernv_led_common->lock);
powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);
platform_set_drvdata(pdev, powernv_led_common);
rc = powernv_led_classdev(pdev, led_node, powernv_led_common);
out:
of_node_put(led_node);
return rc;
return powernv_led_classdev(pdev, led_node, powernv_led_common);
}
/* Platform driver remove */

View File

@ -29,10 +29,11 @@
*/
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/spi/spi.h>
#include <uapi/linux/uleds.h>
struct spi_byte_chipdef {
@ -55,13 +56,6 @@ static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = {
.max_value = 0x3F,
};
static const struct of_device_id spi_byte_dt_ids[] = {
{ .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef },
{},
};
MODULE_DEVICE_TABLE(of, spi_byte_dt_ids);
static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
enum led_brightness brightness)
{
@ -80,73 +74,60 @@ static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
static int spi_byte_probe(struct spi_device *spi)
{
struct device_node *child;
struct fwnode_handle *child __free(fwnode_handle) = NULL;
struct device *dev = &spi->dev;
struct spi_byte_led *led;
struct led_init_data init_data = {};
const char *state;
enum led_default_state state;
int ret;
if (of_get_available_child_count(dev_of_node(dev)) != 1) {
if (device_get_child_node_count(dev) != 1) {
dev_err(dev, "Device must have exactly one LED sub-node.");
return -EINVAL;
}
child = of_get_next_available_child(dev_of_node(dev), NULL);
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
ret = devm_mutex_init(dev, &led->mutex);
if (ret)
return ret;
led->spi = spi;
mutex_init(&led->mutex);
led->cdef = device_get_match_data(dev);
led->ldev.brightness = LED_OFF;
led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value;
led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking;
state = of_get_property(child, "default-state", NULL);
if (state) {
if (!strcmp(state, "on")) {
led->ldev.brightness = led->ldev.max_brightness;
} else if (strcmp(state, "off")) {
/* all other cases except "off" */
dev_err(dev, "default-state can only be 'on' or 'off'");
return -EINVAL;
}
}
child = device_get_next_child_node(dev, NULL);
state = led_init_default_state_get(child);
if (state == LEDS_DEFSTATE_ON)
led->ldev.brightness = led->ldev.max_brightness;
spi_byte_brightness_set_blocking(&led->ldev,
led->ldev.brightness);
init_data.fwnode = of_fwnode_handle(child);
init_data.fwnode = child;
init_data.devicename = "leds-spi-byte";
init_data.default_label = ":";
ret = devm_led_classdev_register_ext(&spi->dev, &led->ldev, &init_data);
if (ret) {
mutex_destroy(&led->mutex);
return ret;
}
spi_set_drvdata(spi, led);
return 0;
return devm_led_classdev_register_ext(dev, &led->ldev, &init_data);
}
static void spi_byte_remove(struct spi_device *spi)
{
struct spi_byte_led *led = spi_get_drvdata(spi);
mutex_destroy(&led->mutex);
}
static const struct of_device_id spi_byte_dt_ids[] = {
{ .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef },
{}
};
MODULE_DEVICE_TABLE(of, spi_byte_dt_ids);
static struct spi_driver spi_byte_driver = {
.probe = spi_byte_probe,
.remove = spi_byte_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = spi_byte_dt_ids,
},
};
module_spi_driver(spi_byte_driver);
MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");

View File

@ -356,8 +356,10 @@ static int ich7_lpc_probe(struct pci_dev *dev,
nas_gpio_pci_dev = dev;
status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base);
if (status)
if (status) {
status = pcibios_err_to_errno(status);
goto out;
}
g_pm_io_base &= 0x00000ff80;
status = pci_read_config_dword(dev, GPIO_CTRL, &gc);
@ -369,8 +371,9 @@ static int ich7_lpc_probe(struct pci_dev *dev,
}
status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base);
if (0 > status) {
if (status) {
dev_info(&dev->dev, "Unable to read GPIOBASE.\n");
status = pcibios_err_to_errno(status);
goto out;
}
dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base);

View File

@ -146,7 +146,7 @@ MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
static int
tlc591xx_probe(struct i2c_client *client)
{
struct device_node *np, *child;
struct device_node *np;
struct device *dev = &client->dev;
const struct tlc591xx *tlc591xx;
struct tlc591xx_priv *priv;
@ -182,22 +182,20 @@ tlc591xx_probe(struct i2c_client *client)
if (err < 0)
return err;
for_each_available_child_of_node(np, child) {
for_each_available_child_of_node_scoped(np, child) {
struct tlc591xx_led *led;
struct led_init_data init_data = {};
init_data.fwnode = of_fwnode_handle(child);
err = of_property_read_u32(child, "reg", &reg);
if (err) {
of_node_put(child);
if (err)
return err;
}
if (reg < 0 || reg >= tlc591xx->max_leds ||
priv->leds[reg].active) {
of_node_put(child);
priv->leds[reg].active)
return -EINVAL;
}
led = &priv->leds[reg];
led->active = true;
@ -207,12 +205,10 @@ tlc591xx_probe(struct i2c_client *client)
led->ldev.max_brightness = TLC591XX_MAX_BRIGHTNESS;
err = devm_led_classdev_register_ext(dev, &led->ldev,
&init_data);
if (err < 0) {
of_node_put(child);
if (err < 0)
return dev_err_probe(dev, err,
"couldn't register LED %s\n",
led->ldev.name);
}
}
return 0;
}

View File

@ -534,7 +534,7 @@ static const struct of_device_id of_omnia_leds_match[] = {
};
static const struct i2c_device_id omnia_id[] = {
{ "omnia", 0 },
{ "omnia" },
{ }
};
MODULE_DEVICE_TABLE(i2c, omnia_id);

View File

@ -17,7 +17,6 @@ config 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

View File

@ -99,7 +99,7 @@ struct ktd202x {
struct device *dev;
struct regmap *regmap;
bool enabled;
int num_leds;
unsigned long num_leds;
struct ktd202x_led leds[] __counted_by(num_leds);
};
@ -381,16 +381,19 @@ static int ktd202x_blink_mc_set(struct led_classdev *cdev,
mc->num_colors);
}
static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct fwnode_handle *fwnode,
struct ktd202x_led *led, struct led_init_data *init_data)
{
struct fwnode_handle *child;
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);
num_channels = 0;
fwnode_for_each_available_child_node(fwnode, child)
num_channels++;
if (!num_channels || num_channels > chip->num_leds)
return -EINVAL;
@ -398,22 +401,22 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
if (!info)
return -ENOMEM;
for_each_available_child_of_node(np, child) {
fwnode_for_each_available_child_node(fwnode, child) {
u32 mono_color;
u32 reg;
int ret;
ret = of_property_read_u32(child, "reg", &reg);
ret = fwnode_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;
dev_err(chip->dev, "invalid 'reg' of %pfw\n", child);
fwnode_handle_put(child);
return ret;
}
ret = of_property_read_u32(child, "color", &mono_color);
ret = fwnode_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);
dev_err(chip->dev, "failed to parse 'color' of %pfw\n", child);
fwnode_handle_put(child);
return ret;
}
@ -433,16 +436,16 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
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,
static int ktd202x_setup_led_single(struct ktd202x *chip, struct fwnode_handle *fwnode,
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);
ret = fwnode_property_read_u32(fwnode, "reg", &reg);
if (ret != 0 || reg >= chip->num_leds) {
dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np);
dev_err(chip->dev, "invalid 'reg' of %pfw\n", fwnode);
return -EINVAL;
}
led->index = reg;
@ -454,7 +457,7 @@ static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np
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)
static int ktd202x_add_led(struct ktd202x *chip, struct fwnode_handle *fwnode, unsigned int index)
{
struct ktd202x_led *led = &chip->leds[index];
struct led_init_data init_data = {};
@ -463,21 +466,21 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne
int ret;
/* Color property is optional in single color case */
ret = of_property_read_u32(np, "color", &color);
ret = fwnode_property_read_u32(fwnode, "color", &color);
if (ret < 0 && ret != -EINVAL) {
dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np);
dev_err(chip->dev, "failed to parse 'color' of %pfw\n", fwnode);
return ret;
}
led->chip = chip;
init_data.fwnode = of_fwnode_handle(np);
init_data.fwnode = fwnode;
if (color == LED_COLOR_ID_RGB) {
cdev = &led->mcdev.led_cdev;
ret = ktd202x_setup_led_rgb(chip, np, led, &init_data);
ret = ktd202x_setup_led_rgb(chip, fwnode, led, &init_data);
} else {
cdev = &led->cdev;
ret = ktd202x_setup_led_single(chip, np, led, &init_data);
ret = ktd202x_setup_led_single(chip, fwnode, led, &init_data);
}
if (ret) {
@ -490,15 +493,14 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne
return 0;
}
static int ktd202x_probe_dt(struct ktd202x *chip)
static int ktd202x_probe_fw(struct ktd202x *chip)
{
struct device_node *np = dev_of_node(chip->dev), *child;
struct fwnode_handle *child;
struct device *dev = chip->dev;
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);
count = device_get_child_node_count(dev);
if (!count || count > chip->num_leds)
return -EINVAL;
@ -507,11 +509,11 @@ static int ktd202x_probe_dt(struct ktd202x *chip)
/* Allow the device to execute the complete reset */
usleep_range(200, 300);
for_each_available_child_of_node(np, child) {
device_for_each_child_node(dev, child) {
int ret = ktd202x_add_led(chip, child, i);
if (ret) {
of_node_put(child);
fwnode_handle_put(child);
return ret;
}
i++;
@ -554,6 +556,12 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
ret = devm_mutex_init(dev, &chip->mutex);
if (ret)
return ret;
chip->num_leds = (unsigned long)i2c_get_match_data(client);
chip->regulators[0].supply = "vin";
chip->regulators[1].supply = "vio";
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators);
@ -568,7 +576,7 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
ret = ktd202x_probe_dt(chip);
ret = ktd202x_probe_fw(chip);
if (ret < 0) {
regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
return ret;
@ -580,8 +588,6 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
mutex_init(&chip->mutex);
return 0;
}
@ -590,8 +596,6 @@ 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)
@ -602,10 +606,17 @@ static void ktd202x_shutdown(struct i2c_client *client)
regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
}
static const struct i2c_device_id ktd202x_id[] = {
{"ktd2026", KTD2026_NUM_LEDS},
{"ktd2027", KTD2027_NUM_LEDS},
{}
};
MODULE_DEVICE_TABLE(i2c, ktd202x_id);
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);
@ -617,6 +628,7 @@ static struct i2c_driver ktd202x_driver = {
.probe = ktd202x_probe,
.remove = ktd202x_remove,
.shutdown = ktd202x_shutdown,
.id_table = ktd202x_id,
};
module_i2c_driver(ktd202x_driver);

View File

@ -183,16 +183,12 @@ static int ncp5623_probe(struct i2c_client *client)
fwnode_for_each_available_child_node(mc_node, led_node) {
ret = fwnode_property_read_u32(led_node, "color", &color_index);
if (ret) {
fwnode_handle_put(led_node);
goto release_mc_node;
}
if (ret)
goto release_led_node;
ret = fwnode_property_read_u32(led_node, "reg", &reg);
if (ret) {
fwnode_handle_put(led_node);
goto release_mc_node;
}
if (ret)
goto release_led_node;
subled_info[ncp->mc_dev.num_colors].channel = reg;
subled_info[ncp->mc_dev.num_colors++].color_index = color_index;
@ -223,6 +219,10 @@ release_mc_node:
fwnode_handle_put(mc_node);
return ret;
release_led_node:
fwnode_handle_put(led_node);
goto release_mc_node;
}
static void ncp5623_remove(struct i2c_client *client)

View File

@ -2,7 +2,7 @@
/*
* Copyright (c) 2017-2022 Linaro Ltd
* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
* Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/bits.h>
#include <linux/bitfield.h>
@ -254,6 +254,9 @@ static int lpg_clear_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
u8 val = 0;
int rc;
if (!lpg->lpg_chan_sdam)
return 0;
lpg->pbs_en_bitmap &= (~lut_mask);
if (!lpg->pbs_en_bitmap) {
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
@ -276,6 +279,9 @@ static int lpg_set_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
u8 val = PBS_SW_TRIG_BIT;
int rc;
if (!lpg->lpg_chan_sdam)
return 0;
if (!lpg->pbs_en_bitmap) {
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
if (rc < 0)

View File

@ -60,6 +60,7 @@ static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = {
};
module_platform_driver(simatic_ipc_led_gpio_apollolake_driver);
MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:apollolake-pinctrl");

View File

@ -102,6 +102,7 @@ out:
}
EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe);
MODULE_DESCRIPTION("Siemens SIMATIC IPC core driver for GPIO based LEDs");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: platform:leds-gpio");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -50,6 +50,7 @@ static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
};
module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver);
MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl");

View File

@ -100,6 +100,7 @@ static struct platform_driver simatic_ipc_led_gpio_driver = {
};
module_platform_driver(simatic_ipc_led_gpio_driver);
MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Nuvoton GPIO");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x");

View File

@ -128,6 +128,7 @@ static struct platform_driver simatic_ipc_led_driver = {
};
module_platform_driver(simatic_ipc_led_driver);
MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");

View File

@ -145,4 +145,20 @@ config LEDS_TRIGGER_TTY
When build as a module this driver will be called ledtrig-tty.
config LEDS_TRIGGER_INPUT_EVENTS
tristate "LED Input events trigger"
depends on INPUT
help
Turn LEDs on when there is input (/dev/input/event*) activity and turn
them back off again after there has been no activity for 5 seconds.
This is primarily intended to control LEDs which are a backlight for
capacitive touch-buttons, such as e.g. the menu / home / back buttons
found on the bottom bezel of many older smartphones and tablets.
This can also be used to turn on the keyboard backlight LED on
input events and turn the keyboard backlight off again when idle.
When build as a module this driver will be called ledtrig-input-events.
endif # LEDS_TRIGGERS

View File

@ -15,3 +15,4 @@ obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o
obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o
obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o
obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS) += ledtrig-input-events.o

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Input Events LED trigger
*
* Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
*/
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include "../leds.h"
static unsigned long led_off_delay_ms = 5000;
module_param(led_off_delay_ms, ulong, 0644);
MODULE_PARM_DESC(led_off_delay_ms,
"Specify delay in ms for turning LEDs off after last input event");
static struct input_events_data {
struct delayed_work work;
spinlock_t lock;
/* To avoid repeatedly setting the brightness while there are events */
bool led_on;
unsigned long led_off_time;
} input_events_data;
static struct led_trigger *input_events_led_trigger;
static void led_input_events_work(struct work_struct *work)
{
struct input_events_data *data =
container_of(work, struct input_events_data, work.work);
spin_lock_irq(&data->lock);
/*
* This time_after_eq() check avoids a race where this work starts
* running before a new event pushed led_off_time back.
*/
if (time_after_eq(jiffies, data->led_off_time)) {
led_trigger_event(input_events_led_trigger, LED_OFF);
data->led_on = false;
}
spin_unlock_irq(&data->lock);
}
static void input_events_event(struct input_handle *handle, unsigned int type,
unsigned int code, int val)
{
struct input_events_data *data = &input_events_data;
unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms);
unsigned long flags;
spin_lock_irqsave(&data->lock, flags);
if (!data->led_on) {
led_trigger_event(input_events_led_trigger, LED_FULL);
data->led_on = true;
}
data->led_off_time = jiffies + led_off_delay;
spin_unlock_irqrestore(&data->lock, flags);
mod_delayed_work(system_wq, &data->work, led_off_delay);
}
static int input_events_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct input_handle *handle;
int ret;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = KBUILD_MODNAME;
ret = input_register_handle(handle);
if (ret)
goto err_free_handle;
ret = input_open_device(handle);
if (ret)
goto err_unregister_handle;
return 0;
err_unregister_handle:
input_unregister_handle(handle);
err_free_handle:
kfree(handle);
return ret;
}
static void input_events_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
static const struct input_device_id input_events_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_KEY) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_REL) },
},
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_ABS) },
},
{ }
};
static struct input_handler input_events_handler = {
.name = KBUILD_MODNAME,
.event = input_events_event,
.connect = input_events_connect,
.disconnect = input_events_disconnect,
.id_table = input_events_ids,
};
static int __init input_events_init(void)
{
int ret;
INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
spin_lock_init(&input_events_data.lock);
led_trigger_register_simple("input-events", &input_events_led_trigger);
ret = input_register_handler(&input_events_handler);
if (ret) {
led_trigger_unregister_simple(input_events_led_trigger);
return ret;
}
return 0;
}
static void __exit input_events_exit(void)
{
input_unregister_handler(&input_events_handler);
cancel_delayed_work_sync(&input_events_data.work);
led_trigger_unregister_simple(input_events_led_trigger);
}
module_init(input_events_init);
module_exit(input_events_exit);
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("Input Events LED trigger");
MODULE_LICENSE("GPL");
MODULE_ALIAS("ledtrig:input-events");

View File

@ -110,11 +110,6 @@ static int timer_trig_activate(struct led_classdev *led_cdev)
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
}
/*
* If "set brightness to 0" is pending in workqueue, we don't
* want that to be reordered after blink_set()
*/
flush_work(&led_cdev->set_brightness_work);
led_blink_set(led_cdev, &led_cdev->blink_delay_on,
&led_cdev->blink_delay_off);

View File

@ -22,6 +22,8 @@
static void power_supply_update_bat_leds(struct power_supply *psy)
{
union power_supply_propval status;
unsigned int intensity_green[3] = { 0, 255, 0 };
unsigned int intensity_orange[3] = { 255, 128, 0 };
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
return;
@ -36,12 +38,20 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
/* Going from blink to LED on requires a LED_OFF event to stop blink */
led_trigger_event(psy->charging_blink_full_solid_trig, LED_OFF);
led_trigger_event(psy->charging_blink_full_solid_trig, LED_FULL);
led_mc_trigger_event(psy->charging_orange_full_green_trig,
intensity_green,
ARRAY_SIZE(intensity_green),
LED_FULL);
break;
case POWER_SUPPLY_STATUS_CHARGING:
led_trigger_event(psy->charging_full_trig, LED_FULL);
led_trigger_event(psy->charging_trig, LED_FULL);
led_trigger_event(psy->full_trig, LED_OFF);
led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0);
led_mc_trigger_event(psy->charging_orange_full_green_trig,
intensity_orange,
ARRAY_SIZE(intensity_orange),
LED_FULL);
break;
default:
led_trigger_event(psy->charging_full_trig, LED_OFF);
@ -49,6 +59,8 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
led_trigger_event(psy->full_trig, LED_OFF);
led_trigger_event(psy->charging_blink_full_solid_trig,
LED_OFF);
led_trigger_event(psy->charging_orange_full_green_trig,
LED_OFF);
break;
}
}
@ -74,6 +86,11 @@ static int power_supply_create_bat_triggers(struct power_supply *psy)
if (!psy->charging_blink_full_solid_trig_name)
goto charging_blink_full_solid_failed;
psy->charging_orange_full_green_trig_name = kasprintf(GFP_KERNEL,
"%s-charging-orange-full-green", psy->desc->name);
if (!psy->charging_orange_full_green_trig_name)
goto charging_red_full_green_failed;
led_trigger_register_simple(psy->charging_full_trig_name,
&psy->charging_full_trig);
led_trigger_register_simple(psy->charging_trig_name,
@ -82,9 +99,13 @@ static int power_supply_create_bat_triggers(struct power_supply *psy)
&psy->full_trig);
led_trigger_register_simple(psy->charging_blink_full_solid_trig_name,
&psy->charging_blink_full_solid_trig);
led_trigger_register_simple(psy->charging_orange_full_green_trig_name,
&psy->charging_orange_full_green_trig);
return 0;
charging_red_full_green_failed:
kfree(psy->charging_blink_full_solid_trig_name);
charging_blink_full_solid_failed:
kfree(psy->full_trig_name);
full_failed:
@ -101,10 +122,12 @@ static void power_supply_remove_bat_triggers(struct power_supply *psy)
led_trigger_unregister_simple(psy->charging_trig);
led_trigger_unregister_simple(psy->full_trig);
led_trigger_unregister_simple(psy->charging_blink_full_solid_trig);
led_trigger_unregister_simple(psy->charging_orange_full_green_trig);
kfree(psy->charging_blink_full_solid_trig_name);
kfree(psy->full_trig_name);
kfree(psy->charging_trig_name);
kfree(psy->charging_full_trig_name);
kfree(psy->charging_orange_full_green_trig_name);
}
/* Generated power specific LEDs triggers. */

View File

@ -108,6 +108,7 @@ struct led_classdev {
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
#define LED_REJECT_NAME_CONFLICT BIT(24)
#define LED_MULTI_COLOR BIT(25)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
@ -374,6 +375,25 @@ void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness);
*/
int led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value);
/**
* led_mc_set_brightness - set mc LED color intensity values and brightness
* @led_cdev: the LED to set
* @intensity_value: array of per color intensity values to set
* @num_colors: amount of entries in intensity_value array
* @brightness: the brightness to set the LED to
*
* Set a multi-color LED's per color intensity values and brightness.
* If necessary, this cancels the software blink timer. This function is
* guaranteed not to sleep.
*
* Calling this function on a non multi-color led_classdev or with the wrong
* num_colors value is an error. In this case an error will be logged once
* and the call will do nothing.
*/
void led_mc_set_brightness(struct led_classdev *led_cdev,
unsigned int *intensity_value, unsigned int num_colors,
unsigned int brightness);
/**
* led_update_brightness - update LED brightness
* @led_cdev: the LED to query
@ -501,6 +521,9 @@ void led_trigger_register_simple(const char *name,
struct led_trigger **trigger);
void led_trigger_unregister_simple(struct led_trigger *trigger);
void led_trigger_event(struct led_trigger *trigger, enum led_brightness event);
void led_mc_trigger_event(struct led_trigger *trig,
unsigned int *intensity_value, unsigned int num_colors,
enum led_brightness brightness);
void led_trigger_blink(struct led_trigger *trigger, unsigned long delay_on,
unsigned long delay_off);
void led_trigger_blink_oneshot(struct led_trigger *trigger,
@ -543,6 +566,9 @@ static inline void led_trigger_register_simple(const char *name,
static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {}
static inline void led_trigger_event(struct led_trigger *trigger,
enum led_brightness event) {}
static inline void led_mc_trigger_event(struct led_trigger *trig,
unsigned int *intensity_value, unsigned int num_colors,
enum led_brightness brightness) {}
static inline void led_trigger_blink(struct led_trigger *trigger,
unsigned long delay_on,
unsigned long delay_off) {}

View File

@ -319,6 +319,8 @@ struct power_supply {
char *online_trig_name;
struct led_trigger *charging_blink_full_solid_trig;
char *charging_blink_full_solid_trig_name;
struct led_trigger *charging_orange_full_green_trig;
char *charging_orange_full_green_trig_name;
#endif
};