mirror of
https://github.com/torvalds/linux.git
synced 2026-09-25 17:42:03 +02:00
Add support for loading AIE4 firmware through the common PSP interfaces. Compared to AIE2, AIE4 introduces an additional CERT firmware image. aiem_psp_create() performs CERT setup when the CERT image size is non-zero. Co-developed-by: Hayden Laccabue <Hayden.Laccabue@amd.com> Signed-off-by: Hayden Laccabue <Hayden.Laccabue@amd.com> Signed-off-by: David Zhang <yidong.zhang@amd.com> Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com> Link: https://patch.msgid.link/20260330163705.3153647-5-lizhi.hou@amd.com
236 lines
5.4 KiB
C
236 lines
5.4 KiB
C
// 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;
|
|
}
|