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 * struct 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 * struct 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 * struct 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 * struct 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"); 436