Vadim Pasternak | be4fdf9 | 2016-09-08 07:25:53 +0000 | [diff] [blame] | 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 | |
Vadim Pasternak | dca897e | 2016-11-03 19:17:19 +0000 | [diff] [blame] | 403 | if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) |
| 404 | return -ENODEV; |
| 405 | |
Vadim Pasternak | be4fdf9 | 2016-09-08 07:25:53 +0000 | [diff] [blame] | 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"); |
Vadim Pasternak | dca897e | 2016-11-03 19:17:19 +0000 | [diff] [blame] | 432 | MODULE_LICENSE("Dual BSD/GPL"); |
Vadim Pasternak | be4fdf9 | 2016-09-08 07:25:53 +0000 | [diff] [blame] | 433 | MODULE_ALIAS("platform:leds_mlxcpld"); |