From: Yoshinori Sato Date: Thu, 11 Feb 2016 17:27:27 +0000 (+0900) Subject: x68k: Add keyboard driver. X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=d43333b7707cf4aa28dddd90895d1222a665c92c;p=uclinux-h8%2Flinux.git x68k: Add keyboard driver. Signed-off-by: Yoshinori Sato --- diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 8911bc2ec42a..14bd45e2e307 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -775,4 +775,13 @@ config KEYBOARD_MTK_PMIC To compile this driver as a module, choose M here: the module will be called pmic-keys. +config KEYBOARD_X68000 + tristate "X68000 keyboard" + default y + depends on X68000 + select SERIO + + help + Say Y here if you want to use a X68000 keyboard. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 9510325c0c5d..e9cd818acffe 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -68,3 +68,4 @@ obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o obj-$(CONFIG_KEYBOARD_TM2_TOUCHKEY) += tm2-touchkey.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o +obj-$(CONFIG_KEYBOARD_X68000) += x68kkbd.o diff --git a/drivers/input/keyboard/x68kkbd.c b/drivers/input/keyboard/x68kkbd.c new file mode 100644 index 000000000000..8ed30a60ad44 --- /dev/null +++ b/drivers/input/keyboard/x68kkbd.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2016 Yoshinori Sato + * + * Based on amikbd.c + */ + +/* + * X68000 keyboard driver for Linux/m68k + */ + +/* + * This program 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 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to , or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define X68KKBD_LED_EVENT_BIT 0 +#define X68KKBD_REP_EVENT_BIT 1 + +#define DRIVER_DESC "X68000 keyboard driver" + +MODULE_AUTHOR("Yoshinori Sato "); +MODULE_DESCRIPTION("X68000 keyboard driver"); +MODULE_LICENSE("GPL"); + +static const unsigned char x68kkbd_keycode[0x73] = { + [1] = KEY_ESC, + [2] = KEY_1, + [3] = KEY_2, + [4] = KEY_3, + [5] = KEY_4, + [6] = KEY_5, + [7] = KEY_6, + [8] = KEY_7, + [9] = KEY_8, + [10] = KEY_9, + [11] = KEY_0, + [12] = KEY_MINUS, + [13] = KEY_GRAVE, + [14] = KEY_BACKSLASH, + [15] = KEY_BACKSPACE, + [16] = KEY_TAB, + [17] = KEY_Q, + [18] = KEY_W, + [19] = KEY_E, + [20] = KEY_R, + [21] = KEY_T, + [22] = KEY_Y, + [23] = KEY_U, + [24] = KEY_I, + [25] = KEY_O, + [26] = KEY_P, + [27] = KEY_LEFTBRACE, + [28] = KEY_RIGHTBRACE, + [29] = KEY_ENTER, + [30] = KEY_A, + [31] = KEY_S, + [32] = KEY_D, + [33] = KEY_F, + [34] = KEY_G, + [35] = KEY_H, + [36] = KEY_J, + [37] = KEY_K, + [38] = KEY_L, + [39] = KEY_SEMICOLON, + [40] = KEY_APOSTROPHE, + [42] = KEY_Z, + [43] = KEY_X, + [44] = KEY_C, + [45] = KEY_V, + [46] = KEY_B, + [47] = KEY_N, + [48] = KEY_M, + [49] = KEY_COMMA, + [50] = KEY_DOT, + [51] = KEY_SLASH, + [53] = KEY_SPACE, + [54] = KEY_HOME, + [55] = KEY_DELETE, + [56] = KEY_PAGEDOWN, + [57] = KEY_PAGEUP, + [58] = KEY_END, + [59] = KEY_RIGHT, + [60] = KEY_UP, + [61] = KEY_RIGHT, + [62] = KEY_DOWN, + [64] = KEY_KPSLASH, + [65] = KEY_KPASTERISK, + [66] = KEY_KPMINUS, + [67] = KEY_KP7, + [68] = KEY_KP8, + [69] = KEY_KP9, + [70] = KEY_KPPLUS, + [71] = KEY_KP4, + [72] = KEY_KP5, + [73] = KEY_KP6, + [74] = KEY_KPEQUAL, + [75] = KEY_KP1, + [76] = KEY_KP2, + [77] = KEY_KP3, + [78] = KEY_KPENTER, + [79] = KEY_KP0, + [80] = KEY_KPCOMMA, + [81] = KEY_KPDOT, + [86] = KEY_LEFTALT, + [87] = KEY_RIGHTALT, + [90] = KEY_F11, + [91] = KEY_F12, + [94] = KEY_INSERT, + [97] = KEY_PAUSE, + [98] = KEY_SYSRQ, + [99] = KEY_F1, + [100] = KEY_F2, + [101] = KEY_F3, + [102] = KEY_F4, + [103] = KEY_F5, + [104] = KEY_F6, + [105] = KEY_F7, + [106] = KEY_F8, + [107] = KEY_F9, + [108] = KEY_F10, + [112] = KEY_LEFTSHIFT, + [113] = KEY_LEFTCTRL, +}; + +struct x68kkbd { + struct serio *serio; + struct input_dev *dev; + + /* Written only during init */ + char name[64]; + char phys[32]; + + unsigned short id; + DECLARE_BITMAP(force_release_mask, 0x80); + unsigned char set; + bool translated; + bool extra; + bool write; + bool softrepeat; + bool softraw; + bool scroll; + bool enabled; + + /* Accessed only from interrupt */ + unsigned char emul; + bool resend; + bool release; + unsigned long xl_bit; + unsigned int last; + unsigned long time; + unsigned long err_count; + + struct delayed_work event_work; + unsigned long event_jiffies; + unsigned long event_mask; + + /* Serializes reconnect(), attr->set() and event work */ + struct mutex mutex; +}; + +static irqreturn_t x68kkbd_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct x68kkbd *x68kkbd = serio_get_drvdata(serio); + struct input_dev *dev = x68kkbd->dev; + unsigned char scancode, down; + + scancode = data; + down = !(scancode & 0x80); + scancode &= 0x7f; + + if (scancode < 0x73) { + input_report_key(dev, x68kkbd_keycode[scancode], down); + if (data & 0x80) + input_sync(dev); + } + return IRQ_HANDLED; +} + +static void x68kkbd_command(struct serio *serio, unsigned char cmd) +{ + if (serio->write) + serio->write(serio, cmd); +} + +static int x68kkbd_set_repeat_rate(struct x68kkbd *x68kkbd) +{ + const short period[32] = + { 30, 35, 50, 75, 110, 155, 210, 275, 350, 435, 530, + 635, 750, 875, 1010, 1155}; + const short delay[] = + { 200, 300, 400, 500, 600, 700, 800, 900, + 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700 }; + + struct input_dev *dev = x68kkbd->dev; + int i = 0, j = 0; + + while (i < ARRAY_SIZE(period) - 1 && period[i] < dev->rep[REP_PERIOD]) + i++; + dev->rep[REP_PERIOD] = period[i]; + x68kkbd_command(x68kkbd->serio, 0x70 | i); + + while (j < ARRAY_SIZE(delay) - 1 && delay[j] < dev->rep[REP_DELAY]) + j++; + dev->rep[REP_DELAY] = delay[j]; + + x68kkbd_command(x68kkbd->serio, 0x60 | j); + return 0; +} + +static int x68kkbd_set_leds(struct x68kkbd *x68kkbd) +{ + struct input_dev *dev = x68kkbd->dev; + unsigned char led; + + led = (test_bit(LED_CAPSL, dev->led) ? 8 : 0); + x68kkbd_command(x68kkbd->serio, 0x80 | led); + return 0; +} + +static void x68kkbd_event_work(struct work_struct *work) +{ + struct x68kkbd *x68kkbd = container_of(work, struct x68kkbd, event_work.work); + + mutex_lock(&x68kkbd->mutex); + + if (test_and_clear_bit(X68KKBD_LED_EVENT_BIT, &x68kkbd->event_mask)) + x68kkbd_set_leds(x68kkbd); + + if (test_and_clear_bit(X68KKBD_REP_EVENT_BIT, &x68kkbd->event_mask)) + x68kkbd_set_repeat_rate(x68kkbd); + + mutex_unlock(&x68kkbd->mutex); +} + +static int x68kkbd_connect(struct serio *serio, struct serio_driver *drv) +{ + struct x68kkbd *x68kkbd; + struct input_dev *dev; + int err = -ENOMEM; + int i; + + x68kkbd = kzalloc(sizeof(struct x68kkbd), GFP_KERNEL); + dev = input_allocate_device(); + if (!x68kkbd || !dev) + goto fail1; + + x68kkbd->dev = dev; + x68kkbd->serio = serio; + x68kkbd->dev->name = "X68000 Keyboard"; + INIT_DELAYED_WORK(&x68kkbd->event_work, x68kkbd_event_work); + mutex_init(&x68kkbd->mutex); + dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + for (i = 0; i < sizeof(x68kkbd_keycode); i++) + if (x68kkbd_keycode[i]) + set_bit(i, dev->keybit); + + serio_set_drvdata(serio, x68kkbd); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(x68kkbd->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(dev); + kfree(x68kkbd); + return err; +} + +static struct serio_device_id x68kkbd_serio_ids[] = { + { + .type = SERIO_X68K_MFP, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { } +}; + +MODULE_DEVICE_TABLE(serio, x68kkbd_serio_ids); + +static struct serio_driver x68kkbd_drv = { + .driver = { + .name = "x68kkbd", + }, + .description = DRIVER_DESC, + .id_table = x68kkbd_serio_ids, + .interrupt = x68kkbd_interrupt, + .connect = x68kkbd_connect, +}; + +static int __init x68kkbd_init(void) +{ + return serio_register_driver(&x68kkbd_drv); +} + +static void __exit x68kkbd_exit(void) +{ + serio_unregister_driver(&x68kkbd_drv); +} + +module_init(x68kkbd_init); +module_exit(x68kkbd_exit); diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index f3e18f8ef9ca..10da0a4b7ac5 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -308,4 +308,11 @@ config USERIO If you are unsure, say N. +config SERIO_X68K_MFP + tristate "SHARP X68000 MFP serial support" + depends on X68000 || COMPILE_TEST + help + This selects support for the MFP serial on + SHARP X68000. + endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 67950a5ccb3f..a0579b715656 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_HYPERV_KEYBOARD) += hyperv-keyboard.o obj-$(CONFIG_SERIO_SUN4I_PS2) += sun4i-ps2.o obj-$(CONFIG_SERIO_GPIO_PS2) += ps2-gpio.o obj-$(CONFIG_USERIO) += userio.o +obj-$(CONFIG_SERIO_X68K_MFP) += x68kmfp.o diff --git a/drivers/input/serio/x68kmfp.c b/drivers/input/serio/x68kmfp.c new file mode 100644 index 000000000000..0695faa735ee --- /dev/null +++ b/drivers/input/serio/x68kmfp.c @@ -0,0 +1,344 @@ +/* + * X68000 68901 serial driver for Linux + * + * Copyright (c) 2016 Yoshinori Sato + */ + +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +MODULE_AUTHOR("Yoshinori Sato "); +MODULE_DESCRIPTION("X68000 MFP serial driver"); +MODULE_LICENSE("GPL"); + +#define MFP_USART (void *)0xe88027 +#define MFP_CSR (MFP_USART + 0) +#define MFP_UCR (MFP_USART + 2) +#define MFP_RSR (MFP_USART + 4) +#define MFP_TSR (MFP_USART + 6) +#define MFP_UDR (MFP_USART + 8) + +#define MFP_RSR_BF (0x80) +#define MFP_RSR_OE (0x40) +#define MFP_RSR_PE (0x20) +#define MFP_RSR_FE (0x10) +#define MFP_TSR_BE (0x80) + +#define MFP_CTL_TIMEOUT 1000 +#define MFP_KBD_PHYS_DESC "X68Kbus/serio" +#define MFP_KBD_IRQ 0x4c + +/* + * mfp_lock protects serialization between mfp_command and + * the interrupt handler. + */ +static DEFINE_SPINLOCK(mfp_lock); + +/* + * Writers to AUX and KBD ports as well as users issuing mfp_command + * directly should acquire mfp_mutex (by means of calling + * mfp_lock_chip() and mfp_unlock_ship() helpers) to ensure that + * they do not disturb each other (unfortunately in many mfp + * implementations write to one of the ports will immediately abort + * command that is being processed by another port). + */ +static DEFINE_MUTEX(mfp_mutex); + +struct mfp_port { + struct serio *serio; + int irq; + bool exists; + bool driver_bound; +} mfp_port; + +static bool mfp_kbd_irq_registered; +static struct platform_device *mfp_platform_device; +static struct notifier_block mfp_kbd_bind_notifier_block; + +static irqreturn_t mfp_interrupt(int irq, void *dev_id); +/* + * The mfp_wait_read() and mfp_wait_write functions wait for the mfp to + * be ready for reading values from it / writing values to it. + * Called always with mfp_lock held. + */ + +static int mfp_wait_write(void) +{ + int i = 0; + + while (!(__raw_readb(MFP_TSR) & MFP_TSR_BE) && (i < MFP_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == MFP_CTL_TIMEOUT); +} + +static int mfp_write(struct serio *port, unsigned char c) +{ + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&mfp_lock, flags); + + if (!(retval = mfp_wait_write())) { + __raw_writeb(c, MFP_UDR); + } + + spin_unlock_irqrestore(&mfp_lock, flags); + + return retval; +} + +/* + * mfp_interrupt() is the most important function in this driver - + * it handles the interrupts from the mfp, and sends incoming bytes + * to the upper layers. + */ + +static irqreturn_t mfp_interrupt(int irq, void *dev_id) +{ + unsigned long flags; + unsigned char rsr, data; + unsigned int dfl; + + spin_lock_irqsave(&mfp_lock, flags); + + rsr = __raw_readb(MFP_RSR); + data = __raw_readb(MFP_UDR); + + + dfl = (rsr & (MFP_RSR_PE | MFP_RSR_OE | MFP_RSR_FE)); + + spin_unlock_irqrestore(&mfp_lock, flags); + + serio_interrupt(mfp_port.serio, data, dfl); + + return IRQ_HANDLED; +} + +/* + * mfp_panic_blink() will turn the keyboard LEDs on or off and is called + * when kernel panics. Flashing LEDs is useful for users running X who may + * not see the console and will help distinguishing panics from "real" + * lockups. + * + * Note that DELAY has a limit of 10ms so we will not get stuck here + * waiting for KBC to free up even if KBD interrupt is off + */ + +#define DELAY do { mdelay(1); if (++delay > 10) return delay; } while(0) + +static long mfp_panic_blink(int state) +{ + long delay = 0; + char led; + + led = (state) ? 0x01 | 0xff : 0x80; + while (!(__raw_readb(MFP_TSR) & MFP_TSR_BE)) + DELAY; + __raw_writeb(0x80, MFP_UDR); + DELAY; + while (!(__raw_readb(MFP_TSR) & MFP_TSR_BE)) + DELAY; + DELAY; + __raw_writeb(led, MFP_UDR); + DELAY; + return delay; +} + +#undef DELAY + +static int __init mfp_create_kbd_port(void) +{ + struct serio *serio; + + serio = kzalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + serio->id.type = SERIO_X68K_MFP; + serio->write = mfp_write; + serio->port_data = &mfp_port; + serio->dev.parent = &mfp_platform_device->dev; + strlcpy(serio->name, "X68000 MFP serial", sizeof(serio->name)); + strlcpy(serio->phys, MFP_KBD_PHYS_DESC, sizeof(serio->phys)); + + mfp_port.serio = serio; + mfp_port.irq = MFP_KBD_IRQ; + + return 0; +} + +static void __init mfp_free_kbd_port(void) +{ + kfree(mfp_port.serio); + mfp_port.serio = NULL; +} + +static void __init mfp_register_ports(void) +{ + struct serio *serio = mfp_port.serio; + + if (serio) { + printk(KERN_INFO "serio: %s at %#lx irq %d\n", + serio->name, + (unsigned long) MFP_UDR, + mfp_port.irq); + serio_register_port(serio); + device_set_wakeup_capable(&serio->dev, true); + } +} + +static void mfp_unregister_ports(void) +{ + if (mfp_port.serio) { + serio_unregister_port(mfp_port.serio); + mfp_port.serio = NULL; + } +} + +static void mfp_free_irqs(void) +{ + if (mfp_kbd_irq_registered) + free_irq(MFP_KBD_IRQ, mfp_platform_device); + + mfp_kbd_irq_registered = false; +} + +static int __init mfp_setup_kbd(void) +{ + int error; + + error = mfp_create_kbd_port(); + if (error) + return error; + + error = request_irq(MFP_KBD_IRQ, mfp_interrupt, IRQF_SHARED, + "mfp-serial", mfp_platform_device); + if (error) + goto err_free_port; + + mfp_kbd_irq_registered = true; + return 0; + + err_free_port: + mfp_free_kbd_port(); + return error; +} + +static int mfp_kbd_bind_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + struct serio *serio = to_serio_port(dev); + struct mfp_port *port = serio->port_data; + + if (serio != mfp_port.serio) + return 0; + + switch (action) { + case BUS_NOTIFY_BOUND_DRIVER: + port->driver_bound = true; + break; + + case BUS_NOTIFY_UNBIND_DRIVER: + port->driver_bound = false; + break; + } + + return 0; +} + +static int __init mfp_probe(struct platform_device *dev) +{ + int error; + + mfp_platform_device = dev; + + error = mfp_setup_kbd(); + if (error) + goto out_fail; +/* + * Ok, everything is ready, let's register all serio ports + */ + mfp_register_ports(); + + return 0; + + out_fail: + mfp_free_irqs(); + mfp_platform_device = NULL; + + return error; +} + +static int mfp_remove(struct platform_device *dev) +{ + mfp_unregister_ports(); + mfp_free_irqs(); + mfp_platform_device = NULL; + + return 0; +} + +static struct platform_driver mfp_driver = { + .driver = { + .name = "X68K-mfp", + }, + .remove = mfp_remove, +}; + +static struct notifier_block mfp_kbd_bind_notifier_block = { + .notifier_call = mfp_kbd_bind_notifier, +}; + +static int __init mfp_init(void) +{ + struct platform_device *pdev; + int err; + + pdev = platform_create_bundle(&mfp_driver, mfp_probe, NULL, 0, NULL, 0); + if (IS_ERR(pdev)) { + err = PTR_ERR(pdev); + goto err_platform_exit; + } + + bus_register_notifier(&serio_bus, &mfp_kbd_bind_notifier_block); + panic_blink = mfp_panic_blink; + + return 0; + + err_platform_exit: + return err; +} + +static void __exit mfp_exit(void) +{ + platform_device_unregister(mfp_platform_device); + platform_driver_unregister(&mfp_driver); + + bus_unregister_notifier(&serio_bus, &mfp_kbd_bind_notifier_block); + panic_blink = NULL; +} + +module_init(mfp_init); +module_exit(mfp_exit); diff --git a/include/uapi/linux/serio.h b/include/uapi/linux/serio.h index 50e991952c97..0b576b419b2d 100644 --- a/include/uapi/linux/serio.h +++ b/include/uapi/linux/serio.h @@ -83,5 +83,6 @@ #define SERIO_PULSE8_CEC 0x40 #define SERIO_RAINSHADOW_CEC 0x41 #define SERIO_FSIA6B 0x42 +#define SERIO_X68K_MFP 0x43 #endif /* _UAPI_SERIO_H */