pci: Add support for p2sb uclass
The Primary-to-Sideband bus (P2SB) is used to access various peripherals
through memory-mapped I/O in a large chunk of PCI space. The space is
segmented into different channels and peripherals are accessed by
device-specific means within those channels. Devices should be added in
the device tree as subnodes of the p2sb.
This adds a uclass and enables it for sandbox.
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
diff --git a/drivers/misc/p2sb-uclass.c b/drivers/misc/p2sb-uclass.c
new file mode 100644
index 0000000..a198700
--- /dev/null
+++ b/drivers/misc/p2sb-uclass.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Uclass for Primary-to-sideband bus, used to access various peripherals
+ *
+ * Copyright 2019 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <mapmem.h>
+#include <p2sb.h>
+#include <spl.h>
+#include <asm/io.h>
+#include <dm/uclass-internal.h>
+
+#define PCR_COMMON_IOSF_1_0 1
+
+static void *_pcr_reg_address(struct udevice *dev, uint offset)
+{
+ struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev);
+ struct udevice *p2sb = dev_get_parent(dev);
+ struct p2sb_uc_priv *upriv = dev_get_uclass_priv(p2sb);
+ uintptr_t reg_addr;
+
+ /* Create an address based off of port id and offset */
+ reg_addr = upriv->mmio_base;
+ reg_addr += pplat->pid << PCR_PORTID_SHIFT;
+ reg_addr += offset;
+
+ return map_sysmem(reg_addr, 4);
+}
+
+/*
+ * The mapping of addresses via the SBREG_BAR assumes the IOSF-SB
+ * agents are using 32-bit aligned accesses for their configuration
+ * registers. For IOSF versions greater than 1_0, IOSF-SB
+ * agents can use any access (8/16/32 bit aligned) for their
+ * configuration registers
+ */
+static inline void check_pcr_offset_align(uint offset, uint size)
+{
+ const size_t align = PCR_COMMON_IOSF_1_0 ? sizeof(uint32_t) : size;
+
+ assert(IS_ALIGNED(offset, align));
+}
+
+uint pcr_read32(struct udevice *dev, uint offset)
+{
+ void *ptr;
+ uint val;
+
+ /* Ensure the PCR offset is correctly aligned */
+ assert(IS_ALIGNED(offset, sizeof(uint32_t)));
+
+ ptr = _pcr_reg_address(dev, offset);
+ val = readl(ptr);
+ unmap_sysmem(ptr);
+
+ return val;
+}
+
+uint pcr_read16(struct udevice *dev, uint offset)
+{
+ /* Ensure the PCR offset is correctly aligned */
+ check_pcr_offset_align(offset, sizeof(uint16_t));
+
+ return readw(_pcr_reg_address(dev, offset));
+}
+
+uint pcr_read8(struct udevice *dev, uint offset)
+{
+ /* Ensure the PCR offset is correctly aligned */
+ check_pcr_offset_align(offset, sizeof(uint8_t));
+
+ return readb(_pcr_reg_address(dev, offset));
+}
+
+/*
+ * After every write one needs to perform a read an innocuous register to
+ * ensure the writes are completed for certain ports. This is done for
+ * all ports so that the callers don't need the per-port knowledge for
+ * each transaction.
+ */
+static void write_completion(struct udevice *dev, uint offset)
+{
+ readl(_pcr_reg_address(dev, ALIGN_DOWN(offset, sizeof(uint32_t))));
+}
+
+void pcr_write32(struct udevice *dev, uint offset, uint indata)
+{
+ /* Ensure the PCR offset is correctly aligned */
+ assert(IS_ALIGNED(offset, sizeof(indata)));
+
+ writel(indata, _pcr_reg_address(dev, offset));
+ /* Ensure the writes complete */
+ write_completion(dev, offset);
+}
+
+void pcr_write16(struct udevice *dev, uint offset, uint indata)
+{
+ /* Ensure the PCR offset is correctly aligned */
+ check_pcr_offset_align(offset, sizeof(uint16_t));
+
+ writew(indata, _pcr_reg_address(dev, offset));
+ /* Ensure the writes complete */
+ write_completion(dev, offset);
+}
+
+void pcr_write8(struct udevice *dev, uint offset, uint indata)
+{
+ /* Ensure the PCR offset is correctly aligned */
+ check_pcr_offset_align(offset, sizeof(uint8_t));
+
+ writeb(indata, _pcr_reg_address(dev, offset));
+ /* Ensure the writes complete */
+ write_completion(dev, offset);
+}
+
+void pcr_clrsetbits32(struct udevice *dev, uint offset, uint clr, uint set)
+{
+ uint data32;
+
+ data32 = pcr_read32(dev, offset);
+ data32 &= ~clr;
+ data32 |= set;
+ pcr_write32(dev, offset, data32);
+}
+
+void pcr_clrsetbits16(struct udevice *dev, uint offset, uint clr, uint set)
+{
+ uint data16;
+
+ data16 = pcr_read16(dev, offset);
+ data16 &= ~clr;
+ data16 |= set;
+ pcr_write16(dev, offset, data16);
+}
+
+void pcr_clrsetbits8(struct udevice *dev, uint offset, uint clr, uint set)
+{
+ uint data8;
+
+ data8 = pcr_read8(dev, offset);
+ data8 &= ~clr;
+ data8 |= set;
+ pcr_write8(dev, offset, data8);
+}
+
+int p2sb_get_port_id(struct udevice *dev)
+{
+ struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev);
+
+ return pplat->pid;
+}
+
+int p2sb_set_port_id(struct udevice *dev, int portid)
+{
+ struct udevice *ps2b;
+ struct p2sb_child_platdata *pplat;
+
+ if (!CONFIG_IS_ENABLED(OF_PLATDATA))
+ return -ENOSYS;
+
+ uclass_find_first_device(UCLASS_P2SB, &ps2b);
+ if (!ps2b)
+ return -EDEADLK;
+ dev->parent = ps2b;
+
+ /*
+ * We must allocate this, since when the device was bound it did not
+ * have a parent.
+ * TODO(sjg@chromium.org): Add a parent pointer to child devices in dtoc
+ */
+ dev->parent_platdata = malloc(sizeof(*pplat));
+ if (!dev->parent_platdata)
+ return -ENOMEM;
+ pplat = dev_get_parent_platdata(dev);
+ pplat->pid = portid;
+
+ return 0;
+}
+
+static int p2sb_child_post_bind(struct udevice *dev)
+{
+#if !CONFIG_IS_ENABLED(OF_PLATDATA)
+ struct p2sb_child_platdata *pplat = dev_get_parent_platdata(dev);
+ int ret;
+ u32 pid;
+
+ ret = dev_read_u32(dev, "intel,p2sb-port-id", &pid);
+ if (ret)
+ return ret;
+ pplat->pid = pid;
+#endif
+
+ return 0;
+}
+
+static int p2sb_post_bind(struct udevice *dev)
+{
+ if (spl_phase() > PHASE_TPL && !CONFIG_IS_ENABLED(OF_PLATDATA))
+ return dm_scan_fdt_dev(dev);
+
+ return 0;
+}
+
+UCLASS_DRIVER(p2sb) = {
+ .id = UCLASS_P2SB,
+ .name = "p2sb",
+ .per_device_auto_alloc_size = sizeof(struct p2sb_uc_priv),
+ .post_bind = p2sb_post_bind,
+ .child_post_bind = p2sb_child_post_bind,
+ .per_child_platdata_auto_alloc_size =
+ sizeof(struct p2sb_child_platdata),
+};