Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Lin Huang and committed by
MyungJoo Ham
b9d1262b d3d81969

+264
+7
drivers/devfreq/event/Kconfig
··· 30 30 (Platform Performance Monitoring Unit) counters to estimate the 31 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 40 endif # PM_DEVFREQ_EVENT
+1
drivers/devfreq/event/Makefile
··· 2 2 3 3 obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o 4 4 obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o 5 + obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o
+256
drivers/devfreq/event/rockchip-dfi.c
··· 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");