]>
Commit | Line | Data |
---|---|---|
c8d3328a HT |
1 | /* |
2 | * Chromium OS cros_ec driver - LPC interface | |
3 | * | |
4 | * Copyright (c) 2012 The Chromium OS Authors. | |
c8d3328a | 5 | * |
1a459660 | 6 | * SPDX-License-Identifier: GPL-2.0+ |
c8d3328a HT |
7 | */ |
8 | ||
9 | /* | |
10 | * The Matrix Keyboard Protocol driver handles talking to the keyboard | |
11 | * controller chip. Mostly this is for keyboard functions, but some other | |
12 | * things have slipped in, so we provide generic services to talk to the | |
13 | * KBC. | |
14 | */ | |
15 | ||
16 | #include <common.h> | |
72a38e06 | 17 | #include <dm.h> |
c8d3328a HT |
18 | #include <command.h> |
19 | #include <cros_ec.h> | |
20 | #include <asm/io.h> | |
21 | ||
22 | #ifdef DEBUG_TRACE | |
23 | #define debug_trace(fmt, b...) debug(fmt, ##b) | |
24 | #else | |
25 | #define debug_trace(fmt, b...) | |
26 | #endif | |
27 | ||
28 | static int wait_for_sync(struct cros_ec_dev *dev) | |
29 | { | |
30 | unsigned long start; | |
31 | ||
32 | start = get_timer(0); | |
33 | while (inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK) { | |
34 | if (get_timer(start) > 1000) { | |
35 | debug("%s: Timeout waiting for CROS_EC sync\n", | |
36 | __func__); | |
37 | return -1; | |
38 | } | |
39 | } | |
40 | ||
41 | return 0; | |
42 | } | |
43 | ||
72a38e06 SG |
44 | int cros_ec_lpc_command(struct udevice *udev, uint8_t cmd, int cmd_version, |
45 | const uint8_t *dout, int dout_len, | |
46 | uint8_t **dinp, int din_len) | |
47 | { | |
48 | struct cros_ec_dev *dev = dev_get_uclass_priv(udev); | |
c8d3328a HT |
49 | const int cmd_addr = EC_LPC_ADDR_HOST_CMD; |
50 | const int data_addr = EC_LPC_ADDR_HOST_DATA; | |
51 | const int args_addr = EC_LPC_ADDR_HOST_ARGS; | |
52 | const int param_addr = EC_LPC_ADDR_HOST_PARAM; | |
53 | ||
54 | struct ec_lpc_host_args args; | |
55 | uint8_t *d; | |
56 | int csum; | |
57 | int i; | |
58 | ||
f1269925 | 59 | if (dout_len > EC_PROTO2_MAX_PARAM_SIZE) { |
c8d3328a HT |
60 | debug("%s: Cannot send %d bytes\n", __func__, dout_len); |
61 | return -1; | |
62 | } | |
63 | ||
64 | /* Fill in args */ | |
65 | args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; | |
66 | args.command_version = cmd_version; | |
67 | args.data_size = dout_len; | |
68 | ||
69 | /* Calculate checksum */ | |
70 | csum = cmd + args.flags + args.command_version + args.data_size; | |
71 | for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) | |
72 | csum += *d; | |
73 | ||
74 | args.checksum = (uint8_t)csum; | |
75 | ||
76 | if (wait_for_sync(dev)) { | |
77 | debug("%s: Timeout waiting ready\n", __func__); | |
78 | return -1; | |
79 | } | |
80 | ||
81 | /* Write args */ | |
82 | for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) | |
83 | outb(*d, args_addr + i); | |
84 | ||
85 | /* Write data, if any */ | |
86 | debug_trace("cmd: %02x, ver: %02x", cmd, cmd_version); | |
87 | for (i = 0, d = (uint8_t *)dout; i < dout_len; i++, d++) { | |
88 | outb(*d, param_addr + i); | |
89 | debug_trace("%02x ", *d); | |
90 | } | |
91 | ||
92 | outb(cmd, cmd_addr); | |
93 | debug_trace("\n"); | |
94 | ||
95 | if (wait_for_sync(dev)) { | |
96 | debug("%s: Timeout waiting for response\n", __func__); | |
97 | return -1; | |
98 | } | |
99 | ||
100 | /* Check result */ | |
101 | i = inb(data_addr); | |
102 | if (i) { | |
103 | debug("%s: CROS_EC result code %d\n", __func__, i); | |
104 | return -i; | |
105 | } | |
106 | ||
107 | /* Read back args */ | |
108 | for (i = 0, d = (uint8_t *)&args; i < sizeof(args); i++, d++) | |
109 | *d = inb(args_addr + i); | |
110 | ||
111 | /* | |
112 | * If EC didn't modify args flags, then somehow we sent a new-style | |
113 | * command to an old EC, which means it would have read its params | |
114 | * from the wrong place. | |
115 | */ | |
116 | if (!(args.flags & EC_HOST_ARGS_FLAG_TO_HOST)) { | |
117 | debug("%s: CROS_EC protocol mismatch\n", __func__); | |
118 | return -EC_RES_INVALID_RESPONSE; | |
119 | } | |
120 | ||
121 | if (args.data_size > din_len) { | |
122 | debug("%s: CROS_EC returned too much data %d > %d\n", | |
123 | __func__, args.data_size, din_len); | |
124 | return -EC_RES_INVALID_RESPONSE; | |
125 | } | |
126 | ||
127 | /* Read data, if any */ | |
128 | for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) { | |
129 | *d = inb(param_addr + i); | |
130 | debug_trace("%02x ", *d); | |
131 | } | |
132 | debug_trace("\n"); | |
133 | ||
134 | /* Verify checksum */ | |
135 | csum = cmd + args.flags + args.command_version + args.data_size; | |
136 | for (i = 0, d = (uint8_t *)dev->din; i < args.data_size; i++, d++) | |
137 | csum += *d; | |
138 | ||
139 | if (args.checksum != (uint8_t)csum) { | |
140 | debug("%s: CROS_EC response has invalid checksum\n", __func__); | |
141 | return -EC_RES_INVALID_CHECKSUM; | |
142 | } | |
143 | *dinp = dev->din; | |
144 | ||
145 | /* Return actual amount of data received */ | |
146 | return args.data_size; | |
147 | } | |
148 | ||
149 | /** | |
150 | * Initialize LPC protocol. | |
151 | * | |
152 | * @param dev CROS_EC device | |
153 | * @param blob Device tree blob | |
154 | * @return 0 if ok, -1 on error | |
155 | */ | |
156 | int cros_ec_lpc_init(struct cros_ec_dev *dev, const void *blob) | |
157 | { | |
158 | int byte, i; | |
159 | ||
160 | /* See if we can find an EC at the other end */ | |
161 | byte = 0xff; | |
162 | byte &= inb(EC_LPC_ADDR_HOST_CMD); | |
163 | byte &= inb(EC_LPC_ADDR_HOST_DATA); | |
f1269925 | 164 | for (i = 0; i < EC_PROTO2_MAX_PARAM_SIZE && (byte == 0xff); i++) |
c8d3328a HT |
165 | byte &= inb(EC_LPC_ADDR_HOST_PARAM + i); |
166 | if (byte == 0xff) { | |
167 | debug("%s: CROS_EC device not found on LPC bus\n", | |
168 | __func__); | |
169 | return -1; | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | /* | |
176 | * Test if LPC command args are supported. | |
177 | * | |
178 | * The cheapest way to do this is by looking for the memory-mapped | |
179 | * flag. This is faster than sending a new-style 'hello' command and | |
180 | * seeing whether the EC sets the EC_HOST_ARGS_FLAG_FROM_HOST flag | |
181 | * in args when it responds. | |
182 | */ | |
72a38e06 | 183 | static int cros_ec_lpc_check_version(struct udevice *dev) |
c8d3328a HT |
184 | { |
185 | if (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) == 'E' && | |
186 | inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) | |
187 | == 'C' && | |
188 | (inb(EC_LPC_ADDR_MEMMAP + | |
189 | EC_MEMMAP_HOST_CMD_FLAGS) & | |
190 | EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED)) { | |
4ff9b461 | 191 | return 0; |
c8d3328a | 192 | } |
c8d3328a | 193 | |
4ff9b461 VB |
194 | printf("%s: ERROR: old EC interface not supported\n", __func__); |
195 | return -1; | |
c8d3328a | 196 | } |
72a38e06 | 197 | |
72a38e06 SG |
198 | static int cros_ec_probe(struct udevice *dev) |
199 | { | |
200 | return cros_ec_register(dev); | |
201 | } | |
202 | ||
203 | static struct dm_cros_ec_ops cros_ec_ops = { | |
204 | .command = cros_ec_lpc_command, | |
205 | .check_version = cros_ec_lpc_check_version, | |
206 | }; | |
207 | ||
208 | static const struct udevice_id cros_ec_ids[] = { | |
3fbb7871 | 209 | { .compatible = "google,cros-ec-lpc" }, |
72a38e06 SG |
210 | { } |
211 | }; | |
212 | ||
213 | U_BOOT_DRIVER(cros_ec_lpc) = { | |
3fbb7871 | 214 | .name = "cros_ec_lpc", |
72a38e06 SG |
215 | .id = UCLASS_CROS_EC, |
216 | .of_match = cros_ec_ids, | |
217 | .probe = cros_ec_probe, | |
218 | .ops = &cros_ec_ops, | |
219 | }; |