1 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 2 // 3 // Copyright (c) 2018 Mellanox Technologies. All rights reserved. 4 // Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com> 5 6 #include <linux/bitops.h> 7 #include <linux/device.h> 8 #include <linux/io.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> 14 15 /* Codes for LEDs. */ 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 */ 25 26 /** 27 * struct mlxreg_led_data - led control data: 28 * 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 34 */ 35 struct mlxreg_led_data { 36 struct mlxreg_core_data *data; 37 struct led_classdev led_cdev; 38 u8 base_color; 39 void *data_parent; 40 char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE]; 41 }; 42 43 #define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev) 44 45 /** 46 * struct mlxreg_led_priv_data - platform private data: 47 * 48 * @pdev: platform device; 49 * @pdata: platform data; 50 * @access_lock: mutex for attribute IO access; 51 */ 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 */ 56 }; 57 58 static int 59 mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset) 60 { 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; 64 u32 regval; 65 u32 nib; 66 int ret; 67 68 /* 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 73 * green. 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). 77 */ 78 mutex_lock(&priv->access_lock); 79 80 ret = regmap_read(led_pdata->regmap, data->reg, ®val); 81 if (ret) 82 goto access_error; 83 84 nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) : 85 rol32(vset, data->bit + 4); 86 regval = (regval & data->mask) | nib; 87 88 ret = regmap_write(led_pdata->regmap, data->reg, regval); 89 90 access_error: 91 mutex_unlock(&priv->access_lock); 92 93 return ret; 94 } 95 96 static enum led_brightness 97 mlxreg_led_get_hw(struct mlxreg_led_data *led_data) 98 { 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; 102 u32 regval; 103 int err; 104 105 /* 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 110 * green. 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). 114 */ 115 err = regmap_read(led_pdata->regmap, data->reg, ®val); 116 if (err < 0) { 117 dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n", 118 err); 119 /* Assume the LED is OFF */ 120 return LED_OFF; 121 } 122 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)) 128 return LED_FULL; 129 130 return LED_OFF; 131 } 132 133 static int 134 mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value) 135 { 136 struct mlxreg_led_data *led_data = cdev_to_priv(cled); 137 138 if (value) 139 return mlxreg_led_store_hw(led_data, led_data->base_color); 140 else 141 return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF); 142 } 143 144 static enum led_brightness 145 mlxreg_led_brightness_get(struct led_classdev *cled) 146 { 147 struct mlxreg_led_data *led_data = cdev_to_priv(cled); 148 149 return mlxreg_led_get_hw(led_data); 150 } 151 152 static int 153 mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on, 154 unsigned long *delay_off) 155 { 156 struct mlxreg_led_data *led_data = cdev_to_priv(cled); 157 int err; 158 159 /* 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. 163 */ 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)) 169 return -EINVAL; 170 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); 177 else 178 err = mlxreg_led_store_hw(led_data, led_data->base_color); 179 180 return err; 181 } 182 183 static int mlxreg_led_config(struct mlxreg_led_priv_data *priv) 184 { 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; 190 u32 regval; 191 int i; 192 int err; 193 194 for (i = 0; i < led_pdata->counter; i++, data++) { 195 led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data), 196 GFP_KERNEL); 197 if (!led_data) 198 return -ENOMEM; 199 200 if (data->capability) { 201 err = regmap_read(led_pdata->regmap, data->capability, 202 ®val); 203 if (err) { 204 dev_err(&priv->pdev->dev, "Failed to query capability register\n"); 205 return err; 206 } 207 if (!(regval & data->bit)) 208 continue; 209 /* 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. 213 */ 214 data->bit &= MLXREG_LED_CAPABILITY_CLEAR; 215 } 216 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; 226 } else { 227 brightness = LED_OFF; 228 led_data->base_color = MLXREG_LED_GREEN_SOLID; 229 } 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); 242 if (err) 243 return err; 244 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); 250 } 251 252 return 0; 253 } 254 255 static int mlxreg_led_probe(struct platform_device *pdev) 256 { 257 struct mlxreg_core_platform_data *led_pdata; 258 struct mlxreg_led_priv_data *priv; 259 260 led_pdata = dev_get_platdata(&pdev->dev); 261 if (!led_pdata) { 262 dev_err(&pdev->dev, "Failed to get platform data.\n"); 263 return -EINVAL; 264 } 265 266 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 267 if (!priv) 268 return -ENOMEM; 269 270 mutex_init(&priv->access_lock); 271 priv->pdev = pdev; 272 priv->pdata = led_pdata; 273 274 return mlxreg_led_config(priv); 275 } 276 277 static void mlxreg_led_remove(struct platform_device *pdev) 278 { 279 struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); 280 281 mutex_destroy(&priv->access_lock); 282 } 283 284 static struct platform_driver mlxreg_led_driver = { 285 .driver = { 286 .name = "leds-mlxreg", 287 }, 288 .probe = mlxreg_led_probe, 289 .remove_new = mlxreg_led_remove, 290 }; 291 292 module_platform_driver(mlxreg_led_driver); 293 294 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 295 MODULE_DESCRIPTION("Mellanox LED regmap driver"); 296 MODULE_LICENSE("Dual BSD/GPL"); 297 MODULE_ALIAS("platform:leds-mlxreg"); 298