162306a36Sopenharmony_ci// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/bitfield.h>
562306a36Sopenharmony_ci#include <linux/circ_buf.h>
662306a36Sopenharmony_ci#include <linux/device.h>
762306a36Sopenharmony_ci#include <linux/firmware.h>
862306a36Sopenharmony_ci#include <linux/iopoll.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/pci.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "prestera.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define PRESTERA_MSG_MAX_SIZE 1500
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define PRESTERA_SUPP_FW_MAJ_VER	4
1862306a36Sopenharmony_ci#define PRESTERA_SUPP_FW_MIN_VER	1
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define PRESTERA_PREV_FW_MAJ_VER	4
2162306a36Sopenharmony_ci#define PRESTERA_PREV_FW_MIN_VER	0
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define PRESTERA_FW_PATH_FMT	"mrvl/prestera/mvsw_prestera_fw-v%u.%u.img"
2462306a36Sopenharmony_ci#define PRESTERA_FW_ARM64_PATH_FMT "mrvl/prestera/mvsw_prestera_fw_arm64-v%u.%u.img"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define PRESTERA_FW_HDR_MAGIC		0x351D9D06
2762306a36Sopenharmony_ci#define PRESTERA_FW_DL_TIMEOUT_MS	50000
2862306a36Sopenharmony_ci#define PRESTERA_FW_BLK_SZ		1024
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define PRESTERA_FW_VER_MAJ_MUL 1000000
3162306a36Sopenharmony_ci#define PRESTERA_FW_VER_MIN_MUL 1000
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define PRESTERA_FW_VER_MAJ(v)	((v) / PRESTERA_FW_VER_MAJ_MUL)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define PRESTERA_FW_VER_MIN(v) \
3662306a36Sopenharmony_ci	(((v) - (PRESTERA_FW_VER_MAJ(v) * PRESTERA_FW_VER_MAJ_MUL)) / \
3762306a36Sopenharmony_ci			PRESTERA_FW_VER_MIN_MUL)
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define PRESTERA_FW_VER_PATCH(v) \
4062306a36Sopenharmony_ci	((v) - (PRESTERA_FW_VER_MAJ(v) * PRESTERA_FW_VER_MAJ_MUL) - \
4162306a36Sopenharmony_ci			(PRESTERA_FW_VER_MIN(v) * PRESTERA_FW_VER_MIN_MUL))
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cienum prestera_pci_bar_t {
4462306a36Sopenharmony_ci	PRESTERA_PCI_BAR_FW = 2,
4562306a36Sopenharmony_ci	PRESTERA_PCI_BAR_PP = 4,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistruct prestera_fw_header {
4962306a36Sopenharmony_ci	__be32 magic_number;
5062306a36Sopenharmony_ci	__be32 version_value;
5162306a36Sopenharmony_ci	u8 reserved[8];
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistruct prestera_ldr_regs {
5562306a36Sopenharmony_ci	u32 ldr_ready;
5662306a36Sopenharmony_ci	u32 pad1;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	u32 ldr_img_size;
5962306a36Sopenharmony_ci	u32 ldr_ctl_flags;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	u32 ldr_buf_offs;
6262306a36Sopenharmony_ci	u32 ldr_buf_size;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	u32 ldr_buf_rd;
6562306a36Sopenharmony_ci	u32 pad2;
6662306a36Sopenharmony_ci	u32 ldr_buf_wr;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	u32 ldr_status;
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#define PRESTERA_LDR_REG_OFFSET(f)	offsetof(struct prestera_ldr_regs, f)
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci#define PRESTERA_LDR_READY_MAGIC	0xf00dfeed
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci#define PRESTERA_LDR_STATUS_IMG_DL	BIT(0)
7662306a36Sopenharmony_ci#define PRESTERA_LDR_STATUS_START_FW	BIT(1)
7762306a36Sopenharmony_ci#define PRESTERA_LDR_STATUS_INVALID_IMG	BIT(2)
7862306a36Sopenharmony_ci#define PRESTERA_LDR_STATUS_NOMEM	BIT(3)
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci#define PRESTERA_LDR_REG_BASE(fw)	((fw)->ldr_regs)
8162306a36Sopenharmony_ci#define PRESTERA_LDR_REG_ADDR(fw, reg)	(PRESTERA_LDR_REG_BASE(fw) + (reg))
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci/* fw loader registers */
8462306a36Sopenharmony_ci#define PRESTERA_LDR_READY_REG		PRESTERA_LDR_REG_OFFSET(ldr_ready)
8562306a36Sopenharmony_ci#define PRESTERA_LDR_IMG_SIZE_REG	PRESTERA_LDR_REG_OFFSET(ldr_img_size)
8662306a36Sopenharmony_ci#define PRESTERA_LDR_CTL_REG		PRESTERA_LDR_REG_OFFSET(ldr_ctl_flags)
8762306a36Sopenharmony_ci#define PRESTERA_LDR_BUF_SIZE_REG	PRESTERA_LDR_REG_OFFSET(ldr_buf_size)
8862306a36Sopenharmony_ci#define PRESTERA_LDR_BUF_OFFS_REG	PRESTERA_LDR_REG_OFFSET(ldr_buf_offs)
8962306a36Sopenharmony_ci#define PRESTERA_LDR_BUF_RD_REG		PRESTERA_LDR_REG_OFFSET(ldr_buf_rd)
9062306a36Sopenharmony_ci#define PRESTERA_LDR_BUF_WR_REG		PRESTERA_LDR_REG_OFFSET(ldr_buf_wr)
9162306a36Sopenharmony_ci#define PRESTERA_LDR_STATUS_REG		PRESTERA_LDR_REG_OFFSET(ldr_status)
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define PRESTERA_LDR_CTL_DL_START	BIT(0)
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#define PRESTERA_EVT_QNUM_MAX	4
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistruct prestera_fw_evtq_regs {
9862306a36Sopenharmony_ci	u32 rd_idx;
9962306a36Sopenharmony_ci	u32 pad1;
10062306a36Sopenharmony_ci	u32 wr_idx;
10162306a36Sopenharmony_ci	u32 pad2;
10262306a36Sopenharmony_ci	u32 offs;
10362306a36Sopenharmony_ci	u32 len;
10462306a36Sopenharmony_ci};
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci#define PRESTERA_CMD_QNUM_MAX	4
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistruct prestera_fw_cmdq_regs {
10962306a36Sopenharmony_ci	u32 req_ctl;
11062306a36Sopenharmony_ci	u32 req_len;
11162306a36Sopenharmony_ci	u32 rcv_ctl;
11262306a36Sopenharmony_ci	u32 rcv_len;
11362306a36Sopenharmony_ci	u32 offs;
11462306a36Sopenharmony_ci	u32 len;
11562306a36Sopenharmony_ci};
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistruct prestera_fw_regs {
11862306a36Sopenharmony_ci	u32 fw_ready;
11962306a36Sopenharmony_ci	u32 cmd_offs;
12062306a36Sopenharmony_ci	u32 cmd_len;
12162306a36Sopenharmony_ci	u32 cmd_qnum;
12262306a36Sopenharmony_ci	u32 evt_offs;
12362306a36Sopenharmony_ci	u32 evt_qnum;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	u32 fw_status;
12662306a36Sopenharmony_ci	u32 rx_status;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	struct prestera_fw_cmdq_regs cmdq_list[PRESTERA_EVT_QNUM_MAX];
12962306a36Sopenharmony_ci	struct prestera_fw_evtq_regs evtq_list[PRESTERA_CMD_QNUM_MAX];
13062306a36Sopenharmony_ci};
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci#define PRESTERA_FW_REG_OFFSET(f)	offsetof(struct prestera_fw_regs, f)
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci#define PRESTERA_FW_READY_MAGIC		0xcafebabe
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci/* fw registers */
13762306a36Sopenharmony_ci#define PRESTERA_FW_READY_REG		PRESTERA_FW_REG_OFFSET(fw_ready)
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci#define PRESTERA_CMD_BUF_OFFS_REG	PRESTERA_FW_REG_OFFSET(cmd_offs)
14062306a36Sopenharmony_ci#define PRESTERA_CMD_BUF_LEN_REG	PRESTERA_FW_REG_OFFSET(cmd_len)
14162306a36Sopenharmony_ci#define PRESTERA_CMD_QNUM_REG		PRESTERA_FW_REG_OFFSET(cmd_qnum)
14262306a36Sopenharmony_ci#define PRESTERA_EVT_BUF_OFFS_REG	PRESTERA_FW_REG_OFFSET(evt_offs)
14362306a36Sopenharmony_ci#define PRESTERA_EVT_QNUM_REG		PRESTERA_FW_REG_OFFSET(evt_qnum)
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci#define PRESTERA_CMDQ_REG_OFFSET(q, f)			\
14662306a36Sopenharmony_ci	(PRESTERA_FW_REG_OFFSET(cmdq_list) +		\
14762306a36Sopenharmony_ci	 (q) * sizeof(struct prestera_fw_cmdq_regs) +	\
14862306a36Sopenharmony_ci	 offsetof(struct prestera_fw_cmdq_regs, f))
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci#define PRESTERA_CMDQ_REQ_CTL_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, req_ctl)
15162306a36Sopenharmony_ci#define PRESTERA_CMDQ_REQ_LEN_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, req_len)
15262306a36Sopenharmony_ci#define PRESTERA_CMDQ_RCV_CTL_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, rcv_ctl)
15362306a36Sopenharmony_ci#define PRESTERA_CMDQ_RCV_LEN_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, rcv_len)
15462306a36Sopenharmony_ci#define PRESTERA_CMDQ_OFFS_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, offs)
15562306a36Sopenharmony_ci#define PRESTERA_CMDQ_LEN_REG(q)	PRESTERA_CMDQ_REG_OFFSET(q, len)
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci#define PRESTERA_FW_STATUS_REG		PRESTERA_FW_REG_OFFSET(fw_status)
15862306a36Sopenharmony_ci#define PRESTERA_RX_STATUS_REG		PRESTERA_FW_REG_OFFSET(rx_status)
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci/* PRESTERA_CMD_REQ_CTL_REG flags */
16162306a36Sopenharmony_ci#define PRESTERA_CMD_F_REQ_SENT		BIT(0)
16262306a36Sopenharmony_ci#define PRESTERA_CMD_F_REPL_RCVD	BIT(1)
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci/* PRESTERA_CMD_RCV_CTL_REG flags */
16562306a36Sopenharmony_ci#define PRESTERA_CMD_F_REPL_SENT	BIT(0)
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci#define PRESTERA_FW_EVT_CTL_STATUS_MASK	GENMASK(1, 0)
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci#define PRESTERA_FW_EVT_CTL_STATUS_ON	0
17062306a36Sopenharmony_ci#define PRESTERA_FW_EVT_CTL_STATUS_OFF	1
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci#define PRESTERA_EVTQ_REG_OFFSET(q, f)			\
17362306a36Sopenharmony_ci	(PRESTERA_FW_REG_OFFSET(evtq_list) +		\
17462306a36Sopenharmony_ci	 (q) * sizeof(struct prestera_fw_evtq_regs) +	\
17562306a36Sopenharmony_ci	 offsetof(struct prestera_fw_evtq_regs, f))
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci#define PRESTERA_EVTQ_RD_IDX_REG(q)	PRESTERA_EVTQ_REG_OFFSET(q, rd_idx)
17862306a36Sopenharmony_ci#define PRESTERA_EVTQ_WR_IDX_REG(q)	PRESTERA_EVTQ_REG_OFFSET(q, wr_idx)
17962306a36Sopenharmony_ci#define PRESTERA_EVTQ_OFFS_REG(q)	PRESTERA_EVTQ_REG_OFFSET(q, offs)
18062306a36Sopenharmony_ci#define PRESTERA_EVTQ_LEN_REG(q)	PRESTERA_EVTQ_REG_OFFSET(q, len)
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci#define PRESTERA_FW_REG_BASE(fw)	((fw)->dev.ctl_regs)
18362306a36Sopenharmony_ci#define PRESTERA_FW_REG_ADDR(fw, reg)	PRESTERA_FW_REG_BASE((fw)) + (reg)
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci#define PRESTERA_FW_CMD_DEFAULT_WAIT_MS	30000
18662306a36Sopenharmony_ci#define PRESTERA_FW_READY_WAIT_MS	20000
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci#define PRESTERA_DEV_ID_AC3X_98DX_55	0xC804
18962306a36Sopenharmony_ci#define PRESTERA_DEV_ID_AC3X_98DX_65	0xC80C
19062306a36Sopenharmony_ci#define PRESTERA_DEV_ID_ALDRIN2		0xCC1E
19162306a36Sopenharmony_ci#define PRESTERA_DEV_ID_98DX7312M	0x981F
19262306a36Sopenharmony_ci#define PRESTERA_DEV_ID_98DX3500	0x9820
19362306a36Sopenharmony_ci#define PRESTERA_DEV_ID_98DX3501	0x9826
19462306a36Sopenharmony_ci#define PRESTERA_DEV_ID_98DX3510	0x9821
19562306a36Sopenharmony_ci#define PRESTERA_DEV_ID_98DX3520	0x9822
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistruct prestera_fw_evtq {
19862306a36Sopenharmony_ci	u8 __iomem *addr;
19962306a36Sopenharmony_ci	size_t len;
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistruct prestera_fw_cmdq {
20362306a36Sopenharmony_ci	/* serialize access to dev->send_req */
20462306a36Sopenharmony_ci	struct mutex cmd_mtx;
20562306a36Sopenharmony_ci	u8 __iomem *addr;
20662306a36Sopenharmony_ci	size_t len;
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistruct prestera_fw {
21062306a36Sopenharmony_ci	struct prestera_fw_rev rev_supp;
21162306a36Sopenharmony_ci	const struct firmware *bin;
21262306a36Sopenharmony_ci	struct workqueue_struct *wq;
21362306a36Sopenharmony_ci	struct prestera_device dev;
21462306a36Sopenharmony_ci	struct pci_dev *pci_dev;
21562306a36Sopenharmony_ci	u8 __iomem *ldr_regs;
21662306a36Sopenharmony_ci	u8 __iomem *ldr_ring_buf;
21762306a36Sopenharmony_ci	u32 ldr_buf_len;
21862306a36Sopenharmony_ci	u32 ldr_wr_idx;
21962306a36Sopenharmony_ci	size_t cmd_mbox_len;
22062306a36Sopenharmony_ci	u8 __iomem *cmd_mbox;
22162306a36Sopenharmony_ci	struct prestera_fw_cmdq cmd_queue[PRESTERA_CMD_QNUM_MAX];
22262306a36Sopenharmony_ci	u8 cmd_qnum;
22362306a36Sopenharmony_ci	struct prestera_fw_evtq evt_queue[PRESTERA_EVT_QNUM_MAX];
22462306a36Sopenharmony_ci	u8 evt_qnum;
22562306a36Sopenharmony_ci	struct work_struct evt_work;
22662306a36Sopenharmony_ci	u8 __iomem *evt_buf;
22762306a36Sopenharmony_ci	u8 *evt_msg;
22862306a36Sopenharmony_ci};
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int prestera_fw_load(struct prestera_fw *fw);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic void prestera_fw_write(struct prestera_fw *fw, u32 reg, u32 val)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	writel(val, PRESTERA_FW_REG_ADDR(fw, reg));
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic u32 prestera_fw_read(struct prestera_fw *fw, u32 reg)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	return readl(PRESTERA_FW_REG_ADDR(fw, reg));
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic u32 prestera_fw_evtq_len(struct prestera_fw *fw, u8 qid)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	return fw->evt_queue[qid].len;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic u32 prestera_fw_evtq_avail(struct prestera_fw *fw, u8 qid)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	u32 wr_idx = prestera_fw_read(fw, PRESTERA_EVTQ_WR_IDX_REG(qid));
25062306a36Sopenharmony_ci	u32 rd_idx = prestera_fw_read(fw, PRESTERA_EVTQ_RD_IDX_REG(qid));
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return CIRC_CNT(wr_idx, rd_idx, prestera_fw_evtq_len(fw, qid));
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic void prestera_fw_evtq_rd_set(struct prestera_fw *fw,
25662306a36Sopenharmony_ci				    u8 qid, u32 idx)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	u32 rd_idx = idx & (prestera_fw_evtq_len(fw, qid) - 1);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	prestera_fw_write(fw, PRESTERA_EVTQ_RD_IDX_REG(qid), rd_idx);
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic u8 __iomem *prestera_fw_evtq_buf(struct prestera_fw *fw, u8 qid)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	return fw->evt_queue[qid].addr;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic u32 prestera_fw_evtq_read32(struct prestera_fw *fw, u8 qid)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	u32 rd_idx = prestera_fw_read(fw, PRESTERA_EVTQ_RD_IDX_REG(qid));
27162306a36Sopenharmony_ci	u32 val;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	val = readl(prestera_fw_evtq_buf(fw, qid) + rd_idx);
27462306a36Sopenharmony_ci	prestera_fw_evtq_rd_set(fw, qid, rd_idx + 4);
27562306a36Sopenharmony_ci	return val;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic ssize_t prestera_fw_evtq_read_buf(struct prestera_fw *fw,
27962306a36Sopenharmony_ci					 u8 qid, void *buf, size_t len)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	u32 idx = prestera_fw_read(fw, PRESTERA_EVTQ_RD_IDX_REG(qid));
28262306a36Sopenharmony_ci	u8 __iomem *evtq_addr = prestera_fw_evtq_buf(fw, qid);
28362306a36Sopenharmony_ci	u32 *buf32 = buf;
28462306a36Sopenharmony_ci	int i;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	for (i = 0; i < len / 4; buf32++, i++) {
28762306a36Sopenharmony_ci		*buf32 = readl_relaxed(evtq_addr + idx);
28862306a36Sopenharmony_ci		idx = (idx + 4) & (prestera_fw_evtq_len(fw, qid) - 1);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	prestera_fw_evtq_rd_set(fw, qid, idx);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	return i;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic u8 prestera_fw_evtq_pick(struct prestera_fw *fw)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	int qid;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	for (qid = 0; qid < fw->evt_qnum; qid++) {
30162306a36Sopenharmony_ci		if (prestera_fw_evtq_avail(fw, qid) >= 4)
30262306a36Sopenharmony_ci			return qid;
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return PRESTERA_EVT_QNUM_MAX;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic void prestera_fw_evt_ctl_status_set(struct prestera_fw *fw, u32 val)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	u32 status = prestera_fw_read(fw, PRESTERA_FW_STATUS_REG);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	u32p_replace_bits(&status, val, PRESTERA_FW_EVT_CTL_STATUS_MASK);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	prestera_fw_write(fw, PRESTERA_FW_STATUS_REG, status);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic void prestera_fw_evt_work_fn(struct work_struct *work)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct prestera_fw *fw;
32062306a36Sopenharmony_ci	void *msg;
32162306a36Sopenharmony_ci	u8 qid;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	fw = container_of(work, struct prestera_fw, evt_work);
32462306a36Sopenharmony_ci	msg = fw->evt_msg;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	prestera_fw_evt_ctl_status_set(fw, PRESTERA_FW_EVT_CTL_STATUS_OFF);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	while ((qid = prestera_fw_evtq_pick(fw)) < PRESTERA_EVT_QNUM_MAX) {
32962306a36Sopenharmony_ci		u32 idx;
33062306a36Sopenharmony_ci		u32 len;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		len = prestera_fw_evtq_read32(fw, qid);
33362306a36Sopenharmony_ci		idx = prestera_fw_read(fw, PRESTERA_EVTQ_RD_IDX_REG(qid));
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci		WARN_ON(prestera_fw_evtq_avail(fw, qid) < len);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci		if (WARN_ON(len > PRESTERA_MSG_MAX_SIZE)) {
33862306a36Sopenharmony_ci			prestera_fw_evtq_rd_set(fw, qid, idx + len);
33962306a36Sopenharmony_ci			continue;
34062306a36Sopenharmony_ci		}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci		prestera_fw_evtq_read_buf(fw, qid, msg, len);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci		if (fw->dev.recv_msg)
34562306a36Sopenharmony_ci			fw->dev.recv_msg(&fw->dev, msg, len);
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	prestera_fw_evt_ctl_status_set(fw, PRESTERA_FW_EVT_CTL_STATUS_ON);
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic int prestera_fw_wait_reg32(struct prestera_fw *fw, u32 reg, u32 cmp,
35262306a36Sopenharmony_ci				  unsigned int waitms)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	u8 __iomem *addr = PRESTERA_FW_REG_ADDR(fw, reg);
35562306a36Sopenharmony_ci	u32 val;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	return readl_poll_timeout(addr, val, cmp == val,
35862306a36Sopenharmony_ci				  1 * USEC_PER_MSEC, waitms * USEC_PER_MSEC);
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic void prestera_fw_cmdq_lock(struct prestera_fw *fw, u8 qid)
36262306a36Sopenharmony_ci{
36362306a36Sopenharmony_ci	mutex_lock(&fw->cmd_queue[qid].cmd_mtx);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic void prestera_fw_cmdq_unlock(struct prestera_fw *fw, u8 qid)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	mutex_unlock(&fw->cmd_queue[qid].cmd_mtx);
36962306a36Sopenharmony_ci}
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_cistatic u32 prestera_fw_cmdq_len(struct prestera_fw *fw, u8 qid)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	return fw->cmd_queue[qid].len;
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic u8 __iomem *prestera_fw_cmdq_buf(struct prestera_fw *fw, u8 qid)
37762306a36Sopenharmony_ci{
37862306a36Sopenharmony_ci	return fw->cmd_queue[qid].addr;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int prestera_fw_cmd_send(struct prestera_fw *fw, int qid,
38262306a36Sopenharmony_ci				void *in_msg, size_t in_size,
38362306a36Sopenharmony_ci				void *out_msg, size_t out_size,
38462306a36Sopenharmony_ci				unsigned int waitms)
38562306a36Sopenharmony_ci{
38662306a36Sopenharmony_ci	u32 ret_size;
38762306a36Sopenharmony_ci	int err;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	if (!waitms)
39062306a36Sopenharmony_ci		waitms = PRESTERA_FW_CMD_DEFAULT_WAIT_MS;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	if (ALIGN(in_size, 4) > prestera_fw_cmdq_len(fw, qid))
39362306a36Sopenharmony_ci		return -EMSGSIZE;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	/* wait for finish previous reply from FW */
39662306a36Sopenharmony_ci	err = prestera_fw_wait_reg32(fw, PRESTERA_CMDQ_RCV_CTL_REG(qid), 0, 30);
39762306a36Sopenharmony_ci	if (err) {
39862306a36Sopenharmony_ci		dev_err(fw->dev.dev, "finish reply from FW is timed out\n");
39962306a36Sopenharmony_ci		return err;
40062306a36Sopenharmony_ci	}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	prestera_fw_write(fw, PRESTERA_CMDQ_REQ_LEN_REG(qid), in_size);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	memcpy_toio(prestera_fw_cmdq_buf(fw, qid), in_msg, in_size);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	prestera_fw_write(fw, PRESTERA_CMDQ_REQ_CTL_REG(qid),
40762306a36Sopenharmony_ci			  PRESTERA_CMD_F_REQ_SENT);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	/* wait for reply from FW */
41062306a36Sopenharmony_ci	err = prestera_fw_wait_reg32(fw, PRESTERA_CMDQ_RCV_CTL_REG(qid),
41162306a36Sopenharmony_ci				     PRESTERA_CMD_F_REPL_SENT, waitms);
41262306a36Sopenharmony_ci	if (err) {
41362306a36Sopenharmony_ci		dev_err(fw->dev.dev, "reply from FW is timed out\n");
41462306a36Sopenharmony_ci		goto cmd_exit;
41562306a36Sopenharmony_ci	}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	ret_size = prestera_fw_read(fw, PRESTERA_CMDQ_RCV_LEN_REG(qid));
41862306a36Sopenharmony_ci	if (ret_size > out_size) {
41962306a36Sopenharmony_ci		dev_err(fw->dev.dev, "ret_size (%u) > out_len(%zu)\n",
42062306a36Sopenharmony_ci			ret_size, out_size);
42162306a36Sopenharmony_ci		err = -EMSGSIZE;
42262306a36Sopenharmony_ci		goto cmd_exit;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	memcpy_fromio(out_msg,
42662306a36Sopenharmony_ci		      prestera_fw_cmdq_buf(fw, qid) + in_size, ret_size);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_cicmd_exit:
42962306a36Sopenharmony_ci	prestera_fw_write(fw, PRESTERA_CMDQ_REQ_CTL_REG(qid),
43062306a36Sopenharmony_ci			  PRESTERA_CMD_F_REPL_RCVD);
43162306a36Sopenharmony_ci	return err;
43262306a36Sopenharmony_ci}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_cistatic int prestera_fw_send_req(struct prestera_device *dev, int qid,
43562306a36Sopenharmony_ci				void *in_msg, size_t in_size, void *out_msg,
43662306a36Sopenharmony_ci				size_t out_size, unsigned int waitms)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	struct prestera_fw *fw;
43962306a36Sopenharmony_ci	ssize_t ret;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	fw = container_of(dev, struct prestera_fw, dev);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	prestera_fw_cmdq_lock(fw, qid);
44462306a36Sopenharmony_ci	ret = prestera_fw_cmd_send(fw, qid, in_msg, in_size, out_msg, out_size,
44562306a36Sopenharmony_ci				   waitms);
44662306a36Sopenharmony_ci	prestera_fw_cmdq_unlock(fw, qid);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	return ret;
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_cistatic int prestera_fw_init(struct prestera_fw *fw)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	u8 __iomem *base;
45462306a36Sopenharmony_ci	int err;
45562306a36Sopenharmony_ci	u8 qid;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	fw->dev.send_req = prestera_fw_send_req;
45862306a36Sopenharmony_ci	fw->ldr_regs = fw->dev.ctl_regs;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	err = prestera_fw_load(fw);
46162306a36Sopenharmony_ci	if (err)
46262306a36Sopenharmony_ci		return err;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	err = prestera_fw_wait_reg32(fw, PRESTERA_FW_READY_REG,
46562306a36Sopenharmony_ci				     PRESTERA_FW_READY_MAGIC,
46662306a36Sopenharmony_ci				     PRESTERA_FW_READY_WAIT_MS);
46762306a36Sopenharmony_ci	if (err) {
46862306a36Sopenharmony_ci		dev_err(fw->dev.dev, "FW failed to start\n");
46962306a36Sopenharmony_ci		return err;
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	base = fw->dev.ctl_regs;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	fw->cmd_mbox = base + prestera_fw_read(fw, PRESTERA_CMD_BUF_OFFS_REG);
47562306a36Sopenharmony_ci	fw->cmd_mbox_len = prestera_fw_read(fw, PRESTERA_CMD_BUF_LEN_REG);
47662306a36Sopenharmony_ci	fw->cmd_qnum = prestera_fw_read(fw, PRESTERA_CMD_QNUM_REG);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	for (qid = 0; qid < fw->cmd_qnum; qid++) {
47962306a36Sopenharmony_ci		u32 offs = prestera_fw_read(fw, PRESTERA_CMDQ_OFFS_REG(qid));
48062306a36Sopenharmony_ci		struct prestera_fw_cmdq *cmdq = &fw->cmd_queue[qid];
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci		cmdq->len = prestera_fw_read(fw, PRESTERA_CMDQ_LEN_REG(qid));
48362306a36Sopenharmony_ci		cmdq->addr = fw->cmd_mbox + offs;
48462306a36Sopenharmony_ci		mutex_init(&cmdq->cmd_mtx);
48562306a36Sopenharmony_ci	}
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	fw->evt_buf = base + prestera_fw_read(fw, PRESTERA_EVT_BUF_OFFS_REG);
48862306a36Sopenharmony_ci	fw->evt_qnum = prestera_fw_read(fw, PRESTERA_EVT_QNUM_REG);
48962306a36Sopenharmony_ci	fw->evt_msg = kmalloc(PRESTERA_MSG_MAX_SIZE, GFP_KERNEL);
49062306a36Sopenharmony_ci	if (!fw->evt_msg)
49162306a36Sopenharmony_ci		return -ENOMEM;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	for (qid = 0; qid < fw->evt_qnum; qid++) {
49462306a36Sopenharmony_ci		u32 offs = prestera_fw_read(fw, PRESTERA_EVTQ_OFFS_REG(qid));
49562306a36Sopenharmony_ci		struct prestera_fw_evtq *evtq = &fw->evt_queue[qid];
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci		evtq->len = prestera_fw_read(fw, PRESTERA_EVTQ_LEN_REG(qid));
49862306a36Sopenharmony_ci		evtq->addr = fw->evt_buf + offs;
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	return 0;
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic void prestera_fw_uninit(struct prestera_fw *fw)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	kfree(fw->evt_msg);
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic irqreturn_t prestera_pci_irq_handler(int irq, void *dev_id)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct prestera_fw *fw = dev_id;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	if (prestera_fw_read(fw, PRESTERA_RX_STATUS_REG)) {
51462306a36Sopenharmony_ci		prestera_fw_write(fw, PRESTERA_RX_STATUS_REG, 0);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci		if (fw->dev.recv_pkt)
51762306a36Sopenharmony_ci			fw->dev.recv_pkt(&fw->dev);
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	queue_work(fw->wq, &fw->evt_work);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	return IRQ_HANDLED;
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic void prestera_ldr_write(struct prestera_fw *fw, u32 reg, u32 val)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	writel(val, PRESTERA_LDR_REG_ADDR(fw, reg));
52862306a36Sopenharmony_ci}
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_cistatic u32 prestera_ldr_read(struct prestera_fw *fw, u32 reg)
53162306a36Sopenharmony_ci{
53262306a36Sopenharmony_ci	return readl(PRESTERA_LDR_REG_ADDR(fw, reg));
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_cistatic int prestera_ldr_wait_reg32(struct prestera_fw *fw,
53662306a36Sopenharmony_ci				   u32 reg, u32 cmp, unsigned int waitms)
53762306a36Sopenharmony_ci{
53862306a36Sopenharmony_ci	u8 __iomem *addr = PRESTERA_LDR_REG_ADDR(fw, reg);
53962306a36Sopenharmony_ci	u32 val;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	return readl_poll_timeout(addr, val, cmp == val,
54262306a36Sopenharmony_ci				  10 * USEC_PER_MSEC, waitms * USEC_PER_MSEC);
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_cistatic u32 prestera_ldr_wait_buf(struct prestera_fw *fw, size_t len)
54662306a36Sopenharmony_ci{
54762306a36Sopenharmony_ci	u8 __iomem *addr = PRESTERA_LDR_REG_ADDR(fw, PRESTERA_LDR_BUF_RD_REG);
54862306a36Sopenharmony_ci	u32 buf_len = fw->ldr_buf_len;
54962306a36Sopenharmony_ci	u32 wr_idx = fw->ldr_wr_idx;
55062306a36Sopenharmony_ci	u32 rd_idx;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	return readl_poll_timeout(addr, rd_idx,
55362306a36Sopenharmony_ci				 CIRC_SPACE(wr_idx, rd_idx, buf_len) >= len,
55462306a36Sopenharmony_ci				 1 * USEC_PER_MSEC, 100 * USEC_PER_MSEC);
55562306a36Sopenharmony_ci}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cistatic int prestera_ldr_wait_dl_finish(struct prestera_fw *fw)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	u8 __iomem *addr = PRESTERA_LDR_REG_ADDR(fw, PRESTERA_LDR_STATUS_REG);
56062306a36Sopenharmony_ci	unsigned long mask = ~(PRESTERA_LDR_STATUS_IMG_DL);
56162306a36Sopenharmony_ci	u32 val;
56262306a36Sopenharmony_ci	int err;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	err = readl_poll_timeout(addr, val, val & mask, 10 * USEC_PER_MSEC,
56562306a36Sopenharmony_ci				 PRESTERA_FW_DL_TIMEOUT_MS * USEC_PER_MSEC);
56662306a36Sopenharmony_ci	if (err) {
56762306a36Sopenharmony_ci		dev_err(fw->dev.dev, "Timeout to load FW img [state=%d]",
56862306a36Sopenharmony_ci			prestera_ldr_read(fw, PRESTERA_LDR_STATUS_REG));
56962306a36Sopenharmony_ci		return err;
57062306a36Sopenharmony_ci	}
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	return 0;
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cistatic void prestera_ldr_wr_idx_move(struct prestera_fw *fw, unsigned int n)
57662306a36Sopenharmony_ci{
57762306a36Sopenharmony_ci	fw->ldr_wr_idx = (fw->ldr_wr_idx + (n)) & (fw->ldr_buf_len - 1);
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic void prestera_ldr_wr_idx_commit(struct prestera_fw *fw)
58162306a36Sopenharmony_ci{
58262306a36Sopenharmony_ci	prestera_ldr_write(fw, PRESTERA_LDR_BUF_WR_REG, fw->ldr_wr_idx);
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic u8 __iomem *prestera_ldr_wr_ptr(struct prestera_fw *fw)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	return fw->ldr_ring_buf + fw->ldr_wr_idx;
58862306a36Sopenharmony_ci}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_cistatic int prestera_ldr_send(struct prestera_fw *fw, const u8 *buf, size_t len)
59162306a36Sopenharmony_ci{
59262306a36Sopenharmony_ci	int err;
59362306a36Sopenharmony_ci	int i;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	err = prestera_ldr_wait_buf(fw, len);
59662306a36Sopenharmony_ci	if (err) {
59762306a36Sopenharmony_ci		dev_err(fw->dev.dev, "failed wait for sending firmware\n");
59862306a36Sopenharmony_ci		return err;
59962306a36Sopenharmony_ci	}
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	for (i = 0; i < len; i += 4) {
60262306a36Sopenharmony_ci		writel_relaxed(*(u32 *)(buf + i), prestera_ldr_wr_ptr(fw));
60362306a36Sopenharmony_ci		prestera_ldr_wr_idx_move(fw, 4);
60462306a36Sopenharmony_ci	}
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	prestera_ldr_wr_idx_commit(fw);
60762306a36Sopenharmony_ci	return 0;
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_cistatic int prestera_ldr_fw_send(struct prestera_fw *fw,
61162306a36Sopenharmony_ci				const char *img, u32 fw_size)
61262306a36Sopenharmony_ci{
61362306a36Sopenharmony_ci	u32 status;
61462306a36Sopenharmony_ci	u32 pos;
61562306a36Sopenharmony_ci	int err;
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	err = prestera_ldr_wait_reg32(fw, PRESTERA_LDR_STATUS_REG,
61862306a36Sopenharmony_ci				      PRESTERA_LDR_STATUS_IMG_DL,
61962306a36Sopenharmony_ci				      5 * MSEC_PER_SEC);
62062306a36Sopenharmony_ci	if (err) {
62162306a36Sopenharmony_ci		dev_err(fw->dev.dev, "Loader is not ready to load image\n");
62262306a36Sopenharmony_ci		return err;
62362306a36Sopenharmony_ci	}
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	for (pos = 0; pos < fw_size; pos += PRESTERA_FW_BLK_SZ) {
62662306a36Sopenharmony_ci		if (pos + PRESTERA_FW_BLK_SZ > fw_size)
62762306a36Sopenharmony_ci			break;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci		err = prestera_ldr_send(fw, img + pos, PRESTERA_FW_BLK_SZ);
63062306a36Sopenharmony_ci		if (err)
63162306a36Sopenharmony_ci			return err;
63262306a36Sopenharmony_ci	}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	if (pos < fw_size) {
63562306a36Sopenharmony_ci		err = prestera_ldr_send(fw, img + pos, fw_size - pos);
63662306a36Sopenharmony_ci		if (err)
63762306a36Sopenharmony_ci			return err;
63862306a36Sopenharmony_ci	}
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	err = prestera_ldr_wait_dl_finish(fw);
64162306a36Sopenharmony_ci	if (err)
64262306a36Sopenharmony_ci		return err;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	status = prestera_ldr_read(fw, PRESTERA_LDR_STATUS_REG);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	switch (status) {
64762306a36Sopenharmony_ci	case PRESTERA_LDR_STATUS_INVALID_IMG:
64862306a36Sopenharmony_ci		dev_err(fw->dev.dev, "FW img has bad CRC\n");
64962306a36Sopenharmony_ci		return -EINVAL;
65062306a36Sopenharmony_ci	case PRESTERA_LDR_STATUS_NOMEM:
65162306a36Sopenharmony_ci		dev_err(fw->dev.dev, "Loader has no enough mem\n");
65262306a36Sopenharmony_ci		return -ENOMEM;
65362306a36Sopenharmony_ci	}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	return 0;
65662306a36Sopenharmony_ci}
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_cistatic void prestera_fw_rev_parse(const struct prestera_fw_header *hdr,
65962306a36Sopenharmony_ci				  struct prestera_fw_rev *rev)
66062306a36Sopenharmony_ci{
66162306a36Sopenharmony_ci	u32 version = be32_to_cpu(hdr->version_value);
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	rev->maj = PRESTERA_FW_VER_MAJ(version);
66462306a36Sopenharmony_ci	rev->min = PRESTERA_FW_VER_MIN(version);
66562306a36Sopenharmony_ci	rev->sub = PRESTERA_FW_VER_PATCH(version);
66662306a36Sopenharmony_ci}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_cistatic int prestera_fw_rev_check(struct prestera_fw *fw)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	struct prestera_fw_rev *rev = &fw->dev.fw_rev;
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	if (rev->maj == fw->rev_supp.maj && rev->min >= fw->rev_supp.min)
67362306a36Sopenharmony_ci		return 0;
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	dev_err(fw->dev.dev, "Driver supports FW version only '%u.%u.x'",
67662306a36Sopenharmony_ci		fw->rev_supp.maj, fw->rev_supp.min);
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	return -EINVAL;
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic int prestera_fw_hdr_parse(struct prestera_fw *fw)
68262306a36Sopenharmony_ci{
68362306a36Sopenharmony_ci	struct prestera_fw_rev *rev = &fw->dev.fw_rev;
68462306a36Sopenharmony_ci	struct prestera_fw_header *hdr;
68562306a36Sopenharmony_ci	u32 magic;
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	hdr = (struct prestera_fw_header *)fw->bin->data;
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	magic = be32_to_cpu(hdr->magic_number);
69062306a36Sopenharmony_ci	if (magic != PRESTERA_FW_HDR_MAGIC) {
69162306a36Sopenharmony_ci		dev_err(fw->dev.dev, "FW img hdr magic is invalid");
69262306a36Sopenharmony_ci		return -EINVAL;
69362306a36Sopenharmony_ci	}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	prestera_fw_rev_parse(hdr, rev);
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	dev_info(fw->dev.dev, "FW version '%u.%u.%u'\n",
69862306a36Sopenharmony_ci		 rev->maj, rev->min, rev->sub);
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	return prestera_fw_rev_check(fw);
70162306a36Sopenharmony_ci}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic const char *prestera_fw_path_fmt_get(struct prestera_fw *fw)
70462306a36Sopenharmony_ci{
70562306a36Sopenharmony_ci	switch (fw->pci_dev->device) {
70662306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3500:
70762306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3501:
70862306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3510:
70962306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3520:
71062306a36Sopenharmony_ci		return PRESTERA_FW_ARM64_PATH_FMT;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	default:
71362306a36Sopenharmony_ci		return PRESTERA_FW_PATH_FMT;
71462306a36Sopenharmony_ci	}
71562306a36Sopenharmony_ci}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_cistatic int prestera_fw_get(struct prestera_fw *fw)
71862306a36Sopenharmony_ci{
71962306a36Sopenharmony_ci	int ver_maj = PRESTERA_SUPP_FW_MAJ_VER;
72062306a36Sopenharmony_ci	int ver_min = PRESTERA_SUPP_FW_MIN_VER;
72162306a36Sopenharmony_ci	char fw_path[128];
72262306a36Sopenharmony_ci	int err;
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_cipick_fw_ver:
72562306a36Sopenharmony_ci	snprintf(fw_path, sizeof(fw_path), prestera_fw_path_fmt_get(fw),
72662306a36Sopenharmony_ci		 ver_maj, ver_min);
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	err = request_firmware_direct(&fw->bin, fw_path, fw->dev.dev);
72962306a36Sopenharmony_ci	if (err) {
73062306a36Sopenharmony_ci		if (ver_maj != PRESTERA_PREV_FW_MAJ_VER ||
73162306a36Sopenharmony_ci		    ver_min != PRESTERA_PREV_FW_MIN_VER) {
73262306a36Sopenharmony_ci			ver_maj = PRESTERA_PREV_FW_MAJ_VER;
73362306a36Sopenharmony_ci			ver_min = PRESTERA_PREV_FW_MIN_VER;
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci			dev_warn(fw->dev.dev,
73662306a36Sopenharmony_ci				 "missing latest %s firmware, fall-back to previous %u.%u version\n",
73762306a36Sopenharmony_ci				 fw_path, ver_maj, ver_min);
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci			goto pick_fw_ver;
74062306a36Sopenharmony_ci		} else {
74162306a36Sopenharmony_ci			dev_err(fw->dev.dev, "failed to request previous firmware: %s\n",
74262306a36Sopenharmony_ci				fw_path);
74362306a36Sopenharmony_ci			return err;
74462306a36Sopenharmony_ci		}
74562306a36Sopenharmony_ci	}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	dev_info(fw->dev.dev, "Loading %s ...", fw_path);
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	fw->rev_supp.maj = ver_maj;
75062306a36Sopenharmony_ci	fw->rev_supp.min = ver_min;
75162306a36Sopenharmony_ci	fw->rev_supp.sub = 0;
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	return 0;
75462306a36Sopenharmony_ci}
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_cistatic void prestera_fw_put(struct prestera_fw *fw)
75762306a36Sopenharmony_ci{
75862306a36Sopenharmony_ci	release_firmware(fw->bin);
75962306a36Sopenharmony_ci}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_cistatic int prestera_fw_load(struct prestera_fw *fw)
76262306a36Sopenharmony_ci{
76362306a36Sopenharmony_ci	size_t hlen = sizeof(struct prestera_fw_header);
76462306a36Sopenharmony_ci	int err;
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci	err = prestera_ldr_wait_reg32(fw, PRESTERA_LDR_READY_REG,
76762306a36Sopenharmony_ci				      PRESTERA_LDR_READY_MAGIC,
76862306a36Sopenharmony_ci				      5 * MSEC_PER_SEC);
76962306a36Sopenharmony_ci	if (err) {
77062306a36Sopenharmony_ci		dev_err(fw->dev.dev, "waiting for FW loader is timed out");
77162306a36Sopenharmony_ci		return err;
77262306a36Sopenharmony_ci	}
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	fw->ldr_ring_buf = fw->ldr_regs +
77562306a36Sopenharmony_ci		prestera_ldr_read(fw, PRESTERA_LDR_BUF_OFFS_REG);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	fw->ldr_buf_len =
77862306a36Sopenharmony_ci		prestera_ldr_read(fw, PRESTERA_LDR_BUF_SIZE_REG);
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	fw->ldr_wr_idx = 0;
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci	err = prestera_fw_get(fw);
78362306a36Sopenharmony_ci	if (err)
78462306a36Sopenharmony_ci		return err;
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	err = prestera_fw_hdr_parse(fw);
78762306a36Sopenharmony_ci	if (err) {
78862306a36Sopenharmony_ci		dev_err(fw->dev.dev, "FW image header is invalid\n");
78962306a36Sopenharmony_ci		goto out_release;
79062306a36Sopenharmony_ci	}
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	prestera_ldr_write(fw, PRESTERA_LDR_IMG_SIZE_REG, fw->bin->size - hlen);
79362306a36Sopenharmony_ci	prestera_ldr_write(fw, PRESTERA_LDR_CTL_REG, PRESTERA_LDR_CTL_DL_START);
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	err = prestera_ldr_fw_send(fw, fw->bin->data + hlen,
79662306a36Sopenharmony_ci				   fw->bin->size - hlen);
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ciout_release:
79962306a36Sopenharmony_ci	prestera_fw_put(fw);
80062306a36Sopenharmony_ci	return err;
80162306a36Sopenharmony_ci}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_cistatic bool prestera_pci_pp_use_bar2(struct pci_dev *pdev)
80462306a36Sopenharmony_ci{
80562306a36Sopenharmony_ci	switch (pdev->device) {
80662306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX7312M:
80762306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3500:
80862306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3501:
80962306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3510:
81062306a36Sopenharmony_ci	case PRESTERA_DEV_ID_98DX3520:
81162306a36Sopenharmony_ci		return true;
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci	default:
81462306a36Sopenharmony_ci		return false;
81562306a36Sopenharmony_ci	}
81662306a36Sopenharmony_ci}
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_cistatic u32 prestera_pci_pp_bar2_offs(struct pci_dev *pdev)
81962306a36Sopenharmony_ci{
82062306a36Sopenharmony_ci	if (pci_resource_len(pdev, 2) == 0x1000000)
82162306a36Sopenharmony_ci		return 0x0;
82262306a36Sopenharmony_ci	else
82362306a36Sopenharmony_ci		return (pci_resource_len(pdev, 2) / 2);
82462306a36Sopenharmony_ci}
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_cistatic u32 prestera_pci_fw_bar2_offs(struct pci_dev *pdev)
82762306a36Sopenharmony_ci{
82862306a36Sopenharmony_ci	if (pci_resource_len(pdev, 2) == 0x1000000)
82962306a36Sopenharmony_ci		return 0x400000;
83062306a36Sopenharmony_ci	else
83162306a36Sopenharmony_ci		return 0x0;
83262306a36Sopenharmony_ci}
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_cistatic int prestera_pci_probe(struct pci_dev *pdev,
83562306a36Sopenharmony_ci			      const struct pci_device_id *id)
83662306a36Sopenharmony_ci{
83762306a36Sopenharmony_ci	const char *driver_name = dev_driver_string(&pdev->dev);
83862306a36Sopenharmony_ci	u8 __iomem *mem_addr, *pp_addr = NULL;
83962306a36Sopenharmony_ci	struct prestera_fw *fw;
84062306a36Sopenharmony_ci	int err;
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ci	err = pcim_enable_device(pdev);
84362306a36Sopenharmony_ci	if (err) {
84462306a36Sopenharmony_ci		dev_err(&pdev->dev, "pci_enable_device failed\n");
84562306a36Sopenharmony_ci		goto err_pci_enable_device;
84662306a36Sopenharmony_ci	}
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	err = pci_request_regions(pdev, driver_name);
84962306a36Sopenharmony_ci	if (err) {
85062306a36Sopenharmony_ci		dev_err(&pdev->dev, "pci_request_regions failed\n");
85162306a36Sopenharmony_ci		goto err_pci_request_regions;
85262306a36Sopenharmony_ci	}
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(30));
85562306a36Sopenharmony_ci	if (err) {
85662306a36Sopenharmony_ci		dev_err(&pdev->dev, "fail to set DMA mask\n");
85762306a36Sopenharmony_ci		goto err_dma_mask;
85862306a36Sopenharmony_ci	}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_ci	mem_addr = pcim_iomap(pdev, 2, 0);
86162306a36Sopenharmony_ci	if (!mem_addr) {
86262306a36Sopenharmony_ci		dev_err(&pdev->dev, "pci mem ioremap failed\n");
86362306a36Sopenharmony_ci		err = -EIO;
86462306a36Sopenharmony_ci		goto err_mem_ioremap;
86562306a36Sopenharmony_ci	}
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_ci	/* AC5X devices use second half of BAR2 */
86862306a36Sopenharmony_ci	if (prestera_pci_pp_use_bar2(pdev)) {
86962306a36Sopenharmony_ci		pp_addr = mem_addr + prestera_pci_pp_bar2_offs(pdev);
87062306a36Sopenharmony_ci		mem_addr = mem_addr + prestera_pci_fw_bar2_offs(pdev);
87162306a36Sopenharmony_ci	} else {
87262306a36Sopenharmony_ci		pp_addr = pcim_iomap(pdev, 4, 0);
87362306a36Sopenharmony_ci		if (!pp_addr) {
87462306a36Sopenharmony_ci			dev_err(&pdev->dev, "pp regs ioremap failed\n");
87562306a36Sopenharmony_ci			err = -EIO;
87662306a36Sopenharmony_ci			goto err_pp_ioremap;
87762306a36Sopenharmony_ci		}
87862306a36Sopenharmony_ci	}
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci	pci_set_master(pdev);
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	fw = devm_kzalloc(&pdev->dev, sizeof(*fw), GFP_KERNEL);
88362306a36Sopenharmony_ci	if (!fw) {
88462306a36Sopenharmony_ci		err = -ENOMEM;
88562306a36Sopenharmony_ci		goto err_pci_dev_alloc;
88662306a36Sopenharmony_ci	}
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci	fw->pci_dev = pdev;
88962306a36Sopenharmony_ci	fw->dev.ctl_regs = mem_addr;
89062306a36Sopenharmony_ci	fw->dev.pp_regs = pp_addr;
89162306a36Sopenharmony_ci	fw->dev.dev = &pdev->dev;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	pci_set_drvdata(pdev, fw);
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	err = prestera_fw_init(fw);
89662306a36Sopenharmony_ci	if (err)
89762306a36Sopenharmony_ci		goto err_prestera_fw_init;
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci	dev_info(fw->dev.dev, "Prestera FW is ready\n");
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	fw->wq = alloc_workqueue("prestera_fw_wq", WQ_HIGHPRI, 1);
90262306a36Sopenharmony_ci	if (!fw->wq) {
90362306a36Sopenharmony_ci		err = -ENOMEM;
90462306a36Sopenharmony_ci		goto err_wq_alloc;
90562306a36Sopenharmony_ci	}
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	INIT_WORK(&fw->evt_work, prestera_fw_evt_work_fn);
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci	err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
91062306a36Sopenharmony_ci	if (err < 0) {
91162306a36Sopenharmony_ci		dev_err(&pdev->dev, "MSI IRQ init failed\n");
91262306a36Sopenharmony_ci		goto err_irq_alloc;
91362306a36Sopenharmony_ci	}
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_ci	err = request_irq(pci_irq_vector(pdev, 0), prestera_pci_irq_handler,
91662306a36Sopenharmony_ci			  0, driver_name, fw);
91762306a36Sopenharmony_ci	if (err) {
91862306a36Sopenharmony_ci		dev_err(&pdev->dev, "fail to request IRQ\n");
91962306a36Sopenharmony_ci		goto err_request_irq;
92062306a36Sopenharmony_ci	}
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	err = prestera_device_register(&fw->dev);
92362306a36Sopenharmony_ci	if (err)
92462306a36Sopenharmony_ci		goto err_prestera_dev_register;
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	return 0;
92762306a36Sopenharmony_ci
92862306a36Sopenharmony_cierr_prestera_dev_register:
92962306a36Sopenharmony_ci	free_irq(pci_irq_vector(pdev, 0), fw);
93062306a36Sopenharmony_cierr_request_irq:
93162306a36Sopenharmony_ci	pci_free_irq_vectors(pdev);
93262306a36Sopenharmony_cierr_irq_alloc:
93362306a36Sopenharmony_ci	destroy_workqueue(fw->wq);
93462306a36Sopenharmony_cierr_wq_alloc:
93562306a36Sopenharmony_ci	prestera_fw_uninit(fw);
93662306a36Sopenharmony_cierr_prestera_fw_init:
93762306a36Sopenharmony_cierr_pci_dev_alloc:
93862306a36Sopenharmony_cierr_pp_ioremap:
93962306a36Sopenharmony_cierr_mem_ioremap:
94062306a36Sopenharmony_cierr_dma_mask:
94162306a36Sopenharmony_ci	pci_release_regions(pdev);
94262306a36Sopenharmony_cierr_pci_request_regions:
94362306a36Sopenharmony_cierr_pci_enable_device:
94462306a36Sopenharmony_ci	return err;
94562306a36Sopenharmony_ci}
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_cistatic void prestera_pci_remove(struct pci_dev *pdev)
94862306a36Sopenharmony_ci{
94962306a36Sopenharmony_ci	struct prestera_fw *fw = pci_get_drvdata(pdev);
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	prestera_device_unregister(&fw->dev);
95262306a36Sopenharmony_ci	free_irq(pci_irq_vector(pdev, 0), fw);
95362306a36Sopenharmony_ci	pci_free_irq_vectors(pdev);
95462306a36Sopenharmony_ci	destroy_workqueue(fw->wq);
95562306a36Sopenharmony_ci	prestera_fw_uninit(fw);
95662306a36Sopenharmony_ci	pci_release_regions(pdev);
95762306a36Sopenharmony_ci}
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_cistatic const struct pci_device_id prestera_pci_devices[] = {
96062306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_AC3X_98DX_55) },
96162306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_AC3X_98DX_65) },
96262306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_ALDRIN2) },
96362306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_98DX7312M) },
96462306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_98DX3500) },
96562306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_98DX3501) },
96662306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_98DX3510) },
96762306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PRESTERA_DEV_ID_98DX3520) },
96862306a36Sopenharmony_ci	{ }
96962306a36Sopenharmony_ci};
97062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, prestera_pci_devices);
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_cistatic struct pci_driver prestera_pci_driver = {
97362306a36Sopenharmony_ci	.name     = "Prestera DX",
97462306a36Sopenharmony_ci	.id_table = prestera_pci_devices,
97562306a36Sopenharmony_ci	.probe    = prestera_pci_probe,
97662306a36Sopenharmony_ci	.remove   = prestera_pci_remove,
97762306a36Sopenharmony_ci};
97862306a36Sopenharmony_cimodule_pci_driver(prestera_pci_driver);
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL");
98162306a36Sopenharmony_ciMODULE_DESCRIPTION("Marvell Prestera switch PCI interface");
982