root/drivers/leds/leds-mlxcpld.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. mlxcpld_led_platform_check_sys_type
  2. mlxcpld_led_bus_access_func
  3. mlxcpld_led_store_hw
  4. mlxcpld_led_brightness_set
  5. mlxcpld_led_blink_set
  6. mlxcpld_led_config
  7. mlxcpld_led_probe
  8. mlxcpld_led_init
  9. mlxcpld_led_exit

   1 /*
   2  * drivers/leds/leds-mlxcpld.c
   3  * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
   4  * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
   5  *
   6  * Redistribution and use in source and binary forms, with or without
   7  * modification, are permitted provided that the following conditions are met:
   8  *
   9  * 1. Redistributions of source code must retain the above copyright
  10  *    notice, this list of conditions and the following disclaimer.
  11  * 2. Redistributions in binary form must reproduce the above copyright
  12  *    notice, this list of conditions and the following disclaimer in the
  13  *    documentation and/or other materials provided with the distribution.
  14  * 3. Neither the names of the copyright holders nor the names of its
  15  *    contributors may be used to endorse or promote products derived from
  16  *    this software without specific prior written permission.
  17  *
  18  * Alternatively, this software may be distributed under the terms of the
  19  * GNU General Public License ("GPL") version 2 as published by the Free
  20  * Software Foundation.
  21  *
  22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  26  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32  * POSSIBILITY OF SUCH DAMAGE.
  33  */
  34 
  35 #include <linux/acpi.h>
  36 #include <linux/device.h>
  37 #include <linux/dmi.h>
  38 #include <linux/hwmon.h>
  39 #include <linux/hwmon-sysfs.h>
  40 #include <linux/io.h>
  41 #include <linux/leds.h>
  42 #include <linux/module.h>
  43 #include <linux/mod_devicetable.h>
  44 #include <linux/platform_device.h>
  45 #include <linux/slab.h>
  46 
  47 #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR     0x2500 /* LPC bus access */
  48 
  49 /* Color codes for LEDs */
  50 #define MLXCPLD_LED_OFFSET_HALF         0x01 /* Offset from solid: 3Hz blink */
  51 #define MLXCPLD_LED_OFFSET_FULL         0x02 /* Offset from solid: 6Hz blink */
  52 #define MLXCPLD_LED_IS_OFF              0x00 /* Off */
  53 #define MLXCPLD_LED_RED_STATIC_ON       0x05 /* Solid red */
  54 #define MLXCPLD_LED_RED_BLINK_HALF      (MLXCPLD_LED_RED_STATIC_ON + \
  55                                          MLXCPLD_LED_OFFSET_HALF)
  56 #define MLXCPLD_LED_RED_BLINK_FULL      (MLXCPLD_LED_RED_STATIC_ON + \
  57                                          MLXCPLD_LED_OFFSET_FULL)
  58 #define MLXCPLD_LED_GREEN_STATIC_ON     0x0D /* Solid green */
  59 #define MLXCPLD_LED_GREEN_BLINK_HALF    (MLXCPLD_LED_GREEN_STATIC_ON + \
  60                                          MLXCPLD_LED_OFFSET_HALF)
  61 #define MLXCPLD_LED_GREEN_BLINK_FULL    (MLXCPLD_LED_GREEN_STATIC_ON + \
  62                                          MLXCPLD_LED_OFFSET_FULL)
  63 #define MLXCPLD_LED_BLINK_3HZ           167 /* ~167 msec off/on */
  64 #define MLXCPLD_LED_BLINK_6HZ           83 /* ~83 msec off/on */
  65 
  66 /**
  67  * mlxcpld_param - LED access parameters:
  68  * @offset - offset for LED access in CPLD device
  69  * @mask - mask for LED access in CPLD device
  70  * @base_color - base color code for LED
  71 **/
  72 struct mlxcpld_param {
  73         u8 offset;
  74         u8 mask;
  75         u8 base_color;
  76 };
  77 
  78 /**
  79  * mlxcpld_led_priv - LED private data:
  80  * @cled - LED class device instance
  81  * @param - LED CPLD access parameters
  82 **/
  83 struct mlxcpld_led_priv {
  84         struct led_classdev cdev;
  85         struct mlxcpld_param param;
  86 };
  87 
  88 #define cdev_to_priv(c)         container_of(c, struct mlxcpld_led_priv, cdev)
  89 
  90 /**
  91  * mlxcpld_led_profile - system LED profile (defined per system class):
  92  * @offset - offset for LED access in CPLD device
  93  * @mask - mask for LED access in CPLD device
  94  * @base_color - base color code
  95  * @brightness - default brightness setting (on/off)
  96  * @name - LED name
  97 **/
  98 struct mlxcpld_led_profile {
  99         u8 offset;
 100         u8 mask;
 101         u8 base_color;
 102         enum led_brightness brightness;
 103         const char *name;
 104 };
 105 
 106 /**
 107  * mlxcpld_led_pdata - system LED private data
 108  * @pdev - platform device pointer
 109  * @pled - LED class device instance
 110  * @profile - system configuration profile
 111  * @num_led_instances - number of LED instances
 112  * @lock - device access lock
 113 **/
 114 struct mlxcpld_led_pdata {
 115         struct platform_device *pdev;
 116         struct mlxcpld_led_priv *pled;
 117         struct mlxcpld_led_profile *profile;
 118         int num_led_instances;
 119         spinlock_t lock;
 120 };
 121 
 122 static struct mlxcpld_led_pdata *mlxcpld_led;
 123 
 124 /* Default profile fit the next Mellanox systems:
 125  * "msx6710", "msx6720", "msb7700", "msn2700", "msx1410",
 126  * "msn2410", "msb7800", "msn2740"
 127  */
 128 static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = {
 129         {
 130                 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 131                 "mlxcpld:fan1:green",
 132         },
 133         {
 134                 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 135                 "mlxcpld:fan1:red",
 136         },
 137         {
 138                 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 139                 "mlxcpld:fan2:green",
 140         },
 141         {
 142                 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 143                 "mlxcpld:fan2:red",
 144         },
 145         {
 146                 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 147                 "mlxcpld:fan3:green",
 148         },
 149         {
 150                 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 151                 "mlxcpld:fan3:red",
 152         },
 153         {
 154                 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 155                 "mlxcpld:fan4:green",
 156         },
 157         {
 158                 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 159                 "mlxcpld:fan4:red",
 160         },
 161         {
 162                 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 163                 "mlxcpld:psu:green",
 164         },
 165         {
 166                 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 167                 "mlxcpld:psu:red",
 168         },
 169         {
 170                 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 171                 "mlxcpld:status:green",
 172         },
 173         {
 174                 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 175                 "mlxcpld:status:red",
 176         },
 177 };
 178 
 179 /* Profile fit the Mellanox systems based on "msn2100" */
 180 static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = {
 181         {
 182                 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 183                 "mlxcpld:fan:green",
 184         },
 185         {
 186                 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 187                 "mlxcpld:fan:red",
 188         },
 189         {
 190                 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 191                 "mlxcpld:psu1:green",
 192         },
 193         {
 194                 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 195                 "mlxcpld:psu1:red",
 196         },
 197         {
 198                 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 199                 "mlxcpld:psu2:green",
 200         },
 201         {
 202                 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 203                 "mlxcpld:psu2:red",
 204         },
 205         {
 206                 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
 207                 "mlxcpld:status:green",
 208         },
 209         {
 210                 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
 211                 "mlxcpld:status:red",
 212         },
 213         {
 214                 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF,
 215                 "mlxcpld:uid:blue",
 216         },
 217 };
 218 
 219 enum mlxcpld_led_platform_types {
 220         MLXCPLD_LED_PLATFORM_DEFAULT,
 221         MLXCPLD_LED_PLATFORM_MSN2100,
 222 };
 223 
 224 static const char *mlx_product_names[] = {
 225         "DEFAULT",
 226         "MSN2100",
 227 };
 228 
 229 static enum
 230 mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void)
 231 {
 232         const char *mlx_product_name;
 233         int i;
 234 
 235         mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
 236         if (!mlx_product_name)
 237                 return MLXCPLD_LED_PLATFORM_DEFAULT;
 238 
 239         for (i = 1;  i < ARRAY_SIZE(mlx_product_names); i++) {
 240                 if (strstr(mlx_product_name, mlx_product_names[i]))
 241                         return i;
 242         }
 243 
 244         return MLXCPLD_LED_PLATFORM_DEFAULT;
 245 }
 246 
 247 static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag,
 248                                         u8 *data)
 249 {
 250         u32 addr = base + offset;
 251 
 252         if (rw_flag == 0)
 253                 outb(*data, addr);
 254         else
 255                 *data = inb(addr);
 256 }
 257 
 258 static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset)
 259 {
 260         u8 nib, val;
 261 
 262         /*
 263          * Each LED is controlled through low or high nibble of the relevant
 264          * CPLD register. Register offset is specified by off parameter.
 265          * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
 266          * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
 267          * green.
 268          * Parameter mask specifies which nibble is used for specific LED: mask
 269          * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
 270          * higher nibble (bits from 4 to 7).
 271          */
 272         spin_lock(&mlxcpld_led->lock);
 273         mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1,
 274                                     &val);
 275         nib = (mask == 0xf0) ? vset : (vset << 4);
 276         val = (val & mask) | nib;
 277         mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0,
 278                                     &val);
 279         spin_unlock(&mlxcpld_led->lock);
 280 }
 281 
 282 static void mlxcpld_led_brightness_set(struct led_classdev *led,
 283                                        enum led_brightness value)
 284 {
 285         struct mlxcpld_led_priv *pled = cdev_to_priv(led);
 286 
 287         if (value) {
 288                 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
 289                                      pled->param.base_color);
 290                 return;
 291         }
 292 
 293         mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
 294                              MLXCPLD_LED_IS_OFF);
 295 }
 296 
 297 static int mlxcpld_led_blink_set(struct led_classdev *led,
 298                                  unsigned long *delay_on,
 299                                  unsigned long *delay_off)
 300 {
 301         struct mlxcpld_led_priv *pled = cdev_to_priv(led);
 302 
 303         /*
 304          * HW supports two types of blinking: full (6Hz) and half (3Hz).
 305          * For delay on/off zero default setting 3Hz is used.
 306          */
 307         if (!(*delay_on == 0 && *delay_off == 0) &&
 308             !(*delay_on == MLXCPLD_LED_BLINK_3HZ &&
 309               *delay_off == MLXCPLD_LED_BLINK_3HZ) &&
 310             !(*delay_on == MLXCPLD_LED_BLINK_6HZ &&
 311               *delay_off == MLXCPLD_LED_BLINK_6HZ))
 312                 return -EINVAL;
 313 
 314         if (*delay_on == MLXCPLD_LED_BLINK_6HZ)
 315                 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
 316                                      pled->param.base_color +
 317                                      MLXCPLD_LED_OFFSET_FULL);
 318         else
 319                 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
 320                                      pled->param.base_color +
 321                                      MLXCPLD_LED_OFFSET_HALF);
 322 
 323         return 0;
 324 }
 325 
 326 static int mlxcpld_led_config(struct device *dev,
 327                               struct mlxcpld_led_pdata *cpld)
 328 {
 329         int i;
 330         int err;
 331 
 332         cpld->pled = devm_kcalloc(dev,
 333                                   cpld->num_led_instances,
 334                                   sizeof(struct mlxcpld_led_priv),
 335                                   GFP_KERNEL);
 336         if (!cpld->pled)
 337                 return -ENOMEM;
 338 
 339         for (i = 0; i < cpld->num_led_instances; i++) {
 340                 cpld->pled[i].cdev.name = cpld->profile[i].name;
 341                 cpld->pled[i].cdev.brightness = cpld->profile[i].brightness;
 342                 cpld->pled[i].cdev.max_brightness = 1;
 343                 cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set;
 344                 cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set;
 345                 cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME;
 346                 err = devm_led_classdev_register(dev, &cpld->pled[i].cdev);
 347                 if (err)
 348                         return err;
 349 
 350                 cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset;
 351                 cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask;
 352                 cpld->pled[i].param.base_color =
 353                                         mlxcpld_led->profile[i].base_color;
 354 
 355                 if (mlxcpld_led->profile[i].brightness)
 356                         mlxcpld_led_brightness_set(&cpld->pled[i].cdev,
 357                                         mlxcpld_led->profile[i].brightness);
 358         }
 359 
 360         return 0;
 361 }
 362 
 363 static int __init mlxcpld_led_probe(struct platform_device *pdev)
 364 {
 365         enum mlxcpld_led_platform_types mlxcpld_led_plat =
 366                                         mlxcpld_led_platform_check_sys_type();
 367 
 368         mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led),
 369                                    GFP_KERNEL);
 370         if (!mlxcpld_led)
 371                 return -ENOMEM;
 372 
 373         mlxcpld_led->pdev = pdev;
 374 
 375         switch (mlxcpld_led_plat) {
 376         case MLXCPLD_LED_PLATFORM_MSN2100:
 377                 mlxcpld_led->profile = mlxcpld_led_msn2100_profile;
 378                 mlxcpld_led->num_led_instances =
 379                                 ARRAY_SIZE(mlxcpld_led_msn2100_profile);
 380                 break;
 381 
 382         default:
 383                 mlxcpld_led->profile = mlxcpld_led_default_profile;
 384                 mlxcpld_led->num_led_instances =
 385                                 ARRAY_SIZE(mlxcpld_led_default_profile);
 386                 break;
 387         }
 388 
 389         spin_lock_init(&mlxcpld_led->lock);
 390 
 391         return mlxcpld_led_config(&pdev->dev, mlxcpld_led);
 392 }
 393 
 394 static struct platform_driver mlxcpld_led_driver = {
 395         .driver = {
 396                 .name   = KBUILD_MODNAME,
 397         },
 398 };
 399 
 400 static int __init mlxcpld_led_init(void)
 401 {
 402         struct platform_device *pdev;
 403         int err;
 404 
 405         if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd."))
 406                 return -ENODEV;
 407 
 408         pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
 409         if (IS_ERR(pdev)) {
 410                 pr_err("Device allocation failed\n");
 411                 return PTR_ERR(pdev);
 412         }
 413 
 414         err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe);
 415         if (err) {
 416                 pr_err("Probe platform driver failed\n");
 417                 platform_device_unregister(pdev);
 418         }
 419 
 420         return err;
 421 }
 422 
 423 static void __exit mlxcpld_led_exit(void)
 424 {
 425         platform_device_unregister(mlxcpld_led->pdev);
 426         platform_driver_unregister(&mlxcpld_led_driver);
 427 }
 428 
 429 module_init(mlxcpld_led_init);
 430 module_exit(mlxcpld_led_exit);
 431 
 432 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
 433 MODULE_DESCRIPTION("Mellanox board LED driver");
 434 MODULE_LICENSE("Dual BSD/GPL");
 435 MODULE_ALIAS("platform:leds_mlxcpld");

/* [<][>][^][v][top][bottom][index][help] */