diff options
-rw-r--r-- | src/soc/intel/common/block/include/intelblocks/pmc_ipc.h | 33 | ||||
-rw-r--r-- | src/soc/intel/common/block/pmc/Kconfig | 8 | ||||
-rw-r--r-- | src/soc/intel/common/block/pmc/Makefile.inc | 1 | ||||
-rw-r--r-- | src/soc/intel/common/block/pmc/pmc_ipc.c | 156 |
4 files changed, 195 insertions, 3 deletions
diff --git a/src/soc/intel/common/block/include/intelblocks/pmc_ipc.h b/src/soc/intel/common/block/include/intelblocks/pmc_ipc.h index 0c90cd7df6..fbf9a6ee0e 100644 --- a/src/soc/intel/common/block/include/intelblocks/pmc_ipc.h +++ b/src/soc/intel/common/block/include/intelblocks/pmc_ipc.h @@ -21,6 +21,14 @@ #define PMC_IPC_CMD_NO_MSI 0 +/* IPC command to enable/disable PCIe SRCCLK */ +#define PMC_IPC_CMD_ID_SET_PCIE_CLOCK 0xAC + +/* IPC return values */ +#define PMC_IPC_SUCCESS 0 +#define PMC_IPC_ERROR 1 +#define PMC_IPC_TIMEOUT 2 + /* * Create the IPC CMD to send to PMC */ @@ -46,4 +54,29 @@ struct pmc_ipc_buffer { enum cb_err pmc_send_ipc_cmd(uint32_t cmd, const struct pmc_ipc_buffer *wbuf, struct pmc_ipc_buffer *rbuf); +/* + * Provides an ACPI method in the SSDT to read/write to the IPC mailbox which is + * defined in the PMC device MMIO address space. + * + * One possible use of this method is to to enable/disable the clock for a + * particular PCIe root port at runtime when the device is in D3 state. + * + * The ACPI method takes 7 arguments: + * IPCW (COMMAND, SUB_ID, SIZE, DATA0, DATA1, DATA2, DATA3) + * + * And will return a package with 5 elements: + * 0 = Return code + * PMC_IPC_SUCCESS + * PMC_IPC_ERROR + * PMC_IPC_TIMEOUT + * 1..4 = Data read from IPC if return code is PMC_IPC_SUCCESS + */ +void pmc_ipc_acpi_fill_ssdt(void); + +/* + * Call the ACPI method to write to the IPC mailbox and enable/disable the + * specified clock pin connected to the specified PCIe root port. + */ +void pmc_ipc_acpi_set_pci_clock(unsigned int pcie_rp, unsigned int clock_pin, bool enable); + #endif /* SOC_INTEL_COMMON_BLOCK_PMC_IPC_H */ diff --git a/src/soc/intel/common/block/pmc/Kconfig b/src/soc/intel/common/block/pmc/Kconfig index ce41b23620..5cb2bea1d9 100644 --- a/src/soc/intel/common/block/pmc/Kconfig +++ b/src/soc/intel/common/block/pmc/Kconfig @@ -22,6 +22,14 @@ config PMC_INVALID_READ_AFTER_WRITE Enable this for PMC devices where a read back of ACPI BAR and IO access bit does not return the previously written value. +config PMC_IPC_ACPI_INTERFACE + bool + default n + depends on HAVE_ACPI_TABLES + help + Enable this to have the PMC IPC mailbox ACPI interface added + to the SSDT for use by other drivers. + config PMC_GLOBAL_RESET_ENABLE_LOCK bool help diff --git a/src/soc/intel/common/block/pmc/Makefile.inc b/src/soc/intel/common/block/pmc/Makefile.inc index 796a039aed..49a0902847 100644 --- a/src/soc/intel/common/block/pmc/Makefile.inc +++ b/src/soc/intel/common/block/pmc/Makefile.inc @@ -6,4 +6,5 @@ ramstage-y += pmclib.c pmc_ipc.c smm-y += pmclib.c verstage-y += pmclib.c postcar-y += pmclib.c +ramstage-$(CONFIG_PMC_IPC_ACPI_INTERFACE) += pmc_ipc.c endif diff --git a/src/soc/intel/common/block/pmc/pmc_ipc.c b/src/soc/intel/common/block/pmc/pmc_ipc.c index 7decf790a9..7c811aed44 100644 --- a/src/soc/intel/common/block/pmc/pmc_ipc.c +++ b/src/soc/intel/common/block/pmc/pmc_ipc.c @@ -1,10 +1,12 @@ /* 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> @@ -64,16 +66,16 @@ static int check_ipc_sts(void) if (IPC_STS_HAS_ERROR(ipc_sts)) { printk(BIOS_ERR, "IPC_STS.error_code 0x%x\n", IPC_STS_ERROR_CODE(ipc_sts)); - return -1; + return PMC_IPC_ERROR; } - return 0; + 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 -1; + return PMC_IPC_TIMEOUT; } enum cb_err pmc_send_ipc_cmd(uint32_t cmd, const struct pmc_ipc_buffer *wbuf, @@ -94,3 +96,151 @@ enum cb_err pmc_send_ipc_cmd(uint32_t cmd, const struct pmc_ipc_buffer *wbuf, 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]); +} |