#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)
#define PSP_VALIDATE 1
#define PSP_START 2
#define PSP_RELEASE_TMR 3
#define PSP_VALIDATE_CERT 4
#define PSP_START_COPY_FW 1
#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
#define PSP_POLL_TIMEOUT 1000000
#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;
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;
}
for (i = 0; i < PSP_NUM_IN_REGS; i++)
writel(reg_vals[i], PSP_REG(psp, i));
writel(0, PSP_REG(psp, PSP_INTR_REG));
writel(psp->conf.notify_val, PSP_REG(psp, PSP_INTR_REG));
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);
}
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;
}
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;
}