Index: drivers/input/keyboard/qci_kbd.c |
diff --git a/drivers/input/keyboard/qci_kbd.c b/drivers/input/keyboard/qci_kbd.c |
new file mode 100755 |
index 0000000000000000000000000000000000000000..9c228459e53f1ffb32010e00aa88388357c1217b |
--- /dev/null |
+++ b/drivers/input/keyboard/qci_kbd.c |
@@ -0,0 +1,549 @@ |
+/* Quanta I2C Keyboard Driver |
+ * |
+ * Copyright (C) 2009 Quanta Computer Inc. |
+ * Author: Hsin Wu <hsin.wu@quantatw.com> |
+ * Author: Austin Lai <austin.lai@quantatw.com> |
+ * |
+ * This software is licensed under the terms of the GNU General Public |
+ * License version 2, as published by the Free Software Foundation, and |
+ * may be copied, distributed, and modified under those terms. |
+ * |
+ * 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. |
+ * |
+ */ |
+ |
+ /* |
+ * |
+ * The Driver with I/O communications via the I2C Interface for ON2 of AP BU. |
+ * And it is only working on the nuvoTon WPCE775x Embedded Controller. |
+ * |
+ */ |
+ |
+#include <linux/kernel.h> |
+#include <linux/init.h> |
+#include <linux/module.h> |
+#include <linux/slab.h> |
+#include <linux/jiffies.h> |
+#include <linux/i2c.h> |
+#include <linux/mutex.h> |
+#include <linux/interrupt.h> |
+#include <linux/input.h> |
+#include <linux/keyboard.h> |
+#include <linux/gpio.h> |
+#include <linux/delay.h> |
+ |
+/* Keyboard special scancode */ |
+#define RC_KEY_FN 0x70 |
+#define RC_KEY_BREAK 0x80 |
+#define KEY_ACK_FA 0xFA |
+ |
+/* Keyboard keycodes */ |
+#define NOKEY KEY_RESERVED |
+#define KEY_LEFTWIN KEY_LEFTMETA |
+#define KEY_RIGHTWIN KEY_RIGHTMETA |
+#define KEY_APPS KEY_COMPOSE |
+#define KEY_PRINTSCR KEY_SYSRQ |
+ |
+#define KEYBOARD_ID_NAME "qci-i2ckbd" |
+#define KEYBOARD_NAME "Quanta Keyboard" |
+#define KEYBOARD_DEVICE "/i2c/input0" |
+#define KEYBOARD_CMD_ENABLE 0xF4 |
+ |
+/*----------------------------------------------------------------------------- |
+ * Keyboard scancode to linux keycode translation table |
+ *---------------------------------------------------------------------------*/ |
+ |
+static const unsigned char on2_keycode[256] = { |
+ [0] = NOKEY, |
+ [1] = NOKEY, |
+ [2] = NOKEY, |
+ [3] = KEY_5, |
+ [4] = KEY_7, |
+ [5] = KEY_9, |
+ [6] = KEY_MINUS, |
+ [7] = NOKEY, |
+ [8] = NOKEY, |
+ [9] = NOKEY, |
+ [10] = NOKEY, |
+ [11] = KEY_LEFTBRACE, |
+ [12] = KEY_F10, |
+ [13] = KEY_INSERT, |
+ [14] = KEY_F11, |
+ [15] = KEY_ESC, |
+ [16] = NOKEY, |
+ [17] = NOKEY, |
+ [18] = NOKEY, |
+ [19] = KEY_4, |
+ [20] = KEY_6, |
+ [21] = KEY_8, |
+ [22] = KEY_0, |
+ [23] = KEY_EQUAL, |
+ [24] = NOKEY, |
+ [25] = NOKEY, |
+ [26] = NOKEY, |
+ [27] = KEY_P, |
+ [28] = KEY_F9, |
+ [29] = KEY_DELETE, |
+ [30] = KEY_F12, |
+ [31] = KEY_GRAVE, |
+ [32] = KEY_W, |
+ [33] = NOKEY, |
+ [34] = NOKEY, |
+ [35] = KEY_R, |
+ [36] = KEY_T, |
+ [37] = KEY_U, |
+ [38] = KEY_O, |
+ [39] = KEY_RIGHTBRACE, |
+ [40] = NOKEY, |
+ [41] = NOKEY, |
+ [42] = NOKEY, |
+ [43] = KEY_APOSTROPHE, |
+ [44] = KEY_BACKSPACE, |
+ [45] = NOKEY, |
+ [46] = KEY_F8, |
+ [47] = KEY_F5, |
+ [48] = KEY_S, |
+ [49] = NOKEY, |
+ [50] = NOKEY, |
+ [51] = KEY_E, |
+ [52] = KEY_H, |
+ [53] = KEY_Y, |
+ [54] = KEY_I, |
+ [55] = KEY_ENTER, |
+ [56] = NOKEY, |
+ [57] = NOKEY, |
+ [58] = NOKEY, |
+ [59] = KEY_SEMICOLON, |
+ [60] = KEY_3, |
+ [61] = KEY_PAGEUP, |
+ [62] = KEY_Q, |
+ [63] = KEY_TAB, |
+ [64] = KEY_A, |
+ [65] = NOKEY, |
+ [66] = NOKEY, |
+ [67] = KEY_F, |
+ [68] = KEY_G, |
+ [69] = KEY_J, |
+ [70] = KEY_L, |
+ [71] = NOKEY, |
+ [72] = KEY_RIGHTSHIFT, |
+ [73] = NOKEY, |
+ [74] = NOKEY, |
+ [75] = KEY_SLASH, |
+ [76] = KEY_2, |
+ [77] = KEY_PAGEDOWN, |
+ [78] = KEY_F4, |
+ [79] = KEY_F1, |
+ [80] = KEY_Z, |
+ [81] = NOKEY, |
+ [82] = NOKEY, |
+ [83] = KEY_D, |
+ [84] = KEY_V, |
+ [85] = KEY_N, |
+ [86] = KEY_K, |
+ [87] = NOKEY, |
+ [88] = KEY_LEFTSHIFT, |
+ [89] = KEY_RIGHTCTRL, |
+ [90] = NOKEY, |
+ [91] = KEY_DOT, |
+ [92] = KEY_UP, |
+ [93] = KEY_RIGHT, |
+ [94] = KEY_F3, |
+ [95] = KEY_F2, |
+ [96] = NOKEY, |
+ [97] = NOKEY, |
+ [98] = KEY_RIGHTALT, |
+ [99] = KEY_X, |
+ [100] = KEY_C, |
+ [101] = KEY_B, |
+ [102] = KEY_COMMA, |
+ [103] = NOKEY, |
+ [104] = NOKEY, |
+ [105] = NOKEY, |
+ [106] = NOKEY, |
+ [107] = NOKEY, |
+ [108] = KEY_PRINTSCR, |
+ [109] = KEY_DOWN, |
+ [110] = KEY_1, |
+ [111] = KEY_CAPSLOCK, |
+ [112] = KEY_F24, |
+ [113] = KEY_HOME, |
+ [114] = KEY_LEFTALT, |
+ [115] = NOKEY, |
+ [116] = KEY_SPACE, |
+ [117] = KEY_BACKSLASH, |
+ [118] = KEY_M, |
+ [119] = KEY_COMPOSE, |
+ [120] = NOKEY, |
+ [121] = KEY_LEFTCTRL, |
+ [122] = NOKEY, |
+ [123] = NOKEY, |
+ [124] = KEY_PAUSE, |
+ [125] = KEY_LEFT, |
+ [126] = KEY_F7, |
+ [127] = KEY_F6, |
+ [128] = NOKEY, |
+ [129] = NOKEY, |
+ [130] = NOKEY, |
+ [131] = NOKEY, |
+ [132] = NOKEY, |
+ [133] = NOKEY, |
+ [134] = NOKEY, |
+ [135] = NOKEY, |
+ [136] = NOKEY, |
+ [137] = NOKEY, |
+ [138] = NOKEY, |
+ [139] = NOKEY, |
+ [140] = NOKEY, |
+ [141] = NOKEY, |
+ [142] = NOKEY, |
+ [143] = NOKEY, |
+ [144] = NOKEY, |
+ [145] = NOKEY, |
+ [146] = NOKEY, |
+ [147] = NOKEY, |
+ [148] = NOKEY, |
+ [149] = NOKEY, |
+ [150] = NOKEY, |
+ [151] = NOKEY, |
+ [152] = NOKEY, |
+ [153] = NOKEY, |
+ [154] = NOKEY, |
+ [155] = NOKEY, |
+ [156] = NOKEY, |
+ [157] = NOKEY, |
+ [158] = NOKEY, |
+ [159] = NOKEY, |
+ [160] = NOKEY, |
+ [161] = NOKEY, |
+ [162] = NOKEY, |
+ [163] = NOKEY, |
+ [164] = NOKEY, |
+ [165] = NOKEY, |
+ [166] = NOKEY, |
+ [167] = NOKEY, |
+ [168] = NOKEY, |
+ [169] = NOKEY, |
+ [170] = NOKEY, |
+ [171] = NOKEY, |
+ [172] = NOKEY, |
+ [173] = NOKEY, |
+ [174] = NOKEY, |
+ [175] = NOKEY, |
+ [176] = NOKEY, |
+ [177] = NOKEY, |
+ [178] = NOKEY, |
+ [179] = NOKEY, |
+ [180] = NOKEY, |
+ [181] = NOKEY, |
+ [182] = NOKEY, |
+ [183] = NOKEY, |
+ [184] = NOKEY, |
+ [185] = NOKEY, |
+ [186] = NOKEY, |
+ [187] = NOKEY, |
+ [188] = NOKEY, |
+ [189] = KEY_HOME, |
+ [190] = NOKEY, |
+ [191] = NOKEY, |
+ [192] = NOKEY, |
+ [193] = NOKEY, |
+ [194] = NOKEY, |
+ [195] = NOKEY, |
+ [196] = NOKEY, |
+ [197] = NOKEY, |
+ [198] = NOKEY, |
+ [199] = NOKEY, |
+ [200] = NOKEY, |
+ [201] = NOKEY, |
+ [202] = NOKEY, |
+ [203] = NOKEY, |
+ [204] = NOKEY, |
+ [205] = KEY_END, |
+ [206] = NOKEY, |
+ [207] = NOKEY, |
+ [208] = NOKEY, |
+ [209] = NOKEY, |
+ [210] = NOKEY, |
+ [211] = NOKEY, |
+ [212] = NOKEY, |
+ [213] = NOKEY, |
+ [214] = NOKEY, |
+ [215] = NOKEY, |
+ [216] = NOKEY, |
+ [217] = NOKEY, |
+ [218] = NOKEY, |
+ [219] = NOKEY, |
+ [220] = KEY_VOLUMEUP, |
+ [221] = KEY_BRIGHTNESSUP, |
+ [222] = NOKEY, |
+ [223] = NOKEY, |
+ [224] = NOKEY, |
+ [225] = NOKEY, |
+ [226] = NOKEY, |
+ [227] = NOKEY, |
+ [228] = NOKEY, |
+ [229] = NOKEY, |
+ [230] = NOKEY, |
+ [231] = NOKEY, |
+ [232] = NOKEY, |
+ [233] = NOKEY, |
+ [234] = NOKEY, |
+ [235] = NOKEY, |
+ [236] = NOKEY, |
+ [237] = KEY_VOLUMEDOWN, |
+ [238] = NOKEY, |
+ [239] = NOKEY, |
+ [240] = NOKEY, |
+ [241] = NOKEY, |
+ [242] = NOKEY, |
+ [243] = NOKEY, |
+ [244] = NOKEY, |
+ [245] = NOKEY, |
+ [246] = NOKEY, |
+ [247] = NOKEY, |
+ [248] = NOKEY, |
+ [249] = NOKEY, |
+ [250] = NOKEY, |
+ [251] = NOKEY, |
+ [252] = NOKEY, |
+ [253] = KEY_BRIGHTNESSDOWN, |
+ [254] = NOKEY, |
+ [255] = NOKEY, |
+}; |
+/*----------------------------------------------------------------------------- |
+ * Global variables |
+ *---------------------------------------------------------------------------*/ |
+ |
+struct input_dev *g_qci_keyboard_dev; |
+ |
+/* General structure to hold the driver data */ |
+struct i2ckbd_drv_data { |
+ struct i2c_client *ki2c_client; |
+ struct work_struct work; |
+ struct input_dev *qcikbd_dev; |
+ unsigned int qcikbd_gpio; /* GPIO used for interrupt */ |
+ unsigned int qcikbd_irq; |
+ unsigned int key_down; |
+ unsigned int escape; |
+ unsigned int pause_seq; |
+ unsigned int fn; |
+}; |
+#ifdef CONFIG_PM |
+static int qcikbd_suspend(struct device *dev) |
+{ |
+ return 0; |
+} |
+ |
+static int qcikbd_resume(struct device *dev) |
+{ |
+ return 0; |
+} |
+#endif |
+static int __devinit qcikbd_probe(struct i2c_client *client, |
+ const struct i2c_device_id *id); |
+static int __devexit qcikbd_remove(struct i2c_client *kbd); |
+ |
+static const struct i2c_device_id qcikbd_idtable[] = { |
+ { KEYBOARD_ID_NAME, 0 }, |
+ { } |
+}; |
+ |
+MODULE_DEVICE_TABLE(i2c, qcikbd_idtable); |
+ |
+#ifdef CONFIG_PM |
+static struct dev_pm_ops qcikbd_pm_ops = { |
+ .suspend = qcikbd_suspend, |
+ .resume = qcikbd_resume, |
+}; |
+#endif |
+static struct i2c_driver i2ckbd_driver = { |
+ .driver = { |
+ .owner = THIS_MODULE, |
+ .name = KEYBOARD_ID_NAME, |
+#ifdef CONFIG_PM |
+ .pm = &qcikbd_pm_ops, |
+#endif |
+ }, |
+ .probe = qcikbd_probe, |
+ .remove = __devexit_p(qcikbd_remove), |
+ .id_table = qcikbd_idtable, |
+}; |
+ |
+/*----------------------------------------------------------------------------- |
+ * Driver functions |
+ *---------------------------------------------------------------------------*/ |
+ |
+static irqreturn_t qcikbd_interrupt(int irq, void *dev_id) |
+{ |
+ struct i2ckbd_drv_data *ikbd_drv_data = dev_id; |
+ schedule_work(&ikbd_drv_data->work); |
+ return IRQ_HANDLED; |
+} |
+ |
+static void qcikbd_work_handler(struct work_struct *_work) |
+{ |
+ unsigned char scancode; |
+ unsigned int keycode; |
+ |
+ struct i2ckbd_drv_data *ikbd_drv_data = |
+ container_of(_work, struct i2ckbd_drv_data, work); |
+ |
+ struct i2c_client *ikbdclient = ikbd_drv_data->ki2c_client; |
+ struct input_dev *ikbdev = ikbd_drv_data->qcikbd_dev; |
+ |
+ scancode = i2c_smbus_read_byte(ikbdclient); |
+ |
+ if (scancode == KEY_ACK_FA) { |
+ return; |
+ } else if (scancode == RC_KEY_FN) { |
+ ikbd_drv_data->fn = 0x80; /* select keycode table > 0x7F */ |
+ } else { |
+ ikbd_drv_data->key_down = 1; |
+ if (scancode & RC_KEY_BREAK) { |
+ ikbd_drv_data->key_down = 0; |
+ if ((scancode & 0x7F) == RC_KEY_FN) |
+ ikbd_drv_data->fn = 0; |
+ } |
+ keycode = on2_keycode[(scancode & 0x7F) | ikbd_drv_data->fn]; |
+ if (keycode != NOKEY) { |
+ input_report_key(ikbdev, |
+ keycode, |
+ ikbd_drv_data->key_down); |
+ input_sync(ikbdev); |
+ } |
+ } |
+} |
+ |
+ |
+static int qcikbd_open(struct input_dev *dev) |
+{ |
+ struct i2ckbd_drv_data *ikbd_drv_data = input_get_drvdata(dev); |
+ struct i2c_client *ikbdclient = ikbd_drv_data->ki2c_client; |
+ |
+ /* Send F4h - enable keyboard */ |
+ i2c_smbus_write_byte(ikbdclient, KEYBOARD_CMD_ENABLE); |
+ return 0; |
+} |
+ |
+static int __devinit qcikbd_probe(struct i2c_client *client, |
+ const struct i2c_device_id *id) |
+{ |
+ int err; |
+ int i; |
+ struct i2ckbd_drv_data *context; |
+ context = kzalloc(sizeof(struct i2ckbd_drv_data), GFP_KERNEL); |
+ if (!context) |
+ return -ENOMEM; |
+ i2c_set_clientdata(client, context); |
+ context->ki2c_client = client; |
+ context->qcikbd_gpio = client->irq; |
+ client->driver = &i2ckbd_driver; |
+ |
+ INIT_WORK(&context->work, qcikbd_work_handler); |
+ |
+ err = gpio_request(context->qcikbd_gpio, "qci-kbd"); |
+ if (err) { |
+ pr_err("[KBD] err gpio request\n"); |
+ goto gpio_request_fail; |
+ } |
+ |
+ context->qcikbd_irq = gpio_to_irq(context->qcikbd_gpio); |
+ err = request_irq(context->qcikbd_irq, |
+ qcikbd_interrupt, |
+ IRQF_TRIGGER_FALLING, |
+ KEYBOARD_ID_NAME, |
+ context); |
+ if (err) { |
+ pr_err("[KBD] err unable to get IRQ\n"); |
+ goto request_irq_fail; |
+ } |
+ |
+ context->qcikbd_dev = input_allocate_device(); |
+ if (!context->qcikbd_dev) { |
+ pr_err("[KBD]allocting memory err\n"); |
+ err = -ENOMEM; |
+ goto allocate_fail; |
+ } |
+ |
+ context->qcikbd_dev->name = KEYBOARD_NAME; |
+ context->qcikbd_dev->phys = KEYBOARD_DEVICE; |
+ context->qcikbd_dev->id.bustype = BUS_I2C; |
+ context->qcikbd_dev->id.vendor = 0x1050; |
+ context->qcikbd_dev->id.product = 0x0006; |
+ context->qcikbd_dev->id.version = 0x0004; |
+ context->qcikbd_dev->open = qcikbd_open; |
+ context->qcikbd_dev->evbit[0] = BIT_MASK(EV_KEY); |
+ |
+ /* Enable all supported keys */ |
+ for (i = 1; i < ARRAY_SIZE(on2_keycode) ; i++) |
+ set_bit(on2_keycode[i], context->qcikbd_dev->keybit); |
+ |
+ set_bit(KEY_POWER, context->qcikbd_dev->keybit); |
+ set_bit(KEY_END, context->qcikbd_dev->keybit); |
+ set_bit(KEY_VOLUMEUP, context->qcikbd_dev->keybit); |
+ set_bit(KEY_VOLUMEDOWN, context->qcikbd_dev->keybit); |
+ set_bit(KEY_ZOOMIN, context->qcikbd_dev->keybit); |
+ set_bit(KEY_ZOOMOUT, context->qcikbd_dev->keybit); |
+ |
+ input_set_drvdata(context->qcikbd_dev, context); |
+ err = input_register_device(context->qcikbd_dev); |
+ if (err) { |
+ pr_err("[KBD] err input register device\n"); |
+ goto register_fail; |
+ } |
+ g_qci_keyboard_dev = context->qcikbd_dev; |
+ return 0; |
+register_fail: |
+ input_free_device(context->qcikbd_dev); |
+ |
+allocate_fail: |
+ free_irq(context->qcikbd_irq, context); |
+ |
+request_irq_fail: |
+ gpio_free(context->qcikbd_gpio); |
+ |
+gpio_request_fail: |
+ i2c_set_clientdata(client, NULL); |
+ kfree(context); |
+ return err; |
+} |
+ |
+static int __devexit qcikbd_remove(struct i2c_client *dev) |
+{ |
+ struct i2ckbd_drv_data *context = i2c_get_clientdata(dev); |
+ |
+ free_irq(context->qcikbd_irq, context); |
+ gpio_free(context->qcikbd_gpio); |
+ input_free_device(context->qcikbd_dev); |
+ input_unregister_device(context->qcikbd_dev); |
+ kfree(context); |
+ |
+ return 0; |
+} |
+ |
+static int __init qcikbd_init(void) |
+{ |
+ return i2c_add_driver(&i2ckbd_driver); |
+} |
+ |
+static void __exit qcikbd_exit(void) |
+{ |
+ i2c_del_driver(&i2ckbd_driver); |
+} |
+ |
+struct input_dev *nkbc_keypad_get_input_dev(void) |
+{ |
+ return g_qci_keyboard_dev; |
+} |
+EXPORT_SYMBOL(nkbc_keypad_get_input_dev); |
+module_init(qcikbd_init); |
+module_exit(qcikbd_exit); |
+ |
+MODULE_AUTHOR("Quanta Computer Inc."); |
+MODULE_DESCRIPTION("Quanta Embedded Controller I2C Keyboard Driver"); |
+MODULE_LICENSE("GPL v2"); |
+ |