18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * linux/drivers/input/serio/pcips2.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2003 Russell King, All Rights Reserved. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * I'm not sure if this is a generic PS/2 PCI interface or specific to 88c2ecf20Sopenharmony_ci * the Mobility Electronics docking station. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/ioport.h> 138c2ecf20Sopenharmony_ci#include <linux/input.h> 148c2ecf20Sopenharmony_ci#include <linux/pci.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/serio.h> 178c2ecf20Sopenharmony_ci#include <linux/delay.h> 188c2ecf20Sopenharmony_ci#include <asm/io.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define PS2_CTRL (0) 218c2ecf20Sopenharmony_ci#define PS2_STATUS (1) 228c2ecf20Sopenharmony_ci#define PS2_DATA (2) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define PS2_CTRL_CLK (1<<0) 258c2ecf20Sopenharmony_ci#define PS2_CTRL_DAT (1<<1) 268c2ecf20Sopenharmony_ci#define PS2_CTRL_TXIRQ (1<<2) 278c2ecf20Sopenharmony_ci#define PS2_CTRL_ENABLE (1<<3) 288c2ecf20Sopenharmony_ci#define PS2_CTRL_RXIRQ (1<<4) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define PS2_STAT_CLK (1<<0) 318c2ecf20Sopenharmony_ci#define PS2_STAT_DAT (1<<1) 328c2ecf20Sopenharmony_ci#define PS2_STAT_PARITY (1<<2) 338c2ecf20Sopenharmony_ci#define PS2_STAT_RXFULL (1<<5) 348c2ecf20Sopenharmony_ci#define PS2_STAT_TXBUSY (1<<6) 358c2ecf20Sopenharmony_ci#define PS2_STAT_TXEMPTY (1<<7) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct pcips2_data { 388c2ecf20Sopenharmony_ci struct serio *io; 398c2ecf20Sopenharmony_ci unsigned int base; 408c2ecf20Sopenharmony_ci struct pci_dev *dev; 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int pcips2_write(struct serio *io, unsigned char val) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct pcips2_data *ps2if = io->port_data; 468c2ecf20Sopenharmony_ci unsigned int stat; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci do { 498c2ecf20Sopenharmony_ci stat = inb(ps2if->base + PS2_STATUS); 508c2ecf20Sopenharmony_ci cpu_relax(); 518c2ecf20Sopenharmony_ci } while (!(stat & PS2_STAT_TXEMPTY)); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci outb(val, ps2if->base + PS2_DATA); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci return 0; 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic irqreturn_t pcips2_interrupt(int irq, void *devid) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci struct pcips2_data *ps2if = devid; 618c2ecf20Sopenharmony_ci unsigned char status, scancode; 628c2ecf20Sopenharmony_ci int handled = 0; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci do { 658c2ecf20Sopenharmony_ci unsigned int flag; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci status = inb(ps2if->base + PS2_STATUS); 688c2ecf20Sopenharmony_ci if (!(status & PS2_STAT_RXFULL)) 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci handled = 1; 718c2ecf20Sopenharmony_ci scancode = inb(ps2if->base + PS2_DATA); 728c2ecf20Sopenharmony_ci if (status == 0xff && scancode == 0xff) 738c2ecf20Sopenharmony_ci break; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci flag = (status & PS2_STAT_PARITY) ? 0 : SERIO_PARITY; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci if (hweight8(scancode) & 1) 788c2ecf20Sopenharmony_ci flag ^= SERIO_PARITY; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci serio_interrupt(ps2if->io, scancode, flag); 818c2ecf20Sopenharmony_ci } while (1); 828c2ecf20Sopenharmony_ci return IRQ_RETVAL(handled); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void pcips2_flush_input(struct pcips2_data *ps2if) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci unsigned char status, scancode; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci do { 908c2ecf20Sopenharmony_ci status = inb(ps2if->base + PS2_STATUS); 918c2ecf20Sopenharmony_ci if (!(status & PS2_STAT_RXFULL)) 928c2ecf20Sopenharmony_ci break; 938c2ecf20Sopenharmony_ci scancode = inb(ps2if->base + PS2_DATA); 948c2ecf20Sopenharmony_ci if (status == 0xff && scancode == 0xff) 958c2ecf20Sopenharmony_ci break; 968c2ecf20Sopenharmony_ci } while (1); 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic int pcips2_open(struct serio *io) 1008c2ecf20Sopenharmony_ci{ 1018c2ecf20Sopenharmony_ci struct pcips2_data *ps2if = io->port_data; 1028c2ecf20Sopenharmony_ci int ret, val = 0; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci outb(PS2_CTRL_ENABLE, ps2if->base); 1058c2ecf20Sopenharmony_ci pcips2_flush_input(ps2if); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci ret = request_irq(ps2if->dev->irq, pcips2_interrupt, IRQF_SHARED, 1088c2ecf20Sopenharmony_ci "pcips2", ps2if); 1098c2ecf20Sopenharmony_ci if (ret == 0) 1108c2ecf20Sopenharmony_ci val = PS2_CTRL_ENABLE | PS2_CTRL_RXIRQ; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci outb(val, ps2if->base); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic void pcips2_close(struct serio *io) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct pcips2_data *ps2if = io->port_data; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci outb(0, ps2if->base); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci free_irq(ps2if->dev->irq, ps2if); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic int pcips2_probe(struct pci_dev *dev, const struct pci_device_id *id) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci struct pcips2_data *ps2if; 1298c2ecf20Sopenharmony_ci struct serio *serio; 1308c2ecf20Sopenharmony_ci int ret; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci ret = pci_enable_device(dev); 1338c2ecf20Sopenharmony_ci if (ret) 1348c2ecf20Sopenharmony_ci goto out; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci ret = pci_request_regions(dev, "pcips2"); 1378c2ecf20Sopenharmony_ci if (ret) 1388c2ecf20Sopenharmony_ci goto disable; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci ps2if = kzalloc(sizeof(struct pcips2_data), GFP_KERNEL); 1418c2ecf20Sopenharmony_ci serio = kzalloc(sizeof(struct serio), GFP_KERNEL); 1428c2ecf20Sopenharmony_ci if (!ps2if || !serio) { 1438c2ecf20Sopenharmony_ci ret = -ENOMEM; 1448c2ecf20Sopenharmony_ci goto release; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci serio->id.type = SERIO_8042; 1498c2ecf20Sopenharmony_ci serio->write = pcips2_write; 1508c2ecf20Sopenharmony_ci serio->open = pcips2_open; 1518c2ecf20Sopenharmony_ci serio->close = pcips2_close; 1528c2ecf20Sopenharmony_ci strlcpy(serio->name, pci_name(dev), sizeof(serio->name)); 1538c2ecf20Sopenharmony_ci strlcpy(serio->phys, dev_name(&dev->dev), sizeof(serio->phys)); 1548c2ecf20Sopenharmony_ci serio->port_data = ps2if; 1558c2ecf20Sopenharmony_ci serio->dev.parent = &dev->dev; 1568c2ecf20Sopenharmony_ci ps2if->io = serio; 1578c2ecf20Sopenharmony_ci ps2if->dev = dev; 1588c2ecf20Sopenharmony_ci ps2if->base = pci_resource_start(dev, 0); 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci pci_set_drvdata(dev, ps2if); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci serio_register_port(ps2if->io); 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci release: 1668c2ecf20Sopenharmony_ci kfree(ps2if); 1678c2ecf20Sopenharmony_ci kfree(serio); 1688c2ecf20Sopenharmony_ci pci_release_regions(dev); 1698c2ecf20Sopenharmony_ci disable: 1708c2ecf20Sopenharmony_ci pci_disable_device(dev); 1718c2ecf20Sopenharmony_ci out: 1728c2ecf20Sopenharmony_ci return ret; 1738c2ecf20Sopenharmony_ci} 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_cistatic void pcips2_remove(struct pci_dev *dev) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci struct pcips2_data *ps2if = pci_get_drvdata(dev); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci serio_unregister_port(ps2if->io); 1808c2ecf20Sopenharmony_ci kfree(ps2if); 1818c2ecf20Sopenharmony_ci pci_release_regions(dev); 1828c2ecf20Sopenharmony_ci pci_disable_device(dev); 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic const struct pci_device_id pcips2_ids[] = { 1868c2ecf20Sopenharmony_ci { 1878c2ecf20Sopenharmony_ci .vendor = 0x14f2, /* MOBILITY */ 1888c2ecf20Sopenharmony_ci .device = 0x0123, /* Keyboard */ 1898c2ecf20Sopenharmony_ci .subvendor = PCI_ANY_ID, 1908c2ecf20Sopenharmony_ci .subdevice = PCI_ANY_ID, 1918c2ecf20Sopenharmony_ci .class = PCI_CLASS_INPUT_KEYBOARD << 8, 1928c2ecf20Sopenharmony_ci .class_mask = 0xffff00, 1938c2ecf20Sopenharmony_ci }, 1948c2ecf20Sopenharmony_ci { 1958c2ecf20Sopenharmony_ci .vendor = 0x14f2, /* MOBILITY */ 1968c2ecf20Sopenharmony_ci .device = 0x0124, /* Mouse */ 1978c2ecf20Sopenharmony_ci .subvendor = PCI_ANY_ID, 1988c2ecf20Sopenharmony_ci .subdevice = PCI_ANY_ID, 1998c2ecf20Sopenharmony_ci .class = PCI_CLASS_INPUT_MOUSE << 8, 2008c2ecf20Sopenharmony_ci .class_mask = 0xffff00, 2018c2ecf20Sopenharmony_ci }, 2028c2ecf20Sopenharmony_ci { 0, } 2038c2ecf20Sopenharmony_ci}; 2048c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pcips2_ids); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic struct pci_driver pcips2_driver = { 2078c2ecf20Sopenharmony_ci .name = "pcips2", 2088c2ecf20Sopenharmony_ci .id_table = pcips2_ids, 2098c2ecf20Sopenharmony_ci .probe = pcips2_probe, 2108c2ecf20Sopenharmony_ci .remove = pcips2_remove, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cimodule_pci_driver(pcips2_driver); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2168c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); 2178c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PCI PS/2 keyboard/mouse driver"); 218