162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd. 662306a36Sopenharmony_ci * Author : Chanwoo Choi <cw00.choi@samsung.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * This driver is based on drivers/devfreq/exynos/exynos_ppmu.c 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/clk.h> 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/of_address.h> 1662306a36Sopenharmony_ci#include <linux/of_device.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/regmap.h> 1962306a36Sopenharmony_ci#include <linux/suspend.h> 2062306a36Sopenharmony_ci#include <linux/devfreq-event.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include "exynos-ppmu.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cienum exynos_ppmu_type { 2562306a36Sopenharmony_ci EXYNOS_TYPE_PPMU, 2662306a36Sopenharmony_ci EXYNOS_TYPE_PPMU_V2, 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct exynos_ppmu_data { 3062306a36Sopenharmony_ci struct clk *clk; 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct exynos_ppmu { 3462306a36Sopenharmony_ci struct devfreq_event_dev **edev; 3562306a36Sopenharmony_ci struct devfreq_event_desc *desc; 3662306a36Sopenharmony_ci unsigned int num_events; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci struct device *dev; 3962306a36Sopenharmony_ci struct regmap *regmap; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci struct exynos_ppmu_data ppmu; 4262306a36Sopenharmony_ci enum exynos_ppmu_type ppmu_type; 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define PPMU_EVENT(name) \ 4662306a36Sopenharmony_ci { "ppmu-event0-"#name, PPMU_PMNCNT0 }, \ 4762306a36Sopenharmony_ci { "ppmu-event1-"#name, PPMU_PMNCNT1 }, \ 4862306a36Sopenharmony_ci { "ppmu-event2-"#name, PPMU_PMNCNT2 }, \ 4962306a36Sopenharmony_ci { "ppmu-event3-"#name, PPMU_PMNCNT3 } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic struct __exynos_ppmu_events { 5262306a36Sopenharmony_ci char *name; 5362306a36Sopenharmony_ci int id; 5462306a36Sopenharmony_ci} ppmu_events[] = { 5562306a36Sopenharmony_ci /* For Exynos3250, Exynos4 and Exynos5260 */ 5662306a36Sopenharmony_ci PPMU_EVENT(g3d), 5762306a36Sopenharmony_ci PPMU_EVENT(fsys), 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* For Exynos4 SoCs and Exynos3250 */ 6062306a36Sopenharmony_ci PPMU_EVENT(dmc0), 6162306a36Sopenharmony_ci PPMU_EVENT(dmc1), 6262306a36Sopenharmony_ci PPMU_EVENT(cpu), 6362306a36Sopenharmony_ci PPMU_EVENT(rightbus), 6462306a36Sopenharmony_ci PPMU_EVENT(leftbus), 6562306a36Sopenharmony_ci PPMU_EVENT(lcd0), 6662306a36Sopenharmony_ci PPMU_EVENT(camif), 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* Only for Exynos3250 and Exynos5260 */ 6962306a36Sopenharmony_ci PPMU_EVENT(mfc), 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* Only for Exynos4 SoCs */ 7262306a36Sopenharmony_ci PPMU_EVENT(mfc-left), 7362306a36Sopenharmony_ci PPMU_EVENT(mfc-right), 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* Only for Exynos5260 SoCs */ 7662306a36Sopenharmony_ci PPMU_EVENT(drex0-s0), 7762306a36Sopenharmony_ci PPMU_EVENT(drex0-s1), 7862306a36Sopenharmony_ci PPMU_EVENT(drex1-s0), 7962306a36Sopenharmony_ci PPMU_EVENT(drex1-s1), 8062306a36Sopenharmony_ci PPMU_EVENT(eagle), 8162306a36Sopenharmony_ci PPMU_EVENT(kfc), 8262306a36Sopenharmony_ci PPMU_EVENT(isp), 8362306a36Sopenharmony_ci PPMU_EVENT(fimc), 8462306a36Sopenharmony_ci PPMU_EVENT(gscl), 8562306a36Sopenharmony_ci PPMU_EVENT(mscl), 8662306a36Sopenharmony_ci PPMU_EVENT(fimd0x), 8762306a36Sopenharmony_ci PPMU_EVENT(fimd1x), 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* Only for Exynos5433 SoCs */ 9062306a36Sopenharmony_ci PPMU_EVENT(d0-cpu), 9162306a36Sopenharmony_ci PPMU_EVENT(d0-general), 9262306a36Sopenharmony_ci PPMU_EVENT(d0-rt), 9362306a36Sopenharmony_ci PPMU_EVENT(d1-cpu), 9462306a36Sopenharmony_ci PPMU_EVENT(d1-general), 9562306a36Sopenharmony_ci PPMU_EVENT(d1-rt), 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* For Exynos5422 SoC, deprecated (backwards compatible) */ 9862306a36Sopenharmony_ci PPMU_EVENT(dmc0_0), 9962306a36Sopenharmony_ci PPMU_EVENT(dmc0_1), 10062306a36Sopenharmony_ci PPMU_EVENT(dmc1_0), 10162306a36Sopenharmony_ci PPMU_EVENT(dmc1_1), 10262306a36Sopenharmony_ci /* For Exynos5422 SoC */ 10362306a36Sopenharmony_ci PPMU_EVENT(dmc0-0), 10462306a36Sopenharmony_ci PPMU_EVENT(dmc0-1), 10562306a36Sopenharmony_ci PPMU_EVENT(dmc1-0), 10662306a36Sopenharmony_ci PPMU_EVENT(dmc1-1), 10762306a36Sopenharmony_ci}; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int __exynos_ppmu_find_ppmu_id(const char *edev_name) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci int i; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) 11462306a36Sopenharmony_ci if (!strcmp(edev_name, ppmu_events[i].name)) 11562306a36Sopenharmony_ci return ppmu_events[i].id; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return -EINVAL; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci return __exynos_ppmu_find_ppmu_id(edev->desc->name); 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci/* 12662306a36Sopenharmony_ci * The devfreq-event ops structure for PPMU v1.1 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_cistatic int exynos_ppmu_disable(struct devfreq_event_dev *edev) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 13162306a36Sopenharmony_ci int ret; 13262306a36Sopenharmony_ci u32 pmnc; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Disable all counters */ 13562306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_CNTENC, 13662306a36Sopenharmony_ci PPMU_CCNT_MASK | 13762306a36Sopenharmony_ci PPMU_PMCNT0_MASK | 13862306a36Sopenharmony_ci PPMU_PMCNT1_MASK | 13962306a36Sopenharmony_ci PPMU_PMCNT2_MASK | 14062306a36Sopenharmony_ci PPMU_PMCNT3_MASK); 14162306a36Sopenharmony_ci if (ret < 0) 14262306a36Sopenharmony_ci return ret; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* Disable PPMU */ 14562306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); 14662306a36Sopenharmony_ci if (ret < 0) 14762306a36Sopenharmony_ci return ret; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci pmnc &= ~PPMU_PMNC_ENABLE_MASK; 15062306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); 15162306a36Sopenharmony_ci if (ret < 0) 15262306a36Sopenharmony_ci return ret; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic int exynos_ppmu_set_event(struct devfreq_event_dev *edev) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 16062306a36Sopenharmony_ci int id = exynos_ppmu_find_ppmu_id(edev); 16162306a36Sopenharmony_ci int ret; 16262306a36Sopenharmony_ci u32 pmnc, cntens; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (id < 0) 16562306a36Sopenharmony_ci return id; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* Enable specific counter */ 16862306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_CNTENS, &cntens); 16962306a36Sopenharmony_ci if (ret < 0) 17062306a36Sopenharmony_ci return ret; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); 17362306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_CNTENS, cntens); 17462306a36Sopenharmony_ci if (ret < 0) 17562306a36Sopenharmony_ci return ret; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* Set the event of proper data type monitoring */ 17862306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_BEVTxSEL(id), 17962306a36Sopenharmony_ci edev->desc->event_type); 18062306a36Sopenharmony_ci if (ret < 0) 18162306a36Sopenharmony_ci return ret; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci /* Reset cycle counter/performance counter and enable PPMU */ 18462306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); 18562306a36Sopenharmony_ci if (ret < 0) 18662306a36Sopenharmony_ci return ret; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci pmnc &= ~(PPMU_PMNC_ENABLE_MASK 18962306a36Sopenharmony_ci | PPMU_PMNC_COUNTER_RESET_MASK 19062306a36Sopenharmony_ci | PPMU_PMNC_CC_RESET_MASK); 19162306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); 19262306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); 19362306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); 19462306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); 19562306a36Sopenharmony_ci if (ret < 0) 19662306a36Sopenharmony_ci return ret; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic int exynos_ppmu_get_event(struct devfreq_event_dev *edev, 20262306a36Sopenharmony_ci struct devfreq_event_data *edata) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 20562306a36Sopenharmony_ci int id = exynos_ppmu_find_ppmu_id(edev); 20662306a36Sopenharmony_ci unsigned int total_count, load_count; 20762306a36Sopenharmony_ci unsigned int pmcnt3_high, pmcnt3_low; 20862306a36Sopenharmony_ci unsigned int pmnc, cntenc; 20962306a36Sopenharmony_ci int ret; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (id < 0) 21262306a36Sopenharmony_ci return -EINVAL; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* Disable PPMU */ 21562306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMNC, &pmnc); 21662306a36Sopenharmony_ci if (ret < 0) 21762306a36Sopenharmony_ci return ret; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci pmnc &= ~PPMU_PMNC_ENABLE_MASK; 22062306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_PMNC, pmnc); 22162306a36Sopenharmony_ci if (ret < 0) 22262306a36Sopenharmony_ci return ret; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci /* Read cycle count */ 22562306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_CCNT, &total_count); 22662306a36Sopenharmony_ci if (ret < 0) 22762306a36Sopenharmony_ci return ret; 22862306a36Sopenharmony_ci edata->total_count = total_count; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* Read performance count */ 23162306a36Sopenharmony_ci switch (id) { 23262306a36Sopenharmony_ci case PPMU_PMNCNT0: 23362306a36Sopenharmony_ci case PPMU_PMNCNT1: 23462306a36Sopenharmony_ci case PPMU_PMNCNT2: 23562306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMNCT(id), &load_count); 23662306a36Sopenharmony_ci if (ret < 0) 23762306a36Sopenharmony_ci return ret; 23862306a36Sopenharmony_ci edata->load_count = load_count; 23962306a36Sopenharmony_ci break; 24062306a36Sopenharmony_ci case PPMU_PMNCNT3: 24162306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMCNT3_HIGH, &pmcnt3_high); 24262306a36Sopenharmony_ci if (ret < 0) 24362306a36Sopenharmony_ci return ret; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_PMCNT3_LOW, &pmcnt3_low); 24662306a36Sopenharmony_ci if (ret < 0) 24762306a36Sopenharmony_ci return ret; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci edata->load_count = ((pmcnt3_high << 8) | pmcnt3_low); 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci default: 25262306a36Sopenharmony_ci return -EINVAL; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci /* Disable specific counter */ 25662306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_CNTENC, &cntenc); 25762306a36Sopenharmony_ci if (ret < 0) 25862306a36Sopenharmony_ci return ret; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); 26162306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_CNTENC, cntenc); 26262306a36Sopenharmony_ci if (ret < 0) 26362306a36Sopenharmony_ci return ret; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name, 26662306a36Sopenharmony_ci edata->load_count, edata->total_count); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic const struct devfreq_event_ops exynos_ppmu_ops = { 27262306a36Sopenharmony_ci .disable = exynos_ppmu_disable, 27362306a36Sopenharmony_ci .set_event = exynos_ppmu_set_event, 27462306a36Sopenharmony_ci .get_event = exynos_ppmu_get_event, 27562306a36Sopenharmony_ci}; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci/* 27862306a36Sopenharmony_ci * The devfreq-event ops structure for PPMU v2.0 27962306a36Sopenharmony_ci */ 28062306a36Sopenharmony_cistatic int exynos_ppmu_v2_disable(struct devfreq_event_dev *edev) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 28362306a36Sopenharmony_ci int ret; 28462306a36Sopenharmony_ci u32 pmnc, clear; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* Disable all counters */ 28762306a36Sopenharmony_ci clear = (PPMU_CCNT_MASK | PPMU_PMCNT0_MASK | PPMU_PMCNT1_MASK 28862306a36Sopenharmony_ci | PPMU_PMCNT2_MASK | PPMU_PMCNT3_MASK); 28962306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_FLAG, clear); 29062306a36Sopenharmony_ci if (ret < 0) 29162306a36Sopenharmony_ci return ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_INTENC, clear); 29462306a36Sopenharmony_ci if (ret < 0) 29562306a36Sopenharmony_ci return ret; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CNTENC, clear); 29862306a36Sopenharmony_ci if (ret < 0) 29962306a36Sopenharmony_ci return ret; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CNT_RESET, clear); 30262306a36Sopenharmony_ci if (ret < 0) 30362306a36Sopenharmony_ci return ret; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG0, 0x0); 30662306a36Sopenharmony_ci if (ret < 0) 30762306a36Sopenharmony_ci return ret; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG1, 0x0); 31062306a36Sopenharmony_ci if (ret < 0) 31162306a36Sopenharmony_ci return ret; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CIG_CFG2, 0x0); 31462306a36Sopenharmony_ci if (ret < 0) 31562306a36Sopenharmony_ci return ret; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CIG_RESULT, 0x0); 31862306a36Sopenharmony_ci if (ret < 0) 31962306a36Sopenharmony_ci return ret; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CNT_AUTO, 0x0); 32262306a36Sopenharmony_ci if (ret < 0) 32362306a36Sopenharmony_ci return ret; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CH_EV0_TYPE, 0x0); 32662306a36Sopenharmony_ci if (ret < 0) 32762306a36Sopenharmony_ci return ret; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CH_EV1_TYPE, 0x0); 33062306a36Sopenharmony_ci if (ret < 0) 33162306a36Sopenharmony_ci return ret; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CH_EV2_TYPE, 0x0); 33462306a36Sopenharmony_ci if (ret < 0) 33562306a36Sopenharmony_ci return ret; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CH_EV3_TYPE, 0x0); 33862306a36Sopenharmony_ci if (ret < 0) 33962306a36Sopenharmony_ci return ret; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_SM_ID_V, 0x0); 34262306a36Sopenharmony_ci if (ret < 0) 34362306a36Sopenharmony_ci return ret; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_SM_ID_A, 0x0); 34662306a36Sopenharmony_ci if (ret < 0) 34762306a36Sopenharmony_ci return ret; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_V, 0x0); 35062306a36Sopenharmony_ci if (ret < 0) 35162306a36Sopenharmony_ci return ret; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_SM_OTHERS_A, 0x0); 35462306a36Sopenharmony_ci if (ret < 0) 35562306a36Sopenharmony_ci return ret; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_INTERRUPT_RESET, 0x0); 35862306a36Sopenharmony_ci if (ret < 0) 35962306a36Sopenharmony_ci return ret; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* Disable PPMU */ 36262306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); 36362306a36Sopenharmony_ci if (ret < 0) 36462306a36Sopenharmony_ci return ret; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci pmnc &= ~PPMU_PMNC_ENABLE_MASK; 36762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); 36862306a36Sopenharmony_ci if (ret < 0) 36962306a36Sopenharmony_ci return ret; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci return 0; 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic int exynos_ppmu_v2_set_event(struct devfreq_event_dev *edev) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 37762306a36Sopenharmony_ci unsigned int pmnc, cntens; 37862306a36Sopenharmony_ci int id = exynos_ppmu_find_ppmu_id(edev); 37962306a36Sopenharmony_ci int ret; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci /* Enable all counters */ 38262306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_CNTENS, &cntens); 38362306a36Sopenharmony_ci if (ret < 0) 38462306a36Sopenharmony_ci return ret; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci cntens |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); 38762306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CNTENS, cntens); 38862306a36Sopenharmony_ci if (ret < 0) 38962306a36Sopenharmony_ci return ret; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci /* Set the event of proper data type monitoring */ 39262306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id), 39362306a36Sopenharmony_ci edev->desc->event_type); 39462306a36Sopenharmony_ci if (ret < 0) 39562306a36Sopenharmony_ci return ret; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci /* Reset cycle counter/performance counter and enable PPMU */ 39862306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); 39962306a36Sopenharmony_ci if (ret < 0) 40062306a36Sopenharmony_ci return ret; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci pmnc &= ~(PPMU_PMNC_ENABLE_MASK 40362306a36Sopenharmony_ci | PPMU_PMNC_COUNTER_RESET_MASK 40462306a36Sopenharmony_ci | PPMU_PMNC_CC_RESET_MASK 40562306a36Sopenharmony_ci | PPMU_PMNC_CC_DIVIDER_MASK 40662306a36Sopenharmony_ci | PPMU_V2_PMNC_START_MODE_MASK); 40762306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_ENABLE_SHIFT); 40862306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_COUNTER_RESET_SHIFT); 40962306a36Sopenharmony_ci pmnc |= (PPMU_ENABLE << PPMU_PMNC_CC_RESET_SHIFT); 41062306a36Sopenharmony_ci pmnc |= (PPMU_V2_MODE_MANUAL << PPMU_V2_PMNC_START_MODE_SHIFT); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); 41362306a36Sopenharmony_ci if (ret < 0) 41462306a36Sopenharmony_ci return ret; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci return 0; 41762306a36Sopenharmony_ci} 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_cistatic int exynos_ppmu_v2_get_event(struct devfreq_event_dev *edev, 42062306a36Sopenharmony_ci struct devfreq_event_data *edata) 42162306a36Sopenharmony_ci{ 42262306a36Sopenharmony_ci struct exynos_ppmu *info = devfreq_event_get_drvdata(edev); 42362306a36Sopenharmony_ci int id = exynos_ppmu_find_ppmu_id(edev); 42462306a36Sopenharmony_ci int ret; 42562306a36Sopenharmony_ci unsigned int pmnc, cntenc; 42662306a36Sopenharmony_ci unsigned int pmcnt_high, pmcnt_low; 42762306a36Sopenharmony_ci unsigned int total_count, count; 42862306a36Sopenharmony_ci unsigned long load_count = 0; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci /* Disable PPMU */ 43162306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc); 43262306a36Sopenharmony_ci if (ret < 0) 43362306a36Sopenharmony_ci return ret; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci pmnc &= ~PPMU_PMNC_ENABLE_MASK; 43662306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_PMNC, pmnc); 43762306a36Sopenharmony_ci if (ret < 0) 43862306a36Sopenharmony_ci return ret; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci /* Read cycle count and performance count */ 44162306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_CCNT, &total_count); 44262306a36Sopenharmony_ci if (ret < 0) 44362306a36Sopenharmony_ci return ret; 44462306a36Sopenharmony_ci edata->total_count = total_count; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci switch (id) { 44762306a36Sopenharmony_ci case PPMU_PMNCNT0: 44862306a36Sopenharmony_ci case PPMU_PMNCNT1: 44962306a36Sopenharmony_ci case PPMU_PMNCNT2: 45062306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMNCT(id), &count); 45162306a36Sopenharmony_ci if (ret < 0) 45262306a36Sopenharmony_ci return ret; 45362306a36Sopenharmony_ci load_count = count; 45462306a36Sopenharmony_ci break; 45562306a36Sopenharmony_ci case PPMU_PMNCNT3: 45662306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_HIGH, 45762306a36Sopenharmony_ci &pmcnt_high); 45862306a36Sopenharmony_ci if (ret < 0) 45962306a36Sopenharmony_ci return ret; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_PMCNT3_LOW, &pmcnt_low); 46262306a36Sopenharmony_ci if (ret < 0) 46362306a36Sopenharmony_ci return ret; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci load_count = ((u64)((pmcnt_high & 0xff)) << 32)+ (u64)pmcnt_low; 46662306a36Sopenharmony_ci break; 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci edata->load_count = load_count; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci /* Disable all counters */ 47162306a36Sopenharmony_ci ret = regmap_read(info->regmap, PPMU_V2_CNTENC, &cntenc); 47262306a36Sopenharmony_ci if (ret < 0) 47362306a36Sopenharmony_ci return 0; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci cntenc |= (PPMU_CCNT_MASK | (PPMU_ENABLE << id)); 47662306a36Sopenharmony_ci ret = regmap_write(info->regmap, PPMU_V2_CNTENC, cntenc); 47762306a36Sopenharmony_ci if (ret < 0) 47862306a36Sopenharmony_ci return ret; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci dev_dbg(&edev->dev, "%25s (load: %ld / %ld)\n", edev->desc->name, 48162306a36Sopenharmony_ci edata->load_count, edata->total_count); 48262306a36Sopenharmony_ci return 0; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic const struct devfreq_event_ops exynos_ppmu_v2_ops = { 48662306a36Sopenharmony_ci .disable = exynos_ppmu_v2_disable, 48762306a36Sopenharmony_ci .set_event = exynos_ppmu_v2_set_event, 48862306a36Sopenharmony_ci .get_event = exynos_ppmu_v2_get_event, 48962306a36Sopenharmony_ci}; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_cistatic const struct of_device_id exynos_ppmu_id_match[] = { 49262306a36Sopenharmony_ci { 49362306a36Sopenharmony_ci .compatible = "samsung,exynos-ppmu", 49462306a36Sopenharmony_ci .data = (void *)EXYNOS_TYPE_PPMU, 49562306a36Sopenharmony_ci }, { 49662306a36Sopenharmony_ci .compatible = "samsung,exynos-ppmu-v2", 49762306a36Sopenharmony_ci .data = (void *)EXYNOS_TYPE_PPMU_V2, 49862306a36Sopenharmony_ci }, 49962306a36Sopenharmony_ci { /* sentinel */ }, 50062306a36Sopenharmony_ci}; 50162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_ppmu_id_match); 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_cistatic int of_get_devfreq_events(struct device_node *np, 50462306a36Sopenharmony_ci struct exynos_ppmu *info) 50562306a36Sopenharmony_ci{ 50662306a36Sopenharmony_ci struct devfreq_event_desc *desc; 50762306a36Sopenharmony_ci struct device *dev = info->dev; 50862306a36Sopenharmony_ci struct device_node *events_np, *node; 50962306a36Sopenharmony_ci int i, j, count; 51062306a36Sopenharmony_ci const struct of_device_id *of_id; 51162306a36Sopenharmony_ci int ret; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci events_np = of_get_child_by_name(np, "events"); 51462306a36Sopenharmony_ci if (!events_np) { 51562306a36Sopenharmony_ci dev_err(dev, 51662306a36Sopenharmony_ci "failed to get child node of devfreq-event devices\n"); 51762306a36Sopenharmony_ci return -EINVAL; 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci count = of_get_child_count(events_np); 52162306a36Sopenharmony_ci desc = devm_kcalloc(dev, count, sizeof(*desc), GFP_KERNEL); 52262306a36Sopenharmony_ci if (!desc) { 52362306a36Sopenharmony_ci of_node_put(events_np); 52462306a36Sopenharmony_ci return -ENOMEM; 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci info->num_events = count; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci of_id = of_match_device(exynos_ppmu_id_match, dev); 52962306a36Sopenharmony_ci if (of_id) 53062306a36Sopenharmony_ci info->ppmu_type = (enum exynos_ppmu_type)of_id->data; 53162306a36Sopenharmony_ci else { 53262306a36Sopenharmony_ci of_node_put(events_np); 53362306a36Sopenharmony_ci return -EINVAL; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci j = 0; 53762306a36Sopenharmony_ci for_each_child_of_node(events_np, node) { 53862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) { 53962306a36Sopenharmony_ci if (!ppmu_events[i].name) 54062306a36Sopenharmony_ci continue; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if (of_node_name_eq(node, ppmu_events[i].name)) 54362306a36Sopenharmony_ci break; 54462306a36Sopenharmony_ci } 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci if (i == ARRAY_SIZE(ppmu_events)) { 54762306a36Sopenharmony_ci dev_warn(dev, 54862306a36Sopenharmony_ci "don't know how to configure events : %pOFn\n", 54962306a36Sopenharmony_ci node); 55062306a36Sopenharmony_ci continue; 55162306a36Sopenharmony_ci } 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci switch (info->ppmu_type) { 55462306a36Sopenharmony_ci case EXYNOS_TYPE_PPMU: 55562306a36Sopenharmony_ci desc[j].ops = &exynos_ppmu_ops; 55662306a36Sopenharmony_ci break; 55762306a36Sopenharmony_ci case EXYNOS_TYPE_PPMU_V2: 55862306a36Sopenharmony_ci desc[j].ops = &exynos_ppmu_v2_ops; 55962306a36Sopenharmony_ci break; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci desc[j].driver_data = info; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci of_property_read_string(node, "event-name", &desc[j].name); 56562306a36Sopenharmony_ci ret = of_property_read_u32(node, "event-data-type", 56662306a36Sopenharmony_ci &desc[j].event_type); 56762306a36Sopenharmony_ci if (ret) { 56862306a36Sopenharmony_ci /* Set the event of proper data type counting. 56962306a36Sopenharmony_ci * Check if the data type has been defined in DT, 57062306a36Sopenharmony_ci * use default if not. 57162306a36Sopenharmony_ci */ 57262306a36Sopenharmony_ci if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) { 57362306a36Sopenharmony_ci /* Not all registers take the same value for 57462306a36Sopenharmony_ci * read+write data count. 57562306a36Sopenharmony_ci */ 57662306a36Sopenharmony_ci switch (ppmu_events[i].id) { 57762306a36Sopenharmony_ci case PPMU_PMNCNT0: 57862306a36Sopenharmony_ci case PPMU_PMNCNT1: 57962306a36Sopenharmony_ci case PPMU_PMNCNT2: 58062306a36Sopenharmony_ci desc[j].event_type = PPMU_V2_RO_DATA_CNT 58162306a36Sopenharmony_ci | PPMU_V2_WO_DATA_CNT; 58262306a36Sopenharmony_ci break; 58362306a36Sopenharmony_ci case PPMU_PMNCNT3: 58462306a36Sopenharmony_ci desc[j].event_type = 58562306a36Sopenharmony_ci PPMU_V2_EVT3_RW_DATA_CNT; 58662306a36Sopenharmony_ci break; 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci } else { 58962306a36Sopenharmony_ci desc[j].event_type = PPMU_RO_DATA_CNT | 59062306a36Sopenharmony_ci PPMU_WO_DATA_CNT; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci } 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci j++; 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci info->desc = desc; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci of_node_put(events_np); 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci return 0; 60162306a36Sopenharmony_ci} 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_cistatic struct regmap_config exynos_ppmu_regmap_config = { 60462306a36Sopenharmony_ci .reg_bits = 32, 60562306a36Sopenharmony_ci .val_bits = 32, 60662306a36Sopenharmony_ci .reg_stride = 4, 60762306a36Sopenharmony_ci}; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_cistatic int exynos_ppmu_parse_dt(struct platform_device *pdev, 61062306a36Sopenharmony_ci struct exynos_ppmu *info) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci struct device *dev = info->dev; 61362306a36Sopenharmony_ci struct device_node *np = dev->of_node; 61462306a36Sopenharmony_ci struct resource *res; 61562306a36Sopenharmony_ci void __iomem *base; 61662306a36Sopenharmony_ci int ret = 0; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci if (!np) { 61962306a36Sopenharmony_ci dev_err(dev, "failed to find devicetree node\n"); 62062306a36Sopenharmony_ci return -EINVAL; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci /* Maps the memory mapped IO to control PPMU register */ 62462306a36Sopenharmony_ci base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 62562306a36Sopenharmony_ci if (IS_ERR(base)) 62662306a36Sopenharmony_ci return PTR_ERR(base); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci exynos_ppmu_regmap_config.max_register = resource_size(res) - 4; 62962306a36Sopenharmony_ci info->regmap = devm_regmap_init_mmio(dev, base, 63062306a36Sopenharmony_ci &exynos_ppmu_regmap_config); 63162306a36Sopenharmony_ci if (IS_ERR(info->regmap)) { 63262306a36Sopenharmony_ci dev_err(dev, "failed to initialize regmap\n"); 63362306a36Sopenharmony_ci return PTR_ERR(info->regmap); 63462306a36Sopenharmony_ci } 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci info->ppmu.clk = devm_clk_get(dev, "ppmu"); 63762306a36Sopenharmony_ci if (IS_ERR(info->ppmu.clk)) { 63862306a36Sopenharmony_ci info->ppmu.clk = NULL; 63962306a36Sopenharmony_ci dev_warn(dev, "cannot get PPMU clock\n"); 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci ret = of_get_devfreq_events(np, info); 64362306a36Sopenharmony_ci if (ret < 0) { 64462306a36Sopenharmony_ci dev_err(dev, "failed to parse exynos ppmu dt node\n"); 64562306a36Sopenharmony_ci return ret; 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci return 0; 64962306a36Sopenharmony_ci} 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_cistatic int exynos_ppmu_probe(struct platform_device *pdev) 65262306a36Sopenharmony_ci{ 65362306a36Sopenharmony_ci struct exynos_ppmu *info; 65462306a36Sopenharmony_ci struct devfreq_event_dev **edev; 65562306a36Sopenharmony_ci struct devfreq_event_desc *desc; 65662306a36Sopenharmony_ci int i, ret = 0, size; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 65962306a36Sopenharmony_ci if (!info) 66062306a36Sopenharmony_ci return -ENOMEM; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci info->dev = &pdev->dev; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci /* Parse dt data to get resource */ 66562306a36Sopenharmony_ci ret = exynos_ppmu_parse_dt(pdev, info); 66662306a36Sopenharmony_ci if (ret < 0) { 66762306a36Sopenharmony_ci dev_err(&pdev->dev, 66862306a36Sopenharmony_ci "failed to parse devicetree for resource\n"); 66962306a36Sopenharmony_ci return ret; 67062306a36Sopenharmony_ci } 67162306a36Sopenharmony_ci desc = info->desc; 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci size = sizeof(struct devfreq_event_dev *) * info->num_events; 67462306a36Sopenharmony_ci info->edev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); 67562306a36Sopenharmony_ci if (!info->edev) 67662306a36Sopenharmony_ci return -ENOMEM; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci edev = info->edev; 67962306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci for (i = 0; i < info->num_events; i++) { 68262306a36Sopenharmony_ci edev[i] = devm_devfreq_event_add_edev(&pdev->dev, &desc[i]); 68362306a36Sopenharmony_ci if (IS_ERR(edev[i])) { 68462306a36Sopenharmony_ci dev_err(&pdev->dev, 68562306a36Sopenharmony_ci "failed to add devfreq-event device\n"); 68662306a36Sopenharmony_ci return PTR_ERR(edev[i]); 68762306a36Sopenharmony_ci } 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci pr_info("exynos-ppmu: new PPMU device registered %s (%s)\n", 69062306a36Sopenharmony_ci dev_name(&pdev->dev), desc[i].name); 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci ret = clk_prepare_enable(info->ppmu.clk); 69462306a36Sopenharmony_ci if (ret) { 69562306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to prepare ppmu clock\n"); 69662306a36Sopenharmony_ci return ret; 69762306a36Sopenharmony_ci } 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci return 0; 70062306a36Sopenharmony_ci} 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_cistatic int exynos_ppmu_remove(struct platform_device *pdev) 70362306a36Sopenharmony_ci{ 70462306a36Sopenharmony_ci struct exynos_ppmu *info = platform_get_drvdata(pdev); 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci clk_disable_unprepare(info->ppmu.clk); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci return 0; 70962306a36Sopenharmony_ci} 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_cistatic struct platform_driver exynos_ppmu_driver = { 71262306a36Sopenharmony_ci .probe = exynos_ppmu_probe, 71362306a36Sopenharmony_ci .remove = exynos_ppmu_remove, 71462306a36Sopenharmony_ci .driver = { 71562306a36Sopenharmony_ci .name = "exynos-ppmu", 71662306a36Sopenharmony_ci .of_match_table = exynos_ppmu_id_match, 71762306a36Sopenharmony_ci }, 71862306a36Sopenharmony_ci}; 71962306a36Sopenharmony_cimodule_platform_driver(exynos_ppmu_driver); 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ciMODULE_DESCRIPTION("Exynos PPMU(Platform Performance Monitoring Unit) driver"); 72262306a36Sopenharmony_ciMODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); 72362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 724