18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 1999-2001 Vojtech Pavlik 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci/* 78c2ecf20Sopenharmony_ci * 82C710 C&T mouse port chip driver for Linux 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/ioport.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 188c2ecf20Sopenharmony_ci#include <linux/serio.h> 198c2ecf20Sopenharmony_ci#include <linux/errno.h> 208c2ecf20Sopenharmony_ci#include <linux/err.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <asm/io.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); 278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("82C710 C&T mouse port chip driver"); 288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* 318c2ecf20Sopenharmony_ci * ct82c710 interface 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define CT82C710_DEV_IDLE 0x01 /* Device Idle */ 358c2ecf20Sopenharmony_ci#define CT82C710_RX_FULL 0x02 /* Device Char received */ 368c2ecf20Sopenharmony_ci#define CT82C710_TX_IDLE 0x04 /* Device XMIT Idle */ 378c2ecf20Sopenharmony_ci#define CT82C710_RESET 0x08 /* Device Reset */ 388c2ecf20Sopenharmony_ci#define CT82C710_INTS_ON 0x10 /* Device Interrupt On */ 398c2ecf20Sopenharmony_ci#define CT82C710_ERROR_FLAG 0x20 /* Device Error */ 408c2ecf20Sopenharmony_ci#define CT82C710_CLEAR 0x40 /* Device Clear */ 418c2ecf20Sopenharmony_ci#define CT82C710_ENABLE 0x80 /* Device Enable */ 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define CT82C710_IRQ 12 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define CT82C710_DATA ct82c710_iores.start 468c2ecf20Sopenharmony_ci#define CT82C710_STATUS (ct82c710_iores.start + 1) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic struct serio *ct82c710_port; 498c2ecf20Sopenharmony_cistatic struct platform_device *ct82c710_device; 508c2ecf20Sopenharmony_cistatic struct resource ct82c710_iores; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci/* 538c2ecf20Sopenharmony_ci * Interrupt handler for the 82C710 mouse port. A character 548c2ecf20Sopenharmony_ci * is waiting in the 82C710. 558c2ecf20Sopenharmony_ci */ 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic irqreturn_t ct82c710_interrupt(int cpl, void *dev_id) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0); 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci/* 638c2ecf20Sopenharmony_ci * Wait for device to send output char and flush any input char. 648c2ecf20Sopenharmony_ci */ 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic int ct82c170_wait(void) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci int timeout = 60000; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE)) 718c2ecf20Sopenharmony_ci != (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) { 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci udelay(1); 768c2ecf20Sopenharmony_ci timeout--; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci return !timeout; 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void ct82c710_close(struct serio *serio) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci if (ct82c170_wait()) 858c2ecf20Sopenharmony_ci printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (ct82c170_wait()) 908c2ecf20Sopenharmony_ci printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci free_irq(CT82C710_IRQ, NULL); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic int ct82c710_open(struct serio *serio) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci unsigned char status; 988c2ecf20Sopenharmony_ci int err; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci err = request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL); 1018c2ecf20Sopenharmony_ci if (err) 1028c2ecf20Sopenharmony_ci return err; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci status = inb_p(CT82C710_STATUS); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci status |= (CT82C710_ENABLE | CT82C710_RESET); 1078c2ecf20Sopenharmony_ci outb_p(status, CT82C710_STATUS); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci status &= ~(CT82C710_RESET); 1108c2ecf20Sopenharmony_ci outb_p(status, CT82C710_STATUS); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci status |= CT82C710_INTS_ON; 1138c2ecf20Sopenharmony_ci outb_p(status, CT82C710_STATUS); /* Enable interrupts */ 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci while (ct82c170_wait()) { 1168c2ecf20Sopenharmony_ci printk(KERN_ERR "ct82c710: Device busy in open()\n"); 1178c2ecf20Sopenharmony_ci status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON); 1188c2ecf20Sopenharmony_ci outb_p(status, CT82C710_STATUS); 1198c2ecf20Sopenharmony_ci free_irq(CT82C710_IRQ, NULL); 1208c2ecf20Sopenharmony_ci return -EBUSY; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci return 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * Write to the 82C710 mouse device. 1288c2ecf20Sopenharmony_ci */ 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic int ct82c710_write(struct serio *port, unsigned char c) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci if (ct82c170_wait()) return -1; 1338c2ecf20Sopenharmony_ci outb_p(c, CT82C710_DATA); 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci/* 1388c2ecf20Sopenharmony_ci * See if we can find a 82C710 device. Read mouse address. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int __init ct82c710_detect(void) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */ 1448c2ecf20Sopenharmony_ci outb_p(0xaa, 0x3fa); /* Inverse of 55 */ 1458c2ecf20Sopenharmony_ci outb_p(0x36, 0x3fa); /* Address the chip */ 1468c2ecf20Sopenharmony_ci outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */ 1478c2ecf20Sopenharmony_ci outb_p(0x1b, 0x2fa); /* Inverse of e4 */ 1488c2ecf20Sopenharmony_ci outb_p(0x0f, 0x390); /* Write index */ 1498c2ecf20Sopenharmony_ci if (inb_p(0x391) != 0xe4) /* Config address found? */ 1508c2ecf20Sopenharmony_ci return -ENODEV; /* No: no 82C710 here */ 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci outb_p(0x0d, 0x390); /* Write index */ 1538c2ecf20Sopenharmony_ci ct82c710_iores.start = inb_p(0x391) << 2; /* Get mouse I/O address */ 1548c2ecf20Sopenharmony_ci ct82c710_iores.end = ct82c710_iores.start + 1; 1558c2ecf20Sopenharmony_ci ct82c710_iores.flags = IORESOURCE_IO; 1568c2ecf20Sopenharmony_ci outb_p(0x0f, 0x390); 1578c2ecf20Sopenharmony_ci outb_p(0x0f, 0x391); /* Close config mode */ 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return 0; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic int ct82c710_probe(struct platform_device *dev) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci ct82c710_port = kzalloc(sizeof(struct serio), GFP_KERNEL); 1658c2ecf20Sopenharmony_ci if (!ct82c710_port) 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci ct82c710_port->id.type = SERIO_8042; 1698c2ecf20Sopenharmony_ci ct82c710_port->dev.parent = &dev->dev; 1708c2ecf20Sopenharmony_ci ct82c710_port->open = ct82c710_open; 1718c2ecf20Sopenharmony_ci ct82c710_port->close = ct82c710_close; 1728c2ecf20Sopenharmony_ci ct82c710_port->write = ct82c710_write; 1738c2ecf20Sopenharmony_ci strlcpy(ct82c710_port->name, "C&T 82c710 mouse port", 1748c2ecf20Sopenharmony_ci sizeof(ct82c710_port->name)); 1758c2ecf20Sopenharmony_ci snprintf(ct82c710_port->phys, sizeof(ct82c710_port->phys), 1768c2ecf20Sopenharmony_ci "isa%16llx/serio0", (unsigned long long)CT82C710_DATA); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci serio_register_port(ct82c710_port); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci printk(KERN_INFO "serio: C&T 82c710 mouse port at %#llx irq %d\n", 1818c2ecf20Sopenharmony_ci (unsigned long long)CT82C710_DATA, CT82C710_IRQ); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int ct82c710_remove(struct platform_device *dev) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci serio_unregister_port(ct82c710_port); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic struct platform_driver ct82c710_driver = { 1948c2ecf20Sopenharmony_ci .driver = { 1958c2ecf20Sopenharmony_ci .name = "ct82c710", 1968c2ecf20Sopenharmony_ci }, 1978c2ecf20Sopenharmony_ci .probe = ct82c710_probe, 1988c2ecf20Sopenharmony_ci .remove = ct82c710_remove, 1998c2ecf20Sopenharmony_ci}; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int __init ct82c710_init(void) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci int error; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci error = ct82c710_detect(); 2078c2ecf20Sopenharmony_ci if (error) 2088c2ecf20Sopenharmony_ci return error; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci error = platform_driver_register(&ct82c710_driver); 2118c2ecf20Sopenharmony_ci if (error) 2128c2ecf20Sopenharmony_ci return error; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci ct82c710_device = platform_device_alloc("ct82c710", -1); 2158c2ecf20Sopenharmony_ci if (!ct82c710_device) { 2168c2ecf20Sopenharmony_ci error = -ENOMEM; 2178c2ecf20Sopenharmony_ci goto err_unregister_driver; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci error = platform_device_add_resources(ct82c710_device, &ct82c710_iores, 1); 2218c2ecf20Sopenharmony_ci if (error) 2228c2ecf20Sopenharmony_ci goto err_free_device; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci error = platform_device_add(ct82c710_device); 2258c2ecf20Sopenharmony_ci if (error) 2268c2ecf20Sopenharmony_ci goto err_free_device; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci err_free_device: 2318c2ecf20Sopenharmony_ci platform_device_put(ct82c710_device); 2328c2ecf20Sopenharmony_ci err_unregister_driver: 2338c2ecf20Sopenharmony_ci platform_driver_unregister(&ct82c710_driver); 2348c2ecf20Sopenharmony_ci return error; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic void __exit ct82c710_exit(void) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci platform_device_unregister(ct82c710_device); 2408c2ecf20Sopenharmony_ci platform_driver_unregister(&ct82c710_driver); 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cimodule_init(ct82c710_init); 2448c2ecf20Sopenharmony_cimodule_exit(ct82c710_exit); 245