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_cdev: 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;
35 * @led_cdev_name: class device name
37 struct mlxreg_led_data
{
38 struct mlxreg_core_data
*data
;
39 struct led_classdev led_cdev
;
42 char led_cdev_name
[MLXREG_CORE_LABEL_MAX_SIZE
];
45 #define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
48 * struct mlxreg_led_priv_data - platform private data:
50 * @pdev: platform device;
51 * @pdata: platform data;
52 * @access_lock: mutex for attribute IO access;
54 struct mlxreg_led_priv_data
{
55 struct platform_device
*pdev
;
56 struct mlxreg_core_platform_data
*pdata
;
57 struct mutex access_lock
; /* protect IO operations */
61 mlxreg_led_store_hw(struct mlxreg_led_data
*led_data
, u8 vset
)
63 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
64 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
65 struct mlxreg_core_data
*data
= led_data
->data
;
71 * Each LED is controlled through low or high nibble of the relevant
72 * register byte. Register offset is specified by off parameter.
73 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
74 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
76 * Parameter mask specifies which nibble is used for specific LED: mask
77 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
78 * higher nibble (bits from 4 to 7).
80 mutex_lock(&priv
->access_lock
);
82 ret
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
86 nib
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? rol32(vset
, data
->bit
) :
87 rol32(vset
, data
->bit
+ 4);
88 regval
= (regval
& data
->mask
) | nib
;
90 ret
= regmap_write(led_pdata
->regmap
, data
->reg
, regval
);
93 mutex_unlock(&priv
->access_lock
);
98 static enum led_brightness
99 mlxreg_led_get_hw(struct mlxreg_led_data
*led_data
)
101 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
102 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
103 struct mlxreg_core_data
*data
= led_data
->data
;
108 * Each LED is controlled through low or high nibble of the relevant
109 * register byte. Register offset is specified by off parameter.
110 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
111 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
113 * Parameter mask specifies which nibble is used for specific LED: mask
114 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
115 * higher nibble (bits from 4 to 7).
117 err
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
119 dev_warn(led_data
->led_cdev
.dev
, "Failed to get current brightness, error: %d\n",
121 /* Assume the LED is OFF */
125 regval
= regval
& ~data
->mask
;
126 regval
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? ror32(regval
,
127 data
->bit
) : ror32(regval
, data
->bit
+ 4);
128 if (regval
>= led_data
->base_color
&&
129 regval
<= (led_data
->base_color
+ MLXREG_LED_OFFSET_BLINK_6HZ
))
136 mlxreg_led_brightness_set(struct led_classdev
*cled
, enum led_brightness value
)
138 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
141 return mlxreg_led_store_hw(led_data
, led_data
->base_color
);
143 return mlxreg_led_store_hw(led_data
, MLXREG_LED_IS_OFF
);
146 static enum led_brightness
147 mlxreg_led_brightness_get(struct led_classdev
*cled
)
149 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
151 return mlxreg_led_get_hw(led_data
);
155 mlxreg_led_blink_set(struct led_classdev
*cled
, unsigned long *delay_on
,
156 unsigned long *delay_off
)
158 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
162 * HW supports two types of blinking: full (6Hz) and half (3Hz).
163 * For delay on/off zero LED is setting to solid color. For others
164 * combination blinking is to be controlled by the software timer.
166 if (!(*delay_on
== 0 && *delay_off
== 0) &&
167 !(*delay_on
== MLXREG_LED_BLINK_3HZ
&&
168 *delay_off
== MLXREG_LED_BLINK_3HZ
) &&
169 !(*delay_on
== MLXREG_LED_BLINK_6HZ
&&
170 *delay_off
== MLXREG_LED_BLINK_6HZ
))
173 if (*delay_on
== MLXREG_LED_BLINK_6HZ
)
174 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
175 MLXREG_LED_OFFSET_BLINK_6HZ
);
176 else if (*delay_on
== MLXREG_LED_BLINK_3HZ
)
177 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
178 MLXREG_LED_OFFSET_BLINK_3HZ
);
180 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
);
185 static int mlxreg_led_config(struct mlxreg_led_priv_data
*priv
)
187 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
188 struct mlxreg_core_data
*data
= led_pdata
->data
;
189 struct mlxreg_led_data
*led_data
;
190 struct led_classdev
*led_cdev
;
191 enum led_brightness brightness
;
196 for (i
= 0; i
< led_pdata
->counter
; i
++, data
++) {
197 led_data
= devm_kzalloc(&priv
->pdev
->dev
, sizeof(*led_data
),
202 if (data
->capability
) {
203 err
= regmap_read(led_pdata
->regmap
, data
->capability
,
206 dev_err(&priv
->pdev
->dev
, "Failed to query capability register\n");
209 if (!(regval
& data
->bit
))
212 * Field "bit" can contain one capability bit in 0 byte
213 * and offset bit in 1-3 bytes. Clear capability bit and
214 * keep only offset bit.
216 data
->bit
&= MLXREG_LED_CAPABILITY_CLEAR
;
219 led_cdev
= &led_data
->led_cdev
;
220 led_data
->data_parent
= priv
;
221 if (strstr(data
->label
, "red") ||
222 strstr(data
->label
, "orange")) {
223 brightness
= LED_OFF
;
224 led_data
->base_color
= MLXREG_LED_RED_SOLID
;
225 } else if (strstr(data
->label
, "amber")) {
226 brightness
= LED_OFF
;
227 led_data
->base_color
= MLXREG_LED_AMBER_SOLID
;
229 brightness
= LED_OFF
;
230 led_data
->base_color
= MLXREG_LED_GREEN_SOLID
;
232 snprintf(led_data
->led_cdev_name
, sizeof(led_data
->led_cdev_name
),
233 "mlxreg:%s", data
->label
);
234 led_cdev
->name
= led_data
->led_cdev_name
;
235 led_cdev
->brightness
= brightness
;
236 led_cdev
->max_brightness
= LED_ON
;
237 led_cdev
->brightness_set_blocking
=
238 mlxreg_led_brightness_set
;
239 led_cdev
->brightness_get
= mlxreg_led_brightness_get
;
240 led_cdev
->blink_set
= mlxreg_led_blink_set
;
241 led_cdev
->flags
= LED_CORE_SUSPENDRESUME
;
242 led_data
->data
= data
;
243 err
= devm_led_classdev_register(&priv
->pdev
->dev
, led_cdev
);
247 if (led_cdev
->brightness
)
248 mlxreg_led_brightness_set(led_cdev
,
249 led_cdev
->brightness
);
250 dev_info(led_cdev
->dev
, "label: %s, mask: 0x%02x, offset:0x%02x\n",
251 data
->label
, data
->mask
, data
->reg
);
257 static int mlxreg_led_probe(struct platform_device
*pdev
)
259 struct mlxreg_core_platform_data
*led_pdata
;
260 struct mlxreg_led_priv_data
*priv
;
262 led_pdata
= dev_get_platdata(&pdev
->dev
);
264 dev_err(&pdev
->dev
, "Failed to get platform data.\n");
268 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
272 mutex_init(&priv
->access_lock
);
274 priv
->pdata
= led_pdata
;
276 return mlxreg_led_config(priv
);
279 static int mlxreg_led_remove(struct platform_device
*pdev
)
281 struct mlxreg_led_priv_data
*priv
= dev_get_drvdata(&pdev
->dev
);
283 mutex_destroy(&priv
->access_lock
);
288 static struct platform_driver mlxreg_led_driver
= {
290 .name
= "leds-mlxreg",
292 .probe
= mlxreg_led_probe
,
293 .remove
= mlxreg_led_remove
,
296 module_platform_driver(mlxreg_led_driver
);
298 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
299 MODULE_DESCRIPTION("Mellanox LED regmap driver");
300 MODULE_LICENSE("Dual BSD/GPL");
301 MODULE_ALIAS("platform:leds-mlxreg");