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:
Linus Torvalds 2016-10-04 10:25:53 -07:00
commit f80fa1822d
20 changed files with 1446 additions and 99 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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";
};
};

View File

@ -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";
};
};
};

View File

@ -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:

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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");

View File

@ -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",

View File

@ -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", &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, &regmap_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");

430
drivers/leds/leds-mlxcpld.c Normal file
View File

@ -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");

191
drivers/leds/leds-pm8058.c Normal file
View File

@ -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");

View File

@ -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