M7350/kernel/drivers/soc/qcom/cache_m4m_erp64.c

636 lines
17 KiB
C
Raw Normal View History

2024-09-09 08:57:42 +00:00
/*
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "msm_cache_erp64: " fmt
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/cpu.h>
#include <linux/workqueue.h>
#include <linux/of.h>
#include <linux/cpu_pm.h>
#include <linux/smp.h>
#include <soc/qcom/kryo-l2-accessors.h>
/* Instruction cache */
#define ICECR_EL1 S3_1_c11_c1_0
#define ICECR_IRQ_EN (BIT(1) | BIT(3) | BIT(5) | BIT(7))
#define ICESR_EL1 S3_1_c11_c1_1
#define ICESR_BIT_L1DPE BIT(3)
#define ICESR_BIT_L1TPE BIT(2)
#define ICESR_BIT_L0DPE BIT(1)
#define ICESR_BIT_L0TPE BIT(0)
#define ICESYNR0_EL1 S3_1_c11_c1_3
#define ICESYNR1_EL1 S3_1_c11_c1_4
#define ICEAR0_EL1 S3_1_c11_c1_5
#define ICEAR1_EL1 S3_1_c11_c1_6
#define ICESRS_EL1 S3_1_c11_c1_2
/* Data cache */
#define DCECR_EL1 S3_1_c11_c5_0
#define DCECR_IRQ_EN (BIT(1) | BIT(3) | BIT(5) | BIT(7) | \
BIT(9))
#define DCESR_EL1 S3_1_c11_c5_1
#define DCESR_BIT_S1FTLBDPE BIT(4)
#define DCESR_BIT_S1FTLBTPE BIT(3)
#define DCESR_BIT_L1DPE BIT(2)
#define DCESR_BIT_L1PTPE BIT(1)
#define DCESR_BIT_L1VTPE BIT(0)
#define DCESYNR0_EL1 S3_1_c11_c5_3
#define DCESYNR1_EL1 S3_1_c11_c5_4
#define DCESRS_EL1 S3_1_c11_c5_2
#define DCEAR0_EL1 S3_1_c11_c5_5
#define DCEAR1_EL1 S3_1_c11_c5_6
/* L2 cache */
#define L2CPUSRSELR_EL1I S3_3_c15_c0_6
#define L2CPUSRDR_EL1 S3_3_c15_c0_7
#define L2ECR0_IA 0x200
#define L2ECR0_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
BIT(11) | BIT(13) | BIT(16) | \
BIT(19) | BIT(21) | BIT(23) | \
BIT(26) | BIT(29))
#define L2ECR1_IA 0x201
#define L2ECR1_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
BIT(11) | BIT(13) | BIT(16) | \
BIT(19) | BIT(21) | BIT(23) | BIT(29))
#define L2ECR2_IA 0x202
#define L2ECR2_IRQ_EN_MASK 0x3FFFFFF
#define L2ECR2_IRQ_EN (BIT(1) | BIT(3) | BIT(6) | BIT(9) | \
BIT(12) | BIT(15) | BIT(17) | \
BIT(19) | BIT(22) | BIT(25))
#define L2ESR0_IA 0x204
#define L2ESR0_MASK 0x00FFFFFF
#define L2ESR0_CE ((BIT(0) | BIT(1) | BIT(2) | BIT(3) | \
BIT(4) | BIT(5) | BIT(12) | BIT(13) | \
BIT(14) | BIT(15) | BIT(16) | BIT(17)) \
& L2ESR0_MASK)
#define L2ESR0_UE (~L2ESR0_CE & L2ESR0_MASK)
#define L2ESRS0_IA 0x205
#define L2ESR1_IA 0x206
#define L2ESR1_MASK 0x80FFFBFF
#define L2ESRS1_IA 0x207
#define L2ESYNR0_IA 0x208
#define L2ESYNR1_IA 0x209
#define L2ESYNR2_IA 0x20A
#define L2ESYNR3_IA 0x20B
#define L2ESYNR4_IA 0x20C
#define L2EAR0_IA 0x20E
#define L2EAR1_IA 0x20F
#define L3_QLL_HML3_FIRA 0x3000
#define L3_QLL_HML3_FIRA_CE (BIT(1) | BIT(3) | BIT(5))
#define L3_QLL_HML3_FIRA_UE (BIT(2) | BIT(4) | BIT(6))
#define L3_QLL_HML3_FIRAC 0x3008
#define L3_QLL_HML3_FIRAS 0x3010
#define L3_QLL_HML3_FIRAT0C 0x3020
#define L3_QLL_HML3_FIRAT0C_IRQ_EN 0xFFFFFFFF
#define L3_QLL_HML3_FIRAT1C 0x3024
#define L3_QLL_HML3_FIRAT1S 0x302C
#define L3_QLL_HML3_FIRAT1S_IRQ_EN 0x01EFC8FE
#define L3_QLL_HML3_FIRSYNA 0x3100
#define L3_QLL_HML3_FIRSYNB 0x3104
#define L3_QLL_HML3_FIRSYNC 0x3108
#define L3_QLL_HML3_FIRSYND 0x310C
#define M4M_ERR_STATUS 0x10000
#define M4M_ERR_STATUS_MASK 0x1FF
#define M4M_ERR_Q22SIB_RET_DEC_ERR (BIT(7))
#define M4M_ERR_Q22SIB_RET_SLV_ERR (BIT(6))
#define M4M_ERR_CLR 0x10008
#define M4M_INT_CTRL 0x10010
#define M4M_INT_CTRL_IRQ_EN 0x1FF
#define M4M_ERR_CTRL 0x10018
#define M4M_ERR_INJ 0x10020
#define M4M_ERR_CAP_0 0x10030
#define M4M_ERR_CAP_1 0x10038
#define M4M_ERR_CAP_2 0x10040
#define M4M_ERR_CAP_3 0x10048
#define AFFINITY_LEVEL_L3 3
#ifdef CONFIG_MSM_CACHE_M4M_ERP64_PANIC_ON_CE
static bool __read_mostly panic_on_ce = true;
#else
static bool __read_mostly panic_on_ce;
#endif
#ifdef CONFIG_MSM_CACHE_M4M_ERP64_PANIC_ON_UE
static bool __read_mostly panic_on_ue = true;
#else
static bool __read_mostly panic_on_ue;
#endif
module_param(panic_on_ce, bool, false);
module_param(panic_on_ue, bool, false);
static void __iomem *hml3_base;
static void __iomem *m4m_base;
enum erp_irq_index { IRQ_L1, IRQ_L2_INFO0, IRQ_L2_INFO1, IRQ_L2_ERR0,
IRQ_L2_ERR1, IRQ_L3, IRQ_M4M, IRQ_MAX };
static const char * const erp_irq_names[] = {
"l1_irq", "l2_irq_info_0", "l2_irq_info_1", "l2_irq_err_0",
"l2_irq_err_1", "l3_irq", "m4m_irq"
};
static int erp_irqs[IRQ_MAX];
struct msm_l1_err_stats {
/* nothing */
};
static DEFINE_PER_CPU(struct msm_l1_err_stats, msm_l1_erp_stats);
static DEFINE_PER_CPU(struct call_single_data, handler_csd);
#define erp_mrs(reg) ({ \
u64 __val; \
asm volatile("mrs %0, " __stringify(reg) : "=r" (__val)); \
__val; \
})
#define erp_msr(reg, val) { \
asm volatile("msr " __stringify(reg) ", %0" : : "r" (val)); \
}
static void msm_erp_show_icache_error(void)
{
u64 icesr;
int cpu = raw_smp_processor_id();
icesr = erp_mrs(ICESR_EL1);
if (!(icesr & (ICESR_BIT_L0TPE | ICESR_BIT_L0DPE | ICESR_BIT_L1TPE |
ICESR_BIT_L1DPE))) {
pr_debug("CPU%d: No I-cache error detected ICESR 0x%llx\n",
cpu, icesr);
goto clear_out;
}
pr_alert("CPU%d: I-cache error\n", cpu);
pr_alert("CPU%d: ICESR_EL1 0x%llx ICESYNR0 0x%llx ICESYNR1 0x%llx ICEAR0 0x%llx IECAR1 0x%llx\n",
cpu, icesr, erp_mrs(ICESYNR0_EL1), erp_mrs(ICESYNR1_EL1),
erp_mrs(ICEAR0_EL1), erp_mrs(ICEAR1_EL1));
/*
* all detectable I-cache erros are recoverable as
* corrupted lines are refetched
*/
if (panic_on_ce)
BUG_ON(1);
else
WARN_ON(1);
clear_out:
erp_msr(ICESR_EL1, icesr);
}
static void msm_erp_show_dcache_error(void)
{
u64 dcesr;
int cpu = raw_smp_processor_id();
dcesr = erp_mrs(DCESR_EL1);
if (!(dcesr & (DCESR_BIT_L1VTPE | DCESR_BIT_L1PTPE | DCESR_BIT_L1DPE |
DCESR_BIT_S1FTLBTPE | DCESR_BIT_S1FTLBDPE))) {
pr_debug("CPU%d: No D-cache error detected DCESR 0x%llx\n",
cpu, dcesr);
goto clear_out;
}
pr_alert("CPU%d: D-cache error detected\n", cpu);
pr_alert("CPU%d: L1 DCESR 0x%llx, DCESYNR0 0x%llx, DCESYNR1 0x%llx, DCEAR0 0x%llx, DCEAR1 0x%llx\n",
cpu, dcesr, erp_mrs(DCESYNR0_EL1), erp_mrs(DCESYNR1_EL1),
erp_mrs(DCEAR0_EL1), erp_mrs(DCEAR1_EL1));
/* all D-cache erros are correctable */
if (panic_on_ce)
BUG_ON(1);
else
WARN_ON(1);
clear_out:
erp_msr(DCESR_EL1, dcesr);
}
static irqreturn_t msm_l1_erp_irq(int irq, void *dev_id)
{
msm_erp_show_icache_error();
msm_erp_show_dcache_error();
return IRQ_HANDLED;
}
static DEFINE_SPINLOCK(local_handler_lock);
static void msm_l2_erp_local_handler(void *force)
{
unsigned long flags;
u64 esr0, esr1;
bool parity_ue, parity_ce, misc_ue;
int cpu;
spin_lock_irqsave(&local_handler_lock, flags);
esr0 = get_l2_indirect_reg(L2ESR0_IA);
esr1 = get_l2_indirect_reg(L2ESR1_IA);
parity_ue = esr0 & L2ESR0_UE;
parity_ce = esr0 & L2ESR0_CE;
misc_ue = esr1;
cpu = raw_smp_processor_id();
if (force || parity_ue || parity_ce || misc_ue) {
if (parity_ue)
pr_alert("CPU%d: L2 uncorrectable parity error\n", cpu);
if (parity_ce)
pr_alert("CPU%d: L2 correctable parity error\n", cpu);
if (misc_ue)
pr_alert("CPU%d: L2 (non-parity) error\n", cpu);
pr_alert("CPU%d: L2ESR0 0x%llx, L2ESR1 0x%llx\n",
cpu, esr0, esr1);
pr_alert("CPU%d: L2ESYNR0 0x%llx, L2ESYNR1 0x%llx, L2ESYNR2 0x%llx\n",
cpu, get_l2_indirect_reg(L2ESYNR0_IA),
get_l2_indirect_reg(L2ESYNR1_IA),
get_l2_indirect_reg(L2ESYNR2_IA));
pr_alert("CPU%d: L2EAR0 0x%llx, L2EAR1 0x%llx\n", cpu,
get_l2_indirect_reg(L2EAR0_IA),
get_l2_indirect_reg(L2EAR1_IA));
} else {
pr_info("CPU%d: No L2 error detected in L2ESR0 0x%llx, L2ESR1 0x%llx)\n",
cpu, esr0, esr1);
}
/* clear */
set_l2_indirect_reg(L2ESR0_IA, esr0);
set_l2_indirect_reg(L2ESR1_IA, esr1);
if (panic_on_ue)
BUG_ON(parity_ue || misc_ue);
else
WARN_ON(parity_ue || misc_ue);
if (panic_on_ce)
BUG_ON(parity_ce);
else
WARN_ON(parity_ce);
spin_unlock_irqrestore(&local_handler_lock, flags);
}
static irqreturn_t msm_l2_erp_irq(int irq, void *dev_id)
{
int cpu;
struct call_single_data *csd;
for_each_online_cpu(cpu) {
csd = &per_cpu(handler_csd, cpu);
csd->func = msm_l2_erp_local_handler;
smp_call_function_single_async(cpu, csd);
}
return IRQ_HANDLED;
}
static irqreturn_t msm_l3_erp_irq(int irq, void *dev_id)
{
u32 hml3_fira;
bool parity_ue, parity_ce, misc_ue;
hml3_fira = readl_relaxed(hml3_base + L3_QLL_HML3_FIRA);
parity_ue = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
L3_QLL_HML3_FIRA_UE;
parity_ce = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
L3_QLL_HML3_FIRA_CE;
misc_ue = (hml3_fira & L3_QLL_HML3_FIRAT1S_IRQ_EN) &
~(L3_QLL_HML3_FIRA_UE | L3_QLL_HML3_FIRA_CE);
if (parity_ue)
pr_alert("L3 uncorrectable parity error\n");
if (parity_ce)
pr_alert("L3 correctable parity error\n");
if (misc_ue)
pr_alert("L3 (non-parity) error\n");
pr_alert("HML3_FIRA 0x%0x\n", hml3_fira);
pr_alert("HML3_FIRSYNA 0x%0x, HML3_FIRSYNB 0x%0x\n",
readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNA),
readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNB));
pr_alert("HML3_FIRSYNC 0x%0x, HML3_FIRSYND 0x%0x\n",
readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYNC),
readl_relaxed(hml3_base + L3_QLL_HML3_FIRSYND));
if (panic_on_ue)
BUG_ON(parity_ue || misc_ue);
else
WARN_ON(parity_ue || misc_ue);
if (panic_on_ce)
BUG_ON(parity_ce);
else
WARN_ON(parity_ce);
writel_relaxed(hml3_fira, hml3_base + L3_QLL_HML3_FIRAC);
/* ensure of irq clear */
wmb();
return IRQ_HANDLED;
}
static irqreturn_t msm_m4m_erp_irq(int irq, void *dev_id)
{
u32 m4m_status;
pr_alert("CPU%d: M4M error detected\n", raw_smp_processor_id());
m4m_status = readl_relaxed(m4m_base + M4M_ERR_STATUS);
pr_alert("M4M_ERR_STATUS 0x%0x\n", m4m_status);
if ((m4m_status & M4M_ERR_STATUS_MASK) &
~(M4M_ERR_Q22SIB_RET_DEC_ERR | M4M_ERR_Q22SIB_RET_SLV_ERR)) {
pr_alert("M4M_ERR_CAP_0 0x%0x, M4M_ERR_CAP_1 0x%x\n",
readl_relaxed(m4m_base + M4M_ERR_CAP_0),
readl_relaxed(m4m_base + M4M_ERR_CAP_1));
pr_alert("M4M_ERR_CAP_2 0x%0x, M4M_ERR_CAP_3 0x%x\n",
readl_relaxed(m4m_base + M4M_ERR_CAP_2),
readl_relaxed(m4m_base + M4M_ERR_CAP_3));
} else {
/*
* M4M error-capture registers not valid when error detected
* due to DEC_ERR or SLV_ERR. L2E registers are still valid.
*/
pr_alert("Omit dumping M4M_ERR_CAP\n");
}
/*
* On QSB errors, the L2 captures the bad address and syndrome in
* L2E error registers. Therefore dump L2E always whenever M4M error
* detected.
*/
on_each_cpu(msm_l2_erp_local_handler, (void *)1, 1);
writel_relaxed(1, m4m_base + M4M_ERR_CLR);
/* ensure of irq clear */
wmb();
if (panic_on_ue)
BUG_ON(1);
else
WARN_ON(1);
return IRQ_HANDLED;
}
static void enable_erp_irq_callback(void *info)
{
enable_percpu_irq(erp_irqs[IRQ_L1], IRQ_TYPE_NONE);
}
static void disable_erp_irq_callback(void *info)
{
disable_percpu_irq(erp_irqs[IRQ_L1]);
}
static void msm_cache_erp_irq_init(void *param)
{
u64 v;
/* Enable L0/L1 I/D cache error reporting. */
erp_msr(ICECR_EL1, ICECR_IRQ_EN);
erp_msr(DCECR_EL1, DCECR_IRQ_EN);
/*
* Enable L2 data, tag, QSB and possion error reporting.
*/
set_l2_indirect_reg(L2ECR0_IA, L2ECR0_IRQ_EN);
set_l2_indirect_reg(L2ECR1_IA, L2ECR1_IRQ_EN);
v = (get_l2_indirect_reg(L2ECR2_IA) & ~L2ECR2_IRQ_EN_MASK)
| L2ECR2_IRQ_EN;
set_l2_indirect_reg(L2ECR2_IA, v);
}
static void msm_cache_erp_l3_init(void)
{
writel_relaxed(L3_QLL_HML3_FIRAT0C_IRQ_EN,
hml3_base + L3_QLL_HML3_FIRAT0C);
writel_relaxed(L3_QLL_HML3_FIRAT1S_IRQ_EN,
hml3_base + L3_QLL_HML3_FIRAT1S);
}
static int cache_erp_cpu_pm_callback(struct notifier_block *self,
unsigned long cmd, void *v)
{
unsigned long aff_level = (unsigned long) v;
switch (cmd) {
case CPU_CLUSTER_PM_EXIT:
msm_cache_erp_irq_init(NULL);
if (aff_level >= AFFINITY_LEVEL_L3)
msm_cache_erp_l3_init();
break;
}
return NOTIFY_OK;
}
static struct notifier_block cache_erp_cpu_pm_notifier = {
.notifier_call = cache_erp_cpu_pm_callback,
};
static int cache_erp_cpu_callback(struct notifier_block *nfb,
unsigned long action, void *hcpu)
{
switch (action & ~CPU_TASKS_FROZEN) {
case CPU_STARTING:
msm_cache_erp_irq_init(NULL);
enable_erp_irq_callback(NULL);
break;
case CPU_DYING:
disable_erp_irq_callback(NULL);
break;
}
return NOTIFY_OK;
}
static struct notifier_block cache_erp_cpu_notifier = {
.notifier_call = cache_erp_cpu_callback,
};
static int msm_cache_erp_probe(struct platform_device *pdev)
{
int i, ret = 0;
struct resource *r;
dev_dbg(&pdev->dev, "enter\n");
/* L3 */
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hml3_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(hml3_base)) {
dev_err(&pdev->dev, "failed to ioremap (0x%p)\n", hml3_base);
return PTR_ERR(hml3_base);
}
for (i = 0; i <= IRQ_L3; i++) {
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
erp_irq_names[i]);
if (!r) {
dev_err(&pdev->dev, "failed to get %s\n",
erp_irq_names[i]);
return -ENODEV;
}
erp_irqs[i] = r->start;
}
msm_cache_erp_l3_init();
/* L0/L1 erp irq per cpu */
dev_info(&pdev->dev, "Registering for L1 error interrupts\n");
ret = request_percpu_irq(erp_irqs[IRQ_L1], msm_l1_erp_irq,
erp_irq_names[IRQ_L1], &msm_l1_erp_stats);
if (ret) {
dev_err(&pdev->dev, "failed to request L0/L1 ERP irq %s (%d)\n",
erp_irq_names[IRQ_L1], ret);
return ret;
} else {
dev_dbg(&pdev->dev, "requested L0/L1 ERP irq %s\n",
erp_irq_names[IRQ_L1]);
}
get_online_cpus();
register_hotcpu_notifier(&cache_erp_cpu_notifier);
cpu_pm_register_notifier(&cache_erp_cpu_pm_notifier);
/* Perform L1/L2 cache error detection init on online cpus */
on_each_cpu(msm_cache_erp_irq_init, NULL, 1);
/* Enable irqs */
on_each_cpu(enable_erp_irq_callback, NULL, 1);
put_online_cpus();
/* L2 erp irq per cluster */
dev_info(&pdev->dev, "Registering for L2 error interrupts\n");
for (i = IRQ_L2_INFO0; i <= IRQ_L2_ERR1; i++) {
ret = devm_request_irq(&pdev->dev, erp_irqs[i],
msm_l2_erp_irq,
IRQF_ONESHOT |
IRQF_TRIGGER_HIGH,
erp_irq_names[i], NULL);
if (ret) {
dev_err(&pdev->dev, "failed to request irq %s (%d)\n",
erp_irq_names[i], ret);
goto cleanup;
}
}
/* L3 erp irq */
dev_info(&pdev->dev, "Registering for L3 error interrupts\n");
ret = devm_request_irq(&pdev->dev, erp_irqs[IRQ_L3], msm_l3_erp_irq,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
erp_irq_names[IRQ_L3], NULL);
if (ret) {
dev_err(&pdev->dev, "failed to request L3 irq %s (%d)\n",
erp_irq_names[IRQ_L3], ret);
goto cleanup;
}
return 0;
cleanup:
free_percpu_irq(erp_irqs[IRQ_L1], NULL);
return ret;
}
static void msm_m4m_erp_irq_init(void)
{
writel_relaxed(M4M_INT_CTRL_IRQ_EN, m4m_base + M4M_INT_CTRL);
writel_relaxed(0, m4m_base + M4M_ERR_CTRL);
}
static int msm_m4m_erp_m4m_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *r;
dev_dbg(&pdev->dev, "enter\n");
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
m4m_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(m4m_base)) {
dev_err(&pdev->dev, "failed to ioremap (0x%p)\n", m4m_base);
return PTR_ERR(m4m_base);
}
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
erp_irq_names[IRQ_M4M]);
if (!r) {
dev_err(&pdev->dev, "failed to get %s\n",
erp_irq_names[IRQ_M4M]);
ret = -ENODEV;
goto exit;
}
erp_irqs[IRQ_M4M] = r->start;
dev_info(&pdev->dev, "Registering for M4M error interrupts\n");
ret = devm_request_irq(&pdev->dev, erp_irqs[IRQ_M4M],
msm_m4m_erp_irq,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
erp_irq_names[IRQ_M4M], NULL);
if (ret) {
dev_err(&pdev->dev, "failed to request irq %s (%d)\n",
erp_irq_names[IRQ_M4M], ret);
goto exit;
}
msm_m4m_erp_irq_init();
exit:
return ret;
}
static struct of_device_id cache_erp_dt_ids[] = {
{ .compatible = "qcom,kryo_cache_erp64", },
{}
};
MODULE_DEVICE_TABLE(of, cache_erp_dt_ids);
static struct platform_driver msm_cache_erp_driver = {
.probe = msm_cache_erp_probe,
.driver = {
.name = "msm_cache_erp64",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(cache_erp_dt_ids),
},
};
static struct of_device_id m4m_erp_dt_ids[] = {
{ .compatible = "qcom,m4m_erp", },
{}
};
MODULE_DEVICE_TABLE(of, m4m_erp_dt_ids);
static struct platform_driver msm_m4m_erp_driver = {
.probe = msm_m4m_erp_m4m_probe,
.driver = {
.name = "msm_m4m_erp",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(m4m_erp_dt_ids),
},
};
static int __init msm_cache_erp_init(void)
{
int r;
r = platform_driver_register(&msm_cache_erp_driver);
if (!r)
r = platform_driver_register(&msm_m4m_erp_driver);
if (r)
pr_err("failed to register driver %d\n", r);
return r;
}
arch_initcall(msm_cache_erp_init);