/*
 * delta_ag9032v1_platform.c - delta-ag9032v1 Platform Support.
 *
 * Copyright (C) 2016, 2017 Cumulus Networks, Inc.  All Rights Reserved.
 * Author: Alan Liebthal (alanl@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/gpio.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/i2c-ismt.h>
#include <linux/i2c-mux.h>
#include <linux/i2c-mux-gpio.h>
#include <linux/platform_device.h>
#include <linux/platform_data/at24.h>
#include <linux/platform_data/pca953x.h>
#include <linux/i2c/sff-8436.h>
#include <linux/i2c/pca954x.h>

#include "platform_defs.h"
#include "delta_ag9032v1_cpld.h"

#define DRIVER_NAME        "delta_ag9032v1_platform"
#define DRIVER_VERSION     "0.2"

/*---------------------------------------------------------------------
 *
 * Platform driver
 *
 *-------------------------------------------------------------------*/
static struct i2c_client *delta_ag9032v1_cpld_clients[NUM_CPLD_I2C_CLIENTS];

/*
 * This platform has two i2c busses:
 *  SMBus_0: SMBus I801 adapter at PCIe address 0000:00:1f.3
 *  SMBus_1: SMBus iSMT adapter at PCIe address 0000:00:13.0
 */

/* i2c bus adapter numbers for the down stream i2c busses */
enum {
	I2C_ISMT_BUS = 0,
	I2C_I801_BUS,
	I2C_MUX1_BUS0 = 10,
	I2C_MUX1_BUS1,
	I2C_MUX1_BUS2,
	I2C_MUX1_BUS3,
	I2C_MUX1_BUS4,
	I2C_MUX1_BUS5,
	I2C_MUX1_BUS6,
	I2C_MUX1_BUS7,
	CPLD_FAN_MUX_BUS0 = 20,
	CPLD_FAN_MUX_BUS1,
	CPLD_FAN_MUX_BUS2,
	CPLD_FAN_MUX_BUS3,
	CPLD_FAN_MUX_BUS4,
	CPLD_FAN_MUX_BUS5,
	CPLD_FAN_MUX_BUS6,
	CPLD_FAN_MUX_BUS7,
	CPLD_PSU_MUX_BUS0 = 30,
	CPLD_PSU_MUX_BUS1,
	CPLD_PSU_MUX_BUS2,
	CPLD_PSU_MUX_BUS3,
	CPLD_QSFP_MUX_BUS0 = 40,
	CPLD_QSFP_MUX_BUS1,
	CPLD_QSFP_MUX_BUS2,
	CPLD_QSFP_MUX_BUS3,
	CPLD_QSFP_MUX_BUS4,
	CPLD_QSFP_MUX_BUS5,
	CPLD_QSFP_MUX_BUS6,
	CPLD_QSFP_MUX_BUS7,
	CPLD_QSFP_MUX_BUS8,
	CPLD_QSFP_MUX_BUS9,
	CPLD_QSFP_MUX_BUS10,
	CPLD_QSFP_MUX_BUS11,
	CPLD_QSFP_MUX_BUS12,
	CPLD_QSFP_MUX_BUS13,
	CPLD_QSFP_MUX_BUS14,
	CPLD_QSFP_MUX_BUS15,
	CPLD_QSFP_MUX_BUS16,
	CPLD_QSFP_MUX_BUS17,
	CPLD_QSFP_MUX_BUS18,
	CPLD_QSFP_MUX_BUS19,
	CPLD_QSFP_MUX_BUS20,
	CPLD_QSFP_MUX_BUS21,
	CPLD_QSFP_MUX_BUS22,
	CPLD_QSFP_MUX_BUS23,
	CPLD_QSFP_MUX_BUS24,
	CPLD_QSFP_MUX_BUS25,
	CPLD_QSFP_MUX_BUS26,
	CPLD_QSFP_MUX_BUS27,
	CPLD_QSFP_MUX_BUS28,
	CPLD_QSFP_MUX_BUS29,
	CPLD_QSFP_MUX_BUS30,
	CPLD_QSFP_MUX_BUS31,
};

mk_eeprom(spd,   52, 256, AT24_FLAG_READONLY | AT24_FLAG_IRUGO |
		AT24_FLAG_DISABLE_I2CBLOCK);
mk_eeprom(board, 53, 256, AT24_FLAG_IRUGO);

/*
 * PCA9547 at iSMT 0x71
 */
static struct pca954x_platform_mode mux1_platform_modes[] = {
	{
		.adap_id = I2C_MUX1_BUS0, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS1, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS2, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS3, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS4, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS5, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS6, .deselect_on_exit = 1,
	},
	{
		.adap_id = I2C_MUX1_BUS7, .deselect_on_exit = 1,
	},
};

/*
 *
*/
static struct pca954x_platform_data mux1_platform_data = {
	.modes = mux1_platform_modes,
	.num_modes = ARRAY_SIZE(mux1_platform_modes),
};

struct delta_ag9032v1_i2c_device_info {
	int bus;
	struct i2c_board_info board_info;
};

/*
 * the list of i2c devices and their bus connections for this platform
 */
static struct delta_ag9032v1_i2c_device_info delta_ag9032v1_i2c_devices[] = {
	{
		.bus = I2C_ISMT_BUS,
		{
			.type = "pca9547",
			.addr = 0x71,
			.platform_data = &mux1_platform_data,
		}
	},
	{
		.bus = I2C_I801_BUS,
		{
			I2C_BOARD_INFO("spd", 0x52),
			.platform_data = &spd_52_at24,
		}
	},
	{
		.bus = I2C_MUX1_BUS0,
		{
			I2C_BOARD_INFO("24c02", 0x53),
			.platform_data = &board_53_at24,
		}
	},
	{
		.bus = I2C_MUX1_BUS0,
		{
			I2C_BOARD_INFO("dummy", 0x31),       /* CPU CPLD */
		}
	},
	{
		.bus = I2C_MUX1_BUS0,
		{
			I2C_BOARD_INFO("tmp75", 0x4d),
		}
	},
	{
		.bus = I2C_MUX1_BUS4,
		{
			I2C_BOARD_INFO("dummy", 0x31),       /* SW CPLD */
		}
	},
	{
		.bus = I2C_MUX1_BUS5,
		{
			I2C_BOARD_INFO("tmp75", 0x4c),
		},
	},
	{
		.bus = I2C_MUX1_BUS5,
		{
			I2C_BOARD_INFO("tmp75", 0x4d),
		},
	},
	{
		.bus = I2C_MUX1_BUS5,
		{
			I2C_BOARD_INFO("tmp75", 0x4e),
		},
	},
};

/**
 * delta_ag9032v1_i2c_init -- Initialize the I2C subsystem.
 *
 *
 */
static struct i2c_client *delta_ag9032v1_clients[
			ARRAY_SIZE(delta_ag9032v1_i2c_devices)];

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

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

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 adapter %u\n", bus);
		client = ERR_PTR(-ENODEV);
		goto exit;
	}
	client = i2c_new_device(adapter, board_info);
	if (!client) {
		pr_err("could not add device\n");
		client = ERR_PTR(-ENODEV);
	}
	i2c_put_adapter(adapter);
exit:
	return client;
}

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

	for (i = 0; i < 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 int populate_i2c_devices(struct delta_ag9032v1_i2c_device_info *devices,
				int num_devices, struct i2c_client **clientss,
				int ISMT_bus, int I801_bus)
{
	int i;
	int ret;
	int cpld_count = 0;
	struct i2c_client *client;

	for (i = 0; i < num_devices; i++) {
		if (devices[i].bus == I2C_ISMT_BUS)
			devices[i].bus = ISMT_bus;
		else if (devices[i].bus == I2C_I801_BUS)
			devices[i].bus = I801_bus;
		client = add_i2c_client(devices[i].bus, &devices[i].board_info);
		if (IS_ERR(client)) {
			ret = PTR_ERR(client);
			goto err_exit;
		}
		delta_ag9032v1_clients[i] = client;
		if (strcmp(devices[i].board_info.type, "dummy") == 0)
			delta_ag9032v1_cpld_clients[cpld_count++] = client;
	}
	return 0;

err_exit:
	return ret;
}

static void free_delta_ag9032v1_i2c_data(void)
{
	int i, idx;

	for (i = ARRAY_SIZE(delta_ag9032v1_i2c_devices); i; i--) {
		idx = i - 1;
		if (delta_ag9032v1_clients[idx])
			i2c_unregister_device(delta_ag9032v1_clients[idx]);
	}
}

static int __init delta_ag9032v1_i2c_init(void)
{
	int ISMT_bus;
	int I801_bus;
	int ret;

	ret = -1;
	ISMT_bus = get_bus_by_name(ISMT_ADAPTER_NAME);
	if (ISMT_bus < 0) {
		pr_err("could not find iSMT adapter bus\n");
		ret = -ENODEV;
		goto err_exit;
	}
	I801_bus = get_bus_by_name(I801_ADAPTER_NAME);
	if (I801_bus < 0) {
		pr_err("could not find I801 adapter bus\n");
		ret = -ENODEV;
		goto err_exit;
	}

	/* populate the i2c devices
	*/
	ret = populate_i2c_devices(delta_ag9032v1_i2c_devices,
				   ARRAY_SIZE(delta_ag9032v1_i2c_devices),
				   &delta_ag9032v1_clients[0], ISMT_bus,
				   I801_bus);
	if (ret)
		goto err_exit;

	return 0;

err_exit:
	free_delta_ag9032v1_i2c_data();
	return ret;
}

static void __exit delta_ag9032v1_i2c_exit(void)
{
	free_delta_ag9032v1_i2c_data();
}

/*---------------------------------------------------------------------
 *
 * CPLD driver
 *
 *-------------------------------------------------------------------*/
static int __init delta_ag9032v1_cpld_init(void);
static void __exit delta_ag9032v1_cpld_exit(void);

static int __init delta_ag9032v1_cpld_mux_init(void);
static void __exit delta_ag9032v1_cpld_mux_exit(void);

static int delta_ag9032v1_add_cpld_mux_devices(void);
static int delta_ag9032v1_add_gpio_devices(void);

static struct platform_device *delta_ag9032v1_cpld_device;

#define CPLD_NAME  "delta_ag9032v1_cpld"

static uint8_t cpld_reg_read(uint32_t reg)
{
	int cpld_idx = GET_CPLD_IDX(reg);
	uint8_t val;
	struct i2c_client *client;

	if (cpld_idx < 0 || cpld_idx >= NUM_CPLD_I2C_CLIENTS) {
		pr_err("attempt to read invalid CPLD register [0x%02X]", reg);
		return -EINVAL;
	}

	reg = STRIP_CPLD_IDX(reg);
	client = delta_ag9032v1_cpld_clients[cpld_idx];
	val = i2c_smbus_read_byte_data(client, reg);
	if (val < 0)
		pr_err("I2C read error - addr: 0x%02X, offset: 0x%02X",
		       client->addr, reg);
	return val;
}

static s32 cpld_reg_write(uint32_t reg, uint8_t write_val)
{
	int cpld_idx = GET_CPLD_IDX(reg);
	int res;
	struct i2c_client *client;

	if (cpld_idx < 0 || cpld_idx >= NUM_CPLD_I2C_CLIENTS) {
		pr_err("write to invalid CPLD register [0x%02X]", reg);
		return -EINVAL;
	}

	reg = STRIP_CPLD_IDX(reg);
	client = delta_ag9032v1_cpld_clients[cpld_idx];
	res = i2c_smbus_write_byte_data(client, reg, write_val);
	if (res)
		pr_err("CPLD wr - addr: 0x%02X, reg: 0x%02X, val: 0x%02X",
		       client->addr, reg, write_val);
	return res;
}

/*
 * cpu_cpld_version
 */
static ssize_t cpuld_version_show(struct device *dev,
				  struct device_attribute *dattr,
				  char *buf)
{
	return sprintf(buf, "0x%02X\n",
		cpld_reg_read(DELTA_AG9032V1_CPULD_VERSION_REG));
}
static SYSFS_ATTR_RO(cpu_cpld_version, cpuld_version_show);

/*
 * cpu_board_id
 */
static ssize_t cpu_board_id_show(struct device *dev,
				 struct device_attribute *dattr,
				 char *buf)
{
	return sprintf(buf, "0x%02X\n",
		cpld_reg_read(DELTA_AG9032V1_CPULD_BOARD_ID_REG));
}
static SYSFS_ATTR_RO(cpu_board_id, cpu_board_id_show);

/*
 * eeprom_protect
 */
static ssize_t eeprom_protect_show(struct device *dev,
				   struct device_attribute *dattr,
				   char *buf)
{
	u8 data;

	data = cpld_reg_read(DELTA_AG9032V1_CPULD_FLASH_PROTECT_REG);
	return sprintf(buf, "%c\n",
		(data & DELTA_AG9032V1_BOARD_EEPROM_PROTECT) ? '1' : '0');
}

static ssize_t eeprom_protect_store(struct device *dev,
				    struct device_attribute *dattr,
				    const char *buf, size_t count)
{
	long protect = 0;
	u8 data;
	u8 reg = DELTA_AG9032V1_CPULD_FLASH_PROTECT_REG;

	if (kstrtol(buf, 0, &protect) < 0)
		return -EINVAL;
	data = cpld_reg_read(reg);
	if (protect)
		data |= DELTA_AG9032V1_BOARD_EEPROM_PROTECT;
	else
		data &= ~DELTA_AG9032V1_BOARD_EEPROM_PROTECT;
	cpld_reg_write(reg, protect);

	return count;
}
static SYSFS_ATTR_RW(eeprom_protect, eeprom_protect_show, eeprom_protect_store);

/*
 * sw_cpld_version
 */
static ssize_t swpld_version_show(struct device *dev,
				  struct device_attribute *dattr,
				  char *buf)
{
	return sprintf(buf, "0x%02X\n",
			cpld_reg_read(DELTA_AG9032V1_SWPLD_VERSION_REG));
}
static SYSFS_ATTR_RO(sw_cpld_version, swpld_version_show);

/*
 * sw_board_id
 */
static ssize_t sw_board_id_show(struct device *dev,
				struct device_attribute *dattr,
				char *buf)
{
	return sprintf(buf, "0x%02X\n",
		cpld_reg_read(DELTA_AG9032V1_SWPLD_BOARD_ID_REG));
}
static SYSFS_ATTR_RO(sw_board_id, sw_board_id_show);

/*
 * psu_pwr1
 */
static ssize_t bulk_power_show(struct device *dev,
			       struct device_attribute *dattr,
			       char *buf)
{
	uint8_t read_val;
	uint8_t good_bit;

	read_val = cpld_reg_read(DELTA_AG9032V1_SWPLD_PWR_STATUS3);
	if (strcmp(dattr->attr.name, xstr(PLATFORM_PS_NAME_0)) == 0)
		good_bit = PSU1_PWR_GOOD;
	else
		good_bit = PSU2_PWR_GOOD;

	if (read_val & good_bit)
		strcpy(buf, PLATFORM_OK);
	else
		strcpy(buf, PLATFORM_PS_POWER_BAD);
	strcat(buf, "\n");

	return strlen(buf);
}
static SYSFS_ATTR_RO(PLATFORM_PS_NAME_0, bulk_power_show);
static SYSFS_ATTR_RO(PLATFORM_PS_NAME_1, bulk_power_show);

struct led {
	char name[PLATFORM_LED_COLOR_NAME_SIZE];
	unsigned int offset;
	uint8_t mask;
	int n_colors;
	struct led_color colors[8];
};

static struct led cpld_leds[] = {
	{
		.name = "led_system",
		.offset = DELTA_AG9032V1_SWPLD_SYS_LED_REG,
		.mask = DELTA_AG9032V1_SYS_LED_MASK,
		.n_colors = 4,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_SYS_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_SYS_LED_GREEN },
			{ PLATFORM_LED_GREEN_BLINKING,
					DELTA_AG9032V1_SYS_LED_GREEN_BLINK },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_SYS_LED_RED },
		},
	},
	{
		.name = "led_fan",
		.offset = DELTA_AG9032V1_SWPLD_SYS_LED_REG,
		.mask = DELTA_AG9032V1_FAN_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN_LED_GREEN },
			{ PLATFORM_LED_AMBER, DELTA_AG9032V1_FAN_LED_AMBER },
		},
	},
	{
		.name = "led_psu1",
		.offset = DELTA_AG9032V1_SWPLD_SYS_LED_REG,
		.mask = DELTA_AG9032V1_PSU1_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_PSU1_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_PSU1_LED_GREEN },
			{ PLATFORM_LED_AMBER_BLINKING,
					DELTA_AG9032V1_PSU1_LED_AMBER_BLINK },
		},
	},
	{
		.name = "led_psu2",
		.offset = DELTA_AG9032V1_SWPLD_SYS_LED_REG,
		.mask = DELTA_AG9032V1_PSU2_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_PSU2_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_PSU2_LED_GREEN },
			{ PLATFORM_LED_AMBER_BLINKING,
					DELTA_AG9032V1_PSU2_LED_AMBER_BLINK },
		},
	},
	{
		.name = "led_fan_tray1",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY1_4_LED_REG,
		.mask = DELTA_AG9032V1_FAN1_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN1_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN1_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN1_LED_RED },
		},
	},
	{
		.name = "led_fan_tray1",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY1_4_LED_REG,
		.mask = DELTA_AG9032V1_FAN1_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN1_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN1_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN1_LED_RED },
		},
	},
	{
		.name = "led_fan_tray2",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY1_4_LED_REG,
		.mask = DELTA_AG9032V1_FAN2_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN2_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN2_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN2_LED_RED },
		},
	},
	{
		.name = "led_fan_tray3",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY1_4_LED_REG,
		.mask = DELTA_AG9032V1_FAN3_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN3_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN3_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN3_LED_RED },
		},
	},
	{
		.name = "led_fan_tray4",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY1_4_LED_REG,
		.mask = DELTA_AG9032V1_FAN4_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN4_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN4_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN4_LED_RED },
		},
	},
	{
		.name = "led_fan_tray5",
		.offset = DELTA_AG9032V1_SWPLD_FAN_TRAY5_LED_REG,
		.mask = DELTA_AG9032V1_FAN5_LED_MASK,
		.n_colors = 3,
		.colors = {
			{ PLATFORM_LED_OFF, DELTA_AG9032V1_FAN5_LED_OFF },
			{ PLATFORM_LED_GREEN, DELTA_AG9032V1_FAN5_LED_GREEN },
			{ PLATFORM_LED_RED, DELTA_AG9032V1_FAN5_LED_RED },
		},
	},
};

static int n_leds = ARRAY_SIZE(cpld_leds);

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

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

	/* read the register */
	tmp = cpld_reg_read(target->offset);

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

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

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

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

	/* set the new value */
	tmp = cpld_reg_read(target->offset);
	tmp &= ~target->mask;
	tmp |= target->colors[i].value;

	ret = cpld_reg_write(target->offset, tmp);
	if (ret < 0)
		return ret;

	return count;
}
static SYSFS_ATTR_RW(led_system,    led_show, led_store);
static SYSFS_ATTR_RW(led_psu1,      led_show, led_store);
static SYSFS_ATTR_RW(led_psu2,      led_show, led_store);
static SYSFS_ATTR_RW(led_fan,       led_show, led_store);
static SYSFS_ATTR_RW(led_fan_tray1, led_show, led_store);
static SYSFS_ATTR_RW(led_fan_tray2, led_show, led_store);
static SYSFS_ATTR_RW(led_fan_tray3, led_show, led_store);
static SYSFS_ATTR_RW(led_fan_tray4, led_show, led_store);
static SYSFS_ATTR_RW(led_fan_tray5, led_show, led_store);

#define NUM_QSFP_REGS  4
struct qsfp_reg_info {
	char name[PLATFORM_LED_COLOR_NAME_SIZE];
	u8   regs[NUM_QSFP_REGS];
	int  active_low;
};

static struct qsfp_reg_info delta_ag9032v1_qsfp_reg_info[] = {
	{
		.name = "qsfp_lp_mode",
		.regs = { DELTA_AG9032V1_SWPLD_LPMODE_1_8_REG,
				DELTA_AG9032V1_SWPLD_LPMODE_9_16_REG,
				DELTA_AG9032V1_SWPLD_LPMODE_17_24_REG,
				DELTA_AG9032V1_SWPLD_LPMODE_25_32_REG
				},
	},
	{
		.name = "qsfp_present",
		.regs = { DELTA_AG9032V1_SWPLD_PRES_1_8_REG,
				DELTA_AG9032V1_SWPLD_PRES_9_16_REG,
				DELTA_AG9032V1_SWPLD_PRES_17_24_REG,
				DELTA_AG9032V1_SWPLD_PRES_25_32_REG
				},
		.active_low = 1,
	},
	{
		.name = "qsfp_reset",
		.regs = { DELTA_AG9032V1_SWPLD_RESET_1_8_REG,
				DELTA_AG9032V1_SWPLD_RESET_9_16_REG,
				DELTA_AG9032V1_SWPLD_RESET_17_24_REG,
				DELTA_AG9032V1_SWPLD_RESET_25_32_REG
				},
		.active_low = 1,
	},
	{
		.name = "qsfp_modsel",
		.regs = { DELTA_AG9032V1_SWPLD_MODSEL_1_8_REG,
				DELTA_AG9032V1_SWPLD_MODSEL_9_16_REG,
				DELTA_AG9032V1_SWPLD_MODSEL_17_24_REG,
				DELTA_AG9032V1_SWPLD_MODSEL_25_32_REG
				},
		.active_low = 1,
	},
};

int n_qsfp_info = ARRAY_SIZE(delta_ag9032v1_qsfp_reg_info);

static ssize_t qsfp_show(struct device *dev,
			 struct device_attribute *dattr,
			 char *buf)
{
	uint8_t tmp;
	uint32_t data;
	int i;
	struct qsfp_reg_info *target = NULL;

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

	data = 0;
	for (i = 0; i < NUM_QSFP_REGS; i++) {
		tmp = cpld_reg_read(target->regs[i]);
		if (target->active_low)
			tmp = ~tmp;
		/* the registers have the highest numbered port in
		 * the first bit, we have to reverse the order.
		 */
		tmp = (tmp & 0xf0) >> 4 | (tmp & 0x0f) << 4;
		tmp = (tmp & 0xcc) >> 2 | (tmp & 0x33) << 2;
		tmp = (tmp & 0xaa) >> 1 | (tmp & 0x55) << 1;
		data |= ((uint32_t)tmp << (8 * i));
	}
	return sprintf(buf, "0x%04X\n", data);
}

static ssize_t qsfp_store(struct device *dev,
			  struct device_attribute *dattr,
			  const char *buf, size_t count)
{
	uint8_t tmp;
	uint32_t val;
	int i;
	int ret;
	struct qsfp_reg_info *target = NULL;

	for (i = 0; i < n_qsfp_info; i++) {
		if (strcmp(dattr->attr.name,
			   delta_ag9032v1_qsfp_reg_info[i].name) == 0) {
			target = &delta_ag9032v1_qsfp_reg_info[i];
			break;
		}
	}
	if (!target)
		return -EINVAL;

	ret = kstrtou32(buf, 0, &val);
	if (ret < 0)
		return ret;
	for (i = 0; i < NUM_QSFP_REGS; i++) {
		tmp = val >> (i * 8) & 0xff;
		if (target->active_low)
			tmp = ~tmp;
		/* the registers have the highest numbered port in
		 * the first bit, we have to reverse the order.
		 */
		tmp = (tmp & 0xf0) >> 4 | (tmp & 0x0f) << 4;
		tmp = (tmp & 0xcc) >> 2 | (tmp & 0x33) << 2;
		tmp = (tmp & 0xaa) >> 1 | (tmp & 0x55) << 1;
		cpld_reg_write(target->regs[i], tmp);
	}
	return count;
}
SYSFS_ATTR_RO(qsfp_present, qsfp_show);
SYSFS_ATTR_RW(qsfp_lp_mode, qsfp_show, qsfp_store);
SYSFS_ATTR_RW(qsfp_reset,   qsfp_show, qsfp_store);
SYSFS_ATTR_RW(qsfp_modsel,  qsfp_show, qsfp_store);

static struct attribute *delta_ag9032v1_cpld_attrs[] = {
	&dev_attr_cpu_cpld_version.attr,
	&dev_attr_cpu_board_id.attr,
	&dev_attr_eeprom_protect.attr,
	&dev_attr_sw_cpld_version.attr,
	&dev_attr_sw_board_id.attr,
	&dev_attr_psu_pwr1.attr,
	&dev_attr_psu_pwr2.attr,
	&dev_attr_led_system.attr,
	&dev_attr_led_psu1.attr,
	&dev_attr_led_psu2.attr,
	&dev_attr_led_fan.attr,
	&dev_attr_led_fan_tray1.attr,
	&dev_attr_led_fan_tray2.attr,
	&dev_attr_led_fan_tray3.attr,
	&dev_attr_led_fan_tray4.attr,
	&dev_attr_led_fan_tray5.attr,
	&dev_attr_qsfp_lp_mode.attr,
	&dev_attr_qsfp_present.attr,
	&dev_attr_qsfp_reset.attr,
	&dev_attr_qsfp_modsel.attr,
	NULL,
};

static struct attribute_group delta_ag9032v1_cpld_attr_group = {
	.attrs = delta_ag9032v1_cpld_attrs,
};

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

	ret = sysfs_create_group(&dev->dev.kobj,
				 &delta_ag9032v1_cpld_attr_group);
	if (ret)
		pr_err("sysfs_cpld_driver_group failed for cpld driver");

	return ret;
}

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

static struct platform_driver delta_ag9032v1_cpld_driver = {
	.driver = {
		.name = CPLD_NAME,
		.owner = THIS_MODULE,
	},
	.probe = delta_ag9032v1_cpld_probe,
	.remove = delta_ag9032v1_cpld_remove,
};

static int __init delta_ag9032v1_cpld_init(void)
{
	int ret;

	ret = platform_driver_register(&delta_ag9032v1_cpld_driver);
	if (ret) {
		pr_err("platform_driver_register() failed for CPLD device");
		goto err_drvr;
	}

	delta_ag9032v1_cpld_device = platform_device_alloc(CPLD_NAME, 0);
	if (!delta_ag9032v1_cpld_device) {
		pr_err("platform_device_alloc() failed for CPLD device");
		ret = -ENOMEM;
		goto err_dev_alloc;
	}

	ret = platform_device_add(delta_ag9032v1_cpld_device);
	if (ret) {
		pr_err("platform_device_add() failed for CPLD device.\n");
		goto err_dev_add;
	}

	ret = delta_ag9032v1_cpld_mux_init();
	if (ret) {
		pr_err("Initializing CPLD mux failed.\n");
		goto err_dev_add;
	}

	ret = delta_ag9032v1_add_cpld_mux_devices();
	if (ret) {
		pr_err("Initializing CPLD devices failed.\n");
		goto err_dev_add;
	};

	ret = delta_ag9032v1_add_gpio_devices();
	if (ret)
		goto err_dev_add;

	return 0;

err_dev_add:
	platform_device_put(delta_ag9032v1_cpld_device);

err_dev_alloc:
	platform_driver_unregister(&delta_ag9032v1_cpld_driver);

err_drvr:
	return ret;
}

static void __exit delta_ag9032v1_cpld_exit(void)
{
	delta_ag9032v1_cpld_mux_exit();
	platform_driver_unregister(&delta_ag9032v1_cpld_driver);
	platform_device_unregister(delta_ag9032v1_cpld_device);
}

/*---------------------------------------------------------------------
 *
 * The CPLD on the delta-ag9032 performs a number of mux functions
 *
 *  +---------+
 *  | PCA9457 | 0
 *  |         +--
 *  |         | 1  CPLD fan mux
 *  |         +--------- 0..7 (FAN1-FAN5 EEPROM, EMC2305, tmp75a, pa9555a)
 *  |         | 2  CPLD psu mux
 *  |         +--------- 0..3 (PSU1 EEPROM, LTC-4215, PSU2 EEPROM, LTC-4215)
 *  |         | 3  CPLD qsfp mux
 *  |         +--------- 0x1..0x32 (QSFP 1-32)
 *  |   ...   |
 *
 *-------------------------------------------------------------------*/
#define MUX_NAME_LEN                16
#define DELTA_AG9032V1_FAN_MUX_SIZE     8
#define DELTA_AG9032V1_PSU_MUX_SIZE     4
#define DELTA_AG9032V1_QSFP_MUX_SIZE    32
#define CPLD_MUX_INVALID_CHANNEL    0xDEADBEEF

struct mux_info_item {
	char mux_name[MUX_NAME_LEN];
	int num_busses;
	int first_bus_num;
	int parent_bus;
	u32 prev_chan;
	u8  mux_reg;
	u8  mux_mask;
	u8  mux_shift;
	u8  mux_offset;
	u8  *channel_lookup;
};

static u8 qsfp_channel_lookup[] = {
	0x1,  0x2,  0x3,  0x4,  0x5,  0x6,  0x7,  0x8,  0x9,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
	0x30, 0x31, 0x32,
};

static struct mux_info_item delta_ag9032v1_mux_info[] = {
	{
		.mux_name = "cpld-fan-mux",
		.num_busses = 8,
		.first_bus_num = CPLD_FAN_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS1,
		.mux_reg = DELTA_AG9032V1_SWPLD_PSU_FAN_MUX_REG,
		.mux_mask = DELTA_AG9032V1_FAN_MUX_MASK,
		.mux_shift = DELTA_AG9032V1_FAN_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
	},
	{
		.mux_name = "cpld-psu-mux",
		.num_busses = 4,
		.first_bus_num = CPLD_PSU_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS2,
		.mux_reg = DELTA_AG9032V1_SWPLD_PSU_FAN_MUX_REG,
		.mux_mask = DELTA_AG9032V1_PSU_MUX_MASK,
		.mux_shift = DELTA_AG9032V1_PSU_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
	},
	{
		.mux_name = "cpld-qsfp-mux",
		.num_busses = 32,
		.first_bus_num = CPLD_QSFP_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS3,
		.mux_reg = DELTA_AG9032V1_SWPLD_QSFP_MUX_REG,
		.mux_mask = DELTA_AG9032V1_QSFP_MUX_MASK,
		.mux_shift = DELTA_AG9032V1_QSFP_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
		.channel_lookup = qsfp_channel_lookup,
	},
};

#define DELTA_AG9032V1_NUM_MUXES  ARRAY_SIZE(delta_ag9032v1_mux_info)

/*
 * one mux_info_item per mux (psu, fan, qsfp)
 */
struct mux_data_item {
	int num_clients;
	struct platform_device *mux_dev;
	struct i2c_adapter **mux_adapters;
	struct i2c_client **mux_clients;
};

/*
 * a container for all of our muxes
 */
static struct mux_data_item delta_ag9032v1_mux_data[DELTA_AG9032V1_NUM_MUXES];

/*
 * platform data for the gpio chip
 */
#define DELTA_AG9032V1_GPIO_PIN_COUNT     5
#define DELTA_AG9032V1_GPIO_BASE          430
#define DELTA_AG9032V1_GPIO_FAN5_ABSENT  (DELTA_AG9032V1_GPIO_BASE)
#define DELTA_AG9032V1_GPIO_FAN4_ABSENT  (DELTA_AG9032V1_GPIO_BASE + 1)
#define DELTA_AG9032V1_GPIO_FAN3_ABSENT  (DELTA_AG9032V1_GPIO_BASE + 2)
#define DELTA_AG9032V1_GPIO_FAN2_ABSENT  (DELTA_AG9032V1_GPIO_BASE + 3)
#define DELTA_AG9032V1_GPIO_FAN1_ABSENT  (DELTA_AG9032V1_GPIO_BASE + 4)

struct gpio_pin {
	int num;
	const char *name;
	bool output;
	bool active_low;
	bool value;		/* only for output pins */
	bool allocated;
};

#define mk_gpio_pins(_name)			\
	static struct gpio_pin _name##_pins[]
#define mk_gpio_pin(_num, _name, _output, _active_low, _value) \
	{ \
		.num = (_num), \
		.name = #_name, \
		.output = (_output), \
		.active_low = (_active_low), \
		.value = (_value), \
	}
#define mk_gpio_platform_data(_name, _base, _numpins) \
	static char const *_name##_pinnames[_numpins]; \
	static struct pca953x_platform_data _name##_platform_data = { \
		.gpio_base = (_base), \
		.names = _name##_pinnames, \
	}

mk_gpio_pins(delta_ag9032v1_gpio) = {
	mk_gpio_pin(DELTA_AG9032V1_GPIO_FAN1_ABSENT, fan_tray1_absent, 0, 0, 0),
	mk_gpio_pin(DELTA_AG9032V1_GPIO_FAN2_ABSENT, fan_tray2_absent, 0, 0, 0),
	mk_gpio_pin(DELTA_AG9032V1_GPIO_FAN3_ABSENT, fan_tray3_absent, 0, 0, 0),
	mk_gpio_pin(DELTA_AG9032V1_GPIO_FAN4_ABSENT, fan_tray4_absent, 0, 0, 0),
	mk_gpio_pin(DELTA_AG9032V1_GPIO_FAN5_ABSENT, fan_tray5_absent, 0, 0, 0),
};

static const char *pca953x_gpio_names[DELTA_AG9032V1_GPIO_PIN_COUNT];

static struct pca953x_platform_data gpio_platform_data = {
	.gpio_base = DELTA_AG9032V1_GPIO_BASE,
	.names = pca953x_gpio_names,
};

static int num_gpio_pins = ARRAY_SIZE(delta_ag9032v1_gpio_pins);
static int claimed_gpio_pins = ARRAY_SIZE(delta_ag9032v1_gpio_pins);

/* all of the devices that exist on one of the CPLD muxes */

mk_eeprom(fan1,  51, 256, AT24_FLAG_IRUGO);
mk_eeprom(fan2,  52, 256, AT24_FLAG_IRUGO);
mk_eeprom(fan3,  53, 256, AT24_FLAG_IRUGO);
mk_eeprom(fan4,  54, 256, AT24_FLAG_IRUGO);
mk_eeprom(fan5,  55, 256, AT24_FLAG_IRUGO);
mk_eeprom(psu1,  50, 256, AT24_FLAG_IRUGO);
mk_eeprom(psu2,  50, 256, AT24_FLAG_IRUGO);

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);
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);

static struct delta_ag9032v1_i2c_device_info delta_cpld_devices[] = {
	/* CPLD Fan Mux */
	mk_i2cdev(CPLD_FAN_MUX_BUS0, "24c02",   0x51, &fan1_51_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS1, "24c02",   0x52, &fan2_52_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS2, "24c02",   0x53, &fan3_53_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS3, "24c02",   0x54, &fan4_54_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS4, "24c02",   0x55, &fan5_55_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS5, "emc2305", 0x2c, NULL),
	mk_i2cdev(CPLD_FAN_MUX_BUS5, "emc2305", 0x2d, NULL),
	mk_i2cdev(CPLD_FAN_MUX_BUS6, "lm75a",  0x4f, NULL),
	mk_i2cdev(CPLD_FAN_MUX_BUS7, "pca9555", 0x27, &gpio_platform_data),

	/* CPLD PSU Mux */
	mk_i2cdev(CPLD_PSU_MUX_BUS0, "24c02",   0x50, &psu1_50_at24),
	mk_i2cdev(CPLD_PSU_MUX_BUS0, "dps460",  0x58, NULL),
	mk_i2cdev(CPLD_PSU_MUX_BUS1, "ltc4215", 0x40, NULL),
	mk_i2cdev(CPLD_PSU_MUX_BUS2, "24c02",   0x50, &psu2_50_at24),
	mk_i2cdev(CPLD_PSU_MUX_BUS2, "dps460",  0x58, NULL),
	mk_i2cdev(CPLD_PSU_MUX_BUS3, "ltc4215", 0x40, NULL),

	/* CPLD QSFP Mux */
	mk_i2cdev(CPLD_QSFP_MUX_BUS0,  "sff8436", 0x50, &port1_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS1,  "sff8436", 0x50, &port2_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS2,  "sff8436", 0x50, &port3_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS3,  "sff8436", 0x50, &port4_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS4,  "sff8436", 0x50, &port5_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS5,  "sff8436", 0x50, &port6_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS6,  "sff8436", 0x50, &port7_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS7,  "sff8436", 0x50, &port8_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS8,  "sff8436", 0x50, &port9_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS9,  "sff8436", 0x50, &port10_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS10, "sff8436", 0x50, &port11_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS11, "sff8436", 0x50, &port12_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS12, "sff8436", 0x50, &port13_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS13, "sff8436", 0x50, &port14_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS14, "sff8436", 0x50, &port15_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS15, "sff8436", 0x50, &port16_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS16, "sff8436", 0x50, &port17_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS17, "sff8436", 0x50, &port18_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS18, "sff8436", 0x50, &port19_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS19, "sff8436", 0x50, &port20_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS20, "sff8436", 0x50, &port21_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS21, "sff8436", 0x50, &port22_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS22, "sff8436", 0x50, &port23_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS23, "sff8436", 0x50, &port24_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS24, "sff8436", 0x50, &port25_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS25, "sff8436", 0x50, &port26_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS26, "sff8436", 0x50, &port27_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS27, "sff8436", 0x50, &port28_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS28, "sff8436", 0x50, &port29_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS29, "sff8436", 0x50, &port30_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS30, "sff8436", 0x50, &port31_50_sff8436),
	mk_i2cdev(CPLD_QSFP_MUX_BUS31, "sff8436", 0x50, &port32_50_sff8436),
};

#define NUM_CPLD_MUX_DEVICES  ARRAY_SIZE(delta_cpld_devices)

static struct i2c_client *delta_ag9032v1_cpld_mux_clients[NUM_CPLD_MUX_DEVICES];
static struct mutex delta_ag9032v1_cpld_mux_mutex;

static int cpld_mux_i2c_reg_write(uint8_t command, u8 val)
{
	int rv;
	struct i2c_client *client;
	struct i2c_adapter *adap;

	adap = get_adapter(I2C_MUX1_BUS4);
	if (!adap) {
		pr_err("Unable to get mux adapter %u.\n",
		       I2C_MUX1_BUS4);
		return -ENODEV;
	}

	client = delta_ag9032v1_cpld_clients[GET_CPLD_IDX(command)];
	command = STRIP_CPLD_IDX(command);

	if (adap->algo->master_xfer) {
		struct i2c_msg msg = { 0 };
		char buf[2];

		msg.addr = client->addr;
		msg.flags = I2C_SMBUS_WRITE;
		msg.len = 2;
		buf[0] = command;
		buf[1] = val;
		msg.buf = buf;
		rv = adap->algo->master_xfer(adap, &msg, 1);
	} else {
		union i2c_smbus_data data;

		data.byte = val;
		rv = adap->algo->smbus_xfer(adap, client->addr,
			client->flags,
			I2C_SMBUS_WRITE,
			command, I2C_SMBUS_BYTE_DATA, &data);
	}
	i2c_put_adapter(adap);
	return rv;
}

s32 cpld_mux_i2c_reg_read(uint8_t command)
{
	s32 rv;
	struct i2c_client *client;
	struct i2c_adapter *adap;
	u8 data;

	adap = get_adapter(I2C_MUX1_BUS4);
	if (!adap) {
		pr_err("Unable to get mux adapter %u.\n",
		       I2C_MUX1_BUS4);
		return -ENODEV;
	}

	client = delta_ag9032v1_cpld_clients[GET_CPLD_IDX(command)];
	command = STRIP_CPLD_IDX(command);

	if (adap->algo->master_xfer) {
		struct i2c_msg msg = { 0 };
		char buf[2] = {0};

		msg.addr = client->addr;
		msg.flags = I2C_SMBUS_READ;
		msg.len = 2;
		msg.buf = buf;
		rv = adap->algo->master_xfer(adap, &msg, 1);
		data = buf[0];
	} else {
		union i2c_smbus_data tdata = {0};

		rv = adap->algo->smbus_xfer(adap, client->addr,
			client->flags,
			I2C_SMBUS_READ,
			command, I2C_SMBUS_BYTE_DATA, &tdata);
		data = tdata.byte;
	}
	i2c_put_adapter(adap);
	if (!rv)
		rv = data;
	return rv;
}

static int cpld_mux_select_chan(struct i2c_adapter *adapter,
				void *client, u32 chan)
{
	struct mux_info_item *mux_info = (struct mux_info_item *)client;
	uint8_t val;
	int ret;

	if (mux_info->channel_lookup)
		chan = mux_info->channel_lookup[chan];
	if (likely(chan == mux_info->prev_chan))
		return 0;

	mutex_lock(&delta_ag9032v1_cpld_mux_mutex);
	mux_info->prev_chan = chan;
	val = cpld_mux_i2c_reg_read(mux_info->mux_reg);
	val &= ~mux_info->mux_mask;
	val |= (chan << mux_info->mux_shift);
	ret = cpld_mux_i2c_reg_write(mux_info->mux_reg, val);
	mutex_unlock(&delta_ag9032v1_cpld_mux_mutex);
	if (ret < 0)
		return ret;

	return 0;
}

static void cpld_mux_free_data(void)
{
	int i, j, index;
	struct mux_data_item *mux_data;

	for (i = 0; i < DELTA_AG9032V1_NUM_MUXES; i++) {
		mux_data = &delta_ag9032v1_mux_data[i];
		for (j = mux_data->num_clients; j; j--) {
			index = j - 1;
			if (mux_data->mux_adapters[index])
				i2c_del_mux_adapter(
					mux_data->mux_adapters[index]);
			if (mux_data->mux_clients[index])
				i2c_unregister_device(
					mux_data->mux_clients[index]);
		}
		kfree(mux_data->mux_clients[i]);
		kfree(mux_data->mux_adapters[i]);
		platform_device_unregister(mux_data->mux_dev);
	}
	for (i = 0; i < NUM_CPLD_MUX_DEVICES; i++)
		i2c_unregister_device(delta_ag9032v1_cpld_mux_clients[i - 1]);
}

static int __init delta_ag9032v1_cpld_mux_init(void)
{
	struct i2c_adapter *adapter;
	struct mux_data_item *mux_data;
	struct mux_info_item *mux_info;
	int ret, i, j;

	mutex_init(&delta_ag9032v1_cpld_mux_mutex);
	for (i = 0; i < DELTA_AG9032V1_NUM_MUXES; i++) {
		mux_info = &delta_ag9032v1_mux_info[i];
		mux_data = &delta_ag9032v1_mux_data[i];
		adapter = i2c_get_adapter(mux_info->parent_bus);
		if (!adapter) {
			pr_err("Could not find i2c adapter for %s bus %d.\n",
			       mux_info->mux_name, mux_info->parent_bus);
			return -ENODEV;
		}

		mux_data->mux_clients = kzalloc(mux_info->num_busses *
			sizeof(struct i2c_client *), GFP_KERNEL);
		if (!mux_data->mux_clients)
			goto err_exit;

		mux_data->mux_adapters = kzalloc(mux_info->num_busses *
			sizeof(struct i2c_adapter *), GFP_KERNEL);
		if (!mux_data->mux_adapters)
			goto err_exit_adapters;

		mux_data->mux_dev =
			platform_device_alloc(mux_info->mux_name, 0);
		if (!mux_data->mux_dev) {
			pr_err("platform_device_alloc() failed for %s.\n",
			       mux_info->mux_name);
			goto err_exit_dev;
		}

		ret = platform_device_add(mux_data->mux_dev);
		if (ret) {
			pr_err("platform_device_add() failed for %s.\n",
			       mux_info->mux_name);
			goto err_exit_dev_add;
		}
		mux_data->num_clients = mux_info->num_busses;
		for (j = 0; j < mux_info->num_busses; j++) {
			mux_data->mux_adapters[j] = i2c_add_mux_adapter(adapter,
						&mux_data->mux_dev->dev,
						mux_info,
						mux_info->first_bus_num + j,
						j, 0,
						cpld_mux_select_chan, NULL);
			if (!mux_data->mux_adapters[j]) {
				pr_err("%s failed to add mux adapter for channel %u.\n",
				       mux_info->mux_name,
				       mux_info->first_bus_num + j);
				goto err_exit;
			}
		}
	}

	return 0;

err_exit_dev_add:
	platform_device_put(mux_data->mux_dev);

err_exit_dev:
	kfree(mux_data->mux_adapters);

err_exit_adapters:
	kfree(mux_data->mux_clients);

err_exit:
	cpld_mux_free_data();
	return -ENOMEM;
}

static int delta_ag9032v1_add_cpld_mux_devices(void)
{
	int i;
	int ret;
	struct i2c_client *client;

	for (i = 0; i < NUM_CPLD_MUX_DEVICES; i++) {
		client = add_i2c_client(delta_cpld_devices[i].bus,
					&delta_cpld_devices[i].board_info);
		if (IS_ERR(client)) {
			ret = PTR_ERR(client);
			goto err_exit;
		}
		delta_ag9032v1_cpld_mux_clients[i] = client;
	}
	return 0;

err_exit:
	return ret;
}

static int init_gpio_pins(struct gpio_pin *gpio_pin_ptr, int num_pins,
			  int *claimed_list)
{
	int i;
	int ret;

	for (i = 0; i < num_pins; i++, gpio_pin_ptr++) {
		ret = gpio_request(gpio_pin_ptr->num, NULL);
		if (ret) {
			pr_err("request for gpio pin %s (%u) failed\n",
			       gpio_pin_ptr->name, gpio_pin_ptr->num);
			return ret;
		}
		claimed_list[i] = gpio_pin_ptr->num;

		if (gpio_pin_ptr->output)
			ret = gpio_direction_output(gpio_pin_ptr->num,
						    gpio_pin_ptr->value);
		else
			ret = gpio_direction_input(gpio_pin_ptr->num);
		if (ret) {
			pr_err("unable to set direction on gpio pin %s (%u)\n",
			       gpio_pin_ptr->name, gpio_pin_ptr->num);
			return ret;
		}

		ret = gpio_sysfs_set_active_low(gpio_pin_ptr->num,
						gpio_pin_ptr->active_low);
		if (ret) {
			pr_err("unable to set active_low on gpio pin %s (%u)\n",
			       gpio_pin_ptr->name, gpio_pin_ptr->num);
			return ret;
		}

		ret = gpio_export(gpio_pin_ptr->num, false);
		if (ret) {
			pr_err("unable to export gpio pin %s (%u)\n",
			       gpio_pin_ptr->name, gpio_pin_ptr->num);
			return ret;
		}
	}
	return 0;
}

static void free_gpio_pins(int *pins, int num_pins)
{
	int i;

	for (i = 0; i < num_pins; i++) {
		if (pins[i])
			gpio_free(pins[i]);
	}
}

static int delta_ag9032v1_add_gpio_devices(void)
{
	int i;
	int ret;

	for (i = 0; i < num_gpio_pins; i++)
		pca953x_gpio_names[delta_ag9032v1_gpio_pins[i].num -
			DELTA_AG9032V1_GPIO_BASE] =
			delta_ag9032v1_gpio_pins[i].name;

	ret = init_gpio_pins(delta_ag9032v1_gpio_pins,
			     num_gpio_pins, &claimed_gpio_pins);
	if (ret)
		pr_err("failed to initialize gpio pins.\n");
	return ret;
}

static void __exit delta_ag9032v1_cpld_mux_exit(void)
{
	free_gpio_pins(&claimed_gpio_pins, num_gpio_pins);
	cpld_mux_free_data();
}

static int delta_ag9032v1_reboot_callback(struct notifier_block *self,
					  unsigned long val,
					  void *data)
{
	u8 reset;

	if (val == SYS_RESTART) {
		reset = cpld_reg_read(DELTA_AG9032V1_SWPLD_SWRESET_REG);
		cpld_reg_write(DELTA_AG9032V1_SWPLD_SWRESET_REG,
			       reset & ~DELTA_AG9032V1_RESET_BCM56960_L);
		usleep_range(50, 100);
		cpld_reg_write(DELTA_AG9032V1_SWPLD_SWRESET_REG, reset);
	}
	return NOTIFY_DONE;
}

static struct notifier_block delta_ag9032v1_reboot_notifier = {
	.notifier_call = delta_ag9032v1_reboot_callback,
};

/*---------------------------------------------------------------------
 *
 * Module init/exit
 *
 *-------------------------------------------------------------------*/
static int __init delta_ag9032v1_init(void)
{
	int ret = 0;

	ret = delta_ag9032v1_i2c_init();
	if (ret) {
		pr_err("Initializing I2C subsystem failed\n");
		return ret;
	}

	ret = register_reboot_notifier(&delta_ag9032v1_reboot_notifier);
	if (ret) {
		pr_err(DRIVER_NAME ": unable to register reboot notifier.\n");
		return ret;
	}

	ret = delta_ag9032v1_cpld_init();
	if (ret) {
		pr_err("Registering CPLD driver failed.\n");
		return ret;
	}

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

static void __exit delta_ag9032v1_exit(void)
{
	delta_ag9032v1_cpld_exit();
	delta_ag9032v1_i2c_exit();
	unregister_reboot_notifier(&delta_ag9032v1_reboot_notifier);
	pr_err(DRIVER_NAME " driver unloaded\n");
}

module_init(delta_ag9032v1_init);
module_exit(delta_ag9032v1_exit);

MODULE_AUTHOR("Alan Liebthal (alanl@cumulusnetworks.com)");
MODULE_DESCRIPTION("Delta AG9032v1 Platform Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
