437 lines
12 KiB
C
437 lines
12 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
|
|
#include <generated/autoconf.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <mt-plat/aee.h>
|
|
#include <mt-plat/upmu_common.h>
|
|
#ifdef CONFIG_MTK_PMIC_WRAP_HAL
|
|
#include <mach/mtk_pmic_wrap.h>
|
|
#endif
|
|
#include <mach/mtk_pmic.h>
|
|
#include "include/pmic.h"
|
|
#include "include/pmic_irq.h"
|
|
|
|
#include <mt-plat/mtk_ccci_common.h>
|
|
#include <linux/mfd/mt6358/core.h>
|
|
|
|
struct legacy_pmic_callback {
|
|
bool has_requested;
|
|
void (*callback)(void);
|
|
};
|
|
static struct device *pmic_dev;
|
|
static struct legacy_pmic_callback pmic_cbs[300];
|
|
|
|
/* KEY Int Handler */
|
|
irqreturn_t key_int_handler(int irq, void *data)
|
|
{
|
|
#if !defined(CONFIG_FPGA_EARLY_PORTING) && defined(CONFIG_KPD_PWRKEY_USE_PMIC)
|
|
struct irq_desc *desc = irq_to_desc(irq);
|
|
unsigned int hwirq;
|
|
|
|
if (desc)
|
|
hwirq = irqd_to_hwirq(&desc->irq_data);
|
|
else
|
|
return IRQ_HANDLED;
|
|
|
|
switch (hwirq) {
|
|
case INT_PWRKEY:
|
|
IRQLOG("Press pwrkey %d\n",
|
|
pmic_get_register_value(PMIC_PWRKEY_DEB));
|
|
kpd_pwrkey_pmic_handler(0x1);
|
|
break;
|
|
case INT_PWRKEY_R:
|
|
IRQLOG("Release pwrkey %d\n",
|
|
pmic_get_register_value(PMIC_PWRKEY_DEB));
|
|
kpd_pwrkey_pmic_handler(0x0);
|
|
break;
|
|
case INT_HOMEKEY:
|
|
IRQLOG("Press homekey %d\n",
|
|
pmic_get_register_value(PMIC_HOMEKEY_DEB));
|
|
kpd_pmic_rstkey_handler(0x1);
|
|
break;
|
|
case INT_HOMEKEY_R:
|
|
IRQLOG("Release homekey %d\n",
|
|
pmic_get_register_value(PMIC_HOMEKEY_DEB));
|
|
kpd_pmic_rstkey_handler(0x0);
|
|
break;
|
|
}
|
|
#endif
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t legacy_pmic_int_handler(int irq, void *data)
|
|
{
|
|
struct legacy_pmic_callback *pmic_cb = data;
|
|
|
|
pmic_cb->callback();
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* PMIC Interrupt service
|
|
*/
|
|
void pmic_enable_interrupt(enum PMIC_IRQ_ENUM intNo, unsigned int en, char *str)
|
|
{
|
|
int ret;
|
|
unsigned int irq;
|
|
const char *name;
|
|
struct legacy_pmic_callback *pmic_cb = &pmic_cbs[intNo];
|
|
struct irq_desc *desc;
|
|
|
|
if (intNo == INT_ENUM_MAX) {
|
|
pr_notice(PMICTAG "[%s] disable intNo=%d\n", __func__, intNo);
|
|
return;
|
|
} else if (pmic_cb->callback == NULL) {
|
|
pr_notice(PMICTAG "[%s] No callback at intNo=%d\n",
|
|
__func__, intNo);
|
|
return;
|
|
}
|
|
irq = mt6358_irq_get_virq(pmic_dev->parent, intNo);
|
|
if (!irq) {
|
|
pr_notice(PMICTAG "[%s] fail intNo=%d\n", __func__, intNo);
|
|
return;
|
|
}
|
|
name = mt6358_irq_get_name(pmic_dev->parent, intNo);
|
|
if (name == NULL) {
|
|
pr_notice(PMICTAG "[%s] no irq name at intNo=%d\n",
|
|
__func__, intNo);
|
|
return;
|
|
}
|
|
if (en == 1) {
|
|
if (!(pmic_cb->has_requested)) {
|
|
ret = devm_request_threaded_irq(pmic_dev, irq, NULL,
|
|
legacy_pmic_int_handler, IRQF_TRIGGER_HIGH,
|
|
name, pmic_cb);
|
|
if (ret < 0)
|
|
pr_notice(PMICTAG "[%s] request %s irq fail\n",
|
|
__func__, name);
|
|
else
|
|
pmic_cb->has_requested = true;
|
|
} else
|
|
enable_irq(irq);
|
|
} else if (en == 0 && pmic_cb->has_requested)
|
|
disable_irq_nosync(irq);
|
|
desc = irq_to_desc(irq);
|
|
IRQLOG("[%s] intNo=%d, en=%d, depth=%d\n",
|
|
__func__, intNo, en, desc ? desc->depth : -1);
|
|
}
|
|
|
|
void pmic_register_interrupt_callback(enum PMIC_IRQ_ENUM intNo,
|
|
void (EINT_FUNC_PTR) (void))
|
|
{
|
|
struct legacy_pmic_callback *pmic_cb = &pmic_cbs[intNo];
|
|
|
|
if (intNo == INT_ENUM_MAX) {
|
|
pr_info(PMICTAG "[%s] disable intNo=%d\n", __func__, intNo);
|
|
return;
|
|
}
|
|
pr_info("[%s] intNo=%d, callback=%pf\n",
|
|
__func__, intNo, EINT_FUNC_PTR);
|
|
pmic_cb->callback = EINT_FUNC_PTR;
|
|
}
|
|
|
|
#if ENABLE_ALL_OC_IRQ
|
|
/* General OC Int Handler */
|
|
static void oc_int_handler(enum PMIC_IRQ_ENUM intNo, const char *int_name)
|
|
{
|
|
unsigned int spNo, sp_conNo, sp_irqNo;
|
|
unsigned int times;
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
char oc_str[30] = "";
|
|
#endif
|
|
|
|
if (pmic_check_intNo(intNo, &spNo, &sp_conNo, &sp_irqNo)) {
|
|
pr_notice(PMICTAG "[%s] fail intNo=%d\n", __func__, intNo);
|
|
return;
|
|
}
|
|
times = sp_interrupts[spNo].sp_irqs[sp_conNo][sp_irqNo].times;
|
|
|
|
IRQLOG("[%s] int name=%s\n", __func__, int_name);
|
|
switch (intNo) {
|
|
case INT_VBIF28_OC:
|
|
case INT_VIO28_OC:
|
|
pmic_enable_interrupt(intNo, 0, "PMIC");
|
|
g_oc_work.oc_intNo = intNo;
|
|
g_oc_work.name = int_name;
|
|
schedule_work(&g_oc_work.work);
|
|
break;
|
|
case INT_VCN33_1_OC:
|
|
case INT_VCN33_2_OC:
|
|
case INT_VA12_OC:
|
|
case INT_VUSB_OC:
|
|
/* keep OC interrupt and keep tracking */
|
|
pr_notice(PMICTAG "[PMIC_INT] PMIC OC: %s\n", int_name);
|
|
if (times >= 10) {
|
|
pmic_enable_interrupt(intNo, 0, "PMIC");
|
|
pr_notice("disable OC interrupt: %s\n", int_name);
|
|
}
|
|
break;
|
|
case INT_VIO18_OC:
|
|
pr_notice("VIO18_PG_DEB=%d,RGS_VIO18_PG_STATUS=%d\n",
|
|
pmic_get_register_value(PMIC_VIO18_PG_DEB),
|
|
pmic_get_register_value(PMIC_RGS_VIO18_PG_STATUS));
|
|
pr_notice("RG_INT_EN_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_EN_VIO18_OC));
|
|
pr_notice("RG_INT_MASK_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_MASK_VIO18_OC));
|
|
pr_notice("RG_INT_STATUS_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_STATUS_VIO18_OC));
|
|
pr_notice("RG_INT_RAW_STATUS_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(
|
|
PMIC_RG_INT_RAW_STATUS_VIO18_OC));
|
|
pr_notice("LDO_VIO18_CON0=0x%x,LDO_VIO18_MON=0x%x\n",
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_CON0),
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_MON));
|
|
pr_notice("LDO_VIO18_OP_EN=0x%x,LDO_VIO18_OP_CFG=0x%x\n",
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_OP_EN),
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_OP_CFG));
|
|
pr_notice("VIO18_ANA_CON0=0x%x,VIO18_ANA_CON1=0x%x\n",
|
|
upmu_get_reg_value(MT6359_VIO18_ANA_CON0),
|
|
upmu_get_reg_value(MT6359_VIO18_ANA_CON1));
|
|
pr_notice("XO_FPM_ISEL_M=0x%x\n",
|
|
pmic_get_register_value(PMIC_XO_FPM_ISEL_M));
|
|
if (times >= 2) {
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
snprintf(oc_str, 30, "PMIC OC:%s", int_name);
|
|
aee_kernel_warning(
|
|
oc_str,
|
|
"\nCRDISPATCH_KEY:PMIC OC\nOC Interrupt: %s",
|
|
int_name);
|
|
#endif
|
|
pmic_enable_interrupt(intNo, 0, "PMIC");
|
|
pr_notice("disable OC interrupt: %s\n", int_name);
|
|
}
|
|
break;
|
|
default:
|
|
/* issue AEE exception and disable OC interrupt */
|
|
if (times >= 3) {
|
|
kernel_dump_exception_reg();
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
snprintf(oc_str, 30, "PMIC OC:%s", int_name);
|
|
aee_kernel_warning(
|
|
oc_str,
|
|
"\nCRDISPATCH_KEY:PMIC OC\nOC Interrupt: %s",
|
|
int_name);
|
|
#endif
|
|
pmic_enable_interrupt(intNo, 0, "PMIC");
|
|
pr_notice("disable OC interrupt: %s\n", int_name);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void md_oc_int_handler(enum PMIC_IRQ_ENUM intNo, const char *int_name)
|
|
{
|
|
int ret = 0;
|
|
int data_int32 = 0;
|
|
#ifdef CONFIG_MTK_CCCI_DEVICES
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
char oc_str[30] = "";
|
|
#endif
|
|
#endif
|
|
unsigned int spNo, sp_conNo, sp_irqNo;
|
|
unsigned int times;
|
|
|
|
if (pmic_check_intNo(intNo, &spNo, &sp_conNo, &sp_irqNo)) {
|
|
pr_notice(PMICTAG "[%s] fail intNo=%d\n", __func__, intNo);
|
|
return;
|
|
}
|
|
times = sp_interrupts[spNo].sp_irqs[sp_conNo][sp_irqNo].times;
|
|
switch (intNo) {
|
|
case INT_VPA_OC:
|
|
data_int32 = 1 << 0;
|
|
break;
|
|
case INT_VFE28_OC:
|
|
data_int32 = 1 << 1;
|
|
pr_notice("Reg[0x1B8A]=0x%x,Reg[0x1B88]=0x%x,Reg[0x1B8C]=0x%x,Reg[0x1B92]=0x%x\n"
|
|
, upmu_get_reg_value(0x1B8A),
|
|
upmu_get_reg_value(0x1B88),
|
|
upmu_get_reg_value(0x1B8C),
|
|
upmu_get_reg_value(0x1B92));
|
|
if (times >= 10) {
|
|
pmic_enable_interrupt(intNo, 0, "PMIC");
|
|
pr_notice("disable OC interrupt: %s\n", int_name);
|
|
}
|
|
break;
|
|
case INT_VRF12_OC:
|
|
data_int32 = 1 << 2;
|
|
break;
|
|
case INT_VRF18_OC:
|
|
data_int32 = 1 << 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#ifdef CONFIG_MTK_CCCI_DEVICES
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
snprintf(oc_str, 30, "PMIC OC:%s", int_name);
|
|
aee_kernel_warning(oc_str, "\nCRDISPATCH_KEY:MD OC\nOC Interrupt: %s"
|
|
, int_name);
|
|
#endif
|
|
ret = exec_ccci_kern_func_by_md_id(MD_SYS1, ID_PMIC_INTR,
|
|
(char *)&data_int32, 4);
|
|
#endif
|
|
if (ret)
|
|
pr_notice("[%s] - exec_ccci_kern_func_by_md_id - msg fail\n"
|
|
, __func__);
|
|
pr_info("[%s]Send msg pass\n", __func__);
|
|
}
|
|
|
|
/* register general oc interrupt handler */
|
|
void pmic_register_oc_interrupt_callback(enum PMIC_IRQ_ENUM intNo)
|
|
{
|
|
unsigned int spNo, sp_conNo, sp_irqNo;
|
|
|
|
if (pmic_check_intNo(intNo, &spNo, &sp_conNo, &sp_irqNo)) {
|
|
pr_notice(PMICTAG "[%s] fail intNo=%d\n", __func__, intNo);
|
|
return;
|
|
}
|
|
IRQLOG("[%s] intNo=%d\n", __func__, intNo);
|
|
switch (intNo) {
|
|
case INT_VPA_OC:
|
|
case INT_VFE28_OC:
|
|
case INT_VRF12_OC:
|
|
case INT_VRF18_OC:
|
|
sp_interrupts[spNo].sp_irqs[sp_conNo][sp_irqNo].oc_callback =
|
|
md_oc_int_handler;
|
|
break;
|
|
default:
|
|
sp_interrupts[spNo].sp_irqs[sp_conNo][sp_irqNo].oc_callback =
|
|
oc_int_handler;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* register and enable all oc interrupt */
|
|
void register_all_oc_interrupts(void)
|
|
{
|
|
enum PMIC_IRQ_ENUM oc_int;
|
|
|
|
/* BUCK OC */
|
|
for (oc_int = INT_VPU_OC; oc_int <= INT_VPA_OC; oc_int++) {
|
|
pmic_register_oc_interrupt_callback(oc_int);
|
|
pmic_enable_interrupt(oc_int, 1, "PMIC");
|
|
}
|
|
/* LDO OC */
|
|
for (oc_int = INT_VFE28_OC; oc_int <= INT_VUFS_OC; oc_int++) {
|
|
switch (oc_int) {
|
|
case INT_VSIM1_OC:
|
|
case INT_VSIM2_OC:
|
|
case INT_VCAMIO_OC:
|
|
IRQLOG("[PMIC_INT] non-enabled OC: %d\n", oc_int);
|
|
break;
|
|
default:
|
|
pmic_register_oc_interrupt_callback(oc_int);
|
|
pmic_enable_interrupt(oc_int, 1, "PMIC");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void vio18_oc_int_handler(void)
|
|
{
|
|
static unsigned int times;
|
|
int len = 0;
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
char oc_str[30] = "";
|
|
#endif
|
|
pr_info("[%s]\n", __func__);
|
|
|
|
pr_notice("VIO18_PG_DEB=%d,RGS_VIO18_PG_STATUS=%d\n",
|
|
pmic_get_register_value(PMIC_VIO18_PG_DEB),
|
|
pmic_get_register_value(PMIC_RGS_VIO18_PG_STATUS));
|
|
pr_notice("RG_INT_EN_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_EN_VIO18_OC));
|
|
pr_notice("RG_INT_MASK_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_MASK_VIO18_OC));
|
|
pr_notice("RG_INT_STATUS_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(PMIC_RG_INT_STATUS_VIO18_OC));
|
|
pr_notice("RG_INT_RAW_STATUS_VIO18_OC=0x%x\n",
|
|
pmic_get_register_value(
|
|
PMIC_RG_INT_RAW_STATUS_VIO18_OC));
|
|
pr_notice("LDO_VIO18_CON0=0x%x,LDO_VIO18_MON=0x%x\n",
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_CON0),
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_MON));
|
|
pr_notice("LDO_VIO18_OP_EN=0x%x,LDO_VIO18_OP_CFG=0x%x\n",
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_OP_EN),
|
|
upmu_get_reg_value(MT6359_LDO_VIO18_OP_CFG));
|
|
pr_notice("VIO18_ANA_CON0=0x%x,VIO18_ANA_CON1=0x%x\n",
|
|
upmu_get_reg_value(MT6359_VIO18_ANA_CON0),
|
|
upmu_get_reg_value(MT6359_VIO18_ANA_CON1));
|
|
pr_notice("XO_FPM_ISEL_M=0x%x\n",
|
|
pmic_get_register_value(PMIC_XO_FPM_ISEL_M));
|
|
#if defined(CONFIG_MTK_AEE_FEATURE)
|
|
len = snprintf(oc_str, 30, "PMIC OC:%s", "INT_VIO18_OC");
|
|
if (len < 0)
|
|
pr_err("[%s] error: snprintf return len < 0\n", __func__);
|
|
|
|
aee_kernel_warning(oc_str,
|
|
"\nCRDISPATCH_KEY:PMIC OC\nOC Interrupt: %s",
|
|
"INT_VIO18_OC");
|
|
#endif
|
|
if (times >= 3)
|
|
pmic_enable_interrupt(INT_VIO18_OC, 0, "PMIC");
|
|
times++;
|
|
pr_notice("disable OC interrupt: INT_VIO18_OC\n");
|
|
}
|
|
|
|
|
|
static void register_vio18_oc_interrupts(void)
|
|
{
|
|
pmic_register_interrupt_callback(INT_VIO18_OC, vio18_oc_int_handler);
|
|
pmic_enable_interrupt(INT_VIO18_OC, 1, "PMIC");
|
|
}
|
|
|
|
void PMIC_EINT_SETTING(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* MT6359 disable VIO18_PG/OC to debug VIO18 OC, must check!! */
|
|
pmic_set_register_value(PMIC_RG_LDO_VIO18_OCFB_EN, 0x0);
|
|
pmic_set_register_value(PMIC_RG_STRUP_VIO18_PG_ENB, 0x1);
|
|
pmic_set_register_value(PMIC_RG_STRUP_VIO18_OC_ENB, 0x1);
|
|
|
|
pmic_dev = &pdev->dev;
|
|
ret = devm_request_threaded_irq(&pdev->dev,
|
|
platform_get_irq_byname(pdev, "pwrkey"),
|
|
NULL, key_int_handler, IRQF_TRIGGER_NONE,
|
|
"pwrkey", NULL);
|
|
if (ret < 0)
|
|
dev_notice(&pdev->dev, "request PWRKEY irq fail\n");
|
|
ret = devm_request_threaded_irq(&pdev->dev,
|
|
platform_get_irq_byname(pdev, "pwrkey_r"),
|
|
NULL, key_int_handler, IRQF_TRIGGER_NONE,
|
|
"pwrkey_r", NULL);
|
|
if (ret < 0)
|
|
dev_notice(&pdev->dev, "request PWRKEY_R irq fail\n");
|
|
ret = devm_request_threaded_irq(&pdev->dev,
|
|
platform_get_irq_byname(pdev, "homekey"),
|
|
NULL, key_int_handler, IRQF_TRIGGER_NONE,
|
|
"homekey", NULL);
|
|
if (ret < 0)
|
|
dev_notice(&pdev->dev, "request HOMEKEY irq fail\n");
|
|
ret = devm_request_threaded_irq(&pdev->dev,
|
|
platform_get_irq_byname(pdev, "homekey_r"),
|
|
NULL, key_int_handler, IRQF_TRIGGER_NONE,
|
|
"homekey_r", NULL);
|
|
if (ret < 0)
|
|
dev_notice(&pdev->dev, "request HOMEKEY_R irq fail\n");
|
|
|
|
register_vio18_oc_interrupts();
|
|
}
|
|
|
|
MODULE_AUTHOR("Jeter Chen");
|
|
MODULE_DESCRIPTION("MT PMIC Interrupt Driver");
|
|
MODULE_LICENSE("GPL");
|
|
|