[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