/*
 * Copyright 2019 Broadcom.
 * The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
 *
 * 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.
 *
 *
 * A pddf kernel module to manage various LEDs of a switch
 */

#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/hwmon-sysfs.h>
#include "pddf_led_defs.h"
#include "pddf_client_defs.h"
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/slab.h>

#define DEBUG 0
LED_OPS_DATA sys_led_ops_data[1]={0};
LED_OPS_DATA* psu_led_ops_data=NULL;
LED_OPS_DATA diag_led_ops_data[1]= {0};
LED_OPS_DATA fan_led_ops_data[1]= {0};
LED_OPS_DATA loc_led_ops_data[1]= {0};
LED_OPS_DATA* fantray_led_ops_data=NULL;
LED_OPS_DATA temp_data={0};
LED_OPS_DATA* dev_list[LED_TYPE_MAX] = {
	sys_led_ops_data,
	NULL,
	fan_led_ops_data,
	NULL,
	diag_led_ops_data,
	loc_led_ops_data,
};
int num_psus = 0;
int num_fantrays = 0;

extern int board_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
extern int board_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
extern ssize_t show_pddf_data(struct device *dev, struct device_attribute *da, char *buf);
extern ssize_t store_pddf_data(struct device *dev, struct device_attribute *da, const char *buf, size_t count);

static LED_STATUS find_state_index(const char* state_str) {
     int index;
     char *ptr = (char *)state_str; 
     while (*ptr && *ptr!= '\n' && *ptr !='\0') ptr++;
     *ptr='\0';
     for ( index = 0; index < MAX_LED_STATUS; index++) {
         /*int rc = strcmp(state_str, LED_STATUS_STR[index]) ;*/
         if (strcmp(state_str, LED_STATUS_STR[index]) == 0 ) {
                 return index;
          }
     }
     return MAX_LED_STATUS;
}

static LED_TYPE get_dev_type(char* name)
{
        LED_TYPE ret = LED_TYPE_MAX;
        if(strcasecmp(name, "SYS_LED")==0) {
                ret = LED_SYS;
        } else if(strcasecmp(name, "FAN_LED")==0) {
                ret = LED_FAN;
        } else if(strstr(name, "PSU_LED")) {
                ret = LED_PSU;
        } else if(strcasecmp(name, "DIAG_LED")==0) {
                ret = LED_DIAG;
        } else if(strcasecmp(name, "LOC_LED")==0) {
                ret = LED_LOC;
        } else if(strstr(name, "FANTRAY_LED")) {
                ret = LED_FANTRAY;
        }
#if DEBUG > 1
        pddf_dbg(LED, KERN_INFO "LED get_dev_type: %s; %d\n", name, ret);
#endif
        return (ret);
}
static int dev_index_check(LED_TYPE type, int index)
{
#if DEBUG
	pddf_dbg(LED, "dev_index_check: type:%s index:%d num_psus:%d num_fantrays:%d\n", 
		LED_TYPE_STR[type], index, num_psus, num_fantrays);
#endif
        switch(type)
        {
                case LED_PSU:
                        if(index >= num_psus) return (-1);
                break;
                case LED_FANTRAY:
                        if(index >= num_fantrays) return (-1);
                break;
                default:
                        if(index >= 1) return (-1);
                break;
        }
        return (0);
}

static LED_OPS_DATA* find_led_ops_data(struct device_attribute *da)
{
        struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da;
        LED_OPS_DATA* ptr=(LED_OPS_DATA*)_ptr->addr;
        LED_TYPE led_type;
        if(!ptr || strlen(ptr->device_name)==0 ) return(NULL);


        if((led_type=get_dev_type(ptr->device_name))==LED_TYPE_MAX) {
                printk(KERN_ERR "PDDF_LED ERROR *%s Unsupported Led Type\n", __func__);
                return(NULL);
        }
        if(dev_index_check(led_type, ptr->index)==-1) {
                printk(KERN_ERR "PDDF_LED ERROR %s invalid index: %d for type:%s\n", __func__, ptr->index, ptr->device_name);
                return(NULL);
        }
#if DEBUG > 1
        pddf_dbg(LED, "find_led_ops_data: name:%s; index=%d tempAddr:%p actualAddr:%p\n",
                                ptr->device_name, ptr->index, ptr, dev_list[led_type]+ptr->index);
#endif
        return (dev_list[led_type]+ptr->index);
}

static void print_led_data(LED_OPS_DATA *ptr, LED_STATUS state)
{
    int i = 0;
	if(!ptr) return ;
	pddf_dbg(LED, KERN_INFO "Print %s index:%d num_psus:%d num_fantrays:%d ADDR=%p\n", 
					ptr->device_name, ptr->index, num_psus, num_fantrays, ptr);
	pddf_dbg(LED, KERN_INFO "\tindex: %d\n", ptr->index); 
	pddf_dbg(LED, KERN_INFO  "\tcur_state: %d; %s \n", ptr->cur_state.state, ptr->cur_state.color); 
        for (i = 0; i< MAX_LED_STATUS; i++) {
	    if(ptr->data[i].swpld_addr && (i == state || state == -1)) {
		pddf_dbg(LED, KERN_INFO "\t\t[%s]: addr/offset:0x%x;0x%x color:%s; value:%x; mask_bits: 0x%x; pos:%d\n", 
                LED_STATUS_STR[i],
		ptr->data[i].swpld_addr, ptr->data[i].swpld_addr_offset,
		LED_STATUS_STR[i], ptr->data[i].value, ptr->data[i].bits.mask_bits, ptr->data[i].bits.pos); 
            }
        }
}
	
ssize_t get_status_led(struct device_attribute *da)
{
	int ret=0;
	struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da;
	LED_OPS_DATA* temp_data_ptr=(LED_OPS_DATA*)_ptr->addr;
	LED_OPS_DATA* ops_ptr=find_led_ops_data(da);
	uint32_t color_val=0, sys_val=0;
	int state=0;
	if (!ops_ptr) { 
		pddf_dbg(LED, KERN_ERR "ERROR %s: Cannot find LED Ptr", __func__);
		return (-1);
	}
	if (ops_ptr->swpld_addr == 0x0) {
		pddf_dbg(LED, KERN_ERR "ERROR %s: device: %s %d not configured\n", __func__,
			temp_data_ptr->device_name, temp_data_ptr->index);
		return (-1);
	}
    sys_val = board_i2c_cpld_read(ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset);
	if (sys_val < 0)
		return sys_val;

	strcpy(temp_data.cur_state.color, "None"); 
	for (state=0; state<MAX_LED_STATUS; state++) {
        	color_val = (sys_val & ~ops_ptr->data[state].bits.mask_bits);
		if ((color_val ^ (ops_ptr->data[state].value<<ops_ptr->data[state].bits.pos))==0) {
		      strcpy(temp_data.cur_state.color, LED_STATUS_STR[state]);
		}
	}
#if DEBUG > 1
        pddf_dbg(LED, KERN_ERR "Get : %s:%d addr/offset:0x%x; 0x%x value=0x%x [%s]\n",
		ops_ptr->device_name, ops_ptr->index, 
                ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset, sys_val, 
		temp_data.cur_state.color);
#endif

	return(ret);	
}

ssize_t set_status_led(struct device_attribute *da)
{
	int ret=0;
	uint32_t sys_val=0, new_val=0;
	LED_STATUS cur_state = MAX_LED_STATUS;
	struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da;
	LED_OPS_DATA* temp_data_ptr=(LED_OPS_DATA*)_ptr->addr;
	LED_OPS_DATA* ops_ptr=find_led_ops_data(da);
	char* _buf=temp_data_ptr->cur_state.color;

	if (!ops_ptr) { 
		pddf_dbg(LED, KERN_ERR "PDDF_LED ERROR %s: Cannot find LED Ptr", __func__);
		return (-1);
	}
	if (ops_ptr->swpld_addr == 0x0) {
		pddf_dbg(LED, KERN_ERR "PDDF_LED ERROR %s: device: %s %d not configured\n",
			__func__, ops_ptr->device_name, ops_ptr->index);
		return (-1);
	}
	pddf_dbg(LED, KERN_ERR "%s: Set [%s;%d] color[%s]\n", __func__,
		temp_data_ptr->device_name, temp_data_ptr->index,
		temp_data_ptr->cur_state.color);
        cur_state = find_state_index(_buf);

        if (cur_state == MAX_LED_STATUS) {
                pddf_dbg(LED, KERN_ERR "ERROR %s: not supported: %s\n", _buf, __func__);
                return (-1);
        }

	if(ops_ptr->data[cur_state].swpld_addr != 0x0) {
        	sys_val = board_i2c_cpld_read(ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset);
			if (sys_val < 0)
				return sys_val;

        	new_val = (sys_val & ops_ptr->data[cur_state].bits.mask_bits) |
                                (ops_ptr->data[cur_state].value << ops_ptr->data[cur_state].bits.pos);

	} else {
		pddf_dbg(LED, KERN_ERR "ERROR %s: %s %d state %d; %s not configured\n",__func__, 
			ops_ptr->device_name, ops_ptr->index, cur_state, _buf);
		return (-1);
	}

        board_i2c_cpld_write(ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset, new_val);
        pddf_dbg(LED, KERN_INFO "Set color:%s; 0x%x:0x%x sys_val:0x%x new_val:0x%x read:0x%x\n",
		LED_STATUS_STR[cur_state],
                ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset,
                sys_val, new_val,
		ret = board_i2c_cpld_read(ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset));
		if (ret < 0)
		{
			pddf_dbg(LED, KERN_ERR "PDDF_LED ERROR %s: Error %d in reading from cpld(0x%x) offset 0x%x\n", __FUNCTION__, ret, ops_ptr->swpld_addr, ops_ptr->swpld_addr_offset);
			return ret;
		}
	return(ret);
}


ssize_t show_pddf_data(struct device *dev, struct device_attribute *da,
             char *buf)
{
        int ret = 0;
        struct pddf_data_attribute *ptr = (struct pddf_data_attribute *)da;
        switch(ptr->type)
        {
                case PDDF_CHAR:
                        ret = sprintf(buf, "%s\n", ptr->addr);
                        break;
                case PDDF_INT_DEC:
                        ret = sprintf(buf, "%d\n", *(int*)(ptr->addr));
                        break;
                case PDDF_INT_HEX:
                        ret = sprintf(buf, "0x%x\n", *(int*)(ptr->addr));
                        break;
                case PDDF_USHORT:
                        ret = sprintf(buf, "0x%x\n", *(unsigned short *)(ptr->addr));
                        break;
                case PDDF_UINT32:
                        ret = sprintf(buf, "0x%x\n", *(uint32_t *)(ptr->addr));
                        break;
                default:
                        break;
        }
#if DEBUG > 1
        pddf_dbg(LED, "[ READ ] DATA ATTR PTR [%s] TYPE:%d, Value:[%s]\n", 
		ptr->dev_attr.attr.name, ptr->type, buf);
#endif
        return ret;
}

ssize_t store_pddf_data(struct device *dev, struct device_attribute *da, const char *buf, size_t count)
{
        int ret = 0, num = 0;
        struct pddf_data_attribute *ptr = (struct pddf_data_attribute *)da;
        switch(ptr->type)
        {
                case PDDF_CHAR:
                        strncpy(ptr->addr, buf, strlen(buf)-1); // to discard newline char form buf
                        ptr->addr[strlen(buf)-1] = '\0';
#if DEBUG
        		pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR [%s] PDDF_CHAR  VALUE:%s\n", 
				ptr->dev_attr.attr.name, ptr->addr);
#endif
                        break;
                case PDDF_INT_DEC:
                        ret = kstrtoint(buf,10,&num);
                        if (ret==0)
                                *(int *)(ptr->addr) = num;
#if DEBUG
        		pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR [%s] PDDF_DEC  VALUE:%d\n", 
				ptr->dev_attr.attr.name, *(int *)(ptr->addr));
#endif
                        break;
                case PDDF_INT_HEX:
                        ret = kstrtoint(buf,16,&num);
                        if (ret==0)
                                *(int *)(ptr->addr) = num;
#if DEBUG
        		pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR [%s] PDDF_HEX  VALUE:0x%x\n", 
				ptr->dev_attr.attr.name, *(int *)(ptr->addr));
#endif
                        break;
                case PDDF_USHORT:
                        ret = kstrtoint(buf,16,&num);
                        if (ret==0)
                                *(unsigned short *)(ptr->addr) = (unsigned short)num;
#if DEBUG
        		pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR [%s] PDDF_USHORT  VALUE:%x\n", 
				ptr->dev_attr.attr.name, *(unsigned short *)(ptr->addr));
#endif
                        break;
                case PDDF_UINT32:
                        ret = kstrtoint(buf,16,&num);
                        if (ret==0)
                                *(uint32_t *)(ptr->addr) = (uint32_t)num;
#if DEBUG
        		pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR [%s] PDDF_UINT32 VALUE:%d\n", 
				ptr->dev_attr.attr.name, *(uint32_t *)(ptr->addr));
#endif
                        break;
                default:
                        break;
        }
        return count;
}

static int load_led_ops_data(struct device_attribute *da, LED_STATUS state)
{
	struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da;
	LED_OPS_DATA* ptr=(LED_OPS_DATA*)_ptr->addr;
	LED_TYPE led_type;
	LED_OPS_DATA* ops_ptr=NULL;
	if(!ptr || strlen(ptr->device_name)==0 ) {
		pddf_dbg(LED, KERN_INFO "SYSTEM_LED: load_led_ops_data return -1 device_name:%s\n", ptr? ptr->device_name:"NULL");
		return(-1); 
	}
	if(ptr->device_name)
    {
        pddf_dbg(LED, KERN_INFO "[%s]: load_led_ops_data: index=%d addr=0x%x;0x%x valu=0x%x\n", 
				ptr->device_name, ptr->index, ptr->swpld_addr, ptr->swpld_addr_offset, ptr->data[0].value);
    }
	if((led_type=get_dev_type(ptr->device_name))==LED_TYPE_MAX) {
		pddf_dbg(LED, KERN_ERR "PDDF_LED ERROR *%s Unsupported Led Type\n", __func__);
		return(-1);
	}
	if(dev_index_check(led_type, ptr->index)==-1) {
		pddf_dbg(LED, KERN_ERR "PDDF_LED ERROR %s invalid index: %d for type:%d\n", __func__, ptr->index, led_type);
		return(-1);
	}
	ops_ptr = dev_list[led_type]+ptr->index;

	memcpy(ops_ptr->device_name, ptr->device_name, sizeof(ops_ptr->device_name));
	ops_ptr->index = ptr->index;
	memcpy(&ops_ptr->data[state], &ptr->data[0], sizeof(LED_DATA));
	ops_ptr->data[state].swpld_addr = ptr->swpld_addr;
	ops_ptr->data[state].swpld_addr_offset = ptr->swpld_addr_offset;
	ops_ptr->swpld_addr = ptr->swpld_addr;
	ops_ptr->swpld_addr_offset = ptr->swpld_addr_offset;

	print_led_data(dev_list[led_type]+ptr->index, state);

	memset(ptr, 0, sizeof(LED_OPS_DATA));
	return (0);
}

static int show_led_ops_data(struct device_attribute *da)
{
        LED_OPS_DATA* ops_ptr=find_led_ops_data(da);
        print_led_data(ops_ptr, -1);
	return(0);
}

static int verify_led_ops_data(struct device_attribute *da)
{
	struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da;
	LED_OPS_DATA* ptr=(LED_OPS_DATA*)_ptr->addr;
	LED_OPS_DATA* ops_ptr=find_led_ops_data(da);

	if(ops_ptr) 
		memcpy(ptr, ops_ptr, sizeof(LED_OPS_DATA));
	else
    {
		pddf_dbg(LED, "SYSTEM_LED: verify_led_ops_data: Failed to find ops_ptr name:%s; index=%d\n", ptr->device_name, ptr->index);
    }
	return (0);
}


ssize_t dev_operation(struct device *dev, struct device_attribute *da, const char *buf, size_t count)
{
#if DEBUG
	pddf_dbg(LED, KERN_INFO "dev_operation [%s]\n", buf);
#endif
	if(strstr(buf, "STATUS_LED_COLOR")!= NULL) {
                LED_STATUS index = find_state_index(buf);
                if (index < MAX_LED_STATUS ) {
		    load_led_ops_data(da, index);
                } else {
		    printk(KERN_ERR "PDDF_ERROR %s: Invalid state for dev_ops %s", __FUNCTION__, buf);
                }
	}
	else if(strncmp(buf, "show", strlen("show"))==0 ) {
		show_led_ops_data(da);
	}
	else if(strncmp(buf, "verify", strlen("verify"))==0 ) {
		verify_led_ops_data(da);
	}
	else if(strncmp(buf, "get_status", strlen("get_status"))==0 ) {
		get_status_led(da);
	}
	else if(strncmp(buf, "set_status", strlen("set_status"))==0 ) {
		set_status_led(da);
	}
	else {
		printk(KERN_ERR "PDDF_ERROR %s: Invalid value for dev_ops %s", __FUNCTION__, buf);
	}
	return(count);
}

ssize_t store_config_data(struct device *dev, struct device_attribute *da, const char *buf, size_t count)
{
	int ret, num;
	struct pddf_data_attribute *ptr = (struct pddf_data_attribute *)da;
	if(strncmp(ptr->dev_attr.attr.name, "num_psus", strlen("num_psus"))==0 ) {
	       ret = kstrtoint(buf,10,&num);
               if (ret==0)
                      *(int *)(ptr->addr) = num;
	       if(psu_led_ops_data == NULL) { 
	       		if ((psu_led_ops_data = kzalloc(num * sizeof(LED_OPS_DATA), GFP_KERNEL)) == NULL) {
				printk(KERN_ERR "PDDF_LED ERROR failed to allocate memory for PSU LED\n");
				return (count);
	       		}
			pddf_dbg(LED, "Allocate PSU LED Memory ADDR=%p\n", psu_led_ops_data);
			dev_list[LED_PSU]=psu_led_ops_data;
		}
#if DEBUG
        pddf_dbg(LED, "[ WRITE ] ATTR CONFIG [%s] VALUE:%d; %d\n",
                        ptr->dev_attr.attr.name, num, num_psus);
#endif
		return(count);
	}
        if(strncmp(ptr->dev_attr.attr.name, "num_fantrays", strlen("num_fantrays"))==0 ) {
               ret = kstrtoint(buf,10,&num);
               if (ret==0)
                      *(int *)(ptr->addr) = num;
	       if (fantray_led_ops_data == NULL) {
               		if ((fantray_led_ops_data = kzalloc(num * sizeof(LED_OPS_DATA), GFP_KERNEL)) == NULL) {
                        	printk(KERN_ERR "PDDF_LED ERROR failed to allocate memory for FANTRAY LED\n");
                        	return (count);
			}
			pddf_dbg(LED, "Allocate FanTray LED Memory ADDR=%p\n", fantray_led_ops_data);
			dev_list[LED_FANTRAY]=fantray_led_ops_data;
               }
#if DEBUG
        pddf_dbg(LED, "[ WRITE ] ATTR CONFIG [%s] VALUE:%d; %d\n",
                        ptr->dev_attr.attr.name, num, num_fantrays);
#endif
                return(count);
        }
        return (count);
}

ssize_t store_bits_data(struct device *dev, struct device_attribute *da, const char *buf, size_t count)
{
	int len = 0, num1 = 0, num2 = 0, i=0, rc1=0, rc2=0;
	char mask=0xFF;
	char *pptr=NULL;
	char bits[NAME_SIZE];
	struct pddf_data_attribute *ptr = (struct pddf_data_attribute *)da;
	MASK_BITS* bits_ptr=(MASK_BITS*)(ptr->addr); 
	strncpy(bits_ptr->bits, buf, strlen(buf)-1); // to discard newline char form buf
	bits_ptr->bits[strlen(buf)-1] = '\0';
	if((pptr=strstr(buf,":")) != NULL) {
        len=pptr-buf;
        sprintf(bits, buf);
        bits[len]='\0';
        rc1=kstrtoint(bits,16,&num1);
        if (rc1==0)
        {
            sprintf(bits, ++pptr);
            rc2=kstrtoint(bits,16,&num2);
            if (rc2==0)
            {
                for (i=num2; i<=num1; i++) {
                   mask &=  ~(1 << i);
                }
                bits_ptr->mask_bits = mask;
                bits_ptr->pos = num2;
            }
        }
	} else {
		rc1=kstrtoint(buf,16,&num1);
        if (rc1==0)
        {
            bits_ptr->mask_bits = mask & ~(1 << num1);
            bits_ptr->pos = num1;
        }
	}
#if DEBUG
        pddf_dbg(LED, KERN_ERR "[ WRITE ] ATTR PTR Bits [%s] VALUE:%s mask:0x%x; pos:0x%x\n", 
			ptr->dev_attr.attr.name, bits_ptr->bits, bits_ptr->mask_bits, bits_ptr->pos);
#endif
	return (count);
}

/**************************************************************************
 * platform/ attributes 
 **************************************************************************/
PDDF_LED_DATA_ATTR(platform, num_psus, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_config_data, PDDF_INT_DEC, sizeof(int), (void*)&num_psus); 
PDDF_LED_DATA_ATTR(platform, num_fantrays, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_config_data, PDDF_INT_DEC, sizeof(int), (void*)&num_fantrays); 

struct attribute* attrs_platform[]={
                &pddf_dev_platform_attr_num_psus.dev_attr.attr,
                &pddf_dev_platform_attr_num_fantrays.dev_attr.attr,
                NULL,
};
struct attribute_group attr_group_platform={
                .attrs = attrs_platform,
};

/**************************************************************************
 * led/ attributes 
 **************************************************************************/
PDDF_LED_DATA_ATTR(dev, device_name, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_pddf_data, PDDF_CHAR, NAME_SIZE, (void*)&temp_data.device_name); 
PDDF_LED_DATA_ATTR(dev, index, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_pddf_data, PDDF_INT_DEC, sizeof(int), (void*)&temp_data.index); 
PDDF_LED_DATA_ATTR(dev, swpld_addr, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_pddf_data, PDDF_INT_HEX, sizeof(int), (void*)&temp_data.swpld_addr); 
PDDF_LED_DATA_ATTR(dev, swpld_addr_offset, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_pddf_data, PDDF_INT_HEX, sizeof(int), (void*)&temp_data.swpld_addr_offset); 
PDDF_LED_DATA_ATTR(dev, dev_ops , S_IWUSR, NULL,  
                dev_operation, PDDF_CHAR, NAME_SIZE, (void*)&temp_data);  

struct attribute* attrs_dev[]={ 
                &pddf_dev_dev_attr_device_name.dev_attr.attr, 
                &pddf_dev_dev_attr_index.dev_attr.attr, 
                &pddf_dev_dev_attr_swpld_addr.dev_attr.attr, 
                &pddf_dev_dev_attr_swpld_addr_offset.dev_attr.attr, 
                &pddf_dev_dev_attr_dev_ops.dev_attr.attr, 
                NULL,
}; 
struct attribute_group attr_group_dev={ 
                .attrs = attrs_dev, 
}; 

/**************************************************************************
 * state_attr/ attributes 
 **************************************************************************/
#define LED_DEV_STATE_ATTR_GROUP(name, func) \
	PDDF_LED_DATA_ATTR(name, bits, S_IWUSR|S_IRUGO, show_pddf_data, \
		store_bits_data, PDDF_CHAR, NAME_SIZE, func.bits.bits); \
	PDDF_LED_DATA_ATTR(name, value, S_IWUSR|S_IRUGO, show_pddf_data, \
                store_pddf_data, PDDF_USHORT, sizeof(unsigned short), func.value); \
	struct attribute* attrs_##name[]={ \
		&pddf_dev_##name##_attr_bits.dev_attr.attr, \
        	&pddf_dev_##name##_attr_value.dev_attr.attr, \
        	NULL, \
	}; \
	struct attribute_group attr_group_##name={ \
        	.attrs = attrs_##name, \
	}; \


LED_DEV_STATE_ATTR_GROUP(state_attr, (void*)&temp_data.data[0])

/**************************************************************************
 * cur_state/ attributes 
 **************************************************************************/
PDDF_LED_DATA_ATTR(cur_state, color, S_IWUSR|S_IRUGO, show_pddf_data, 
                store_pddf_data, PDDF_CHAR, NAME_SIZE, (void*)&temp_data.cur_state.color); 

struct attribute* attrs_cur_state[]={
                &pddf_dev_cur_state_attr_color.dev_attr.attr,
                NULL,
};
struct attribute_group attr_group_cur_state={
                .attrs = attrs_cur_state,
};

/*************************************************************************/
#define KOBJ_FREE(obj) \
	if(obj) kobject_put(obj); \

void free_kobjs(void)
{
        KOBJ_FREE(cur_state_kobj)
        KOBJ_FREE(state_attr_kobj)
        KOBJ_FREE(led_kobj)
        KOBJ_FREE(platform_kobj)
}

int KBOJ_CREATE(char* name, struct kobject* parent, struct kobject** child)
{
	if (parent) {
        	*child = kobject_create_and_add(name, parent); 
	} else {
		printk(KERN_ERR "PDDF_LED ERROR to create %s kobj; null parent\n", name);
                free_kobjs(); 
                return (-ENOMEM); 
	}
	return (0);
}

int LED_DEV_ATTR_CREATE(struct kobject *kobj, const struct attribute_group *attr, const char* name) 
{
	int status = sysfs_create_group(kobj, attr);  
    if(status) { 
        pddf_dbg(LED, KERN_ERR "Driver ERROR: sysfs_create %s failed rc=%d\n", name, status); 
	}
    return (status);
}


static int __init led_init(void) {
	struct kobject *device_kobj;
	pddf_dbg(LED, KERN_INFO "PDDF GENERIC LED MODULE init..\n");

        device_kobj = get_device_i2c_kobj();
        if(!device_kobj) 
                return -ENOMEM;

	KBOJ_CREATE("platform", device_kobj, &platform_kobj);
	KBOJ_CREATE("led", device_kobj, &led_kobj);
	KBOJ_CREATE("state_attr", led_kobj, &state_attr_kobj);
	KBOJ_CREATE("cur_state", led_kobj, &cur_state_kobj);

        LED_DEV_ATTR_CREATE(platform_kobj, &attr_group_platform, "attr_group_platform");
        LED_DEV_ATTR_CREATE(led_kobj, &attr_group_dev, "attr_group_dev");
        LED_DEV_ATTR_CREATE(state_attr_kobj, &attr_group_state_attr, "attr_group_state_attr");
        LED_DEV_ATTR_CREATE(cur_state_kobj, &attr_group_cur_state, "attr_group_cur_state");
	return (0);
}


static void __exit led_exit(void) {
	pddf_dbg(LED, "PDDF GENERIC LED MODULE exit..\n");
	free_kobjs();
	if(psu_led_ops_data) kfree(psu_led_ops_data);
	if(fantray_led_ops_data) kfree(fantray_led_ops_data);
}

module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("led driver");
MODULE_LICENSE("GPL");