| // 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 <malloc.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), |
| }; |