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_kzalloc(dev, sizeof(struct mlxcpld_led_priv) * 333 cpld->num_led_instances, GFP_KERNEL); 334 if (!cpld->pled) 335 return -ENOMEM; 336 337 for (i = 0; i < cpld->num_led_instances; i++) { 338 cpld->pled[i].cdev.name = cpld->profile[i].name; 339 cpld->pled[i].cdev.brightness = cpld->profile[i].brightness; 340 cpld->pled[i].cdev.max_brightness = 1; 341 cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set; 342 cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set; 343 cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME; 344 err = devm_led_classdev_register(dev, &cpld->pled[i].cdev); 345 if (err) 346 return err; 347 348 cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset; 349 cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask; 350 cpld->pled[i].param.base_color = 351 mlxcpld_led->profile[i].base_color; 352 353 if (mlxcpld_led->profile[i].brightness) 354 mlxcpld_led_brightness_set(&cpld->pled[i].cdev, 355 mlxcpld_led->profile[i].brightness); 356 } 357 358 return 0; 359 } 360 361 static int __init mlxcpld_led_probe(struct platform_device *pdev) 362 { 363 enum mlxcpld_led_platform_types mlxcpld_led_plat = 364 mlxcpld_led_platform_check_sys_type(); 365 366 mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led), 367 GFP_KERNEL); 368 if (!mlxcpld_led) 369 return -ENOMEM; 370 371 mlxcpld_led->pdev = pdev; 372 373 switch (mlxcpld_led_plat) { 374 case MLXCPLD_LED_PLATFORM_MSN2100: 375 mlxcpld_led->profile = mlxcpld_led_msn2100_profile; 376 mlxcpld_led->num_led_instances = 377 ARRAY_SIZE(mlxcpld_led_msn2100_profile); 378 break; 379 380 default: 381 mlxcpld_led->profile = mlxcpld_led_default_profile; 382 mlxcpld_led->num_led_instances = 383 ARRAY_SIZE(mlxcpld_led_default_profile); 384 break; 385 } 386 387 spin_lock_init(&mlxcpld_led->lock); 388 389 return mlxcpld_led_config(&pdev->dev, mlxcpld_led); 390 } 391 392 static struct platform_driver mlxcpld_led_driver = { 393 .driver = { 394 .name = KBUILD_MODNAME, 395 }, 396 }; 397 398 static int __init mlxcpld_led_init(void) 399 { 400 struct platform_device *pdev; 401 int err; 402 403 if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) 404 return -ENODEV; 405 406 pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); 407 if (IS_ERR(pdev)) { 408 pr_err("Device allocation failed\n"); 409 return PTR_ERR(pdev); 410 } 411 412 err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe); 413 if (err) { 414 pr_err("Probe platform driver failed\n"); 415 platform_device_unregister(pdev); 416 } 417 418 return err; 419 } 420 421 static void __exit mlxcpld_led_exit(void) 422 { 423 platform_device_unregister(mlxcpld_led->pdev); 424 platform_driver_unregister(&mlxcpld_led_driver); 425 } 426 427 module_init(mlxcpld_led_init); 428 module_exit(mlxcpld_led_exit); 429 430 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 431 MODULE_DESCRIPTION("Mellanox board LED driver"); 432 MODULE_LICENSE("Dual BSD/GPL"); 433 MODULE_ALIAS("platform:leds_mlxcpld"); 434