summaryrefslogtreecommitdiff
path: root/src/drivers/hdi/ps2/ps2mouse.c
blob: 766e737cc57d61394aee21aa1aba1dfafa1a2fd6 (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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#include "ps2mouse.h"

#include "stdio.h"

// status register on command port (read)
#define STATUS_REG_OUTPUT_BUF_FULL 0x01
#define STATUS_REG_INPUT_BUF_FULL 0x02
#define STATUS_REG_OUTPUT_BUF2_FULL 0x20

// status control register flags
#define STATUS_CONTROL_CONFIG_ENABLE_IRQ_PORT2 0x02
#define STATUS_CONTROL_CONFIG_CLOCK_PORT2 0x20

// generic PS/2 commands
#define COMMAND_GET_STATE 0x20
#define COMMAND_SET_STATE 0x60
#define COMMAND_DISABLE_PORT2 0xA7
#define COMMAND_ENABLE_PORT2 0xA8
#define COMMAND_SEND_TO_PORT2 0xD4

// mouse commands
#define MOUSE_COMMAND_GET_DEVICE_ID 0xF2
#define MOUSE_COMMAND_SET_SAMPLE_RATE 0xF3
#define MOUSE_COMMAND_ENABLE_DATAREPORTING 0xF4
#define MOUSE_COMMAND_SET_DEFAULTS 0xF6
#define DEFAULT_NOF_PACKETS 3

// mouse data packet one
#define MOUSE_BYTE1_LEFT_BUTTON 0x01
#define MOUSE_BYTE1_RIGHT_BUTTON 0x02
#define MOUSE_BYTE1_MIDDLE_BUTTON 0x04
#define MOUSE_BYTE1_ID_BIT 0x08
#define MOUSE_BYTE1_X_SIGN 0x10
#define MOUSE_BYTE1_Y_SIGN 0x20
#define MOUSE_BYTE1_X_OVERFLOW 0x40
#define MOUSE_BYTE1_Y_OVERFLOW 0x80

#undef DEBUG

static uint8_t read_data( ps2mouse_t *ps2mouse )
{
	while( ( port8_read( &ps2mouse->command_port ) & STATUS_REG_OUTPUT_BUF_FULL ) == 0 ) { }
	return port8_read( &ps2mouse->data_port );
}

static void send_command( ps2mouse_t *ps2mouse, uint8_t command )
{
	while( port8_read( &ps2mouse->command_port ) & STATUS_REG_INPUT_BUF_FULL ) { }
	port8_write( &ps2mouse->command_port, command );
}

static void write_data( ps2mouse_t *ps2mouse, uint8_t data )
{
	while( port8_read( &ps2mouse->command_port ) & STATUS_REG_INPUT_BUF_FULL ) { }
	port8_write( &ps2mouse->data_port, data );
}

static void read_ack( ps2mouse_t *ps2mouse )
{
	uint8_t data = read_data( ps2mouse );
	if( data == 0xFA ) {
		return;
	} else {
		printf( "ERR PS/2 mouse: acknoledgment failed 0x%X\n", data );
	}
}

static void set_sample_rate( ps2mouse_t *ps2mouse, uint8_t samples )
{
	send_command( ps2mouse, COMMAND_SEND_TO_PORT2 );
	write_data( ps2mouse, MOUSE_COMMAND_SET_SAMPLE_RATE );
	read_ack( ps2mouse );

	send_command( ps2mouse, COMMAND_SEND_TO_PORT2 );
	write_data( ps2mouse, samples );
	read_ack( ps2mouse );
}

static ps2mouse_vtable_t const ps2mouse_vtable = {
	{
		{
			ps2mouse_activate,
			ps2mouse_deactivate,
			driver_deinit,
			ps2mouse_print_name,
			ps2mouse_print_info
		},
		mouse_set_resolution,
		mouse_set_position
	}
};

void ps2mouse_init( ps2mouse_t *ps2mouse, mouse_event_handler_t handler, interrupt_t *interrupt, void *context )
{
	memset( ps2mouse, 0, sizeof( ps2mouse_t ) );

	mouse_init( (mouse_t *)ps2mouse, handler, interrupt, context );

	ps2mouse->nof_packets = DEFAULT_NOF_PACKETS;

	port8_init( &ps2mouse->command_port, 0x64 );
	port8_init( &ps2mouse->data_port, 0x60 );

	((driver_t *)ps2mouse)->vtable = (driver_vtable_t *)&ps2mouse_vtable;
}

void ps2mouse_activate( void *obj )
{
	puts( "Activating driver for PS/2 mouse.." );

	driver_t *driver = obj;
	ps2mouse_t *ps2mouse = obj;

	// enable port 2
	send_command( ps2mouse, COMMAND_ENABLE_PORT2 );
	
	// enable interrupts for port 2
	send_command( ps2mouse, COMMAND_GET_STATE );
	uint8_t status = read_data( ps2mouse );
	status |= STATUS_CONTROL_CONFIG_ENABLE_IRQ_PORT2;
	status &= ~STATUS_CONTROL_CONFIG_CLOCK_PORT2;
	send_command( ps2mouse, COMMAND_SET_STATE );
	write_data( ps2mouse, status );

	// detecting mouse wheel (and 4th data packet)
	set_sample_rate( ps2mouse, 200 );
	set_sample_rate( ps2mouse, 100 );
	set_sample_rate( ps2mouse, 80 );
	send_command( ps2mouse, COMMAND_SEND_TO_PORT2 );
	write_data( ps2mouse, MOUSE_COMMAND_GET_DEVICE_ID );
	read_ack( ps2mouse );
	uint8_t id = read_data( ps2mouse );
	printf( "PS/2 mouse model id is 0x%X\n", id );
	if( id == 0x03 ) {
		ps2mouse->nof_packets = 4;
	}

	// set mouse defaults
//	send_command( ps2mouse, COMMAND_SEND_TO_PORT2 );
//	write_data( ps2mouse, MOUSE_COMMAND_SET_DEFAULTS );

	interrupt_handler_init( &ps2mouse->interrupt_handler, IRQ_BASE + 0x0C, driver->interrupt, ps2mouse_handle_interrupt, obj );
	interrupts_register_interrupt_handler( ps2mouse->interrupt_handler );

	// enable mouse on second port
	send_command( ps2mouse, COMMAND_SEND_TO_PORT2 );
	write_data( ps2mouse, MOUSE_COMMAND_ENABLE_DATAREPORTING );
	read_ack( ps2mouse );
}

void ps2mouse_deactivate( void *obj )
{
	puts( "Dectivating driver for PS/2 mouse.." );

	ps2mouse_t *ps2mouse = obj;

	send_command( ps2mouse, COMMAND_DISABLE_PORT2 );
}

uint32_t ps2mouse_handle_interrupt( interrupt_handler_t *handler, uint32_t esp )
{
	ps2mouse_t *ps2mouse = (ps2mouse_t *)handler->driver;

	uint8_t status = port8_read( &ps2mouse->command_port );
	if( !( status & STATUS_REG_OUTPUT_BUF2_FULL ) ) {
		return esp;
	}

	ps2mouse->buf[ps2mouse->offset] = port8_read( &ps2mouse->data_port );

#ifdef DEBUG
	printf( "PS2 MOUSE: %d 0x%X\n", ps2mouse->offset, ps2mouse->buf[ps2mouse->offset] );
#endif
	
	switch( ps2mouse->offset ) {
		case 0:
			if( ps2mouse->buf[0] & MOUSE_BYTE1_ID_BIT ) {
				for( int i = 0; i < 3; i++ ) {
					if( ( ps2mouse->buf[0] & ( 0x01 << i ) ) != ( ps2mouse->buttons & ( 0x01 << i ) ) ) {
						mouse_event_t event;
						event.button = i + 1;
						if( ps2mouse->buttons & ( 0x01 << i ) ) {
							event.type = MOUSE_EVENT_TYPE_BUTTON_UP;
						} else {
							event.type = MOUSE_EVENT_TYPE_BUTTON_DOWN;
						}
						event.cursor_x = ps2mouse->base.cursor_x;
						event.cursor_y = ps2mouse->base.cursor_y;
						ps2mouse->base.handler( &event, ps2mouse->base.base.context );
					}
				}
				ps2mouse->buttons = ps2mouse->buf[0];
			}
			break;

		case 1:
			// delay evaluation of move events till second byte
			break;

		case 2:
			if( ps2mouse->buf[1] !=0 || ps2mouse->buf[2] != 0 ) {

				mouse_event_t event;
				event.type = MOUSE_EVENT_TYPE_MOVE;
				event.old_cursor_x = ps2mouse->base.cursor_x;
				event.old_cursor_y = ps2mouse->base.cursor_y;

				ps2mouse->base.cursor_x += (int8_t)ps2mouse->buf[1];
				ps2mouse->base.cursor_y -= (int8_t)ps2mouse->buf[2];
				if( ps2mouse->base.cursor_x < 0 ) {
					ps2mouse->base.cursor_x = 0;
				}
				if( ps2mouse->base.cursor_x > ps2mouse->base.res_x - 1 ) {
					ps2mouse->base.cursor_x = ps2mouse->base.res_x - 1;
				}
				if( ps2mouse->base.cursor_y < 0 ) {
					ps2mouse->base.cursor_y = 0;
				}
				if( ps2mouse->base.cursor_y > ps2mouse->base.res_y - 1 ) {
					ps2mouse->base.cursor_y = ps2mouse->base.res_y - 1;
				}

				event.cursor_x = ps2mouse->base.cursor_x;
				event.cursor_y = ps2mouse->base.cursor_y;

				ps2mouse->base.handler( &event, ps2mouse->base.base.context );
			}

			break;

		case 3:
			// TODO: read fourth data packet, but don't handle
			// it yet
			break;
	}

	// advance offset
	ps2mouse->offset = ( ps2mouse->offset + 1 ) % ps2mouse->nof_packets;

	return esp;
}

void ps2mouse_print_name( void *obj )
{
	puts( "PS/2 mouse driver" );
}

void ps2mouse_print_info( void *obj )
{
	// TODO: print IRQ, mouse type, wheel, buttons, etc.
}