18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd 48c2ecf20Sopenharmony_ci * Author: Lin Huang <hl@rock-chips.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci#include <linux/devfreq-event.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/err.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/regmap.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/list.h> 198c2ecf20Sopenharmony_ci#include <linux/of.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <soc/rockchip/rk3399_grf.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define RK3399_DMC_NUM_CH 2 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* DDRMON_CTRL */ 268c2ecf20Sopenharmony_ci#define DDRMON_CTRL 0x04 278c2ecf20Sopenharmony_ci#define CLR_DDRMON_CTRL (0x1f0000 << 0) 288c2ecf20Sopenharmony_ci#define LPDDR4_EN (0x10001 << 4) 298c2ecf20Sopenharmony_ci#define HARDWARE_EN (0x10001 << 3) 308c2ecf20Sopenharmony_ci#define LPDDR3_EN (0x10001 << 2) 318c2ecf20Sopenharmony_ci#define SOFTWARE_EN (0x10001 << 1) 328c2ecf20Sopenharmony_ci#define SOFTWARE_DIS (0x10000 << 1) 338c2ecf20Sopenharmony_ci#define TIME_CNT_EN (0x10001 << 0) 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define DDRMON_CH0_COUNT_NUM 0x28 368c2ecf20Sopenharmony_ci#define DDRMON_CH0_DFI_ACCESS_NUM 0x2c 378c2ecf20Sopenharmony_ci#define DDRMON_CH1_COUNT_NUM 0x3c 388c2ecf20Sopenharmony_ci#define DDRMON_CH1_DFI_ACCESS_NUM 0x40 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistruct dmc_usage { 418c2ecf20Sopenharmony_ci u32 access; 428c2ecf20Sopenharmony_ci u32 total; 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* 468c2ecf20Sopenharmony_ci * The dfi controller can monitor DDR load. It has an upper and lower threshold 478c2ecf20Sopenharmony_ci * for the operating points. Whenever the usage leaves these bounds an event is 488c2ecf20Sopenharmony_ci * generated to indicate the DDR frequency should be changed. 498c2ecf20Sopenharmony_ci */ 508c2ecf20Sopenharmony_cistruct rockchip_dfi { 518c2ecf20Sopenharmony_ci struct devfreq_event_dev *edev; 528c2ecf20Sopenharmony_ci struct devfreq_event_desc *desc; 538c2ecf20Sopenharmony_ci struct dmc_usage ch_usage[RK3399_DMC_NUM_CH]; 548c2ecf20Sopenharmony_ci struct device *dev; 558c2ecf20Sopenharmony_ci void __iomem *regs; 568c2ecf20Sopenharmony_ci struct regmap *regmap_pmu; 578c2ecf20Sopenharmony_ci struct clk *clk; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 638c2ecf20Sopenharmony_ci void __iomem *dfi_regs = info->regs; 648c2ecf20Sopenharmony_ci u32 val; 658c2ecf20Sopenharmony_ci u32 ddr_type; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* get ddr type */ 688c2ecf20Sopenharmony_ci regmap_read(info->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val); 698c2ecf20Sopenharmony_ci ddr_type = (val >> RK3399_PMUGRF_DDRTYPE_SHIFT) & 708c2ecf20Sopenharmony_ci RK3399_PMUGRF_DDRTYPE_MASK; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* clear DDRMON_CTRL setting */ 738c2ecf20Sopenharmony_ci writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* set ddr type to dfi */ 768c2ecf20Sopenharmony_ci if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR3) 778c2ecf20Sopenharmony_ci writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL); 788c2ecf20Sopenharmony_ci else if (ddr_type == RK3399_PMUGRF_DDRTYPE_LPDDR4) 798c2ecf20Sopenharmony_ci writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* enable count, use software mode */ 828c2ecf20Sopenharmony_ci writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev) 868c2ecf20Sopenharmony_ci{ 878c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 888c2ecf20Sopenharmony_ci void __iomem *dfi_regs = info->regs; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci writel_relaxed(SOFTWARE_DIS, dfi_regs + DDRMON_CTRL); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 968c2ecf20Sopenharmony_ci u32 tmp, max = 0; 978c2ecf20Sopenharmony_ci u32 i, busier_ch = 0; 988c2ecf20Sopenharmony_ci void __iomem *dfi_regs = info->regs; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci rockchip_dfi_stop_hardware_counter(edev); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Find out which channel is busier */ 1038c2ecf20Sopenharmony_ci for (i = 0; i < RK3399_DMC_NUM_CH; i++) { 1048c2ecf20Sopenharmony_ci info->ch_usage[i].access = readl_relaxed(dfi_regs + 1058c2ecf20Sopenharmony_ci DDRMON_CH0_DFI_ACCESS_NUM + i * 20) * 4; 1068c2ecf20Sopenharmony_ci info->ch_usage[i].total = readl_relaxed(dfi_regs + 1078c2ecf20Sopenharmony_ci DDRMON_CH0_COUNT_NUM + i * 20); 1088c2ecf20Sopenharmony_ci tmp = info->ch_usage[i].access; 1098c2ecf20Sopenharmony_ci if (tmp > max) { 1108c2ecf20Sopenharmony_ci busier_ch = i; 1118c2ecf20Sopenharmony_ci max = tmp; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci rockchip_dfi_start_hardware_counter(edev); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci return busier_ch; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int rockchip_dfi_disable(struct devfreq_event_dev *edev) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci rockchip_dfi_stop_hardware_counter(edev); 1248c2ecf20Sopenharmony_ci clk_disable_unprepare(info->clk); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci return 0; 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic int rockchip_dfi_enable(struct devfreq_event_dev *edev) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 1328c2ecf20Sopenharmony_ci int ret; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci ret = clk_prepare_enable(info->clk); 1358c2ecf20Sopenharmony_ci if (ret) { 1368c2ecf20Sopenharmony_ci dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret); 1378c2ecf20Sopenharmony_ci return ret; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci rockchip_dfi_start_hardware_counter(edev); 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int rockchip_dfi_set_event(struct devfreq_event_dev *edev) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci return 0; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic int rockchip_dfi_get_event(struct devfreq_event_dev *edev, 1508c2ecf20Sopenharmony_ci struct devfreq_event_data *edata) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci struct rockchip_dfi *info = devfreq_event_get_drvdata(edev); 1538c2ecf20Sopenharmony_ci int busier_ch; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci busier_ch = rockchip_dfi_get_busier_ch(edev); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci edata->load_count = info->ch_usage[busier_ch].access; 1588c2ecf20Sopenharmony_ci edata->total_count = info->ch_usage[busier_ch].total; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci return 0; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic const struct devfreq_event_ops rockchip_dfi_ops = { 1648c2ecf20Sopenharmony_ci .disable = rockchip_dfi_disable, 1658c2ecf20Sopenharmony_ci .enable = rockchip_dfi_enable, 1668c2ecf20Sopenharmony_ci .get_event = rockchip_dfi_get_event, 1678c2ecf20Sopenharmony_ci .set_event = rockchip_dfi_set_event, 1688c2ecf20Sopenharmony_ci}; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_cistatic const struct of_device_id rockchip_dfi_id_match[] = { 1718c2ecf20Sopenharmony_ci { .compatible = "rockchip,rk3399-dfi" }, 1728c2ecf20Sopenharmony_ci { }, 1738c2ecf20Sopenharmony_ci}; 1748c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rockchip_dfi_id_match); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic int rockchip_dfi_probe(struct platform_device *pdev) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1798c2ecf20Sopenharmony_ci struct rockchip_dfi *data; 1808c2ecf20Sopenharmony_ci struct devfreq_event_desc *desc; 1818c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node, *node; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL); 1848c2ecf20Sopenharmony_ci if (!data) 1858c2ecf20Sopenharmony_ci return -ENOMEM; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci data->regs = devm_platform_ioremap_resource(pdev, 0); 1888c2ecf20Sopenharmony_ci if (IS_ERR(data->regs)) 1898c2ecf20Sopenharmony_ci return PTR_ERR(data->regs); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci data->clk = devm_clk_get(dev, "pclk_ddr_mon"); 1928c2ecf20Sopenharmony_ci if (IS_ERR(data->clk)) { 1938c2ecf20Sopenharmony_ci dev_err(dev, "Cannot get the clk dmc_clk\n"); 1948c2ecf20Sopenharmony_ci return PTR_ERR(data->clk); 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci node = of_parse_phandle(np, "rockchip,pmu", 0); 1988c2ecf20Sopenharmony_ci if (!node) 1998c2ecf20Sopenharmony_ci return dev_err_probe(&pdev->dev, -ENODEV, "Can't find pmu_grf registers\n"); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci data->regmap_pmu = syscon_node_to_regmap(node); 2028c2ecf20Sopenharmony_ci of_node_put(node); 2038c2ecf20Sopenharmony_ci if (IS_ERR(data->regmap_pmu)) 2048c2ecf20Sopenharmony_ci return PTR_ERR(data->regmap_pmu); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci data->dev = dev; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); 2098c2ecf20Sopenharmony_ci if (!desc) 2108c2ecf20Sopenharmony_ci return -ENOMEM; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci desc->ops = &rockchip_dfi_ops; 2138c2ecf20Sopenharmony_ci desc->driver_data = data; 2148c2ecf20Sopenharmony_ci desc->name = np->name; 2158c2ecf20Sopenharmony_ci data->desc = desc; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc); 2188c2ecf20Sopenharmony_ci if (IS_ERR(data->edev)) { 2198c2ecf20Sopenharmony_ci dev_err(&pdev->dev, 2208c2ecf20Sopenharmony_ci "failed to add devfreq-event device\n"); 2218c2ecf20Sopenharmony_ci return PTR_ERR(data->edev); 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic struct platform_driver rockchip_dfi_driver = { 2308c2ecf20Sopenharmony_ci .probe = rockchip_dfi_probe, 2318c2ecf20Sopenharmony_ci .driver = { 2328c2ecf20Sopenharmony_ci .name = "rockchip-dfi", 2338c2ecf20Sopenharmony_ci .of_match_table = rockchip_dfi_id_match, 2348c2ecf20Sopenharmony_ci }, 2358c2ecf20Sopenharmony_ci}; 2368c2ecf20Sopenharmony_cimodule_platform_driver(rockchip_dfi_driver); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2398c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lin Huang <hl@rock-chips.com>"); 2408c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Rockchip DFI driver"); 241