| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright (C) 2017 Intel Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <arch/io.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 !defined(CONFIG_PCR_BASE_ADDRESS) || (CONFIG_PCR_BASE_ADDRESS == 0) |
| #error "PCR_BASE_ADDRESS need to be non-zero!" |
| #endif |
| |
| #if !IS_ENABLED(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 (IS_ENABLED(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 = IS_ENABLED(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 !IS_ENABLED(CONFIG_PCR_COMMON_IOSF_1_0) |
| |
| static int pcr_wait_for_completion(device_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(struct pcr_sbi_msg *msg, uint32_t *data, |
| uint8_t *response) |
| { |
| device_t dev = PCH_DEV_P2SB; |
| uint32_t sbi_data; |
| uint16_t sbi_status; |
| uint16_t sbi_rid; |
| |
| assert(msg && data && response); |
| |
| 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!\n"); |
| 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: |
| /* |
| * 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; |
| } else { |
| printk(BIOS_ERR, "SBI Failure: Transaction Status = %x\n", |
| *response); |
| return -1; |
| } |
| } |
| #endif |