/* SPI devices, functions, and attributes */

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

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

	return value;
}

/* SPI Devices */
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_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)"
);

/* Generic SPI functions */
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;
}

/* ----------------------------------------------------------------------------
 *
 * SPI-based attribute show/store functions
 *
 * ----------------------------------------------------------------------------
*/

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

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

/* ----------------------------------------------------------------------------
 *
 * SPI-based attributes
 *
 * ----------------------------------------------------------------------------
*/

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

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

/* SPI driver setup */
static int 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 = mts_spi_sout_remove,
};

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

	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 = mts_spi_dout_remove,
};

static int mts_spi_din_probe(struct spi_device *spi)
{
	int tmp;
	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 = mts_spi_din_remove,
};

static int 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 = mts_spi_board_temp_remove,
};