mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-06 13:14:27 +00:00
5bee27aa41
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>
216 lines
5.1 KiB
C
216 lines
5.1 KiB
C
// 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),
|
|
};
|