/*
 * Bluetooth Smart Keyboard driver for Linux
 * by Maikichi <maikichi@gypsyblue.ddo.jp>
 */

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>

#define DRIVER_DESC	"Smart keyboard driver"

static char debug = 0;
MODULE_PARM(debug, "c");

#define SERIO_SMTKBD	0x23

/*
  Use Standard keycode defined by <linux/input.h>

      HOME   -> LEFT META
      CIRCLE -> GRAVE
      DEL    -> ESC
*/
static unsigned char smtkbd_keycode[128] = {
	0               , KEY_1           , KEY_2           , KEY_3           ,
	KEY_4           , KEY_5           , KEY_6           , KEY_Q           ,
	KEY_W           , KEY_E           , KEY_R           , KEY_T           ,
	KEY_A           , KEY_S           , KEY_D           , KEY_F           ,

	KEY_G           , KEY_Z           , KEY_X           , KEY_C           ,
	KEY_V           , KEY_B           , KEY_SPACE       , KEY_TAB         ,
	KEY_CAPSLOCK    , KEY_LEFTMETA    , KEY_7           , KEY_8           ,
	KEY_9           , KEY_0           , KEY_MINUS       , KEY_EQUAL       ,

	KEY_BACKSPACE   , KEY_Y           , KEY_U           , KEY_I           ,
	KEY_O           , KEY_P           , KEY_LEFTBRACE   , KEY_RIGHTBRACE  ,
	KEY_H           , KEY_J           , KEY_K           , KEY_L           ,
	KEY_SEMICOLON   , KEY_APOSTROPHE  , KEY_ENTER       , KEY_N           ,

	KEY_M           , KEY_COMMA       , KEY_DOT         , KEY_SLASH       ,
	KEY_UP          , KEY_SPACE       , KEY_GRAVE       , KEY_LEFT        ,
	KEY_DOWN        , KEY_RIGHT       , KEY_LEFTCTRL    , KEY_RIGHTSHIFT  ,
	KEY_LEFTSHIFT   , KEY_ESC/*DEL*/  , 0/*FUNC*/       , KEY_LEFTALT     ,

	/* +Funk Key */
	0               , KEY_F1          , KEY_F2          , KEY_F3          ,
	KEY_F4          , KEY_F5          , KEY_F6          , 0/*KEY_Q*/      ,
	0/*KEY_W*/      , 0/*KEY_E*/      , 0/*KEY_R*/      , 0/*KEY_T*/      ,
	0/*KEY_A*/      , 0/*KEY_S*/      , 0/*KEY_D*/      , 0/*KEY_F*/      ,

	0/*KEY_G*/      , 0/*KEY_Z*/      , 0/*KEY_X*/      , 0/*KEY_C*/      ,
	0/*KEY_V*/      , 0/*KEY_B*/      , KEY_SPACE       , KEY_TAB         ,
	KEY_CAPSLOCK    , KEY_RIGHTMETA   , KEY_F7          , KEY_F8          ,
	KEY_F9          , KEY_F10         , KEY_F11         , KEY_F12         ,

	KEY_DELETE      , 0/*KEY_Y*/      , 0/*KEY_U*/      , KEY_SYSRQ       ,
	KEY_SCROLLLOCK  , KEY_PAUSE       , KEY_UP          , KEY_BACKSLASH   ,
	KEY_KPASTERISK  , KEY_KPSLASH     , KEY_HOME        , KEY_PAGEUP      ,
	KEY_LEFT        , KEY_RIGHT       , KEY_ENTER       , KEY_KPPLUS      ,

	KEY_KPMINUS     , KEY_END         , KEY_PAGEDOWN    , KEY_DOWN        ,
	KEY_UP          , KEY_SPACE       , KEY_GRAVE       , KEY_LEFT        ,
	KEY_DOWN        , KEY_RIGHT       , KEY_RIGHTCTRL   , KEY_RIGHTSHIFT  ,
	KEY_LEFTSHIFT   , KEY_ESC/*DEL*/  , 0/*FUNC*/       , KEY_RIGHTALT
};

struct smart_keyboard {
	unsigned char		keycode[128];
	unsigned char		press[32];	/* 256/8 */
	unsigned char		func;
	struct input_dev	dev;
	struct serio		*serio;
	char phys[32];
};

static irqreturn_t smtkbd_interrupt(struct serio *serio, unsigned char data,
				    unsigned int flags, struct pt_regs *regs)
{
	struct smart_keyboard *smtkbd = serio_get_drvdata(serio);
	unsigned char keycode;
	unsigned char release;
	int i, j;

	if (data == 0x3e || data == 0x7d) {

		/* Press or release [Func] key */
		smtkbd->func = (data & 0x40) ? 0 : 1;

		/* Release all keys */
		for (i = 0; i < 32; i++) {
			for (j = 0; j < 8; j++)
				if (smtkbd->press[i] & (1 << j))
					input_report_key(&smtkbd->dev, i * 8 + j, 0);
			smtkbd->press[i] = 0;
		}

	} else {
		release = (data & 0x40) ? 1 : 0;
		keycode = smtkbd->keycode[(data & 0x3f) + release
					  + (smtkbd->func * 64)];
		if (keycode) {
			input_report_key(&smtkbd->dev, keycode,
					 release ? 0 : 1);
			if (release)
				smtkbd->press[keycode / 8] &= ~(1 << (keycode % 8));
			else
				smtkbd->press[keycode / 8] |= (1 << (keycode % 8));

			if (debug)
				printk(KERN_INFO "Smart Keyboard [%d] : %s\n",
				       keycode, release ? "Release" : "Press");
		}
	}

	return IRQ_HANDLED;
}

static int smtkbd_connect(struct serio *serio, struct serio_driver *drv)
{
	struct smart_keyboard *smtkbd;
	int i;
	int err;

	if (!(smtkbd = kmalloc(sizeof(struct smart_keyboard), GFP_KERNEL)))
		return -ENOMEM;

	memset(smtkbd, 0, sizeof(struct smart_keyboard));

	smtkbd->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP);

	smtkbd->serio = serio;

	init_input_dev(&smtkbd->dev);
	smtkbd->dev.keycode = smtkbd->keycode;
	smtkbd->dev.keycodesize = sizeof(unsigned char);
	smtkbd->dev.keycodemax = ARRAY_SIZE(smtkbd_keycode);
	smtkbd->dev.private = smtkbd;

	serio_set_drvdata(serio, smtkbd);

	err = serio_open(serio, drv);
	if (err) {
		serio_set_drvdata(serio, NULL);
		kfree(smtkbd);
		return err;
	}

	memcpy(smtkbd->keycode, smtkbd_keycode, sizeof(smtkbd->keycode));
	for (i = 0; i < 128; i++)
		set_bit(smtkbd->keycode[i], smtkbd->dev.keybit);
	clear_bit(0, smtkbd->dev.keybit);

	sprintf(smtkbd->phys, "%s/input0", serio->phys);

	smtkbd->dev.name = DRIVER_DESC;
	smtkbd->dev.phys = smtkbd->phys;
	smtkbd->dev.id.bustype = BUS_RS232;
	smtkbd->dev.id.vendor = SERIO_SMTKBD;
	smtkbd->dev.id.product = 0x0001;
	smtkbd->dev.id.version = 0x0100;
	smtkbd->dev.dev = &serio->dev;

	input_register_device(&smtkbd->dev);

	printk(KERN_INFO "input: %s on %s\n", DRIVER_DESC, serio->phys);

	return 0;
}

void smtkbd_disconnect(struct serio *serio)
{
	struct smart_keyboard *smtkbd = serio_get_drvdata(serio);
#if 0
	int i, j;

	/* Release All Key */
	for (i = 0; i < 32; i++) {
		for (j = 0; j < 8; j++)
			if (smtkbd->press[i] & (1 << j))
				input_report_key(&smtkbd->dev, i * 8 + j, 0);
		smtkbd->press[i]=0;
	}
#endif

	input_unregister_device(&smtkbd->dev);
	serio_close(serio);
	serio_set_drvdata(serio, NULL);
	kfree(smtkbd);
}

static struct serio_device_id smtkbd_serio_ids[] = {
	{
		.type	= SERIO_RS232,
		.proto	= SERIO_SMTKBD,
		//.proto	= SERIO_STOWAWAY,
		.id	= SERIO_ANY,
		.extra	= SERIO_ANY,
	},
	{ 0 }
};
MODULE_DEVICE_TABLE(serio, smtkbd_serio_ids);

static struct serio_driver smtkbd_drv = {
	.driver		= {
		.name	= "smtkbd",
	},
	.description	= DRIVER_DESC,
	.id_table	= smtkbd_serio_ids,
	.interrupt	= smtkbd_interrupt,
	.connect	= smtkbd_connect,
	.disconnect	= smtkbd_disconnect
};

static int __init smtkbd_init(void)
{
	serio_register_driver(&smtkbd_drv);
	return 0;
}

static void __exit smtkbd_exit(void)
{
	serio_unregister_driver(&smtkbd_drv);
}

module_init(smtkbd_init);
module_exit(smtkbd_exit);

MODULE_AUTHOR("maikichi <maikichi@gypsyblue.ddo.jp>");
MODULE_DESCRIPTION("Bluetooth Smart Keyboard driver");
MODULE_LICENSE("GPL");
