/* * Copyright (c) Hisilicon Technologies Co., Ltd. 2014-2021. All rights reserved. * Description: ci hal func impl. * Author: Hisilicon * Create: 2014-08-02 */ #include "soc_log.h" #include "drv_gpio_ext.h" #include "hal_ci.h" #include "hal_ci_reg.h" #include "osal_ext.h" #include "soc_errno.h" #include "drv_ci.h" #include "linux/huanglong/securec.h" #undef LOG_MODULE_ID #define LOG_MODULE_ID SOC_ID_CI #define CI_TIME_10MS 10 /* 10ms */ #define CI_PCCD_READY_COUNT 5 #define CI_PCCD_DETECT_MAX 5 #define CI_PCCD_DETECT_MIN 3 #define CI_PCCD_RESUME_ABSENT_COUNT 10 #define DAIE 0x80 #define FRIE 0x40 #define COM_STAT_REG 1 typedef enum { CMD_TYPE_IOW = 0, CMD_TYPE_IOR, CMD_TYPE_MW, CMD_TYPE_MR, } cmd_type_list; typedef enum { EXT_CI_PCCD_RUN_STEP_RESUME, EXT_CI_PCCD_RUN_STEP_STANDBY, EXT_CI_PCCD_RUN_STEP_RUNNING, EXT_CI_PCCD_RUN_STEP_CRAD_BUSY, EXT_CI_PCCD_RUN_STEP_BUIT, } ext_ci_pccd_run_step; #define write_reg(addr, value) osal_writel((value), (addr)) #define read_reg(addr) osal_readl(addr) #define ci_write_reg(offset, value) write_reg(g_ci_reg_base + (offset), value) #define ci_read_reg(offset) read_reg(g_ci_reg_base + (offset)) #define crg_write_reg(offset, value) write_reg(g_crg_reg_base + (offset), value) #define crg_read_reg(offset) read_reg(g_crg_reg_base + (offset)) #define peri_write_reg(offset, value) write_reg(g_peri_reg_base + (offset), value) #define peri_read_reg(offset) read_reg(g_peri_reg_base + (offset)) #define EXT_CI_CHECK_PCCD_VALID(card) do { \ if ((card) >= EXT_CI_PCCD_MAX) { \ soc_log_err("invalid card id:%d.\n", card); \ return SOC_ERR_CI_INVALID_PARA; \ } \ } while (0) typedef struct { td_bool is_power_ctrl_gpio_used; td_u32 power_ctrl_gpio_no[EXT_CI_PCCD_MAX]; td_bool ts_by_pass[EXT_CI_PCCD_MAX]; ext_ci_pccd_run_step run_step[EXT_CI_PCCD_MAX]; ext_ci_pccd_status status[EXT_CI_PCCD_MAX]; td_u32 resume_absent_cnt[EXT_CI_PCCD_MAX]; } ci_param; static td_void *g_ci_reg_base = TD_NULL; static td_void *g_crg_reg_base = TD_NULL; static td_void *g_peri_reg_base = TD_NULL; static gpio_ext_func* g_gpio_func = TD_NULL; static ci_param g_ci_param[EXT_CI_PORT_MAX] = {{0}}; td_s32 ci_wait_done(td_u8 *data) { td_u32 time_out = 20000; cmd0_rdata rdata; if (data == TD_NULL) { soc_err_print_err_code(SOC_ERR_CI_INVALID_PARA); return SOC_ERR_CI_INVALID_PARA; } *data = 0x00; rdata.u32 = ci_read_reg(CMD0_RDATA); while (time_out > 0) { rdata.u32 = ci_read_reg(CMD0_RDATA); if (rdata.bits.done == 1) { break; } time_out--; } if (time_out == 0) { soc_log_err("wait data read timeout.\n"); return SOC_ERR_CI_TIMEOUT; } *data = rdata.bits.rdata; return TD_SUCCESS; } td_s32 ci_base_addr_remap(td_void) { td_u32 ci_phy_base_addr = 0; td_s32 ret; ret = osal_dts_get_u32_byname("huanglong,ci", "ci_phy_base_addr", &ci_phy_base_addr); if (ret < 0) { soc_log_err("CI get ci_phy_base_addr failed, ret[0x%x]\n", ret); return TD_FAILURE; } g_ci_reg_base = (td_void *)osal_ioremap_nocache(ci_phy_base_addr, 0x1000); if (g_ci_reg_base == TD_NULL) { soc_log_err("CI reg base ioremap_nocache failed.\n"); return TD_FAILURE; } g_crg_reg_base = (td_void *)osal_ioremap_nocache(PERI_CRG_BASE, 0x1000); if (g_crg_reg_base == TD_NULL) { soc_log_err("CRG base ioremap_nocache failed.\n"); osal_iounmap(g_ci_reg_base, OSAL_IOUNMAP_SIZE); return TD_FAILURE; } g_peri_reg_base = (td_void *)osal_ioremap_nocache(PERI_CTRL_BASE, 0x1000); if (g_peri_reg_base == TD_NULL) { soc_log_err("CRG base ioremap_nocache failed.\n"); osal_iounmap(g_ci_reg_base, OSAL_IOUNMAP_SIZE); osal_iounmap(g_crg_reg_base, OSAL_IOUNMAP_SIZE); return TD_FAILURE; } return TD_SUCCESS; } td_void ci_base_addr_unmap(td_void) { if (g_ci_reg_base != TD_NULL) { osal_iounmap(g_ci_reg_base, OSAL_IOUNMAP_SIZE); g_ci_reg_base = TD_NULL; } if (g_crg_reg_base != TD_NULL) { osal_iounmap(g_crg_reg_base, OSAL_IOUNMAP_SIZE); g_crg_reg_base = TD_NULL; } if (g_peri_reg_base != TD_NULL) { osal_iounmap(g_peri_reg_base, OSAL_IOUNMAP_SIZE); g_peri_reg_base = TD_NULL; } } td_s32 ci_init(td_void) { td_s32 ret; peri_crg98 crg98; ci_inf_set inf_set; cmd_cfg cfg; slave_mode mode; crg98.u32 = crg_read_reg(PERI_CRG98); /* open clcok */ crg98.bits.ci_cken = 1; crg_write_reg(PERI_CRG98, crg98.u32); osal_msleep(1); /* assert reset */ crg98.bits.ci_srst_req = 1; crg_write_reg(PERI_CRG98, crg98.u32); osal_msleep(1); /* remove reset */ crg98.bits.ci_srst_req = 0; crg_write_reg(PERI_CRG98, crg98.u32); osal_msleep(1); /* enable pin output */ inf_set.u32 = ci_read_reg(CI_INF_SET); inf_set.bits.sw_com_oen = 0; inf_set.bits.sw_card2_com_oen = 0; inf_set.bits.sw_rst_oen = 0; inf_set.bits.sw_card2_rst_oen = 0; ci_write_reg(CI_INF_SET, inf_set.u32); /* check ready single but don't check inpackn single */ cfg.u32 = ci_read_reg(CMD_CFG); cfg.bits.inpackn_check = 0; cfg.bits.rdy_check = 1; ci_write_reg(CMD_CFG, cfg.u32); /* indirect mode */ mode.u32 = ci_read_reg(SLAVE_MODE); mode.bits.slave_mode = 0; ci_write_reg(SLAVE_MODE, mode.u32); ret = hal_ci_pccd_ts_by_pass(EXT_CI_PORT_0, EXT_CI_PCCD_A, TD_TRUE); if (ret != TD_SUCCESS) { soc_err_print_call_fun_err(hal_ci_pccd_ts_by_pass, ret); return ret; } return TD_SUCCESS; } td_s32 hal_ci_init(td_void) { td_s32 ret; td_u32 i; ret = ci_base_addr_remap(); if (ret != TD_SUCCESS) { soc_err_print_call_fun_err(ci_base_addr_remap, ret); return ret; } ret = ci_init(); if (ret != TD_SUCCESS) { soc_err_print_call_fun_err(ci_init, ret); return ret; } g_gpio_func = TD_NULL; ret = osal_exportfunc_get(SOC_ID_GPIO, (td_void **)&g_gpio_func); if (ret != TD_SUCCESS) { soc_err_print_call_fun_err(osal_exportfunc_get, ret); return ret; } ret = memset_s(&g_ci_param, sizeof(g_ci_param), 0x00, sizeof(g_ci_param)); if (ret != EOK) { return ret; } for (i = 0; i < EXT_CI_PCCD_MAX; i++) { g_ci_param[EXT_CI_PORT_0].run_step[i] = EXT_CI_PCCD_RUN_STEP_RUNNING; g_ci_param[EXT_CI_PORT_0].ts_by_pass[i] = TD_TRUE; g_ci_param[EXT_CI_PORT_0].status[i] = EXT_CI_PCCD_STATUS_MAX; g_ci_param[EXT_CI_PORT_0].is_power_ctrl_gpio_used = TD_FALSE; } return TD_SUCCESS; } td_void hal_ci_deinit(td_void) { peri_crg98 crg98; crg98.u32 = crg_read_reg(PERI_CRG98); /* assert reset */ crg98.bits.ci_srst_req = 1; crg_write_reg(PERI_CRG98, crg98.u32); osal_msleep(1); /* close clcok */ crg98.bits.ci_cken = 0; crg_write_reg(PERI_CRG98, crg98.u32); osal_msleep(1); ci_base_addr_unmap(); return; } td_s32 hal_ci_device_open(ext_ci_port port) { if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } return TD_SUCCESS; } td_s32 hal_ci_device_close(ext_ci_port port) { if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } return TD_SUCCESS; } td_s32 hal_ci_set_attr(ext_ci_port port, ext_ci_attr attr) { if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } g_ci_param[port].is_power_ctrl_gpio_used = attr.dev_attr.extci.is_power_ctrl_gpio_used; g_ci_param[port].power_ctrl_gpio_no[EXT_CI_PCCD_A] = attr.dev_attr.extci.power_ctrl_gpio_no[EXT_CI_PCCD_A]; g_ci_param[port].power_ctrl_gpio_no[EXT_CI_PCCD_B] = attr.dev_attr.extci.power_ctrl_gpio_no[EXT_CI_PCCD_B]; return TD_SUCCESS; } td_s32 hal_ci_pccd_open(ext_ci_port port, ext_ci_pccd card) { if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } return TD_SUCCESS; } td_void hal_ci_pccd_close(ext_ci_port port, ext_ci_pccd card) { } static td_void hal_pccd_select(ext_ci_pccd card) { ci_inf_set inf_set; inf_set.u32 = ci_read_reg(CI_INF_SET); inf_set.bits.card2_sel = card; ci_write_reg(CI_INF_SET, inf_set.u32); } static td_s32 ci_pccd_byte_rw(ext_ci_port port, ext_ci_pccd card, cmd_type_list type, td_u32 address, td_u8 *value) { td_s32 ret; cmd0_set cmd; td_u8 out = 0; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); if (g_ci_param[port].run_step[card] == EXT_CI_PCCD_RUN_STEP_RESUME) { soc_log_err("Resume from standby, will re-insert CAM soon.\n"); return TD_FAILURE; } if (g_ci_param[port].run_step[card] == EXT_CI_PCCD_RUN_STEP_CRAD_BUSY) { /* card is busy means that card may be dead, * to avoid keep printing error messages, * here just return 0 with TD_SUCCESS. */ *value = 0x00; return TD_SUCCESS; } hal_pccd_select(card); cmd.u32 = ci_read_reg(CMD0_SET); cmd.bits.cmd_type = type; cmd.bits.addr = address; cmd.bits.wdata = *value; ci_write_reg(CMD0_SET, cmd.u32); /* wait done */ ret = ci_wait_done(&out); if (ret != TD_SUCCESS) { g_ci_param[port].run_step[card] = EXT_CI_PCCD_RUN_STEP_CRAD_BUSY; soc_err_print_call_fun_err(ci_wait_done, ret); return ret; } *value = out; return TD_SUCCESS; } td_s32 hal_ci_pccd_io_read_byte(ext_ci_port port, ext_ci_pccd card, td_u32 address, td_u8 *value) { return ci_pccd_byte_rw(port, card, CMD_TYPE_IOR, address, value); } td_s32 hal_ci_pccd_io_write_byte(ext_ci_port port, ext_ci_pccd card, td_u32 address, td_u8 value) { if (address == COM_STAT_REG) { value |= (DAIE | FRIE); } return ci_pccd_byte_rw(port, card, CMD_TYPE_IOW, address, &value); } td_s32 hal_ci_pccd_mem_read_byte(ext_ci_port port, ext_ci_pccd card, td_u32 address, td_u8 *value) { return ci_pccd_byte_rw(port, card, CMD_TYPE_MR, address, value); } td_s32 hal_ci_pccd_mem_write_byte(ext_ci_port port, ext_ci_pccd card, td_u32 address, td_u8 value) { return ci_pccd_byte_rw(port, card, CMD_TYPE_MW, address, &value); } td_u32 ci_pccd0_detect_once(td_u32 cd2_dis) { dbg_in_sig in_sig; in_sig.u32 = ci_read_reg(DBG_IN_SIG); /* check cd1, 0: card present */ if (in_sig.bits.cd1 == 0x01) { /* card0 absent */ return 0; } if (cd2_dis == 1) { /* disregard cd2, direct return present when cd1 = 0 */ return 1; } if (in_sig.bits.cd2 == 0x00) { /* card0 present */ return 1; } /* card0 absent */ return 0; } td_u32 ci_pccd1_detect_once(td_u32 cd2_dis) { ci_inf_set inf_set; /* check cd1, 0: card present */ inf_set.u32 = ci_read_reg(CI_INF_SET); if (inf_set.bits.card2_cd1_n == 0x01) { /* card1 absent */ return 0; } if (cd2_dis == 1) { /* disregard cd2, direct return present when cd1 = 0 */ return 1; } if (inf_set.bits.card2_cd2_n == 0x00) { /* card1 present */ return 1; } /* card1 absent */ return 0; } td_s32 hal_ci_pccd_detect(ext_ci_port port, ext_ci_pccd card, ext_ci_pccd_status* status) { td_u32 try_count; td_u32 present_count = 0; ci_inf_cmd_mode cmd_mode; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); if (status == TD_NULL) { soc_err_print_err_code(SOC_ERR_CI_INVALID_PARA); return SOC_ERR_CI_INVALID_PARA; } if (g_ci_param[port].run_step[card] == EXT_CI_PCCD_RUN_STEP_RESUME) { /* assert card absent after resume to force a higher level CAM initialization */ *status = EXT_CI_PCCD_STATUS_ABSENT; if (g_ci_param[port].resume_absent_cnt[card] == 0) { g_ci_param[port].run_step[card] = EXT_CI_PCCD_RUN_STEP_RUNNING; } else { g_ci_param[port].resume_absent_cnt[card] -= 1; } return TD_SUCCESS; } cmd_mode.u32 = ci_read_reg(CI_INF_CMD_MODE); for (try_count = 0; try_count < CI_PCCD_DETECT_MAX; try_count++) { if (card == EXT_CI_PCCD_A) { present_count += ci_pccd0_detect_once(cmd_mode.bits.cd2_dis); } else { present_count += ci_pccd1_detect_once(cmd_mode.bits.cd2_dis); } osal_msleep(CI_TIME_10MS); } if (present_count >= CI_PCCD_DETECT_MIN) { *status = EXT_CI_PCCD_STATUS_PRESENT; } else { *status = EXT_CI_PCCD_STATUS_ABSENT; } return TD_SUCCESS; } td_s32 hal_ci_pccd_ready_or_busy(ext_ci_port port, ext_ci_pccd card, ext_ci_pccd_ready* ready) { td_u32 elapsed_time; dbg_in_sig in_sig; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); if (ready == TD_NULL) { soc_err_print_err_code(SOC_ERR_CI_INVALID_PARA); return SOC_ERR_CI_INVALID_PARA; } *ready = EXT_CI_PCCD_BUSY; /* the host shall explicitly check for the READY signal until it is set by the * module or until a timeout of 5s has expired. */ for (elapsed_time = 0; elapsed_time < CI_PCCD_READY_COUNT; elapsed_time++) { in_sig.u32 = ci_read_reg(DBG_IN_SIG); if (card == EXT_CI_PCCD_A) { if (in_sig.bits.rdy == 1) { *ready = EXT_CI_PCCD_READY; break; } } else { if (in_sig.bits.card2_ready == 1) { *ready = EXT_CI_PCCD_READY; break; } } osal_msleep(CI_TIME_10MS); } return TD_SUCCESS; } td_s32 hal_ci_pccd_reset(ext_ci_port port, ext_ci_pccd card) { ci_inf_set inf_set; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); inf_set.u32 = ci_read_reg(CI_INF_SET); if (card == EXT_CI_PCCD_A) { inf_set.bits.set_ci_reset = 1; ci_write_reg(CI_INF_SET, inf_set.u32); osal_msleep(CI_TIME_10MS); inf_set.bits.set_ci_reset = 0; ci_write_reg(CI_INF_SET, inf_set.u32); osal_msleep(CI_TIME_10MS); } else { inf_set.bits.set_card2_reset = 1; ci_write_reg(CI_INF_SET, inf_set.u32); osal_msleep(CI_TIME_10MS); inf_set.bits.set_card2_reset = 0; ci_write_reg(CI_INF_SET, inf_set.u32); osal_msleep(CI_TIME_10MS); } /* enter running state after reset */ g_ci_param[EXT_CI_PORT_0].run_step[card] = EXT_CI_PCCD_RUN_STEP_RUNNING; soc_log_info("reset CAM OK!\n"); return TD_SUCCESS; } /* * power ON/OFF * notice: Current solution only support power control on CI port, but not for each PCCD. * so, if you call power off but some cards are present, will return SOC_ERR_CI_CANNOT_POWEROFF. */ td_s32 hal_ci_pccd_ctrl_power(ext_ci_port port, ext_ci_pccd card, ext_ci_pccd_ctrl_power ctrl_power) { td_s32 ret, ret_bak; td_u32 gpio_no; td_u32 gpio_val; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); if (g_ci_param[port].is_power_ctrl_gpio_used == TD_FALSE) { return TD_SUCCESS; } if ((g_gpio_func == TD_NULL) || (g_gpio_func->pfn_gpio_direction_set_bit == TD_NULL) || (g_gpio_func->pfn_gpio_write_bit == TD_NULL)) { soc_log_err("gpio func invalid.\n"); return TD_FAILURE; } gpio_no = g_ci_param[port].power_ctrl_gpio_no[card]; gpio_val = (ctrl_power == EXT_CI_PCCD_CTRLPOWER_ON ? 1 : 0); ret = g_gpio_func->pfn_gpio_direction_set_bit(gpio_no, 0); osal_msleep(1); ret_bak = g_gpio_func->pfn_gpio_write_bit(gpio_no, gpio_val); ret = (td_s32)((td_u32)ret | (td_u32)ret_bak); if (ret != TD_SUCCESS) { soc_log_err("power off device fail, GPIO.\n"); soc_err_print_h32(gpio_no); return ret; } osal_msleep(CI_TIME_10MS); soc_log_info("power card %d, gpio_no 0x%02x, gpio_val %d\n", card, gpio_no, gpio_val); return TD_SUCCESS; } td_s32 hal_ci_pccd_ts_by_pass(ext_ci_port port, ext_ci_pccd card, td_bool by_pass) { peri_io_oen io_oen; if (port != EXT_CI_PORT_0) { soc_log_err("only support EXT_CI_PORT_0 now.\n"); return SOC_ERR_CI_UNSUPPORT; } EXT_CI_CHECK_PCCD_VALID(card); io_oen.u32 = peri_read_reg(PERI_IO_OEN); if (card == EXT_CI_PCCD_A) { io_oen.bits.peri_tso_loop_sel = (by_pass == TD_TRUE) ? 1 : 0; peri_write_reg(PERI_IO_OEN, io_oen.u32); } else if (card == EXT_CI_PCCD_B) { /* * tuner->demod->card_b->TSI1->demux->decode * card B direct link to demod chip, so can't bypass the TS. */ soc_log_err("card_id == EXT_CI_PCCD_B\n"); } else { soc_log_err("only support card A and card B.\n"); return SOC_ERR_CI_UNSUPPORT; } g_ci_param[port].ts_by_pass[card] = by_pass; return TD_SUCCESS; } td_s32 hal_ci_pccd_get_bypass_mode(ext_ci_port port, ext_ci_pccd card, td_bool *bypass) { peri_io_oen io_oen; if (bypass == TD_NULL) { soc_err_print_err_code(SOC_ERR_CI_INVALID_PARA); return SOC_ERR_CI_INVALID_PARA; } if (card == EXT_CI_PCCD_A) { io_oen.u32 = peri_read_reg(PERI_IO_OEN); *bypass = (io_oen.bits.peri_tso_loop_sel == 1) ? TD_TRUE : TD_FALSE; } else { *bypass = TD_FALSE; } return TD_SUCCESS; } /* low power */ td_s32 hal_ci_standby(ext_ci_port port) { td_s32 ret; ext_ci_pccd card = 0; if (port != EXT_CI_PORT_0) { return TD_SUCCESS; } for (card = EXT_CI_PCCD_A; card < EXT_CI_PCCD_MAX; card++) { g_ci_param[port].run_step[card] = EXT_CI_PCCD_RUN_STEP_STANDBY; ret = hal_ci_pccd_ctrl_power(port, card, EXT_CI_PCCD_CTRLPOWER_OFF); if (ret != TD_SUCCESS) { return ret; } } return TD_SUCCESS; } /* resume CI */ td_s32 hal_ci_resume(ext_ci_port port) { td_s32 ret; ext_ci_pccd card = 0; if (port != EXT_CI_PORT_0) { return TD_SUCCESS; } ret = ci_init(); if (ret != TD_SUCCESS) { return ret; } for (card = EXT_CI_PCCD_A; card < EXT_CI_PCCD_MAX; card++) { /* during standby CAM may be always power on, * but host CI is power down, * so the CAM maybe died as not receive any respond from host * we need to reset CAM after resume. */ ret = hal_ci_pccd_reset(port, card); if (ret != TD_SUCCESS) { return ret; } g_ci_param[port].run_step[card] = EXT_CI_PCCD_RUN_STEP_RESUME; g_ci_param[port].resume_absent_cnt[card] = CI_PCCD_RESUME_ABSENT_COUNT; } return TD_SUCCESS; }