162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * exynos-nocp.c - Exynos NoC (Network On Chip) Probe support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2016 Samsung Electronics Co., Ltd. 662306a36Sopenharmony_ci * Author : Chanwoo Choi <cw00.choi@samsung.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/clk.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/devfreq-event.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "exynos-nocp.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cistruct exynos_nocp { 2062306a36Sopenharmony_ci struct devfreq_event_dev *edev; 2162306a36Sopenharmony_ci struct devfreq_event_desc desc; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci struct device *dev; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci struct regmap *regmap; 2662306a36Sopenharmony_ci struct clk *clk; 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * The devfreq-event ops structure for nocp probe. 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistatic int exynos_nocp_set_event(struct devfreq_event_dev *edev) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); 3562306a36Sopenharmony_ci int ret; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* Disable NoC probe */ 3862306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, 3962306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK, 0); 4062306a36Sopenharmony_ci if (ret < 0) { 4162306a36Sopenharmony_ci dev_err(nocp->dev, "failed to disable the NoC probe device\n"); 4262306a36Sopenharmony_ci return ret; 4362306a36Sopenharmony_ci } 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* Set a statistics dump period to 0 */ 4662306a36Sopenharmony_ci ret = regmap_write(nocp->regmap, NOCP_STAT_PERIOD, 0x0); 4762306a36Sopenharmony_ci if (ret < 0) 4862306a36Sopenharmony_ci goto out; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* Set the IntEvent fields of *_SRC */ 5162306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_SRC, 5262306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_MASK, 5362306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_BYTE_MASK); 5462306a36Sopenharmony_ci if (ret < 0) 5562306a36Sopenharmony_ci goto out; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_SRC, 5862306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_MASK, 5962306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); 6062306a36Sopenharmony_ci if (ret < 0) 6162306a36Sopenharmony_ci goto out; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_SRC, 6462306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_MASK, 6562306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_CYCLE_MASK); 6662306a36Sopenharmony_ci if (ret < 0) 6762306a36Sopenharmony_ci goto out; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_SRC, 7062306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_MASK, 7162306a36Sopenharmony_ci NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); 7262306a36Sopenharmony_ci if (ret < 0) 7362306a36Sopenharmony_ci goto out; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* Set an alarm with a max/min value of 0 to generate StatALARM */ 7762306a36Sopenharmony_ci ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MIN, 0x0); 7862306a36Sopenharmony_ci if (ret < 0) 7962306a36Sopenharmony_ci goto out; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MAX, 0x0); 8262306a36Sopenharmony_ci if (ret < 0) 8362306a36Sopenharmony_ci goto out; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* Set AlarmMode */ 8662306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_0_ALARM_MODE, 8762306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MASK, 8862306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); 8962306a36Sopenharmony_ci if (ret < 0) 9062306a36Sopenharmony_ci goto out; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_1_ALARM_MODE, 9362306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MASK, 9462306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); 9562306a36Sopenharmony_ci if (ret < 0) 9662306a36Sopenharmony_ci goto out; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_2_ALARM_MODE, 9962306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MASK, 10062306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); 10162306a36Sopenharmony_ci if (ret < 0) 10262306a36Sopenharmony_ci goto out; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_COUNTERS_3_ALARM_MODE, 10562306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MASK, 10662306a36Sopenharmony_ci NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); 10762306a36Sopenharmony_ci if (ret < 0) 10862306a36Sopenharmony_ci goto out; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Enable the measurements by setting AlarmEn and StatEn */ 11162306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, 11262306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK, 11362306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK); 11462306a36Sopenharmony_ci if (ret < 0) 11562306a36Sopenharmony_ci goto out; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* Set GlobalEN */ 11862306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_CFG_CTL, 11962306a36Sopenharmony_ci NOCP_CFG_CTL_GLOBALEN_MASK, 12062306a36Sopenharmony_ci NOCP_CFG_CTL_GLOBALEN_MASK); 12162306a36Sopenharmony_ci if (ret < 0) 12262306a36Sopenharmony_ci goto out; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Enable NoC probe */ 12562306a36Sopenharmony_ci ret = regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, 12662306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK, 12762306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK); 12862306a36Sopenharmony_ci if (ret < 0) 12962306a36Sopenharmony_ci goto out; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return 0; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ciout: 13462306a36Sopenharmony_ci /* Reset NoC probe */ 13562306a36Sopenharmony_ci if (regmap_update_bits(nocp->regmap, NOCP_MAIN_CTL, 13662306a36Sopenharmony_ci NOCP_MAIN_CTL_STATEN_MASK, 0)) { 13762306a36Sopenharmony_ci dev_err(nocp->dev, "Failed to reset NoC probe device\n"); 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return ret; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int exynos_nocp_get_event(struct devfreq_event_dev *edev, 14462306a36Sopenharmony_ci struct devfreq_event_data *edata) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); 14762306a36Sopenharmony_ci unsigned int counter[4]; 14862306a36Sopenharmony_ci int ret; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* Read cycle count */ 15162306a36Sopenharmony_ci ret = regmap_read(nocp->regmap, NOCP_COUNTERS_0_VAL, &counter[0]); 15262306a36Sopenharmony_ci if (ret < 0) 15362306a36Sopenharmony_ci goto out; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci ret = regmap_read(nocp->regmap, NOCP_COUNTERS_1_VAL, &counter[1]); 15662306a36Sopenharmony_ci if (ret < 0) 15762306a36Sopenharmony_ci goto out; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ret = regmap_read(nocp->regmap, NOCP_COUNTERS_2_VAL, &counter[2]); 16062306a36Sopenharmony_ci if (ret < 0) 16162306a36Sopenharmony_ci goto out; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci ret = regmap_read(nocp->regmap, NOCP_COUNTERS_3_VAL, &counter[3]); 16462306a36Sopenharmony_ci if (ret < 0) 16562306a36Sopenharmony_ci goto out; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci edata->load_count = ((counter[1] << 16) | counter[0]); 16862306a36Sopenharmony_ci edata->total_count = ((counter[3] << 16) | counter[2]); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n", edev->desc->name, 17162306a36Sopenharmony_ci edata->load_count, edata->total_count); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return 0; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ciout: 17662306a36Sopenharmony_ci dev_err(nocp->dev, "Failed to read the counter of NoC probe device\n"); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return ret; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic const struct devfreq_event_ops exynos_nocp_ops = { 18262306a36Sopenharmony_ci .set_event = exynos_nocp_set_event, 18362306a36Sopenharmony_ci .get_event = exynos_nocp_get_event, 18462306a36Sopenharmony_ci}; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic const struct of_device_id exynos_nocp_id_match[] = { 18762306a36Sopenharmony_ci { .compatible = "samsung,exynos5420-nocp", }, 18862306a36Sopenharmony_ci { /* sentinel */ }, 18962306a36Sopenharmony_ci}; 19062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_nocp_id_match); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic struct regmap_config exynos_nocp_regmap_config = { 19362306a36Sopenharmony_ci .reg_bits = 32, 19462306a36Sopenharmony_ci .val_bits = 32, 19562306a36Sopenharmony_ci .reg_stride = 4, 19662306a36Sopenharmony_ci .max_register = NOCP_COUNTERS_3_VAL, 19762306a36Sopenharmony_ci}; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int exynos_nocp_parse_dt(struct platform_device *pdev, 20062306a36Sopenharmony_ci struct exynos_nocp *nocp) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct device *dev = nocp->dev; 20362306a36Sopenharmony_ci struct device_node *np = dev->of_node; 20462306a36Sopenharmony_ci struct resource *res; 20562306a36Sopenharmony_ci void __iomem *base; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (!np) { 20862306a36Sopenharmony_ci dev_err(dev, "failed to find devicetree node\n"); 20962306a36Sopenharmony_ci return -EINVAL; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci nocp->clk = devm_clk_get(dev, "nocp"); 21362306a36Sopenharmony_ci if (IS_ERR(nocp->clk)) 21462306a36Sopenharmony_ci nocp->clk = NULL; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* Maps the memory mapped IO to control nocp register */ 21762306a36Sopenharmony_ci base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 21862306a36Sopenharmony_ci if (IS_ERR(base)) 21962306a36Sopenharmony_ci return PTR_ERR(base); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci exynos_nocp_regmap_config.max_register = resource_size(res) - 4; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci nocp->regmap = devm_regmap_init_mmio(dev, base, 22462306a36Sopenharmony_ci &exynos_nocp_regmap_config); 22562306a36Sopenharmony_ci if (IS_ERR(nocp->regmap)) { 22662306a36Sopenharmony_ci dev_err(dev, "failed to initialize regmap\n"); 22762306a36Sopenharmony_ci return PTR_ERR(nocp->regmap); 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic int exynos_nocp_probe(struct platform_device *pdev) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci struct device *dev = &pdev->dev; 23662306a36Sopenharmony_ci struct device_node *np = dev->of_node; 23762306a36Sopenharmony_ci struct exynos_nocp *nocp; 23862306a36Sopenharmony_ci int ret; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci nocp = devm_kzalloc(&pdev->dev, sizeof(*nocp), GFP_KERNEL); 24162306a36Sopenharmony_ci if (!nocp) 24262306a36Sopenharmony_ci return -ENOMEM; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci nocp->dev = &pdev->dev; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* Parse dt data to get resource */ 24762306a36Sopenharmony_ci ret = exynos_nocp_parse_dt(pdev, nocp); 24862306a36Sopenharmony_ci if (ret < 0) { 24962306a36Sopenharmony_ci dev_err(&pdev->dev, 25062306a36Sopenharmony_ci "failed to parse devicetree for resource\n"); 25162306a36Sopenharmony_ci return ret; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* Add devfreq-event device to measure the bandwidth of NoC */ 25562306a36Sopenharmony_ci nocp->desc.ops = &exynos_nocp_ops; 25662306a36Sopenharmony_ci nocp->desc.driver_data = nocp; 25762306a36Sopenharmony_ci nocp->desc.name = np->full_name; 25862306a36Sopenharmony_ci nocp->edev = devm_devfreq_event_add_edev(&pdev->dev, &nocp->desc); 25962306a36Sopenharmony_ci if (IS_ERR(nocp->edev)) { 26062306a36Sopenharmony_ci dev_err(&pdev->dev, 26162306a36Sopenharmony_ci "failed to add devfreq-event device\n"); 26262306a36Sopenharmony_ci return PTR_ERR(nocp->edev); 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci platform_set_drvdata(pdev, nocp); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci ret = clk_prepare_enable(nocp->clk); 26762306a36Sopenharmony_ci if (ret) { 26862306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to prepare ppmu clock\n"); 26962306a36Sopenharmony_ci return ret; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci pr_info("exynos-nocp: new NoC Probe device registered: %s\n", 27362306a36Sopenharmony_ci dev_name(dev)); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci return 0; 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_cistatic int exynos_nocp_remove(struct platform_device *pdev) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci struct exynos_nocp *nocp = platform_get_drvdata(pdev); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci clk_disable_unprepare(nocp->clk); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci return 0; 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic struct platform_driver exynos_nocp_driver = { 28862306a36Sopenharmony_ci .probe = exynos_nocp_probe, 28962306a36Sopenharmony_ci .remove = exynos_nocp_remove, 29062306a36Sopenharmony_ci .driver = { 29162306a36Sopenharmony_ci .name = "exynos-nocp", 29262306a36Sopenharmony_ci .of_match_table = exynos_nocp_id_match, 29362306a36Sopenharmony_ci }, 29462306a36Sopenharmony_ci}; 29562306a36Sopenharmony_cimodule_platform_driver(exynos_nocp_driver); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ciMODULE_DESCRIPTION("Exynos NoC (Network on Chip) Probe driver"); 29862306a36Sopenharmony_ciMODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); 29962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 300