platform/broadcom/sonic-platform-modules-accton/as7712-32x/modules/leds-accton_as7712_32x.c (560 lines of code) (raw):
/*
* A LED driver for the accton_as7712_32x_led
*
* Copyright (C) 2014 Accton Technology Corporation.
* Brandon Chuang <brandon_chuang@accton.com.tw>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*#define DEBUG*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/dmi.h>
extern int accton_i2c_cpld_read (unsigned short cpld_addr, u8 reg);
extern int accton_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
extern void led_classdev_unregister(struct led_classdev *led_cdev);
extern void led_classdev_resume(struct led_classdev *led_cdev);
extern void led_classdev_suspend(struct led_classdev *led_cdev);
#define DRVNAME "accton_as7712_32x_led"
#define ENABLE_PORT_LED 1
struct accton_as7712_32x_led_data {
struct platform_device *pdev;
struct mutex update_lock;
char valid; /* != 0 if registers are valid */
unsigned long last_updated; /* In jiffies */
u8 reg_val[1]; /* only 1 register*/
};
static struct accton_as7712_32x_led_data *ledctl = NULL;
/* LED related data
*/
#define LED_CNTRLER_I2C_ADDRESS (0x60)
#define LED_TYPE_DIAG_REG_MASK (0x3)
#define LED_MODE_DIAG_GREEN_VALUE (0x02)
#define LED_MODE_DIAG_RED_VALUE (0x01)
#define LED_MODE_DIAG_AMBER_VALUE (0x00) /*It's yellow actually. Green+Red=Yellow*/
#define LED_MODE_DIAG_OFF_VALUE (0x03)
#define LED_TYPE_LOC_REG_MASK (0x80)
#define LED_MODE_LOC_ON_VALUE (0)
#define LED_MODE_LOC_OFF_VALUE (0x80)
#if (ENABLE_PORT_LED == 1)
#define LED_TYPE_PORT_LED(port) \
LED_TYPE_PORT##port##_LED0, \
LED_TYPE_PORT##port##_LED1, \
LED_TYPE_PORT##port##_LED2, \
LED_TYPE_PORT##port##_LED3
#endif
enum led_type {
LED_TYPE_DIAG,
LED_TYPE_LOC,
LED_TYPE_FAN,
LED_TYPE_PSU1,
LED_TYPE_PSU2,
#if (ENABLE_PORT_LED == 1)
LED_TYPE_PORT_LED(0),
LED_TYPE_PORT_LED(1),
LED_TYPE_PORT_LED(2),
LED_TYPE_PORT_LED(3),
LED_TYPE_PORT_LED(4),
LED_TYPE_PORT_LED(5),
LED_TYPE_PORT_LED(6),
LED_TYPE_PORT_LED(7),
LED_TYPE_PORT_LED(8),
LED_TYPE_PORT_LED(9),
LED_TYPE_PORT_LED(10),
LED_TYPE_PORT_LED(11),
LED_TYPE_PORT_LED(12),
LED_TYPE_PORT_LED(13),
LED_TYPE_PORT_LED(14),
LED_TYPE_PORT_LED(15),
LED_TYPE_PORT_LED(16),
LED_TYPE_PORT_LED(17),
LED_TYPE_PORT_LED(18),
LED_TYPE_PORT_LED(19),
LED_TYPE_PORT_LED(20),
LED_TYPE_PORT_LED(21),
LED_TYPE_PORT_LED(22),
LED_TYPE_PORT_LED(23),
LED_TYPE_PORT_LED(24),
LED_TYPE_PORT_LED(25),
LED_TYPE_PORT_LED(26),
LED_TYPE_PORT_LED(27),
LED_TYPE_PORT_LED(28),
LED_TYPE_PORT_LED(29),
LED_TYPE_PORT_LED(30),
LED_TYPE_PORT_LED(31),
#endif
};
struct led_reg {
u32 types;
u8 reg_addr;
};
static const struct led_reg led_reg_map[] = {
{(1<<LED_TYPE_LOC) | (1<<LED_TYPE_DIAG), 0x41},
};
#if 0
enum led_light_mode {
LED_MODE_OFF = 0,
LED_MODE_GREEN,
LED_MODE_AMBER,
LED_MODE_RED,
LED_MODE_BLUE,
LED_MODE_GREEN_BLINK,
LED_MODE_AMBER_BLINK,
LED_MODE_RED_BLINK,
LED_MODE_BLUE_BLINK,
LED_MODE_AUTO,
LED_MODE_UNKNOWN
};
#else
enum led_light_mode {
LED_MODE_OFF,
LED_MODE_RED = 10,
LED_MODE_RED_BLINKING = 11,
LED_MODE_ORANGE = 12,
LED_MODE_ORANGE_BLINKING = 13,
LED_MODE_YELLOW = 14,
LED_MODE_YELLOW_BLINKING = 15,
LED_MODE_GREEN = 16,
LED_MODE_GREEN_BLINKING = 17,
LED_MODE_BLUE = 18,
LED_MODE_BLUE_BLINKING = 19,
LED_MODE_PURPLE = 20,
LED_MODE_PURPLE_BLINKING = 21,
LED_MODE_AUTO = 22,
LED_MODE_AUTO_BLINKING = 23,
LED_MODE_WHITE = 24,
LED_MODE_WHITE_BLINKING = 25,
LED_MODE_CYAN = 26,
LED_MODE_CYAN_BLINKING = 27,
};
#endif
struct led_type_mode {
enum led_type type;
enum led_light_mode mode;
int reg_bit_mask;
int mode_value;
};
static struct led_type_mode led_type_mode_data[] = {
{LED_TYPE_LOC, LED_MODE_OFF, LED_TYPE_LOC_REG_MASK, LED_MODE_LOC_OFF_VALUE},
{LED_TYPE_LOC, LED_MODE_BLUE, LED_TYPE_LOC_REG_MASK, LED_MODE_LOC_ON_VALUE},
{LED_TYPE_DIAG, LED_MODE_OFF, LED_TYPE_DIAG_REG_MASK, LED_MODE_DIAG_OFF_VALUE},
{LED_TYPE_DIAG, LED_MODE_GREEN, LED_TYPE_DIAG_REG_MASK, LED_MODE_DIAG_GREEN_VALUE},
{LED_TYPE_DIAG, LED_MODE_RED, LED_TYPE_DIAG_REG_MASK, LED_MODE_DIAG_RED_VALUE},
{LED_TYPE_DIAG, LED_MODE_ORANGE,LED_TYPE_DIAG_REG_MASK, LED_MODE_DIAG_AMBER_VALUE},
};
static void accton_as7712_32x_led_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode, enum led_type type);
static int accton_getLedReg(enum led_type type, u8 *reg)
{
int i;
for (i = 0; i < ARRAY_SIZE(led_reg_map); i++) {
if(led_reg_map[i].types & (type<<1)){
*reg = led_reg_map[i].reg_addr;
return 0;
}
}
return 1;
}
static int led_reg_val_to_light_mode(enum led_type type, u8 reg_val) {
int i;
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
if (type != led_type_mode_data[i].type)
continue;
if ((led_type_mode_data[i].reg_bit_mask & reg_val) ==
led_type_mode_data[i].mode_value)
{
return led_type_mode_data[i].mode;
}
}
return 0;
}
static u8 led_light_mode_to_reg_val(enum led_type type,
enum led_light_mode mode, u8 reg_val) {
int i;
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
if (type != led_type_mode_data[i].type)
continue;
if (mode != led_type_mode_data[i].mode)
continue;
reg_val = led_type_mode_data[i].mode_value |
(reg_val & (~led_type_mode_data[i].reg_bit_mask));
break;
}
return reg_val;
}
static int accton_as7712_32x_led_read_value(u8 reg)
{
return accton_i2c_cpld_read(LED_CNTRLER_I2C_ADDRESS, reg);
}
static int accton_as7712_32x_led_write_value(u8 reg, u8 value)
{
return accton_i2c_cpld_write(LED_CNTRLER_I2C_ADDRESS, reg, value);
}
static void accton_as7712_32x_led_update(void)
{
mutex_lock(&ledctl->update_lock);
if (time_after(jiffies, ledctl->last_updated + HZ + HZ / 2)
|| !ledctl->valid) {
int i;
dev_dbg(&ledctl->pdev->dev, "Starting accton_as7712_32x_led update\n");
/* Update LED data
*/
for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) {
int status = accton_as7712_32x_led_read_value(led_reg_map[i].reg_addr);
if (status < 0) {
ledctl->valid = 0;
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", led_reg_map[i].reg_addr, status);
goto exit;
}
else
{
ledctl->reg_val[i] = status;
}
}
ledctl->last_updated = jiffies;
ledctl->valid = 1;
}
exit:
mutex_unlock(&ledctl->update_lock);
}
static void accton_as7712_32x_led_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode,
enum led_type type)
{
int reg_val;
u8 reg ;
mutex_lock(&ledctl->update_lock);
if( !accton_getLedReg(type, ®))
{
dev_dbg(&ledctl->pdev->dev, "Not match item for %d.\n", type);
}
reg_val = accton_as7712_32x_led_read_value(reg);
if (reg_val < 0) {
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", reg, reg_val);
goto exit;
}
reg_val = led_light_mode_to_reg_val(type, led_light_mode, reg_val);
accton_as7712_32x_led_write_value(reg, reg_val);
/* to prevent the slow-update issue */
ledctl->valid = 0;
exit:
mutex_unlock(&ledctl->update_lock);
}
static void accton_as7712_32x_led_diag_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as7712_32x_led_set(led_cdev, led_light_mode, LED_TYPE_DIAG);
}
static enum led_brightness accton_as7712_32x_led_diag_get(struct led_classdev *cdev)
{
accton_as7712_32x_led_update();
return led_reg_val_to_light_mode(LED_TYPE_DIAG, ledctl->reg_val[0]);
}
static void accton_as7712_32x_led_loc_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as7712_32x_led_set(led_cdev, led_light_mode, LED_TYPE_LOC);
}
static enum led_brightness accton_as7712_32x_led_loc_get(struct led_classdev *cdev)
{
accton_as7712_32x_led_update();
return led_reg_val_to_light_mode(LED_TYPE_LOC, ledctl->reg_val[0]);
}
static void accton_as7712_32x_led_auto_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
}
static enum led_brightness accton_as7712_32x_led_auto_get(struct led_classdev *cdev)
{
return LED_MODE_AUTO;
}
#if (ENABLE_PORT_LED == 1)
#define PORT_LED_COLOR_MASK (0x7 << 2)
#define PORT_LED_COLOR1_REG_VAL (0x0 << 2)
#define PORT_LED_COLOR2_REG_VAL (0x1 << 2)
#define PORT_LED_COLOR3_REG_VAL (0x2 << 2)
#define PORT_LED_COLOR4_REG_VAL (0x3 << 2)
#define PORT_LED_COLOR5_REG_VAL (0x4 << 2)
#define PORT_LED_COLOR6_REG_VAL (0x5 << 2)
#define PORT_LED_COLOR7_REG_VAL (0x6 << 2)
#define PORT_LED_COLOR8_REG_VAL (0x7 << 2)
static int accton_as7712_32x_port_led_read_value(unsigned short cpld_addr, u8 reg)
{
return accton_i2c_cpld_read(cpld_addr, reg);
}
static int accton_as7712_32x_port_led_write_value(unsigned short cpld_addr, u8 reg, u8 value)
{
return accton_i2c_cpld_write(cpld_addr, reg, value);
}
static int port_led_mode_to_cpld_val(int mode)
{
u8 color = 0;
u8 blinking = 0;
u8 on = 1 << 0;
switch (mode) {
case LED_MODE_WHITE_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_WHITE: color = 0x0 << 2;
break;
case LED_MODE_YELLOW_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_YELLOW: color = 0x1 << 2;
break;
case LED_MODE_ORANGE_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_ORANGE: color = 0x2 << 2;
break;
case LED_MODE_PURPLE_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_PURPLE: color = 0x3 << 2;
break;
case LED_MODE_CYAN_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_CYAN: color = 0x4 << 2;
break;
case LED_MODE_RED_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_RED: color = 0x5 << 2;
break;
case LED_MODE_GREEN_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_GREEN: color = 0x6 << 2;
break;
case LED_MODE_BLUE_BLINKING: blinking = 1 << 1; /* fall through */
case LED_MODE_BLUE: color = 0x7 << 2;
break;
case LED_MODE_OFF: on = 0 << 0;
break;
default:
return -EINVAL;
}
return (color | blinking | on);
}
static int cpld_val_to_port_led_mode(uint8_t value)
{
int on = (value & 0x1);
int blinking = (value & 0x2);
int color = (value & PORT_LED_COLOR_MASK) ;
if (!on) {
return LED_MODE_OFF;
}
switch (color) {
case PORT_LED_COLOR1_REG_VAL:
return blinking ? LED_MODE_WHITE_BLINKING : LED_MODE_WHITE;
case PORT_LED_COLOR2_REG_VAL:
return blinking ? LED_MODE_YELLOW_BLINKING : LED_MODE_YELLOW;
case PORT_LED_COLOR3_REG_VAL:
return blinking ? LED_MODE_ORANGE_BLINKING : LED_MODE_ORANGE;
case PORT_LED_COLOR4_REG_VAL:
return blinking ? LED_MODE_PURPLE_BLINKING : LED_MODE_PURPLE;
case PORT_LED_COLOR5_REG_VAL:
return blinking ? LED_MODE_CYAN_BLINKING : LED_MODE_CYAN;
case PORT_LED_COLOR6_REG_VAL:
return blinking ? LED_MODE_RED_BLINKING : LED_MODE_RED;
case PORT_LED_COLOR7_REG_VAL:
return blinking ? LED_MODE_GREEN_BLINKING : LED_MODE_GREEN;
case PORT_LED_COLOR8_REG_VAL:
return blinking ? LED_MODE_BLUE_BLINKING : LED_MODE_BLUE;
default:
return -EINVAL;;
}
}
static void accton_as7712_32x_port_led_set(struct led_classdev *cdev,
enum led_brightness led_light_mode)
{
unsigned int port, lid;
unsigned short cpld_addr;
u8 reg, value;
sscanf(cdev->name, "accton_as7712_32x_led::port%u_led%u", &port, &lid);
if (port > 32 || lid > 4) {
dev_dbg(&ledctl->pdev->dev, "Port(%u), Led_id(%u) not match\n", port, lid);
return;
}
cpld_addr = (port < 16) ? 0x64 : 0x62;
reg = (0x50 + (port % 16) * 4 + lid);
value = port_led_mode_to_cpld_val(led_light_mode);
if (value < 0) {
dev_dbg(&ledctl->pdev->dev, "Unknow port led mode(%d)\n", led_light_mode);
return;
}
accton_as7712_32x_port_led_write_value(cpld_addr, reg, value);
}
static enum led_brightness accton_as7712_32x_port_led_get(struct led_classdev *cdev)
{
unsigned int port, lid;
unsigned short cpld_addr;
u8 reg, value;
sscanf(cdev->name, "accton_as7712_32x_led::port%u_led%u", &port, &lid);
if (port > 32 || lid > 4) {
dev_dbg(&ledctl->pdev->dev, "Port(%u), Led_id(%u) not match\n", port, lid);
return -EINVAL;
}
cpld_addr = (port < 16) ? 0x64 : 0x62;
reg = (0x50 + (port % 16) * 4 + lid);
value = accton_as7712_32x_port_led_read_value(cpld_addr, reg);
if (value < 0) {
dev_dbg(&ledctl->pdev->dev, "Unable to read reg value from cpld(0x%x), reg(0x%x)\n", cpld_addr, reg);
return value;
}
return cpld_val_to_port_led_mode(value);
}
#define _PORT_LED_CLASSDEV(port, lid) \
[LED_TYPE_PORT##port##_LED##lid] = { \
.name = "accton_as7712_32x_led::port"#port"_led"#lid,\
.default_trigger = "unused", \
.brightness_set = accton_as7712_32x_port_led_set, \
.brightness_get = accton_as7712_32x_port_led_get, \
.max_brightness = LED_MODE_CYAN_BLINKING, \
}
#define PORT_LED_CLASSDEV(port) \
_PORT_LED_CLASSDEV(port, 0),\
_PORT_LED_CLASSDEV(port, 1),\
_PORT_LED_CLASSDEV(port, 2),\
_PORT_LED_CLASSDEV(port, 3)
#endif
static struct led_classdev accton_as7712_32x_leds[] = {
[LED_TYPE_DIAG] = {
.name = "accton_as7712_32x_led::diag",
.default_trigger = "unused",
.brightness_set = accton_as7712_32x_led_diag_set,
.brightness_get = accton_as7712_32x_led_diag_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_RED,
},
[LED_TYPE_LOC] = {
.name = "accton_as7712_32x_led::loc",
.default_trigger = "unused",
.brightness_set = accton_as7712_32x_led_loc_set,
.brightness_get = accton_as7712_32x_led_loc_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_BLUE,
},
[LED_TYPE_FAN] = {
.name = "accton_as7712_32x_led::fan",
.default_trigger = "unused",
.brightness_set = accton_as7712_32x_led_auto_set,
.brightness_get = accton_as7712_32x_led_auto_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_PSU1] = {
.name = "accton_as7712_32x_led::psu1",
.default_trigger = "unused",
.brightness_set = accton_as7712_32x_led_auto_set,
.brightness_get = accton_as7712_32x_led_auto_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_PSU2] = {
.name = "accton_as7712_32x_led::psu2",
.default_trigger = "unused",
.brightness_set = accton_as7712_32x_led_auto_set,
.brightness_get = accton_as7712_32x_led_auto_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
#if (ENABLE_PORT_LED == 1)
PORT_LED_CLASSDEV(0),
PORT_LED_CLASSDEV(1),
PORT_LED_CLASSDEV(2),
PORT_LED_CLASSDEV(3),
PORT_LED_CLASSDEV(4),
PORT_LED_CLASSDEV(5),
PORT_LED_CLASSDEV(6),
PORT_LED_CLASSDEV(7),
PORT_LED_CLASSDEV(8),
PORT_LED_CLASSDEV(9),
PORT_LED_CLASSDEV(10),
PORT_LED_CLASSDEV(11),
PORT_LED_CLASSDEV(12),
PORT_LED_CLASSDEV(13),
PORT_LED_CLASSDEV(14),
PORT_LED_CLASSDEV(15),
PORT_LED_CLASSDEV(16),
PORT_LED_CLASSDEV(17),
PORT_LED_CLASSDEV(18),
PORT_LED_CLASSDEV(19),
PORT_LED_CLASSDEV(20),
PORT_LED_CLASSDEV(21),
PORT_LED_CLASSDEV(22),
PORT_LED_CLASSDEV(23),
PORT_LED_CLASSDEV(24),
PORT_LED_CLASSDEV(25),
PORT_LED_CLASSDEV(26),
PORT_LED_CLASSDEV(27),
PORT_LED_CLASSDEV(28),
PORT_LED_CLASSDEV(29),
PORT_LED_CLASSDEV(30),
PORT_LED_CLASSDEV(31),
#endif
};
static int accton_as7712_32x_led_suspend(struct platform_device *dev,
pm_message_t state)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(accton_as7712_32x_leds); i++) {
led_classdev_suspend(&accton_as7712_32x_leds[i]);
}
return 0;
}
static int accton_as7712_32x_led_resume(struct platform_device *dev)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(accton_as7712_32x_leds); i++) {
led_classdev_resume(&accton_as7712_32x_leds[i]);
}
return 0;
}
static int accton_as7712_32x_led_probe(struct platform_device *pdev)
{
int ret, i;
for (i = 0; i < ARRAY_SIZE(accton_as7712_32x_leds); i++) {
ret = led_classdev_register(&pdev->dev, &accton_as7712_32x_leds[i]);
if (ret < 0)
break;
}
/* Check if all LEDs were successfully registered */
if (i != ARRAY_SIZE(accton_as7712_32x_leds)){
int j;
/* only unregister the LEDs that were successfully registered */
for (j = 0; j < i; j++) {
led_classdev_unregister(&accton_as7712_32x_leds[i]);
}
}
return ret;
}
static int accton_as7712_32x_led_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(accton_as7712_32x_leds); i++) {
led_classdev_unregister(&accton_as7712_32x_leds[i]);
}
return 0;
}
static struct platform_driver accton_as7712_32x_led_driver = {
.probe = accton_as7712_32x_led_probe,
.remove = accton_as7712_32x_led_remove,
.suspend = accton_as7712_32x_led_suspend,
.resume = accton_as7712_32x_led_resume,
.driver = {
.name = DRVNAME,
.owner = THIS_MODULE,
},
};
static int __init accton_as7712_32x_led_init(void)
{
int ret;
ret = platform_driver_register(&accton_as7712_32x_led_driver);
if (ret < 0) {
goto exit;
}
ledctl = kzalloc(sizeof(struct accton_as7712_32x_led_data), GFP_KERNEL);
if (!ledctl) {
ret = -ENOMEM;
platform_driver_unregister(&accton_as7712_32x_led_driver);
goto exit;
}
mutex_init(&ledctl->update_lock);
ledctl->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
if (IS_ERR(ledctl->pdev)) {
ret = PTR_ERR(ledctl->pdev);
platform_driver_unregister(&accton_as7712_32x_led_driver);
kfree(ledctl);
goto exit;
}
exit:
return ret;
}
static void __exit accton_as7712_32x_led_exit(void)
{
platform_device_unregister(ledctl->pdev);
platform_driver_unregister(&accton_as7712_32x_led_driver);
kfree(ledctl);
}
module_init(accton_as7712_32x_led_init);
module_exit(accton_as7712_32x_led_exit);
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
MODULE_DESCRIPTION("accton_as7712_32x_led driver");
MODULE_LICENSE("GPL");