/*
 * Accton AS5835-54T Platform Support
 *
 * Author: David Yen <dhyen@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/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/platform_data/at24.h>
#include <linux/i2c/sff-8436.h>
#include <linux/i2c/pca954x.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/cumulus-platform.h>

#include "platform_defs.h"
#include "accton_as5835.h"

#define DRIVER_NAME    AS5835_54T_PLATFORM_NAME
#define DRIVER_VERSION "1.0"

/*
 * The platform has 2 types of I2C SMBUSes:
 *   i2c-0 = SMBus iSMT adapter
 *   i2c-1 = SMBus I801 adapter
 */

enum {
	I2C_I801_BUS = -1,
	I2C_ISMT_BUS,

	/* mux at 0x77 */
	I2C_MUX1_BUS0 = 10, /* Main Board CH1 */
	I2C_MUX1_BUS1,      /* Main Board CH2 */
	I2C_MUX1_BUS2,
	I2C_MUX1_BUS3,
	I2C_MUX1_BUS4,
	I2C_MUX1_BUS5,
	I2C_MUX1_BUS6,
	I2C_MUX1_BUS7,

	/* mux at 0x77.0 -> 0x70 */
	I2C_MUX2_BUS0,
	I2C_MUX2_BUS1,
	I2C_MUX2_BUS2,
	I2C_MUX2_BUS3,
	I2C_MUX2_BUS4,
	I2C_MUX2_BUS5,
	I2C_MUX2_BUS6,
	I2C_MUX2_BUS7,

	/* mux at 0x77.0 -> 0x72 */
	I2C_MUX3_BUS0,
	I2C_MUX3_BUS1,
	I2C_MUX3_BUS2,
	I2C_MUX3_BUS3,
	I2C_MUX3_BUS4,
	I2C_MUX3_BUS5,
	I2C_MUX3_BUS6,
	I2C_MUX3_BUS7,

	/* mux at 0x77.0 -> 0x71 */
	I2C_MUX4_BUS0,
	I2C_MUX4_BUS1,
	I2C_MUX4_BUS2,
	I2C_MUX4_BUS3,
};

mk_pca9548(mux1,  I2C_MUX1_BUS0,  1)
mk_pca9548(mux2,  I2C_MUX2_BUS0,  1)
mk_pca9548(mux3,  I2C_MUX3_BUS0,  1)
mk_pca9545(mux4,  I2C_MUX4_BUS0,  1)

mk_eeprom(board, 57, 256, AT24_FLAG_IRUGO)

mk_qsfp_port_eeprom(port49, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port50, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port51, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port52, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port53, 50, 256, SFF_8436_FLAG_IRUGO)
mk_qsfp_port_eeprom(port54, 50, 256, SFF_8436_FLAG_IRUGO)

static struct platform_i2c_device_info i2c_devices[] = {
	mk_i2cdev(I2C_ISMT_BUS,  "24c02",          0x57, &board_57_at24),
	mk_i2cdev(I2C_ISMT_BUS,  "as5835_cpucpld", 0x65, NULL),
	mk_i2cdev(I2C_ISMT_BUS,  "pca9548",        0x77, &mux1_platform_data),

	mk_i2cdev(I2C_MUX1_BUS0, "pca9548",        0x70, &mux2_platform_data),
	mk_i2cdev(I2C_MUX1_BUS0, "pca9548",        0x72, &mux3_platform_data),
	mk_i2cdev(I2C_MUX3_BUS2, "sff8436",        0x50, &port49_50_sff8436),
	mk_i2cdev(I2C_MUX3_BUS3, "sff8436",        0x50, &port50_50_sff8436),
	mk_i2cdev(I2C_MUX3_BUS0, "sff8436",        0x50, &port51_50_sff8436),
	mk_i2cdev(I2C_MUX3_BUS4, "sff8436",        0x50, &port52_50_sff8436),
	mk_i2cdev(I2C_MUX3_BUS5, "sff8436",        0x50, &port53_50_sff8436),
	mk_i2cdev(I2C_MUX3_BUS1, "sff8436",        0x50, &port54_50_sff8436),

	mk_i2cdev(I2C_MUX1_BUS0, "pca9545",        0x71, &mux4_platform_data),
	mk_i2cdev(I2C_MUX4_BUS0, "tmp75",          0x4b, NULL),
	mk_i2cdev(I2C_MUX4_BUS1, "tmp75",          0x4c, NULL),
	mk_i2cdev(I2C_MUX4_BUS2, "tmp75",          0x49, NULL),
	mk_i2cdev(I2C_MUX4_BUS3, "tmp75",          0x4a, NULL),

	mk_i2cdev(I2C_MUX1_BUS1, "as5835_cpld1",   0x60, NULL),
	mk_i2cdev(I2C_MUX1_BUS1, "as5835_cpld2",   0x61, NULL),
	mk_i2cdev(I2C_MUX1_BUS1, "as5835_cpld3",   0x62, NULL),
	mk_i2cdev(I2C_MUX1_BUS1, "as5835_cpld4",   0x63, NULL),
};

/* I2C init */

static void free_i2c_devices(void)
{
	int i;

	for (i = ARRAY_SIZE(i2c_devices); --i >= 0;) {
		if (i2c_devices[i].client) {
			i2c_unregister_device(i2c_devices[i].client);
			i2c_devices[i].client = NULL;
		}
	}
}

static int init_i2c_devices(void)
{
	int ismt_bus;
	int i801_bus;
	int i = 0;
	int ret;

	ret = -1;
	ismt_bus = cumulus_i2c_find_adapter(ISMT_ADAPTER_NAME);
	if (ismt_bus < 0) {
		pr_err(DRIVER_NAME ": could not find the iSMT adapter bus\n");
		ret = -ENXIO;
		goto err_exit;
	}
	i801_bus = cumulus_i2c_find_adapter(I801_ADAPTER_NAME);
	if (i801_bus < 0) {
		pr_err(DRIVER_NAME ": could not find the i801 adapter bus\n");
		ret = -ENODEV;
		goto err_exit;
	}

	for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
		int bus = i2c_devices[i].bus;
		struct i2c_client *client;

		/* skip this client if it's already been added */
		if (i2c_devices[i].client)
			continue;

		if (bus == I2C_ISMT_BUS)
			bus = ismt_bus;
		if (bus == I2C_I801_BUS)
			bus = i801_bus;
		client = cumulus_i2c_add_client(bus,
						&i2c_devices[i].board_info);
		if (IS_ERR(client)) {
			ret = PTR_ERR(client);
			goto err_exit;
		};
		i2c_devices[i].client = client;
	}
	return 0;

err_exit:
	while (--i >= 0) {
		if (i2c_devices[i].client) {
			i2c_unregister_device(i2c_devices[i].client);
			i2c_devices[i].client = NULL;
		}
	}
	return ret;
}

static int i2c_init(void)
{
	int ret;

	ret = init_i2c_devices();
	if (ret) {
		pr_err(DRIVER_NAME ": i2c subsystem initialization failed\n");
		return ret;
	}

	return 0;
}

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

/* platform driver */

static int platform_probe(struct platform_device *dev)
{
	int ret;

	ret = i2c_init();
	if (ret) {
		dev_err(&dev->dev, "i2c initialization failed (%d)\n", ret);
		return ret;
	}

	pr_info(DRIVER_NAME ": probe completed\n");
	return 0;
}

static int platform_remove(struct platform_device *dev)
{
	i2c_exit();
	return 0;
}

static const struct platform_device_id platform_id[] = {
	{ DRIVER_NAME, 0 },
	{ }
};

MODULE_DEVICE_TABLE(platform, platform_id);

static struct platform_driver platform_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	},
	.probe = platform_probe,
	.remove = platform_remove,
	.id_table = platform_id
};

static struct platform_device *plat_device;

/* module init/exit */

static int __init platform_init(void)
{
	int ret = 0;

	/* register the platform driver */
	if (!driver_find(platform_driver.driver.name, &platform_bus_type)) {
		ret = platform_driver_register(&platform_driver);
		if (ret) {
			pr_err(DRIVER_NAME ": %s driver registration failed (%d)\n",
			       platform_driver.driver.name, ret);
			return ret;
		}
	}

	/* create the platform device */
	if (!plat_device) {
		plat_device = platform_device_register_simple(DRIVER_NAME, -1,
							      NULL, 0);
		if (IS_ERR(plat_device)) {
			ret = PTR_ERR(plat_device);
			plat_device = NULL;
			pr_err(DRIVER_NAME ": platform device registration failed (%d)\n",
			       ret);
			platform_driver_unregister(&platform_driver);
			return ret;
		}
	}

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

static void __exit platform_exit(void)
{
	platform_device_unregister(plat_device);
	plat_device = NULL;
	platform_driver_unregister(&platform_driver);
	pr_info(DRIVER_NAME ": driver unloaded\n");
}

module_init(platform_init);
module_exit(platform_exit);

MODULE_AUTHOR("David Yen (dhyen@cumulusnetworks.com)");
MODULE_DESCRIPTION("Accton AS5835-54T Platform Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
