/*
 * quanta_ix7_platform.c - Quanta IX7 Platform Support.
 *
 * Copyright (C) 2018 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/platform_device.h>
#include <linux/i2c/sff-8436.h>
#include <linux/i2c/pca954x.h>
#include <linux/platform_data/pca953x.h>
#include <linux/platform_data/at24.h>
#include <linux/gpio.h>

#include <linux/cumulus-platform.h>
#include "platform_defs.h"
#include "quanta_ix7.h"

#define DRIVER_NAME	"quanta_ix7_platform"
#define DRIVER_VERSION	"1.0"

/*
 * The ismt bus is connected to the following devices on the CPU board:
 *
 *    pca9546 4-channel mux (0x71)
 *	 0 pca9555 io expander (0x20)
 *	 2 pca9554 io expander (0x21)
 *	 3 pca9555 io expander (0x22)
 *    so-dimm spd eeprom (0x52)
 *
 * The i801 bus is connected to the following devices on the main board:
 *    pca9546 4-channel mux (0x72)
 *	 0 cpld#2 io expander qsfp28 1-16 (0x38)
 *	   cpld#1 port leds qsfp28 1-16 (0x39)
 *	 1 cpld#3 io expander qsfp28 17-32 (0x38)
 *	   cpld#4 port leds qsfp28 17-32 (0x39)
 *	 2 board eeprom (0x54)
 *	 3 pca9555 8-bit io expander for board id (0x23)
 *    pca9548 8-channel mux (0x77)
 *	 0 pca9548 8 channel mux qsfp28 1-8 (0x73)
 *	 1 pca9548 8 channel mux qsfp28 9-16 (0x73)
 *	 2 pca9548 8 channel mux qsfp28 17-24 (0x73)
 *	 3 pca9548 8 channel mux qsfp28 25-32 (0x73)
 */

enum {
	I2C_ISMT_BUS = 0,
	I2C_I801_BUS = 1,

	I2C_MUX1_BUS0 = 10,
	I2C_MUX1_BUS1,
	I2C_MUX1_BUS2,
	I2C_MUX1_BUS3,

	I2C_MUX2_BUS0,
	I2C_MUX2_BUS1,
	I2C_MUX2_BUS2,
	I2C_MUX2_BUS3,

	I2C_MUX3_BUS0,
	I2C_MUX3_BUS1,
	I2C_MUX3_BUS2,
	I2C_MUX3_BUS3,
	I2C_MUX3_BUS4,
	I2C_MUX3_BUS5,
	I2C_MUX3_BUS6,
	I2C_MUX3_BUS7,

	I2C_MUX4_BUS0 = 31,
	I2C_MUX4_BUS1,
	I2C_MUX4_BUS2,
	I2C_MUX4_BUS3,
	I2C_MUX4_BUS4,
	I2C_MUX4_BUS5,
	I2C_MUX4_BUS6,
	I2C_MUX4_BUS7,

	I2C_MUX5_BUS0,
	I2C_MUX5_BUS1,
	I2C_MUX5_BUS2,
	I2C_MUX5_BUS3,
	I2C_MUX5_BUS4,
	I2C_MUX5_BUS5,
	I2C_MUX5_BUS6,
	I2C_MUX5_BUS7,

	I2C_MUX6_BUS0,
	I2C_MUX6_BUS1,
	I2C_MUX6_BUS2,
	I2C_MUX6_BUS3,
	I2C_MUX6_BUS4,
	I2C_MUX6_BUS5,
	I2C_MUX6_BUS6,
	I2C_MUX6_BUS7,

	I2C_MUX7_BUS0,
	I2C_MUX7_BUS1,
	I2C_MUX7_BUS2,
	I2C_MUX7_BUS3,
	I2C_MUX7_BUS4,
	I2C_MUX7_BUS5,
	I2C_MUX7_BUS6,
	I2C_MUX7_BUS7,
};

/*
 * The list of I2C devices and their bus connections for this platform.
 *
 * Each entry is a bus number and a i2c_board_info.
 * The i2c_board_info specifies the device type, address,
 * and platform data depending on the device type.
 *
 * 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.
 * All done in the magic 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.
 */

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

mk_pca9548(mux4, I2C_MUX4_BUS0, 1)
mk_pca9548(mux5, I2C_MUX5_BUS0, 1)
mk_pca9548(mux6, I2C_MUX6_BUS0, 1)
mk_pca9548(mux7, I2C_MUX7_BUS0, 1)

mk_eeprom(board, 54, 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 quanta_ix7_platform_data cpld1 = {
	.idx = IX7_LED_QSFP28_1_16_CPLD_ID,
};

static struct quanta_ix7_platform_data cpld2 = {
	.idx = IX7_IO_QSFP28_1_16_CPLD_ID,
};

static struct quanta_ix7_platform_data cpld3 = {
	.idx = IX7_IO_QSFP28_17_32_CPLD_ID,
};

static struct quanta_ix7_platform_data cpld4 = {
	.idx = IX7_LED_QSFP28_17_32_CPLD_ID,
};

/*
 * GPIO definitions
 * num, name, output, active_low, value
 */

mk_gpio_pins(cpu_gpio_20) = {
	mk_gpio_pin(0,	mb_i2c_rst,	     1, 1, 1),
	mk_gpio_pin(1,	mb_led_rst,	     1, 1, 1),
	mk_gpio_pin(2,	mb_eth_rst,	     1, 1, 1),
	mk_gpio_pin(3,	mb_sw_rst,	     1, 1, 1),
	mk_gpio_pin(7,	usb_rst,	     1, 1, 1),

	mk_gpio_pin(9,	i2c_switch,	     1, 0, 1),
	mk_gpio_pin(10, boot_led,	     1, 1, 1),
	mk_gpio_pin(11, sys_led,	     1, 1, 0),
	mk_gpio_pin(12, board_pwr_led,	     1, 0, 1),
};

mk_gpio_pins(cpu_gpio_21) = {
	mk_gpio_pin(0,	sb_id0,		     0, 0, 0),
	mk_gpio_pin(1,	sb_id1,		     0, 0, 0),
	mk_gpio_pin(2,	sb_id2,		     0, 0, 0),
	mk_gpio_pin(3,	sb_id3,		     0, 0, 0),
	mk_gpio_pin(4,	sb_id4,		     0, 0, 0),
	mk_gpio_pin(5,	sb_id5,		     0, 0, 0),
};

mk_gpio_pins(cpu_gpio_22) = {
	mk_gpio_pin(3,	sb_present,	     0, 1, 0),
	mk_gpio_pin(4,	usb_overcurrent,     0, 1, 0),
	mk_gpio_pin(9,	mb_pca9555_int,	     0, 1, 0),
	mk_gpio_pin(11, psoc_irq,	     0, 1, 0),
	mk_gpio_pin(12, psu_9555_irq,	     0, 1, 0),
	mk_gpio_pin(15, bios_wp,	     1, 1, 0),
};

mk_gpio_pins(mb_gpio_23) = {
	mk_gpio_pin(0,	cpld23_int,	     0, 1, 0),
	mk_gpio_pin(1,	cpld14_int,	     0, 1, 0),
	mk_gpio_pin(4,	qsfp28_power_good,   0, 0, 0),
	mk_gpio_pin(5,	qsfp28_power_enable, 1, 0, 1),
	mk_gpio_pin(6,	mac_reset,	     1, 1, 1),
	mk_gpio_pin(7,	usb_reset,	     1, 1, 1),
	mk_gpio_pin(8,	mgmt_present,	     0, 1, 0),
	mk_gpio_pin(10, board_id_0,	     0, 0, 0),
	mk_gpio_pin(11, board_id_1,	     0, 0, 0),
	mk_gpio_pin(12, board_id_2,	     0, 0, 0),
	mk_gpio_pin(13, board_id_3,	     0, 0, 0),
	mk_gpio_pin(14, board_id_4,	     0, 0, 0),
	mk_gpio_pin(15, board_id_5,	     0, 0, 0),
};

mk_gpio_platform_data(cpu_gpio_20, IX7_CPU_GPIO_20_BASE, PCA_9555_GPIO_COUNT);
mk_gpio_platform_data(cpu_gpio_21, IX7_CPU_GPIO_21_BASE, PCA_9554_GPIO_COUNT);
mk_gpio_platform_data(cpu_gpio_22, IX7_CPU_GPIO_22_BASE, PCA_9555_GPIO_COUNT);

mk_gpio_platform_data(mb_gpio_23,  IX7_MB_GPIO_23_BASE, PCA_9555_GPIO_COUNT);

static struct platform_i2c_device_info i2c_devices[] = {
	/* cpu board */
	mk_i2cdev(I2C_I801_BUS,	 "pca9546",  0x71, &mux1_platform_data),
	mk_i2cdev(I2C_MUX1_BUS0, "pca9555",  0x20, &cpu_gpio_20_platform_data),
	mk_i2cdev(I2C_MUX1_BUS2, "pca9554",  0x21, &cpu_gpio_21_platform_data),
	mk_i2cdev(I2C_MUX1_BUS3, "pca9555",  0x22, &cpu_gpio_22_platform_data),

	/* main board */
	mk_i2cdev(I2C_I801_BUS,	 "pca9546",  0x72, &mux2_platform_data),
	mk_i2cdev(I2C_I801_BUS,	 "pca9548",  0x77, &mux3_platform_data),

	/* mux2 */
	mk_i2cdev(I2C_MUX2_BUS0, "quanta_ix7_cpld", 0x38, &cpld2),
	mk_i2cdev(I2C_MUX2_BUS0, "quanta_ix7_cpld", 0x39, &cpld1),
	mk_i2cdev(I2C_MUX2_BUS1, "quanta_ix7_cpld", 0x38, &cpld3),
	mk_i2cdev(I2C_MUX2_BUS1, "quanta_ix7_cpld", 0x39, &cpld4),
	mk_i2cdev(I2C_MUX2_BUS2, "24c02",    0x54, &board_54_at24),
	mk_i2cdev(I2C_MUX2_BUS3, "pca9555",  0x23, &mb_gpio_23_platform_data),

	/* mux3 */
	mk_i2cdev(I2C_MUX3_BUS0, "pca9548",  0x73, &mux4_platform_data),
	mk_i2cdev(I2C_MUX3_BUS1, "pca9548",  0x73, &mux5_platform_data),
	mk_i2cdev(I2C_MUX3_BUS2, "pca9548",  0x73, &mux6_platform_data),
	mk_i2cdev(I2C_MUX3_BUS3, "pca9548",  0x73, &mux7_platform_data),

	/* mux4 */
	mk_i2cdev(I2C_MUX4_BUS0, "sff8436",  0x50, &port1_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS1, "sff8436",  0x50, &port2_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS2, "sff8436",  0x50, &port3_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS3, "sff8436",  0x50, &port4_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS4, "sff8436",  0x50, &port5_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS5, "sff8436",  0x50, &port6_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS6, "sff8436",  0x50, &port7_50_sff8436),
	mk_i2cdev(I2C_MUX4_BUS7, "sff8436",  0x50, &port8_50_sff8436),

	/* mux5 */
	mk_i2cdev(I2C_MUX5_BUS0, "sff8436",  0x50, &port9_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS1, "sff8436",  0x50, &port10_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS2, "sff8436",  0x50, &port11_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS3, "sff8436",  0x50, &port12_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS4, "sff8436",  0x50, &port13_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS5, "sff8436",  0x50, &port14_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS6, "sff8436",  0x50, &port15_50_sff8436),
	mk_i2cdev(I2C_MUX5_BUS7, "sff8436",  0x50, &port16_50_sff8436),

	/* mux6 */
	mk_i2cdev(I2C_MUX6_BUS0, "sff8436",  0x50, &port17_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS1, "sff8436",  0x50, &port18_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS2, "sff8436",  0x50, &port19_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS3, "sff8436",  0x50, &port20_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS4, "sff8436",  0x50, &port21_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS5, "sff8436",  0x50, &port22_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS6, "sff8436",  0x50, &port23_50_sff8436),
	mk_i2cdev(I2C_MUX6_BUS7, "sff8436",  0x50, &port24_50_sff8436),

	/* mux7 */
	mk_i2cdev(I2C_MUX7_BUS0, "sff8436",  0x50, &port25_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS1, "sff8436",  0x50, &port26_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS2, "sff8436",  0x50, &port27_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS3, "sff8436",  0x50, &port28_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS4, "sff8436",  0x50, &port29_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS5, "sff8436",  0x50, &port30_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS6, "sff8436",  0x50, &port31_50_sff8436),
	mk_i2cdev(I2C_MUX7_BUS7, "sff8436",  0x50, &port32_50_sff8436),
};

/*
 * Utility functions for I2C
 */

static void i2c_exit(void)
{
	int i;

	for (i = ARRAY_SIZE(i2c_devices); --i >= 0;) {
		struct i2c_client *c = i2c_devices[i].client;

		if (c) {
			i2c_devices[i].client = NULL;
			i2c_unregister_device(c);
		}
	}
}

static int i2c_init(void)
{
	int ismt_bus = -1;
	int i801_bus = -1;
	int i;
	int ret;

	ret = -1;
	ismt_bus = cumulus_i2c_find_adapter(SMBUS_ISMT_NAME);
	if (ismt_bus < 0) {
		pr_err("could not find iSMT adapter bus\n");
		ret = -ENODEV;
		goto err_exit;
	}
	i801_bus = cumulus_i2c_find_adapter(SMBUS_I801_NAME);
	if (i801_bus < 0) {
		pr_err("could not find 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;

		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:
	return ret;
}

/*
 * Utility functions for GPIO
 */

static void init_gpio_platform_data(struct gpio_pin *pins,
				    int num_pins,
				    struct pca953x_platform_data *pdata)
{
	int i;
	/* pdata->names is *const*, we have to cast it */
	const char **names = (const char **)pdata->names;

	for (i = 0; i < num_pins; i++) {
		names[pins[i].num] = pins[i].name;
		pins[i].num += pdata->gpio_base;
	}
}

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

	pr_info("Registering %d GPIO pins\n", num_pins);
	for (i = 0; i < num_pins; i++) {
		struct gpio_pin *pin = pins + i;
		int bail;
		/* try a few times because probe happens asynchronously */
		for (bail = 10; --bail >= 0;) {
			ret = gpio_request(pin->num, NULL);
			if (!ret)
				break;
			pr_err("failed to request GPIO pin %s (%d), err %d\n",
			       pin->name, pin->num, ret);
			if (ret != -EPROBE_DEFER)
				break;
			msleep(100);
		}
		if (ret)
			return ret;
		pin->allocated = true;

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

		ret = gpio_sysfs_set_active_low(pin->num, pin->active_low);
		if (ret) {
			pr_err("failed to set active_low on GPIO pin %s (%u), err %d\n",
			       pin->name, pin->num, ret);
			return ret;
		}

		ret = gpio_export(pin->num, false);
		if (ret) {
			pr_err("failed to export GPIO pin %s (%u), err %d\n",
			       pin->name, pin->num, ret);
			return ret;
		pr_info("GPIO pin %s (%d) initialized\n", pin->name, pin->num);
		}
	}
	return 0;
}

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

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

/*
 * Module init and exit for all I2C devices, including GPIO
 */

static void free_all(void)
{
	free_gpio_pins(mb_gpio_23_pins, ARRAY_SIZE(mb_gpio_23_pins));
	free_gpio_pins(cpu_gpio_22_pins, ARRAY_SIZE(cpu_gpio_22_pins));
	free_gpio_pins(cpu_gpio_21_pins, ARRAY_SIZE(cpu_gpio_21_pins));
	free_gpio_pins(cpu_gpio_20_pins, ARRAY_SIZE(cpu_gpio_20_pins));

	i2c_exit();
}

static int __init ix7_platform_init(void)
{
	int ret;

	init_gpio_platform_data(cpu_gpio_20_pins, ARRAY_SIZE(cpu_gpio_20_pins),
				&cpu_gpio_20_platform_data);
	init_gpio_platform_data(cpu_gpio_21_pins, ARRAY_SIZE(cpu_gpio_21_pins),
				&cpu_gpio_21_platform_data);
	init_gpio_platform_data(cpu_gpio_22_pins, ARRAY_SIZE(cpu_gpio_22_pins),
				&cpu_gpio_22_platform_data);
	init_gpio_platform_data(mb_gpio_23_pins, ARRAY_SIZE(mb_gpio_23_pins),
				&mb_gpio_23_platform_data);

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

	ret = init_gpio_pins(cpu_gpio_20_pins, ARRAY_SIZE(cpu_gpio_20_pins));
	if (ret)
		goto err_exit;
	ret = init_gpio_pins(cpu_gpio_21_pins, ARRAY_SIZE(cpu_gpio_21_pins));
	if (ret)
		goto err_exit;
	ret = init_gpio_pins(cpu_gpio_22_pins, ARRAY_SIZE(cpu_gpio_22_pins));
	if (ret)
		goto err_exit;
	ret = init_gpio_pins(mb_gpio_23_pins, ARRAY_SIZE(mb_gpio_23_pins));
	if (ret)
		goto err_exit;

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

err_exit:
	pr_err("I2C initialization failed\n");
	free_all();
	return ret;
}

static void __exit ix7_platform_exit(void)
{
	free_all();
	pr_info(DRIVER_NAME ": version " DRIVER_VERSION " driver unloaded\n");
}

module_init(ix7_platform_init);
module_exit(ix7_platform_exit);

MODULE_AUTHOR("David Yen (dhyen@cumulusnetworks.com)");
MODULE_DESCRIPTION("Quanta IX7 Platform Support");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
