summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/soc/intel/common/block/include/intelblocks/pmc_ipc.h33
-rw-r--r--src/soc/intel/common/block/pmc/Kconfig8
-rw-r--r--src/soc/intel/common/block/pmc/Makefile.inc1
-rw-r--r--src/soc/intel/common/block/pmc/pmc_ipc.c156
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]);
+}