linux/drivers/devfreq/event/rockchip-dfi.c
<<
>>
Prefs
   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};
 191MODULE_DEVICE_TABLE(of, rockchip_dfi_id_match);
 192
 193static int rockchip_dfi_probe(struct platform_device *pdev)
 194{
 195        struct device *dev = &pdev->dev;
 196        struct rockchip_dfi *data;
 197        struct resource *res;
 198        struct devfreq_event_desc *desc;
 199        struct device_node *np = pdev->dev.of_node, *node;
 200
 201        data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL);
 202        if (!data)
 203                return -ENOMEM;
 204
 205        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 206        data->regs = devm_ioremap_resource(&pdev->dev, res);
 207        if (IS_ERR(data->regs))
 208                return PTR_ERR(data->regs);
 209
 210        data->clk = devm_clk_get(dev, "pclk_ddr_mon");
 211        if (IS_ERR(data->clk)) {
 212                dev_err(dev, "Cannot get the clk dmc_clk\n");
 213                return PTR_ERR(data->clk);
 214        };
 215
 216        /* try to find the optional reference to the pmu syscon */
 217        node = of_parse_phandle(np, "rockchip,pmu", 0);
 218        if (node) {
 219                data->regmap_pmu = syscon_node_to_regmap(node);
 220                if (IS_ERR(data->regmap_pmu))
 221                        return PTR_ERR(data->regmap_pmu);
 222        }
 223        data->dev = dev;
 224
 225        desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
 226        if (!desc)
 227                return -ENOMEM;
 228
 229        desc->ops = &rockchip_dfi_ops;
 230        desc->driver_data = data;
 231        desc->name = np->name;
 232        data->desc = desc;
 233
 234        data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc);
 235        if (IS_ERR(data->edev)) {
 236                dev_err(&pdev->dev,
 237                        "failed to add devfreq-event device\n");
 238                return PTR_ERR(data->edev);
 239        }
 240
 241        platform_set_drvdata(pdev, data);
 242
 243        return 0;
 244}
 245
 246static struct platform_driver rockchip_dfi_driver = {
 247        .probe  = rockchip_dfi_probe,
 248        .driver = {
 249                .name   = "rockchip-dfi",
 250                .of_match_table = rockchip_dfi_id_match,
 251        },
 252};
 253module_platform_driver(rockchip_dfi_driver);
 254
 255MODULE_LICENSE("GPL v2");
 256MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
 257MODULE_DESCRIPTION("Rockchip DFI driver");
 258