--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _ISP4_FW_CMD_RESP_H_
+#define _ISP4_FW_CMD_RESP_H_
+
+/*
+ * Two types of command/response channel.
+ * Type Global Command has one command/response channel.
+ * Type Stream Command has one command/response channel.
+ *----------- ------------
+ *| | --------------------------- | |
+ *| | ---->| Global Command |----> | |
+ *| | --------------------------- | |
+ *| | | |
+ *| | | |
+ *| | --------------------------- | |
+ *| | ---->| Stream Command |----> | |
+ *| | --------------------------- | |
+ *| | | |
+ *| | | |
+ *| | | |
+ *| HOST | | Firmware |
+ *| | | |
+ *| | | |
+ *| | -------------------------- | |
+ *| | <----| Global Response |<---- | |
+ *| | -------------------------- | |
+ *| | | |
+ *| | | |
+ *| | -------------------------- | |
+ *| | <----| Stream Response |<---- | |
+ *| | -------------------------- | |
+ *| | | |
+ *| | | |
+ *----------- ------------
+ */
+
+/*
+ * cmd_id is in the format of following type:
+ * type: indicate command type, global/stream commands.
+ * group: indicate the command group.
+ * id: A unique command identification in one type and group.
+ * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
+ * | type | group | id |
+ */
+
+#define ISP4FW_CMD_TYPE_SHIFT 24
+#define ISP4FW_CMD_GROUP_SHIFT 16
+#define ISP4FW_CMD_TYPE_STREAM_CTRL (0x2U << ISP4FW_CMD_TYPE_SHIFT)
+
+#define ISP4FW_CMD_GROUP_STREAM_CTRL (0x1U << ISP4FW_CMD_GROUP_SHIFT)
+#define ISP4FW_CMD_GROUP_STREAM_BUFFER (0x4U << ISP4FW_CMD_GROUP_SHIFT)
+
+/* Stream Command */
+#define ISP4FW_CMD_ID_SET_STREAM_CONFIG (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x1)
+#define ISP4FW_CMD_ID_SET_OUT_CHAN_PROP (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x3)
+#define ISP4FW_CMD_ID_ENABLE_OUT_CHAN (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x5)
+#define ISP4FW_CMD_ID_START_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x7)
+#define ISP4FW_CMD_ID_STOP_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_CTRL | 0x8)
+
+/* Stream Buffer Command */
+#define ISP4FW_CMD_ID_SEND_BUFFER (ISP4FW_CMD_TYPE_STREAM_CTRL\
+ | ISP4FW_CMD_GROUP_STREAM_BUFFER | 0x1)
+
+/*
+ * resp_id is in the format of following type:
+ * type: indicate command type, global/stream commands.
+ * group: indicate the command group.
+ * id: A unique command identification in one type and group.
+ * |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
+ * | type | group | id |
+ */
+
+#define ISP4FW_RESP_GROUP_SHIFT 16
+
+#define ISP4FW_RESP_GROUP_GENERAL (0x1 << ISP4FW_RESP_GROUP_SHIFT)
+#define ISP4FW_RESP_GROUP_NOTIFICATION (0x3 << ISP4FW_RESP_GROUP_SHIFT)
+
+/* General Response */
+#define ISP4FW_RESP_ID_CMD_DONE (ISP4FW_RESP_GROUP_GENERAL | 0x1)
+
+/* Notification */
+#define ISP4FW_RESP_ID_NOTI_FRAME_DONE (ISP4FW_RESP_GROUP_NOTIFICATION | 0x1)
+
+#define ISP4FW_CMD_STATUS_SUCCESS 0
+#define ISP4FW_CMD_STATUS_FAIL 1
+#define ISP4FW_CMD_STATUS_SKIPPED 2
+
+#define ISP4FW_ADDR_SPACE_TYPE_GPU_VA 4
+
+#define ISP4FW_MEMORY_POOL_SIZE (100 * 1024 * 1024)
+
+/*
+ * standard ISP pipeline: mipicsi=>isp
+ */
+#define ISP4FW_MIPI0_ISP_PIPELINE_ID 0x5f91
+
+enum isp4fw_sensor_id {
+ /* Sensor id for ISP input from MIPI port 0 */
+ ISP4FW_SENSOR_ID_ON_MIPI0 = 0,
+};
+
+enum isp4fw_stream_id {
+ ISP4FW_STREAM_ID_INVALID = -1,
+ ISP4FW_STREAM_ID_1 = 0,
+ ISP4FW_STREAM_ID_2 = 1,
+ ISP4FW_STREAM_ID_3 = 2,
+ ISP4FW_STREAM_ID_MAXIMUM
+};
+
+enum isp4fw_image_format {
+ /* 4:2:0,semi-planar, 8-bit */
+ ISP4FW_IMAGE_FORMAT_NV12 = 1,
+ /* interleave, 4:2:2, 8-bit */
+ ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED = 7,
+};
+
+enum isp4fw_pipe_out_ch {
+ ISP4FW_ISP_PIPE_OUT_CH_PREVIEW = 0,
+};
+
+enum isp4fw_yuv_range {
+ ISP4FW_ISP_YUV_RANGE_FULL = 0, /* YUV value range in 0~255 */
+ ISP4FW_ISP_YUV_RANGE_NARROW = 1, /* YUV value range in 16~235 */
+ ISP4FW_ISP_YUV_RANGE_MAX
+};
+
+enum isp4fw_buffer_type {
+ ISP4FW_BUFFER_TYPE_PREVIEW = 8,
+ ISP4FW_BUFFER_TYPE_META_INFO = 10,
+ ISP4FW_BUFFER_TYPE_MEM_POOL = 15,
+};
+
+enum isp4fw_buffer_status {
+ /* The buffer is INVALID */
+ ISP4FW_BUFFER_STATUS_INVALID,
+ /* The buffer is not filled with image data */
+ ISP4FW_BUFFER_STATUS_SKIPPED,
+ /* The buffer is available and awaiting to be filled */
+ ISP4FW_BUFFER_STATUS_EXIST,
+ /* The buffer is filled with image data */
+ ISP4FW_BUFFER_STATUS_DONE,
+ /* The buffer is unavailable */
+ ISP4FW_BUFFER_STATUS_LACK,
+ /* The buffer is dirty, probably caused by LMI leakage */
+ ISP4FW_BUFFER_STATUS_DIRTY,
+ ISP4FW_BUFFER_STATUS_MAX
+};
+
+enum isp4fw_buffer_source {
+ /* The buffer is from the stream buffer queue */
+ ISP4FW_BUFFER_SOURCE_STREAM,
+};
+
+struct isp4fw_error_code {
+ u32 code1;
+ u32 code2;
+ u32 code3;
+ u32 code4;
+ u32 code5;
+};
+
+/* Command Structure for FW */
+
+struct isp4fw_cmd {
+ u32 cmd_seq_num;
+ u32 cmd_id;
+ u32 cmd_param[12];
+ u16 cmd_stream_id;
+ u8 cmd_silent_resp;
+ u8 reserved;
+ u32 cmd_check_sum;
+};
+
+struct isp4fw_resp_cmd_done {
+ /*
+ * The host2fw command seqNum.
+ * To indicate which command this response refers to.
+ */
+ u32 cmd_seq_num;
+ /* The host2fw command id for host double check. */
+ u32 cmd_id;
+ /*
+ * Indicate the command process status.
+ * 0 means success. 1 means fail. 2 means skipped
+ */
+ u16 cmd_status;
+ /*
+ * If cmd_status is 1, the command failed. The host can check
+ * isp4fw_error_code for details.
+ */
+ u16 isp4fw_error_code;
+ /* The response payload type varies by cmd. */
+ u8 payload[36];
+};
+
+struct isp4fw_resp_param_package {
+ u32 package_addr_lo; /* The low 32 bit of the pkg address. */
+ u32 package_addr_hi; /* The high 32 bit of the pkg address. */
+ u32 package_size; /* The total pkg size in bytes. */
+ u32 package_check_sum; /* The byte sum of the pkg. */
+};
+
+struct isp4fw_resp {
+ u32 resp_seq_num;
+ u32 resp_id;
+ union {
+ struct isp4fw_resp_cmd_done cmd_done;
+ struct isp4fw_resp_param_package frame_done;
+ u32 resp_param[12];
+ } param;
+ u8 reserved[4];
+ u32 resp_check_sum;
+};
+
+struct isp4fw_mipi_pipe_path_cfg {
+ u32 b_enable;
+ enum isp4fw_sensor_id isp4fw_sensor_id;
+};
+
+struct isp4fw_isp_pipe_path_cfg {
+ u32 isp_pipe_id; /* pipe ids for pipeline construction */
+};
+
+struct isp4fw_isp_stream_cfg {
+ /* Isp mipi path */
+ struct isp4fw_mipi_pipe_path_cfg mipi_pipe_path_cfg;
+ /* Isp pipe path */
+ struct isp4fw_isp_pipe_path_cfg isp_pipe_path_cfg;
+ /* enable TNR */
+ u32 b_enable_tnr;
+ /*
+ * Number of frames for RTA processing.
+ * Set to 0 to use the firmware's default value.
+ */
+ u32 rta_frames_per_proc;
+};
+
+struct isp4fw_image_prop {
+ enum isp4fw_image_format image_format;
+ u32 width;
+ u32 height;
+ u32 luma_pitch;
+ u32 chroma_pitch;
+ enum isp4fw_yuv_range yuv_range;
+};
+
+struct isp4fw_buffer {
+ /*
+ * A check num for debug usage, host can set the buf_tags
+ * to different numbers
+ */
+ u32 buf_tags;
+ union {
+ u32 value;
+ struct {
+ u32 space : 16;
+ u32 vmid : 16;
+ } bit;
+ } vmid_space;
+ u32 buf_base_a_lo; /* Low address of buffer A */
+ u32 buf_base_a_hi; /* High address of buffer A */
+ u32 buf_size_a; /* Buffer size of buffer A */
+
+ u32 buf_base_b_lo; /* Low address of buffer B */
+ u32 buf_base_b_hi; /* High address of buffer B */
+ u32 buf_size_b; /* Buffer size of buffer B */
+
+ u32 buf_base_c_lo; /* Low address of buffer C */
+ u32 buf_base_c_hi; /* High address of buffer C */
+ u32 buf_size_c; /* Buffer size of buffer C */
+};
+
+struct isp4fw_buffer_meta_info {
+ u32 enabled; /* enabled flag */
+ enum isp4fw_buffer_status status; /* BufferStatus */
+ struct isp4fw_error_code err; /* err code */
+ enum isp4fw_buffer_source source; /* BufferSource */
+ struct isp4fw_image_prop image_prop; /* image_prop */
+ struct isp4fw_buffer buffer; /* buffer info */
+};
+
+struct isp4fw_meta_info {
+ u32 poc; /* frame id */
+ u32 fc_id; /* frame ctl id */
+ u32 time_stamp_lo; /* timestamp low 32 bits */
+ u32 time_stamp_hi; /* timestamp_high 32 bits */
+ struct isp4fw_buffer_meta_info preview; /* preview BufferMetaInfo */
+};
+
+struct isp4fw_cmd_send_buffer {
+ enum isp4fw_buffer_type buffer_type;
+ struct isp4fw_buffer buffer; /* buffer info */
+};
+
+struct isp4fw_cmd_set_out_ch_prop {
+ enum isp4fw_pipe_out_ch ch; /* ISP output channel */
+ struct isp4fw_image_prop image_prop; /* image property */
+};
+
+struct isp4fw_cmd_enable_out_ch {
+ enum isp4fw_pipe_out_ch ch; /* ISP output channel */
+ u32 is_enable; /* If channel is enabled or not */
+};
+
+struct isp4fw_cmd_set_stream_cfg {
+ struct isp4fw_isp_stream_cfg stream_cfg; /* stream path config */
+};
+
+#endif /* _ISP4_FW_CMD_RESP_H_ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/iopoll.h>
+
+#include "isp4_fw_cmd_resp.h"
+#include "isp4_hw_reg.h"
+#include "isp4_interface.h"
+
+#define ISP4IF_FW_RESP_RB_IRQ_EN_MASK \
+ (ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK\
+ | ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK)
+
+#define ISP4IF_FW_CMD_TIMEOUT (HZ / 2)
+
+struct isp4if_rb_config {
+ const char *name;
+ u32 index;
+ u32 reg_rptr;
+ u32 reg_wptr;
+ u32 reg_base_lo;
+ u32 reg_base_hi;
+ u32 reg_size;
+ u32 val_size;
+ u64 base_mc_addr;
+ void *base_sys_addr;
+};
+
+/* FW cmd ring buffer configuration */
+static struct isp4if_rb_config isp4if_cmd_rb_config[ISP4IF_STREAM_ID_MAX] = {
+ {
+ .name = "CMD_RB_GBL0",
+ .index = 3,
+ .reg_rptr = ISP_RB_RPTR4,
+ .reg_wptr = ISP_RB_WPTR4,
+ .reg_base_lo = ISP_RB_BASE_LO4,
+ .reg_base_hi = ISP_RB_BASE_HI4,
+ .reg_size = ISP_RB_SIZE4,
+ },
+ {
+ .name = "CMD_RB_STR1",
+ .index = 0,
+ .reg_rptr = ISP_RB_RPTR1,
+ .reg_wptr = ISP_RB_WPTR1,
+ .reg_base_lo = ISP_RB_BASE_LO1,
+ .reg_base_hi = ISP_RB_BASE_HI1,
+ .reg_size = ISP_RB_SIZE1,
+ },
+ {
+ .name = "CMD_RB_STR2",
+ .index = 1,
+ .reg_rptr = ISP_RB_RPTR2,
+ .reg_wptr = ISP_RB_WPTR2,
+ .reg_base_lo = ISP_RB_BASE_LO2,
+ .reg_base_hi = ISP_RB_BASE_HI2,
+ .reg_size = ISP_RB_SIZE2,
+ },
+ {
+ .name = "CMD_RB_STR3",
+ .index = 2,
+ .reg_rptr = ISP_RB_RPTR3,
+ .reg_wptr = ISP_RB_WPTR3,
+ .reg_base_lo = ISP_RB_BASE_LO3,
+ .reg_base_hi = ISP_RB_BASE_HI3,
+ .reg_size = ISP_RB_SIZE3,
+ },
+};
+
+/* FW resp ring buffer configuration */
+static struct isp4if_rb_config isp4if_resp_rb_config[ISP4IF_STREAM_ID_MAX] = {
+ {
+ .name = "RES_RB_GBL0",
+ .index = 3,
+ .reg_rptr = ISP_RB_RPTR12,
+ .reg_wptr = ISP_RB_WPTR12,
+ .reg_base_lo = ISP_RB_BASE_LO12,
+ .reg_base_hi = ISP_RB_BASE_HI12,
+ .reg_size = ISP_RB_SIZE12,
+ },
+ {
+ .name = "RES_RB_STR1",
+ .index = 0,
+ .reg_rptr = ISP_RB_RPTR9,
+ .reg_wptr = ISP_RB_WPTR9,
+ .reg_base_lo = ISP_RB_BASE_LO9,
+ .reg_base_hi = ISP_RB_BASE_HI9,
+ .reg_size = ISP_RB_SIZE9,
+ },
+ {
+ .name = "RES_RB_STR2",
+ .index = 1,
+ .reg_rptr = ISP_RB_RPTR10,
+ .reg_wptr = ISP_RB_WPTR10,
+ .reg_base_lo = ISP_RB_BASE_LO10,
+ .reg_base_hi = ISP_RB_BASE_HI10,
+ .reg_size = ISP_RB_SIZE10,
+ },
+ {
+ .name = "RES_RB_STR3",
+ .index = 2,
+ .reg_rptr = ISP_RB_RPTR11,
+ .reg_wptr = ISP_RB_WPTR11,
+ .reg_base_lo = ISP_RB_BASE_LO11,
+ .reg_base_hi = ISP_RB_BASE_HI11,
+ .reg_size = ISP_RB_SIZE11,
+ },
+};
+
+/* FW log ring buffer configuration */
+static struct isp4if_rb_config isp4if_log_rb_config = {
+ .name = "LOG_RB",
+ .index = 0,
+ .reg_rptr = ISP_LOG_RB_RPTR0,
+ .reg_wptr = ISP_LOG_RB_WPTR0,
+ .reg_base_lo = ISP_LOG_RB_BASE_LO0,
+ .reg_base_hi = ISP_LOG_RB_BASE_HI0,
+ .reg_size = ISP_LOG_RB_SIZE0,
+};
+
+static struct isp4if_gpu_mem_info *
+isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size)
+{
+ struct isp4if_gpu_mem_info *mem_info;
+ struct device *dev = ispif->dev;
+ int ret;
+
+ mem_info = kmalloc_obj(*mem_info, GFP_KERNEL);
+ if (!mem_info)
+ return NULL;
+
+ mem_info->mem_size = mem_size;
+ ret = isp_kernel_buffer_alloc(dev, mem_info->mem_size,
+ &mem_info->mem_handle,
+ &mem_info->gpu_mc_addr,
+ &mem_info->sys_addr);
+ if (ret) {
+ kfree(mem_info);
+ return NULL;
+ }
+
+ return mem_info;
+}
+
+static void isp4if_gpu_mem_free(struct isp4_interface *ispif,
+ struct isp4if_gpu_mem_info **mem_info_ptr)
+{
+ struct isp4if_gpu_mem_info *mem_info = *mem_info_ptr;
+ struct device *dev = ispif->dev;
+
+ if (!mem_info) {
+ dev_err(dev, "invalid mem_info\n");
+ return;
+ }
+
+ *mem_info_ptr = NULL;
+ isp_kernel_buffer_free(&mem_info->mem_handle, &mem_info->gpu_mc_addr,
+ &mem_info->sys_addr);
+ kfree(mem_info);
+}
+
+static void isp4if_dealloc_fw_gpumem(struct isp4_interface *ispif)
+{
+ isp4if_gpu_mem_free(ispif, &ispif->fw_mem_pool);
+ isp4if_gpu_mem_free(ispif, &ispif->fw_cmd_resp_buf);
+ isp4if_gpu_mem_free(ispif, &ispif->fw_log_buf);
+
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++)
+ isp4if_gpu_mem_free(ispif, &ispif->meta_info_buf[i]);
+}
+
+static int isp4if_alloc_fw_gpumem(struct isp4_interface *ispif)
+{
+ struct device *dev = ispif->dev;
+
+ ispif->fw_mem_pool = isp4if_gpu_mem_alloc(ispif,
+ ISP4FW_MEMORY_POOL_SIZE);
+ if (!ispif->fw_mem_pool)
+ goto error_no_memory;
+
+ ispif->fw_cmd_resp_buf =
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_RB_PMBMAP_MEM_SIZE);
+ if (!ispif->fw_cmd_resp_buf)
+ goto error_no_memory;
+
+ ispif->fw_log_buf =
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_FW_LOG_RINGBUF_SIZE);
+ if (!ispif->fw_log_buf)
+ goto error_no_memory;
+
+ for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) {
+ ispif->meta_info_buf[i] =
+ isp4if_gpu_mem_alloc(ispif, ISP4IF_META_INFO_BUF_SIZE);
+ if (!ispif->meta_info_buf[i])
+ goto error_no_memory;
+ }
+
+ return 0;
+
+error_no_memory:
+ dev_err(dev, "failed to allocate gpu memory\n");
+ return -ENOMEM;
+}
+
+static u32 isp4if_compute_check_sum(const void *buf, size_t buf_size)
+{
+ const u8 *surplus_ptr;
+ const u32 *buffer;
+ u32 checksum = 0;
+ size_t i;
+
+ buffer = (const u32 *)buf;
+ for (i = 0; i < buf_size / sizeof(u32); i++)
+ checksum += buffer[i];
+
+ surplus_ptr = (const u8 *)&buffer[i];
+ /* add surplus data crc checksum */
+ for (i = 0; i < buf_size % sizeof(u32); i++)
+ checksum += surplus_ptr[i];
+
+ return checksum;
+}
+
+void isp4if_clear_cmdq(struct isp4_interface *ispif)
+{
+ struct isp4if_cmd_element *buf_node, *tmp_node;
+ LIST_HEAD(free_list);
+
+ scoped_guard(spinlock, &ispif->cmdq_lock)
+ list_splice_init(&ispif->cmdq, &free_list);
+
+ list_for_each_entry_safe(buf_node, tmp_node, &free_list, list)
+ kfree(buf_node);
+}
+
+static bool isp4if_is_cmdq_rb_full(struct isp4_interface *ispif,
+ enum isp4if_stream_id stream)
+{
+ struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
+ u32 len = rb_config->val_size;
+ u32 rd_ptr, wr_ptr;
+ u32 bytes_free;
+
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
+
+ /*
+ * Read and write pointers are equal, indicating the ring buffer
+ * is empty
+ */
+ if (wr_ptr == rd_ptr)
+ return false;
+
+ if (wr_ptr > rd_ptr)
+ bytes_free = len - (wr_ptr - rd_ptr);
+ else
+ bytes_free = rd_ptr - wr_ptr;
+
+ /*
+ * Ignore one byte from the bytes free to prevent rd_ptr from equaling
+ * wr_ptr when the ring buffer is full, because rd_ptr == wr_ptr is
+ * supposed to indicate that the ring buffer is empty.
+ */
+ return bytes_free <= sizeof(struct isp4fw_cmd);
+}
+
+struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif,
+ u32 seq_num, u32 cmd_id)
+{
+ struct isp4if_cmd_element *ele;
+
+ guard(spinlock)(&ispif->cmdq_lock);
+
+ list_for_each_entry(ele, &ispif->cmdq, list) {
+ if (ele->seq_num == seq_num && ele->cmd_id == cmd_id) {
+ list_del(&ele->list);
+ return ele;
+ }
+ }
+
+ return NULL;
+}
+
+/* Must check that isp4if_is_cmdq_rb_full() == false before calling */
+static int isp4if_insert_isp_fw_cmd(struct isp4_interface *ispif,
+ enum isp4if_stream_id stream,
+ const struct isp4fw_cmd *cmd)
+{
+ struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
+ void *mem_sys = rb_config->base_sys_addr;
+ const u32 cmd_sz = sizeof(*cmd);
+ struct device *dev = ispif->dev;
+ u32 len = rb_config->val_size;
+ const void *src = cmd;
+ u32 rd_ptr, wr_ptr;
+ u32 bytes_to_end;
+
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
+ if (rd_ptr >= len || wr_ptr >= len) {
+ dev_err(dev,
+ "rb invalid: stream=%u, rd=%u, wr=%u, len=%u, cmd_sz=%u\n",
+ stream, rd_ptr, wr_ptr, len, cmd_sz);
+ return -EINVAL;
+ }
+
+ bytes_to_end = len - wr_ptr;
+ if (bytes_to_end >= cmd_sz) {
+ /* FW cmd is just a straight copy to the write pointer */
+ memcpy(mem_sys + wr_ptr, src, cmd_sz);
+ isp4hw_wreg(ispif->mmio, wreg, (wr_ptr + cmd_sz) % len);
+ } else {
+ /*
+ * FW cmd is split because the ring buffer needs to wrap
+ * around
+ */
+ memcpy(mem_sys + wr_ptr, src, bytes_to_end);
+ memcpy(mem_sys, src + bytes_to_end, cmd_sz - bytes_to_end);
+ isp4hw_wreg(ispif->mmio, wreg, cmd_sz - bytes_to_end);
+ }
+
+ return 0;
+}
+
+static inline enum isp4if_stream_id isp4if_get_fw_stream(u32 cmd_id)
+{
+ return ISP4IF_STREAM_ID_1;
+}
+
+static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id,
+ const void *package,
+ u32 package_size, bool sync)
+{
+ enum isp4if_stream_id stream = isp4if_get_fw_stream(cmd_id);
+ struct isp4if_cmd_element *ele = NULL;
+ struct device *dev = ispif->dev;
+ struct isp4fw_cmd cmd;
+ u32 seq_num;
+ int ret;
+
+ if (package_size > sizeof(cmd.cmd_param)) {
+ dev_err(dev, "fail pkgsize(%u) > %zu cmd:0x%x, stream %d\n",
+ package_size, sizeof(cmd.cmd_param), cmd_id, stream);
+ return -EINVAL;
+ }
+
+ /*
+ * The struct will be shared with ISP FW, use memset() to guarantee
+ * padding bits are zeroed, since this is not guaranteed on all
+ * compilers.
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.cmd_id = cmd_id;
+ switch (stream) {
+ case ISP4IF_STREAM_ID_GLOBAL:
+ cmd.cmd_stream_id = ISP4FW_STREAM_ID_INVALID;
+ break;
+ case ISP4IF_STREAM_ID_1:
+ cmd.cmd_stream_id = ISP4FW_STREAM_ID_1;
+ break;
+ default:
+ dev_err(dev, "fail bad stream id %d\n", stream);
+ return -EINVAL;
+ }
+
+ /* Allocate the sync command object early and outside of the lock */
+ if (sync) {
+ ele = kmalloc_obj(*ele, GFP_KERNEL);
+ if (!ele)
+ return -ENOMEM;
+
+ /* Get two references: one for the resp thread, one for us */
+ atomic_set(&ele->refcnt, 2);
+ init_completion(&ele->cmd_done);
+ }
+
+ if (package && package_size)
+ memcpy(cmd.cmd_param, package, package_size);
+
+ scoped_guard(mutex, &ispif->isp4if_mutex) {
+ ret = read_poll_timeout(isp4if_is_cmdq_rb_full, ret, !ret,
+ ISP4IF_RB_FULL_SLEEP_US,
+ ISP4IF_RB_FULL_TIMEOUT_US, false, ispif,
+ stream);
+ if (ret) {
+ struct isp4if_rb_config *rb_config =
+ &isp4if_resp_rb_config[stream];
+ u32 rd_ptr = isp4hw_rreg(ispif->mmio,
+ rb_config->reg_rptr);
+ u32 wr_ptr = isp4hw_rreg(ispif->mmio,
+ rb_config->reg_wptr);
+
+ dev_err(dev,
+ "fail to get free cmdq slot, stream (%d),rd %u, wr %u\n",
+ stream, rd_ptr, wr_ptr);
+ ret = -ETIMEDOUT;
+ goto free_ele;
+ }
+
+ seq_num = ispif->host2fw_seq_num++;
+ cmd.cmd_seq_num = seq_num;
+ cmd.cmd_check_sum = isp4if_compute_check_sum(&cmd, sizeof(cmd)
+ - sizeof(u32));
+
+ /*
+ * only append the fw cmd to queue when its response needs to
+ * be waited for, currently there are only two such commands,
+ * disable channel and stop stream which are only sent after
+ * close camera
+ */
+ if (ele) {
+ ele->seq_num = seq_num;
+ ele->cmd_id = cmd_id;
+ scoped_guard(spinlock, &ispif->cmdq_lock)
+ list_add_tail(&ele->list, &ispif->cmdq);
+ }
+
+ ret = isp4if_insert_isp_fw_cmd(ispif, stream, &cmd);
+ if (ret) {
+ dev_err(dev,
+ "fail for insert_isp_fw_cmd cmd_id (0x%08x)\n",
+ cmd_id);
+ goto err_dequeue_ele;
+ }
+ }
+
+ if (ele) {
+ ret = wait_for_completion_timeout(&ele->cmd_done,
+ ISP4IF_FW_CMD_TIMEOUT);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto err_dequeue_ele;
+ }
+
+ ret = 0;
+ goto put_ele_ref;
+ }
+
+ return 0;
+
+err_dequeue_ele:
+ /*
+ * Try to remove the command from the queue. If that fails, then it
+ * means the response thread is currently using the object, and we need
+ * to use the refcount to avoid a use-after-free by either side.
+ */
+ if (ele && isp4if_rm_cmd_from_cmdq(ispif, seq_num, cmd_id))
+ goto free_ele;
+
+put_ele_ref:
+ /* Don't free the command if we didn't put the last reference */
+ if (ele && atomic_dec_return(&ele->refcnt))
+ ele = NULL;
+
+free_ele:
+ /*
+ * The response handler or the timeout/error path must dequeue the
+ * command element before we own the final reference.
+ */
+ if (ele)
+ INIT_LIST_HEAD(&ele->list);
+ kfree(ele);
+ return ret;
+}
+
+static int isp4if_send_buffer(struct isp4_interface *ispif,
+ struct isp4if_img_buf_info *buf_info)
+{
+ struct isp4fw_cmd_send_buffer cmd;
+
+ /*
+ * The struct will be shared with ISP FW, use memset() to guarantee
+ * padding bits are zeroed, since this is not guaranteed on all
+ * compilers.
+ */
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.buffer_type = ISP4FW_BUFFER_TYPE_PREVIEW;
+ cmd.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
+ isp4if_split_addr64(buf_info->planes[0].mc_addr,
+ &cmd.buffer.buf_base_a_lo,
+ &cmd.buffer.buf_base_a_hi);
+ cmd.buffer.buf_size_a = buf_info->planes[0].len;
+
+ isp4if_split_addr64(buf_info->planes[1].mc_addr,
+ &cmd.buffer.buf_base_b_lo,
+ &cmd.buffer.buf_base_b_hi);
+ cmd.buffer.buf_size_b = buf_info->planes[1].len;
+
+ isp4if_split_addr64(buf_info->planes[2].mc_addr,
+ &cmd.buffer.buf_base_c_lo,
+ &cmd.buffer.buf_base_c_hi);
+ cmd.buffer.buf_size_c = buf_info->planes[2].len;
+
+ return isp4if_send_fw_cmd(ispif, ISP4FW_CMD_ID_SEND_BUFFER, &cmd,
+ sizeof(cmd), false);
+}
+
+static void isp4if_init_rb_config(struct isp4_interface *ispif,
+ struct isp4if_rb_config *rb_config)
+{
+ isp4hw_wreg(ispif->mmio, rb_config->reg_rptr, 0x0);
+ isp4hw_wreg(ispif->mmio, rb_config->reg_wptr, 0x0);
+ isp4hw_wreg(ispif->mmio, rb_config->reg_base_lo,
+ rb_config->base_mc_addr);
+ isp4hw_wreg(ispif->mmio, rb_config->reg_base_hi,
+ rb_config->base_mc_addr >> 32);
+ isp4hw_wreg(ispif->mmio, rb_config->reg_size, rb_config->val_size);
+}
+
+static int isp4if_fw_init(struct isp4_interface *ispif)
+{
+ u32 aligned_rb_chunk_size = ISP4IF_RB_PMBMAP_MEM_CHUNK & 0xffffffc0;
+ struct isp4if_rb_config *rb_config;
+ u32 offset;
+ unsigned int i;
+
+ /* initialize CMD_RB streams */
+ for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
+ rb_config = (isp4if_cmd_rb_config + i);
+ offset = aligned_rb_chunk_size * rb_config->index;
+
+ rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
+ rb_config->base_sys_addr =
+ ispif->fw_cmd_resp_buf->sys_addr + offset;
+ rb_config->base_mc_addr =
+ ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
+
+ isp4if_init_rb_config(ispif, rb_config);
+ }
+
+ /* initialize RESP_RB streams */
+ for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
+ rb_config = (isp4if_resp_rb_config + i);
+ offset = aligned_rb_chunk_size *
+ (rb_config->index + ISP4IF_RESP_CHAN_TO_RB_OFFSET - 1);
+
+ rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
+ rb_config->base_sys_addr =
+ ispif->fw_cmd_resp_buf->sys_addr + offset;
+ rb_config->base_mc_addr =
+ ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
+
+ isp4if_init_rb_config(ispif, rb_config);
+ }
+
+ /* initialize LOG_RB stream */
+ rb_config = &isp4if_log_rb_config;
+ rb_config->val_size = ISP4IF_FW_LOG_RINGBUF_SIZE;
+ rb_config->base_mc_addr = ispif->fw_log_buf->gpu_mc_addr;
+ rb_config->base_sys_addr = ispif->fw_log_buf->sys_addr;
+
+ isp4if_init_rb_config(ispif, rb_config);
+
+ return 0;
+}
+
+static int isp4if_wait_fw_ready(struct isp4_interface *ispif,
+ u32 isp_status_addr)
+{
+ struct device *dev = ispif->dev;
+ u32 timeout_ms = 100;
+ u32 interval_ms = 1;
+ u32 reg_val;
+
+ /* wait for FW initialize done! */
+ if (!read_poll_timeout(isp4hw_rreg, reg_val, reg_val
+ & ISP_STATUS__CCPU_REPORT_MASK,
+ interval_ms * 1000, timeout_ms * 1000, false,
+ ispif->mmio, isp_status_addr))
+ return 0;
+
+ dev_err(dev, "ISP CCPU FW boot failed\n");
+
+ return -ETIME;
+}
+
+static void isp4if_enable_ccpu(struct isp4_interface *ispif)
+{
+ u32 reg_val;
+
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
+ reg_val &= (~ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK);
+ isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
+
+ usleep_range(100, 150);
+
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
+ reg_val &= (~ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK);
+ isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
+}
+
+static void isp4if_disable_ccpu(struct isp4_interface *ispif)
+{
+ u32 reg_val;
+
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
+ reg_val |= ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK;
+ isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
+
+ usleep_range(100, 150);
+
+ reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
+ reg_val |= ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK;
+ isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
+}
+
+static int isp4if_fw_boot(struct isp4_interface *ispif)
+{
+ struct device *dev = ispif->dev;
+
+ if (ispif->status != ISP4IF_STATUS_PWR_ON) {
+ dev_err(dev, "invalid isp power status %d\n", ispif->status);
+ return -EINVAL;
+ }
+
+ isp4if_disable_ccpu(ispif);
+
+ isp4if_fw_init(ispif);
+
+ /* clear ccpu status */
+ isp4hw_wreg(ispif->mmio, ISP_STATUS, 0x0);
+
+ isp4if_enable_ccpu(ispif);
+
+ if (isp4if_wait_fw_ready(ispif, ISP_STATUS)) {
+ isp4if_disable_ccpu(ispif);
+ return -EINVAL;
+ }
+
+ /* enable interrupts */
+ isp4hw_wreg(ispif->mmio, ISP_SYS_INT0_EN,
+ ISP4IF_FW_RESP_RB_IRQ_EN_MASK);
+
+ ispif->status = ISP4IF_STATUS_FW_RUNNING;
+
+ dev_dbg(dev, "ISP CCPU FW boot success\n");
+
+ return 0;
+}
+
+int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream,
+ struct isp4fw_resp *resp)
+{
+ struct isp4if_rb_config *rb_config = &isp4if_resp_rb_config[stream];
+ u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
+ void *mem_sys = rb_config->base_sys_addr;
+ const u32 resp_sz = sizeof(*resp);
+ struct device *dev = ispif->dev;
+ u32 len = rb_config->val_size;
+ u32 rd_ptr, wr_ptr;
+ u32 bytes_to_end;
+ void *dst = resp;
+ u32 checksum;
+
+ rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
+ wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
+ if (rd_ptr >= len || wr_ptr >= len)
+ goto err_rb_invalid;
+
+ /*
+ * Read and write pointers are equal, indicating the ring buffer is
+ * empty
+ */
+ if (rd_ptr == wr_ptr)
+ return -ENODATA;
+
+ bytes_to_end = len - rd_ptr;
+ if (bytes_to_end >= resp_sz) {
+ /* FW response is just a straight copy from the read pointer */
+ if (wr_ptr > rd_ptr && wr_ptr - rd_ptr < resp_sz)
+ goto err_rb_invalid;
+
+ memcpy(dst, mem_sys + rd_ptr, resp_sz);
+ isp4hw_wreg(ispif->mmio, rreg, (rd_ptr + resp_sz) % len);
+ } else {
+ /*
+ * FW response is split because the ring buffer wrapped
+ * around
+ */
+ if (wr_ptr > rd_ptr || wr_ptr < resp_sz - bytes_to_end)
+ goto err_rb_invalid;
+
+ memcpy(dst, mem_sys + rd_ptr, bytes_to_end);
+ memcpy(dst + bytes_to_end, mem_sys, resp_sz - bytes_to_end);
+ isp4hw_wreg(ispif->mmio, rreg, resp_sz - bytes_to_end);
+ }
+
+ checksum = isp4if_compute_check_sum(resp, resp_sz - sizeof(u32));
+ if (checksum != resp->resp_check_sum) {
+ dev_err(dev, "resp checksum 0x%x,should 0x%x,rptr %u,wptr %u\n",
+ checksum, resp->resp_check_sum, rd_ptr, wr_ptr);
+ dev_err(dev, "(%u), seqNo %u, resp_id (0x%x)\n",
+ stream, resp->resp_seq_num,
+ resp->resp_id);
+ return -EINVAL;
+ }
+
+ return 0;
+
+err_rb_invalid:
+ dev_err(dev,
+ "rb invalid: stream=%u, rd=%u, wr=%u, len=%u, resp_sz=%u\n",
+ stream, rd_ptr, wr_ptr, len, resp_sz);
+ return -EINVAL;
+}
+
+int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id,
+ const void *package, u32 package_size)
+{
+ return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, false);
+}
+
+int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id,
+ const void *package, u32 package_size)
+{
+ return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, true);
+}
+
+void isp4if_clear_bufq(struct isp4_interface *ispif)
+{
+ struct isp4if_img_buf_node *buf_node, *tmp_node;
+ LIST_HEAD(free_list);
+
+ scoped_guard(spinlock, &ispif->bufq_lock)
+ list_splice_init(&ispif->bufq, &free_list);
+
+ list_for_each_entry_safe(buf_node, tmp_node, &free_list, node)
+ kfree(buf_node);
+}
+
+void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node)
+{
+ kfree(buf_node);
+}
+
+struct isp4if_img_buf_node *
+isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info)
+{
+ struct isp4if_img_buf_node *node;
+
+ node = kmalloc_obj(*node, GFP_KERNEL);
+ if (node)
+ node->buf_info = *buf_info;
+
+ return node;
+}
+
+struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif)
+{
+ struct isp4if_img_buf_node *buf_node;
+
+ guard(spinlock)(&ispif->bufq_lock);
+
+ buf_node = list_first_entry_or_null(&ispif->bufq, typeof(*buf_node),
+ node);
+ if (buf_node)
+ list_del(&buf_node->node);
+
+ return buf_node;
+}
+
+int isp4if_queue_buffer(struct isp4_interface *ispif,
+ struct isp4if_img_buf_node *buf_node)
+{
+ int ret;
+
+ ret = isp4if_send_buffer(ispif, &buf_node->buf_info);
+ if (ret)
+ return ret;
+
+ scoped_guard(spinlock, &ispif->bufq_lock)
+ list_add_tail(&buf_node->node, &ispif->bufq);
+
+ return 0;
+}
+
+int isp4if_stop(struct isp4_interface *ispif)
+{
+ isp4if_disable_ccpu(ispif);
+
+ isp4if_dealloc_fw_gpumem(ispif);
+
+ return 0;
+}
+
+int isp4if_start(struct isp4_interface *ispif)
+{
+ int ret;
+
+ ret = isp4if_alloc_fw_gpumem(ispif);
+ if (ret)
+ return ret;
+
+ ret = isp4if_fw_boot(ispif);
+ if (ret)
+ goto failed_fw_boot;
+
+ return 0;
+
+failed_fw_boot:
+ isp4if_dealloc_fw_gpumem(ispif);
+ return ret;
+}
+
+int isp4if_deinit(struct isp4_interface *ispif)
+{
+ isp4if_clear_cmdq(ispif);
+
+ isp4if_clear_bufq(ispif);
+
+ mutex_destroy(&ispif->isp4if_mutex);
+
+ return 0;
+}
+
+int isp4if_init(struct isp4_interface *ispif, struct device *dev,
+ void __iomem *isp_mmio)
+{
+ ispif->dev = dev;
+ ispif->mmio = isp_mmio;
+
+ spin_lock_init(&ispif->cmdq_lock); /* used for cmdq access */
+ spin_lock_init(&ispif->bufq_lock); /* used for bufq access */
+ mutex_init(&ispif->isp4if_mutex); /* used for commands sent to ispfw */
+
+ INIT_LIST_HEAD(&ispif->cmdq);
+ INIT_LIST_HEAD(&ispif->bufq);
+
+ return 0;
+}