diff options
author | Lin Huang <hl@rock-chips.com> | 2016-09-05 01:06:08 -0400 |
---|---|---|
committer | MyungJoo Ham <myungjoo.ham@samsung.com> | 2016-09-06 00:25:35 -0400 |
commit | b9d1262bca0afcbb67fdb309ed45b34f0226e964 (patch) | |
tree | 149de30c807ef68f526fb0c5b8a1542f8c698f2f | |
parent | d3d81969e38023265dfe5de18ea6a4d898dc6375 (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/Kconfig | 7 | ||||
-rw-r--r-- | drivers/devfreq/event/Makefile | 1 | ||||
-rw-r--r-- | drivers/devfreq/event/rockchip-dfi.c | 256 |
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 | ||
33 | config 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 | |||
33 | endif # PM_DEVFREQ_EVENT | 40 | endif # 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 | ||
3 | obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o | 3 | obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o |
4 | obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o | 4 | obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o |
5 | obj-$(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 | |||
51 | enum { | ||
52 | DDR3 = 3, | ||
53 | LPDDR3 = 6, | ||
54 | LPDDR4 = 7, | ||
55 | UNUSED = 0xFF | ||
56 | }; | ||
57 | |||
58 | struct 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 | */ | ||
68 | struct 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 | |||
78 | static 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 | |||
102 | static 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 | |||
110 | static 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 | |||
136 | static 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 | |||
146 | static 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 | |||
161 | static int rockchip_dfi_set_event(struct devfreq_event_dev *edev) | ||
162 | { | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static 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 | |||
180 | static 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 | |||
187 | static const struct of_device_id rockchip_dfi_id_match[] = { | ||
188 | { .compatible = "rockchip,rk3399-dfi" }, | ||
189 | { }, | ||
190 | }; | ||
191 | |||
192 | static 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 | |||
245 | static 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 | }; | ||
252 | module_platform_driver(rockchip_dfi_driver); | ||
253 | |||
254 | MODULE_LICENSE("GPL v2"); | ||
255 | MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); | ||
256 | MODULE_DESCRIPTION("Rockchip DFI driver"); | ||