aboutsummaryrefslogtreecommitdiff
path: root/drivers/input/serio/maceps2.c
blob: 629e15089c219e28c1b0746f401461951057ed4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// SPDX-License-Identifier: GPL-2.0-only
/*
 * SGI O2 MACE PS2 controller driver for linux
 *
 * Copyright (C) 2002 Vivien Chappelier
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/err.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/ip32/mace.h>
#include <asm/ip32/ip32_ints.h>

MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org");
MODULE_DESCRIPTION("SGI O2 MACE PS2 controller driver");
MODULE_LICENSE("GPL");

#define MACE_PS2_TIMEOUT 10000 /* in 50us unit */

#define PS2_STATUS_CLOCK_SIGNAL  BIT(0) /* external clock signal */
#define PS2_STATUS_CLOCK_INHIBIT BIT(1) /* clken output signal */
#define PS2_STATUS_TX_INPROGRESS BIT(2) /* transmission in progress */
#define PS2_STATUS_TX_EMPTY      BIT(3) /* empty transmit buffer */
#define PS2_STATUS_RX_FULL       BIT(4) /* full receive buffer */
#define PS2_STATUS_RX_INPROGRESS BIT(5) /* reception in progress */
#define PS2_STATUS_ERROR_PARITY  BIT(6) /* parity error */
#define PS2_STATUS_ERROR_FRAMING BIT(7) /* framing error */

#define PS2_CONTROL_TX_CLOCK_DISABLE BIT(0) /* inhibit clock signal after TX */
#define PS2_CONTROL_TX_ENABLE        BIT(1) /* transmit enable */
#define PS2_CONTROL_TX_INT_ENABLE    BIT(2) /* enable transmit interrupt */
#define PS2_CONTROL_RX_INT_ENABLE    BIT(3) /* enable receive interrupt */
#define PS2_CONTROL_RX_CLOCK_ENABLE  BIT(4) /* pause reception if set to 0 */
#define PS2_CONTROL_RESET            BIT(5) /* reset */

struct maceps2_data {
	struct mace_ps2port *port;
	int irq;
};

static struct maceps2_data port_data[2];
static struct serio *maceps2_port[2];
static struct platform_device *maceps2_device;

static int maceps2_write(struct serio *dev, unsigned char val)
{
	struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port;
	unsigned int timeout = MACE_PS2_TIMEOUT;

	do {
		if (port->status & PS2_STATUS_TX_EMPTY) {
			port->tx = val;
			return 0;
		}
		udelay(50);
	} while (timeout--);

	return -1;
}

static irqreturn_t maceps2_interrupt(int irq, void *dev_id)
{
	struct serio *dev = dev_id;
	struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port;
	unsigned long byte;

	if (port->status & PS2_STATUS_RX_FULL) {
		byte = port->rx;
		serio_interrupt(dev, byte & 0xff, 0);
        }

	return IRQ_HANDLED;
}

static int maceps2_open(struct serio *dev)
{
	struct maceps2_data *data = (struct maceps2_data *)dev->port_data;

	if (request_irq(data->irq, maceps2_interrupt, 0, "PS2 port", dev)) {
		printk(KERN_ERR "Could not allocate PS/2 IRQ\n");
		return -EBUSY;
	}

	/* Reset port */
	data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET;
	udelay(100);

        /* Enable interrupts */
	data->port->control = PS2_CONTROL_RX_CLOCK_ENABLE |
			      PS2_CONTROL_TX_ENABLE |
			      PS2_CONTROL_RX_INT_ENABLE;

	return 0;
}

static void maceps2_close(struct serio *dev)
{
	struct maceps2_data *data = (struct maceps2_data *)dev->port_data;

	data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET;
	udelay(100);
	free_irq(data->irq, dev);
}


static struct serio *maceps2_allocate_port(int idx)
{
	struct serio *serio;

	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
	if (serio) {
		serio->id.type		= SERIO_8042;
		serio->write		= maceps2_write;
		serio->open		= maceps2_open;
		serio->close		= maceps2_close;
		snprintf(serio->name, sizeof(serio->name), "MACE PS/2 port%d", idx);
		snprintf(serio->phys, sizeof(serio->phys), "mace/serio%d", idx);
		serio->port_data	= &port_data[idx];
		serio->dev.parent	= &maceps2_device->dev;
	}

	return serio;
}

static int maceps2_probe(struct platform_device *dev)
{
	maceps2_port[0] = maceps2_allocate_port(0);
	maceps2_port[1] = maceps2_allocate_port(1);
	if (!maceps2_port[0] || !maceps2_port[1]) {
		kfree(maceps2_port[0]);
		kfree(maceps2_port[1]);
		return -ENOMEM;
	}

	serio_register_port(maceps2_port[0]);
	serio_register_port(maceps2_port[1]);

	return 0;
}

static int maceps2_remove(struct platform_device *dev)
{
	serio_unregister_port(maceps2_port[0]);
	serio_unregister_port(maceps2_port[1]);

	return 0;
}

static struct platform_driver maceps2_driver = {
	.driver		= {
		.name	= "maceps2",
	},
	.probe		= maceps2_probe,
	.remove		= maceps2_remove,
};

static int __init maceps2_init(void)
{
	int error;

	error = platform_driver_register(&maceps2_driver);
	if (error)
		return error;

	maceps2_device = platform_device_alloc("maceps2", -1);
	if (!maceps2_device) {
		error = -ENOMEM;
		goto err_unregister_driver;
	}

	port_data[0].port = &mace->perif.ps2.keyb;
	port_data[0].irq  = MACEISA_KEYB_IRQ;
	port_data[1].port = &mace->perif.ps2.mouse;
	port_data[1].irq  = MACEISA_MOUSE_IRQ;

	error = platform_device_add(maceps2_device);
	if (error)
		goto err_free_device;

	return 0;

 err_free_device:
	platform_device_put(maceps2_device);
 err_unregister_driver:
	platform_driver_unregister(&maceps2_driver);
	return error;
}

static void __exit maceps2_exit(void)
{
	platform_device_unregister(maceps2_device);
	platform_driver_unregister(&maceps2_driver);
}

module_init(maceps2_init);
module_exit(maceps2_exit);