/*
 * cel_bigstone_g_platform.c -
 *      Celestica BigStone-G Platform Support.
 *
 * Copyright (C) 2016 Cumulus Networks, Inc.
 * Author: Ellen Wang (ellen@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	"cel_bigstone_g_platform"
#define DRIVER_VERSION	"0.1"

/*
 * There are (so far) three platform variants:
 *   line card left half
 *   line card right half
 *   fabric card
 * 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_LC1L,
	VAR_LC1R,
	VAR_FAB1,
	NUM_VARIANTS
};

static char *platform_names[NUM_VARIANTS + 1] = {
	[VAR_LC1L] = "cel_bigstone_g_lc1l",
	[VAR_LC1R] = "cel_bigstone_g_lc1r",
	[VAR_FAB1] = "cel_bigstone_g_fab1",
	[NUM_VARIANTS] = NULL
};

static int platform_variant = VAR_UNKNOWN;

/*
 * This platform has one CP2112 USB-to-I2C bridge, and possibly
 * other I2C buses that we don't care about.
 *
 * On either half of the line card, the CP2112 i2c bus has
 * an at24c64 EEPROM, the QSFP CPLD behind a pca9541 I2C master
 * switch, and two pca9548 (8-port) I2C switches that go to 16 QSFP
 * slots.
 *
 * 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).
 *
 * On a fabric card, there is only the EEPROM.
 */

enum {
	CELBSG_I2C_CP2112_BUS = -1,

	CELBSG_I2C_MUX1_BUS0 = 10,
	CELBSG_I2C_MUX1_BUS1,
	CELBSG_I2C_MUX1_BUS2,
	CELBSG_I2C_MUX1_BUS3,
	CELBSG_I2C_MUX1_BUS4,
	CELBSG_I2C_MUX1_BUS5,
	CELBSG_I2C_MUX1_BUS6,
	CELBSG_I2C_MUX1_BUS7,

	CELBSG_I2C_MUX2_BUS0 = 20,
	CELBSG_I2C_MUX2_BUS1,
	CELBSG_I2C_MUX2_BUS2,
	CELBSG_I2C_MUX2_BUS3,
	CELBSG_I2C_MUX2_BUS4,
	CELBSG_I2C_MUX2_BUS5,
	CELBSG_I2C_MUX2_BUS6,
	CELBSG_I2C_MUX2_BUS7,

	CELBSG_I2C_MUX3_BUS = 30,
};

/*
 * The list of I2C devices and their bus connections for this platform.
 *
 * 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 but (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 three platform variants, each with
 * its own platform_i2c_device_info table.
 */

/* line card halves (lc1l, lc1r) have these muxes */
mk_pca9548(mux1, CELBSG_I2C_MUX1_BUS0, 1)
mk_pca9548(mux2, CELBSG_I2C_MUX2_BUS0, 1)
mk_pca9541(mux3, CELBSG_I2C_MUX3_BUS)

/* all three have an EEPROM */
mk_eeprom(board, 52, 8192, AT24_FLAG_ADDR16 | AT24_FLAG_IRUGO)

/* lc1 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)

static struct platform_i2c_device_info i2c_devices_lc1[] = {
	/*
	 * devices on CP2112 bus
	 *
	 * List devices in an order that makes the main EEPROM eeprom0,
	 * and the port EEPROMs in port order.  Not required, just nice.
	 */
	mk_i2cdev(CELBSG_I2C_CP2112_BUS, "24c64",   0x52, &board_52_at24),
	mk_i2cdev(CELBSG_I2C_CP2112_BUS, "pca9548", 0x70, &mux1_platform_data),
	mk_i2cdev(CELBSG_I2C_CP2112_BUS, "pca9548", 0x71, &mux2_platform_data),
	mk_i2cdev(CELBSG_I2C_CP2112_BUS, "pca9541", 0x74, &mux3_platform_data),

	/* devices on pca9548 1 */
	mk_i2cdev(CELBSG_I2C_MUX1_BUS0, "sff8436", 0x50, &port1_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS1, "sff8436", 0x50, &port2_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS2, "sff8436", 0x50, &port3_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS3, "sff8436", 0x50, &port4_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS4, "sff8436", 0x50, &port5_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS5, "sff8436", 0x50, &port6_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS6, "sff8436", 0x50, &port7_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX1_BUS7, "sff8436", 0x50, &port8_50_sff8436),

	/* devices on pca9548 2 */
	mk_i2cdev(CELBSG_I2C_MUX2_BUS0, "sff8436", 0x50, &port9_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS1, "sff8436", 0x50, &port10_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS2, "sff8436", 0x50, &port11_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS3, "sff8436", 0x50, &port12_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS4, "sff8436", 0x50, &port13_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS5, "sff8436", 0x50, &port14_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS6, "sff8436", 0x50, &port15_50_sff8436),
	mk_i2cdev(CELBSG_I2C_MUX2_BUS7, "sff8436", 0x50, &port16_50_sff8436),

	/* devices on pca9541 */
	mk_i2cdev(CELBSG_I2C_MUX3_BUS, "qsfpcpld", 0x39, NULL),

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

static struct platform_i2c_device_info i2c_devices_fab1[] = {
	/* devices on CP2112 bus */
	mk_i2cdev(CELBSG_I2C_CP2112_BUS, "24c64",   0x52, &board_52_at24),

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

static struct platform_i2c_device_info *i2c_devices[NUM_VARIANTS] = {
	[VAR_LC1L] = i2c_devices_lc1,
	[VAR_LC1R] = i2c_devices_lc1,
	[VAR_FAB1] = i2c_devices_fab1,
};

/*
 * 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 < CELBSG_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 cp2112_bus;
	struct platform_i2c_device_info *info;
	int ret;

	cp2112_bus = get_bus_by_name("CP2112 SMBus Bridge");
	if (cp2112_bus < 0) {
		pr_err("could not find CP2112 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;

		if (bus == CELBSG_I2C_CP2112_BUS)
			bus = cp2112_bus;
		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, including GPIO
 */

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

static int __init cel_bigstone_g_i2c_init(void)
{
	int ret;

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

	return 0;

err_exit:
	free_all();
	return ret;
}

static void cel_bigstone_g_i2c_exit(void)
{
	free_all();
}

/*
 * QSFP CPLD driver
 */

#define QSFPCPLD_NREGS 0x2a

#define qsfpcpld_read_reg bf_i2c_read_reg
#define qsfpcpld_write_reg bf_i2c_write_reg

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

mk_bf_ro(qsfpcpld, cpld_revision,	0x01, 0, 6,  NULL, BF_DECIMAL)
mk_bf_ro(qsfpcpld, cpld_released,	0x01, 6, 1,  NULL, 0)
mk_bf_ro(qsfpcpld, cpld_subversion,	0x02, 0, 8,  NULL, BF_DECIMAL)
mk_bf_rw(qsfpcpld, led_test_mode,	0x09, 0, 1,  NULL, 0)
mk_bf_rw(qsfpcpld, led_test_color,	0x10, 0, 3,  cpld_rgb, BF_COMPLEMENT)
mk_bf_rw(qsfpcpld, led_test_red,	0x10, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(qsfpcpld, led_test_green,	0x10, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(qsfpcpld, led_test_blue,	0x10, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(qsfpcpld, qsfp_reset,		0x10, 0, 16, NULL, BF_COMPLEMENT)
mk_bf_rw(qsfpcpld, qsfp_lpmode,		0x12, 0, 16, NULL, 0)
mk_bf_ro(qsfpcpld, qsfp_interrupt,	0x14, 0, 16, NULL, BF_COMPLEMENT)
mk_bf_ro(qsfpcpld, qsfp_present,	0x16, 0, 16, NULL, BF_COMPLEMENT)

struct attribute *qsfpcpld_attrs[] = {
	&qsfpcpld_cpld_revision.attr,
	&qsfpcpld_cpld_released.attr,
	&qsfpcpld_cpld_subversion.attr,
	&qsfpcpld_led_test_mode.attr,
	&qsfpcpld_led_test_color.attr,
	&qsfpcpld_led_test_red.attr,
	&qsfpcpld_led_test_green.attr,
	&qsfpcpld_led_test_blue.attr,
	&qsfpcpld_qsfp_reset.attr,
	&qsfpcpld_qsfp_lpmode.attr,
	&qsfpcpld_qsfp_interrupt.attr,
	&qsfpcpld_qsfp_present.attr,
	NULL
};

static struct attribute_group qsfpcpld_attr_group = {
	.attrs = qsfpcpld_attrs,
};

static int qsfpcpld_probe(struct i2c_client *client,
			  const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	u8 revision;
	u8 subversion;
	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 numbers.
	 */
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD revision register error %d\n", ret);
		goto err;
	}
	revision = ret;
	ret = i2c_smbus_read_byte_data(client, 2);
	if (ret < 0) {
		dev_err(&client->dev,
			"read CPLD subversion register error %d\n", ret);
		goto err;
	}
	subversion = ret;

	/*
	 * Create sysfs nodes.
	 */
	ret = sysfs_create_group(&client->dev.kobj, &qsfpcpld_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 rev %d %s subversion %d\n",
		 revision & 0x3f,
		 revision & 0x40 ? "released" : "unreleased",
		 subversion);

#ifdef DEBUG
	for (i = 0; i < QSFPCPLD_NREGS; i++) {
		ret = i2c_smbus_read_byte_data(client, i);
		dev_dbg(&client->dev,
			ret < 0 ? "CPLD[%d] read error %d\n" :
				  "CPLD[%d] %#04x\n",
			i, ret);
	}
#endif

	return 0;

err:
	return ret;
}

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

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

static struct i2c_driver qsfpcpld_driver = {
	.driver = {
		.name = "qsfpcpld",
		.owner = THIS_MODULE,
	},
	.probe = qsfpcpld_probe,
	.remove = qsfpcpld_remove,
	.id_table = qsfpcpld_id,
};

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

static void cel_bigstone_g_qsfpcpld_exit(void)
{
	i2c_del_driver(&qsfpcpld_driver);
	pr_info(DRIVER_NAME " QSFP CPLD driver unloaded\n");
}

/*
 * System CPLD driver
 *
 * The system CPLD is on the LPC bus, so it appears directly
 * in the x86 IO address space.
 */

#define SYSCPLD_BASE 0x100

static int syscpld_read_reg(struct device *dev,
			    int reg,
			    int nregs,
			    u32 *val)
{
	int nbits = nregs * 8;
	int bit;

	reg += SYSCPLD_BASE;
	*val = 0;
	for (bit = 0; bit < nbits; bit += 8, reg++)
		*val |= inb(reg) << bit;

	return 0;
}

static int syscpld_write_reg(struct device *dev,
			     int reg,
			     int nregs,
			     u32 val)
{
	reg += SYSCPLD_BASE;
	for (; nregs > 0; nregs--, reg++, val >>= 8)
		outb(val, reg);

	return 0;
}

static const char * const syscpld_models[] = {
	"bigstone-g-lc-type1",
	"unknown1",
	"unknown2",
	"unknown3",
	"bigstone-g-fab-type1",
	"unknown5",
	"unknown6",
	"unknown7",
	"unknown8",
	"unknown9",
	"unknown10",
	"unknown11",
	"bigstone-g-ccm-type1",
	"unknown13",
	"unknown14",
	"unknown15",
};

static const char * const syscpld_slots[] = {
	"lc1",
	"lc2",
	"lc3",
	"lc4",
	"lc5",
	"lc6",
	"lc7",
	"lc8",
	"fab1",
	"fab2",
	"fab3",
	"fab4",
	"unknown12",
	"unknown13",
	"unknown14",
	"unknown15",
};

static const char * const syscpld_swes[] = {
	"left", "right", "unknown2", "unknown3",
};

static const char * const syscpld_cmm[] = {
	"master", "slave",
};

mk_bf_ro(syscpld, board_revision,	0x00, 0, 4,  NULL, BF_DECIMAL)
mk_bf_ro(syscpld, board_model_id,	0x00, 4, 4,  NULL, 0)
mk_bf_ro(syscpld, board_model,		0x00, 4, 4,  syscpld_models, 0)
mk_bf_ro(syscpld, cpld_revision,	0x01, 0, 6,  NULL, BF_DECIMAL)
mk_bf_ro(syscpld, cpld_released,	0x01, 6, 1,  NULL, 0)
mk_bf_ro(syscpld, cpld_subversion,	0x02, 0, 8,  NULL, BF_DECIMAL)
mk_bf_ro(syscpld, slot_id,		0x03, 0, 4,  NULL, 0)
mk_bf_ro(syscpld, slot,			0x03, 0, 4,  syscpld_slots, 0)
mk_bf_ro(syscpld, swe_id,		0x05, 0, 2,  NULL, 0)
mk_bf_ro(syscpld, swe,			0x05, 0, 2,  syscpld_swes, 0)
mk_bf_rw(syscpld, led_color,		0x06, 0, 3,  cpld_rgb, 0)
mk_bf_rw(syscpld, led_red,		0x06, 0, 1,  NULL, 0)
mk_bf_rw(syscpld, led_green,		0x06, 1, 1,  NULL, 0)
mk_bf_rw(syscpld, led_blue,		0x06, 2, 1,  NULL, 0)
mk_bf_rw(syscpld, led_blink,		0x06, 3, 1,  NULL, 0)
mk_bf_rw(syscpld, peer_present,		0x09, 0, 4,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, peer1_present,	0x09, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, peer2_present,	0x09, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, peer3_present,	0x09, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, peer4_present,	0x09, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_cp2112,		0x14, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_usb_hub,	0x14, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_bcm5389,	0x14, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_switch_phy,	0x14, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_bcm54616s,	0x14, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_scm_cpld_i2c_slave, 0x14, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(syscpld, reset_lpc_interface,	0x14, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(syscpld, cmm1_status,		0x60, 2, 1,  syscpld_cmm, 0)
mk_bf_ro(syscpld, cmm2_status,		0x60, 3, 1,  syscpld_cmm, 0)

struct attribute *syscpld_attrs[] = {
	&syscpld_board_revision.attr,
	&syscpld_board_model_id.attr,
	&syscpld_board_model.attr,
	&syscpld_cpld_revision.attr,
	&syscpld_cpld_released.attr,
	&syscpld_cpld_subversion.attr,
	&syscpld_slot_id.attr,
	&syscpld_slot.attr,
	&syscpld_swe_id.attr,
	&syscpld_swe.attr,
	&syscpld_led_color.attr,
	&syscpld_led_red.attr,
	&syscpld_led_green.attr,
	&syscpld_led_blue.attr,
	&syscpld_led_blink.attr,
	&syscpld_peer_present.attr,
	&syscpld_peer1_present.attr,
	&syscpld_peer2_present.attr,
	&syscpld_peer3_present.attr,
	&syscpld_peer4_present.attr,
	&syscpld_reset_cp2112.attr,
	&syscpld_reset_usb_hub.attr,
	&syscpld_reset_bcm5389.attr,
	&syscpld_reset_switch_phy.attr,
	&syscpld_reset_bcm54616s.attr,
	&syscpld_reset_scm_cpld_i2c_slave.attr,
	&syscpld_reset_lpc_interface.attr,
	&syscpld_cmm1_status.attr,
	&syscpld_cmm2_status.attr,
	NULL
};

static struct attribute_group syscpld_attr_group = {
	.attrs = syscpld_attrs,
};

static int syscpld_probe(struct platform_device *dev)
{
	u8 revision;
	u8 subversion;
	int ret;

	revision = 0;
	subversion = 0;

	/*
	 * Probe the hardware by reading the revision numbers.
	 * OK, This is not really a good test.
	 */
	revision = inb(SYSCPLD_BASE + 1);
	subversion = inb(SYSCPLD_BASE + 2);

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

	/*
	 * All clear from this point on
	 */
	dev_info(&dev->dev,
		 "device created, system CPLD rev %d %s subversion %d\n",
		 revision & 0x3f,
		 revision & 0x40 ? "released" : "unreleased",
		 subversion);

	return 0;
}

static int syscpld_remove(struct platform_device *dev)
{
	sysfs_remove_group(&dev->dev.kobj, &syscpld_attr_group);
	dev_info(&dev->dev, "device removed\n");
	return 0;
}

static struct platform_driver syscpld_driver = {
	.driver = {
		.name = "syscpld",
		.owner = THIS_MODULE,
	},
	.probe = syscpld_probe,
	.remove = syscpld_remove,
};

static struct platform_device *syscpld_device;

static int __init cel_bigstone_g_syscpld_init(void)
{
	int ret;

	pr_info(DRIVER_NAME " loading system CPLD driver\n");

	ret = platform_driver_register(&syscpld_driver);
	if (ret)
		return ret;

	syscpld_device = platform_device_alloc("syscpld", 0);
	if (!syscpld_device) {
		platform_driver_unregister(&syscpld_driver);
		return -ENOMEM;
	}

	ret = platform_device_add(syscpld_device);
	if (ret) {
		platform_device_put(syscpld_device);
		platform_driver_unregister(&syscpld_driver);
		return ret;
	}

	return 0;
}

static void cel_bigstone_g_syscpld_exit(void)
{
	struct platform_device *dev = syscpld_device;

	syscpld_device = NULL;
	platform_device_unregister(dev);
	platform_driver_unregister(&syscpld_driver);
	pr_info(DRIVER_NAME " system CPLD driver unloaded\n");
}

/*
 * Module init and exit
 */

static int __init cel_bigstone_g_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 QSFP CPLD driver first, because it's an I2C
	 * device and will get instantiated along with the other
	 * I2C devices in cel_bigstone_g_i2c_init().
	 */
	ret = cel_bigstone_g_qsfpcpld_init();
	if (ret) {
		pr_err("QSFP CPLD initialization failed\n");
		return ret;
	}

	ret = cel_bigstone_g_i2c_init();
	if (ret) {
		pr_err("I2C initialization failed\n");
		cel_bigstone_g_qsfpcpld_exit();
		return ret;
	}

	ret = cel_bigstone_g_syscpld_init();
	if (ret) {
		pr_err("system CPLD initialization failed\n");
		cel_bigstone_g_i2c_exit();
		cel_bigstone_g_qsfpcpld_exit();
		return ret;
	}

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

static void __exit cel_bigstone_g_exit(void)
{
	cel_bigstone_g_syscpld_exit();
	cel_bigstone_g_i2c_exit();
	cel_bigstone_g_qsfpcpld_exit();
	pr_info(DRIVER_NAME " driver unloaded\n");
}

module_init(cel_bigstone_g_init);
module_exit(cel_bigstone_g_exit);

MODULE_AUTHOR("Ellen Wang (ellen@cumulusnetworks.com)");
MODULE_DESCRIPTION("Celestica BigStone-G Platform Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
