/*
 * CPLD sysfs driver for cel_redstone.
 *
 * Copyright (C) 2013 Cumulus Networks, Inc.
 * Author: Vidya Sagar Ravipati <vidya@cumulusnetworks.com>
 *
 * 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.
 */

#include <stddef.h>

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <asm/io.h>

#include "cel_redstone_cpld.h"
#include "platform_defs.h"

static const char driver_name[] = "cel_redstone_cpld";
#define DRIVER_VERSION "1.0"

#define CPLDNAMSZ 20

/*------------------------------------------------------------------------------
 *
 * Information that need to kept for each device
 *
 */

struct cpld_data {
	char name[CPLDNAMSZ];
	struct mutex cel_redstone_cpld_mutex;
	uint8_t __iomem* cel_redstone_cpld_regs;
};

/********************************************************************************
 *
 * CPLD I/O
 *
 */
static inline uint8_t cpld_rd(struct cpld_data *priv, uint32_t reg)
{
	return readb(priv->cel_redstone_cpld_regs + reg);
}

static inline void cpld_wr(struct cpld_data *priv, uint32_t reg, uint8_t data)
{
	writeb(data, priv->cel_redstone_cpld_regs + reg);
}

#define CEL_REDSTONE_CPLD_STRING_NAME_SIZE 30

/*
 * board version
 */
static ssize_t board_revision_show(struct device * dev,
				   struct device_attribute * dattr,
				   char * buf)
{
	uint8_t hw_info;
	uint8_t plat_type;
	uint8_t ver;
	uint8_t board_type;
	char* str;
	char* board_str;
	struct cpld_data *data = dev_get_drvdata(dev);

	hw_info = cpld_rd(data, CPLD1_REG_PLAT_TYPE_OFFSET);

	plat_type = hw_info & CPLD1_PLAT_HW_MASK;

	switch (plat_type) {
	case CPLD1_PLAT_CELESTICA:
		str = "Celestica";
		break;
	case CPLD1_PLAT_DCN:
		str = "DCN";
		break;
	case CPLD1_PLAT_TENCENT:
		str = "Tencent";
		break;
	case CPLD1_PLAT_JUNIPER:
		str = "Juniper";
		break;
	default:
		str = "undefined";
	};

	ver = hw_info & CPLD1_PLAT_BOARD_VER_MASK;

	board_type = hw_info & CPLD1_PLAT_BOARD_ID_MASK;

	switch (board_type) {
	case CPLD1_PLAT_BOARD_84757:
		board_str = "FC/FCOE PHY";
		break;
	case CPLD1_PLAT_BOARD_84754:
		board_str = "10GbE PHY";
		break;
	case CPLD1_PLAT_BOARD_NONE:
		board_str = "None";
		break;
	default:
		board_str = "undefined PHY";
	};
	

	return sprintf(buf, "%s, %s, %d\n", str, board_str, ver);
}
static SYSFS_ATTR_RO(board_revision, board_revision_show);

/*
 * cpld version
 */
static ssize_t cpld_version_show(struct device * dev,
				 struct device_attribute * dattr,
				 char * buf)
{
	uint8_t tmp;
	uint8_t type;
	uint8_t rev;
	struct cpld_data *data = dev_get_drvdata(dev);

	tmp = cpld_rd(data, CPLD1_REG_VERSION_OFFSET);
	type = (tmp & CPLD1_VERSION_H_MASK) >> CPLD1_VERSION_H_SHIFT;
	rev  = (tmp & CPLD1_VERSION_L_MASK)  >> CPLD1_VERSION_L_SHIFT;

	return sprintf(buf, "%d.%d\n", type, rev);
}
static SYSFS_ATTR_RO(cpld_version, cpld_version_show);

/*
 * cpld-2 version
 */
static ssize_t cpld_2_version_show(struct device * dev,
				   struct device_attribute * dattr,
 				   char * buf)
{
	uint8_t tmp;
	uint8_t type;
	uint8_t rev;
	struct cpld_data *data = dev_get_drvdata(dev);

	tmp = cpld_rd(data, CPLD2_REG_VERSION_OFFSET);
	type = (tmp & CPLD2_VERSION_H_MASK) >> CPLD2_VERSION_H_SHIFT;
	rev = (tmp & CPLD2_VERSION_L_MASK)  >> CPLD2_VERSION_L_SHIFT;

	return sprintf(buf, "%d.%d\n", type, rev);
}
static SYSFS_ATTR_RO(cpld2_version, cpld_2_version_show);

/*
 * cpld version-3
 */
static ssize_t cpld3_version_show(struct device * dev,
				  struct device_attribute * dattr,
				  char * buf)
{
	uint8_t tmp;
	uint8_t type;
	uint8_t rev;
	struct cpld_data *data = dev_get_drvdata(dev);

	tmp = cpld_rd(data, CPLD3_REG_VERSION_OFFSET);
	type = (tmp & CPLD3_VERSION_H_MASK) >> CPLD3_VERSION_H_SHIFT;
	rev  = (tmp & CPLD3_VERSION_L_MASK)  >> CPLD3_VERSION_L_SHIFT;

	return sprintf(buf, "%d.%d\n", type, rev);
}
static SYSFS_ATTR_RO(cpld3_version, cpld3_version_show);

/*
 * cpld version-4
 */
static ssize_t cpld4_version_show(struct device * dev,
				  struct device_attribute * dattr,
				  char * buf)
{
	uint8_t tmp;
	uint8_t type;
	uint8_t rev;
	struct cpld_data *data = dev_get_drvdata(dev);

	tmp = cpld_rd(data, CPLD4_REG_VERSION_OFFSET);
	type = (tmp & CPLD4_VERSION_H_MASK) >> CPLD4_VERSION_H_SHIFT;
	rev  = (tmp & CPLD4_VERSION_L_MASK)  >> CPLD4_VERSION_L_SHIFT;

	return sprintf(buf, "%d.%d\n", type, rev);
}
static SYSFS_ATTR_RO(cpld4_version, cpld4_version_show);

/*------------------------------------------------------------------------------
 *
 * PSU status definitions
 *
 * All the definition names use "positive" logic and return "1" for OK
 * and "0" for not OK.
 */
struct cpld_status {
	char name[CEL_REDSTONE_CPLD_STRING_NAME_SIZE];
	uint8_t good_mask;  // set bits for "good behaviour"
	uint8_t bad_mask;   // set bits for "bad behaviour"
	char msg_good[CEL_REDSTONE_CPLD_STRING_NAME_SIZE]; // positive string message
	char msg_bad[CEL_REDSTONE_CPLD_STRING_NAME_SIZE]; // negative string message
};

static struct cpld_status cpld_psu_status[] = {
	{
		.name = "psu_pwr1_all_ok",
		.good_mask = CPLD1_PSU_1_OK,
		.bad_mask = CPLD1_PSU_1_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "psu_pwr1_present",
		.good_mask = 0,
		.bad_mask = CPLD1_PSU_1_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "psu_pwr1_ac_ok",
		.good_mask = CPLD1_PSU_1_OK,
		.bad_mask = 0,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "psu_pwr2_all_ok",
		.good_mask = CPLD1_PSU_2_OK,
		.bad_mask = CPLD1_PSU_2_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "psu_pwr2_present",
		.good_mask = 0,
		.bad_mask = CPLD1_PSU_2_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "psu_pwr2_ac_ok",
		.good_mask = CPLD1_PSU_2_OK,
		.bad_mask = 0,
		.msg_good = "1",
		.msg_bad = "0",
	},
};
static int n_psu_states = ARRAY_SIZE(cpld_psu_status);

/*
 * power supply status
 */

static ssize_t psu_power_show(struct device * dev,
			      struct device_attribute * dattr,
			      char * buf)
{
	uint8_t tmp, i;
	uint8_t bad = 0;
	struct cpld_status* target = NULL;
	struct cpld_data *data = dev_get_drvdata(dev);


	tmp = cpld_rd(data, CPLD1_REG_PSU_STATUS);

	for (i = 0; i < n_psu_states; i++) {
		if (strcmp(dattr->attr.name, cpld_psu_status[i].name) == 0) {
			target = &cpld_psu_status[i];
			break;
		}
	}
	if (target == NULL) {
		return sprintf(buf, "undefined\n");
	}

	/*
	** All of the "good" bits must be set.
	** None of the "bad" bits can be set.
	*/
	if ((tmp & target->good_mask) == target->good_mask) {
		if (tmp & target->bad_mask) {
			bad++;
		}
	} else {
		bad++;
	}

	return sprintf(buf, "%s\n", bad ? target->msg_bad : target->msg_good);

}

static SYSFS_ATTR_RO(psu_pwr1_all_ok, psu_power_show);
static SYSFS_ATTR_RO(psu_pwr1_present, psu_power_show);
static SYSFS_ATTR_RO(psu_pwr1_ac_ok, psu_power_show);
static SYSFS_ATTR_RO(psu_pwr2_all_ok, psu_power_show);
static SYSFS_ATTR_RO(psu_pwr2_present, psu_power_show);
static SYSFS_ATTR_RO(psu_pwr2_ac_ok, psu_power_show);

/*
** System status register
*/
static struct cpld_status cpld_fan_status[] = {
	{
		.name = "fan1_present",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_1_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan1_all_ok",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_1_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan2_present",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_2_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan2_all_ok",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_2_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan3_present",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_3_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan3_all_ok",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_3_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan4_present",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_4_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan4_all_ok",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_4_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan5_present",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_5_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
	{
		.name = "fan5_all_ok",
		.good_mask = 0,
		.bad_mask = CPLD1_FAN_5_PRESENT_L,
		.msg_good = "1",
		.msg_bad = "0",
	},
};
static int n_fan_states = ARRAY_SIZE(cpld_fan_status);

/*
 * fan status
 */
static ssize_t fan_show(struct device * dev,
			struct device_attribute * dattr,
			char * buf)
{
	uint8_t tmp;
	uint8_t i;
	uint8_t bad = 0;
	struct cpld_status* target = NULL;
	struct cpld_data *data = dev_get_drvdata(dev);

	tmp = cpld_rd(data, CPLD1_REG_FAN_PRESENT_STATUS_OFFSET);

	for (i = 0; i < n_fan_states; i++) {
		if (strcmp(dattr->attr.name, cpld_fan_status[i].name) == 0) {
			target = &cpld_fan_status[i];
			break;
		}
	}
	if (target == NULL) {
		return sprintf(buf, "undefined\n");
	}

	/*
	** All of the "good" bits must be set.
	** None of the "bad" bits can be set.
	*/
	if ((tmp & target->good_mask) == target->good_mask) {
		if (tmp & target->bad_mask) {
			bad++;
		}
	} else {
		bad++;
	}

	return sprintf(buf, "%s\n", bad ? target->msg_bad : target->msg_good);
}

static SYSFS_ATTR_RO(fan1_present, fan_show);
static SYSFS_ATTR_RO(fan1_all_ok, fan_show);
static SYSFS_ATTR_RO(fan2_present, fan_show);
static SYSFS_ATTR_RO(fan2_all_ok, fan_show);
static SYSFS_ATTR_RO(fan3_present, fan_show);
static SYSFS_ATTR_RO(fan3_all_ok, fan_show);
static SYSFS_ATTR_RO(fan4_present, fan_show);
static SYSFS_ATTR_RO(fan4_all_ok, fan_show);
static SYSFS_ATTR_RO(fan5_present, fan_show);
static SYSFS_ATTR_RO(fan5_all_ok, fan_show);

/*------------------------------------------------------------------------------
 *
 * LED definitions
 *
 */

struct led {
	char name[CEL_REDSTONE_CPLD_STRING_NAME_SIZE];
	uint8_t reg;
	uint8_t mask;
	int n_colors;
	struct led_color colors[8];
};

static struct led cpld_leds[] = {
	{
		.name = "led_status",
		.reg  = CPLD1_REG_SYS_LED_CTRL_OFFSET,
		.mask = CPLD1_SYS_LED_MASK,
		.n_colors = 4,
		.colors = {
			{ PLATFORM_LED_GREEN, CPLD1_SYS_LED_GREEN},
			{ PLATFORM_LED_GREEN_SLOW_BLINKING, CPLD1_SYS_LED_GREEN_SLOW_BLINK},
			{ PLATFORM_LED_GREEN_BLINKING, CPLD1_SYS_LED_GREEN_FAST_BLINK},
			{ PLATFORM_LED_OFF, CPLD1_SYS_LED_OFF},
		},
	},
};
static int n_leds = ARRAY_SIZE(cpld_leds);

/*
 * LEDs
 */
static ssize_t led_show(struct device * dev,
			struct device_attribute * dattr,
			char * buf)
{
	uint8_t tmp;
	int i;
	struct led * target = NULL;
	struct cpld_data *data = dev_get_drvdata(dev);

	/* find the target led */
	for (i = 0; i < n_leds; i++) {
		if (strcmp(dattr->attr.name, cpld_leds[i].name) == 0) {
			target = &cpld_leds[i];
			break;
		}
	}
	if (target == NULL) {
		return sprintf(buf, "undefined target\n");
	}

	/* read the register */
	tmp = cpld_rd(data, target->reg);

	/* find the color */
	tmp &= target->mask;
	for (i = 0; i < target->n_colors; i++) {
		if (tmp == target->colors[i].value) {
			break;
		}
	}
	if (i == target->n_colors) {
		return sprintf(buf, "undefined color\n");
	} else {
		return sprintf(buf, "%s\n", target->colors[i].name);
	}
}

static ssize_t led_store(struct device * dev,
			 struct device_attribute * dattr,
			 const char * buf, size_t count)
{
	uint8_t tmp;
	int i;
	struct led * target = NULL;
	char raw[PLATFORM_LED_COLOR_NAME_SIZE];
	struct cpld_data *data = dev_get_drvdata(dev);

	/* find the target led */
	for (i = 0; i < n_leds; i++) {
		if (strcmp(dattr->attr.name, cpld_leds[i].name) == 0) {
			target = &cpld_leds[i];
			break;
		}
	}
	if (target == NULL) {
		return -EINVAL;
	}

	/* find the color */
	if (sscanf(buf, "%19s", raw) <= 0) {
		return -EINVAL;
	}
	for (i = 0; i < target->n_colors; i++) {
		if (strcmp(raw, target->colors[i].name) == 0) {
			break;
		}
	}
	if (i == target->n_colors) {
		return -EINVAL;
	}

	/* set the new value */
	mutex_lock(&data->cel_redstone_cpld_mutex);

	tmp = cpld_rd(data, target->reg);
	tmp &= ~target->mask;
	tmp |= target->colors[i].value;
	cpld_wr(data, target->reg, tmp);

	mutex_unlock(&data->cel_redstone_cpld_mutex);

	return count;
}
static SYSFS_ATTR_RW(led_status, led_show, led_store);

static const uint8_t qsfp_control_offsets[] = { CPLD_REG_QSFP_1_OFFSET,
						CPLD_REG_QSFP_2_OFFSET,
						CPLD_REG_QSFP_3_OFFSET,
						CPLD_REG_QSFP_4_OFFSET };

static ssize_t qsfp_control_show(struct device *dev,
				 struct device_attribute *dattr,
				 char *buf)
{
	struct cpld_data *cpld = dev_get_drvdata(dev);
	int offset_idx;
	uint32_t reg;
	uint8_t data;

	/* qsfpX_control */
	offset_idx = dattr->attr.name[4] - '1';
	if (offset_idx >= ARRAY_SIZE(qsfp_control_offsets))
		return -EINVAL;

	reg = qsfp_control_offsets[offset_idx];
	data = cpld_rd(cpld, reg);

	return sprintf(buf, "%u\n", data);
}

static ssize_t qsfp_control_store(struct device *dev,
				  struct device_attribute *dattr,
				  const char *buf, size_t count)
{
	struct cpld_data *cpld = dev_get_drvdata(dev);
	int offset_idx;
	uint32_t reg;
	unsigned long data;

	if (strict_strtoul(buf, 0, &data) < 0)
		return -EINVAL;

	/* qsfpX_control */
	offset_idx = dattr->attr.name[4] - '1';
	if (offset_idx >= ARRAY_SIZE(qsfp_control_offsets))
		return -EINVAL;

	reg = qsfp_control_offsets[offset_idx];
	cpld_wr(cpld, reg, data & 0xff);

	return count;
}

static SYSFS_ATTR_RW(qsfp1_control, qsfp_control_show, qsfp_control_store);
static SYSFS_ATTR_RW(qsfp2_control, qsfp_control_show, qsfp_control_store);
static SYSFS_ATTR_RW(qsfp3_control, qsfp_control_show, qsfp_control_store);
static SYSFS_ATTR_RW(qsfp4_control, qsfp_control_show, qsfp_control_store);

static const uint8_t qsfp_reset_offsets[] = { CPLD2_QSFPP_RESET_L1,
					      CPLD2_QSFPP_RESET_L2,
					      CPLD2_QSFPP_RESET_L3,
					      CPLD2_QSFPP_RESET_L4 };

static const uint8_t qsfp_lpmod_offsets[] = { CPLD2_QSFPP_LPMOD_L1,
					      CPLD2_QSFPP_LPMOD_L2,
					      CPLD2_QSFPP_LPMOD_L3,
					      CPLD2_QSFPP_LPMOD_L4 };

static const uint8_t qsfp_present_offsets[] = { CPLD2_QSFPP_PRESENT_L1,
						CPLD2_QSFPP_PRESENT_L2,
						CPLD2_QSFPP_PRESENT_L3,
						CPLD2_QSFPP_PRESENT_L4 };

static ssize_t qsfp_show(struct device *dev,
			 struct device_attribute *dattr,
			 char *buf)
{
	struct cpld_data *cpld = dev_get_drvdata(dev);
	const char *signal;
	const uint8_t *offsets;
	uint32_t data;
	int offset_idx;

	/* qsfp_(reset|lpmod|present) */
	signal = &dattr->attr.name[5];
	if (strcmp(signal, "reset") == 0) {
		offsets = qsfp_reset_offsets;
	} else if (strcmp(signal, "lpmod") == 0) {
		offsets = qsfp_lpmod_offsets;
	} else if (strcmp(signal, "present") == 0) {
		offsets = qsfp_present_offsets;
	} else {
		return -EINVAL;
	}

	data = 0;
	for (offset_idx = 0; offset_idx < 4; offset_idx++) {
		data |= cpld_rd(cpld, offsets[offset_idx]) << (offset_idx * 8);
	}

	return sprintf(buf, "%u\n", data);
}

static ssize_t qsfp_store(struct device *dev,
			  struct device_attribute *dattr,
			  const char *buf, size_t count)
{
	struct cpld_data *cpld = dev_get_drvdata(dev);
	const char *signal;
	const uint8_t *offsets;
	unsigned long int data;
	int offset_idx;

	if (strict_strtoul(buf, 0, &data) < 0)
		return -EINVAL;

	/* qsfp_(reset|lpmod|present) */
	signal = &dattr->attr.name[5];
	if (strcmp(signal, "reset") == 0) {
		offsets = qsfp_reset_offsets;
	} else if (strcmp(signal, "lpmod") == 0) {
		offsets = qsfp_lpmod_offsets;
	} else if (strcmp(signal, "present") == 0) {
		offsets = qsfp_present_offsets;
	} else {
		return -EINVAL;
	}

	for (offset_idx = 0; offset_idx < 4; offset_idx++) {
		cpld_wr(cpld, offsets[offset_idx], (data >> (offset_idx * 8)) & 0xff);
	}

	return count;
}

static SYSFS_ATTR_RW(qsfp_reset, qsfp_show, qsfp_store);
static SYSFS_ATTR_RW(qsfp_lpmod, qsfp_show, qsfp_store);
static SYSFS_ATTR_RW(qsfp_present, qsfp_show, qsfp_store);

/*
 * Help / README
 *
 * The string length must be less than 4K.
 *
 */
#define HELP_STR "Description of the CPLD driver files:\n\
\n\
board_revision\n\
\n\
  Read-Only:\n\
\n\
  Platform Type information with customer ID,.\n\
  board type and version.\n\
  Board version register,in the following format:\n\
\n\
  Example: Celestica, None, 0\n\
\n\
\n\
cpld_version\n\
\n\
  Read-Only:\n\
\n\
  CPLD version register for CPLD 1\n\
  CPLD version register,in the following format:\n\
\n\
  cpld_version\n\
\n\
  Example: 1.3\n\
\n\
\n\
cpld2_version\n\
\n\
  Read-Only:\n\
\n\
  CPLD version register for CPLD 2\n\
  CPLD version register,in the following format:\n\
\n\
  cpld_version\n\
\n\
  Example: 2.3\n\
\n\
\n\
cpld3_version\n\
\n\
  Read-Only:\n\
\n\
  CPLD version register for CPLD 3\n\
  CPLD version register,in the following format:\n\
\n\
  cpld_version\n\
\n\
  Example: 3.3\n\
\n\
\n\
cpld4_version\n\
\n\
  Read-Only:\n\
\n\
  CPLD version register for CPLD 4\n\
  CPLD version register,in the following format:\n\
\n\
  cpld_version\n\
\n\
  Example: 4.3\n\
\n\
\n\
led_status\n\
\n\
  Read-Write:\n\
\n\
  System State LED color.\n\
  The following values are possible: green, green blinking,\n\
  green slow blinking and off.\n\
\n\
\n\
psu_pwr1_all_ok\n\
psu_pwr1_ac_ok\n\
psu_pwr1_present\n\
psu_pwr2_all_ok\n\
psu_pwr2_ac_ok\n\
psu_pwr2_present\n\
\n\
  Read-Only:\n\
\n\
  Hot swap power supply status info.\n\
  The following values are possible: 1, 0\n\
\n\
  1 means OK.  0 means not OK.\n\
\n\
  The psu_pwr[12]_all_ok values are calculated from the other\n\
  psu_pwr[12]_* values as follows:\n\
\n\
    psu_pwr1_all_ok =\n\
      psu_pwr1_present && psu_pwr1_ac_ok\n\
\n\
\n\
fan1_all_ok\n\
fan1_present\n\
fan2_all_ok\n\
fan2_present\n\
fan3_all_ok\n\
fan3_present\n\
fan4_all_ok\n\
fan4_present\n\
fan5_all_ok\n\
fan5_present\n\
\n\
  Read-Only:\n\
\n\
  System fan status.\n\
  The following values are possible: 1, 0\n\
\n\
  1 means OK.  0 means not OK.\n\
\n\
  The fan[12345]_all_ok value is calculated from the fan_present\n\
  values as follows:\n\
\n\
    fan0_all_ok = fan0_present;\n\
\n\
\n\
help\n\
README\n\
\n\
  Read-Only:\n\
\n\
  The text you are reading now.\n\
\n\
"

static ssize_t help_show(struct device * dev,
			 struct device_attribute * dattr,
			 char * buf)
{
	return sprintf(buf, HELP_STR);
}

static SYSFS_ATTR_RO(help, help_show);
static SYSFS_ATTR_RO(README, help_show);

/*------------------------------------------------------------------------------
 *
 * sysfs registration
 *
 */
static struct attribute *cel_redstone_cpld_attrs[] = {
	&dev_attr_board_revision.attr,
	&dev_attr_cpld_version.attr,
	&dev_attr_psu_pwr1_all_ok.attr,
	&dev_attr_psu_pwr1_present.attr,
	&dev_attr_psu_pwr1_ac_ok.attr,
	&dev_attr_psu_pwr2_all_ok.attr,
	&dev_attr_psu_pwr2_present.attr,
	&dev_attr_psu_pwr2_ac_ok.attr,
	&dev_attr_fan1_present.attr,
	&dev_attr_fan1_all_ok.attr,
	&dev_attr_fan2_present.attr,
	&dev_attr_fan2_all_ok.attr,
	&dev_attr_fan3_present.attr,
	&dev_attr_fan3_all_ok.attr,
	&dev_attr_fan4_present.attr,
	&dev_attr_fan4_all_ok.attr,
	&dev_attr_fan5_present.attr,
	&dev_attr_fan5_all_ok.attr,
	&dev_attr_led_status.attr,
	&dev_attr_help.attr,
	&dev_attr_README.attr,
	NULL,
};
static struct attribute *cel_redstone_cpld_2_attrs[] = {
	&dev_attr_cpld2_version.attr,
	&dev_attr_cpld3_version.attr,
	&dev_attr_cpld4_version.attr,
	&dev_attr_qsfp1_control.attr,
	&dev_attr_qsfp2_control.attr,
	&dev_attr_qsfp3_control.attr,
	&dev_attr_qsfp4_control.attr,
	&dev_attr_help.attr,
	&dev_attr_README.attr,
	NULL
};
static struct attribute *cel_smallstone_cpld_2_attrs[] = {
	&dev_attr_cpld2_version.attr,
	&dev_attr_cpld3_version.attr,
	&dev_attr_cpld4_version.attr,
	&dev_attr_qsfp_reset.attr,
	&dev_attr_qsfp_lpmod.attr,
	&dev_attr_qsfp_present.attr,
	&dev_attr_help.attr,
	&dev_attr_README.attr,
	NULL
};
static struct attribute_group cel_redstone_cpld_attr_group = {
	.attrs = cel_redstone_cpld_attrs,
};
static struct attribute_group cel_redstone_cpld_2_attr_group = {
	.attrs = cel_redstone_cpld_2_attrs,
};
static struct attribute_group cel_smallstone_cpld_2_attr_group = {
	.attrs = cel_smallstone_cpld_2_attrs,
};

/*------------------------------------------------------------------------------
 *
 * driver interface
 *
 */

static int cel_redstone_cpld_setup(void)
{
	// Put some interesting, one-time initializations here.
	return 0;
}

static int cel_redstone_cpld_probe(struct platform_device * ofdev)
{
	int retval = 0;
	struct device_node * np = ofdev->dev.of_node;
	struct kobject * kobj = &ofdev->dev.kobj;
	const char *name, *compatible;
	int len;
	struct cpld_data *data;
	bool smallstone = false;

	if (dev_get_drvdata(&ofdev->dev)) {
		dev_info(&ofdev->dev, "already probed\n");
		return 0;
	}


	if (!(data = kzalloc(sizeof(struct cpld_data), GFP_KERNEL))) {
		return -ENOMEM;
	}

	mutex_init(&data->cel_redstone_cpld_mutex);
	platform_set_drvdata(ofdev, data);

	data->cel_redstone_cpld_regs = of_iomap(np,0);
	if (!data->cel_redstone_cpld_regs) {
		retval = -EIO;
		goto out;
	}

	compatible = of_get_property(np, "compatible", &len);
	if (strncmp(compatible, "smallstone", strlen("smallstone")) == 0) {
		smallstone = true;
	}

	name = of_get_property(np, "label", &len);
	strncpy(data->name, name, CPLDNAMSZ);
	if (strcmp(name, "cpld_1") == 0) {
		retval = sysfs_create_group(kobj, &cel_redstone_cpld_attr_group);
		if (retval) {
			goto out;
		}
	}
	else if ((strcmp(name, "cpld_234") == 0) ||
		 (strcmp(name, "cpld_23") == 0)) {
		if (smallstone) {
			retval = sysfs_create_group(kobj, &cel_smallstone_cpld_2_attr_group);
		} else {
			retval = sysfs_create_group(kobj, &cel_redstone_cpld_2_attr_group);
		}
		if (retval) {
			goto out;
		}
	} else {
		printk(KERN_ERR "%s unsupported label (%s)\n",
			driver_name, name);
		retval = -EIO;
		goto out;
	}

	if (cel_redstone_cpld_setup()) {
		retval =  -EIO;
		goto out;
	}

	dev_info(&ofdev->dev, "probed & iomapped @ 0x%p\n", cel_redstone_cpld_setup);

	return 0;

out:
	kfree (data);
	return retval;
}

static int cel_redstone_cpld_remove(struct platform_device * ofdev)
{
	struct kobject * kobj = &ofdev->dev.kobj;
	struct cpld_data *data = platform_get_drvdata(ofdev);

	/* don't iounmap(regs)... the platform driver uses it for reset	*/
	sysfs_remove_group(kobj, &cel_redstone_cpld_attr_group);
	sysfs_remove_group(kobj, &cel_redstone_cpld_2_attr_group);

	kfree(data);
	platform_set_drvdata(ofdev, NULL);
	dev_info(&ofdev->dev, "removed\n");
	return 0;
}

static struct of_device_id cel_redstone_cpld_ids[] = {
	{
		.compatible = "redstone_cpld1",
	},
	{
		.compatible = "redstone_cpld234",
	},
	{
		.compatible = "smallstone_cpld1",
	},
	{
		.compatible = "smallstone_cpld23",
	},
	{ /* end of list */ },
};

static struct platform_driver cel_redstone_cpld_driver = {
	.probe = cel_redstone_cpld_probe,
	.remove = cel_redstone_cpld_remove,
	.driver = {
		.name  = driver_name,
		.owner = THIS_MODULE,
		.of_match_table = cel_redstone_cpld_ids,
	},
};


/*------------------------------------------------------------------------------
 *
 * module interface
 *
 */

/* move the action of this to probe and remove and change these to
   of_register_platform_driver/register_platform_driver
   and
   of_unregister_platform_driver/unregister_platform_driver
*/

static int __init cel_redstone_cpld_init(void)
{
	int rv;

	rv = platform_driver_register(&cel_redstone_cpld_driver);
	if (rv) {
		printk(KERN_ERR "%s platform_driver_register failed (%i)\n",
		       driver_name, rv);
	}
	return rv;
}

static void __exit cel_redstone_cpld_exit(void)
{
	return platform_driver_unregister(&cel_redstone_cpld_driver);
}

MODULE_AUTHOR("Vidya Sagar Ravipati <vidya@cumulusnetworks.com>");
MODULE_DESCRIPTION("CPLD driver for Celestica Inc., redstone");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);

module_init(cel_redstone_cpld_init);
module_exit(cel_redstone_cpld_exit);
