diff mbox series

[U-Boot,074/126] pci: Add support for p2sb uclass

Message ID 20190925145750.200592-75-sjg@chromium.org
State Superseded
Delegated to: Bin Meng
Headers show
Series x86: Add initial support for apollolake | expand

Commit Message

Simon Glass Sept. 25, 2019, 2:56 p.m. UTC
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>
---

 configs/sandbox_defconfig     |   1 +
 configs/sandbox_spl_defconfig |   1 +
 drivers/misc/Kconfig          |  33 ++++++
 drivers/misc/Makefile         |   1 +
 drivers/misc/p2sb-uclass.c    | 199 ++++++++++++++++++++++++++++++++++
 include/dm/uclass-id.h        |   1 +
 include/p2sb.h                | 127 ++++++++++++++++++++++
 7 files changed, 363 insertions(+)
 create mode 100644 drivers/misc/p2sb-uclass.c
 create mode 100644 include/p2sb.h

Comments

Bin Meng Oct. 10, 2019, 4:57 a.m. UTC | #1
Hi Simon,

On Wed, Sep 25, 2019 at 10:59 PM Simon Glass <sjg@chromium.org> wrote:
>
> 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.
>

Again, I see no value of bringing P2SB to the uclass driver level,
given as of today it is only seen in ApolloLake. Similar mechanism was
seen in old x86 SoCs, and at the old time, it was called "Message
Port", that existed in old Atom SoCs.

> This adds a uclass and enables it for sandbox.
>
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
>
>  configs/sandbox_defconfig     |   1 +
>  configs/sandbox_spl_defconfig |   1 +
>  drivers/misc/Kconfig          |  33 ++++++
>  drivers/misc/Makefile         |   1 +
>  drivers/misc/p2sb-uclass.c    | 199 ++++++++++++++++++++++++++++++++++
>  include/dm/uclass-id.h        |   1 +
>  include/p2sb.h                | 127 ++++++++++++++++++++++
>  7 files changed, 363 insertions(+)
>  create mode 100644 drivers/misc/p2sb-uclass.c
>  create mode 100644 include/p2sb.h
>
> diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
> index f77b9e8a7d1..6f4d8449290 100644
> --- a/configs/sandbox_defconfig
> +++ b/configs/sandbox_defconfig
> @@ -149,6 +149,7 @@ CONFIG_PCI=y
>  CONFIG_DM_PCI=y
>  CONFIG_DM_PCI_COMPAT=y
>  CONFIG_PCI_SANDBOX=y
> +CONFIG_P2SB=y
>  CONFIG_PHY=y
>  CONFIG_PHY_SANDBOX=y
>  CONFIG_PINCTRL=y
> diff --git a/configs/sandbox_spl_defconfig b/configs/sandbox_spl_defconfig
> index 409b8a38d5e..c49e05ec319 100644
> --- a/configs/sandbox_spl_defconfig
> +++ b/configs/sandbox_spl_defconfig
> @@ -135,6 +135,7 @@ CONFIG_PCI=y
>  CONFIG_DM_PCI=y
>  CONFIG_DM_PCI_COMPAT=y
>  CONFIG_PCI_SANDBOX=y
> +CONFIG_P2SB=y
>  CONFIG_PHY=y
>  CONFIG_PHY_SANDBOX=y
>  CONFIG_PINCTRL=y
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 8037b6ee2d7..74055a35516 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -217,6 +217,39 @@ config NUVOTON_NCT6102D
>           disable the legacy UART, the watchdog or other devices
>           in the Nuvoton Super IO chips on X86 platforms.
>
> +config P2SB
> +       bool "Intel Primary-to-Sideband Bus"
> +       depends on X86 || SANDBOX
> +       help
> +         This enables support for the Intel Primary-to-Sideband bus,
> +         abbreviated to P2SB. The P2SB is used to access various peripherals
> +         such as eSPI, GPIO, 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. A
> +         Peripheral Channel Register? (PCR) API is provided to access those
> +         devices - see pcr_readl(), etc.
> +
> +config SPL_P2SB
> +       bool "Intel Primary-to-Sideband Bus in SPL"
> +       depends on SPL && (X86 || SANDBOX)
> +       help
> +         The Primary-to-Sideband bus 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.
> +
> +config TPL_P2SB
> +       bool "Intel Primary-to-Sideband Bus in TPL"
> +       depends on TPL && (X86 || SANDBOX)
> +       help
> +         The Primary-to-Sideband bus 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.
> +
>  config PWRSEQ
>         bool "Enable power-sequencing drivers"
>         depends on DM
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index 0001d105bae..840d9fa5e39 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -49,6 +49,7 @@ obj-$(CONFIG_MXC_OCOTP) += mxc_ocotp.o
>  obj-$(CONFIG_MXS_OCOTP) += mxs_ocotp.o
>  obj-$(CONFIG_NS87308) += ns87308.o
>  obj-$(CONFIG_NUVOTON_NCT6102D) += nuvoton_nct6102d.o
> +obj-$(CONFIG_P2SB) += p2sb-uclass.o
>  obj-$(CONFIG_PCA9551_LED) += pca9551_led.o
>  obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o
>  obj-$(CONFIG_QFW) += qfw.o
> diff --git a/drivers/misc/p2sb-uclass.c b/drivers/misc/p2sb-uclass.c
> new file mode 100644
> index 00000000000..7915eb5c3ed
> --- /dev/null
> +++ b/drivers/misc/p2sb-uclass.c
> @@ -0,0 +1,199 @@
> +// 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 <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;

Having PCR_PORTID_SHIFT defined in the p2sb.h is not appropriate. A
different SoC may have a different value, or even choose a completely
different register layout, that makes the function here useless.

> +       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_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;
> +}
> +
> +UCLASS_DRIVER(p2sb) = {
> +       .id             = UCLASS_P2SB,
> +       .name           = "p2sb",
> +       .per_device_auto_alloc_size = sizeof(struct p2sb_uc_priv),
> +       .child_post_bind = p2sb_child_post_bind,
> +       .per_child_platdata_auto_alloc_size =
> +               sizeof(struct p2sb_child_platdata),
> +};
> diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
> index 907fe257ad1..496570ff47d 100644
> --- a/include/dm/uclass-id.h
> +++ b/include/dm/uclass-id.h
> @@ -68,6 +68,7 @@ enum uclass_id {
>         UCLASS_NOP,             /* No-op devices */
>         UCLASS_NORTHBRIDGE,     /* Intel Northbridge / SDRAM controller */
>         UCLASS_NVME,            /* NVM Express device */
> +       UCLASS_P2SB,            /* (x86) Primary-to-Sideband Bus */
>         UCLASS_PANEL,           /* Display panel, such as an LCD */
>         UCLASS_PANEL_BACKLIGHT, /* Backlight controller for panel */
>         UCLASS_PCH,             /* x86 platform controller hub */
> diff --git a/include/p2sb.h b/include/p2sb.h
> new file mode 100644
> index 00000000000..370f127058c
> --- /dev/null
> +++ b/include/p2sb.h
> @@ -0,0 +1,127 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2019 Google LLC
> + * Written by Simon Glass <sjg@chromium.org>
> + */
> +
> +#ifndef __p2sb_h
> +#define __p2sb_h
> +
> +/* Port Id lives in bits 23:16 and register offset lives in 15:0 of address */
> +#define PCR_PORTID_SHIFT       16
> +
> +/**
> + * struct p2sb_child_platdata - Information about each child of a p2sb device
> + *
> + * @pid: Port ID for this child
> + */
> +struct p2sb_child_platdata {
> +       uint pid;
> +};
> +
> +/**
> + * struct p2sb_uc_priv - information for the uclass about each device
> + *
> + * This must be set up by the driver when it is probed
> + *
> + * @mmio_base: Base address of P2SB region
> + */
> +struct p2sb_uc_priv {
> +       uint mmio_base;
> +};
> +
> +/**
> + * struct p2sb_ops - Operations for the P2SB (none at present)
> + */
> +struct p2sb_ops {
> +};
> +
> +#define p2sb_get_ops(dev)        ((struct p2sb_ops *)(dev)->driver->ops)
> +
> +/**
> + * pcr_read32/16/8() - Read from a PCR device
> + *
> + * Reads data from a PCR device within the P2SB
> + *
> + * @dev: Device to read from
> + * @offset: Offset within device to read
> + * @return value read
> + */
> +uint pcr_read32(struct udevice *dev, uint offset);
> +uint pcr_read16(struct udevice *dev, uint offset);
> +uint pcr_read8(struct udevice *dev, uint offset);
> +
> +/**
> + * pcr_read32/16/8() - Write to a PCR device
> + *
> + * Writes data to a PCR device within the P2SB
> + *
> + * @dev: Device to write to
> + * @offset: Offset within device to write
> + * @data: Data to write
> + */
> +void pcr_write32(struct udevice *dev, uint offset, uint data);
> +void pcr_write16(struct udevice *dev, uint offset, uint data);
> +void pcr_write8(struct udevice *dev, uint offset, uint data);
> +
> +/**
> + * pcr_clrsetbits32/16/8() - Update a PCR device
> + *
> + * Updates dat in a PCR device within the P2SB
> + *
> + * This reads from the device, clears and set bits, then writes back.
> + *
> + * new_data = (old_data & ~clr) | set
> + *
> + * @dev: Device to update
> + * @offset: Offset within device to update
> + * @clr: Bits to clear after reading
> + * @set: Bits to set before writing
> + */
> +void pcr_clrsetbits32(struct udevice *dev, uint offset, uint clr, uint set);
> +void pcr_clrsetbits16(struct udevice *dev, uint offset, uint clr, uint set);
> +void pcr_clrsetbits8(struct udevice *dev, uint offset, uint clr, uint set);
> +
> +static inline void pcr_setbits32(struct udevice *dev, uint offset, uint set)
> +{
> +       return pcr_clrsetbits32(dev, offset, 0, set);
> +}
> +
> +static inline void pcr_setbits16(struct udevice *dev, uint offset, uint set)
> +{
> +       return pcr_clrsetbits16(dev, offset, 0, set);
> +}
> +
> +static inline void pcr_setbits8(struct udevice *dev, uint offset, uint set)
> +{
> +       return pcr_clrsetbits8(dev, offset, 0, set);
> +}
> +
> +static inline void pcr_clrbits32(struct udevice *dev, uint offset, uint clr)
> +{
> +       return pcr_clrsetbits32(dev, offset, clr, 0);
> +}
> +
> +static inline void pcr_clrbits16(struct udevice *dev, uint offset, uint clr)
> +{
> +       return pcr_clrsetbits16(dev, offset, clr, 0);
> +}
> +
> +static inline void pcr_clrbits8(struct udevice *dev, uint offset, uint clr)
> +{
> +       return pcr_clrsetbits8(dev, offset, clr, 0);
> +}
> +
> +/**
> + * p2sb_set_port_id() - Set the port ID for a p2sb child device
> + *
> + * This must be called in a device's bind() method when OF_PLATDATA is used
> + * since the uclass cannot access the device's of-platdata.
> + *
> + * @dev: Child device (whose parent is UCLASS_P2SB)
> + * @portid: Port ID of child device
> + * @return 0 if OK, -ENODEV is the p2sb device could not be found
> + */
> +int p2sb_set_port_id(struct udevice *dev, int portid);
> +
> +#endif
> --

Regards,
Bin
Simon Glass Oct. 12, 2019, 3:37 a.m. UTC | #2
Hi Bin,

On Wed, 9 Oct 2019 at 22:57, Bin Meng <bmeng.cn@gmail.com> wrote:
>
> Hi Simon,
>
> On Wed, Sep 25, 2019 at 10:59 PM Simon Glass <sjg@chromium.org> wrote:
> >
> > 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.
> >
>
> Again, I see no value of bringing P2SB to the uclass driver level,
> given as of today it is only seen in ApolloLake. Similar mechanism was
> seen in old x86 SoCs, and at the old time, it was called "Message
> Port", that existed in old Atom SoCs.

OK, I'll make it a SYSCON I suppose.

Regards,
Simon
Simon Glass Oct. 12, 2019, 8:57 p.m. UTC | #3
Hi Bin,

On Fri, 11 Oct 2019 at 21:37, Simon Glass <sjg@chromium.org> wrote:
>
> Hi Bin,
>
> On Wed, 9 Oct 2019 at 22:57, Bin Meng <bmeng.cn@gmail.com> wrote:
> >
> > Hi Simon,
> >
> > On Wed, Sep 25, 2019 at 10:59 PM Simon Glass <sjg@chromium.org> wrote:
> > >
> > > 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.
> > >
> >
> > Again, I see no value of bringing P2SB to the uclass driver level,
> > given as of today it is only seen in ApolloLake. Similar mechanism was
> > seen in old x86 SoCs, and at the old time, it was called "Message
> > Port", that existed in old Atom SoCs.
>
> OK, I'll make it a SYSCON I suppose.

I dug into this a bit and I think it is better as a separate uclass.

The reason is that it has child devices which access their resources
through the uclass. Currently we have GPIO and ITSS that do this. But
from the datasheet the RTC is there alsol. The p2sb has child platdata
which supports mmio access. Take a look at p2sb-uclass.c for this.

If it is a syscon:
1. We can't really have child devices with properties known to the
uclass - syscon is for simple devices
2. The devices themselves would have to set the port ID, when really
this is a shared/uclass thing
3. We end up with an odd syscon device in the PCI bus in the device
tree, with children and no indication of why they are there

So I think a uclass is the right thing. I don't think it matters that
we end up with a few more uclasses than we would like. There is no
particular limit unless we want to have a tiny driver model with a
byte for the uclass ID.

Regards,
Simon
diff mbox series

Patch

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index f77b9e8a7d1..6f4d8449290 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -149,6 +149,7 @@  CONFIG_PCI=y
 CONFIG_DM_PCI=y
 CONFIG_DM_PCI_COMPAT=y
 CONFIG_PCI_SANDBOX=y
+CONFIG_P2SB=y
 CONFIG_PHY=y
 CONFIG_PHY_SANDBOX=y
 CONFIG_PINCTRL=y
diff --git a/configs/sandbox_spl_defconfig b/configs/sandbox_spl_defconfig
index 409b8a38d5e..c49e05ec319 100644
--- a/configs/sandbox_spl_defconfig
+++ b/configs/sandbox_spl_defconfig
@@ -135,6 +135,7 @@  CONFIG_PCI=y
 CONFIG_DM_PCI=y
 CONFIG_DM_PCI_COMPAT=y
 CONFIG_PCI_SANDBOX=y
+CONFIG_P2SB=y
 CONFIG_PHY=y
 CONFIG_PHY_SANDBOX=y
 CONFIG_PINCTRL=y
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8037b6ee2d7..74055a35516 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -217,6 +217,39 @@  config NUVOTON_NCT6102D
 	  disable the legacy UART, the watchdog or other devices
 	  in the Nuvoton Super IO chips on X86 platforms.
 
+config P2SB
+	bool "Intel Primary-to-Sideband Bus"
+	depends on X86 || SANDBOX
+	help
+	  This enables support for the Intel Primary-to-Sideband bus,
+	  abbreviated to P2SB. The P2SB is used to access various peripherals
+	  such as eSPI, GPIO, 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. A
+	  Peripheral Channel Register? (PCR) API is provided to access those
+	  devices - see pcr_readl(), etc.
+
+config SPL_P2SB
+	bool "Intel Primary-to-Sideband Bus in SPL"
+	depends on SPL && (X86 || SANDBOX)
+	help
+	  The Primary-to-Sideband bus 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.
+
+config TPL_P2SB
+	bool "Intel Primary-to-Sideband Bus in TPL"
+	depends on TPL && (X86 || SANDBOX)
+	help
+	  The Primary-to-Sideband bus 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.
+
 config PWRSEQ
 	bool "Enable power-sequencing drivers"
 	depends on DM
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 0001d105bae..840d9fa5e39 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -49,6 +49,7 @@  obj-$(CONFIG_MXC_OCOTP) += mxc_ocotp.o
 obj-$(CONFIG_MXS_OCOTP) += mxs_ocotp.o
 obj-$(CONFIG_NS87308) += ns87308.o
 obj-$(CONFIG_NUVOTON_NCT6102D) += nuvoton_nct6102d.o
+obj-$(CONFIG_P2SB) += p2sb-uclass.o
 obj-$(CONFIG_PCA9551_LED) += pca9551_led.o
 obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o
 obj-$(CONFIG_QFW) += qfw.o
diff --git a/drivers/misc/p2sb-uclass.c b/drivers/misc/p2sb-uclass.c
new file mode 100644
index 00000000000..7915eb5c3ed
--- /dev/null
+++ b/drivers/misc/p2sb-uclass.c
@@ -0,0 +1,199 @@ 
+// 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 <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_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;
+}
+
+UCLASS_DRIVER(p2sb) = {
+	.id		= UCLASS_P2SB,
+	.name		= "p2sb",
+	.per_device_auto_alloc_size = sizeof(struct p2sb_uc_priv),
+	.child_post_bind = p2sb_child_post_bind,
+	.per_child_platdata_auto_alloc_size =
+		sizeof(struct p2sb_child_platdata),
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 907fe257ad1..496570ff47d 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -68,6 +68,7 @@  enum uclass_id {
 	UCLASS_NOP,		/* No-op devices */
 	UCLASS_NORTHBRIDGE,	/* Intel Northbridge / SDRAM controller */
 	UCLASS_NVME,		/* NVM Express device */
+	UCLASS_P2SB,		/* (x86) Primary-to-Sideband Bus */
 	UCLASS_PANEL,		/* Display panel, such as an LCD */
 	UCLASS_PANEL_BACKLIGHT,	/* Backlight controller for panel */
 	UCLASS_PCH,		/* x86 platform controller hub */
diff --git a/include/p2sb.h b/include/p2sb.h
new file mode 100644
index 00000000000..370f127058c
--- /dev/null
+++ b/include/p2sb.h
@@ -0,0 +1,127 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __p2sb_h
+#define __p2sb_h
+
+/* Port Id lives in bits 23:16 and register offset lives in 15:0 of address */
+#define PCR_PORTID_SHIFT	16
+
+/**
+ * struct p2sb_child_platdata - Information about each child of a p2sb device
+ *
+ * @pid: Port ID for this child
+ */
+struct p2sb_child_platdata {
+	uint pid;
+};
+
+/**
+ * struct p2sb_uc_priv - information for the uclass about each device
+ *
+ * This must be set up by the driver when it is probed
+ *
+ * @mmio_base: Base address of P2SB region
+ */
+struct p2sb_uc_priv {
+	uint mmio_base;
+};
+
+/**
+ * struct p2sb_ops - Operations for the P2SB (none at present)
+ */
+struct p2sb_ops {
+};
+
+#define p2sb_get_ops(dev)        ((struct p2sb_ops *)(dev)->driver->ops)
+
+/**
+ * pcr_read32/16/8() - Read from a PCR device
+ *
+ * Reads data from a PCR device within the P2SB
+ *
+ * @dev: Device to read from
+ * @offset: Offset within device to read
+ * @return value read
+ */
+uint pcr_read32(struct udevice *dev, uint offset);
+uint pcr_read16(struct udevice *dev, uint offset);
+uint pcr_read8(struct udevice *dev, uint offset);
+
+/**
+ * pcr_read32/16/8() - Write to a PCR device
+ *
+ * Writes data to a PCR device within the P2SB
+ *
+ * @dev: Device to write to
+ * @offset: Offset within device to write
+ * @data: Data to write
+ */
+void pcr_write32(struct udevice *dev, uint offset, uint data);
+void pcr_write16(struct udevice *dev, uint offset, uint data);
+void pcr_write8(struct udevice *dev, uint offset, uint data);
+
+/**
+ * pcr_clrsetbits32/16/8() - Update a PCR device
+ *
+ * Updates dat in a PCR device within the P2SB
+ *
+ * This reads from the device, clears and set bits, then writes back.
+ *
+ * new_data = (old_data & ~clr) | set
+ *
+ * @dev: Device to update
+ * @offset: Offset within device to update
+ * @clr: Bits to clear after reading
+ * @set: Bits to set before writing
+ */
+void pcr_clrsetbits32(struct udevice *dev, uint offset, uint clr, uint set);
+void pcr_clrsetbits16(struct udevice *dev, uint offset, uint clr, uint set);
+void pcr_clrsetbits8(struct udevice *dev, uint offset, uint clr, uint set);
+
+static inline void pcr_setbits32(struct udevice *dev, uint offset, uint set)
+{
+	return pcr_clrsetbits32(dev, offset, 0, set);
+}
+
+static inline void pcr_setbits16(struct udevice *dev, uint offset, uint set)
+{
+	return pcr_clrsetbits16(dev, offset, 0, set);
+}
+
+static inline void pcr_setbits8(struct udevice *dev, uint offset, uint set)
+{
+	return pcr_clrsetbits8(dev, offset, 0, set);
+}
+
+static inline void pcr_clrbits32(struct udevice *dev, uint offset, uint clr)
+{
+	return pcr_clrsetbits32(dev, offset, clr, 0);
+}
+
+static inline void pcr_clrbits16(struct udevice *dev, uint offset, uint clr)
+{
+	return pcr_clrsetbits16(dev, offset, clr, 0);
+}
+
+static inline void pcr_clrbits8(struct udevice *dev, uint offset, uint clr)
+{
+	return pcr_clrsetbits8(dev, offset, clr, 0);
+}
+
+/**
+ * p2sb_set_port_id() - Set the port ID for a p2sb child device
+ *
+ * This must be called in a device's bind() method when OF_PLATDATA is used
+ * since the uclass cannot access the device's of-platdata.
+ *
+ * @dev: Child device (whose parent is UCLASS_P2SB)
+ * @portid: Port ID of child device
+ * @return 0 if OK, -ENODEV is the p2sb device could not be found
+ */
+int p2sb_set_port_id(struct udevice *dev, int portid);
+
+#endif