aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLin Huang <hl@rock-chips.com>2016-09-05 01:06:08 -0400
committerMyungJoo Ham <myungjoo.ham@samsung.com>2016-09-06 00:25:35 -0400
commitb9d1262bca0afcbb67fdb309ed45b34f0226e964 (patch)
tree149de30c807ef68f526fb0c5b8a1542f8c698f2f
parentd3d81969e38023265dfe5de18ea6a4d898dc6375 (diff)
PM / devfreq: event: support rockchip dfi controller
on rk3399 platform, there is dfi conroller can monitor ddr load, base on this result, we can do ddr freqency scaling. Signed-off-by: Lin Huang <hl@rock-chips.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Acked-by: Chanwoo Choi <cw00.choi@samsung.com>
-rw-r--r--drivers/devfreq/event/Kconfig7
-rw-r--r--drivers/devfreq/event/Makefile1
-rw-r--r--drivers/devfreq/event/rockchip-dfi.c256
3 files changed, 264 insertions, 0 deletions
diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig
index 1d1cd781e386..0fdae8608961 100644
--- a/drivers/devfreq/event/Kconfig
+++ b/drivers/devfreq/event/Kconfig
@@ -30,4 +30,11 @@ config DEVFREQ_EVENT_EXYNOS_PPMU
30 (Platform Performance Monitoring Unit) counters to estimate the 30 (Platform Performance Monitoring Unit) counters to estimate the
31 utilization of each module. 31 utilization of each module.
32 32
33config DEVFREQ_EVENT_ROCKCHIP_DFI
34 tristate "ROCKCHIP DFI DEVFREQ event Driver"
35 depends on ARCH_ROCKCHIP
36 help
37 This add the devfreq-event driver for Rockchip SoC. It provides DFI
38 (DDR Monitor Module) driver to count ddr load.
39
33endif # PM_DEVFREQ_EVENT 40endif # PM_DEVFREQ_EVENT
diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile
index 3d6afd352253..dda7090a47c6 100644
--- a/drivers/devfreq/event/Makefile
+++ b/drivers/devfreq/event/Makefile
@@ -2,3 +2,4 @@
2 2
3obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o 3obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o
4obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o 4obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
5obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o
diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c
new file mode 100644
index 000000000000..43fcc5a7f515
--- /dev/null
+++ b/drivers/devfreq/event/rockchip-dfi.c
@@ -0,0 +1,256 @@
1/*
2 * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
3 * Author: Lin Huang <hl@rock-chips.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 */
14
15#include <linux/clk.h>
16#include <linux/devfreq-event.h>
17#include <linux/kernel.h>
18#include <linux/err.h>
19#include <linux/init.h>
20#include <linux/io.h>
21#include <linux/mfd/syscon.h>
22#include <linux/module.h>
23#include <linux/platform_device.h>
24#include <linux/regmap.h>
25#include <linux/slab.h>
26#include <linux/list.h>
27#include <linux/of.h>
28
29#define RK3399_DMC_NUM_CH 2
30
31/* DDRMON_CTRL */
32#define DDRMON_CTRL 0x04
33#define CLR_DDRMON_CTRL (0x1f0000 << 0)
34#define LPDDR4_EN (0x10001 << 4)
35#define HARDWARE_EN (0x10001 << 3)
36#define LPDDR3_EN (0x10001 << 2)
37#define SOFTWARE_EN (0x10001 << 1)
38#define SOFTWARE_DIS (0x10000 << 1)
39#define TIME_CNT_EN (0x10001 << 0)
40
41#define DDRMON_CH0_COUNT_NUM 0x28
42#define DDRMON_CH0_DFI_ACCESS_NUM 0x2c
43#define DDRMON_CH1_COUNT_NUM 0x3c
44#define DDRMON_CH1_DFI_ACCESS_NUM 0x40
45
46/* pmu grf */
47#define PMUGRF_OS_REG2 0x308
48#define DDRTYPE_SHIFT 13
49#define DDRTYPE_MASK 7
50
51enum {
52 DDR3 = 3,
53 LPDDR3 = 6,
54 LPDDR4 = 7,
55 UNUSED = 0xFF
56};
57
58struct dmc_usage {
59 u32 access;
60 u32 total;
61};
62
63/*
64 * The dfi controller can monitor DDR load. It has an upper and lower threshold
65 * for the operating points. Whenever the usage leaves these bounds an event is
66 * generated to indicate the DDR frequency should be changed.
67 */
68struct rockchip_dfi {
69 struct devfreq_event_dev *edev;
70 struct devfreq_event_desc *desc;
71 struct dmc_usage ch_usage[RK3399_DMC_NUM_CH];
72 struct device *dev;
73 void __iomem *regs;
74 struct regmap *regmap_pmu;
75 struct clk *clk;
76};
77
78static void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev)
79{
80 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
81 void __iomem *dfi_regs = info->regs;
82 u32 val;
83 u32 ddr_type;
84
85 /* get ddr type */
86 regmap_read(info->regmap_pmu, PMUGRF_OS_REG2, &val);
87 ddr_type = (val >> DDRTYPE_SHIFT) & DDRTYPE_MASK;
88
89 /* clear DDRMON_CTRL setting */
90 writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL);
91
92 /* set ddr type to dfi */
93 if (ddr_type == LPDDR3)
94 writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL);
95 else if (ddr_type == LPDDR4)
96 writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL);
97
98 /* enable count, use software mode */
99 writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL);
100}
101
102static void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev)
103{
104 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
105 void __iomem *dfi_regs = info->regs;
106
107 writel_relaxed(SOFTWARE_DIS, dfi_regs + DDRMON_CTRL);
108}
109
110static int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev)
111{
112 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
113 u32 tmp, max = 0;
114 u32 i, busier_ch = 0;
115 void __iomem *dfi_regs = info->regs;
116
117 rockchip_dfi_stop_hardware_counter(edev);
118
119 /* Find out which channel is busier */
120 for (i = 0; i < RK3399_DMC_NUM_CH; i++) {
121 info->ch_usage[i].access = readl_relaxed(dfi_regs +
122 DDRMON_CH0_DFI_ACCESS_NUM + i * 20) * 4;
123 info->ch_usage[i].total = readl_relaxed(dfi_regs +
124 DDRMON_CH0_COUNT_NUM + i * 20);
125 tmp = info->ch_usage[i].access;
126 if (tmp > max) {
127 busier_ch = i;
128 max = tmp;
129 }
130 }
131 rockchip_dfi_start_hardware_counter(edev);
132
133 return busier_ch;
134}
135
136static int rockchip_dfi_disable(struct devfreq_event_dev *edev)
137{
138 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
139
140 rockchip_dfi_stop_hardware_counter(edev);
141 clk_disable_unprepare(info->clk);
142
143 return 0;
144}
145
146static int rockchip_dfi_enable(struct devfreq_event_dev *edev)
147{
148 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
149 int ret;
150
151 ret = clk_prepare_enable(info->clk);
152 if (ret) {
153 dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret);
154 return ret;
155 }
156
157 rockchip_dfi_start_hardware_counter(edev);
158 return 0;
159}
160
161static int rockchip_dfi_set_event(struct devfreq_event_dev *edev)
162{
163 return 0;
164}
165
166static int rockchip_dfi_get_event(struct devfreq_event_dev *edev,
167 struct devfreq_event_data *edata)
168{
169 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
170 int busier_ch;
171
172 busier_ch = rockchip_dfi_get_busier_ch(edev);
173
174 edata->load_count = info->ch_usage[busier_ch].access;
175 edata->total_count = info->ch_usage[busier_ch].total;
176
177 return 0;
178}
179
180static const struct devfreq_event_ops rockchip_dfi_ops = {
181 .disable = rockchip_dfi_disable,
182 .enable = rockchip_dfi_enable,
183 .get_event = rockchip_dfi_get_event,
184 .set_event = rockchip_dfi_set_event,
185};
186
187static const struct of_device_id rockchip_dfi_id_match[] = {
188 { .compatible = "rockchip,rk3399-dfi" },
189 { },
190};
191
192static int rockchip_dfi_probe(struct platform_device *pdev)
193{
194 struct device *dev = &pdev->dev;
195 struct rockchip_dfi *data;
196 struct resource *res;
197 struct devfreq_event_desc *desc;
198 struct device_node *np = pdev->dev.of_node, *node;
199
200 data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL);
201 if (!data)
202 return -ENOMEM;
203
204 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
205 data->regs = devm_ioremap_resource(&pdev->dev, res);
206 if (IS_ERR(data->regs))
207 return PTR_ERR(data->regs);
208
209 data->clk = devm_clk_get(dev, "pclk_ddr_mon");
210 if (IS_ERR(data->clk)) {
211 dev_err(dev, "Cannot get the clk dmc_clk\n");
212 return PTR_ERR(data->clk);
213 };
214
215 /* try to find the optional reference to the pmu syscon */
216 node = of_parse_phandle(np, "rockchip,pmu", 0);
217 if (node) {
218 data->regmap_pmu = syscon_node_to_regmap(node);
219 if (IS_ERR(data->regmap_pmu))
220 return PTR_ERR(data->regmap_pmu);
221 }
222 data->dev = dev;
223
224 desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
225 if (!desc)
226 return -ENOMEM;
227
228 desc->ops = &rockchip_dfi_ops;
229 desc->driver_data = data;
230 desc->name = np->name;
231 data->desc = desc;
232
233 data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc);
234 if (IS_ERR(data->edev)) {
235 dev_err(&pdev->dev,
236 "failed to add devfreq-event device\n");
237 return PTR_ERR(data->edev);
238 }
239
240 platform_set_drvdata(pdev, data);
241
242 return 0;
243}
244
245static struct platform_driver rockchip_dfi_driver = {
246 .probe = rockchip_dfi_probe,
247 .driver = {
248 .name = "rockchip-dfi",
249 .of_match_table = rockchip_dfi_id_match,
250 },
251};
252module_platform_driver(rockchip_dfi_driver);
253
254MODULE_LICENSE("GPL v2");
255MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
256MODULE_DESCRIPTION("Rockchip DFI driver");