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/of_device.h> 12 #include <linux/platform_data/mlxreg.h> 13 #include <linux/platform_device.h> 14 #include <linux/regmap.h> 15 16 /* Codes for LEDs. */ 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 26 /** 27 * struct mlxreg_led_data - led control data: 28 * 29 * @data: led configuration data; 30 * @led_classdev: 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 */ 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 int i; 191 int err; 192 193 for (i = 0; i < led_pdata->counter; i++, data++) { 194 led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data), 195 GFP_KERNEL); 196 if (!led_data) 197 return -ENOMEM; 198 199 led_cdev = &led_data->led_cdev; 200 led_data->data_parent = priv; 201 if (strstr(data->label, "red") || 202 strstr(data->label, "orange")) { 203 brightness = LED_OFF; 204 led_data->base_color = MLXREG_LED_RED_SOLID; 205 } else if (strstr(data->label, "amber")) { 206 brightness = LED_OFF; 207 led_data->base_color = MLXREG_LED_AMBER_SOLID; 208 } else { 209 brightness = LED_OFF; 210 led_data->base_color = MLXREG_LED_GREEN_SOLID; 211 } 212 sprintf(led_data->led_cdev_name, "%s:%s", "mlxreg", 213 data->label); 214 led_cdev->name = led_data->led_cdev_name; 215 led_cdev->brightness = brightness; 216 led_cdev->max_brightness = LED_ON; 217 led_cdev->brightness_set_blocking = 218 mlxreg_led_brightness_set; 219 led_cdev->brightness_get = mlxreg_led_brightness_get; 220 led_cdev->blink_set = mlxreg_led_blink_set; 221 led_cdev->flags = LED_CORE_SUSPENDRESUME; 222 led_data->data = data; 223 err = devm_led_classdev_register(&priv->pdev->dev, led_cdev); 224 if (err) 225 return err; 226 227 if (led_cdev->brightness) 228 mlxreg_led_brightness_set(led_cdev, 229 led_cdev->brightness); 230 dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n", 231 data->label, data->mask, data->reg); 232 } 233 234 return 0; 235 } 236 237 static int mlxreg_led_probe(struct platform_device *pdev) 238 { 239 struct mlxreg_core_platform_data *led_pdata; 240 struct mlxreg_led_priv_data *priv; 241 242 led_pdata = dev_get_platdata(&pdev->dev); 243 if (!led_pdata) { 244 dev_err(&pdev->dev, "Failed to get platform data.\n"); 245 return -EINVAL; 246 } 247 248 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 249 if (!priv) 250 return -ENOMEM; 251 252 mutex_init(&priv->access_lock); 253 priv->pdev = pdev; 254 priv->pdata = led_pdata; 255 256 return mlxreg_led_config(priv); 257 } 258 259 static int mlxreg_led_remove(struct platform_device *pdev) 260 { 261 struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); 262 263 mutex_destroy(&priv->access_lock); 264 265 return 0; 266 } 267 268 static struct platform_driver mlxreg_led_driver = { 269 .driver = { 270 .name = "leds-mlxreg", 271 }, 272 .probe = mlxreg_led_probe, 273 .remove = mlxreg_led_remove, 274 }; 275 276 module_platform_driver(mlxreg_led_driver); 277 278 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 279 MODULE_DESCRIPTION("Mellanox LED regmap driver"); 280 MODULE_LICENSE("Dual BSD/GPL"); 281 MODULE_ALIAS("platform:leds-mlxreg"); 282