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