| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 MediaTek Inc. |
| */ |
| |
| #include <linux/arm-smccc.h> |
| #include <linux/clk.h> |
| #include <linux/fs.h> |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_address.h> |
| #include <linux/proc_fs.h> |
| #include <linux/sched/debug.h> |
| #include <linux/uaccess.h> |
| #include <linux/soc/mediatek/mtk_sip_svc.h> |
| #include <linux/soc/mediatek/devapc_public.h> |
| #ifdef CONFIG_MTK_SERROR_HOOK |
| #include <trace/hooks/traps.h> |
| #endif |
| #include <../drivers/misc/mediatek/include/mt-plat/aee.h> |
| #include "devapc-mtk-multi-ao.h" |
| |
| static struct mtk_devapc_context { |
| struct clk *devapc_infra_clk; |
| uint32_t devapc_irq[IRQ_TYPE_NUM_MAX]; |
| int current_irq_type; |
| bool serror; |
| |
| /* HW reg mapped addr */ |
| void __iomem *devapc_pd_base[SLAVE_TYPE_NUM_MAX]; |
| void __iomem *devapc_ao_base[SLAVE_TYPE_NUM_MAX]; |
| void __iomem *infracfg_base; |
| void __iomem *sramrom_base; |
| |
| struct mtk_devapc_soc *soc; |
| struct mutex viocb_list_lock; |
| struct mtk_devapc_pd_reg pd_reg[SLAVE_TYPE_NUM_MAX]; |
| |
| unsigned long subsys_enabled[SLAVE_TYPE_NUM_MAX]; |
| } mtk_devapc_ctx[1]; |
| |
| static LIST_HEAD(viocb_list); |
| static LIST_HEAD(powercb_list); |
| static DEFINE_SPINLOCK(devapc_lock); |
| |
| static void devapc_test_cb(void) |
| { |
| pr_info(PFX "%s success !\n", __func__); |
| } |
| |
| static enum devapc_cb_status devapc_test_adv_cb(uint32_t vio_addr) |
| { |
| pr_info(PFX "%s success !\n", __func__); |
| pr_info(PFX "vio_addr: 0x%x\n", vio_addr); |
| |
| return DEVAPC_NOT_KE; |
| } |
| |
| static struct devapc_vio_callbacks devapc_test_handle = { |
| .id = DEVAPC_SUBSYS_TEST, |
| .debug_dump = devapc_test_cb, |
| .debug_dump_adv = devapc_test_adv_cb, |
| }; |
| |
| static bool is_matched_devapc_type(int slave_type) |
| { |
| const struct mtk_device_num *ndevices = mtk_devapc_ctx->soc->ndevices; |
| int current_irq_type = mtk_devapc_ctx->current_irq_type; |
| bool ret = false; |
| |
| if (ndevices[slave_type].irq_type == current_irq_type) |
| ret = true; |
| |
| return ret; |
| } |
| |
| static void query_devapc_subsys_status(int slave_type) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(MTK_SIP_KERNEL_DAPC_SUBSYS_GET, |
| slave_type, 0, 0, 0, 0, 0, 0, &res); |
| mtk_devapc_ctx->subsys_enabled[slave_type] = res.a0; |
| } |
| |
| static bool is_devapc_subsys_enabled(int slave_type) |
| { |
| return mtk_devapc_ctx->subsys_enabled[slave_type]; |
| } |
| |
| static bool is_devapc_subsys_power_on(int slave_type) |
| { |
| struct devapc_power_callbacks *powercb; |
| |
| if (slave_type != DEVAPC_TYPE_ADSP && |
| slave_type != DEVAPC_TYPE_MMINFRA && |
| slave_type != DEVAPC_TYPE_MMUP) { |
| return true; |
| } |
| |
| list_for_each_entry(powercb, &powercb_list, list) { |
| if (slave_type == DEVAPC_TYPE_ADSP) { |
| if (powercb->type == DEVAPC_TYPE_ADSP && powercb->query_power) |
| return powercb->query_power() && |
| is_devapc_subsys_enabled(slave_type); |
| else |
| return false; |
| } |
| |
| if (slave_type == DEVAPC_TYPE_MMINFRA) { |
| if (powercb->type == DEVAPC_TYPE_MMINFRA && powercb->query_power) |
| return powercb->query_power() && |
| is_devapc_subsys_enabled(slave_type); |
| else |
| return false; |
| } |
| |
| if (slave_type == DEVAPC_TYPE_MMUP) { |
| if (powercb->type == DEVAPC_TYPE_MMUP && powercb->query_power) |
| return powercb->query_power() && |
| is_devapc_subsys_enabled(slave_type); |
| else |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * mtk_devapc_pd_get - get devapc pd_types of register address. |
| * |
| * Returns the value of reg addr |
| */ |
| static void __iomem *mtk_devapc_pd_get(int slave_type, |
| enum DEVAPC_PD_REG_TYPE pd_reg_type, |
| uint32_t index) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| const uint32_t *devapc_pds = mtk_devapc_ctx->soc->devapc_pds; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| void __iomem *reg; |
| |
| if (unlikely(devapc_pds == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return NULL; |
| } |
| |
| if ((slave_type < slave_type_num && |
| index < vio_info->vio_mask_sta_num[slave_type]) && |
| (pd_reg_type < PD_REG_TYPE_NUM)) { |
| |
| reg = mtk_devapc_ctx->devapc_pd_base[slave_type] + |
| devapc_pds[pd_reg_type]; |
| |
| if (pd_reg_type == VIO_MASK || pd_reg_type == VIO_STA) |
| reg += 0x4 * index; |
| |
| } else { |
| pr_err(PFX "%s:0x%x or %s:0x%x or %s:0x%x is out of boundary\n", |
| "slave_type", slave_type, |
| "pd_reg_type", pd_reg_type, |
| "index", index); |
| return NULL; |
| } |
| |
| return reg; |
| } |
| |
| /* |
| * sramrom_vio_handler - clean sramrom violation & print violation information |
| * for debugging. |
| */ |
| static void sramrom_vio_handler(void) |
| { |
| const struct mtk_sramrom_sec_vio_desc *sramrom_vios; |
| struct mtk_devapc_vio_info *vio_info; |
| struct arm_smccc_res res; |
| size_t sramrom_vio_sta; |
| int sramrom_vio; |
| uint32_t rw; |
| uint32_t sramrom_vio_id; |
| |
| sramrom_vios = mtk_devapc_ctx->soc->sramrom_sec_vios; |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| |
| arm_smccc_smc(MTK_SIP_KERNEL_CLR_SRAMROM_VIO, |
| 0, 0, 0, 0, 0, 0, 0, &res); |
| |
| sramrom_vio = res.a0; |
| sramrom_vio_sta = res.a1; |
| vio_info->vio_addr = res.a2; |
| sramrom_vio_id = res.a3; |
| |
| if (sramrom_vio == SRAM_VIOLATION) |
| pr_info(PFX "%s, SRAM violation is triggered\n", __func__); |
| else if (sramrom_vio == ROM_VIOLATION) |
| pr_info(PFX "%s, ROM violation is triggered\n", __func__); |
| else { |
| pr_info(PFX "SRAMROM violation is not triggered\n"); |
| return; |
| } |
| |
| if (sramrom_vio_id) |
| vio_info->master_id = sramrom_vio_id; |
| else |
| vio_info->master_id = (sramrom_vio_sta & sramrom_vios->vio_id_mask) |
| >> sramrom_vios->vio_id_shift; |
| vio_info->domain_id = (sramrom_vio_sta & sramrom_vios->vio_domain_mask) |
| >> sramrom_vios->vio_domain_shift; |
| rw = (sramrom_vio_sta & sramrom_vios->vio_rw_mask) >> |
| sramrom_vios->vio_rw_shift; |
| |
| if (rw) |
| vio_info->write = 1; |
| else |
| vio_info->read = 1; |
| |
| pr_info(PFX "%s: %s:0x%x, %s:0x%x, %s:%s, %s:0x%x, %s:0x%x\n", |
| __func__, "master_id", vio_info->master_id, |
| "domain_id", vio_info->domain_id, |
| "rw", rw ? "Write" : "Read", |
| "vio_addr", vio_info->vio_addr, |
| "vio_id", sramrom_vio_id); |
| } |
| |
| static void mask_module_irq(int slave_type, uint32_t module, bool mask) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| uint32_t apc_register_index; |
| uint32_t apc_set_index; |
| void __iomem *reg; |
| |
| apc_register_index = module / (MOD_NO_IN_1_DEVAPC * 2); |
| apc_set_index = module % (MOD_NO_IN_1_DEVAPC * 2); |
| |
| if ((slave_type < slave_type_num) && |
| (apc_register_index < vio_info->vio_mask_sta_num[slave_type])) { |
| |
| reg = mtk_devapc_pd_get(slave_type, VIO_MASK, |
| apc_register_index); |
| |
| if (mask) |
| writel(readl(reg) | (1 << apc_set_index), reg); |
| else |
| writel(readl(reg) & (~(1 << apc_set_index)), reg); |
| |
| } else |
| pr_err(PFX "%s: %s, %s:0x%x, %s:0x%x, %s:%s\n", |
| __func__, "out of boundary", |
| "slave_type", slave_type, |
| "module_index", module, |
| "mask", mask ? "true" : "false"); |
| } |
| |
| static int32_t check_vio_status(int slave_type, uint32_t module) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| uint32_t apc_register_index; |
| uint32_t apc_set_index; |
| void __iomem *reg; |
| |
| apc_register_index = module / (MOD_NO_IN_1_DEVAPC * 2); |
| apc_set_index = module % (MOD_NO_IN_1_DEVAPC * 2); |
| |
| if ((slave_type < slave_type_num) && |
| (apc_register_index < vio_info->vio_mask_sta_num[slave_type])) { |
| |
| reg = mtk_devapc_pd_get(slave_type, VIO_STA, |
| apc_register_index); |
| |
| } else { |
| pr_err(PFX "%s: %s, %s:0x%x, %s:0x%x\n", |
| __func__, "out of boundary", |
| "slave_type", slave_type, |
| "module_index", module); |
| return -EOVERFLOW; |
| } |
| |
| if (readl(reg) & (0x1 << apc_set_index)) |
| return VIOLATION_TRIGGERED; |
| else |
| return 0; |
| } |
| |
| static int32_t clear_vio_status(int slave_type, uint32_t module) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| uint32_t apc_register_index; |
| uint32_t apc_set_index; |
| void __iomem *reg; |
| |
| apc_register_index = module / (MOD_NO_IN_1_DEVAPC * 2); |
| apc_set_index = module % (MOD_NO_IN_1_DEVAPC * 2); |
| |
| if ((slave_type < slave_type_num) && |
| (apc_register_index < vio_info->vio_mask_sta_num[slave_type])) { |
| |
| reg = mtk_devapc_pd_get(slave_type, VIO_STA, |
| apc_register_index); |
| writel(0x1 << apc_set_index, reg); |
| |
| } else { |
| pr_err(PFX "%s: %s, %s:0x%x, %s:0x%x\n", |
| __func__, "out of boundary", |
| "slave_type", slave_type, |
| "module_index", module); |
| return -EOVERFLOW; |
| } |
| |
| if (check_vio_status(slave_type, module)) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| static const char *slave_type_to_string(uint32_t slave_type) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| const char * const *slave_type_arr; |
| |
| slave_type_arr = mtk_devapc_ctx->soc->slave_type_arr; |
| |
| if (slave_type < slave_type_num) |
| return slave_type_arr[slave_type]; |
| else |
| return slave_type_arr[slave_type_num]; |
| } |
| |
| static void print_vio_mask_sta(bool force) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| void __iomem *pd_vio_shift_sta_reg; |
| int slave_type, i; |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| /* Only dump the info of subsystem which got violation */ |
| if (!is_matched_devapc_type(slave_type) && !force) |
| continue; |
| |
| if (!is_devapc_subsys_power_on(slave_type)) |
| continue; |
| |
| pd_vio_shift_sta_reg = mtk_devapc_pd_get(slave_type, |
| VIO_SHIFT_STA, 0); |
| |
| pr_info(PFX "[%s] %s: 0x%x\n", |
| slave_type_to_string(slave_type), |
| "VIO_SHIFT_STA", |
| readl(pd_vio_shift_sta_reg) |
| ); |
| |
| for (i = 0; i < vio_info->vio_mask_sta_num[slave_type]; i++) { |
| pr_info(PFX "%s: %s_%d: 0x%x, %s_%d: 0x%x\n", |
| slave_type_to_string(slave_type), |
| "VIO_MASK", i, |
| readl(mtk_devapc_pd_get(slave_type, |
| VIO_MASK, i)), |
| "VIO_STA", i, |
| readl(mtk_devapc_pd_get(slave_type, |
| VIO_STA, i)) |
| ); |
| } |
| } |
| } |
| |
| static void devapc_vio_info_print(void) |
| { |
| struct mtk_devapc_vio_info *vio_info; |
| |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| |
| /* Print violation information */ |
| if (vio_info->write) |
| pr_info(PFX "Write Violation\n"); |
| else if (vio_info->read) |
| pr_info(PFX "Read Violation\n"); |
| else |
| pr_err(PFX "R/W Violation are not raised\n"); |
| |
| pr_info(PFX "%s%x, %s%x, %s%x, %s%x\n", |
| "Vio Addr:0x", vio_info->vio_addr, |
| "High:0x", vio_info->vio_addr_high, |
| "Bus ID:0x", vio_info->master_id, |
| "Dom ID:0x", vio_info->domain_id); |
| |
| pr_info(PFX "%s - %s%s, %s%x\n", |
| "Violation", |
| "Current Process:", current->comm, |
| "PID:", current->pid); |
| } |
| |
| static bool check_type2_vio_status(int slave_type, int *vio_idx, int *index) |
| { |
| uint32_t sramrom_vio_idx, mdp_vio_idx, disp2_vio_idx, mmsys_vio_idx; |
| const struct mtk_device_info **device_info; |
| const struct mtk_device_num *ndevices; |
| int sramrom_slv_type, mm2nd_slv_type; |
| bool mdp_vio, disp2_vio, mmsys_vio; |
| int i; |
| |
| sramrom_slv_type = mtk_devapc_ctx->soc->vio_info->sramrom_slv_type; |
| sramrom_vio_idx = mtk_devapc_ctx->soc->vio_info->sramrom_vio_idx; |
| |
| mm2nd_slv_type = mtk_devapc_ctx->soc->vio_info->mm2nd_slv_type; |
| mdp_vio_idx = mtk_devapc_ctx->soc->vio_info->mdp_vio_idx; |
| disp2_vio_idx = mtk_devapc_ctx->soc->vio_info->disp2_vio_idx; |
| mmsys_vio_idx = mtk_devapc_ctx->soc->vio_info->mmsys_vio_idx; |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| ndevices = mtk_devapc_ctx->soc->ndevices; |
| |
| /* check SRAMROM */ |
| if (slave_type == sramrom_slv_type && |
| check_vio_status(slave_type, sramrom_vio_idx)) { |
| |
| pr_info(PFX "SRAMROM violation is triggered\n"); |
| sramrom_vio_handler(); |
| |
| *vio_idx = sramrom_vio_idx; |
| for (i = 0; i < ndevices[slave_type].vio_slave_num; i++) { |
| if (device_info[slave_type][i].vio_index == *vio_idx) |
| *index = i; |
| } |
| |
| return true; |
| } |
| |
| /* check mm2nd */ |
| if (slave_type == mm2nd_slv_type) { |
| mdp_vio = check_vio_status(slave_type, mdp_vio_idx) == |
| VIOLATION_TRIGGERED; |
| disp2_vio = check_vio_status(slave_type, disp2_vio_idx) == |
| VIOLATION_TRIGGERED; |
| mmsys_vio = check_vio_status(slave_type, mmsys_vio_idx) == |
| VIOLATION_TRIGGERED; |
| |
| if (mdp_vio || disp2_vio || mmsys_vio) { |
| |
| pr_info(PFX "MM2nd violation is triggered\n"); |
| mtk_devapc_ctx->soc->mm2nd_vio_handler( |
| mtk_devapc_ctx->infracfg_base, |
| mtk_devapc_ctx->soc->vio_info, |
| mdp_vio, disp2_vio, mmsys_vio); |
| |
| if (mdp_vio) |
| *vio_idx = mdp_vio_idx; |
| else if (disp2_vio) |
| *vio_idx = disp2_vio_idx; |
| else if (mmsys_vio) |
| *vio_idx = mmsys_vio_idx; |
| |
| for (i = 0; i < ndevices[slave_type].vio_slave_num; |
| i++) { |
| if (device_info[slave_type][i].vio_index == |
| *vio_idx) |
| *index = i; |
| } |
| |
| devapc_vio_info_print(); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* |
| * sync_vio_dbg - start to get violation information by selecting violation |
| * group and enable violation shift. |
| * |
| * Returns sync done or not |
| */ |
| static uint32_t sync_vio_dbg(int slave_type, uint32_t shift_bit) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| void __iomem *pd_vio_shift_sta_reg; |
| void __iomem *pd_vio_shift_sel_reg; |
| void __iomem *pd_vio_shift_con_reg; |
| uint32_t shift_count; |
| uint32_t sync_done; |
| |
| if (slave_type >= slave_type_num || |
| shift_bit >= (MOD_NO_IN_1_DEVAPC * 2)) { |
| pr_err(PFX "param check failed, %s:0x%x, %s:0x%x\n", |
| "slave_type", slave_type, |
| "shift_bit", shift_bit); |
| return 0; |
| } |
| |
| pd_vio_shift_sta_reg = mtk_devapc_pd_get(slave_type, VIO_SHIFT_STA, 0); |
| pd_vio_shift_sel_reg = mtk_devapc_pd_get(slave_type, VIO_SHIFT_SEL, 0); |
| pd_vio_shift_con_reg = mtk_devapc_pd_get(slave_type, VIO_SHIFT_CON, 0); |
| |
| pr_debug(PFX "%s:0x%x %s:0x%x\n", |
| "slave_type", slave_type, |
| "VIO_SHIFT_STA", readl(pd_vio_shift_sta_reg)); |
| |
| writel(0x1 << shift_bit, pd_vio_shift_sel_reg); |
| writel(0x1, pd_vio_shift_con_reg); |
| |
| for (shift_count = 0; (shift_count < 100) && |
| ((readl(pd_vio_shift_con_reg) & 0x3) != 0x3); |
| ++shift_count) |
| ; |
| |
| if ((readl(pd_vio_shift_con_reg) & 0x3) == 0x3) |
| sync_done = 1; |
| else { |
| sync_done = 0; |
| pr_info(PFX "sync failed, shift_bit:0x%x\n", shift_bit); |
| } |
| |
| /* Disable shift mechanism */ |
| writel(0x0, pd_vio_shift_con_reg); |
| writel(0x0, pd_vio_shift_sel_reg); |
| writel(0x1 << shift_bit, pd_vio_shift_sta_reg); |
| |
| pr_debug(PFX "(Post) %s:0x%x, %s:0x%x, %s:0x%x\n", |
| "VIO_SHIFT_STA", |
| readl(pd_vio_shift_sta_reg), |
| "VIO_SHIFT_SEL", |
| readl(pd_vio_shift_sel_reg), |
| "VIO_SHIFT_CON", |
| readl(pd_vio_shift_con_reg)); |
| |
| return sync_done; |
| } |
| |
| static const char * const perm_to_str[] = { |
| "NO_PROTECTION", |
| "SECURE_RW_ONLY", |
| "SECURE_RW_NS_R_ONLY", |
| "FORBIDDEN", |
| "NO_PERM_CTRL" |
| }; |
| |
| static const char *perm_to_string(uint8_t perm) |
| { |
| if (perm < 4) |
| return perm_to_str[perm]; |
| else |
| return perm_to_str[4]; |
| } |
| |
| static void devapc_vio_reason(uint8_t perm) |
| { |
| pr_info(PFX "Permission setting: %s\n", perm_to_string(perm)); |
| |
| if (perm == 0 || perm > 3) |
| pr_info(PFX "Reason: power/clock is not enabled\n"); |
| else if (perm == 1 || perm == 2 || perm == 3) |
| pr_info(PFX "Reason: might be permission denied\n"); |
| } |
| |
| /* |
| * get_permission - get slave's access permission of domain id. |
| * |
| * Returns the value of access permission |
| */ |
| static uint8_t get_permission(int slave_type, int module_index, int domain) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| const struct mtk_device_info **device_info; |
| const struct mtk_device_num *ndevices; |
| int sys_index, ctrl_index, vio_index, irq_type; |
| uint32_t ret, apc_set_index; |
| struct arm_smccc_res res; |
| |
| ndevices = mtk_devapc_ctx->soc->ndevices; |
| |
| if (slave_type >= slave_type_num || |
| module_index >= ndevices[slave_type].vio_slave_num) { |
| pr_err(PFX "%s: param check failed, %s:0x%x, %s:0x%x\n", |
| __func__, |
| "slave_type", slave_type, |
| "module_index", module_index); |
| return 0xFF; |
| } |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| |
| sys_index = device_info[slave_type][module_index].sys_index; |
| ctrl_index = device_info[slave_type][module_index].ctrl_index; |
| vio_index = device_info[slave_type][module_index].vio_index; |
| irq_type = ndevices[slave_type].irq_type; |
| |
| if (sys_index == -1 || ctrl_index == -1) { |
| pr_err(PFX "%s: cannot get sys_index & ctrl_index\n", |
| __func__); |
| return 0xFF; |
| } else if (sys_index == -2) { |
| pr_info(PFX "%s: check ATF logs for type2 permssion\n", |
| __func__); |
| } |
| |
| arm_smccc_smc(MTK_SIP_KERNEL_DAPC_PERM_GET, slave_type, sys_index, |
| domain, ctrl_index, vio_index, irq_type, 0, &res); |
| ret = res.a0; |
| |
| if (ret == DEAD) { |
| pr_err(PFX "%s: permission get failed, ret:0x%x\n", |
| __func__, ret); |
| return 0xFF; |
| } |
| |
| apc_set_index = ctrl_index % MOD_NO_IN_1_DEVAPC; |
| ret = (ret & (0x3 << (apc_set_index * 2))) >> (apc_set_index * 2); |
| |
| return (ret & 0x3); |
| } |
| |
| /* |
| * mtk_devapc_vio_check - check violation shift status is raised or not. |
| * |
| * Returns the value of violation shift status reg |
| */ |
| static void mtk_devapc_vio_check(int slave_type, int *shift_bit) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| struct mtk_devapc_vio_info *vio_info; |
| uint32_t vio_shift_sta; |
| int i; |
| |
| if (slave_type >= slave_type_num) { |
| pr_err(PFX "%s: param check failed, %s:0x%x\n", |
| __func__, "slave_type", slave_type); |
| return; |
| } |
| |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| vio_shift_sta = readl(mtk_devapc_pd_get(slave_type, VIO_SHIFT_STA, 0)); |
| |
| if (!vio_shift_sta) { |
| pr_info(PFX "violation is triggered before. %s:0x%x\n", |
| "shift_bit", *shift_bit); |
| |
| } else if (vio_shift_sta & (0x1UL << *shift_bit)) { |
| pr_info(PFX "%s: 0x%x is matched with %s:%d\n", |
| "vio_shift_sta", vio_shift_sta, |
| "shift_bit", *shift_bit); |
| |
| } else { |
| pr_info(PFX "%s: 0x%x is not matched with %s:%d\n", |
| "vio_shift_sta", vio_shift_sta, |
| "shift_bit", *shift_bit); |
| |
| for (i = 0; i < MOD_NO_IN_1_DEVAPC * 2; i++) { |
| if (vio_shift_sta & (0x1 << i)) { |
| *shift_bit = i; |
| break; |
| } |
| } |
| } |
| |
| vio_info->shift_sta_bit = *shift_bit; |
| } |
| |
| static void devapc_extract_vio_dbg(int slave_type) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| void __iomem *vio_dbg0_reg, *vio_dbg1_reg, *vio_dbg2_reg, *vio_dbg3_reg; |
| const struct mtk_infra_vio_dbg_desc *vio_dbgs; |
| struct mtk_devapc_vio_info *vio_info; |
| uint32_t dbg0; |
| |
| if (slave_type >= slave_type_num) { |
| pr_err(PFX "%s: param check failed, %s:0x%x\n", |
| __func__, "slave_type", slave_type); |
| return; |
| } |
| |
| vio_dbg0_reg = mtk_devapc_pd_get(slave_type, VIO_DBG0, 0); |
| vio_dbg1_reg = mtk_devapc_pd_get(slave_type, VIO_DBG1, 0); |
| vio_dbg2_reg = mtk_devapc_pd_get(slave_type, VIO_DBG2, 0); |
| vio_dbg3_reg = mtk_devapc_pd_get(slave_type, VIO_DBG3, 0); |
| |
| vio_dbgs = mtk_devapc_ctx->soc->vio_dbgs; |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| |
| /* Extract violation information */ |
| dbg0 = readl(vio_dbg0_reg); |
| vio_info->master_id = readl(vio_dbg1_reg); |
| vio_info->vio_addr = readl(vio_dbg2_reg); |
| |
| vio_info->domain_id = (dbg0 & vio_dbgs->vio_dbg_dmnid) |
| >> vio_dbgs->vio_dbg_dmnid_start_bit; |
| vio_info->write = ((dbg0 & vio_dbgs->vio_dbg_w_vio) |
| >> vio_dbgs->vio_dbg_w_vio_start_bit) == 1; |
| vio_info->read = ((dbg0 & vio_dbgs->vio_dbg_r_vio) |
| >> vio_dbgs->vio_dbg_r_vio_start_bit) == 1; |
| if (vio_dbgs->vio_addr_high == VIO_ADDR_HIGH_MASK && |
| !vio_dbgs->vio_addr_high_start_bit) |
| vio_info->vio_addr_high = readl(vio_dbg3_reg); |
| else |
| vio_info->vio_addr_high = (dbg0 & vio_dbgs->vio_addr_high) |
| >> vio_dbgs->vio_addr_high_start_bit; |
| |
| devapc_vio_info_print(); |
| } |
| |
| /* |
| * mtk_devapc_dump_vio_dbg - shift & dump the violation debug information. |
| */ |
| static bool mtk_devapc_dump_vio_dbg(int slave_type, int *vio_idx, int *index) |
| { |
| const struct mtk_device_info **device_info; |
| const struct mtk_device_num *ndevices; |
| void __iomem *pd_vio_shift_sta_reg; |
| uint32_t shift_bit; |
| int i; |
| |
| if (unlikely(vio_idx == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return NULL; |
| } |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| ndevices = mtk_devapc_ctx->soc->ndevices; |
| |
| pd_vio_shift_sta_reg = mtk_devapc_pd_get(slave_type, VIO_SHIFT_STA, 0); |
| |
| for (i = 0; i < ndevices[slave_type].vio_slave_num; i++) { |
| if (!device_info[slave_type][i].enable_vio_irq) |
| continue; |
| |
| *vio_idx = device_info[slave_type][i].vio_index; |
| if (check_vio_status(slave_type, *vio_idx) != |
| VIOLATION_TRIGGERED) |
| continue; |
| |
| shift_bit = mtk_devapc_ctx->soc->shift_group_get( |
| slave_type, *vio_idx); |
| |
| mtk_devapc_vio_check(slave_type, &shift_bit); |
| |
| if (!sync_vio_dbg(slave_type, shift_bit)) |
| continue; |
| |
| devapc_extract_vio_dbg(slave_type); |
| *index = i; |
| |
| return true; |
| } |
| |
| if (!mtk_devapc_ctx->serror) |
| pr_info(PFX "check_devapc_vio_status: no violation for %s:0x%x\n", |
| "slave_type", slave_type); |
| return false; |
| } |
| |
| /* |
| * start_devapc - initialize devapc status and start receiving interrupt |
| * while devapc violation is triggered. |
| */ |
| static void start_devapc(void) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| const struct mtk_device_info **device_info; |
| const struct mtk_device_num *ndevices; |
| void __iomem *pd_vio_shift_sta_reg; |
| void __iomem *pd_apc_con_reg; |
| uint32_t vio_shift_sta; |
| int slave_type, i, vio_idx, index; |
| uint32_t retry = RETRY_COUNT; |
| |
| ndevices = mtk_devapc_ctx->soc->ndevices; |
| device_info = mtk_devapc_ctx->soc->device_info; |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| if (!is_devapc_subsys_enabled(slave_type)) |
| continue; |
| |
| pd_apc_con_reg = mtk_devapc_pd_get(slave_type, APC_CON, 0); |
| pd_vio_shift_sta_reg = mtk_devapc_pd_get( |
| slave_type, VIO_SHIFT_STA, 0); |
| |
| if (unlikely(pd_apc_con_reg == NULL || |
| pd_vio_shift_sta_reg == NULL || |
| device_info == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return; |
| } |
| |
| /* Clear DEVAPC violation status */ |
| writel(BIT(31), pd_apc_con_reg); |
| |
| /* Clear violation shift status */ |
| vio_shift_sta = readl(pd_vio_shift_sta_reg); |
| if (vio_shift_sta) { |
| writel(vio_shift_sta, pd_vio_shift_sta_reg); |
| pr_info(PFX "clear %s:0x%x %s:0x%x to 0x%x\n", |
| "slave_type", slave_type, |
| "VIO_SHIFT_STA", vio_shift_sta, |
| readl(pd_vio_shift_sta_reg)); |
| } |
| |
| check_type2_vio_status(slave_type, &vio_idx, &i); |
| |
| /* Clear violation status */ |
| for (i = 0; i < ndevices[slave_type].vio_slave_num; i++) { |
| if (!device_info[slave_type][i].enable_vio_irq) |
| continue; |
| |
| vio_idx = device_info[slave_type][i].vio_index; |
| if ((check_vio_status(slave_type, vio_idx) == |
| VIOLATION_TRIGGERED) && |
| clear_vio_status(slave_type, vio_idx)) { |
| pr_warn(PFX "%s, %s:0x%x, %s:0x%x, %s:%d\n", |
| "clear vio status failed", |
| "slave_type", slave_type, |
| "vio_index", vio_idx, |
| "retry", retry); |
| |
| index = i; |
| mtk_devapc_dump_vio_dbg(slave_type, &vio_idx, |
| &index); |
| |
| if (--retry) |
| i = index - 1; |
| else |
| retry = RETRY_COUNT; |
| } |
| |
| mask_module_irq(slave_type, vio_idx, false); |
| } |
| } |
| |
| print_vio_mask_sta(false); |
| |
| /* register subsys test cb */ |
| register_devapc_vio_callback(&devapc_test_handle); |
| |
| pr_info(PFX "%s done\n", __func__); |
| } |
| |
| /* |
| * devapc_extra_handler - |
| * 1. trigger kernel exception/aee exception/kernel warning to increase devapc |
| * violation severity level |
| * 2. call subsys handler to get more debug information |
| */ |
| static void devapc_extra_handler(int slave_type, const char *vio_master, |
| uint32_t vio_index, uint32_t vio_addr) |
| { |
| const struct mtk_device_info **device_info; |
| struct mtk_devapc_dbg_status *dbg_stat; |
| struct mtk_devapc_vio_info *vio_info; |
| struct devapc_vio_callbacks *viocb; |
| char dispatch_key[48] = {0}; |
| enum infra_subsys_id id; |
| uint32_t ret_cb = 0; |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| dbg_stat = mtk_devapc_ctx->soc->dbg_stat; |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| |
| pr_info(PFX "%s:%d\n", "vio_trigger_times", |
| mtk_devapc_ctx->soc->vio_info->vio_trigger_times++); |
| |
| /* Dispatch slave owner if these masters access. |
| * Others, dispatch master owner. |
| */ |
| if (!strncmp(vio_master, "CPUM_M", 6) || |
| !strncmp(vio_master, "VLPSYS_M", 8) || |
| !strncmp(vio_master, "PERI2INFRA1_M", 13) || |
| !strncmp(vio_master, "MCU_AP_M", 8) || |
| !strncmp(vio_master, "AP", 2) || |
| !strncmp(vio_master, "PCIE", 4) || |
| !strncmp(vio_master, "others", 6) || |
| !strncasecmp(vio_master, "UNKNOWN_MASTER", 14)) |
| strncpy(dispatch_key, mtk_devapc_ctx->soc->subsys_get( |
| slave_type, vio_index, vio_addr), |
| sizeof(dispatch_key) - 1); |
| else |
| strncpy(dispatch_key, vio_master, sizeof(dispatch_key) - 1); |
| |
| dispatch_key[sizeof(dispatch_key) - 1] = '\0'; |
| |
| /* Callback func for vio master */ |
| if (!strncasecmp(vio_master, "MD", 2)) |
| id = INFRA_SUBSYS_MD; |
| |
| else if (!strncasecmp(vio_master, "CONN", 4) || |
| !strncasecmp(dispatch_key, "CONN", 4)) |
| id = INFRA_SUBSYS_CONN; |
| |
| else if (!strncasecmp(vio_master, "TINYSYS", 7)) |
| id = INFRA_SUBSYS_ADSP; |
| |
| else if (!strncasecmp(vio_master, "GCE", 3) || |
| !strncasecmp(dispatch_key, "GCE", 3)) |
| id = INFRA_SUBSYS_GCE; |
| |
| else if (!strncasecmp(vio_master, "APMCU", 5)) |
| if (vio_info->domain_id == 0) |
| id = INFRA_SUBSYS_APMCU; |
| else |
| id = INFRA_SUBSYS_GZ; |
| else |
| id = DEVAPC_SUBSYS_RESERVED; |
| |
| /* enable_ut to test callback */ |
| if (dbg_stat->enable_ut) |
| id = DEVAPC_SUBSYS_TEST; |
| |
| list_for_each_entry(viocb, &viocb_list, list) { |
| if (viocb->id == id && viocb->debug_dump) |
| viocb->debug_dump(); |
| |
| /* call MD cb_adv if it's registered */ |
| if (viocb->id == id && id == INFRA_SUBSYS_MD && |
| viocb->debug_dump_adv) |
| ret_cb = viocb->debug_dump_adv(vio_addr); |
| |
| /* always call clkmgr cb if it's registered */ |
| if (viocb->id == DEVAPC_SUBSYS_CLKMGR && |
| viocb->debug_dump) |
| viocb->debug_dump(); |
| } |
| |
| /* Severity level */ |
| if (dbg_stat->enable_KE && (ret_cb != DEVAPC_NOT_KE)) { |
| pr_info(PFX "Device APC Violation Issue/%s", dispatch_key); |
| BUG_ON(id != INFRA_SUBSYS_CONN); |
| |
| } else if (dbg_stat->enable_AEE) { |
| /* call mtk aee_kernel_exception */ |
| #if IS_ENABLED(CONFIG_MTK_AEE_FEATURE) |
| aee_kernel_exception("[DEVAPC]", |
| "%s%s\n", |
| "CRDISPATCH_KEY:Device APC Violation Issue/", |
| dispatch_key); |
| #endif |
| } else if (dbg_stat->enable_WARN) { |
| WARN(1, "Device APC Violation Issue/%s", dispatch_key); |
| } |
| } |
| |
| /* |
| * devapc_dump_info - the devapc will dump violation information |
| * including which master violates access slave. |
| */ |
| static void devapc_dump_info(bool booting) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| const struct mtk_device_info **device_info; |
| struct mtk_devapc_vio_info *vio_info; |
| int slave_type, vio_idx, index; |
| const char *vio_master; |
| uint8_t perm; |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| vio_idx = index = -1; |
| |
| /* There are multiple DEVAPC_PD */ |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| if (booting) { |
| if (!is_devapc_subsys_enabled(slave_type)) |
| continue; |
| } else { |
| if (!is_devapc_subsys_power_on(slave_type)) |
| continue; |
| } |
| |
| if (!check_type2_vio_status(slave_type, &vio_idx, &index)) |
| if (!mtk_devapc_dump_vio_dbg(slave_type, &vio_idx, |
| &index)) |
| continue; |
| |
| /* Ensure that violation info are written before |
| * further operations |
| */ |
| smp_mb(); |
| |
| mask_module_irq(slave_type, vio_idx, true); |
| |
| if (clear_vio_status(slave_type, vio_idx)) |
| pr_warn(PFX "%s, %s:0x%x, %s:0x%x\n", |
| "clear vio status failed", |
| "slave_type", slave_type, |
| "vio_index", vio_idx); |
| |
| perm = get_permission(slave_type, index, vio_info->domain_id); |
| |
| vio_master = mtk_devapc_ctx->soc->master_get( |
| vio_info->master_id, |
| vio_info->vio_addr, |
| slave_type, |
| vio_info->shift_sta_bit, |
| vio_info->domain_id); |
| |
| if (!vio_master) { |
| pr_warn(PFX "master_get failed\n"); |
| vio_master = "UNKNOWN_MASTER"; |
| } |
| |
| pr_info(PFX "%s - %s:0x%x, %s:0x%x, %s:0x%x, %s:0x%x\n", |
| "Violation", "slave_type", slave_type, |
| "sys_index", |
| device_info[slave_type][index].sys_index, |
| "ctrl_index", |
| device_info[slave_type][index].ctrl_index, |
| "vio_index", |
| device_info[slave_type][index].vio_index); |
| |
| pr_info(PFX "%s %s %s %s\n", |
| "Violation - master:", vio_master, |
| "access violation slave:", |
| device_info[slave_type][index].device); |
| |
| devapc_vio_reason(perm); |
| |
| devapc_extra_handler(slave_type, vio_master, vio_idx, |
| vio_info->vio_addr); |
| |
| mask_module_irq(slave_type, vio_idx, false); |
| } |
| } |
| |
| /* |
| * devapc_violation_irq - the devapc Interrupt Service Routine (ISR) will dump |
| * violation information including which master violates |
| * access slave. |
| */ |
| static irqreturn_t devapc_violation_irq(int irq_number, void *dev_id) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| uint32_t irq_type_num = IRQ_TYPE_NUM_DEFAULT; |
| const struct mtk_device_info **device_info; |
| struct mtk_devapc_vio_info *vio_info; |
| int slave_type, vio_idx, index; |
| int irq_type; |
| const char *vio_master; |
| unsigned long flags; |
| uint8_t perm; |
| bool normal; |
| |
| spin_lock_irqsave(&devapc_lock, flags); |
| |
| pr_info(PFX "irq_number: %d\n", irq_number); |
| |
| if (mtk_devapc_ctx->soc->irq_type_num) |
| irq_type_num = mtk_devapc_ctx->soc->irq_type_num; |
| |
| for (irq_type = 0; irq_type < irq_type_num; irq_type++) { |
| if (irq_number == mtk_devapc_ctx->devapc_irq[irq_type]) { |
| pr_info(PFX "irq_type: %d\n", irq_type); |
| mtk_devapc_ctx->current_irq_type = irq_type; |
| break; |
| } |
| } |
| |
| #ifdef CONFIG_MTK_SERROR_HOOK |
| if (mtk_devapc_ctx->serror) { |
| spin_unlock_irqrestore(&devapc_lock, flags); |
| return IRQ_HANDLED; |
| } |
| #endif |
| |
| print_vio_mask_sta(false); |
| |
| device_info = mtk_devapc_ctx->soc->device_info; |
| vio_info = mtk_devapc_ctx->soc->vio_info; |
| normal = false; |
| vio_idx = index = -1; |
| |
| /* There are multiple DEVAPC_PD */ |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| /* Only dump the info of subsystem which got violation */ |
| if (!is_matched_devapc_type(slave_type)) |
| continue; |
| |
| if (!is_devapc_subsys_power_on(slave_type)) |
| continue; |
| |
| if (!check_type2_vio_status(slave_type, &vio_idx, &index)) |
| if (!mtk_devapc_dump_vio_dbg(slave_type, &vio_idx, |
| &index)) |
| continue; |
| |
| /* Ensure that violation info are written before |
| * further operations |
| */ |
| smp_mb(); |
| normal = true; |
| |
| mask_module_irq(slave_type, vio_idx, true); |
| |
| if (clear_vio_status(slave_type, vio_idx)) |
| pr_warn(PFX "%s, %s:0x%x, %s:0x%x\n", |
| "clear vio status failed", |
| "slave_type", slave_type, |
| "vio_index", vio_idx); |
| |
| perm = get_permission(slave_type, index, vio_info->domain_id); |
| |
| vio_master = mtk_devapc_ctx->soc->master_get( |
| vio_info->master_id, |
| vio_info->vio_addr, |
| slave_type, |
| vio_info->shift_sta_bit, |
| vio_info->domain_id); |
| |
| if (!vio_master) { |
| pr_warn(PFX "master_get failed\n"); |
| vio_master = "UNKNOWN_MASTER"; |
| } |
| |
| pr_info(PFX "%s - %s:0x%x, %s:0x%x, %s:0x%x, %s:0x%x\n", |
| "Violation", "slave_type", slave_type, |
| "sys_index", |
| device_info[slave_type][index].sys_index, |
| "ctrl_index", |
| device_info[slave_type][index].ctrl_index, |
| "vio_index", |
| device_info[slave_type][index].vio_index); |
| |
| pr_info(PFX "%s %s %s %s\n", |
| "Violation - master:", vio_master, |
| "access violation slave:", |
| device_info[slave_type][index].device); |
| |
| devapc_vio_reason(perm); |
| |
| devapc_extra_handler(slave_type, vio_master, vio_idx, |
| vio_info->vio_addr); |
| |
| mask_module_irq(slave_type, vio_idx, false); |
| } |
| |
| if (normal) { |
| spin_unlock_irqrestore(&devapc_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| /* It's an abnormal status */ |
| pr_info(PFX "WARNING: Abnormal Status\n"); |
| print_vio_mask_sta(false); |
| BUG_ON(1); |
| |
| spin_unlock_irqrestore(&devapc_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| void register_devapc_vio_callback(struct devapc_vio_callbacks *viocb) |
| { |
| INIT_LIST_HEAD(&viocb->list); |
| list_add_tail(&viocb->list, &viocb_list); |
| } |
| EXPORT_SYMBOL(register_devapc_vio_callback); |
| |
| void register_devapc_power_callback(struct devapc_power_callbacks *powercb) |
| { |
| INIT_LIST_HEAD(&powercb->list); |
| list_add_tail(&powercb->list, &powercb_list); |
| } |
| EXPORT_SYMBOL(register_devapc_power_callback); |
| |
| /* |
| * devapc_ut - There are two UT commands to support |
| * 1. test permission denied violation |
| * 2. test sramrom decode error violation |
| */ |
| static void devapc_ut(uint32_t cmd) |
| { |
| void __iomem *dapc_ao_base; |
| void __iomem *sramrom_base = mtk_devapc_ctx->sramrom_base; |
| uint32_t irq_type; |
| |
| if (!cmd) { |
| pr_info(PFX "%s, cmd(0x%x) not supported\n", __func__, cmd); |
| return; |
| } |
| |
| pr_info(PFX "%s, cmd:0x%x\n", __func__, cmd); |
| |
| irq_type = cmd - 1; |
| dapc_ao_base = mtk_devapc_ctx->devapc_ao_base[irq_type]; |
| |
| pr_info(PFX "%s, irq_type:0x%x\n", __func__, irq_type); |
| |
| if (cmd == DEVAPC_UT_DAPC_INFRA_VIO || |
| cmd == DEVAPC_UT_DAPC_PERI_VIO || |
| cmd == DEVAPC_UT_DAPC_VLP_VIO || |
| cmd == DEVAPC_UT_DAPC_ADSP_VIO || |
| cmd == DEVAPC_UT_DAPC_MMINFRA_VIO || |
| cmd == DEVAPC_UT_DAPC_MMUP_VIO || |
| cmd == DEVAPC_UT_DAPC_GPU_VIO) { |
| if (unlikely(dapc_ao_base == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return; |
| } |
| |
| pr_info(PFX "%s, devapc_ao_base:0x%x\n", __func__, |
| readl(dapc_ao_base)); |
| |
| pr_info(PFX "test done, it should generate violation!\n"); |
| |
| } else if (cmd == DEVAPC_UT_SRAM_VIO) { |
| if (unlikely(sramrom_base == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return; |
| } |
| |
| pr_info(PFX "%s, sramrom_base:0x%x\n", __func__, |
| readl(sramrom_base + RANDOM_OFFSET)); |
| |
| pr_info(PFX "test done, it should generate violation!\n"); |
| |
| } else { |
| pr_info(PFX "%s, cmd(0x%x) not supported\n", __func__, cmd); |
| } |
| } |
| |
| /* |
| * mtk_devapc_dbg_read - dump status of struct mtk_devapc_dbg_status. |
| * Currently, we have 5 debug status: |
| * 1. enable_ut: enable/disable devapc ut commands |
| * 2~4. enable_KE/enable_AEE/enable_WARN |
| * 5. enable_dapc: enable/disable dump access permission control |
| * |
| */ |
| ssize_t mtk_devapc_dbg_read(struct file *file, char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct mtk_devapc_dbg_status *dbg_stat = mtk_devapc_ctx->soc->dbg_stat; |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| char msg_buf[1024] = {0}; |
| char *p = msg_buf; |
| int len; |
| |
| if (unlikely(dbg_stat == NULL) || unlikely(vio_info == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| devapc_log(p, msg_buf, "DEVAPC debug status:\n"); |
| devapc_log(p, msg_buf, "\tenable_ut = %d\n", dbg_stat->enable_ut); |
| devapc_log(p, msg_buf, "\tenable_KE = %d\n", dbg_stat->enable_KE); |
| devapc_log(p, msg_buf, "\tenable_AEE = %d\n", dbg_stat->enable_AEE); |
| devapc_log(p, msg_buf, "\tenable_WARN = %d\n", dbg_stat->enable_WARN); |
| devapc_log(p, msg_buf, "\tenable_dapc = %d\n", dbg_stat->enable_dapc); |
| devapc_log(p, msg_buf, "\tviolation count = %d\n", |
| vio_info->vio_trigger_times); |
| devapc_log(p, msg_buf, "\n"); |
| |
| len = p - msg_buf; |
| |
| return simple_read_from_buffer(buffer, count, ppos, msg_buf, len); |
| } |
| |
| /* |
| * mtk_devapc_dbg_write - control status of struct mtk_devapc_dbg_status. |
| * There are 7 nodes we can control: |
| * 1. enable_ut |
| * 2~4. enable_KE/enable_AEE/enable_WARN |
| * 5. enable_dapc |
| * 6. devapc_ut |
| * 7. dump_apc |
| */ |
| ssize_t mtk_devapc_dbg_write(struct file *file, const char __user *buffer, |
| size_t count, loff_t *data) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| long param, sys_index, domain, ctrl_index; |
| struct mtk_devapc_dbg_status *dbg_stat; |
| uint32_t slave_type, apc_set_idx, ret; |
| char *parm_str, *cmd_str, *pinput; |
| struct arm_smccc_res res; |
| char input[32] = {0}; |
| int err, len; |
| |
| dbg_stat = mtk_devapc_ctx->soc->dbg_stat; |
| if (unlikely(dbg_stat == NULL)) { |
| pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__); |
| return -EINVAL; |
| } |
| |
| len = (count < (sizeof(input) - 1)) ? count : (sizeof(input) - 1); |
| if (copy_from_user(input, buffer, len)) { |
| pr_err(PFX "copy from user failed!\n"); |
| return -EFAULT; |
| } |
| |
| input[len] = '\0'; |
| pinput = input; |
| |
| cmd_str = strsep(&pinput, " "); |
| |
| if (!cmd_str) |
| return -EINVAL; |
| |
| parm_str = strsep(&pinput, " "); |
| |
| if (!parm_str) |
| return -EINVAL; |
| |
| err = kstrtol(parm_str, 10, ¶m); |
| |
| if (err) |
| return err; |
| |
| if (!strncmp(cmd_str, "enable_ut", sizeof("enable_ut"))) { |
| dbg_stat->enable_ut = (param != 0); |
| pr_info(PFX "debapc_dbg_stat->enable_ut = %s\n", |
| dbg_stat->enable_ut ? "enable" : "disable"); |
| return count; |
| |
| } else if (!strncmp(cmd_str, "devapc_ut", sizeof("devapc_ut"))) { |
| if (dbg_stat->enable_ut) |
| devapc_ut(param); |
| else |
| pr_info(PFX "devapc_ut is not enabled\n"); |
| |
| return count; |
| |
| } else if (!strncmp(cmd_str, "enable_KE", sizeof("enable_KE"))) { |
| if (dbg_stat->enable_ut) { |
| dbg_stat->enable_KE = (param != 0); |
| pr_info(PFX "debapc_dbg_stat->enable_KE = %s\n", |
| dbg_stat->enable_KE ? |
| "enable" : "disable"); |
| } else |
| pr_info(PFX "devapc_ut is not enabled\n"); |
| |
| return count; |
| |
| } else if (!strncmp(cmd_str, "enable_AEE", sizeof("enable_AEE"))) { |
| if (dbg_stat->enable_ut) { |
| dbg_stat->enable_AEE = (param != 0); |
| pr_info(PFX "debapc_dbg_stat->enable_AEE = %s\n", |
| dbg_stat->enable_AEE ? |
| "enable" : "disable"); |
| } else |
| pr_info(PFX "devapc_ut is not enabled\n"); |
| |
| return count; |
| |
| } else if (!strncmp(cmd_str, "enable_WARN", sizeof("enable_WARN"))) { |
| if (dbg_stat->enable_ut) { |
| dbg_stat->enable_WARN = (param != 0); |
| pr_info(PFX "debapc_dbg_stat->enable_WARN = %s\n", |
| dbg_stat->enable_WARN ? |
| "enable" : "disable"); |
| } else |
| pr_info(PFX "devapc_ut is not enabled\n"); |
| |
| return count; |
| |
| } else if (!strncmp(cmd_str, "enable_dapc", sizeof("enable_dapc"))) { |
| dbg_stat->enable_dapc = (param != 0); |
| pr_info(PFX "debapc_dbg_stat->enable_dapc = %s\n", |
| dbg_stat->enable_dapc ? "enable" : "disable"); |
| |
| return count; |
| |
| } else if (!strncmp(cmd_str, "dump_apc", sizeof("dump_apc"))) { |
| if (!dbg_stat->enable_dapc) { |
| pr_info(PFX "dump_apc is not enabled\n"); |
| return -EINVAL; |
| } |
| |
| /* slave_type is already parse before */ |
| slave_type = (uint32_t)param; |
| |
| if (slave_type >= slave_type_num) { |
| pr_err(PFX "Wrong slave type:0x%x\n", slave_type); |
| return -EFAULT; |
| } |
| |
| sys_index = 0xFFFFFFFF; |
| ctrl_index = 0xFFFFFFFF; |
| domain = DOMAIN_OTHERS; |
| |
| /* Parse sys_index */ |
| parm_str = strsep(&pinput, " "); |
| if (parm_str) |
| err = kstrtol(parm_str, 10, &sys_index); |
| |
| /* Parse domain id */ |
| parm_str = strsep(&pinput, " "); |
| if (parm_str) |
| err = kstrtol(parm_str, 10, &domain); |
| |
| /* Parse ctrl_index */ |
| parm_str = strsep(&pinput, " "); |
| if (parm_str != NULL) |
| err = kstrtol(parm_str, 10, &ctrl_index); |
| |
| pr_info(PFX "%s:0x%x, %s:0x%lx, %s:0x%lx, %s:0x%lx\n", |
| "slave_type", slave_type, |
| "sys_index", sys_index, |
| "domain_id", domain, |
| "ctrl_index", ctrl_index); |
| |
| arm_smccc_smc(MTK_SIP_KERNEL_DAPC_PERM_GET, slave_type, |
| sys_index, domain, ctrl_index, 0, 0, 0, &res); |
| ret = res.a0; |
| |
| if (ret == DEAD) { |
| pr_err(PFX "%s, SMC call failed, ret: 0x%x\n", |
| __func__, ret); |
| return -EINVAL; |
| } |
| |
| apc_set_idx = ctrl_index % MOD_NO_IN_1_DEVAPC; |
| ret = (ret & (0x3 << (apc_set_idx * 2))) >> (apc_set_idx * 2); |
| |
| pr_info(PFX "Permission is %s\n", |
| perm_to_string((ret & 0x3))); |
| return count; |
| } else |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static const struct proc_ops devapc_dbg_fops = { |
| .proc_write = mtk_devapc_dbg_write, |
| .proc_read = mtk_devapc_dbg_read, |
| }; |
| |
| #ifdef CONFIG_DEVAPC_SWP_SUPPORT |
| static struct devapc_swp_context { |
| void __iomem *devapc_swp_base; |
| bool swp_enable; |
| bool swp_clr; |
| bool swp_rw; |
| uint32_t swp_phy_addr; |
| uint32_t swp_rg; |
| uint32_t swp_wr_val; |
| uint32_t swp_wr_mask; |
| } devapc_swp_ctx[1]; |
| |
| static ssize_t set_swp_addr_show(struct device_driver *driver, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, |
| "%s:%s\n\t%s:%s\n\t%s:0x%x\n\t%s:0x%x\n\t%s:0x%x\n\t%s:0x%x\n", |
| "devapc_swp", |
| devapc_swp_ctx->swp_enable ? "enable" : "disable", |
| "swp_rw", |
| devapc_swp_ctx->swp_rw ? "write" : "read", |
| "swp_physical_addr", devapc_swp_ctx->swp_phy_addr, |
| "swp_rg", devapc_swp_ctx->swp_rg, |
| "swp_wr_val", devapc_swp_ctx->swp_wr_val, |
| "swp_wr_mask", devapc_swp_ctx->swp_wr_mask |
| ); |
| } |
| |
| static ssize_t set_swp_addr_store(struct device_driver *driver, |
| const char *buf, size_t count) |
| { |
| char *cmd_str, *param_str; |
| unsigned int param; |
| int err; |
| |
| pr_info(PFX "buf: %s", buf); |
| |
| cmd_str = strsep((char **)&buf, " "); |
| if (!cmd_str) |
| return -EINVAL; |
| |
| param_str = strsep((char **)&buf, " "); |
| if (!param_str) |
| return -EINVAL; |
| |
| err = kstrtou32(param_str, 16, ¶m); |
| if (err) |
| return err; |
| |
| if (!strncmp(cmd_str, "enable_swp", sizeof("enable_swp"))) { |
| devapc_swp_ctx->swp_enable = (param != 0); |
| pr_info(PFX "devapc_swp_enable = %s\n", |
| devapc_swp_ctx->swp_enable ? "enable" : "disable"); |
| |
| writel(param, devapc_swp_ctx->devapc_swp_base); |
| if (!devapc_swp_ctx->swp_enable) |
| devapc_swp_ctx->swp_phy_addr = 0x0; |
| |
| } else if (!strncmp(cmd_str, "set_swp_clr", sizeof("set_swp_clr"))) { |
| pr_info(PFX "set swp clear: 0x%x\n", param); |
| devapc_swp_ctx->swp_clr = (param != 0); |
| |
| if (devapc_swp_ctx->swp_clr) |
| writel(0x1 << DEVAPC_SWP_CON_CLEAR, |
| devapc_swp_ctx->devapc_swp_base); |
| |
| } else if (!strncmp(cmd_str, "set_swp_rw", sizeof("set_swp_rw"))) { |
| pr_info(PFX "set swp r/w: %s\n", param ? "write" : "read"); |
| devapc_swp_ctx->swp_rw = (param != 0); |
| |
| if (devapc_swp_ctx->swp_rw) |
| writel(0x1 << DEVAPC_SWP_CON_RW, |
| devapc_swp_ctx->devapc_swp_base); |
| |
| } else if (!strncmp(cmd_str, "set_swp_addr", sizeof("set_swp_addr"))) { |
| pr_info(PFX "set swp physical addr: 0x%x\n", param); |
| devapc_swp_ctx->swp_phy_addr = param; |
| |
| writel(devapc_swp_ctx->swp_phy_addr, |
| devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_SA_OFFSET); |
| |
| } else if (!strncmp(cmd_str, "set_swp_rg", sizeof("set_swp_rg"))) { |
| pr_info(PFX "set swp range: 0x%x\n", param); |
| devapc_swp_ctx->swp_rg = param; |
| |
| writel(devapc_swp_ctx->swp_rg, |
| devapc_swp_ctx->devapc_swp_base + DEVAPC_SWP_RG_OFFSET); |
| |
| } else if (!strncmp(cmd_str, "set_swp_wr_val", |
| sizeof("set_swp_wr_val"))) { |
| pr_info(PFX "set swp write value: 0x%x\n", param); |
| devapc_swp_ctx->swp_wr_val = param; |
| |
| writel(devapc_swp_ctx->swp_wr_val, |
| devapc_swp_ctx->devapc_swp_base + |
| DEVAPC_SWP_WR_VAL_OFFSET); |
| |
| } else if (!strncmp(cmd_str, "set_swp_wr_mask", |
| sizeof("set_swp_wr_mask"))) { |
| pr_info(PFX "set swp write mask: 0x%x\n", param); |
| devapc_swp_ctx->swp_wr_mask = param; |
| |
| writel(devapc_swp_ctx->swp_wr_mask, |
| devapc_swp_ctx->devapc_swp_base + |
| DEVAPC_SWP_WR_MASK_OFFSET); |
| |
| } else |
| return -EINVAL; |
| |
| return count; |
| } |
| static DRIVER_ATTR_RW(set_swp_addr); |
| #endif /* CONFIG_DEVAPC_SWP_SUPPORT */ |
| |
| #ifdef CONFIG_MTK_SERROR_HOOK |
| static void devapc_arm64_serror_panic_hook(void *data, |
| struct pt_regs *regs, unsigned int esr) |
| { |
| mtk_devapc_ctx->serror = true; |
| mtk_devapc_ctx->soc->dbg_stat->enable_KE = false; |
| devapc_dump_info(false); |
| } |
| #endif |
| |
| static int devapc_hre_init(void) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| int slave_type; |
| int ret; |
| size_t size; |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| size = vio_info->vio_mask_sta_num[slave_type] * sizeof(uint32_t); |
| |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg = kzalloc(size, GFP_KERNEL); |
| if (!mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg) { |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg = kzalloc(size, GFP_KERNEL); |
| if (!mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg) { |
| ret = -ENOMEM; |
| goto exit; |
| } |
| } |
| |
| return 0; |
| |
| exit: |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| if (mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg != NULL) |
| kfree(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg); |
| if (mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg != NULL) |
| kfree(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg); |
| } |
| return ret; |
| } |
| |
| static void devapc_hre_deinit(void) |
| { |
| uint32_t slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| int slave_type; |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| if (mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg != NULL) |
| kfree(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg); |
| if (mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg != NULL) |
| kfree(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg); |
| } |
| } |
| |
| static void devapc_hre_backup(int slave_type) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t size = vio_info->vio_mask_sta_num[slave_type]; |
| uint32_t i; |
| |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg0_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_DBG0, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg1_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_DBG1, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg2_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_DBG2, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg3_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_DBG3, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_apc_con_reg = |
| readl(mtk_devapc_pd_get(slave_type, APC_CON, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_sta_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_SHIFT_STA, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_sel_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_SHIFT_SEL, 0)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_con_reg = |
| readl(mtk_devapc_pd_get(slave_type, VIO_SHIFT_CON, 0)); |
| for (i = 0; i < size; i++) { |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg[i] = |
| readl(mtk_devapc_pd_get(slave_type, VIO_MASK, i)); |
| mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg[i] = |
| readl(mtk_devapc_pd_get(slave_type, VIO_STA, i)); |
| } |
| } |
| |
| static void devapc_hre_restore(int slave_type) |
| { |
| struct mtk_devapc_vio_info *vio_info = mtk_devapc_ctx->soc->vio_info; |
| uint32_t size = vio_info->vio_mask_sta_num[slave_type]; |
| uint32_t i; |
| |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg0_reg, |
| mtk_devapc_pd_get(slave_type, VIO_DBG0, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg1_reg, |
| mtk_devapc_pd_get(slave_type, VIO_DBG1, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg2_reg, |
| mtk_devapc_pd_get(slave_type, VIO_DBG2, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_dbg3_reg, |
| mtk_devapc_pd_get(slave_type, VIO_DBG3, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_apc_con_reg, |
| mtk_devapc_pd_get(slave_type, APC_CON, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_sta_reg, |
| mtk_devapc_pd_get(slave_type, VIO_SHIFT_STA, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_sel_reg, |
| mtk_devapc_pd_get(slave_type, VIO_SHIFT_SEL, 0)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_shift_con_reg, |
| mtk_devapc_pd_get(slave_type, VIO_SHIFT_CON, 0)); |
| for (i = 0; i < size; i++) { |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_mask_reg[i], |
| mtk_devapc_pd_get(slave_type, VIO_MASK, i)); |
| writel(mtk_devapc_ctx->pd_reg[slave_type].pd_vio_sta_reg[i], |
| mtk_devapc_pd_get(slave_type, VIO_STA, i)); |
| } |
| } |
| |
| int devapc_suspend_noirq(struct device *dev) |
| { |
| devapc_hre_backup(DEVAPC_TYPE_INFRA); |
| devapc_hre_backup(DEVAPC_TYPE_INFRA1); |
| devapc_hre_backup(DEVAPC_TYPE_PERI_PAR); |
| devapc_hre_backup(DEVAPC_TYPE_VLP); |
| return 0; |
| } |
| EXPORT_SYMBOL(devapc_suspend_noirq); |
| |
| int devapc_resume_noirq(struct device *dev) |
| { |
| devapc_hre_restore(DEVAPC_TYPE_INFRA); |
| devapc_hre_restore(DEVAPC_TYPE_INFRA1); |
| devapc_hre_restore(DEVAPC_TYPE_PERI_PAR); |
| devapc_hre_restore(DEVAPC_TYPE_VLP); |
| return 0; |
| } |
| EXPORT_SYMBOL(devapc_resume_noirq); |
| |
| int mtk_devapc_probe(struct platform_device *pdev, |
| struct mtk_devapc_soc *soc) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| uint32_t slave_type_num; |
| uint32_t irq_type_num = IRQ_TYPE_NUM_DEFAULT; |
| uint32_t dt_index; |
| int slave_type; |
| int irq_type; |
| int ret; |
| |
| pr_info(PFX "driver registered\n"); |
| |
| #ifdef CONFIG_MTK_SERROR_HOOK |
| ret = register_trace_android_rvh_arm64_serror_panic( |
| devapc_arm64_serror_panic_hook, NULL); |
| if (ret) |
| pr_info(PFX "register android_rvh_arm64_serror_panic failed!\n"); |
| #endif |
| |
| if (IS_ERR(node)) { |
| pr_err(PFX "cannot find device node\n"); |
| return -ENODEV; |
| } |
| |
| mtk_devapc_ctx->soc = soc; |
| slave_type_num = mtk_devapc_ctx->soc->slave_type_num; |
| if (mtk_devapc_ctx->soc->irq_type_num) |
| irq_type_num = mtk_devapc_ctx->soc->irq_type_num; |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| query_devapc_subsys_status(slave_type); |
| pr_info(PFX "subsys_enabled[%d]:%lu\n", slave_type, |
| mtk_devapc_ctx->subsys_enabled[slave_type]); |
| } |
| |
| for (slave_type = 0; slave_type < slave_type_num; slave_type++) { |
| if (!is_devapc_subsys_enabled(slave_type)) |
| continue; |
| mtk_devapc_ctx->devapc_pd_base[slave_type] = of_iomap(node, |
| slave_type); |
| if (unlikely(mtk_devapc_ctx->devapc_pd_base[slave_type] |
| == NULL)) { |
| pr_err(PFX "parse devapc_pd_base:0x%x failed\n", |
| slave_type); |
| return -EINVAL; |
| } |
| } |
| dt_index = slave_type_num; |
| |
| for (irq_type = 0; irq_type < irq_type_num; irq_type++) { |
| mtk_devapc_ctx->devapc_ao_base[irq_type] = of_iomap(node, |
| dt_index + irq_type); |
| if (unlikely(mtk_devapc_ctx->devapc_ao_base[irq_type] |
| == NULL)) { |
| pr_err(PFX "parse devapc_ao_base:0x%x failed\n", |
| irq_type); |
| return -EINVAL; |
| } |
| } |
| dt_index += irq_type_num; |
| |
| mtk_devapc_ctx->infracfg_base = of_iomap(node, dt_index); |
| if (unlikely(mtk_devapc_ctx->infracfg_base == NULL)) { |
| pr_err(PFX "parse infracfg_base failed\n"); |
| return -EINVAL; |
| } |
| |
| #ifdef CONFIG_DEVAPC_SWP_SUPPORT |
| devapc_swp_ctx->devapc_swp_base = of_iomap(node, dt_index + 1); |
| ret = driver_create_file(pdev->dev.driver, |
| &driver_attr_set_swp_addr); |
| if (ret) |
| pr_info(PFX "create SWP sysfs file failed, ret:%d\n", ret); |
| #endif |
| |
| mtk_devapc_ctx->sramrom_base = of_iomap(node, dt_index + 2); |
| if (unlikely(mtk_devapc_ctx->sramrom_base == NULL)) |
| pr_info(PFX "parse sramrom_base failed\n"); |
| |
| for (irq_type = 0; irq_type < irq_type_num; irq_type++) { |
| mtk_devapc_ctx->devapc_irq[irq_type] = irq_of_parse_and_map(node, |
| irq_type); |
| if (!mtk_devapc_ctx->devapc_irq[irq_type]) { |
| pr_err(PFX "parse and map the interrupt[%d] failed\n", |
| irq_type); |
| return -EINVAL; |
| } |
| pr_info(PFX "IRQ[%d]:%d\n", irq_type, |
| mtk_devapc_ctx->devapc_irq[irq_type]); |
| } |
| |
| for (irq_type = 0; irq_type < irq_type_num; irq_type++) { |
| ret = devm_request_irq(&pdev->dev, |
| mtk_devapc_ctx->devapc_irq[irq_type], |
| (irq_handler_t)devapc_violation_irq, |
| IRQF_TRIGGER_NONE, "devapc", NULL); |
| if (ret) |
| pr_info(PFX "request devapc irq[%d] failed, ret:%d\n", |
| irq_type, ret); |
| } |
| |
| /* CCF (Common Clock Framework) */ |
| mtk_devapc_ctx->devapc_infra_clk = devm_clk_get(&pdev->dev, |
| "devapc-infra-clock"); |
| if (IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) |
| pr_info(PFX "(Infra) Cannot get devapc clock from CCF (%ld)\n", |
| PTR_ERR(mtk_devapc_ctx->devapc_infra_clk)); |
| |
| proc_create("devapc_dbg", 0664, NULL, &devapc_dbg_fops); |
| |
| if (!IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) { |
| if (clk_prepare_enable(mtk_devapc_ctx->devapc_infra_clk)) { |
| pr_err(PFX " Cannot enable devapc clock\n"); |
| return -EINVAL; |
| } |
| } |
| |
| ret = devapc_hre_init(); |
| if (ret) { |
| pr_info(PFX "hre init failed, ret %d\n", ret); |
| return ret; |
| } |
| |
| devapc_dump_info(true); |
| start_devapc(); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mtk_devapc_probe); |
| |
| int mtk_devapc_remove(struct platform_device *dev) |
| { |
| devapc_hre_deinit(); |
| if (!IS_ERR(mtk_devapc_ctx->devapc_infra_clk)) |
| clk_disable_unprepare(mtk_devapc_ctx->devapc_infra_clk); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mtk_devapc_remove); |
| |
| MODULE_DESCRIPTION("Mediatek Device APC Driver"); |
| MODULE_AUTHOR("Jackson Chang <jackson-kt.chang@mediatek.com>"); |
| MODULE_LICENSE("GPL"); |