162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * AMD Passthru DMA device driver
462306a36Sopenharmony_ci * -- Based on the CCP driver
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2016,2021 Advanced Micro Devices, Inc.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Author: Sanjay R Mehta <sanju.mehta@amd.com>
962306a36Sopenharmony_ci * Author: Gary R Hook <gary.hook@amd.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/bitfield.h>
1362306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1462306a36Sopenharmony_ci#include <linux/debugfs.h>
1562306a36Sopenharmony_ci#include <linux/interrupt.h>
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/pci.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "ptdma.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/* Human-readable error strings */
2362306a36Sopenharmony_cistatic char *pt_error_codes[] = {
2462306a36Sopenharmony_ci	"",
2562306a36Sopenharmony_ci	"ERR 01: ILLEGAL_ENGINE",
2662306a36Sopenharmony_ci	"ERR 03: ILLEGAL_FUNCTION_TYPE",
2762306a36Sopenharmony_ci	"ERR 04: ILLEGAL_FUNCTION_MODE",
2862306a36Sopenharmony_ci	"ERR 06: ILLEGAL_FUNCTION_SIZE",
2962306a36Sopenharmony_ci	"ERR 08: ILLEGAL_FUNCTION_RSVD",
3062306a36Sopenharmony_ci	"ERR 09: ILLEGAL_BUFFER_LENGTH",
3162306a36Sopenharmony_ci	"ERR 10: VLSB_FAULT",
3262306a36Sopenharmony_ci	"ERR 11: ILLEGAL_MEM_ADDR",
3362306a36Sopenharmony_ci	"ERR 12: ILLEGAL_MEM_SEL",
3462306a36Sopenharmony_ci	"ERR 13: ILLEGAL_CONTEXT_ID",
3562306a36Sopenharmony_ci	"ERR 15: 0xF Reserved",
3662306a36Sopenharmony_ci	"ERR 18: CMD_TIMEOUT",
3762306a36Sopenharmony_ci	"ERR 19: IDMA0_AXI_SLVERR",
3862306a36Sopenharmony_ci	"ERR 20: IDMA0_AXI_DECERR",
3962306a36Sopenharmony_ci	"ERR 21: 0x15 Reserved",
4062306a36Sopenharmony_ci	"ERR 22: IDMA1_AXI_SLAVE_FAULT",
4162306a36Sopenharmony_ci	"ERR 23: IDMA1_AIXI_DECERR",
4262306a36Sopenharmony_ci	"ERR 24: 0x18 Reserved",
4362306a36Sopenharmony_ci	"ERR 27: 0x1B Reserved",
4462306a36Sopenharmony_ci	"ERR 38: ODMA0_AXI_SLVERR",
4562306a36Sopenharmony_ci	"ERR 39: ODMA0_AXI_DECERR",
4662306a36Sopenharmony_ci	"ERR 40: 0x28 Reserved",
4762306a36Sopenharmony_ci	"ERR 41: ODMA1_AXI_SLVERR",
4862306a36Sopenharmony_ci	"ERR 42: ODMA1_AXI_DECERR",
4962306a36Sopenharmony_ci	"ERR 43: LSB_PARITY_ERR",
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic void pt_log_error(struct pt_device *d, int e)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	dev_err(d->dev, "PTDMA error: %s (0x%x)\n", pt_error_codes[e], e);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_civoid pt_start_queue(struct pt_cmd_queue *cmd_q)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	/* Turn on the run bit */
6062306a36Sopenharmony_ci	iowrite32(cmd_q->qcontrol | CMD_Q_RUN, cmd_q->reg_control);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_civoid pt_stop_queue(struct pt_cmd_queue *cmd_q)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	/* Turn off the run bit */
6662306a36Sopenharmony_ci	iowrite32(cmd_q->qcontrol & ~CMD_Q_RUN, cmd_q->reg_control);
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int pt_core_execute_cmd(struct ptdma_desc *desc, struct pt_cmd_queue *cmd_q)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	bool soc = FIELD_GET(DWORD0_SOC, desc->dw0);
7262306a36Sopenharmony_ci	u8 *q_desc = (u8 *)&cmd_q->qbase[cmd_q->qidx];
7362306a36Sopenharmony_ci	u32 tail;
7462306a36Sopenharmony_ci	unsigned long flags;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (soc) {
7762306a36Sopenharmony_ci		desc->dw0 |= FIELD_PREP(DWORD0_IOC, desc->dw0);
7862306a36Sopenharmony_ci		desc->dw0 &= ~DWORD0_SOC;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	spin_lock_irqsave(&cmd_q->q_lock, flags);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* Copy 32-byte command descriptor to hw queue. */
8362306a36Sopenharmony_ci	memcpy(q_desc, desc, 32);
8462306a36Sopenharmony_ci	cmd_q->qidx = (cmd_q->qidx + 1) % CMD_Q_LEN;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	/* The data used by this command must be flushed to memory */
8762306a36Sopenharmony_ci	wmb();
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/* Write the new tail address back to the queue register */
9062306a36Sopenharmony_ci	tail = lower_32_bits(cmd_q->qdma_tail + cmd_q->qidx * Q_DESC_SIZE);
9162306a36Sopenharmony_ci	iowrite32(tail, cmd_q->reg_control + 0x0004);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/* Turn the queue back on using our cached control register */
9462306a36Sopenharmony_ci	pt_start_queue(cmd_q);
9562306a36Sopenharmony_ci	spin_unlock_irqrestore(&cmd_q->q_lock, flags);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return 0;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ciint pt_core_perform_passthru(struct pt_cmd_queue *cmd_q,
10162306a36Sopenharmony_ci			     struct pt_passthru_engine *pt_engine)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	struct ptdma_desc desc;
10462306a36Sopenharmony_ci	struct pt_device *pt = container_of(cmd_q, struct pt_device, cmd_q);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	cmd_q->cmd_error = 0;
10762306a36Sopenharmony_ci	cmd_q->total_pt_ops++;
10862306a36Sopenharmony_ci	memset(&desc, 0, sizeof(desc));
10962306a36Sopenharmony_ci	desc.dw0 = CMD_DESC_DW0_VAL;
11062306a36Sopenharmony_ci	desc.length = pt_engine->src_len;
11162306a36Sopenharmony_ci	desc.src_lo = lower_32_bits(pt_engine->src_dma);
11262306a36Sopenharmony_ci	desc.dw3.src_hi = upper_32_bits(pt_engine->src_dma);
11362306a36Sopenharmony_ci	desc.dst_lo = lower_32_bits(pt_engine->dst_dma);
11462306a36Sopenharmony_ci	desc.dw5.dst_hi = upper_32_bits(pt_engine->dst_dma);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (cmd_q->int_en)
11762306a36Sopenharmony_ci		pt_core_enable_queue_interrupts(pt);
11862306a36Sopenharmony_ci	else
11962306a36Sopenharmony_ci		pt_core_disable_queue_interrupts(pt);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return pt_core_execute_cmd(&desc, cmd_q);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic void pt_do_cmd_complete(unsigned long data)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct pt_tasklet_data *tdata = (struct pt_tasklet_data *)data;
12762306a36Sopenharmony_ci	struct pt_cmd *cmd = tdata->cmd;
12862306a36Sopenharmony_ci	struct pt_cmd_queue *cmd_q = &cmd->pt->cmd_q;
12962306a36Sopenharmony_ci	u32 tail;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (cmd_q->cmd_error) {
13262306a36Sopenharmony_ci	       /*
13362306a36Sopenharmony_ci		* Log the error and flush the queue by
13462306a36Sopenharmony_ci		* moving the head pointer
13562306a36Sopenharmony_ci		*/
13662306a36Sopenharmony_ci		tail = lower_32_bits(cmd_q->qdma_tail + cmd_q->qidx * Q_DESC_SIZE);
13762306a36Sopenharmony_ci		pt_log_error(cmd_q->pt, cmd_q->cmd_error);
13862306a36Sopenharmony_ci		iowrite32(tail, cmd_q->reg_control + 0x0008);
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	cmd->pt_cmd_callback(cmd->data, cmd->ret);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_civoid pt_check_status_trans(struct pt_device *pt, struct pt_cmd_queue *cmd_q)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	u32 status;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	status = ioread32(cmd_q->reg_control + 0x0010);
14962306a36Sopenharmony_ci	if (status) {
15062306a36Sopenharmony_ci		cmd_q->int_status = status;
15162306a36Sopenharmony_ci		cmd_q->q_status = ioread32(cmd_q->reg_control + 0x0100);
15262306a36Sopenharmony_ci		cmd_q->q_int_status = ioread32(cmd_q->reg_control + 0x0104);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci		/* On error, only save the first error value */
15562306a36Sopenharmony_ci		if ((status & INT_ERROR) && !cmd_q->cmd_error)
15662306a36Sopenharmony_ci			cmd_q->cmd_error = CMD_Q_ERROR(cmd_q->q_status);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		/* Acknowledge the completion */
15962306a36Sopenharmony_ci		iowrite32(status, cmd_q->reg_control + 0x0010);
16062306a36Sopenharmony_ci		pt_do_cmd_complete((ulong)&pt->tdata);
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic irqreturn_t pt_core_irq_handler(int irq, void *data)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct pt_device *pt = data;
16762306a36Sopenharmony_ci	struct pt_cmd_queue *cmd_q = &pt->cmd_q;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	pt_core_disable_queue_interrupts(pt);
17062306a36Sopenharmony_ci	pt->total_interrupts++;
17162306a36Sopenharmony_ci	pt_check_status_trans(pt, cmd_q);
17262306a36Sopenharmony_ci	pt_core_enable_queue_interrupts(pt);
17362306a36Sopenharmony_ci	return IRQ_HANDLED;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ciint pt_core_init(struct pt_device *pt)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	char dma_pool_name[MAX_DMAPOOL_NAME_LEN];
17962306a36Sopenharmony_ci	struct pt_cmd_queue *cmd_q = &pt->cmd_q;
18062306a36Sopenharmony_ci	u32 dma_addr_lo, dma_addr_hi;
18162306a36Sopenharmony_ci	struct device *dev = pt->dev;
18262306a36Sopenharmony_ci	struct dma_pool *dma_pool;
18362306a36Sopenharmony_ci	int ret;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/* Allocate a dma pool for the queue */
18662306a36Sopenharmony_ci	snprintf(dma_pool_name, sizeof(dma_pool_name), "%s_q", dev_name(pt->dev));
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	dma_pool = dma_pool_create(dma_pool_name, dev,
18962306a36Sopenharmony_ci				   PT_DMAPOOL_MAX_SIZE,
19062306a36Sopenharmony_ci				   PT_DMAPOOL_ALIGN, 0);
19162306a36Sopenharmony_ci	if (!dma_pool)
19262306a36Sopenharmony_ci		return -ENOMEM;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/* ptdma core initialisation */
19562306a36Sopenharmony_ci	iowrite32(CMD_CONFIG_VHB_EN, pt->io_regs + CMD_CONFIG_OFFSET);
19662306a36Sopenharmony_ci	iowrite32(CMD_QUEUE_PRIO, pt->io_regs + CMD_QUEUE_PRIO_OFFSET);
19762306a36Sopenharmony_ci	iowrite32(CMD_TIMEOUT_DISABLE, pt->io_regs + CMD_TIMEOUT_OFFSET);
19862306a36Sopenharmony_ci	iowrite32(CMD_CLK_GATE_CONFIG, pt->io_regs + CMD_CLK_GATE_CTL_OFFSET);
19962306a36Sopenharmony_ci	iowrite32(CMD_CONFIG_REQID, pt->io_regs + CMD_REQID_CONFIG_OFFSET);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	cmd_q->pt = pt;
20262306a36Sopenharmony_ci	cmd_q->dma_pool = dma_pool;
20362306a36Sopenharmony_ci	spin_lock_init(&cmd_q->q_lock);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	/* Page alignment satisfies our needs for N <= 128 */
20662306a36Sopenharmony_ci	cmd_q->qsize = Q_SIZE(Q_DESC_SIZE);
20762306a36Sopenharmony_ci	cmd_q->qbase = dma_alloc_coherent(dev, cmd_q->qsize,
20862306a36Sopenharmony_ci					  &cmd_q->qbase_dma,
20962306a36Sopenharmony_ci					  GFP_KERNEL);
21062306a36Sopenharmony_ci	if (!cmd_q->qbase) {
21162306a36Sopenharmony_ci		dev_err(dev, "unable to allocate command queue\n");
21262306a36Sopenharmony_ci		ret = -ENOMEM;
21362306a36Sopenharmony_ci		goto e_destroy_pool;
21462306a36Sopenharmony_ci	}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	cmd_q->qidx = 0;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Preset some register values */
21962306a36Sopenharmony_ci	cmd_q->reg_control = pt->io_regs + CMD_Q_STATUS_INCR;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* Turn off the queues and disable interrupts until ready */
22262306a36Sopenharmony_ci	pt_core_disable_queue_interrupts(pt);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	cmd_q->qcontrol = 0; /* Start with nothing */
22562306a36Sopenharmony_ci	iowrite32(cmd_q->qcontrol, cmd_q->reg_control);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	ioread32(cmd_q->reg_control + 0x0104);
22862306a36Sopenharmony_ci	ioread32(cmd_q->reg_control + 0x0100);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	/* Clear the interrupt status */
23162306a36Sopenharmony_ci	iowrite32(SUPPORTED_INTERRUPTS, cmd_q->reg_control + 0x0010);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Request an irq */
23462306a36Sopenharmony_ci	ret = request_irq(pt->pt_irq, pt_core_irq_handler, 0, dev_name(pt->dev), pt);
23562306a36Sopenharmony_ci	if (ret) {
23662306a36Sopenharmony_ci		dev_err(dev, "unable to allocate an IRQ\n");
23762306a36Sopenharmony_ci		goto e_free_dma;
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* Update the device registers with queue information. */
24162306a36Sopenharmony_ci	cmd_q->qcontrol &= ~CMD_Q_SIZE;
24262306a36Sopenharmony_ci	cmd_q->qcontrol |= FIELD_PREP(CMD_Q_SIZE, QUEUE_SIZE_VAL);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	cmd_q->qdma_tail = cmd_q->qbase_dma;
24562306a36Sopenharmony_ci	dma_addr_lo = lower_32_bits(cmd_q->qdma_tail);
24662306a36Sopenharmony_ci	iowrite32((u32)dma_addr_lo, cmd_q->reg_control + 0x0004);
24762306a36Sopenharmony_ci	iowrite32((u32)dma_addr_lo, cmd_q->reg_control + 0x0008);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	dma_addr_hi = upper_32_bits(cmd_q->qdma_tail);
25062306a36Sopenharmony_ci	cmd_q->qcontrol |= (dma_addr_hi << 16);
25162306a36Sopenharmony_ci	iowrite32(cmd_q->qcontrol, cmd_q->reg_control);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	pt_core_enable_queue_interrupts(pt);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/* Register the DMA engine support */
25662306a36Sopenharmony_ci	ret = pt_dmaengine_register(pt);
25762306a36Sopenharmony_ci	if (ret)
25862306a36Sopenharmony_ci		goto e_free_irq;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/* Set up debugfs entries */
26162306a36Sopenharmony_ci	ptdma_debugfs_setup(pt);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	return 0;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cie_free_irq:
26662306a36Sopenharmony_ci	free_irq(pt->pt_irq, pt);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cie_free_dma:
26962306a36Sopenharmony_ci	dma_free_coherent(dev, cmd_q->qsize, cmd_q->qbase, cmd_q->qbase_dma);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cie_destroy_pool:
27262306a36Sopenharmony_ci	dma_pool_destroy(pt->cmd_q.dma_pool);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	return ret;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_civoid pt_core_destroy(struct pt_device *pt)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	struct device *dev = pt->dev;
28062306a36Sopenharmony_ci	struct pt_cmd_queue *cmd_q = &pt->cmd_q;
28162306a36Sopenharmony_ci	struct pt_cmd *cmd;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	/* Unregister the DMA engine */
28462306a36Sopenharmony_ci	pt_dmaengine_unregister(pt);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	/* Disable and clear interrupts */
28762306a36Sopenharmony_ci	pt_core_disable_queue_interrupts(pt);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	/* Turn off the run bit */
29062306a36Sopenharmony_ci	pt_stop_queue(cmd_q);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* Clear the interrupt status */
29362306a36Sopenharmony_ci	iowrite32(SUPPORTED_INTERRUPTS, cmd_q->reg_control + 0x0010);
29462306a36Sopenharmony_ci	ioread32(cmd_q->reg_control + 0x0104);
29562306a36Sopenharmony_ci	ioread32(cmd_q->reg_control + 0x0100);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	free_irq(pt->pt_irq, pt);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	dma_free_coherent(dev, cmd_q->qsize, cmd_q->qbase,
30062306a36Sopenharmony_ci			  cmd_q->qbase_dma);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	/* Flush the cmd queue */
30362306a36Sopenharmony_ci	while (!list_empty(&pt->cmd)) {
30462306a36Sopenharmony_ci		/* Invoke the callback directly with an error code */
30562306a36Sopenharmony_ci		cmd = list_first_entry(&pt->cmd, struct pt_cmd, entry);
30662306a36Sopenharmony_ci		list_del(&cmd->entry);
30762306a36Sopenharmony_ci		cmd->pt_cmd_callback(cmd->data, -ENODEV);
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci}
310