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/device.h> 36 #include <linux/dmi.h> 37 #include <linux/hwmon.h> 38 #include <linux/hwmon-sysfs.h> 39 #include <linux/io.h> 40 #include <linux/leds.h> 41 #include <linux/module.h> 42 #include <linux/platform_device.h> 43 #include <linux/slab.h> 44 45 #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */ 46 47 /* Color codes for LEDs */ 48 #define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */ 49 #define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */ 50 #define MLXCPLD_LED_IS_OFF 0x00 /* Off */ 51 #define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */ 52 #define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \ 53 MLXCPLD_LED_OFFSET_HALF) 54 #define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \ 55 MLXCPLD_LED_OFFSET_FULL) 56 #define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */ 57 #define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \ 58 MLXCPLD_LED_OFFSET_HALF) 59 #define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \ 60 MLXCPLD_LED_OFFSET_FULL) 61 #define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */ 62 #define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */ 63 64 /** 65 * struct mlxcpld_param - LED access parameters: 66 * @offset: offset for LED access in CPLD device 67 * @mask: mask for LED access in CPLD device 68 * @base_color: base color code for LED 69 **/ 70 struct mlxcpld_param { 71 u8 offset; 72 u8 mask; 73 u8 base_color; 74 }; 75 76 /** 77 * struct mlxcpld_led_priv - LED private data: 78 * @cdev: LED class device instance 79 * @param: LED CPLD access parameters 80 **/ 81 struct mlxcpld_led_priv { 82 struct led_classdev cdev; 83 struct mlxcpld_param param; 84 }; 85 86 #define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev) 87 88 /** 89 * struct mlxcpld_led_profile - system LED profile (defined per system class): 90 * @offset: offset for LED access in CPLD device 91 * @mask: mask for LED access in CPLD device 92 * @base_color: base color code 93 * @brightness: default brightness setting (on/off) 94 * @name: LED name 95 **/ 96 struct mlxcpld_led_profile { 97 u8 offset; 98 u8 mask; 99 u8 base_color; 100 enum led_brightness brightness; 101 const char *name; 102 }; 103 104 /** 105 * struct mlxcpld_led_pdata - system LED private data 106 * @pdev: platform device pointer 107 * @pled: LED class device instance 108 * @profile: system configuration profile 109 * @num_led_instances: number of LED instances 110 * @lock: device access lock 111 **/ 112 struct mlxcpld_led_pdata { 113 struct platform_device *pdev; 114 struct mlxcpld_led_priv *pled; 115 struct mlxcpld_led_profile *profile; 116 int num_led_instances; 117 spinlock_t lock; 118 }; 119 120 static struct mlxcpld_led_pdata *mlxcpld_led; 121 122 /* Default profile fit the next Mellanox systems: 123 * "msx6710", "msx6720", "msb7700", "msn2700", "msx1410", 124 * "msn2410", "msb7800", "msn2740" 125 */ 126 static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = { 127 { 128 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 129 "mlxcpld:fan1:green", 130 }, 131 { 132 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 133 "mlxcpld:fan1:red", 134 }, 135 { 136 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, 137 "mlxcpld:fan2:green", 138 }, 139 { 140 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 141 "mlxcpld:fan2:red", 142 }, 143 { 144 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 145 "mlxcpld:fan3:green", 146 }, 147 { 148 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 149 "mlxcpld:fan3:red", 150 }, 151 { 152 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, 153 "mlxcpld:fan4:green", 154 }, 155 { 156 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 157 "mlxcpld:fan4:red", 158 }, 159 { 160 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, 161 "mlxcpld:psu:green", 162 }, 163 { 164 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 165 "mlxcpld:psu:red", 166 }, 167 { 168 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 169 "mlxcpld:status:green", 170 }, 171 { 172 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 173 "mlxcpld:status:red", 174 }, 175 }; 176 177 /* Profile fit the Mellanox systems based on "msn2100" */ 178 static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = { 179 { 180 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 181 "mlxcpld:fan:green", 182 }, 183 { 184 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 185 "mlxcpld:fan:red", 186 }, 187 { 188 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 189 "mlxcpld:psu1:green", 190 }, 191 { 192 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 193 "mlxcpld:psu1:red", 194 }, 195 { 196 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, 197 "mlxcpld:psu2:green", 198 }, 199 { 200 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 201 "mlxcpld:psu2:red", 202 }, 203 { 204 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, 205 "mlxcpld:status:green", 206 }, 207 { 208 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, 209 "mlxcpld:status:red", 210 }, 211 { 212 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF, 213 "mlxcpld:uid:blue", 214 }, 215 }; 216 217 enum mlxcpld_led_platform_types { 218 MLXCPLD_LED_PLATFORM_DEFAULT, 219 MLXCPLD_LED_PLATFORM_MSN2100, 220 }; 221 222 static const char *mlx_product_names[] = { 223 "DEFAULT", 224 "MSN2100", 225 }; 226 227 static enum 228 mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void) 229 { 230 const char *mlx_product_name; 231 int i; 232 233 mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); 234 if (!mlx_product_name) 235 return MLXCPLD_LED_PLATFORM_DEFAULT; 236 237 for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) { 238 if (strstr(mlx_product_name, mlx_product_names[i])) 239 return i; 240 } 241 242 return MLXCPLD_LED_PLATFORM_DEFAULT; 243 } 244 245 static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag, 246 u8 *data) 247 { 248 u32 addr = base + offset; 249 250 if (rw_flag == 0) 251 outb(*data, addr); 252 else 253 *data = inb(addr); 254 } 255 256 static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset) 257 { 258 u8 nib, val; 259 260 /* 261 * Each LED is controlled through low or high nibble of the relevant 262 * CPLD register. Register offset is specified by off parameter. 263 * Parameter vset provides color code: 0x0 for off, 0x5 for solid red, 264 * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink 265 * green. 266 * Parameter mask specifies which nibble is used for specific LED: mask 267 * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - 268 * higher nibble (bits from 4 to 7). 269 */ 270 spin_lock(&mlxcpld_led->lock); 271 mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1, 272 &val); 273 nib = (mask == 0xf0) ? vset : (vset << 4); 274 val = (val & mask) | nib; 275 mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0, 276 &val); 277 spin_unlock(&mlxcpld_led->lock); 278 } 279 280 static void mlxcpld_led_brightness_set(struct led_classdev *led, 281 enum led_brightness value) 282 { 283 struct mlxcpld_led_priv *pled = cdev_to_priv(led); 284 285 if (value) { 286 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, 287 pled->param.base_color); 288 return; 289 } 290 291 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, 292 MLXCPLD_LED_IS_OFF); 293 } 294 295 static int mlxcpld_led_blink_set(struct led_classdev *led, 296 unsigned long *delay_on, 297 unsigned long *delay_off) 298 { 299 struct mlxcpld_led_priv *pled = cdev_to_priv(led); 300 301 /* 302 * HW supports two types of blinking: full (6Hz) and half (3Hz). 303 * For delay on/off zero default setting 3Hz is used. 304 */ 305 if (!(*delay_on == 0 && *delay_off == 0) && 306 !(*delay_on == MLXCPLD_LED_BLINK_3HZ && 307 *delay_off == MLXCPLD_LED_BLINK_3HZ) && 308 !(*delay_on == MLXCPLD_LED_BLINK_6HZ && 309 *delay_off == MLXCPLD_LED_BLINK_6HZ)) 310 return -EINVAL; 311 312 if (*delay_on == MLXCPLD_LED_BLINK_6HZ) 313 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, 314 pled->param.base_color + 315 MLXCPLD_LED_OFFSET_FULL); 316 else 317 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, 318 pled->param.base_color + 319 MLXCPLD_LED_OFFSET_HALF); 320 321 return 0; 322 } 323 324 static int mlxcpld_led_config(struct device *dev, 325 struct mlxcpld_led_pdata *cpld) 326 { 327 int i; 328 int err; 329 330 cpld->pled = devm_kcalloc(dev, 331 cpld->num_led_instances, 332 sizeof(struct mlxcpld_led_priv), 333 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