| /* Copyright (c) 2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/timer.h> |
| #include <linux/slab.h> |
| |
| #include "cam_cpas_hw_intf.h" |
| #include "cam_cpas_hw.h" |
| #include "cam_cpastop_hw.h" |
| #include "cam_io_util.h" |
| #include "cam_cpas_soc.h" |
| #include "cpastop100.h" |
| #include "cpastop_v170_110.h" |
| |
| struct cam_camnoc_info *camnoc_info; |
| |
| static int cam_cpastop_get_hw_info(struct cam_hw_info *cpas_hw, |
| struct cam_cpas_hw_caps *hw_caps) |
| { |
| 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 reg_value; |
| |
| if (reg_indx == -1) |
| return -EINVAL; |
| |
| hw_caps->camera_family = CAM_FAMILY_CPAS_SS; |
| |
| reg_value = cam_io_r_mb(soc_info->reg_map[reg_indx].mem_base + 0x0); |
| hw_caps->camera_version.major = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xff0000, 0x10); |
| hw_caps->camera_version.minor = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xff00, 0x8); |
| hw_caps->camera_version.incr = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xff, 0x0); |
| |
| reg_value = cam_io_r_mb(soc_info->reg_map[reg_indx].mem_base + 0x4); |
| hw_caps->cpas_version.major = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xf0000000, 0x1c); |
| hw_caps->cpas_version.minor = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xfff0000, 0x10); |
| hw_caps->cpas_version.incr = |
| CAM_BITS_MASK_SHIFT(reg_value, 0xffff, 0x0); |
| |
| reg_value = cam_io_r_mb(soc_info->reg_map[reg_indx].mem_base + 0x8); |
| hw_caps->camera_capability = reg_value; |
| |
| CAM_DBG(CAM_FD, "Family %d, version %d.%d.%d, cpas %d.%d.%d, cap 0x%x", |
| hw_caps->camera_family, hw_caps->camera_version.major, |
| hw_caps->camera_version.minor, hw_caps->camera_version.incr, |
| hw_caps->cpas_version.major, hw_caps->cpas_version.minor, |
| hw_caps->cpas_version.incr, hw_caps->camera_capability); |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_setup_regbase_indices(struct cam_hw_soc_info *soc_info, |
| int32_t regbase_index[], int32_t num_reg_map) |
| { |
| uint32_t index; |
| int rc; |
| |
| if (num_reg_map > CAM_CPAS_REG_MAX) { |
| CAM_ERR(CAM_CPAS, "invalid num_reg_map=%d", num_reg_map); |
| return -EINVAL; |
| } |
| |
| if (soc_info->num_mem_block > CAM_SOC_MAX_BLOCK) { |
| CAM_ERR(CAM_CPAS, "invalid num_mem_block=%d", |
| soc_info->num_mem_block); |
| return -EINVAL; |
| } |
| |
| rc = cam_common_util_get_string_index(soc_info->mem_block_name, |
| soc_info->num_mem_block, "cam_cpas_top", &index); |
| if ((rc == 0) && (index < num_reg_map)) { |
| regbase_index[CAM_CPAS_REG_CPASTOP] = index; |
| } else { |
| CAM_ERR(CAM_CPAS, "regbase not found for CPASTOP, rc=%d, %d %d", |
| rc, index, num_reg_map); |
| return -EINVAL; |
| } |
| |
| rc = cam_common_util_get_string_index(soc_info->mem_block_name, |
| soc_info->num_mem_block, "cam_camnoc", &index); |
| if ((rc == 0) && (index < num_reg_map)) { |
| regbase_index[CAM_CPAS_REG_CAMNOC] = index; |
| } else { |
| CAM_ERR(CAM_CPAS, "regbase not found for CAMNOC, rc=%d, %d %d", |
| rc, index, num_reg_map); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_handle_errlogger(struct cam_cpas *cpas_core, |
| struct cam_hw_soc_info *soc_info) |
| { |
| uint32_t reg_value[4]; |
| int i; |
| int size = camnoc_info->error_logger_size; |
| int camnoc_index = cpas_core->regbase_index[CAM_CPAS_REG_CAMNOC]; |
| |
| for (i = 0; (i + 3) < size; i = i + 4) { |
| reg_value[0] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i]); |
| reg_value[1] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 1]); |
| reg_value[2] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 2]); |
| reg_value[3] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 3]); |
| CAM_ERR(CAM_CPAS, |
| "offset[0x%x] values [0x%x] [0x%x] [0x%x] [0x%x]", |
| camnoc_info->error_logger[i], reg_value[0], |
| reg_value[1], reg_value[2], reg_value[3]); |
| } |
| |
| if ((i + 2) < size) { |
| reg_value[0] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i]); |
| reg_value[1] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 1]); |
| reg_value[2] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 2]); |
| CAM_ERR(CAM_CPAS, "offset[0x%x] values [0x%x] [0x%x] [0x%x]", |
| camnoc_info->error_logger[i], reg_value[0], |
| reg_value[1], reg_value[2]); |
| i = i + 3; |
| } |
| |
| if ((i + 1) < size) { |
| reg_value[0] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i]); |
| reg_value[1] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i + 1]); |
| CAM_ERR(CAM_CPAS, "offset[0x%x] values [0x%x] [0x%x]", |
| camnoc_info->error_logger[i], reg_value[0], |
| reg_value[1]); |
| i = i + 2; |
| } |
| |
| if (i < size) { |
| reg_value[0] = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->error_logger[i]); |
| CAM_ERR(CAM_CPAS, "offset[0x%x] values [0x%x]", |
| camnoc_info->error_logger[i], reg_value[0]); |
| } |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_handle_ubwc_err(struct cam_cpas *cpas_core, |
| struct cam_hw_soc_info *soc_info, int i) |
| { |
| uint32_t reg_value; |
| int camnoc_index = cpas_core->regbase_index[CAM_CPAS_REG_CAMNOC]; |
| |
| reg_value = cam_io_r_mb(soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->irq_err[i].err_status.offset); |
| |
| CAM_ERR(CAM_CPAS, |
| "Dumping ubwc error status [%d]: offset[0x%x] value[0x%x]", |
| i, camnoc_info->irq_err[i].err_status.offset, reg_value); |
| |
| return reg_value; |
| } |
| |
| static int cam_cpastop_handle_ahb_timeout_err(struct cam_hw_info *cpas_hw) |
| { |
| CAM_ERR(CAM_CPAS, "ahb timout error"); |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_disable_test_irq(struct cam_hw_info *cpas_hw) |
| { |
| camnoc_info->irq_sbm->sbm_clear.value &= ~0x4; |
| camnoc_info->irq_sbm->sbm_enable.value &= ~0x100; |
| camnoc_info->irq_err[CAM_CAMNOC_HW_IRQ_CAMNOC_TEST].enable = false; |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_reset_irq(struct cam_hw_info *cpas_hw) |
| { |
| int i; |
| |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->irq_sbm->sbm_clear); |
| for (i = 0; i < camnoc_info->irq_err_size; i++) { |
| if (camnoc_info->irq_err[i].enable) |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->irq_err[i].err_clear); |
| } |
| |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->irq_sbm->sbm_enable); |
| for (i = 0; i < camnoc_info->irq_err_size; i++) { |
| if (camnoc_info->irq_err[i].enable) |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->irq_err[i].err_enable); |
| } |
| |
| return 0; |
| } |
| |
| static void cam_cpastop_notify_clients(struct cam_cpas *cpas_core, |
| enum cam_camnoc_hw_irq_type irq_type, uint32_t irq_data) |
| { |
| int i; |
| struct cam_cpas_client *cpas_client; |
| |
| CAM_DBG(CAM_CPAS, |
| "Notify CB : num_clients=%d, registered=%d, started=%d", |
| cpas_core->num_clients, cpas_core->registered_clients, |
| cpas_core->streamon_clients); |
| |
| for (i = 0; i < cpas_core->num_clients; i++) { |
| if (CAM_CPAS_CLIENT_STARTED(cpas_core, i)) { |
| cpas_client = cpas_core->cpas_client[i]; |
| if (cpas_client->data.cam_cpas_client_cb) { |
| CAM_DBG(CAM_CPAS, |
| "Calling client CB %d : %d 0x%x", |
| i, irq_type, irq_data); |
| cpas_client->data.cam_cpas_client_cb( |
| cpas_client->data.client_handle, |
| cpas_client->data.userdata, |
| (enum cam_camnoc_irq_type)irq_type, |
| irq_data); |
| } |
| } |
| } |
| } |
| |
| static void cam_cpastop_work(struct work_struct *work) |
| { |
| struct cam_cpas_work_payload *payload; |
| struct cam_hw_info *cpas_hw; |
| struct cam_cpas *cpas_core; |
| struct cam_hw_soc_info *soc_info; |
| int i; |
| enum cam_camnoc_hw_irq_type irq_type; |
| uint32_t irq_data; |
| |
| payload = container_of(work, struct cam_cpas_work_payload, work); |
| if (!payload) { |
| CAM_ERR(CAM_CPAS, "NULL payload"); |
| return; |
| } |
| |
| cpas_hw = payload->hw; |
| cpas_core = (struct cam_cpas *) cpas_hw->core_info; |
| soc_info = &cpas_hw->soc_info; |
| |
| for (i = 0; i < camnoc_info->irq_err_size; i++) { |
| if ((payload->irq_status & camnoc_info->irq_err[i].sbm_port) && |
| (camnoc_info->irq_err[i].enable)) { |
| irq_type = camnoc_info->irq_err[i].irq_type; |
| CAM_ERR(CAM_CPAS, "Error occurred, type=%d", irq_type); |
| irq_data = 0; |
| |
| switch (irq_type) { |
| case CAM_CAMNOC_HW_IRQ_SLAVE_ERROR: |
| irq_data = cam_cpastop_handle_errlogger( |
| cpas_core, soc_info); |
| break; |
| case CAM_CAMNOC_HW_IRQ_IFE02_UBWC_ENCODE_ERROR: |
| case CAM_CAMNOC_HW_IRQ_IFE13_UBWC_ENCODE_ERROR: |
| case CAM_CAMNOC_HW_IRQ_IPE_BPS_UBWC_DECODE_ERROR: |
| case CAM_CAMNOC_HW_IRQ_IPE_BPS_UBWC_ENCODE_ERROR: |
| irq_data = cam_cpastop_handle_ubwc_err( |
| cpas_core, soc_info, i); |
| break; |
| case CAM_CAMNOC_HW_IRQ_AHB_TIMEOUT: |
| irq_data = cam_cpastop_handle_ahb_timeout_err( |
| cpas_hw); |
| break; |
| case CAM_CAMNOC_HW_IRQ_CAMNOC_TEST: |
| CAM_DBG(CAM_CPAS, "TEST IRQ"); |
| break; |
| default: |
| CAM_ERR(CAM_CPAS, "Invalid IRQ type"); |
| break; |
| } |
| |
| cam_cpastop_notify_clients(cpas_core, irq_type, |
| irq_data); |
| |
| payload->irq_status &= |
| ~camnoc_info->irq_err[i].sbm_port; |
| } |
| } |
| |
| if (payload->irq_status) |
| CAM_ERR(CAM_CPAS, "IRQ not handled irq_status=0x%x", |
| payload->irq_status); |
| |
| kfree(payload); |
| } |
| |
| static irqreturn_t cam_cpastop_handle_irq(int irq_num, void *data) |
| { |
| struct cam_hw_info *cpas_hw = (struct cam_hw_info *)data; |
| struct cam_cpas *cpas_core = (struct cam_cpas *) cpas_hw->core_info; |
| struct cam_hw_soc_info *soc_info = &cpas_hw->soc_info; |
| int camnoc_index = cpas_core->regbase_index[CAM_CPAS_REG_CAMNOC]; |
| struct cam_cpas_work_payload *payload; |
| |
| payload = kzalloc(sizeof(struct cam_cpas_work_payload), GFP_ATOMIC); |
| if (!payload) |
| return IRQ_HANDLED; |
| |
| payload->irq_status = cam_io_r_mb( |
| soc_info->reg_map[camnoc_index].mem_base + |
| camnoc_info->irq_sbm->sbm_status.offset); |
| |
| CAM_DBG(CAM_CPAS, "IRQ callback, irq_status=0x%x", payload->irq_status); |
| |
| payload->hw = cpas_hw; |
| INIT_WORK((struct work_struct *)&payload->work, cam_cpastop_work); |
| |
| if (TEST_IRQ_ENABLE) |
| cam_cpastop_disable_test_irq(cpas_hw); |
| |
| cam_cpastop_reset_irq(cpas_hw); |
| |
| queue_work(cpas_core->work_queue, &payload->work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int cam_cpastop_poweron(struct cam_hw_info *cpas_hw) |
| { |
| int i; |
| |
| cam_cpastop_reset_irq(cpas_hw); |
| |
| for (i = 0; i < camnoc_info->specific_size; i++) { |
| if (camnoc_info->specific[i].enable) { |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].priority_lut_low); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].priority_lut_high); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].urgency); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].danger_lut); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].safe_lut); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].ubwc_ctl); |
| cam_cpas_util_reg_update(cpas_hw, CAM_CPAS_REG_CAMNOC, |
| &camnoc_info->specific[i].flag_out_set0_low); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int cam_cpastop_poweroff(struct cam_hw_info *cpas_hw) |
| { |
| struct cam_cpas *cpas_core = (struct cam_cpas *) cpas_hw->core_info; |
| struct cam_hw_soc_info *soc_info = &cpas_hw->soc_info; |
| int camnoc_index = cpas_core->regbase_index[CAM_CPAS_REG_CAMNOC]; |
| int rc = 0; |
| struct cam_cpas_hw_errata_wa_list *errata_wa_list = |
| camnoc_info->errata_wa_list; |
| |
| if (!errata_wa_list) |
| return 0; |
| |
| if (errata_wa_list->camnoc_flush_slave_pending_trans.enable) { |
| struct cam_cpas_hw_errata_wa *errata_wa = |
| &errata_wa_list->camnoc_flush_slave_pending_trans; |
| |
| rc = cam_io_poll_value_wmask( |
| soc_info->reg_map[camnoc_index].mem_base + |
| errata_wa->data.reg_info.offset, |
| errata_wa->data.reg_info.value, |
| errata_wa->data.reg_info.mask, |
| CAM_CPAS_POLL_RETRY_CNT, |
| CAM_CPAS_POLL_MIN_USECS, CAM_CPAS_POLL_MAX_USECS); |
| if (rc) { |
| CAM_DBG(CAM_CPAS, |
| "camnoc flush slave pending trans failed"); |
| /* Do not return error, passthrough */ |
| rc = 0; |
| } |
| } |
| |
| return rc; |
| } |
| |
| static int cam_cpastop_init_hw_version(struct cam_hw_info *cpas_hw, |
| struct cam_cpas_hw_caps *hw_caps) |
| { |
| if ((hw_caps->camera_version.major == 1) && |
| (hw_caps->camera_version.minor == 7) && |
| (hw_caps->camera_version.incr == 0)) { |
| if ((hw_caps->cpas_version.major == 1) && |
| (hw_caps->cpas_version.minor == 0) && |
| (hw_caps->cpas_version.incr == 0)) { |
| camnoc_info = &cam170_cpas100_camnoc_info; |
| } else if ((hw_caps->cpas_version.major == 1) && |
| (hw_caps->cpas_version.minor == 1) && |
| (hw_caps->cpas_version.incr == 0)) { |
| camnoc_info = &cam170_cpas110_camnoc_info; |
| } else { |
| CAM_ERR(CAM_CPAS, "CPAS Version not supported %d.%d.%d", |
| hw_caps->cpas_version.major, |
| hw_caps->cpas_version.minor, |
| hw_caps->cpas_version.incr); |
| return -EINVAL; |
| } |
| } else { |
| CAM_ERR(CAM_CPAS, "Camera Version not supported %d.%d.%d", |
| hw_caps->camera_version.major, |
| hw_caps->camera_version.minor, |
| hw_caps->camera_version.incr); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int cam_cpastop_get_internal_ops(struct cam_cpas_internal_ops *internal_ops) |
| { |
| if (!internal_ops) { |
| CAM_ERR(CAM_CPAS, "invalid NULL param"); |
| return -EINVAL; |
| } |
| |
| internal_ops->get_hw_info = cam_cpastop_get_hw_info; |
| internal_ops->init_hw_version = cam_cpastop_init_hw_version; |
| internal_ops->handle_irq = cam_cpastop_handle_irq; |
| internal_ops->setup_regbase = cam_cpastop_setup_regbase_indices; |
| internal_ops->power_on = cam_cpastop_poweron; |
| internal_ops->power_off = cam_cpastop_poweroff; |
| |
| return 0; |
| } |