summaryrefslogtreecommitdiffstats
path: root/hdata/i2c.c
diff options
context:
space:
mode:
authorOliver O'Halloran <oohall@gmail.com>2017-04-06 14:01:58 +1000
committerStewart Smith <stewart@linux.vnet.ibm.com>2017-04-07 14:51:34 +1000
commitae4e50eb35695f9438b6b9544a02fc891c4dd20d (patch)
tree7a617c3a1dabc54836428b4aca1d0501d24a7006 /hdata/i2c.c
parent510e2b066b172a4b46e18315ddac30721b5a7526 (diff)
downloadtalos-skiboot-ae4e50eb35695f9438b6b9544a02fc891c4dd20d.tar.gz
talos-skiboot-ae4e50eb35695f9438b6b9544a02fc891c4dd20d.zip
hdat: parse processor attached i2c devices
Adds basic parsing for i2c devices that are attached to the processor I2C interfaces. This is mainly VPD SEEPROMs. Signed-off-by: Oliver O'Halloran <oohall@gmail.com> Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hdata/i2c.c')
-rw-r--r--hdata/i2c.c192
1 files changed, 192 insertions, 0 deletions
diff --git a/hdata/i2c.c b/hdata/i2c.c
new file mode 100644
index 00000000..127068fa
--- /dev/null
+++ b/hdata/i2c.c
@@ -0,0 +1,192 @@
+#include <device.h>
+#include <cpu.h>
+#include <vpd.h>
+#include <interrupts.h>
+#include <ccan/str/str.h>
+#include <chip.h>
+
+#include "spira.h"
+#include "hdata.h"
+
+struct i2c_dev {
+ uint8_t i2cm_engine;
+ uint8_t i2cm_port;
+ __be16 i2c_bus_freq;
+
+ /* i2c slave info */
+ uint8_t type;
+ uint8_t i2c_addr;
+ uint8_t i2c_port;
+ uint8_t __reserved;
+
+ __be32 purpose;
+ __be32 i2c_link;
+ __be16 slca_index;
+};
+
+#define P9_I2CM_XSCOM_SIZE 0x1000
+#define P9_I2CM_XSCOM_BASE 0xa0000
+
+static struct dt_node *get_i2cm_node(struct dt_node *xscom, int engine)
+{
+ uint64_t xscom_base = P9_I2CM_XSCOM_BASE + P9_I2CM_XSCOM_SIZE * engine;
+ struct dt_node *i2cm;
+
+ i2cm = dt_find_by_name_addr(xscom, "i2cm", xscom_base);
+ if (!i2cm) {
+ i2cm = dt_new_addr(xscom, "i2cm", xscom_base);
+ dt_add_property_cells(i2cm, "reg", xscom_base,
+ P9_I2CM_XSCOM_SIZE);
+
+ dt_add_property_strings(i2cm, "compatible",
+ "ibm,power8-i2cm", "ibm,power9-i2cm");
+
+ dt_add_property_cells(i2cm, "#size-cells", 0);
+ dt_add_property_cells(i2cm, "#address-cells", 1);
+ dt_add_property_cells(i2cm, "chip-engine#", engine);
+
+ /* XXX: verify this */
+ dt_add_property_cells(i2cm, "clock-frequency", 150000000);
+ }
+
+ return i2cm;
+}
+
+static struct dt_node *get_bus_node(struct dt_node *i2cm, int port, int freq)
+{
+ struct dt_node *bus;
+
+ bus = dt_find_by_name_addr(i2cm, "i2c-bus", port);
+ if (!bus) {
+ bus = dt_new_addr(i2cm, "i2c-bus", port);
+ dt_add_property_cells(bus, "reg", port);
+ dt_add_property_cells(bus, "#size-cells", 0);
+ dt_add_property_cells(bus, "#address-cells", 1);
+
+ /* The P9 I2C master is fully compatible with the P8 one */
+ dt_add_property_strings(bus, "compatible", "ibm,opal-i2c",
+ "ibm,power8-i2c-port", "ibm,power9-i2c-port");
+
+ /*
+ * use the clock frequency as the bus frequency until we
+ * have actual devices on the bus. Adding a device will
+ * reduce the frequency to something that all devices
+ * can tolerate.
+ */
+ dt_add_property_cells(bus, "bus-frequency", freq);
+ }
+
+ return bus;
+}
+
+struct hdat_i2c_type {
+ uint32_t id;
+ const char *name;
+ const char *compat;
+};
+
+struct hdat_i2c_type hdat_i2c_devs[] = {
+ /* XXX: Please verify that all VPD EEPROMs are of this type */
+ { 0x2, "eeprom", "atmel,24c128" }
+};
+
+struct hdat_i2c_label {
+ uint32_t id;
+ const char *label;
+};
+
+struct hdat_i2c_label hdat_i2c_labels[] = {
+ { 0x1, "9551-led-controller" },
+ { 0x2, "seeprom" },
+ { 0x5, "module-vpd" },
+ { 0x6, "dimm SPD" },
+ { 0x7, "proc-vpd" },
+ { 0x8, "sbe-eeprom" },
+ { 0x9, "planar-vpd" }
+};
+
+/*
+ * 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 const char *map_label(uint32_t type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hdat_i2c_labels); i++)
+ if (hdat_i2c_labels[i].id == type)
+ return hdat_i2c_labels[i].label;
+
+ return NULL;
+}
+
+int parse_i2c_devs(const struct HDIF_common_hdr *hdr, int idata_index,
+ struct dt_node *xscom)
+{
+ struct dt_node *i2cm, *bus, *node;
+ const struct hdat_i2c_type *type;
+ const struct i2c_dev *dev;
+ const char *label, *name, *compat;
+ uint32_t i2c_addr;
+ 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);
+
+ count = HDIF_get_iarray_size(hdr, idata_index);
+ for (i = 0; i < count; i++) {
+ dev = HDIF_get_iarray_item(hdr, idata_index, i, NULL);
+
+ i2cm = get_i2cm_node(xscom, dev->i2cm_engine);
+ bus = get_bus_node(i2cm, dev->i2cm_port,
+ be16_to_cpu(dev->i2c_bus_freq));
+
+ /*
+ * 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.
+ */
+ i2c_addr = dev->i2c_addr >> 1;
+
+ prlog(PR_TRACE, "HDAT I2C: found e%dp%d - %x\n",
+ dev->i2cm_engine, dev->i2cm_port, i2c_addr);
+
+ type = map_type(dev->type);
+ label = map_label(be32_to_cpu(dev->purpose));
+ if (type) {
+ compat = type->compat;
+ name = type->name;
+ } else {
+ name = "unknown";
+ compat = NULL;
+ }
+
+ node = dt_new_addr(bus, name, i2c_addr);
+ dt_add_property_cells(node, "reg", i2c_addr);
+ dt_add_property_cells(node, "link-id",
+ be32_to_cpu(dev->i2c_link));
+ if (compat)
+ dt_add_property_string(node, "compatible", compat);
+ if (label)
+ dt_add_property_string(node, "label", label);
+
+ /* XXX: SLCA index? */
+ }
+
+ return 0;
+}
OpenPOWER on IntegriCloud