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>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/88395
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: David Hendricks <david.hendricks@gmail.com>
Reviewed-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Reviewed-by: Máté Kukri <km@mkukri.xyz>
This commit is contained in:
Matt DeVillier 2025-07-11 16:11:47 -05:00
commit 96e381766e
8 changed files with 270 additions and 0 deletions

View file

@ -0,0 +1,24 @@
## SPDX-License-Identifier: GPL-2.0-only
config EC_LENOVO_MEC1653
bool
if EC_LENOVO_MEC1653
config MEC1653_HAS_DEBUG_UNLOCK
bool
config MEC1653_DEBUG_UNLOCK_KEY
string
depends on MEC1653_HAS_DEBUG_UNLOCK
help
Debug unlock key necessary to enable the UART.
config MEC1653_ENABLE_UART
bool "Enable EC UART"
depends on MEC1653_HAS_DEBUG_UNLOCK
help
The EC UART physical interface is board-specific and requires
a board-specific debug unlock key.
endif

View file

@ -0,0 +1,9 @@
## SPDX-License-Identifier: GPL-2.0-only
ifeq ($(CONFIG_EC_LENOVO_MEC1653),y)
bootblock-y += mec1653.c
bootblock-$(CONFIG_MEC1653_ENABLE_UART) += debug.c
bootblock-$(CONFIG_MEC1653_ENABLE_UART) += uart.c
endif

View file

@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <arch/io.h>
#include <ec/acpi/ec.h>
#include "debug.h"
#include "mec1653.h"
static void debug_cmd(uint8_t cmd)
{
ec_set_ports(EC_SC, EC_DATA);
ec_write(EC_DEBUG_CMD, cmd);
while (ec_read(EC_DEBUG_CMD) & 0x80)
;
}
uint32_t debug_read_dword(uint32_t addr)
{
ec_set_ports(EC3_CMD, EC3_DATA);
ec_clear_out_queue();
ec_ready_send(EC_SEND_TIMEOUT_US);
outl(addr << 8 | 0xE2, EC3_DATA);
ec_ready_recv(EC_SEND_TIMEOUT_US);
return inl(EC3_DATA);
}
void debug_write_dword(uint32_t addr, uint32_t val)
{
ec_set_ports(EC3_CMD, EC3_DATA);
ec_clear_out_queue();
ec_ready_send(EC_SEND_TIMEOUT_US);
outl(addr << 8 | 0xEA, EC3_DATA);
ec_ready_send(EC_SEND_TIMEOUT_US);
outl(val, EC3_DATA);
}
// Helper function to convert hex character to byte
static uint8_t hex_char_to_byte(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0; // Invalid character
}
// Helper function to convert 16-char hex string to 8 bytes
static void hex_string_to_bytes(const char *hex_str, uint8_t *bytes)
{
for (int i = 0; i < 8; ++i) {
bytes[i] = (hex_char_to_byte(hex_str[i * 2]) << 4) |
hex_char_to_byte(hex_str[i * 2 + 1]);
}
}
void debug_write_key(uint8_t i, const char *hex_key)
{
uint8_t key_bytes[8];
ec_set_ports(EC_SC, EC_DATA);
hex_string_to_bytes(hex_key, key_bytes);
for (int j = 0; j < 8; ++j)
ec_write(0x3e + j, key_bytes[j]);
debug_cmd(0xc0 | (i & 0xf));
}
void debug_read_key(uint8_t i, uint8_t *key)
{
debug_cmd(0x80 | (i & 0xf));
for (int j = 0; j < 8; ++j)
key[j] = ec_read(0x3e + j);
}
uint16_t debug_loaded_keys(void)
{
ec_set_ports(EC_SC, EC_DATA);
return (uint16_t) ec_read(0x87) << 8 | (uint16_t) ec_read(0x86);
}

View file

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef EC_LENOVO_MEC1653_DEBUG_H
#define EC_LENOVO_MEC1653_DEBUG_H
// The following location (via either EC0 or EC1) can be used to interact with the debug interface
#define EC_DEBUG_CMD 0x3d
// RW unlock key index
#define DEBUG_RW_KEY_IDX 1
#define EC_SEND_TIMEOUT_US 20000 // 20ms
#define EC_RECV_TIMEOUT_US 320000 // 320ms
#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
// Read loaded debug key mask
uint16_t debug_loaded_keys(void);
void debug_read_key(uint8_t i, uint8_t *key);
void debug_write_key(uint8_t i, const char *hex_key);
uint32_t debug_read_dword(uint32_t addr);
void debug_write_dword(uint32_t addr, uint32_t val);
#endif /* EC_LENOVO_MEC1653_DEBUG_H */

View file

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <ec/acpi/ec.h>
#include <arch/io.h>
#include "mec1653.h"
#include "uart.h"
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
ec_set_ports(EC_SC, EC_DATA);
ec_clear_out_queue();
ec_write(ec_read(0), 0x20);
// Reset LEDs to power on state
// (Without this warm reboot leaves LEDs off)
ec_write(0x0c, 0x80);
ec_write(0x0c, 0x07);
ec_write(0x0c, 0x8a);
// Setup debug UART
if (CONFIG(MEC1653_ENABLE_UART))
mec1653_configure_uart();
}

View file

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef EC_LENOVO_MEC1653_H
#define EC_LENOVO_MEC1653_H
void bootblock_ec_init(void);
#endif /* EC_LENOVO_MEC1653_H */

View file

@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <device/pnp_ops.h>
#include <ec/lenovo/mec1653/mec1653.h>
#include "debug.h"
#include "uart.h"
static void pnp_write_config32(pnp_devfn_t dev, uint8_t offset, uint32_t value)
{
pnp_write_config(dev, offset, value & 0xff);
pnp_write_config(dev, offset + 1, (value >> 8) & 0xff);
pnp_write_config(dev, offset + 2, (value >> 16) & 0xff);
pnp_write_config(dev, offset + 3, (value >> 24) & 0xff);
}
void mec1653_configure_uart(void)
{
pnp_devfn_t lpc_dev = PNP_DEV(EC_CFG_PORT, LDN_LPCIF);
pnp_devfn_t uart_dev = PNP_DEV(EC_CFG_PORT, LDN_UART);
// Enter PNP conf state
outb(MEC_CFG_ENTRY_KEY, EC_CFG_PORT);
// Select LPC I/F LDN
pnp_set_logical_device(lpc_dev);
// Write UART BAR
pnp_write_config32(lpc_dev, LPCIF_BAR_UART, (uint32_t) UART_PORT << 16 | 0x8707);
// Set SIRQ4 to UART
pnp_write_config(lpc_dev, LPCIF_SIRQ(UART_IRQ), LDN_UART);
// Enable and configure UART LDN
pnp_set_logical_device(uart_dev);
pnp_set_enable(uart_dev, 1);
pnp_write_config(uart_dev, UART_CONFIG_SELECT, 0);
// Exit PNP conf state
outb(MEC_CFG_EXIT_KEY, EC_CFG_PORT);
// Supply debug unlock key
debug_write_key(DEBUG_RW_KEY_IDX, CONFIG_MEC1653_DEBUG_UNLOCK_KEY);
// Use debug writes to set UART_TX and UART_RX GPIOs
debug_write_dword(MEC1653_CFG_REG + MEC1653_CFG_TX_GPIO_OFFSET, 0x1000);
debug_write_dword(MEC1653_CFG_REG + MEC1653_CFG_RX_GPIO_OFFSET, 0x1000);
}

View file

@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef EC_LENOVO_MEC1653_UART_H
#define EC_LENOVO_MEC1653_UART_H
#define MEC_CFG_ENTRY_KEY 0x55
#define MEC_CFG_EXIT_KEY 0xaa
#define UART_PORT 0x3f8
#define UART_IRQ 4
#define MEC1653_CFG_REG 0xf0c400
#define MEC1653_CFG_TX_GPIO_OFFSET 0x110
#define MEC1653_CFG_RX_GPIO_OFFSET 0x114
// 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
// LPC I/F registers
#define LPCIF_SIRQ(i) (0x40 + (i))
#define LPCIF_BAR_UART 0x80
// UART registers
#define UART_ACTIVATE 0x30
#define UART_CONFIG_SELECT 0xf0
void mec1653_configure_uart(void);
#endif /* EC_LENOVO_MEC1653_UART_H */