Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
0002 //
0003 // Copyright (c) 2018 Mellanox Technologies. All rights reserved.
0004 // Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
0005 
0006 #include <linux/bitops.h>
0007 #include <linux/device.h>
0008 #include <linux/io.h>
0009 #include <linux/leds.h>
0010 #include <linux/module.h>
0011 #include <linux/of_device.h>
0012 #include <linux/platform_data/mlxreg.h>
0013 #include <linux/platform_device.h>
0014 #include <linux/regmap.h>
0015 
0016 /* Codes for LEDs. */
0017 #define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */
0018 #define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */
0019 #define MLXREG_LED_IS_OFF       0x00 /* Off */
0020 #define MLXREG_LED_RED_SOLID        0x05 /* Solid red */
0021 #define MLXREG_LED_GREEN_SOLID      0x0D /* Solid green */
0022 #define MLXREG_LED_AMBER_SOLID      0x09 /* Solid amber */
0023 #define MLXREG_LED_BLINK_3HZ        167 /* ~167 msec off/on - HW support */
0024 #define MLXREG_LED_BLINK_6HZ        83 /* ~83 msec off/on - HW support */
0025 #define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */
0026 
0027 /**
0028  * struct mlxreg_led_data - led control data:
0029  *
0030  * @data: led configuration data;
0031  * @led_cdev: led class data;
0032  * @base_color: base led color (other colors have constant offset from base);
0033  * @led_data: led data;
0034  * @data_parent: pointer to private device control data of parent;
0035  * @led_cdev_name: class device name
0036  */
0037 struct mlxreg_led_data {
0038     struct mlxreg_core_data *data;
0039     struct led_classdev led_cdev;
0040     u8 base_color;
0041     void *data_parent;
0042     char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE];
0043 };
0044 
0045 #define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
0046 
0047 /**
0048  * struct mlxreg_led_priv_data - platform private data:
0049  *
0050  * @pdev: platform device;
0051  * @pdata: platform data;
0052  * @access_lock: mutex for attribute IO access;
0053  */
0054 struct mlxreg_led_priv_data {
0055     struct platform_device *pdev;
0056     struct mlxreg_core_platform_data *pdata;
0057     struct mutex access_lock; /* protect IO operations */
0058 };
0059 
0060 static int
0061 mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset)
0062 {
0063     struct mlxreg_led_priv_data *priv = led_data->data_parent;
0064     struct mlxreg_core_platform_data *led_pdata = priv->pdata;
0065     struct mlxreg_core_data *data = led_data->data;
0066     u32 regval;
0067     u32 nib;
0068     int ret;
0069 
0070     /*
0071      * Each LED is controlled through low or high nibble of the relevant
0072      * register byte. Register offset is specified by off parameter.
0073      * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
0074      * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
0075      * green.
0076      * Parameter mask specifies which nibble is used for specific LED: mask
0077      * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
0078      * higher nibble (bits from 4 to 7).
0079      */
0080     mutex_lock(&priv->access_lock);
0081 
0082     ret = regmap_read(led_pdata->regmap, data->reg, &regval);
0083     if (ret)
0084         goto access_error;
0085 
0086     nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) :
0087           rol32(vset, data->bit + 4);
0088     regval = (regval & data->mask) | nib;
0089 
0090     ret = regmap_write(led_pdata->regmap, data->reg, regval);
0091 
0092 access_error:
0093     mutex_unlock(&priv->access_lock);
0094 
0095     return ret;
0096 }
0097 
0098 static enum led_brightness
0099 mlxreg_led_get_hw(struct mlxreg_led_data *led_data)
0100 {
0101     struct mlxreg_led_priv_data *priv = led_data->data_parent;
0102     struct mlxreg_core_platform_data *led_pdata = priv->pdata;
0103     struct mlxreg_core_data *data = led_data->data;
0104     u32 regval;
0105     int err;
0106 
0107     /*
0108      * Each LED is controlled through low or high nibble of the relevant
0109      * register byte. Register offset is specified by off parameter.
0110      * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
0111      * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
0112      * green.
0113      * Parameter mask specifies which nibble is used for specific LED: mask
0114      * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
0115      * higher nibble (bits from 4 to 7).
0116      */
0117     err = regmap_read(led_pdata->regmap, data->reg, &regval);
0118     if (err < 0) {
0119         dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n",
0120              err);
0121         /* Assume the LED is OFF */
0122         return LED_OFF;
0123     }
0124 
0125     regval = regval & ~data->mask;
0126     regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval,
0127          data->bit) : ror32(regval, data->bit + 4);
0128     if (regval >= led_data->base_color &&
0129         regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ))
0130         return LED_FULL;
0131 
0132     return LED_OFF;
0133 }
0134 
0135 static int
0136 mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value)
0137 {
0138     struct mlxreg_led_data *led_data = cdev_to_priv(cled);
0139 
0140     if (value)
0141         return mlxreg_led_store_hw(led_data, led_data->base_color);
0142     else
0143         return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF);
0144 }
0145 
0146 static enum led_brightness
0147 mlxreg_led_brightness_get(struct led_classdev *cled)
0148 {
0149     struct mlxreg_led_data *led_data = cdev_to_priv(cled);
0150 
0151     return mlxreg_led_get_hw(led_data);
0152 }
0153 
0154 static int
0155 mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on,
0156              unsigned long *delay_off)
0157 {
0158     struct mlxreg_led_data *led_data = cdev_to_priv(cled);
0159     int err;
0160 
0161     /*
0162      * HW supports two types of blinking: full (6Hz) and half (3Hz).
0163      * For delay on/off zero LED is setting to solid color. For others
0164      * combination blinking is to be controlled by the software timer.
0165      */
0166     if (!(*delay_on == 0 && *delay_off == 0) &&
0167         !(*delay_on == MLXREG_LED_BLINK_3HZ &&
0168           *delay_off == MLXREG_LED_BLINK_3HZ) &&
0169         !(*delay_on == MLXREG_LED_BLINK_6HZ &&
0170           *delay_off == MLXREG_LED_BLINK_6HZ))
0171         return -EINVAL;
0172 
0173     if (*delay_on == MLXREG_LED_BLINK_6HZ)
0174         err = mlxreg_led_store_hw(led_data, led_data->base_color +
0175                       MLXREG_LED_OFFSET_BLINK_6HZ);
0176     else if (*delay_on == MLXREG_LED_BLINK_3HZ)
0177         err = mlxreg_led_store_hw(led_data, led_data->base_color +
0178                       MLXREG_LED_OFFSET_BLINK_3HZ);
0179     else
0180         err = mlxreg_led_store_hw(led_data, led_data->base_color);
0181 
0182     return err;
0183 }
0184 
0185 static int mlxreg_led_config(struct mlxreg_led_priv_data *priv)
0186 {
0187     struct mlxreg_core_platform_data *led_pdata = priv->pdata;
0188     struct mlxreg_core_data *data = led_pdata->data;
0189     struct mlxreg_led_data *led_data;
0190     struct led_classdev *led_cdev;
0191     enum led_brightness brightness;
0192     u32 regval;
0193     int i;
0194     int err;
0195 
0196     for (i = 0; i < led_pdata->counter; i++, data++) {
0197         led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data),
0198                     GFP_KERNEL);
0199         if (!led_data)
0200             return -ENOMEM;
0201 
0202         if (data->capability) {
0203             err = regmap_read(led_pdata->regmap, data->capability,
0204                       &regval);
0205             if (err) {
0206                 dev_err(&priv->pdev->dev, "Failed to query capability register\n");
0207                 return err;
0208             }
0209             if (!(regval & data->bit))
0210                 continue;
0211             /*
0212              * Field "bit" can contain one capability bit in 0 byte
0213              * and offset bit in 1-3 bytes. Clear capability bit and
0214              * keep only offset bit.
0215              */
0216             data->bit &= MLXREG_LED_CAPABILITY_CLEAR;
0217         }
0218 
0219         led_cdev = &led_data->led_cdev;
0220         led_data->data_parent = priv;
0221         if (strstr(data->label, "red") ||
0222             strstr(data->label, "orange")) {
0223             brightness = LED_OFF;
0224             led_data->base_color = MLXREG_LED_RED_SOLID;
0225         } else if (strstr(data->label, "amber")) {
0226             brightness = LED_OFF;
0227             led_data->base_color = MLXREG_LED_AMBER_SOLID;
0228         } else {
0229             brightness = LED_OFF;
0230             led_data->base_color = MLXREG_LED_GREEN_SOLID;
0231         }
0232         snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name),
0233              "mlxreg:%s", data->label);
0234         led_cdev->name = led_data->led_cdev_name;
0235         led_cdev->brightness = brightness;
0236         led_cdev->max_brightness = LED_ON;
0237         led_cdev->brightness_set_blocking =
0238                         mlxreg_led_brightness_set;
0239         led_cdev->brightness_get = mlxreg_led_brightness_get;
0240         led_cdev->blink_set = mlxreg_led_blink_set;
0241         led_cdev->flags = LED_CORE_SUSPENDRESUME;
0242         led_data->data = data;
0243         err = devm_led_classdev_register(&priv->pdev->dev, led_cdev);
0244         if (err)
0245             return err;
0246 
0247         if (led_cdev->brightness)
0248             mlxreg_led_brightness_set(led_cdev,
0249                           led_cdev->brightness);
0250         dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n",
0251              data->label, data->mask, data->reg);
0252     }
0253 
0254     return 0;
0255 }
0256 
0257 static int mlxreg_led_probe(struct platform_device *pdev)
0258 {
0259     struct mlxreg_core_platform_data *led_pdata;
0260     struct mlxreg_led_priv_data *priv;
0261 
0262     led_pdata = dev_get_platdata(&pdev->dev);
0263     if (!led_pdata) {
0264         dev_err(&pdev->dev, "Failed to get platform data.\n");
0265         return -EINVAL;
0266     }
0267 
0268     priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
0269     if (!priv)
0270         return -ENOMEM;
0271 
0272     mutex_init(&priv->access_lock);
0273     priv->pdev = pdev;
0274     priv->pdata = led_pdata;
0275 
0276     return mlxreg_led_config(priv);
0277 }
0278 
0279 static int mlxreg_led_remove(struct platform_device *pdev)
0280 {
0281     struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev);
0282 
0283     mutex_destroy(&priv->access_lock);
0284 
0285     return 0;
0286 }
0287 
0288 static struct platform_driver mlxreg_led_driver = {
0289     .driver = {
0290         .name = "leds-mlxreg",
0291     },
0292     .probe = mlxreg_led_probe,
0293     .remove = mlxreg_led_remove,
0294 };
0295 
0296 module_platform_driver(mlxreg_led_driver);
0297 
0298 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
0299 MODULE_DESCRIPTION("Mellanox LED regmap driver");
0300 MODULE_LICENSE("Dual BSD/GPL");
0301 MODULE_ALIAS("platform:leds-mlxreg");