kernel_samsung_a34x-permissive/drivers/soc/mediatek/devapc/devapc-mtk-multi-ao.c

1447 lines
39 KiB
C
Raw Permalink Normal View History

// 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 <mt-plat/aee.h>
#include <mt-plat/devapc_public.h>
#include <mt-plat/mtk_secure_api.h>
#include "devapc-mtk-multi-ao.h"
static struct mtk_devapc_context {
struct clk *devapc_infra_clk;
uint32_t devapc_irq;
/* HW reg mapped addr */
void __iomem *devapc_pd_base[4];
void __iomem *devapc_infra_ao_base;
void __iomem *infracfg_base;
void __iomem *sramrom_base;
struct mtk_devapc_soc *soc;
struct mutex viocb_list_lock;
} mtk_devapc_ctx[1];
static LIST_HEAD(viocb_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,
};
/*
* 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;
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;
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_vio:0x%x, sramrom_vio_sta:0x%zx, vio_addr:0x%x\n",
sramrom_vio,
sramrom_vio_sta,
vio_info->vio_addr);
pr_info(PFX "SRAMROM violation is not triggered\n");
return;
}
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\n",
__func__, "master_id", vio_info->master_id,
"domain_id", vio_info->domain_id,
"rw", rw ? "Write" : "Read",
"vio_addr", vio_info->vio_addr);
}
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 debug)
{
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++) {
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++) {
if (debug)
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))
);
else
pr_debug(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;
}
}
pr_info(PFX "%s: no violation for %s:0x%x\n", __func__,
"slave_type", slave_type);
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;
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;
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, 0, 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;
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_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;
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;
}
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;
print_vio_mask_sta(false);
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++) {
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 /* reset retry and continue */
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 APMCU access. Others, dispatch master */
if (!strncmp(vio_master, "APMCU", 5))
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;
strncpy(dispatch_key, "MD", sizeof(dispatch_key) - 1);
} else if (!strncasecmp(vio_master, "CONN", 4) ||
!strncasecmp(dispatch_key, "CONN", 4)) {
id = INFRA_SUBSYS_CONN;
strncpy(dispatch_key, "CONNSYS", sizeof(dispatch_key) - 1);
} else if (!strncasecmp(vio_master, "TINYSYS", 7)) {
id = INFRA_SUBSYS_ADSP;
strncpy(dispatch_key, "TINYSYS", sizeof(dispatch_key) - 1);
} else if (!strncasecmp(vio_master, "GCE", 3) ||
!strncasecmp(dispatch_key, "GCE", 3)) {
id = INFRA_SUBSYS_GCE;
strncpy(dispatch_key, "GCE", sizeof(dispatch_key) - 1);
} else if (!strncasecmp(vio_master, "AUDIO", 5)) {
id = INFRA_SUBSYS_AUDIO;
strncpy(dispatch_key, "AUDIO", sizeof(dispatch_key) - 1);
} 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 */
aee_kernel_exception("[DEVAPC]",
"%s%s\n",
"CRDISPATCH_KEY:Device APC Violation Issue/",
dispatch_key
);
} else if (dbg_stat->enable_WARN) {
WARN(1, "Device APC Violation Issue/%s", dispatch_key);
}
}
/*
* 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;
const struct mtk_device_info **device_info;
struct mtk_devapc_vio_info *vio_info;
int slave_type, vio_idx, index;
const char *vio_master;
unsigned long flags;
uint8_t perm;
bool normal;
spin_lock_irqsave(&devapc_lock, flags);
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++) {
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(true);
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);
/*
* 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 *devapc_ao_base;
void __iomem *sramrom_base = mtk_devapc_ctx->sramrom_base;
pr_info(PFX "%s, cmd:0x%x\n", __func__, cmd);
devapc_ao_base = mtk_devapc_ctx->devapc_infra_ao_base;
if (cmd == DEVAPC_UT_DAPC_VIO) {
if (unlikely(devapc_ao_base == NULL)) {
pr_err(PFX "%s:%d NULL pointer\n", __func__, __LINE__);
return;
}
pr_info(PFX "%s, devapc_ao_infra_base:0x%x\n", __func__,
readl(devapc_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_info(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, &param);
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 file_operations devapc_dbg_fops = {
.owner = THIS_MODULE,
.write = mtk_devapc_dbg_write,
.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, &param);
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 */
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;
int slave_type;
int ret;
pr_info(PFX "driver registered\n");
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;
for (slave_type = 0; slave_type < slave_type_num; slave_type++) {
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;
}
}
mtk_devapc_ctx->devapc_infra_ao_base = of_iomap(node, slave_type_num);
if (unlikely(mtk_devapc_ctx->devapc_infra_ao_base == NULL)) {
pr_err(PFX "parse devapc_infra_ao_base failed\n");
return -EINVAL;
}
mtk_devapc_ctx->infracfg_base = of_iomap(node, slave_type_num + 1);
if (unlikely(mtk_devapc_ctx->infracfg_base == NULL)) {
pr_err(PFX "parse infracfg_base failed\n");
return -EINVAL;
}
mtk_devapc_ctx->devapc_irq = irq_of_parse_and_map(node, 0);
if (!mtk_devapc_ctx->devapc_irq) {
pr_err(PFX "parse and map the interrupt failed\n");
return -EINVAL;
}
for (slave_type = 0; slave_type < slave_type_num; slave_type++)
pr_debug(PFX "%s:0x%x %s:0x%px\n",
"slave_type", slave_type,
"devapc_pd_base",
mtk_devapc_ctx->devapc_pd_base[slave_type]);
pr_debug(PFX " IRQ:%d\n", mtk_devapc_ctx->devapc_irq);
/* 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 (%d)\n",
PTR_ERR(mtk_devapc_ctx->devapc_infra_clk));
proc_create("devapc_dbg", 0664, NULL, &devapc_dbg_fops);
#ifdef CONFIG_DEVAPC_SWP_SUPPORT
devapc_swp_ctx->devapc_swp_base = of_iomap(node, slave_type_num + 2);
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, slave_type_num + 3);
if (unlikely(mtk_devapc_ctx->sramrom_base == NULL))
pr_info(PFX "parse sramrom_base failed\n");
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;
}
}
start_devapc();
ret = devm_request_irq(&pdev->dev, mtk_devapc_ctx->devapc_irq,
(irq_handler_t)devapc_violation_irq,
IRQF_TRIGGER_NONE, "devapc", NULL);
if (ret) {
pr_err(PFX "request devapc irq failed, ret:%d\n", ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(mtk_devapc_probe);
int mtk_devapc_remove(struct platform_device *dev)
{
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("Neal Liu <neal.liu@mediatek.com>");
MODULE_LICENSE("GPL");