| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (c) 2019 MediaTek Inc. | 
 |  */ | 
 |  | 
 | #include <linux/cpu.h> | 
 | #include <linux/cpu_pm.h> | 
 | #include <linux/perf_event.h> | 
 |  | 
 | #if (IS_ENABLED(CONFIG_ARM64) || IS_ENABLED(CONFIG_ARM)) | 
 | #include <linux/platform_device.h> | 
 | #include <linux/perf/arm_pmu.h> | 
 | #endif | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/perf/arm_pmu.h> | 
 | #include <linux/irqreturn.h> | 
 | #include <linux/irq_work.h> | 
 | #include "met_drv.h" | 
 | #include "met_kernel_symbol.h" | 
 | #include "interface.h" | 
 | #include "trace.h" | 
 | #include "cpu_pmu.h" | 
 | #include "mtk_typedefs.h" | 
 |  | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) | 
 | #include "sspm/ondiemet_sspm.h" | 
 | #elif defined(TINYSYS_SSPM_SUPPORT) | 
 | #include "tinysys_sspm.h" | 
 | #include "tinysys_mgr.h" /* for ondiemet_module */ | 
 | #include "sspm_met_ipi_handle.h" | 
 | #endif | 
 | #endif | 
 |  | 
 | struct cpu_pmu_hw *cpu_pmu; | 
 | static int counter_cnt[MXNR_CPU]; | 
 | static int nr_arg[MXNR_CPU]; | 
 |  | 
 | int met_perf_cpupmu_status; | 
 |  | 
 | static int mtk_pmu_event_enable = 0; | 
 | static struct kobject *kobj_cpu; | 
 | DECLARE_KOBJ_ATTR_INT(mtk_pmu_event_enable, mtk_pmu_event_enable); | 
 | #define KOBJ_ATTR_LIST \ | 
 | 	do { \ | 
 | 		KOBJ_ATTR_ITEM(mtk_pmu_event_enable); \ | 
 | 	} while (0) | 
 |  | 
 | DEFINE_MUTEX(handle_irq_lock); | 
 | irqreturn_t (*handle_irq_orig)(struct arm_pmu *pmu); | 
 |  | 
 | #if IS_ENABLED(CONFIG_CPU_PM) | 
 | static int use_cpu_pm_pmu_notifier = 0; | 
 |  | 
 | /* helper notifier for maintaining pmu states before cpu state transition */ | 
 | static int cpu_pm_pmu_notify(struct notifier_block *b, | 
 | 			     unsigned long cmd, | 
 | 			     void *p) | 
 | { | 
 | 	int ii; | 
 | 	int cpu, count; | 
 | 	unsigned int pmu_value[MXNR_PMU_EVENTS]; | 
 |  | 
 | 	if (!met_perf_cpupmu_status) | 
 | 		return NOTIFY_OK; | 
 |  | 
 | 	cpu = raw_smp_processor_id(); | 
 |  | 
 | 	switch (cmd) { | 
 | 	case CPU_PM_ENTER: | 
 | 		count = cpu_pmu->polling(cpu_pmu->pmu[cpu], cpu_pmu->event_count[cpu], pmu_value); | 
 | 		for (ii = 0; ii < count; ii ++) | 
 | 			cpu_pmu->cpu_pm_unpolled_loss[cpu][ii] += pmu_value[ii]; | 
 |  | 
 | 		cpu_pmu->stop(cpu_pmu->event_count[cpu]); | 
 | 		break; | 
 | 	case CPU_PM_ENTER_FAILED: | 
 | 	case CPU_PM_EXIT: | 
 | 		cpu_pmu->start(cpu_pmu->pmu[cpu], cpu_pmu->event_count[cpu]); | 
 | 		break; | 
 | 	default: | 
 | 		return NOTIFY_DONE; | 
 | 	} | 
 | 	return NOTIFY_OK; | 
 | } | 
 |  | 
 | struct notifier_block cpu_pm_pmu_notifier = { | 
 | 	.notifier_call = cpu_pm_pmu_notify, | 
 | }; | 
 | #endif | 
 |  | 
 | static DEFINE_PER_CPU(unsigned long long[MXNR_PMU_EVENTS], perfCurr); | 
 | static DEFINE_PER_CPU(unsigned long long[MXNR_PMU_EVENTS], perfPrev); | 
 | static DEFINE_PER_CPU(int[MXNR_PMU_EVENTS], perfCntFirst); | 
 | static DEFINE_PER_CPU(struct perf_event * [MXNR_PMU_EVENTS], pevent); | 
 | static DEFINE_PER_CPU(struct perf_event_attr [MXNR_PMU_EVENTS], pevent_attr); | 
 | static DEFINE_PER_CPU(int, perfSet); | 
 | static DEFINE_PER_CPU(int, cpu_status); | 
 |  | 
 | #ifdef CPUPMU_V8_2 | 
 | #include <linux/of.h> | 
 | #include <linux/of_address.h> | 
 |  | 
 | #ifdef USE_KERNEL_SYNC_WRITE_H | 
 | #include <mt-plat/sync_write.h> | 
 | #else | 
 | #include "sync_write.h" | 
 | #endif | 
 |  | 
 | #ifdef USE_KERNEL_MTK_IO_H | 
 | #include <mt-plat/mtk_io.h> | 
 | #else | 
 | #include "mtk_io.h" | 
 | #endif | 
 |  | 
 | static char mcucfg_desc[] = "mediatek,mcucfg"; | 
 | static void __iomem *mcucfg_base = NULL; | 
 | #define DBG_CONTROL_CPU6	((unsigned long)mcucfg_base + 0x3000 + 0x308)  /* DBG_CONTROL */ | 
 | #define DBG_CONTROL_CPU7	((unsigned long)mcucfg_base + 0x3800 + 0x308)  /* DBG_CONTROL */ | 
 | #define ENABLE_MTK_PMU_EVENTS_OFFSET 1 | 
 | static int restore_dbg_ctrl_cpu6; | 
 | static int restore_dbg_ctrl_cpu7; | 
 |  | 
 | int cpu_pmu_debug_init(void) | 
 | { | 
 | 	struct device_node  *node = NULL; | 
 | 	unsigned int value6,value7; | 
 |  | 
 | 	 /*for A75 MTK internal event*/ | 
 | 	 if (mcucfg_base == NULL) { | 
 | 		node = of_find_compatible_node(NULL, NULL, mcucfg_desc); | 
 | 		if (node == NULL) { | 
 | 			MET_TRACE("[MET_PMU_DB] of_find node == NULL\n"); | 
 | 			pr_debug("[MET_PMU_DB] of_find node == NULL\n"); | 
 | 			goto out; | 
 | 		} | 
 | 		mcucfg_base = of_iomap(node, 0); | 
 | 		of_node_put(node); | 
 | 		if (mcucfg_base == NULL) { | 
 | 			MET_TRACE("[MET_PMU_DB] mcucfg_base == NULL\n"); | 
 | 			pr_debug("[MET_PMU_DB] mcucfg_base == NULL\n"); | 
 | 			goto out; | 
 | 		} | 
 | 		MET_TRACE("[MET_PMU_DB] regbase %08lx\n", DBG_CONTROL_CPU7); | 
 | 		pr_debug("[MET_PMU_DB] regbase %08lx\n", DBG_CONTROL_CPU7); | 
 | 	} | 
 |  | 
 | 	value6 = readl(IOMEM(DBG_CONTROL_CPU6)); | 
 | 	if (value6 & (1 << ENABLE_MTK_PMU_EVENTS_OFFSET)) { | 
 | 		restore_dbg_ctrl_cpu6 = 1; | 
 | 	} else { | 
 | 		restore_dbg_ctrl_cpu6 = 0; | 
 | 		mt_reg_sync_writel(value6 | (1 << ENABLE_MTK_PMU_EVENTS_OFFSET), DBG_CONTROL_CPU6); | 
 | 	} | 
 |  | 
 | 	value7 = readl(IOMEM(DBG_CONTROL_CPU7)); | 
 | 	if (value7 & (1 << ENABLE_MTK_PMU_EVENTS_OFFSET)) { | 
 | 		restore_dbg_ctrl_cpu7 = 1; | 
 | 	} else { | 
 | 		restore_dbg_ctrl_cpu7 = 0; | 
 | 		mt_reg_sync_writel(value7 | (1 << ENABLE_MTK_PMU_EVENTS_OFFSET), DBG_CONTROL_CPU7); | 
 | 	} | 
 |  | 
 | 	value6 = readl(IOMEM(DBG_CONTROL_CPU6)); | 
 | 	value7 = readl(IOMEM(DBG_CONTROL_CPU7)); | 
 | 	MET_TRACE("[MET_PMU_DB]DBG_CONTROL_CPU6 = %08x,  DBG_CONTROL_CPU7 = %08x\n", value6, value7); | 
 | 	pr_debug("[MET_PMU_DB]DBG_CONTROL_CPU6 = %08x,  DBG_CONTROL_CPU7 = %08x\n", value6, value7); | 
 | 	return 1; | 
 |  | 
 | out: | 
 | 	if (mcucfg_base != NULL) { | 
 | 		iounmap(mcucfg_base); | 
 | 		mcucfg_base = NULL; | 
 | 	} | 
 | 	MET_TRACE("[MET_PMU_DB]DBG_CONTROL init error"); | 
 | 	pr_debug("[MET_PMU_DB]DBG_CONTROL init error"); | 
 | 	return 0; | 
 | } | 
 |  | 
 | int cpu_pmu_debug_uninit(void) | 
 | { | 
 | 	unsigned int value6,value7; | 
 |  | 
 | 	if (restore_dbg_ctrl_cpu6 == 0) { | 
 | 		value6 = readl(IOMEM(DBG_CONTROL_CPU6)); | 
 | 		mt_reg_sync_writel(value6 & (~(1 << ENABLE_MTK_PMU_EVENTS_OFFSET)), DBG_CONTROL_CPU6); | 
 | 	} | 
 | 	if (restore_dbg_ctrl_cpu7 == 0) { | 
 | 		value7 = readl(IOMEM(DBG_CONTROL_CPU7)); | 
 | 		mt_reg_sync_writel(value7 & (~(1 << ENABLE_MTK_PMU_EVENTS_OFFSET)), DBG_CONTROL_CPU7); | 
 | 	} | 
 |  | 
 | 	value6 = readl(IOMEM(DBG_CONTROL_CPU6)); | 
 | 	value7 = readl(IOMEM(DBG_CONTROL_CPU7)); | 
 | 	MET_TRACE("[MET_PMU_DB]DBG_CONTROL_CPU6 = %08x,  DBG_CONTROL_CPU7 = %08x\n", value6, value7); | 
 | 	pr_debug("[MET_PMU_DB]DBG_CONTROL_CPU6 = %08x,  DBG_CONTROL_CPU7 = %08x\n", value6, value7); | 
 |  | 
 | 	if (mcucfg_base != NULL) { | 
 | 		iounmap(mcucfg_base); | 
 | 		mcucfg_base = NULL; | 
 | 	} | 
 | 	restore_dbg_ctrl_cpu6 = 0; | 
 | 	restore_dbg_ctrl_cpu7 = 0; | 
 | 	return 1; | 
 | } | 
 | #endif | 
 |  | 
 |  | 
 |  | 
 |  | 
 | noinline void mp_cpu(unsigned char cnt, unsigned int *value) | 
 | { | 
 | 	MET_GENERAL_PRINT(MET_TRACE, cnt, value); | 
 | } | 
 |  | 
 | static void dummy_handler(struct perf_event *event, struct perf_sample_data *data, | 
 | 			  struct pt_regs *regs) | 
 | { | 
 | 	/* | 
 | 	 * Required as perf_event_create_kernel_counter() requires an overflow handler, | 
 | 	 * even though all we do is poll. | 
 | 	 */ | 
 | } | 
 |  | 
 | static void perf_cpupmu_polling(unsigned long long stamp, int cpu) | 
 | { | 
 | 	int			event_count = cpu_pmu->event_count[cpu]; | 
 | 	struct met_pmu		*pmu = cpu_pmu->pmu[cpu]; | 
 | 	int			i, count; | 
 | 	unsigned long long	delta; | 
 | 	struct perf_event	*ev; | 
 | 	unsigned int		pmu_value[MXNR_PMU_EVENTS]; | 
 | 	u64 value; | 
 | 	int ret; | 
 |  | 
 | 	if (per_cpu(perfSet, cpu) == 0) | 
 | 		return; | 
 |  | 
 | 	count = 0; | 
 | 	for (i = 0; i < event_count; i++) { | 
 | 		if (pmu[i].mode == 0) | 
 | 			continue; | 
 |  | 
 | 		ev = per_cpu(pevent, cpu)[i]; | 
 | 		if ((ev != NULL) && (ev->state == PERF_EVENT_STATE_ACTIVE)) { | 
 | 			if (!met_export_api_symbol->met_perf_event_read_local) | 
 | 				continue; | 
 |  | 
 | 			ret = met_export_api_symbol->met_perf_event_read_local(ev, &value); | 
 | 			if (ret < 0) { | 
 | 				PR_BOOTMSG_ONCE("[MET_PMU] perf_event_read_local fail (ret=%d)\n", ret); | 
 | 				pr_debug("[MET_PMU] perf_event_read_local fail (ret=%d)\n", ret); | 
 | 				continue; | 
 | 			} | 
 |  | 
 | 			per_cpu(perfCurr, cpu)[i] = value; | 
 | 			delta = (per_cpu(perfCurr, cpu)[i] - per_cpu(perfPrev, cpu)[i]); | 
 | 			per_cpu(perfPrev, cpu)[i] = per_cpu(perfCurr, cpu)[i]; | 
 | 			if (per_cpu(perfCntFirst, cpu)[i] == 1) { | 
 | 				/* we shall omit delta counter when we get first counter */ | 
 | 				per_cpu(perfCntFirst, cpu)[i] = 0; | 
 | 				continue; | 
 | 			} | 
 | 			pmu_value[count] = (unsigned int)delta; | 
 | 			count++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (count == counter_cnt[cpu]) | 
 | 		mp_cpu(count, pmu_value); | 
 | } | 
 |  | 
 | static struct perf_event* perf_event_create(int cpu, unsigned short event, int count) | 
 | { | 
 | 	struct perf_event_attr	*ev_attr; | 
 | 	struct perf_event	*ev; | 
 |  | 
 | 	ev_attr = per_cpu(pevent_attr, cpu)+count; | 
 | 	memset(ev_attr, 0, sizeof(*ev_attr)); | 
 | 	if (event == 0xff) { | 
 | 		ev_attr->config = PERF_COUNT_HW_CPU_CYCLES; | 
 | 		ev_attr->type = PERF_TYPE_HARDWARE; | 
 | 	} else { | 
 | 		ev_attr->config = event; | 
 | 		ev_attr->type = PERF_TYPE_RAW; | 
 | 	} | 
 | 	ev_attr->size = sizeof(*ev_attr); | 
 | 	ev_attr->sample_period = 0; | 
 | 	ev_attr->pinned = 1; | 
 |  | 
 | 	ev = perf_event_create_kernel_counter(ev_attr, cpu, NULL, dummy_handler, NULL); | 
 | 	if (IS_ERR(ev)) | 
 | 		return NULL; | 
 | 	do { | 
 | 		if (ev->state == PERF_EVENT_STATE_ACTIVE) | 
 | 			break; | 
 | 		if (ev->state == PERF_EVENT_STATE_ERROR) { | 
 | 			perf_event_enable(ev); | 
 | 			if (ev->state == PERF_EVENT_STATE_ACTIVE) | 
 | 				break; | 
 | 		} | 
 | 		perf_event_release_kernel(ev); | 
 | 		return NULL; | 
 | 	} while (0); | 
 |  | 
 | 	return ev; | 
 | } | 
 |  | 
 | static void perf_event_release(int cpu, struct perf_event *ev) | 
 | { | 
 | 	if (ev->state == PERF_EVENT_STATE_ACTIVE) | 
 | 		perf_event_disable(ev); | 
 | 	perf_event_release_kernel(ev); | 
 | } | 
 |  | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) | 
 | #define	PMU_OVERFLOWED_MASK	0xffffffff | 
 |  | 
 | static inline int pmu_has_overflowed(u32 pmovsr) | 
 | { | 
 | 	return pmovsr & PMU_OVERFLOWED_MASK; | 
 | } | 
 |  | 
 | static irqreturn_t perf_event_handle_irq_ignore_overflow(struct arm_pmu *pmu) | 
 | { | 
 | 	u32 pmovsr; | 
 |  | 
 | 	pmovsr = cpu_pmu->pmu_read_clear_overflow_flag(); | 
 |  | 
 | 	if (!pmu_has_overflowed(pmovsr)) { | 
 | 		return IRQ_NONE; | 
 | 	} | 
 | 	else { | 
 | 		irq_work_run(); | 
 | 		return IRQ_HANDLED; | 
 | 	} | 
 | } | 
 | #endif | 
 | #endif | 
 |  | 
 | static int perf_thread_set_perf_events(int cpu) | 
 | { | 
 | 	int			i, size; | 
 | 	struct perf_event	*ev; | 
 |  | 
 | 	size = sizeof(struct perf_event_attr); | 
 | 	if (per_cpu(perfSet, cpu) == 0) { | 
 | 		int event_count = cpu_pmu->event_count[cpu]; | 
 | 		struct met_pmu *pmu = cpu_pmu->pmu[cpu]; | 
 | 		for (i = 0; i < event_count; i++) { | 
 | 			if (!pmu[i].mode) | 
 | 				continue;	/* Skip disabled counters */ | 
 | 			ev = perf_event_create(cpu, pmu[i].event, i); | 
 | 			if (ev == NULL) { | 
 | 				met_cpupmu.mode = 0; | 
 | 				met_perf_cpupmu_status = 0; | 
 |  | 
 | 				MET_TRACE("[MET_PMU] cpu %d failed to register pmu event %4x\n", cpu, pmu[i].event); | 
 | 				pr_notice("[MET_PMU] cpu %d failed to register pmu event %4x\n", cpu, pmu[i].event); | 
 | 				continue; | 
 | 			} | 
 |  | 
 | 			/* | 
 | 			 * in perf-event implementation, hardware pmu slot and cycle counter | 
 | 			 * was mapped to perf_event::hw::idx as follows: | 
 | 			 * | 
 | 			 * | idx | hardware slot | | 
 | 			 * |-----+---------------| | 
 | 			 * |   0 | pmccntr_el0   | | 
 | 			 * |   1 | 0             | | 
 | 			 * |   2 | 1             | | 
 | 			 * |   3 | 2             | | 
 | 			 * |   4 | 3             | | 
 | 			 * |   5 | 4             | | 
 | 			 * |   6 | 5             | | 
 | 			 */ | 
 | 			if (ev->hw.idx != 0) { | 
 | 				MET_TRACE("[MET_PMU] cpu %d registered in pmu slot: [%d] evt=%#04x\n", | 
 | 					  cpu, ev->hw.idx-1, pmu[i].event); | 
 | 				pr_debug("[MET_PMU] cpu %d registered in pmu slot: [%d] evt=%#04x\n", | 
 | 					 cpu, ev->hw.idx-1, pmu[i].event); | 
 | 			} else if (ev->hw.idx == 0) { | 
 | 				MET_TRACE("[MET_PMU] cpu %d registered cycle count evt=%#04x\n", | 
 | 					  cpu, pmu[i].event); | 
 | 				pr_debug("[MET_PMU] cpu %d registered cycle count evt=%#04x\n", | 
 | 					 cpu, pmu[i].event); | 
 | 			} | 
 |  | 
 | 			per_cpu(pevent, cpu)[i] = ev; | 
 | 			per_cpu(perfPrev, cpu)[i] = 0; | 
 | 			per_cpu(perfCurr, cpu)[i] = 0; | 
 | 			perf_event_enable(ev); | 
 | 			per_cpu(perfCntFirst, cpu)[i] = 1; | 
 |  | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) | 
 | 			if (met_cpupmu.ondiemet_mode) { | 
 | 				struct arm_pmu *armpmu; | 
 | 				armpmu = container_of(ev->pmu, struct arm_pmu, pmu); | 
 | 				mutex_lock(&handle_irq_lock); | 
 | 				if (armpmu && armpmu->handle_irq != perf_event_handle_irq_ignore_overflow) { | 
 | 					pr_debug("[MET_PMU] replaced original handle_irq=%p with dummy function\n", | 
 | 						 armpmu->handle_irq); | 
 | 					handle_irq_orig = armpmu->handle_irq; | 
 | 					armpmu->handle_irq = perf_event_handle_irq_ignore_overflow; | 
 | 				} | 
 | 				mutex_unlock(&handle_irq_lock); | 
 | 			} | 
 | #endif | 
 | #endif | 
 | 		}	/* for all PMU counter */ | 
 | 		per_cpu(perfSet, cpu) = 1; | 
 | 	}	/* for perfSet */ | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void met_perf_cpupmu_start(int cpu) | 
 | { | 
 | 	if (met_cpupmu.mode == 0) | 
 | 		return; | 
 |  | 
 | 	perf_thread_set_perf_events(cpu); | 
 | } | 
 |  | 
 | static void perf_thread_down(int cpu) | 
 | { | 
 | 	int			i; | 
 | 	struct perf_event	*ev; | 
 | 	int			event_count; | 
 | 	struct met_pmu		*pmu; | 
 |  | 
 | 	if (per_cpu(perfSet, cpu) == 0) | 
 | 		return; | 
 |  | 
 | 	per_cpu(perfSet, cpu) = 0; | 
 | 	event_count = cpu_pmu->event_count[cpu]; | 
 | 	pmu = cpu_pmu->pmu[cpu]; | 
 | 	for (i = 0; i < event_count; i++) { | 
 | 		ev = per_cpu(pevent, cpu)[i]; | 
 | 		if (ev != NULL) { | 
 |  | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) | 
 | 			if (met_cpupmu.ondiemet_mode) { | 
 | 				struct arm_pmu *armpmu; | 
 | 				armpmu = container_of(ev->pmu, struct arm_pmu, pmu); | 
 | 				mutex_lock(&handle_irq_lock); | 
 | 				if (armpmu && armpmu->handle_irq == perf_event_handle_irq_ignore_overflow) { | 
 | 					pr_debug("[MET_PMU] restore original handle_irq=%p\n", handle_irq_orig); | 
 | 					armpmu->handle_irq = handle_irq_orig; | 
 | 					handle_irq_orig = NULL; | 
 | 				} | 
 | 				mutex_unlock(&handle_irq_lock); | 
 | 			} | 
 | #endif | 
 | #endif | 
 |  | 
 | 			perf_event_release(cpu, ev); | 
 | 			per_cpu(pevent, cpu)[i] = NULL; | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static void met_perf_cpupmu_stop(int cpu) | 
 | { | 
 | 	perf_thread_down(cpu); | 
 | } | 
 |  | 
 | static int cpupmu_create_subfs(struct kobject *parent) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	cpu_pmu = cpu_pmu_hw_init(); | 
 | 	if (cpu_pmu == NULL) { | 
 | 		PR_BOOTMSG("Failed to init CPU PMU HW!!\n"); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	kobj_cpu = parent; | 
 |  | 
 | #define KOBJ_ATTR_ITEM(attr_name) \ | 
 | 	do { \ | 
 | 		ret = sysfs_create_file(kobj_cpu, &attr_name ## _attr.attr); \ | 
 | 		if (ret != 0) { \ | 
 | 			pr_notice("Failed to create " #attr_name " in sysfs\n"); \ | 
 | 			return ret; \ | 
 | 		} \ | 
 | 	} while (0) | 
 | 	KOBJ_ATTR_LIST; | 
 | #undef  KOBJ_ATTR_ITEM | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void cpupmu_delete_subfs(void) | 
 | { | 
 | #define KOBJ_ATTR_ITEM(attr_name) \ | 
 | 	sysfs_remove_file(kobj_cpu, &attr_name ## _attr.attr) | 
 |  | 
 | 	if (kobj_cpu != NULL) { | 
 | 		KOBJ_ATTR_LIST; | 
 | 		kobj_cpu = NULL; | 
 | 	} | 
 | #undef  KOBJ_ATTR_ITEM | 
 | } | 
 |  | 
 | void met_perf_cpupmu_polling(unsigned long long stamp, int cpu) | 
 | { | 
 | 	int count; | 
 | 	unsigned int pmu_value[MXNR_PMU_EVENTS]; | 
 |  | 
 | 	if (per_cpu(cpu_status, cpu) != MET_CPU_ONLINE) | 
 | 		return; | 
 |  | 
 | 	if (met_cpu_pmu_method) { | 
 | 		perf_cpupmu_polling(stamp, cpu); | 
 | 	} else { | 
 | 		count = cpu_pmu->polling(cpu_pmu->pmu[cpu], cpu_pmu->event_count[cpu], pmu_value); | 
 |  | 
 | #if IS_ENABLED(CONFIG_CPU_PM) | 
 | 		if (met_cpu_pm_pmu_reconfig) { | 
 | 			int ii; | 
 | 			for (ii = 0; ii < count; ii ++) | 
 | 				pmu_value[ii] += cpu_pmu->cpu_pm_unpolled_loss[cpu][ii]; | 
 | 		} | 
 | #endif | 
 |  | 
 | 		mp_cpu(count, pmu_value); | 
 |  | 
 | #if IS_ENABLED(CONFIG_CPU_PM) | 
 | 		if (met_cpu_pm_pmu_reconfig) { | 
 | 			memset(cpu_pmu->cpu_pm_unpolled_loss[cpu], 0, sizeof (cpu_pmu->cpu_pm_unpolled_loss[0])); | 
 | 		} | 
 | #endif | 
 | 	} | 
 | } | 
 |  | 
 | static void cpupmu_start(void) | 
 | { | 
 | 	int	cpu = raw_smp_processor_id(); | 
 |  | 
 | 	if (!met_cpu_pmu_method) { | 
 | 		nr_arg[cpu] = 0; | 
 | 		cpu_pmu->start(cpu_pmu->pmu[cpu], cpu_pmu->event_count[cpu]); | 
 |  | 
 | 		met_perf_cpupmu_status = 1; | 
 | 		per_cpu(cpu_status, cpu) = MET_CPU_ONLINE; | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | static void cpupmu_unique_start(void) | 
 | { | 
 | 	int cpu; | 
 |  | 
 | #ifdef CPUPMU_V8_2 | 
 | 	int ret = 0; | 
 | 	if (mtk_pmu_event_enable == 1){ | 
 | 		ret = cpu_pmu_debug_init(); | 
 | 		if (ret == 0) | 
 | 			PR_BOOTMSG("Failed to init CPU PMU debug!!\n"); | 
 | 	} | 
 | #endif | 
 |  | 
 | #if IS_ENABLED(CONFIG_CPU_PM) | 
 | 	use_cpu_pm_pmu_notifier = 0; | 
 | 	if (met_cpu_pm_pmu_reconfig) { | 
 | 		if (met_cpu_pmu_method) { | 
 | 			met_cpu_pm_pmu_reconfig = 0; | 
 | 			MET_TRACE("[MET_PMU] met_cpu_pmu_method=%d, met_cpu_pm_pmu_reconfig forced disabled\n", met_cpu_pmu_method); | 
 | 			pr_debug("[MET_PMU] met_cpu_pmu_method=%d, met_cpu_pm_pmu_reconfig forced disabled\n", met_cpu_pmu_method); | 
 | 		} else { | 
 | 			memset(cpu_pmu->cpu_pm_unpolled_loss, 0, sizeof (cpu_pmu->cpu_pm_unpolled_loss)); | 
 | 			cpu_pm_register_notifier(&cpu_pm_pmu_notifier); | 
 | 			use_cpu_pm_pmu_notifier = 1; | 
 | 		} | 
 | 	} | 
 | #else | 
 | 	if (met_cpu_pm_pmu_reconfig) { | 
 | 		met_cpu_pm_pmu_reconfig = 0; | 
 | 		MET_TRACE("[MET_PMU] CONFIG_CPU_PM=%d, met_cpu_pm_pmu_reconfig forced disabled\n", CONFIG_CPU_PM); | 
 | 		pr_debug("[MET_PMU] CONFIG_CPU_PM=%d, met_cpu_pm_pmu_reconfig forced disabled\n", CONFIG_CPU_PM); | 
 | 	} | 
 | #endif | 
 | 	MET_TRACE("[MET_PMU] met_cpu_pm_pmu_reconfig=%u\n", met_cpu_pm_pmu_reconfig); | 
 | 	pr_debug("[MET_PMU] met_cpu_pm_pmu_reconfig=%u\n", met_cpu_pm_pmu_reconfig); | 
 |  | 
 | 	if (met_cpu_pmu_method) { | 
 | 		for_each_possible_cpu(cpu) { | 
 | 			met_perf_cpupmu_start(cpu); | 
 |  | 
 | 			met_perf_cpupmu_status = 1; | 
 | 			per_cpu(cpu_status, cpu) = MET_CPU_ONLINE; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return; | 
 | } | 
 |  | 
 | static void cpupmu_stop(void) | 
 | { | 
 | 	int	cpu = raw_smp_processor_id(); | 
 |  | 
 | 	met_perf_cpupmu_status = 0; | 
 |  | 
 | 	if (!met_cpu_pmu_method) | 
 | 		cpu_pmu->stop(cpu_pmu->event_count[cpu]); | 
 | } | 
 |  | 
 | static void cpupmu_unique_stop(void) | 
 | { | 
 | 	int cpu; | 
 |  | 
 | 	if (met_cpu_pmu_method) { | 
 | 		for_each_possible_cpu(cpu) { | 
 | 			met_perf_cpupmu_stop(cpu); | 
 | 		} | 
 | 	} | 
 |  | 
 | #ifdef CPUPMU_V8_2 | 
 | 	if (mtk_pmu_event_enable == 1) | 
 | 		cpu_pmu_debug_uninit(); | 
 | #endif | 
 |  | 
 | #if IS_ENABLED(CONFIG_CPU_PM) | 
 | 	if (use_cpu_pm_pmu_notifier) { | 
 | 		cpu_pm_unregister_notifier(&cpu_pm_pmu_notifier); | 
 | 	} | 
 | #endif | 
 | 	return; | 
 | } | 
 |  | 
 | static const char cache_line_header[] = | 
 | 	"met-info [000] 0.0: met_cpu_cache_line_size: %d\n"; | 
 | static const char header[] = | 
 | 	"met-info [000] 0.0: met_cpu_header_v2: %d"; | 
 |  | 
 | static const char help[] = | 
 | 	"  --pmu-cpu-evt=[cpu_list:]event_list   select CPU-PMU events in %s\n" | 
 | 	"                                        cpu_list: specify the cpu_id list or apply to all the cores\n" | 
 | 	"                                            example: 0,1,2\n" | 
 | 	"                                        event_list: specify the event number\n" | 
 | 	"                                            example: 0x8,0xff\n"; | 
 |  | 
 | static int cpupmu_print_help(char *buf, int len) | 
 | { | 
 | 	return snprintf(buf, PAGE_SIZE, help, cpu_pmu->cpu_name); | 
 | } | 
 |  | 
 | static int reset_driver_stat(void) | 
 | { | 
 | 	int		cpu, i; | 
 | 	int		event_count; | 
 | 	struct met_pmu	*pmu; | 
 |  | 
 | 	met_cpupmu.mode = 0; | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		event_count = cpu_pmu->event_count[cpu]; | 
 | 		pmu = cpu_pmu->pmu[cpu]; | 
 | 		counter_cnt[cpu] = 0; | 
 | 		nr_arg[cpu] = 0; | 
 | 		for (i = 0; i < event_count; i++) { | 
 | 			pmu[i].mode = MODE_DISABLED; | 
 | 			pmu[i].event = 0; | 
 | 			pmu[i].freq = 0; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int cpupmu_print_header(char *buf, int len) | 
 | { | 
 | 	int		cpu, i, ret, first; | 
 | 	int		event_count; | 
 | 	struct met_pmu	*pmu; | 
 |  | 
 | 	ret = 0; | 
 |  | 
 | 	/*append CPU PMU access method*/ | 
 | 	if (met_cpu_pmu_method) | 
 | 		ret += snprintf(buf + ret, len, | 
 | 			"met-info [000] 0.0: CPU_PMU_method: perf APIs\n"); | 
 | 	else | 
 | 		ret += snprintf(buf + ret, len, | 
 | 			"met-info [000] 0.0: CPU_PMU_method: MET pmu driver\n"); | 
 |  | 
 | 	/*append cache line size*/ | 
 | 	ret += snprintf(buf + ret, len - ret, cache_line_header, cache_line_size()); | 
 | 	ret += snprintf(buf + ret, len - ret, "# mp_cpu: pmu_value1, ...\n"); | 
 |  | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		event_count = cpu_pmu->event_count[cpu]; | 
 | 		pmu = cpu_pmu->pmu[cpu]; | 
 | 		first = 1; | 
 | 		for (i = 0; i < event_count; i++) { | 
 | 			if (pmu[i].mode == 0) | 
 | 				continue; | 
 | 			if (first) { | 
 | 				ret += snprintf(buf + ret, len - ret, header, cpu); | 
 | 				first = 0; | 
 | 			} | 
 | 			ret += snprintf(buf + ret, len - ret, ",0x%x", pmu[i].event); | 
 | 			pmu[i].mode = 0; | 
 | 		} | 
 | 		if (!first) | 
 | 			ret += snprintf(buf + ret, len - ret, "\n"); | 
 | 	} | 
 |  | 
 | 	reset_driver_stat(); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int met_parse_num_list(char *arg, int len, int *list, int list_cnt) | 
 | { | 
 | 	int	nr_num = 0; | 
 | 	char	*num; | 
 | 	int	num_len; | 
 |  | 
 | 	/* search ',' as the splitter */ | 
 | 	while (len) { | 
 | 		num = arg; | 
 | 		num_len = 0; | 
 | 		if (list_cnt <= 0) | 
 | 			return -1; | 
 | 		while (len) { | 
 | 			len--; | 
 | 			if (*arg == ',') { | 
 | 				*(arg++) = '\0'; | 
 | 				break; | 
 | 			} | 
 | 			arg++; | 
 | 			num_len++; | 
 | 		} | 
 | 		if (met_parse_num(num, list, num_len) < 0) | 
 | 			return -1; | 
 | 		list++; | 
 | 		list_cnt--; | 
 | 		nr_num++; | 
 | 	} | 
 |  | 
 | 	return nr_num; | 
 | } | 
 |  | 
 | static const struct perf_pmu_events_attr * | 
 | perf_event_get_evt_attr_by_name(const struct perf_event *ev, | 
 | 			       const char *name) { | 
 | 	struct arm_pmu *arm_pmu; | 
 | 	struct attribute **attrp; | 
 | 	struct device_attribute *dev_attr_p; | 
 | 	struct perf_pmu_events_attr *ev_attr_p; | 
 |  | 
 | 	arm_pmu = container_of(ev->pmu, struct arm_pmu, pmu); | 
 |  | 
 | 	for (attrp = arm_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS]->attrs; | 
 | 	     *attrp != NULL; | 
 | 	     attrp ++) { | 
 |  | 
 | 		dev_attr_p = container_of(*attrp, struct device_attribute, attr); | 
 | 		ev_attr_p = container_of(dev_attr_p, struct perf_pmu_events_attr, attr); | 
 |  | 
 | 		if (0 == strcmp((*attrp)->name, name)) { | 
 | 			return ev_attr_p; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int cpupmu_process_argument(const char *arg, int len) | 
 | { | 
 | 	char		*arg1 = (char*)arg; | 
 | 	int		len1 = len; | 
 | 	int		cpu, cpu_list[MXNR_CPU]; | 
 | 	int		nr_events, event_list[MXNR_PMU_EVENTS]={0}; | 
 | 	int		i; | 
 | 	int		nr_counters; | 
 | 	struct met_pmu	*pmu; | 
 | 	int		arg_nr; | 
 | 	int		event_no; | 
 | 	int		is_cpu_cycle_evt; | 
 | 	const struct perf_pmu_events_attr *ev_attr_p; | 
 |  | 
 | 	/* | 
 | 	 * split cpu_list and event_list by ':' | 
 | 	 *   arg, len: cpu_list when found (i < len) | 
 | 	 *   arg1, len1: event_list | 
 | 	 */ | 
 | 	for (i = 0; i < len; i++) { | 
 | 		if (arg[i] == ':') { | 
 | 			arg1[i] = '\0'; | 
 | 			arg1 += i+1; | 
 | 			len1 = len - i - 1; | 
 | 			len = i; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * setup cpu_list array | 
 | 	 *   1: selected | 
 | 	 *   0: unselected | 
 | 	 */ | 
 | 	if (arg1 != arg) {	/* is cpu_id list specified? */ | 
 | 		int list[MXNR_CPU]={0}, cnt; | 
 | 		int cpu_id; | 
 | 		if ((cnt = met_parse_num_list((char*)arg, len, list, ARRAY_SIZE(list))) <= 0) | 
 | 			goto arg_out; | 
 | 		memset(cpu_list, 0, sizeof(cpu_list)); | 
 | 		for (i = 0; i < cnt; i++) { | 
 | 			cpu_id = list[i]; | 
 | 			if (cpu_id < 0 || cpu_id >= ARRAY_SIZE(cpu_list)) | 
 | 				goto arg_out; | 
 | 			cpu_list[cpu_id] = 1; | 
 | 		} | 
 | 	} | 
 | 	else | 
 | 		memset(cpu_list, 1, sizeof(cpu_list)); | 
 |  | 
 | 	/* get event_list */ | 
 | 	if ((nr_events = met_parse_num_list(arg1, len1, event_list, ARRAY_SIZE(event_list))) <= 0) | 
 | 		goto arg_out; | 
 |  | 
 | 	/* for each cpu in cpu_list, add all the events in event_list */ | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		pmu = cpu_pmu->pmu[cpu]; | 
 | 		arg_nr = nr_arg[cpu]; | 
 |  | 
 | 		if (cpu_list[cpu] == 0) | 
 | 			continue; | 
 |  | 
 | 		if (met_cpu_pmu_method) { | 
 | 			nr_counters = perf_num_counters(); | 
 | 		} else { | 
 | 			nr_counters = cpu_pmu->event_count[cpu]; | 
 | 		} | 
 |  | 
 | 		pr_debug("[MET_PMU] pmu slot count=%d\n", nr_counters); | 
 |  | 
 | 		if (nr_counters == 0) | 
 | 			goto arg_out; | 
 |  | 
 | 		for (i = 0; i < nr_events; i++) { | 
 | 			event_no = event_list[i]; | 
 | 			is_cpu_cycle_evt = 0; | 
 | 			/* | 
 | 			 * check if event is duplicate, but does not include 0xff | 
 | 			 */ | 
 | 			if (cpu_pmu->check_event(pmu, arg_nr, event_no) < 0) | 
 | 				goto arg_out; | 
 |  | 
 | 			/* | 
 | 			 * test if this event is available when in perf_APIs mode | 
 | 			 */ | 
 | 			if (met_cpu_pmu_method) { | 
 | 				struct perf_event *ev; | 
 |  | 
 | 				if (!cpu_pmu->perf_event_get_evttype) { | 
 | 					MET_TRACE("[MET_PMU] cpu_pmu->perf_event_get_evttype=NULL, " | 
 | 						  "met pmu on perf-event was not supported on this platform\n"); | 
 | 					pr_debug("[MET_PMU] cpu_pmu->perf_event_get_evttype=NULL, " | 
 | 						 "met pmu on perf-event was not supported on this platform\n"); | 
 | 					goto arg_out; | 
 | 				} | 
 |  | 
 | 				ev = perf_event_create(cpu, event_no, arg_nr); | 
 | 				if (ev == NULL) { | 
 | 					pr_debug("!!!!!!!! [MET_PMU] failed pmu alloction test (event_no=%#04x)\n", event_no); | 
 | 					goto arg_out; | 
 | 				} else { | 
 | 					perf_event_release(cpu, ev); | 
 | 				} | 
 |  | 
 | 				ev_attr_p = perf_event_get_evt_attr_by_name(ev, "cpu_cycles"); | 
 | 				if (ev_attr_p && cpu_pmu->perf_event_get_evttype(ev) == ev_attr_p->id) | 
 | 					is_cpu_cycle_evt = 1; | 
 | 			} | 
 |  | 
 | 			if (met_cpu_pmu_method) { | 
 | 				if (is_cpu_cycle_evt) { | 
 | 					if (pmu[nr_counters-1].mode == MODE_POLLING) | 
 | 						goto arg_out; | 
 | 					pmu[nr_counters-1].mode = MODE_POLLING; | 
 | 					pmu[nr_counters-1].event = event_no; | 
 | 					pmu[nr_counters-1].freq = 0; | 
 | 				} else { | 
 | 					if (arg_nr >= (nr_counters - 1)) | 
 | 						goto arg_out; | 
 | 					pmu[arg_nr].mode = MODE_POLLING; | 
 | 					pmu[arg_nr].event = event_no; | 
 | 					pmu[arg_nr].freq = 0; | 
 | 					arg_nr++; | 
 | 				} | 
 | 			} else { | 
 | 				if (event_no == 0xff) { | 
 | 					if (pmu[nr_counters-1].mode == MODE_POLLING) | 
 | 						goto arg_out; | 
 | 					pmu[nr_counters-1].mode = MODE_POLLING; | 
 | 					pmu[nr_counters-1].event = 0xff; | 
 | 					pmu[nr_counters-1].freq = 0; | 
 | 				} else { | 
 | 					if (arg_nr >= (nr_counters - 1)) | 
 | 						goto arg_out; | 
 | 					pmu[arg_nr].mode = MODE_POLLING; | 
 | 					pmu[arg_nr].event = event_no; | 
 | 					pmu[arg_nr].freq = 0; | 
 | 					arg_nr++; | 
 | 				} | 
 | 			} | 
 | 			counter_cnt[cpu]++; | 
 | 		} | 
 | 		nr_arg[cpu] = arg_nr; | 
 | 	} | 
 |  | 
 | 	met_cpupmu.mode = 1; | 
 | 	return 0; | 
 |  | 
 | arg_out: | 
 | 	reset_driver_stat(); | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static void cpupmu_cpu_state_notify(long cpu, unsigned long action) | 
 | { | 
 | 	per_cpu(cpu_status, cpu) = action; | 
 |  | 
 | #if (IS_ENABLED(CONFIG_ARM64) || IS_ENABLED(CONFIG_ARM)) | 
 | 	if (met_cpu_pmu_method && action == MET_CPU_OFFLINE) { | 
 | 		struct perf_event *event = NULL; | 
 | 		struct arm_pmu *armpmu = NULL; | 
 | 		struct platform_device *pmu_device = NULL; | 
 | 		int irq = 0; | 
 |  | 
 | 		event = per_cpu(pevent, cpu)[0]; | 
 | 		if (event) | 
 | 			armpmu = to_arm_pmu(event->pmu); | 
 | 		pr_debug("!!!!!!!! %s_%ld, event=%p\n", __FUNCTION__, cpu, event); | 
 |  | 
 | 		if (armpmu) | 
 | 			pmu_device = armpmu->plat_device; | 
 | 		pr_debug("!!!!!!!! %s_%ld, armpmu=%p\n", __FUNCTION__, cpu, armpmu); | 
 |  | 
 | 		if (pmu_device) | 
 | 			irq = platform_get_irq(pmu_device, 0); | 
 | 		pr_debug("!!!!!!!! %s_%ld, pmu_device=%p\n", __FUNCTION__, cpu, pmu_device); | 
 |  | 
 | 		if (irq > 0) | 
 | 			disable_percpu_irq(irq); | 
 | 		pr_debug("!!!!!!!! %s_%ld, irq=%d\n", __FUNCTION__, cpu, irq); | 
 | 	} | 
 | #endif | 
 | } | 
 |  | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) | 
 | static void sspm_pmu_start(void) | 
 | { | 
 | 	ondiemet_module[ONDIEMET_SSPM] |= ID_PMU; | 
 |  | 
 | 	if (met_cpupmu.ondiemet_mode == 1) | 
 | 		cpupmu_start(); | 
 | } | 
 |  | 
 | static int cycle_count_mode_enabled(int cpu) { | 
 |  | 
 | 	int event_cnt; | 
 | 	struct met_pmu	*pmu; | 
 |  | 
 | 	pmu = cpu_pmu->pmu[cpu]; | 
 |  | 
 | 	if (met_cpu_pmu_method) { | 
 | 		event_cnt = perf_num_counters(); | 
 | 	} else { | 
 | 		event_cnt = cpu_pmu->event_count[cpu]; | 
 | 	} | 
 |  | 
 | 	return pmu[event_cnt-1].mode == MODE_POLLING; | 
 | } | 
 |  | 
 | static void ipi_config_pmu_counter_cnt(void) { | 
 |  | 
 | 	int ret, cpu, ii, cnt_num; | 
 | 	unsigned int rdata; | 
 | 	unsigned int ipi_buf[4]; | 
 | 	struct hw_perf_event *hwc; | 
 | 	unsigned int base_offset; | 
 |  | 
 | 	for_each_possible_cpu(cpu) { | 
 | 		for (ii = 0; ii < 4; ii++) | 
 | 			ipi_buf[ii] = 0; | 
 |  | 
 | 		ipi_buf[0] = MET_MAIN_ID | (MID_PMU << MID_BIT_SHIFT) | MET_ARGU | SET_PMU_EVT_CNT; | 
 | 		/* | 
 | 		 *  XXX: on sspm side, cycle counter was not counted in | 
 | 		 *       total event number `counter_cnt', but controlled by | 
 | 		 *       an addtional argument `SET_PMU_CYCCNT_ENABLE' instead | 
 | 		 */ | 
 | 		cnt_num = (cycle_count_mode_enabled(cpu) ? | 
 | 			   (counter_cnt[cpu]-1) : counter_cnt[cpu]); | 
 | 		ipi_buf[1] = (cpu << 16) | (cnt_num & 0xffff); | 
 |  | 
 | 		MET_TRACE("[MET_PMU][IPI_CONFIG] core=%d, pmu_counter_cnt=%d\n", cpu, cnt_num); | 
 | 		pr_debug("[MET_PMU][IPI_CONFIG] core=%d, pmu_counter_cnt=%d\n", cpu, cnt_num); | 
 |  | 
 | 		MET_TRACE("[MET_PMU][IPI_CONFIG] sspm_buf_available=%d, in_interrupt()=%lu\n", sspm_buf_available, in_interrupt()); | 
 | 		pr_debug("[MET_PMU][IPI_CONFIG] sspm_buf_available=%d, in_interrupt()=%lu\n", sspm_buf_available, in_interrupt()); | 
 |  | 
 | 		if (sspm_buf_available == 1) { | 
 | 			ret = met_ipi_to_sspm_command((void *) ipi_buf, 0, &rdata, 1); | 
 | 		} | 
 |  | 
 | 		for (ii = 0; ii < 4; ii++) | 
 | 			ipi_buf[ii] = 0; | 
 |  | 
 | 		if (per_cpu(pevent, cpu)[0]) { | 
 | 			hwc = &(per_cpu(pevent, cpu)[0]->hw); | 
 | 			base_offset = hwc->idx-1; | 
 | 		} else { | 
 | 			base_offset = 0; | 
 | 		} | 
 |  | 
 | 		ipi_buf[0] = MET_MAIN_ID | (MID_PMU << MID_BIT_SHIFT) | MET_ARGU | SET_PMU_BASE_OFFSET; | 
 | 		ipi_buf[1] = (cpu << 16) | (base_offset & 0xffff); | 
 |  | 
 | 		MET_TRACE("[MET_PMU][IPI_CONFIG] core=%d, base offset set to %u\n", cpu, base_offset); | 
 | 		pr_debug("[MET_PMU][IPI_CONFIG] core=%d, base offset set to %u\n", cpu, base_offset); | 
 |  | 
 | 		if (sspm_buf_available == 1) { | 
 | 			ret = met_ipi_to_sspm_command((void *) ipi_buf, 0, &rdata, 1); | 
 | 		} | 
 |  | 
 | 		if (cycle_count_mode_enabled(cpu)) { | 
 |  | 
 | 			for (ii = 0; ii < 4; ii++) | 
 | 				ipi_buf[ii] = 0; | 
 |  | 
 | 			ipi_buf[0] = MET_MAIN_ID | (MID_PMU << MID_BIT_SHIFT) | MET_ARGU | SET_PMU_CYCCNT_ENABLE; | 
 | 			ipi_buf[1] = cpu & 0xffff; | 
 |  | 
 | 			MET_TRACE("[MET_PMU][IPI_CONFIG] core=%d, pmu cycle cnt enable\n", cpu); | 
 | 			pr_debug("[MET_PMU][IPI_CONFIG] core=%d, pmu cycle cnt enable\n", cpu); | 
 |  | 
 | 			if (sspm_buf_available == 1) { | 
 | 				ret = met_ipi_to_sspm_command((void *) ipi_buf, 0, &rdata, 1); | 
 | 			} | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static int __is_perf_event_hw_slot_seq_order(int cpu) { | 
 |  | 
 | 	struct hw_perf_event *hwc, *hwc_prev; | 
 | 	int event_count = cpu_pmu->event_count[cpu]; | 
 | 	int ii; | 
 |  | 
 | 	/* | 
 | 	 * perf-event descriptor list would not have any hole | 
 | 	 * (excepts special 0xff, which will always be the last element) | 
 | 	 */ | 
 | 	if (per_cpu(pevent, cpu)[0] == NULL) | 
 | 		return 1; | 
 |  | 
 | 	/* | 
 | 	 * XXX: no need to check the last slot, | 
 | 	 *      which is reserved for 0xff | 
 | 	 */ | 
 | 	for (ii = 1; ii < event_count - 1; ii++) { | 
 |  | 
 | 		if (per_cpu(pevent, cpu)[ii] == NULL) | 
 | 			return 1; | 
 |  | 
 | 		hwc = &(per_cpu(pevent, cpu)[ii]->hw); | 
 | 		hwc_prev = &(per_cpu(pevent, cpu)[ii-1]->hw); | 
 |  | 
 | 		if (hwc->idx != hwc_prev->idx + 1) | 
 | 			return 0; | 
 | 	} | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | static int __validate_sspm_compatibility(void) { | 
 |  | 
 | 	int cpu; | 
 |  | 
 | 	for_each_possible_cpu(cpu) { | 
 |  | 
 | 		if (!__is_perf_event_hw_slot_seq_order(cpu)) { | 
 | 			MET_TRACE("[MET_PMU] pmu not sequentially allocated on cpu %d\n" | 
 | 				  ,cpu); | 
 | 			pr_debug("[MET_PMU] pmu not sequentially allocated on cpu %d\n" | 
 | 				 ,cpu); | 
 | 			return -1; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void sspm_pmu_unique_start(void) { | 
 |  | 
 | 	if (met_cpupmu.ondiemet_mode == 1) | 
 | 		cpupmu_unique_start(); | 
 |  | 
 | 	if (met_cpupmu.ondiemet_mode == 1) { | 
 | 		if (__validate_sspm_compatibility() == -1) { | 
 | 			MET_TRACE("[MET_PMU] turned off sspm side polling\n"); | 
 | 			pr_debug("[MET_PMU] turned off sspm side polling\n"); | 
 | 			/* return without sending init IPIs, leaving sspm side to poll nothing */ | 
 | 			return; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ipi_config_pmu_counter_cnt(); | 
 | } | 
 |  | 
 | static void sspm_pmu_unique_stop(void) | 
 | { | 
 | 	if (met_cpupmu.ondiemet_mode == 1) | 
 | 		cpupmu_unique_stop(); | 
 | 	return; | 
 | } | 
 |  | 
 | static void sspm_pmu_stop(void) | 
 | { | 
 | 	if (met_cpupmu.ondiemet_mode == 1) | 
 | 		cpupmu_stop(); | 
 | } | 
 |  | 
 | static const char sspm_pmu_header[] = "met-info [000] 0.0: pmu_sampler: sspm\n"; | 
 |  | 
 | static int sspm_pmu_print_header(char *buf, int len) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = snprintf(buf, len, sspm_pmu_header); | 
 |  | 
 | 	if (met_cpupmu.ondiemet_mode == 1) | 
 | 		ret += cpupmu_print_header(buf + ret, len - ret); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int sspm_pmu_process_argument(const char *arg, int len) | 
 | { | 
 | 	if (met_cpupmu.ondiemet_mode == 1) { | 
 |  | 
 | 		if (!cpu_pmu->pmu_read_clear_overflow_flag) { | 
 | 			MET_TRACE("[MET_PMU] cpu_pmu->pmu_read_clear_overflow_flag=NULL, " | 
 | 				  "pmu on sspm was not supported on this platform\n"); | 
 | 			pr_debug("[MET_PMU] cpu_pmu->pmu_read_clear_overflow_flag=NULL, " | 
 | 				 "pmu on sspm was not supported on this platform\n"); | 
 | 			return -EINVAL; | 
 | 		} | 
 |  | 
 | 		return cpupmu_process_argument(arg, len); | 
 | 	} | 
 | 	return 0; | 
 | } | 
 | #endif /* end of #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) */ | 
 | #endif /* end of #if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) */ | 
 |  | 
 | struct metdevice met_cpupmu = { | 
 | 	.name = "cpu", | 
 | 	.type = MET_TYPE_PMU, | 
 | 	.cpu_related = 1, | 
 | 	.create_subfs = cpupmu_create_subfs, | 
 | 	.delete_subfs = cpupmu_delete_subfs, | 
 | 	.start = cpupmu_start, | 
 | 	.uniq_start = cpupmu_unique_start, | 
 | 	.stop = cpupmu_stop, | 
 | 	.uniq_stop = cpupmu_unique_stop, | 
 | 	.polling_interval = 1, | 
 | 	.timed_polling = met_perf_cpupmu_polling, | 
 | 	.print_help = cpupmu_print_help, | 
 | 	.print_header = cpupmu_print_header, | 
 | 	.process_argument = cpupmu_process_argument, | 
 | 	.cpu_state_notify = cpupmu_cpu_state_notify, | 
 | #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) | 
 | #if defined(ONDIEMET_SUPPORT) || defined(TINYSYS_SSPM_SUPPORT) | 
 | 	.ondiemet_mode = 1, | 
 | 	.ondiemet_start = sspm_pmu_start, | 
 | 	.uniq_ondiemet_start = sspm_pmu_unique_start, | 
 | 	.uniq_ondiemet_stop = sspm_pmu_unique_stop, | 
 | 	.ondiemet_stop = sspm_pmu_stop, | 
 | 	.ondiemet_print_header = sspm_pmu_print_header, | 
 | 	.ondiemet_process_argument = sspm_pmu_process_argument | 
 | #endif | 
 | #endif | 
 | }; |