/* Quanta I2C Keyboard Driver * * Copyright (C) 2009 Quanta Computer Inc. * Copyright (c) 2010, The Linux Foundation. All rights reserved. * Author: Hsin Wu * Author: Austin Lai * * 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 #include #include #include #include #include #include #include #include #include #include #include #include /* Keyboard special scancode */ #define RC_KEY_FN 0x70 #define RC_KEY_BREAK 0x80 #define KEY_ACK_FA 0xFA #define SCAN_EMUL0 0xE0 #define SCAN_EMUL1 0xE1 #define SCAN_PAUSE1 0x1D #define SCAN_PAUSE2 0x45 #define SCAN_LIDSW_OPEN 0x70 #define SCAN_LIDSW_CLOSE 0x71 /* 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 #define KEYBOARD_CMD_SET_LED 0xED /*----------------------------------------------------------------------------- * 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, }; static const u8 emul0_map[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 0, 0, 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 115, 0, 0, 0, 0, 98, 0, 99, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 103, 104, 0, 105, 0, 106, 0, 107, 108, 109, 110, 111, 0, 0, 0, 0, 0, 0, 0, 139, 0, 150, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; /*----------------------------------------------------------------------------- * 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; struct mutex kb_mutex; 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; unsigned char led_status; bool standard_scancodes; bool kb_leds; bool event_led; bool emul0; bool emul1; bool pause1; }; #ifdef CONFIG_PM static int qcikbd_suspend(struct device *dev) { struct i2ckbd_drv_data *context = input_get_drvdata(g_qci_keyboard_dev); enable_irq_wake(context->qcikbd_irq); return 0; } static int qcikbd_resume(struct device *dev) { struct i2ckbd_drv_data *context = input_get_drvdata(g_qci_keyboard_dev); struct i2c_client *ikbdclient = context->ki2c_client; disable_irq_wake(context->qcikbd_irq); /* consume any keypress generated while suspended */ i2c_smbus_read_byte(ikbdclient); 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 *---------------------------------------------------------------------------*/ #ifdef CONFIG_KEYBOARD_QCIKBD_LID static void process_lid(struct input_dev *ikbdev, unsigned char scancode) { if (scancode == SCAN_LIDSW_OPEN) input_report_switch(ikbdev, SW_LID, 0); else if (scancode == SCAN_LIDSW_CLOSE) input_report_switch(ikbdev, SW_LID, 1); else return; input_sync(ikbdev); } #endif 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 char scancode_only; 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; mutex_lock(&ikbd_drv_data->kb_mutex); if ((ikbd_drv_data->kb_leds) && (ikbd_drv_data->event_led)) { i2c_smbus_write_byte(ikbdclient, KEYBOARD_CMD_SET_LED); i2c_smbus_write_byte(ikbdclient, ikbd_drv_data->led_status); ikbd_drv_data->event_led = 0; goto work_exit; } scancode = i2c_smbus_read_byte(ikbdclient); if (scancode == KEY_ACK_FA) goto work_exit; if (ikbd_drv_data->standard_scancodes) { /* pause key is E1 1D 45 */ if (scancode == SCAN_EMUL1) { ikbd_drv_data->emul1 = 1; goto work_exit; } if (ikbd_drv_data->emul1) { ikbd_drv_data->emul1 = 0; if ((scancode & 0x7f) == SCAN_PAUSE1) ikbd_drv_data->pause1 = 1; goto work_exit; } if (ikbd_drv_data->pause1) { ikbd_drv_data->pause1 = 0; if ((scancode & 0x7f) == SCAN_PAUSE2) { input_report_key(ikbdev, KEY_PAUSE, !(scancode & 0x80)); input_sync(ikbdev); } goto work_exit; } if (scancode == SCAN_EMUL0) { ikbd_drv_data->emul0 = 1; goto work_exit; } if (ikbd_drv_data->emul0) { ikbd_drv_data->emul0 = 0; scancode_only = scancode & 0x7f; #ifdef CONFIG_KEYBOARD_QCIKBD_LID if ((scancode_only == SCAN_LIDSW_OPEN) || (scancode_only == SCAN_LIDSW_CLOSE)) { process_lid(ikbdev, scancode); goto work_exit; } #endif keycode = emul0_map[scancode_only]; if (!keycode) { dev_err(&ikbdev->dev, "Unrecognized scancode %02x %02x\n", SCAN_EMUL0, scancode); goto work_exit; } } else { keycode = scancode & 0x7f; } /* MS bit of scancode indicates direction of keypress */ ikbd_drv_data->key_down = !(scancode & 0x80); if (keycode) { input_event(ikbdev, EV_MSC, MSC_SCAN, scancode); input_report_key(ikbdev, keycode, ikbd_drv_data->key_down); input_sync(ikbdev); } goto work_exit; } mutex_unlock(&ikbd_drv_data->kb_mutex); 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); } } return; work_exit: mutex_unlock(&ikbd_drv_data->kb_mutex); } static int qcikbd_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct i2ckbd_drv_data *ikbd_drv_data = input_get_drvdata(dev); struct input_dev *ikbdev = ikbd_drv_data->qcikbd_dev; if (type != EV_LED) return -EINVAL; ikbd_drv_data->led_status = (test_bit(LED_SCROLLL, ikbdev->led) ? 1 : 0) | (test_bit(LED_NUML, ikbdev->led) ? 2 : 0) | (test_bit(LED_CAPSL, ikbdev->led) ? 4 : 0); ikbd_drv_data->event_led = 1; schedule_work(&ikbd_drv_data->work); return 0; } 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; struct qci_kbd_platform_data *pdata = client->dev.platform_data; if (!pdata) { pr_err("[KBD] platform data not supplied\n"); return -EINVAL; } 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); mutex_init(&context->kb_mutex); 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->standard_scancodes = pdata->standard_scancodes; context->kb_leds = pdata->kb_leds; 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; set_bit(EV_KEY, context->qcikbd_dev->evbit); __set_bit(MSC_SCAN, context->qcikbd_dev->mscbit); if (pdata->repeat) set_bit(EV_REP, context->qcikbd_dev->evbit); /* 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); #ifdef CONFIG_KEYBOARD_QCIKBD_LID set_bit(EV_SW, context->qcikbd_dev->evbit); set_bit(SW_LID, context->qcikbd_dev->swbit); #endif if (context->kb_leds) { context->qcikbd_dev->event = qcikbd_input_event; __set_bit(EV_LED, context->qcikbd_dev->evbit); __set_bit(LED_NUML, context->qcikbd_dev->ledbit); __set_bit(LED_CAPSL, context->qcikbd_dev->ledbit); __set_bit(LED_SCROLLL, context->qcikbd_dev->ledbit); } 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");