| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2026, Advanced Micro Devices, Inc. |
| */ |
| |
| #include <drm/drm_device.h> |
| #include <drm/drm_managed.h> |
| #include <drm/drm_print.h> |
| #include <linux/bitfield.h> |
| #include <linux/iopoll.h> |
| #include <linux/slab.h> |
| |
| #include "aie.h" |
| |
| #define PSP_STATUS_READY BIT(31) |
| |
| /* PSP commands */ |
| #define PSP_VALIDATE 1 |
| #define PSP_START 2 |
| #define PSP_RELEASE_TMR 3 |
| #define PSP_VALIDATE_CERT 4 |
| |
| /* PSP special arguments */ |
| #define PSP_START_COPY_FW 1 |
| |
| /* PSP response error code */ |
| #define PSP_ERROR_CANCEL 0xFFFF0002 |
| #define PSP_ERROR_BAD_STATE 0xFFFF0007 |
| |
| #define PSP_FW_ALIGN 0x10000 |
| #define PSP_CFW_ALIGN 0x8000 |
| #define PSP_POLL_INTERVAL 20000 /* us */ |
| #define PSP_POLL_TIMEOUT 1000000 /* us */ |
| |
| #define PSP_REG(p, reg) ((p)->conf.psp_regs[reg]) |
| #define PSP_SET_CMD(psp, reg_vals, cmd, arg0, arg1, arg2) \ |
| ({ \ |
| u32 *_regs = reg_vals; \ |
| u32 _cmd = cmd; \ |
| _regs[0] = _cmd; \ |
| _regs[1] = arg0; \ |
| _regs[2] = arg1; \ |
| _regs[3] = ((arg2) | ((_cmd) << 24)) & (psp)->conf.arg2_mask; \ |
| }) |
| |
| struct psp_device { |
| struct drm_device *ddev; |
| struct psp_config conf; |
| u32 fw_buf_sz; |
| u64 fw_paddr; |
| void *fw_buffer; |
| u32 certfw_buf_sz; |
| u64 certfw_paddr; |
| void *certfw_buffer; |
| }; |
| |
| static int psp_exec(struct psp_device *psp, u32 *reg_vals) |
| { |
| u32 resp_code; |
| int ret, i; |
| u32 ready; |
| |
| /* Check for PSP ready before any write */ |
| ret = readx_poll_timeout(readl, PSP_REG(psp, PSP_STATUS_REG), ready, |
| FIELD_GET(PSP_STATUS_READY, ready), |
| PSP_POLL_INTERVAL, PSP_POLL_TIMEOUT); |
| if (ret) { |
| drm_err(psp->ddev, "PSP is not ready, ret 0x%x", ret); |
| return ret; |
| } |
| |
| /* Write command and argument registers */ |
| for (i = 0; i < PSP_NUM_IN_REGS; i++) |
| writel(reg_vals[i], PSP_REG(psp, i)); |
| |
| /* clear and set PSP INTR register to kick off */ |
| writel(0, PSP_REG(psp, PSP_INTR_REG)); |
| writel(psp->conf.notify_val, PSP_REG(psp, PSP_INTR_REG)); |
| |
| /* PSP should be busy. Wait for ready, so we know task is done. */ |
| ret = readx_poll_timeout(readl, PSP_REG(psp, PSP_STATUS_REG), ready, |
| FIELD_GET(PSP_STATUS_READY, ready), |
| PSP_POLL_INTERVAL, PSP_POLL_TIMEOUT); |
| if (ret) { |
| drm_err(psp->ddev, "PSP is not ready, ret 0x%x", ret); |
| return ret; |
| } |
| |
| resp_code = readl(PSP_REG(psp, PSP_RESP_REG)); |
| if (resp_code) { |
| drm_err(psp->ddev, "fw return error 0x%x", resp_code); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| int aie_psp_waitmode_poll(struct psp_device *psp) |
| { |
| struct amdxdna_dev *xdna = to_xdna_dev(psp->ddev); |
| u32 mode_reg; |
| int ret; |
| |
| ret = readx_poll_timeout(readl, PSP_REG(psp, PSP_PWAITMODE_REG), mode_reg, |
| (mode_reg & 0x1) == 1, |
| PSP_POLL_INTERVAL, PSP_POLL_TIMEOUT); |
| if (ret) |
| XDNA_ERR(xdna, "fw waitmode reg error, ret %d", ret); |
| |
| return ret; |
| } |
| |
| void aie_psp_stop(struct psp_device *psp) |
| { |
| u32 reg_vals[PSP_NUM_IN_REGS]; |
| int ret; |
| |
| PSP_SET_CMD(psp, reg_vals, PSP_RELEASE_TMR, 0, 0, 0); |
| |
| ret = psp_exec(psp, reg_vals); |
| if (ret) |
| drm_err(psp->ddev, "release tmr failed, ret %d", ret); |
| } |
| |
| static int psp_validate_fw(struct psp_device *psp, u8 cmd, u64 paddr, u32 buf_sz) |
| { |
| u32 reg_vals[PSP_NUM_IN_REGS]; |
| int ret; |
| |
| PSP_SET_CMD(psp, reg_vals, cmd, lower_32_bits(paddr), |
| upper_32_bits(paddr), buf_sz); |
| |
| ret = psp_exec(psp, reg_vals); |
| if (ret) |
| drm_err(psp->ddev, "failed to validate fw, ret %d", ret); |
| |
| return ret; |
| } |
| |
| static int psp_start(struct psp_device *psp) |
| { |
| u32 reg_vals[PSP_NUM_IN_REGS]; |
| int ret; |
| |
| PSP_SET_CMD(psp, reg_vals, PSP_START, PSP_START_COPY_FW, 0, 0); |
| |
| ret = psp_exec(psp, reg_vals); |
| if (ret) |
| drm_err(psp->ddev, "failed to start fw, ret %d", ret); |
| |
| return ret; |
| } |
| |
| int aie_psp_start(struct psp_device *psp) |
| { |
| int ret; |
| |
| ret = psp_validate_fw(psp, PSP_VALIDATE, |
| psp->fw_paddr, psp->fw_buf_sz); |
| if (ret) |
| return ret; |
| |
| if (!psp->certfw_buf_sz) |
| goto psp_start; |
| |
| ret = psp_validate_fw(psp, PSP_VALIDATE_CERT, |
| psp->certfw_paddr, psp->certfw_buf_sz); |
| if (ret) |
| return ret; |
| psp_start: |
| return psp_start(psp); |
| } |
| |
| /* |
| * PSP requires host physical address to load firmware. |
| * Allocate a buffer, obtain its physical address, align, and copy data in. |
| */ |
| static void *psp_alloc_fw_buf(struct psp_device *psp, const void *fw_data, |
| u32 fw_size, u32 align, u32 *buf_sz, |
| u64 *paddr) |
| { |
| u32 alloc_sz; |
| void *buffer; |
| u64 offset; |
| |
| *buf_sz = ALIGN(fw_size, align); |
| alloc_sz = *buf_sz + align; |
| |
| buffer = drmm_kmalloc(psp->ddev, alloc_sz, GFP_KERNEL); |
| if (!buffer) |
| return NULL; |
| |
| *paddr = virt_to_phys(buffer); |
| offset = ALIGN(*paddr, align) - *paddr; |
| *paddr += offset; |
| memcpy(buffer + offset, fw_data, fw_size); |
| |
| return buffer; |
| } |
| |
| struct psp_device *aiem_psp_create(struct drm_device *ddev, struct psp_config *conf) |
| { |
| struct psp_device *psp; |
| |
| psp = drmm_kzalloc(ddev, sizeof(*psp), GFP_KERNEL); |
| if (!psp) |
| return NULL; |
| |
| psp->ddev = ddev; |
| psp->fw_buffer = psp_alloc_fw_buf(psp, conf->fw_buf, conf->fw_size, |
| PSP_FW_ALIGN, &psp->fw_buf_sz, |
| &psp->fw_paddr); |
| if (!psp->fw_buffer) |
| return NULL; |
| |
| if (!conf->certfw_size) { |
| drm_dbg(ddev, "no cert fw"); |
| goto done; |
| } |
| |
| /* CERT firmware */ |
| psp->certfw_buffer = psp_alloc_fw_buf(psp, conf->certfw_buf, |
| conf->certfw_size, PSP_CFW_ALIGN, |
| &psp->certfw_buf_sz, |
| &psp->certfw_paddr); |
| if (!psp->certfw_buffer) { |
| drm_err(ddev, "no memory for cert fw buffer"); |
| return NULL; |
| } |
| |
| done: |
| memcpy(&psp->conf, conf, sizeof(psp->conf)); |
| |
| return psp; |
| } |