162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
462306a36Sopenharmony_ci * Author: Lin Huang <hl@rock-chips.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk.h>
862306a36Sopenharmony_ci#include <linux/devfreq-event.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/regmap.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/list.h>
1962306a36Sopenharmony_ci#include <linux/of.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <soc/rockchip/rk3399_grf.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define RK3399_DMC_NUM_CH	2
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/* DDRMON_CTRL */
2662306a36Sopenharmony_ci#define DDRMON_CTRL	0x04
2762306a36Sopenharmony_ci#define CLR_DDRMON_CTRL	(0x1f0000 << 0)
2862306a36Sopenharmony_ci#define LPDDR4_EN	(0x10001 << 4)
2962306a36Sopenharmony_ci#define HARDWARE_EN	(0x10001 << 3)
3062306a36Sopenharmony_ci#define LPDDR3_EN	(0x10001 << 2)
3162306a36Sopenharmony_ci#define SOFTWARE_EN	(0x10001 << 1)
3262306a36Sopenharmony_ci#define SOFTWARE_DIS	(0x10000 << 1)
3362306a36Sopenharmony_ci#define TIME_CNT_EN	(0x10001 << 0)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define DDRMON_CH0_COUNT_NUM		0x28
3662306a36Sopenharmony_ci#define DDRMON_CH0_DFI_ACCESS_NUM	0x2c
3762306a36Sopenharmony_ci#define DDRMON_CH1_COUNT_NUM		0x3c
3862306a36Sopenharmony_ci#define DDRMON_CH1_DFI_ACCESS_NUM	0x40
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistruct dmc_usage {
4162306a36Sopenharmony_ci	u32 access;
4262306a36Sopenharmony_ci	u32 total;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/*
4662306a36Sopenharmony_ci * The dfi controller can monitor DDR load. It has an upper and lower threshold
4762306a36Sopenharmony_ci * for the operating points. Whenever the usage leaves these bounds an event is
4862306a36Sopenharmony_ci * generated to indicate the DDR frequency should be changed.
4962306a36Sopenharmony_ci */
5062306a36Sopenharmony_cistruct rockchip_dfi {
5162306a36Sopenharmony_ci	struct devfreq_event_dev *edev;
5262306a36Sopenharmony_ci	struct devfreq_event_desc *desc;
5362306a36Sopenharmony_ci	struct dmc_usage ch_usage[RK3399_DMC_NUM_CH];
5462306a36Sopenharmony_ci	struct device *dev;
5562306a36Sopenharmony_ci	void __iomem *regs;
5662306a36Sopenharmony_ci	struct regmap *regmap_pmu;
5762306a36Sopenharmony_ci	struct clk *clk;
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
6362306a36Sopenharmony_ci	void __iomem *dfi_regs = info->regs;
6462306a36Sopenharmony_ci	u32 val;
6562306a36Sopenharmony_ci	u32 ddr_type;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	/* get ddr type */
6862306a36Sopenharmony_ci	regmap_read(info->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val);
6962306a36Sopenharmony_ci	ddr_type = (val >> RK3399_PMUGRF_DDRTYPE_SHIFT) &
7062306a36Sopenharmony_ci		    RK3399_PMUGRF_DDRTYPE_MASK;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/* clear DDRMON_CTRL setting */
7362306a36Sopenharmony_ci	writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* set ddr type to dfi */
7662306a36Sopenharmony_ci	if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR3)
7762306a36Sopenharmony_ci		writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL);
7862306a36Sopenharmony_ci	else if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR4)
7962306a36Sopenharmony_ci		writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* enable count, use software mode */
8262306a36Sopenharmony_ci	writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
8862306a36Sopenharmony_ci	void __iomem *dfi_regs = info->regs;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	writel_relaxed(SOFTWARE_DIS, dfi_regs + DDRMON_CTRL);
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
9662306a36Sopenharmony_ci	u32 tmp, max = 0;
9762306a36Sopenharmony_ci	u32 i, busier_ch = 0;
9862306a36Sopenharmony_ci	void __iomem *dfi_regs = info->regs;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	rockchip_dfi_stop_hardware_counter(edev);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* Find out which channel is busier */
10362306a36Sopenharmony_ci	for (i = 0; i < RK3399_DMC_NUM_CH; i++) {
10462306a36Sopenharmony_ci		info->ch_usage[i].access = readl_relaxed(dfi_regs +
10562306a36Sopenharmony_ci				DDRMON_CH0_DFI_ACCESS_NUM + i * 20) * 4;
10662306a36Sopenharmony_ci		info->ch_usage[i].total = readl_relaxed(dfi_regs +
10762306a36Sopenharmony_ci				DDRMON_CH0_COUNT_NUM + i * 20);
10862306a36Sopenharmony_ci		tmp = info->ch_usage[i].access;
10962306a36Sopenharmony_ci		if (tmp > max) {
11062306a36Sopenharmony_ci			busier_ch = i;
11162306a36Sopenharmony_ci			max = tmp;
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	rockchip_dfi_start_hardware_counter(edev);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return busier_ch;
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic int rockchip_dfi_disable(struct devfreq_event_dev *edev)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	rockchip_dfi_stop_hardware_counter(edev);
12462306a36Sopenharmony_ci	clk_disable_unprepare(info->clk);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int rockchip_dfi_enable(struct devfreq_event_dev *edev)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
13262306a36Sopenharmony_ci	int ret;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	ret = clk_prepare_enable(info->clk);
13562306a36Sopenharmony_ci	if (ret) {
13662306a36Sopenharmony_ci		dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret);
13762306a36Sopenharmony_ci		return ret;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	rockchip_dfi_start_hardware_counter(edev);
14162306a36Sopenharmony_ci	return 0;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int rockchip_dfi_set_event(struct devfreq_event_dev *edev)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	return 0;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic int rockchip_dfi_get_event(struct devfreq_event_dev *edev,
15062306a36Sopenharmony_ci				  struct devfreq_event_data *edata)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
15362306a36Sopenharmony_ci	int busier_ch;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	busier_ch = rockchip_dfi_get_busier_ch(edev);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	edata->load_count = info->ch_usage[busier_ch].access;
15862306a36Sopenharmony_ci	edata->total_count = info->ch_usage[busier_ch].total;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return 0;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic const struct devfreq_event_ops rockchip_dfi_ops = {
16462306a36Sopenharmony_ci	.disable = rockchip_dfi_disable,
16562306a36Sopenharmony_ci	.enable = rockchip_dfi_enable,
16662306a36Sopenharmony_ci	.get_event = rockchip_dfi_get_event,
16762306a36Sopenharmony_ci	.set_event = rockchip_dfi_set_event,
16862306a36Sopenharmony_ci};
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic const struct of_device_id rockchip_dfi_id_match[] = {
17162306a36Sopenharmony_ci	{ .compatible = "rockchip,rk3399-dfi" },
17262306a36Sopenharmony_ci	{ },
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rockchip_dfi_id_match);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic int rockchip_dfi_probe(struct platform_device *pdev)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
17962306a36Sopenharmony_ci	struct rockchip_dfi *data;
18062306a36Sopenharmony_ci	struct devfreq_event_desc *desc;
18162306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node, *node;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL);
18462306a36Sopenharmony_ci	if (!data)
18562306a36Sopenharmony_ci		return -ENOMEM;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	data->regs = devm_platform_ioremap_resource(pdev, 0);
18862306a36Sopenharmony_ci	if (IS_ERR(data->regs))
18962306a36Sopenharmony_ci		return PTR_ERR(data->regs);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	data->clk = devm_clk_get(dev, "pclk_ddr_mon");
19262306a36Sopenharmony_ci	if (IS_ERR(data->clk))
19362306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(data->clk),
19462306a36Sopenharmony_ci				     "Cannot get the clk pclk_ddr_mon\n");
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	node = of_parse_phandle(np, "rockchip,pmu", 0);
19762306a36Sopenharmony_ci	if (!node)
19862306a36Sopenharmony_ci		return dev_err_probe(&pdev->dev, -ENODEV, "Can't find pmu_grf registers\n");
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	data->regmap_pmu = syscon_node_to_regmap(node);
20162306a36Sopenharmony_ci	of_node_put(node);
20262306a36Sopenharmony_ci	if (IS_ERR(data->regmap_pmu))
20362306a36Sopenharmony_ci		return PTR_ERR(data->regmap_pmu);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	data->dev = dev;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
20862306a36Sopenharmony_ci	if (!desc)
20962306a36Sopenharmony_ci		return -ENOMEM;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	desc->ops = &rockchip_dfi_ops;
21262306a36Sopenharmony_ci	desc->driver_data = data;
21362306a36Sopenharmony_ci	desc->name = np->name;
21462306a36Sopenharmony_ci	data->desc = desc;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc);
21762306a36Sopenharmony_ci	if (IS_ERR(data->edev)) {
21862306a36Sopenharmony_ci		dev_err(&pdev->dev,
21962306a36Sopenharmony_ci			"failed to add devfreq-event device\n");
22062306a36Sopenharmony_ci		return PTR_ERR(data->edev);
22162306a36Sopenharmony_ci	}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic struct platform_driver rockchip_dfi_driver = {
22962306a36Sopenharmony_ci	.probe	= rockchip_dfi_probe,
23062306a36Sopenharmony_ci	.driver = {
23162306a36Sopenharmony_ci		.name	= "rockchip-dfi",
23262306a36Sopenharmony_ci		.of_match_table = rockchip_dfi_id_match,
23362306a36Sopenharmony_ci	},
23462306a36Sopenharmony_ci};
23562306a36Sopenharmony_cimodule_platform_driver(rockchip_dfi_driver);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
23862306a36Sopenharmony_ciMODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
23962306a36Sopenharmony_ciMODULE_DESCRIPTION("Rockchip DFI driver");
240