diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-04 10:25:53 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-04 10:25:53 -0700 |
commit | f80fa1822d6ccca369578108dc70576cff6c67a0 (patch) | |
tree | 52f048d60d00ea6f44c70ef8f4dc0d69247cc8ad | |
parent | 21f54ddae449f4bdd9f1498124901d67202243d9 (diff) | |
parent | 6f3bad9670729ea3a7c78b3752a89c94ffa2397a (diff) | |
download | linux-f80fa1822d6ccca369578108dc70576cff6c67a0.tar.bz2 |
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
20 files changed, 1446 insertions, 99 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led index 3646ec85d513..86ace287d48b 100644 --- a/Documentation/ABI/testing/sysfs-class-led +++ b/Documentation/ABI/testing/sysfs-class-led @@ -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 diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-oneshot b/Documentation/ABI/testing/sysfs-class-led-trigger-oneshot new file mode 100644 index 000000000000..378a3a4df3c8 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-oneshot @@ -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. diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt index 93ef6e6e43b5..696be5792625 100644 --- a/Documentation/devicetree/bindings/leds/common.txt +++ b/Documentation/devicetree/bindings/leds/common.txt @@ -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 diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6328.txt b/Documentation/devicetree/bindings/leds/leds-bcm6328.txt index 3f48c1eaf085..ccebce597f37 100644 --- a/Documentation/devicetree/bindings/leds/leds-bcm6328.txt +++ b/Documentation/devicetree/bindings/leds/leds-bcm6328.txt @@ -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 diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt b/Documentation/devicetree/bindings/leds/leds-bcm6358.txt index b22a55bcc65d..da5708e7b43b 100644 --- a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt +++ b/Documentation/devicetree/bindings/leds/leds-bcm6358.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 diff --git a/Documentation/devicetree/bindings/leds/leds-gpio.txt b/Documentation/devicetree/bindings/leds/leds-gpio.txt index 5b1b43a64265..76535ca37120 100644 --- a/Documentation/devicetree/bindings/leds/leds-gpio.txt +++ b/Documentation/devicetree/bindings/leds/leds-gpio.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) diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl319x.txt b/Documentation/devicetree/bindings/leds/leds-is31fl319x.txt new file mode 100644 index 000000000000..fc2603484544 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-is31fl319x.txt @@ -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"; + }; +}; diff --git a/Documentation/devicetree/bindings/leds/leds-pm8058.txt b/Documentation/devicetree/bindings/leds/leds-pm8058.txt new file mode 100644 index 000000000000..89584c49aab2 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-pm8058.txt @@ -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"; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/leds/register-bit-led.txt b/Documentation/devicetree/bindings/leds/register-bit-led.txt index 379cefdc0bda..59b56365f648 100644 --- a/Documentation/devicetree/bindings/leds/register-bit-led.txt +++ b/Documentation/devicetree/bindings/leds/register-bit-led.txt @@ -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: diff --git a/Documentation/leds/leds-mlxcpld.txt b/Documentation/leds/leds-mlxcpld.txt new file mode 100644 index 000000000000..a0e8fd457117 --- /dev/null +++ b/Documentation/leds/leds-mlxcpld.txt @@ -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. diff --git a/Documentation/leds/ledtrig-oneshot.txt b/Documentation/leds/ledtrig-oneshot.txt index 07cd1fa41a3a..fe57474a12e2 100644 --- a/Documentation/leds/ledtrig-oneshot.txt +++ b/Documentation/leds/ledtrig-oneshot.txt @@ -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: diff --git a/MAINTAINERS b/MAINTAINERS index d33c0383575b..ac8f3cec03f9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9dcc9b13d495..7a628c6516f6 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -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" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 0684c865a1c0..3965070190f5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -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 diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index c92702a684ce..431123b048a2 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -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"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 9b991d46ed84..d400dcaf4d29 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -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", diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c new file mode 100644 index 000000000000..f123309597e4 --- /dev/null +++ b/drivers/leds/leds-is31fl319x.c @@ -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"); diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c new file mode 100644 index 000000000000..197ab9b29a9c --- /dev/null +++ b/drivers/leds/leds-mlxcpld.c @@ -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"); diff --git a/drivers/leds/leds-pm8058.c b/drivers/leds/leds-pm8058.c new file mode 100644 index 000000000000..a52674327857 --- /dev/null +++ b/drivers/leds/leds-pm8058.c @@ -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"); diff --git a/include/linux/leds.h b/include/linux/leds.h index 8a3b5d29602f..ddfcb2df3656 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -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 |