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