From 9c19184f3d98342d84cbb4e4e61fb8b3a782e654 Mon Sep 17 00:00:00 2001
From: z3deverp <z3@vmsv-debian.(none)>
Date: Sat, 14 Nov 2009 01:27:27 +0900
Subject: [PATCH] sha_pon keyboard support

---
 drivers/input/keyboard/Kconfig      |   11 +
 drivers/input/keyboard/Makefile     |    1 +
 drivers/input/keyboard/sha_ponkbd.c |  402 +++++++++++++++++++++++++++++++++++
 3 files changed, 414 insertions(+), 0 deletions(-)

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index a6b989a..46c2751 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -361,4 +361,15 @@ config KEYBOARD_XTKBD
 	  To compile this driver as a module, choose M here: the
 	  module will be called xtkbd.
 
+config KEYBOARD_SHA_PON
+	tristate "Sha_pon keyboard"
+	depends on PXA_SHA_PON
+	default y
+	help
+	  Say Y here to enable the keyboard on the Sharp W-ZERO3 series
+	  of smartphones.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sha_ponkbd.
+
 endif
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index b5b5eae..306e24b 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_KEYBOARD_STOWAWAY)		+= stowaway.o
 obj-$(CONFIG_KEYBOARD_SUNKBD)		+= sunkbd.o
 obj-$(CONFIG_KEYBOARD_TOSA)		+= tosakbd.o
 obj-$(CONFIG_KEYBOARD_XTKBD)		+= xtkbd.o
+obj-$(CONFIG_KEYBOARD_SHA_PON)		+= sha_ponkbd.o
diff --git a/drivers/input/keyboard/sha_ponkbd.c b/drivers/input/keyboard/sha_ponkbd.c
new file mode 100644
index 0000000..d5c2b9f
--- /dev/null
+++ b/drivers/input/keyboard/sha_ponkbd.c
@@ -0,0 +1,402 @@
+/*
+ *  Keyboard driver for the Sharp W-ZERO3 series(sha_pon0xx)
+ *
+ *  Auther: zaki
+ *
+ *  Based on aaed2000_kbd.c
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/input-polldev.h>
+#include <linux/module.h>
+
+#include <mach/sha_pon.h>
+
+#define KB_ROWS			8
+#define KB_COLS			12
+
+#define KB_ROWMASK(r)		(1 << (r))
+#define SCANCODE(r,c)		(((c) * KB_ROWS) + (r))
+#define NR_SCANCODES		(KB_COLS * KB_ROWS)
+#define SCAN_INTERVAL		(50) /* ms */
+
+#define KB_ACTIVATE_DELAY	(20) /* us */
+#define KBDSCAN_STABLE_COUNT 2
+
+
+#define	KEY_ALT	KEY_LEFTALT
+#define	KEY_CTRL	KEY_LEFTCTRL
+#define	KEY_SHIFT	KEY_LEFTSHIFT	/* LEFT&RIGHT is same circuit */
+
+
+static unsigned char sha_pon_keycode[] = {
+/* KEY_ZENKAKUHANKAKU, KEY_MUHENKAN: WS011SH only */
+	KEY_CTRL,	0,	KEY_TAB,	0,	0,	0,	0,	0,
+	KEY_1,	KEY_2,	KEY_Q,	KEY_W,	KEY_A,	KEY_Z,	KEY_HENKAN,	0,
+	KEY_3,	KEY_4,	KEY_E,	KEY_S,	KEY_D,	KEY_X,	KEY_ZENKAKUHANKAKU,	0,
+	KEY_5,	KEY_R,	KEY_T,	KEY_F,	KEY_C,	KEY_MINUS,	KEY_MUHENKAN,	0,
+
+	KEY_6,	KEY_Y,	KEY_G,	KEY_V,	KEY_B,	KEY_SPACE,	0,	0,
+	KEY_7,	KEY_8,	KEY_U,	KEY_H,	KEY_N,	KEY_SLASH,	KEY_COMMA,	0,
+	KEY_9,	KEY_I,	KEY_J,	KEY_M,	KEY_DOT,	0,	KEY_LEFT,	0,
+	KEY_0,	KEY_O,	KEY_K,	KEY_L,	0,	KEY_UP,	KEY_DOWN,	0,
+
+	KEY_BACKSPACE,	KEY_P,	0,	0,	KEY_ENTER,	0,	KEY_RIGHT,	0,
+	0,	0,	0,	0,	0,	0,	0,	0,
+	KEY_SWITCHVIDEOMODE,	0,	0,	KEY_SHIFT,	0,	0,	0,	0,
+	KEY_VOLUMEDOWN,	KEY_VOLUMEUP,	0,	0,	0,	KEY_ALT,	0,	0,
+};
+
+
+static unsigned char sha_pon_togglekey[] = {
+	KEY_SHIFT,
+	KEY_CTRL,
+	KEY_ALT, 
+	0	/* 0:end mark */
+};
+
+
+struct sha_ponkbd {
+	unsigned char keycode[KB_ROWS*KB_COLS];
+	struct input_polled_dev *poll_dev;
+	int kbdscan_state[KB_COLS];
+	int kbdscan_state2[KB_COLS];
+	int kbdscan_count[KB_COLS];
+	int kbdscan_togglekey[KB_COLS];
+	int kbdscan_toggle_state[KB_COLS];
+	int toggle_enable;
+	int toggle_sense;
+	int showkeys;
+};
+
+
+static void sha_pon_clear_toggle(struct sha_ponkbd *sha_ponkbd)
+{
+	unsigned int col, i;
+	
+	for( i=0; sha_pon_togglekey[i]!=0; i++ ) {
+		input_report_key(sha_ponkbd->poll_dev->input, sha_pon_togglekey[i], 0);
+	}
+	for (col = 0; col < KB_COLS; col++) {
+		sha_ponkbd->kbdscan_toggle_state[col] = 0;
+	}
+}
+
+
+/*
+ * Toggle-keys : Shift, Ctrl, Fn(Alt)
+ * 
+ * Pressing toggle-key, toggle mode ON/OFF.
+ * Pressing toggle-key continuous and pressing NOT-toggle-key, turn mode to OFF after input.
+ * 
+ * if toggle_sense == 0, stop toggle mode.
+ */
+static void sha_pon_report_col(struct sha_ponkbd *sha_ponkbd,
+				unsigned int col, unsigned int rowd, unsigned int rowd_changed, unsigned int toggle_pressed)
+{
+	unsigned int scancode, pressed, keycode;
+	unsigned int row, changed, togglekey;
+	
+	for (row = 0; row < KB_ROWS; row++) {
+		changed = rowd_changed & KB_ROWMASK(row);
+		pressed = rowd & KB_ROWMASK(row);
+		togglekey = sha_ponkbd->kbdscan_togglekey[col] & KB_ROWMASK(row);
+		
+		scancode = SCANCODE(row, col);
+		keycode = sha_ponkbd->keycode[scancode];
+		
+		if( sha_ponkbd->showkeys && changed && pressed ) {
+			printk( KERN_WARNING "(sha_ponkbd: keycode=%d/0x%04x, col=%d, row=%d)\n", 
+					keycode, keycode, col, row );
+		}
+
+		if( !sha_ponkbd->toggle_sense ) {
+			if( changed ) {
+				input_report_key(sha_ponkbd->poll_dev->input, keycode, pressed);
+			}
+			continue;
+		}
+
+		if(togglekey) {
+			if( changed && pressed ) {
+				input_report_key(sha_ponkbd->poll_dev->input, keycode, pressed);
+			}
+			if( changed && !pressed ) {
+				if( sha_ponkbd->toggle_enable ) {
+					sha_ponkbd->kbdscan_toggle_state[col] ^= KB_ROWMASK(row);
+					input_report_key(sha_ponkbd->poll_dev->input, keycode, 
+							sha_ponkbd->kbdscan_toggle_state[col]&KB_ROWMASK(row));
+				} else {
+					sha_pon_clear_toggle(sha_ponkbd);
+					sha_ponkbd->toggle_enable = 1;
+				}
+			}
+		} else {
+			if( changed || pressed ) {
+				input_report_key(sha_ponkbd->poll_dev->input, keycode, pressed);
+				if( toggle_pressed ) {
+					sha_ponkbd->toggle_enable = 0;
+				} else {
+					sha_pon_clear_toggle(sha_ponkbd);
+					sha_ponkbd->toggle_enable = 1;
+				}
+			}
+		}
+	}
+}
+
+
+static DEFINE_SPINLOCK(keyboard_lock);
+
+static inline void sha_pon_discharge(void)
+{
+	unsigned long	flags;
+	
+	spin_lock_irqsave(&keyboard_lock, flags);
+	
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_COL_L, 0x00);
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_COL_H, 0x00);
+	
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_DISCH, 0x01);
+	udelay(KB_ACTIVATE_DELAY);
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_DISCH, 0x00);
+	
+	spin_unlock_irqrestore(&keyboard_lock, flags);
+}
+
+
+static inline unsigned int sha_pon_scanline(unsigned int col)
+{
+	unsigned int rowd;
+	unsigned long	flags;
+	
+	spin_lock_irqsave(&keyboard_lock, flags);
+	
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_COL_L, (1<< col   )&0xff);
+	sha_pon_cpld_set(SHA_PON_W8_KEYMATRIX_COL_H, (1<<(col-8))&0xff);
+	
+	udelay(KB_ACTIVATE_DELAY);
+	rowd = sha_pon_cpld_get(SHA_PON_R8_KEYMATRIX_ROW) & 0xff;
+	
+	spin_unlock_irqrestore(&keyboard_lock, flags);
+	
+	return( rowd );
+}
+
+
+/* Scan the hardware keyboard and push any changes up through the input layer */
+static void sha_pon_poll(struct input_polled_dev *dev)
+{
+	struct sha_ponkbd *sha_ponkbd = dev->private;
+	unsigned int col, rowd;
+	unsigned int toggle_pressed;
+	
+	toggle_pressed = 0;
+	for (col = 0; col < KB_COLS; col++) {
+		toggle_pressed |= ( sha_ponkbd->kbdscan_state2[col] & sha_ponkbd->kbdscan_togglekey[col] );
+	}
+	
+	col = 0;
+	do {
+		sha_pon_discharge();
+		rowd = sha_pon_scanline(col);
+		
+		if (rowd != sha_ponkbd->kbdscan_state[col]) {
+			sha_ponkbd->kbdscan_count[col] = 0;
+			sha_ponkbd->kbdscan_state[col] = rowd;
+		} else if (++sha_ponkbd->kbdscan_count[col] >= KBDSCAN_STABLE_COUNT) {
+			sha_ponkbd->kbdscan_count[col] = KBDSCAN_STABLE_COUNT;
+			sha_pon_report_col(sha_ponkbd, col, rowd, rowd^sha_ponkbd->kbdscan_state2[col], toggle_pressed);
+			sha_ponkbd->kbdscan_state2[col] = rowd;
+			col++;
+		}
+	} while (col < KB_COLS);
+	
+	sha_pon_discharge();
+	input_sync(dev->input);
+}
+
+
+static ssize_t show_toggle(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sha_ponkbd *sha_ponkbd = dev_get_drvdata(dev);
+	
+	return sprintf( buf, "%d\n", sha_ponkbd->toggle_sense );
+}
+
+
+static ssize_t store_toggle(struct device *dev, struct device_attribute *attr, 
+			const char *buf, size_t count)
+{
+	struct sha_ponkbd *sha_ponkbd = dev_get_drvdata(dev);
+	int	flag, n;
+	
+	sha_pon_clear_toggle(sha_ponkbd);
+	
+	n = sscanf( buf, "%d", &flag );
+	if( n != 1 )
+		return -EINVAL;
+	
+	sha_ponkbd->toggle_sense = flag ? 1 : 0 ;
+	return strlen(buf);
+}
+
+
+static ssize_t show_showkeys(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct sha_ponkbd *sha_ponkbd = dev_get_drvdata(dev);
+	
+	return sprintf( buf, "%d\n", sha_ponkbd->showkeys );
+}
+
+
+static ssize_t store_showkeys(struct device *dev, struct device_attribute *attr, 
+			const char *buf, size_t count)
+{
+	struct sha_ponkbd *sha_ponkbd = dev_get_drvdata(dev);
+	int	flag, n;
+	
+	n = sscanf( buf, "%d", &flag );
+	if( n != 1 )
+		return -EINVAL;
+	
+	sha_ponkbd->showkeys = flag ? 1 : 0 ;
+	return strlen(buf);
+}
+
+
+static DEVICE_ATTR( toggle, S_IRUGO|S_IWUSR, show_toggle, store_toggle);
+static DEVICE_ATTR( showkeys, S_IRUGO|S_IWUSR, show_showkeys, store_showkeys);
+
+
+static int __devinit sha_pon_probe(struct platform_device *pdev)
+{
+	struct sha_ponkbd *sha_ponkbd;
+	struct input_dev *input_dev;
+	struct input_polled_dev *poll_dev;
+	int i;
+	int error;
+	unsigned int row, col, scancode;
+	
+	sha_ponkbd = kzalloc(sizeof(struct sha_ponkbd), GFP_KERNEL);
+	poll_dev = input_allocate_polled_device();
+	if (!sha_ponkbd || !poll_dev) {
+		error = -ENOMEM;
+		goto fail;
+	}
+	
+	sha_ponkbd->poll_dev = poll_dev;
+	platform_set_drvdata(pdev, sha_ponkbd);
+	
+	poll_dev->private = sha_ponkbd;
+	poll_dev->poll = sha_pon_poll;
+	poll_dev->poll_interval = SCAN_INTERVAL;
+
+	input_dev = poll_dev->input;
+	input_dev->name = "Sha_pon Keyboard";
+	input_dev->phys = "sha_ponkbd/input0";
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0001;
+	input_dev->id.version = 0x0100;
+	input_dev->dev.parent = &pdev->dev;
+
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+	input_dev->keycode = sha_ponkbd->keycode;
+	input_dev->keycodesize = sizeof(sha_ponkbd->keycode[0]);
+	input_dev->keycodemax = ARRAY_SIZE(sha_ponkbd->keycode);
+
+	for( col=0; col<KB_COLS; col++ ) {
+		sha_ponkbd->kbdscan_state[col] = 0;
+		sha_ponkbd->kbdscan_state2[col] = 0;
+		sha_ponkbd->kbdscan_count[col] = 0;
+		sha_ponkbd->kbdscan_togglekey[col] = 0;
+		sha_ponkbd->kbdscan_toggle_state[col] = 0;
+	}
+	sha_ponkbd->toggle_enable = 1;
+	for (col = 0; col < KB_COLS; col++) {
+		for (row = 0; row < KB_ROWS; row++) {
+			scancode = SCANCODE(row, col);
+			sha_ponkbd->keycode[scancode] = sha_pon_keycode[scancode];
+			if( sha_ponkbd->keycode[scancode] ) {
+				set_bit(sha_ponkbd->keycode[scancode], input_dev->keybit);
+			}
+			for( i=0; sha_pon_togglekey[i]!=0; i++ ) {
+				if( sha_ponkbd->keycode[scancode] == sha_pon_togglekey[i] ) {
+					sha_ponkbd->kbdscan_togglekey[col] |= KB_ROWMASK(row);
+				}
+			}
+		}
+	}
+	sha_ponkbd->toggle_sense = 0;
+	sha_ponkbd->showkeys = 0;
+	
+	error = input_register_polled_device(sha_ponkbd->poll_dev);
+	if (error)
+		goto fail;
+
+	error = device_create_file(&pdev->dev, &dev_attr_toggle);
+	if (error)
+		goto fail_1;
+
+	error = device_create_file(&pdev->dev, &dev_attr_showkeys);
+	if (error)
+		goto fail_2;
+
+	return 0;
+
+ fail_2:
+	device_remove_file(&pdev->dev, &dev_attr_toggle);
+ fail_1:
+	input_unregister_polled_device(sha_ponkbd->poll_dev);
+ fail:
+	input_free_polled_device(poll_dev);
+ 	kfree(sha_ponkbd);
+	return error;
+}
+
+static int __devexit sha_pon_remove(struct platform_device *pdev)
+{
+	struct sha_ponkbd *sha_ponkbd = platform_get_drvdata(pdev);
+
+	device_remove_file(&pdev->dev, &dev_attr_showkeys);
+	device_remove_file(&pdev->dev, &dev_attr_toggle);
+
+	input_unregister_polled_device(sha_ponkbd->poll_dev);
+	input_free_polled_device(sha_ponkbd->poll_dev);
+	kfree(sha_ponkbd);
+
+	return 0;
+}
+
+static struct platform_driver sha_pon_driver = {
+	.probe		= sha_pon_probe,
+	.remove		= __devexit_p(sha_pon_remove),
+	.driver		= {
+		.name	= "sha_pon-keyboard",
+	},
+};
+
+static int __init sha_pon_init(void)
+{
+	return platform_driver_register(&sha_pon_driver);
+}
+
+static void __exit sha_pon_exit(void)
+{
+	platform_driver_unregister(&sha_pon_driver);
+}
+
+module_init(sha_pon_init);
+module_exit(sha_pon_exit);
+
+MODULE_AUTHOR("zaki");
+MODULE_DESCRIPTION("Sha_pon Keyboard Driver");
+MODULE_LICENSE("GPLv2");
-- 
1.4.4.4

