summaryrefslogtreecommitdiff
path: root/common/i8042.c
blob: 5c3b1bbf0658704a2f0e054baaae3a86c700ea61 (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
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * i8042 interface to host
 *
 * i8042 commands are processed by keyboard.c.
 */

#include "common.h"
#include "config.h"
#include "console.h"
#include "i8042.h"
#include "keyboard.h"
#include "lpc.h"
#include "queue.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPRINTF(format, args...) cprintf(CC_I8042, format, ## args)

static int i8042_irq_enabled;

/*
 * Mutex to control write access to the to-host buffer head.  Don't need to
 * mutex the tail because reads are only done in one place.
 */
static struct mutex to_host_mutex;

static uint8_t to_host_buffer[16];
static struct queue to_host = {
	.buf_bytes  = sizeof(to_host_buffer),
	.unit_bytes = sizeof(uint8_t),
	.buf        = to_host_buffer,
};

/* Queue command/data from the host */
enum {
	HOST_COMMAND = 0,
	HOST_DATA,
};
struct host_byte {
	uint8_t type;
	uint8_t byte;
};

/* 4 is big enough for all i8042 commands */
static uint8_t from_host_buffer[4 * sizeof(struct host_byte)];
static struct queue from_host = {
	.buf_bytes  = sizeof(from_host_buffer),
	.unit_bytes = sizeof(struct host_byte),
	.buf        = from_host_buffer,
};

void i8042_flush_buffer()
{
	mutex_lock(&to_host_mutex);
	queue_reset(&to_host);
	mutex_unlock(&to_host_mutex);
	lpc_keyboard_clear_buffer();
}

void i8042_receive(int data, int is_cmd)
{
	struct host_byte h;

	h.type = is_cmd ? HOST_COMMAND : HOST_DATA;
	h.byte = data;
	queue_add_units(&from_host, &h, 1);
	task_wake(TASK_ID_I8042CMD);
}

void i8042_enable_keyboard_irq(int enable)
{
	i8042_irq_enabled = enable;
	if (enable)
		lpc_keyboard_resume_irq();
}

static void i8042_handle_from_host(void)
{
	struct host_byte h;
	int ret_len;
	uint8_t output[MAX_SCAN_CODE_LEN];

	while (queue_remove_unit(&from_host, &h)) {
		if (h.type == HOST_COMMAND)
			ret_len = handle_keyboard_command(h.byte, output);
		else
			ret_len = handle_keyboard_data(h.byte, output);

		i8042_send_to_host(ret_len, output);
	}
}

void i8042_command_task(void)
{
	while (1) {
		/* Wait for next host read/write */
		task_wait_event(-1);

		while (1) {
			uint8_t chr;

			/* Handle command/data write from host */
			i8042_handle_from_host();

			/* Check if we have data to send to host */
			if (queue_is_empty(&to_host))
				break;

			/* Host interface must have space */
			if (lpc_keyboard_has_char())
				break;

			/* Get a char from buffer. */
			kblog_put('k', to_host.head);
			queue_remove_unit(&to_host, &chr);
			kblog_put('K', chr);

			/* Write to host. */
			lpc_keyboard_put_char(chr, i8042_irq_enabled);
		}
	}
}

void i8042_send_to_host(int len, const uint8_t *bytes)
{
	int i;

	for (i = 0; i < len; i++)
		kblog_put('s', bytes[i]);

	/* Enqueue output data if there's space */
	mutex_lock(&to_host_mutex);
	if (queue_has_space(&to_host, len)) {
		kblog_put('t', to_host.tail);
		queue_add_units(&to_host, bytes, len);
	}
	mutex_unlock(&to_host_mutex);

	/* Wake up the task to move from queue to host */
	task_wake(TASK_ID_I8042CMD);
}