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