diff --git a/drivers/perf/qcom_llcc_pmu.c b/drivers/perf/qcom_llcc_pmu.c index e5e50244a35a..ed82c97ed37d 100644 --- a/drivers/perf/qcom_llcc_pmu.c +++ b/drivers/perf/qcom_llcc_pmu.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -14,11 +15,17 @@ #include #include +enum llcc_pmu_version { + LLCC_PMU_VER1 = 1, + LLCC_PMU_VER2, +}; + struct llcc_pmu { struct pmu pmu; struct hlist_node node; void __iomem *lagg_base; struct perf_event event; + enum llcc_pmu_version ver; }; #define MON_CFG(m) ((m)->lagg_base + 0x200) @@ -26,9 +33,10 @@ struct llcc_pmu { #define to_llcc_pmu(ptr) (container_of(ptr, struct llcc_pmu, pmu)) #define LLCC_RD_EV 0x1000 -#define ENABLE 0x01 +#define ENABLE 0x1 #define CLEAR 0x10 -#define DISABLE 0x00 +#define CLEAR_POS 16 +#define DISABLE 0x0 #define SCALING_FACTOR 0x3 #define NUM_COUNTERS NR_CPUS #define VALUE_MASK 0xFFFFFF @@ -38,6 +46,93 @@ static unsigned int users; static raw_spinlock_t counter_lock; static raw_spinlock_t users_lock; static ktime_t last_read; +static DEFINE_PER_CPU(unsigned int, users_alive); + +static void mon_disable(struct llcc_pmu *llccpmu, int cpu) +{ + u32 reg; + + if (!llccpmu->ver) { + pr_err("LLCCPMU version not correct\n"); + return; + } + + switch (llccpmu->ver) { + case LLCC_PMU_VER1: + writel_relaxed(DISABLE, MON_CFG(llccpmu)); + break; + case LLCC_PMU_VER2: + reg = readl_relaxed(MON_CFG(llccpmu)); + reg &= ~(ENABLE << cpu); + writel_relaxed(reg, MON_CFG(llccpmu)); + break; + } +} + +static void mon_clear(struct llcc_pmu *llccpmu, int cpu) +{ + int clear_bit = CLEAR_POS + cpu; + u32 reg; + + if (!llccpmu->ver) { + pr_err("LLCCPMU version not correct\n"); + return; + } + + switch (llccpmu->ver) { + case LLCC_PMU_VER1: + writel_relaxed(CLEAR, MON_CFG(llccpmu)); + break; + case LLCC_PMU_VER2: + reg = readl_relaxed(MON_CFG(llccpmu)); + reg |= (ENABLE << clear_bit); + writel_relaxed(reg, MON_CFG(llccpmu)); + reg &= ~(ENABLE << clear_bit); + writel_relaxed(reg, MON_CFG(llccpmu)); + break; + } +} + +static void mon_enable(struct llcc_pmu *llccpmu, int cpu) +{ + u32 reg; + + if (!llccpmu->ver) { + pr_err("LLCCPMU version not correct\n"); + return; + } + + switch (llccpmu->ver) { + case LLCC_PMU_VER1: + writel_relaxed(ENABLE, MON_CFG(llccpmu)); + break; + case LLCC_PMU_VER2: + reg = readl_relaxed(MON_CFG(llccpmu)); + reg |= (ENABLE << cpu); + writel_relaxed(reg, MON_CFG(llccpmu)); + break; + } +} + +static unsigned long read_cnt(struct llcc_pmu *llccpmu, int cpu) +{ + unsigned long value; + + if (!llccpmu->ver) { + pr_err("LLCCPMU version not correct\n"); + return -EINVAL; + } + + switch (llccpmu->ver) { + case LLCC_PMU_VER1: + value = readl_relaxed(MON_CNT(llccpmu, cpu)); + break; + case LLCC_PMU_VER2: + value = readl_relaxed(MON_CNT(llccpmu, cpu)); + break; + } + return value; +} static int qcom_llcc_event_init(struct perf_event *event) { @@ -58,17 +153,26 @@ static void qcom_llcc_event_read(struct perf_event *event) ktime_t cur; raw_spin_lock_irqsave(&counter_lock, irq_flags); - cur = ktime_get(); - if (ktime_ms_delta(cur, last_read) > 1) { - writel_relaxed(DISABLE, MON_CFG(llccpmu)); - for (i = 0; i < NUM_COUNTERS; i++) { - raw = readl_relaxed(MON_CNT(llccpmu, i)); - raw &= VALUE_MASK; - llcc_stats[i] += (u64) raw << SCALING_FACTOR; + if (llccpmu->ver == LLCC_PMU_VER1) { + cur = ktime_get(); + if (ktime_ms_delta(cur, last_read) > 1) { + mon_disable(llccpmu, cpu); + for (i = 0; i < NUM_COUNTERS; i++) { + raw = read_cnt(llccpmu, i); + raw &= VALUE_MASK; + llcc_stats[i] += (u64) raw << SCALING_FACTOR; + } + last_read = cur; + mon_clear(llccpmu, cpu); + mon_enable(llccpmu, cpu); } - last_read = cur; - writel_relaxed(CLEAR, MON_CFG(llccpmu)); - writel_relaxed(ENABLE, MON_CFG(llccpmu)); + } else { + mon_disable(llccpmu, cpu); + raw = read_cnt(llccpmu, cpu); + raw &= VALUE_MASK; + llcc_stats[cpu] += (u64) raw << SCALING_FACTOR; + mon_clear(llccpmu, cpu); + mon_enable(llccpmu, cpu); } if (!(event->hw.state & PERF_HES_STOPPED)) @@ -92,11 +196,22 @@ static void qcom_llcc_event_stop(struct perf_event *event, int flags) static int qcom_llcc_event_add(struct perf_event *event, int flags) { struct llcc_pmu *llccpmu = to_llcc_pmu(event->pmu); + unsigned int cpu_users; raw_spin_lock(&users_lock); - if (!users) - writel_relaxed(ENABLE, MON_CFG(llccpmu)); - users++; + + if (llccpmu->ver == LLCC_PMU_VER1) { + if (!users) + mon_enable(llccpmu, event->cpu); + users++; + } else { + cpu_users = per_cpu(users_alive, event->cpu); + if (!cpu_users) + mon_enable(llccpmu, event->cpu); + cpu_users++; + per_cpu(users_alive, event->cpu) = cpu_users; + } + raw_spin_unlock(&users_lock); event->hw.state = PERF_HES_STOPPED | PERF_HES_UPTODATE; @@ -110,11 +225,22 @@ static int qcom_llcc_event_add(struct perf_event *event, int flags) static void qcom_llcc_event_del(struct perf_event *event, int flags) { struct llcc_pmu *llccpmu = to_llcc_pmu(event->pmu); + unsigned int cpu_users; raw_spin_lock(&users_lock); - users--; - if (!users) - writel_relaxed(DISABLE, MON_CFG(llccpmu)); + + if (llccpmu->ver == LLCC_PMU_VER1) { + users--; + if (!users) + mon_disable(llccpmu, event->cpu); + } else { + cpu_users = per_cpu(users_alive, event->cpu); + cpu_users--; + if (!cpu_users) + mon_disable(llccpmu, event->cpu); + per_cpu(users_alive, event->cpu) = cpu_users; + } + raw_spin_unlock(&users_lock); } @@ -128,6 +254,13 @@ static int qcom_llcc_pmu_probe(struct platform_device *pdev) if (!llccpmu) return -ENOMEM; + llccpmu->ver = (enum llcc_pmu_version) + of_device_get_match_data(&pdev->dev); + if (!llccpmu->ver) { + pr_err("Unknown device type!\n"); + return -ENODEV; + } + llccpmu->pmu = (struct pmu) { .task_ctx_nr = perf_invalid_context, @@ -161,7 +294,8 @@ static int qcom_llcc_pmu_probe(struct platform_device *pdev) } static const struct of_device_id qcom_llcc_pmu_match_table[] = { - { .compatible = "qcom,qcom-llcc-pmu" }, + { .compatible = "qcom,llcc-pmu-ver1", .data = (void *) LLCC_PMU_VER1 }, + { .compatible = "qcom,llcc-pmu-ver2", .data = (void *) LLCC_PMU_VER2 }, {} };