aboutsummaryrefslogtreecommitdiff
path: root/src/ec/google/chromeec/ec_mec.c
blob: 264a2560369bf2286274118374fe0c4b3b4f5fcc (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
/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2015 Google Inc.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <arch/io.h>
#include "ec.h"
#include "ec_commands.h"

enum {
	/* 8-bit access */
	ACCESS_TYPE_BYTE = 0x0,
	/* 16-bit access */
	ACCESS_TYPE_WORD = 0x1,
	/* 32-bit access */
	ACCESS_TYPE_LONG = 0x2,
	/*
	 * 32-bit access, read or write of MEC_EMI_EC_DATA_B3 causes the
	 * EC data register to be incremented.
	 */
	ACCESS_TYPE_LONG_AUTO_INCREMENT = 0x3,
};

/* EMI registers are relative to base */
#define MEC_EMI_BASE		0x800
#define MEC_EMI_HOST_TO_EC	(MEC_EMI_BASE + 0)
#define MEC_EMI_EC_TO_HOST	(MEC_EMI_BASE + 1)
#define MEC_EMI_EC_ADDRESS_B0	(MEC_EMI_BASE + 2)
#define MEC_EMI_EC_ADDRESS_B1	(MEC_EMI_BASE + 3)
#define MEC_EMI_EC_DATA_B0	(MEC_EMI_BASE + 4)
#define MEC_EMI_EC_DATA_B1	(MEC_EMI_BASE + 5)
#define MEC_EMI_EC_DATA_B2 	(MEC_EMI_BASE + 6)
#define MEC_EMI_EC_DATA_B3	(MEC_EMI_BASE + 7)

/*
 * cros_ec_lpc_mec_emi_write_address
 *
 * Initialize EMI read / write at a given address.
 *
 * @addr:        Starting read / write address
 * @access_mode: Type of access, typically 32-bit auto-increment
 */
static void mec_emi_write_address(u16 addr, u8 access_mode)
{
	/* Address relative to start of EMI range */
	addr -= MEC_EMI_RANGE_START;
	outb((addr & 0xfc) | access_mode, MEC_EMI_EC_ADDRESS_B0);
	outb((addr >> 8) & 0x7f, MEC_EMI_EC_ADDRESS_B1);
}

/*
 * mec_io_bytes - Read / write bytes to MEC EMI port
 *
 * @write:   1 on write operation, 0 on read
 * @port:    Base read / write address
 * @length:  Number of bytes to read / write
 * @buf:     Destination / source buffer
 * @csum:    Optional parameter, sums data transferred
 *
 */
void mec_io_bytes(int write, u16 port, unsigned int length, u8 *buf, u8 *csum)
{
	int i = 0;
	int io_addr;
	u8 access_mode, new_access_mode;

	if (length == 0)
		return;

	/*
	 * Long access cannot be used on misaligned data since reading B0 loads
	 * the data register and writing B3 flushes it.
	 */
	if (port & 0x3)
		access_mode = ACCESS_TYPE_BYTE;
	else
		access_mode = ACCESS_TYPE_LONG_AUTO_INCREMENT;

	/* Initialize I/O at desired address */
	mec_emi_write_address(port, access_mode);

	/* Skip bytes in case of misaligned port */
	io_addr = MEC_EMI_EC_DATA_B0 + (port & 0x3);
	while (i < length) {
		while (io_addr <= MEC_EMI_EC_DATA_B3) {
			if (write)
				outb(buf[i], io_addr++);
			else
				buf[i] = inb(io_addr++);
			if (csum)
				*csum += buf[i];

			port++;
			/* Extra bounds check in case of misaligned length */
			if (++i == length)
				return;
		}

		/*
		 * Use long auto-increment access except for misaligned write,
		 * since writing B3 triggers the flush.
		 */
		if (length - i < 4 && write)
			new_access_mode = ACCESS_TYPE_BYTE;
		else
			new_access_mode = ACCESS_TYPE_LONG_AUTO_INCREMENT;
		if (new_access_mode != access_mode ||
		    access_mode != ACCESS_TYPE_LONG_AUTO_INCREMENT) {
			access_mode = new_access_mode;
			mec_emi_write_address(port, access_mode);
		}

		/* Access [B0, B3] on each loop pass */
		io_addr = MEC_EMI_EC_DATA_B0;
	}
}