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