/*
 * MTS-IO Controller
 *
 * Copyright (C) 2013 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 "mts_io.h"

#define DRIVER_VERSION	"v0.8.2"
#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 DEBUG		0

#define __log(level, name, format, args...) \
	printk(level "[" name "] " DRIVER_NAME ":%s:%d: " format "\n" , \
			__func__ , __LINE__ , ## args)

#define log_emerg(format, args...)	__log(KERN_EMERG, "EMERG", format , ## args)
#define log_alert(format, args...)	__log(KERN_ALERT, "ALERT", format , ## args)
#define log_crit(format, args...)	__log(KERN_CRIT, "CRIT", format , ## args)
#define log_error(format, args...)	__log(KERN_ERR, "ERROR", format , ## args)
#define log_warning(format, args...)	__log(KERN_WARNING, "WARNING", format , ## args)
#define log_notice(format, args...)	__log(KERN_NOTICE, "NOTICE", format , ## args)
#define log_info(format, args...)	__log(KERN_INFO, "INFO", format , ## args)
#if DEBUG
#  define log_debug(format, args...)	__log(KERN_DEBUG, "DEBUG", format , ## args)
#else
#  define log_debug(format, args...) do {} while (0)
#endif

#define LED_STATUS_CONTROLLABLE_MTCDP		0
#define LED_LS_CONTROLLABLE		0
#define USBH2_PS_CONTROLLABLE		0

static int led_mode_status = LED_OFF;

#define SOUT_LED_CD_BIT			BIT(0)
#define SOUT_EXTSERIAL_RI_BIT		BIT(1)
#define SOUT_EXTSERIAL_DSR_BIT		BIT(2)
#define SOUT_LED_DTR			BIT(3)
#define SOUT_LED_SIG1_BIT		BIT(4)
#define SOUT_LED_SIG2_BIT		BIT(5)
#define SOUT_LED_SIG3_BIT		BIT(6)
#define SOUT_EXTSERIAL_DCD_BIT		BIT(7)

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

/* daughter card EEPROM */
#ifdef MTOCGD2
extern uint8_t mts_dc_eeprom[512];
#else
uint8_t mts_dc_eeprom[512] = {};
#endif
static struct mts_dc_eeprom_layout dc_eeprom;

bool daughter_card_capable = false;
bool has_daughter_card = false;

static uint8_t mts_product_id;
static uint8_t mts_dc_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 attribute_group *attr_group;

static struct gpio_pin *gpio_pins;

static struct gpio_pin gpio_pins_mtcdp_0_0[] = {
	{
		.name = "ENIO",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "ETH0_ENABLED",
		.attr_name = "eth0-enabled",
		.pin = AT91_PIN_PB31,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "RADIO_RESET",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PB30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PA22,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LS_LED",
		.attr_name = "led-ls",
		.pin = AT91_PIN_PC9,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
#if LED_STATUS_CONTROLLABLE_MTCDP
	{
		.name = "STATUS_LED",
		.attr_name = "led-status",
		.pin = AT91_PIN_PA30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
#endif
	{
		.name = "RSERSRC",
		.attr_name = "rsersrc",
		.pin = AT91_PIN_PC7,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DTR1",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PC10,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{ },
};

static struct gpio_pin gpio_pins_mtcdp_1_0[] = {
	{
		.name = "ENIO",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "ETH0_ENABLED",
		.attr_name = "eth0-enabled",
		.pin = AT91_PIN_PB31,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "RADIO_RESET",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PB30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PA22,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LS_LED",
		.attr_name = "led-ls",
		.pin = AT91_PIN_PC9,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
#if LED_STATUS_CONTROLLABLE_MTCDP
	{
		.name = "STATUS_LED",
		.attr_name = "led-status",
		.pin = AT91_PIN_PA30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
#endif
	{
		.name = "RSERSRC",
		.attr_name = "rsersrc",
		.pin = AT91_PIN_PC7,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "TXD1",
		.pin = AT91_PIN_PB17,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "DTR1",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PB18,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "USBH2_PS_OC",
		.attr_name = "usbh2-ps-oc",
		.pin = AT91_PIN_PB19,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
#if USBH2_PS_CONTROLLABLE
	{
		.name = "USBH2_PS_ENABLED",
		.attr_name = "usbh2-ps-enabled",
		.pin = AT91_PIN_PB20,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
#endif
	{
		.name = "NDC_RESET",
		.attr_name = "ndc-reset",
		.pin = AT91_PIN_PB21,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "ADC0",
		.pin = AT91_PIN_PC0,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC1",
		.pin = AT91_PIN_PC1,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC2",
		.pin = AT91_PIN_PC2,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC3",
		.pin = AT91_PIN_PC3,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{ },
};

static struct gpio_pin gpio_pins_mt100eocg_0_0[] = {
	{
		.name = "ENIO",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "ETH0_ENABLED",
		.attr_name = "eth0-enabled",
		.pin = AT91_PIN_PB31,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "RADIO_RESET",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PB30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PA22,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED3",
		.attr_name = "led3",
		.pin = AT91_PIN_PC9,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED2",
		.attr_name = "led2",
		.pin = AT91_PIN_PA30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "RSERSRC",
		.attr_name = "rsersrc",
		.pin = AT91_PIN_PC7,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "TXD1",
		.pin = AT91_PIN_PB17,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "DTR1",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PB18,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DCD1",
		.attr_name = "extserial-dcd",
		.pin = AT91_PIN_PB3,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "GPIO11",
		.attr_name = "gpio11",
		.pin = AT91_PIN_PB19,
		.direction = GPIO_DIR_OD,
		.output_value = 1,
		.use_pullup = 1,
	},
	{
		.name = "GPIO12",
		.attr_name = "gpio12",
		.pin = AT91_PIN_PB20,
		.direction = GPIO_DIR_OD,
		.output_value = 1,
		.use_pullup = 1,
	},
	{
		.name = "ADC0",
		.pin = AT91_PIN_PC0,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC1",
		.pin = AT91_PIN_PC1,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC2",
		.pin = AT91_PIN_PC2,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "ADC3",
		.pin = AT91_PIN_PC3,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{ },
};

static struct gpio_pin gpio_pins_mtr2_0_0[] = {
	{
		.name = "NETH_RST",
		.attr_name = "eth-switch-enabled",
		.pin = AT91_PIN_PC6,
		.direction = GPIO_DIR_OD,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "RADIO_RESET",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PC5,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "RADIO_RESET",
		.attr_name = "radio-power",
		.pin = AT91_PIN_PC5,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PC4,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LS_LED",
		.attr_name = "led-ls",
		.pin = AT91_PIN_PA14,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "STATUS_LED",
		.attr_name = "led-status",
		.pin = AT91_PIN_PA24,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-cd",
		.pin = AT91_PIN_PA25,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-c",
		.pin = AT91_PIN_PA25,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED10",
		.attr_name = "led-sig1",
		.pin = AT91_PIN_PA26,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED10",
		.attr_name = "led-d",
		.pin = AT91_PIN_PA26,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED11",
		.attr_name = "led-sig2",
		.pin = AT91_PIN_PA27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED11",
		.attr_name = "led-e",
		.pin = AT91_PIN_PA27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED12",
		.attr_name = "led-sig3",
		.pin = AT91_PIN_PA28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED12",
		.attr_name = "led-f",
		.pin = AT91_PIN_PA28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED13",
		.attr_name = "led-wifi",
		.pin = AT91_PIN_PA29,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED13",
		.attr_name = "led-b",
		.pin = AT91_PIN_PA29,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "UART3_DTR",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PC12,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "UART3_DSR",
		.attr_name = "extserial-dsr",
		.pin = AT91_PIN_PC11,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "UART3_DCD",
		.attr_name = "extserial-dcd",
		.pin = AT91_PIN_PC10,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "UART3_RI",
		.attr_name = "extserial-ri",
		.pin = AT91_PIN_PC13,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "NDC_RESET",
		.attr_name = "ndc-reset",
		.pin = AT91_PIN_PC3,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "NDC_EEPROM_WRITE_PROTECT",
		.attr_name = "ndc-eeprom-wp",
		.pin = AT91_PIN_PC26,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "BT_EN",
		.attr_name = "bt-enabled",
		.pin = AT91_PIN_PD21,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "WLAN_EN",
		.attr_name = "wlan-enabled",
		.pin = AT91_PIN_PC1,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "SERIAL_MODE0",
		.attr_name = "serial_mode",
		.pin = AT91_PIN_PC23,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "SERIAL_MODE1",
		.attr_name = "serial_mode",
		.pin = AT91_PIN_PC24,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "SERIAL_MODE2",
		.attr_name = "serial_mode",
		.pin = AT91_PIN_PC25,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "RS4XX_TERM_RES",
		.attr_name = "rs4xx-term-res",
		.pin = AT91_PIN_PC26,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_GPIO1",
		.attr_name = "dc-gpio1",
		.pin = AT91_PIN_PC0,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_GPIO2",
		.attr_name = "dc-gpio2",
		.pin = AT91_PIN_PC14,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_GPIO3",
		.attr_name = "dc-gpio3",
		.pin = AT91_PIN_PC29,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_GPIO4",
		.attr_name = "dc-gpio4",
		.pin = AT91_PIN_PC30,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_INTERRUPT1",
		.attr_name = "dc-int1",
		.pin = AT91_PIN_PC20,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "NDC_INTERRUPT2",
		.attr_name = "dc-int2",
		.pin = AT91_PIN_PC21,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{ },
};

static struct gpio_pin gpio_pins_mtr_0_0[] = {
	{
		.name = "NETH_RST",
		.attr_name = "eth0-enabled",
		.pin = AT91_PIN_PC6,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "PWRMON",
		.attr_name = "radio-power",
		.pin = AT91_PIN_PA23,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 0,
	},
	{
		.name = "3G_RST",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PA22,
		.direction = GPIO_DIR_OD,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "3G_ONOFF",
		.attr_name = "radio-enabled",
		.pin = AT91_PIN_PA21,
		.direction = GPIO_DIR_OD,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PC4,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LS_LED",
		.attr_name = "led-ls",
		.pin = AT91_PIN_PC16,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "STATUS_LED",
		.attr_name = "led-status",
		.pin = AT91_PIN_PC21,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED3",
		.attr_name = "led-wifi",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED3",
		.attr_name = "led-b",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED4",
		.attr_name = "led-cd",
		.pin = AT91_PIN_PC20,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED4",
		.attr_name = "led-c",
		.pin = AT91_PIN_PC20,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED6",
		.attr_name = "led-sig1",
		.pin = AT91_PIN_PC19,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED6",
		.attr_name = "led-d",
		.pin = AT91_PIN_PC19,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-sig2",
		.pin = AT91_PIN_PC18,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-e",
		.pin = AT91_PIN_PC18,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED8",
		.attr_name = "led-sig3",
		.pin = AT91_PIN_PC17,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED8",
		.attr_name = "led-f",
		.pin = AT91_PIN_PC17,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "RI_B",
		.attr_name = "extserial-ri",
		.pin = AT91_PIN_PC25,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DTR_B",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PC26,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DSR_B",
		.attr_name = "extserial-dsr",
		.pin = AT91_PIN_PC27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DCD_B",
		.attr_name = "extserial-dcd",
		.pin = AT91_PIN_PC28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "BT_EN",
		.attr_name = "bt-enabled",
		.pin = AT91_PIN_PA28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "WLAN_EN",
		.attr_name = "wlan-enabled",
		.pin = AT91_PIN_PA27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{ },
};

static struct gpio_pin gpio_pins_mtr_0_1[] = {
	{
		.name = "NETH_RST",
		.attr_name = "eth0-enabled",
		.pin = AT91_PIN_PC6,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
	},
	{
		.name = "PWRMON",
		.attr_name = "radio-power",
		.pin = AT91_PIN_PA23,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 0,
	},
	{
		.name = "3G_RST",
		.attr_name = "radio-reset",
		.pin = AT91_PIN_PA22,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 1,
	},
	{
		.name = "3G_ONOFF",
		.attr_name = "radio-enabled",
		.pin = AT91_PIN_PA21,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 1,
	},
	{
		.name = "DEVICE_RESET",
		.attr_name = "reset",
		.pin = AT91_PIN_PC4,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LS_LED",
		.attr_name = "led-ls",
		.pin = AT91_PIN_PC16,
#if LED_LS_CONTROLLABLE
		.direction = GPIO_DIR_OUTPUT,
#else
		.direction = GPIO_DIR_INPUT,
#endif
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "STATUS_LED",
		.attr_name = "led-status",
		.pin = AT91_PIN_PC21,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED3",
		.attr_name = "led-wifi",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED3",
		.attr_name = "led-b",
		.pin = AT91_PIN_PC15,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED4",
		.attr_name = "led-cd",
		.pin = AT91_PIN_PC20,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED4",
		.attr_name = "led-c",
		.pin = AT91_PIN_PC20,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED6",
		.attr_name = "led-sig1",
		.pin = AT91_PIN_PC19,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED6",
		.attr_name = "led-d",
		.pin = AT91_PIN_PC19,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-sig2",
		.pin = AT91_PIN_PC18,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED7",
		.attr_name = "led-e",
		.pin = AT91_PIN_PC18,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED8",
		.attr_name = "led-sig3",
		.pin = AT91_PIN_PC17,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "LED8",
		.attr_name = "led-f",
		.pin = AT91_PIN_PC17,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "RI_B",
		.attr_name = "extserial-ri",
		.pin = AT91_PIN_PC25,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DTR_B",
		.attr_name = "extserial-dtr",
		.pin = AT91_PIN_PC26,
		.direction = GPIO_DIR_INPUT,
		.output_value = 0,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DSR_B",
		.attr_name = "extserial-dsr",
		.pin = AT91_PIN_PC27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "DCD_B",
		.attr_name = "extserial-dcd",
		.pin = AT91_PIN_PC28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 1,
		.use_pullup = 0,
		.active_low = 1,
	},
	{
		.name = "BT_EN",
		.attr_name = "bt-enabled",
		.pin = AT91_PIN_PA28,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{
		.name = "WLAN_EN",
		.attr_name = "wlan-enabled",
		.pin = AT91_PIN_PA27,
		.direction = GPIO_DIR_OUTPUT,
		.output_value = 0,
		.use_pullup = 0,
	},
	{ },
};

struct gpio_pin *gpio_pin_by_name(const char *name) {
	struct gpio_pin *pin;

	for (pin = gpio_pins; *pin->name; pin++) {
		if (!strcmp(pin->name, name)) {
			return pin;
		}
	}

	log_error("pin named %s not found", name);

	return NULL;
}

struct gpio_pin *gpio_pin_by_attr_name(const char *name) {
	struct gpio_pin *pin;

	for (pin = gpio_pins; *pin->name; pin++) {
		if (!strcmp(pin->attr_name, name)) {
			return pin;
		}
	}

	log_error("pin with attr name %s not found", name);

	return NULL;
}


static struct spi_device *spi_sout_dev;
static u8 spi_sout_value;
static DEFINE_MUTEX(spi_sout_mutex);
static unsigned int sout_max_speed_hz = 1 * 1000 * 1000;
module_param(sout_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	sout_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static struct spi_device *spi_dout_dev;
static u8 spi_dout_value;
static DEFINE_MUTEX(spi_dout_mutex);
static unsigned int dout_max_speed_hz = 1 * 1000 * 1000;
module_param(dout_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	dout_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static struct spi_device *spi_din_dev;
static unsigned int din_max_speed_hz = 1 * 1000 * 1000;
module_param(din_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	din_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static struct spi_device *spi_dc_dout_dev;
static u8 spi_dc_dout_value;
static DEFINE_MUTEX(spi_dc_dout_mutex);
static unsigned int dc_dout_max_speed_hz = 1 * 1000 * 1000;
module_param(dc_dout_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	dc_dout_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static struct spi_device *spi_dc_din_dev;
static unsigned int dc_din_max_speed_hz = 1 * 1000 * 1000;
module_param(dc_din_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	dc_din_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static struct spi_device *spi_dc_adc_dev;
static unsigned int dc_adc_max_speed_hz = 20 * 1000 * 1000;
module_param(dc_adc_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	dc_adc_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 20 MHz)"
);

static struct spi_device *spi_board_temp_dev;
static unsigned int board_temp_max_speed_hz = 1 * 1000 * 1000;
module_param(board_temp_max_speed_hz, uint, S_IRUGO);
MODULE_PARM_DESC(
	board_temp_max_speed_hz,
	"Maximum clock rate to be used with this device (default: 1 MHz)"
);

static inline int spi_writen(struct spi_device *spi, const u8 *buf, size_t len)
{
	int tmp;
	u8 *tx;

	tx = kmalloc(len, GFP_KERNEL);
	if (!tx) {
		return -ENOMEM;
	}

	memcpy(tx, buf, len);
	tmp = spi_write(spi, tx, len);

	kfree(tx);

	return tmp;
}

static inline int spi_readn(struct spi_device *spi, u8 *buf, size_t len)
{
	int tmp;
	u8 *rx;

	rx = kmalloc(len, GFP_KERNEL);
	if (!rx) {
		return -ENOMEM;
	}

	tmp = spi_read(spi, rx, len);
	memcpy(buf, rx, len);

	kfree(rx);

	return tmp;
}

#define ADC_SHTIME_DEFAULT			0x05
#define ADC_STARTUP_DEFAULT			0x04
#define ADC_PRESCALE_DEFAULT			0x3F
#define ADC_MODE_DEFAULT \
	((ADC_SHTIME_DEFAULT & 0x0F) << 24) | \
	((ADC_STARTUP_DEFAULT & 0x1F) << 16) | \
	((ADC_PRESCALE_DEFAULT & 0x3F) << 8)

#define ADC_CR_OFFSET			0x00
#define ADC_MR_OFFSET			0x04
#define ADC_CHER_OFFSET			0x10
#define ADC_CHDR_OFFSET			0x14
#define ADC_CHSR_OFFSET			0x18
#define ADC_SR_OFFSET			0x1C
#define ADC_LDCR_OFFSET			0x20
#define ADC_IER_OFFSET			0x14
#define ADC_IDR_OFFSET			0x28
#define ADC_IMR_OFFSET			0x2C
#define ADC_CDR0_OFFSET			0x30
#define ADC_CDR1_OFFSET			0x34
#define ADC_CDR2_OFFSET			0x38
#define ADC_CDR3_OFFSET			0x3C

void __iomem *adc_base;
struct clk *adc_clk;

#define ADC_CONVERT_RESET(base)			writel(0x01, (base) + ADC_CR_OFFSET)
#define ADC_CONVERT_START(base)			writel(0x02, (base) + ADC_CR_OFFSET)

#define BLINK_PER_SEC		8
#define BLINK_INTERVAL		(HZ / BLINK_PER_SEC)
#define RESET_HOLD_COUNT	(BLINK_PER_SEC * 3)
#define RESET_LONG_HOLD_COUNT   (BLINK_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 DEFINE_MUTEX(mts_io_mutex);

static void blink_callback(struct work_struct *ignored);

static DECLARE_DELAYED_WORK(blink_work, blink_callback);

static void blink_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 = !at91_get_gpio_value(pin->pin);
	}

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

	if (led_mode_status == LED_FLASHING) {
		pin = gpio_pin_by_name("STATUS_LED");
		if (pin) {
			at91_set_gpio_value(pin->pin, !at91_get_gpio_value(pin->pin));
		}
	}

	mutex_unlock(&mts_io_mutex);

	schedule_delayed_work(&blink_work, BLINK_INTERVAL);
}

static int radio_off_telit(void)
{
	int value, ret;
	struct gpio_pin *pwrmon_pin = gpio_pin_by_name("PWRMON");
	struct gpio_pin *onoff_pin = gpio_pin_by_name("3G_ONOFF");
	struct gpio_pin *rst_pin = gpio_pin_by_name("3G_RST");

	if (!onoff_pin || !pwrmon_pin || !rst_pin) {
		return -ENODEV;
	}

	value = at91_get_gpio_value(pwrmon_pin->pin);
	if(value == 0) {
		log_error("radio is already off");
		return -EINVAL;
	}

	// drive on/off pin low for at least 3 sec
	log_info("shutting down radio");
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 0, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	msleep(3500);

	// set on/off pin high
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 1, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	// wait for radio to power off
	msleep(5000);

	// check that power is low
	value = at91_get_gpio_value(pwrmon_pin->pin);
	if(value != 0) {
		log_warning("radio is still on. performing radio reset.");
		//Perform Hard Reset
		ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 0, rst_pin->use_pullup);
		if (ret) {
			return ret;
		}

		msleep(500);

		// set pin high
		ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 1, rst_pin->use_pullup);
		if (ret) {
			return ret;
		}
	} else {
		log_info("radio has been shut down");
	}

	return ret;
}

static int radio_on_telit(void)
{
	int value, ret;
	struct gpio_pin *pwrmon_pin = gpio_pin_by_name("PWRMON");
	struct gpio_pin *onoff_pin = gpio_pin_by_name("3G_ONOFF");
	struct gpio_pin *rst_pin = gpio_pin_by_name("3G_RST");

	if (!onoff_pin || !pwrmon_pin || !rst_pin) {
		return -ENODEV;
	}

	value = at91_get_gpio_value(pwrmon_pin->pin);
	if(value != 0) {
		log_error("radio is already on");
		return -EINVAL;
	}

	// drive on/off pin low for at least 5 sec
	log_info("turning on radio");
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 0, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	msleep(5500);

	// set on/off pin high
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 1, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	msleep(200);

	// check that power is high
	value = at91_get_gpio_value(pwrmon_pin->pin);
	if(value == 0) {
		log_warning("radio is still off. performing radio reset");
		//Perform Hard Reset
		ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 0, rst_pin->use_pullup);
		if (ret) {
			return ret;
		}

		msleep(500);

		// set pin high
		ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 1, rst_pin->use_pullup);
		if (ret) {
			return ret;
		}
	} else {
		log_info("radio has been turned on");
	}

	return ret;
}

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

	if (sscanf(buf, "%i", &value) != 1) {
		return -EINVAL;
	}
	if (value == 0) {
		mutex_lock(&mts_io_mutex);
		err = radio_off_telit();
		mutex_unlock(&mts_io_mutex);
	} else {
		mutex_lock(&mts_io_mutex);
		err = radio_on_telit();
		mutex_unlock(&mts_io_mutex);
	}

	if (err) {
		return err;
	}

	return count;
}

static int radio_reset_telit(void)
{
	int ret;
	struct gpio_pin *rst_pin = gpio_pin_by_name("3G_RST");
	struct gpio_pin *onoff_pin = gpio_pin_by_name("3G_ONOFF");

	if (!rst_pin || !onoff_pin) {
		return -ENODEV;
	}

	// drive reset pin low for 500ms
	ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 0, rst_pin->use_pullup);
	if (ret) {
		return ret;
	}

	msleep(500);

	// set pin high
	ret = at91_set_gpio_output_with_pullup(rst_pin->pin, 1, rst_pin->use_pullup);
	if (ret) {
		return ret;
	}

	// wait for 2 sec before toggling on/off pin
	msleep(2000);

	// drive on/off pin low for 6 sec
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 0, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	msleep(6000);

	// set on/off pin high
	ret = at91_set_gpio_output_with_pullup(onoff_pin->pin, 1, onoff_pin->use_pullup);
	if (ret) {
		return ret;
	}

	return ret;
}

static int radio_reset(void)
{
	int ret;
	struct gpio_pin *pin = gpio_pin_by_name("RADIO_RESET");

	if (!pin) {
		return -ENODEV;
	}

	ret = at91_set_gpio_value(pin->pin, 0);
	if (ret) {
		return ret;
	}

	mdelay(250);

	ret = at91_set_gpio_value(pin->pin, 1);

	return ret;
}

static int ndc_reset(void)
{
	int ret;
	struct gpio_pin *pin = gpio_pin_by_name("NDC_RESET");

	if (!pin) {
		return -ENODEV;
	}

	ret = at91_set_gpio_value(pin->pin, 0);
	if (ret) {
		return ret;
	}

	mdelay(1);

	ret = at91_set_gpio_value(pin->pin, 1);

	return ret;
}

static int ADT7302_to_celsius(int value)
{
	if (value & 0x2000) {
		value = value - 16384;
	}

	value = value / 32 + 1 * ((value % 32) >= 16);

	return value;
}

static ssize_t mts_attr_show_gpio_pin(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int value;
	struct gpio_pin *pin = gpio_pin_by_attr_name(attr->attr.name);

	if (!pin) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	value = at91_get_gpio_value(pin->pin);

	mutex_unlock(&mts_io_mutex);

	if (value < 0) {
		return value;
	}

	if (pin->active_low) {
		value = !value;
	}

	return sprintf(buf, "%d\n", value);
}

static ssize_t mts_attr_store_gpio_pin(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	int err;
	struct gpio_pin *pin = gpio_pin_by_attr_name(attr->attr.name);

	if (!pin) {
		return -ENODEV;
	}

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

	if (pin->active_low) {
		value = !value;
	}

	mutex_lock(&mts_io_mutex);

	err = at91_set_gpio_value(pin->pin, value);

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

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;

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

	mutex_lock(&mts_io_mutex);

	err = radio_reset();

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

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

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

	mutex_lock(&mts_io_mutex);

	err = radio_reset_telit();

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

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_radio_reset, "radio-reset",
	mts_attr_show_gpio_pin, mts_attr_store_radio_reset);

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;

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

	mutex_lock(&mts_io_mutex);

	err = ndc_reset();

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}
static DEVICE_ATTR_MTS(dev_attr_ndc_reset, "ndc-reset",
	mts_attr_show_gpio_pin, mts_attr_store_ndc_reset);

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 ssize_t mts_attr_store_sout(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	u8 bit;

	if (!spi_sout_dev) {
		log_notice("sout device not present");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "extserial-ri")) {
		bit = SOUT_EXTSERIAL_RI_BIT;
	} else if (!strcmp(attr->attr.name, "extserial-dsr")) {
		bit = SOUT_EXTSERIAL_DSR_BIT;
	} else if (!strcmp(attr->attr.name, "extserial-dcd")) {
		bit = SOUT_EXTSERIAL_DCD_BIT;
	} else if (!strcmp(attr->attr.name, "led-cd") ||
			!strcmp(attr->attr.name, "led-sdk-b")) {
		bit = SOUT_LED_CD_BIT;
	} else if (!strcmp(attr->attr.name, "led-dtr") ||
			!strcmp(attr->attr.name, "led-sdk-f")) {
		bit = SOUT_LED_DTR;
	} else if (!strcmp(attr->attr.name, "led-sig1") ||
			!strcmp(attr->attr.name, "led-sdk-c")) {
		bit = SOUT_LED_SIG1_BIT;
	} else if (!strcmp(attr->attr.name, "led-sig2") ||
			!strcmp(attr->attr.name, "led-sdk-d")) {
		bit = SOUT_LED_SIG2_BIT;
	} else if (!strcmp(attr->attr.name, "led-sig3") ||
			!strcmp(attr->attr.name, "led-sdk-e")) {
		bit = SOUT_LED_SIG3_BIT;
	} else {
		log_notice("sout attr does not exist");
		return -ENOENT;
	}

	if (sscanf(buf, "%i", &value) != 1) {
		log_notice("sout attr invalid argument");
		return -EINVAL;
	}

	mutex_lock(&spi_sout_mutex);

	if (value) {
		spi_sout_value &= ~bit;
	} else {
		spi_sout_value |= bit;
	}
	spi_writen(spi_sout_dev, &spi_sout_value, 1);

	mutex_unlock(&spi_sout_mutex);

	return count;
}

static ssize_t mts_attr_show_sout(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int value;
	u8 bit;

	if (!spi_sout_dev) {
		log_error("sout device not present");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "extserial-ri")) {
		bit = SOUT_EXTSERIAL_RI_BIT;
	} else if (!strcmp(attr->attr.name, "extserial-dsr")) {
		bit = SOUT_EXTSERIAL_DSR_BIT;
	} else if (!strcmp(attr->attr.name, "extserial-dcd")) {
		bit = SOUT_EXTSERIAL_DCD_BIT;
	} else if (!strcmp(attr->attr.name, "led-cd") ||
			!strcmp(attr->attr.name, "led-sdk-b")) {
		bit = SOUT_LED_CD_BIT;
	} else if (!strcmp(attr->attr.name, "led-dtr") ||
			!strcmp(attr->attr.name, "led-sdk-f")) {
		bit = SOUT_LED_DTR;
	} else if (!strcmp(attr->attr.name, "led-sig1") ||
			!strcmp(attr->attr.name, "led-sdk-c")) {
		bit = SOUT_LED_SIG1_BIT;
	} else if (!strcmp(attr->attr.name, "led-sig2") ||
			!strcmp(attr->attr.name, "led-sdk-d")) {
		bit = SOUT_LED_SIG2_BIT;
	} else if (!strcmp(attr->attr.name, "led-sig3") ||
			!strcmp(attr->attr.name, "led-sdk-e")) {
		bit = SOUT_LED_SIG3_BIT;
	} else {
		log_notice("sout attr does not exist");
		return -ENOENT;
	}

	mutex_lock(&spi_sout_mutex);

	value = spi_sout_value & bit ? 0 : 1;

	mutex_unlock(&spi_sout_mutex);

	return sprintf(buf, "%d\n", value);
}

static DEVICE_ATTR_MTS(dev_attr_extserial_dcd, "extserial-dcd",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_extserial_ri, "extserial-ri",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_extserial_dsr, "extserial-dsr",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_cd, "led-cd",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sdk_b, "led-sdk-b",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sig1, "led-sig1",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sdk_c, "led-sdk-c",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sig2, "led-sig2",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sdk_d, "led-sdk-d",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sig3, "led-sig3",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sdk_e, "led-sdk-e",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_dtr, "led-dtr",
	mts_attr_show_sout, mts_attr_store_sout);

static DEVICE_ATTR_MTS(dev_attr_led_sdk_f, "led-sdk-f",
	mts_attr_show_sout, mts_attr_store_sout);

static ssize_t mts_attr_store_dout(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	u8 bit;

	if (!spi_dout_dev) {
		log_notice("dout device not present");
		return -ENODEV;
	}

	if ((!strcmp(attr->attr.name, "dout0")) || (!strcmp(attr->attr.name, "gpo1"))) {
		bit = BIT(0);
	} else if ((!strcmp(attr->attr.name, "dout1")) || (!strcmp(attr->attr.name, "gpo2"))) {
		bit = BIT(1);
	} else if ((!strcmp(attr->attr.name, "dout2")) || (!strcmp(attr->attr.name, "gpo3"))) {
		bit = BIT(2);
	} else if ((!strcmp(attr->attr.name, "dout3")) || (!strcmp(attr->attr.name, "gpo4"))) {
		bit = BIT(3);
	} else if ((!strcmp(attr->attr.name, "dout4")) || (!strcmp(attr->attr.name, "led1"))) {
		bit = BIT(4);
	} else if ((!strcmp(attr->attr.name, "dout5")) || (!strcmp(attr->attr.name, "led4"))) {
		bit = BIT(5);
	} else if ((!strcmp(attr->attr.name, "dout6")) || (!strcmp(attr->attr.name, "led5"))) {
		bit = BIT(6);
	} else if ((!strcmp(attr->attr.name, "dout7")) || (!strcmp(attr->attr.name, "led6"))) {
		bit = BIT(7);
	} else {
		log_notice("dout attr does not exist");
		return -ENOENT;
	}

	if (sscanf(buf, "%i", &value) != 1) {
		log_notice("dout attr invalid argument");
		return -EINVAL;
	}

	mutex_lock(&spi_dout_mutex);

	if (value) {
		spi_dout_value &= ~bit;
	} else {
		spi_dout_value |= bit;
	}

	spi_writen(spi_dout_dev, &spi_dout_value, 1);

	mutex_unlock(&spi_dout_mutex);

	return count;
}

static ssize_t mts_attr_show_dout(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int value;
	u8 bit;

	if (!spi_dout_dev) {
		log_error("dout device not present");
		return -ENODEV;
	}

	if ((!strcmp(attr->attr.name, "dout0")) || (!strcmp(attr->attr.name, "gpo1"))) {
		bit = BIT(0);
	} else if ((!strcmp(attr->attr.name, "dout1")) || (!strcmp(attr->attr.name, "gpo2"))) {
		bit = BIT(1);
	} else if ((!strcmp(attr->attr.name, "dout2")) || (!strcmp(attr->attr.name, "gpo3"))) {
		bit = BIT(2);
	} else if ((!strcmp(attr->attr.name, "dout3")) || (!strcmp(attr->attr.name, "gpo4"))) {
		bit = BIT(3);
	} else if ((!strcmp(attr->attr.name, "dout4")) || (!strcmp(attr->attr.name, "led1"))) {
		bit = BIT(4);
	} else if ((!strcmp(attr->attr.name, "dout5")) || (!strcmp(attr->attr.name, "led4"))) {
		bit = BIT(5);
	} else if ((!strcmp(attr->attr.name, "dout6")) || (!strcmp(attr->attr.name, "led5"))) {
		bit = BIT(6);
	} else if ((!strcmp(attr->attr.name, "dout7")) || (!strcmp(attr->attr.name, "led6"))) {
		bit = BIT(7);
	} else {
		log_notice("dout attr does not exist");
		return -ENOENT;
	}

	mutex_lock(&spi_dout_mutex);

	value = spi_dout_value & bit ? 0 : 1;

	mutex_unlock(&spi_dout_mutex);

	return sprintf(buf, "%d\n", value);
}


static DEVICE_ATTR_MTS(dev_attr_dout0, "dout0",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout1, "dout1",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout2, "dout2",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout3, "dout3",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout4, "dout4",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout5, "dout5",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout6, "dout6",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_dout7, "dout7",
	mts_attr_show_dout, mts_attr_store_dout);


static ssize_t mts_attr_show_din(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int tmp;
	u8 bit;
	u8 byte;

	if (!spi_din_dev) {
		log_error("din device not present");
		return -ENODEV;
	}

	if ((!strcmp(attr->attr.name, "din0")) || (!strcmp(attr->attr.name, "gpi5"))) {
		bit = BIT(0);
	} else if ((!strcmp(attr->attr.name, "din1")) || (!strcmp(attr->attr.name, "gpi6"))) {
		bit = BIT(1);
	} else if ((!strcmp(attr->attr.name, "din2")) || (!strcmp(attr->attr.name, "gpi7"))) {
		bit = BIT(2);
	} else if ((!strcmp(attr->attr.name, "din3")) || (!strcmp(attr->attr.name, "gpi8"))) {
		bit = BIT(3);
	} else if ((!strcmp(attr->attr.name, "din4")) || (!strcmp(attr->attr.name, "gpi9"))) {
		bit = BIT(4);
	} else if ((!strcmp(attr->attr.name, "din5")) || (!strcmp(attr->attr.name, "gpi10"))) {
		bit = BIT(5);
	} else if (!strcmp(attr->attr.name, "din6")) {
		bit = BIT(6);
	} else if (!strcmp(attr->attr.name, "din7")) {
		bit = BIT(7);
	} else {
		log_notice("din attr does not exist");
		return -ENOENT;
	}

	tmp = spi_readn(spi_din_dev, &byte, 1);
	if (tmp) {
		log_error("spi_read failed %d", tmp);
		return tmp;
	}

	tmp = byte & bit ? 1 : 0;

	return sprintf(buf, "%d\n", tmp);
}

static DEVICE_ATTR_RO_MTS(dev_attr_din0, "din0", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din1, "din1", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din2, "din2", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din3, "din3", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din4, "din4", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din5, "din5", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din6, "din6", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_din7, "din7", mts_attr_show_din);

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 ssize_t mts_attr_show_led_status(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	return sprintf(buf, "%d\n", led_mode_status);
}

static ssize_t mts_attr_store_led_status(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	int ret;
	struct gpio_pin *pin = gpio_pin_by_name("STATUS_LED");

	if (!pin) {
		return -ENODEV;
	}

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

	mutex_lock(&mts_io_mutex);

	switch (value) {
	case LED_OFF:
		led_mode_status = LED_OFF;
		ret = at91_set_gpio_value(pin->pin, 1);

		break;

	case LED_ON:
		led_mode_status = LED_ON;
		ret = at91_set_gpio_value(pin->pin, 0);

		break;

	case LED_FLASHING:
		led_mode_status = LED_FLASHING;
		ret = at91_set_gpio_value(pin->pin, 0);

		break;

	default:
		ret = -EINVAL;
	}

	mutex_unlock(&mts_io_mutex);

	return count;
}

static DEVICE_ATTR_MTS(dev_attr_led_status, "led-status",
	mts_attr_show_led_status, mts_attr_store_led_status);
static DEVICE_ATTR_MTS(dev_attr_led_a_gpio, "led-a",
	mts_attr_show_led_status, mts_attr_store_led_status);

#if LED_STATUS_CONTROLLABLE_MTCDP
static DEVICE_ATTR_MTS(dev_attr_led_sdk_a, "led-sdk-a",
	mts_attr_show_led_status, mts_attr_store_led_status);
#endif

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

static DEVICE_ATTR_RO_MTS(dev_attr_reset, "reset", mts_attr_show_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_rs4xx_term_res, "rs4xx-term-res",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

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 ssize_t mts_attr_show_board_temperature(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int tmp;
	u16 temp_raw;

	if (!spi_board_temp_dev) {
		log_notice("spi_board_temp device not present");
		return -ENODEV;
	}

	tmp = spi_readn(spi_board_temp_dev, (u8 *) buf, 2);
	if (tmp) {
		log_error("spi_readn failed %d", tmp);
		return tmp;
	}
	temp_raw = ((u8 *) buf)[0] << 8 | ((u8 *) buf)[1];

	log_debug("temp: 0x%04X", temp_raw);

	return sprintf(buf, "%d\n", ADT7302_to_celsius(temp_raw));
}

static DEVICE_ATTR_RO_MTS(dev_attr_board_temperature, "board-temperature",
	mts_attr_show_board_temperature);

static ssize_t mts_attr_show_adc(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int offset;
	u32 value;
	u32 chan_mask;

	if (!DEVICE_CAPA(id_eeprom.capa, CAPA_ADC)) {
		log_debug("ADC not available");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "adc0")) {
		offset = ADC_CDR0_OFFSET;
		chan_mask = 0x01;
	} else if (!strcmp(attr->attr.name, "adc1")) {
		offset = ADC_CDR1_OFFSET;
		chan_mask = 0x02;
	} else if (!strcmp(attr->attr.name, "adc2")) {
		offset = ADC_CDR2_OFFSET;
		chan_mask = 0x04;
	} else if (!strcmp(attr->attr.name, "adc3")) {
		offset = ADC_CDR3_OFFSET;
		chan_mask = 0x08;
	} else {
		log_notice("adc attr does not exist");
		return -ENOENT;
	}

	mutex_lock(&mts_io_mutex);

	// disable all channels and enable the one we want
	writel(0x0F, adc_base + ADC_CHDR_OFFSET);
	writel(chan_mask, adc_base + ADC_CHER_OFFSET);

	ADC_CONVERT_START(adc_base);

	// wait for conversion to complete (EOC bit set)
	value = 0;
	while (value != chan_mask) {
		value = readl(adc_base + ADC_SR_OFFSET) & chan_mask;
		log_debug("ADC_SR EOC [%X]", value);
	}

	// read result
	value = readl(adc_base + offset);

	mutex_unlock(&mts_io_mutex);

	return sprintf(buf, "%lu\n", (unsigned long) value);
}

static DEVICE_ATTR_RO_MTS(dev_attr_adc0, "adc0", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc1, "adc1", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc2, "adc2", mts_attr_show_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_adc3, "adc3", mts_attr_show_adc);

static DEVICE_ATTR_RO_MTS(dev_attr_usbh2_ps_oc, "usbh2-ps-oc",
	   	mts_attr_show_gpio_pin);

#if USBH2_PS_CONTROLLABLE
static DEVICE_ATTR_MTS(dev_attr_usbh2_ps_enabled, "usbh2-ps-enabled",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
#endif

static DEVICE_ATTR_MTS(dev_attr_gpo1, "gpo1",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_gpo2, "gpo2",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_gpo3, "gpo3",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_gpo4, "gpo4",
	mts_attr_show_dout, mts_attr_store_dout);

static DEVICE_ATTR_RO_MTS(dev_attr_gpi5, "gpi5", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_gpi6, "gpi6", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_gpi7, "gpi7", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_gpi8, "gpi8", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_gpi9, "gpi9", mts_attr_show_din);
static DEVICE_ATTR_RO_MTS(dev_attr_gpi10, "gpi10", mts_attr_show_din);

static DEVICE_ATTR_MTS(dev_attr_led1, "led1",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_led2, "led2",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

#if LED_LS_CONTROLLABLE
static DEVICE_ATTR_MTS(dev_attr_led3, "led3",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
#else
static DEVICE_ATTR_RO_MTS(dev_attr_led3, "led3", mts_attr_show_gpio_pin);
#endif

static DEVICE_ATTR_MTS(dev_attr_led4, "led4",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_led5, "led5",
	mts_attr_show_dout, mts_attr_store_dout);
static DEVICE_ATTR_MTS(dev_attr_led6, "led6",
	mts_attr_show_dout, mts_attr_store_dout);

static DEVICE_ATTR_MTS(dev_attr_gpio11, "gpio11",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_gpio12, "gpio12",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_rsersrc, "rsersrc",
	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);

static ssize_t mts_attr_show_serial_mode(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int ret;
	int smode0;
	int smode1;
	int smode2;

	struct gpio_pin *pin_smode0 = gpio_pin_by_name("SERIAL_MODE0");
	struct gpio_pin *pin_smode1 = gpio_pin_by_name("SERIAL_MODE1");
	struct gpio_pin *pin_smode2 = gpio_pin_by_name("SERIAL_MODE2");

	if (!pin_smode0 || !pin_smode1 || !pin_smode2)
		return -ENODEV;

	mutex_lock(&mts_io_mutex);

	smode0 = at91_get_gpio_value(pin_smode0->pin);
	smode1 = at91_get_gpio_value(pin_smode1->pin);
	smode2 = at91_get_gpio_value(pin_smode2->pin);

	if (smode2 == 0 && smode1 == 0 && smode0 == 1)
		ret = sprintf(buf, "rs232\n");
	else if (smode2 == 0 && smode1 == 1 && smode0 == 0)
		ret = sprintf(buf, "rs485\n");
	else if (smode2 == 1 && smode1 == 0 && smode0 == 0)
		ret = sprintf(buf, "rs422\n");
	else if (smode2 == 0 && smode1 == 0 && smode0 == 0)
		ret = sprintf(buf, "loopback\n");
	else
		ret = sprintf(buf, "error\n");

	mutex_unlock(&mts_io_mutex);

	return ret;
}

static ssize_t mts_attr_store_serial_mode(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int smode0;
	int smode1;
	int smode2;
	struct gpio_pin *pin_smode0 = gpio_pin_by_name("SERIAL_MODE0");
	struct gpio_pin *pin_smode1 = gpio_pin_by_name("SERIAL_MODE1");
	struct gpio_pin *pin_smode2 = gpio_pin_by_name("SERIAL_MODE2");

	if (!pin_smode0 || !pin_smode1 || !pin_smode2)
		return -ENODEV;

	if (!strcasecmp(buf, "rs232")) {
		smode2 = 0;
		smode1 = 0;
		smode0 = 1;
	}
	else if (!strcasecmp(buf, "rs485")) {
		smode2 = 0;
		smode1 = 1;
		smode0 = 0;
	}
	else if (!strcasecmp(buf, "rs422")) {
		smode2 = 1;
		smode1 = 0;
		smode0 = 0;
	}
	else if (!strcasecmp(buf, "loopback")) {
		smode2 = 0;
		smode1 = 0;
		smode0 = 0;
	}
	else  {
		return -EINVAL;
	}

	mutex_lock(&mts_io_mutex);

	at91_set_gpio_value(pin_smode2->pin, smode2);
	at91_set_gpio_value(pin_smode1->pin, smode1);
	at91_set_gpio_value(pin_smode0->pin, smode0);

	mutex_unlock(&mts_io_mutex);

	return count;
}

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_MTS(dev_attr_serial_mode, "serial-mode",
	mts_attr_show_serial_mode, mts_attr_store_serial_mode);

static DEVICE_ATTR_MTS(dev_attr_eth_switch_enabled, "eth-switch-enabled",
	mts_attr_show_gpio_pin, mts_attr_store_gpio_pin);

static DEVICE_ATTR_MTS(dev_attr_radio_power_telit, "radio-power",
	mts_attr_show_gpio_pin, mts_attr_store_radio_power_telit);

static DEVICE_ATTR_MTS(dev_attr_radio_reset_telit, "radio-reset",
	mts_attr_show_gpio_pin, mts_attr_store_radio_reset_telit);

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

struct gpio_pin *dc_gpio_pin_by_attr_name(const char *name) {
	struct gpio_pin *pin;
	char *pin_attr_name;

	if (!strcmp(name, "led1")) {
		pin_attr_name = "dc-gpio1";
	} else if (!strcmp(name, "led2")) {
		pin_attr_name = "dc-gpio2";
	} else if (!strcmp(name, "dout-enable")) {
		pin_attr_name = "dc-gpio3";
	} else {
		log_error("daughter card attribute %s not available", name);
		return NULL;
	}

	for (pin = gpio_pins; *pin->name; pin++) {
		if (!strcmp(pin->attr_name, pin_attr_name)) {
			return pin;
		}
	}

	log_error("pin with attr name %s not found", name);

	return NULL;
}


static ssize_t mts_attr_show_dc_gpio_pin(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int value;
	struct gpio_pin *pin = dc_gpio_pin_by_attr_name(attr->attr.name);

	if (!pin) {
		return -ENODEV;
	}

	mutex_lock(&mts_io_mutex);

	value = at91_get_gpio_value(pin->pin);

	mutex_unlock(&mts_io_mutex);

	if (value < 0) {
		return value;
	}

	if (pin->active_low) {
		value = !value;
	}

	return sprintf(buf, "%d\n", value);
}

static ssize_t mts_attr_store_dc_gpio_pin(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	int err;
	struct gpio_pin *pin = dc_gpio_pin_by_attr_name(attr->attr.name);

	if (!pin) {
		return -ENODEV;
	}

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

	if (pin->active_low) {
		value = !value;
	}

	mutex_lock(&mts_io_mutex);

	err = at91_set_gpio_value(pin->pin, value);

	mutex_unlock(&mts_io_mutex);

	if (err) {
		return err;
	}

	return count;
}

static ssize_t mts_attr_show_dc_din(struct device *dev, struct device_attribute *attr, char *buf)
{
	int tmp;
	u8 bit;
	u8 byte;

	if (!spi_dc_din_dev) {
		log_error("dc din device not present");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "din0")) {
		bit = BIT(0);
	} else if (!strcmp(attr->attr.name, "din1")) {
		bit = BIT(1);
	} else if (!strcmp(attr->attr.name, "din2")) {
		bit = BIT(2);
	} else if (!strcmp(attr->attr.name, "din3")) {
		bit = BIT(3);
	} else {
		log_error("dc din attr does not exist");
		return -ENOENT;
	}

	tmp = spi_readn(spi_dc_din_dev, &byte, 1);
	if (tmp) {
		log_error("spi_read failed %d", tmp);
		return tmp;
	}

	tmp = byte & bit ? 1 : 0;

	return sprintf(buf, "%d\n", tmp);
}

static ssize_t mts_attr_store_dc_dout(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int value;
	u8 bit;

	if (!spi_dc_dout_dev) {
		log_error("dc dout device not present");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "dout0")) {
		bit = BIT(0);
	} else if (!strcmp(attr->attr.name, "dout1")) {
		bit = BIT(1);
	} else if (!strcmp(attr->attr.name, "dout2")) {
		bit = BIT(2);
	} else if (!strcmp(attr->attr.name, "dout3")) {
		bit = BIT(3);
	} else {
		log_error("dc dout attr does not exist");
		return -ENOENT;
	}

	if (sscanf(buf, "%i", &value) != 1) {
		log_error("dc dout attr invalid argument");
		return -EINVAL;
	}

	mutex_lock(&spi_dc_dout_mutex);

	if (value) {
		spi_dc_dout_value &= ~bit;
	} else {
		spi_dc_dout_value |= bit;
	}

	spi_writen(spi_dc_dout_dev, &spi_dc_dout_value, 1);

	mutex_unlock(&spi_dc_dout_mutex);

	return count;
}

static ssize_t mts_attr_show_dc_dout(struct device *dev, struct device_attribute *attr, char *buf)
{
	int value;
	u8 bit;

	if (!spi_dc_dout_dev) {
		log_error("dc dout device not present");
		return -ENODEV;
	}

	if (!strcmp(attr->attr.name, "dout0")) {
		bit = BIT(0);
	} else if (!strcmp(attr->attr.name, "dout1")) {
		bit = BIT(1);
	} else if (!strcmp(attr->attr.name, "dout2")) {
		bit = BIT(2);
	} else if (!strcmp(attr->attr.name, "dout3")) {
		bit = BIT(3);
	} else {
		log_error("dc dout attr does not exist");
		return -ENOENT;
	}

	mutex_lock(&spi_dc_dout_mutex);

	value = spi_dc_dout_value & bit ? 0 : 1;

	mutex_unlock(&spi_dc_dout_mutex);

	return sprintf(buf, "%d\n", value);
}

static ssize_t mts_attr_show_dc_adc(struct device *dev, struct device_attribute *attr, char *buf)
{
	int tmp;
	int tx_data;
	int rx_data;
	int channel;
	int channel_mask = 0x0180;  /* 0b 0000 0001 1000 0000 */
	int manual_mode = 0x1840;   /* 0b 0001 1000 0100 0000 */
	uint8_t tx[2];
	uint8_t rx[2];

	if (!spi_dc_adc_dev) {
		log_error("dc adc device not present");
		return -ENODEV;
	}

	memset(tx, 0, sizeof(tx));
	memset(rx, 0, sizeof(rx));

	if (!strcmp(attr->attr.name, "adc0")) {
		channel = 0;
	} else if (!strcmp(attr->attr.name, "adc1")) {
		channel = 1;
	} else if (! strcmp(attr->attr.name, "adc2")) {
		channel = 2;
	} else {
		log_error("dc adc attr does not exist");
		return -ENOENT;
	}

	/* 1st transfer to set up (5V reference, channel to read from) */
	tx_data = manual_mode | ((channel << 7) & channel_mask);
	tx[0] = tx_data >> 8;
	tx[1] = tx_data & 0xFF;
	tmp = spi_writen(spi_dc_adc_dev, tx, 2);
	if (tmp) {
		log_error("spi_write failed %d", tmp);
		return tmp;
	}

	/* 2nd transfer to clock chip for ADC conversion
	 * this can be a throw-away read or an empty write,
	 * the ADC just needs the clock running so it can convert */
	tx[0] = 0;
	tx[1] = 0;
	tmp = spi_writen(spi_dc_adc_dev, tx, 2);
	if (tmp) {
		log_error("2nd spi_write failed %d", tmp);
		return tmp;
	}

	/* 3rd transfer to read data */
	tmp = spi_readn(spi_dc_adc_dev, rx, 2);
	if (tmp) {
		log_error("spi_read failed %d", tmp);
		return tmp;
	}
	rx_data = ((rx[0] & 0x0F) << 8) | (rx[1] & 0xFF);

	return sprintf(buf, "%lu\n", (unsigned long) rx_data);
}

/* MTDC-GPIOB */
static DEVICE_ATTR_RO_MTS(dev_attr_dc_din0, "din0", mts_attr_show_dc_din);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_din1, "din1", mts_attr_show_dc_din);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_din2, "din2", mts_attr_show_dc_din);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_din3, "din3", mts_attr_show_dc_din);
static DEVICE_ATTR_MTS(dev_attr_dc_dout0, "dout0", mts_attr_show_dc_dout, mts_attr_store_dc_dout);
static DEVICE_ATTR_MTS(dev_attr_dc_dout1, "dout1", mts_attr_show_dc_dout, mts_attr_store_dc_dout);
static DEVICE_ATTR_MTS(dev_attr_dc_dout2, "dout2", mts_attr_show_dc_dout, mts_attr_store_dc_dout);
static DEVICE_ATTR_MTS(dev_attr_dc_dout3, "dout3", mts_attr_show_dc_dout, mts_attr_store_dc_dout);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_adc0, "adc0", mts_attr_show_dc_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_adc1, "adc1", mts_attr_show_dc_adc);
static DEVICE_ATTR_RO_MTS(dev_attr_dc_adc2, "adc2", mts_attr_show_dc_adc);
static DEVICE_ATTR_MTS(dev_attr_dc_led1, "led1", mts_attr_show_dc_gpio_pin, mts_attr_store_dc_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_dc_led2, "led2", mts_attr_show_dc_gpio_pin, mts_attr_store_dc_gpio_pin);
static DEVICE_ATTR_MTS(dev_attr_dc_oe, "dout-enable", mts_attr_show_dc_gpio_pin, mts_attr_store_dc_gpio_pin);

static struct attribute *mt100eocg_platform_attributes[] = {
	&dev_attr_vendor_id.attr,
	&dev_attr_product_id.attr,
	&dev_attr_device_id.attr,
	&dev_attr_hw_version.attr,
	&dev_attr_imei.attr,
	&dev_attr_eth_mac.attr,
	&dev_attr_extserial_dtr.attr,
	&dev_attr_extserial_dcd_gpio.attr,
	&dev_attr_rsersrc.attr,
	&dev_attr_radio_reset.attr,
	&dev_attr_eth0_enabled.attr,
	&dev_attr_gpio11.attr,
	&dev_attr_gpio12.attr,

	&dev_attr_gpo1.attr,
	&dev_attr_gpo2.attr,
	&dev_attr_gpo3.attr,
	&dev_attr_gpo4.attr,
	&dev_attr_led1.attr,
	&dev_attr_led2.attr,
	&dev_attr_led3.attr,
	&dev_attr_led4.attr,
	&dev_attr_led5.attr,
	&dev_attr_led6.attr,

	&dev_attr_gpi5.attr,
	&dev_attr_gpi6.attr,
	&dev_attr_gpi7.attr,
	&dev_attr_gpi8.attr,
	&dev_attr_gpi9.attr,
	&dev_attr_gpi10.attr,

	&dev_attr_board_temperature.attr,

	&dev_attr_adc0.attr,
	&dev_attr_adc1.attr,
	&dev_attr_adc2.attr,
	&dev_attr_adc3.attr,

	NULL,
};

static struct attribute_group mt100eocg_platform_attribute_group = {
	.attrs = mt100eocg_platform_attributes
};

struct attribute *mtr2_platform_attributes[] = {
	&dev_attr_vendor_id.attr,
	&dev_attr_product_id.attr,
	&dev_attr_device_id.attr,
	&dev_attr_hw_version.attr,
	&dev_attr_imei.attr,
	&dev_attr_eth_mac.attr,
	&dev_attr_wifi_mac.attr,
	&dev_attr_reset.attr,
	&dev_attr_reset_monitor.attr,
	&dev_attr_radio_power.attr,
	&dev_attr_radio_reset.attr,
	&dev_attr_ndc_reset.attr,
	&dev_attr_extserial_dtr.attr,
	&dev_attr_extserial_dsr_gpio.attr,
	&dev_attr_extserial_ri_gpio.attr,
	&dev_attr_extserial_dcd_gpio.attr,
	&dev_attr_eth_switch_enabled.attr,
	&dev_attr_bt_enabled.attr,
	&dev_attr_wlan_enabled.attr,

	&dev_attr_serial_mode.attr,
	&dev_attr_rs4xx_term_res.attr,

	&dev_attr_led_status.attr,
	&dev_attr_led_wifi_gpio.attr,
	&dev_attr_led_cd_gpio.attr,
	&dev_attr_led_sig1_gpio.attr,
	&dev_attr_led_sig2_gpio.attr,
	&dev_attr_led_sig3_gpio.attr,

	&dev_attr_led_a_gpio.attr,
	&dev_attr_led_b_gpio.attr,
	&dev_attr_led_c_gpio.attr,
	&dev_attr_led_d_gpio.attr,
	&dev_attr_led_e_gpio.attr,
	&dev_attr_led_f_gpio.attr,

	&dev_attr_board_temperature.attr,

	/* extra space for the daughter card attributes */
	NULL,	    // index 34
	NULL,	    // index 35
	NULL,	    // index 36
	NULL,	    // index 37
	NULL,	    // index 38
	NULL,	    // index 39
	NULL,	    // index 40
	NULL,	    // index 41
	NULL,	    // index 42
	NULL,	    // index 43
	NULL,	    // index 44
	NULL,	    // index 45
	NULL,	    // index 46
	NULL,	    // index 47
	NULL,	    // index 48
	NULL,	    // index 49
	NULL,	    // index 50
	NULL,	    // index 51
	NULL,
};

static struct attribute *mtr2_daughter_card_attributes[] = {
	&dev_attr_dc_din0.attr,
	&dev_attr_dc_din1.attr,
	&dev_attr_dc_din2.attr,
	&dev_attr_dc_din3.attr,

	&dev_attr_dc_dout0.attr,
	&dev_attr_dc_dout1.attr,
	&dev_attr_dc_dout2.attr,
	&dev_attr_dc_dout3.attr,

	&dev_attr_dc_adc0.attr,
	&dev_attr_dc_adc1.attr,
	&dev_attr_dc_adc2.attr,

	&dev_attr_dc_led1.attr,
	&dev_attr_dc_led2.attr,
	&dev_attr_dc_oe.attr,

	NULL,
};

static struct attribute_group mtr2_platform_attribute_group = {
	.attrs = mtr2_platform_attributes
};

bool mtr2_add_daughter_card_attributes(void)
{
	size_t platform_attrs_size = sizeof(mtr2_platform_attributes) / sizeof(struct attribute *);
	size_t daughter_card_attrs_size = sizeof(mtr2_daughter_card_attributes) / sizeof(struct attribute *);
	size_t platform_attrs_index;
	size_t daughter_card_attrs_index;
	size_t copy_length = daughter_card_attrs_size - 1; /* don't need to copy the NULL at the end */

	for (platform_attrs_index = 0; platform_attrs_index < platform_attrs_size; platform_attrs_index++) {
		if (! mtr2_platform_attributes[platform_attrs_index]) {
			break;
		}
	}

	if (platform_attrs_size < platform_attrs_index + daughter_card_attrs_size) {
		log_error("not enough room for MTR2 daughter card attributes!");
		return false;
	}

	for (daughter_card_attrs_index = 0; daughter_card_attrs_index < copy_length; daughter_card_attrs_index++, platform_attrs_index++) {
		mtr2_platform_attributes[platform_attrs_index] = mtr2_daughter_card_attributes[daughter_card_attrs_index];
	}

	return true;
}

static struct attribute *mtcdp_platform_attributes[] = {
	&dev_attr_vendor_id.attr,
	&dev_attr_product_id.attr,
	&dev_attr_device_id.attr,
	&dev_attr_hw_version.attr,
	&dev_attr_imei.attr,
	&dev_attr_eth_mac.attr,
	&dev_attr_reset.attr,
	&dev_attr_reset_monitor.attr,
	&dev_attr_radio_reset.attr,
	&dev_attr_ndc_reset.attr,
	&dev_attr_eth0_enabled.attr,
	&dev_attr_extserial_dtr.attr,
	&dev_attr_led_ls.attr,
#if LED_STATUS_CONTROLLABLE_MTCDP
	&dev_attr_led_status.attr,
	&dev_attr_led_sdk_a.attr,
#endif
	&dev_attr_usbh2_ps_oc.attr,
#if USBH2_PS_CONTROLLABLE
	&dev_attr_usbh2_ps_enabled.attr,
#endif

	&dev_attr_extserial_dcd.attr,
	&dev_attr_extserial_ri.attr,
	&dev_attr_extserial_dsr.attr,
	&dev_attr_led_cd.attr,
	&dev_attr_led_sdk_b.attr,
	&dev_attr_led_sig1.attr,
	&dev_attr_led_sdk_c.attr,
	&dev_attr_led_sig2.attr,
	&dev_attr_led_sdk_d.attr,
	&dev_attr_led_sig3.attr,
	&dev_attr_led_sdk_e.attr,
	&dev_attr_led_dtr.attr,
	&dev_attr_led_sdk_f.attr,

	&dev_attr_dout0.attr,
	&dev_attr_dout1.attr,
	&dev_attr_dout2.attr,
	&dev_attr_dout3.attr,
	&dev_attr_dout4.attr,
	&dev_attr_dout5.attr,
	&dev_attr_dout6.attr,
	&dev_attr_dout7.attr,

	&dev_attr_din0.attr,
	&dev_attr_din1.attr,
	&dev_attr_din2.attr,
	&dev_attr_din3.attr,
	&dev_attr_din4.attr,
	&dev_attr_din5.attr,
	&dev_attr_din6.attr,
	&dev_attr_din7.attr,

	&dev_attr_board_temperature.attr,

	&dev_attr_adc0.attr,
	&dev_attr_adc1.attr,
	&dev_attr_adc2.attr,
	&dev_attr_adc3.attr,

	NULL,
};

static struct attribute_group mtcdp_platform_attribute_group = {
	.attrs = mtcdp_platform_attributes
};

static struct attribute *mtr_platform_attributes[] = {
	&dev_attr_vendor_id.attr,
	&dev_attr_product_id.attr,
	&dev_attr_device_id.attr,
	&dev_attr_hw_version.attr,
	&dev_attr_imei.attr,
	&dev_attr_eth_mac.attr,
	&dev_attr_wifi_mac.attr,
	&dev_attr_reset.attr,
	&dev_attr_reset_monitor.attr,
	&dev_attr_radio_power_telit.attr,
	&dev_attr_radio_reset_telit.attr,
	&dev_attr_extserial_ri_gpio.attr,
	&dev_attr_extserial_dtr.attr,
	&dev_attr_extserial_dsr_gpio.attr,
	&dev_attr_extserial_dcd_gpio.attr,
	&dev_attr_eth0_enabled.attr,
	&dev_attr_bt_enabled.attr,
	&dev_attr_wlan_enabled.attr,

	&dev_attr_led_status.attr,
	&dev_attr_led_sig1_gpio.attr,
	&dev_attr_led_sig2_gpio.attr,
	&dev_attr_led_sig3_gpio.attr,
	&dev_attr_led_cd_gpio.attr,
	&dev_attr_led_wifi_gpio.attr,

	&dev_attr_led_a_gpio.attr,
	&dev_attr_led_b_gpio.attr,
	&dev_attr_led_c_gpio.attr,
	&dev_attr_led_d_gpio.attr,
	&dev_attr_led_e_gpio.attr,
	&dev_attr_led_f_gpio.attr,

	NULL,
};

static struct attribute_group mtr_platform_attribute_group = {
	.attrs = mtr_platform_attributes
};

static struct platform_device *mts_io_platform_device;

static int __devinit mts_spi_sout_probe(struct spi_device *spi)
{
	int tmp;

	spi->max_speed_hz = sout_max_speed_hz;
	spi->mode = 0;

	log_debug("sout_max_speed_hz: %d", sout_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup sout failed");
		return tmp;
	}

	spi_sout_value = 0xFF;
	spi_writen(spi, &spi_sout_value, 1);

	spi_sout_dev = spi;

	return 0;
}

static int mts_spi_sout_remove(struct spi_device *spi)
{
	spi_sout_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_sout_driver = {
	.driver = {
		.name = "mts-io-sout",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_sout_probe,
	.remove = __devexit_p(mts_spi_sout_remove),
};

static int __devinit mts_spi_dout_probe(struct spi_device *spi)
{
	int tmp;

	if (!DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT)) {
		log_debug("digital outputs not available");
		return -ENODEV;
	}

	spi->max_speed_hz = dout_max_speed_hz;
	spi->mode = 0;

	log_debug("dout_max_speed_hz: %d", dout_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup dout failed");
		return tmp;
	}

	spi_dout_value = 0x00;
	spi_writen(spi, &spi_dout_value, 1);

	spi_dout_dev = spi;

	return 0;
}

static int mts_spi_dout_remove(struct spi_device *spi)
{
	spi_dout_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_dout_driver = {
	.driver = {
		.name = "mts-io-dout",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_dout_probe,
	.remove = __devexit_p(mts_spi_dout_remove),
};

static int __devinit mts_spi_din_probe(struct spi_device *spi)
{
	int tmp;

	if (!DEVICE_CAPA(id_eeprom.capa, CAPA_DIN)) {
		log_debug("digital inputs not available");
		return -ENODEV;
	}

	spi->max_speed_hz = din_max_speed_hz;
	spi->mode = SPI_CPOL;

	log_debug("din_max_speed_hz: %d", din_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup din failed");
		return tmp;
	}

	spi_din_dev = spi;

	return 0;
}

static int mts_spi_din_remove(struct spi_device *spi)
{
	spi_din_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_din_driver = {
	.driver = {
		.name = "mts-io-din",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_din_probe,
	.remove = __devexit_p(mts_spi_din_remove),
};

static int __devinit mts_spi_dc_dout_probe(struct spi_device *spi)
{
	int tmp;

	if (! has_daughter_card || mts_dc_product_id != MTDC_GPIOB_0_0) {
		log_error("daughter card digital outputs not available");
		return -ENODEV;
	}

	spi->max_speed_hz = dc_dout_max_speed_hz;
	spi->mode = 0;

	log_debug("dc_dout_max_speed_hz: %d", dc_dout_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup dc dout failed");
		return tmp;
	}

	spi_dc_dout_value = 0x00;
	spi_writen(spi, &spi_dc_dout_value, 1);

	spi_dc_dout_dev = spi;

	return 0;
}

static int mts_spi_dc_dout_remove(struct spi_device *spi)
{
	spi_dc_dout_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_dc_dout_driver = {
	.driver = {
		.name = "mts-io-dc-dout",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_dc_dout_probe,
	.remove = __devexit_p(mts_spi_dc_dout_remove),
};

static int __devinit mts_spi_dc_din_probe(struct spi_device *spi)
{
	int tmp;

	if (! has_daughter_card || mts_dc_product_id != MTDC_GPIOB_0_0) {
		log_error("daughter card digital inputs not available");
		return -ENODEV;
	}

	spi->max_speed_hz = dc_din_max_speed_hz;
	spi->mode = SPI_CPOL;

	log_debug("dc_din_max_speed_hz: %d", dc_din_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup daughter card din failed");
		return tmp;
	}

	spi_dc_din_dev = spi;

	return 0;
}

static int mts_spi_dc_din_remove(struct spi_device *spi)
{
	spi_dc_din_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_dc_din_driver = {
	.driver = {
		.name = "mts-io-dc-din",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_dc_din_probe,
	.remove = __devexit_p(mts_spi_dc_din_remove),
};

static int __devinit mts_spi_dc_adc_probe(struct spi_device *spi)
{
	int tmp;

	if (! has_daughter_card || mts_dc_product_id != MTDC_GPIOB_0_0) {
		log_error("daughter card analog to digital not available");
		return -ENODEV;
	}

	spi->max_speed_hz = dc_adc_max_speed_hz;
	spi->mode = 0;

	log_debug("dc_adc_max_speed_hz: %d", dc_adc_max_speed_hz);
	log_debug("dc_adc_mode: %d", spi->mode);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup daughter card adc failed");
		return tmp;
	}

	spi_dc_adc_dev = spi;

	return 0;
}

static int mts_spi_dc_adc_remove(struct spi_device *spi)
{
	spi_dc_adc_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_dc_adc_driver = {
	.driver = {
		.name = "mts-io-dc-adc",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_dc_adc_probe,
	.remove = __devexit_p(mts_spi_dc_adc_remove),
};

static int __devinit mts_spi_board_temp_probe(struct spi_device *spi)
{
	int tmp;

	spi->max_speed_hz = board_temp_max_speed_hz;
	spi->mode = SPI_CPOL | SPI_CPHA;

	log_debug("board_temp_max_speed_hz: %d", board_temp_max_speed_hz);

	tmp = spi_setup(spi);
	if (tmp < 0) {
		log_error("spi_setup board-temp failed");
		return tmp;
	}

	spi_board_temp_dev = spi;

	return 0;
}

static int mts_spi_board_temp_remove(struct spi_device *spi)
{
	spi_board_temp_dev = NULL;

	return 0;
}

static struct spi_driver mts_spi_board_temp_driver = {
	.driver = {
		.name = "mts-io-board-temp",
		.bus = &spi_bus_type,
		.owner = THIS_MODULE,
	},

	.probe = mts_spi_board_temp_probe,
	.remove = __devexit_p(mts_spi_board_temp_remove),
};

static int mts_dc_eeprom_load(void)
{
	memcpy(&dc_eeprom, mts_dc_eeprom, sizeof(mts_dc_eeprom));

	if (mts_dc_eeprom[0] == 0xFF) {
		log_error("uninitialized eeprom on daughter card");
		return -EIO;
	} else if (mts_dc_eeprom[0] == 0x00) {
		log_info("no daughter card inserted");
		return 0;
	}

	has_daughter_card = true;

	log_info("daughter card vendor-id: %.32s", dc_eeprom.vendor_id);
	log_info("daughter card product-id: %.32s",  dc_eeprom.product_id);
	log_info("daughter card device-id: %.32s",  dc_eeprom.device_id);
	log_info("daughter card hw-version: %.32s",  dc_eeprom.hw_version);
	/* TODO: only show the mac address if this is the ethernet card */
	log_info("daughter card mac-addr: %02X:%02X:%02X:%02X:%02X:%02X",
            dc_eeprom.mac_addr[0],
            dc_eeprom.mac_addr[1],
            dc_eeprom.mac_addr[2],
            dc_eeprom.mac_addr[3],
            dc_eeprom.mac_addr[4],
            dc_eeprom.mac_addr[5]);

	return 0;
}

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;
		daughter_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;
		daughter_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 {
		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");
	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]);
	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;

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

	if (daughter_card_capable) {
		mts_dc_product_id = MTDC_NONE;
		ret = mts_dc_eeprom_load();
		if (ret) {
			/* error reading the EEPROM from the daughter card */
			log_error("error reading daughter card eeprom: %d", ret);
			log_error("unable to initialize daughter card");
			goto error1;
		} else if (has_daughter_card) {
			/* no error and we have a daughter card */
			if (strstr(dc_eeprom.product_id, PRODUCT_ID_MTDC_GPIOB)) {
				mts_dc_product_id = MTDC_GPIOB_0_0;
			}

			switch(mts_dc_product_id) {
				case MTDC_GPIOB_0_0:
					log_debug("adding GPIO daughter card attributes");
					if (! mtr2_add_daughter_card_attributes()) {
						log_error("failed to add GPIO daughter card attributes");
						goto error1;
					} else {
						log_info("successfully added GPIO daughter card attributes");
					}

					log_debug("registering daughter card dout driver");
					ret = spi_register_driver(&mts_spi_dc_dout_driver);
					if (ret) {
						log_error("failed to register dc dout driver");
						spi_unregister_driver(&mts_spi_dc_dout_driver);
						goto error1;
					}
					log_debug("registering daughter card din driver");
					ret = spi_register_driver(&mts_spi_dc_din_driver);
					if (ret) {
						log_error("failed to register dc din driver");
						spi_unregister_driver(&mts_spi_dc_din_driver);
						goto error1;
					}
					log_debug("registering daughter card adc driver");
					ret = spi_register_driver(&mts_spi_dc_adc_driver);
					if (ret) {
						log_error("failed to register dc adc driver");
						spi_unregister_driver(&mts_spi_dc_adc_driver);
						goto error1;
					}
					break;

				default:
					log_info("daughter card '%s' currently unsupported", dc_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;
		}
	}

#ifdef CONFIG_ARCH_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++) {
		gpio_request(pin->pin, pin->name);

		if (pin->direction == GPIO_DIR_OD) {
			log_info("Setting pin %s to multi-drive\n", pin->name);
			at91_set_multi_drive(pin->pin, true);
		}

		if (pin->direction == GPIO_DIR_OUTPUT || pin->direction == GPIO_DIR_OD) {
			at91_set_gpio_output_with_pullup(pin->pin, pin->output_value, pin->use_pullup);
		} else {
			at91_set_gpio_input(pin->pin, pin->use_pullup);
		}
	}

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

	// No blink_callback for MT100EOCG
	if ( mts_product_id != MT100EOCG_0_0 ) {
		blink_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);
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(&blink_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 (has_daughter_card) {
		switch (mts_dc_product_id) {
			case MTDC_GPIOB_0_0:
				spi_unregister_driver(&mts_spi_dc_dout_driver);
				spi_unregister_driver(&mts_spi_dc_din_driver);
				spi_unregister_driver(&mts_spi_dc_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-dc-dout");
MODULE_ALIAS("mts-io-dc-din");
MODULE_ALIAS("mts-io-dc-adc");