Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds
Pull LED driver updates from Jacek Anaszewski: "Three new LED class drivers and some minor fixes and improvementes to the leds-gpio driver, LED Trigger core and documentation" * 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds: leds: triggers: Check return value of kobject_uevent_env() leds: triggers: Return from led_trigger_set() if there is nothing to do leds: gpio: fix and simplify error handling in gpio_leds_create leds: gpio: switch to managed version of led_classdev_register leds: gpio: fix and simplify reading property "label" leds: gpio: simplify gpio_leds_create leds: gpio: add helper cdev_to_gpio_led_data leds: gpio: fix an unhandled error case in create_gpio_led leds: gpio: introduce gpio_blink_set_t leds: add driver for Mellanox systems LEDs Documentation: move oneshot trigger attributes documentation to ABI leds: centralize definition of "default-state" property leds: add PM8058 LEDs driver leds: pm8058: add device tree bindings leds: do not overflow sysfs buffer in led_trigger_show leds: make triggers explicitly non-modular DT: leds: Add bindings for ISSI is31fl319x leds: is31fl319x: 1/3/6/9-channel light effect led driver
This commit is contained in:
commit
f80fa1822d
|
@ -24,7 +24,8 @@ Description:
|
|||
of led events.
|
||||
You can change triggers in a similar manner to the way an IO
|
||||
scheduler is chosen. Trigger specific parameters can appear in
|
||||
/sys/class/leds/<led> once a given trigger is selected.
|
||||
/sys/class/leds/<led> once a given trigger is selected. For
|
||||
their documentation see sysfs-class-led-trigger-*.
|
||||
|
||||
What: /sys/class/leds/<led>/inverted
|
||||
Date: January 2011
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
What: /sys/class/leds/<led>/delay_on
|
||||
Date: Jun 2012
|
||||
KernelVersion: 3.6
|
||||
Contact: linux-leds@vger.kernel.org
|
||||
Description:
|
||||
Specifies for how many milliseconds the LED has to stay at
|
||||
LED_FULL brightness after it has been armed.
|
||||
Defaults to 100 ms.
|
||||
|
||||
What: /sys/class/leds/<led>/delay_off
|
||||
Date: Jun 2012
|
||||
KernelVersion: 3.6
|
||||
Contact: linux-leds@vger.kernel.org
|
||||
Description:
|
||||
Specifies for how many milliseconds the LED has to stay at
|
||||
LED_OFF brightness after it has been armed.
|
||||
Defaults to 100 ms.
|
||||
|
||||
What: /sys/class/leds/<led>/invert
|
||||
Date: Jun 2012
|
||||
KernelVersion: 3.6
|
||||
Contact: linux-leds@vger.kernel.org
|
||||
Description:
|
||||
Reverse the blink logic. If set to 0 (default) blink on for
|
||||
delay_on ms, then blink off for delay_off ms, leaving the LED
|
||||
normally off. If set to 1, blink off for delay_off ms, then
|
||||
blink on for delay_on ms, leaving the LED normally on.
|
||||
Setting this value also immediately changes the LED state.
|
||||
|
||||
What: /sys/class/leds/<led>/shot
|
||||
Date: Jun 2012
|
||||
KernelVersion: 3.6
|
||||
Contact: linux-leds@vger.kernel.org
|
||||
Description:
|
||||
Write any non-empty string to signal an events, this starts a
|
||||
blink sequence if not already running.
|
|
@ -19,6 +19,13 @@ Optional properties for child nodes:
|
|||
a device, i.e. no other LED class device can be assigned the same
|
||||
label.
|
||||
|
||||
- default-state : The initial state of the LED. Valid values are "on", "off",
|
||||
and "keep". If the LED is already on or off and the default-state property is
|
||||
set the to same value, then no glitch should be produced where the LED
|
||||
momentarily turns off (or on). The "keep" setting will keep the LED at
|
||||
whatever its current state is, without producing a glitch. The default is
|
||||
off if this property is not present.
|
||||
|
||||
- linux,default-trigger : This parameter, if present, is a
|
||||
string defining the trigger assigned to the LED. Current triggers are:
|
||||
"backlight" - LED will act as a back-light, controlled by the framebuffer
|
||||
|
|
|
@ -49,7 +49,7 @@ LED sub-node optional properties:
|
|||
- active-low : Boolean, makes LED active low.
|
||||
Default : false
|
||||
- default-state : see
|
||||
Documentation/devicetree/bindings/leds/leds-gpio.txt
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger : see
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ LED sub-node optional properties:
|
|||
- active-low : Boolean, makes LED active low.
|
||||
Default : false
|
||||
- default-state : see
|
||||
Documentation/devicetree/bindings/leds/leds-gpio.txt
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger : see
|
||||
Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
|
|
|
@ -14,13 +14,8 @@ LED sub-node properties:
|
|||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger : (optional)
|
||||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
- default-state: (optional) The initial state of the LED. Valid
|
||||
values are "on", "off", and "keep". If the LED is already on or off
|
||||
and the default-state property is set the to same value, then no
|
||||
glitch should be produced where the LED momentarily turns off (or
|
||||
on). The "keep" setting will keep the LED at whatever its current
|
||||
state is, without producing a glitch. The default is off if this
|
||||
property is not present.
|
||||
- default-state: (optional) The initial state of the LED.
|
||||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
- retain-state-suspended: (optional) The suspend state can be retained.Such
|
||||
as charge-led gpio.
|
||||
- panic-indicator : (optional)
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
LEDs connected to is31fl319x LED controller chip
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be any of
|
||||
"issi,is31fl3190"
|
||||
"issi,is31fl3191"
|
||||
"issi,is31fl3193"
|
||||
"issi,is31fl3196"
|
||||
"issi,is31fl3199"
|
||||
"si-en,sn3199".
|
||||
- #address-cells: Must be 1.
|
||||
- #size-cells: Must be 0.
|
||||
- reg: 0x64, 0x65, 0x66, or 0x67.
|
||||
|
||||
Optional properties:
|
||||
- audio-gain-db : audio gain selection for external analog modulation input.
|
||||
Valid values: 0 - 21, step by 3 (rounded down)
|
||||
Default: 0
|
||||
|
||||
Each led is represented as a sub-node of the issi,is31fl319x device.
|
||||
There can be less leds subnodes than the chip can support but not more.
|
||||
|
||||
Required led sub-node properties:
|
||||
- reg : number of LED line
|
||||
Valid values: 1 - number of leds supported by the chip variant.
|
||||
|
||||
Optional led sub-node properties:
|
||||
- label : see Documentation/devicetree/bindings/leds/common.txt.
|
||||
- linux,default-trigger :
|
||||
see Documentation/devicetree/bindings/leds/common.txt.
|
||||
- led-max-microamp : (optional)
|
||||
Valid values: 5000 - 40000, step by 5000 (rounded down)
|
||||
Default: 20000 (20 mA)
|
||||
Note: a driver will take the lowest of all led limits since the
|
||||
chip has a single global setting. The lowest value will be chosen
|
||||
due to the PWM specificity, where lower brightness is achieved
|
||||
by reducing the dury-cycle of pulses and not the current, which
|
||||
will always have its peak value equal to led-max-microamp.
|
||||
|
||||
Examples:
|
||||
|
||||
fancy_leds: leds@65 {
|
||||
compatible = "issi,is31fl3196";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0x65>;
|
||||
|
||||
red_aux: led@1 {
|
||||
label = "red:aux";
|
||||
reg = <1>;
|
||||
led-max-microamp = <10000>;
|
||||
};
|
||||
|
||||
green_power: led@5 {
|
||||
label = "green:power";
|
||||
reg = <5>;
|
||||
linux,default-trigger = "default-on";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
Qualcomm PM8058 LED driver
|
||||
|
||||
The Qualcomm PM8058 is a multi-functional device which contains
|
||||
an LED driver block for up to six LEDs: three normal LEDs, two
|
||||
"flash" LEDs and one "keypad backlight" LED. The names are
|
||||
quoted because sometimes these LED drivers are used for wildly
|
||||
different things than flash or keypad backlight: their names
|
||||
are more of a suggestion than a hard-wired usecase.
|
||||
|
||||
Hardware-wise the different LEDs support slightly different
|
||||
output currents. The "flash" LEDs do not need to charge nor
|
||||
do they support external triggers. They are just powerful LED
|
||||
drivers.
|
||||
|
||||
The LEDs appear as children to the PM8058 device, with the
|
||||
proper compatible string. For the PM8058 bindings see:
|
||||
mfd/qcom-pm8xxx.txt.
|
||||
|
||||
Each LED is represented as a sub-node of the syscon device. Each
|
||||
node's name represents the name of the corresponding LED.
|
||||
|
||||
LED sub-node properties:
|
||||
|
||||
Required properties:
|
||||
- compatible: one of
|
||||
"qcom,pm8058-led" (for the normal LEDs at 0x131, 0x132 and 0x133)
|
||||
"qcom,pm8058-keypad-led" (for the "keypad" LED at 0x48)
|
||||
"qcom,pm8058-flash-led" (for the "flash" LEDs at 0x49 and 0xFB)
|
||||
|
||||
Optional properties:
|
||||
- label: see Documentation/devicetree/bindings/leds/common.txt
|
||||
- default-state: see Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger: see Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
Example:
|
||||
|
||||
qcom,ssbi@500000 {
|
||||
pmicintc: pmic@0 {
|
||||
compatible = "qcom,pm8058";
|
||||
led@48 {
|
||||
compatible = "qcom,pm8058-keypad-led";
|
||||
reg = <0x48>;
|
||||
label = "pm8050:white:keypad";
|
||||
default-state = "off";
|
||||
};
|
||||
led@131 {
|
||||
compatible = "qcom,pm8058-led";
|
||||
reg = <0x131>;
|
||||
label = "pm8058:red";
|
||||
default-state = "off";
|
||||
};
|
||||
led@132 {
|
||||
compatible = "qcom,pm8058-led";
|
||||
reg = <0x132>;
|
||||
label = "pm8058:yellow";
|
||||
default-state = "off";
|
||||
linux,default-trigger = "mmc0";
|
||||
};
|
||||
led@133 {
|
||||
compatible = "qcom,pm8058-led";
|
||||
reg = <0x133>;
|
||||
label = "pm8058:green";
|
||||
default-state = "on";
|
||||
linux,default-trigger = "heartbeat";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -23,13 +23,8 @@ Optional properties:
|
|||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
- linux,default-trigger : (optional)
|
||||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
- default-state: (optional) The initial state of the LED. Valid
|
||||
values are "on", "off", and "keep". If the LED is already on or off
|
||||
and the default-state property is set the to same value, then no
|
||||
glitch should be produced where the LED momentarily turns off (or
|
||||
on). The "keep" setting will keep the LED at whatever its current
|
||||
state is, without producing a glitch. The default is off if this
|
||||
property is not present.
|
||||
- default-state: (optional) The initial state of the LED
|
||||
see Documentation/devicetree/bindings/leds/common.txt
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
Kernel driver for Mellanox systems LEDs
|
||||
=======================================
|
||||
|
||||
Provide system LED support for the nex Mellanox systems:
|
||||
"msx6710", "msx6720", "msb7700", "msn2700", "msx1410",
|
||||
"msn2410", "msb7800", "msn2740", "msn2100".
|
||||
|
||||
Description
|
||||
-----------
|
||||
Driver provides the following LEDs for the systems "msx6710", "msx6720",
|
||||
"msb7700", "msn2700", "msx1410", "msn2410", "msb7800", "msn2740":
|
||||
mlxcpld:fan1:green
|
||||
mlxcpld:fan1:red
|
||||
mlxcpld:fan2:green
|
||||
mlxcpld:fan2:red
|
||||
mlxcpld:fan3:green
|
||||
mlxcpld:fan3:red
|
||||
mlxcpld:fan4:green
|
||||
mlxcpld:fan4:red
|
||||
mlxcpld:psu:green
|
||||
mlxcpld:psu:red
|
||||
mlxcpld:status:green
|
||||
mlxcpld:status:red
|
||||
|
||||
"status"
|
||||
CPLD reg offset: 0x20
|
||||
Bits [3:0]
|
||||
|
||||
"psu"
|
||||
CPLD reg offset: 0x20
|
||||
Bits [7:4]
|
||||
|
||||
"fan1"
|
||||
CPLD reg offset: 0x21
|
||||
Bits [3:0]
|
||||
|
||||
"fan2"
|
||||
CPLD reg offset: 0x21
|
||||
Bits [7:4]
|
||||
|
||||
"fan3"
|
||||
CPLD reg offset: 0x22
|
||||
Bits [3:0]
|
||||
|
||||
"fan4"
|
||||
CPLD reg offset: 0x22
|
||||
Bits [7:4]
|
||||
|
||||
Color mask for all the above LEDs:
|
||||
[bit3,bit2,bit1,bit0] or
|
||||
[bit7,bit6,bit5,bit4]:
|
||||
[0,0,0,0] = LED OFF
|
||||
[0,1,0,1] = Red static ON
|
||||
[1,1,0,1] = Green static ON
|
||||
[0,1,1,0] = Red blink 3Hz
|
||||
[1,1,1,0] = Green blink 3Hz
|
||||
[0,1,1,1] = Red blink 6Hz
|
||||
[1,1,1,1] = Green blink 6Hz
|
||||
|
||||
Driver provides the following LEDs for the system "msn2100":
|
||||
mlxcpld:fan:green
|
||||
mlxcpld:fan:red
|
||||
mlxcpld:psu1:green
|
||||
mlxcpld:psu1:red
|
||||
mlxcpld:psu2:green
|
||||
mlxcpld:psu2:red
|
||||
mlxcpld:status:green
|
||||
mlxcpld:status:red
|
||||
mlxcpld:uid:blue
|
||||
|
||||
"status"
|
||||
CPLD reg offset: 0x20
|
||||
Bits [3:0]
|
||||
|
||||
"fan"
|
||||
CPLD reg offset: 0x21
|
||||
Bits [3:0]
|
||||
|
||||
"psu1"
|
||||
CPLD reg offset: 0x23
|
||||
Bits [3:0]
|
||||
|
||||
"psu2"
|
||||
CPLD reg offset: 0x23
|
||||
Bits [7:4]
|
||||
|
||||
"uid"
|
||||
CPLD reg offset: 0x24
|
||||
Bits [3:0]
|
||||
|
||||
Color mask for all the above LEDs, excepted uid:
|
||||
[bit3,bit2,bit1,bit0] or
|
||||
[bit7,bit6,bit5,bit4]:
|
||||
[0,0,0,0] = LED OFF
|
||||
[0,1,0,1] = Red static ON
|
||||
[1,1,0,1] = Green static ON
|
||||
[0,1,1,0] = Red blink 3Hz
|
||||
[1,1,1,0] = Green blink 3Hz
|
||||
[0,1,1,1] = Red blink 6Hz
|
||||
[1,1,1,1] = Green blink 6Hz
|
||||
|
||||
Color mask for uid LED:
|
||||
[bit3,bit2,bit1,bit0]:
|
||||
[0,0,0,0] = LED OFF
|
||||
[1,1,0,1] = Blue static ON
|
||||
[1,1,1,0] = Blue blink 3Hz
|
||||
[1,1,1,1] = Blue blink 6Hz
|
||||
|
||||
Driver supports HW blinking at 3Hz and 6Hz frequency (50% duty cycle).
|
||||
For 3Hz duty cylce is about 167 msec, for 6Hz is about 83 msec.
|
|
@ -21,24 +21,8 @@ below:
|
|||
|
||||
echo oneshot > trigger
|
||||
|
||||
This adds the following sysfs attributes to the LED:
|
||||
|
||||
delay_on - specifies for how many milliseconds the LED has to stay at
|
||||
LED_FULL brightness after it has been armed.
|
||||
Default to 100 ms.
|
||||
|
||||
delay_off - specifies for how many milliseconds the LED has to stay at
|
||||
LED_OFF brightness after it has been armed.
|
||||
Default to 100 ms.
|
||||
|
||||
invert - reverse the blink logic. If set to 0 (default) blink on for delay_on
|
||||
ms, then blink off for delay_off ms, leaving the LED normally off. If
|
||||
set to 1, blink off for delay_off ms, then blink on for delay_on ms,
|
||||
leaving the LED normally on.
|
||||
Setting this value also immediately change the LED state.
|
||||
|
||||
shot - write any non-empty string to signal an events, this starts a blink
|
||||
sequence if not already running.
|
||||
This adds sysfs attributes to the LED that are documented in:
|
||||
Documentation/ABI/testing/sysfs-class-led-trigger-oneshot
|
||||
|
||||
Example use-case: network devices, initialization:
|
||||
|
||||
|
|
|
@ -7678,6 +7678,13 @@ W: http://www.mellanox.com
|
|||
Q: http://patchwork.ozlabs.org/project/netdev/list/
|
||||
F: drivers/net/ethernet/mellanox/mlxsw/
|
||||
|
||||
MELLANOX MLXCPLD LED DRIVER
|
||||
M: Vadim Pasternak <vadimp@mellanox.com>
|
||||
L: linux-leds@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/leds/leds-mlxcpld.c
|
||||
F: Documentation/leds/leds-mlxcpld.txt
|
||||
|
||||
MELLANOX PLATFORM DRIVER
|
||||
M: Vadim Pasternak <vadimp@mellanox.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
|
|
|
@ -584,6 +584,18 @@ config LEDS_SEAD3
|
|||
This driver can also be built as a module. If so the module
|
||||
will be called leds-sead3.
|
||||
|
||||
config LEDS_IS31FL319X
|
||||
tristate "LED Support for ISSI IS31FL319x I2C LED controller family"
|
||||
depends on LEDS_CLASS && I2C && OF
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This option enables support for LEDs connected to ISSI IS31FL319x
|
||||
fancy LED driver chips accessed via the I2C bus.
|
||||
Driver supports individual PWM brightness control for each channel.
|
||||
|
||||
This driver can also be built as a module. If so the module will be
|
||||
called leds-is31fl319x.
|
||||
|
||||
config LEDS_IS31FL32XX
|
||||
tristate "LED support for ISSI IS31FL32XX I2C LED controller family"
|
||||
depends on LEDS_CLASS && I2C && OF
|
||||
|
@ -631,6 +643,22 @@ config LEDS_VERSATILE
|
|||
This option enabled support for the LEDs on the ARM Versatile
|
||||
and RealView boards. Say Y to enabled these.
|
||||
|
||||
config LEDS_PM8058
|
||||
tristate "LED Support for the Qualcomm PM8058 PMIC"
|
||||
depends on MFD_PM8921_CORE
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
Choose this option if you want to use the LED drivers in
|
||||
the Qualcomm PM8058 PMIC.
|
||||
|
||||
config LEDS_MLXCPLD
|
||||
tristate "LED support for the Mellanox boards"
|
||||
depends on X86_64 && DMI
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enabled support for the LEDs on the Mellanox
|
||||
boards. Say Y to enabled these.
|
||||
|
||||
comment "LED Triggers"
|
||||
source "drivers/leds/trigger/Kconfig"
|
||||
|
||||
|
|
|
@ -67,7 +67,10 @@ obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
|
|||
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
|
||||
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
|
||||
obj-$(CONFIG_LEDS_SEAD3) += leds-sead3.o
|
||||
obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o
|
||||
obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
|
||||
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
|
||||
obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
@ -81,21 +81,23 @@ ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
|
|||
down_read(&led_cdev->trigger_lock);
|
||||
|
||||
if (!led_cdev->trigger)
|
||||
len += sprintf(buf+len, "[none] ");
|
||||
len += scnprintf(buf+len, PAGE_SIZE - len, "[none] ");
|
||||
else
|
||||
len += sprintf(buf+len, "none ");
|
||||
len += scnprintf(buf+len, PAGE_SIZE - len, "none ");
|
||||
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
|
||||
trig->name))
|
||||
len += sprintf(buf+len, "[%s] ", trig->name);
|
||||
len += scnprintf(buf+len, PAGE_SIZE - len, "[%s] ",
|
||||
trig->name);
|
||||
else
|
||||
len += sprintf(buf+len, "%s ", trig->name);
|
||||
len += scnprintf(buf+len, PAGE_SIZE - len, "%s ",
|
||||
trig->name);
|
||||
}
|
||||
up_read(&led_cdev->trigger_lock);
|
||||
up_read(&triggers_list_lock);
|
||||
|
||||
len += sprintf(len+buf, "\n");
|
||||
len += scnprintf(len+buf, PAGE_SIZE - len, "\n");
|
||||
return len;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_show);
|
||||
|
@ -108,6 +110,9 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
|
|||
char *envp[2];
|
||||
const char *name;
|
||||
|
||||
if (!led_cdev->trigger && !trig)
|
||||
return;
|
||||
|
||||
name = trig ? trig->name : "none";
|
||||
event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
|
||||
|
||||
|
@ -136,7 +141,9 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
|
|||
if (event) {
|
||||
envp[0] = event;
|
||||
envp[1] = NULL;
|
||||
kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp);
|
||||
if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp))
|
||||
dev_err(led_cdev->dev,
|
||||
"%s: Error sending uevent\n", __func__);
|
||||
kfree(event);
|
||||
}
|
||||
}
|
||||
|
@ -357,7 +364,3 @@ void led_trigger_unregister_simple(struct led_trigger *trig)
|
|||
kfree(trig);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
|
||||
|
||||
MODULE_AUTHOR("Richard Purdie");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("LED Triggers Core");
|
||||
|
|
|
@ -26,15 +26,19 @@ struct gpio_led_data {
|
|||
struct gpio_desc *gpiod;
|
||||
u8 can_sleep;
|
||||
u8 blinking;
|
||||
int (*platform_gpio_blink_set)(struct gpio_desc *desc, int state,
|
||||
unsigned long *delay_on, unsigned long *delay_off);
|
||||
gpio_blink_set_t platform_gpio_blink_set;
|
||||
};
|
||||
|
||||
static inline struct gpio_led_data *
|
||||
cdev_to_gpio_led_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct gpio_led_data, cdev);
|
||||
}
|
||||
|
||||
static void gpio_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct gpio_led_data *led_dat =
|
||||
container_of(led_cdev, struct gpio_led_data, cdev);
|
||||
struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
|
||||
int level;
|
||||
|
||||
if (value == LED_OFF)
|
||||
|
@ -64,8 +68,7 @@ static int gpio_led_set_blocking(struct led_classdev *led_cdev,
|
|||
static int gpio_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on, unsigned long *delay_off)
|
||||
{
|
||||
struct gpio_led_data *led_dat =
|
||||
container_of(led_cdev, struct gpio_led_data, cdev);
|
||||
struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
|
||||
|
||||
led_dat->blinking = 1;
|
||||
return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,
|
||||
|
@ -74,8 +77,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev,
|
|||
|
||||
static int create_gpio_led(const struct gpio_led *template,
|
||||
struct gpio_led_data *led_dat, struct device *parent,
|
||||
int (*blink_set)(struct gpio_desc *, int, unsigned long *,
|
||||
unsigned long *))
|
||||
gpio_blink_set_t blink_set)
|
||||
{
|
||||
int ret, state;
|
||||
|
||||
|
@ -120,10 +122,13 @@ static int create_gpio_led(const struct gpio_led *template,
|
|||
led_dat->platform_gpio_blink_set = blink_set;
|
||||
led_dat->cdev.blink_set = gpio_blink_set;
|
||||
}
|
||||
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
|
||||
state = !!gpiod_get_value_cansleep(led_dat->gpiod);
|
||||
else
|
||||
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
|
||||
state = gpiod_get_value_cansleep(led_dat->gpiod);
|
||||
if (state < 0)
|
||||
return state;
|
||||
} else {
|
||||
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
|
||||
}
|
||||
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
|
||||
if (!template->retain_state_suspended)
|
||||
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
|
@ -134,7 +139,7 @@ static int create_gpio_led(const struct gpio_led *template,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return led_classdev_register(parent, &led_dat->cdev);
|
||||
return devm_led_classdev_register(parent, &led_dat->cdev);
|
||||
}
|
||||
|
||||
struct gpio_leds_priv {
|
||||
|
@ -154,7 +159,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
|
|||
struct fwnode_handle *child;
|
||||
struct gpio_leds_priv *priv;
|
||||
int count, ret;
|
||||
struct device_node *np;
|
||||
|
||||
count = device_get_child_node_count(dev);
|
||||
if (!count)
|
||||
|
@ -168,26 +172,22 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
|
|||
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
|
||||
struct gpio_led led = {};
|
||||
const char *state = NULL;
|
||||
struct device_node *np = to_of_node(child);
|
||||
|
||||
led.gpiod = devm_get_gpiod_from_child(dev, NULL, child);
|
||||
if (IS_ERR(led.gpiod)) {
|
||||
fwnode_handle_put(child);
|
||||
ret = PTR_ERR(led.gpiod);
|
||||
goto err;
|
||||
return ERR_CAST(led.gpiod);
|
||||
}
|
||||
|
||||
np = to_of_node(child);
|
||||
|
||||
if (fwnode_property_present(child, "label")) {
|
||||
fwnode_property_read_string(child, "label", &led.name);
|
||||
} else {
|
||||
if (IS_ENABLED(CONFIG_OF) && !led.name && np)
|
||||
led.name = np->name;
|
||||
if (!led.name) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
ret = fwnode_property_read_string(child, "label", &led.name);
|
||||
if (ret && IS_ENABLED(CONFIG_OF) && np)
|
||||
led.name = np->name;
|
||||
if (!led.name) {
|
||||
fwnode_handle_put(child);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
fwnode_property_read_string(child, "linux,default-trigger",
|
||||
&led.default_trigger);
|
||||
|
||||
|
@ -209,18 +209,13 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
|
|||
ret = create_gpio_led(&led, led_dat, dev, NULL);
|
||||
if (ret < 0) {
|
||||
fwnode_handle_put(child);
|
||||
goto err;
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
led_dat->cdev.dev->of_node = np;
|
||||
priv->num_leds++;
|
||||
}
|
||||
|
||||
return priv;
|
||||
|
||||
err:
|
||||
for (count = priv->num_leds - 1; count >= 0; count--)
|
||||
led_classdev_unregister(&priv->leds[count].cdev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static const struct of_device_id of_gpio_leds_match[] = {
|
||||
|
@ -248,13 +243,8 @@ static int gpio_led_probe(struct platform_device *pdev)
|
|||
ret = create_gpio_led(&pdata->leds[i],
|
||||
&priv->leds[i],
|
||||
&pdev->dev, pdata->gpio_blink_set);
|
||||
if (ret < 0) {
|
||||
/* On failure: unwind the led creations */
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
led_classdev_unregister(
|
||||
&priv->leds[i].cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
priv = gpio_leds_create(pdev);
|
||||
|
@ -267,17 +257,6 @@ static int gpio_led_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->num_leds; i++)
|
||||
led_classdev_unregister(&priv->leds[i].cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gpio_led_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
|
||||
|
@ -292,7 +271,6 @@ static void gpio_led_shutdown(struct platform_device *pdev)
|
|||
|
||||
static struct platform_driver gpio_led_driver = {
|
||||
.probe = gpio_led_probe,
|
||||
.remove = gpio_led_remove,
|
||||
.shutdown = gpio_led_shutdown,
|
||||
.driver = {
|
||||
.name = "leds-gpio",
|
||||
|
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* Copyright 2015-16 Golden Delicious Computers
|
||||
*
|
||||
* Author: Nikolaus Schaller <hns@goldelico.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* LED driver for the IS31FL319{0,1,3,6,9} to drive 1, 3, 6 or 9 light
|
||||
* effect LEDs.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* register numbers */
|
||||
#define IS31FL319X_SHUTDOWN 0x00
|
||||
#define IS31FL319X_CTRL1 0x01
|
||||
#define IS31FL319X_CTRL2 0x02
|
||||
#define IS31FL319X_CONFIG1 0x03
|
||||
#define IS31FL319X_CONFIG2 0x04
|
||||
#define IS31FL319X_RAMP_MODE 0x05
|
||||
#define IS31FL319X_BREATH_MASK 0x06
|
||||
#define IS31FL319X_PWM(channel) (0x07 + channel)
|
||||
#define IS31FL319X_DATA_UPDATE 0x10
|
||||
#define IS31FL319X_T0(channel) (0x11 + channel)
|
||||
#define IS31FL319X_T123_1 0x1a
|
||||
#define IS31FL319X_T123_2 0x1b
|
||||
#define IS31FL319X_T123_3 0x1c
|
||||
#define IS31FL319X_T4(channel) (0x1d + channel)
|
||||
#define IS31FL319X_TIME_UPDATE 0x26
|
||||
#define IS31FL319X_RESET 0xff
|
||||
|
||||
#define IS31FL319X_REG_CNT (IS31FL319X_RESET + 1)
|
||||
|
||||
#define IS31FL319X_MAX_LEDS 9
|
||||
|
||||
/* CS (Current Setting) in CONFIG2 register */
|
||||
#define IS31FL319X_CONFIG2_CS_SHIFT 4
|
||||
#define IS31FL319X_CONFIG2_CS_MASK 0x7
|
||||
#define IS31FL319X_CONFIG2_CS_STEP_REF 12
|
||||
|
||||
#define IS31FL319X_CURRENT_MIN ((u32)5000)
|
||||
#define IS31FL319X_CURRENT_MAX ((u32)40000)
|
||||
#define IS31FL319X_CURRENT_STEP ((u32)5000)
|
||||
#define IS31FL319X_CURRENT_DEFAULT ((u32)20000)
|
||||
|
||||
/* Audio gain in CONFIG2 register */
|
||||
#define IS31FL319X_AUDIO_GAIN_DB_MAX ((u32)21)
|
||||
#define IS31FL319X_AUDIO_GAIN_DB_STEP ((u32)3)
|
||||
|
||||
/*
|
||||
* regmap is used as a cache of chip's register space,
|
||||
* to avoid reading back brightness values from chip,
|
||||
* which is known to hang.
|
||||
*/
|
||||
struct is31fl319x_chip {
|
||||
const struct is31fl319x_chipdef *cdef;
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
u32 audio_gain_db;
|
||||
|
||||
struct is31fl319x_led {
|
||||
struct is31fl319x_chip *chip;
|
||||
struct led_classdev cdev;
|
||||
u32 max_microamp;
|
||||
bool configured;
|
||||
} leds[IS31FL319X_MAX_LEDS];
|
||||
};
|
||||
|
||||
struct is31fl319x_chipdef {
|
||||
int num_leds;
|
||||
};
|
||||
|
||||
static const struct is31fl319x_chipdef is31fl3190_cdef = {
|
||||
.num_leds = 1,
|
||||
};
|
||||
|
||||
static const struct is31fl319x_chipdef is31fl3193_cdef = {
|
||||
.num_leds = 3,
|
||||
};
|
||||
|
||||
static const struct is31fl319x_chipdef is31fl3196_cdef = {
|
||||
.num_leds = 6,
|
||||
};
|
||||
|
||||
static const struct is31fl319x_chipdef is31fl3199_cdef = {
|
||||
.num_leds = 9,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_is31fl319x_match[] = {
|
||||
{ .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, },
|
||||
{ .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, },
|
||||
{ .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, },
|
||||
{ .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, },
|
||||
{ .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, },
|
||||
{ .compatible = "si-en,sn3199", .data = &is31fl3199_cdef, },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_is31fl319x_match);
|
||||
|
||||
static int is31fl319x_brightness_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led,
|
||||
cdev);
|
||||
struct is31fl319x_chip *is31 = led->chip;
|
||||
int chan = led - is31->leds;
|
||||
int ret;
|
||||
int i;
|
||||
u8 ctrl1 = 0, ctrl2 = 0;
|
||||
|
||||
dev_dbg(&is31->client->dev, "%s %d: %d\n", __func__, chan, brightness);
|
||||
|
||||
mutex_lock(&is31->lock);
|
||||
|
||||
/* update PWM register */
|
||||
ret = regmap_write(is31->regmap, IS31FL319X_PWM(chan), brightness);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* read current brightness of all PWM channels */
|
||||
for (i = 0; i < is31->cdef->num_leds; i++) {
|
||||
unsigned int pwm_value;
|
||||
bool on;
|
||||
|
||||
/*
|
||||
* since neither cdev nor the chip can provide
|
||||
* the current setting, we read from the regmap cache
|
||||
*/
|
||||
|
||||
ret = regmap_read(is31->regmap, IS31FL319X_PWM(i), &pwm_value);
|
||||
dev_dbg(&is31->client->dev, "%s read %d: ret=%d: %d\n",
|
||||
__func__, i, ret, pwm_value);
|
||||
on = ret >= 0 && pwm_value > LED_OFF;
|
||||
|
||||
if (i < 3)
|
||||
ctrl1 |= on << i; /* 0..2 => bit 0..2 */
|
||||
else if (i < 6)
|
||||
ctrl1 |= on << (i + 1); /* 3..5 => bit 4..6 */
|
||||
else
|
||||
ctrl2 |= on << (i - 6); /* 6..8 => bit 0..2 */
|
||||
}
|
||||
|
||||
if (ctrl1 > 0 || ctrl2 > 0) {
|
||||
dev_dbg(&is31->client->dev, "power up %02x %02x\n",
|
||||
ctrl1, ctrl2);
|
||||
regmap_write(is31->regmap, IS31FL319X_CTRL1, ctrl1);
|
||||
regmap_write(is31->regmap, IS31FL319X_CTRL2, ctrl2);
|
||||
/* update PWMs */
|
||||
regmap_write(is31->regmap, IS31FL319X_DATA_UPDATE, 0x00);
|
||||
/* enable chip from shut down */
|
||||
ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01);
|
||||
} else {
|
||||
dev_dbg(&is31->client->dev, "power down\n");
|
||||
/* shut down (no need to clear CTRL1/2) */
|
||||
ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x00);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&is31->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int is31fl319x_parse_child_dt(const struct device *dev,
|
||||
const struct device_node *child,
|
||||
struct is31fl319x_led *led)
|
||||
{
|
||||
struct led_classdev *cdev = &led->cdev;
|
||||
int ret;
|
||||
|
||||
if (of_property_read_string(child, "label", &cdev->name))
|
||||
cdev->name = child->name;
|
||||
|
||||
ret = of_property_read_string(child, "linux,default-trigger",
|
||||
&cdev->default_trigger);
|
||||
if (ret < 0 && ret != -EINVAL) /* is optional */
|
||||
return ret;
|
||||
|
||||
led->max_microamp = IS31FL319X_CURRENT_DEFAULT;
|
||||
ret = of_property_read_u32(child, "led-max-microamp",
|
||||
&led->max_microamp);
|
||||
if (!ret) {
|
||||
if (led->max_microamp < IS31FL319X_CURRENT_MIN)
|
||||
return -EINVAL; /* not supported */
|
||||
led->max_microamp = min(led->max_microamp,
|
||||
IS31FL319X_CURRENT_MAX);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is31fl319x_parse_dt(struct device *dev,
|
||||
struct is31fl319x_chip *is31)
|
||||
{
|
||||
struct device_node *np = dev->of_node, *child;
|
||||
const struct of_device_id *of_dev_id;
|
||||
int count;
|
||||
int ret;
|
||||
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
of_dev_id = of_match_device(of_is31fl319x_match, dev);
|
||||
if (!of_dev_id) {
|
||||
dev_err(dev, "Failed to match device with supported chips\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
is31->cdef = of_dev_id->data;
|
||||
|
||||
count = of_get_child_count(np);
|
||||
|
||||
dev_dbg(dev, "probe %s with %d leds defined in DT\n",
|
||||
of_dev_id->compatible, count);
|
||||
|
||||
if (!count || count > is31->cdef->num_leds) {
|
||||
dev_err(dev, "Number of leds defined must be between 1 and %u\n",
|
||||
is31->cdef->num_leds);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
struct is31fl319x_led *led;
|
||||
u32 reg;
|
||||
|
||||
ret = of_property_read_u32(child, "reg", ®);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read led 'reg' property\n");
|
||||
goto put_child_node;
|
||||
}
|
||||
|
||||
if (reg < 1 || reg > is31->cdef->num_leds) {
|
||||
dev_err(dev, "invalid led reg %u\n", reg);
|
||||
ret = -EINVAL;
|
||||
goto put_child_node;
|
||||
}
|
||||
|
||||
led = &is31->leds[reg - 1];
|
||||
|
||||
if (led->configured) {
|
||||
dev_err(dev, "led %u is already configured\n", reg);
|
||||
ret = -EINVAL;
|
||||
goto put_child_node;
|
||||
}
|
||||
|
||||
ret = is31fl319x_parse_child_dt(dev, child, led);
|
||||
if (ret) {
|
||||
dev_err(dev, "led %u DT parsing failed\n", reg);
|
||||
goto put_child_node;
|
||||
}
|
||||
|
||||
led->configured = true;
|
||||
}
|
||||
|
||||
is31->audio_gain_db = 0;
|
||||
ret = of_property_read_u32(np, "audio-gain-db", &is31->audio_gain_db);
|
||||
if (!ret)
|
||||
is31->audio_gain_db = min(is31->audio_gain_db,
|
||||
IS31FL319X_AUDIO_GAIN_DB_MAX);
|
||||
|
||||
return 0;
|
||||
|
||||
put_child_node:
|
||||
of_node_put(child);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg)
|
||||
{ /* we have no readable registers */
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is31fl319x_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{ /* volatile registers are not cached */
|
||||
switch (reg) {
|
||||
case IS31FL319X_DATA_UPDATE:
|
||||
case IS31FL319X_TIME_UPDATE:
|
||||
case IS31FL319X_RESET:
|
||||
return true; /* always write-through */
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct reg_default is31fl319x_reg_defaults[] = {
|
||||
{ IS31FL319X_CONFIG1, 0x00},
|
||||
{ IS31FL319X_CONFIG2, 0x00},
|
||||
{ IS31FL319X_PWM(0), 0x00},
|
||||
{ IS31FL319X_PWM(1), 0x00},
|
||||
{ IS31FL319X_PWM(2), 0x00},
|
||||
{ IS31FL319X_PWM(3), 0x00},
|
||||
{ IS31FL319X_PWM(4), 0x00},
|
||||
{ IS31FL319X_PWM(5), 0x00},
|
||||
{ IS31FL319X_PWM(6), 0x00},
|
||||
{ IS31FL319X_PWM(7), 0x00},
|
||||
{ IS31FL319X_PWM(8), 0x00},
|
||||
};
|
||||
|
||||
static struct regmap_config regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = IS31FL319X_REG_CNT,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
.readable_reg = is31fl319x_readable_reg,
|
||||
.volatile_reg = is31fl319x_volatile_reg,
|
||||
.reg_defaults = is31fl319x_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(is31fl319x_reg_defaults),
|
||||
};
|
||||
|
||||
static inline int is31fl319x_microamp_to_cs(struct device *dev, u32 microamp)
|
||||
{ /* round down to nearest supported value (range check done by caller) */
|
||||
u32 step = microamp / IS31FL319X_CURRENT_STEP;
|
||||
|
||||
return ((IS31FL319X_CONFIG2_CS_STEP_REF - step) &
|
||||
IS31FL319X_CONFIG2_CS_MASK) <<
|
||||
IS31FL319X_CONFIG2_CS_SHIFT; /* CS encoding */
|
||||
}
|
||||
|
||||
static inline int is31fl319x_db_to_gain(u32 dezibel)
|
||||
{ /* round down to nearest supported value (range check done by caller) */
|
||||
return dezibel / IS31FL319X_AUDIO_GAIN_DB_STEP;
|
||||
}
|
||||
|
||||
static int is31fl319x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct is31fl319x_chip *is31;
|
||||
struct device *dev = &client->dev;
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(dev->parent);
|
||||
int err;
|
||||
int i = 0;
|
||||
u32 aggregated_led_microamp = IS31FL319X_CURRENT_MAX;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
|
||||
return -EIO;
|
||||
|
||||
is31 = devm_kzalloc(&client->dev, sizeof(*is31), GFP_KERNEL);
|
||||
if (!is31)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&is31->lock);
|
||||
|
||||
err = is31fl319x_parse_dt(&client->dev, is31);
|
||||
if (err)
|
||||
goto free_mutex;
|
||||
|
||||
is31->client = client;
|
||||
is31->regmap = devm_regmap_init_i2c(client, ®map_config);
|
||||
if (IS_ERR(is31->regmap)) {
|
||||
dev_err(&client->dev, "failed to allocate register map\n");
|
||||
err = PTR_ERR(is31->regmap);
|
||||
goto free_mutex;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, is31);
|
||||
|
||||
/* check for write-reply from chip (we can't read any registers) */
|
||||
err = regmap_write(is31->regmap, IS31FL319X_RESET, 0x00);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev, "no response from chip write: err = %d\n",
|
||||
err);
|
||||
err = -EIO; /* does not answer */
|
||||
goto free_mutex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernel conventions require per-LED led-max-microamp property.
|
||||
* But the chip does not allow to limit individual LEDs.
|
||||
* So we take minimum from all subnodes for safety of hardware.
|
||||
*/
|
||||
for (i = 0; i < is31->cdef->num_leds; i++)
|
||||
if (is31->leds[i].configured &&
|
||||
is31->leds[i].max_microamp < aggregated_led_microamp)
|
||||
aggregated_led_microamp = is31->leds[i].max_microamp;
|
||||
|
||||
regmap_write(is31->regmap, IS31FL319X_CONFIG2,
|
||||
is31fl319x_microamp_to_cs(dev, aggregated_led_microamp) |
|
||||
is31fl319x_db_to_gain(is31->audio_gain_db));
|
||||
|
||||
for (i = 0; i < is31->cdef->num_leds; i++) {
|
||||
struct is31fl319x_led *led = &is31->leds[i];
|
||||
|
||||
if (!led->configured)
|
||||
continue;
|
||||
|
||||
led->chip = is31;
|
||||
led->cdev.brightness_set_blocking = is31fl319x_brightness_set;
|
||||
|
||||
err = devm_led_classdev_register(&client->dev, &led->cdev);
|
||||
if (err < 0)
|
||||
goto free_mutex;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_mutex:
|
||||
mutex_destroy(&is31->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int is31fl319x_remove(struct i2c_client *client)
|
||||
{
|
||||
struct is31fl319x_chip *is31 = i2c_get_clientdata(client);
|
||||
|
||||
mutex_destroy(&is31->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* i2c-core (and modalias) requires that id_table be properly filled,
|
||||
* even though it is not used for DeviceTree based instantiation.
|
||||
*/
|
||||
static const struct i2c_device_id is31fl319x_id[] = {
|
||||
{ "is31fl3190" },
|
||||
{ "is31fl3191" },
|
||||
{ "is31fl3193" },
|
||||
{ "is31fl3196" },
|
||||
{ "is31fl3199" },
|
||||
{ "sn3199" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, is31fl319x_id);
|
||||
|
||||
static struct i2c_driver is31fl319x_driver = {
|
||||
.driver = {
|
||||
.name = "leds-is31fl319x",
|
||||
.of_match_table = of_match_ptr(of_is31fl319x_match),
|
||||
},
|
||||
.probe = is31fl319x_probe,
|
||||
.remove = is31fl319x_remove,
|
||||
.id_table = is31fl319x_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(is31fl319x_driver);
|
||||
|
||||
MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
|
||||
MODULE_AUTHOR("Andrey Utkin <andrey_utkin@fastmail.com>");
|
||||
MODULE_DESCRIPTION("IS31FL319X LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* drivers/leds/leds-mlxcpld.c
|
||||
* Copyright (c) 2016 Mellanox Technologies. All rights reserved.
|
||||
* Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the names of the copyright holders nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* Alternatively, this software may be distributed under the terms of the
|
||||
* GNU General Public License ("GPL") version 2 as published by the Free
|
||||
* Software Foundation.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */
|
||||
|
||||
/* Color codes for LEDs */
|
||||
#define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */
|
||||
#define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */
|
||||
#define MLXCPLD_LED_IS_OFF 0x00 /* Off */
|
||||
#define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */
|
||||
#define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \
|
||||
MLXCPLD_LED_OFFSET_HALF)
|
||||
#define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \
|
||||
MLXCPLD_LED_OFFSET_FULL)
|
||||
#define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */
|
||||
#define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \
|
||||
MLXCPLD_LED_OFFSET_HALF)
|
||||
#define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \
|
||||
MLXCPLD_LED_OFFSET_FULL)
|
||||
#define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */
|
||||
#define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */
|
||||
|
||||
/**
|
||||
* mlxcpld_param - LED access parameters:
|
||||
* @offset - offset for LED access in CPLD device
|
||||
* @mask - mask for LED access in CPLD device
|
||||
* @base_color - base color code for LED
|
||||
**/
|
||||
struct mlxcpld_param {
|
||||
u8 offset;
|
||||
u8 mask;
|
||||
u8 base_color;
|
||||
};
|
||||
|
||||
/**
|
||||
* mlxcpld_led_priv - LED private data:
|
||||
* @cled - LED class device instance
|
||||
* @param - LED CPLD access parameters
|
||||
**/
|
||||
struct mlxcpld_led_priv {
|
||||
struct led_classdev cdev;
|
||||
struct mlxcpld_param param;
|
||||
};
|
||||
|
||||
#define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev)
|
||||
|
||||
/**
|
||||
* mlxcpld_led_profile - system LED profile (defined per system class):
|
||||
* @offset - offset for LED access in CPLD device
|
||||
* @mask - mask for LED access in CPLD device
|
||||
* @base_color - base color code
|
||||
* @brightness - default brightness setting (on/off)
|
||||
* @name - LED name
|
||||
**/
|
||||
struct mlxcpld_led_profile {
|
||||
u8 offset;
|
||||
u8 mask;
|
||||
u8 base_color;
|
||||
enum led_brightness brightness;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/**
|
||||
* mlxcpld_led_pdata - system LED private data
|
||||
* @pdev - platform device pointer
|
||||
* @pled - LED class device instance
|
||||
* @profile - system configuration profile
|
||||
* @num_led_instances - number of LED instances
|
||||
* @lock - device access lock
|
||||
**/
|
||||
struct mlxcpld_led_pdata {
|
||||
struct platform_device *pdev;
|
||||
struct mlxcpld_led_priv *pled;
|
||||
struct mlxcpld_led_profile *profile;
|
||||
int num_led_instances;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static struct mlxcpld_led_pdata *mlxcpld_led;
|
||||
|
||||
/* Default profile fit the next Mellanox systems:
|
||||
* "msx6710", "msx6720", "msb7700", "msn2700", "msx1410",
|
||||
* "msn2410", "msb7800", "msn2740"
|
||||
*/
|
||||
static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = {
|
||||
{
|
||||
0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:fan1:green",
|
||||
},
|
||||
{
|
||||
0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:fan1:red",
|
||||
},
|
||||
{
|
||||
0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:fan2:green",
|
||||
},
|
||||
{
|
||||
0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:fan2:red",
|
||||
},
|
||||
{
|
||||
0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:fan3:green",
|
||||
},
|
||||
{
|
||||
0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:fan3:red",
|
||||
},
|
||||
{
|
||||
0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:fan4:green",
|
||||
},
|
||||
{
|
||||
0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:fan4:red",
|
||||
},
|
||||
{
|
||||
0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:psu:green",
|
||||
},
|
||||
{
|
||||
0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:psu:red",
|
||||
},
|
||||
{
|
||||
0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:status:green",
|
||||
},
|
||||
{
|
||||
0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:status:red",
|
||||
},
|
||||
};
|
||||
|
||||
/* Profile fit the Mellanox systems based on "msn2100" */
|
||||
static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = {
|
||||
{
|
||||
0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:fan:green",
|
||||
},
|
||||
{
|
||||
0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:fan:red",
|
||||
},
|
||||
{
|
||||
0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:psu1:green",
|
||||
},
|
||||
{
|
||||
0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:psu1:red",
|
||||
},
|
||||
{
|
||||
0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:psu2:green",
|
||||
},
|
||||
{
|
||||
0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:psu2:red",
|
||||
},
|
||||
{
|
||||
0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
|
||||
"mlxcpld:status:green",
|
||||
},
|
||||
{
|
||||
0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:status:red",
|
||||
},
|
||||
{
|
||||
0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF,
|
||||
"mlxcpld:uid:blue",
|
||||
},
|
||||
};
|
||||
|
||||
enum mlxcpld_led_platform_types {
|
||||
MLXCPLD_LED_PLATFORM_DEFAULT,
|
||||
MLXCPLD_LED_PLATFORM_MSN2100,
|
||||
};
|
||||
|
||||
static const char *mlx_product_names[] = {
|
||||
"DEFAULT",
|
||||
"MSN2100",
|
||||
};
|
||||
|
||||
static enum
|
||||
mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void)
|
||||
{
|
||||
const char *mlx_product_name;
|
||||
int i;
|
||||
|
||||
mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
|
||||
if (!mlx_product_name)
|
||||
return MLXCPLD_LED_PLATFORM_DEFAULT;
|
||||
|
||||
for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) {
|
||||
if (strstr(mlx_product_name, mlx_product_names[i]))
|
||||
return i;
|
||||
}
|
||||
|
||||
return MLXCPLD_LED_PLATFORM_DEFAULT;
|
||||
}
|
||||
|
||||
static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag,
|
||||
u8 *data)
|
||||
{
|
||||
u32 addr = base + offset;
|
||||
|
||||
if (rw_flag == 0)
|
||||
outb(*data, addr);
|
||||
else
|
||||
*data = inb(addr);
|
||||
}
|
||||
|
||||
static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset)
|
||||
{
|
||||
u8 nib, val;
|
||||
|
||||
/*
|
||||
* Each LED is controlled through low or high nibble of the relevant
|
||||
* CPLD register. Register offset is specified by off parameter.
|
||||
* Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
|
||||
* 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
|
||||
* green.
|
||||
* Parameter mask specifies which nibble is used for specific LED: mask
|
||||
* 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
|
||||
* higher nibble (bits from 4 to 7).
|
||||
*/
|
||||
spin_lock(&mlxcpld_led->lock);
|
||||
mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1,
|
||||
&val);
|
||||
nib = (mask == 0xf0) ? vset : (vset << 4);
|
||||
val = (val & mask) | nib;
|
||||
mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0,
|
||||
&val);
|
||||
spin_unlock(&mlxcpld_led->lock);
|
||||
}
|
||||
|
||||
static void mlxcpld_led_brightness_set(struct led_classdev *led,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct mlxcpld_led_priv *pled = cdev_to_priv(led);
|
||||
|
||||
if (value) {
|
||||
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
|
||||
pled->param.base_color);
|
||||
return;
|
||||
}
|
||||
|
||||
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
|
||||
MLXCPLD_LED_IS_OFF);
|
||||
}
|
||||
|
||||
static int mlxcpld_led_blink_set(struct led_classdev *led,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct mlxcpld_led_priv *pled = cdev_to_priv(led);
|
||||
|
||||
/*
|
||||
* HW supports two types of blinking: full (6Hz) and half (3Hz).
|
||||
* For delay on/off zero default setting 3Hz is used.
|
||||
*/
|
||||
if (!(*delay_on == 0 && *delay_off == 0) &&
|
||||
!(*delay_on == MLXCPLD_LED_BLINK_3HZ &&
|
||||
*delay_off == MLXCPLD_LED_BLINK_3HZ) &&
|
||||
!(*delay_on == MLXCPLD_LED_BLINK_6HZ &&
|
||||
*delay_off == MLXCPLD_LED_BLINK_6HZ))
|
||||
return -EINVAL;
|
||||
|
||||
if (*delay_on == MLXCPLD_LED_BLINK_6HZ)
|
||||
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
|
||||
pled->param.base_color +
|
||||
MLXCPLD_LED_OFFSET_FULL);
|
||||
else
|
||||
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
|
||||
pled->param.base_color +
|
||||
MLXCPLD_LED_OFFSET_HALF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mlxcpld_led_config(struct device *dev,
|
||||
struct mlxcpld_led_pdata *cpld)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
|
||||
cpld->pled = devm_kzalloc(dev, sizeof(struct mlxcpld_led_priv) *
|
||||
cpld->num_led_instances, GFP_KERNEL);
|
||||
if (!cpld->pled)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < cpld->num_led_instances; i++) {
|
||||
cpld->pled[i].cdev.name = cpld->profile[i].name;
|
||||
cpld->pled[i].cdev.brightness = cpld->profile[i].brightness;
|
||||
cpld->pled[i].cdev.max_brightness = 1;
|
||||
cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set;
|
||||
cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set;
|
||||
cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
err = devm_led_classdev_register(dev, &cpld->pled[i].cdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset;
|
||||
cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask;
|
||||
cpld->pled[i].param.base_color =
|
||||
mlxcpld_led->profile[i].base_color;
|
||||
|
||||
if (mlxcpld_led->profile[i].brightness)
|
||||
mlxcpld_led_brightness_set(&cpld->pled[i].cdev,
|
||||
mlxcpld_led->profile[i].brightness);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mlxcpld_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
enum mlxcpld_led_platform_types mlxcpld_led_plat =
|
||||
mlxcpld_led_platform_check_sys_type();
|
||||
|
||||
mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led),
|
||||
GFP_KERNEL);
|
||||
if (!mlxcpld_led)
|
||||
return -ENOMEM;
|
||||
|
||||
mlxcpld_led->pdev = pdev;
|
||||
|
||||
switch (mlxcpld_led_plat) {
|
||||
case MLXCPLD_LED_PLATFORM_MSN2100:
|
||||
mlxcpld_led->profile = mlxcpld_led_msn2100_profile;
|
||||
mlxcpld_led->num_led_instances =
|
||||
ARRAY_SIZE(mlxcpld_led_msn2100_profile);
|
||||
break;
|
||||
|
||||
default:
|
||||
mlxcpld_led->profile = mlxcpld_led_default_profile;
|
||||
mlxcpld_led->num_led_instances =
|
||||
ARRAY_SIZE(mlxcpld_led_default_profile);
|
||||
break;
|
||||
}
|
||||
|
||||
spin_lock_init(&mlxcpld_led->lock);
|
||||
|
||||
return mlxcpld_led_config(&pdev->dev, mlxcpld_led);
|
||||
}
|
||||
|
||||
static struct platform_driver mlxcpld_led_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init mlxcpld_led_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int err;
|
||||
|
||||
pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
pr_err("Device allocation failed\n");
|
||||
return PTR_ERR(pdev);
|
||||
}
|
||||
|
||||
err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe);
|
||||
if (err) {
|
||||
pr_err("Probe platform driver failed\n");
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit mlxcpld_led_exit(void)
|
||||
{
|
||||
platform_device_unregister(mlxcpld_led->pdev);
|
||||
platform_driver_unregister(&mlxcpld_led_driver);
|
||||
}
|
||||
|
||||
module_init(mlxcpld_led_init);
|
||||
module_exit(mlxcpld_led_exit);
|
||||
|
||||
MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
|
||||
MODULE_DESCRIPTION("Mellanox board LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:leds_mlxcpld");
|
|
@ -0,0 +1,191 @@
|
|||
/* Copyright (c) 2010, 2011, 2016 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define PM8058_LED_TYPE_COMMON 0x00
|
||||
#define PM8058_LED_TYPE_KEYPAD 0x01
|
||||
#define PM8058_LED_TYPE_FLASH 0x02
|
||||
|
||||
#define PM8058_LED_TYPE_COMMON_MASK 0xf8
|
||||
#define PM8058_LED_TYPE_KEYPAD_MASK 0xf0
|
||||
#define PM8058_LED_TYPE_COMMON_SHIFT 3
|
||||
#define PM8058_LED_TYPE_KEYPAD_SHIFT 4
|
||||
|
||||
struct pm8058_led {
|
||||
struct regmap *map;
|
||||
u32 reg;
|
||||
u32 ledtype;
|
||||
struct led_classdev cdev;
|
||||
};
|
||||
|
||||
static void pm8058_led_set(struct led_classdev *cled,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pm8058_led *led;
|
||||
int ret = 0;
|
||||
unsigned int mask = 0;
|
||||
unsigned int val = 0;
|
||||
|
||||
led = container_of(cled, struct pm8058_led, cdev);
|
||||
switch (led->ledtype) {
|
||||
case PM8058_LED_TYPE_COMMON:
|
||||
mask = PM8058_LED_TYPE_COMMON_MASK;
|
||||
val = value << PM8058_LED_TYPE_COMMON_SHIFT;
|
||||
break;
|
||||
case PM8058_LED_TYPE_KEYPAD:
|
||||
case PM8058_LED_TYPE_FLASH:
|
||||
mask = PM8058_LED_TYPE_KEYPAD_MASK;
|
||||
val = value << PM8058_LED_TYPE_KEYPAD_SHIFT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(led->map, led->reg, mask, val);
|
||||
if (ret)
|
||||
pr_err("Failed to set LED brightness\n");
|
||||
}
|
||||
|
||||
static enum led_brightness pm8058_led_get(struct led_classdev *cled)
|
||||
{
|
||||
struct pm8058_led *led;
|
||||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
led = container_of(cled, struct pm8058_led, cdev);
|
||||
|
||||
ret = regmap_read(led->map, led->reg, &val);
|
||||
if (ret) {
|
||||
pr_err("Failed to get LED brightness\n");
|
||||
return LED_OFF;
|
||||
}
|
||||
|
||||
switch (led->ledtype) {
|
||||
case PM8058_LED_TYPE_COMMON:
|
||||
val &= PM8058_LED_TYPE_COMMON_MASK;
|
||||
val >>= PM8058_LED_TYPE_COMMON_SHIFT;
|
||||
break;
|
||||
case PM8058_LED_TYPE_KEYPAD:
|
||||
case PM8058_LED_TYPE_FLASH:
|
||||
val &= PM8058_LED_TYPE_KEYPAD_MASK;
|
||||
val >>= PM8058_LED_TYPE_KEYPAD_SHIFT;
|
||||
break;
|
||||
default:
|
||||
val = LED_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int pm8058_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pm8058_led *led;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int ret;
|
||||
struct regmap *map;
|
||||
const char *state;
|
||||
enum led_brightness maxbright;
|
||||
|
||||
led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
|
||||
if (!led)
|
||||
return -ENOMEM;
|
||||
|
||||
led->ledtype = (u32)of_device_get_match_data(&pdev->dev);
|
||||
|
||||
map = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!map) {
|
||||
dev_err(&pdev->dev, "Parent regmap unavailable.\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
led->map = map;
|
||||
|
||||
ret = of_property_read_u32(np, "reg", &led->reg);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "no register offset specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Use label else node name */
|
||||
led->cdev.name = of_get_property(np, "label", NULL) ? : np->name;
|
||||
led->cdev.default_trigger =
|
||||
of_get_property(np, "linux,default-trigger", NULL);
|
||||
led->cdev.brightness_set = pm8058_led_set;
|
||||
led->cdev.brightness_get = pm8058_led_get;
|
||||
if (led->ledtype == PM8058_LED_TYPE_COMMON)
|
||||
maxbright = 31; /* 5 bits */
|
||||
else
|
||||
maxbright = 15; /* 4 bits */
|
||||
led->cdev.max_brightness = maxbright;
|
||||
|
||||
state = of_get_property(np, "default-state", NULL);
|
||||
if (state) {
|
||||
if (!strcmp(state, "keep")) {
|
||||
led->cdev.brightness = pm8058_led_get(&led->cdev);
|
||||
} else if (!strcmp(state, "on")) {
|
||||
led->cdev.brightness = maxbright;
|
||||
pm8058_led_set(&led->cdev, maxbright);
|
||||
} else {
|
||||
led->cdev.brightness = LED_OFF;
|
||||
pm8058_led_set(&led->cdev, LED_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
if (led->ledtype == PM8058_LED_TYPE_KEYPAD ||
|
||||
led->ledtype == PM8058_LED_TYPE_FLASH)
|
||||
led->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = devm_led_classdev_register(&pdev->dev, &led->cdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "unable to register led \"%s\"\n",
|
||||
led->cdev.name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id pm8058_leds_id_table[] = {
|
||||
{
|
||||
.compatible = "qcom,pm8058-led",
|
||||
.data = (void *)PM8058_LED_TYPE_COMMON
|
||||
},
|
||||
{
|
||||
.compatible = "qcom,pm8058-keypad-led",
|
||||
.data = (void *)PM8058_LED_TYPE_KEYPAD
|
||||
},
|
||||
{
|
||||
.compatible = "qcom,pm8058-flash-led",
|
||||
.data = (void *)PM8058_LED_TYPE_FLASH
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pm8058_leds_id_table);
|
||||
|
||||
static struct platform_driver pm8058_led_driver = {
|
||||
.probe = pm8058_led_probe,
|
||||
.driver = {
|
||||
.name = "pm8058-leds",
|
||||
.of_match_table = pm8058_leds_id_table,
|
||||
},
|
||||
};
|
||||
module_platform_driver(pm8058_led_driver);
|
||||
|
||||
MODULE_DESCRIPTION("PM8058 LEDs driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:pm8058-leds");
|
|
@ -359,6 +359,11 @@ struct led_platform_data {
|
|||
struct led_info *leds;
|
||||
};
|
||||
|
||||
struct gpio_desc;
|
||||
typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off);
|
||||
|
||||
/* For the leds-gpio driver */
|
||||
struct gpio_led {
|
||||
const char *name;
|
||||
|
@ -382,9 +387,7 @@ struct gpio_led_platform_data {
|
|||
#define GPIO_LED_NO_BLINK_LOW 0 /* No blink GPIO state low */
|
||||
#define GPIO_LED_NO_BLINK_HIGH 1 /* No blink GPIO state high */
|
||||
#define GPIO_LED_BLINK 2 /* Please, blink */
|
||||
int (*gpio_blink_set)(struct gpio_desc *desc, int state,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off);
|
||||
gpio_blink_set_t gpio_blink_set;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_NEW_LEDS
|
||||
|
|
Loading…
Reference in New Issue