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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
|
From c88b371cb56046f79710143866562e119a8318ca Mon Sep 17 00:00:00 2001
From: Matt DeVillier <matt.devillier@gmail.com>
Date: Fri, 11 Jul 2025 16:11:47 -0500
Subject: [PATCH 40/43] ec/lenovo: Add support for MEC1653 EC
Add support for the MEC1653 EC as used by the Thinkpad T480/480s.
Change-Id: If82a7d27eb3163f51565c0c6e60cab60753611a7
Signed-off-by: Matt DeVillier <matt.devillier@gmail.com>
---
src/ec/lenovo/mec1653/Kconfig | 11 ++
src/ec/lenovo/mec1653/Makefile.mk | 8 ++
src/ec/lenovo/mec1653/mec1653.c | 207 ++++++++++++++++++++++++++++++
src/ec/lenovo/mec1653/mec1653.h | 98 ++++++++++++++
4 files changed, 324 insertions(+)
create mode 100644 src/ec/lenovo/mec1653/Kconfig
create mode 100644 src/ec/lenovo/mec1653/Makefile.mk
create mode 100644 src/ec/lenovo/mec1653/mec1653.c
create mode 100644 src/ec/lenovo/mec1653/mec1653.h
diff --git a/src/ec/lenovo/mec1653/Kconfig b/src/ec/lenovo/mec1653/Kconfig
new file mode 100644
index 0000000000..858f13897b
--- /dev/null
+++ b/src/ec/lenovo/mec1653/Kconfig
@@ -0,0 +1,11 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+config EC_LENOVO_MEC1653
+ bool
+
+if EC_LENOVO_MEC1653
+
+config MEC1653_SEND_DEBUG_UNLOCK
+ bool
+
+endif
diff --git a/src/ec/lenovo/mec1653/Makefile.mk b/src/ec/lenovo/mec1653/Makefile.mk
new file mode 100644
index 0000000000..4cb4b20cbb
--- /dev/null
+++ b/src/ec/lenovo/mec1653/Makefile.mk
@@ -0,0 +1,8 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+ifeq ($(CONFIG_EC_LENOVO_MEC1653),y)
+
+bootblock-y += mec1653.c
+ramstage-y += mec1653.c
+
+endif
diff --git a/src/ec/lenovo/mec1653/mec1653.c b/src/ec/lenovo/mec1653/mec1653.c
new file mode 100644
index 0000000000..098ae47425
--- /dev/null
+++ b/src/ec/lenovo/mec1653/mec1653.c
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <arch/io.h>
+#include "mec1653.h"
+
+#define MICROCHIP_CONFIGURATION_ENTRY_KEY 0x55
+#define MICROCHIP_CONFIGURATION_EXIT_KEY 0xaa
+
+#define UART_PORT 0x3f8
+#define UART_IRQ 4
+
+// RW unlock key for EC version N24HT37W
+const uint8_t debug_rw_key[8] = { 0x7a, 0x41, 0xb1, 0x49, 0xfe, 0x21, 0x01, 0xcf };
+
+void microchip_pnp_enter_conf_state(uint16_t port)
+{
+ outb(MICROCHIP_CONFIGURATION_ENTRY_KEY, port);
+}
+
+void microchip_pnp_exit_conf_state(uint16_t port)
+{
+ outb(MICROCHIP_CONFIGURATION_EXIT_KEY, port);
+}
+
+uint8_t pnp_read(uint16_t port, uint8_t index)
+{
+ outb(index, port);
+ return inb(port + 1);
+}
+
+uint32_t pnp_read_le32(uint16_t port, uint8_t index)
+{
+ return (uint32_t) pnp_read(port, index) |
+ (uint32_t) pnp_read(port, index + 1) << 8 |
+ (uint32_t) pnp_read(port, index + 2) << 16 |
+ (uint32_t) pnp_read(port, index + 3) << 24;
+}
+
+void pnp_write(uint16_t port, uint8_t index, uint8_t value)
+{
+ outb(index, port);
+ outb(value, port + 1);
+}
+
+void pnp_write_le32(uint16_t port, uint8_t index, uint32_t value)
+{
+ pnp_write(port, index, value & 0xff);
+ pnp_write(port, index + 1, value >> 8 & 0xff);
+ pnp_write(port, index + 2, value >> 16 & 0xff);
+ pnp_write(port, index + 3, value >> 24 & 0xff);
+}
+
+static void ecN_clear_out_queue(uint16_t cmd_port, uint16_t data_port)
+{
+ while (inb(cmd_port) & EC_OBF)
+ inb(data_port);
+}
+
+static void ecN_wait_to_send(uint16_t cmd_port, uint16_t data_port)
+{
+ while (inb(cmd_port) & EC_IBF)
+ ;
+}
+
+static void ecN_wait_to_recv(uint16_t cmd_port, uint16_t data_port)
+{
+ while (!(inb(cmd_port) & EC_OBF))
+ ;
+}
+
+uint8_t ecN_read(uint16_t cmd_port, uint16_t data_port, uint8_t addr)
+{
+ ecN_clear_out_queue(cmd_port, data_port);
+ ecN_wait_to_send(cmd_port, data_port);
+ outb(EC_READ, cmd_port);
+ ecN_wait_to_send(cmd_port, data_port);
+ outb(addr, data_port);
+ ecN_wait_to_recv(cmd_port, data_port);
+ return inb(data_port);
+}
+
+void ecN_write(uint16_t cmd_port, uint16_t data_port, uint8_t addr, uint8_t val)
+{
+ ecN_clear_out_queue(cmd_port, data_port);
+ ecN_wait_to_send(cmd_port, data_port);
+ outb(EC_WRITE, cmd_port);
+ ecN_wait_to_send(cmd_port, data_port);
+ outb(addr, data_port);
+ ecN_wait_to_send(cmd_port, data_port);
+ outb(val, data_port);
+}
+
+uint8_t eeprom_read(uint16_t addr)
+{
+ ecN_clear_out_queue(EC2_CMD, EC2_DATA);
+ ecN_wait_to_send(EC2_CMD, EC2_DATA);
+ outl(1, EC2_CMD);
+ ecN_wait_to_send(EC2_CMD, EC2_DATA);
+ outl(addr, EC2_DATA);
+ ecN_wait_to_recv(EC2_CMD, EC2_DATA);
+ return inl(EC2_DATA);
+}
+
+void eeprom_write(uint16_t addr, uint8_t val)
+{
+ ecN_clear_out_queue(EC2_CMD, EC2_DATA);
+ ecN_wait_to_send(EC2_CMD, EC2_DATA);
+ outl(2, EC2_CMD);
+ ecN_wait_to_send(EC2_CMD, EC2_DATA);
+ outl((uint32_t) addr | (uint32_t) val << 16, EC2_DATA);
+ ecN_wait_to_recv(EC2_CMD, EC2_DATA);
+ inl(EC2_DATA);
+}
+
+uint16_t debug_loaded_keys(void)
+{
+ return (uint16_t) ec0_read(0x87) << 8 | (uint16_t) ec0_read(0x86);
+}
+
+static void debug_cmd(uint8_t cmd)
+{
+ ec0_write(EC_DEBUG_CMD, cmd);
+ while (ec0_read(EC_DEBUG_CMD) & 0x80)
+ ;
+}
+
+void debug_read_key(uint8_t i, uint8_t *key)
+{
+ debug_cmd(0x80 | (i & 0xf));
+ for (int j = 0; j < 8; ++j)
+ key[j] = ec0_read(0x3e + j);
+}
+
+void debug_write_key(uint8_t i, const uint8_t *key)
+{
+ for (int j = 0; j < 8; ++j)
+ ec0_write(0x3e + j, key[j]);
+ debug_cmd(0xc0 | (i & 0xf));
+}
+
+uint32_t debug_read_dword(uint32_t addr)
+{
+ ecN_clear_out_queue(EC3_CMD, EC3_DATA);
+ ecN_wait_to_send(EC3_CMD, EC3_DATA);
+ outl(addr << 8 | 0xE2, EC3_DATA);
+ ecN_wait_to_recv(EC3_CMD, EC3_DATA);
+ return inl(EC3_DATA);
+}
+
+void debug_write_dword(uint32_t addr, uint32_t val)
+{
+ ecN_clear_out_queue(EC3_CMD, EC3_DATA);
+ ecN_wait_to_send(EC3_CMD, EC3_DATA);
+ outl(addr << 8 | 0xEA, EC3_DATA);
+ ecN_wait_to_send(EC3_CMD, EC3_DATA);
+ outl(val, EC3_DATA);
+}
+
+static void configure_uart(uint16_t port, uint16_t iobase, uint8_t irqno)
+{
+ microchip_pnp_enter_conf_state(port);
+
+ // Select LPC I/F LDN
+ pnp_write(port, PNP_LDN_SELECT, LDN_LPCIF);
+ // Write UART BAR
+ pnp_write_le32(port, LPCIF_BAR_UART, (uint32_t) iobase << 16 | 0x8707);
+ // Set SIRQ4 to UART
+ pnp_write(port, LPCIF_SIRQ(irqno), LDN_UART);
+
+ // Configure UART LDN
+ pnp_write(port, PNP_LDN_SELECT, LDN_UART);
+ pnp_write(port, UART_ACTIVATE, 0x01);
+ pnp_write(port, UART_CONFIG_SELECT, 0x00);
+
+ microchip_pnp_exit_conf_state(port);
+
+ if (CONFIG(MEC1653_SEND_DEBUG_UNLOCK)) {
+ // Supply debug unlock key
+ debug_write_key(DEBUG_RW_KEY_IDX, debug_rw_key);
+
+ // Use debug writes to set UART_TX and UART_RX GPIOs
+ debug_write_dword(0xf0c400 + 0x110, 0x00001000);
+ debug_write_dword(0xf0c400 + 0x114, 0x00001000);
+ }
+}
+
+void bootblock_ec_init(void)
+{
+ // Tell EC via BIOS Debug Port 1 that the world isn't on fire
+
+ // Let the EC know that BIOS code is running
+ outb(0x11, 0x86);
+ outb(0x6e, 0x86);
+
+ // Enable accesses to EC1 interface
+ ec0_write(0, ec0_read(0) | 0x20);
+
+ // Reset LEDs to power on state
+ // (Without this warm reboot leaves LEDs off)
+ ec0_write(0x0c, 0x80);
+ ec0_write(0x0c, 0x07);
+ ec0_write(0x0c, 0x8a);
+
+ // Setup debug UART
+ if (CONFIG(CONSOLE_SERIAL))
+ configure_uart(EC_CFG_PORT, UART_PORT, UART_IRQ);
+}
diff --git a/src/ec/lenovo/mec1653/mec1653.h b/src/ec/lenovo/mec1653/mec1653.h
new file mode 100644
index 0000000000..7dc4c1f635
--- /dev/null
+++ b/src/ec/lenovo/mec1653/mec1653.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_LENOVO_MEC1653_H
+#define EC_LENOVO_MEC1653_H
+
+// EC configuration base address
+#define EC_CFG_PORT 0x4e
+
+// Chip global registers
+#define PNP_LDN_SELECT 0x07
+# define LDN_UART 0x07
+# define LDN_LPCIF 0x0c
+#define EC_DEVICE_ID 0x20
+#define EC_DEVICE_REV 0x21
+
+// LPC I/F registers
+#define LPCIF_SIRQ(i) (0x40 + (i))
+
+#define LPCIF_BAR_CFG 0x60
+#define LPCIF_BAR_MAILBOX 0x64
+#define LPCIF_BAR_8042 0x68
+#define LPCIF_BAR_ACPI_EC0 0x6c
+#define LPCIF_BAR_ACPI_EC1 0x70
+#define LPCIF_BAR_ACPI_EC2 0x74
+#define LPCIF_BAR_ACPI_EC3 0x78
+#define LPCIF_BAR_ACPI_PM0 0x7c
+#define LPCIF_BAR_UART 0x80
+#define LPCIF_BAR_FAST_KYBD 0x84
+#define LPCIF_BAR_EMBED_FLASH 0x88
+#define LPCIF_BAR_GP_SPI 0x8c
+#define LPCIF_BAR_EMI 0x90
+#define LPCIF_BAR_PMH7 0x94
+#define LPCIF_BAR_PORT80_DBG0 0x98
+#define LPCIF_BAR_PORT80_DBG1 0x9c
+#define LPCIF_BAR_RTC 0xa0
+
+// UART registers
+#define UART_ACTIVATE 0x30
+#define UART_CONFIG_SELECT 0xf0
+
+void microchip_pnp_enter_conf_state(uint16_t port);
+void microchip_pnp_exit_conf_state(uint16_t port);
+uint8_t pnp_read(uint16_t port, uint8_t index);
+uint32_t pnp_read_le32(uint16_t port, uint8_t index);
+void pnp_write(uint16_t port, uint8_t index, uint8_t value);
+void pnp_write_le32(uint16_t port, uint8_t index, uint32_t value);
+
+#define EC0_CMD 0x0066
+#define EC0_DATA 0x0062
+#define EC1_CMD 0x1604
+#define EC1_DATA 0x1600
+#define EC2_CMD 0x1634
+#define EC2_DATA 0x1630
+#define EC3_CMD 0x161c
+#define EC3_DATA 0x1618
+
+#define EC_OBF (1 << 0)
+#define EC_IBF (1 << 1)
+
+#define EC_READ 0x80
+#define EC_WRITE 0x81
+
+uint8_t ecN_read(uint16_t cmd_port, uint16_t data_port, uint8_t addr);
+
+void ecN_write(uint16_t cmd_port, uint16_t data_port, uint8_t addr, uint8_t val);
+
+// EC0 and EC1 mostly are useful with the READ/WRITE commands
+#define ec0_read(addr) ecN_read(EC0_CMD, EC0_DATA, addr)
+#define ec0_write(addr, val) ecN_write(EC0_CMD, EC0_DATA, addr, val)
+#define ec1_read(addr) ecN_read(EC1_CMD, EC1_DATA, addr)
+#define ec1_write(addr, val) ecN_write(EC1_CMD, EC1_DATA, addr, val)
+
+// Read from the emulated EEPROM
+uint8_t eeprom_read(uint16_t addr);
+
+// Write to the emulated EEPROM
+void eeprom_write(uint16_t addr, uint8_t val);
+
+// Read loaded debug key mask
+uint16_t debug_loaded_keys(void);
+
+// The following location (via either EC0 or EC1) can be used to interact with the debug interface
+#define EC_DEBUG_CMD 0x3d
+
+void debug_read_key(uint8_t i, uint8_t *key);
+
+void debug_write_key(uint8_t i, const uint8_t *key);
+
+uint32_t debug_read_dword(uint32_t addr);
+
+void debug_write_dword(uint32_t addr, uint32_t val);
+
+// RW unlock key index
+#define DEBUG_RW_KEY_IDX 1
+
+void bootblock_ec_init(void);
+
+#endif /* EC_LENOVO_MEC1653_H */
--
2.39.5
|