18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2000-2001 Vojtech Pavlik 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Based on the work of: 68c2ecf20Sopenharmony_ci * Richard Zidlicky <Richard.Zidlicky@stud.informatik.uni-erlangen.de> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * Q40 PS/2 keyboard controller driver for Linux/m68k 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci/* 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/serio.h> 188c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 198c2ecf20Sopenharmony_ci#include <linux/err.h> 208c2ecf20Sopenharmony_ci#include <linux/bitops.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <asm/io.h> 258c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 268c2ecf20Sopenharmony_ci#include <asm/q40_master.h> 278c2ecf20Sopenharmony_ci#include <asm/irq.h> 288c2ecf20Sopenharmony_ci#include <asm/q40ints.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define DRV_NAME "q40kbd" 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver"); 348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 358c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct q40kbd { 388c2ecf20Sopenharmony_ci struct serio *port; 398c2ecf20Sopenharmony_ci spinlock_t lock; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic irqreturn_t q40kbd_interrupt(int irq, void *dev_id) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct q40kbd *q40kbd = dev_id; 458c2ecf20Sopenharmony_ci unsigned long flags; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci spin_lock_irqsave(&q40kbd->lock, flags); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)) 508c2ecf20Sopenharmony_ci serio_interrupt(q40kbd->port, master_inb(KEYCODE_REG), 0); 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&q40kbd->lock, flags); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return IRQ_HANDLED; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/* 608c2ecf20Sopenharmony_ci * q40kbd_flush() flushes all data that may be in the keyboard buffers 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic void q40kbd_flush(struct q40kbd *q40kbd) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci int maxread = 100; 668c2ecf20Sopenharmony_ci unsigned long flags; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci spin_lock_irqsave(&q40kbd->lock, flags); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))) 718c2ecf20Sopenharmony_ci master_inb(KEYCODE_REG); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&q40kbd->lock, flags); 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void q40kbd_stop(void) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci master_outb(0, KEY_IRQ_ENABLE_REG); 798c2ecf20Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci/* 838c2ecf20Sopenharmony_ci * q40kbd_open() is called when a port is open by the higher layer. 848c2ecf20Sopenharmony_ci * It allocates the interrupt and enables in in the chip. 858c2ecf20Sopenharmony_ci */ 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int q40kbd_open(struct serio *port) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct q40kbd *q40kbd = port->port_data; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci q40kbd_flush(q40kbd); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* off we go */ 948c2ecf20Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 958c2ecf20Sopenharmony_ci master_outb(1, KEY_IRQ_ENABLE_REG); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void q40kbd_close(struct serio *port) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct q40kbd *q40kbd = port->port_data; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci q40kbd_stop(); 1058c2ecf20Sopenharmony_ci q40kbd_flush(q40kbd); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic int q40kbd_probe(struct platform_device *pdev) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct q40kbd *q40kbd; 1118c2ecf20Sopenharmony_ci struct serio *port; 1128c2ecf20Sopenharmony_ci int error; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci q40kbd = kzalloc(sizeof(struct q40kbd), GFP_KERNEL); 1158c2ecf20Sopenharmony_ci port = kzalloc(sizeof(struct serio), GFP_KERNEL); 1168c2ecf20Sopenharmony_ci if (!q40kbd || !port) { 1178c2ecf20Sopenharmony_ci error = -ENOMEM; 1188c2ecf20Sopenharmony_ci goto err_free_mem; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci q40kbd->port = port; 1228c2ecf20Sopenharmony_ci spin_lock_init(&q40kbd->lock); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci port->id.type = SERIO_8042; 1258c2ecf20Sopenharmony_ci port->open = q40kbd_open; 1268c2ecf20Sopenharmony_ci port->close = q40kbd_close; 1278c2ecf20Sopenharmony_ci port->port_data = q40kbd; 1288c2ecf20Sopenharmony_ci port->dev.parent = &pdev->dev; 1298c2ecf20Sopenharmony_ci strlcpy(port->name, "Q40 Kbd Port", sizeof(port->name)); 1308c2ecf20Sopenharmony_ci strlcpy(port->phys, "Q40", sizeof(port->phys)); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci q40kbd_stop(); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci error = request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, 1358c2ecf20Sopenharmony_ci DRV_NAME, q40kbd); 1368c2ecf20Sopenharmony_ci if (error) { 1378c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Can't get irq %d.\n", Q40_IRQ_KEYBOARD); 1388c2ecf20Sopenharmony_ci goto err_free_mem; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci serio_register_port(q40kbd->port); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, q40kbd); 1448c2ecf20Sopenharmony_ci printk(KERN_INFO "serio: Q40 kbd registered\n"); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return 0; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cierr_free_mem: 1498c2ecf20Sopenharmony_ci kfree(port); 1508c2ecf20Sopenharmony_ci kfree(q40kbd); 1518c2ecf20Sopenharmony_ci return error; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int q40kbd_remove(struct platform_device *pdev) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct q40kbd *q40kbd = platform_get_drvdata(pdev); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* 1598c2ecf20Sopenharmony_ci * q40kbd_close() will be called as part of unregistering 1608c2ecf20Sopenharmony_ci * and will ensure that IRQ is turned off, so it is safe 1618c2ecf20Sopenharmony_ci * to unregister port first and free IRQ later. 1628c2ecf20Sopenharmony_ci */ 1638c2ecf20Sopenharmony_ci serio_unregister_port(q40kbd->port); 1648c2ecf20Sopenharmony_ci free_irq(Q40_IRQ_KEYBOARD, q40kbd); 1658c2ecf20Sopenharmony_ci kfree(q40kbd); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return 0; 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic struct platform_driver q40kbd_driver = { 1718c2ecf20Sopenharmony_ci .driver = { 1728c2ecf20Sopenharmony_ci .name = "q40kbd", 1738c2ecf20Sopenharmony_ci }, 1748c2ecf20Sopenharmony_ci .remove = q40kbd_remove, 1758c2ecf20Sopenharmony_ci}; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cimodule_platform_driver_probe(q40kbd_driver, q40kbd_probe); 178