// SPDX-Licnese-Identifier: GPL-2.0+
/*
 * Dell N3248PXE Platform Support
 *
 * Copyright (c) 2019 Cumulus Networks, Inc.  All rights reserved.
 * 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/err.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c-ismt.h>
#include <linux/i2c-mux.h>
#include <linux/i2c-mux-gpio.h>
#include <linux/interrupt.h>
#include <linux/stddef.h>
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/platform_data/at24.h>
#include <linux/i2c/sff-8436.h>
#include <linux/i2c/pca954x.h>

#include <linux/cumulus-platform.h>
#include "platform_defs.h"
#include "platform_bitfield.h"
#include "dellemc-n3248pxe.h"

#define DRIVER_NAME	"dell_n3248pxe_platform"
#define DRIVER_VERSION	"1.0"

#define CPUCPLD_NAME    "n3248pxe_cpucpld"
#define SYSTEMCPLD_NAME "n3248pxe_systemcpld"

/*
 * This platform has both an iSMT adapter (i2c-0) and an i801 adapter (i2c-1).
 *
 * The iSMT adapter is connected to the following devices:
 *
 *   cpu cpld (0x31)
 *   system cpld (0x32)
 *   pca9548 8-channel mux (0x71)
 *     0 board eeprom (0x50)
 *     1 ir3595 (0x70)
 *       ir3595 (0x72)
 *     5 emc2305 fan controller (0x2c)
 *       emc2302 fan controller (0x2e)
 *       tmp75 (0x49)
 *       tmp75 (0x4a)
 *       tmp75 (0x4b)
 *       tmp75 (0x4c)
 *       tmp75 (0x4f)
 */

/*
 * The list of i2c devices and their bus connections for this platform.
 *
 * First we construct the necessary data struction for each device,
 * using the method specific to the device type.  Then we put them
 * all together in a big table (see i2c_devices below).
 *
 * For muxes, we specify the starting bus number for the block of ports,
 * using the magic mk_pca954*() macros.
 *
 * For eeproms, including ones in the qsfp+ transceivers,
 * we specify the label, i2c address, size, and some flags,
 * all done in mk*_eeprom() macros.  The label is the string
 * that ends up in /sys/class/eeprom_dev/eepromN/label,
 * which we use to identify them at user level.
 *
 * See the comment below for gpio.
 */

mk_pca9548(mux1, I2C_MUX1_BUS0, 1)

mk_eeprom(board, 50, 256, AT24_FLAG_IRUGO)

/*
 * i2c device tables
 *
 * We use the magic mk_i2cdev() macro to construct the entries.  Each entry is
 * a bus number and an i2c_board_info.	The i2c_board_info structure specifies
 * the device type, address, and platform data specific to the device type.
 *
 * The i2c_devices[] table contains all the devices that we expose on the
 * Denverton i2c busses.
 *
 */

static struct platform_i2c_device_info i2c_devices[] = {
	mk_i2cdev(I2C_ISMT_BUS,	 "pca9548",       0x71, &mux1_platform_data),
	mk_i2cdev(I2C_MUX1_BUS0, "24c32",         0x50, &board_50_at24),
	mk_i2cdev(I2C_MUX1_BUS5, "emc2305",       0x2c, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x48, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x49, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x4a, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x4b, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x4c, NULL),
	mk_i2cdev(I2C_MUX1_BUS5, "tmp75",         0x4f, NULL),
	mk_i2cdev(I2C_ISMT_BUS,  CPUCPLD_NAME,    0x31, NULL),
	mk_i2cdev(I2C_ISMT_BUS,  SYSTEMCPLD_NAME, 0x32, NULL),
};

/* I2C Initialization */

static struct i2c_client *systemcpld_client;

static void i2c_exit(void)
{
	int i;

	/* Unregister the i2c clients */
	for (i = ARRAY_SIZE(i2c_devices); --i >= 0;) {
		pr_info(DRIVER_NAME ": unregister i2c_client: %d\n", i);
		
		if (i2c_devices[i].client) {
			i2c_unregister_device(i2c_devices[i].client);
			i2c_devices[i].client = NULL;
		}
	}

	pr_info(DRIVER_NAME ": I2C driver unloaded\n");
}

static int i2c_init(void)
{
	int iSMT_bus;
	int i801_bus;
	int ret;
	int i;
	int bus;
	struct i2c_client *client;

	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");
		return -ENODEV;
	}

	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");
		return -ENODEV;
	}

	for (i = 0; i < ARRAY_SIZE(i2c_devices); i++) {
		bus = i2c_devices[i].bus;
		if (bus == I2C_ISMT_BUS)
			bus = iSMT_bus;
		else 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);
			pr_err(DRIVER_NAME ": add client failed for bus %d: %d\n",
			       bus, ret);
			goto err_exit;
		}
		i2c_devices[i].client = client;
		if (strcmp(i2c_devices[i].board_info.type, SYSTEMCPLD_NAME) == 0)
			systemcpld_client = client;
	}
	pr_info(DRIVER_NAME ": I2C driver loaded\n");
	return 0;

err_exit:
	while (--i>= 0) {
		pr_info(DRIVER_NAME ": unregister i2c_client: %d\n", i);
		if (i2c_devices[i].client) {
			i2c_unregister_device(i2c_devices[i].client);
			i2c_devices[i].client = NULL;
		}
	}
	return ret;
}

/*---------------------------------------------------------------------------*/
/* CPU CPLD Driver */

#define cpucpld_read_reg bf_i2c_read_reg
#define cpucpld_write_reg bf_i2c_write_reg

/* CPU CPLD register bitfields with enum-line values */

static const char * const wd_timer_values[] = {
	"70 sec",
	"65 sec",
	"60 sec",
	"50 sec",
	"40 sec",
	"30 sec",
	"20 sec",
	"15 sec",
};

/* CPU CPLD registers */

mk_bf_ro(cpucpld, major_revision,  0x00, 0, 8, NULL, 0)
mk_bf_ro(cpucpld, minor_revision,  0x01, 0, 8, NULL, 0)
mk_bf_rw(cpucpld, scratch,         0x02, 0, 8, NULL, 0)
mk_bf_ro(cpucpld, board_revision,  0x03, 4, 4, NULL, 0)
mk_bf_ro(cpucpld, board_type,      0x03, 0, 4, NULL, 0)
mk_bf_rw(cpucpld, primary_bios_wp, 0x05, 2, 1, NULL, 0)
mk_bf_rw(cpucpld, backup_bios_wp,  0x05, 1, 1, NULL, 0)
mk_bf_rw(cpucpld, wd_timer,        0x07, 4, 3, wd_timer_values, 0)
mk_bf_rw(cpucpld, wd_enable,       0x07, 3, 1, NULL, 0)
mk_bf_rw(cpucpld, wd_punch,        0x07, 0, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(cpucpld, reboot_cause,    0x09, 0, 8, NULL, 0)
mk_bf_ro(cpucpld, cpu_error_state, 0x10, 0, 8, NULL, 0)
mk_bf_ro(cpucpld, tpm_id,          0x12, 5, 3, NULL, 0)

struct attribute *cpucpld_attrs[] = {
        &cpucpld_minor_revision.attr,
        &cpucpld_major_revision.attr,
	&cpucpld_scratch.attr,
	&cpucpld_board_revision.attr,
	&cpucpld_board_type.attr,
	&cpucpld_primary_bios_wp.attr,
	&cpucpld_backup_bios_wp.attr,
	&cpucpld_wd_timer.attr,
	&cpucpld_wd_enable.attr,
	&cpucpld_wd_punch.attr,
	&cpucpld_reboot_cause.attr,
	&cpucpld_cpu_error_state.attr,
	&cpucpld_tpm_id.attr,
        NULL
};

static struct attribute_group cpucpld_attr_group = {
        .attrs = cpucpld_attrs,
};

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

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev, "smbus read byte data not supported.\n");
		return -ENODEV;
	}

	/* probe the hardware by reading the revision numbers */
	ret = i2c_smbus_read_byte_data(client, 0);
	if (ret < 0) {
		dev_err(&client->dev, "read CPLD major revision register error %d\n", ret);
		goto err;
	}
	major_rev = ret;
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev, "read CPLD minor revision register error %d\n", ret);
		goto err;
	}
	minor_rev = ret;

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

	/* all clear from this point on */
	dev_info(&client->dev,
		 "device created, CPU CPLD major rev %d, minor rev %d\n",
		 major_rev & 0xff,
		 minor_rev & 0xff);
	return 0;

err:
	return ret;
}

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

static const struct i2c_device_id cpucpld_id[] = {
	{ CPUCPLD_NAME, 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, cpucpld_id);

static struct i2c_driver cpucpld_driver = {
	.driver = {
		.name  = CPUCPLD_NAME,
		.owner = THIS_MODULE,
	},
	.probe = cpucpld_probe,
	.remove = cpucpld_remove,
	.id_table = cpucpld_id,
};

/*---------------------------------------------------------------------------*/
/* System CPLD Driver */

#define systemcpld_read_reg bf_i2c_read_reg
#define systemcpld_write_reg bf_i2c_write_reg

/* System CPLD register bitfields with enum-line values */

static const char * const led_fan_values[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_AMBER,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_AMBER_BLINKING,
};

static const char * const led_system_values[] = {
	PLATFORM_LED_GREEN_BLINKING,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_AMBER,
	PLATFORM_LED_AMBER_BLINKING,
};

static const char * const led_beacon_values[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_BLUE_BLINKING,
};

static const char * const led_power_values[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_AMBER,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_AMBER_BLINKING,
};

static const char * const led_stack_values[] = {
	PLATFORM_LED_GREEN,
	PLATFORM_LED_OFF,
};

static const char * const digit_values[] = {
	"0",
	"1",
	"2",
	"3",
	"4",
	"5",
	"6",
	"7",
	"8",
	"9",
	"A",
	"B",
	"C",
	"D",
	"E",
	"F",
};

static const char * const led_fan_tray_values[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_AMBER,
	PLATFORM_LED_OFF,
};

static const char * const airflow_values[] = {
	"F2B",
	"B2F",
};

/* System CPLD registers */

mk_bf_ro(systemcpld, major_revision,         0x00, 0, 8,  NULL, 0)
mk_bf_ro(systemcpld, minor_revision,         0x01, 0, 8,  NULL, 0)
mk_bf_rw(systemcpld, scratch,                0x02, 0, 8,  NULL, 0)
mk_bf_ro(systemcpld, board_revision,         0x03, 5, 3,  NULL, 0)
mk_bf_ro(systemcpld, board_type,             0x03, 0, 5,  NULL, 0)
mk_bf_rw(systemcpld, reset_port_led,         0x04, 7, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, poe_reset_disable,      0x04, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, reset_npu,              0x04, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, reset_mgmt_phy,         0x04, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, cpld_spi_wp,            0x05, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, sys_eeprom_wp,          0x05, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, fan3_eeprom_wp,         0x05, 2, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, fan2_eeprom_wp,         0x05, 1, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, fan1_eeprom_wp,         0x05, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, tpm_int,                0x06, 6, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, usb_fault,              0x06, 5, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, bcm54216s_int,          0x06, 4, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, ucd_irq,                0x06, 3, 1,  NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, fan_alert_int,          0x06, 0, 1,  NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, led_fan,                0x07, 6, 2, led_fan_values, 0)
mk_bf_rw(systemcpld, led_system,             0x07, 4, 2, led_system_values, 0)
mk_bf_rw(systemcpld, led_beacon,             0x07, 3, 1, led_beacon_values, 0)
mk_bf_rw(systemcpld, led_power,              0x07, 1, 2, led_power_values, 0)
mk_bf_rw(systemcpld, led_stack,              0x07, 0, 1, led_stack_values, 0)
mk_bf_rw(systemcpld, digit_blink,            0x08, 6, 1, NULL, 0)
mk_bf_rw(systemcpld, digit_on,               0x08, 5, 1, NULL, 0)
mk_bf_rw(systemcpld, digit_dot,              0x08, 4, 1, NULL, 0)
mk_bf_rw(systemcpld, digit,                  0x08, 0, 4, digit_values, 0)
mk_bf_rw(systemcpld, led_fan_tray3,          0x09, 4, 2, led_fan_tray_values, 0)
mk_bf_rw(systemcpld, led_fan_tray2,          0x09, 2, 2, led_fan_tray_values, 0)
mk_bf_rw(systemcpld, led_fan_tray1,          0x09, 0, 2, led_fan_tray_values, 0)
mk_bf_ro(systemcpld, fan_tray3_type,         0x0a, 6, 1, airflow_values, 0)
mk_bf_ro(systemcpld, fan_tray2_type,         0x0a, 5, 1, airflow_values, 0)
mk_bf_ro(systemcpld, fan_tray1_type,         0x0a, 4, 1, airflow_values, 0)
mk_bf_ro(systemcpld, fan_tray3_present,      0x0a, 2, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, fan_tray2_present,      0x0a, 1, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, fan_tray1_present,      0x0a, 0, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, sys_irq_enable,         0x0b, 2, 1, NULL, 0)
mk_bf_rw(systemcpld, fan_enable,             0x0b, 0, 1, NULL, 0)
mk_bf_ro(systemcpld, psu_pwr1_present,       0x0c, 7, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, psu_pwr1_all_ok,        0x0c, 6, 1, NULL, 0)
mk_bf_ro(systemcpld, psu_pwr1_alarm,         0x0c, 5, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, psu_pwr1_enable,        0x0c, 4, 1, NULL, 0)
mk_bf_ro(systemcpld, psu_pwr2_present,       0x0c, 3, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, psu_pwr2_all_ok,        0x0c, 2, 1, NULL, 0)
mk_bf_ro(systemcpld, psu_pwr2_alarm,         0x0c, 1, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, psu_pwr2_enable,        0x0c, 0, 1, NULL, 0)
mk_bf_rw(systemcpld, reset_poe,              0x0d, 5, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, poe_enable,             0x0d, 4, 1, NULL, 0)
mk_bf_ro(systemcpld, poe_i2c_ready,          0x0d, 3, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, poe_int,                0x0d, 2, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, poe_temp_alarm,         0x0d, 1, 1, NULL, 0)
mk_bf_ro(systemcpld, poe_system_ok,          0x0d, 1, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, reboot_cause,           0x10, 0, 8, NULL, 0)
mk_bf_rw(systemcpld, port_i2c_mux,           0x11, 0, 4, NULL, 0)
mk_bf_rw(systemcpld, fan_i2c_mux,            0x13, 0, 2, NULL, 0)
mk_bf_rw(systemcpld, psu_i2c_mux,            0x12, 0, 2, NULL, 0)
mk_bf_rw(systemcpld, usba_power_enable,      0x15, 2, 1, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, power_down,             0x15, 1, 1, NULL, 0)
mk_bf_rw(systemcpld, power_cycle,            0x15, 0, 1, NULL, 0)
mk_bf_rw(systemcpld, wd_timer,               0x17, 4, 3, wd_timer_values, 0)
mk_bf_rw(systemcpld, wd_enable,              0x17, 3, 1, NULL, 0)
mk_bf_rw(systemcpld, wd_punch,               0x17, 3, 1, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, psu_pwr2_ac_ok,         0x19, 3, 1, NULL, 0)
mk_bf_ro(systemcpld, psu_pwr1_ac_ok,         0x19, 2, 1, NULL, 0)
mk_bf_ro(systemcpld, qsfp28_54_53_present,   0x20, 0, 2, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, qsfp28_54_53_reset,     0x21, 0, 2, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, qsfp28_54_53_lpmode,    0x22, 0, 2, NULL, 0)
mk_bf_ro(systemcpld, qsfp28_54_53_irq_status,0x23, 0, 2, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, qsfp28_54_53_irq_mask,  0x24, 0, 2, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, sfp28_52_49_present,    0x30, 0, 4, NULL, BF_COMPLEMENT)
mk_bf_rw(systemcpld, sfp28_52_49_tx_disable, 0x31, 0, 4, NULL, 0)
mk_bf_ro(systemcpld, sfp28_52_49_rx_los,     0x32, 0, 4, NULL, 0)
mk_bf_ro(systemcpld, sfp28_52_49_tx_fault,   0x33, 0, 4, NULL, 0)
mk_bf_rw(systemcpld, phy_reset,              0x40, 0, 7, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, phy_irq_status,         0x41, 0, 7, NULL, BF_COMPLEMENT)
mk_bf_ro(systemcpld, phy_irq_mask,           0x42, 0, 7, NULL, BF_COMPLEMENT)

struct attribute *systemcpld_attrs[] = {
        &systemcpld_minor_revision.attr,
        &systemcpld_major_revision.attr,
	&systemcpld_scratch.attr,
	&systemcpld_board_revision.attr,
	&systemcpld_board_type.attr,
	&systemcpld_reset_port_led.attr,
	&systemcpld_poe_reset_disable.attr,
	&systemcpld_reset_npu.attr,
	&systemcpld_reset_mgmt_phy.attr,
	&systemcpld_cpld_spi_wp.attr,
	&systemcpld_sys_eeprom_wp.attr,
	&systemcpld_fan3_eeprom_wp.attr,
	&systemcpld_fan2_eeprom_wp.attr,
	&systemcpld_fan1_eeprom_wp.attr,
	&systemcpld_tpm_int.attr,
	&systemcpld_usb_fault.attr,
	&systemcpld_bcm54216s_int.attr,
	&systemcpld_ucd_irq.attr,
	&systemcpld_fan_alert_int.attr,
	&systemcpld_led_fan.attr,
	&systemcpld_led_system.attr,
	&systemcpld_led_beacon.attr,
	&systemcpld_led_power.attr,
	&systemcpld_led_stack.attr,
	&systemcpld_digit_blink.attr,
	&systemcpld_digit_on.attr,
	&systemcpld_digit_dot.attr,
	&systemcpld_digit.attr,
	&systemcpld_led_fan_tray3.attr,
	&systemcpld_led_fan_tray2.attr,
	&systemcpld_led_fan_tray1.attr,
	&systemcpld_fan_tray3_type.attr,
	&systemcpld_fan_tray2_type.attr,
	&systemcpld_fan_tray1_type.attr,
	&systemcpld_fan_tray3_present.attr,
	&systemcpld_fan_tray2_present.attr,
	&systemcpld_fan_tray1_present.attr,
	&systemcpld_sys_irq_enable.attr,
	&systemcpld_fan_enable.attr,
	&systemcpld_psu_pwr1_present.attr,
	&systemcpld_psu_pwr1_all_ok.attr,
	&systemcpld_psu_pwr1_alarm.attr,
	&systemcpld_psu_pwr1_enable.attr,
	&systemcpld_psu_pwr2_present.attr,
	&systemcpld_psu_pwr2_all_ok.attr,
	&systemcpld_psu_pwr2_alarm.attr,
	&systemcpld_psu_pwr2_enable.attr,
	&systemcpld_reset_poe.attr,
	&systemcpld_poe_enable.attr,
	&systemcpld_poe_i2c_ready.attr,
	&systemcpld_poe_int.attr,
	&systemcpld_poe_temp_alarm.attr,
	&systemcpld_poe_system_ok.attr,
	&systemcpld_reboot_cause.attr,
	&systemcpld_port_i2c_mux.attr,
	&systemcpld_fan_i2c_mux.attr,
	&systemcpld_psu_i2c_mux.attr,
	&systemcpld_usba_power_enable.attr,
	&systemcpld_power_down.attr,
	&systemcpld_power_cycle.attr,
	&systemcpld_wd_timer.attr,
	&systemcpld_wd_enable.attr,
	&systemcpld_wd_punch.attr,
	&systemcpld_psu_pwr2_ac_ok.attr,
	&systemcpld_psu_pwr1_ac_ok.attr,
	&systemcpld_qsfp28_54_53_present.attr,
	&systemcpld_qsfp28_54_53_reset.attr,
	&systemcpld_qsfp28_54_53_lpmode.attr,
	&systemcpld_qsfp28_54_53_irq_status.attr,
	&systemcpld_qsfp28_54_53_irq_mask.attr,
	&systemcpld_sfp28_52_49_present.attr,
	&systemcpld_sfp28_52_49_tx_disable.attr,
	&systemcpld_sfp28_52_49_rx_los.attr,
	&systemcpld_sfp28_52_49_tx_fault.attr,
	&systemcpld_phy_reset.attr,
	&systemcpld_phy_irq_status.attr,
	&systemcpld_phy_irq_mask.attr,
        NULL
};

static struct attribute_group systemcpld_attr_group = {
        .attrs = systemcpld_attrs,
};

/* CPLD muxes and devices */

/*
 * The CPLD on the Dell N3248PXE performs a number of mux functions:
 *
 *  +---------+
 *  | PCA9458 | 2
 *  |         +-- CPLD fan mux (fantray1, fantray2, fantray3)
 *  |         | 3
 *  |         +-- CPLD psu mux (psu1, psu2)
 *  |         | 6
 *  |         +-- CPLD port mux (sfp0, sfp1, sfp2, sfp3)
 *  |   ...   |
 *
 */

#define MUX_NAME_LEN     16
#define FAN_MUX_SIZE     3
#define PSU_MUX_SIZE     2
#define SFP_MUX_SIZE     4
#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 fan_channel_lookup[] = {
	0x1,  0x2,  0x3,
};

static u8 psu_channel_lookup[] = {
	0x1,  0x2,
};

static u8 sfp_channel_lookup[] = {
	0x1,  0x2,  0x3,  0x4,  0x5,  0x6,
};

static struct mux_info_item mux_info[] = {
	{
		.mux_name = "cpld-fan-mux",
		.num_busses = 3,
		.first_bus_num = CPLD_FAN_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS2,
		.mux_reg = FAN_I2C_MUX_REG,
		.mux_mask = FAN_I2C_MUX_MASK,
		.mux_shift = FAN_I2C_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
		.channel_lookup = fan_channel_lookup,
	},
	{
		.mux_name = "cpld-psu-mux",
		.num_busses = 2,
		.first_bus_num = CPLD_PSU_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS3,
		.mux_reg = PSU_I2C_MUX_REG,
		.mux_mask = PSU_I2C_MUX_MASK,
		.mux_shift = PSU_I2C_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
		.channel_lookup = psu_channel_lookup,
	},
	{
		.mux_name = "cpld-sfp-mux",
		.num_busses = 6,
		.first_bus_num = CPLD_SFP_MUX_BUS0,
		.parent_bus = I2C_MUX1_BUS6,
		.mux_reg = PORT_I2C_MUX_REG,
		.mux_mask = PORT_I2C_MUX_MASK,
		.mux_shift = PORT_I2C_MUX_SHIFT,
		.prev_chan = CPLD_MUX_INVALID_CHANNEL,
		.channel_lookup = sfp_channel_lookup,
	},
};

#define NUM_MUXES ARRAY_SIZE(mux_info)

/* one mux_info_item per mux (psu, fan, sfp) */

struct mux_data_item {
	int num_clients;
	struct platform_device *mux_dev;
	struct i2c_adapter **mux_adapters;
	struct i2c_client **mux_clients;
};

static struct mux_data_item mux_data[NUM_MUXES];

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

mk_eeprom(fan1, 50, 256, AT24_FLAG_IRUGO)
mk_eeprom(fan2, 50, 256, AT24_FLAG_IRUGO)
mk_eeprom(fan3, 50, 256, AT24_FLAG_IRUGO)
mk_eeprom(psu1, 56, 256, AT24_FLAG_IRUGO)
mk_eeprom(psu2, 56, 256, AT24_FLAG_IRUGO)

mk_port_eeprom(port49, 50, 512, AT24_FLAG_IRUGO)
mk_port_eeprom(port50, 50, 512, AT24_FLAG_IRUGO)
mk_port_eeprom(port51, 50, 512, AT24_FLAG_IRUGO)
mk_port_eeprom(port52, 50, 512, AT24_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 cpld_devices[] = {
	/* CPLD Fan Mux */
	mk_i2cdev(CPLD_FAN_MUX_BUS0, "24c02", 0x50, &fan1_50_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS1, "24c02", 0x50, &fan2_50_at24),
	mk_i2cdev(CPLD_FAN_MUX_BUS2, "24c02", 0x50, &fan3_50_at24),

	/* CPLD PSU Mux */
	mk_i2cdev(CPLD_PSU_MUX_BUS0, "24c02", 0x56, &psu1_56_at24),
	mk_i2cdev(CPLD_PSU_MUX_BUS0, "pmbus", 0x5e, NULL),	
	mk_i2cdev(CPLD_PSU_MUX_BUS1, "24c02", 0x56, &psu2_56_at24),
	mk_i2cdev(CPLD_PSU_MUX_BUS1, "pmbus", 0x5e, NULL),	

	/* CPLD SFP Mux */
	mk_i2cdev(CPLD_SFP_MUX_BUS0, "24c02", 0x50, &port49_50_at24),
	mk_i2cdev(CPLD_SFP_MUX_BUS1, "24c02", 0x50, &port50_50_at24),
	mk_i2cdev(CPLD_SFP_MUX_BUS2, "24c02", 0x50, &port51_50_at24),
	mk_i2cdev(CPLD_SFP_MUX_BUS3, "24c02", 0x50, &port52_50_at24),
	mk_i2cdev(CPLD_SFP_MUX_BUS4, "sff8436", 0x50, &port53_50_sff8436),
	mk_i2cdev(CPLD_SFP_MUX_BUS5, "sff8436", 0x50, &port54_50_sff8436),
};

#define NUM_CPLD_MUX_DEVICES ARRAY_SIZE(cpld_devices)

static struct i2c_client *cpld_mux_clients[NUM_CPLD_MUX_DEVICES];

static struct mutex cpld_mux_mutex;

static int cpld_mux_i2c_reg_write(uint8_t reg, u8 val)
{
	int ret;
	struct i2c_adapter *adap;

	adap = systemcpld_client->adapter;

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

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

		data.byte = val;
		ret = adap->algo->smbus_xfer(adap, systemcpld_client->addr,
			systemcpld_client->flags,
			I2C_SMBUS_WRITE,
			reg, I2C_SMBUS_BYTE_DATA, &data);
	}
	return ret;
}

s32 cpld_mux_i2c_reg_read(uint8_t reg)
{
	s32 ret;
	struct i2c_adapter *adap;
	u8 data;

	adap = systemcpld_client->adapter;

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

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

		ret = adap->algo->smbus_xfer(adap, systemcpld_client->addr,
			systemcpld_client->flags,
			I2C_SMBUS_READ,
			reg, I2C_SMBUS_BYTE_DATA, &tdata);
		data = tdata.byte;
	}
	if (!ret)
		ret = data;
	return ret;
}

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

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

	mutex_lock(&cpld_mux_mutex);
	minfo->prev_chan = chan;
	val = cpld_mux_i2c_reg_read(minfo->mux_reg);
	val &= ~minfo->mux_mask;
	val |= (chan << minfo->mux_shift);
	ret = cpld_mux_i2c_reg_write(minfo->mux_reg, val);
	mutex_unlock(&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 *mdata;

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

static int cpld_mux_init(void)
{
	struct i2c_adapter *adapter;
	struct mux_data_item *mdata;
	struct mux_info_item *minfo;
	int ret, i, j;

	mutex_init(&cpld_mux_mutex);
	for (i = 0; i < NUM_MUXES; i++) {
		minfo = &mux_info[i];
		mdata = &mux_data[i];
		adapter = i2c_get_adapter(minfo->parent_bus);
		if (!adapter) {
			pr_err(DRIVER_NAME ": could not find i2c adapter for %s bus %d.\n",
			       minfo->mux_name, minfo->parent_bus);
			return -ENODEV;
		}

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

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

		mdata->mux_dev = platform_device_alloc(minfo->mux_name, 0);
		if (!mdata->mux_dev) {
			pr_err(DRIVER_NAME ": failed to allocate platform device for %s.\n",
			       minfo->mux_name);
			goto err_exit_dev;
		}

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

	return 0;

err_exit_dev_add:
	platform_device_put(mdata->mux_dev);

err_exit_dev:
	kfree(mdata->mux_adapters);

err_exit_adapters:
	kfree(mdata->mux_clients);

err_exit:
	cpld_mux_free_data();
	return -ENOMEM;
}

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

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

err_exit:
	return ret;
}

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

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev, "smbus read byte data not supported.\n");
		return -ENODEV;
	}

	/* probe the hardware by reading the revision numbers */
	ret = i2c_smbus_read_byte_data(client, 0);
	if (ret < 0) {
		dev_err(&client->dev, "read CPLD major revision register error %d\n", ret);
		goto err;
	}
	major_rev = ret;
	ret = i2c_smbus_read_byte_data(client, 1);
	if (ret < 0) {
		dev_err(&client->dev, "read CPLD minor revision register error %d\n", ret);
		goto err;
	}
	minor_rev = ret;

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

	/* add CPLD muxes */
	ret = cpld_mux_init();
	if (ret) {
		pr_err(DRIVER_NAME ": CPLD mux initialization failed: %d\n", ret);
		goto err;
	}

	/* add CPLD devices */
	ret = add_cpld_mux_devices();
	if (ret) {
		pr_err(DRIVER_NAME ": CPLD device initialization failed: %d\n", ret);
		goto err;
	};

	/* all clear from this point on */
	dev_info(&client->dev,
		 "device created, System CPLD major rev %d, minor rev %d\n",
		 major_rev & 0xff,
		 minor_rev & 0xff);
	return 0;

err:
	return ret;
}

static int systemcpld_remove(struct i2c_client *client)
{
	int i;
	struct i2c_client *c;
	struct mux_data_item *mdata;
	struct mux_info_item *minfo;

	/* Unregister the i2c clients */
	for (i = 0; i < NUM_CPLD_MUX_DEVICES; i++) {
		c = cpld_devices[i].client;
		if (c) {
			i2c_unregister_device(c);
			cpld_devices[i].client = NULL;
		}
	}

	for (i = 0; i < NUM_MUXES; i++) {
		minfo = &mux_info[i];
		mdata = &mux_data[i];
		for (i = 0; i < minfo->num_busses; i++)
			if (mdata->mux_adapters[i])
				i2c_del_mux_adapter(mdata->mux_adapters[i]);
	}

	sysfs_remove_group(&client->dev.kobj, &systemcpld_attr_group);
	dev_info(&client->dev, "device removed\n");
	return 0;
}

static const struct i2c_device_id systemcpld_id[] = {
	{ SYSTEMCPLD_NAME, 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, systemcpld_id);

static struct i2c_driver systemcpld_driver = {
	.driver = {
		.name  = SYSTEMCPLD_NAME,
		.owner = THIS_MODULE,
	},
	.probe = systemcpld_probe,
	.remove = systemcpld_remove,
	.id_table = systemcpld_id,
};

/* module interface */

static void cpld_exit(void)
{
	pr_info(DRIVER_NAME ": do i2c_del_driver(systemcpld)\n");
	i2c_del_driver(&systemcpld_driver);
	pr_info(DRIVER_NAME ": do i2c_del_driver(cpudcpld)\n");
	i2c_del_driver(&cpucpld_driver);
	pr_info(DRIVER_NAME ": CPLD drivers unloaded\n");
}

static int cpld_init(void)
{
	int ret;

	pr_info(DRIVER_NAME ": loading CPU CPLD driver\n");
	ret = i2c_add_driver(&cpucpld_driver);
	if (ret) {
		pr_err(DRIVER_NAME ": failed to add CPU CPLD driver (%i)\n",
		       ret);
		return ret;
	}

	pr_info(DRIVER_NAME ": loading System CPLD driver\n");
	ret = i2c_add_driver(&systemcpld_driver);
	if (ret) {
		pr_err(DRIVER_NAME ": failed to add System CPLD driver (%i)\n",
		       ret);
		i2c_del_driver(&cpucpld_driver);
		return ret;
	}

	pr_info(DRIVER_NAME ": CPLD drivers successfully loaded\n");
	return 0;
}

/* Module init and exit */

static int __init dell_n3248pxe_init(void)
{
	int ret;

	ret = i2c_init();
	if (ret) {
		pr_err(DRIVER_NAME ": version " DRIVER_VERSION
		       ": I2C initialization failed\n");
		i2c_exit();
		return ret;
	}

	ret = cpld_init();
	if (ret) {
		pr_err(DRIVER_NAME ": version " DRIVER_VERSION
		       ": CPLD initialization failed\n");
		cpld_exit();
		i2c_exit();
		return ret;
	}

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

static void __exit dell_n3248pxe_exit(void)
{
	pr_info(DRIVER_NAME ": do cpld_exit()\n");
	cpld_exit();
	pr_info(DRIVER_NAME ": cpld_exit() done\n");
	pr_info(DRIVER_NAME ": do i2c_exit()\n");
	i2c_exit();
	pr_info(DRIVER_NAME ": i2c_exit() done\n");
	pr_info(DRIVER_NAME ": driver unloaded\n");
}

module_init(dell_n3248pxe_init);
module_exit(dell_n3248pxe_exit);

MODULE_AUTHOR("David Yen (dhyen@cumulusnetworks.com)");
MODULE_DESCRIPTION("Dell N3248PXE Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
