aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hdata/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hdata/i2c.c')
-rw-r--r--roms/skiboot/hdata/i2c.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/roms/skiboot/hdata/i2c.c b/roms/skiboot/hdata/i2c.c
new file mode 100644
index 000000000..7d5d655a5
--- /dev/null
+++ b/roms/skiboot/hdata/i2c.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2017-2019 IBM Corp. */
+
+#include <device.h>
+#include <cpu.h>
+#include <vpd.h>
+#include <interrupts.h>
+#include <ccan/str/str.h>
+#include <chip.h>
+#include <i2c.h>
+
+#include "spira.h"
+#include "hdata.h"
+
+/*
+ * These should probably be in hw/p8-i2c.c. However, that would require the HDAT
+ * test to #include hw/p8-i2c.c which is probably going to be more trouble than
+ * it's worth. So these helpers are here instead.
+ */
+struct dt_node *p8_i2c_add_master_node(struct dt_node *xscom, int eng_id)
+{
+ uint64_t clk, size, xscom_base;
+ struct dt_node *i2cm;
+
+ dt_for_each_compatible(xscom, i2cm, "ibm,power8-i2cm")
+ if (dt_prop_get_u32(i2cm, "chip-engine#") == eng_id)
+ return i2cm;
+
+ /* XXX: Might need to be updated for new chips */
+ if (proc_gen >= proc_gen_p9)
+ size = 0x1000;
+ else
+ size = 0x20;
+
+ xscom_base = 0xa0000 + size * eng_id;
+
+ i2cm = dt_new_addr(xscom, "i2cm", xscom_base);
+ if (!i2cm)
+ return NULL;
+
+ if (proc_gen >= proc_gen_p9) {
+ dt_add_property_strings(i2cm, "compatible", "ibm,power8-i2cm",
+ "ibm,power9-i2cm");
+ } else {
+ dt_add_property_strings(i2cm, "compatible", "ibm,power8-i2cm");
+ }
+
+ dt_add_property_cells(i2cm, "reg", xscom_base, size);
+ dt_add_property_cells(i2cm, "#size-cells", 0);
+ dt_add_property_cells(i2cm, "#address-cells", 1);
+ dt_add_property_cells(i2cm, "chip-engine#", eng_id);
+
+ /*
+ * The i2cm runs at 1/4th the PIB frequency. If we don't know the PIB
+ * frequency then pick 150MHz which should be in the right ballpark.
+ */
+ clk = dt_prop_get_u64_def(xscom, "bus-frequency", 0);
+ if (clk)
+ dt_add_property_cells(i2cm, "clock-frequency", clk / 4);
+ else
+ dt_add_property_cells(i2cm, "clock-frequency", 150000000);
+
+ return i2cm;
+}
+
+struct dt_node *__p8_i2c_add_port_node(struct dt_node *master, int port_id,
+ uint32_t bus_speed)
+{
+ struct dt_node *port;
+ uint32_t speed;
+
+ dt_for_each_child(master, port)
+ if (dt_prop_get_u32(port, "reg") == port_id)
+ goto check_speed;
+
+ port = dt_new_addr(master, "i2c-bus", port_id);
+ if (!port)
+ return NULL;
+
+ dt_add_property_cells(port, "reg", port_id);
+ dt_add_property_cells(port, "#size-cells", 0);
+ dt_add_property_cells(port, "#address-cells", 1);
+
+ /* The P9 I2C master is fully compatible with the P8 one */
+ if (proc_gen >= proc_gen_p9) {
+ dt_add_property_strings(port, "compatible", "ibm,opal-i2c",
+ "ibm,power8-i2c-port", "ibm,power9-i2c-port");
+ } else {
+ dt_add_property_strings(port, "compatible", "ibm,opal-i2c",
+ "ibm,power8-i2c-port");
+ }
+
+check_speed:
+ speed = dt_prop_get_u32_def(port, "bus-frequency", 0xffffffff);
+ if (bus_speed < speed) {
+ dt_check_del_prop(port, "bus-frequency");
+ dt_add_property_cells(port, "bus-frequency", bus_speed);
+ }
+
+ return port;
+}
+
+
+struct dt_node *p8_i2c_add_port_node(struct dt_node *xscom, int eng_id,
+ int port_id, uint32_t bus_freq)
+{
+ struct dt_node *i2cm;
+
+ i2cm = p8_i2c_add_master_node(xscom, eng_id);
+ if (!i2cm)
+ return NULL;
+
+ return __p8_i2c_add_port_node(i2cm, port_id, bus_freq);
+}
+
+struct i2c_dev {
+ uint8_t i2cm_engine;
+ uint8_t i2cm_port;
+ __be16 i2c_bus_freq;
+
+ /* i2c slave info */
+ uint8_t type;
+ uint8_t dev_addr;
+ uint8_t dev_port;
+ uint8_t __reserved;
+
+ __be32 purpose;
+ __be32 i2c_link;
+ __be16 slca_index;
+};
+
+struct hdat_i2c_type {
+ uint32_t id;
+ const char *name;
+ const char *compat;
+};
+
+static struct hdat_i2c_type hdat_i2c_devs[] = {
+ { 0x1, "gpio", "nxp,pca9551" },
+ /* XXX: Please verify that all VPD EEPROMs are of this type */
+ { 0x2, "eeprom", "atmel,24c128" },
+ { 0x3, "tpm", "nuvoton,npct650" },
+ { 0x4, "i2c", NULL }, /* MEX-FPGA */
+ { 0x5, "i2c", NULL }, /* UCX90xx devs for PCI Hotplug */
+ { 0x6, "gpio", "nxp,pca9552" },
+ { 0x7, "gpio", "nxp,pca9553" },
+ { 0x8, "gpio", "nxp,pca9554" },
+ { 0x9, "gpio", "nxp,pca9555" },
+ { 0xa, "i2c", NULL }, /* SMP/OpenCAPI Cable */
+ { 0xb, "eeprom", "atmel,24c256" },
+ { 0xc, "i2c", NULL }, /* Thermal Sensor */
+ { 0xd, "eeprom", "atmel,24c04" },
+ { 0xe, "eeprom", "atmel,24c512" },
+ { 0xf, "eeprom", "atmel,24c32" },
+ { 0x10, "eeprom", "atmel,24c64" },
+ { 0x11, "eeprom", "atmel,24c16" },
+ { 0x12, "i2c", NULL }, /* NVDIA GPU */
+ { 0x13, "i2c", "nxp,lpc11u35" },
+};
+
+struct hdat_i2c_info {
+ uint32_t id;
+ bool allowed; /* true if the host may use the device */
+ const char *label;
+};
+
+static struct hdat_i2c_info hdat_i2c_extra_info[] = {
+ { 0x1, false, "led-controller" },
+ { 0x2, false, "pci-hotplug-pgood" },
+ { 0x3, false, "pci-hotplug-control" },
+ { 0x4, true, "tpm" },
+ { 0x5, true, "module-vpd" },
+ { 0x6, true, "dimm-spd" },
+ { 0x7, true, "proc-vpd" },
+ { 0x8, false, "sbe-eeprom"},
+ { 0x9, true, "planar-vpd" },
+ { 0xa, false, "opencapi-topology" },
+ { 0xb, false, "opencapi-micro-reset" },
+ { 0xc, false, "nvlink-cable" },
+ { 0xd, false, "secure-window-open" },
+ { 0xe, false, "physical-presence" },
+ { 0xf, false, "mex-fpga" },
+ { 0x10, false, "thermal-sensor" },
+ { 0x11, false, "host-i2c-enable" },
+ { 0x12, false, "gpu-config" },
+};
+
+/*
+ * this is pretty half-assed, to generate the labels properly we need to look
+ * up associated SLCA index and determine what kind of module the device is on
+ * and why
+ */
+static struct hdat_i2c_type *map_type(uint32_t type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hdat_i2c_devs); i++)
+ if (hdat_i2c_devs[i].id == type)
+ return &hdat_i2c_devs[i];
+
+ return NULL;
+}
+
+static struct hdat_i2c_info *get_info(uint32_t type)
+{
+ static struct hdat_i2c_info no_info =
+ { .id = 0x0, .allowed = false, .label = "" };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hdat_i2c_extra_info); i++)
+ if (hdat_i2c_extra_info[i].id == type)
+ return &hdat_i2c_extra_info[i];
+
+ return &no_info;
+}
+
+static bool is_zeros(const void *p, size_t size)
+{
+ const char *c = p;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if (c[i] != 0)
+ return false;
+
+ return true;
+}
+
+struct host_i2c_hdr {
+ const struct HDIF_array_hdr hdr;
+ __be32 version;
+} __packed __align(0x4);
+
+int parse_i2c_devs(const struct HDIF_common_hdr *hdr, int idata_index,
+ struct dt_node *xscom)
+{
+ struct dt_node *bus, *node;
+ const struct hdat_i2c_type *type;
+ const struct hdat_i2c_info *info;
+ const struct i2c_dev *dev;
+ const char *name, *compat;
+ const struct host_i2c_hdr *ahdr;
+ uint32_t dev_addr;
+ uint32_t version;
+ uint32_t size;
+ uint32_t purpose;
+ int i, count;
+
+ /*
+ * This code makes a few assumptions about XSCOM addrs, etc
+ * and will need updating for new processors
+ */
+ assert(proc_gen == proc_gen_p9 || proc_gen == proc_gen_p10);
+
+ /*
+ * Emit an error if we get a newer version. This is an interim measure
+ * until the new version format is finalised.
+ */
+ ahdr = HDIF_get_idata(hdr, idata_index, &size);
+ if (!ahdr || !size)
+ return -1;
+
+ /*
+ * Some hostboots don't correctly fill the version field. On these
+ * the offset from the start of the header to the start of the array
+ * is 16 bytes.
+ */
+ if (be32_to_cpu(ahdr->hdr.offset) == 16) {
+ version = 1;
+ prerror("I2C: HDAT device array has no version! Assuming v1\n");
+ } else {
+ version = be32_to_cpu(ahdr->version);
+ }
+
+ if (version == 2) {
+ prlog(PR_INFO, "I2C: v%d found, but not supported. Parsing as v1\n",
+ version);
+ } else if (version > 2) {
+ prerror("I2C: v%d found, but not supported! THIS IS A BUG\n",
+ version);
+ return -1;
+ }
+
+ count = HDIF_get_iarray_size(hdr, idata_index);
+ for (i = 0; i < count; i++) {
+ dev = HDIF_get_iarray_item(hdr, idata_index, i, &size);
+
+ /*
+ * XXX: Some broken hostboots populate i2c devs with zeros.
+ * Workaround them for now.
+ */
+ if (is_zeros(dev, size)) {
+ prerror("I2C: Ignoring broken i2c dev %d\n", i);
+ continue;
+ }
+
+ /*
+ * On some systems the CFAM I2C master is represented in the
+ * host I2C table as engine 6. There are only 4 (0, 1, 2, 3)
+ * engines accessible to the host via XSCOM so filter out
+ * engines outside this range so we don't create bogus
+ * i2cm@<addr> nodes.
+ */
+ if (dev->i2cm_engine >= 4 &&
+ (proc_gen == proc_gen_p9 || proc_gen == proc_gen_p10))
+ continue;
+
+ bus = p8_i2c_add_port_node(xscom, dev->i2cm_engine, dev->i2cm_port,
+ be16_to_cpu(dev->i2c_bus_freq) * 1000);
+
+ if (!bus) {
+ prerror("Unable to add node for e%dp%d under %s\n",
+ dev->i2cm_engine, dev->i2cm_port, xscom->name);
+ continue;
+ }
+
+ /*
+ * Looks like hostboot gives the address as an 8 bit, left
+ * justified quantity (i.e it includes the R/W bit). So we need
+ * to strip it off to get an address linux can use.
+ */
+ dev_addr = dev->dev_addr >> 1;
+
+ purpose = be32_to_cpu(dev->purpose);
+ type = map_type(dev->type);
+ info = get_info(purpose);
+
+ /* HACK: Hostboot doesn't export the correct type information
+ * for the DIMM SPD EEPROMs. This is a problem because SPD
+ * EEPROMs have a different wire protocol to the atmel,24XXXX
+ * series. The main difference being that SPD EEPROMs have an
+ * 8bit offset rather than a 16bit offset. This means that the
+ * driver will send 2 bytes when doing a random read,
+ * potentially overwriting part of the SPD information.
+ *
+ * Just to make things interested the FSP also gets the device
+ * type wrong. To work around both just set the device-type to
+ * "spd" for anything in the 0x50 to 0x57 range since that's the
+ * SPD eeprom range.
+ *
+ * XXX: Future chips might not use engine 3 for the DIMM buses.
+ */
+ if (dev->i2cm_engine == 3 && dev_addr >= 0x50
+ && dev_addr < 0x58) {
+ compat = "spd";
+ name = "eeprom";
+ } else if (type) {
+ compat = type->compat;
+ name = type->name;
+ } else {
+ name = "unknown";
+ compat = NULL;
+ }
+
+ /*
+ * An i2c device is unknown if either the i2c device list is
+ * outdated or the device is marked as unknown (0xFF) in the
+ * hdat. Log both cases to see what/where/why.
+ */
+ if (!type || dev->type == 0xFF) {
+ prlog(PR_NOTICE, "HDAT I2C: found e%dp%d - %s@%x dp:%02x (%#x:%s)\n",
+ dev->i2cm_engine, dev->i2cm_port, name, dev_addr,
+ dev->dev_port, purpose, info->label);
+ continue;
+ }
+
+ prlog(PR_DEBUG, "HDAT I2C: found e%dp%d - %s@%x dp:%02x (%#x:%s)\n",
+ dev->i2cm_engine, dev->i2cm_port, name, dev_addr,
+ dev->dev_port, purpose, info->label);
+
+ /*
+ * Multi-port device require special handling since we need to
+ * generate the device-specific DT bindings. For now we're just
+ * going to ignore them since these devices are owned by FW
+ * any way.
+ */
+ if (dev->dev_port != 0xff)
+ continue;
+
+ node = dt_new_addr(bus, name, dev_addr);
+ if (!node)
+ continue;
+
+ dt_add_property_cells(node, "reg", dev_addr);
+ dt_add_property_cells(node, "link-id",
+ be32_to_cpu(dev->i2c_link));
+ if (compat)
+ dt_add_property_string(node, "compatible", compat);
+ if (info->label)
+ dt_add_property_string(node, "label", info->label);
+ if (!info->allowed)
+ dt_add_property_string(node, "status", "reserved");
+
+ /*
+ * Set a default timeout of 2s on the ports with a TPM. This is
+ * to work around a bug with certain TPM firmwares that can
+ * clock stretch for long periods of time and will lock up
+ * until they are power cycled if a STOP condition is sent
+ * during this period.
+ */
+ if (dev->type == 0x3)
+ dt_add_property_cells(bus, "timeout-ms", 2000);
+
+ /* XXX: SLCA index? */
+ }
+
+ return 0;
+}