mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 04:18:39 +08:00
- 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:
commit
fea17683c4
@ -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
|
||||
|
100
Documentation/devicetree/bindings/leds/silergy,sy7802.yaml
Normal file
100
Documentation/devicetree/bindings/leds/silergy,sy7802.yaml
Normal 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>;
|
||||
};
|
||||
};
|
||||
};
|
@ -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
|
||||
|
@ -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/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -643,14 +643,17 @@ static int mt6360_init_isnk_properties(struct mt6360_led *led,
|
||||
|
||||
ret = fwnode_property_read_u32(child, "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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
539
drivers/leds/flash/leds-sy7802.c
Normal file
539
drivers/leds/flash/leds-sy7802.c
Normal 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");
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -718,7 +718,7 @@ static void blinkm_remove(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id blinkm_id[] = {
|
||||
{"blinkm", 0},
|
||||
{ "blinkm" },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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" },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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
544
drivers/leds/leds-lp5569.c
Normal 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");
|
@ -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");
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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 */
|
||||
|
@ -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>");
|
||||
|
@ -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);
|
||||
|
@ -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", ®);
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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", ®);
|
||||
ret = fwnode_property_read_u32(child, "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", ®);
|
||||
ret = fwnode_property_read_u32(fwnode, "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);
|
||||
|
||||
|
@ -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", ®);
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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>");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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>");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
165
drivers/leds/trigger/ledtrig-input-events.c
Normal file
165
drivers/leds/trigger/ledtrig-input-events.c
Normal 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");
|
@ -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);
|
||||
|
||||
|
@ -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. */
|
||||
|
@ -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) {}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user