Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  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");