/*
 * MTS-IO Controller
 *
 * Copyright (C) 2014 by Multi-Tech Systems
 *
 * Authors: James Maki <jmaki@multitech.com>
 *          Jesse Gilles <jgilles@multitech.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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/input.h>
#include <linux/cdev.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/reboot.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/spi/spi.h>
#include <linux/i2c/at24.h>
#include <linux/kmod.h>
#include <linux/io.h>
#include <linux/module.h>

#include "mts_io.h"

#define DRIVER_VERSION	"v0.9.1"
#define DRIVER_AUTHOR	"James Maki <jmaki@multitech.com>"
#define DRIVER_DESC	"MTS-IO Controller"
#define DRIVER_NAME	"mts-io"

#define PLATFORM_NAME	"mts-io"

#define LED_LS_CONTROLLABLE		0

#define AT91SAM9X5_BASE_ADC     0xf804c000

/* on-board EEPROM */
extern uint8_t mts_id_eeprom[512];
static struct mts_id_eeprom_layout id_eeprom;

/* accessory card EEPROMs */
#ifdef MTOCGD2
extern uint8_t mts_ap1_eeprom[512];
extern uint8_t mts_ap2_eeprom[512];
#else
uint8_t mts_ap1_eeprom[512] = {};
uint8_t mts_ap2_eeprom[512] = {};
#endif
static struct mts_ap_eeprom_layout ap1_eeprom;
static struct mts_ap_eeprom_layout ap2_eeprom;

bool accessory_card_capable = false;
bool have_accessory_card_slot_1 = false;
bool have_accessory_card_slot_2 = false;

static uint8_t mts_product_id;
static uint8_t mts_ap1_product_id;
static uint8_t mts_ap2_product_id;
static uint8_t has_spi_sout;
static uint8_t has_spi_din;
static uint8_t has_spi_dout;
static uint8_t has_spi_temp;

static struct platform_device *mts_io_platform_device;
static struct attribute_group *attr_group;
static struct gpio_pin *gpio_pins;

static DEFINE_MUTEX(mts_io_mutex);

/* generic GPIO support */
#include "gpio.c"

/* AT91 built-in ADC */
#include "adc.c"

/* SPI-based stuff */
#include "spi.c"

/* accessory card support */
#include "mtac_gpiob.c"
#include "mtac_mfser.c"

/* telit radio reset handling */
#include "telit_radio.c"

/* reset button handling */
#define RESET_CHECK_PER_SEC		8
#define RESET_INTERVAL		(HZ / RESET_CHECK_PER_SEC)
#define RESET_HOLD_COUNT	(RESET_CHECK_PER_SEC * 3)
#define RESET_LONG_HOLD_COUNT   (RESET_CHECK_PER_SEC * 30)

static pid_t reset_pid = -1;
static pid_t reset_count = 0;
static int reset_short_signal = SIGUSR1;
static int reset_long_signal = SIGUSR2;
static int reset_extra_long_signal = SIGHUP;

static void reset_callback(struct work_struct *ignored);

static DECLARE_DELAYED_WORK(reset_work, reset_callback);

static void reset_callback(struct work_struct *ignored)
{
	struct gpio_pin *pin;
	int reset_pressed = 0;
	struct pid *vpid = NULL;

	mutex_lock(&mts_io_mutex);

	pin = gpio_pin_by_name("DEVICE_RESET");
	if (pin) {
		reset_pressed = !gpio_get_value(pin->pin.gpio);
	}

	if (reset_pid > 0) {
		vpid = find_vpid(reset_pid);
	}

	if (vpid) {
		if (reset_pressed) {
			reset_count++;
		} else {
			//Reset button has not been pressed
			if (reset_count > 0 && reset_count < RESET_HOLD_COUNT) {
				kill_pid(vpid, reset_short_signal, 1);
				reset_count = 0;
			} else if (reset_count >= RESET_HOLD_COUNT && reset_count < RESET_LONG_HOLD_COUNT) {
				reset_count = 0;
				kill_pid(vpid, reset_long_signal, 1);
			}
		}

		if (reset_count >= RESET_LONG_HOLD_COUNT) {
			reset_count = 0;
			kill_pid(vpid, reset_extra_long_signal, 1);
		}
	} else {
		reset_count = 0;
	}

	mutex_unlock(&mts_io_mutex);

	schedule_delayed_work(&reset_work, RESET_INTERVAL);
}

static ssize_t mts_attr_show_reset_monitor(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int ret;

	mutex_lock(&mts_io_mutex);

	ret = sprintf(buf, "%d %d %d %d\n", reset_pid, reset_short_signal, reset_long_signal, reset_extra_long_signal);

	mutex_unlock(&mts_io_mutex);

	return ret;
}

static ssize_t mts_attr_store_reset_monitor(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	pid_t pid;
	int short_signal;
	int long_signal;
	int extra_long_signal;
	int result = sscanf(buf, "%i %i %i %i", &pid, &short_signal, &long_signal, &extra_long_signal);

	if (result < 3 || result > 4) {
		return -EINVAL;
	}

        if(result == 3) {
		mutex_lock(&mts_io_mutex);

		reset_pid = pid;
		reset_short_signal = short_signal;
		reset_long_signal = long_signal;

		mutex_unlock(&mts_io_mutex);
	} else {
		mutex_lock(&mts_io_mutex);

		reset_pid = pid;
		reset_short_signal = short_signal;
		reset_long_signal = long_signal;
		reset_extra_long_signal = extra_long_signal;

		mutex_unlock(&mts_io_mutex);
	}

	return count;
}

static DEVICE_ATTR_MTS(dev_attr_reset_monitor, "reset-monitor",
	mts_attr_show_reset_monitor, mts_attr_store_reset_monitor);
static DEVICE_ATTR_RO_MTS(dev_attr_reset, "reset", mts_attr_show_gpio_pin);

/* active-low socket modem reset */
static ssize_t mts_attr_store_radio_reset(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	int err;
	struct gpio_pin *pin;

	if (sscanf(buf, "%i", &value) != 1) {
		return -EINVAL;
	}
	if (value != 0) {
		return -EINVAL;
	}

	pin = gpio_pin_by_name("RADIO_RESET");

	if (!pin) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	// 250ms low reset
	err = reset_gpio_pin(pin, 250, 0);

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

static ssize_t mts_attr_store_ndc_reset(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	int err;
	struct gpio_pin *pin;

	if (sscanf(buf, "%i", &value) != 1) {
		return -EINVAL;
	}
	if (value != 0) {
		return -EINVAL;
	}

	pin = gpio_pin_by_name("NDC_RESET");

	if (!pin) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	// 1ms low reset
	err = reset_gpio_pin(pin, 1, 0);

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

static DEVICE_ATTR_MTS(dev_attr_radio_reset, "radio-reset",
	mts_attr_show_gpio_pin, mts_attr_store_radio_reset);
static DEVICE_ATTR_MTS(dev_attr_ndc_reset, "ndc-reset",
	mts_attr_show_gpio_pin, mts_attr_store_ndc_reset);

/* shared gpio attributes */
static DEVICE_ATTR_MTS(dev_attr_radio_power, "radio-power",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_eth0_enabled, "eth0-enabled",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_bt_enabled, "bt-enabled",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_wlan_enabled, "wlan-enabled",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_RO_MTS(dev_attr_extserial_dtr, "extserial-dtr", 
		mts_attr_show_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_extserial_dsr_gpio, "extserial-dsr", 
		mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_extserial_ri_gpio, "extserial-ri", 
		mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_extserial_dcd_gpio, "extserial-dcd",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

/* shared gpio-based LEDs */
static DEVICE_ATTR_MTS(dev_attr_led_status, "led-status",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_a_gpio, "led-a",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

#if LED_LS_CONTROLLABLE
static DEVICE_ATTR_MTS(dev_attr_led_ls, "led-ls",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
#else
static DEVICE_ATTR_RO_MTS(dev_attr_led_ls, "led-ls",
	mts_attr_show_gpio_pin);
#endif

static DEVICE_ATTR_MTS(dev_attr_led_wifi_gpio, "led-wifi",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_b_gpio, "led-b",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_led_cd_gpio, "led-cd",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_c_gpio, "led-c",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_led_sig1_gpio, "led-sig1",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_sig2_gpio, "led-sig2",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_sig3_gpio, "led-sig3",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_led_d_gpio, "led-d",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_e_gpio, "led-e",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_led_f_gpio, "led-f",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);


/* eeprom info */
static ssize_t mts_attr_show_product_info(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	ssize_t value;

	if (strcmp(attr->attr.name, "vendor-id") == 0) {
		value = sprintf(buf, "%.32s\n", id_eeprom.vendor_id);
	} else if (strcmp(attr->attr.name, "product-id") == 0) {
		value = sprintf(buf, "%.32s\n", id_eeprom.product_id);
	} else if (strcmp(attr->attr.name, "device-id") == 0) {
		value = sprintf(buf, "%.32s\n", id_eeprom.device_id);
	} else if (strcmp(attr->attr.name, "hw-version") == 0) {
		value = sprintf(buf, "%.32s\n", id_eeprom.hw_version);
	} else if (strcmp(attr->attr.name, "imei") == 0) {
		value = sprintf(buf, "%.32s\n", id_eeprom.imei);
	} else if (strcmp(attr->attr.name, "mac-wifi") == 0) {
		value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n",
			id_eeprom.mac_wifi[0],
			id_eeprom.mac_wifi[1],
			id_eeprom.mac_wifi[2],
			id_eeprom.mac_wifi[3],
			id_eeprom.mac_wifi[4],
			id_eeprom.mac_wifi[5]);
	} else if (strcmp(attr->attr.name, "mac-eth") == 0) {
		value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n",
			id_eeprom.mac_addr[0],
			id_eeprom.mac_addr[1],
			id_eeprom.mac_addr[2],
			id_eeprom.mac_addr[3],
			id_eeprom.mac_addr[4],
			id_eeprom.mac_addr[5]);
	} else {
		log_error("attribute '%s' not found", attr->attr.name);
		value = -1;
	}

	return value;
}

static DEVICE_ATTR_RO_MTS(dev_attr_vendor_id, "vendor-id",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_product_id, "product-id",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_device_id, "device-id",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_hw_version, "hw-version",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_imei, "imei",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_wifi_mac, "mac-wifi",
	mts_attr_show_product_info);
static DEVICE_ATTR_RO_MTS(dev_attr_eth_mac, "mac-eth",
	mts_attr_show_product_info);

/* include per-device pins and attributes */
//#include "mtr2.c"
#include "mtr.c"
#include "mtr2d2.c"

/* currently not supported
#include "mtcdp.c"
#include "mt100eocg.c"
*/

static bool add_accessory_card_attributes(int slot)
{
	size_t device_attrs_size;
	size_t card_attrs_size;
	size_t attrs_index;
	size_t i;

	struct attribute **device_attrs;
	struct attribute **card_attrs;

	switch (mts_product_id) {
		case MTR2D2_0_0:
			device_attrs_size = mtr2d2_platform_attributes_size;
			device_attrs = mtr2d2_platform_attributes;
			break;
		default:
			log_error("accessory cards aren't supported for platform %s", id_eeprom.hw_version);
			return false;
	}

	if (slot == 1) {
		switch (mts_ap1_product_id) {
			case MTAC_GPIOB_0_0:
				card_attrs_size = ap1_gpio_attributes_size;
				card_attrs = ap1_gpio_attributes;
				break;
			case MTAC_MFSER_0_0:
				card_attrs_size = ap1_mfser_attributes_size;
				card_attrs = ap1_mfser_attributes;
				break;
			default:
				log_error("accessory card %s isn't supported", ap1_eeprom.hw_version);
				return false;
		}
	} else if (slot == 2) {
		switch (mts_ap2_product_id) {
			case MTAC_GPIOB_0_0:
				card_attrs_size = ap2_gpio_attributes_size;
				card_attrs = ap2_gpio_attributes;
				break;
			case MTAC_MFSER_0_0:
				card_attrs_size = ap2_mfser_attributes_size;
				card_attrs = ap2_mfser_attributes;
				break;
			default:
				log_error("accessory card %s isn't supported", ap2_eeprom.hw_version);
				return false;
		}
	} else {
		log_error("%d is an invalid slot value", slot);
		return false;
	}

	for (attrs_index = 0; attrs_index < device_attrs_size; attrs_index++) {
		if (! device_attrs[attrs_index]) {
			break;
		}
	}

	if (device_attrs_size < attrs_index + card_attrs_size) {
		log_error("not enough room for accessory card attributes!");
		return false;
	}

	for (i = 0; i < card_attrs_size; i++) {
		device_attrs[attrs_index + i] = card_attrs[i];
	}

	return true;
}

static bool mts_ap_eeprom_load(void)
{
	// Accessory Card Slot 1
	memcpy(&ap1_eeprom, mts_ap1_eeprom, sizeof(mts_ap1_eeprom));

	if (mts_ap1_eeprom[0] == 0xFF) {
		log_error("uninitialized eeprom on accessory card 1");
	} else if (mts_ap1_eeprom[0] == 0x00) {
		log_info("no accessory card inserted in slot 1");
	} else {
		have_accessory_card_slot_1 = true;

		log_info("accessory card 1 vendor-id: %.32s", ap1_eeprom.vendor_id);
		log_info("accessory card 1 product-id: %.32s",  ap1_eeprom.product_id);
		log_info("accessory card 1 device-id: %.32s",  ap1_eeprom.device_id);
		log_info("accessory card 1 hw-version: %.32s",  ap1_eeprom.hw_version);
		if (strncmp(ap1_eeprom.product_id, PRODUCT_ID_MTAC_ETH, strlen(PRODUCT_ID_MTAC_ETH)) == 0) {
			log_info("accessory card 1 mac-addr: %02X:%02X:%02X:%02X:%02X:%02X",
			    ap1_eeprom.mac_addr[0],
			    ap1_eeprom.mac_addr[1],
			    ap1_eeprom.mac_addr[2],
			    ap1_eeprom.mac_addr[3],
			    ap1_eeprom.mac_addr[4],
			    ap1_eeprom.mac_addr[5]);
		}
	}

	// Accessory Card Slot 2
	memcpy(&ap2_eeprom, mts_ap2_eeprom, sizeof(mts_ap2_eeprom));

	if (mts_ap2_eeprom[0] == 0xFF) {
		log_error("uninitialized eeprom on accessory card 2");
	} else if (mts_ap2_eeprom[0] == 0x00) {
		log_info("no accessory card inserted in slot 2");
	} else {
		have_accessory_card_slot_2 = true;

		log_info("accessory card 2 vendor-id: %.32s", ap2_eeprom.vendor_id);
		log_info("accessory card 2 product-id: %.32s",  ap2_eeprom.product_id);
		log_info("accessory card 2 device-id: %.32s",  ap2_eeprom.device_id);
		log_info("accessory card 2 hw-version: %.32s",  ap2_eeprom.hw_version);
		if (strncmp(ap2_eeprom.product_id, PRODUCT_ID_MTAC_ETH, strlen(PRODUCT_ID_MTAC_ETH)) == 0) {
			log_info("accessory card 2 mac-addr: %02X:%02X:%02X:%02X:%02X:%02X",
			    ap2_eeprom.mac_addr[0],
			    ap2_eeprom.mac_addr[1],
			    ap2_eeprom.mac_addr[2],
			    ap2_eeprom.mac_addr[3],
			    ap2_eeprom.mac_addr[4],
			    ap2_eeprom.mac_addr[5]);
		}
	}

	// if either slot has a valid card, return true
	return (have_accessory_card_slot_1 || have_accessory_card_slot_2);
}

static int mts_id_eeprom_load(void)
{
	memcpy(&id_eeprom, mts_id_eeprom, sizeof(mts_id_eeprom));

	mts_product_id = MTCDP_E1_DK_1_0;
	has_spi_sout = 1;
	has_spi_din = 1;
	has_spi_dout = 1;
	has_spi_temp = 1;

	if (mts_id_eeprom[0] == 0xFF) {
		log_error("uninitialized eeprom");
		return -EIO;
		/*
	} else if (mts_id_eeprom[0] == 0x00) {
		strncpy(id_eeprom.vendor_id, VENDOR_ID_MULTITECH, sizeof(id_eeprom.vendor_id) - 1);
		strncpy(id_eeprom.product_id, PRODUCT_ID_MTCDP_E1_DK, sizeof(id_eeprom.product_id) - 1);
		strncpy(id_eeprom.device_id, "", sizeof(id_eeprom.device_id) - 1);
		strncpy(id_eeprom.hw_version, HW_VERSION_MTCDP_0_0, sizeof(id_eeprom.hw_version) - 1);

		DEVICE_CAPA_SET(id_eeprom.capa, CAPA_GPS);

		attr_group = &mtcdp_platform_attribute_group;
		gpio_pins = gpio_pins_mtcdp_0_0;
		mts_product_id = MTCDP_E1_DK_0_0;
		log_info("detected board %s", HW_VERSION_MTCDP_0_0);
	} else if (strncmp(id_eeprom.product_id, PRODUCT_ID_MT100EOCG, strlen(PRODUCT_ID_MT100EOCG)) == 0) {
		attr_group = &mt100eocg_platform_attribute_group;
		gpio_pins = gpio_pins_mt100eocg_0_0;
		mts_product_id = MT100EOCG_0_0;
		has_spi_sout = 0;
		has_spi_din = 1;
		has_spi_dout = 1;
		has_spi_temp = 1;
		log_info("detected board %s", HW_VERSION_MT100EOCG_0_0);
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR2_0_0, strlen(HW_VERSION_MTR2_0_0)) == 0) {
		attr_group = &mtr2_platform_attribute_group;
		gpio_pins = gpio_pins_mtr2_0_0;
		mts_product_id = MTR2_0_0;
		accessory_card_capable = true;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 1;
		log_info("detected board %s", HW_VERSION_MTR2_0_0);
		*/
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR_0_0, strlen(HW_VERSION_MTR_0_0)) == 0) {
		attr_group = &mtr_platform_attribute_group;
		gpio_pins = gpio_pins_mtr_0_0;
		mts_product_id = MTR_0_0;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 0;
		log_info("detected board %s", HW_VERSION_MTR_0_0);
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR_0_1, strlen(HW_VERSION_MTR_0_1)) == 0) {
		attr_group = &mtr_platform_attribute_group;
		gpio_pins = gpio_pins_mtr_0_1;
		mts_product_id = MTR_0_1;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 0;
		log_info("detected board %s", HW_VERSION_MTR_0_1);
		/*
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTOCGD2_0_0, strlen(HW_VERSION_MTOCGD2_0_0)) == 0) {
		attr_group = &mtr2_platform_attribute_group;
		gpio_pins = gpio_pins_mtr2_0_0;
		mts_product_id = MTOCGD2_0_0;
		accessory_card_capable = true;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 1;
		log_info("detected board %s", HW_VERSION_MTOCGD2_0_0);
		*/
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTOCGD_0_0, strlen(HW_VERSION_MTOCGD_0_0)) == 0) {
		attr_group = &mtr_platform_attribute_group;
		gpio_pins = gpio_pins_mtr_0_0;
		mts_product_id = MTOCGD_0_0;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 0;
		log_info("detected board %s", HW_VERSION_MTOCGD_0_0);
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTOCGD_0_1, strlen(HW_VERSION_MTOCGD_0_1)) == 0) {
		attr_group = &mtr_platform_attribute_group;
		gpio_pins = gpio_pins_mtr_0_1;
		mts_product_id = MTOCGD_0_1;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 0;
		log_info("detected board %s", HW_VERSION_MTOCGD_0_1);
	} else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR2D2_0_0, strlen(HW_VERSION_MTR2D2_0_0)) == 0) {
		attr_group = &mtr2d2_platform_attribute_group;
		gpio_pins = gpio_pins_mtr2d2_0_0;
		mts_product_id = MTR2D2_0_0;
		accessory_card_capable = true;
		has_spi_sout = 0;
		has_spi_din = 0;
		has_spi_dout = 0;
		has_spi_temp = 0;
		log_info("detected board %s", HW_VERSION_MTR2D2_0_0);
		/*
	} else {
		attr_group = &mtcdp_platform_attribute_group;
		gpio_pins = gpio_pins_mtcdp_1_0;
		mts_product_id = MTCDP_E1_DK_1_0;
		has_spi_sout = 1;
		has_spi_din = 1;
		has_spi_dout = 1;
		has_spi_temp = 1;
		log_info("detected board %s", HW_VERSION_MTCDP_1_0);
		*/
	}

	log_info("sizeof: %lu", (unsigned long) sizeof(struct mts_id_eeprom_layout));
	log_info("vendor-id: %.32s", id_eeprom.vendor_id);
	log_info("product-id: %.32s",  id_eeprom.product_id);
	log_info("device-id: %.32s",  id_eeprom.device_id);
	log_info("hw-version: %.32s",  id_eeprom.hw_version);
	log_info("mac-addr: %02X:%02X:%02X:%02X:%02X:%02X",
            id_eeprom.mac_addr[0],
            id_eeprom.mac_addr[1],
            id_eeprom.mac_addr[2],
            id_eeprom.mac_addr[3],
            id_eeprom.mac_addr[4],
            id_eeprom.mac_addr[5]);
	log_info("imei: %.32s",  id_eeprom.imei);
	log_info("capa-gps: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_GPS) ? "yes" : "no");
	log_info("capa-din: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DIN) ? "yes" : "no");
	log_info("capa-dout: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT) ? "yes" : "no");
	log_info("capa-adc: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_ADC) ? "yes" : "no");
	log_info("capa-wifi: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI) ? "yes" : "no");
	log_info("capa-bluetooth: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH) ? "yes" : "no");
	if (DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH)) {
		log_info("mac-bluetooth: %02X:%02X:%02X:%02X:%02X:%02X",
			id_eeprom.mac_bluetooth[0],
			id_eeprom.mac_bluetooth[1],
			id_eeprom.mac_bluetooth[2],
			id_eeprom.mac_bluetooth[3],
			id_eeprom.mac_bluetooth[4],
			id_eeprom.mac_bluetooth[5]);
	}
	if (DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) {
		log_info("mac-wifi: %02X:%02X:%02X:%02X:%02X:%02X",
			id_eeprom.mac_wifi[0],
			id_eeprom.mac_wifi[1],
			id_eeprom.mac_wifi[2],
			id_eeprom.mac_wifi[3],
			id_eeprom.mac_wifi[4],
			id_eeprom.mac_wifi[5]);
	}

	return 0;
}

static int __init mts_io_init(void)
{
	struct gpio_pin *pin;
	int ret;
	size_t device_attributes_size;
	size_t card_attributes_size;

	log_info("init: " DRIVER_VERSION);
	
	ret = mts_id_eeprom_load();
	if (ret) {
		goto error1;
	}

	if (accessory_card_capable) {
		mts_ap1_product_id = MTAC_NONE;
		mts_ap2_product_id = MTAC_NONE;
		if (mts_ap_eeprom_load()) {
			// handle both slots, but do slot 1 first
			// probably need special handling if both cards are the same type
			if (have_accessory_card_slot_1) {
				// more elegant way to handle this?
				if (strstr(ap1_eeprom.product_id, PRODUCT_ID_MTAC_GPIOB)) {
					mts_ap1_product_id = MTAC_GPIOB_0_0;
				}
				else if (strstr(ap1_eeprom.product_id, PRODUCT_ID_MTAC_MFSER)) {
					mts_ap1_product_id = MTAC_MFSER_0_0;
				}

				switch(mts_ap1_product_id) {
					case MTAC_GPIOB_0_0:
						log_info("loading GPIO accessory card in slot 1");
						if (! add_accessory_card_attributes(1)) {
							log_error("failed to load GPIO accessory card in slot 1");
						} else {
							log_info("successfully loaded GPIO accessory card in slot 1");
						}
						log_debug("registering accessory card 1 dout driver");
						ret = spi_register_driver(&mts_spi_ap1_dout_driver);
						if (ret) {
							log_error("failed to register accessory card 1 dout driver");
							spi_unregister_driver(&mts_spi_ap1_dout_driver);
							goto error1;
						}
						log_debug("registering accessory card 1 din driver");
						ret = spi_register_driver(&mts_spi_ap1_din_driver);
						if (ret) {
							log_error("failed to register accessory card 1 din driver");
							spi_unregister_driver(&mts_spi_ap1_din_driver);
							goto error1;
						}
						log_debug("registering accessory card 1 adc driver");
						ret = spi_register_driver(&mts_spi_ap1_adc_driver);
						if (ret) {
							log_error("failed to register accessory card 1 adc driver");
							spi_unregister_driver(&mts_spi_ap1_adc_driver);
							goto error1;
						}
						break;
					case MTAC_MFSER_0_0:
						log_info("loading MFSER accessory card in slot 1");
						if (! add_accessory_card_attributes(1)) {
							log_error("failed to load MFSER accessory card in slot 1");
						} else {
							log_info("successfully loaded MFSER accessory card in slot 1");
						}
						break;

					default:
						log_error("accessory card %s unsupported", ap1_eeprom.product_id);
				}
			}

			if (have_accessory_card_slot_2) {
				// more elegant way to handle this?
				if (strstr(ap2_eeprom.product_id, PRODUCT_ID_MTAC_GPIOB)) {
					mts_ap2_product_id = MTAC_GPIOB_0_0;
				}
				else if (strstr(ap2_eeprom.product_id, PRODUCT_ID_MTAC_MFSER)) {
					mts_ap2_product_id = MTAC_MFSER_0_0;
				}

				switch(mts_ap2_product_id) {
					case MTAC_GPIOB_0_0:
						log_info("loading GPIO accessory card in slot 2");
						if (! add_accessory_card_attributes(2)) {
							log_error("failed to load GPIO accessory card in slot 2");
						} else {
							log_info("successfully loaded GPIO accessory card in slot 2");
						}
						log_debug("registering accessory card 2 dout driver");
						ret = spi_register_driver(&mts_spi_ap2_dout_driver);
						if (ret) {
							log_error("failed to register accessory card 2 dout driver");
							spi_unregister_driver(&mts_spi_ap2_dout_driver);
							goto error1;
						}
						log_debug("registering accessory card 2 din driver");
						ret = spi_register_driver(&mts_spi_ap2_din_driver);
						if (ret) {
							log_error("failed to register accessory card 2 din driver");
							spi_unregister_driver(&mts_spi_ap2_din_driver);
							goto error1;
						}
						log_debug("registering accessory card 2 adc driver");
						ret = spi_register_driver(&mts_spi_ap2_adc_driver);
						if (ret) {
							log_error("failed to register accessory card 2 adc driver");
							spi_unregister_driver(&mts_spi_ap2_adc_driver);
							goto error1;
						}
						break;
					case MTAC_MFSER_0_0:
						log_info("loading MFSER accessory card in slot 2");
						if (! add_accessory_card_attributes(2)) {
							log_error("failed to load MFSER accessory card in slot 2");
						} else {
							log_info("successfully loaded MFSER accessory card in slot 2");
						}
						break;

					default:
						log_error("accessory card %s unsupported", ap1_eeprom.product_id);
				}
			}
		}
	}

	mts_io_platform_device = platform_device_alloc(PLATFORM_NAME, -1);
	if (!mts_io_platform_device) {
		ret = -ENOMEM;
		goto error1;
	}

	ret = platform_device_add(mts_io_platform_device);
	if (ret) {
		goto error2;
	}

	/* preserve backwards compatibility with old mtcdp platform name */
	ret = sysfs_create_link(&mts_io_platform_device->dev.parent->kobj,
			&mts_io_platform_device->dev.kobj,
			"mtcdp");
	if (ret) {
		log_error("sysfs_create_link failed: %d", ret);
		goto error3;
	}

	ret = sysfs_create_group(&mts_io_platform_device->dev.kobj, attr_group);
	if (ret) {
		goto error4;
	}

	if ( has_spi_sout ) {
		ret = spi_register_driver(&mts_spi_sout_driver);
		if (ret) {
			goto error5;
		}
	}

	if ( has_spi_dout ) {
		ret = spi_register_driver(&mts_spi_dout_driver);
		if (ret) {
			goto error6;
		}
	}
	if ( has_spi_din ) {
		ret = spi_register_driver(&mts_spi_din_driver);
		if (ret) {
			goto error7;
		}
	}

	if ( has_spi_temp ) {
		ret = spi_register_driver(&mts_spi_board_temp_driver);
		if (ret) {
			goto error8;
		}
	}

	/* ADC Setup */
#ifdef CONFIG_SOC_AT91SAM9X5
	adc_base = ioremap(AT91SAM9X5_BASE_ADC, SZ_16K);
#else
	adc_base = ioremap(AT91SAM9260_BASE_ADC, SZ_16K);
#endif
	if (!adc_base) {
		goto error9;
	}

	adc_clk = clk_get(NULL, "adc_clk");
	if (!adc_clk) {
		goto error10;
	}
	clk_enable(adc_clk);

	ADC_CONVERT_RESET(adc_base);
	writel(ADC_MODE_DEFAULT, adc_base + ADC_MR_OFFSET);
	writel(0x000F0F0F, adc_base + ADC_IDR_OFFSET);
	if (!DEVICE_CAPA(id_eeprom.capa, CAPA_ADC)) {
		writel(0x0F, adc_base + ADC_CHDR_OFFSET);
	}

	for (pin = gpio_pins; *pin->name; pin++) {
		ret = gpio_request_one(pin->pin.gpio, pin->pin.flags, pin->pin.label);
		if (ret) {
			log_debug("could not request pin %s (%d) but it could have already been requested under a different pin name", pin->name, ret);
		}
	}

	if ( has_spi_sout || has_spi_dout || has_spi_din ) {
		pin = gpio_pin_by_name("ENIO");
		if (pin) {
			gpio_set_value(pin->pin.gpio, 0);
		}
	}

	// No reset_callback for MT100EOCG
	if ( mts_product_id != MT100EOCG_0_0 ) {
		reset_callback(NULL);
	}

	return 0;

error10:
	iounmap(adc_base);
error9:
	if ( has_spi_temp ) {
		spi_unregister_driver(&mts_spi_board_temp_driver);
	}
error8:
	if ( has_spi_din ) {
		spi_unregister_driver(&mts_spi_din_driver);
	}
error7:
	if ( has_spi_dout ) {
		spi_unregister_driver(&mts_spi_dout_driver);
	}
error6:
	if ( has_spi_sout ) {
		spi_unregister_driver(&mts_spi_sout_driver);
	}
error5:
	sysfs_remove_group(&mts_io_platform_device->dev.kobj, attr_group);
error4:
	sysfs_remove_link(&mts_io_platform_device->dev.parent->kobj, "mtcdp");
error3:
	platform_device_del(mts_io_platform_device);
error2:
	platform_device_put(mts_io_platform_device);

	if (have_accessory_card_slot_1) {
		switch (mts_ap1_product_id) {
			case MTAC_GPIOB_0_0:
				spi_unregister_driver(&mts_spi_ap1_dout_driver);
				spi_unregister_driver(&mts_spi_ap1_din_driver);
				spi_unregister_driver(&mts_spi_ap1_adc_driver);
				break;

			default:
				break;
		}
	}

	if (have_accessory_card_slot_2) {
		switch (mts_ap2_product_id) {
			case MTAC_GPIOB_0_0:
				spi_unregister_driver(&mts_spi_ap2_dout_driver);
				spi_unregister_driver(&mts_spi_ap2_din_driver);
				spi_unregister_driver(&mts_spi_ap2_adc_driver);
				break;

			default:
				break;
		}
	}
error1:
	log_error("init failed: %d", ret);

	return ret;
}

static void __exit mts_io_exit(void)
{
	if ( mts_product_id != MT100EOCG_0_0 ) {
		cancel_delayed_work_sync(&reset_work);
	}

	iounmap(adc_base);
	clk_disable(adc_clk);
	clk_put(adc_clk);

	if (has_spi_temp)
		spi_unregister_driver(&mts_spi_board_temp_driver);

	if (has_spi_din)
		spi_unregister_driver(&mts_spi_din_driver);

	if (has_spi_dout)
		spi_unregister_driver(&mts_spi_dout_driver);

	if (has_spi_sout)
		spi_unregister_driver(&mts_spi_sout_driver);

	if (have_accessory_card_slot_1) {
		switch (mts_ap1_product_id) {
			case MTAC_GPIOB_0_0:
				spi_unregister_driver(&mts_spi_ap1_dout_driver);
				spi_unregister_driver(&mts_spi_ap1_din_driver);
				spi_unregister_driver(&mts_spi_ap1_adc_driver);
				break;

			default:
				break;
		}
	}

	if (have_accessory_card_slot_2) {
		switch (mts_ap2_product_id) {
			case MTAC_GPIOB_0_0:
				spi_unregister_driver(&mts_spi_ap2_dout_driver);
				spi_unregister_driver(&mts_spi_ap2_din_driver);
				spi_unregister_driver(&mts_spi_ap2_adc_driver);
				break;

			default:
				break;
		}
	}

	sysfs_remove_group(&mts_io_platform_device->dev.kobj, attr_group);

	sysfs_remove_link(&mts_io_platform_device->dev.parent->kobj, "mtcdp");

	platform_device_unregister(mts_io_platform_device);

	log_info("exiting");
}

module_init(mts_io_init);
module_exit(mts_io_exit);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");

MODULE_ALIAS("mts-io-sout");
MODULE_ALIAS("mts-io-board-temp");
MODULE_ALIAS("mts-io-dout");
MODULE_ALIAS("mts-io-din");
MODULE_ALIAS("mts-io-ap1-dout");
MODULE_ALIAS("mts-io-ap1-din");
MODULE_ALIAS("mts-io-ap1-adc");
MODULE_ALIAS("mts-io-ap2-dout");
MODULE_ALIAS("mts-io-ap2-din");
MODULE_ALIAS("mts-io-ap2-adc");