/* SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include /* * This is a library for the VCU (Validation Control Unit) mailbox. This * mailbox is primarily used to adjust some magic PCIe tuning parameters. * * There are two revisions of the VCU mailbox. Rev1 is specific to Haswell * stepping A0, and all other steppings use Rev2. Haswell stepping A0 CPUs * are early Engineering Samples with undocumented errata, and most likely * need special microcode updates to boot. Thus, the code does not support * VCU mailbox Rev1, because no one should need it anymore. */ #define VCU_MAILBOX_INTERFACE 0x6c00 #define VCU_MAILBOX_DATA 0x6c04 #define VCU_RUN_BUSY (1U << 31) enum vcu_opcode { VCU_INVALID_OPCODE = 0x00, VCU_OPCODE_READ_VCU_API_VER_ID = 0x01, VCU_OPCODE_OPEN_SEQ = 0x02, VCU_OPCODE_CLOSE_SEQ = 0x03, VCU_OPCODE_READ_DATA = 0x07, VCU_OPCODE_WRITE_DATA = 0x08, VCU_OPCODE_READ_CSR = 0x13, VCU_OPCODE_WRITE_CSR = 0x14, VCU_OPCODE_READ_MMIO = 0x15, VCU_OPCODE_WRITE_MMIO = 0x16, }; enum vcu_sequence { SEQ_ID_READ_CSR = 0x1, SEQ_ID_WRITE_CSR = 0x2, SEQ_ID_READ_MMIO = 0x3, SEQ_ID_WRITE_MMIO = 0x4, }; #define VCU_RESPONSE_MASK 0xffff #define VCU_RESPONSE_SUCCESS 0x40 #define VCU_RESPONSE_BUSY 0x80 #define VCU_RESPONSE_THREAD_UNAVAILABLE 0x82 #define VCU_RESPONSE_ILLEGAL 0x90 /* FIXME: Use timer API */ static void send_vcu_command(const enum vcu_opcode opcode, const uint32_t data) { if (opcode == VCU_INVALID_OPCODE) return; for (unsigned int i = 0; i < 10; i++) { mchbar_write32(VCU_MAILBOX_DATA, data); mchbar_write32(VCU_MAILBOX_INTERFACE, opcode | VCU_RUN_BUSY); uint32_t vcu_interface; for (unsigned int j = 0; j < 100; j++) { vcu_interface = mchbar_read32(VCU_MAILBOX_INTERFACE); if (!(vcu_interface & VCU_RUN_BUSY)) break; udelay(10); } if (vcu_interface & VCU_RUN_BUSY) continue; if ((vcu_interface & VCU_RESPONSE_MASK) == VCU_RESPONSE_SUCCESS) return; } printk(BIOS_ERR, "VCU: Failed to send command\n"); } static enum vcu_opcode get_register_opcode(enum vcu_sequence seq) { switch (seq) { case SEQ_ID_READ_CSR: return VCU_OPCODE_READ_CSR; case SEQ_ID_WRITE_CSR: return VCU_OPCODE_WRITE_CSR; case SEQ_ID_READ_MMIO: return VCU_OPCODE_READ_MMIO; case SEQ_ID_WRITE_MMIO: return VCU_OPCODE_WRITE_MMIO; default: BUG(); return VCU_INVALID_OPCODE; } } static enum vcu_opcode get_data_opcode(enum vcu_sequence seq) { switch (seq) { case SEQ_ID_READ_CSR: case SEQ_ID_READ_MMIO: return VCU_OPCODE_READ_DATA; case SEQ_ID_WRITE_CSR: case SEQ_ID_WRITE_MMIO: return VCU_OPCODE_WRITE_DATA; default: BUG(); return VCU_INVALID_OPCODE; } } static uint32_t send_vcu_sequence(uint32_t addr, enum vcu_sequence seq, uint32_t wr_data) { send_vcu_command(VCU_OPCODE_OPEN_SEQ, seq); send_vcu_command(get_register_opcode(seq), addr); send_vcu_command(get_data_opcode(seq), wr_data); const uint32_t rd_data = mchbar_read32(VCU_MAILBOX_DATA); send_vcu_command(VCU_OPCODE_CLOSE_SEQ, seq); return rd_data; } #define VCU_WRITE_IGNORED 0 uint32_t vcu_read_csr(uint32_t addr) { return send_vcu_sequence(addr, SEQ_ID_READ_CSR, VCU_WRITE_IGNORED); } void vcu_write_csr(uint32_t addr, uint32_t data) { send_vcu_sequence(addr, SEQ_ID_WRITE_CSR, data); } void vcu_update_csr(uint32_t addr, uint32_t andvalue, uint32_t orvalue) { vcu_write_csr(addr, (vcu_read_csr(addr) & andvalue) | orvalue); } uint32_t vcu_read_mmio(uint32_t addr) { return send_vcu_sequence(addr, SEQ_ID_READ_MMIO, VCU_WRITE_IGNORED); } void vcu_write_mmio(uint32_t addr, uint32_t data) { send_vcu_sequence(addr, SEQ_ID_WRITE_MMIO, data); } void vcu_update_mmio(uint32_t addr, uint32_t andvalue, uint32_t orvalue) { vcu_write_mmio(addr, (vcu_read_mmio(addr) & andvalue) | orvalue); }