/*
 * accton_omp800_platform.c -
 *      Accton OMP 800 Platform Support.
 *
 * Copyright (C) 2017 Cumulus Networks, Inc.
 * Author: Scott Emery (scotte@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.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/i2c/pca954x.h>
#include <linux/i2c/sff-8436.h>
#include <linux/platform_data/pca953x.h>
#include <linux/platform_data/at24.h>
#include <linux/gpio.h>

#include "platform_defs.h"
#include "platform_bitfield.h"

#define DRIVER_NAME	"accton_omp800_platform"
#define DRIVER_VERSION	"0.1"

/*
 * There are (so far) four platform variants:
 *   line card CPU A     (accton_omp800_lca)
 *   line card CPU B     (accton_omp800_lcb)
 *   fabric card CPU A   (accton_omp800_fca)
 *   fabric card CPU B   (accton_omp800_fcb)
 * We are told which it is via the module parameter "platform".
 */

static char *platform;
module_param(platform, charp, 0);
MODULE_PARM_DESC(platform, "Platform name");

enum {
	VAR_UNKNOWN = -1,
	VAR_LCA,
	VAR_LCB,
	VAR_FCA,
	VAR_FCB,
	NUM_VARIANTS
};

static char *platform_names[NUM_VARIANTS + 1] = {
	[VAR_LCA] = "accton_omp800_lca",
	[VAR_LCB] = "accton_omp800_lcb",
	[VAR_FCA] = "accton_omp800_fca",
	[VAR_FCB] = "accton_omp800_fcb",
	[NUM_VARIANTS] = NULL
};

static int platform_variant = VAR_UNKNOWN;

/*
 * This platform has two i2c SMBUSes: i801 (Intel 82801 (ICH/PCH)) and
 * ISMT (Intel SMBus Message Transport).
 *
 * The i801 bus has the DRAM SPD EEPROMs, a few other devices we don't care
 * about, and a PCA9548 i2c MUX, off of which lies the two CPLDs. The ISMT bus
 * has the system EEPROM and three PCA9548 i2x MUXes which primarily provide
 * access to the QSFP28 ports. Because the pca9548s are connected in parallel,
 * and their down-stream devices (the QSFP28s) all have the same address, they
 * need to disable all ports when not used (the deselect_on_exit option).
 */

enum {
	OMP800_I2C_I801_BUS = 0,
	OMP800_I2C_ISMT_BUS,

	OMP800_I2C_MUX1_BUS0 = 10,
	OMP800_I2C_MUX1_BUS1,
	OMP800_I2C_MUX1_BUS2,
	OMP800_I2C_MUX1_BUS3,
	OMP800_I2C_MUX1_BUS4,
	OMP800_I2C_MUX1_BUS5,
	OMP800_I2C_MUX1_BUS6,
	OMP800_I2C_MUX1_BUS7,

	OMP800_I2C_MUX2_BUS0 = 20,
	OMP800_I2C_MUX2_BUS1,
	OMP800_I2C_MUX2_BUS2,
	OMP800_I2C_MUX2_BUS3,
	OMP800_I2C_MUX2_BUS4,
	OMP800_I2C_MUX2_BUS5,
	OMP800_I2C_MUX2_BUS6,
	OMP800_I2C_MUX2_BUS7,

	OMP800_I2C_MUX3_BUS0 = 30,
	OMP800_I2C_MUX3_BUS1,
	OMP800_I2C_MUX3_BUS2,
	OMP800_I2C_MUX3_BUS3,
	OMP800_I2C_MUX3_BUS4,
	OMP800_I2C_MUX3_BUS5,
	OMP800_I2C_MUX3_BUS6,
	OMP800_I2C_MUX3_BUS7,

	OMP800_I2C_MUX4_BUS0 = 40,
	OMP800_I2C_MUX4_BUS1,
	OMP800_I2C_MUX4_BUS2,
	OMP800_I2C_MUX4_BUS3,
	OMP800_I2C_MUX4_BUS4,
	OMP800_I2C_MUX4_BUS5,
	OMP800_I2C_MUX4_BUS6,
	OMP800_I2C_MUX4_BUS7,
};

/*
 * The list of I2C devices and their bus connections for the platforms.
 *
 * Each platform_i2c_device_info structure is a bus number
 * and an i2c_board_info structure, created with mk_i2cdev().
 * The i2c_board_info specifies the device type, address,
 * and optional, device-specific platform data (the last made
 * with mk_* macros).
 *
 * For muxes, we specify the bus number for each port,
 * and set the deselect_on_exit bit (see comment above).
 *
 * For EEPROMs, including ones in the QSFP28 transceivers,
 * we specify the label, I2C address, size, and some flags.
 * The label is the string that ends up in
 * /sys/class/eeprom_dev/eepromN/label, which we use to identify
 * them at user level.
 *
 * There are currently four platform variants, each with
 * its own platform_i2c_device_info table.
 */

/* I2C muxes */
mk_pca9548(mux1, OMP800_I2C_MUX1_BUS0, 1)
mk_pca9548(mux2, OMP800_I2C_MUX2_BUS0, 1)
mk_pca9548(mux3, OMP800_I2C_MUX3_BUS0, 1)
mk_pca9548(mux4, OMP800_I2C_MUX4_BUS0, 1)

/* all boards have 2 SPD and 1 board EEPROM */
mk_eeprom(spd1, 52, 256, AT24_FLAG_READONLY | AT24_FLAG_IRUGO);
mk_eeprom(spd2, 51, 256, AT24_FLAG_READONLY | AT24_FLAG_IRUGO);
mk_eeprom(board, 57, 256, AT24_FLAG_IRUGO);
mk_eeprom(chassis, 51, 32768, AT24_FLAG_IRUGO | AT24_FLAG_ADDR16);

/* lca has these QSFP slots (numbered 1 to 16) */
mk_qsfp_port_eeprom(port1,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port2,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port3,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port4,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port5,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port6,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port7,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port8,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port9,  50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port10, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port11, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port12, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port13, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port14, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port15, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port16, 50, 256, SFF_8436_FLAG_IRUGO)

/* lcb has these QSFP slots (numbered 17 to 32) */
mk_qsfp_port_eeprom(port17, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port18, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port19, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port20, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port21, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port22, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port23, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port24, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port25, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port26, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port27, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port28, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port29, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port30, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port31, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port32, 50, 256, SFF_8436_FLAG_IRUGO)

/*
 * CPLD1 platform data
 * @local - True if this is a locally accessed CPLD1, False if remote.
 */
struct cpld1_platform_data {
	u8  local;
};

struct cpld1_platform_data cpld1_platform_local = {
	.local = 1,
};

struct cpld1_platform_data cpld1_platform_remote = {
	.local = 0,
};

static struct platform_i2c_device_info i2c_devices_lca[] = {
	/*
	 * i2c devices on line card A
	 */
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x52, &spd1_52_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x51, &spd2_51_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "pca9548",  0x76, &mux1_platform_data),

	mk_i2cdev(OMP800_I2C_ISMT_BUS, "24c02",    0x57, &board_57_at24),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x71, &mux2_platform_data),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x72, &mux3_platform_data),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x73, &mux4_platform_data),

	/* devices on pca9548 mux1 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS2, "cpld1",   0x60, &cpld1_platform_local),
	mk_i2cdev(OMP800_I2C_MUX1_BUS3, "cpld23",  0x62, NULL),

	/* devices on pca9548 mux3 */
	mk_i2cdev(OMP800_I2C_MUX3_BUS0, "sff8436", 0x50, &port9_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS1, "sff8436", 0x50, &port10_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS2, "sff8436", 0x50, &port11_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS3, "sff8436", 0x50, &port12_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS4, "sff8436", 0x50, &port1_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS5, "sff8436", 0x50, &port2_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS6, "sff8436", 0x50, &port3_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS7, "sff8436", 0x50, &port4_50_sff8436),

	/* devices on pca9548 mux4 */
	mk_i2cdev(OMP800_I2C_MUX4_BUS0, "sff8436", 0x50, &port6_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS1, "sff8436", 0x50, &port5_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS2, "sff8436", 0x50, &port8_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS3, "sff8436", 0x50, &port7_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS4, "sff8436", 0x50, &port13_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS5, "sff8436", 0x50, &port14_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS6, "sff8436", 0x50, &port15_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS7, "sff8436", 0x50, &port16_50_sff8436),

	mk_i2cdev(0, "", 0, NULL)
};

static struct platform_i2c_device_info i2c_devices_lcb[] = {
	/*
	 * i2c devices on line card B
	 */
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x52, &spd1_52_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x51, &spd2_51_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "pca9548",  0x76, &mux1_platform_data),

	mk_i2cdev(OMP800_I2C_ISMT_BUS, "24c02",    0x57, &board_57_at24),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x71, &mux2_platform_data),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x72, &mux3_platform_data),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x73, &mux4_platform_data),

	/* devices on pca9548 mux1 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS2, "cpld1",   0x60, &cpld1_platform_local),
	mk_i2cdev(OMP800_I2C_MUX1_BUS3, "cpld23",  0x62, NULL),

	/* devices on pca9548 mux3 */
	mk_i2cdev(OMP800_I2C_MUX3_BUS0, "sff8436", 0x50, &port17_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS1, "sff8436", 0x50, &port18_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS2, "sff8436", 0x50, &port19_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS3, "sff8436", 0x50, &port20_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS4, "sff8436", 0x50, &port25_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS5, "sff8436", 0x50, &port26_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS6, "sff8436", 0x50, &port27_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX3_BUS7, "sff8436", 0x50, &port28_50_sff8436),

	/* devices on pca9548 mux4 */
	mk_i2cdev(OMP800_I2C_MUX4_BUS0, "sff8436", 0x50, &port30_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS1, "sff8436", 0x50, &port29_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS2, "sff8436", 0x50, &port32_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS3, "sff8436", 0x50, &port31_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS4, "sff8436", 0x50, &port22_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS5, "sff8436", 0x50, &port21_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS6, "sff8436", 0x50, &port24_50_sff8436),
	mk_i2cdev(OMP800_I2C_MUX4_BUS7, "sff8436", 0x50, &port23_50_sff8436),

	mk_i2cdev(0, "", 0, NULL)
};

static struct platform_i2c_device_info i2c_devices_fca[] = {
	/*
	 * i2c devices on fabric card A
	 */
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x52, &spd1_52_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x51, &spd2_51_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "pca9548",  0x76, &mux1_platform_data),

	mk_i2cdev(OMP800_I2C_ISMT_BUS, "24c02",    0x57, &board_57_at24),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x71, &mux2_platform_data),

	/* devices on pca9548 mux1 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS0, "fancpld", 0x66, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS2, "cpld1",   0x60, &cpld1_platform_local),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x64, &cpld1_platform_remote), /* Remote LC1 or LC5 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x10, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x65, &cpld1_platform_remote), /* Remote LC2 or LC6 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x13, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x66, &cpld1_platform_remote), /* Remote LC3 or LC7 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x50, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x67, &cpld1_platform_remote), /* Remote LC4 or LC8 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x53, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x60, &cpld1_platform_remote), /* Remote FC1 or FC3 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x44, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "cpld1",   0x61, &cpld1_platform_remote), /* Remote FC2 or FC4 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS4, "adm1278", 0x47, NULL),
	mk_i2cdev(OMP800_I2C_MUX1_BUS6, "fancpld", 0x66, NULL),

	/* devices on pca9548 mux2 */
	mk_i2cdev(OMP800_I2C_MUX2_BUS0, "pducpld", 0x61, NULL),
	mk_i2cdev(OMP800_I2C_MUX2_BUS0, "24c256",  0x51, &chassis_51_at24),

	mk_i2cdev(0, "", 0, NULL)
};

static struct platform_i2c_device_info i2c_devices_fcb[] = {
	/*
	 * i2c devices on fabric card A
	 */
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x52, &spd1_52_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "24c02",    0x51, &spd2_51_at24),
	mk_i2cdev(OMP800_I2C_I801_BUS, "pca9548",  0x76, &mux1_platform_data),

	mk_i2cdev(OMP800_I2C_ISMT_BUS, "24c02",    0x57, &board_57_at24),
	mk_i2cdev(OMP800_I2C_ISMT_BUS, "pca9548",  0x71, &mux2_platform_data),

	/* devices on pca9548 mux1 */
	mk_i2cdev(OMP800_I2C_MUX1_BUS2, "cpld1",   0x60, &cpld1_platform_local),

	mk_i2cdev(0, "", 0, NULL)
};

static struct platform_i2c_device_info *i2c_devices[NUM_VARIANTS] = {
	[VAR_LCA] = i2c_devices_lca,
	[VAR_LCB] = i2c_devices_lcb,
	[VAR_FCA] = i2c_devices_fca,
	[VAR_FCB] = i2c_devices_fcb,
};

/*
 * Utility functions for I2C
 */

static struct i2c_adapter *get_adapter(int bus)
{
	int bail;
	struct i2c_adapter *adapter;

	for (bail = 10; --bail >= 0;) {
		adapter = i2c_get_adapter(bus);
		if (adapter)
			return adapter;
		msleep(100);
	}
	return NULL;
}

static int get_bus_by_name(char *name)
{
	struct i2c_adapter *adapter;
	int i;

	for (i = 0; i < OMP800_I2C_MUX1_BUS0; i++) {
		adapter = get_adapter(i);
		if (adapter) {
			if (strncmp(adapter->name, name, strlen(name)) == 0) {
				i2c_put_adapter(adapter);
				return i;
			}
			i2c_put_adapter(adapter);
		}
	}
	return -1;
}

static struct i2c_client *add_i2c_client(int bus,
					 struct i2c_board_info *board_info)
{
	struct i2c_adapter *adapter;
	struct i2c_client *client;

	adapter = get_adapter(bus);
	if (!adapter) {
		pr_err("could not get I2C adapter %d\n", bus);
		client = ERR_PTR(-ENODEV);
		goto exit;
	}
	client = i2c_new_device(adapter, board_info);
	if (!client) {
		pr_err("could not add I2C device at bus %d type %s addr %#x\n",
		       bus, board_info->type, board_info->addr);
		client = ERR_PTR(-ENODEV);
	}
	i2c_put_adapter(adapter);
exit:
	return client;
}

static int init_i2c_devices(void)
{
	int ismt_bus_num;
	int i801_bus_num;
	struct platform_i2c_device_info *info;
	int ret;

	ismt_bus_num = get_bus_by_name(ISMT_ADAPTER_NAME);
	if (ismt_bus_num < 0) {
		pr_err("could not find ismt adapter bus\n");
		ret = -ENXIO;
		goto err_exit;
	}
	i801_bus_num = get_bus_by_name(I801_ADAPTER_NAME);
	if (i801_bus_num < 0) {
		pr_err("could not find I801 adapter bus\n");
		ret = -ENODEV;
		goto err_exit;
	}

	for (info = i2c_devices[platform_variant];
	     *info->board_info.type != '\0';
	     info++) {
		int bus = info->bus;
		struct i2c_client *client;

		switch (bus) {
		case OMP800_I2C_ISMT_BUS:
			bus = ismt_bus_num;
			break;
		case OMP800_I2C_I801_BUS:
			bus = i801_bus_num;
			break;
		default:
			break;
			/* Fall through for PCA9548 buses */
		};
		client = add_i2c_client(bus, &info->board_info);
		if (IS_ERR(client)) {
			ret = PTR_ERR(client);
			goto err_exit;
		}
		info->client = client;
	}
	return 0;

err_exit:
	return ret;
}

static void free_i2c_devices(void)
{
	struct platform_i2c_device_info *info;

	/* free devices in reverse order */
	for (info = i2c_devices[platform_variant];
	     *info->board_info.type != '\0';
	     info++)
		;
	while (--info >= i2c_devices[platform_variant]) {
		struct i2c_client *c = info->client;

		if (c) {
			info->client = NULL;
			i2c_unregister_device(c);
		}
	}
}

/*
 * Module init and exit for all I2C devices
 */

static int __init accton_omp800_i2c_init(void)
{
	int ret;

	ret = init_i2c_devices();
	if (ret)
		goto err_exit;

	return 0;

err_exit:
	free_i2c_devices();
	return ret;
}

static void accton_omp800_i2c_exit(void)
{
	free_i2c_devices();
}

/*
 * CPLD2 and CPLD3 driver
 */

#define cpld23_read_reg  bf_i2c_read_reg
#define cpld23_write_reg bf_i2c_write_reg

mk_bf_ro(cpld23, cpld_version,		0x01, 0, 8,  NULL, BF_DECIMAL)
mk_bf_rw(cpld23, qsfp_reset,		0x04, 0, 16, NULL, BF_COMPLEMENT)
mk_bf_rw(cpld23, qsfp_interrupt,	0x10, 0, 16, NULL, BF_COMPLEMENT)
mk_bf_rw(cpld23, qsfp_mask,		0x20, 0, 16, NULL, 0)
mk_bf_ro(cpld23, qsfp_present,		0x30, 0, 16, NULL, BF_COMPLEMENT)

struct attribute *cpld23_attrs[] = {
	&cpld23_cpld_version.attr,
	&cpld23_qsfp_reset.attr,
	&cpld23_qsfp_interrupt.attr,
	&cpld23_qsfp_mask.attr,
	&cpld23_qsfp_present.attr,
	NULL
};

static struct attribute_group cpld23_attr_group = {
	.attrs = cpld23_attrs,
};

static int cpld23_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	u8 version;
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev,
			"adapter does not support I2C_FUNC_SMBUS_BYTE_DATA\n");
		ret = -EINVAL;
		goto err;
	}

	/*
	 * Probe the hardware by reading the revision number.
	 */
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD version register error %d\n", ret);
		goto err;
	}
	version = ret;

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&client->dev.kobj, &cpld23_attr_group);
	if (ret) {
		dev_err(&client->dev, "sysfs_create_group failed\n");
		goto err;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&client->dev,
		 "device created, QSFP CPLD version %d\n",
		 version);

	return 0;

err:
	return ret;
}

static int cpld23_remove(struct i2c_client *client)
{
	sysfs_remove_group(&client->dev.kobj, &cpld23_attr_group);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id cpld23_id[] = {
	{ "cpld23", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, cpld23_id);

static struct i2c_driver cpld23_driver = {
	.driver = {
		.name = "cpld23",
		.owner = THIS_MODULE,
	},
	.probe = cpld23_probe,
	.remove = cpld23_remove,
	.id_table = cpld23_id,
};

static int __init accton_omp800_cpld23_init(void)
{
	pr_info(DRIVER_NAME ": loading QSFP CPLD driver\n");
	return i2c_add_driver(&cpld23_driver);
}

static void accton_omp800_cpld23_exit(void)
{
	i2c_del_driver(&cpld23_driver);
	pr_info(DRIVER_NAME ": QSFP CPLD driver unloaded\n");
}

/*
 * CPLD1 driver
 */

#define cpld1_read_reg  bf_i2c_read_reg
#define cpld1_write_reg bf_i2c_write_reg

static const char * const cpld1_models[] = {
	"ES7726BT",
	"ES7726BT-LineCard",
	"ES7726BT-FabricCard",
	"unknown",
};

static const char * const cpld1_card_types[] = {
	"LineCard", "FabricCard",
};

static const char * const cpld1_cpus[] = {
	"CPU-A", "CPU-B",
};

static const char * const cpld1_grbs[] = {
	"off", "green", "red", "yellow",
	"blue", "cyan", "magenta", "white"
};

mk_bf_ro(cpld1, board_revision,		0x00, 0, 2,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, board_model_id,		0x00, 2, 2,  NULL, 0)
mk_bf_ro(cpld1, board_model,		0x00, 2, 2,  cpld1_models, 0)
mk_bf_ro(cpld1, cpld_version,		0x01, 0, 8,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, slot_id,		0x02, 0, 3,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, lower_subchassis,	0x02, 3, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, card_type,		0x02, 4, 1,  cpld1_card_types, 0)
mk_bf_ro(cpld1, card_type_id,		0x02, 4, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, cpu,			0x02, 7, 1,  cpld1_cpus, 0)
mk_bf_ro(cpld1, cpu_id,			0x02, 7, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, hotswap_power_good,	0x03, 7, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, hotswap_fault,		0x03, 6, 1,  NULL, BF_DECIMAL | BF_COMPLEMENT)
mk_bf_ro(cpld1, hotswap_enable,		0x03, 2, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, ext_monitor_enable,	0x03, 1, 1,  NULL, BF_DECIMAL)
mk_bf_ro(cpld1, remote_console_enable,	0x03, 0, 1,  NULL, BF_DECIMAL)
mk_bf_rw(cpld1, reset_mgmt_phy,		0x08, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_idt8v89307,	0x08, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_cpld23,		0x08, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_fpga,		0x08, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_mac_pcie,		0x08, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_usb,		0x08, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_manu,		0x08, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_bcm56960,		0x08, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_bcm56960_b,	0x08, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_cpu_b,		0x08, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_bcm56960_a,	0x08, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_cpu_a,		0x08, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_sc16,		0x09, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_bcm53xx,		0x09, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_cpu_jtag,		0x09, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_sys_cpld,		0x09, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_pca9548_71,	0x0a, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_pca9548_72,	0x0a, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_pca9548_73,	0x0a, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, reset_pca9548_76,	0x0a, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, sys_reset_lock,		0x0b, 0, 1,  NULL, 0)
mk_bf_rw(cpld1, sys_reset_lock_a,	0x0b, 0, 1,  NULL, 0)
mk_bf_rw(cpld1, sys_reset_lock_b,	0x0b, 1, 1,  NULL, 0)
mk_bf_rw(cpld1, cpu_thermal,		0x30, 0, 8,  NULL, 0)
mk_bf_rw(cpld1, mac_thermal,		0x31, 0, 8,  NULL, 0)
mk_bf_rw(cpld1, led_diag_color,		0x41, 0, 2,  cpld1_grbs, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_diag_green,		0x41, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_diag_red,		0x41, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_locate,		0x41, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_release_color,	0x41, 4, 3,  cpld1_grbs, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_release_green,	0x41, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_release_red,	0x41, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_release_blue,	0x41, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_fan_color,		0x42, 0, 3,  cpld1_grbs, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_fan_green,		0x42, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_fan_red,		0x42, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_fan_blue,		0x42, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_psu_color,		0x42, 4, 3,  cpld1_grbs, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_psu_green,		0x42, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_psu_red,		0x42, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_psu_blue,		0x42, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, led_debug_mode,		0x42, 7, 1,  NULL, 0)
mk_bf_rw(cpld1, sysled_red_pwm,		0x43, 0, 8,  NULL, 0)
mk_bf_rw(cpld1, sysled_green_pwm,	0x44, 0, 8,  NULL, 0)
mk_bf_rw(cpld1, sysled_blue_pwm,	0x45, 0, 8,  NULL, 0)
mk_bf_rw(cpld1, pdu_present,		0x48, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, esw_spi_select,		0x48, 1, 1,  NULL, 0)
mk_bf_rw(cpld1, eeprom_53xx_sel,	0x48, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(cpld1, misc_cpu,		0x48, 0, 8,  NULL, 0)

struct attribute *cpld1_lca_attrs[] = {
	&cpld1_board_revision.attr,
	&cpld1_board_model_id.attr,
	&cpld1_board_model.attr,
	&cpld1_cpld_version.attr,
	&cpld1_slot_id.attr,
	&cpld1_lower_subchassis.attr,
	&cpld1_card_type.attr,
	&cpld1_card_type_id.attr,
	&cpld1_cpu.attr,
	&cpld1_cpu_id.attr,
	&cpld1_hotswap_power_good.attr,
	&cpld1_hotswap_fault.attr,
	&cpld1_hotswap_enable.attr,
	&cpld1_ext_monitor_enable.attr,
	&cpld1_remote_console_enable.attr,
	&cpld1_reset_idt8v89307.attr,
	&cpld1_reset_cpld23.attr,
	&cpld1_reset_fpga.attr,
	&cpld1_reset_mac_pcie.attr,
	&cpld1_reset_usb.attr,
	&cpld1_reset_manu.attr,
	&cpld1_reset_bcm56960.attr,
	&cpld1_reset_sc16.attr,
	&cpld1_reset_bcm53xx.attr,
	&cpld1_reset_cpu_jtag.attr,
	&cpld1_reset_sys_cpld.attr,
	&cpld1_reset_pca9548_71.attr,
	&cpld1_reset_pca9548_72.attr,
	&cpld1_reset_pca9548_73.attr,
	&cpld1_reset_pca9548_76.attr,
	&cpld1_sys_reset_lock.attr,
	&cpld1_cpu_thermal.attr,
	&cpld1_mac_thermal.attr,
	&cpld1_led_diag_color.attr,
	&cpld1_led_diag_green.attr,
	&cpld1_led_diag_red.attr,
	&cpld1_led_locate.attr,
	&cpld1_led_release_color.attr,
	&cpld1_led_release_green.attr,
	&cpld1_led_release_red.attr,
	&cpld1_led_release_blue.attr,
	&cpld1_led_debug_mode.attr,
	&cpld1_sysled_red_pwm.attr,
	&cpld1_sysled_green_pwm.attr,
	&cpld1_sysled_blue_pwm.attr,
	&cpld1_esw_spi_select.attr,
	&cpld1_eeprom_53xx_sel.attr,
	&cpld1_misc_cpu.attr,
	NULL
};

struct attribute *cpld1_lcb_attrs[] = {
	&cpld1_board_revision.attr,
	&cpld1_board_model_id.attr,
	&cpld1_board_model.attr,
	&cpld1_cpld_version.attr,
	&cpld1_slot_id.attr,
	&cpld1_lower_subchassis.attr,
	&cpld1_card_type.attr,
	&cpld1_card_type_id.attr,
	&cpld1_cpu.attr,
	&cpld1_cpu_id.attr,
	&cpld1_reset_idt8v89307.attr,
	&cpld1_reset_cpld23.attr,
	&cpld1_reset_fpga.attr,
	&cpld1_reset_mac_pcie.attr,
	&cpld1_reset_usb.attr,
	&cpld1_reset_manu.attr,
	&cpld1_reset_bcm56960.attr,
	&cpld1_reset_cpu_jtag.attr,
	&cpld1_reset_sys_cpld.attr,
	&cpld1_reset_pca9548_71.attr,
	&cpld1_reset_pca9548_72.attr,
	&cpld1_reset_pca9548_73.attr,
	&cpld1_reset_pca9548_76.attr,
	&cpld1_sys_reset_lock.attr,
	&cpld1_cpu_thermal.attr,
	&cpld1_mac_thermal.attr,
	NULL
};

struct attribute *cpld1_fca_attrs[] = {
	&cpld1_board_revision.attr,
	&cpld1_board_model_id.attr,
	&cpld1_board_model.attr,
	&cpld1_cpld_version.attr,
	&cpld1_slot_id.attr,
	&cpld1_lower_subchassis.attr,
	&cpld1_card_type.attr,
	&cpld1_card_type_id.attr,
	&cpld1_cpu.attr,
	&cpld1_cpu_id.attr,
	&cpld1_hotswap_power_good.attr,
	&cpld1_hotswap_fault.attr,
	&cpld1_hotswap_enable.attr,
	&cpld1_ext_monitor_enable.attr,
	&cpld1_remote_console_enable.attr,
	&cpld1_reset_mgmt_phy.attr,
	&cpld1_reset_idt8v89307.attr,
	&cpld1_reset_fpga.attr,
	&cpld1_reset_mac_pcie.attr,
	&cpld1_reset_usb.attr,
	&cpld1_reset_manu.attr,
	&cpld1_reset_bcm56960.attr,
	&cpld1_reset_sc16.attr,
	&cpld1_reset_bcm53xx.attr,
	&cpld1_reset_cpu_jtag.attr,
	&cpld1_reset_sys_cpld.attr,
	&cpld1_reset_pca9548_71.attr,
	&cpld1_reset_pca9548_72.attr,
	&cpld1_reset_pca9548_73.attr,
	&cpld1_reset_pca9548_76.attr,
	&cpld1_sys_reset_lock.attr,
	&cpld1_cpu_thermal.attr,
	&cpld1_mac_thermal.attr,
	&cpld1_led_diag_color.attr,
	&cpld1_led_diag_green.attr,
	&cpld1_led_diag_red.attr,
	&cpld1_led_locate.attr,
	&cpld1_led_release_color.attr,
	&cpld1_led_release_green.attr,
	&cpld1_led_release_red.attr,
	&cpld1_led_release_blue.attr,
	&cpld1_led_fan_color.attr,
	&cpld1_led_fan_green.attr,
	&cpld1_led_fan_red.attr,
	&cpld1_led_fan_blue.attr,
	&cpld1_led_psu_color.attr,
	&cpld1_led_psu_green.attr,
	&cpld1_led_psu_red.attr,
	&cpld1_led_psu_blue.attr,
	&cpld1_led_debug_mode.attr,
	&cpld1_sysled_red_pwm.attr,
	&cpld1_sysled_green_pwm.attr,
	&cpld1_sysled_blue_pwm.attr,
	&cpld1_pdu_present.attr,
	&cpld1_esw_spi_select.attr,
	&cpld1_eeprom_53xx_sel.attr,
	&cpld1_misc_cpu.attr,
	NULL
};

struct attribute *cpld1_fcb_attrs[] = {
	&cpld1_board_revision.attr,
	&cpld1_board_model_id.attr,
	&cpld1_board_model.attr,
	&cpld1_cpld_version.attr,
	&cpld1_slot_id.attr,
	&cpld1_lower_subchassis.attr,
	&cpld1_card_type.attr,
	&cpld1_card_type_id.attr,
	&cpld1_cpu.attr,
	&cpld1_cpu_id.attr,
	&cpld1_reset_idt8v89307.attr,
	&cpld1_reset_fpga.attr,
	&cpld1_reset_mac_pcie.attr,
	&cpld1_reset_usb.attr,
	&cpld1_reset_manu.attr,
	&cpld1_reset_bcm56960.attr,
	&cpld1_reset_cpu_jtag.attr,
	&cpld1_reset_sys_cpld.attr,
	&cpld1_reset_pca9548_71.attr,
	&cpld1_reset_pca9548_72.attr,
	&cpld1_reset_pca9548_73.attr,
	&cpld1_reset_pca9548_76.attr,
	&cpld1_sys_reset_lock.attr,
	&cpld1_cpu_thermal.attr,
	&cpld1_mac_thermal.attr,
	NULL
};

struct attribute *cpld1_ext_attrs[] = {
	&cpld1_board_revision.attr,
	&cpld1_board_model_id.attr,
	&cpld1_board_model.attr,
	&cpld1_cpld_version.attr,
	&cpld1_slot_id.attr,
	&cpld1_lower_subchassis.attr,
	&cpld1_card_type.attr,
	&cpld1_card_type_id.attr,
	&cpld1_cpu.attr,
	&cpld1_cpu_id.attr,
	&cpld1_hotswap_power_good.attr,
	&cpld1_hotswap_fault.attr,
	&cpld1_hotswap_enable.attr,
	&cpld1_ext_monitor_enable.attr,
	&cpld1_remote_console_enable.attr,
	&cpld1_reset_bcm56960_b.attr,
	&cpld1_reset_cpu_b.attr,
	&cpld1_reset_bcm56960_a.attr,
	&cpld1_reset_cpu_a.attr,
	&cpld1_sys_reset_lock_a.attr,
	&cpld1_sys_reset_lock_b.attr,
	NULL
};


static struct attribute_group cpld1_attr_groups[NUM_VARIANTS] = {
	[VAR_LCA].attrs = cpld1_lca_attrs,
	[VAR_LCB].attrs = cpld1_lcb_attrs,
	[VAR_FCA].attrs = cpld1_fca_attrs,
	[VAR_FCB].attrs = cpld1_fcb_attrs,
};

static struct attribute_group cpld1_attr_group_ext = {
	.attrs = cpld1_ext_attrs,
};

static struct i2c_client * volatile cpld1_i2c_client = NULL;

static int cpld1_enable_bcm_spi(void)
{
	/*
	 * Set up for CPU SPI access
	 */
	int ret;
	int tries;

	for (tries = 0; tries < 10 && !cpld1_i2c_client; tries++) {
		msleep(100);
	}

	if (!cpld1_i2c_client) {
		return(-EAGAIN);
	}

	/* Enable SPI access */
	ret = i2c_smbus_write_byte_data(cpld1_i2c_client, 0x48, 0x99);
	if (ret < 0) {
		dev_err(&cpld1_i2c_client->dev,
			"write CPLD misc cpu register error %d\n", ret);
	}
	/* Reset the BCM53xx */
	ret = i2c_smbus_write_byte_data(cpld1_i2c_client, 0x09, 0x09);
	if (ret < 0) {
		dev_err(&cpld1_i2c_client->dev,
			"write CPLD system reset-6 error %d\n", ret);
	}
	msleep(10);
	ret = i2c_smbus_write_byte_data(cpld1_i2c_client, 0x09, 0x0b);
	if (ret < 0) {
		dev_err(&cpld1_i2c_client->dev,
			"write CPLD system reset-6 error %d\n", ret);
	}

	return(ret);
}

static int cpld1_probe(struct i2c_client *client,
		       const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct attribute_group *ag;
	struct cpld1_platform_data *cpd;
	u8 version;
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev,
			"adapter does not support I2C_FUNC_SMBUS_BYTE_DATA\n");
		ret = -EINVAL;
		goto err;
	}

	/*
	 * Probe the hardware by reading the revision number.
	 */
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD version register error %d\n", ret);
		goto err;
	}
	version = ret;

	/*
	 * Create sysfs nodes.
	 */
	cpd = dev_get_platdata(&client->dev);
	ag = &cpld1_attr_group_ext;
	if (!cpd || cpd->local) {
		ag = &cpld1_attr_groups[platform_variant]; 
		cpld1_i2c_client = client;
	}
	ret = sysfs_create_group(&client->dev.kobj, ag);
	if (ret) {
		dev_err(&client->dev, "sysfs_create_group failed\n");
		return ret;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&client->dev,
		 "device created, system CPLD version %d\n",
		 version);

	return 0;

err:
	return ret;
}

static int cpld1_remove(struct i2c_client *client)
{
	cpld1_i2c_client = NULL;
	sysfs_remove_group(&client->dev.kobj, &cpld1_attr_groups[platform_variant]);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id cpld1_id[] = {
	{ "cpld1", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, cpld1_id);

static struct i2c_driver cpld1_driver = {
	.driver = {
		.name = "cpld1",
		.owner = THIS_MODULE,
	},
	.probe = cpld1_probe,
	.remove = cpld1_remove,
	.id_table = cpld1_id,
};

static int __init accton_omp800_cpld1_init(void)
{
	pr_info(DRIVER_NAME ": loading system CPLD driver\n");
	return i2c_add_driver(&cpld1_driver);
}

static void accton_omp800_cpld1_exit(void)
{
	i2c_del_driver(&cpld1_driver);
	pr_info(DRIVER_NAME ": system CPLD driver unloaded\n");
}

/*
 * FAN CPLD driver
 */

#define fancpld_read_reg  bf_i2c_read_reg
#define fancpld_write_reg bf_i2c_write_reg

static const char * const fancpld_grs[] = {
	"off", "green", "red", "yellow"
};

mk_bf_ro(fancpld, board_id,			0x00, 0, 2,  NULL, BF_DECIMAL)
mk_bf_ro(fancpld, board_version_id,		0x00, 5, 3,  NULL, BF_DECIMAL)
mk_bf_ro(fancpld, cpld_version,			0x01, 0, 8,  NULL, BF_DECIMAL)
mk_bf_rw(fancpld, reset_cpld,			0x04, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(fancpld, fan_present,			0x0f, 0, 6,  NULL, BF_COMPLEMENT)
mk_bf_rw(fancpld, fan_pwm,			0x11, 0, 4,  NULL, 0)
mk_bf_rw(fancpld, fan_control_cpld,		0x11, 7, 1,  NULL, 0)
mk_bf_ro(fancpld, fan1_front_tach,		0x12, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan2_front_tach,		0x13, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan3_front_tach,		0x14, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan4_front_tach,		0x15, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, fan4_led,			0x1C, 0, 2,  fancpld_grs, 0)
mk_bf_rw(fancpld, fan3_led,			0x1C, 2, 2,  fancpld_grs, 0)
mk_bf_rw(fancpld, fan2_led,			0x1C, 4, 2,  fancpld_grs, 0)
mk_bf_rw(fancpld, fan1_led,			0x1C, 6, 2,  fancpld_grs, 0)
mk_bf_ro(fancpld, fan1_rear_tach,		0x22, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan2_rear_tach,		0x23, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan3_rear_tach,		0x24, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fan4_rear_tach,		0x25, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, fan_power_enable,		0x30, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, fan_watchdog,			0x31, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, fan_watchdog_max_pwm,		0x32, 0, 4,  NULL, 0)
mk_bf_rw(fancpld, fan_watchdog_enable,		0x33, 0, 1,  NULL, 0)
mk_bf_rw(fancpld, fan_control_unlock,		0x33, 7, 1,  NULL, 0)
mk_bf_rw(fancpld, fan_led_debug,		0x34, 7, 1,  NULL, 0)
mk_bf_rw(fancpld, fan_min_speed,		0x40, 0, 4,  NULL, 0)
mk_bf_rw(fancpld, fan_step_1_speed,		0x41, 0, 4,  NULL, 0)
mk_bf_rw(fancpld, fan_step_2_speed,		0x42, 0, 4,  NULL, 0)
mk_bf_rw(fancpld, fan_max_speed,		0x43, 0, 4,  NULL, 0)
mk_bf_ro(fancpld, lca_cpu_temp,			0x50, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lca_asic_temp,		0x51, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lca_lm75a_temp,		0x52, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lca_lm75b_temp,		0x53, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lca_lm75c_temp,		0x54, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lca_lm75d_temp,		0x55, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_cpu_temp,			0x60, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_asic_temp,		0x61, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_lm75a_temp,		0x62, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_lm75b_temp,		0x63, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_lm75c_temp,		0x64, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcb_lm75d_temp,		0x65, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_cpu_temp,			0x70, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_asic_temp,		0x71, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_lm75a_temp,		0x72, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_lm75b_temp,		0x73, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_lm75c_temp,		0x74, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcc_lm75d_temp,		0x75, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_cpu_temp,			0x80, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_asic_temp,		0x81, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_lm75a_temp,		0x82, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_lm75b_temp,		0x83, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_lm75c_temp,		0x84, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, lcd_lm75d_temp,		0x85, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_cpu_temp,			0x90, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_asic_temp,		0x91, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_lm75a_temp,		0x92, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_lm75b_temp,		0x93, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_lm75c_temp,		0x94, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fca_lm75d_temp,		0x95, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_cpu_temp,			0xa0, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_asic_temp,		0xa1, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_lm75a_temp,		0xa2, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_lm75b_temp,		0xa3, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_lm75c_temp,		0xa4, 0, 8,  NULL, 0)
mk_bf_ro(fancpld, fcb_lm75d_temp,		0xa5, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, cpu_step_1_high_thresh,	0xb0, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, cpu_step_2_low_thresh,	0xb1, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, cpu_step_2_high_thresh,	0xb2, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, cpu_max_low_thresh,		0xb3, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, asic_step_1_high_thresh,	0xb4, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, asic_step_2_low_thresh,	0xb5, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, asic_step_2_high_thresh,	0xb6, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, asic_max_low_thresh,		0xb7, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75a_step_1_high_thresh,	0xc0, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75a_step_2_low_thresh,	0xc1, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75a_step_2_high_thresh,	0xc2, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75a_max_low_thresh,		0xc3, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75b_step_1_high_thresh,	0xc4, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75b_step_2_low_thresh,	0xc5, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75b_step_2_high_thresh,	0xc6, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75b_max_low_thresh,		0xc7, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75c_step_1_high_thresh,	0xc8, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75c_step_2_low_thresh,	0xc9, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75c_step_2_high_thresh,	0xca, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75c_max_low_thresh,		0xcb, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75d_step_1_high_thresh,	0xcc, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75d_step_2_low_thresh,	0xcd, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75d_step_2_high_thresh,	0xce, 0, 8,  NULL, 0)
mk_bf_rw(fancpld, lm75d_max_low_thresh,		0xcf, 0, 8,  NULL, 0)

struct attribute *fancpld_attrs[] = {
	&fancpld_board_id.attr,
	&fancpld_board_version_id.attr,
	&fancpld_cpld_version.attr,
	&fancpld_reset_cpld.attr,
	&fancpld_fan_present.attr,
	&fancpld_fan_pwm.attr,
	&fancpld_fan_control_cpld.attr,
	&fancpld_fan1_front_tach.attr,
	&fancpld_fan2_front_tach.attr,
	&fancpld_fan3_front_tach.attr,
	&fancpld_fan4_front_tach.attr,
	&fancpld_fan4_led.attr,
	&fancpld_fan3_led.attr,
	&fancpld_fan2_led.attr,
	&fancpld_fan1_led.attr,
	&fancpld_fan1_rear_tach.attr,
	&fancpld_fan2_rear_tach.attr,
	&fancpld_fan3_rear_tach.attr,
	&fancpld_fan4_rear_tach.attr,
	&fancpld_fan_power_enable.attr,
	&fancpld_fan_watchdog.attr,
	&fancpld_fan_watchdog_max_pwm.attr,
	&fancpld_fan_watchdog_enable.attr,
	&fancpld_fan_control_unlock.attr,
	&fancpld_fan_led_debug.attr,
	&fancpld_fan_min_speed.attr,
	&fancpld_fan_step_1_speed.attr,
	&fancpld_fan_step_2_speed.attr,
	&fancpld_fan_max_speed.attr,
	&fancpld_lca_cpu_temp.attr,
	&fancpld_lca_asic_temp.attr,
	&fancpld_lca_lm75a_temp.attr,
	&fancpld_lca_lm75b_temp.attr,
	&fancpld_lca_lm75c_temp.attr,
	&fancpld_lca_lm75d_temp.attr,
	&fancpld_lcb_cpu_temp.attr,
	&fancpld_lcb_asic_temp.attr,
	&fancpld_lcb_lm75a_temp.attr,
	&fancpld_lcb_lm75b_temp.attr,
	&fancpld_lcb_lm75c_temp.attr,
	&fancpld_lcb_lm75d_temp.attr,
	&fancpld_lcc_cpu_temp.attr,
	&fancpld_lcc_asic_temp.attr,
	&fancpld_lcc_lm75a_temp.attr,
	&fancpld_lcc_lm75b_temp.attr,
	&fancpld_lcc_lm75c_temp.attr,
	&fancpld_lcc_lm75d_temp.attr,
	&fancpld_lcd_cpu_temp.attr,
	&fancpld_lcd_asic_temp.attr,
	&fancpld_lcd_lm75a_temp.attr,
	&fancpld_lcd_lm75b_temp.attr,
	&fancpld_lcd_lm75c_temp.attr,
	&fancpld_lcd_lm75d_temp.attr,
	&fancpld_fca_cpu_temp.attr,
	&fancpld_fca_asic_temp.attr,
	&fancpld_fca_lm75a_temp.attr,
	&fancpld_fca_lm75b_temp.attr,
	&fancpld_fca_lm75c_temp.attr,
	&fancpld_fca_lm75d_temp.attr,
	&fancpld_fcb_cpu_temp.attr,
	&fancpld_fcb_asic_temp.attr,
	&fancpld_fcb_lm75a_temp.attr,
	&fancpld_fcb_lm75b_temp.attr,
	&fancpld_fcb_lm75c_temp.attr,
	&fancpld_fcb_lm75d_temp.attr,
	&fancpld_cpu_step_1_high_thresh.attr,
	&fancpld_cpu_step_2_low_thresh.attr,
	&fancpld_cpu_step_2_high_thresh.attr,
	&fancpld_cpu_max_low_thresh.attr,
	&fancpld_asic_step_1_high_thresh.attr,
	&fancpld_asic_step_2_low_thresh.attr,
	&fancpld_asic_step_2_high_thresh.attr,
	&fancpld_asic_max_low_thresh.attr,
	&fancpld_lm75a_step_1_high_thresh.attr,
	&fancpld_lm75a_step_2_low_thresh.attr,
	&fancpld_lm75a_step_2_high_thresh.attr,
	&fancpld_lm75a_max_low_thresh.attr,
	&fancpld_lm75b_step_1_high_thresh.attr,
	&fancpld_lm75b_step_2_low_thresh.attr,
	&fancpld_lm75b_step_2_high_thresh.attr,
	&fancpld_lm75b_max_low_thresh.attr,
	&fancpld_lm75c_step_1_high_thresh.attr,
	&fancpld_lm75c_step_2_low_thresh.attr,
	&fancpld_lm75c_step_2_high_thresh.attr,
	&fancpld_lm75c_max_low_thresh.attr,
	&fancpld_lm75d_step_1_high_thresh.attr,
	&fancpld_lm75d_step_2_low_thresh.attr,
	&fancpld_lm75d_step_2_high_thresh.attr,
	&fancpld_lm75d_max_low_thresh.attr,
	NULL
};

static struct attribute_group fancpld_attr_group = {
	.attrs = fancpld_attrs,
};

static int fancpld_probe(struct i2c_client *client,
		       const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	u8 version;
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev,
			"adapter does not support I2C_FUNC_SMBUS_BYTE_DATA\n");
		ret = -EINVAL;
		goto err;
	}

	/*
	 * Probe the hardware by reading the revision number.
	 */
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD version register error %d\n", ret);
		goto err;
	}
	version = ret;

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&client->dev.kobj, &fancpld_attr_group);
	if (ret) {
		dev_err(&client->dev, "sysfs_create_group failed\n");
		return ret;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&client->dev,
		 "device created, fan CPLD version %d\n",
		 version);

	return 0;

err:
	return ret;
}

static int fancpld_remove(struct i2c_client *client)
{
	sysfs_remove_group(&client->dev.kobj, &fancpld_attr_group);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id fancpld_id[] = {
	{ "fancpld", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, fancpld_id);

static struct i2c_driver fancpld_driver = {
	.driver = {
		.name = "fancpld",
		.owner = THIS_MODULE,
	},
	.probe = fancpld_probe,
	.remove = fancpld_remove,
	.id_table = fancpld_id,
};

static int __init accton_omp800_fancpld_init(void)
{
	pr_info(DRIVER_NAME ": loading fan CPLD driver\n");
	return i2c_add_driver(&fancpld_driver);
}

static void accton_omp800_fancpld_exit(void)
{
	i2c_del_driver(&fancpld_driver);
	pr_info(DRIVER_NAME ": fan CPLD driver unloaded\n");
}

/*
 * PDU CPLD driver
 */

#define pducpld_read_reg  bf_i2c_read_reg
#define pducpld_write_reg bf_i2c_write_reg

static const char * const pducpld_grs[] = {
	"off", "green", "red", "yellow"
};

mk_bf_ro(pducpld, board_id,			0x00, 0, 2,  NULL, BF_DECIMAL)
mk_bf_ro(pducpld, board_version_id,		0x00, 5, 3,  NULL, BF_DECIMAL)
mk_bf_ro(pducpld, cpld_version,			0x01, 0, 8,  NULL, BF_DECIMAL)
mk_bf_rw(pducpld, reset_pca9548,		0x04, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(pducpld, reset_cpld,			0x04, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_r_present,		0x10, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_m_present,		0x10, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_l_present,		0x10, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_r_input_ok,		0x11, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_m_input_ok,		0x11, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_l_input_ok,		0x11, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_r_output_ok,		0x12, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_m_output_ok,		0x12, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, psu_l_output_ok,		0x12, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(pducpld, psu_r_enable,			0x14, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(pducpld, psu_m_enable,			0x14, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(pducpld, psu_l_enable,			0x14, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(pducpld, pdu_present,			0x15, 0, 1,  NULL, BF_COMPLEMENT)

struct attribute *pducpld_attrs[] = {
	&pducpld_board_id.attr,
	&pducpld_board_version_id.attr,
	&pducpld_cpld_version.attr,
	&pducpld_reset_pca9548.attr,
	&pducpld_reset_cpld.attr,
	&pducpld_psu_r_present.attr,
	&pducpld_psu_m_present.attr,
	&pducpld_psu_l_present.attr,
	&pducpld_psu_r_input_ok.attr,
	&pducpld_psu_m_input_ok.attr,
	&pducpld_psu_l_input_ok.attr,
	&pducpld_psu_r_output_ok.attr,
	&pducpld_psu_m_output_ok.attr,
	&pducpld_psu_l_output_ok.attr,
	&pducpld_psu_r_enable.attr,
	&pducpld_psu_m_enable.attr,
	&pducpld_psu_l_enable.attr,
	&pducpld_pdu_present.attr,
	NULL
};

static struct attribute_group pducpld_attr_group = {
	.attrs = pducpld_attrs,
};

static int pducpld_probe(struct i2c_client *client,
		       const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	u8 version;
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev,
			"adapter does not support I2C_FUNC_SMBUS_BYTE_DATA\n");
		ret = -EINVAL;
		goto err;
	}

	/*
	 * Probe the hardware by reading the revision number.
	 */
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD version register error %d\n", ret);
		goto err;
	}
	version = ret;

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&client->dev.kobj, &pducpld_attr_group);
	if (ret) {
		dev_err(&client->dev, "sysfs_create_group failed\n");
		return ret;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&client->dev,
		 "device created, pdu CPLD version %d\n",
		 version);

	return 0;

err:
	return ret;
}

static int pducpld_remove(struct i2c_client *client)
{
	sysfs_remove_group(&client->dev.kobj, &pducpld_attr_group);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id pducpld_id[] = {
	{ "pducpld", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pducpld_id);

static struct i2c_driver pducpld_driver = {
	.driver = {
		.name = "pducpld",
		.owner = THIS_MODULE,
	},
	.probe = pducpld_probe,
	.remove = pducpld_remove,
	.id_table = pducpld_id,
};

static int __init accton_omp800_pducpld_init(void)
{
	pr_info(DRIVER_NAME ": loading pdu CPLD driver\n");
	return i2c_add_driver(&pducpld_driver);
}

static void accton_omp800_pducpld_exit(void)
{
	i2c_del_driver(&pducpld_driver);
	pr_info(DRIVER_NAME ": pdu CPLD driver unloaded\n");
}

/*
 * ADM1278 driver
 */
#define adm1278_read_reg  bf_i2c_read_reg
#define adm1278_write_reg bf_i2c_write_reg

mk_bf_rw(adm1278, hot_swap_enable,	0x01, 7, 1,  NULL, BF_DECIMAL)

struct attribute *adm1278_attrs[] = {
	&adm1278_hot_swap_enable.attr,
	NULL
};

static struct attribute_group adm1278_attr_group = {
	.attrs = adm1278_attrs,
};

static struct i2c_client * volatile adm1278_i2c_client = NULL;

static int adm1278_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	u8 version;
	int ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev,
			"adapter does not support I2C_FUNC_SMBUS_BYTE_DATA\n");
		ret = -EINVAL;
		goto err;
	}

	/*
	 * Probe the hardware by reading the revision number.
	 */
	ret = i2c_smbus_read_byte_data(client, 0x9b);
	if (ret < 0) {
		dev_err(&client->dev,
			"read adm1278 version register error %d\n", ret);
		goto err;
	}
	version = ret;

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&client->dev.kobj, &adm1278_attr_group);
	if (ret) {
		dev_err(&client->dev, "sysfs_create_group failed\n");
		return ret;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&client->dev,
		 "device created, system adm1278 version %d\n",
		 version);

	return 0;

err:
	return ret;
}

static int adm1278_remove(struct i2c_client *client)
{
	sysfs_remove_group(&client->dev.kobj, &adm1278_attr_group);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id adm1278_id[] = {
	{ "adm1278", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, adm1278_id);

static struct i2c_driver adm1278_driver = {
	.driver = {
		.name = "adm1278",
		.owner = THIS_MODULE,
	},
	.probe = adm1278_probe,
	.remove = adm1278_remove,
	.id_table = adm1278_id,
};

static int __init accton_omp800_adm1278_init(void)
{
	pr_info(DRIVER_NAME ": loading system adm1278 driver\n");
	return i2c_add_driver(&adm1278_driver);
}

static void accton_omp800_adm1278_exit(void)
{
	i2c_del_driver(&adm1278_driver);
	pr_info(DRIVER_NAME ": system adm1278 driver unloaded\n");
}


/*
 * Driver for BCM53XX (BCM5389 and BCM5396)
 */

/*
 *  Each sysfs node represents a bcm53xx register. The node is defined by this
 *  bcm53xx_addr_s structure:
 *      name - The name which will apprear in the sysfs
 *      flags - Bitfield which assits in interpreting the value
 *      len - The number of bytes in the register
 *      page - The page number of the register
 *      base - The base offset of the register in the page
 */

typedef struct bcm53xx_addr_s {
	const char *name;
	u8  flags;
	u8  len;
	u8  page;
	u8  base;
} bcm53xx_addr_t;

/* flags */
#define BCM53XX_COMPLEMENT	0x01	/* complement value */
#define BCM53XX_DECIMAL		0x02	/* show value in decimal */
#define BCM53XX_BYTE_ARRAY      0x04	/* Not a number, a byte array */

#define BCM53XX_CMD_READ	0x60
#define BCM53XX_CMD_FAST_READ   0x10
#define BCM53XX_CMD_WRITE       0x61
#define BCM53XX_CMD_FAST_WRITE  0x11

/* Global registers on all pages */
#define BCM53XX_REG_DATAIO      0xF0
#define BCM53XX_REG_STATUS      0xFE
#define BCM53XX_REG_PAGE	0xFF

#define GPIO_SPI_CLK		484
#define GPIO_SPI_MOSI		485
#define GPIO_SPI_MISO		510
#define GPIO_SPI_CS		511

static u8 curr_page = 0xFF;

static struct gpio spi_gpios[] = {
	{ GPIO_SPI_CLK,  GPIOF_EXPORT | GPIOF_OUT_INIT_HIGH, "clk"  },
	{ GPIO_SPI_MOSI, GPIOF_EXPORT | GPIOF_OUT_INIT_LOW,  "mosi" },
	{ GPIO_SPI_CS,   GPIOF_EXPORT | GPIOF_OUT_INIT_HIGH, "cs"   },
	{ GPIO_SPI_MISO, GPIOF_EXPORT | GPIOF_IN,	     "miso" }
};

static u8 bcm53xx_read_byte(void)
{
	u8 value;
	int i;
	u8 temp;

	value = 0;
	for (i=0; i<8; i++) {
		gpio_set_value(GPIO_SPI_CLK, 0);
		temp = gpio_get_value(GPIO_SPI_MISO);
		value = (value << 1) | temp;
		gpio_set_value(GPIO_SPI_CLK, 1);
	}
	return value;
}

static void bcm53xx_write_byte(u8 value)
{
	int i;

	for (i=0; i<8; i++) {
		gpio_set_value(GPIO_SPI_CLK, 0);
		gpio_set_value(GPIO_SPI_MOSI, value & 0x80 ? 1 : 0);
		value <<= 1;
		gpio_set_value(GPIO_SPI_CLK, 1);
	}
	return;
}

static void bcm53xx_cmd_reg(u8 cmd, u8 reg)
{
	bcm53xx_write_byte(cmd);
	bcm53xx_write_byte(reg);
	return;
}

static int bcm53xx_read_reg(struct device *dev,
			    u8 page,
			    u8 base,
			    u8 len,
			    u64 *val)
{
	u8  ret;
	int tries;

	/* Change the page register if necessary */
	*val = 0;
	if (curr_page != page && base != BCM53XX_REG_PAGE) {
		gpio_set_value(GPIO_SPI_CS, 0);
		bcm53xx_cmd_reg(BCM53XX_CMD_FAST_WRITE, BCM53XX_REG_PAGE);
		bcm53xx_write_byte(page);
		gpio_set_value(GPIO_SPI_CS, 1);
		curr_page = page;
	}

	/* Set the register base address */
	gpio_set_value(GPIO_SPI_CS, 0);
	bcm53xx_cmd_reg(BCM53XX_CMD_FAST_READ, base);

	/* Read SPI status until RACK is set */
	for ( tries = 0; tries < 10 && base < BCM53XX_REG_DATAIO; tries++ ) {
		ret=bcm53xx_read_byte();
		if (ret & 1)
			break;
		msleep(50);
	}

	if (tries == 10)
		goto timeout;

	for (tries=0; tries < len; tries++) {
		ret = bcm53xx_read_byte();
		*val |= (u64)ret << (8*tries);
	}
	gpio_set_value(GPIO_SPI_CS, 1);

	return 0;

timeout:
	gpio_set_value(GPIO_SPI_CS, 0);
	bcm53xx_cmd_reg(BCM53XX_CMD_FAST_WRITE, BCM53XX_REG_PAGE);
	bcm53xx_write_byte(page);
	gpio_set_value(GPIO_SPI_CS, 1);
	curr_page = page;
	return EIO;
}

static int bcm53xx_write_reg(struct device *dev,
			     u8 page,
			     u8 base,
			     u8 len,
			     u64 val)
{
	int tries;

	/* Change the page register if necessary */
	if (curr_page != page && base != BCM53XX_REG_PAGE) {
		gpio_set_value(GPIO_SPI_CS, 0);
		bcm53xx_cmd_reg(BCM53XX_CMD_FAST_WRITE, BCM53XX_REG_PAGE);
		bcm53xx_write_byte(page);
		gpio_set_value(GPIO_SPI_CS, 1);
		curr_page = page;
	}

	/* Set the register base address and write the data */
	gpio_set_value(GPIO_SPI_CS, 0);
	bcm53xx_cmd_reg(BCM53XX_CMD_FAST_WRITE, base);
	for (tries=0; tries < len; tries++) {
		bcm53xx_write_byte(val & 0xFF);
		val >>= 8;
	}
	gpio_set_value(GPIO_SPI_CS, 1);
	if (base == BCM53XX_REG_PAGE)
		curr_page = page;
	return 0;
}

#define mk_bcm53xx_rw(_name, _page, _base, _len, _flags) \
	mk_bcm53xx_struct(_name, _page, _base, _len, _flags) \
	mk_bcm53xx_show(_prefix, _name) \
	mk_bcm53xx_store(_prefix, _name) \
	static struct device_attribute bcm53xx_##_name = \
		__ATTR(_name, S_IRUGO | S_IWUSR, \
		       bcm53xx_##_name##_show, bcm53xx_##_name##_store);

#define mk_bcm53xx_ro(_name, _page, _base, _len, _flags) \
	mk_bcm53xx_struct(_name, _page, _base, _len, _flags) \
	mk_bcm53xx_show(_prefix, _name) \
	static struct device_attribute bcm53xx_##_name = \
		__ATTR(_name, S_IRUGO, bcm53xx_##_name##_show, NULL);

#define mk_bcm53xx_struct(_name, _page, _base, _len, _flags) \
	static bcm53xx_addr_t bcm53xx_##_name##_addr = { \
		.name = #_name, \
		.flags = (_flags), \
		.len = (_len), \
		.page = (_page), \
		.base = (_base), \
	}; \

#define mk_bcm53xx_show(_prefix, _name) \
	static ssize_t \
	bcm53xx_##_name##_show(struct device *_dev, \
			       struct device_attribute *_dattr, \
			       char *_buf) \
	{ \
		return bcm53xx_show(_dev, _dattr, _buf, \
				    &bcm53xx_##_name##_addr); \
	} \

#define mk_bcm53xx_store(_prefix, _name) \
	static ssize_t \
	bcm53xx_##_name##_store(struct device *_dev, \
				struct device_attribute *_dattr, \
				const char *_buf, \
				size_t _size) \
	{ \
		return bcm53xx_store(_dev, _dattr, _buf, _size, \
				     &bcm53xx_##_name##_addr); \
	} \

static ssize_t bcm53xx_show(struct device *dev,
			    struct device_attribute *dattr,
			    char *buf,
			    bcm53xx_addr_t *addr)
{
	u64 val;
	int ret;
	int idx;

	ret = bcm53xx_read_reg(dev, addr->page, addr->base, addr->len, &val);
	if (ret) {
		return ret;
	}

	if (addr->flags & BCM53XX_COMPLEMENT)
		val = ~val;

	if (addr->flags & BCM53XX_BYTE_ARRAY) {
		ret = 0;
		for (idx=addr->len-1; idx; idx--, buf+=3)
			ret += sprintf(buf, "%02llx:", (val>>(idx*8)) & 0xFF);
		return sprintf(buf, "%02llx\n", val & 0xFF) + ret;
	}
	if (addr->flags & BCM53XX_DECIMAL)
		return sprintf(buf, "%lld\n", val);
	return sprintf(buf, "0x%0*llx\n", addr->len*2, val);
}

static int nibble2dec(char nibble)
{
	if (nibble >= '0' && nibble <= '9') {
		return nibble - '0';
	} else if (nibble >= 'A' && nibble <='F') {
		return nibble - 'A' + 10;
	} else if (nibble >= 'a' && nibble <= 'f') {
		return nibble - 'a' + 10;
	}
	return -EINVAL;
}

static ssize_t bcm53xx_store(struct device *dev,
			     struct device_attribute *dattr,
			     const char *buf,
			     size_t size,
			     bcm53xx_addr_t *addr)
{
	/* Write the register with the representation in buf */
	u64 val;
	int ret;
	int nibble;
	int state;

	if (addr->flags & BCM53XX_BYTE_ARRAY) {
		if (addr->len*3-1 != strlen(buf))
			return -EINVAL;
		for(state = 0, val = 0; *buf; state++, buf++) {
			if (state < 2) {
				nibble = nibble2dec(*buf);
				if (nibble < 0) return nibble;
				val = (val << 4) + nibble;
			} else {
				state = -1;
			}
		}
	} else {
		ret = kstrtou64(buf, 0, &val);
		if (ret)
			return ret;
	}

	if (addr->flags & BCM53XX_COMPLEMENT)
		val = ~val;

	ret = bcm53xx_write_reg(dev, addr->page, addr->base, addr->len, val);
	if (ret)
		return ret;

	return size;
}

//	      Name		Page  Base Len Flags
mk_bcm53xx_rw(port0Cntrl,       0x00, 0x00, 1, 0)
mk_bcm53xx_rw(port1Cntrl,       0x00, 0x01, 1, 0)
mk_bcm53xx_rw(port2Cntrl,       0x00, 0x02, 1, 0)
mk_bcm53xx_rw(port3Cntrl,       0x00, 0x03, 1, 0)
mk_bcm53xx_rw(port4Cntrl,       0x00, 0x04, 1, 0)
mk_bcm53xx_rw(port5Cntrl,       0x00, 0x05, 1, 0)
mk_bcm53xx_rw(port6Cntrl,       0x00, 0x06, 1, 0)
mk_bcm53xx_rw(port7Cntrl,       0x00, 0x07, 1, 0)
mk_bcm53xx_rw(port8Cntrl,       0x00, 0x08, 1, 0)
mk_bcm53xx_rw(port9Cntrl,       0x00, 0x09, 1, 0)
mk_bcm53xx_rw(port10Cntrl,      0x00, 0x0a, 1, 0)
mk_bcm53xx_rw(port11Cntrl,      0x00, 0x0b, 1, 0)
mk_bcm53xx_rw(port12Cntrl,      0x00, 0x0c, 1, 0)
mk_bcm53xx_rw(port13Cntrl,      0x00, 0x0d, 1, 0)
mk_bcm53xx_rw(port14Cntrl,      0x00, 0x0e, 1, 0)
mk_bcm53xx_rw(port15Cntrl,      0x00, 0x0f, 1, 0)
mk_bcm53xx_rw(switchMode89,     0x00, 0x0b, 1, 0)
mk_bcm53xx_rw(switchMode96,     0x00, 0x20, 1, 0)
mk_bcm53xx_rw(resMcastCntrl89,  0x00, 0x2f, 1, 0)
mk_bcm53xx_rw(resMcastCntrl96,  0x00, 0x50, 1, 0)
mk_bcm53xx_rw(extPhyCntrl,      0x00, 0x86, 1, 0)
mk_bcm53xx_rw(page,		0x00, 0xFF, 1, 0)
mk_bcm53xx_ro(modelId,		0x02, 0x30, 1, 0)
mk_bcm53xx_ro(revisionId,       0x02, 0x40, 1, 0)
mk_bcm53xx_rw(globalArlCfg,     0x04, 0x00, 1, 0)
mk_bcm53xx_rw(bpduMcastAddr,    0x04, 0x04, 6, BCM53XX_BYTE_ARRAY)
mk_bcm53xx_rw(multiportAddr1,   0x04, 0x10, 6, BCM53XX_BYTE_ARRAY)
mk_bcm53xx_rw(multiportVec1,    0x04, 0x16, 4, 0)
mk_bcm53xx_rw(vlanTblCntrl,     0x05, 0x60, 1, 0)
mk_bcm53xx_rw(vlanTblAddr,      0x05, 0x61, 2, 0)
mk_bcm53xx_rw(vlanTblEntry,     0x05, 0x63, 8, 0)
mk_bcm53xx_rw(port0SGMIICntrl,  0x10, 0x20, 2, 0)
mk_bcm53xx_rw(port1SGMIICntrl,  0x11, 0x20, 2, 0)
mk_bcm53xx_rw(port2SGMIICntrl,  0x12, 0x20, 2, 0)
mk_bcm53xx_rw(port3SGMIICntrl,  0x13, 0x20, 2, 0)
mk_bcm53xx_rw(port4SGMIICntrl,  0x14, 0x20, 2, 0)
mk_bcm53xx_rw(port5SGMIICntrl,  0x15, 0x20, 2, 0)
mk_bcm53xx_rw(port6SGMIICntrl,  0x16, 0x20, 2, 0)
mk_bcm53xx_rw(port7SGMIICntrl,  0x17, 0x20, 2, 0)
mk_bcm53xx_rw(port8SGMIICntrl,  0x18, 0x20, 2, 0)
mk_bcm53xx_rw(port9SGMIICntrl,  0x19, 0x20, 2, 0)
mk_bcm53xx_rw(port10SGMIICntrl, 0x1a, 0x20, 2, 0)
mk_bcm53xx_rw(port11SGMIICntrl, 0x1b, 0x20, 2, 0)
mk_bcm53xx_rw(port12SGMIICntrl, 0x1c, 0x20, 2, 0)
mk_bcm53xx_rw(port13SGMIICntrl, 0x1d, 0x20, 2, 0)
mk_bcm53xx_rw(port14SGMIICntrl, 0x1e, 0x20, 2, 0)
mk_bcm53xx_rw(port15SGMIICntrl, 0x1f, 0x20, 2, 0)
mk_bcm53xx_rw(dot1QGlbCntrl,    0x34, 0x00, 1, 0)
mk_bcm53xx_rw(port0Dot1QTag,    0x34, 0x10, 2, 0)
mk_bcm53xx_rw(port1Dot1QTag,    0x34, 0x12, 2, 0)
mk_bcm53xx_rw(port2Dot1QTag,    0x34, 0x14, 2, 0)
mk_bcm53xx_rw(port3Dot1QTag,    0x34, 0x16, 2, 0)
mk_bcm53xx_rw(port4Dot1QTag,    0x34, 0x18, 2, 0)
mk_bcm53xx_rw(port5Dot1QTag,    0x34, 0x1a, 2, 0)
mk_bcm53xx_rw(port6Dot1QTag,    0x34, 0x1c, 2, 0)
mk_bcm53xx_rw(port7Dot1QTag,    0x34, 0x1e, 2, 0)
mk_bcm53xx_rw(port8Dot1QTag,    0x34, 0x20, 2, 0)
mk_bcm53xx_rw(port9Dot1QTag,    0x34, 0x22, 2, 0)
mk_bcm53xx_rw(port10Dot1QTag,   0x34, 0x24, 2, 0)
mk_bcm53xx_rw(port11Dot1QTag,   0x34, 0x26, 2, 0)
mk_bcm53xx_rw(port12Dot1QTag,   0x34, 0x28, 2, 0)
mk_bcm53xx_rw(port13Dot1QTag,   0x34, 0x2a, 2, 0)
mk_bcm53xx_rw(port14Dot1QTag,   0x34, 0x2c, 2, 0)
mk_bcm53xx_rw(port15Dot1QTag,   0x34, 0x2e, 2, 0)
mk_bcm53xx_rw(port10AutoNegAdv, 0x8a, 0x08, 2, 0)

static struct attribute *bcm53xx_attrs[] = {
	&bcm53xx_port0Cntrl.attr,
	&bcm53xx_port1Cntrl.attr,
	&bcm53xx_port2Cntrl.attr,
	&bcm53xx_port3Cntrl.attr,
	&bcm53xx_port4Cntrl.attr,
	&bcm53xx_port5Cntrl.attr,
	&bcm53xx_port6Cntrl.attr,
	&bcm53xx_port7Cntrl.attr,
	&bcm53xx_port8Cntrl.attr,
	&bcm53xx_port9Cntrl.attr,
	&bcm53xx_port10Cntrl.attr,
	&bcm53xx_port11Cntrl.attr,
	&bcm53xx_port12Cntrl.attr,
	&bcm53xx_port13Cntrl.attr,
	&bcm53xx_port14Cntrl.attr,
	&bcm53xx_port15Cntrl.attr,
	&bcm53xx_switchMode89.attr,
	&bcm53xx_switchMode96.attr,
	&bcm53xx_resMcastCntrl89.attr,
	&bcm53xx_resMcastCntrl96.attr,
	&bcm53xx_extPhyCntrl.attr,
	&bcm53xx_page.attr,
	&bcm53xx_modelId.attr,
	&bcm53xx_revisionId.attr,
	&bcm53xx_globalArlCfg.attr,
	&bcm53xx_bpduMcastAddr.attr,
	&bcm53xx_multiportAddr1.attr,
	&bcm53xx_multiportVec1.attr,
	&bcm53xx_vlanTblCntrl.attr,
	&bcm53xx_vlanTblAddr.attr,
	&bcm53xx_vlanTblEntry.attr,
	&bcm53xx_port0SGMIICntrl.attr,
	&bcm53xx_port1SGMIICntrl.attr,
	&bcm53xx_port2SGMIICntrl.attr,
	&bcm53xx_port3SGMIICntrl.attr,
	&bcm53xx_port4SGMIICntrl.attr,
	&bcm53xx_port5SGMIICntrl.attr,
	&bcm53xx_port6SGMIICntrl.attr,
	&bcm53xx_port7SGMIICntrl.attr,
	&bcm53xx_port8SGMIICntrl.attr,
	&bcm53xx_port9SGMIICntrl.attr,
	&bcm53xx_port10SGMIICntrl.attr,
	&bcm53xx_port11SGMIICntrl.attr,
	&bcm53xx_port12SGMIICntrl.attr,
	&bcm53xx_port13SGMIICntrl.attr,
	&bcm53xx_port14SGMIICntrl.attr,
	&bcm53xx_port15SGMIICntrl.attr,
	&bcm53xx_dot1QGlbCntrl.attr,
	&bcm53xx_port0Dot1QTag.attr,
	&bcm53xx_port1Dot1QTag.attr,
	&bcm53xx_port2Dot1QTag.attr,
	&bcm53xx_port3Dot1QTag.attr,
	&bcm53xx_port4Dot1QTag.attr,
	&bcm53xx_port5Dot1QTag.attr,
	&bcm53xx_port6Dot1QTag.attr,
	&bcm53xx_port7Dot1QTag.attr,
	&bcm53xx_port8Dot1QTag.attr,
	&bcm53xx_port9Dot1QTag.attr,
	&bcm53xx_port10Dot1QTag.attr,
	&bcm53xx_port11Dot1QTag.attr,
	&bcm53xx_port12Dot1QTag.attr,
	&bcm53xx_port13Dot1QTag.attr,
	&bcm53xx_port14Dot1QTag.attr,
	&bcm53xx_port15Dot1QTag.attr,
	&bcm53xx_port10AutoNegAdv.attr,
	NULL
};

static struct attribute_group bcm53xx_attr_group = {
	.attrs = bcm53xx_attrs,
};

static int bcm53xx_probe(struct platform_device *dev)
{
	u64 modelid;
	u64 revisionid;
	int ret;
	int retries;

	/*
	 * Enable SPI access in CPLD1 and then allocate the GPIO which connect
	 * to the BCM53xx SPI pins. 
	 */
	ret = cpld1_enable_bcm_spi();
	if (ret) {
		dev_err(&dev->dev, "Unable to configure SPI mode in cpld1: %d\n", ret);
		goto err_return;
	}
	ret = gpio_request_array(spi_gpios, ARRAY_SIZE(spi_gpios));
	if (ret) {
		if (ret != -EPROBE_DEFER) {
			dev_err(&dev->dev, "Unable to get requested SPI GPIOs,: %d\n", ret); 
		}
		goto err_return;
	}

	/*
	 * Probe the hardware by reading the model id and revision numbers and
	 * checking them against what is expected on this platform.
	 */
	for (retries=0; retries < 20; retries++) {
		ret = bcm53xx_read_reg(&dev->dev, 0x02, 0x30, 1, &modelid);
		if (ret) {
			dev_err(&dev->dev, "Unable to read modelid: %d\n", ret);
			goto err_gpios;
		}
		if ((modelid == 0x96 && platform_variant == VAR_FCA) ||
		    (modelid == 0x89 && platform_variant == VAR_LCA)) {
			break;
		}
		dev_err(&dev->dev, "modelId is incorrect (%lld). Retrying in 5 msec \n", modelid);
		msleep(5);
	}
	if (retries == 20) {
		dev_err(&dev->dev,
			"BCM53XX model ID (%lld) is incorrect for this platform.\n",
			modelid);
		ret = -EINVAL;
		goto err_gpios;
	}
	ret = bcm53xx_read_reg(&dev->dev, 0x02, 0x40, 1, &revisionid);
	if (ret) {
		dev_err(&dev->dev, "Unable to read revisionid: %d\n", ret);
		goto err_gpios;
	}

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&dev->dev.kobj, &bcm53xx_attr_group);
	if (ret) {
		dev_err(&dev->dev, "sysfs_create_group failed\n");
		goto err_gpios;
	}

	/*
	 * All clear from this point on
	 */
	dev_info(&dev->dev,
		 "device created, BCM53xx model %lld revision %lld\n",
		 modelid, revisionid);

	return 0;

err_gpios:
	gpio_free_array(spi_gpios, ARRAY_SIZE(spi_gpios));
err_return:
	return(ret);
}

static int bcm53xx_remove(struct platform_device *dev)
{
	gpio_free_array(spi_gpios, ARRAY_SIZE(spi_gpios));
	sysfs_remove_group(&dev->dev.kobj, &bcm53xx_attr_group);
	dev_info(&dev->dev, "device removed\n");
	return 0;
}

static struct platform_driver bcm53xx_driver = {
	.driver = {
		.name = "bcm53xx",
		.owner = THIS_MODULE,
	},
	.probe = bcm53xx_probe,
	.remove = bcm53xx_remove,
};

static struct platform_device *bcm53xx_device;

static int __init accton_omp800_bcm53xx_init(void)
{
	int ret;

	if (platform_variant == VAR_FCA || platform_variant == VAR_LCA) {
		pr_info(DRIVER_NAME ": loading BCM53xx driver\n");

		ret = platform_driver_register(&bcm53xx_driver);
		if (ret) {
			pr_err("failed to register bcm53xx driver, err %d\n", ret);
			goto err_return;
		}

		bcm53xx_device = platform_device_alloc("bcm53xx", 0);
		if (!bcm53xx_device) {
			pr_err("failed to allocate bcm53xx device\n");
			ret = -ENOMEM;
			goto err_unregister;
		}

		ret = platform_device_add(bcm53xx_device);
		if (ret) {
			dev_err(&bcm53xx_device->dev,
				"bcm53xx platform_device_add failed with code %d\n",
				ret);
			goto err_device_put;
		}
	}

	return 0;

err_device_put:
	platform_device_put(bcm53xx_device);
err_unregister:
	platform_driver_unregister(&bcm53xx_driver);
err_return:
	return ret;

}

static void accton_omp800_bcm53xx_exit(void)
{
	struct platform_device *dev = bcm53xx_device;

	bcm53xx_device = NULL;
	if (platform_variant == VAR_FCA || platform_variant == VAR_LCA) {
		platform_device_unregister(dev);
		platform_driver_unregister(&bcm53xx_driver);
		pr_info(DRIVER_NAME ": BCM53xx driver unloaded\n");
	}
}

/*
 * Module init and exit
 */

static int __init accton_omp800_init(void)
{
	int ret;
	char **p;

	/*
	 * Process module parameter(s)
	 *
	 * There is currently only one: the platform name.
	 * We match that to the known ones to compute the platform
	 * variant we are running on.
	 */
	if (!platform) {
		pr_err(DRIVER_NAME
		       ": \"platform\" parameter must be specified.\n");
		return -EINVAL;
	}
	for (p = platform_names; *p != NULL; p++)
		if (strcmp(*p, platform) == 0)
			break;
	if (!*p) {
		pr_err(DRIVER_NAME
		       ": \"platform\" parameter \"%s\" not recognized.\n",
		       platform);
		return -EINVAL;
	}
	platform_variant = p - platform_names;

	/*
	 * Initialize the CPLD drivers first, because they are I2C devices and
	 * will get instantiated along with the other I2C devices in
	 * accton_omp800_i2c_init().
	 */
	ret = accton_omp800_cpld23_init();
	if (ret) {
		pr_err("QSFP CPLD initialization failed\n");
		goto err_return;
	}

	ret = accton_omp800_cpld1_init();
	if (ret) {
		pr_err("System CPLD initialization failed\n");
		goto err_cpld1;
	}

	ret = accton_omp800_fancpld_init();
	if (ret) {
		pr_err("Fan CPLD initialization failed\n");
		goto err_fan;
	}

	ret = accton_omp800_pducpld_init();
	if (ret) {
		pr_err("PDU CPLD initialization failed\n");
		goto err_pdu;
	}

	ret = accton_omp800_adm1278_init();
	if (ret) {
		pr_err("adm1278 initialization failed\n");
		goto err_adm1278;
	}

	ret = accton_omp800_i2c_init();
	if (ret) {
		pr_err("I2C initialization failed\n");
		goto err_i2c;
	}

	ret = accton_omp800_bcm53xx_init();
	if (ret) {
		pr_err("BCM53xx initialization failed\n");
		goto err_bcm53xx;
	}

	pr_info(DRIVER_NAME ": version " DRIVER_VERSION
		" successfully loaded\n");
	return 0;

err_bcm53xx:
	accton_omp800_i2c_exit();
err_i2c:
	accton_omp800_adm1278_exit();
err_adm1278:
	accton_omp800_pducpld_exit();
err_pdu:
	accton_omp800_fancpld_exit();
err_fan:
	accton_omp800_cpld1_exit();
err_cpld1:
	accton_omp800_cpld23_exit();
err_return:
	return ret;
}

static void __exit accton_omp800_exit(void)
{
	accton_omp800_bcm53xx_exit();
	accton_omp800_i2c_exit();
	accton_omp800_adm1278_exit();
	accton_omp800_pducpld_exit();
	accton_omp800_fancpld_exit();
	accton_omp800_cpld1_exit();
	accton_omp800_cpld23_exit();
	pr_info(DRIVER_NAME ": driver unloaded\n");
}

module_init(accton_omp800_init);
module_exit(accton_omp800_exit);

MODULE_AUTHOR("Scott Emery (scotte@cumulusnetworks.com)");
MODULE_DESCRIPTION("Accton OMP800 Platform Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
