162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2000-2001 Vojtech Pavlik 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Based on the work of: 662306a36Sopenharmony_ci * Richard Zidlicky <Richard.Zidlicky@stud.informatik.uni-erlangen.de> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* 1062306a36Sopenharmony_ci * Q40 PS/2 keyboard controller driver for Linux/m68k 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/serio.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/err.h> 1762306a36Sopenharmony_ci#include <linux/bitops.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <asm/io.h> 2262306a36Sopenharmony_ci#include <linux/uaccess.h> 2362306a36Sopenharmony_ci#include <asm/q40_master.h> 2462306a36Sopenharmony_ci#include <asm/irq.h> 2562306a36Sopenharmony_ci#include <asm/q40ints.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define DRV_NAME "q40kbd" 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 3062306a36Sopenharmony_ciMODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver"); 3162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 3262306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct q40kbd { 3562306a36Sopenharmony_ci struct serio *port; 3662306a36Sopenharmony_ci spinlock_t lock; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic irqreturn_t q40kbd_interrupt(int irq, void *dev_id) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct q40kbd *q40kbd = dev_id; 4262306a36Sopenharmony_ci unsigned long flags; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci spin_lock_irqsave(&q40kbd->lock, flags); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)) 4762306a36Sopenharmony_ci serio_interrupt(q40kbd->port, master_inb(KEYCODE_REG), 0); 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci spin_unlock_irqrestore(&q40kbd->lock, flags); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci return IRQ_HANDLED; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* 5762306a36Sopenharmony_ci * q40kbd_flush() flushes all data that may be in the keyboard buffers 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic void q40kbd_flush(struct q40kbd *q40kbd) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci int maxread = 100; 6362306a36Sopenharmony_ci unsigned long flags; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci spin_lock_irqsave(&q40kbd->lock, flags); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))) 6862306a36Sopenharmony_ci master_inb(KEYCODE_REG); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci spin_unlock_irqrestore(&q40kbd->lock, flags); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic void q40kbd_stop(void) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci master_outb(0, KEY_IRQ_ENABLE_REG); 7662306a36Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* 8062306a36Sopenharmony_ci * q40kbd_open() is called when a port is open by the higher layer. 8162306a36Sopenharmony_ci * It allocates the interrupt and enables in in the chip. 8262306a36Sopenharmony_ci */ 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int q40kbd_open(struct serio *port) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct q40kbd *q40kbd = port->port_data; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci q40kbd_flush(q40kbd); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci /* off we go */ 9162306a36Sopenharmony_ci master_outb(-1, KEYBOARD_UNLOCK_REG); 9262306a36Sopenharmony_ci master_outb(1, KEY_IRQ_ENABLE_REG); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void q40kbd_close(struct serio *port) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct q40kbd *q40kbd = port->port_data; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci q40kbd_stop(); 10262306a36Sopenharmony_ci q40kbd_flush(q40kbd); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int q40kbd_probe(struct platform_device *pdev) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci struct q40kbd *q40kbd; 10862306a36Sopenharmony_ci struct serio *port; 10962306a36Sopenharmony_ci int error; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci q40kbd = kzalloc(sizeof(struct q40kbd), GFP_KERNEL); 11262306a36Sopenharmony_ci port = kzalloc(sizeof(struct serio), GFP_KERNEL); 11362306a36Sopenharmony_ci if (!q40kbd || !port) { 11462306a36Sopenharmony_ci error = -ENOMEM; 11562306a36Sopenharmony_ci goto err_free_mem; 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci q40kbd->port = port; 11962306a36Sopenharmony_ci spin_lock_init(&q40kbd->lock); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci port->id.type = SERIO_8042; 12262306a36Sopenharmony_ci port->open = q40kbd_open; 12362306a36Sopenharmony_ci port->close = q40kbd_close; 12462306a36Sopenharmony_ci port->port_data = q40kbd; 12562306a36Sopenharmony_ci port->dev.parent = &pdev->dev; 12662306a36Sopenharmony_ci strscpy(port->name, "Q40 Kbd Port", sizeof(port->name)); 12762306a36Sopenharmony_ci strscpy(port->phys, "Q40", sizeof(port->phys)); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci q40kbd_stop(); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci error = request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, 13262306a36Sopenharmony_ci DRV_NAME, q40kbd); 13362306a36Sopenharmony_ci if (error) { 13462306a36Sopenharmony_ci dev_err(&pdev->dev, "Can't get irq %d.\n", Q40_IRQ_KEYBOARD); 13562306a36Sopenharmony_ci goto err_free_mem; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci serio_register_port(q40kbd->port); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci platform_set_drvdata(pdev, q40kbd); 14162306a36Sopenharmony_ci printk(KERN_INFO "serio: Q40 kbd registered\n"); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cierr_free_mem: 14662306a36Sopenharmony_ci kfree(port); 14762306a36Sopenharmony_ci kfree(q40kbd); 14862306a36Sopenharmony_ci return error; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic int q40kbd_remove(struct platform_device *pdev) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct q40kbd *q40kbd = platform_get_drvdata(pdev); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* 15662306a36Sopenharmony_ci * q40kbd_close() will be called as part of unregistering 15762306a36Sopenharmony_ci * and will ensure that IRQ is turned off, so it is safe 15862306a36Sopenharmony_ci * to unregister port first and free IRQ later. 15962306a36Sopenharmony_ci */ 16062306a36Sopenharmony_ci serio_unregister_port(q40kbd->port); 16162306a36Sopenharmony_ci free_irq(Q40_IRQ_KEYBOARD, q40kbd); 16262306a36Sopenharmony_ci kfree(q40kbd); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic struct platform_driver q40kbd_driver = { 16862306a36Sopenharmony_ci .driver = { 16962306a36Sopenharmony_ci .name = "q40kbd", 17062306a36Sopenharmony_ci }, 17162306a36Sopenharmony_ci .remove = q40kbd_remove, 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cimodule_platform_driver_probe(q40kbd_driver, q40kbd_probe); 175