// BrightSign Mojito IO driver
//
// (C) 2007, 2008, 2009, 2010 BrightSign LLC
//
// Authors:
//   Mike Crowe <mcrowe@brightsign.biz>
//
// This driver deals with inputs and outputs on the Mojito
// board.
//
// This driver 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 driver 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 driver; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301 USA. Alternatively visit http://www.gnu.org/licenses/.
//

#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/timer.h>
#include <linux/reboot.h>

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

#define ENABLE_PNX833X_GPIO_REGS_DIRECT_ACCESS
#include <pnx833x.h>

MODULE_AUTHOR("Mike Crowe <mcrowe@brightsign.biz>");
MODULE_DESCRIPTION("BrightSign Mojito IO driver");
MODULE_LICENSE("GPL");

//#define debug printk
#define debug(...) do {} while(0)

static uint initial_outputs;
module_param(initial_outputs, uint, 0);
MODULE_PARM_DESC(initial_outputs, "Initial value of Mojito GPIO outputs.");

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

#define POLLS_PER_SECOND 40
#define JIFFY_INCREMENT (HZ/POLLS_PER_SECOND)

#define INPUT_COUNT 13

//Mojito has four simple inputs, a reset button and a front button
#define INPUT_BITS (0xf | (1<<11) | (1<<12))

// The total number of outputs we support including holes
#define OUTPUT_COUNT 11

// Mojito has no simple outputs and two LEDs
#define ERROR_LED (1<<9)
#define UPGRADE_LED (1<<10)

#define OUTPUT_BITS (ERROR_LED | UPGRADE_LED)

// The button that we treat as the Ctrl-Alt-Delete sequence. We
// consider the RESET button to have this behaviour if it is not enabled
// as an input but in order to let people reenable the special
// behaviour we also permit it to be set as an output.
#define RESET_BUTTON 11

// We need to appears as "buttons" rather than "keys" otherwise we'll
// end up generating keyboard events too.
#define FIRST_BUTTON BTN_MOUSE

struct io_input_dev
{
	struct input_dev *input_dev;
	u32 last_input_state;
};

struct io_output_dev
{
	struct input_dev *input_dev;
};

static spinlock_t shared_gpio_lock;
static struct io_input_dev io_input_dev;
static struct io_output_dev io_output_dev;
static struct timer_list io_timer;

// BUTTON0 - GPIO47 (bit 15)
// BUTTON1 - GPIO42 (bit 10)
// BUTTON2 - GPIO43 (bit 11)
// BUTTON3 - GPIO48 (bit 16)

static inline u32 to_bus_bits(u8 value)
{
	return ((value & 1) << 15) | ((value & 6) << (10-1)) | ((value & 8) << (16-3));
}

static inline u8 from_bus_bits(u32 regs)
{
	return ((regs & (1<<15)) >> 15) | ((regs & (3<<10)) >> (10-1)) | ((regs & (1<<16)) >> (16-3));
}
		    
static inline u8 io_read_banked(void)
{
	u32 regs = PNX833X_PIO_IN2;
	return from_bus_bits(regs);
}

static inline void io_gpio_write(u8 value, u8 reg)
{
	unsigned long flags;
	
	// Because a GPIO write is a read-modify-write operation we
	// need to protect ourselves against others nipping in. There
	// is code in NXP proprietary code that also accesses these
	// registers but it also protects itself by disabling
	// interrupts so we're compatible.
	spin_lock_irqsave(&shared_gpio_lock, flags);
	gpio_write(value, reg);
	spin_unlock_irqrestore(&shared_gpio_lock, flags);
}

static inline int io_gpio_read(u8 reg)
{
	return gpio_read(reg);
}

static void io_configure_gpio(void)
{
	unsigned long flags;
	spin_lock_irqsave(&shared_gpio_lock, flags);
	
	// First we need to identify the board
	gpio_select_input(6);
	gpio_select_input(7);

	debug("Board ID: %ld\n", ((PNX833X_PIO_IN >> 6) & 3));

	switch ((PNX833X_PIO_IN >> 6) & 3)
	{
	case 2:
		pr_info("Mojito IO driver detected correct board\n");
		break;
	default:
		pr_err("\nUnknown board hardware revision\n\n");
		break;
	};
	
	gpio_select_input(23); // FRNT BT
	
	gpio_select_output(14); // UPGRADE LED

	// We don't want the LED to blink at startup so we have to set
	// it high before we make it an output.
	gpio_write(1, 7);
	gpio_select_output(7); // ERROR LED
	

	// Configure the latched bank as inputs (tristated)
	PNX833X_PIO_DIR2 &= ~to_bus_bits(0xff);

	spin_unlock_irqrestore(&shared_gpio_lock, flags);
}

//
// OUTPUT
//

static int io_set_output(struct io_output_dev *dev, u8 index, int value)
{
	debug("io_set_output(%u, %d)\n", index, value);
	switch (index)
	{
	case 9:
		// This LED is upsidedown
		io_gpio_write(!value, 7);
		return 0;
	case 10:
		io_gpio_write(value, 14);
		return 0;
	default:
		// Invalid
		return -1;
	}
}

static void io_set_outputs(struct io_output_dev *dev, u32 value)
{
	u8 index;
	for(index = 0; index < OUTPUT_COUNT; ++index, value >>= 1) {
		io_set_output(dev, index, value & 1);
		input_event(dev->input_dev, EV_LED, index, value & 1);
	}
}

static int io_output_event(struct input_dev *input_dev, unsigned int type, unsigned int code, int value)
{
	struct io_output_dev *dev = input_get_drvdata(input_dev);

	debug("io_output_event %u, %d\n", code, value);

	if (type == EV_LED) {
		return io_set_output(dev, code, value);
	}
	else
		return -1;
}

//
// INPUT
//
static inline u32 io_read_inputs(struct io_input_dev *dev)
{
	u32 result = 0U;
	u32 gpio0;

	// First read the IO connector from the bank.
	result = io_read_banked();

	// Now read the side buttons
	gpio0 = PNX833X_PIO_IN;

	// RESET
	result |= ((gpio0 >> 12) & 1) << 11;

	// FRNT_BT
	result |= ((gpio0 >> 23) & 1) << 12;

	return result;
}

static void io_report_inputs(struct io_input_dev *dev, u32 state, int report_down)
{
	u32 mask;
	u32 button;
	u32 changes;

	if (unlikely((state & (1<<RESET_BUTTON)) == 0)) {
		pr_info("Requesting reboot from reset button.\n");
		ctrl_alt_del();
		return;
	}

	if (report_down)
		changes = state ^ ((1<<INPUT_COUNT)-1);
	else
		changes = dev->last_input_state ^ state;
	
	// The buttons are active low so we invert the state when we
	// pass it to report_key. We'll report buttons that don't
	// exist on the first time through here but that doesn't
	// matter because the input layer just ignores them.
	for(mask = 1, button = FIRST_BUTTON; mask <= changes; mask<<=1, ++button)
		if (changes & mask)
			input_report_key(dev->input_dev, button, !(state & mask));
	input_sync(dev->input_dev);

	// Make sure we don't report these changes again.
	dev->last_input_state = state;
}

static void io_input_timer(unsigned long data)
{
	u32 new_inputs;
	struct io_input_dev *dev = (struct io_input_dev *)data;

	new_inputs = io_read_inputs(dev);

	if (unlikely(new_inputs != dev->last_input_state))
		io_report_inputs(dev, new_inputs, FALSE);

	io_timer.expires = jiffies + JIFFY_INCREMENT;
	add_timer(&io_timer);
}

//
// DRIVER
//

static int __devinit io_probe(struct platform_device *dev)
{
        struct input_dev *input_dev = NULL;
	struct input_dev *output_dev = NULL;
        int err;
	unsigned long i;
	u32 valid_input_bits, valid_output_bits;

	spin_lock_init(&shared_gpio_lock);
	
        input_dev = input_allocate_device();
        if (!input_dev) {
                err = -ENOMEM;
		goto err_free_devices;
	}

	output_dev = input_allocate_device();
	if (!output_dev) {
		err = -ENOMEM;
		goto err_free_devices;
	}

        input_dev->name = "BrightSign Mojito input device",
        input_dev->phys = "brightsign/input";
        input_dev->id.bustype = BUS_HOST;
        input_dev->id.vendor  = 0x001f;
        input_dev->id.product = 0x0002;
        input_dev->id.version = 0x0100;	
        input_set_drvdata(input_dev, &io_input_dev);
	set_bit(EV_KEY, input_dev->evbit);
	set_bit(EV_LED, input_dev->evbit);

	valid_input_bits = INPUT_BITS;
	valid_output_bits = OUTPUT_BITS;
		
	// The buttons we support and whether they are bidirectional.
	for(i = 0; i < INPUT_COUNT; ++i) {
		if (valid_input_bits & (1<<i)) {
			set_bit(FIRST_BUTTON + i, input_dev->keybit);
		}
	}

        output_dev->name = "BrightSign Mojito output device",
        output_dev->phys = "brightsign/output";
	output_dev->event = io_output_event;
        output_dev->id.bustype = BUS_HOST;
        output_dev->id.vendor  = 0x001f;
        output_dev->id.product = 0x0002;
        output_dev->id.version = 0x0100;	
	input_set_drvdata(output_dev, &io_output_dev);
	set_bit(EV_LED, output_dev->evbit);

	// The LEDs we support and whether they are bidirectional
	for(i = 0; i < OUTPUT_COUNT; ++i) {
		if (valid_output_bits & (1<<i)) {
			set_bit(i, output_dev->ledbit);
		}
	}

	io_input_dev.input_dev = input_dev;
	io_output_dev.input_dev = output_dev;

	// Set all the GPIOs up how we want them.
	io_configure_gpio();

        err = input_register_device(input_dev);
	if (err)
		goto err_free_devices;

        err = input_register_device(output_dev);
	if (err) {
		input_unregister_device(input_dev);
		goto err_free_devices;
	}

	io_set_outputs(&io_output_dev, initial_outputs);
	
	// Report all the buttons that are currently down before we
	// start the timer so we don't race.
	io_report_inputs(&io_input_dev, io_read_inputs(&io_input_dev), TRUE);

	init_timer(&io_timer);
	io_timer.data = (unsigned long)&io_input_dev;
	io_timer.function = io_input_timer;
	io_timer.expires = jiffies;
	add_timer(&io_timer);
	return 0;

err_free_devices:
	if (output_dev)
		input_free_device(output_dev);
	if (input_dev)
		input_free_device(input_dev);
        return err;
}

static int __devexit io_remove(struct platform_device *dev)
{
	del_timer_sync(&io_timer);
        input_unregister_device(io_input_dev.input_dev);
        input_unregister_device(io_output_dev.input_dev);

        return 0;
}

static void io_shutdown(struct platform_device *dev)
{
	del_timer_sync(&io_timer);
	input_unregister_device(io_input_dev.input_dev);
	input_unregister_device(io_output_dev.input_dev);
}


static struct platform_driver io_platform_driver = {
        .driver         = {
                .name   = "bs-mojito-io",
                .owner  = THIS_MODULE,
        },
        .probe          = io_probe,
        .remove         = io_remove,
        .shutdown       = io_shutdown,
};

static struct platform_device *io_platform_device;

static int __init io_init(void)
{
	int err;
	err = platform_driver_register(&io_platform_driver);
	if (err == 0) {
		io_platform_device = platform_device_alloc("bs-mojito-io", -1);
		if (io_platform_device) {
			err = platform_device_add(io_platform_device);
			if (err == 0) {
				return 0;
			} else {
				platform_device_put(io_platform_device);
			}
		} else {
			err = -ENOMEM;
		}
		
		platform_driver_unregister(&io_platform_driver);
	}
	return err;
}

static void __exit io_exit(void)
{
	platform_device_del(io_platform_device);
	platform_driver_unregister(&io_platform_driver);
}

module_init(io_init);
module_exit(io_exit);

