[Skiboot] [PATCH 3/6] rainier: Introduce PCI hotplug controller interface

Frederic Barrat fbarrat at linux.ibm.com
Mon Oct 18 23:37:48 AEDT 2021


The power of PCI slots can be controlled through i2c
controllers (PCA9552). Rainier has two, one per DCM. This patch adds a
hotplug controller interface, which allow to control the state of each
PCI slot.

As described in the workbook, once power is enable and after a 1 or 2
second window, we need to check pgood to detect any failure and set a
control bit per the hw design.

At power-on, all slots are enabled at once but a later patch will
allow the OS to control the state of each slot individually.

Signed-off-by: Frederic Barrat <fbarrat at linux.ibm.com>
---
 platforms/astbmc/astbmc.h  |   1 +
 platforms/astbmc/rainier.c | 232 +++++++++++++++++++++++++++++++++++++
 2 files changed, 233 insertions(+)

diff --git a/platforms/astbmc/astbmc.h b/platforms/astbmc/astbmc.h
index 00f22123..9fbeb15d 100644
--- a/platforms/astbmc/astbmc.h
+++ b/platforms/astbmc/astbmc.h
@@ -27,6 +27,7 @@ struct slot_table_entry {
 	uint32_t location;
 	const char *name;
 	const struct slot_table_entry *children;
+	const void *platform_data;
 	uint8_t power_limit;
 };
 
diff --git a/platforms/astbmc/rainier.c b/platforms/astbmc/rainier.c
index 605e7b1b..467e6d5a 100644
--- a/platforms/astbmc/rainier.c
+++ b/platforms/astbmc/rainier.c
@@ -9,13 +9,244 @@
 #include <chip.h>
 #include <i2c.h>
 #include <timebase.h>
+#include <timer.h>
+#include <pci-slot.h>
 
 #include "astbmc.h"
 
+#define PGOOD_QUERY_WINDOW	2000 /* ms */
+
+/*
+ * Rainier has one PCI hotplug controller per DCM. Each can control 5
+ * PCI slots through i2c operations.
+ * The bits controlling the slot states are spread over 4 consecutive
+ * 8-bit registers and it's a lot easier to treat them as a 32-bit
+ * word and extract the state of the slot we target by using the
+ * proper masks. The PCA9552 supports address auto-increment so we can
+ * read/write the state of all the slots with one i2c request.
+ * See the system workbook for the i2c register definition.
+ *
+ * Unlike most systems, slots are powered off when reaching skiboot on
+ * Rainier so the first thing to do is to enable them.
+ */
+
+struct hp_controller {
+	uint32_t chip_id;
+	struct lock lock; /* for atomicity of i2c requests */
+	struct i2c_request req;
+	uint32_t i2c_buf;
+	struct timer slot_timer[5];
+};
+
+struct hp_controller hp_controller_dcm0; /* for slots C7 -> C11 */
+struct hp_controller hp_controller_dcm1; /* for slots C0 -> C4 */
+
+struct slot_hp_data {
+	struct hp_controller *hp_controller;
+	uint8_t slot_index;
+};
+
+static int64_t hp_controller_i2c_read(struct hp_controller *hpc,
+				      uint32_t *data)
+{
+	uint64_t rc;
+
+	if (!hpc->req.bus)
+		return OPAL_HARDWARE;
+
+	hpc->req.op = SMBUS_READ;
+	rc = i2c_request_sync(&hpc->req);
+	if (!rc)
+		*data = le32_to_cpu(hpc->i2c_buf);
+	return rc;
+}
+
+static int64_t hp_controller_i2c_write(struct hp_controller *hpc,
+				       uint32_t data)
+{
+	if (!hpc->req.bus)
+		return OPAL_HARDWARE;
+
+	hpc->i2c_buf = cpu_to_le32(data);
+	hpc->req.op = SMBUS_WRITE;
+	return i2c_request_sync(&hpc->req);
+}
+
+static void hp_get_masks(int slot_index, uint32_t *en_mask, uint32_t *pg_mask,
+			 uint32_t *ctl_mask)
+{
+	if (slot_index == -1) {
+		/*
+		 * slot_index = -1 is a shortcut used at boot to
+		 * enable all slots in one operation. Unlike other
+		 * platforms, slots are powered off when we reach
+		 * skiboot on rainier.
+		 */
+		*en_mask  = 0x155;
+		*pg_mask  = 0x155 << 10;
+		*ctl_mask = 0x155 << 20;
+	} else {
+		/* each slot state uses 2 bits */
+		*en_mask  = 0b01 << (2 * slot_index);
+		*pg_mask  = 0b01 << (2 * (slot_index + 5));
+		*ctl_mask = 0b01 << (2 * (slot_index + 10));
+	}
+}
+
+static int64_t hp_controller_check_enable(struct hp_controller *hpc,
+					  int slot_index)
+{
+	uint32_t state, en_mask, ctl_mask, pg_mask;
+	int64_t rc, rc2;
+
+	if (slot_index < -1 || slot_index > 4)
+		return OPAL_PARAMETER;
+
+	hp_get_masks(slot_index, &en_mask, &pg_mask, &ctl_mask);
+
+	/*
+	 * Query PGOOD to make sure the slot is powered on correctly.
+	 * If it is, then raise the CTL bits.
+	 * If not, then disable the slot
+	 */
+	lock(&hpc->lock);
+	rc = hp_controller_i2c_read(hpc, &state);
+	if (rc)
+		goto unlock;
+
+	state |= ctl_mask;
+	if (!(state & pg_mask)) {
+		state &= ~en_mask;
+		rc = OPAL_HARDWARE;
+	}
+	rc2 = hp_controller_i2c_write(hpc, state);
+	if (!rc)
+		rc = rc2;
+unlock:
+	unlock(&hpc->lock);
+	return rc;
+}
+
+static void __unused timer_check_enable(struct timer *t __unused, void *data,
+			       uint64_t now __unused)
+{
+	struct pci_slot *slot = data;
+	struct slot_table_entry *ent = slot->data;
+	const struct slot_hp_data *hp_data;
+	struct hp_controller *hpc;
+	uint32_t slot_index;
+
+	hp_data = ent->platform_data;
+	slot_index = hp_data->slot_index;
+	hpc = hp_data->hp_controller;
+
+	hp_controller_check_enable(hpc, slot_index);
+	pci_slot_set_state(slot, PCI_SLOT_STATE_SPOWER_DONE);
+}
+
+static int64_t hp_controller_enable(struct hp_controller *hpc, int slot_index)
+{
+	uint32_t state, en_mask, ctl_mask, pg_mask;
+	int64_t rc = OPAL_SUCCESS;
+
+	if (slot_index < -1 || slot_index > 4)
+		return OPAL_PARAMETER;
+
+	hp_get_masks(slot_index, &en_mask, &pg_mask, &ctl_mask);
+
+	lock(&hpc->lock);
+	rc = hp_controller_i2c_read(hpc, &state);
+	if (rc)
+		goto unlock;
+
+	/* lower ctrl line */
+	state &= ~0x40000000;
+	state &= ~en_mask;
+	state |= pg_mask;
+	state &= ~ctl_mask;
+	rc = hp_controller_i2c_write(hpc, state);
+	if (rc)
+		goto unlock;
+
+	/* enable slot */
+	state |= en_mask;
+	rc = hp_controller_i2c_write(hpc, state);
+unlock:
+	unlock(&hpc->lock);
+	return rc;
+}
+
+static int64_t __unused hp_controller_disable(struct hp_controller *hpc, int slot_index)
+{
+	uint32_t state, en_mask, ctl_mask, pg_mask;
+	uint64_t rc = OPAL_SUCCESS;
+
+	if (slot_index < 0 || slot_index > 4)
+		return OPAL_PARAMETER;
+
+	hp_get_masks(slot_index, &en_mask, &pg_mask, &ctl_mask);
+
+	lock(&hpc->lock);
+	rc = hp_controller_i2c_read(hpc, &state);
+	if (rc)
+		goto unlock;
+
+	state &= ~ctl_mask;
+	rc = hp_controller_i2c_write(hpc, state);
+	if (rc)
+		goto unlock;
+
+	state &= ~en_mask;
+	rc = hp_controller_i2c_write(hpc, state);
+	if (rc)
+		goto unlock;
+
+	state |= ctl_mask;
+	rc = hp_controller_i2c_write(hpc, state);
+unlock:
+	unlock(&hpc->lock);
+	return rc;
+}
+
+static void hp_controller_init(struct hp_controller *hpc, uint32_t chip_id)
+{
+	init_lock(&hpc->lock);
+	hpc->chip_id = chip_id;
+	hpc->i2c_buf = 0;
+
+	hpc->req.dev_addr = 0xC6 >> 1; /* Docs use 8bit addresses */
+	hpc->req.offset = 0x16; /* PCA9552: register 6 + auto-increment bit */
+	hpc->req.offset_bytes = 1;
+	hpc->req.rw_buf = &hpc->i2c_buf;
+	hpc->req.rw_len = 4;
+	hpc->req.timeout = 100;
+	hpc->req.bus = p8_i2c_find_bus_by_port(chip_id, 2, 1);
+	if (!hpc->req.bus) {
+		prerror("PLAT: Unable to find PCI power controller I2C bus for chip %d\n", chip_id);
+		return;
+	}
+}
+
+static void rainier_pci_probe_complete(void)
+{
+	prlog(PR_DEBUG, "PLAT: checking power of PCI slots\n");
+	hp_controller_check_enable(&hp_controller_dcm0, -1);
+	hp_controller_check_enable(&hp_controller_dcm1, -1);
+}
+
+static void rainier_init_slot_power(void)
+{
+	prlog(PR_DEBUG, "PLAT: powering on PCI slots\n");
+	hp_controller_init(&hp_controller_dcm0, 0);
+	hp_controller_init(&hp_controller_dcm1, 4);
+	hp_controller_enable(&hp_controller_dcm0, -1);
+	hp_controller_enable(&hp_controller_dcm1, -1);
+}
 
 static void rainier_init(void)
 {
 	astbmc_init();
+	rainier_init_slot_power();
 }
 
 static bool rainier_probe(void)
@@ -43,6 +274,7 @@ DECLARE_PLATFORM(rainier) = {
 	.bmc			= &bmc_plat_ast2600_openbmc,
 	.cec_power_down         = astbmc_ipmi_power_down,
 	.cec_reboot             = astbmc_ipmi_reboot,
+	.pci_probe_complete	= rainier_pci_probe_complete,
 	.elog_commit		= ipmi_elog_commit,
 	.exit			= astbmc_exit,
 	.terminate		= ipmi_terminate,
-- 
2.31.1



More information about the Skiboot mailing list