forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
12 KiB
435 lines
12 KiB
/* |
|
* drivers/leds/leds-mlxcpld.c |
|
* Copyright (c) 2016 Mellanox Technologies. All rights reserved. |
|
* Copyright (c) 2016 Vadim Pasternak <[email protected]> |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* 3. Neither the names of the copyright holders nor the names of its |
|
* contributors may be used to endorse or promote products derived from |
|
* this software without specific prior written permission. |
|
* |
|
* Alternatively, this software may be distributed under the terms of the |
|
* GNU General Public License ("GPL") version 2 as published by the Free |
|
* Software Foundation. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
#include <linux/acpi.h> |
|
#include <linux/device.h> |
|
#include <linux/dmi.h> |
|
#include <linux/hwmon.h> |
|
#include <linux/hwmon-sysfs.h> |
|
#include <linux/io.h> |
|
#include <linux/leds.h> |
|
#include <linux/module.h> |
|
#include <linux/mod_devicetable.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/slab.h> |
|
|
|
#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */ |
|
|
|
/* Color codes for LEDs */ |
|
#define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */ |
|
#define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */ |
|
#define MLXCPLD_LED_IS_OFF 0x00 /* Off */ |
|
#define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */ |
|
#define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \ |
|
MLXCPLD_LED_OFFSET_HALF) |
|
#define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \ |
|
MLXCPLD_LED_OFFSET_FULL) |
|
#define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */ |
|
#define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \ |
|
MLXCPLD_LED_OFFSET_HALF) |
|
#define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \ |
|
MLXCPLD_LED_OFFSET_FULL) |
|
#define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */ |
|
#define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */ |
|
|
|
/** |
|
* mlxcpld_param - LED access parameters: |
|
* @offset - offset for LED access in CPLD device |
|
* @mask - mask for LED access in CPLD device |
|
* @base_color - base color code for LED |
|
**/ |
|
struct mlxcpld_param { |
|
u8 offset; |
|
u8 mask; |
|
u8 base_color; |
|
}; |
|
|
|
/** |
|
* mlxcpld_led_priv - LED private data: |
|
* @cled - LED class device instance |
|
* @param - LED CPLD access parameters |
|
**/ |
|
struct mlxcpld_led_priv { |
|
struct led_classdev cdev; |
|
struct mlxcpld_param param; |
|
}; |
|
|
|
#define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev) |
|
|
|
/** |
|
* mlxcpld_led_profile - system LED profile (defined per system class): |
|
* @offset - offset for LED access in CPLD device |
|
* @mask - mask for LED access in CPLD device |
|
* @base_color - base color code |
|
* @brightness - default brightness setting (on/off) |
|
* @name - LED name |
|
**/ |
|
struct mlxcpld_led_profile { |
|
u8 offset; |
|
u8 mask; |
|
u8 base_color; |
|
enum led_brightness brightness; |
|
const char *name; |
|
}; |
|
|
|
/** |
|
* mlxcpld_led_pdata - system LED private data |
|
* @pdev - platform device pointer |
|
* @pled - LED class device instance |
|
* @profile - system configuration profile |
|
* @num_led_instances - number of LED instances |
|
* @lock - device access lock |
|
**/ |
|
struct mlxcpld_led_pdata { |
|
struct platform_device *pdev; |
|
struct mlxcpld_led_priv *pled; |
|
struct mlxcpld_led_profile *profile; |
|
int num_led_instances; |
|
spinlock_t lock; |
|
}; |
|
|
|
static struct mlxcpld_led_pdata *mlxcpld_led; |
|
|
|
/* Default profile fit the next Mellanox systems: |
|
* "msx6710", "msx6720", "msb7700", "msn2700", "msx1410", |
|
* "msn2410", "msb7800", "msn2740" |
|
*/ |
|
static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = { |
|
{ |
|
0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:fan1:green", |
|
}, |
|
{ |
|
0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:fan1:red", |
|
}, |
|
{ |
|
0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:fan2:green", |
|
}, |
|
{ |
|
0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:fan2:red", |
|
}, |
|
{ |
|
0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:fan3:green", |
|
}, |
|
{ |
|
0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:fan3:red", |
|
}, |
|
{ |
|
0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:fan4:green", |
|
}, |
|
{ |
|
0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:fan4:red", |
|
}, |
|
{ |
|
0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:psu:green", |
|
}, |
|
{ |
|
0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:psu:red", |
|
}, |
|
{ |
|
0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:status:green", |
|
}, |
|
{ |
|
0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:status:red", |
|
}, |
|
}; |
|
|
|
/* Profile fit the Mellanox systems based on "msn2100" */ |
|
static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = { |
|
{ |
|
0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:fan:green", |
|
}, |
|
{ |
|
0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:fan:red", |
|
}, |
|
{ |
|
0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:psu1:green", |
|
}, |
|
{ |
|
0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:psu1:red", |
|
}, |
|
{ |
|
0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:psu2:green", |
|
}, |
|
{ |
|
0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:psu2:red", |
|
}, |
|
{ |
|
0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, |
|
"mlxcpld:status:green", |
|
}, |
|
{ |
|
0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, |
|
"mlxcpld:status:red", |
|
}, |
|
{ |
|
0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF, |
|
"mlxcpld:uid:blue", |
|
}, |
|
}; |
|
|
|
enum mlxcpld_led_platform_types { |
|
MLXCPLD_LED_PLATFORM_DEFAULT, |
|
MLXCPLD_LED_PLATFORM_MSN2100, |
|
}; |
|
|
|
static const char *mlx_product_names[] = { |
|
"DEFAULT", |
|
"MSN2100", |
|
}; |
|
|
|
static enum |
|
mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void) |
|
{ |
|
const char *mlx_product_name; |
|
int i; |
|
|
|
mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); |
|
if (!mlx_product_name) |
|
return MLXCPLD_LED_PLATFORM_DEFAULT; |
|
|
|
for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) { |
|
if (strstr(mlx_product_name, mlx_product_names[i])) |
|
return i; |
|
} |
|
|
|
return MLXCPLD_LED_PLATFORM_DEFAULT; |
|
} |
|
|
|
static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag, |
|
u8 *data) |
|
{ |
|
u32 addr = base + offset; |
|
|
|
if (rw_flag == 0) |
|
outb(*data, addr); |
|
else |
|
*data = inb(addr); |
|
} |
|
|
|
static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset) |
|
{ |
|
u8 nib, val; |
|
|
|
/* |
|
* Each LED is controlled through low or high nibble of the relevant |
|
* CPLD register. Register offset is specified by off parameter. |
|
* Parameter vset provides color code: 0x0 for off, 0x5 for solid red, |
|
* 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink |
|
* green. |
|
* Parameter mask specifies which nibble is used for specific LED: mask |
|
* 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - |
|
* higher nibble (bits from 4 to 7). |
|
*/ |
|
spin_lock(&mlxcpld_led->lock); |
|
mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1, |
|
&val); |
|
nib = (mask == 0xf0) ? vset : (vset << 4); |
|
val = (val & mask) | nib; |
|
mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0, |
|
&val); |
|
spin_unlock(&mlxcpld_led->lock); |
|
} |
|
|
|
static void mlxcpld_led_brightness_set(struct led_classdev *led, |
|
enum led_brightness value) |
|
{ |
|
struct mlxcpld_led_priv *pled = cdev_to_priv(led); |
|
|
|
if (value) { |
|
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, |
|
pled->param.base_color); |
|
return; |
|
} |
|
|
|
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, |
|
MLXCPLD_LED_IS_OFF); |
|
} |
|
|
|
static int mlxcpld_led_blink_set(struct led_classdev *led, |
|
unsigned long *delay_on, |
|
unsigned long *delay_off) |
|
{ |
|
struct mlxcpld_led_priv *pled = cdev_to_priv(led); |
|
|
|
/* |
|
* HW supports two types of blinking: full (6Hz) and half (3Hz). |
|
* For delay on/off zero default setting 3Hz is used. |
|
*/ |
|
if (!(*delay_on == 0 && *delay_off == 0) && |
|
!(*delay_on == MLXCPLD_LED_BLINK_3HZ && |
|
*delay_off == MLXCPLD_LED_BLINK_3HZ) && |
|
!(*delay_on == MLXCPLD_LED_BLINK_6HZ && |
|
*delay_off == MLXCPLD_LED_BLINK_6HZ)) |
|
return -EINVAL; |
|
|
|
if (*delay_on == MLXCPLD_LED_BLINK_6HZ) |
|
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, |
|
pled->param.base_color + |
|
MLXCPLD_LED_OFFSET_FULL); |
|
else |
|
mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, |
|
pled->param.base_color + |
|
MLXCPLD_LED_OFFSET_HALF); |
|
|
|
return 0; |
|
} |
|
|
|
static int mlxcpld_led_config(struct device *dev, |
|
struct mlxcpld_led_pdata *cpld) |
|
{ |
|
int i; |
|
int err; |
|
|
|
cpld->pled = devm_kcalloc(dev, |
|
cpld->num_led_instances, |
|
sizeof(struct mlxcpld_led_priv), |
|
GFP_KERNEL); |
|
if (!cpld->pled) |
|
return -ENOMEM; |
|
|
|
for (i = 0; i < cpld->num_led_instances; i++) { |
|
cpld->pled[i].cdev.name = cpld->profile[i].name; |
|
cpld->pled[i].cdev.brightness = cpld->profile[i].brightness; |
|
cpld->pled[i].cdev.max_brightness = 1; |
|
cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set; |
|
cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set; |
|
cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME; |
|
err = devm_led_classdev_register(dev, &cpld->pled[i].cdev); |
|
if (err) |
|
return err; |
|
|
|
cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset; |
|
cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask; |
|
cpld->pled[i].param.base_color = |
|
mlxcpld_led->profile[i].base_color; |
|
|
|
if (mlxcpld_led->profile[i].brightness) |
|
mlxcpld_led_brightness_set(&cpld->pled[i].cdev, |
|
mlxcpld_led->profile[i].brightness); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int __init mlxcpld_led_probe(struct platform_device *pdev) |
|
{ |
|
enum mlxcpld_led_platform_types mlxcpld_led_plat = |
|
mlxcpld_led_platform_check_sys_type(); |
|
|
|
mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led), |
|
GFP_KERNEL); |
|
if (!mlxcpld_led) |
|
return -ENOMEM; |
|
|
|
mlxcpld_led->pdev = pdev; |
|
|
|
switch (mlxcpld_led_plat) { |
|
case MLXCPLD_LED_PLATFORM_MSN2100: |
|
mlxcpld_led->profile = mlxcpld_led_msn2100_profile; |
|
mlxcpld_led->num_led_instances = |
|
ARRAY_SIZE(mlxcpld_led_msn2100_profile); |
|
break; |
|
|
|
default: |
|
mlxcpld_led->profile = mlxcpld_led_default_profile; |
|
mlxcpld_led->num_led_instances = |
|
ARRAY_SIZE(mlxcpld_led_default_profile); |
|
break; |
|
} |
|
|
|
spin_lock_init(&mlxcpld_led->lock); |
|
|
|
return mlxcpld_led_config(&pdev->dev, mlxcpld_led); |
|
} |
|
|
|
static struct platform_driver mlxcpld_led_driver = { |
|
.driver = { |
|
.name = KBUILD_MODNAME, |
|
}, |
|
}; |
|
|
|
static int __init mlxcpld_led_init(void) |
|
{ |
|
struct platform_device *pdev; |
|
int err; |
|
|
|
if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd.")) |
|
return -ENODEV; |
|
|
|
pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); |
|
if (IS_ERR(pdev)) { |
|
pr_err("Device allocation failed\n"); |
|
return PTR_ERR(pdev); |
|
} |
|
|
|
err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe); |
|
if (err) { |
|
pr_err("Probe platform driver failed\n"); |
|
platform_device_unregister(pdev); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static void __exit mlxcpld_led_exit(void) |
|
{ |
|
platform_device_unregister(mlxcpld_led->pdev); |
|
platform_driver_unregister(&mlxcpld_led_driver); |
|
} |
|
|
|
module_init(mlxcpld_led_init); |
|
module_exit(mlxcpld_led_exit); |
|
|
|
MODULE_AUTHOR("Vadim Pasternak <[email protected]>"); |
|
MODULE_DESCRIPTION("Mellanox board LED driver"); |
|
MODULE_LICENSE("Dual BSD/GPL"); |
|
MODULE_ALIAS("platform:leds_mlxcpld");
|
|
|