/*
 *  Copyright (C) 2011 BrightSign Digital Ltd
 *
 * 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/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>

#include <asm/irq.h>
#include <asm/mach-pnx833x/gpio.h>

#define REG_IRQ_CONTROL		0
#define REG_FIFO_STAT		0
#define REG_BAUD_DIV		1
#define REG_DEVICE_ID		2
#define REG_TX_FIFO		3
#define REG_RX_FIFO		3

#define UART_NR		1

#define SERIAL_PIC_MAJOR	204
#define SERIAL_PIC_MINOR	40
#define SERIAL_PIC_NR		UART_NR

static int gpio_mclr = 24;
static int gpio_invert = 16;

//#define DEBUG

struct serial_pic_data
{
	struct i2c_client client;
	struct work_struct work;
	struct work_struct irqmask_work;
	struct uart_port *port;
};

/*
 * Functions declaration
 */
static unsigned short normal_i2c[] = { 0x21, I2C_CLIENT_END };
static unsigned short ignore[] = { I2C_CLIENT_END };

static struct i2c_client_address_data addr_data = {
	.normal_i2c = normal_i2c,
	.probe = ignore,
	.ignore = ignore,
};

static int serial_pic_attach_adapter(struct i2c_adapter *adapter);
static int serial_pic_detect(struct i2c_adapter *adapter, int address, int kind);
static int serial_pic_detach_client(struct i2c_client *client);
static irqreturn_t pic_interrupt(int irq, void *dev_id);

#define tx_enabled(port)	((port)->unused[0])
#define rx_enabled(port)	((port)->unused[1])

/*
 * Driver data (common to all clients)
 */
static struct i2c_driver serial_pic_driver = {
	.driver = {
		.name	= "serial_brightsign_pic",
	},
	.attach_adapter	= serial_pic_attach_adapter,
	.detach_client	= serial_pic_detach_client,
};

static struct uart_driver pic_uart_driver = {
	.driver_name		= "ttyET",
	.dev_name		= "ttyET",
	.major			= SERIAL_PIC_MAJOR,
	.minor			= SERIAL_PIC_MINOR,
	.nr			= UART_NR,
};

static int read_reg (struct uart_port *port, u8 reg, u8 *val)
{
	struct i2c_client *client = port->private_data;
	struct i2c_msg msg[2];

	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &reg;

	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = 1;
	msg[1].buf = val;

	return i2c_transfer(client->adapter, msg, 2);
}

static int write_reg (struct uart_port *port, u8 reg, u8 val)
{
	struct i2c_client *client = port->private_data;
	u8 buf[2];
	struct i2c_msg msg;
	int err;

	buf[0] = reg;
	buf[1] = val;

	msg.addr = client->addr;
	msg.flags = 0;
	msg.len = 2;
	msg.buf = buf;

	err = i2c_transfer(client->adapter, &msg, 1);
	if (err < 0)
		printk ("write_reg failed, error %d\n", err);
	return err;
}

static void update_irq_mask (struct uart_port *port)
{
	write_reg (port, REG_IRQ_CONTROL, (tx_enabled(port) ? 0x1: 0x0) | (rx_enabled(port) ? 0x2 : 0x00));
}

static void pic_uart_fill_tx_buf (struct uart_port *port, int chars)
{
	struct circ_buf *xmit = &port->info->xmit;
	char buf[16];
	char *p = buf;

	*(p++) = REG_TX_FIFO;
	
	if (chars >= sizeof (buf))
		chars = sizeof (buf) - 1;
	
#ifdef DEBUG
	printk ("%s: writing %d chars\n", __FUNCTION__, chars);
#endif

	while (chars--) {
		if (port->x_char) {
			*(p++) = port->x_char;
			port->x_char = 0;
			continue;
		}

		if (uart_circ_empty(xmit))
			break;
		
		*(p++) = xmit->buf[xmit->tail];
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
	}

	if (p != &buf[1]) {
		struct i2c_client *client = port->private_data;
		struct i2c_msg msg;
		int r;

		msg.addr = client->addr;
		msg.flags = 0;
		msg.len = p - buf;
		msg.buf = buf;

		r = i2c_transfer(client->adapter, &msg, 1);
	} else {
		tx_enabled(port) = 0;
		update_irq_mask (port);		
	}

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(port);
}

static void pic_uart_unload_rx (struct uart_port *port, int chars)
{
	unsigned char buf[16];
	struct tty_struct *tty = port->info->tty;
	struct i2c_msg msg[2];
	u8 reg = REG_RX_FIFO;
	struct i2c_client *client = port->private_data;
	int err;
	int i;

	if (chars > sizeof (buf))
		chars = sizeof (buf);

#ifdef DEBUG
	printk ("%s: reading %d chars\n", __FUNCTION__, chars);
#endif

	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &reg;

	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = chars;
	msg[1].buf = buf;

	err = i2c_transfer(client->adapter, msg, 2);
	if (err < 0) {
		printk ("rx fifo read failed, error %d\n", err);
		return;
	}

	for (i = 0; i < chars; i++) {
		port->icount.rx++;
		uart_insert_char (port, 0, 0, buf[i], TTY_NORMAL);
	}

#ifdef DEBUG
	printk ("%s: pushing %d chars\n", __FUNCTION__, chars);
#endif

	tty_flip_buffer_push (tty);
}

static void schedule_update_irq_mask (struct uart_port *port)
{
	struct i2c_client *client = port->private_data;
	struct serial_pic_data *pic = i2c_get_clientdata(client);
	schedule_work (&pic->irqmask_work);
}

static void pic_uart_stop_tx(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	if (tx_enabled(port)) {
		tx_enabled(port) = 0;
		schedule_update_irq_mask (port);
	}
}

static void pic_uart_start_tx(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	if (!tx_enabled(port)) {
		tx_enabled(port) = 1;
		schedule_update_irq_mask (port);
	}
}

static void pic_uart_stop_rx(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	rx_enabled(port) = 0;
}

static void pic_uart_enable_ms(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif
}

static unsigned int pic_uart_tx_empty(struct uart_port *port)
{
	return TIOCSER_TEMT;
}

static unsigned int pic_uart_get_mctrl(struct uart_port *port)
{
	return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
}

static void
pic_uart_set_mctrl_null(struct uart_port *port, unsigned int mctrl)
{
}

static void pic_uart_break_ctl(struct uart_port *port, int break_state)
{
}

static int pic_uart_startup(struct uart_port *port)
{
	int err;

#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	rx_enabled(port) = 1;
	tx_enabled(port) = 0;

	err = request_irq (port->irq, pic_interrupt, 0, "pic_uart", port);
	if (err)
		return err;

	gpio_setup_irq (GPIO_INT_LEVEL_LOW, 15);

	update_irq_mask (port);

#ifdef DEBUG
	printk ("%s: done\n", __FUNCTION__);
#endif

	return 0;
}

static void pic_uart_shutdown(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	free_irq (port->irq, port);

	gpio_setup_irq (0, 15);

#ifdef DEBUG
	printk ("%s: flushing work\n", __FUNCTION__);
#endif

	flush_scheduled_work ();

#ifdef DEBUG
	printk ("%s: done\n", __FUNCTION__);
#endif
}

static void
pic_uart_set_termios(struct uart_port *port, struct ktermios *termios,
		     struct ktermios *old)
{
	unsigned int baud, quot;

#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	/*
	 * We don't implement CREAD.
	 */
	termios->c_cflag |= CREAD;

	/*
	 * Ask the core to calculate the divisor for us.
	 */
	baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); 
	quot = uart_get_divisor(port, baud);

	/*
	 * Update the per-port timeout.
	 */
	uart_update_timeout(port, termios->c_cflag, baud);

	/*
	 * Characters to ignore
	 */
	port->ignore_status_mask = 0;

#ifdef DEBUG
	printk ("calculated quot for %d baud is %d\n", baud, quot);
#endif

	write_reg (port, REG_BAUD_DIV, quot - 1);
}

static const char *pic_uart_type(struct uart_port *port)
{
	return (port->type == PORT_MCF) ? "PIC" : "";
}

/*
 * Configure/autoconfigure the port.
 */
static void pic_uart_config_port(struct uart_port *port, int flags)
{
	if (flags & UART_CONFIG_TYPE)
		port->type = PORT_MCF;
}

static void pic_irqmask_work(struct work_struct *work)
{
	struct serial_pic_data *pic = container_of (work, struct serial_pic_data, irqmask_work);
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif
	update_irq_mask (pic->port);
}

static void pic_work(struct work_struct *work)
{
	u8 fifostat;
	int tx_space, rx_bytes;
	struct serial_pic_data *pic = container_of (work, struct serial_pic_data, work);
	int worked;
	
	do {
		worked = 0;

		read_reg (pic->port, REG_FIFO_STAT, &fifostat);
		tx_space = (fifostat & 0xf);
		rx_bytes = (fifostat & 0xf0) >> 4;
#ifdef DEBUG
		printk ("tx fifo %x bytes available, rx fifo %x bytes available\n", tx_space, rx_bytes);
#endif

		if (rx_bytes && rx_enabled (pic->port)) {
			worked = 1;
			pic_uart_unload_rx (pic->port, rx_bytes);
		}

		if (tx_space && tx_enabled (pic->port)) {
			worked = 1;
			pic_uart_fill_tx_buf (pic->port, tx_space);
		}
	} while (worked);

	enable_irq (pic->port->irq);
}

static irqreturn_t pic_interrupt(int irq, void *dev_id)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	struct uart_port *port = dev_id;
	struct i2c_client *clnt = port->private_data;
	struct serial_pic_data *pic = i2c_get_clientdata(clnt);

	disable_irq (port->irq);
	schedule_work (&pic->work);
	return IRQ_HANDLED;
}

static void pic_uart_release_port(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif
}

static int pic_uart_request_port(struct uart_port *port)
{
#ifdef DEBUG
	printk ("%s\n", __FUNCTION__);
#endif

	return 0;
}

static int
pic_uart_ioctl(struct uart_port *port, unsigned int cmd, unsigned long arg)
{
	int ret = -ENOIOCTLCMD;

	switch (cmd) {
	case TIOCSINVERTED:
		if (arg)
			gpio_write (1, gpio_invert);
		else
			gpio_write (0, gpio_invert);
		ret = 0;
		break;
	}

	return ret;
}

static struct uart_ops uart_pops = {
	.tx_empty	= pic_uart_tx_empty,
	.set_mctrl	= pic_uart_set_mctrl_null,
	.get_mctrl	= pic_uart_get_mctrl,
	.stop_tx	= pic_uart_stop_tx,
	.start_tx	= pic_uart_start_tx,
	.stop_rx	= pic_uart_stop_rx,
	.enable_ms	= pic_uart_enable_ms,
	.break_ctl	= pic_uart_break_ctl,
	.startup	= pic_uart_startup,
	.shutdown	= pic_uart_shutdown,
	.set_termios	= pic_uart_set_termios,
	.type		= pic_uart_type,
	.config_port	= pic_uart_config_port,
	.release_port	= pic_uart_release_port,
	.request_port	= pic_uart_request_port,
	.ioctl		= pic_uart_ioctl,
};

static struct uart_port ports[UART_NR] = {
	{
		.iobase		= 0x100,
		.irq		= PNX833X_GPIO_15_INT,
		.uartclk	= 16000000,
		.fifosize	= 4,
		.ops		= &uart_pops,
		.line		= 0,
		.flags		= UPF_BOOT_AUTOCONF,
	},
};

static int serial_pic_detect(struct i2c_adapter *adapter, int address, int kind)
{
	struct i2c_client *new_client;
	struct serial_pic_data *data;
	int err = 0;
	const char *name = "serial_pic";
	u8 id;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
				     I2C_FUNC_I2C))
		goto exit;

	if (!(data = kzalloc(sizeof(struct serial_pic_data), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}

	new_client = &data->client;
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &serial_pic_driver;
	new_client->flags = 0;

	/* We can fill in the remaining client fields */
	strlcpy(new_client->name, name, I2C_NAME_SIZE);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client)))
		goto exit_free;

	ports[0].private_data = new_client;
	INIT_WORK(&data->work, pic_work);
	INIT_WORK(&data->irqmask_work, pic_irqmask_work);
	data->port = &ports[0];

	if (read_reg (&ports[0], REG_DEVICE_ID, &id) >= 0) {
		printk ("PIC serial expander registered, hardware id %02x.\n", id);
		uart_add_one_port (&pic_uart_driver, &ports[0]);
	} else
		printk ("Couldn't read hardware id\n");

	return 0;

exit_free:
	kfree(data);
exit:
	return err;
}

static int serial_pic_detach_client(struct i2c_client *client)
{
	int err;
	struct serial_pic_data *data = i2c_get_clientdata(client);

	uart_remove_one_port (&pic_uart_driver, &ports[0]);

	if ((err = i2c_detach_client(client)))
		return err;

	kfree(data);
	return 0;
}

static int serial_pic_attach_adapter(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, serial_pic_detect);
}

static int __init serial_pic_init(void)
{
	int ret;

	gpio_select_output (gpio_mclr);
	gpio_write (1, gpio_mclr);

	gpio_select_output (gpio_invert);
	gpio_write (0, gpio_invert);

	ret = uart_register_driver(&pic_uart_driver);
	if (ret)
		return ret;

	return i2c_add_driver(&serial_pic_driver);
}

static void __exit serial_pic_exit(void)
{
	i2c_del_driver(&serial_pic_driver);
	uart_unregister_driver(&pic_uart_driver);
}

MODULE_LICENSE("GPL");

module_init(serial_pic_init);
module_exit(serial_pic_exit);
