blob: 7c811aed44f0765208a1fcf28342bf15e2ded2b9 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <acpi/acpigen.h>
#include <device/mmio.h>
#include <console/console.h>
#include <delay.h>
#include <intelblocks/pmclib.h>
#include <intelblocks/pmc_ipc.h>
#include <soc/pci_devs.h>
#include <stdint.h>
#include <timer.h>
/*
* WBUF register block offset 0x80..0x8f there are 4 consecutive
* 32 bit registers
*/
#define IPC_WBUF0 0x80
/*
* RBUF registers block offset 0x90..0x9f there are 4 consecutive
* 32 bit registers
*/
#define IPC_RBUF0 0x90
/*
* From Intel 500 Series PCH EDS vol2 s4.4
*/
#define PMC_IPC_CMD_OFFSET 0x0
#define PMC_IPC_STS_OFFSET 0x4
#define PMC_IPC_STS_BUSY BIT(0)
#define PMC_IPC_STS_ERR BIT(1)
#define PMC_IPC_ERR_CODE_SHIFT 16
#define PMC_IPC_ERR_CODE_MASK 0xff
#define PMC_IPC_XFER_TIMEOUT_MS (1 * MSECS_PER_SEC) /* max 1s */
#define IS_IPC_STS_BUSY(status) ((status) & PMC_IPC_STS_BUSY)
#define IPC_STS_HAS_ERROR(status) ((status) & PMC_IPC_STS_ERR)
#define IPC_STS_ERROR_CODE(sts) (((sts) >> PMC_IPC_ERR_CODE_SHIFT & \
PMC_IPC_ERR_CODE_MASK))
static void *pmc_reg(unsigned int pmc_reg_offset)
{
const uintptr_t pmcbase = soc_read_pmc_base();
return (void *)(pmcbase + pmc_reg_offset);
}
static const void *pmc_rbuf(unsigned int ix)
{
return pmc_reg(IPC_RBUF0 + ix * sizeof(uint32_t));
}
static void *pmc_wbuf(unsigned int ix)
{
return pmc_reg(IPC_WBUF0 + ix * sizeof(uint32_t));
}
static int check_ipc_sts(void)
{
struct stopwatch sw;
uint32_t ipc_sts;
stopwatch_init_msecs_expire(&sw, PMC_IPC_XFER_TIMEOUT_MS);
do {
ipc_sts = read32(pmc_reg(PMC_IPC_STS_OFFSET));
if (!(IS_IPC_STS_BUSY(ipc_sts))) {
if (IPC_STS_HAS_ERROR(ipc_sts)) {
printk(BIOS_ERR, "IPC_STS.error_code 0x%x\n",
IPC_STS_ERROR_CODE(ipc_sts));
return PMC_IPC_ERROR;
}
return PMC_IPC_SUCCESS;
}
udelay(50);
} while (!stopwatch_expired(&sw));
printk(BIOS_ERR, "PMC IPC timeout after %u ms\n", PMC_IPC_XFER_TIMEOUT_MS);
return PMC_IPC_TIMEOUT;
}
enum cb_err pmc_send_ipc_cmd(uint32_t cmd, const struct pmc_ipc_buffer *wbuf,
struct pmc_ipc_buffer *rbuf)
{
for (int i = 0; i < PMC_IPC_BUF_COUNT; ++i)
write32(pmc_wbuf(i), wbuf->buf[i]);
write32(pmc_reg(PMC_IPC_CMD_OFFSET), cmd);
if (check_ipc_sts()) {
printk(BIOS_ERR, "PMC IPC command 0x%x failed\n", cmd);
return CB_ERR;
}
for (int i = 0; i < PMC_IPC_BUF_COUNT; ++i)
rbuf->buf[i] = read32(pmc_rbuf(i));
return CB_SUCCESS;
}
void pmc_ipc_acpi_fill_ssdt(void)
{
const struct fieldlist ipcs_fields[] = {
FIELDLIST_OFFSET(PMC_IPC_CMD_OFFSET), /* Command */
FIELDLIST_NAMESTR("ICMD", 32), /* Command Register */
FIELDLIST_OFFSET(PMC_IPC_STS_OFFSET), /* Status */
FIELDLIST_NAMESTR("IBSY", 1), /* Status Busy */
FIELDLIST_NAMESTR("IERR", 1), /* Status Error */
FIELDLIST_RESERVED(14),
FIELDLIST_NAMESTR("IERC", 8), /* Status Error Code */
FIELDLIST_OFFSET(IPC_WBUF0), /* Write Buffer */
FIELDLIST_NAMESTR("IWB0", 32), /* Write Buffer 0 */
FIELDLIST_NAMESTR("IWB1", 32), /* Write Buffer 1 */
FIELDLIST_NAMESTR("IWB2", 32), /* Write Buffer 2 */
FIELDLIST_NAMESTR("IWB3", 32), /* Write Buffer 3 */
FIELDLIST_OFFSET(IPC_RBUF0), /* Read Buffer */
FIELDLIST_NAMESTR("IRB0", 32), /* Read Buffer 0 */
FIELDLIST_NAMESTR("IRB1", 32), /* Read Buffer 1 */
FIELDLIST_NAMESTR("IRB2", 32), /* Read Buffer 2 */
FIELDLIST_NAMESTR("IRB3", 32), /* Read Buffer 3 */
};
const struct opregion ipcs_opregion = OPREGION("IPCM", SYSTEMMEMORY,
soc_read_pmc_base(), 0xff);
int i;
/* Package with return value and read buffer. */
acpigen_write_name("RVAL");
acpigen_write_package(5);
for (i = 0; i < 5; ++i)
acpigen_write_integer(0);
acpigen_pop_len();
acpigen_write_method_serialized("IPCS", 7);
acpigen_write_opregion(&ipcs_opregion);
acpigen_write_field("IPCM", ipcs_fields, ARRAY_SIZE(ipcs_fields),
FIELD_DWORDACC | FIELD_NOLOCK | FIELD_PRESERVE);
/* Fill write buffer data registers. */
acpigen_write_store_op_to_namestr(ARG3_OP, "IWB0");
acpigen_write_store_op_to_namestr(ARG4_OP, "IWB1");
acpigen_write_store_op_to_namestr(ARG5_OP, "IWB2");
acpigen_write_store_op_to_namestr(ARG6_OP, "IWB3");
/* Program the command register with command and size of write data. */
acpigen_write_store_int_to_op(0, LOCAL0_OP);
/* Local0 += (Arg0 << PMC_IPC_CMD_COMMAND_SHIFT) */
acpigen_emit_byte(ADD_OP);
acpigen_emit_byte(LOCAL0_OP);
acpigen_write_shiftleft_op_int(ARG0_OP, PMC_IPC_CMD_COMMAND_SHIFT);
acpigen_emit_byte(LOCAL0_OP);
/* Local0 += (Arg1 << PMC_IPC_CMD_SUB_COMMAND_SHIFT) */
acpigen_emit_byte(ADD_OP);
acpigen_emit_byte(LOCAL0_OP);
acpigen_write_shiftleft_op_int(ARG1_OP, PMC_IPC_CMD_SUB_COMMAND_SHIFT);
acpigen_emit_byte(LOCAL0_OP);
/* Local1 = PMC_IPC_CMD_NO_MSI */
acpigen_write_store_int_to_op(PMC_IPC_CMD_NO_MSI, LOCAL1_OP);
/* Local0 += (Local1 << PMC_IPC_CMD_MSI_SHIFT) */
acpigen_emit_byte(ADD_OP);
acpigen_emit_byte(LOCAL0_OP);
acpigen_write_shiftleft_op_int(LOCAL1_OP, PMC_IPC_CMD_MSI_SHIFT);
acpigen_emit_byte(LOCAL0_OP);
/* Local0 += (Arg1 << PMC_IPC_CMD_SIZE_SHIFT) */
acpigen_emit_byte(ADD_OP);
acpigen_emit_byte(LOCAL0_OP);
acpigen_write_shiftleft_op_int(ARG2_OP, PMC_IPC_CMD_SIZE_SHIFT);
acpigen_emit_byte(LOCAL0_OP);
/* Start mailbox command with one 32bit write. */
acpigen_write_store_op_to_namestr(LOCAL0_OP, "ICMD");
/* Read status register to get busy/error status. */
acpigen_write_store_int_to_op(PMC_IPC_XFER_TIMEOUT_MS, LOCAL1_OP);
/* While (Local1 > 0) */
acpigen_emit_byte(WHILE_OP);
acpigen_write_len_f();
acpigen_emit_byte(LGREATER_OP);
acpigen_emit_byte(LOCAL1_OP);
acpigen_emit_byte(ZERO_OP);
/* If (IBSY == 0) { Return (SUCCESS) } */
acpigen_write_if_lequal_namestr_int("IBSY", 0);
acpigen_set_package_element_int("RVAL", 0, PMC_IPC_SUCCESS);
acpigen_set_package_element_namestr("RVAL", 1, "IRB0");
acpigen_set_package_element_namestr("RVAL", 2, "IRB1");
acpigen_set_package_element_namestr("RVAL", 3, "IRB2");
acpigen_set_package_element_namestr("RVAL", 4, "IRB3");
acpigen_write_return_namestr("RVAL");
acpigen_pop_len();
/* If (IERR == 1) { Return (ERROR) } */
acpigen_write_if_lequal_namestr_int("IERR", 1);
acpigen_write_debug_string("IPCS ERROR");
acpigen_write_debug_namestr("IERC");
acpigen_set_package_element_int("RVAL", 0, PMC_IPC_ERROR);
acpigen_write_return_namestr("RVAL");
acpigen_pop_len();
/* Sleep (1) */
acpigen_write_sleep(1);
/* Decrement (Local1) */
acpigen_emit_byte(DECREMENT_OP);
acpigen_emit_byte(LOCAL1_OP);
acpigen_pop_len(); /* While */
/* Return (TIMEOUT) */
acpigen_write_debug_string("IPCS TIMEOUT");
acpigen_set_package_element_int("RVAL", 0, PMC_IPC_TIMEOUT);
acpigen_write_return_namestr("RVAL");
acpigen_pop_len(); /* Method */
}
void pmc_ipc_acpi_set_pci_clock(unsigned int pcie_rp, unsigned int clock_pin, bool enable)
{
const uint32_t data[] = {
1 << clock_pin, /* Clock pin to be modified */
(enable ? 1 : 0) << clock_pin, /* Clock pin to set */
1 << pcie_rp, /* PCIe root port to be modified */
(enable ? 1 : 0) << pcie_rp, /* PCIe root port to set */
};
const char *method = acpi_device_path_join(pcidev_path_on_root(PCH_DEVFN_PMC), "IPCS");
if (!method) {
printk(BIOS_ERR, "%s: Unable to find PMC device IPCS method\n", __func__);
return;
}
/*
* The PMC IPC mailbox method takes 7 arguments:
* IPCS (COMMAND, SUB_ID, SIZE, DATA0, DATA1, DATA2, DATA3)
*/
acpigen_emit_namestring(method);
acpigen_write_integer(PMC_IPC_CMD_ID_SET_PCIE_CLOCK);
acpigen_write_integer(0);
acpigen_write_integer(sizeof(data));
acpigen_write_dword(data[0]);
acpigen_write_dword(data[1]);
acpigen_write_dword(data[2]);
acpigen_write_dword(data[3]);
}