// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include "hw_heci.h" #include "hw_heci_regs.h" #include "hw_msg.h" /** * heci_reg_read - Reads 32bit data from the issei heci device * @hw: the heci hardware structure * @offset: offset from which to read the data * * Return: register value (u32) */ static inline u32 heci_reg_read(const struct issei_heci_hw *hw, unsigned long offset) { return ioread32(hw->mem_addr + offset); } /** * heci_reg_write - Writes 32bit data to the issei heci device * * @hw: the heci hardware structure * @offset: offset from which to write the data * @value: register value to write (u32) */ static inline void heci_reg_write(const struct issei_heci_hw *hw, unsigned long offset, u32 value) { iowrite32(value, hw->mem_addr + offset); } /** * heci_fwcbrw_read - Reads 32bit data from heci circular buffer * @hw: the heci hardware structure * * Return: FW_CB_RW register value (u32) */ static inline u32 heci_fwcbrw_read(const struct issei_heci_hw *hw) { return heci_reg_read(hw, FW_CB_RW); } /** * heci_hcbww_write - write 32bit data to the host circular buffer * @hw: the heci hardware structure * @data: 32bit data to be written to the host circular buffer */ static inline void heci_hcbww_write(const struct issei_heci_hw *hw, u32 data) { heci_reg_write(hw, H_CB_WW, data); } /** * heci_irq_src - Filters IRQ source bits from the host CSR * @hcsr: host CSR register value * * Return: interrupt source bits of host CSR */ static inline u32 heci_irq_src(u32 hcsr) { return hcsr & H_CSR_IS; } /** * heci_hcsr_read - Reads 32bit data from the host CSR * @idev: the device structure * * Return: H_CSR register value (u32) */ static inline u32 heci_hcsr_read(const struct issei_device *idev) { return heci_reg_read(to_heci_hw(idev), H_CSR); } /** * heci_hcsr_write - writes H_CSR register to device * @idev: the device structure * @reg: new register value */ static inline void heci_hcsr_write(struct issei_device *idev, u32 reg) { heci_reg_write(to_heci_hw(idev), H_CSR, reg); } /** * heci_hcsr_set - writes H_CSR register to the heci device * @idev: the device structure * @reg: new register value * * Writes H_CSR register to the heci device * and ignores the H_IS bit for it is write-one-to-zero. * */ static inline void heci_hcsr_set(struct issei_device *idev, u32 reg) { reg &= ~H_CSR_IS; heci_hcsr_write(idev, reg); } /** * heci_hcsr_set_hig - set host interrupt (set H_CSR_IG) * @idev: the device structure */ static inline void heci_hcsr_set_hig(struct issei_device *idev) { u32 reg; reg = heci_hcsr_read(idev) | H_CSR_IG; heci_hcsr_set(idev, reg); } /** * heci_fwcsr_read - Reads 32bit data from the FW CSR * @idev: the device structure * * Return: FW_CSR_HA register value (u32) */ static inline u32 heci_fwcsr_read(const struct issei_device *idev) { return heci_reg_read(to_heci_hw(idev), FW_CSR_HA); } /** * heci_count_full_read_slots - counts read full slots. * @idev: the device structure * * Return: -EOVERFLOW if overflow, otherwise filled slots count */ static int heci_count_full_read_slots(struct issei_device *idev) { u8 buffer_depth, filled_slots; u8 read_ptr, write_ptr; u32 reg; reg = heci_fwcsr_read(idev); buffer_depth = (u8)FIELD_GET(FW_CSR_CBD, reg); read_ptr = (u8)FIELD_GET(FW_CSR_CBRP, reg); write_ptr = (u8)FIELD_GET(FW_CSR_CBWP, reg); filled_slots = write_ptr - read_ptr; /* check for overflow */ if (filled_slots > buffer_depth) return -EOVERFLOW; dev_dbg(&idev->dev, "filled_slots = %08x\n", filled_slots); return filled_slots; } /** * heci_irq_disable - disables heci device interrupts * @idev: the device structure * @reg: supplied hcsr register value * * disables heci device interrupts using supplied hcsr register value. */ static inline void heci_irq_disable(struct issei_device *idev, u32 reg) { reg &= ~H_CSR_IE; heci_hcsr_set(idev, reg); } /** * heci_irq_clear - clear and stop interrupts * @idev: the device structure * @reg: supplied hcsr register value */ static inline void heci_irq_clear(struct issei_device *idev, u32 reg) { if (heci_irq_src(reg)) heci_hcsr_write(idev, reg); } /** * issei_heci_irq_clear - clear and stop interrupts * @idev: the device structure */ static void issei_heci_irq_clear(struct issei_device *idev) { u32 reg = heci_hcsr_read(idev); heci_irq_clear(idev, reg); } /** * issei_heci_irq_enable - enables heci device interrupts * @idev: the device structure */ static void issei_heci_irq_enable(struct issei_device *idev) { u32 reg; reg = heci_hcsr_read(idev) | H_CSR_IE; heci_hcsr_set(idev, reg); } /** * issei_heci_irq_disable - disables heci device interrupts * @idev: the device structure */ static void issei_heci_irq_disable(struct issei_device *idev) { u32 reg = heci_hcsr_read(idev); heci_irq_disable(idev, reg); } static void issei_heci_irq_sync(struct issei_device *idev) { synchronize_irq(to_heci_hw(idev)->irq); } /** * issei_heci_hw_reset_release - release device from the reset * @idev: the device structure */ static void issei_heci_hw_reset_release(struct issei_device *idev) { u32 reg = heci_hcsr_read(idev); reg |= H_CSR_IG; reg &= ~H_CSR_RST; heci_hcsr_set(idev, reg); } /** * heci_hw_is_ready - check whether the hw has turned ready * @idev: the device structure * * Return: bool */ static bool heci_hw_is_ready(struct issei_device *idev) { u32 reg = heci_fwcsr_read(idev); return reg & FW_CSR_RDY; } /** * heci_hw_is_resetting - check whether the hw is in reset * @idev: the device structure * * Return: bool */ static bool heci_hw_is_resetting(struct issei_device *idev) { u32 reg = heci_fwcsr_read(idev); return reg & FW_CSR_RST; } /** * issei_heci_host_set_ready - enable device * @idev: the device structure */ static void issei_heci_host_set_ready(struct issei_device *idev) { u32 reg = heci_hcsr_read(idev); reg |= H_CSR_IE | H_CSR_IG | H_CSR_RDY; heci_hcsr_set(idev, reg); } /** * issei_heci_hw_reset - resets fw via heci csr register. * @idev: the device structure * @enable: if interrupt should be enabled after reset. * * Return: 0 on success an error code otherwise */ static int issei_heci_hw_reset(struct issei_device *idev, bool enable) { u32 reg; if (enable) issei_heci_irq_enable(idev); reg = heci_hcsr_read(idev); /* * H_CSR_RST may be found lit before reset is started, * for example if preceding reset flow hasn't completed. * In that case asserting H_CSR_RST will be ignored, therefore * we need to clean H_CSR_RST bit to start a successful reset sequence. */ if (reg & H_CSR_RST) { dev_warn(&idev->dev, "H_CSR_RST is set = 0x%08X", reg); reg &= ~H_CSR_RST; heci_hcsr_set(idev, reg); reg = heci_hcsr_read(idev); } reg |= H_CSR_RST | H_CSR_IG | H_CSR_IS; if (!enable) reg &= ~H_CSR_IE; heci_hcsr_write(idev, reg); /* * Host reads the H_CSR once to ensure that the * posted write to H_CSR completes. */ reg = heci_hcsr_read(idev); if (!(reg & H_CSR_RST)) dev_warn(&idev->dev, "H_CSR_RST is not set = 0x%08X", reg); if (reg & H_CSR_RDY) dev_warn(&idev->dev, "H_CSR_RDY is not cleared 0x%08X", reg); if (!enable) issei_heci_hw_reset_release(idev); return 0; } /** * issei_heci_irq_write_generate - generate interrupt to signal write completion. * @idev: the device structure * * Return: 0 on success, -EIO if hardware is not ready and requires reset */ static int issei_heci_irq_write_generate(struct issei_device *idev) { struct issei_heci_hw *hw = to_heci_hw(idev); scoped_guard(spinlock_irqsave, &hw->access_lock) heci_hcsr_set_hig(idev); if (!heci_hw_is_ready(idev)) return -EIO; return 0; } /** * issei_heci_hw_config - initial hardware configuration. * @idev: the device structure * * Return: 0 always */ static int issei_heci_hw_config(struct issei_device *idev) { struct issei_heci_hw *hw = to_heci_hw(idev); u32 reg; /* Doesn't change in runtime */ reg = heci_hcsr_read(idev); hw->hbuf_depth = FIELD_GET(H_CSR_CBD, reg); return 0; } /** * heci_write_hbuf - write to hardware buffer * @idev: the device structure * @data: data to write * @data_len: data size * * Return: 0 on success, <0 on error */ static int heci_write_hbuf(struct issei_device *idev, const void *data, size_t data_len) { struct issei_heci_hw *hw = to_heci_hw(idev); if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) { dev_err(&idev->dev, "Data size %zu not aligned to slot size %zu\n", data_len, CB_SLOT_SIZE); return -EINVAL; } scoped_guard(spinlock_irqsave, &hw->access_lock) { const u32 *reg_buf = data; size_t i; for (i = 0; i < data_len / CB_SLOT_SIZE; i++) heci_hcbww_write(hw, reg_buf[i]); } return issei_heci_irq_write_generate(idev); } /** * heci_read_hbuf - read data from hardware buffer * @idev: the device structure * @data: buffer to store read data * @data_len: buffer size * * Return: 0 on success, <0 on error */ static int heci_read_hbuf(struct issei_device *idev, void *data, size_t data_len) { struct issei_heci_hw *hw = to_heci_hw(idev); u32 *reg_buf = data; if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) { dev_err(&idev->dev, "Data size %zu not aligned to slot size %zu\n", data_len, CB_SLOT_SIZE); return -EINVAL; } scoped_guard(spinlock_irqsave, &hw->access_lock) { for (; data_len >= CB_SLOT_SIZE; data_len -= CB_SLOT_SIZE) *reg_buf++ = heci_fwcbrw_read(hw); } return 0; } /** * issei_heci_setup_message_send - send setup message to firmware * @idev: the device structure * * Return: 0 on success, <0 on error */ static int issei_heci_setup_message_send(struct issei_device *idev) { struct ham_setup_shared_memory_req req = { .msg_id = HAM_CB_MESSAGE_ID_REQ, .ver = HAM_CB_MESSAGE_VER, .reserved = 0, .buffer_physical_address = idev->dma.daddr, .host_to_fw_section_length = idev->dma.length.h2f, .fw_to_host_section_length = idev->dma.length.f2h, .control_length = idev->dma.length.ctl }; int ret; ret = heci_write_hbuf(idev, &req, sizeof(req)); if (ret) dev_err(&idev->dev, "Shared memory req write failed ret = %d\n", ret); return ret; } /** * issei_heci_setup_message_recv - receive setup message response from firmware * @idev: the device structure * * Return: 0 on success, <0 on error */ static int issei_heci_setup_message_recv(struct issei_device *idev) { struct ham_setup_shared_memory_res res; int ret; if (heci_count_full_read_slots(idev) != sizeof(res) / CB_SLOT_SIZE) { dev_dbg(&idev->dev, "Setup response is not fully received\n"); return -ENODATA; } ret = heci_read_hbuf(idev, &res, sizeof(res)); if (ret) { dev_err(&idev->dev, "Shared memory res read failed ret = %d\n", ret); return ret; } if (res.msg_id != HAM_CB_MESSAGE_ID_RES) { dev_err(&idev->dev, "Shared memory res header 0x%x != 0x%x\n", res.msg_id, HAM_CB_MESSAGE_ID_RES); return -EPROTO; } if (res.status != 0) { dev_err(&idev->dev, "Shared memory res status %d != 0\n", res.status); return -EPROTO; } return 0; } static const struct issei_hw_ops hw_heci_ops = { .irq_clear = issei_heci_irq_clear, .irq_enable = issei_heci_irq_enable, .irq_disable = issei_heci_irq_disable, .irq_sync = issei_heci_irq_sync, .hw_reset = issei_heci_hw_reset, .hw_config = issei_heci_hw_config, .hw_reset_release = issei_heci_hw_reset_release, .host_set_ready = issei_heci_host_set_ready, .hw_is_ready = heci_hw_is_ready, .setup_message_send = issei_heci_setup_message_send, .setup_message_recv = issei_heci_setup_message_recv, .irq_write_generate = issei_heci_irq_write_generate, }; const struct issei_hw_ops *issei_heci_get_ops(void) { return &hw_heci_ops; } irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id) { struct issei_device *idev = dev_id; struct issei_heci_hw *hw = to_heci_hw(idev); u32 reg; reg = heci_hcsr_read(idev); if (!heci_irq_src(reg)) return IRQ_NONE; scoped_guard(spinlock_irqsave, &hw->access_lock) { reg = heci_hcsr_read(idev); heci_irq_clear(idev, reg); if (heci_hw_is_resetting(idev)) heci_hcsr_set_hig(idev); } dev_dbg(&idev->dev, "interrupt source 0x%08X\n", heci_irq_src(reg)); issei_poke_process_thread(idev); return IRQ_HANDLED; } static const struct hw_heci_cfg hw_heci_pch_cfg = { .dma_length.h2f = SZ_64K, .dma_length.f2h = SZ_64K, .dma_length.ctl = SZ_4K, }; const struct hw_heci_cfg *issei_heci_get_cfg(kernel_ulong_t idx) { return &hw_heci_pch_cfg; } /** * issei_heci_dev_init - initializes the issei device structure with hw_heci * @idev: device structure * @mem_addr: memory address on bar * @cfg: per device generation config */ void issei_heci_dev_init(struct issei_device *idev, void __iomem *mem_addr, const struct hw_heci_cfg *cfg) { struct issei_heci_hw *hw = to_heci_hw(idev); spin_lock_init(&hw->access_lock); hw->mem_addr = mem_addr; hw->cfg = cfg; }