162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (c) 1999-2001 Vojtech Pavlik
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci/*
762306a36Sopenharmony_ci *  82C710 C&T mouse port chip driver for Linux
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/ioport.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/serio.h>
1662306a36Sopenharmony_ci#include <linux/errno.h>
1762306a36Sopenharmony_ci#include <linux/err.h>
1862306a36Sopenharmony_ci#include <linux/platform_device.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <asm/io.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciMODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
2462306a36Sopenharmony_ciMODULE_DESCRIPTION("82C710 C&T mouse port chip driver");
2562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/*
2862306a36Sopenharmony_ci * ct82c710 interface
2962306a36Sopenharmony_ci */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define CT82C710_DEV_IDLE     0x01		/* Device Idle */
3262306a36Sopenharmony_ci#define CT82C710_RX_FULL      0x02		/* Device Char received */
3362306a36Sopenharmony_ci#define CT82C710_TX_IDLE      0x04		/* Device XMIT Idle */
3462306a36Sopenharmony_ci#define CT82C710_RESET        0x08		/* Device Reset */
3562306a36Sopenharmony_ci#define CT82C710_INTS_ON      0x10		/* Device Interrupt On */
3662306a36Sopenharmony_ci#define CT82C710_ERROR_FLAG   0x20		/* Device Error */
3762306a36Sopenharmony_ci#define CT82C710_CLEAR        0x40		/* Device Clear */
3862306a36Sopenharmony_ci#define CT82C710_ENABLE       0x80		/* Device Enable */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define CT82C710_IRQ          12
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define CT82C710_DATA         ct82c710_iores.start
4362306a36Sopenharmony_ci#define CT82C710_STATUS       (ct82c710_iores.start + 1)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic struct serio *ct82c710_port;
4662306a36Sopenharmony_cistatic struct platform_device *ct82c710_device;
4762306a36Sopenharmony_cistatic struct resource ct82c710_iores;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/*
5062306a36Sopenharmony_ci * Interrupt handler for the 82C710 mouse port. A character
5162306a36Sopenharmony_ci * is waiting in the 82C710.
5262306a36Sopenharmony_ci */
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic irqreturn_t ct82c710_interrupt(int cpl, void *dev_id)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/*
6062306a36Sopenharmony_ci * Wait for device to send output char and flush any input char.
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic int ct82c170_wait(void)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	int timeout = 60000;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE))
6862306a36Sopenharmony_ci		       != (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) {
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci		udelay(1);
7362306a36Sopenharmony_ci		timeout--;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return !timeout;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic void ct82c710_close(struct serio *serio)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	if (ct82c170_wait())
8262306a36Sopenharmony_ci		printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (ct82c170_wait())
8762306a36Sopenharmony_ci		printk(KERN_WARNING "ct82c710.c: Device busy in close()\n");
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	free_irq(CT82C710_IRQ, NULL);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int ct82c710_open(struct serio *serio)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	unsigned char status;
9562306a36Sopenharmony_ci	int err;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	err = request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL);
9862306a36Sopenharmony_ci	if (err)
9962306a36Sopenharmony_ci		return err;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	status = inb_p(CT82C710_STATUS);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	status |= (CT82C710_ENABLE | CT82C710_RESET);
10462306a36Sopenharmony_ci	outb_p(status, CT82C710_STATUS);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	status &= ~(CT82C710_RESET);
10762306a36Sopenharmony_ci	outb_p(status, CT82C710_STATUS);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	status |= CT82C710_INTS_ON;
11062306a36Sopenharmony_ci	outb_p(status, CT82C710_STATUS);	/* Enable interrupts */
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	while (ct82c170_wait()) {
11362306a36Sopenharmony_ci		printk(KERN_ERR "ct82c710: Device busy in open()\n");
11462306a36Sopenharmony_ci		status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON);
11562306a36Sopenharmony_ci		outb_p(status, CT82C710_STATUS);
11662306a36Sopenharmony_ci		free_irq(CT82C710_IRQ, NULL);
11762306a36Sopenharmony_ci		return -EBUSY;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return 0;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/*
12462306a36Sopenharmony_ci * Write to the 82C710 mouse device.
12562306a36Sopenharmony_ci */
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic int ct82c710_write(struct serio *port, unsigned char c)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	if (ct82c170_wait()) return -1;
13062306a36Sopenharmony_ci	outb_p(c, CT82C710_DATA);
13162306a36Sopenharmony_ci	return 0;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci/*
13562306a36Sopenharmony_ci * See if we can find a 82C710 device. Read mouse address.
13662306a36Sopenharmony_ci */
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int __init ct82c710_detect(void)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	outb_p(0x55, 0x2fa);				/* Any value except 9, ff or 36 */
14162306a36Sopenharmony_ci	outb_p(0xaa, 0x3fa);				/* Inverse of 55 */
14262306a36Sopenharmony_ci	outb_p(0x36, 0x3fa);				/* Address the chip */
14362306a36Sopenharmony_ci	outb_p(0xe4, 0x3fa);				/* 390/4; 390 = config address */
14462306a36Sopenharmony_ci	outb_p(0x1b, 0x2fa);				/* Inverse of e4 */
14562306a36Sopenharmony_ci	outb_p(0x0f, 0x390);				/* Write index */
14662306a36Sopenharmony_ci	if (inb_p(0x391) != 0xe4)			/* Config address found? */
14762306a36Sopenharmony_ci		return -ENODEV;				/* No: no 82C710 here */
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	outb_p(0x0d, 0x390);				/* Write index */
15062306a36Sopenharmony_ci	ct82c710_iores.start = inb_p(0x391) << 2;	/* Get mouse I/O address */
15162306a36Sopenharmony_ci	ct82c710_iores.end = ct82c710_iores.start + 1;
15262306a36Sopenharmony_ci	ct82c710_iores.flags = IORESOURCE_IO;
15362306a36Sopenharmony_ci	outb_p(0x0f, 0x390);
15462306a36Sopenharmony_ci	outb_p(0x0f, 0x391);				/* Close config mode */
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return 0;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic int ct82c710_probe(struct platform_device *dev)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	ct82c710_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
16262306a36Sopenharmony_ci	if (!ct82c710_port)
16362306a36Sopenharmony_ci		return -ENOMEM;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	ct82c710_port->id.type = SERIO_8042;
16662306a36Sopenharmony_ci	ct82c710_port->dev.parent = &dev->dev;
16762306a36Sopenharmony_ci	ct82c710_port->open = ct82c710_open;
16862306a36Sopenharmony_ci	ct82c710_port->close = ct82c710_close;
16962306a36Sopenharmony_ci	ct82c710_port->write = ct82c710_write;
17062306a36Sopenharmony_ci	strscpy(ct82c710_port->name, "C&T 82c710 mouse port",
17162306a36Sopenharmony_ci		sizeof(ct82c710_port->name));
17262306a36Sopenharmony_ci	snprintf(ct82c710_port->phys, sizeof(ct82c710_port->phys),
17362306a36Sopenharmony_ci		 "isa%16llx/serio0", (unsigned long long)CT82C710_DATA);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	serio_register_port(ct82c710_port);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	printk(KERN_INFO "serio: C&T 82c710 mouse port at %#llx irq %d\n",
17862306a36Sopenharmony_ci		(unsigned long long)CT82C710_DATA, CT82C710_IRQ);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	return 0;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int ct82c710_remove(struct platform_device *dev)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	serio_unregister_port(ct82c710_port);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic struct platform_driver ct82c710_driver = {
19162306a36Sopenharmony_ci	.driver		= {
19262306a36Sopenharmony_ci		.name	= "ct82c710",
19362306a36Sopenharmony_ci	},
19462306a36Sopenharmony_ci	.probe		= ct82c710_probe,
19562306a36Sopenharmony_ci	.remove		= ct82c710_remove,
19662306a36Sopenharmony_ci};
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int __init ct82c710_init(void)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	int error;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	error = ct82c710_detect();
20462306a36Sopenharmony_ci	if (error)
20562306a36Sopenharmony_ci		return error;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	error = platform_driver_register(&ct82c710_driver);
20862306a36Sopenharmony_ci	if (error)
20962306a36Sopenharmony_ci		return error;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	ct82c710_device = platform_device_alloc("ct82c710", -1);
21262306a36Sopenharmony_ci	if (!ct82c710_device) {
21362306a36Sopenharmony_ci		error = -ENOMEM;
21462306a36Sopenharmony_ci		goto err_unregister_driver;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	error = platform_device_add_resources(ct82c710_device, &ct82c710_iores, 1);
21862306a36Sopenharmony_ci	if (error)
21962306a36Sopenharmony_ci		goto err_free_device;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	error = platform_device_add(ct82c710_device);
22262306a36Sopenharmony_ci	if (error)
22362306a36Sopenharmony_ci		goto err_free_device;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci err_free_device:
22862306a36Sopenharmony_ci	platform_device_put(ct82c710_device);
22962306a36Sopenharmony_ci err_unregister_driver:
23062306a36Sopenharmony_ci	platform_driver_unregister(&ct82c710_driver);
23162306a36Sopenharmony_ci	return error;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic void __exit ct82c710_exit(void)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	platform_device_unregister(ct82c710_device);
23762306a36Sopenharmony_ci	platform_driver_unregister(&ct82c710_driver);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cimodule_init(ct82c710_init);
24162306a36Sopenharmony_cimodule_exit(ct82c710_exit);
242