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/platform_data/mlxreg.h>
12 #include <linux/platform_device.h>
13 #include <linux/regmap.h>
16 #define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */
17 #define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */
18 #define MLXREG_LED_IS_OFF 0x00 /* Off */
19 #define MLXREG_LED_RED_SOLID 0x05 /* Solid red */
20 #define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */
21 #define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */
22 #define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */
23 #define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */
24 #define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */
27 * struct mlxreg_led_data - led control data:
29 * @data: led configuration data;
30 * @led_cdev: led class data;
31 * @base_color: base led color (other colors have constant offset from base);
32 * @data_parent: pointer to private device control data of parent;
33 * @led_cdev_name: class device name
35 struct mlxreg_led_data
{
36 struct mlxreg_core_data
*data
;
37 struct led_classdev led_cdev
;
40 char led_cdev_name
[MLXREG_CORE_LABEL_MAX_SIZE
];
43 #define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
46 * struct mlxreg_led_priv_data - platform private data:
48 * @pdev: platform device;
49 * @pdata: platform data;
50 * @access_lock: mutex for attribute IO access;
52 struct mlxreg_led_priv_data
{
53 struct platform_device
*pdev
;
54 struct mlxreg_core_platform_data
*pdata
;
55 struct mutex access_lock
; /* protect IO operations */
59 mlxreg_led_store_hw(struct mlxreg_led_data
*led_data
, u8 vset
)
61 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
62 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
63 struct mlxreg_core_data
*data
= led_data
->data
;
69 * Each LED is controlled through low or high nibble of the relevant
70 * register byte. Register offset is specified by off parameter.
71 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
72 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
74 * Parameter mask specifies which nibble is used for specific LED: mask
75 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
76 * higher nibble (bits from 4 to 7).
78 mutex_lock(&priv
->access_lock
);
80 ret
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
84 nib
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? rol32(vset
, data
->bit
) :
85 rol32(vset
, data
->bit
+ 4);
86 regval
= (regval
& data
->mask
) | nib
;
88 ret
= regmap_write(led_pdata
->regmap
, data
->reg
, regval
);
91 mutex_unlock(&priv
->access_lock
);
96 static enum led_brightness
97 mlxreg_led_get_hw(struct mlxreg_led_data
*led_data
)
99 struct mlxreg_led_priv_data
*priv
= led_data
->data_parent
;
100 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
101 struct mlxreg_core_data
*data
= led_data
->data
;
106 * Each LED is controlled through low or high nibble of the relevant
107 * register byte. Register offset is specified by off parameter.
108 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
109 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
111 * Parameter mask specifies which nibble is used for specific LED: mask
112 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
113 * higher nibble (bits from 4 to 7).
115 err
= regmap_read(led_pdata
->regmap
, data
->reg
, ®val
);
117 dev_warn(led_data
->led_cdev
.dev
, "Failed to get current brightness, error: %d\n",
119 /* Assume the LED is OFF */
123 regval
= regval
& ~data
->mask
;
124 regval
= (ror32(data
->mask
, data
->bit
) == 0xf0) ? ror32(regval
,
125 data
->bit
) : ror32(regval
, data
->bit
+ 4);
126 if (regval
>= led_data
->base_color
&&
127 regval
<= (led_data
->base_color
+ MLXREG_LED_OFFSET_BLINK_6HZ
))
134 mlxreg_led_brightness_set(struct led_classdev
*cled
, enum led_brightness value
)
136 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
139 return mlxreg_led_store_hw(led_data
, led_data
->base_color
);
141 return mlxreg_led_store_hw(led_data
, MLXREG_LED_IS_OFF
);
144 static enum led_brightness
145 mlxreg_led_brightness_get(struct led_classdev
*cled
)
147 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
149 return mlxreg_led_get_hw(led_data
);
153 mlxreg_led_blink_set(struct led_classdev
*cled
, unsigned long *delay_on
,
154 unsigned long *delay_off
)
156 struct mlxreg_led_data
*led_data
= cdev_to_priv(cled
);
160 * HW supports two types of blinking: full (6Hz) and half (3Hz).
161 * For delay on/off zero LED is setting to solid color. For others
162 * combination blinking is to be controlled by the software timer.
164 if (!(*delay_on
== 0 && *delay_off
== 0) &&
165 !(*delay_on
== MLXREG_LED_BLINK_3HZ
&&
166 *delay_off
== MLXREG_LED_BLINK_3HZ
) &&
167 !(*delay_on
== MLXREG_LED_BLINK_6HZ
&&
168 *delay_off
== MLXREG_LED_BLINK_6HZ
))
171 if (*delay_on
== MLXREG_LED_BLINK_6HZ
)
172 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
173 MLXREG_LED_OFFSET_BLINK_6HZ
);
174 else if (*delay_on
== MLXREG_LED_BLINK_3HZ
)
175 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
+
176 MLXREG_LED_OFFSET_BLINK_3HZ
);
178 err
= mlxreg_led_store_hw(led_data
, led_data
->base_color
);
183 static int mlxreg_led_config(struct mlxreg_led_priv_data
*priv
)
185 struct mlxreg_core_platform_data
*led_pdata
= priv
->pdata
;
186 struct mlxreg_core_data
*data
= led_pdata
->data
;
187 struct mlxreg_led_data
*led_data
;
188 struct led_classdev
*led_cdev
;
189 enum led_brightness brightness
;
194 for (i
= 0; i
< led_pdata
->counter
; i
++, data
++) {
195 led_data
= devm_kzalloc(&priv
->pdev
->dev
, sizeof(*led_data
),
200 if (data
->capability
) {
201 err
= regmap_read(led_pdata
->regmap
, data
->capability
,
204 dev_err(&priv
->pdev
->dev
, "Failed to query capability register\n");
207 if (!(regval
& data
->bit
))
210 * Field "bit" can contain one capability bit in 0 byte
211 * and offset bit in 1-3 bytes. Clear capability bit and
212 * keep only offset bit.
214 data
->bit
&= MLXREG_LED_CAPABILITY_CLEAR
;
217 led_cdev
= &led_data
->led_cdev
;
218 led_data
->data_parent
= priv
;
219 if (strstr(data
->label
, "red") ||
220 strstr(data
->label
, "orange")) {
221 brightness
= LED_OFF
;
222 led_data
->base_color
= MLXREG_LED_RED_SOLID
;
223 } else if (strstr(data
->label
, "amber")) {
224 brightness
= LED_OFF
;
225 led_data
->base_color
= MLXREG_LED_AMBER_SOLID
;
227 brightness
= LED_OFF
;
228 led_data
->base_color
= MLXREG_LED_GREEN_SOLID
;
230 snprintf(led_data
->led_cdev_name
, sizeof(led_data
->led_cdev_name
),
231 "mlxreg:%s", data
->label
);
232 led_cdev
->name
= led_data
->led_cdev_name
;
233 led_cdev
->brightness
= brightness
;
234 led_cdev
->max_brightness
= LED_ON
;
235 led_cdev
->brightness_set_blocking
=
236 mlxreg_led_brightness_set
;
237 led_cdev
->brightness_get
= mlxreg_led_brightness_get
;
238 led_cdev
->blink_set
= mlxreg_led_blink_set
;
239 led_cdev
->flags
= LED_CORE_SUSPENDRESUME
;
240 led_data
->data
= data
;
241 err
= devm_led_classdev_register(&priv
->pdev
->dev
, led_cdev
);
245 if (led_cdev
->brightness
)
246 mlxreg_led_brightness_set(led_cdev
,
247 led_cdev
->brightness
);
248 dev_info(led_cdev
->dev
, "label: %s, mask: 0x%02x, offset:0x%02x\n",
249 data
->label
, data
->mask
, data
->reg
);
255 static int mlxreg_led_probe(struct platform_device
*pdev
)
257 struct mlxreg_core_platform_data
*led_pdata
;
258 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 err
= devm_mutex_init(&pdev
->dev
, &priv
->access_lock
);
276 priv
->pdata
= led_pdata
;
278 return mlxreg_led_config(priv
);
281 static struct platform_driver mlxreg_led_driver
= {
283 .name
= "leds-mlxreg",
285 .probe
= mlxreg_led_probe
,
288 module_platform_driver(mlxreg_led_driver
);
290 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
291 MODULE_DESCRIPTION("Mellanox LED regmap driver");
292 MODULE_LICENSE("Dual BSD/GPL");
293 MODULE_ALIAS("platform:leds-mlxreg");