1 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
3 // Copyright (c) 2018 Mellanox Technologies. All rights reserved.
4 // Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
6 #include <linux/bitops.h>
7 #include <linux/device.h>
9 #include <linux/leds.h>
10 #include <linux/module.h>
11 #include <linux/of_device.h>
12 #include <linux/platform_data/mlxreg.h>
13 #include <linux/platform_device.h>
14 #include <linux/regmap.h>
17 #define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */
18 #define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */
19 #define MLXREG_LED_IS_OFF 0x00 /* Off */
20 #define MLXREG_LED_RED_SOLID 0x05 /* Solid red */
21 #define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */
22 #define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */
23 #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */
24 #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */
25 #define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */
28 * struct mlxreg_led_data - led control data:
30 * @data: led configuration data;
31 * @led_classdev: led class data;
32 * @base_color: base led color (other colors have constant offset from base);
33 * @led_data: led data;
34 * @data_parent: pointer to private device control data of parent;
36 struct mlxreg_led_data
{
37 struct mlxreg_core_data
*data
;
38 struct led_classdev led_cdev
;
41 char led_cdev_name
[MLXREG_CORE_LABEL_MAX_SIZE
];
44 #define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
47 * struct mlxreg_led_priv_data - platform private data:
49 * @pdev: platform device;
50 * @pdata: platform data;
51 * @access_lock: mutex for attribute IO access;
53 struct mlxreg_led_priv_data
{
54 struct platform_device
*pdev
;
55 struct mlxreg_core_platform_data
*pdata
;
56 struct mutex access_lock
; /* protect IO operations */
60 mlxreg_led_store_hw(struct mlxreg_led_data
*led_data
, u8 vset
)
62 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
63 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
64 struct mlxreg_core_data
*data
= led_data
->data
;
70 * Each LED is controlled through low or high nibble of the relevant
71 * register byte. Register offset is specified by off parameter.
72 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
73 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
75 * Parameter mask specifies which nibble is used for specific LED: mask
76 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
77 * higher nibble (bits from 4 to 7).
79 mutex_lock(&priv
->access_lock
);
81 ret
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
85 nib
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? rol32(vset
, data
->bit
) :
86 rol32(vset
, data
->bit
+ 4);
87 regval
= (regval
& data
->mask
) | nib
;
89 ret
= regmap_write(led_pdata
->regmap
, data
->reg
, regval
);
92 mutex_unlock(&priv
->access_lock
);
97 static enum led_brightness
98 mlxreg_led_get_hw(struct mlxreg_led_data
*led_data
)
100 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
101 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
102 struct mlxreg_core_data
*data
= led_data
->data
;
107 * Each LED is controlled through low or high nibble of the relevant
108 * register byte. Register offset is specified by off parameter.
109 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
110 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
112 * Parameter mask specifies which nibble is used for specific LED: mask
113 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
114 * higher nibble (bits from 4 to 7).
116 err
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
118 dev_warn(led_data
->led_cdev
.dev
, "Failed to get current brightness, error: %d\n",
120 /* Assume the LED is OFF */
124 regval
= regval
& ~data
->mask
;
125 regval
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? ror32(regval
,
126 data
->bit
) : ror32(regval
, data
->bit
+ 4);
127 if (regval
>= led_data
->base_color
&&
128 regval
<= (led_data
->base_color
+ MLXREG_LED_OFFSET_BLINK_6HZ
))
135 mlxreg_led_brightness_set(struct led_classdev
*cled
, enum led_brightness value
)
137 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
140 return mlxreg_led_store_hw(led_data
, led_data
->base_color
);
142 return mlxreg_led_store_hw(led_data
, MLXREG_LED_IS_OFF
);
145 static enum led_brightness
146 mlxreg_led_brightness_get(struct led_classdev
*cled
)
148 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
150 return mlxreg_led_get_hw(led_data
);
154 mlxreg_led_blink_set(struct led_classdev
*cled
, unsigned long *delay_on
,
155 unsigned long *delay_off
)
157 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
161 * HW supports two types of blinking: full (6Hz) and half (3Hz).
162 * For delay on/off zero LED is setting to solid color. For others
163 * combination blinking is to be controlled by the software timer.
165 if (!(*delay_on
== 0 && *delay_off
== 0) &&
166 !(*delay_on
== MLXREG_LED_BLINK_3HZ
&&
167 *delay_off
== MLXREG_LED_BLINK_3HZ
) &&
168 !(*delay_on
== MLXREG_LED_BLINK_6HZ
&&
169 *delay_off
== MLXREG_LED_BLINK_6HZ
))
172 if (*delay_on
== MLXREG_LED_BLINK_6HZ
)
173 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
174 MLXREG_LED_OFFSET_BLINK_6HZ
);
175 else if (*delay_on
== MLXREG_LED_BLINK_3HZ
)
176 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
177 MLXREG_LED_OFFSET_BLINK_3HZ
);
179 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
);
184 static int mlxreg_led_config(struct mlxreg_led_priv_data
*priv
)
186 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
187 struct mlxreg_core_data
*data
= led_pdata
->data
;
188 struct mlxreg_led_data
*led_data
;
189 struct led_classdev
*led_cdev
;
190 enum led_brightness brightness
;
195 for (i
= 0; i
< led_pdata
->counter
; i
++, data
++) {
196 led_data
= devm_kzalloc(&priv
->pdev
->dev
, sizeof(*led_data
),
201 if (data
->capability
) {
202 err
= regmap_read(led_pdata
->regmap
, data
->capability
,
205 dev_err(&priv
->pdev
->dev
, "Failed to query capability register\n");
208 if (!(regval
& data
->bit
))
211 * Field "bit" can contain one capability bit in 0 byte
212 * and offset bit in 1-3 bytes. Clear capability bit and
213 * keep only offset bit.
215 data
->bit
&= MLXREG_LED_CAPABILITY_CLEAR
;
218 led_cdev
= &led_data
->led_cdev
;
219 led_data
->data_parent
= priv
;
220 if (strstr(data
->label
, "red") ||
221 strstr(data
->label
, "orange")) {
222 brightness
= LED_OFF
;
223 led_data
->base_color
= MLXREG_LED_RED_SOLID
;
224 } else if (strstr(data
->label
, "amber")) {
225 brightness
= LED_OFF
;
226 led_data
->base_color
= MLXREG_LED_AMBER_SOLID
;
228 brightness
= LED_OFF
;
229 led_data
->base_color
= MLXREG_LED_GREEN_SOLID
;
231 snprintf(led_data
->led_cdev_name
, sizeof(led_data
->led_cdev_name
),
232 "mlxreg:%s", data
->label
);
233 led_cdev
->name
= led_data
->led_cdev_name
;
234 led_cdev
->brightness
= brightness
;
235 led_cdev
->max_brightness
= LED_ON
;
236 led_cdev
->brightness_set_blocking
=
237 mlxreg_led_brightness_set
;
238 led_cdev
->brightness_get
= mlxreg_led_brightness_get
;
239 led_cdev
->blink_set
= mlxreg_led_blink_set
;
240 led_cdev
->flags
= LED_CORE_SUSPENDRESUME
;
241 led_data
->data
= data
;
242 err
= devm_led_classdev_register(&priv
->pdev
->dev
, led_cdev
);
246 if (led_cdev
->brightness
)
247 mlxreg_led_brightness_set(led_cdev
,
248 led_cdev
->brightness
);
249 dev_info(led_cdev
->dev
, "label: %s, mask: 0x%02x, offset:0x%02x\n",
250 data
->label
, data
->mask
, data
->reg
);
256 static int mlxreg_led_probe(struct platform_device
*pdev
)
258 struct mlxreg_core_platform_data
*led_pdata
;
259 struct mlxreg_led_priv_data
*priv
;
261 led_pdata
= dev_get_platdata(&pdev
->dev
);
263 dev_err(&pdev
->dev
, "Failed to get platform data.\n");
267 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
271 mutex_init(&priv
->access_lock
);
273 priv
->pdata
= led_pdata
;
275 return mlxreg_led_config(priv
);
278 static int mlxreg_led_remove(struct platform_device
*pdev
)
280 struct mlxreg_led_priv_data
*priv
= dev_get_drvdata(&pdev
->dev
);
282 mutex_destroy(&priv
->access_lock
);
287 static struct platform_driver mlxreg_led_driver
= {
289 .name
= "leds-mlxreg",
291 .probe
= mlxreg_led_probe
,
292 .remove
= mlxreg_led_remove
,
295 module_platform_driver(mlxreg_led_driver
);
297 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
298 MODULE_DESCRIPTION("Mellanox LED regmap driver");
299 MODULE_LICENSE("Dual BSD/GPL");
300 MODULE_ALIAS("platform:leds-mlxreg");