linux/drivers/leds/leds-mlxreg.c
<<
>>
Prefs
   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_classdev: 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 */
  36struct 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 */
  53struct 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
  59static int
  60mlxreg_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, &regval);
  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
  91access_error:
  92        mutex_unlock(&priv->access_lock);
  93
  94        return ret;
  95}
  96
  97static enum led_brightness
  98mlxreg_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, &regval);
 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
 134static int
 135mlxreg_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
 145static enum led_brightness
 146mlxreg_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
 153static int
 154mlxreg_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
 184static 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                                          &regval);
 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                sprintf(led_data->led_cdev_name, "%s:%s", "mlxreg",
 232                        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
 256static 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
 278static int 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        return 0;
 285}
 286
 287static struct platform_driver mlxreg_led_driver = {
 288        .driver = {
 289            .name = "leds-mlxreg",
 290        },
 291        .probe = mlxreg_led_probe,
 292        .remove = mlxreg_led_remove,
 293};
 294
 295module_platform_driver(mlxreg_led_driver);
 296
 297MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
 298MODULE_DESCRIPTION("Mellanox LED regmap driver");
 299MODULE_LICENSE("Dual BSD/GPL");
 300MODULE_ALIAS("platform:leds-mlxreg");
 301