blob: 5ea693b1d3c560bee03ae8997c8948e712ce04db [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
*/
/* -------------------------------------------------------------------------
* Includes
* -------------------------------------------------------------------------
*/
#include "npu_hw_access.h"
#include "npu_mgr.h"
#include "npu_firmware.h"
#include "npu_hw.h"
#include "npu_host_ipc.h"
/* -------------------------------------------------------------------------
* Defines
* -------------------------------------------------------------------------
*/
/* HFI IPC interface */
#define TX_HDR_TYPE 0x01000000
#define RX_HDR_TYPE 0x00010000
#define HFI_QTBL_STATUS_ENABLED 0x00000001
#define QUEUE_TBL_VERSION 0x87654321
/* -------------------------------------------------------------------------
* Data Structures
* -------------------------------------------------------------------------
*/
struct npu_queue_tuple {
uint32_t size;
uint32_t hdr;
};
static const struct npu_queue_tuple npu_q_setup[6] = {
{ 1024, IPC_QUEUE_CMD_HIGH_PRIORITY | TX_HDR_TYPE | RX_HDR_TYPE },
{ 4096, IPC_QUEUE_APPS_EXEC | TX_HDR_TYPE | RX_HDR_TYPE },
{ 4096, IPC_QUEUE_DSP_EXEC | TX_HDR_TYPE | RX_HDR_TYPE },
{ 4096, IPC_QUEUE_APPS_RSP | TX_HDR_TYPE | RX_HDR_TYPE },
{ 4096, IPC_QUEUE_DSP_RSP | TX_HDR_TYPE | RX_HDR_TYPE },
{ 1024, IPC_QUEUE_LOG | TX_HDR_TYPE | RX_HDR_TYPE },
};
/* -------------------------------------------------------------------------
* File Scope Function Prototypes
* -------------------------------------------------------------------------
*/
static int npu_host_ipc_init_hfi(struct npu_device *npu_dev);
static int npu_host_ipc_send_cmd_hfi(struct npu_device *npu_dev,
uint32_t q_idx, void *cmd_ptr);
static int npu_host_ipc_read_msg_hfi(struct npu_device *npu_dev,
uint32_t q_idx, uint32_t *msg_ptr);
static int ipc_queue_read(struct npu_device *npu_dev, uint32_t target_que,
uint8_t *packet, uint8_t *is_tx_req_set);
static int ipc_queue_write(struct npu_device *npu_dev, uint32_t target_que,
uint8_t *packet, uint8_t *is_rx_req_set);
/* -------------------------------------------------------------------------
* Function Definitions
* -------------------------------------------------------------------------
*/
static int npu_host_ipc_init_hfi(struct npu_device *npu_dev)
{
int status = 0;
struct hfi_queue_tbl_header *q_tbl_hdr = NULL;
struct hfi_queue_header *q_hdr_arr = NULL;
struct hfi_queue_header *q_hdr = NULL;
void *q_tbl_addr = 0;
uint32_t reg_val = 0;
uint32_t q_idx = 0;
uint32_t q_tbl_size = sizeof(struct hfi_queue_tbl_header) +
(NPU_HFI_NUMBER_OF_QS * sizeof(struct hfi_queue_header));
uint32_t q_size = 0;
uint32_t cur_start_offset = 0;
reg_val = REGR(npu_dev, REG_NPU_FW_CTRL_STATUS);
/*
* If the firmware is already running and we're just attaching,
* we do not need to do this
*/
if ((reg_val & FW_CTRL_STATUS_LOG_READY_VAL) != 0)
return status;
/* check for valid interface queue table start address */
q_tbl_addr = kzalloc(q_tbl_size, GFP_KERNEL);
if (q_tbl_addr == NULL)
return -ENOMEM;
/* retrieve interface queue table start address */
q_tbl_hdr = q_tbl_addr;
q_hdr_arr = (struct hfi_queue_header *)((uint8_t *)q_tbl_addr +
sizeof(struct hfi_queue_tbl_header));
/* initialize the interface queue table header */
q_tbl_hdr->qtbl_version = QUEUE_TBL_VERSION;
q_tbl_hdr->qtbl_size = q_tbl_size;
q_tbl_hdr->qtbl_qhdr0_offset = sizeof(struct hfi_queue_tbl_header);
q_tbl_hdr->qtbl_qhdr_size = sizeof(struct hfi_queue_header);
q_tbl_hdr->qtbl_num_q = NPU_HFI_NUMBER_OF_QS;
q_tbl_hdr->qtbl_num_active_q = NPU_HFI_NUMBER_OF_ACTIVE_QS;
cur_start_offset = q_tbl_size;
for (q_idx = IPC_QUEUE_CMD_HIGH_PRIORITY;
q_idx <= IPC_QUEUE_LOG; q_idx++) {
q_hdr = &q_hdr_arr[q_idx];
/* queue is active */
q_hdr->qhdr_status = 0x01;
q_hdr->qhdr_start_offset = cur_start_offset;
q_size = npu_q_setup[q_idx].size;
q_hdr->qhdr_type = npu_q_setup[q_idx].hdr;
/* in bytes */
q_hdr->qhdr_q_size = q_size;
/* variable size packets */
q_hdr->qhdr_pkt_size = 0;
q_hdr->qhdr_pkt_drop_cnt = 0;
q_hdr->qhdr_rx_wm = 0x1;
q_hdr->qhdr_tx_wm = 0x1;
/* since queue is initially empty */
q_hdr->qhdr_rx_req = 0x1;
q_hdr->qhdr_tx_req = 0x0;
/* not used */
q_hdr->qhdr_rx_irq_status = 0;
/* not used */
q_hdr->qhdr_tx_irq_status = 0;
q_hdr->qhdr_read_idx = 0;
q_hdr->qhdr_write_idx = 0;
cur_start_offset += q_size;
}
MEMW(npu_dev, IPC_ADDR, (uint8_t *)q_tbl_hdr, q_tbl_size);
kfree(q_tbl_addr);
/* Write in the NPU's address for where IPC starts */
REGW(npu_dev, (uint32_t)REG_NPU_HOST_CTRL_VALUE,
(uint32_t)(npu_dev->tcm_io.phy_addr +
IPC_MEM_OFFSET_FROM_SSTCM));
/* Set value bit */
reg_val = REGR(npu_dev, (uint32_t)REG_NPU_HOST_CTRL_STATUS);
REGW(npu_dev, (uint32_t)REG_NPU_HOST_CTRL_STATUS, reg_val |
HOST_CTRL_STATUS_IPC_ADDRESS_READY_VAL);
return status;
}
static int npu_host_ipc_send_cmd_hfi(struct npu_device *npu_dev,
uint32_t q_idx, void *cmd_ptr)
{
int status = 0;
uint8_t is_rx_req_set = 0;
uint32_t retry_cnt = 5;
status = ipc_queue_write(npu_dev, q_idx, (uint8_t *)cmd_ptr,
&is_rx_req_set);
if (status == -ENOSPC) {
do {
msleep(20);
status = ipc_queue_write(npu_dev, q_idx,
(uint8_t *)cmd_ptr, &is_rx_req_set);
} while ((status == -ENOSPC) && (--retry_cnt > 0));
}
if (status == 0) {
if (is_rx_req_set == 1)
status = INTERRUPT_RAISE_NPU(npu_dev);
}
if (status)
NPU_ERR("Cmd Msg put on Command Queue - FAILURE\n");
return status;
}
static int npu_host_ipc_read_msg_hfi(struct npu_device *npu_dev,
uint32_t q_idx, uint32_t *msg_ptr)
{
int status = 0;
uint8_t is_tx_req_set;
status = ipc_queue_read(npu_dev, q_idx, (uint8_t *)msg_ptr,
&is_tx_req_set);
if (status == 0) {
/* raise interrupt if qhdr_tx_req is set */
if (is_tx_req_set == 1)
status = INTERRUPT_RAISE_NPU(npu_dev);
}
return status;
}
static int ipc_queue_read(struct npu_device *npu_dev,
uint32_t target_que, uint8_t *packet,
uint8_t *is_tx_req_set)
{
int status = 0;
struct hfi_queue_header queue;
uint32_t packet_size, new_read_idx;
size_t read_ptr;
size_t offset = 0;
offset = (size_t)IPC_ADDR + sizeof(struct hfi_queue_tbl_header) +
target_que * sizeof(struct hfi_queue_header);
if ((packet == NULL) || (is_tx_req_set == NULL))
return -EINVAL;
/* Read the queue */
MEMR(npu_dev, (void *)((size_t)offset), (uint8_t *)&queue,
HFI_QUEUE_HEADER_SIZE);
/* check if queue is empty */
if (queue.qhdr_read_idx == queue.qhdr_write_idx) {
/*
* set qhdr_rx_req, to inform the sender that the Interrupt
* needs to be raised with the next packet queued
*/
queue.qhdr_rx_req = 1;
*is_tx_req_set = 0;
status = -EIO;
goto exit;
}
read_ptr = ((size_t)(size_t)IPC_ADDR +
queue.qhdr_start_offset + queue.qhdr_read_idx);
/* Read packet size */
MEMR(npu_dev, (void *)((size_t)read_ptr), packet, 4);
packet_size = *((uint32_t *)packet);
NPU_DBG("target_que: %d, packet_size: %d\n",
target_que,
packet_size);
if ((packet_size == 0) ||
(packet_size > NPU_IPC_BUF_LENGTH)) {
NPU_ERR("Invalid packet size %d\n", packet_size);
status = -EINVAL;
goto exit;
}
new_read_idx = queue.qhdr_read_idx + packet_size;
if (new_read_idx < (queue.qhdr_q_size)) {
MEMR(npu_dev, (void *)((size_t)read_ptr), packet, packet_size);
} else {
new_read_idx -= (queue.qhdr_q_size);
MEMR(npu_dev, (void *)((size_t)read_ptr), packet,
packet_size - new_read_idx);
MEMR(npu_dev, (void *)((size_t)IPC_ADDR +
queue.qhdr_start_offset),
(void *)((size_t)packet + (packet_size-new_read_idx)),
new_read_idx);
}
queue.qhdr_read_idx = new_read_idx;
if (queue.qhdr_read_idx == queue.qhdr_write_idx)
/*
* receiver wants an interrupt from transmitter
* (when next item queued) because queue is empty
*/
queue.qhdr_rx_req = 1;
else
/* clear qhdr_rx_req since the queue is not empty */
queue.qhdr_rx_req = 0;
if (queue.qhdr_tx_req == 1)
/* transmitter requested an interrupt */
*is_tx_req_set = 1;
else
*is_tx_req_set = 0;
exit:
/* Update RX interrupt request -- queue.qhdr_rx_req */
MEMW(npu_dev, (void *)((size_t)offset +
(uint32_t)((size_t)&(queue.qhdr_rx_req) -
(size_t)&queue)), (uint8_t *)&queue.qhdr_rx_req,
sizeof(queue.qhdr_rx_req));
/* Update Read pointer -- queue.qhdr_read_idx */
MEMW(npu_dev, (void *)((size_t)offset + (uint32_t)(
(size_t)&(queue.qhdr_read_idx) - (size_t)&queue)),
(uint8_t *)&queue.qhdr_read_idx, sizeof(queue.qhdr_read_idx));
return status;
}
static int ipc_queue_write(struct npu_device *npu_dev,
uint32_t target_que, uint8_t *packet,
uint8_t *is_rx_req_set)
{
int status = 0;
struct hfi_queue_header queue;
uint32_t packet_size, new_write_idx;
uint32_t empty_space;
void *write_ptr;
uint32_t read_idx;
size_t offset = (size_t)IPC_ADDR +
sizeof(struct hfi_queue_tbl_header) +
target_que * sizeof(struct hfi_queue_header);
if ((packet == NULL) || (is_rx_req_set == NULL))
return -EINVAL;
MEMR(npu_dev, (void *)((size_t)offset), (uint8_t *)&queue,
HFI_QUEUE_HEADER_SIZE);
packet_size = (*(uint32_t *)packet);
if (packet_size == 0) {
/* assign failed status and return */
status = -EINVAL;
goto exit;
}
/* sample Read Idx */
read_idx = queue.qhdr_read_idx;
/* Calculate Empty Space(UWord32) in the Queue */
empty_space = (queue.qhdr_write_idx >= read_idx) ?
((queue.qhdr_q_size) - (queue.qhdr_write_idx - read_idx)) :
(read_idx - queue.qhdr_write_idx);
if (empty_space <= packet_size) {
/*
* If Queue is FULL/ no space for message
* set qhdr_tx_req.
*/
queue.qhdr_tx_req = 1;
/*
* Queue is FULL, force raise an interrupt to Receiver
*/
*is_rx_req_set = 1;
status = -ENOSPC;
goto exit;
}
/*
* clear qhdr_tx_req so that receiver does not raise an interrupt
* on reading packets from Queue, since there is space to write
* the next packet
*/
queue.qhdr_tx_req = 0;
new_write_idx = (queue.qhdr_write_idx + packet_size);
write_ptr = (void *)(size_t)((size_t)IPC_ADDR +
queue.qhdr_start_offset + queue.qhdr_write_idx);
if (new_write_idx < queue.qhdr_q_size) {
MEMW(npu_dev, (void *)((size_t)write_ptr), (uint8_t *)packet,
packet_size);
} else {
/* wraparound case */
new_write_idx -= (queue.qhdr_q_size);
MEMW(npu_dev, (void *)((size_t)write_ptr), (uint8_t *)packet,
packet_size - new_write_idx);
MEMW(npu_dev, (void *)((size_t)((size_t)IPC_ADDR +
queue.qhdr_start_offset)), (uint8_t *)(packet +
(packet_size - new_write_idx)), new_write_idx);
}
/* Update qhdr_write_idx */
queue.qhdr_write_idx = new_write_idx;
/* Update Write pointer -- queue.qhdr_write_idx */
exit:
/* Update TX request -- queue.qhdr_tx_req */
MEMW(npu_dev, (void *)((size_t)(offset + (uint32_t)(
(size_t)&(queue.qhdr_tx_req) - (size_t)&queue))),
&queue.qhdr_tx_req, sizeof(queue.qhdr_tx_req));
MEMW(npu_dev, (void *)((size_t)(offset + (uint32_t)(
(size_t)&(queue.qhdr_write_idx) - (size_t)&queue))),
&queue.qhdr_write_idx, sizeof(queue.qhdr_write_idx));
/* check if irq is required after write_idx is updated */
MEMR(npu_dev, (void *)((size_t)(offset + (uint32_t)(
(size_t)&(queue.qhdr_rx_req) - (size_t)&queue))),
(uint8_t *)&queue.qhdr_rx_req,
sizeof(queue.qhdr_rx_req));
*is_rx_req_set = (queue.qhdr_rx_req == 1) ? 1 : 0;
return status;
}
/* -------------------------------------------------------------------------
* IPC Interface functions
* -------------------------------------------------------------------------
*/
int npu_host_ipc_send_cmd(struct npu_device *npu_dev, uint32_t q_idx,
void *cmd_ptr)
{
return npu_host_ipc_send_cmd_hfi(npu_dev, q_idx, cmd_ptr);
}
int npu_host_ipc_read_msg(struct npu_device *npu_dev, uint32_t q_idx,
uint32_t *msg_ptr)
{
return npu_host_ipc_read_msg_hfi(npu_dev, q_idx, msg_ptr);
}
int npu_host_ipc_pre_init(struct npu_device *npu_dev)
{
return npu_host_ipc_init_hfi(npu_dev);
}
int npu_host_ipc_post_init(struct npu_device *npu_dev)
{
return 0;
}
int npu_host_get_ipc_queue_size(struct npu_device *npu_dev, uint32_t q_idx)
{
if (q_idx >= ARRAY_SIZE(npu_q_setup)) {
NPU_ERR("Invalid ipc queue index %d\n", q_idx);
return -EINVAL;
}
return npu_q_setup[q_idx].size;
}