diff --git a/drivers/cam_cpas/cam_cpas_hw.c b/drivers/cam_cpas/cam_cpas_hw.c index 56bea71d400b..2119e1b6885b 100644 --- a/drivers/cam_cpas/cam_cpas_hw.c +++ b/drivers/cam_cpas/cam_cpas_hw.c @@ -1544,6 +1544,7 @@ static int cam_cpas_hw_stop(void *hw_priv, void *stop_args, struct cam_cpas_private_soc *soc_private = NULL; int rc = 0; long result; + int retry_camnoc_idle = 0; if (!hw_priv || !stop_args) { CAM_ERR(CAM_CPAS, "Invalid arguments %pK %pK", @@ -1597,6 +1598,18 @@ static int cam_cpas_hw_stop(void *hw_priv, void *stop_args, } } + if (cpas_core->internal_ops.qchannel_handshake) { + rc = cpas_core->internal_ops.qchannel_handshake( + cpas_hw, false); + if (rc) { + CAM_ERR(CAM_CPAS, + "failed in qchannel_handshake rc=%d", + rc); + retry_camnoc_idle = 1; + /* Do not return error, passthrough */ + } + } + rc = cam_cpas_soc_disable_irq(&cpas_hw->soc_info); if (rc) { CAM_ERR(CAM_CPAS, "disable_irq failed, rc=%d", rc); @@ -1612,6 +1625,19 @@ static int cam_cpas_hw_stop(void *hw_priv, void *stop_args, atomic_read(&cpas_core->irq_count)); } + /* try again incase camnoc is still not idle */ + if (cpas_core->internal_ops.qchannel_handshake && + retry_camnoc_idle) { + rc = cpas_core->internal_ops.qchannel_handshake( + cpas_hw, false); + if (rc) { + CAM_ERR(CAM_CPAS, + "failed in qchannel_handshake rc=%d", + rc); + /* Do not return error, passthrough */ + } + } + rc = cam_cpas_soc_disable_resources(&cpas_hw->soc_info, true, false); if (rc) { diff --git a/drivers/cam_cpas/cam_cpas_hw.h b/drivers/cam_cpas/cam_cpas_hw.h index 1c25d0f56203..a7c7f429ccc2 100644 --- a/drivers/cam_cpas/cam_cpas_hw.h +++ b/drivers/cam_cpas/cam_cpas_hw.h @@ -70,6 +70,8 @@ enum cam_cpas_access_type { * @power_off: Function pointer for hw core specific power off settings * @setup_qos_settings: Function pointer for hw to select a specific qos header * @print_poweron_settings: Function pointer for hw to print poweron settings + * @qchannel_handshake: Function pointer for hw core specific qchannel + * handshake settings * */ struct cam_cpas_internal_ops { @@ -85,6 +87,7 @@ struct cam_cpas_internal_ops { int (*setup_qos_settings)(struct cam_hw_info *cpas_hw, uint32_t selection_mask); int (*print_poweron_settings)(struct cam_hw_info *cpas_hw); + int (*qchannel_handshake)(struct cam_hw_info *cpas_hw, bool power_on); }; /** diff --git a/drivers/cam_cpas/cam_cpas_hw_intf.h b/drivers/cam_cpas/cam_cpas_hw_intf.h index fc9297ac69c1..09d1180a4b01 100644 --- a/drivers/cam_cpas/cam_cpas_hw_intf.h +++ b/drivers/cam_cpas/cam_cpas_hw_intf.h @@ -19,6 +19,8 @@ #define CAM_CPAS_POLL_MIN_USECS 200 /* Maximum usecs to sleep while polling */ #define CAM_CPAS_POLL_MAX_USECS 250 +/* Number of times to retry while polling */ +#define CAM_CPAS_POLL_QH_RETRY_CNT 50 /** * enum cam_cpas_hw_type - Enum for CPAS HW type diff --git a/drivers/cam_cpas/camss_top/cam_camsstop_hw.c b/drivers/cam_cpas/camss_top/cam_camsstop_hw.c index 23f8542cdd87..d8552c56d1db 100644 --- a/drivers/cam_cpas/camss_top/cam_camsstop_hw.c +++ b/drivers/cam_cpas/camss_top/cam_camsstop_hw.c @@ -79,6 +79,7 @@ int cam_camsstop_get_internal_ops(struct cam_cpas_internal_ops *internal_ops) internal_ops->power_off = NULL; internal_ops->setup_qos_settings = NULL; internal_ops->print_poweron_settings = NULL; + internal_ops->qchannel_handshake = NULL; return 0; } diff --git a/drivers/cam_cpas/cpas_top/cam_cpastop_hw.c b/drivers/cam_cpas/cpas_top/cam_cpastop_hw.c index d8cb87978ffd..bbcdbed8fc35 100644 --- a/drivers/cam_cpas/cpas_top/cam_cpastop_hw.c +++ b/drivers/cam_cpas/cpas_top/cam_cpastop_hw.c @@ -34,6 +34,8 @@ #include "cam_req_mgr_workq.h" struct cam_camnoc_info *camnoc_info; +struct cam_cpas_camnoc_qchannel *qchannel_info; + #define CAMNOC_SLAVE_MAX_ERR_CODE 7 static const char * const camnoc_salve_err_code[] = { @@ -818,11 +820,65 @@ static int cam_cpastop_poweroff(struct cam_hw_info *cpas_hw) return rc; } +static int cam_cpastop_qchannel_handshake(struct cam_hw_info *cpas_hw, + bool power_on) +{ + struct cam_cpas *cpas_core = (struct cam_cpas *) cpas_hw->core_info; + struct cam_hw_soc_info *soc_info = &cpas_hw->soc_info; + int32_t reg_indx = cpas_core->regbase_index[CAM_CPAS_REG_CPASTOP]; + uint32_t mask = 0; + uint32_t wait_data, qchannel_status, qdeny; + int rc = 0; + + if (reg_indx == -1) + return -EINVAL; + + if (!qchannel_info) + return 0; + + if (power_on) { + /* wait for QACCEPTN in QCHANNEL status*/ + mask = BIT(0); + wait_data = 1; + } else { + /* Clear the quiecience request in QCHANNEL ctrl*/ + cam_io_w_mb(0, soc_info->reg_map[reg_indx].mem_base + + qchannel_info->qchannel_ctrl); + /* wait for QACCEPTN and QDENY in QCHANNEL status*/ + mask = BIT(1) | BIT(0); + wait_data = 0; + } + + rc = cam_io_poll_value_wmask( + soc_info->reg_map[reg_indx].mem_base + + qchannel_info->qchannel_status, + wait_data, mask, CAM_CPAS_POLL_QH_RETRY_CNT, + CAM_CPAS_POLL_MIN_USECS, CAM_CPAS_POLL_MAX_USECS); + if (rc) { + CAM_ERR(CAM_CPAS, + "camnoc idle sequence failed, qstat 0x%x", + cam_io_r(soc_info->reg_map[reg_indx].mem_base + + qchannel_info->qchannel_status)); + /* Do not return error, passthrough */ + rc = 0; + } + + /* check if deny bit is set */ + qchannel_status = cam_io_r_mb(soc_info->reg_map[reg_indx].mem_base + + qchannel_info->qchannel_status); + qdeny = (qchannel_status & BIT(1)); + if (!power_on && qdeny) + rc = -EBUSY; + + return rc; +} + static int cam_cpastop_init_hw_version(struct cam_hw_info *cpas_hw, struct cam_cpas_hw_caps *hw_caps) { int rc = 0; struct cam_hw_soc_info *soc_info = &cpas_hw->soc_info; + qchannel_info = NULL; CAM_DBG(CAM_CPAS, "hw_version=0x%x Camera Version %d.%d.%d, cpas version %d.%d.%d", @@ -864,18 +920,23 @@ static int cam_cpastop_init_hw_version(struct cam_hw_info *cpas_hw, break; case CAM_CPAS_TITAN_580_V100: camnoc_info = &cam580_cpas100_camnoc_info; + qchannel_info = &cam580_cpas100_qchannel_info; break; case CAM_CPAS_TITAN_540_V100: camnoc_info = &cam540_cpas100_camnoc_info; + qchannel_info = &cam540_cpas100_qchannel_info; break; case CAM_CPAS_TITAN_520_V100: camnoc_info = &cam520_cpas100_camnoc_info; + qchannel_info = &cam520_cpas100_qchannel_info; break; case CAM_CPAS_TITAN_545_V100: camnoc_info = &cam545_cpas100_camnoc_info; + qchannel_info = &cam545_cpas100_qchannel_info; break; case CAM_CPAS_TITAN_570_V200: camnoc_info = &cam570_cpas200_camnoc_info; + qchannel_info = &cam570_cpas200_qchannel_info; break; case CAM_CPAS_TITAN_680_V100: camnoc_info = &cam680_cpas100_camnoc_info; @@ -961,6 +1022,7 @@ int cam_cpastop_get_internal_ops(struct cam_cpas_internal_ops *internal_ops) internal_ops->setup_qos_settings = cam_cpastop_setup_qos_settings; internal_ops->print_poweron_settings = cam_cpastop_print_poweron_settings; + internal_ops->qchannel_handshake = cam_cpastop_qchannel_handshake; return 0; } diff --git a/drivers/cam_cpas/cpas_top/cam_cpastop_hw.h b/drivers/cam_cpas/cpas_top/cam_cpastop_hw.h index 2d59122cab92..3992e079ad18 100644 --- a/drivers/cam_cpas/cpas_top/cam_cpastop_hw.h +++ b/drivers/cam_cpas/cpas_top/cam_cpastop_hw.h @@ -356,4 +356,16 @@ struct cam_cpas_work_payload { struct work_struct work; }; +/** + * struct cam_cpas_camnoc_qchannel : Cpas camnoc qchannel info + * + * @qchannel_ctrl: offset to configure to control camnoc qchannel interface + * @qchannel_status: offset to read camnoc qchannel interface status + * + */ +struct cam_cpas_camnoc_qchannel { + uint32_t qchannel_ctrl; + uint32_t qchannel_status; +}; + #endif /* _CAM_CPASTOP_HW_H_ */ diff --git a/drivers/cam_cpas/cpas_top/cpastop_v520_100.h b/drivers/cam_cpas/cpas_top/cpastop_v520_100.h index ec99574ebf10..4a8c4f945d03 100644 --- a/drivers/cam_cpas/cpas_top/cpastop_v520_100.h +++ b/drivers/cam_cpas/cpas_top/cpastop_v520_100.h @@ -237,4 +237,8 @@ static struct cam_camnoc_info cam520_cpas100_camnoc_info = { .errata_wa_list = NULL, }; +static struct cam_cpas_camnoc_qchannel cam520_cpas100_qchannel_info = { + .qchannel_ctrl = 0x14, + .qchannel_status = 0x18, +}; #endif /* _CPASTOP_V520_100_H_ */ diff --git a/drivers/cam_cpas/cpas_top/cpastop_v540_100.h b/drivers/cam_cpas/cpas_top/cpastop_v540_100.h index 65cb1fabc701..3d9e65ed3c1f 100644 --- a/drivers/cam_cpas/cpas_top/cpastop_v540_100.h +++ b/drivers/cam_cpas/cpas_top/cpastop_v540_100.h @@ -238,4 +238,8 @@ static struct cam_camnoc_info cam540_cpas100_camnoc_info = { .errata_wa_list = NULL, }; +static struct cam_cpas_camnoc_qchannel cam540_cpas100_qchannel_info = { + .qchannel_ctrl = 0x14, + .qchannel_status = 0x18, +}; #endif /* _CPASTOP_V540_100_H_ */ diff --git a/drivers/cam_cpas/cpas_top/cpastop_v545_100.h b/drivers/cam_cpas/cpas_top/cpastop_v545_100.h index d7cbe7794f6e..d3ad5fbc1075 100644 --- a/drivers/cam_cpas/cpas_top/cpastop_v545_100.h +++ b/drivers/cam_cpas/cpas_top/cpastop_v545_100.h @@ -315,4 +315,8 @@ static struct cam_camnoc_info cam545_cpas100_camnoc_info = { .errata_wa_list = NULL, }; +static struct cam_cpas_camnoc_qchannel cam545_cpas100_qchannel_info = { + .qchannel_ctrl = 0x14, + .qchannel_status = 0x18, +}; #endif /* _CPASTOP_V545_100_H_ */ diff --git a/drivers/cam_cpas/cpas_top/cpastop_v570_200.h b/drivers/cam_cpas/cpas_top/cpastop_v570_200.h index 02410cc00876..49978a0f5279 100644 --- a/drivers/cam_cpas/cpas_top/cpastop_v570_200.h +++ b/drivers/cam_cpas/cpas_top/cpastop_v570_200.h @@ -881,4 +881,8 @@ static struct cam_camnoc_info cam570_cpas200_camnoc_info = { .errata_wa_list = &cam570_cpas200_errata_wa_list, }; +static struct cam_cpas_camnoc_qchannel cam570_cpas200_qchannel_info = { + .qchannel_ctrl = 0x5C, + .qchannel_status = 0x60, +}; #endif /* _CPASTOP_V570_200_H_ */ diff --git a/drivers/cam_cpas/cpas_top/cpastop_v580_100.h b/drivers/cam_cpas/cpas_top/cpastop_v580_100.h index 9855fdf6821c..7c01ae3cdcb0 100644 --- a/drivers/cam_cpas/cpas_top/cpastop_v580_100.h +++ b/drivers/cam_cpas/cpas_top/cpastop_v580_100.h @@ -1043,5 +1043,9 @@ static struct cam_camnoc_info cam580_cpas100_camnoc_info = { .errata_wa_list = &cam580_cpas100_errata_wa_list, }; +static struct cam_cpas_camnoc_qchannel cam580_cpas100_qchannel_info = { + .qchannel_ctrl = 0x5C, + .qchannel_status = 0x60, +}; #endif /* _CPASTOP_V580_100_H_ */