1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd 4 * Author: Lin Huang <hl@rock-chips.com> 5 */ 6 7 #include <linux/clk.h> 8 #include <linux/devfreq-event.h> 9 #include <linux/kernel.h> 10 #include <linux/err.h> 11 #include <linux/init.h> 12 #include <linux/io.h> 13 #include <linux/mfd/syscon.h> 14 #include <linux/module.h> 15 #include <linux/platform_device.h> 16 #include <linux/regmap.h> 17 #include <linux/slab.h> 18 #include <linux/list.h> 19 #include <linux/of.h> 20 21 #include <soc/rockchip/rk3399_grf.h> 22 23 #define RK3399_DMC_NUM_CH 2 24 25 /* DDRMON_CTRL */ 26 #define DDRMON_CTRL 0x04 27 #define CLR_DDRMON_CTRL (0x1f0000 << 0) 28 #define LPDDR4_EN (0x10001 << 4) 29 #define HARDWARE_EN (0x10001 << 3) 30 #define LPDDR3_EN (0x10001 << 2) 31 #define SOFTWARE_EN (0x10001 << 1) 32 #define SOFTWARE_DIS (0x10000 << 1) 33 #define TIME_CNT_EN (0x10001 << 0) 34 35 #define DDRMON_CH0_COUNT_NUM 0x28 36 #define DDRMON_CH0_DFI_ACCESS_NUM 0x2c 37 #define DDRMON_CH1_COUNT_NUM 0x3c 38 #define DDRMON_CH1_DFI_ACCESS_NUM 0x40 39 40 struct dmc_usage { 41 u32 access; 42 u32 total; 43 }; 44 45 /* 46 * The dfi controller can monitor DDR load. It has an upper and lower threshold 47 * for the operating points. Whenever the usage leaves these bounds an event is 48 * generated to indicate the DDR frequency should be changed. 49 */ 50 struct rockchip_dfi { 51 struct devfreq_event_dev *edev; 52 struct devfreq_event_desc *desc; 53 struct dmc_usage ch_usage[RK3399_DMC_NUM_CH]; 54 struct device *dev; 55 void __iomem *regs; 56 struct regmap *regmap_pmu; 57 struct clk *clk; 58 }; 59 60 static void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev) 61 { 62 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 63 void __iomem *dfi_regs = info->regs; 64 u32 val; 65 u32 ddr_type; 66 67 /* get ddr type */ 68 regmap_read(info->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val); 69 ddr_type = (val >> RK3399_PMUGRF_DDRTYPE_SHIFT) & 70 RK3399_PMUGRF_DDRTYPE_MASK; 71 72 /* clear DDRMON_CTRL setting */ 73 writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL); 74 75 /* set ddr type to dfi */ 76 if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR3) 77 writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL); 78 else if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR4) 79 writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL); 80 81 /* enable count, use software mode */ 82 writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL); 83 } 84 85 static void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev) 86 { 87 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 88 void __iomem *dfi_regs = info->regs; 89 90 writel_relaxed(SOFTWARE_DIS, dfi_regs + DDRMON_CTRL); 91 } 92 93 static int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev) 94 { 95 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 96 u32 tmp, max = 0; 97 u32 i, busier_ch = 0; 98 void __iomem *dfi_regs = info->regs; 99 100 rockchip_dfi_stop_hardware_counter(edev); 101 102 /* Find out which channel is busier */ 103 for (i = 0; i < RK3399_DMC_NUM_CH; i++) { 104 info->ch_usage[i].access = readl_relaxed(dfi_regs + 105 DDRMON_CH0_DFI_ACCESS_NUM + i * 20) * 4; 106 info->ch_usage[i].total = readl_relaxed(dfi_regs + 107 DDRMON_CH0_COUNT_NUM + i * 20); 108 tmp = info->ch_usage[i].access; 109 if (tmp > max) { 110 busier_ch = i; 111 max = tmp; 112 } 113 } 114 rockchip_dfi_start_hardware_counter(edev); 115 116 return busier_ch; 117 } 118 119 static int rockchip_dfi_disable(struct devfreq_event_dev *edev) 120 { 121 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 122 123 rockchip_dfi_stop_hardware_counter(edev); 124 clk_disable_unprepare(info->clk); 125 126 return 0; 127 } 128 129 static int rockchip_dfi_enable(struct devfreq_event_dev *edev) 130 { 131 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 132 int ret; 133 134 ret = clk_prepare_enable(info->clk); 135 if (ret) { 136 dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret); 137 return ret; 138 } 139 140 rockchip_dfi_start_hardware_counter(edev); 141 return 0; 142 } 143 144 static int rockchip_dfi_set_event(struct devfreq_event_dev *edev) 145 { 146 return 0; 147 } 148 149 static int rockchip_dfi_get_event(struct devfreq_event_dev *edev, 150 struct devfreq_event_data *edata) 151 { 152 struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 153 int busier_ch; 154 155 busier_ch = rockchip_dfi_get_busier_ch(edev); 156 157 edata->load_count = info->ch_usage[busier_ch].access; 158 edata->total_count = info->ch_usage[busier_ch].total; 159 160 return 0; 161 } 162 163 static const struct devfreq_event_ops rockchip_dfi_ops = { 164 .disable = rockchip_dfi_disable, 165 .enable = rockchip_dfi_enable, 166 .get_event = rockchip_dfi_get_event, 167 .set_event = rockchip_dfi_set_event, 168 }; 169 170 static const struct of_device_id rockchip_dfi_id_match[] = { 171 { .compatible = "rockchip,rk3399-dfi" }, 172 { }, 173 }; 174 MODULE_DEVICE_TABLE(of, rockchip_dfi_id_match); 175 176 static int rockchip_dfi_probe(struct platform_device *pdev) 177 { 178 struct device *dev = &pdev->dev; 179 struct rockchip_dfi *data; 180 struct resource *res; 181 struct devfreq_event_desc *desc; 182 struct device_node *np = pdev->dev.of_node, *node; 183 184 data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL); 185 if (!data) 186 return -ENOMEM; 187 188 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 189 data->regs = devm_ioremap_resource(&pdev->dev, res); 190 if (IS_ERR(data->regs)) 191 return PTR_ERR(data->regs); 192 193 data->clk = devm_clk_get(dev, "pclk_ddr_mon"); 194 if (IS_ERR(data->clk)) { 195 dev_err(dev, "Cannot get the clk dmc_clk\n"); 196 return PTR_ERR(data->clk); 197 } 198 199 /* try to find the optional reference to the pmu syscon */ 200 node = of_parse_phandle(np, "rockchip,pmu", 0); 201 if (node) { 202 data->regmap_pmu = syscon_node_to_regmap(node); 203 if (IS_ERR(data->regmap_pmu)) 204 return PTR_ERR(data->regmap_pmu); 205 } 206 data->dev = dev; 207 208 desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); 209 if (!desc) 210 return -ENOMEM; 211 212 desc->ops = &rockchip_dfi_ops; 213 desc->driver_data = data; 214 desc->name = np->name; 215 data->desc = desc; 216 217 data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc); 218 if (IS_ERR(data->edev)) { 219 dev_err(&pdev->dev, 220 "failed to add devfreq-event device\n"); 221 return PTR_ERR(data->edev); 222 } 223 224 platform_set_drvdata(pdev, data); 225 226 return 0; 227 } 228 229 static struct platform_driver rockchip_dfi_driver = { 230 .probe = rockchip_dfi_probe, 231 .driver = { 232 .name = "rockchip-dfi", 233 .of_match_table = rockchip_dfi_id_match, 234 }, 235 }; 236 module_platform_driver(rockchip_dfi_driver); 237 238 MODULE_LICENSE("GPL v2"); 239 MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); 240 MODULE_DESCRIPTION("Rockchip DFI driver"); 241