/* SPDX-License-Identifier: GPL-2.0-only */ #define __SIMPLE_DEVICE__ #include <device/mmio.h> #include <assert.h> #include <console/console.h> #include <intelblocks/pcr.h> #include <device/pci_ops.h> #include <soc/pci_devs.h> #include <timer.h> #if (CONFIG_PCR_BASE_ADDRESS == 0) #error "PCR_BASE_ADDRESS need to be non-zero!" #endif #if !CONFIG(PCR_COMMON_IOSF_1_0) #define PCR_SBI_CMD_TIMEOUT 10 /* 10ms */ /* P2SB PCI configuration register */ #define P2SB_CR_SBI_ADDR 0xd0 #define P2SB_CR_SBI_DESTID 24 #define P2SB_CR_SBI_DATA 0xd4 #define P2SB_CR_SBI_STATUS 0xd8 /* Bit 15:8 */ #define P2SB_CR_SBI_OPCODE 8 #define P2SB_CR_SBI_OPCODE_MASK 0xFF00 /* Bit 7 */ #define P2SB_CR_SBI_POSTED 7 #define P2SB_CR_SBI_POSTED_MASK 0x0080 /* Bit 2-1 */ #define P2SB_CR_SBI_STATUS_MASK 0x0006 #define P2SB_CR_SBI_STATUS_SUCCESS 0 #define P2SB_CR_SBI_STATUS_NOT_SUPPORTED 1 #define P2SB_CR_SBI_STATUS_POWERED_DOWN 2 #define P2SB_CR_SBI_STATUS_MULTI_CAST_MIXED 3 /* Bit 0 */ #define P2SB_CR_SBI_STATUS_READY 0 #define P2SB_CR_SBI_STATUS_BUSY 1 #define P2SB_CR_SBI_ROUTE_IDEN 0xda /* Bit 15-12 */ #define P2SB_CR_SBI_FBE 12 #define P2SB_CR_SBI_FBE_MASK 0xF /* Bit 10-8 */ #define P2SB_CR_SBI_BAR 8 #define P2SB_CR_SBI_MASK 0x7 /* Bit 7-0 */ #define P2SB_CR_SBI_FID 0 #define P2SB_CR_SBI_FID_MASK 0xFF #define P2SB_CR_SBI_EXT_ADDR 0xdc #endif static void *__pcr_reg_address(uint8_t pid, uint16_t offset) { uintptr_t reg_addr; /* Create an address based off of port id and offset. */ reg_addr = CONFIG_PCR_BASE_ADDRESS; reg_addr += ((uintptr_t)pid) << PCR_PORTID_SHIFT; reg_addr += (uintptr_t)offset; return (void *)reg_addr; } void *pcr_reg_address(uint8_t pid, uint16_t offset) { if (CONFIG(PCR_COMMON_IOSF_1_0)) assert(IS_ALIGNED(offset, sizeof(uint32_t))); return __pcr_reg_address(pid, offset); } /* * 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(uint16_t offset, size_t size) { const size_t align = CONFIG(PCR_COMMON_IOSF_1_0) ? sizeof(uint32_t) : size; assert(IS_ALIGNED(offset, align)); } uint32_t pcr_read32(uint8_t pid, uint16_t offset) { /* Ensure the PCR offset is correctly aligned. */ assert(IS_ALIGNED(offset, sizeof(uint32_t))); return read32(__pcr_reg_address(pid, offset)); } uint16_t pcr_read16(uint8_t pid, uint16_t offset) { /* Ensure the PCR offset is correctly aligned. */ check_pcr_offset_align(offset, sizeof(uint16_t)); return read16(__pcr_reg_address(pid, offset)); } uint8_t pcr_read8(uint8_t pid, uint16_t offset) { /* Ensure the PCR offset is correctly aligned. */ check_pcr_offset_align(offset, sizeof(uint8_t)); return read8(__pcr_reg_address(pid, 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 inline void write_completion(uint8_t pid, uint16_t offset) { read32(__pcr_reg_address(pid, ALIGN_DOWN(offset, sizeof(uint32_t)))); } void pcr_write32(uint8_t pid, uint16_t offset, uint32_t indata) { /* Ensure the PCR offset is correctly aligned. */ assert(IS_ALIGNED(offset, sizeof(indata))); write32(__pcr_reg_address(pid, offset), indata); /* Ensure the writes complete. */ write_completion(pid, offset); } void pcr_write16(uint8_t pid, uint16_t offset, uint16_t indata) { /* Ensure the PCR offset is correctly aligned. */ check_pcr_offset_align(offset, sizeof(uint16_t)); write16(__pcr_reg_address(pid, offset), indata); /* Ensure the writes complete. */ write_completion(pid, offset); } void pcr_write8(uint8_t pid, uint16_t offset, uint8_t indata) { /* Ensure the PCR offset is correctly aligned. */ check_pcr_offset_align(offset, sizeof(uint8_t)); write8(__pcr_reg_address(pid, offset), indata); /* Ensure the writes complete. */ write_completion(pid, offset); } void pcr_rmw32(uint8_t pid, uint16_t offset, uint32_t anddata, uint32_t ordata) { uint32_t data32; data32 = pcr_read32(pid, offset); data32 &= anddata; data32 |= ordata; pcr_write32(pid, offset, data32); } void pcr_rmw16(uint8_t pid, uint16_t offset, uint16_t anddata, uint16_t ordata) { uint16_t data16; data16 = pcr_read16(pid, offset); data16 &= anddata; data16 |= ordata; pcr_write16(pid, offset, data16); } void pcr_rmw8(uint8_t pid, uint16_t offset, uint8_t anddata, uint8_t ordata) { uint8_t data8; data8 = pcr_read8(pid, offset); data8 &= anddata; data8 |= ordata; pcr_write8(pid, offset, data8); } void pcr_or32(uint8_t pid, uint16_t offset, uint32_t ordata) { uint32_t data32; data32 = pcr_read32(pid, offset); data32 |= ordata; pcr_write32(pid, offset, data32); } void pcr_or16(uint8_t pid, uint16_t offset, uint16_t ordata) { uint16_t data16; data16 = pcr_read16(pid, offset); data16 |= ordata; pcr_write16(pid, offset, data16); } void pcr_or8(uint8_t pid, uint16_t offset, uint8_t ordata) { uint8_t data8; data8 = pcr_read8(pid, offset); data8 |= ordata; pcr_write8(pid, offset, data8); } #if !CONFIG(PCR_COMMON_IOSF_1_0) static int pcr_wait_for_completion(const pci_devfn_t dev) { struct stopwatch sw; stopwatch_init_msecs_expire(&sw, PCR_SBI_CMD_TIMEOUT); do { if ((pci_read_config16(dev, P2SB_CR_SBI_STATUS) & P2SB_CR_SBI_STATUS_BUSY) == 0) return 0; } while (!stopwatch_expired(&sw)); return -1; } /* * API to perform sideband communication * * Input: * struct pcr_sbi_msg * data - read/write for sbi message * response - * 0 - successful * 1 - unsuccessful * 2 - powered down * 3 - multi-cast mixed * * Output: * 0: SBI message is successfully completed * -1: SBI message failure */ int pcr_execute_sideband_msg(pci_devfn_t dev, struct pcr_sbi_msg *msg, uint32_t *data, uint8_t *response) { uint32_t sbi_data; uint16_t sbi_status; uint16_t sbi_rid; if (!msg || !data || !response) { printk(BIOS_ERR, "Pointer checked for NULL Fail! " "msg = %p \t data = %p \t response = %p\n", msg, data, response); return -1; } switch (msg->opcode) { case MEM_READ: case MEM_WRITE: case PCI_CONFIG_READ: case PCI_CONFIG_WRITE: case PCR_READ: case PCR_WRITE: case GPIO_LOCK_UNLOCK: break; default: printk(BIOS_ERR, "SBI Failure: Wrong Input = %x!\n", msg->opcode); return -1; break; } if (pci_read_config16(dev, PCI_VENDOR_ID) == 0xffff) { printk(BIOS_ERR, "SBI Failure: P2SB device Hidden!\n"); return -1; } /* * BWG Section 2.2.1 * 1. Poll P2SB PCI offset D8h[0] = 0b * Make sure the previous operation is completed. */ if (pcr_wait_for_completion(dev)) { printk(BIOS_ERR, "SBI Failure: Time Out!\n"); return -1; } /* Initial Response status */ *response = P2SB_CR_SBI_STATUS_NOT_SUPPORTED; /* * 2. Write P2SB PCI offset D0h[31:0] with Address * and Destination Port ID */ pci_write_config32(dev, P2SB_CR_SBI_ADDR, (msg->pid << P2SB_CR_SBI_DESTID) | msg->offset); /* * 3. Write P2SB PCI offset DCh[31:0] with extended address, * which is expected to be 0 */ pci_write_config32(dev, P2SB_CR_SBI_EXT_ADDR, msg->offset >> 16); /* * 4. Set P2SB PCI offset D8h[15:8] = 00000110b for read * Set P2SB PCI offset D8h[15:8] = 00000111b for write * * Set SBISTAT[15:8] to the opcode passed in * Set SBISTAT[7] to the posted passed in */ sbi_status = pci_read_config16(dev, P2SB_CR_SBI_STATUS); sbi_status &= ~(P2SB_CR_SBI_OPCODE_MASK | P2SB_CR_SBI_POSTED_MASK); sbi_status |= (msg->opcode << P2SB_CR_SBI_OPCODE) | (msg->is_posted << P2SB_CR_SBI_POSTED); pci_write_config16(dev, P2SB_CR_SBI_STATUS, sbi_status); /* * 5. Write P2SB PCI offset DAh[15:0] = F000h * * Set RID[15:0] = Fbe << 12 | Bar << 8 | Fid */ sbi_rid = ((msg->fast_byte_enable & P2SB_CR_SBI_FBE_MASK) << P2SB_CR_SBI_FBE) | ((msg->bar & P2SB_CR_SBI_MASK) << P2SB_CR_SBI_BAR) | (msg->fid & P2SB_CR_SBI_FID_MASK); pci_write_config16(dev, P2SB_CR_SBI_ROUTE_IDEN, sbi_rid); switch (msg->opcode) { case MEM_WRITE: case PCI_CONFIG_WRITE: case PCR_WRITE: case GPIO_LOCK_UNLOCK: /* * 6. Write P2SB PCI offset D4h[31:0] with the * intended data accordingly */ sbi_data = *data; pci_write_config32(dev, P2SB_CR_SBI_DATA, sbi_data); break; default: /* 6. Write P2SB PCI offset D4h[31:0] with dummy data */ pci_write_config32(dev, P2SB_CR_SBI_DATA, 0); break; } /* * 7. Set P2SB PCI offset D8h[0] = 1b, Poll P2SB PCI offset D8h[0] = 0b * * Set SBISTAT[0] = 1b, trigger the SBI operation */ sbi_status = pci_read_config16(dev, P2SB_CR_SBI_STATUS); sbi_status |= P2SB_CR_SBI_STATUS_BUSY; pci_write_config16(dev, P2SB_CR_SBI_STATUS, sbi_status); /* Poll SBISTAT[0] = 0b, Polling for Busy bit */ if (pcr_wait_for_completion(dev)) { printk(BIOS_ERR, "SBI Failure: Time Out!\n"); return -1; } /* * 8. Check if P2SB PCI offset D8h[2:1] = 00b for * successful transaction */ *response = (sbi_status & P2SB_CR_SBI_STATUS_MASK) >> 1; if (*response == P2SB_CR_SBI_STATUS_SUCCESS) { switch (msg->opcode) { case MEM_READ: case PCI_CONFIG_READ: case PCR_READ: sbi_data = pci_read_config32(dev, P2SB_CR_SBI_DATA); *data = sbi_data; break; default: break; } return 0; } printk(BIOS_ERR, "SBI Failure: Transaction Status = %x\n", *response); return -1; } #endif