linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/7] rk3399 support ddr frequency scaling
@ 2016-07-22  9:07 Lin Huang
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
                   ` (6 more replies)
  0 siblings, 7 replies; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

rk3399 platform have dfi controller can monitor ddr load,
and dcf controller to handle ddr register so we can get the
right ddr frequency and make ddr controller happy work(which
will implement in bl31). So we do ddr frequency scaling with
following flow:

	     kernel                                bl31

	monitor ddr load
		|
		|
	get_target_rate
		|
		|           pass rate to bl31
	clk_set_rate(ddr) --------------------->run dcf flow
		|                                   |
		|                                   |
	wait dcf interrupt<-------------------trigger dcf interrupt  
		|
		|
	      return

Lin Huang (6):
  clk: rockchip: add new clock-type for the ddrclk
  clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc
  clk: rockchip: rk3399: add ddrc clock support
  PM / devfreq: event: support rockchip dfi controller
  PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  drm/rockchip: Add dmc notifier in vop driver

Shengfei xu (1):
  firmware: rockchip: sip: Add rockchip SIP runtime service

 drivers/clk/rockchip/Makefile               |   1 +
 drivers/clk/rockchip/clk-ddr.c              | 139 ++++++++
 drivers/clk/rockchip/clk-rk3399.c           |  19 ++
 drivers/clk/rockchip/clk.c                  |   9 +
 drivers/clk/rockchip/clk.h                  |  27 ++
 drivers/devfreq/Kconfig                     |   1 +
 drivers/devfreq/Makefile                    |   1 +
 drivers/devfreq/event/Kconfig               |   7 +
 drivers/devfreq/event/Makefile              |   1 +
 drivers/devfreq/event/rockchip-dfi.c        | 253 +++++++++++++++
 drivers/devfreq/rockchip/Kconfig            |  15 +
 drivers/devfreq/rockchip/Makefile           |   2 +
 drivers/devfreq/rockchip/rk3399_dmc.c       | 482 ++++++++++++++++++++++++++++
 drivers/devfreq/rockchip/rockchip_dmc.c     | 143 +++++++++
 drivers/firmware/Kconfig                    |   7 +
 drivers/firmware/Makefile                   |   1 +
 drivers/firmware/rockchip_sip.c             |  64 ++++
 drivers/firmware/rockchip_sip.h             |  59 ++++
 drivers/gpu/drm/rockchip/rockchip_drm_vop.c |  75 ++++-
 include/dt-bindings/clock/rk3399-cru.h      |   1 +
 include/soc/rockchip/rockchip_dmc.h         |  45 +++
 21 files changed, 1346 insertions(+), 6 deletions(-)
 create mode 100644 drivers/clk/rockchip/clk-ddr.c
 create mode 100644 drivers/devfreq/event/rockchip-dfi.c
 create mode 100644 drivers/devfreq/rockchip/Kconfig
 create mode 100644 drivers/devfreq/rockchip/Makefile
 create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
 create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
 create mode 100644 drivers/firmware/rockchip_sip.c
 create mode 100644 drivers/firmware/rockchip_sip.h
 create mode 100644 include/soc/rockchip/rockchip_dmc.h

-- 
2.6.6

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-22 10:00   ` Heiko Stübner
                     ` (3 more replies)
  2016-07-22  9:07 ` [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk Lin Huang
                   ` (5 subsequent siblings)
  6 siblings, 4 replies; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

From: Shengfei xu <xsf@rock-chips.com>

This patch adds support for the SiP interface, we can pass dram
paramtert to bl31, and control ddr frequency scaling in bl31.

Signed-off-by: Shengfei xu <xsf@rock-chips.com>
Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
- None

Changes in v2:
- None

Changes in v1:
- None

 drivers/firmware/Kconfig        |  7 +++++
 drivers/firmware/Makefile       |  1 +
 drivers/firmware/rockchip_sip.c | 64 +++++++++++++++++++++++++++++++++++++++++
 drivers/firmware/rockchip_sip.h | 59 +++++++++++++++++++++++++++++++++++++
 4 files changed, 131 insertions(+)
 create mode 100644 drivers/firmware/rockchip_sip.c
 create mode 100644 drivers/firmware/rockchip_sip.h

diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 0e22f24..6f585c5 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -206,6 +206,13 @@ config QCOM_SCM_64
 config HAVE_ARM_SMCCC
 	bool
 
+config ROCKCHIP_SIP
+	bool "Rockchip SIP interface"
+	depends on ARM64 && ARM_PSCI_FW
+	help
+	  Say Y here if you want to enable SIP callbacks for Rockchip platforms
+	  This option enables support for communicating with the ATF.
+
 source "drivers/firmware/broadcom/Kconfig"
 source "drivers/firmware/google/Kconfig"
 source "drivers/firmware/efi/Kconfig"
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 44a59dc..e9eab5b 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -25,3 +25,4 @@ obj-y				+= broadcom/
 obj-$(CONFIG_GOOGLE_FIRMWARE)	+= google/
 obj-$(CONFIG_EFI)		+= efi/
 obj-$(CONFIG_UEFI_CPER)		+= efi/
+obj-$(CONFIG_ROCKCHIP_SIP)	+= rockchip_sip.o
diff --git a/drivers/firmware/rockchip_sip.c b/drivers/firmware/rockchip_sip.c
new file mode 100644
index 0000000..7756af9
--- /dev/null
+++ b/drivers/firmware/rockchip_sip.c
@@ -0,0 +1,64 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2016 ARM Limited
+ */
+#include <linux/errno.h>
+#include <linux/linkage.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/printk.h>
+#include "rockchip_sip.h"
+
+typedef unsigned long (psci_fn)(unsigned long, unsigned long,
+				unsigned long, unsigned long);
+asmlinkage psci_fn __invoke_psci_fn_smc;
+
+#define CONFIG_DRAM_INIT	0x00
+#define CONFIG_DRAM_SET_RATE	0x01
+#define CONFIG_DRAM_ROUND_RATE	0x02
+#define CONFIG_DRAM_SET_AT_SR	0x03
+#define CONFIG_DRAM_GET_BW	0x04
+#define CONFIG_DRAM_GET_RATE	0x05
+#define CONFIG_DRAM_CLR_IRQ	0x06
+#define CONFIG_DRAM_SET_PARAM   0x07
+
+uint64_t sip_smc_ddr_init(void)
+{
+	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
+				    0, CONFIG_DRAM_INIT);
+}
+
+uint64_t sip_smc_set_ddr_param(uint64_t param)
+{
+	return __invoke_psci_fn_smc(SIP_DDR_FREQ, param,
+				    0, CONFIG_DRAM_SET_PARAM);
+}
+
+uint64_t sip_smc_set_ddr_rate(uint64_t rate)
+{
+	return __invoke_psci_fn_smc(SIP_DDR_FREQ, rate, 0,
+				    CONFIG_DRAM_SET_RATE);
+}
+
+uint64_t sip_smc_get_ddr_rate(void)
+{
+	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_GET_RATE);
+}
+
+uint64_t sip_smc_clr_ddr_irq(void)
+{
+	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_CLR_IRQ);
+}
+
+uint64_t sip_smc_get_call_count(void)
+{
+	return __invoke_psci_fn_smc(SIP_SVC_CALL_COUNT, 0, 0, 0);
+}
diff --git a/drivers/firmware/rockchip_sip.h b/drivers/firmware/rockchip_sip.h
new file mode 100644
index 0000000..6487734
--- /dev/null
+++ b/drivers/firmware/rockchip_sip.h
@@ -0,0 +1,59 @@
+/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SIP_INT_H
+#define __SIP_INT_H
+
+/* SMC function IDs for SiP Service queries */
+#define SIP_SVC_CALL_COUNT		0x8200ff00
+#define SIP_SVC_UID			0x8200ff01
+#define SIP_SVC_VERSION			0x8200ff03
+#define SIP_DDR_FREQ			0xC2000008
+
+#if IS_ENABLED(CONFIG_ROCKCHIP_SIP)
+uint64_t sip_smc_set_ddr_rate(uint64_t rate);
+uint64_t sip_smc_get_ddr_rate(void);
+uint64_t sip_smc_clr_ddr_irq(void);
+uint64_t sip_smc_get_call_count(void);
+uint64_t sip_smc_ddr_init(void);
+uint64_t sip_smc_set_ddr_param(uint64_t param);
+#else
+static inline uint64_t sip_smc_set_ddr_rate(uint64_t rate)
+{
+	return 0;
+}
+
+static inline uint64_t sip_smc_get_ddr_rate(void)
+{
+	return 0;
+}
+
+static inline uint64_t sip_smc_clr_ddr_irq(void)
+{
+	return 0;
+}
+
+static inline uint64_t sip_smc_get_call_count(void)
+{
+	return 0;
+}
+
+static inline uint64_t sip_smc_ddr_init(void)
+{
+	return 0;
+}
+
+static inline uint64_t sip_smc_set_ddr_param(uint64_t param)
+{
+	return 0;
+}
+#endif
+#endif
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-24  9:09   ` Heiko Stübner
  2016-07-22  9:07 ` [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc Lin Huang
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

On new rockchip platform(rk3399 etc), there have dcf controller to
do ddr frequency scaling, and this controller will implement in
arm-trust-firmware. We add a special clock-type to handle that.

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
- use sip call to set/read ddr rate

Changes in v2:
- use GENMASK instead val_mask
- use divider_recalc_rate() instead DIV_ROUND_UP_ULL
- cleanup code

Changes in v1:
- None

 drivers/clk/rockchip/Makefile  |   1 +
 drivers/clk/rockchip/clk-ddr.c | 139 +++++++++++++++++++++++++++++++++++++++++
 drivers/clk/rockchip/clk.c     |   9 +++
 drivers/clk/rockchip/clk.h     |  27 ++++++++
 4 files changed, 176 insertions(+)
 create mode 100644 drivers/clk/rockchip/clk-ddr.c

diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index f47a2fa..b5f2c8e 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -8,6 +8,7 @@ obj-y	+= clk-pll.o
 obj-y	+= clk-cpu.o
 obj-y	+= clk-inverter.o
 obj-y	+= clk-mmc-phase.o
+obj-y	+= clk-ddr.o
 obj-$(CONFIG_RESET_CONTROLLER)	+= softrst.o
 
 obj-y	+= clk-rk3036.o
diff --git a/drivers/clk/rockchip/clk-ddr.c b/drivers/clk/rockchip/clk-ddr.c
new file mode 100644
index 0000000..6f5f05e
--- /dev/null
+++ b/drivers/clk/rockchip/clk-ddr.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
+ * Author: Lin Huang <hl@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include "../../firmware/rockchip_sip.h"
+#include "clk.h"
+
+struct rockchip_ddrclk {
+	struct clk_hw	hw;
+	void __iomem	*reg_base;
+	int		mux_offset;
+	int		mux_shift;
+	int		mux_width;
+	int		mux_flag;
+	int		div_shift;
+	int		div_width;
+	int		div_flag;
+	spinlock_t	*lock;
+};
+
+#define to_rockchip_ddrclk_hw(hw) container_of(hw, struct rockchip_ddrclk, hw)
+
+static int rockchip_ddrclk_set_rate(struct clk_hw *hw, unsigned long drate,
+				    unsigned long prate)
+{
+	u64 value;
+	struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
+	unsigned long flags;
+
+	spin_lock_irqsave(ddrclk->lock, flags);
+	value = sip_smc_set_ddr_rate(drate);
+	spin_unlock_irqrestore(ddrclk->lock, flags);
+
+	return 0;
+}
+
+static unsigned long
+rockchip_ddrclk_recalc_rate(struct clk_hw *hw,
+			    unsigned long parent_rate)
+{
+	return sip_smc_get_ddr_rate();
+}
+
+static long clk_ddrclk_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *prate)
+{
+	return rate;
+}
+
+static u8 rockchip_ddrclk_get_parent(struct clk_hw *hw)
+{
+	struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
+	int num_parents = clk_hw_get_num_parents(hw);
+	u32 val;
+
+	val = clk_readl(ddrclk->reg_base +
+			ddrclk->mux_offset) >> ddrclk->mux_shift;
+	val &= GENMASK(ddrclk->mux_width - 1, 0);
+
+	if (val >= num_parents)
+		return -EINVAL;
+
+	return val;
+}
+
+static const struct clk_ops rockchip_ddrclk_ops = {
+	.recalc_rate = rockchip_ddrclk_recalc_rate,
+	.set_rate = rockchip_ddrclk_set_rate,
+	.round_rate = clk_ddrclk_round_rate,
+	.get_parent = rockchip_ddrclk_get_parent,
+};
+
+struct clk *rockchip_clk_register_ddrclk(const char *name, int flags,
+					 const char *const *parent_names,
+					 u8 num_parents, int mux_offset,
+					 int mux_shift, int mux_width,
+					 int mux_flag, int div_shift,
+					 int div_width, int div_flag,
+					 void __iomem *reg_base,
+					 spinlock_t *lock)
+{
+	struct rockchip_ddrclk *ddrclk;
+	struct clk_init_data init;
+	struct clk *clk;
+
+	ddrclk = kzalloc(sizeof(*ddrclk), GFP_KERNEL);
+	if (!ddrclk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = name;
+	init.parent_names = parent_names;
+	init.num_parents = num_parents;
+	init.ops = &rockchip_ddrclk_ops;
+
+	init.flags = flags;
+	init.flags |= CLK_SET_RATE_NO_REPARENT;
+	init.flags |= CLK_GET_RATE_NOCACHE;
+
+	ddrclk->reg_base = reg_base;
+	ddrclk->lock = lock;
+	ddrclk->hw.init = &init;
+	ddrclk->mux_offset = mux_offset;
+	ddrclk->mux_shift = mux_shift;
+	ddrclk->mux_width = mux_width;
+	ddrclk->mux_flag = mux_flag;
+	ddrclk->div_shift = div_shift;
+	ddrclk->div_width = div_width;
+	ddrclk->div_flag = div_flag;
+
+	clk = clk_register(NULL, &ddrclk->hw);
+	if (IS_ERR(clk)) {
+		pr_err("%s: could not register ddrclk %s\n", __func__,	name);
+		goto free_ddrclk;
+	}
+
+	return clk;
+
+free_ddrclk:
+	kfree(ddrclk);
+
+	return NULL;
+}
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index 7ffd134..6ac1aa5 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -484,6 +484,15 @@ void __init rockchip_clk_register_branches(
 				list->gate_offset, list->gate_shift,
 				list->gate_flags, flags, &ctx->lock);
 			break;
+		case branch_ddrc:
+			clk = rockchip_clk_register_ddrclk(
+				list->name, list->flags,
+				list->parent_names, list->num_parents,
+				list->muxdiv_offset, list->mux_shift,
+				list->mux_width, list->mux_flags,
+				list->div_shift, list->div_width,
+				list->div_flags, ctx->reg_base, &ctx->lock);
+			break;
 		}
 
 		/* none of the cases above matched */
diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h
index 2194ffa..4033fe4 100644
--- a/drivers/clk/rockchip/clk.h
+++ b/drivers/clk/rockchip/clk.h
@@ -281,6 +281,13 @@ struct clk *rockchip_clk_register_mmc(const char *name,
 				const char *const *parent_names, u8 num_parents,
 				void __iomem *reg, int shift);
 
+struct clk *rockchip_clk_register_ddrclk(const char *name, int flags,
+			const char *const *parent_names, u8 num_parents,
+			int mux_offset, int mux_shift, int mux_width,
+			int mux_flag, int div_shift, int div_width,
+			int div_flag, void __iomem *reg_base,
+			spinlock_t *lock);
+
 #define ROCKCHIP_INVERTER_HIWORD_MASK	BIT(0)
 
 struct clk *rockchip_clk_register_inverter(const char *name,
@@ -299,6 +306,7 @@ enum rockchip_clk_branch_type {
 	branch_mmc,
 	branch_inverter,
 	branch_factor,
+	branch_ddrc,
 };
 
 struct rockchip_clk_branch {
@@ -488,6 +496,25 @@ struct rockchip_clk_branch {
 		.child		= ch,				\
 	}
 
+#define COMPOSITE_DDRC(_id, cname, pnames, f, mo, ms, mw, mf,	\
+			 ds, dw, df)				\
+	{							\
+		.id		= _id,				\
+		.branch_type	= branch_ddrc,			\
+		.name		= cname,			\
+		.parent_names	= pnames,			\
+		.num_parents	= ARRAY_SIZE(pnames),		\
+		.flags		= f,				\
+		.muxdiv_offset	= mo,				\
+		.mux_shift	= ms,				\
+		.mux_width	= mw,				\
+		.mux_flags	= mf,				\
+		.div_shift	= ds,				\
+		.div_width	= dw,				\
+		.div_flags	= df,				\
+		.gate_offset	= -1,				\
+	}
+
 #define MUX(_id, cname, pnames, f, o, s, w, mf)			\
 	{							\
 		.id		= _id,				\
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
  2016-07-22  9:07 ` [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-22 10:08   ` Heiko Stübner
  2016-07-22  9:07 ` [PATCH v3 4/7] clk: rockchip: rk3399: add ddrc clock support Lin Huang
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
-None

Changes in v2:
- None 
Changes in v1:
- None

 include/dt-bindings/clock/rk3399-cru.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/dt-bindings/clock/rk3399-cru.h b/include/dt-bindings/clock/rk3399-cru.h
index 50a44cf..8a0f0442 100644
--- a/include/dt-bindings/clock/rk3399-cru.h
+++ b/include/dt-bindings/clock/rk3399-cru.h
@@ -131,6 +131,7 @@
 #define SCLK_DPHY_RX0_CFG		165
 #define SCLK_RMII_SRC			166
 #define SCLK_PCIEPHY_REF100M		167
+#define SCLK_DDRCLK			168
 
 #define DCLK_VOP0			180
 #define DCLK_VOP1			181
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 4/7] clk: rockchip: rk3399: add ddrc clock support
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
                   ` (2 preceding siblings ...)
  2016-07-22  9:07 ` [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-22  9:07 ` [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller Lin Huang
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

add ddrc clock setting, so we can do ddr frequency
scaling on rk3399 platform in future.

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
- None

Changes in v2:
- remove clk_ddrc_dpll_src from critical clock list

Changes in v1:
- remove ddrc source CLK_IGNORE_UNUSED flag
- move clk_ddrc and clk_ddrc_dpll_src to critical

 drivers/clk/rockchip/clk-rk3399.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/drivers/clk/rockchip/clk-rk3399.c b/drivers/clk/rockchip/clk-rk3399.c
index c109d80..7ef9d82 100644
--- a/drivers/clk/rockchip/clk-rk3399.c
+++ b/drivers/clk/rockchip/clk-rk3399.c
@@ -118,6 +118,10 @@ PNAME(mux_armclkb_p)				= { "clk_core_b_lpll_src",
 						    "clk_core_b_bpll_src",
 						    "clk_core_b_dpll_src",
 						    "clk_core_b_gpll_src" };
+PNAME(mux_ddrclk_p)				= { "clk_ddrc_lpll_src",
+						    "clk_ddrc_bpll_src",
+						    "clk_ddrc_dpll_src",
+						    "clk_ddrc_gpll_src" };
 PNAME(mux_aclk_cci_p)				= { "cpll_aclk_cci_src",
 						    "gpll_aclk_cci_src",
 						    "npll_aclk_cci_src",
@@ -1377,6 +1381,18 @@ static struct rockchip_clk_branch rk3399_clk_branches[] __initdata = {
 	COMPOSITE_NOMUX(0, "clk_test", "clk_test_pre", CLK_IGNORE_UNUSED,
 			RK3368_CLKSEL_CON(58), 0, 5, DFLAGS,
 			RK3368_CLKGATE_CON(13), 11, GFLAGS),
+
+	/* ddrc */
+	GATE(0, "clk_ddrc_lpll_src", "lpll", 0, RK3399_CLKGATE_CON(3),
+	     0, GFLAGS),
+	GATE(0, "clk_ddrc_bpll_src", "bpll", 0, RK3399_CLKGATE_CON(3),
+	     1, GFLAGS),
+	GATE(0, "clk_ddrc_dpll_src", "dpll", 0, RK3399_CLKGATE_CON(3),
+	     2, GFLAGS),
+	GATE(0, "clk_ddrc_gpll_src", "gpll", 0, RK3399_CLKGATE_CON(3),
+	     3, GFLAGS),
+	COMPOSITE_DDRC(SCLK_DDRCLK, "clk_ddrc", mux_ddrclk_p, 0,
+		       RK3399_CLKSEL_CON(6), 4, 2, MFLAGS, 0, 3, DFLAGS),
 };
 
 static struct rockchip_clk_branch rk3399_clk_pmu_branches[] __initdata = {
@@ -1487,6 +1503,9 @@ static const char *const rk3399_cru_critical_clocks[] __initconst = {
 	"gpll_hclk_perilp1_src",
 	"gpll_aclk_perilp0_src",
 	"gpll_aclk_perihp_src",
+
+	/* ddrc */
+	"clk_ddrc"
 };
 
 static const char *const rk3399_pmucru_critical_clocks[] __initconst = {
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
                   ` (3 preceding siblings ...)
  2016-07-22  9:07 ` [PATCH v3 4/7] clk: rockchip: rk3399: add ddrc clock support Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-22 20:28   ` Paul Gortmaker
  2016-07-22  9:07 ` [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc Lin Huang
  2016-07-22  9:07 ` [PATCH v3 7/7] drm/rockchip: Add dmc notifier in vop driver Lin Huang
  6 siblings, 1 reply; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

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>
Acked-by: Chanwoo Choi <cw00.choi@samsung.com>
---
Changes in v3:
- None

Changes in v2:
- use clk_disable_unprepare and clk_enable_prepare
- remove clk_enable_prepare in probe
- remove rockchip_dfi_remove function

Changes in v1:
- None

 drivers/devfreq/event/Kconfig        |   7 +
 drivers/devfreq/event/Makefile       |   1 +
 drivers/devfreq/event/rockchip-dfi.c | 253 +++++++++++++++++++++++++++++++++++
 3 files changed, 261 insertions(+)
 create mode 100644 drivers/devfreq/event/rockchip-dfi.c

diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig
index eb6f74a..0d32d68 100644
--- a/drivers/devfreq/event/Kconfig
+++ b/drivers/devfreq/event/Kconfig
@@ -30,4 +30,11 @@ config DEVFREQ_EVENT_EXYNOS_PPMU
 	  (Platform Performance Monitoring Unit) counters to estimate the
 	  utilization of each module.
 
+config DEVFREQ_EVENT_ROCKCHIP_DFI
+	bool "ROCKCHIP DFI DEVFREQ event Driver"
+	depends on ARCH_ROCKCHIP
+	help
+	  This add the devfreq-event driver for Rockchip SoC. It provides DFI
+	  (DDR Monitor Module) driver to count ddr load.
+
 endif # PM_DEVFREQ_EVENT
diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile
index 3d6afd3..dda7090 100644
--- a/drivers/devfreq/event/Makefile
+++ b/drivers/devfreq/event/Makefile
@@ -2,3 +2,4 @@
 
 obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o
 obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
+obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o
diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c
new file mode 100644
index 0000000..96a0307
--- /dev/null
+++ b/drivers/devfreq/event/rockchip-dfi.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Author: Lin Huang <hl@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/devfreq-event.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/of.h>
+
+#define RK3399_DMC_NUM_CH	2
+
+/* DDRMON_CTRL */
+#define DDRMON_CTRL	0x04
+#define CLR_DDRMON_CTRL	(0x1f0000 << 0)
+#define LPDDR4_EN	(0x10001 << 4)
+#define HARDWARE_EN	(0x10001 << 3)
+#define LPDDR3_EN	(0x10001 << 2)
+#define SOFTWARE_EN	(0x10001 << 1)
+#define TIME_CNT_EN	(0x10001 << 0)
+
+#define DDRMON_CH0_COUNT_NUM		0x28
+#define DDRMON_CH0_DFI_ACCESS_NUM	0x2c
+#define DDRMON_CH1_COUNT_NUM		0x3c
+#define DDRMON_CH1_DFI_ACCESS_NUM	0x40
+
+/* pmu grf */
+#define PMUGRF_OS_REG2	0x308
+#define DDRTYPE_SHIFT	13
+#define DDRTYPE_MASK	7
+
+enum {
+	DDR3 = 3,
+	LPDDR3 = 6,
+	LPDDR4 = 7,
+	UNUSED = 0xFF
+};
+
+struct dmc_usage {
+	u32 access;
+	u32 total;
+};
+
+struct rockchip_dfi {
+	struct devfreq_event_dev *edev;
+	struct devfreq_event_desc *desc;
+	struct dmc_usage ch_usage[RK3399_DMC_NUM_CH];
+	struct device *dev;
+	void __iomem *regs;
+	struct regmap *regmap_pmu;
+	struct clk *clk;
+};
+
+static void rockchip_dfi_start_hardware_counter(struct devfreq_event_dev *edev)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+	void __iomem *dfi_regs = info->regs;
+	u32 val;
+	u32 ddr_type;
+
+	/* get ddr type */
+	regmap_read(info->regmap_pmu, PMUGRF_OS_REG2, &val);
+	ddr_type = (val >> DDRTYPE_SHIFT) & DDRTYPE_MASK;
+
+	/* clear DDRMON_CTRL setting */
+	writel_relaxed(CLR_DDRMON_CTRL, dfi_regs + DDRMON_CTRL);
+
+	/* set ddr type to dfi */
+	if (ddr_type == LPDDR3)
+		writel_relaxed(LPDDR3_EN, dfi_regs + DDRMON_CTRL);
+	else if (ddr_type == LPDDR4)
+		writel_relaxed(LPDDR4_EN, dfi_regs + DDRMON_CTRL);
+
+	/* enable count, use software mode */
+	writel_relaxed(SOFTWARE_EN, dfi_regs + DDRMON_CTRL);
+}
+
+static void rockchip_dfi_stop_hardware_counter(struct devfreq_event_dev *edev)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+	void __iomem *dfi_regs = info->regs;
+	u32 val;
+
+	val = readl_relaxed(dfi_regs + DDRMON_CTRL);
+	val &= ~SOFTWARE_EN;
+	writel_relaxed(val, dfi_regs + DDRMON_CTRL);
+}
+
+static int rockchip_dfi_get_busier_ch(struct devfreq_event_dev *edev)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+	u32 tmp, max = 0;
+	u32 i, busier_ch = 0;
+	void __iomem *dfi_regs = info->regs;
+
+	rockchip_dfi_stop_hardware_counter(edev);
+
+	/* Find out which channel is busier */
+	for (i = 0; i < RK3399_DMC_NUM_CH; i++) {
+		info->ch_usage[i].access = readl_relaxed(dfi_regs +
+				DDRMON_CH0_DFI_ACCESS_NUM + i * 20);
+		info->ch_usage[i].total = readl_relaxed(dfi_regs +
+				DDRMON_CH0_COUNT_NUM + i * 20);
+		tmp = info->ch_usage[i].access;
+		if (tmp > max) {
+			busier_ch = i;
+			max = tmp;
+		}
+	}
+	rockchip_dfi_start_hardware_counter(edev);
+
+	return busier_ch;
+}
+
+static int rockchip_dfi_disable(struct devfreq_event_dev *edev)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+
+	rockchip_dfi_stop_hardware_counter(edev);
+	clk_disable_unprepare(info->clk);
+
+	return 0;
+}
+
+static int rockchip_dfi_enable(struct devfreq_event_dev *edev)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+	int ret;
+
+	ret = clk_prepare_enable(info->clk);
+	if (ret) {
+		dev_err(&edev->dev, "failed to enable dfi clk: %d\n", ret);
+		return ret;
+	}
+
+	rockchip_dfi_start_hardware_counter(edev);
+	return 0;
+}
+
+static int rockchip_dfi_set_event(struct devfreq_event_dev *edev)
+{
+	return 0;
+}
+
+static int rockchip_dfi_get_event(struct devfreq_event_dev *edev,
+				  struct devfreq_event_data *edata)
+{
+	struct rockchip_dfi *info = devfreq_event_get_drvdata(edev);
+	int busier_ch;
+
+	busier_ch = rockchip_dfi_get_busier_ch(edev);
+
+	edata->load_count = info->ch_usage[busier_ch].access;
+	edata->total_count = info->ch_usage[busier_ch].total;
+
+	return 0;
+}
+
+static const struct devfreq_event_ops rockchip_dfi_ops = {
+	.disable = rockchip_dfi_disable,
+	.enable = rockchip_dfi_enable,
+	.get_event = rockchip_dfi_get_event,
+	.set_event = rockchip_dfi_set_event,
+};
+
+static const struct of_device_id rockchip_dfi_id_match[] = {
+	{ .compatible = "rockchip,rk3399-dfi" },
+	{ },
+};
+
+static int rockchip_dfi_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rockchip_dfi *data;
+	struct resource *res;
+	struct devfreq_event_desc *desc;
+	struct device_node *np = pdev->dev.of_node, *node;
+
+	data = devm_kzalloc(dev, sizeof(struct rockchip_dfi), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(data->regs))
+		return PTR_ERR(data->regs);
+
+	data->clk = devm_clk_get(dev, "pclk_ddr_mon");
+	if (IS_ERR(data->clk)) {
+		dev_err(dev, "Cannot get the clk dmc_clk\n");
+		return PTR_ERR(data->clk);
+	};
+
+	/* try to find the optional reference to the pmu syscon */
+	node = of_parse_phandle(np, "rockchip,pmu", 0);
+	if (node) {
+		data->regmap_pmu = syscon_node_to_regmap(node);
+		if (IS_ERR(data->regmap_pmu))
+			return PTR_ERR(data->regmap_pmu);
+	}
+	data->dev = dev;
+
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	desc->ops = &rockchip_dfi_ops;
+	desc->driver_data = data;
+	desc->name = np->name;
+	data->desc = desc;
+
+	data->edev = devm_devfreq_event_add_edev(&pdev->dev, desc);
+	if (IS_ERR(data->edev)) {
+		dev_err(&pdev->dev,
+			"failed to add devfreq-event device\n");
+		return PTR_ERR(data->edev);
+	}
+
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static struct platform_driver rockchip_dfi_driver = {
+	.probe	= rockchip_dfi_probe,
+	.driver = {
+		.name	= "rockchip-dfi",
+		.of_match_table = rockchip_dfi_id_match,
+	},
+};
+module_platform_driver(rockchip_dfi_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip dfi driver");
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
                   ` (4 preceding siblings ...)
  2016-07-22  9:07 ` [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  2016-07-22 20:24   ` Paul Gortmaker
  2016-07-25  6:01   ` Chanwoo Choi
  2016-07-22  9:07 ` [PATCH v3 7/7] drm/rockchip: Add dmc notifier in vop driver Lin Huang
  6 siblings, 2 replies; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

base on dfi result, we do ddr frequency scaling, register
dmc driver to devfreq framework, and use simple-ondemand
policy.

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
- operate dram setting through sip call
- imporve set rate flow

Changes in v2:
- None
 
Changes in v1:
- move dfi controller to event
- fix set voltage sequence when set rate fail
- change Kconfig type from tristate to bool
- move unuse EXPORT_SYMBOL_GPL()

 drivers/devfreq/Kconfig                 |   1 +
 drivers/devfreq/Makefile                |   1 +
 drivers/devfreq/rockchip/Kconfig        |  15 +
 drivers/devfreq/rockchip/Makefile       |   2 +
 drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
 drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
 include/soc/rockchip/rockchip_dmc.h     |  45 +++
 7 files changed, 689 insertions(+)
 create mode 100644 drivers/devfreq/rockchip/Kconfig
 create mode 100644 drivers/devfreq/rockchip/Makefile
 create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
 create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
 create mode 100644 include/soc/rockchip/rockchip_dmc.h

diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index a5be56e..cb67246 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
          operating frequencies and voltages with OPP support.
 
 source "drivers/devfreq/event/Kconfig"
+source "drivers/devfreq/rockchip/Kconfig"
 
 endif # PM_DEVFREQ
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 09f11d9..48e2ae6 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
 # DEVFREQ Drivers
 obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
 obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra-devfreq.o
+obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
 
 # DEVFREQ Event Drivers
 obj-$(CONFIG_PM_DEVFREQ_EVENT)		+= event/
diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
new file mode 100644
index 0000000..7fb1cff
--- /dev/null
+++ b/drivers/devfreq/rockchip/Kconfig
@@ -0,0 +1,15 @@
+config ARM_ROCKCHIP_DMC_DEVFREQ
+	bool "ARM ROCKCHIP DMC DEVFREQ Driver"
+	depends on ARCH_ROCKCHIP
+	help
+	  This adds the DEVFREQ driver framework for the rockchip dmc.
+
+config ARM_RK3399_DMC_DEVFREQ
+	bool "ARM RK3399 DMC DEVFREQ Driver"
+	depends on ARM_ROCKCHIP_DMC_DEVFREQ
+	select PM_OPP
+	select DEVFREQ_GOV_SIMPLE_ONDEMAND
+	help
+          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
+          for the memory controller and reads the usage counts from hardware.
+
diff --git a/drivers/devfreq/rockchip/Makefile b/drivers/devfreq/rockchip/Makefile
new file mode 100644
index 0000000..caca525
--- /dev/null
+++ b/drivers/devfreq/rockchip/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)	+= rockchip_dmc.o
+obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
diff --git a/drivers/devfreq/rockchip/rk3399_dmc.c b/drivers/devfreq/rockchip/rk3399_dmc.c
new file mode 100644
index 0000000..f1d6120
--- /dev/null
+++ b/drivers/devfreq/rockchip/rk3399_dmc.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Author: Lin Huang <hl@rock-chips.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/devfreq.h>
+#include <linux/devfreq-event.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+#include <linux/rwsem.h>
+#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
+
+#include <soc/rockchip/rockchip_dmc.h>
+#include "../../firmware/rockchip_sip.h"
+
+struct dram_timing {
+	unsigned int ddr3_speed_bin;
+	unsigned int pd_idle;
+	unsigned int sr_idle;
+	unsigned int sr_mc_gate_idle;
+	unsigned int srpd_lite_idle;
+	unsigned int standby_idle;
+	unsigned int dram_dll_dis_freq;
+	unsigned int phy_dll_dis_freq;
+	unsigned int ddr3_odt_dis_freq;
+	unsigned int ddr3_drv;
+	unsigned int ddr3_odt;
+	unsigned int phy_ddr3_ca_drv;
+	unsigned int phy_ddr3_dq_drv;
+	unsigned int phy_ddr3_odt;
+	unsigned int lpddr3_odt_dis_freq;
+	unsigned int lpddr3_drv;
+	unsigned int lpddr3_odt;
+	unsigned int phy_lpddr3_ca_drv;
+	unsigned int phy_lpddr3_dq_drv;
+	unsigned int phy_lpddr3_odt;
+	unsigned int lpddr4_odt_dis_freq;
+	unsigned int lpddr4_drv;
+	unsigned int lpddr4_dq_odt;
+	unsigned int lpddr4_ca_odt;
+	unsigned int phy_lpddr4_ca_drv;
+	unsigned int phy_lpddr4_ck_cs_drv;
+	unsigned int phy_lpddr4_dq_drv;
+	unsigned int phy_lpddr4_odt;
+};
+
+struct rk3399_dmcfreq {
+	struct device *dev;
+	struct devfreq *devfreq;
+	struct devfreq_simple_ondemand_data ondemand_data;
+	struct clk *dmc_clk;
+	struct devfreq_event_dev *edev;
+	struct mutex lock;
+	struct notifier_block dmc_nb;
+	struct dram_timing *timing;
+	wait_queue_head_t	wait_dcf_queue;
+	int irq;
+	int wait_dcf_flag;
+	struct regulator *vdd_center;
+	unsigned long rate, target_rate;
+	unsigned long volt, target_volt;
+};
+
+static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
+				 u32 flags)
+{
+	struct platform_device *pdev = container_of(dev, struct platform_device,
+						    dev);
+	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
+	struct dev_pm_opp *opp;
+	unsigned long old_clk_rate = dmcfreq->rate;
+	unsigned long target_volt, target_rate;
+	int err;
+
+	rcu_read_lock();
+	opp = devfreq_recommended_opp(dev, freq, flags);
+	if (IS_ERR(opp)) {
+		rcu_read_unlock();
+		return PTR_ERR(opp);
+	}
+	target_rate = dev_pm_opp_get_freq(opp);
+	target_volt = dev_pm_opp_get_voltage(opp);
+	opp = devfreq_recommended_opp(dev, &dmcfreq->rate, flags);
+	if (IS_ERR(opp)) {
+		rcu_read_unlock();
+		return PTR_ERR(opp);
+	}
+	dmcfreq->volt = dev_pm_opp_get_voltage(opp);
+	rcu_read_unlock();
+
+	if (dmcfreq->rate == target_rate)
+		return 0;
+
+	mutex_lock(&dmcfreq->lock);
+	/*
+	 * if frequency scaling from low to high, adjust voltage first;
+	 * if frequency scaling from high to low, adjuset frequency first;
+	 */
+	if (old_clk_rate < target_rate) {
+		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
+					    target_volt);
+		if (err) {
+			dev_err(dev, "Unable to set vol %lu\n", target_volt);
+			goto out;
+		}
+	}
+
+	dmc_event(DMCFREQ_ADJUST);
+	dmcfreq->wait_dcf_flag = 1;
+
+	err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
+	if (err) {
+		dev_err(dev,
+			"Unable to set freq %lu. Current freq %lu. Error %d\n",
+			target_rate, old_clk_rate, err);
+		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
+				      dmcfreq->volt);
+		dmc_event(DMCFREQ_FINISH);
+		goto out;
+	}
+
+	/*
+	 * wait until bcf irq happen, it means freq scaling finish in bl31,
+	 * use 100ms as timeout time
+	 */
+	wait_event_timeout(dmcfreq->wait_dcf_queue,
+			   !dmcfreq->wait_dcf_flag, HZ / 10);
+
+	dmc_event(DMCFREQ_FINISH);
+
+	/*
+	 * check the dpll rate
+	 * there only two result we will get,
+	 * 1. ddr frequency scaling fail, we still get the old rate
+	 * 2, ddr frequency scaling sucessful, we get the rate we set
+	 */
+	dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk);
+
+	/* if get the incorrect rate, set voltage to old value */
+	if (dmcfreq->rate != target_rate) {
+		dev_err(dev, "get wrong ddr frequency, Request freq %lu,\
+			Current freq %lu\n", target_rate, dmcfreq->rate);
+		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
+				      dmcfreq->volt);
+	} else if (old_clk_rate > target_rate)
+		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
+					    target_volt);
+	if (err)
+		dev_err(dev, "Unable to set vol %lu\n", target_volt);
+
+out:
+	mutex_unlock(&dmcfreq->lock);
+	return err;
+}
+
+static int rk3399_dmcfreq_get_dev_status(struct device *dev,
+					 struct devfreq_dev_status *stat)
+{
+	struct platform_device *pdev = container_of(dev, struct platform_device,
+						    dev);
+	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
+	struct devfreq_event_data edata;
+
+	devfreq_event_get_event(dmcfreq->edev, &edata);
+
+	stat->current_frequency = dmcfreq->rate;
+	stat->busy_time = edata.load_count;
+	stat->total_time = edata.total_count;
+
+	return 0;
+}
+
+static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
+{
+	struct platform_device *pdev = container_of(dev, struct platform_device,
+						    dev);
+	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
+
+	*freq = dmcfreq->rate;
+
+	return 0;
+}
+
+static void rk3399_dmcfreq_exit(struct device *dev)
+{
+	struct platform_device *pdev = container_of(dev, struct platform_device,
+						    dev);
+	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
+
+	devfreq_unregister_opp_notifier(dev, dmcfreq->devfreq);
+}
+
+static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = {
+	.polling_ms	= 200,
+	.target		= rk3399_dmcfreq_target,
+	.get_dev_status	= rk3399_dmcfreq_get_dev_status,
+	.get_cur_freq	= rk3399_dmcfreq_get_cur_freq,
+	.exit		= rk3399_dmcfreq_exit,
+};
+
+static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev)
+{
+	rockchip_dmc_disable();
+	return 0;
+}
+
+static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
+{
+	rockchip_dmc_enable();
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
+			 rk3399_dmcfreq_resume);
+
+static int rk3399_dmc_enable_notify(struct notifier_block *nb,
+				    unsigned long event, void *data)
+{
+	struct rk3399_dmcfreq *dmcfreq =
+			      container_of(nb, struct rk3399_dmcfreq, dmc_nb);
+	unsigned long freq = ULONG_MAX;
+
+	if (event == DMC_ENABLE) {
+		devfreq_event_enable_edev(dmcfreq->edev);
+		devfreq_resume_device(dmcfreq->devfreq);
+		return NOTIFY_OK;
+	} else if (event == DMC_DISABLE) {
+		devfreq_event_disable_edev(dmcfreq->edev);
+		devfreq_suspend_device(dmcfreq->devfreq);
+
+		/* when disable dmc, set sdram to max frequency */
+		rk3399_dmcfreq_target(dmcfreq->dev, &freq, 0);
+		return NOTIFY_OK;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
+{
+	struct rk3399_dmcfreq *dmcfreq = dev_id;
+
+	dmcfreq->wait_dcf_flag = 0;
+	wake_up(&dmcfreq->wait_dcf_queue);
+
+	/* clr dcf irq */
+	sip_smc_clr_ddr_irq();
+
+	return IRQ_HANDLED;
+}
+
+static int of_do_get_timing(struct device_node *np,
+		struct dram_timing *timing)
+{
+	int ret;
+
+	ret = of_property_read_u32(np, "ddr3_speed_bin",
+				   &timing->ddr3_speed_bin);
+	ret |= of_property_read_u32(np, "pd_idle", &timing->pd_idle);
+	ret |= of_property_read_u32(np, "sr_idle", &timing->sr_idle);
+	ret |= of_property_read_u32(np, "sr_mc_gate_idle",
+				    &timing->sr_mc_gate_idle);
+	ret |= of_property_read_u32(np, "srpd_lite_idle",
+				    &timing->srpd_lite_idle);
+	ret |= of_property_read_u32(np, "standby_idle", &timing->standby_idle);
+	ret |= of_property_read_u32(np, "dram_dll_dis_freq",
+				    &timing->dram_dll_dis_freq);
+	ret |= of_property_read_u32(np, "phy_dll_dis_freq",
+				    &timing->phy_dll_dis_freq);
+	ret |= of_property_read_u32(np, "ddr3_odt_dis_freq",
+				    &timing->ddr3_odt_dis_freq);
+	ret |= of_property_read_u32(np, "ddr3_drv", &timing->ddr3_drv);
+	ret |= of_property_read_u32(np, "ddr3_odt", &timing->ddr3_odt);
+	ret |= of_property_read_u32(np, "phy_ddr3_ca_drv",
+				    &timing->phy_ddr3_ca_drv);
+	ret |= of_property_read_u32(np, "phy_ddr3_dq_drv",
+				    &timing->phy_ddr3_dq_drv);
+	ret |= of_property_read_u32(np, "phy_ddr3_odt", &timing->phy_ddr3_odt);
+	ret |= of_property_read_u32(np, "lpddr3_odt_dis_freq",
+				    &timing->lpddr3_odt_dis_freq);
+	ret |= of_property_read_u32(np, "lpddr3_drv", &timing->lpddr3_drv);
+	ret |= of_property_read_u32(np, "lpddr3_odt", &timing->lpddr3_odt);
+	ret |= of_property_read_u32(np, "phy_lpddr3_ca_drv",
+				    &timing->phy_lpddr3_ca_drv);
+	ret |= of_property_read_u32(np, "phy_lpddr3_dq_drv",
+				    &timing->phy_lpddr3_dq_drv);
+	ret |= of_property_read_u32(np, "phy_lpddr3_odt",
+				    &timing->phy_lpddr3_odt);
+	ret |= of_property_read_u32(np, "lpddr4_odt_dis_freq",
+				    &timing->lpddr4_odt_dis_freq);
+	ret |= of_property_read_u32(np, "lpddr4_drv",
+				    &timing->lpddr4_drv);
+	ret |= of_property_read_u32(np, "lpddr4_dq_odt",
+				    &timing->lpddr4_dq_odt);
+	ret |= of_property_read_u32(np, "lpddr4_ca_odt",
+				    &timing->lpddr4_ca_odt);
+	ret |= of_property_read_u32(np, "phy_lpddr4_ca_drv",
+				    &timing->phy_lpddr4_ca_drv);
+	ret |= of_property_read_u32(np, "phy_lpddr4_ck_cs_drv",
+				    &timing->phy_lpddr4_ck_cs_drv);
+	ret |= of_property_read_u32(np, "phy_lpddr4_dq_drv",
+				    &timing->phy_lpddr4_dq_drv);
+	ret |= of_property_read_u32(np, "phy_lpddr4_odt",
+				    &timing->phy_lpddr4_odt);
+
+	return ret;
+}
+
+static struct dram_timing *of_get_ddr_timings(struct device *dev,
+					      struct device_node *np)
+{
+	struct dram_timing	*timing = NULL;
+	struct device_node	*np_tim;
+
+	np_tim = of_parse_phandle(np, "ddr_timing", 0);
+	if (np_tim) {
+		timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
+		if (!timing)
+			goto err;
+		if (of_do_get_timing(np_tim, timing)) {
+			devm_kfree(dev, timing);
+			goto err;
+		}
+		return timing;
+	}
+
+err:
+	if (timing) {
+		devm_kfree(dev, timing);
+		timing = NULL;
+	}
+
+	return timing;
+}
+
+static int rk3399_dmcfreq_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rk3399_dmcfreq *data;
+	int ret, irq, index;
+	uint64_t param = 0;
+	uint32_t *timing;
+	struct device_node *np = pdev->dev.of_node;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no dmc irq resource\n");
+		return -EINVAL;
+	}
+
+	data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+
+	data->vdd_center = devm_regulator_get(dev, "center");
+	if (IS_ERR(data->vdd_center)) {
+		dev_err(dev, "Cannot get the regulator \"center\"\n");
+		return PTR_ERR(data->vdd_center);
+	}
+
+	data->dmc_clk = devm_clk_get(dev, "dmc_clk");
+	if (IS_ERR(data->dmc_clk)) {
+		dev_err(dev, "Cannot get the clk dmc_clk\n");
+		return PTR_ERR(data->dmc_clk);
+	};
+
+	data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
+	if (IS_ERR(data->edev))
+		return -EPROBE_DEFER;
+
+	ret = devfreq_event_enable_edev(data->edev);
+	if (ret < 0) {
+		dev_err(dev, "failed to enable devfreq-event devices\n");
+		return ret;
+	}
+
+	/*
+	 * We add a devfreq driver to our parent since it has a device tree node
+	 * with operating points.
+	 */
+	if (dev_pm_opp_of_add_table(dev)) {
+		dev_err(dev, "Invalid operating-points in device tree.\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(np, "upthreshold",
+			     &data->ondemand_data.upthreshold);
+
+	of_property_read_u32(np, "downdifferential",
+			     &data->ondemand_data.downdifferential);
+
+	data->rate = clk_get_rate(data->dmc_clk);
+	rk3399_devfreq_dmc_profile.initial_freq = data->rate;
+	data->devfreq = devfreq_add_device(dev,
+					   &rk3399_devfreq_dmc_profile,
+					   "simple_ondemand",
+					   &data->ondemand_data);
+	if (IS_ERR(data->devfreq))
+		return PTR_ERR(data->devfreq);
+
+	devfreq_register_opp_notifier(dev, data->devfreq);
+
+	data->dmc_nb.notifier_call = rk3399_dmc_enable_notify;
+	dmc_register_notifier(&data->dmc_nb);
+	data->irq = irq;
+	ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
+			       dev_name(dev), data);
+	if (ret) {
+		dev_err(dev, "failed to request dmc irq: %d\n", ret);
+		return ret;
+	}
+
+	/* get dram timing and pass it to bl31 */
+	data->timing = of_get_ddr_timings(dev, np);
+	if (data->timing) {
+		timing = (uint32_t *)data->timing;
+		for (index = 0; index < (sizeof(struct dram_timing) / 4);
+		     index++) {
+			param = index;
+			param = param << 32 | *timing++;
+			sip_smc_set_ddr_param(param);
+			param = 0;
+		}
+	}
+	sip_smc_ddr_init();
+
+	init_waitqueue_head(&data->wait_dcf_queue);
+	data->wait_dcf_flag = 0;
+
+	data->dev = dev;
+	platform_set_drvdata(pdev, data);
+
+	return 0;
+}
+
+static int rk3399_dmcfreq_remove(struct platform_device *pdev)
+{
+	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
+
+	devfreq_remove_device(dmcfreq->devfreq);
+	regulator_put(dmcfreq->vdd_center);
+
+	return 0;
+}
+
+static const struct of_device_id rk3399dmc_devfreq_of_match[] = {
+	{ .compatible = "rockchip,rk3399-dmc" },
+	{ },
+};
+
+static struct platform_driver rk3399_dmcfreq_driver = {
+	.probe	= rk3399_dmcfreq_probe,
+	.remove	= rk3399_dmcfreq_remove,
+	.driver = {
+		.name	= "rk3399-dmc-freq",
+		.pm	= &rk3399_dmcfreq_pm,
+		.of_match_table = rk3399dmc_devfreq_of_match,
+	},
+};
+module_platform_driver(rk3399_dmcfreq_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework");
diff --git a/drivers/devfreq/rockchip/rockchip_dmc.c b/drivers/devfreq/rockchip/rockchip_dmc.c
new file mode 100644
index 0000000..78baee5
--- /dev/null
+++ b/drivers/devfreq/rockchip/rockchip_dmc.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ * Author: Lin Huang <hl@rock-chips.com>
+ * Base on: https://chromium-review.googlesource.com/#/c/231477/
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/mutex.h>
+#include <soc/rockchip/rockchip_dmc.h>
+
+static int num_wait;
+static int num_disable;
+static BLOCKING_NOTIFIER_HEAD(dmc_notifier_list);
+static DEFINE_MUTEX(dmc_en_lock);
+static DEFINE_MUTEX(dmc_sync_lock);
+
+/**
+ * rockchip_dmc_enabled - Returns true if dmc freq is enabled, false otherwise.
+ */
+bool rockchip_dmc_enabled(void)
+{
+	return num_disable <= 0 && num_wait <= 1;
+}
+
+/**
+ * rockchip_dmc_enable - Enable dmc frequency scaling. Will only enable
+ * frequency scaling if there are 1 or fewer notifiers. Call to undo
+ * rockchip_dmc_disable.
+ */
+void rockchip_dmc_enable(void)
+{
+	mutex_lock(&dmc_en_lock);
+	num_disable--;
+	WARN_ON(num_disable < 0);
+	if (rockchip_dmc_enabled())
+		dmc_event(DMC_ENABLE);
+	mutex_unlock(&dmc_en_lock);
+}
+
+/**
+ * rockchip_dmc_disable - Disable dmc frequency scaling. Call when something
+ * cannot coincide with dmc frequency scaling.
+ */
+void rockchip_dmc_disable(void)
+{
+	mutex_lock(&dmc_en_lock);
+	if (rockchip_dmc_enabled())
+		dmc_event(DMC_DISABLE);
+	num_disable++;
+	mutex_unlock(&dmc_en_lock);
+}
+
+void dmc_event(int event)
+{
+	mutex_lock(&dmc_sync_lock);
+	blocking_notifier_call_chain(&dmc_notifier_list, event, NULL);
+	mutex_unlock(&dmc_sync_lock);
+}
+
+/**
+ * dmc_register_notifier - register a driver to dmc chain
+ * @nb: notifier function to register
+ */
+int dmc_register_notifier(struct notifier_block *nb)
+{
+	int ret;
+
+	if (!nb)
+		return -EINVAL;
+
+	ret = blocking_notifier_chain_register(&dmc_notifier_list, nb);
+
+	return ret;
+}
+
+/**
+ * dmc_unregister_notifier - unregister a driver from dmc chain
+ * @nb: remove notifier function
+ */
+int dmc_unregister_notifier(struct notifier_block *nb)
+{
+	int ret;
+
+	if (!nb)
+		return -EINVAL;
+
+	ret = blocking_notifier_chain_unregister(&dmc_notifier_list, nb);
+
+	return ret;
+}
+
+/**
+ * rockchip_dmc_get - Register the notifier block for the dmc chain.
+ * @nb The dmc notifier block to register
+ */
+int rockchip_dmc_get(struct notifier_block *nb)
+{
+	if (!nb)
+		return -EINVAL;
+
+	mutex_lock(&dmc_en_lock);
+
+	/*
+	 * if have two notifier(enable two vop etc),
+	 * need to disable dmc
+	 */
+	if (num_wait == 1 && num_disable <= 0)
+		dmc_event(DMC_DISABLE);
+	num_wait++;
+	dmc_register_notifier(nb);
+	mutex_unlock(&dmc_en_lock);
+
+	return 0;
+}
+
+/**
+ * rockchip_dmc_put - Remove the notifier block from the dmc chain.
+ * @nb The dmc notifier block to unregister
+ */
+int rockchip_dmc_put(struct notifier_block *nb)
+{
+	if (!nb)
+		return -EINVAL;
+
+	mutex_lock(&dmc_en_lock);
+	num_wait--;
+
+	/* if notifier from 2 back to 1, enable dmc again */
+	if (num_wait == 1 && num_disable <= 0)
+		dmc_event(DMC_ENABLE);
+	dmc_unregister_notifier(nb);
+	mutex_unlock(&dmc_en_lock);
+
+	return 0;
+}
diff --git a/include/soc/rockchip/rockchip_dmc.h b/include/soc/rockchip/rockchip_dmc.h
new file mode 100644
index 0000000..3f69cbf
--- /dev/null
+++ b/include/soc/rockchip/rockchip_dmc.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef __SOC_ROCKCHIP_DMC_H
+#define __SOC_ROCKCHIP_DMC_H
+
+#include <linux/notifier.h>
+
+#define DMC_ENABLE	0
+#define DMC_DISABLE	1
+#define DMCFREQ_ADJUST	2
+#define DMCFREQ_FINISH	3
+
+#if IS_ENABLED(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)
+int rockchip_dmc_get(struct notifier_block *nb);
+int rockchip_dmc_put(struct notifier_block *nb);
+#else
+static inline int rockchip_dmc_get(struct notifier_block *nb)
+{
+	return 0;
+}
+
+static inline int rockchip_dmc_put(struct notifier_block *nb)
+{
+	return 0;
+}
+#endif
+
+void dmc_event(int event);
+int dmc_register_notifier(struct notifier_block *nb);
+int dmc_unregister_notifier(struct notifier_block *nb);
+void rockchip_dmc_enable(void);
+void rockchip_dmc_disable(void);
+bool rockchip_dmc_enabled(void);
+#endif
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 7/7] drm/rockchip: Add dmc notifier in vop driver
  2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
                   ` (5 preceding siblings ...)
  2016-07-22  9:07 ` [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc Lin Huang
@ 2016-07-22  9:07 ` Lin Huang
  6 siblings, 0 replies; 24+ messages in thread
From: Lin Huang @ 2016-07-22  9:07 UTC (permalink / raw)
  To: linux-arm-kernel

when in ddr frequency scaling process, vop can not do
enable or disable operate, since dcf will base on vop vblank
time to do frequency scaling and need to get vop irq if there
have vop enabled. So need register to dmc notifier, and we can
get the dmc status.

Signed-off-by: Lin Huang <hl@rock-chips.com>
---
Changes in v3:
- when do vop eanble/disable, dmc will wait until it finish

Changes in v2:
- None

Changes in v1:
- use wait_event instead usleep

 drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 75 ++++++++++++++++++++++++++---
 1 file changed, 69 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 6255e5b..4220629 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -31,6 +31,8 @@
 #include <linux/reset.h>
 #include <linux/delay.h>
 
+#include <soc/rockchip/rockchip_dmc.h>
+
 #include "rockchip_drm_drv.h"
 #include "rockchip_drm_gem.h"
 #include "rockchip_drm_fb.h"
@@ -120,6 +122,11 @@ struct vop {
 
 	const struct vop_data *data;
 
+	struct notifier_block dmc_nb;
+	int dmc_in_process;
+	int vop_switch_status;
+	wait_queue_head_t	wait_dmc_queue;
+	wait_queue_head_t	wait_vop_switch_queue;
 	uint32_t *regsbak;
 	void __iomem *regs;
 
@@ -430,21 +437,56 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop)
 	spin_unlock_irqrestore(&vop->irq_lock, flags);
 }
 
+static int dmc_notify(struct notifier_block *nb, unsigned long event,
+		      void *data)
+{
+	struct vop *vop = container_of(nb, struct vop, dmc_nb);
+
+	if (event == DMCFREQ_ADJUST) {
+
+		/*
+		 * check if vop in enable or disable process,
+		 * if yes, wait until it finish, use 200ms as
+		 * timeout.
+		 */
+		wait_event_timeout(vop->wait_vop_switch_queue,
+			   !vop->vop_switch_status, HZ / 5);
+		vop->dmc_in_process = 1;
+	} else if (event == DMCFREQ_FINISH) {
+		vop->dmc_in_process = 0;
+		wake_up(&vop->wait_dmc_queue);
+	}
+
+	return NOTIFY_OK;
+}
+
 static void vop_enable(struct drm_crtc *crtc)
 {
 	struct vop *vop = to_vop(crtc);
 	int ret;
 
+	if (vop->is_enabled)
+		return;
+
+	/*
+	 * if in dmc scaling frequency process, wait until it finish
+	 * use 100ms as timeout time.
+	 */
+	wait_event_timeout(vop->wait_dmc_queue,
+			   !vop->dmc_in_process, HZ / 5);
+
+	vop->vop_switch_status = 1;
+
 	ret = pm_runtime_get_sync(vop->dev);
 	if (ret < 0) {
 		dev_err(vop->dev, "failed to get pm runtime: %d\n", ret);
-		return;
+		goto err;
 	}
 
 	ret = clk_enable(vop->hclk);
 	if (ret < 0) {
 		dev_err(vop->dev, "failed to enable hclk - %d\n", ret);
-		return;
+		goto err;
 	}
 
 	ret = clk_enable(vop->dclk);
@@ -458,7 +500,6 @@ static void vop_enable(struct drm_crtc *crtc)
 		dev_err(vop->dev, "failed to enable aclk - %d\n", ret);
 		goto err_disable_dclk;
 	}
-
 	/*
 	 * Slave iommu shares power, irq and clock with vop.  It was associated
 	 * automatically with this master device via common driver code.
@@ -486,7 +527,9 @@ static void vop_enable(struct drm_crtc *crtc)
 	enable_irq(vop->irq);
 
 	drm_crtc_vblank_on(crtc);
-
+	vop->vop_switch_status = 0;
+	wake_up(&vop->wait_vop_switch_queue);
+	rockchip_dmc_get(&vop->dmc_nb);
 	return;
 
 err_disable_aclk:
@@ -495,6 +538,10 @@ err_disable_dclk:
 	clk_disable(vop->dclk);
 err_disable_hclk:
 	clk_disable(vop->hclk);
+err:
+	vop->vop_switch_status = 0;
+	wake_up(&vop->wait_vop_switch_queue);
+	return;
 }
 
 static void vop_crtc_disable(struct drm_crtc *crtc)
@@ -505,6 +552,15 @@ static void vop_crtc_disable(struct drm_crtc *crtc)
 	WARN_ON(vop->event);
 
 	/*
+	 * if in dmc scaling frequency process, wait until it finish
+	 * use 100ms as timeout time.
+	 */
+	wait_event_timeout(vop->wait_dmc_queue,
+			   !vop->dmc_in_process, HZ / 5);
+
+	vop->vop_switch_status = 1;
+
+	/*
 	 * We need to make sure that all windows are disabled before we
 	 * disable that crtc. Otherwise we might try to scan from a destroyed
 	 * buffer later.
@@ -517,7 +573,6 @@ static void vop_crtc_disable(struct drm_crtc *crtc)
 		VOP_WIN_SET(vop, win, enable, 0);
 		spin_unlock(&vop->reg_lock);
 	}
-
 	drm_crtc_vblank_off(crtc);
 
 	/*
@@ -548,7 +603,6 @@ static void vop_crtc_disable(struct drm_crtc *crtc)
 	 * vop standby complete, so iommu detach is safe.
 	 */
 	rockchip_drm_dma_detach_device(vop->drm_dev, vop->dev);
-
 	clk_disable(vop->dclk);
 	clk_disable(vop->aclk);
 	clk_disable(vop->hclk);
@@ -561,6 +615,9 @@ static void vop_crtc_disable(struct drm_crtc *crtc)
 
 		crtc->state->event = NULL;
 	}
+	vop->vop_switch_status = 0;
+	wake_up(&vop->wait_vop_switch_queue);
+	rockchip_dmc_put(&vop->dmc_nb);
 }
 
 static void vop_plane_destroy(struct drm_plane *plane)
@@ -1247,6 +1304,7 @@ static int vop_create_crtc(struct vop *vop)
 		goto err_cleanup_crtc;
 	}
 
+	vop->dmc_nb.notifier_call = dmc_notify;
 	init_completion(&vop->dsp_hold_completion);
 	init_completion(&vop->wait_update_complete);
 	crtc->port = port;
@@ -1468,6 +1526,11 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
 	/* IRQ is initially disabled; it gets enabled in power_on */
 	disable_irq(vop->irq);
 
+	init_waitqueue_head(&vop->wait_vop_switch_queue);
+	vop->vop_switch_status = 0;
+	init_waitqueue_head(&vop->wait_dmc_queue);
+	vop->dmc_in_process = 0;
+
 	ret = vop_create_crtc(vop);
 	if (ret)
 		return ret;
-- 
2.6.6

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
@ 2016-07-22 10:00   ` Heiko Stübner
  2016-07-22 20:50   ` Heiko Stübner
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 24+ messages in thread
From: Heiko Stübner @ 2016-07-22 10:00 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

Am Freitag, 22. Juli 2016, 17:07:14 schrieb Lin Huang:
> From: Shengfei xu <xsf@rock-chips.com>
> 
> This patch adds support for the SiP interface, we can pass dram
> paramtert to bl31, and control ddr frequency scaling in bl31.
> 
> Signed-off-by: Shengfei xu <xsf@rock-chips.com>
> Signed-off-by: Lin Huang <hl@rock-chips.com>

[...]

> +++ b/drivers/firmware/rockchip_sip.c
> @@ -0,0 +1,64 @@
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * Copyright (C) 2016 ARM Limited
> + */
> +#include <linux/errno.h>
> +#include <linux/linkage.h>
> +#include <linux/of.h>
> +#include <linux/pm.h>
> +#include <linux/printk.h>
> +#include "rockchip_sip.h"
> +
> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
> +				unsigned long, unsigned long);
> +asmlinkage psci_fn __invoke_psci_fn_smc;
> +
> +#define CONFIG_DRAM_INIT	0x00
> +#define CONFIG_DRAM_SET_RATE	0x01
> +#define CONFIG_DRAM_ROUND_RATE	0x02
> +#define CONFIG_DRAM_SET_AT_SR	0x03
> +#define CONFIG_DRAM_GET_BW	0x04
> +#define CONFIG_DRAM_GET_RATE	0x05
> +#define CONFIG_DRAM_CLR_IRQ	0x06
> +#define CONFIG_DRAM_SET_PARAM   0x07
> +
> +uint64_t sip_smc_ddr_init(void)

if I'm reading the ATF writers guide [0] correctly, the SiP calls are soc-
specific things:
"For example, two SoC providers can use the same Function ID within the SiP
Service calls OEN range to mean different things - as these calls should be
specific to the SoC. "

So I guess the function calls, as well as the constants should have a prefix.
ROCKCHIP_SIP_DDR_FREQ, rockchip_sip_smc_set_ddr_param(), etc.

Looking at Zynq in the ATF [1] seems to strengthen that theory with the 
similar constants having the ZYNQMP_ prefix.

I guess it doesn't matter to much on the ATF side, as you build a soc-specific 
image, but on the kernel-side I think it should unambiguos, especially as 
functions and constants are part of a public header.

I think the code itself looks fine though :-)


Heiko

[0] https://github.com/ARM-software/arm-trusted-firmware/blob/master/docs/rt-svc-writers-guide.md
[1] https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/xilinx/zynqmp/sip_svc_setup.c



> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
> +				    0, CONFIG_DRAM_INIT);
> +}
> +
> +uint64_t sip_smc_set_ddr_param(uint64_t param)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, param,
> +				    0, CONFIG_DRAM_SET_PARAM);
> +}
> +
> +uint64_t sip_smc_set_ddr_rate(uint64_t rate)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, rate, 0,
> +				    CONFIG_DRAM_SET_RATE);
> +}
> +
> +uint64_t sip_smc_get_ddr_rate(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_GET_RATE);
> +}
> +
> +uint64_t sip_smc_clr_ddr_irq(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_CLR_IRQ);
> +}
> +
> +uint64_t sip_smc_get_call_count(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_SVC_CALL_COUNT, 0, 0, 0);
> +}
> diff --git a/drivers/firmware/rockchip_sip.h
> b/drivers/firmware/rockchip_sip.h new file mode 100644
> index 0000000..6487734
> --- /dev/null
> +++ b/drivers/firmware/rockchip_sip.h
> @@ -0,0 +1,59 @@
> +/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +#ifndef __SIP_INT_H
> +#define __SIP_INT_H
> +
> +/* SMC function IDs for SiP Service queries */
> +#define SIP_SVC_CALL_COUNT		0x8200ff00
> +#define SIP_SVC_UID			0x8200ff01
> +#define SIP_SVC_VERSION			0x8200ff03
> +#define SIP_DDR_FREQ			0xC2000008
> +
> +#if IS_ENABLED(CONFIG_ROCKCHIP_SIP)
> +uint64_t sip_smc_set_ddr_rate(uint64_t rate);
> +uint64_t sip_smc_get_ddr_rate(void);
> +uint64_t sip_smc_clr_ddr_irq(void);
> +uint64_t sip_smc_get_call_count(void);
> +uint64_t sip_smc_ddr_init(void);
> +uint64_t sip_smc_set_ddr_param(uint64_t param);
> +#else
> +static inline uint64_t sip_smc_set_ddr_rate(uint64_t rate)
> +{
> +	return 0;
> +}
> +
> +static inline uint64_t sip_smc_get_ddr_rate(void)
> +{
> +	return 0;
> +}
> +
> +static inline uint64_t sip_smc_clr_ddr_irq(void)
> +{
> +	return 0;
> +}
> +
> +static inline uint64_t sip_smc_get_call_count(void)
> +{
> +	return 0;
> +}
> +
> +static inline uint64_t sip_smc_ddr_init(void)
> +{
> +	return 0;
> +}
> +
> +static inline uint64_t sip_smc_set_ddr_param(uint64_t param)
> +{
> +	return 0;
> +}
> +#endif
> +#endif

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc
  2016-07-22  9:07 ` [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc Lin Huang
@ 2016-07-22 10:08   ` Heiko Stübner
  0 siblings, 0 replies; 24+ messages in thread
From: Heiko Stübner @ 2016-07-22 10:08 UTC (permalink / raw)
  To: linux-arm-kernel

Am Freitag, 22. Juli 2016, 17:07:16 schrieb Lin Huang:
> Signed-off-by: Lin Huang <hl@rock-chips.com>
> ---
> Changes in v3:
> -None
> 
> Changes in v2:
> - None
> Changes in v1:
> - None
> 
>  include/dt-bindings/clock/rk3399-cru.h | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/include/dt-bindings/clock/rk3399-cru.h
> b/include/dt-bindings/clock/rk3399-cru.h index 50a44cf..8a0f0442 100644
> --- a/include/dt-bindings/clock/rk3399-cru.h
> +++ b/include/dt-bindings/clock/rk3399-cru.h
> @@ -131,6 +131,7 @@
>  #define SCLK_DPHY_RX0_CFG		165
>  #define SCLK_RMII_SRC			166
>  #define SCLK_PCIEPHY_REF100M		167
> +#define SCLK_DDRCLK			168

nit: SCLK_DDR (i.e. without the CLK at the end)?

>  #define DCLK_VOP0			180
>  #define DCLK_VOP1			181

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-22  9:07 ` [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc Lin Huang
@ 2016-07-22 20:24   ` Paul Gortmaker
  2016-07-24  7:54     ` hl
  2016-07-25  6:01   ` Chanwoo Choi
  1 sibling, 1 reply; 24+ messages in thread
From: Paul Gortmaker @ 2016-07-22 20:24 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jul 22, 2016 at 5:07 AM, Lin Huang <hl@rock-chips.com> wrote:
> base on dfi result, we do ddr frequency scaling, register
> dmc driver to devfreq framework, and use simple-ondemand
> policy.
>
> Signed-off-by: Lin Huang <hl@rock-chips.com>
> ---
> Changes in v3:
> - operate dram setting through sip call
> - imporve set rate flow
>
> Changes in v2:
> - None
>
> Changes in v1:
> - move dfi controller to event
> - fix set voltage sequence when set rate fail
> - change Kconfig type from tristate to bool
> - move unuse EXPORT_SYMBOL_GPL()
>
>  drivers/devfreq/Kconfig                 |   1 +
>  drivers/devfreq/Makefile                |   1 +
>  drivers/devfreq/rockchip/Kconfig        |  15 +
>  drivers/devfreq/rockchip/Makefile       |   2 +
>  drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>  drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>  include/soc/rockchip/rockchip_dmc.h     |  45 +++
>  7 files changed, 689 insertions(+)
>  create mode 100644 drivers/devfreq/rockchip/Kconfig
>  create mode 100644 drivers/devfreq/rockchip/Makefile
>  create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>  create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>  create mode 100644 include/soc/rockchip/rockchip_dmc.h
>
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index a5be56e..cb67246 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>           operating frequencies and voltages with OPP support.
>
>  source "drivers/devfreq/event/Kconfig"
> +source "drivers/devfreq/rockchip/Kconfig"
>
>  endif # PM_DEVFREQ
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 09f11d9..48e2ae6 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)       += governor_passive.o
>  # DEVFREQ Drivers
>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)   += exynos-bus.o
>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)                += tegra-devfreq.o
> +obj-$(CONFIG_ARCH_ROCKCHIP)            += rockchip/
>
>  # DEVFREQ Event Drivers
>  obj-$(CONFIG_PM_DEVFREQ_EVENT)         += event/
> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
> new file mode 100644
> index 0000000..7fb1cff
> --- /dev/null
> +++ b/drivers/devfreq/rockchip/Kconfig
> @@ -0,0 +1,15 @@
> +config ARM_ROCKCHIP_DMC_DEVFREQ
> +       bool "ARM ROCKCHIP DMC DEVFREQ Driver"
> +       depends on ARCH_ROCKCHIP
> +       help
> +         This adds the DEVFREQ driver framework for the rockchip dmc.
> +
> +config ARM_RK3399_DMC_DEVFREQ
> +       bool "ARM RK3399 DMC DEVFREQ Driver"

Since you are using bool Kconfigs for your driver, please do not use
module.h or MODULE_<xyz> tags in your driver, and use the builtin
register function.

Alternatively if there really is  a use case for it to be a modular driver
then use a tristate Kconfig.

THanks,
Paul.
--

> +       depends on ARM_ROCKCHIP_DMC_DEVFREQ
> +       select PM_OPP
> +       select DEVFREQ_GOV_SIMPLE_ONDEMAND
> +       help
> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
> +          for the memory controller and reads the usage counts from hardware.
> +

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller
  2016-07-22  9:07 ` [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller Lin Huang
@ 2016-07-22 20:28   ` Paul Gortmaker
  0 siblings, 0 replies; 24+ messages in thread
From: Paul Gortmaker @ 2016-07-22 20:28 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jul 22, 2016 at 5:07 AM, Lin Huang <hl@rock-chips.com> wrote:
> 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>
> Acked-by: Chanwoo Choi <cw00.choi@samsung.com>
> ---
> Changes in v3:
> - None
>
> Changes in v2:
> - use clk_disable_unprepare and clk_enable_prepare
> - remove clk_enable_prepare in probe
> - remove rockchip_dfi_remove function
>
> Changes in v1:
> - None
>
>  drivers/devfreq/event/Kconfig        |   7 +
>  drivers/devfreq/event/Makefile       |   1 +
>  drivers/devfreq/event/rockchip-dfi.c | 253 +++++++++++++++++++++++++++++++++++
>  3 files changed, 261 insertions(+)
>  create mode 100644 drivers/devfreq/event/rockchip-dfi.c
>
> diff --git a/drivers/devfreq/event/Kconfig b/drivers/devfreq/event/Kconfig
> index eb6f74a..0d32d68 100644
> --- a/drivers/devfreq/event/Kconfig
> +++ b/drivers/devfreq/event/Kconfig
> @@ -30,4 +30,11 @@ config DEVFREQ_EVENT_EXYNOS_PPMU
>           (Platform Performance Monitoring Unit) counters to estimate the
>           utilization of each module.
>
> +config DEVFREQ_EVENT_ROCKCHIP_DFI
> +       bool "ROCKCHIP DFI DEVFREQ event Driver"
> +       depends on ARCH_ROCKCHIP
> +       help
> +         This add the devfreq-event driver for Rockchip SoC. It provides DFI
> +         (DDR Monitor Module) driver to count ddr load.
> +
>  endif # PM_DEVFREQ_EVENT
> diff --git a/drivers/devfreq/event/Makefile b/drivers/devfreq/event/Makefile
> index 3d6afd3..dda7090 100644
> --- a/drivers/devfreq/event/Makefile
> +++ b/drivers/devfreq/event/Makefile
> @@ -2,3 +2,4 @@
>
>  obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_NOCP) += exynos-nocp.o
>  obj-$(CONFIG_DEVFREQ_EVENT_EXYNOS_PPMU) += exynos-ppmu.o
> +obj-$(CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI) += rockchip-dfi.o
> diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c
> new file mode 100644
> index 0000000..96a0307
> --- /dev/null
> +++ b/drivers/devfreq/event/rockchip-dfi.c
> @@ -0,0 +1,253 @@
> +/*
> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
> + * Author: Lin Huang <hl@rock-chips.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/devfreq-event.h>
> +#include <linux/kernel.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>

Same comment applies here.   Also might want to check any instances
of "IS_ENABLED" being used on bool items, as that is overkill too.

Thanks,
Paul.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
  2016-07-22 10:00   ` Heiko Stübner
@ 2016-07-22 20:50   ` Heiko Stübner
  2016-07-24  8:00     ` hl
  2016-07-25 17:36     ` Sudeep Holla
  2016-07-22 21:32   ` kbuild test robot
  2016-07-26 18:29   ` Mark Rutland
  3 siblings, 2 replies; 24+ messages in thread
From: Heiko Stübner @ 2016-07-22 20:50 UTC (permalink / raw)
  To: linux-arm-kernel

Hi again,

one bigger thing I noticed only now.

Am Freitag, 22. Juli 2016, 17:07:14 schrieben Sie:
> diff --git a/drivers/firmware/rockchip_sip.c
> b/drivers/firmware/rockchip_sip.c new file mode 100644
> index 0000000..7756af9
> --- /dev/null
> +++ b/drivers/firmware/rockchip_sip.c
> @@ -0,0 +1,64 @@
> +/*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * Copyright (C) 2016 ARM Limited
> + */
> +#include <linux/errno.h>
> +#include <linux/linkage.h>
> +#include <linux/of.h>
> +#include <linux/pm.h>
> +#include <linux/printk.h>
> +#include "rockchip_sip.h"
> +
> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
> +				unsigned long, unsigned long);
> +asmlinkage psci_fn __invoke_psci_fn_smc;
> +
> +#define CONFIG_DRAM_INIT	0x00
> +#define CONFIG_DRAM_SET_RATE	0x01
> +#define CONFIG_DRAM_ROUND_RATE	0x02
> +#define CONFIG_DRAM_SET_AT_SR	0x03
> +#define CONFIG_DRAM_GET_BW	0x04
> +#define CONFIG_DRAM_GET_RATE	0x05
> +#define CONFIG_DRAM_CLR_IRQ	0x06
> +#define CONFIG_DRAM_SET_PARAM   0x07
> +
> +uint64_t sip_smc_ddr_init(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
> +				    0, CONFIG_DRAM_INIT);

I don't think that is legal to use. For one this function itself is declared 
static in the psci code - most likely for a specific reason.

And also if anything invoke_psci_fn would hold the correct pointer depending 
on the calling method.

But as said above, accessing psci static stuff is most likely wrong. Maybe the 
two psci people I've included can tell us how this is to be accessed.


Heiko

> +}
> +
> +uint64_t sip_smc_set_ddr_param(uint64_t param)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, param,
> +				    0, CONFIG_DRAM_SET_PARAM);
> +}
> +
> +uint64_t sip_smc_set_ddr_rate(uint64_t rate)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, rate, 0,
> +				    CONFIG_DRAM_SET_RATE);
> +}
> +
> +uint64_t sip_smc_get_ddr_rate(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_GET_RATE);
> +}
> +
> +uint64_t sip_smc_clr_ddr_irq(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_CLR_IRQ);
> +}
> +
> +uint64_t sip_smc_get_call_count(void)
> +{
> +	return __invoke_psci_fn_smc(SIP_SVC_CALL_COUNT, 0, 0, 0);
> +}

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
  2016-07-22 10:00   ` Heiko Stübner
  2016-07-22 20:50   ` Heiko Stübner
@ 2016-07-22 21:32   ` kbuild test robot
  2016-07-26 18:29   ` Mark Rutland
  3 siblings, 0 replies; 24+ messages in thread
From: kbuild test robot @ 2016-07-22 21:32 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

[auto build test ERROR on next-20160722]
[also build test ERROR on v4.7-rc7]
[cannot apply to rockchip/for-next stable/master linux/master v4.7-rc7 v4.7-rc6 v4.7-rc5]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Lin-Huang/rk3399-support-ddr-frequency-scaling/20160722-173343
config: arm64-allmodconfig (attached as .config)
compiler: aarch64-linux-gnu-gcc (Debian 5.4.0-6) 5.4.0 20160609
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=arm64 

All errors (new ones prefixed by >>):

   drivers/built-in.o: In function `sip_smc_ddr_init':
>> binder.c:(.text+0x279074): undefined reference to `__invoke_psci_fn_smc'
   drivers/built-in.o: In function `sip_smc_set_ddr_param':
   binder.c:(.text+0x2790ac): undefined reference to `__invoke_psci_fn_smc'
   drivers/built-in.o: In function `sip_smc_set_ddr_rate':
   binder.c:(.text+0x2790ec): undefined reference to `__invoke_psci_fn_smc'
   drivers/built-in.o: In function `sip_smc_get_ddr_rate':
   binder.c:(.text+0x279124): undefined reference to `__invoke_psci_fn_smc'
   drivers/built-in.o: In function `sip_smc_clr_ddr_irq':
   binder.c:(.text+0x279154): undefined reference to `__invoke_psci_fn_smc'
   drivers/built-in.o:binder.c:(.text+0x279184): more undefined references to `__invoke_psci_fn_smc' follow

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
-------------- next part --------------
A non-text attachment was scrubbed...
Name: .config.gz
Type: application/octet-stream
Size: 51332 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160723/7c7845a2/attachment-0001.obj>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-22 20:24   ` Paul Gortmaker
@ 2016-07-24  7:54     ` hl
  0 siblings, 0 replies; 24+ messages in thread
From: hl @ 2016-07-24  7:54 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Paul,

On 2016?07?23? 04:24, Paul Gortmaker wrote:
> On Fri, Jul 22, 2016 at 5:07 AM, Lin Huang <hl@rock-chips.com> wrote:
>> base on dfi result, we do ddr frequency scaling, register
>> dmc driver to devfreq framework, and use simple-ondemand
>> policy.
>>
>> Signed-off-by: Lin Huang <hl@rock-chips.com>
>> ---
>> Changes in v3:
>> - operate dram setting through sip call
>> - imporve set rate flow
>>
>> Changes in v2:
>> - None
>>
>> Changes in v1:
>> - move dfi controller to event
>> - fix set voltage sequence when set rate fail
>> - change Kconfig type from tristate to bool
>> - move unuse EXPORT_SYMBOL_GPL()
>>
>>   drivers/devfreq/Kconfig                 |   1 +
>>   drivers/devfreq/Makefile                |   1 +
>>   drivers/devfreq/rockchip/Kconfig        |  15 +
>>   drivers/devfreq/rockchip/Makefile       |   2 +
>>   drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>>   drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>>   include/soc/rockchip/rockchip_dmc.h     |  45 +++
>>   7 files changed, 689 insertions(+)
>>   create mode 100644 drivers/devfreq/rockchip/Kconfig
>>   create mode 100644 drivers/devfreq/rockchip/Makefile
>>   create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>>   create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>>   create mode 100644 include/soc/rockchip/rockchip_dmc.h
>>
>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>> index a5be56e..cb67246 100644
>> --- a/drivers/devfreq/Kconfig
>> +++ b/drivers/devfreq/Kconfig
>> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>>            operating frequencies and voltages with OPP support.
>>
>>   source "drivers/devfreq/event/Kconfig"
>> +source "drivers/devfreq/rockchip/Kconfig"
>>
>>   endif # PM_DEVFREQ
>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>> index 09f11d9..48e2ae6 100644
>> --- a/drivers/devfreq/Makefile
>> +++ b/drivers/devfreq/Makefile
>> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)       += governor_passive.o
>>   # DEVFREQ Drivers
>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)   += exynos-bus.o
>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)                += tegra-devfreq.o
>> +obj-$(CONFIG_ARCH_ROCKCHIP)            += rockchip/
>>
>>   # DEVFREQ Event Drivers
>>   obj-$(CONFIG_PM_DEVFREQ_EVENT)         += event/
>> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
>> new file mode 100644
>> index 0000000..7fb1cff
>> --- /dev/null
>> +++ b/drivers/devfreq/rockchip/Kconfig
>> @@ -0,0 +1,15 @@
>> +config ARM_ROCKCHIP_DMC_DEVFREQ
>> +       bool "ARM ROCKCHIP DMC DEVFREQ Driver"
>> +       depends on ARCH_ROCKCHIP
>> +       help
>> +         This adds the DEVFREQ driver framework for the rockchip dmc.
>> +
>> +config ARM_RK3399_DMC_DEVFREQ
>> +       bool "ARM RK3399 DMC DEVFREQ Driver"
> Since you are using bool Kconfigs for your driver, please do not use
> module.h or MODULE_<xyz> tags in your driver, and use the builtin
> register function.
>
> Alternatively if there really is  a use case for it to be a modular driver
> then use a tristate Kconfig.
Thanks for pointing it, will fix it next version.
> THanks,
> Paul.
> --
>
>> +       depends on ARM_ROCKCHIP_DMC_DEVFREQ
>> +       select PM_OPP
>> +       select DEVFREQ_GOV_SIMPLE_ONDEMAND
>> +       help
>> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
>> +          for the memory controller and reads the usage counts from hardware.
>> +
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip
>
>
>

-- 
Lin Huang

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22 20:50   ` Heiko Stübner
@ 2016-07-24  8:00     ` hl
  2016-07-25 17:36     ` Sudeep Holla
  1 sibling, 0 replies; 24+ messages in thread
From: hl @ 2016-07-24  8:00 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Heiko,

On 2016?07?23? 04:50, Heiko St?bner wrote:
> Hi again,
>
> one bigger thing I noticed only now.
>
> Am Freitag, 22. Juli 2016, 17:07:14 schrieben Sie:
>> diff --git a/drivers/firmware/rockchip_sip.c
>> b/drivers/firmware/rockchip_sip.c new file mode 100644
>> index 0000000..7756af9
>> --- /dev/null
>> +++ b/drivers/firmware/rockchip_sip.c
>> @@ -0,0 +1,64 @@
>> +/*
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + * Copyright (C) 2016 ARM Limited
>> + */
>> +#include <linux/errno.h>
>> +#include <linux/linkage.h>
>> +#include <linux/of.h>
>> +#include <linux/pm.h>
>> +#include <linux/printk.h>
>> +#include "rockchip_sip.h"
>> +
>> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
>> +				unsigned long, unsigned long);
>> +asmlinkage psci_fn __invoke_psci_fn_smc;
>> +
>> +#define CONFIG_DRAM_INIT	0x00
>> +#define CONFIG_DRAM_SET_RATE	0x01
>> +#define CONFIG_DRAM_ROUND_RATE	0x02
>> +#define CONFIG_DRAM_SET_AT_SR	0x03
>> +#define CONFIG_DRAM_GET_BW	0x04
>> +#define CONFIG_DRAM_GET_RATE	0x05
>> +#define CONFIG_DRAM_CLR_IRQ	0x06
>> +#define CONFIG_DRAM_SET_PARAM   0x07
>> +
>> +uint64_t sip_smc_ddr_init(void)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
>> +				    0, CONFIG_DRAM_INIT);
> I don't think that is legal to use. For one this function itself is declared
> static in the psci code - most likely for a specific reason.
>
> And also if anything invoke_psci_fn would hold the correct pointer depending
> on the calling method.
>
> But as said above, accessing psci static stuff is most likely wrong. Maybe the
> two psci people I've included can tell us how this is to be accessed.
>
   Thanks Heiko. Hope Sudeep Holla and Lorenzo Pieralisi can give me advice.
> Heiko
>
>> +}
>> +
>> +uint64_t sip_smc_set_ddr_param(uint64_t param)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, param,
>> +				    0, CONFIG_DRAM_SET_PARAM);
>> +}
>> +
>> +uint64_t sip_smc_set_ddr_rate(uint64_t rate)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, rate, 0,
>> +				    CONFIG_DRAM_SET_RATE);
>> +}
>> +
>> +uint64_t sip_smc_get_ddr_rate(void)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_GET_RATE);
>> +}
>> +
>> +uint64_t sip_smc_clr_ddr_irq(void)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0, 0, CONFIG_DRAM_CLR_IRQ);
>> +}
>> +
>> +uint64_t sip_smc_get_call_count(void)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_SVC_CALL_COUNT, 0, 0, 0);
>> +}
>
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip
>
>
>

-- 
Lin Huang

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk
  2016-07-22  9:07 ` [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk Lin Huang
@ 2016-07-24  9:09   ` Heiko Stübner
  0 siblings, 0 replies; 24+ messages in thread
From: Heiko Stübner @ 2016-07-24  9:09 UTC (permalink / raw)
  To: linux-arm-kernel

Am Freitag, 22. Juli 2016, 17:07:15 schrieb Lin Huang:
> @@ -488,6 +496,25 @@ struct rockchip_clk_branch {
>  		.child		= ch,				\
>  	}
> 
> +#define COMPOSITE_DDRC(_id, cname, pnames, f, mo, ms, mw, mf,	\
> +			 ds, dw, df)				\
> +	{							\
> +		.id		= _id,				\
> +		.branch_type	= branch_ddrc,			\
> +		.name		= cname,			\
> +		.parent_names	= pnames,			\
> +		.num_parents	= ARRAY_SIZE(pnames),		\
> +		.flags		= f,				\
> +		.muxdiv_offset	= mo,				\
> +		.mux_shift	= ms,				\
> +		.mux_width	= mw,				\
> +		.mux_flags	= mf,				\
> +		.div_shift	= ds,				\
> +		.div_width	= dw,				\
> +		.div_flags	= df,				\
> +		.gate_offset	= -1,				\
> +	}
> +

the mux and div-flags are not used in your clock type. Instead I think we could 
go a similar way as the inverter-type, having one flag type, which could for 
example encode the interface type ROCKCHIP_DDRCLK_SIP, etc.

Because right now there exist 3 types on how to set the ddr clk, SIP, an SCPI-
variant and through code in SRAM (rk3288), while the core clock type on the 
side of the CRU is pretty much the same.

And while it is definitly not necessary to do that now, I think the ddrclk-type 
can hold the other interfaces as well in the future.


Heiko

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-22  9:07 ` [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc Lin Huang
  2016-07-22 20:24   ` Paul Gortmaker
@ 2016-07-25  6:01   ` Chanwoo Choi
  2016-07-25  8:47     ` hl
  1 sibling, 1 reply; 24+ messages in thread
From: Chanwoo Choi @ 2016-07-25  6:01 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Lin,

I'm glad to support the for dmc ddr clock scaling with devfreq/devfreq-event.

But, I think that you have to use the standard interface.
As I already mentioned[1] on previous mail, devfreq fwk support
the standard DEVFREQ_TRANSITION_NOTIFIER notifier which has the two notifications.
- DEVFREQ_PRECHANGE
- DEVFREQ_POSTCHANGE
[1] https://patchwork.kernel.org/patch/9194305/

You can use the DEVFREQ_PRECHANGE instead of DMCFREQ_ADJUST
and use the DEVFREQ_POSTCHANGE instead of DMCFREQ_FINISH.

On 2016? 07? 22? 18:07, Lin Huang wrote:
> base on dfi result, we do ddr frequency scaling, register
> dmc driver to devfreq framework, and use simple-ondemand
> policy.
> 
> Signed-off-by: Lin Huang <hl@rock-chips.com>
> ---
> Changes in v3:
> - operate dram setting through sip call
> - imporve set rate flow
> 
> Changes in v2:
> - None
>  
> Changes in v1:
> - move dfi controller to event
> - fix set voltage sequence when set rate fail
> - change Kconfig type from tristate to bool
> - move unuse EXPORT_SYMBOL_GPL()
> 
>  drivers/devfreq/Kconfig                 |   1 +
>  drivers/devfreq/Makefile                |   1 +
>  drivers/devfreq/rockchip/Kconfig        |  15 +
>  drivers/devfreq/rockchip/Makefile       |   2 +
>  drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>  drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>  include/soc/rockchip/rockchip_dmc.h     |  45 +++
>  7 files changed, 689 insertions(+)
>  create mode 100644 drivers/devfreq/rockchip/Kconfig
>  create mode 100644 drivers/devfreq/rockchip/Makefile
>  create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>  create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>  create mode 100644 include/soc/rockchip/rockchip_dmc.h
> 
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index a5be56e..cb67246 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>           operating frequencies and voltages with OPP support.
>  
>  source "drivers/devfreq/event/Kconfig"
> +source "drivers/devfreq/rockchip/Kconfig"
>  
>  endif # PM_DEVFREQ
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 09f11d9..48e2ae6 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>  # DEVFREQ Drivers
>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>  obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra-devfreq.o
> +obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
>  
>  # DEVFREQ Event Drivers
>  obj-$(CONFIG_PM_DEVFREQ_EVENT)		+= event/
> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
> new file mode 100644
> index 0000000..7fb1cff
> --- /dev/null
> +++ b/drivers/devfreq/rockchip/Kconfig
> @@ -0,0 +1,15 @@
> +config ARM_ROCKCHIP_DMC_DEVFREQ
> +	bool "ARM ROCKCHIP DMC DEVFREQ Driver"
> +	depends on ARCH_ROCKCHIP
> +	help
> +	  This adds the DEVFREQ driver framework for the rockchip dmc.
> +
> +config ARM_RK3399_DMC_DEVFREQ
> +	bool "ARM RK3399 DMC DEVFREQ Driver"
> +	depends on ARM_ROCKCHIP_DMC_DEVFREQ
> +	select PM_OPP
> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
> +	help
> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
> +          for the memory controller and reads the usage counts from hardware.
> +
> diff --git a/drivers/devfreq/rockchip/Makefile b/drivers/devfreq/rockchip/Makefile
> new file mode 100644
> index 0000000..caca525
> --- /dev/null
> +++ b/drivers/devfreq/rockchip/Makefile
> @@ -0,0 +1,2 @@
> +obj-$(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)	+= rockchip_dmc.o
> +obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
> diff --git a/drivers/devfreq/rockchip/rk3399_dmc.c b/drivers/devfreq/rockchip/rk3399_dmc.c
> new file mode 100644
> index 0000000..f1d6120
> --- /dev/null
> +++ b/drivers/devfreq/rockchip/rk3399_dmc.c
> @@ -0,0 +1,482 @@
> +/*
> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
> + * Author: Lin Huang <hl@rock-chips.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/devfreq.h>
> +#include <linux/devfreq-event.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_opp.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/rwsem.h>
> +#include <linux/suspend.h>
> +#include <linux/syscore_ops.h>
> +
> +#include <soc/rockchip/rockchip_dmc.h>
> +#include "../../firmware/rockchip_sip.h"
> +
> +struct dram_timing {
> +	unsigned int ddr3_speed_bin;
> +	unsigned int pd_idle;
> +	unsigned int sr_idle;
> +	unsigned int sr_mc_gate_idle;
> +	unsigned int srpd_lite_idle;
> +	unsigned int standby_idle;
> +	unsigned int dram_dll_dis_freq;
> +	unsigned int phy_dll_dis_freq;
> +	unsigned int ddr3_odt_dis_freq;
> +	unsigned int ddr3_drv;
> +	unsigned int ddr3_odt;
> +	unsigned int phy_ddr3_ca_drv;
> +	unsigned int phy_ddr3_dq_drv;
> +	unsigned int phy_ddr3_odt;
> +	unsigned int lpddr3_odt_dis_freq;
> +	unsigned int lpddr3_drv;
> +	unsigned int lpddr3_odt;
> +	unsigned int phy_lpddr3_ca_drv;
> +	unsigned int phy_lpddr3_dq_drv;
> +	unsigned int phy_lpddr3_odt;
> +	unsigned int lpddr4_odt_dis_freq;
> +	unsigned int lpddr4_drv;
> +	unsigned int lpddr4_dq_odt;
> +	unsigned int lpddr4_ca_odt;
> +	unsigned int phy_lpddr4_ca_drv;
> +	unsigned int phy_lpddr4_ck_cs_drv;
> +	unsigned int phy_lpddr4_dq_drv;
> +	unsigned int phy_lpddr4_odt;
> +};
> +
> +struct rk3399_dmcfreq {
> +	struct device *dev;
> +	struct devfreq *devfreq;
> +	struct devfreq_simple_ondemand_data ondemand_data;
> +	struct clk *dmc_clk;
> +	struct devfreq_event_dev *edev;
> +	struct mutex lock;
> +	struct notifier_block dmc_nb;
> +	struct dram_timing *timing;
> +	wait_queue_head_t	wait_dcf_queue;
> +	int irq;
> +	int wait_dcf_flag;
> +	struct regulator *vdd_center;
> +	unsigned long rate, target_rate;
> +	unsigned long volt, target_volt;
> +};
> +
> +static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
> +				 u32 flags)
> +{
> +	struct platform_device *pdev = container_of(dev, struct platform_device,
> +						    dev);
> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
> +	struct dev_pm_opp *opp;
> +	unsigned long old_clk_rate = dmcfreq->rate;
> +	unsigned long target_volt, target_rate;
> +	int err;
> +
> +	rcu_read_lock();
> +	opp = devfreq_recommended_opp(dev, freq, flags);
> +	if (IS_ERR(opp)) {
> +		rcu_read_unlock();
> +		return PTR_ERR(opp);
> +	}
> +	target_rate = dev_pm_opp_get_freq(opp);
> +	target_volt = dev_pm_opp_get_voltage(opp);
> +	opp = devfreq_recommended_opp(dev, &dmcfreq->rate, flags);
> +	if (IS_ERR(opp)) {
> +		rcu_read_unlock();
> +		return PTR_ERR(opp);
> +	}
> +	dmcfreq->volt = dev_pm_opp_get_voltage(opp);
> +	rcu_read_unlock();
> +
> +	if (dmcfreq->rate == target_rate)
> +		return 0;
> +
> +	mutex_lock(&dmcfreq->lock);
> +	/*
> +	 * if frequency scaling from low to high, adjust voltage first;
> +	 * if frequency scaling from high to low, adjuset frequency first;
> +	 */
> +	if (old_clk_rate < target_rate) {
> +		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
> +					    target_volt);
> +		if (err) {
> +			dev_err(dev, "Unable to set vol %lu\n", target_volt);
> +			goto out;
> +		}
> +	}
> +
> +	dmc_event(DMCFREQ_ADJUST);

You don't need to send the notification in target function.
When you use the DEVFREQ_TRANSITION_NOTIFIER notifier,
devfreq send the notification before calling the target() and after calling the target().

> +	dmcfreq->wait_dcf_flag = 1;
> +
> +	err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
> +	if (err) {
> +		dev_err(dev,
> +			"Unable to set freq %lu. Current freq %lu. Error %d\n",
> +			target_rate, old_clk_rate, err);
> +		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
> +				      dmcfreq->volt);
> +		dmc_event(DMCFREQ_FINISH);

ditto.

> +		goto out;
> +	}
> +
> +	/*
> +	 * wait until bcf irq happen, it means freq scaling finish in bl31,
> +	 * use 100ms as timeout time
> +	 */
> +	wait_event_timeout(dmcfreq->wait_dcf_queue,
> +			   !dmcfreq->wait_dcf_flag, HZ / 10);
> +
> +	dmc_event(DMCFREQ_FINISH);

ditto.

> +
> +	/*
> +	 * check the dpll rate
> +	 * there only two result we will get,
> +	 * 1. ddr frequency scaling fail, we still get the old rate
> +	 * 2, ddr frequency scaling sucessful, we get the rate we set
> +	 */
> +	dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk);
> +
> +	/* if get the incorrect rate, set voltage to old value */
> +	if (dmcfreq->rate != target_rate) {
> +		dev_err(dev, "get wrong ddr frequency, Request freq %lu,\
> +			Current freq %lu\n", target_rate, dmcfreq->rate);
> +		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
> +				      dmcfreq->volt);
> +	} else if (old_clk_rate > target_rate)
> +		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
> +					    target_volt);
> +	if (err)
> +		dev_err(dev, "Unable to set vol %lu\n", target_volt);
> +
> +out:
> +	mutex_unlock(&dmcfreq->lock);
> +	return err;
> +}
> +
> +static int rk3399_dmcfreq_get_dev_status(struct device *dev,
> +					 struct devfreq_dev_status *stat)
> +{
> +	struct platform_device *pdev = container_of(dev, struct platform_device,
> +						    dev);
> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
> +	struct devfreq_event_data edata;
> +
> +	devfreq_event_get_event(dmcfreq->edev, &edata);
> +
> +	stat->current_frequency = dmcfreq->rate;
> +	stat->busy_time = edata.load_count;
> +	stat->total_time = edata.total_count;
> +
> +	return 0;
> +}
> +
> +static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
> +{
> +	struct platform_device *pdev = container_of(dev, struct platform_device,
> +						    dev);
> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
> +
> +	*freq = dmcfreq->rate;
> +
> +	return 0;
> +}
> +
> +static void rk3399_dmcfreq_exit(struct device *dev)
> +{
> +	struct platform_device *pdev = container_of(dev, struct platform_device,
> +						    dev);
> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
> +
> +	devfreq_unregister_opp_notifier(dev, dmcfreq->devfreq);
> +}
> +
> +static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = {
> +	.polling_ms	= 200,
> +	.target		= rk3399_dmcfreq_target,
> +	.get_dev_status	= rk3399_dmcfreq_get_dev_status,
> +	.get_cur_freq	= rk3399_dmcfreq_get_cur_freq,
> +	.exit		= rk3399_dmcfreq_exit,
> +};
> +
> +static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev)
> +{
> +	rockchip_dmc_disable();
> +	return 0;
> +}
> +
> +static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
> +{
> +	rockchip_dmc_enable();
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
> +			 rk3399_dmcfreq_resume);
> +
> +static int rk3399_dmc_enable_notify(struct notifier_block *nb,
> +				    unsigned long event, void *data)
> +{
> +	struct rk3399_dmcfreq *dmcfreq =
> +			      container_of(nb, struct rk3399_dmcfreq, dmc_nb);
> +	unsigned long freq = ULONG_MAX;
> +
> +	if (event == DMC_ENABLE) {
> +		devfreq_event_enable_edev(dmcfreq->edev);
> +		devfreq_resume_device(dmcfreq->devfreq);
> +		return NOTIFY_OK;
> +	} else if (event == DMC_DISABLE) {
> +		devfreq_event_disable_edev(dmcfreq->edev);
> +		devfreq_suspend_device(dmcfreq->devfreq);
> +
> +		/* when disable dmc, set sdram to max frequency */
> +		rk3399_dmcfreq_target(dmcfreq->dev, &freq, 0);
> +		return NOTIFY_OK;
> +	}
> +
> +	return NOTIFY_DONE;
> +}
> +
> +static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
> +{
> +	struct rk3399_dmcfreq *dmcfreq = dev_id;
> +
> +	dmcfreq->wait_dcf_flag = 0;
> +	wake_up(&dmcfreq->wait_dcf_queue);
> +
> +	/* clr dcf irq */
> +	sip_smc_clr_ddr_irq();
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int of_do_get_timing(struct device_node *np,
> +		struct dram_timing *timing)
> +{
> +	int ret;
> +
> +	ret = of_property_read_u32(np, "ddr3_speed_bin",
> +				   &timing->ddr3_speed_bin);
> +	ret |= of_property_read_u32(np, "pd_idle", &timing->pd_idle);
> +	ret |= of_property_read_u32(np, "sr_idle", &timing->sr_idle);
> +	ret |= of_property_read_u32(np, "sr_mc_gate_idle",
> +				    &timing->sr_mc_gate_idle);
> +	ret |= of_property_read_u32(np, "srpd_lite_idle",
> +				    &timing->srpd_lite_idle);
> +	ret |= of_property_read_u32(np, "standby_idle", &timing->standby_idle);
> +	ret |= of_property_read_u32(np, "dram_dll_dis_freq",
> +				    &timing->dram_dll_dis_freq);
> +	ret |= of_property_read_u32(np, "phy_dll_dis_freq",
> +				    &timing->phy_dll_dis_freq);
> +	ret |= of_property_read_u32(np, "ddr3_odt_dis_freq",
> +				    &timing->ddr3_odt_dis_freq);
> +	ret |= of_property_read_u32(np, "ddr3_drv", &timing->ddr3_drv);
> +	ret |= of_property_read_u32(np, "ddr3_odt", &timing->ddr3_odt);
> +	ret |= of_property_read_u32(np, "phy_ddr3_ca_drv",
> +				    &timing->phy_ddr3_ca_drv);
> +	ret |= of_property_read_u32(np, "phy_ddr3_dq_drv",
> +				    &timing->phy_ddr3_dq_drv);
> +	ret |= of_property_read_u32(np, "phy_ddr3_odt", &timing->phy_ddr3_odt);
> +	ret |= of_property_read_u32(np, "lpddr3_odt_dis_freq",
> +				    &timing->lpddr3_odt_dis_freq);
> +	ret |= of_property_read_u32(np, "lpddr3_drv", &timing->lpddr3_drv);
> +	ret |= of_property_read_u32(np, "lpddr3_odt", &timing->lpddr3_odt);
> +	ret |= of_property_read_u32(np, "phy_lpddr3_ca_drv",
> +				    &timing->phy_lpddr3_ca_drv);
> +	ret |= of_property_read_u32(np, "phy_lpddr3_dq_drv",
> +				    &timing->phy_lpddr3_dq_drv);
> +	ret |= of_property_read_u32(np, "phy_lpddr3_odt",
> +				    &timing->phy_lpddr3_odt);
> +	ret |= of_property_read_u32(np, "lpddr4_odt_dis_freq",
> +				    &timing->lpddr4_odt_dis_freq);
> +	ret |= of_property_read_u32(np, "lpddr4_drv",
> +				    &timing->lpddr4_drv);
> +	ret |= of_property_read_u32(np, "lpddr4_dq_odt",
> +				    &timing->lpddr4_dq_odt);
> +	ret |= of_property_read_u32(np, "lpddr4_ca_odt",
> +				    &timing->lpddr4_ca_odt);
> +	ret |= of_property_read_u32(np, "phy_lpddr4_ca_drv",
> +				    &timing->phy_lpddr4_ca_drv);
> +	ret |= of_property_read_u32(np, "phy_lpddr4_ck_cs_drv",
> +				    &timing->phy_lpddr4_ck_cs_drv);
> +	ret |= of_property_read_u32(np, "phy_lpddr4_dq_drv",
> +				    &timing->phy_lpddr4_dq_drv);
> +	ret |= of_property_read_u32(np, "phy_lpddr4_odt",
> +				    &timing->phy_lpddr4_odt);
> +
> +	return ret;
> +}
> +
> +static struct dram_timing *of_get_ddr_timings(struct device *dev,
> +					      struct device_node *np)
> +{
> +	struct dram_timing	*timing = NULL;
> +	struct device_node	*np_tim;
> +
> +	np_tim = of_parse_phandle(np, "ddr_timing", 0);
> +	if (np_tim) {
> +		timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
> +		if (!timing)
> +			goto err;
> +		if (of_do_get_timing(np_tim, timing)) {
> +			devm_kfree(dev, timing);
> +			goto err;
> +		}
> +		return timing;
> +	}
> +
> +err:
> +	if (timing) {
> +		devm_kfree(dev, timing);
> +		timing = NULL;
> +	}
> +
> +	return timing;
> +}
> +
> +static int rk3399_dmcfreq_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct rk3399_dmcfreq *data;
> +	int ret, irq, index;
> +	uint64_t param = 0;
> +	uint32_t *timing;
> +	struct device_node *np = pdev->dev.of_node;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "no dmc irq resource\n");
> +		return -EINVAL;
> +	}
> +
> +	data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	mutex_init(&data->lock);
> +
> +	data->vdd_center = devm_regulator_get(dev, "center");
> +	if (IS_ERR(data->vdd_center)) {
> +		dev_err(dev, "Cannot get the regulator \"center\"\n");
> +		return PTR_ERR(data->vdd_center);
> +	}
> +
> +	data->dmc_clk = devm_clk_get(dev, "dmc_clk");
> +	if (IS_ERR(data->dmc_clk)) {
> +		dev_err(dev, "Cannot get the clk dmc_clk\n");
> +		return PTR_ERR(data->dmc_clk);
> +	};
> +
> +	data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
> +	if (IS_ERR(data->edev))
> +		return -EPROBE_DEFER;
> +
> +	ret = devfreq_event_enable_edev(data->edev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to enable devfreq-event devices\n");
> +		return ret;
> +	}
> +
> +	/*
> +	 * We add a devfreq driver to our parent since it has a device tree node
> +	 * with operating points.
> +	 */
> +	if (dev_pm_opp_of_add_table(dev)) {
> +		dev_err(dev, "Invalid operating-points in device tree.\n");
> +		return -EINVAL;
> +	}
> +
> +	of_property_read_u32(np, "upthreshold",
> +			     &data->ondemand_data.upthreshold);
> +
> +	of_property_read_u32(np, "downdifferential",
> +			     &data->ondemand_data.downdifferential);
> +
> +	data->rate = clk_get_rate(data->dmc_clk);
> +	rk3399_devfreq_dmc_profile.initial_freq = data->rate;
> +	data->devfreq = devfreq_add_device(dev,
> +					   &rk3399_devfreq_dmc_profile,
> +					   "simple_ondemand",
> +					   &data->ondemand_data);
> +	if (IS_ERR(data->devfreq))
> +		return PTR_ERR(data->devfreq);
> +
> +	devfreq_register_opp_notifier(dev, data->devfreq);
> +
> +	data->dmc_nb.notifier_call = rk3399_dmc_enable_notify;
> +	dmc_register_notifier(&data->dmc_nb);
> +	data->irq = irq;
> +	ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
> +			       dev_name(dev), data);
> +	if (ret) {
> +		dev_err(dev, "failed to request dmc irq: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* get dram timing and pass it to bl31 */
> +	data->timing = of_get_ddr_timings(dev, np);
> +	if (data->timing) {
> +		timing = (uint32_t *)data->timing;
> +		for (index = 0; index < (sizeof(struct dram_timing) / 4);
> +		     index++) {
> +			param = index;
> +			param = param << 32 | *timing++;
> +			sip_smc_set_ddr_param(param);
> +			param = 0;
> +		}
> +	}
> +	sip_smc_ddr_init();
> +
> +	init_waitqueue_head(&data->wait_dcf_queue);
> +	data->wait_dcf_flag = 0;
> +
> +	data->dev = dev;
> +	platform_set_drvdata(pdev, data);
> +
> +	return 0;
> +}
> +
> +static int rk3399_dmcfreq_remove(struct platform_device *pdev)
> +{
> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
> +
> +	devfreq_remove_device(dmcfreq->devfreq);
> +	regulator_put(dmcfreq->vdd_center);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id rk3399dmc_devfreq_of_match[] = {
> +	{ .compatible = "rockchip,rk3399-dmc" },
> +	{ },
> +};
> +
> +static struct platform_driver rk3399_dmcfreq_driver = {
> +	.probe	= rk3399_dmcfreq_probe,
> +	.remove	= rk3399_dmcfreq_remove,
> +	.driver = {
> +		.name	= "rk3399-dmc-freq",
> +		.pm	= &rk3399_dmcfreq_pm,
> +		.of_match_table = rk3399dmc_devfreq_of_match,
> +	},
> +};
> +module_platform_driver(rk3399_dmcfreq_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework");
> diff --git a/drivers/devfreq/rockchip/rockchip_dmc.c b/drivers/devfreq/rockchip/rockchip_dmc.c
> new file mode 100644
> index 0000000..78baee5
> --- /dev/null
> +++ b/drivers/devfreq/rockchip/rockchip_dmc.c
> @@ -0,0 +1,143 @@
> +/*
> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
> + * Author: Lin Huang <hl@rock-chips.com>
> + * Base on: https://chromium-review.googlesource.com/#/c/231477/
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#include <linux/mutex.h>
> +#include <soc/rockchip/rockchip_dmc.h>
> +
> +static int num_wait;
> +static int num_disable;
> +static BLOCKING_NOTIFIER_HEAD(dmc_notifier_list);
> +static DEFINE_MUTEX(dmc_en_lock);
> +static DEFINE_MUTEX(dmc_sync_lock);
> +
> +/**
> + * rockchip_dmc_enabled - Returns true if dmc freq is enabled, false otherwise.
> + */
> +bool rockchip_dmc_enabled(void)
> +{
> +	return num_disable <= 0 && num_wait <= 1;
> +}
> +
> +/**
> + * rockchip_dmc_enable - Enable dmc frequency scaling. Will only enable
> + * frequency scaling if there are 1 or fewer notifiers. Call to undo
> + * rockchip_dmc_disable.
> + */
> +void rockchip_dmc_enable(void)
> +{
> +	mutex_lock(&dmc_en_lock);
> +	num_disable--;
> +	WARN_ON(num_disable < 0);
> +	if (rockchip_dmc_enabled())
> +		dmc_event(DMC_ENABLE);
> +	mutex_unlock(&dmc_en_lock);
> +}
> +
> +/**
> + * rockchip_dmc_disable - Disable dmc frequency scaling. Call when something
> + * cannot coincide with dmc frequency scaling.
> + */
> +void rockchip_dmc_disable(void)
> +{
> +	mutex_lock(&dmc_en_lock);
> +	if (rockchip_dmc_enabled())
> +		dmc_event(DMC_DISABLE);
> +	num_disable++;
> +	mutex_unlock(&dmc_en_lock);
> +}
> +
> +void dmc_event(int event)
> +{
> +	mutex_lock(&dmc_sync_lock);
> +	blocking_notifier_call_chain(&dmc_notifier_list, event, NULL);
> +	mutex_unlock(&dmc_sync_lock);
> +}
> +
> +/**
> + * dmc_register_notifier - register a driver to dmc chain
> + * @nb: notifier function to register
> + */
> +int dmc_register_notifier(struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	if (!nb)
> +		return -EINVAL;
> +
> +	ret = blocking_notifier_chain_register(&dmc_notifier_list, nb);
> +
> +	return ret;
> +}
> +
> +/**
> + * dmc_unregister_notifier - unregister a driver from dmc chain
> + * @nb: remove notifier function
> + */
> +int dmc_unregister_notifier(struct notifier_block *nb)
> +{
> +	int ret;
> +
> +	if (!nb)
> +		return -EINVAL;
> +
> +	ret = blocking_notifier_chain_unregister(&dmc_notifier_list, nb);
> +
> +	return ret;
> +}
> +
> +/**
> + * rockchip_dmc_get - Register the notifier block for the dmc chain.
> + * @nb The dmc notifier block to register
> + */
> +int rockchip_dmc_get(struct notifier_block *nb)
> +{
> +	if (!nb)
> +		return -EINVAL;
> +
> +	mutex_lock(&dmc_en_lock);
> +
> +	/*
> +	 * if have two notifier(enable two vop etc),
> +	 * need to disable dmc
> +	 */
> +	if (num_wait == 1 && num_disable <= 0)
> +		dmc_event(DMC_DISABLE);
> +	num_wait++;
> +	dmc_register_notifier(nb);
> +	mutex_unlock(&dmc_en_lock);
> +
> +	return 0;
> +}
> +
> +/**
> + * rockchip_dmc_put - Remove the notifier block from the dmc chain.
> + * @nb The dmc notifier block to unregister
> + */
> +int rockchip_dmc_put(struct notifier_block *nb)
> +{
> +	if (!nb)
> +		return -EINVAL;
> +
> +	mutex_lock(&dmc_en_lock);
> +	num_wait--;
> +
> +	/* if notifier from 2 back to 1, enable dmc again */
> +	if (num_wait == 1 && num_disable <= 0)
> +		dmc_event(DMC_ENABLE);
> +	dmc_unregister_notifier(nb);
> +	mutex_unlock(&dmc_en_lock);
> +
> +	return 0;
> +}
> diff --git a/include/soc/rockchip/rockchip_dmc.h b/include/soc/rockchip/rockchip_dmc.h
> new file mode 100644
> index 0000000..3f69cbf
> --- /dev/null
> +++ b/include/soc/rockchip/rockchip_dmc.h
> @@ -0,0 +1,45 @@
> +/*
> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#ifndef __SOC_ROCKCHIP_DMC_H
> +#define __SOC_ROCKCHIP_DMC_H
> +
> +#include <linux/notifier.h>
> +
> +#define DMC_ENABLE	0
> +#define DMC_DISABLE	1
> +#define DMCFREQ_ADJUST	2
> +#define DMCFREQ_FINISH	3
> +
> +#if IS_ENABLED(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)
> +int rockchip_dmc_get(struct notifier_block *nb);
> +int rockchip_dmc_put(struct notifier_block *nb);
> +#else
> +static inline int rockchip_dmc_get(struct notifier_block *nb)
> +{
> +	return 0;
> +}
> +
> +static inline int rockchip_dmc_put(struct notifier_block *nb)
> +{
> +	return 0;
> +}
> +#endif
> +
> +void dmc_event(int event);
> +int dmc_register_notifier(struct notifier_block *nb);
> +int dmc_unregister_notifier(struct notifier_block *nb);
> +void rockchip_dmc_enable(void);
> +void rockchip_dmc_disable(void);
> +bool rockchip_dmc_enabled(void);
> +#endif
> 

Regards,
Chanwoo Choi

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-25  6:01   ` Chanwoo Choi
@ 2016-07-25  8:47     ` hl
  2016-07-25  9:45       ` Chanwoo Choi
  0 siblings, 1 reply; 24+ messages in thread
From: hl @ 2016-07-25  8:47 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Chanwoo Choi,

On 2016?07?25? 14:01, Chanwoo Choi wrote:
> Hi Lin,
>
> I'm glad to support the for dmc ddr clock scaling with devfreq/devfreq-event.
>
> But, I think that you have to use the standard interface.
> As I already mentioned[1] on previous mail, devfreq fwk support
> the standard DEVFREQ_TRANSITION_NOTIFIER notifier which has the two notifications.
> - DEVFREQ_PRECHANGE
> - DEVFREQ_POSTCHANGE
> [1] https://patchwork.kernel.org/patch/9194305/
>
> You can use the DEVFREQ_PRECHANGE instead of DMCFREQ_ADJUST
> and use the DEVFREQ_POSTCHANGE instead of DMCFREQ_FINISH.
>
> On 2016? 07? 22? 18:07, Lin Huang wrote:
>> base on dfi result, we do ddr frequency scaling, register
>> dmc driver to devfreq framework, and use simple-ondemand
>> policy.
>>
>> Signed-off-by: Lin Huang <hl@rock-chips.com>
>> ---
>> Changes in v3:
>> - operate dram setting through sip call
>> - imporve set rate flow
>>
>> Changes in v2:
>> - None
>>   
>> Changes in v1:
>> - move dfi controller to event
>> - fix set voltage sequence when set rate fail
>> - change Kconfig type from tristate to bool
>> - move unuse EXPORT_SYMBOL_GPL()
>>
>>   drivers/devfreq/Kconfig                 |   1 +
>>   drivers/devfreq/Makefile                |   1 +
>>   drivers/devfreq/rockchip/Kconfig        |  15 +
>>   drivers/devfreq/rockchip/Makefile       |   2 +
>>   drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>>   drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>>   include/soc/rockchip/rockchip_dmc.h     |  45 +++
>>   7 files changed, 689 insertions(+)
>>   create mode 100644 drivers/devfreq/rockchip/Kconfig
>>   create mode 100644 drivers/devfreq/rockchip/Makefile
>>   create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>>   create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>>   create mode 100644 include/soc/rockchip/rockchip_dmc.h
>>
>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>> index a5be56e..cb67246 100644
>> --- a/drivers/devfreq/Kconfig
>> +++ b/drivers/devfreq/Kconfig
>> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>>            operating frequencies and voltages with OPP support.
>>   
>>   source "drivers/devfreq/event/Kconfig"
>> +source "drivers/devfreq/rockchip/Kconfig"
>>   
>>   endif # PM_DEVFREQ
>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>> index 09f11d9..48e2ae6 100644
>> --- a/drivers/devfreq/Makefile
>> +++ b/drivers/devfreq/Makefile
>> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
>>   # DEVFREQ Drivers
>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra-devfreq.o
>> +obj-$(CONFIG_ARCH_ROCKCHIP)		+= rockchip/
>>   
>>   # DEVFREQ Event Drivers
>>   obj-$(CONFIG_PM_DEVFREQ_EVENT)		+= event/
>> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
>> new file mode 100644
>> index 0000000..7fb1cff
>> --- /dev/null
>> +++ b/drivers/devfreq/rockchip/Kconfig
>> @@ -0,0 +1,15 @@
>> +config ARM_ROCKCHIP_DMC_DEVFREQ
>> +	bool "ARM ROCKCHIP DMC DEVFREQ Driver"
>> +	depends on ARCH_ROCKCHIP
>> +	help
>> +	  This adds the DEVFREQ driver framework for the rockchip dmc.
>> +
>> +config ARM_RK3399_DMC_DEVFREQ
>> +	bool "ARM RK3399 DMC DEVFREQ Driver"
>> +	depends on ARM_ROCKCHIP_DMC_DEVFREQ
>> +	select PM_OPP
>> +	select DEVFREQ_GOV_SIMPLE_ONDEMAND
>> +	help
>> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
>> +          for the memory controller and reads the usage counts from hardware.
>> +
>> diff --git a/drivers/devfreq/rockchip/Makefile b/drivers/devfreq/rockchip/Makefile
>> new file mode 100644
>> index 0000000..caca525
>> --- /dev/null
>> +++ b/drivers/devfreq/rockchip/Makefile
>> @@ -0,0 +1,2 @@
>> +obj-$(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)	+= rockchip_dmc.o
>> +obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)	+= rk3399_dmc.o
>> diff --git a/drivers/devfreq/rockchip/rk3399_dmc.c b/drivers/devfreq/rockchip/rk3399_dmc.c
>> new file mode 100644
>> index 0000000..f1d6120
>> --- /dev/null
>> +++ b/drivers/devfreq/rockchip/rk3399_dmc.c
>> @@ -0,0 +1,482 @@
>> +/*
>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>> + * Author: Lin Huang <hl@rock-chips.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/devfreq.h>
>> +#include <linux/devfreq-event.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_opp.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/rwsem.h>
>> +#include <linux/suspend.h>
>> +#include <linux/syscore_ops.h>
>> +
>> +#include <soc/rockchip/rockchip_dmc.h>
>> +#include "../../firmware/rockchip_sip.h"
>> +
>> +struct dram_timing {
>> +	unsigned int ddr3_speed_bin;
>> +	unsigned int pd_idle;
>> +	unsigned int sr_idle;
>> +	unsigned int sr_mc_gate_idle;
>> +	unsigned int srpd_lite_idle;
>> +	unsigned int standby_idle;
>> +	unsigned int dram_dll_dis_freq;
>> +	unsigned int phy_dll_dis_freq;
>> +	unsigned int ddr3_odt_dis_freq;
>> +	unsigned int ddr3_drv;
>> +	unsigned int ddr3_odt;
>> +	unsigned int phy_ddr3_ca_drv;
>> +	unsigned int phy_ddr3_dq_drv;
>> +	unsigned int phy_ddr3_odt;
>> +	unsigned int lpddr3_odt_dis_freq;
>> +	unsigned int lpddr3_drv;
>> +	unsigned int lpddr3_odt;
>> +	unsigned int phy_lpddr3_ca_drv;
>> +	unsigned int phy_lpddr3_dq_drv;
>> +	unsigned int phy_lpddr3_odt;
>> +	unsigned int lpddr4_odt_dis_freq;
>> +	unsigned int lpddr4_drv;
>> +	unsigned int lpddr4_dq_odt;
>> +	unsigned int lpddr4_ca_odt;
>> +	unsigned int phy_lpddr4_ca_drv;
>> +	unsigned int phy_lpddr4_ck_cs_drv;
>> +	unsigned int phy_lpddr4_dq_drv;
>> +	unsigned int phy_lpddr4_odt;
>> +};
>> +
>> +struct rk3399_dmcfreq {
>> +	struct device *dev;
>> +	struct devfreq *devfreq;
>> +	struct devfreq_simple_ondemand_data ondemand_data;
>> +	struct clk *dmc_clk;
>> +	struct devfreq_event_dev *edev;
>> +	struct mutex lock;
>> +	struct notifier_block dmc_nb;
>> +	struct dram_timing *timing;
>> +	wait_queue_head_t	wait_dcf_queue;
>> +	int irq;
>> +	int wait_dcf_flag;
>> +	struct regulator *vdd_center;
>> +	unsigned long rate, target_rate;
>> +	unsigned long volt, target_volt;
>> +};
>> +
>> +static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
>> +				 u32 flags)
>> +{
>> +	struct platform_device *pdev = container_of(dev, struct platform_device,
>> +						    dev);
>> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>> +	struct dev_pm_opp *opp;
>> +	unsigned long old_clk_rate = dmcfreq->rate;
>> +	unsigned long target_volt, target_rate;
>> +	int err;
>> +
>> +	rcu_read_lock();
>> +	opp = devfreq_recommended_opp(dev, freq, flags);
>> +	if (IS_ERR(opp)) {
>> +		rcu_read_unlock();
>> +		return PTR_ERR(opp);
>> +	}
>> +	target_rate = dev_pm_opp_get_freq(opp);
>> +	target_volt = dev_pm_opp_get_voltage(opp);
>> +	opp = devfreq_recommended_opp(dev, &dmcfreq->rate, flags);
>> +	if (IS_ERR(opp)) {
>> +		rcu_read_unlock();
>> +		return PTR_ERR(opp);
>> +	}
>> +	dmcfreq->volt = dev_pm_opp_get_voltage(opp);
>> +	rcu_read_unlock();
>> +
>> +	if (dmcfreq->rate == target_rate)
>> +		return 0;
>> +
>> +	mutex_lock(&dmcfreq->lock);
>> +	/*
>> +	 * if frequency scaling from low to high, adjust voltage first;
>> +	 * if frequency scaling from high to low, adjuset frequency first;
>> +	 */
>> +	if (old_clk_rate < target_rate) {
>> +		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>> +					    target_volt);
>> +		if (err) {
>> +			dev_err(dev, "Unable to set vol %lu\n", target_volt);
>> +			goto out;
>> +		}
>> +	}
>> +
>> +	dmc_event(DMCFREQ_ADJUST);
> You don't need to send the notification in target function.
> When you use the DEVFREQ_TRANSITION_NOTIFIER notifier,
> devfreq send the notification before calling the target() and after calling the target().
I checked DEVFREQ_TRANSISTION_NOTIFIER notifier before, but i have some 
question:
Now i want to use
     DEVFREQ_PRECHANGE and DEVFREQ_POSTCHANGE in vop driver, but it hard 
to get the *devfreq
in vop driver, or i can register a DEVFREQ_TRANSITION_NOTIFIER notifier 
in rk3399_dmc.c and
when get message, post DEVFREQ_PRECHANGE or DEVFREQ_POSTCHANGE to vop 
driver through dmc_event(),
do you have a better way to do it?
>
>> +	dmcfreq->wait_dcf_flag = 1;
>> +
>> +	err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
>> +	if (err) {
>> +		dev_err(dev,
>> +			"Unable to set freq %lu. Current freq %lu. Error %d\n",
>> +			target_rate, old_clk_rate, err);
>> +		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>> +				      dmcfreq->volt);
>> +		dmc_event(DMCFREQ_FINISH);
> ditto.
>
>> +		goto out;
>> +	}
>> +
>> +	/*
>> +	 * wait until bcf irq happen, it means freq scaling finish in bl31,
>> +	 * use 100ms as timeout time
>> +	 */
>> +	wait_event_timeout(dmcfreq->wait_dcf_queue,
>> +			   !dmcfreq->wait_dcf_flag, HZ / 10);
>> +
>> +	dmc_event(DMCFREQ_FINISH);
> ditto.
>
>> +
>> +	/*
>> +	 * check the dpll rate
>> +	 * there only two result we will get,
>> +	 * 1. ddr frequency scaling fail, we still get the old rate
>> +	 * 2, ddr frequency scaling sucessful, we get the rate we set
>> +	 */
>> +	dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk);
>> +
>> +	/* if get the incorrect rate, set voltage to old value */
>> +	if (dmcfreq->rate != target_rate) {
>> +		dev_err(dev, "get wrong ddr frequency, Request freq %lu,\
>> +			Current freq %lu\n", target_rate, dmcfreq->rate);
>> +		regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>> +				      dmcfreq->volt);
>> +	} else if (old_clk_rate > target_rate)
>> +		err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>> +					    target_volt);
>> +	if (err)
>> +		dev_err(dev, "Unable to set vol %lu\n", target_volt);
>> +
>> +out:
>> +	mutex_unlock(&dmcfreq->lock);
>> +	return err;
>> +}
>> +
>> +static int rk3399_dmcfreq_get_dev_status(struct device *dev,
>> +					 struct devfreq_dev_status *stat)
>> +{
>> +	struct platform_device *pdev = container_of(dev, struct platform_device,
>> +						    dev);
>> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>> +	struct devfreq_event_data edata;
>> +
>> +	devfreq_event_get_event(dmcfreq->edev, &edata);
>> +
>> +	stat->current_frequency = dmcfreq->rate;
>> +	stat->busy_time = edata.load_count;
>> +	stat->total_time = edata.total_count;
>> +
>> +	return 0;
>> +}
>> +
>> +static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
>> +{
>> +	struct platform_device *pdev = container_of(dev, struct platform_device,
>> +						    dev);
>> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>> +
>> +	*freq = dmcfreq->rate;
>> +
>> +	return 0;
>> +}
>> +
>> +static void rk3399_dmcfreq_exit(struct device *dev)
>> +{
>> +	struct platform_device *pdev = container_of(dev, struct platform_device,
>> +						    dev);
>> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>> +
>> +	devfreq_unregister_opp_notifier(dev, dmcfreq->devfreq);
>> +}
>> +
>> +static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = {
>> +	.polling_ms	= 200,
>> +	.target		= rk3399_dmcfreq_target,
>> +	.get_dev_status	= rk3399_dmcfreq_get_dev_status,
>> +	.get_cur_freq	= rk3399_dmcfreq_get_cur_freq,
>> +	.exit		= rk3399_dmcfreq_exit,
>> +};
>> +
>> +static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev)
>> +{
>> +	rockchip_dmc_disable();
>> +	return 0;
>> +}
>> +
>> +static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
>> +{
>> +	rockchip_dmc_enable();
>> +	return 0;
>> +}
>> +
>> +static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
>> +			 rk3399_dmcfreq_resume);
>> +
>> +static int rk3399_dmc_enable_notify(struct notifier_block *nb,
>> +				    unsigned long event, void *data)
>> +{
>> +	struct rk3399_dmcfreq *dmcfreq =
>> +			      container_of(nb, struct rk3399_dmcfreq, dmc_nb);
>> +	unsigned long freq = ULONG_MAX;
>> +
>> +	if (event == DMC_ENABLE) {
>> +		devfreq_event_enable_edev(dmcfreq->edev);
>> +		devfreq_resume_device(dmcfreq->devfreq);
>> +		return NOTIFY_OK;
>> +	} else if (event == DMC_DISABLE) {
>> +		devfreq_event_disable_edev(dmcfreq->edev);
>> +		devfreq_suspend_device(dmcfreq->devfreq);
>> +
>> +		/* when disable dmc, set sdram to max frequency */
>> +		rk3399_dmcfreq_target(dmcfreq->dev, &freq, 0);
>> +		return NOTIFY_OK;
>> +	}
>> +
>> +	return NOTIFY_DONE;
>> +}
>> +
>> +static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
>> +{
>> +	struct rk3399_dmcfreq *dmcfreq = dev_id;
>> +
>> +	dmcfreq->wait_dcf_flag = 0;
>> +	wake_up(&dmcfreq->wait_dcf_queue);
>> +
>> +	/* clr dcf irq */
>> +	sip_smc_clr_ddr_irq();
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int of_do_get_timing(struct device_node *np,
>> +		struct dram_timing *timing)
>> +{
>> +	int ret;
>> +
>> +	ret = of_property_read_u32(np, "ddr3_speed_bin",
>> +				   &timing->ddr3_speed_bin);
>> +	ret |= of_property_read_u32(np, "pd_idle", &timing->pd_idle);
>> +	ret |= of_property_read_u32(np, "sr_idle", &timing->sr_idle);
>> +	ret |= of_property_read_u32(np, "sr_mc_gate_idle",
>> +				    &timing->sr_mc_gate_idle);
>> +	ret |= of_property_read_u32(np, "srpd_lite_idle",
>> +				    &timing->srpd_lite_idle);
>> +	ret |= of_property_read_u32(np, "standby_idle", &timing->standby_idle);
>> +	ret |= of_property_read_u32(np, "dram_dll_dis_freq",
>> +				    &timing->dram_dll_dis_freq);
>> +	ret |= of_property_read_u32(np, "phy_dll_dis_freq",
>> +				    &timing->phy_dll_dis_freq);
>> +	ret |= of_property_read_u32(np, "ddr3_odt_dis_freq",
>> +				    &timing->ddr3_odt_dis_freq);
>> +	ret |= of_property_read_u32(np, "ddr3_drv", &timing->ddr3_drv);
>> +	ret |= of_property_read_u32(np, "ddr3_odt", &timing->ddr3_odt);
>> +	ret |= of_property_read_u32(np, "phy_ddr3_ca_drv",
>> +				    &timing->phy_ddr3_ca_drv);
>> +	ret |= of_property_read_u32(np, "phy_ddr3_dq_drv",
>> +				    &timing->phy_ddr3_dq_drv);
>> +	ret |= of_property_read_u32(np, "phy_ddr3_odt", &timing->phy_ddr3_odt);
>> +	ret |= of_property_read_u32(np, "lpddr3_odt_dis_freq",
>> +				    &timing->lpddr3_odt_dis_freq);
>> +	ret |= of_property_read_u32(np, "lpddr3_drv", &timing->lpddr3_drv);
>> +	ret |= of_property_read_u32(np, "lpddr3_odt", &timing->lpddr3_odt);
>> +	ret |= of_property_read_u32(np, "phy_lpddr3_ca_drv",
>> +				    &timing->phy_lpddr3_ca_drv);
>> +	ret |= of_property_read_u32(np, "phy_lpddr3_dq_drv",
>> +				    &timing->phy_lpddr3_dq_drv);
>> +	ret |= of_property_read_u32(np, "phy_lpddr3_odt",
>> +				    &timing->phy_lpddr3_odt);
>> +	ret |= of_property_read_u32(np, "lpddr4_odt_dis_freq",
>> +				    &timing->lpddr4_odt_dis_freq);
>> +	ret |= of_property_read_u32(np, "lpddr4_drv",
>> +				    &timing->lpddr4_drv);
>> +	ret |= of_property_read_u32(np, "lpddr4_dq_odt",
>> +				    &timing->lpddr4_dq_odt);
>> +	ret |= of_property_read_u32(np, "lpddr4_ca_odt",
>> +				    &timing->lpddr4_ca_odt);
>> +	ret |= of_property_read_u32(np, "phy_lpddr4_ca_drv",
>> +				    &timing->phy_lpddr4_ca_drv);
>> +	ret |= of_property_read_u32(np, "phy_lpddr4_ck_cs_drv",
>> +				    &timing->phy_lpddr4_ck_cs_drv);
>> +	ret |= of_property_read_u32(np, "phy_lpddr4_dq_drv",
>> +				    &timing->phy_lpddr4_dq_drv);
>> +	ret |= of_property_read_u32(np, "phy_lpddr4_odt",
>> +				    &timing->phy_lpddr4_odt);
>> +
>> +	return ret;
>> +}
>> +
>> +static struct dram_timing *of_get_ddr_timings(struct device *dev,
>> +					      struct device_node *np)
>> +{
>> +	struct dram_timing	*timing = NULL;
>> +	struct device_node	*np_tim;
>> +
>> +	np_tim = of_parse_phandle(np, "ddr_timing", 0);
>> +	if (np_tim) {
>> +		timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
>> +		if (!timing)
>> +			goto err;
>> +		if (of_do_get_timing(np_tim, timing)) {
>> +			devm_kfree(dev, timing);
>> +			goto err;
>> +		}
>> +		return timing;
>> +	}
>> +
>> +err:
>> +	if (timing) {
>> +		devm_kfree(dev, timing);
>> +		timing = NULL;
>> +	}
>> +
>> +	return timing;
>> +}
>> +
>> +static int rk3399_dmcfreq_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct rk3399_dmcfreq *data;
>> +	int ret, irq, index;
>> +	uint64_t param = 0;
>> +	uint32_t *timing;
>> +	struct device_node *np = pdev->dev.of_node;
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	if (irq < 0) {
>> +		dev_err(&pdev->dev, "no dmc irq resource\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
>> +	if (!data)
>> +		return -ENOMEM;
>> +
>> +	mutex_init(&data->lock);
>> +
>> +	data->vdd_center = devm_regulator_get(dev, "center");
>> +	if (IS_ERR(data->vdd_center)) {
>> +		dev_err(dev, "Cannot get the regulator \"center\"\n");
>> +		return PTR_ERR(data->vdd_center);
>> +	}
>> +
>> +	data->dmc_clk = devm_clk_get(dev, "dmc_clk");
>> +	if (IS_ERR(data->dmc_clk)) {
>> +		dev_err(dev, "Cannot get the clk dmc_clk\n");
>> +		return PTR_ERR(data->dmc_clk);
>> +	};
>> +
>> +	data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
>> +	if (IS_ERR(data->edev))
>> +		return -EPROBE_DEFER;
>> +
>> +	ret = devfreq_event_enable_edev(data->edev);
>> +	if (ret < 0) {
>> +		dev_err(dev, "failed to enable devfreq-event devices\n");
>> +		return ret;
>> +	}
>> +
>> +	/*
>> +	 * We add a devfreq driver to our parent since it has a device tree node
>> +	 * with operating points.
>> +	 */
>> +	if (dev_pm_opp_of_add_table(dev)) {
>> +		dev_err(dev, "Invalid operating-points in device tree.\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	of_property_read_u32(np, "upthreshold",
>> +			     &data->ondemand_data.upthreshold);
>> +
>> +	of_property_read_u32(np, "downdifferential",
>> +			     &data->ondemand_data.downdifferential);
>> +
>> +	data->rate = clk_get_rate(data->dmc_clk);
>> +	rk3399_devfreq_dmc_profile.initial_freq = data->rate;
>> +	data->devfreq = devfreq_add_device(dev,
>> +					   &rk3399_devfreq_dmc_profile,
>> +					   "simple_ondemand",
>> +					   &data->ondemand_data);
>> +	if (IS_ERR(data->devfreq))
>> +		return PTR_ERR(data->devfreq);
>> +
>> +	devfreq_register_opp_notifier(dev, data->devfreq);
>> +
>> +	data->dmc_nb.notifier_call = rk3399_dmc_enable_notify;
>> +	dmc_register_notifier(&data->dmc_nb);
>> +	data->irq = irq;
>> +	ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
>> +			       dev_name(dev), data);
>> +	if (ret) {
>> +		dev_err(dev, "failed to request dmc irq: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	/* get dram timing and pass it to bl31 */
>> +	data->timing = of_get_ddr_timings(dev, np);
>> +	if (data->timing) {
>> +		timing = (uint32_t *)data->timing;
>> +		for (index = 0; index < (sizeof(struct dram_timing) / 4);
>> +		     index++) {
>> +			param = index;
>> +			param = param << 32 | *timing++;
>> +			sip_smc_set_ddr_param(param);
>> +			param = 0;
>> +		}
>> +	}
>> +	sip_smc_ddr_init();
>> +
>> +	init_waitqueue_head(&data->wait_dcf_queue);
>> +	data->wait_dcf_flag = 0;
>> +
>> +	data->dev = dev;
>> +	platform_set_drvdata(pdev, data);
>> +
>> +	return 0;
>> +}
>> +
>> +static int rk3399_dmcfreq_remove(struct platform_device *pdev)
>> +{
>> +	struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>> +
>> +	devfreq_remove_device(dmcfreq->devfreq);
>> +	regulator_put(dmcfreq->vdd_center);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id rk3399dmc_devfreq_of_match[] = {
>> +	{ .compatible = "rockchip,rk3399-dmc" },
>> +	{ },
>> +};
>> +
>> +static struct platform_driver rk3399_dmcfreq_driver = {
>> +	.probe	= rk3399_dmcfreq_probe,
>> +	.remove	= rk3399_dmcfreq_remove,
>> +	.driver = {
>> +		.name	= "rk3399-dmc-freq",
>> +		.pm	= &rk3399_dmcfreq_pm,
>> +		.of_match_table = rk3399dmc_devfreq_of_match,
>> +	},
>> +};
>> +module_platform_driver(rk3399_dmcfreq_driver);
>> +
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework");
>> diff --git a/drivers/devfreq/rockchip/rockchip_dmc.c b/drivers/devfreq/rockchip/rockchip_dmc.c
>> new file mode 100644
>> index 0000000..78baee5
>> --- /dev/null
>> +++ b/drivers/devfreq/rockchip/rockchip_dmc.c
>> @@ -0,0 +1,143 @@
>> +/*
>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>> + * Author: Lin Huang <hl@rock-chips.com>
>> + * Base on: https://chromium-review.googlesource.com/#/c/231477/
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + */
>> +
>> +#include <linux/mutex.h>
>> +#include <soc/rockchip/rockchip_dmc.h>
>> +
>> +static int num_wait;
>> +static int num_disable;
>> +static BLOCKING_NOTIFIER_HEAD(dmc_notifier_list);
>> +static DEFINE_MUTEX(dmc_en_lock);
>> +static DEFINE_MUTEX(dmc_sync_lock);
>> +
>> +/**
>> + * rockchip_dmc_enabled - Returns true if dmc freq is enabled, false otherwise.
>> + */
>> +bool rockchip_dmc_enabled(void)
>> +{
>> +	return num_disable <= 0 && num_wait <= 1;
>> +}
>> +
>> +/**
>> + * rockchip_dmc_enable - Enable dmc frequency scaling. Will only enable
>> + * frequency scaling if there are 1 or fewer notifiers. Call to undo
>> + * rockchip_dmc_disable.
>> + */
>> +void rockchip_dmc_enable(void)
>> +{
>> +	mutex_lock(&dmc_en_lock);
>> +	num_disable--;
>> +	WARN_ON(num_disable < 0);
>> +	if (rockchip_dmc_enabled())
>> +		dmc_event(DMC_ENABLE);
>> +	mutex_unlock(&dmc_en_lock);
>> +}
>> +
>> +/**
>> + * rockchip_dmc_disable - Disable dmc frequency scaling. Call when something
>> + * cannot coincide with dmc frequency scaling.
>> + */
>> +void rockchip_dmc_disable(void)
>> +{
>> +	mutex_lock(&dmc_en_lock);
>> +	if (rockchip_dmc_enabled())
>> +		dmc_event(DMC_DISABLE);
>> +	num_disable++;
>> +	mutex_unlock(&dmc_en_lock);
>> +}
>> +
>> +void dmc_event(int event)
>> +{
>> +	mutex_lock(&dmc_sync_lock);
>> +	blocking_notifier_call_chain(&dmc_notifier_list, event, NULL);
>> +	mutex_unlock(&dmc_sync_lock);
>> +}
>> +
>> +/**
>> + * dmc_register_notifier - register a driver to dmc chain
>> + * @nb: notifier function to register
>> + */
>> +int dmc_register_notifier(struct notifier_block *nb)
>> +{
>> +	int ret;
>> +
>> +	if (!nb)
>> +		return -EINVAL;
>> +
>> +	ret = blocking_notifier_chain_register(&dmc_notifier_list, nb);
>> +
>> +	return ret;
>> +}
>> +
>> +/**
>> + * dmc_unregister_notifier - unregister a driver from dmc chain
>> + * @nb: remove notifier function
>> + */
>> +int dmc_unregister_notifier(struct notifier_block *nb)
>> +{
>> +	int ret;
>> +
>> +	if (!nb)
>> +		return -EINVAL;
>> +
>> +	ret = blocking_notifier_chain_unregister(&dmc_notifier_list, nb);
>> +
>> +	return ret;
>> +}
>> +
>> +/**
>> + * rockchip_dmc_get - Register the notifier block for the dmc chain.
>> + * @nb The dmc notifier block to register
>> + */
>> +int rockchip_dmc_get(struct notifier_block *nb)
>> +{
>> +	if (!nb)
>> +		return -EINVAL;
>> +
>> +	mutex_lock(&dmc_en_lock);
>> +
>> +	/*
>> +	 * if have two notifier(enable two vop etc),
>> +	 * need to disable dmc
>> +	 */
>> +	if (num_wait == 1 && num_disable <= 0)
>> +		dmc_event(DMC_DISABLE);
>> +	num_wait++;
>> +	dmc_register_notifier(nb);
>> +	mutex_unlock(&dmc_en_lock);
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * rockchip_dmc_put - Remove the notifier block from the dmc chain.
>> + * @nb The dmc notifier block to unregister
>> + */
>> +int rockchip_dmc_put(struct notifier_block *nb)
>> +{
>> +	if (!nb)
>> +		return -EINVAL;
>> +
>> +	mutex_lock(&dmc_en_lock);
>> +	num_wait--;
>> +
>> +	/* if notifier from 2 back to 1, enable dmc again */
>> +	if (num_wait == 1 && num_disable <= 0)
>> +		dmc_event(DMC_ENABLE);
>> +	dmc_unregister_notifier(nb);
>> +	mutex_unlock(&dmc_en_lock);
>> +
>> +	return 0;
>> +}
>> diff --git a/include/soc/rockchip/rockchip_dmc.h b/include/soc/rockchip/rockchip_dmc.h
>> new file mode 100644
>> index 0000000..3f69cbf
>> --- /dev/null
>> +++ b/include/soc/rockchip/rockchip_dmc.h
>> @@ -0,0 +1,45 @@
>> +/*
>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> + */
>> +
>> +#ifndef __SOC_ROCKCHIP_DMC_H
>> +#define __SOC_ROCKCHIP_DMC_H
>> +
>> +#include <linux/notifier.h>
>> +
>> +#define DMC_ENABLE	0
>> +#define DMC_DISABLE	1
>> +#define DMCFREQ_ADJUST	2
>> +#define DMCFREQ_FINISH	3
>> +
>> +#if IS_ENABLED(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)
>> +int rockchip_dmc_get(struct notifier_block *nb);
>> +int rockchip_dmc_put(struct notifier_block *nb);
>> +#else
>> +static inline int rockchip_dmc_get(struct notifier_block *nb)
>> +{
>> +	return 0;
>> +}
>> +
>> +static inline int rockchip_dmc_put(struct notifier_block *nb)
>> +{
>> +	return 0;
>> +}
>> +#endif
>> +
>> +void dmc_event(int event);
>> +int dmc_register_notifier(struct notifier_block *nb);
>> +int dmc_unregister_notifier(struct notifier_block *nb);
>> +void rockchip_dmc_enable(void);
>> +void rockchip_dmc_disable(void);
>> +bool rockchip_dmc_enabled(void);
>> +#endif
>>
> Regards,
> Chanwoo Choi
>
>
> _______________________________________________
> Linux-rockchip mailing list
> Linux-rockchip at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-rockchip

-- 
Lin Huang

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-25  8:47     ` hl
@ 2016-07-25  9:45       ` Chanwoo Choi
  2016-07-26  1:15         ` hl
  0 siblings, 1 reply; 24+ messages in thread
From: Chanwoo Choi @ 2016-07-25  9:45 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Lin,

On 2016? 07? 25? 17:47, hl wrote:
> Hi Chanwoo Choi,
> 
> On 2016?07?25? 14:01, Chanwoo Choi wrote:
>> Hi Lin,
>>
>> I'm glad to support the for dmc ddr clock scaling with devfreq/devfreq-event.
>>
>> But, I think that you have to use the standard interface.
>> As I already mentioned[1] on previous mail, devfreq fwk support
>> the standard DEVFREQ_TRANSITION_NOTIFIER notifier which has the two notifications.
>> - DEVFREQ_PRECHANGE
>> - DEVFREQ_POSTCHANGE
>> [1] https://patchwork.kernel.org/patch/9194305/
>>
>> You can use the DEVFREQ_PRECHANGE instead of DMCFREQ_ADJUST
>> and use the DEVFREQ_POSTCHANGE instead of DMCFREQ_FINISH.
>>
>> On 2016? 07? 22? 18:07, Lin Huang wrote:
>>> base on dfi result, we do ddr frequency scaling, register
>>> dmc driver to devfreq framework, and use simple-ondemand
>>> policy.
>>>
>>> Signed-off-by: Lin Huang <hl@rock-chips.com>
>>> ---
>>> Changes in v3:
>>> - operate dram setting through sip call
>>> - imporve set rate flow
>>>
>>> Changes in v2:
>>> - None
>>>   Changes in v1:
>>> - move dfi controller to event
>>> - fix set voltage sequence when set rate fail
>>> - change Kconfig type from tristate to bool
>>> - move unuse EXPORT_SYMBOL_GPL()
>>>
>>>   drivers/devfreq/Kconfig                 |   1 +
>>>   drivers/devfreq/Makefile                |   1 +
>>>   drivers/devfreq/rockchip/Kconfig        |  15 +
>>>   drivers/devfreq/rockchip/Makefile       |   2 +
>>>   drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>>>   drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>>>   include/soc/rockchip/rockchip_dmc.h     |  45 +++
>>>   7 files changed, 689 insertions(+)
>>>   create mode 100644 drivers/devfreq/rockchip/Kconfig
>>>   create mode 100644 drivers/devfreq/rockchip/Makefile
>>>   create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>>>   create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>>>   create mode 100644 include/soc/rockchip/rockchip_dmc.h
>>>
>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>> index a5be56e..cb67246 100644
>>> --- a/drivers/devfreq/Kconfig
>>> +++ b/drivers/devfreq/Kconfig
>>> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>>>            operating frequencies and voltages with OPP support.
>>>     source "drivers/devfreq/event/Kconfig"
>>> +source "drivers/devfreq/rockchip/Kconfig"
>>>     endif # PM_DEVFREQ
>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>> index 09f11d9..48e2ae6 100644
>>> --- a/drivers/devfreq/Makefile
>>> +++ b/drivers/devfreq/Makefile
>>> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
>>>   # DEVFREQ Drivers
>>>   obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)    += exynos-bus.o
>>>   obj-$(CONFIG_ARM_TEGRA_DEVFREQ)        += tegra-devfreq.o
>>> +obj-$(CONFIG_ARCH_ROCKCHIP)        += rockchip/
>>>     # DEVFREQ Event Drivers
>>>   obj-$(CONFIG_PM_DEVFREQ_EVENT)        += event/
>>> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
>>> new file mode 100644
>>> index 0000000..7fb1cff
>>> --- /dev/null
>>> +++ b/drivers/devfreq/rockchip/Kconfig
>>> @@ -0,0 +1,15 @@
>>> +config ARM_ROCKCHIP_DMC_DEVFREQ
>>> +    bool "ARM ROCKCHIP DMC DEVFREQ Driver"
>>> +    depends on ARCH_ROCKCHIP
>>> +    help
>>> +      This adds the DEVFREQ driver framework for the rockchip dmc.
>>> +
>>> +config ARM_RK3399_DMC_DEVFREQ
>>> +    bool "ARM RK3399 DMC DEVFREQ Driver"
>>> +    depends on ARM_ROCKCHIP_DMC_DEVFREQ
>>> +    select PM_OPP
>>> +    select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>> +    help
>>> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
>>> +          for the memory controller and reads the usage counts from hardware.
>>> +
>>> diff --git a/drivers/devfreq/rockchip/Makefile b/drivers/devfreq/rockchip/Makefile
>>> new file mode 100644
>>> index 0000000..caca525
>>> --- /dev/null
>>> +++ b/drivers/devfreq/rockchip/Makefile
>>> @@ -0,0 +1,2 @@
>>> +obj-$(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)    += rockchip_dmc.o
>>> +obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)    += rk3399_dmc.o
>>> diff --git a/drivers/devfreq/rockchip/rk3399_dmc.c b/drivers/devfreq/rockchip/rk3399_dmc.c
>>> new file mode 100644
>>> index 0000000..f1d6120
>>> --- /dev/null
>>> +++ b/drivers/devfreq/rockchip/rk3399_dmc.c
>>> @@ -0,0 +1,482 @@
>>> +/*
>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>> + * Author: Lin Huang <hl@rock-chips.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify it
>>> + * under the terms and conditions of the GNU General Public License,
>>> + * version 2, as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>> + * more details.
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/devfreq.h>
>>> +#include <linux/devfreq-event.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_opp.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/rwsem.h>
>>> +#include <linux/suspend.h>
>>> +#include <linux/syscore_ops.h>
>>> +
>>> +#include <soc/rockchip/rockchip_dmc.h>
>>> +#include "../../firmware/rockchip_sip.h"
>>> +
>>> +struct dram_timing {
>>> +    unsigned int ddr3_speed_bin;
>>> +    unsigned int pd_idle;
>>> +    unsigned int sr_idle;
>>> +    unsigned int sr_mc_gate_idle;
>>> +    unsigned int srpd_lite_idle;
>>> +    unsigned int standby_idle;
>>> +    unsigned int dram_dll_dis_freq;
>>> +    unsigned int phy_dll_dis_freq;
>>> +    unsigned int ddr3_odt_dis_freq;
>>> +    unsigned int ddr3_drv;
>>> +    unsigned int ddr3_odt;
>>> +    unsigned int phy_ddr3_ca_drv;
>>> +    unsigned int phy_ddr3_dq_drv;
>>> +    unsigned int phy_ddr3_odt;
>>> +    unsigned int lpddr3_odt_dis_freq;
>>> +    unsigned int lpddr3_drv;
>>> +    unsigned int lpddr3_odt;
>>> +    unsigned int phy_lpddr3_ca_drv;
>>> +    unsigned int phy_lpddr3_dq_drv;
>>> +    unsigned int phy_lpddr3_odt;
>>> +    unsigned int lpddr4_odt_dis_freq;
>>> +    unsigned int lpddr4_drv;
>>> +    unsigned int lpddr4_dq_odt;
>>> +    unsigned int lpddr4_ca_odt;
>>> +    unsigned int phy_lpddr4_ca_drv;
>>> +    unsigned int phy_lpddr4_ck_cs_drv;
>>> +    unsigned int phy_lpddr4_dq_drv;
>>> +    unsigned int phy_lpddr4_odt;
>>> +};
>>> +
>>> +struct rk3399_dmcfreq {
>>> +    struct device *dev;
>>> +    struct devfreq *devfreq;
>>> +    struct devfreq_simple_ondemand_data ondemand_data;
>>> +    struct clk *dmc_clk;
>>> +    struct devfreq_event_dev *edev;
>>> +    struct mutex lock;
>>> +    struct notifier_block dmc_nb;
>>> +    struct dram_timing *timing;
>>> +    wait_queue_head_t    wait_dcf_queue;
>>> +    int irq;
>>> +    int wait_dcf_flag;
>>> +    struct regulator *vdd_center;
>>> +    unsigned long rate, target_rate;
>>> +    unsigned long volt, target_volt;
>>> +};
>>> +
>>> +static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
>>> +                 u32 flags)
>>> +{
>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>> +                            dev);
>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>> +    struct dev_pm_opp *opp;
>>> +    unsigned long old_clk_rate = dmcfreq->rate;
>>> +    unsigned long target_volt, target_rate;
>>> +    int err;
>>> +
>>> +    rcu_read_lock();
>>> +    opp = devfreq_recommended_opp(dev, freq, flags);
>>> +    if (IS_ERR(opp)) {
>>> +        rcu_read_unlock();
>>> +        return PTR_ERR(opp);
>>> +    }
>>> +    target_rate = dev_pm_opp_get_freq(opp);
>>> +    target_volt = dev_pm_opp_get_voltage(opp);
>>> +    opp = devfreq_recommended_opp(dev, &dmcfreq->rate, flags);
>>> +    if (IS_ERR(opp)) {
>>> +        rcu_read_unlock();
>>> +        return PTR_ERR(opp);
>>> +    }
>>> +    dmcfreq->volt = dev_pm_opp_get_voltage(opp);
>>> +    rcu_read_unlock();
>>> +
>>> +    if (dmcfreq->rate == target_rate)
>>> +        return 0;
>>> +
>>> +    mutex_lock(&dmcfreq->lock);
>>> +    /*
>>> +     * if frequency scaling from low to high, adjust voltage first;
>>> +     * if frequency scaling from high to low, adjuset frequency first;
>>> +     */
>>> +    if (old_clk_rate < target_rate) {
>>> +        err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>>> +                        target_volt);
>>> +        if (err) {
>>> +            dev_err(dev, "Unable to set vol %lu\n", target_volt);
>>> +            goto out;
>>> +        }
>>> +    }
>>> +
>>> +    dmc_event(DMCFREQ_ADJUST);
>> You don't need to send the notification in target function.
>> When you use the DEVFREQ_TRANSITION_NOTIFIER notifier,
>> devfreq send the notification before calling the target() and after calling the target().
> I checked DEVFREQ_TRANSISTION_NOTIFIER notifier before, but i have some question:
> Now i want to use
>     DEVFREQ_PRECHANGE and DEVFREQ_POSTCHANGE in vop driver, but it hard to get the *devfreq
> in vop driver, or i can register a DEVFREQ_TRANSITION_NOTIFIER notifier in rk3399_dmc.c and
> when get message, post DEVFREQ_PRECHANGE or DEVFREQ_POSTCHANGE to vop driver through dmc_event(),
> do you have a better way to do it?

Firstly, 
The devfreq has the devfreq_get_devfreq_by_phandle() function
to get the devfreq pointer in device tree. The vop_bind()
get the IORESOURCE_MEM and interrupt. So, I think that you
would get the devfreq pointer in the vop_bind() function.
So, DEVFREQ_TRANSITION_NOTIFIER can alter the following two notifications:
- dmc_event(DMCFREQ_ADJUST);
- dmc_event(DMCFREQ_FINISH);


Also,
devfreq-event fwk provide the devfreq_event_get_edev_by_phandle() funtcion
to get the devfreq-event pointer in the device tree. 

You could call the devfreq_event_[enable/disable[_edev()
and devfreq_[resume/suspend]_device in the drivers/gpu/drm/rockchip/rockchip_drm_vop.c
instead of dmc_event(DMC_ENABLE) / dmc_event(DMC_DISABLE).

dmc_event(DMC_ENABLE);
	devfreq_event_enable_edev(dmcfreq->edev);
	devfreq_resume_device(dmcfreq->devfreq);

dmc_event(DMC_DISABLE);
	devfreq_event_disable_edev(dmcfreq->edev);
	devfreq_suspend_device(dmcfreq->devfreq);

I think that we have to use the standard interfaces and
functions providing by framework. It is not appropriate way
to define the specific functions for only one device driver
between other framework.

Regards,
Chanwoo Choi

>>
>>> +    dmcfreq->wait_dcf_flag = 1;
>>> +
>>> +    err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
>>> +    if (err) {
>>> +        dev_err(dev,
>>> +            "Unable to set freq %lu. Current freq %lu. Error %d\n",
>>> +            target_rate, old_clk_rate, err);
>>> +        regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>>> +                      dmcfreq->volt);
>>> +        dmc_event(DMCFREQ_FINISH);
>> ditto.
>>
>>> +        goto out;
>>> +    }
>>> +
>>> +    /*
>>> +     * wait until bcf irq happen, it means freq scaling finish in bl31,
>>> +     * use 100ms as timeout time
>>> +     */
>>> +    wait_event_timeout(dmcfreq->wait_dcf_queue,
>>> +               !dmcfreq->wait_dcf_flag, HZ / 10);
>>> +
>>> +    dmc_event(DMCFREQ_FINISH);
>> ditto.
>>
>>> +
>>> +    /*
>>> +     * check the dpll rate
>>> +     * there only two result we will get,
>>> +     * 1. ddr frequency scaling fail, we still get the old rate
>>> +     * 2, ddr frequency scaling sucessful, we get the rate we set
>>> +     */
>>> +    dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk);
>>> +
>>> +    /* if get the incorrect rate, set voltage to old value */
>>> +    if (dmcfreq->rate != target_rate) {
>>> +        dev_err(dev, "get wrong ddr frequency, Request freq %lu,\
>>> +            Current freq %lu\n", target_rate, dmcfreq->rate);
>>> +        regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>>> +                      dmcfreq->volt);
>>> +    } else if (old_clk_rate > target_rate)
>>> +        err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>>> +                        target_volt);
>>> +    if (err)
>>> +        dev_err(dev, "Unable to set vol %lu\n", target_volt);
>>> +
>>> +out:
>>> +    mutex_unlock(&dmcfreq->lock);
>>> +    return err;
>>> +}
>>> +
>>> +static int rk3399_dmcfreq_get_dev_status(struct device *dev,
>>> +                     struct devfreq_dev_status *stat)
>>> +{
>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>> +                            dev);
>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>> +    struct devfreq_event_data edata;
>>> +
>>> +    devfreq_event_get_event(dmcfreq->edev, &edata);
>>> +
>>> +    stat->current_frequency = dmcfreq->rate;
>>> +    stat->busy_time = edata.load_count;
>>> +    stat->total_time = edata.total_count;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
>>> +{
>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>> +                            dev);
>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>> +
>>> +    *freq = dmcfreq->rate;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void rk3399_dmcfreq_exit(struct device *dev)
>>> +{
>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>> +                            dev);
>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>> +
>>> +    devfreq_unregister_opp_notifier(dev, dmcfreq->devfreq);
>>> +}
>>> +
>>> +static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = {
>>> +    .polling_ms    = 200,
>>> +    .target        = rk3399_dmcfreq_target,
>>> +    .get_dev_status    = rk3399_dmcfreq_get_dev_status,
>>> +    .get_cur_freq    = rk3399_dmcfreq_get_cur_freq,
>>> +    .exit        = rk3399_dmcfreq_exit,
>>> +};
>>> +
>>> +static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev)
>>> +{
>>> +    rockchip_dmc_disable();
>>> +    return 0;
>>> +}
>>> +
>>> +static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
>>> +{
>>> +    rockchip_dmc_enable();
>>> +    return 0;
>>> +}
>>> +
>>> +static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
>>> +             rk3399_dmcfreq_resume);
>>> +
>>> +static int rk3399_dmc_enable_notify(struct notifier_block *nb,
>>> +                    unsigned long event, void *data)
>>> +{
>>> +    struct rk3399_dmcfreq *dmcfreq =
>>> +                  container_of(nb, struct rk3399_dmcfreq, dmc_nb);
>>> +    unsigned long freq = ULONG_MAX;
>>> +
>>> +    if (event == DMC_ENABLE) {
>>> +        devfreq_event_enable_edev(dmcfreq->edev);
>>> +        devfreq_resume_device(dmcfreq->devfreq);
>>> +        return NOTIFY_OK;
>>> +    } else if (event == DMC_DISABLE) {
>>> +        devfreq_event_disable_edev(dmcfreq->edev);
>>> +        devfreq_suspend_device(dmcfreq->devfreq);
>>> +
>>> +        /* when disable dmc, set sdram to max frequency */
>>> +        rk3399_dmcfreq_target(dmcfreq->dev, &freq, 0);
>>> +        return NOTIFY_OK;
>>> +    }
>>> +
>>> +    return NOTIFY_DONE;
>>> +}
>>> +
>>> +static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
>>> +{
>>> +    struct rk3399_dmcfreq *dmcfreq = dev_id;
>>> +
>>> +    dmcfreq->wait_dcf_flag = 0;
>>> +    wake_up(&dmcfreq->wait_dcf_queue);
>>> +
>>> +    /* clr dcf irq */
>>> +    sip_smc_clr_ddr_irq();
>>> +
>>> +    return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int of_do_get_timing(struct device_node *np,
>>> +        struct dram_timing *timing)
>>> +{
>>> +    int ret;
>>> +
>>> +    ret = of_property_read_u32(np, "ddr3_speed_bin",
>>> +                   &timing->ddr3_speed_bin);
>>> +    ret |= of_property_read_u32(np, "pd_idle", &timing->pd_idle);
>>> +    ret |= of_property_read_u32(np, "sr_idle", &timing->sr_idle);
>>> +    ret |= of_property_read_u32(np, "sr_mc_gate_idle",
>>> +                    &timing->sr_mc_gate_idle);
>>> +    ret |= of_property_read_u32(np, "srpd_lite_idle",
>>> +                    &timing->srpd_lite_idle);
>>> +    ret |= of_property_read_u32(np, "standby_idle", &timing->standby_idle);
>>> +    ret |= of_property_read_u32(np, "dram_dll_dis_freq",
>>> +                    &timing->dram_dll_dis_freq);
>>> +    ret |= of_property_read_u32(np, "phy_dll_dis_freq",
>>> +                    &timing->phy_dll_dis_freq);
>>> +    ret |= of_property_read_u32(np, "ddr3_odt_dis_freq",
>>> +                    &timing->ddr3_odt_dis_freq);
>>> +    ret |= of_property_read_u32(np, "ddr3_drv", &timing->ddr3_drv);
>>> +    ret |= of_property_read_u32(np, "ddr3_odt", &timing->ddr3_odt);
>>> +    ret |= of_property_read_u32(np, "phy_ddr3_ca_drv",
>>> +                    &timing->phy_ddr3_ca_drv);
>>> +    ret |= of_property_read_u32(np, "phy_ddr3_dq_drv",
>>> +                    &timing->phy_ddr3_dq_drv);
>>> +    ret |= of_property_read_u32(np, "phy_ddr3_odt", &timing->phy_ddr3_odt);
>>> +    ret |= of_property_read_u32(np, "lpddr3_odt_dis_freq",
>>> +                    &timing->lpddr3_odt_dis_freq);
>>> +    ret |= of_property_read_u32(np, "lpddr3_drv", &timing->lpddr3_drv);
>>> +    ret |= of_property_read_u32(np, "lpddr3_odt", &timing->lpddr3_odt);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_ca_drv",
>>> +                    &timing->phy_lpddr3_ca_drv);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_dq_drv",
>>> +                    &timing->phy_lpddr3_dq_drv);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_odt",
>>> +                    &timing->phy_lpddr3_odt);
>>> +    ret |= of_property_read_u32(np, "lpddr4_odt_dis_freq",
>>> +                    &timing->lpddr4_odt_dis_freq);
>>> +    ret |= of_property_read_u32(np, "lpddr4_drv",
>>> +                    &timing->lpddr4_drv);
>>> +    ret |= of_property_read_u32(np, "lpddr4_dq_odt",
>>> +                    &timing->lpddr4_dq_odt);
>>> +    ret |= of_property_read_u32(np, "lpddr4_ca_odt",
>>> +                    &timing->lpddr4_ca_odt);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_ca_drv",
>>> +                    &timing->phy_lpddr4_ca_drv);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_ck_cs_drv",
>>> +                    &timing->phy_lpddr4_ck_cs_drv);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_dq_drv",
>>> +                    &timing->phy_lpddr4_dq_drv);
>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_odt",
>>> +                    &timing->phy_lpddr4_odt);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static struct dram_timing *of_get_ddr_timings(struct device *dev,
>>> +                          struct device_node *np)
>>> +{
>>> +    struct dram_timing    *timing = NULL;
>>> +    struct device_node    *np_tim;
>>> +
>>> +    np_tim = of_parse_phandle(np, "ddr_timing", 0);
>>> +    if (np_tim) {
>>> +        timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
>>> +        if (!timing)
>>> +            goto err;
>>> +        if (of_do_get_timing(np_tim, timing)) {
>>> +            devm_kfree(dev, timing);
>>> +            goto err;
>>> +        }
>>> +        return timing;
>>> +    }
>>> +
>>> +err:
>>> +    if (timing) {
>>> +        devm_kfree(dev, timing);
>>> +        timing = NULL;
>>> +    }
>>> +
>>> +    return timing;
>>> +}
>>> +
>>> +static int rk3399_dmcfreq_probe(struct platform_device *pdev)
>>> +{
>>> +    struct device *dev = &pdev->dev;
>>> +    struct rk3399_dmcfreq *data;
>>> +    int ret, irq, index;
>>> +    uint64_t param = 0;
>>> +    uint32_t *timing;
>>> +    struct device_node *np = pdev->dev.of_node;
>>> +
>>> +    irq = platform_get_irq(pdev, 0);
>>> +    if (irq < 0) {
>>> +        dev_err(&pdev->dev, "no dmc irq resource\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
>>> +    if (!data)
>>> +        return -ENOMEM;
>>> +
>>> +    mutex_init(&data->lock);
>>> +
>>> +    data->vdd_center = devm_regulator_get(dev, "center");
>>> +    if (IS_ERR(data->vdd_center)) {
>>> +        dev_err(dev, "Cannot get the regulator \"center\"\n");
>>> +        return PTR_ERR(data->vdd_center);
>>> +    }
>>> +
>>> +    data->dmc_clk = devm_clk_get(dev, "dmc_clk");
>>> +    if (IS_ERR(data->dmc_clk)) {
>>> +        dev_err(dev, "Cannot get the clk dmc_clk\n");
>>> +        return PTR_ERR(data->dmc_clk);
>>> +    };
>>> +
>>> +    data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
>>> +    if (IS_ERR(data->edev))
>>> +        return -EPROBE_DEFER;
>>> +
>>> +    ret = devfreq_event_enable_edev(data->edev);
>>> +    if (ret < 0) {
>>> +        dev_err(dev, "failed to enable devfreq-event devices\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    /*
>>> +     * We add a devfreq driver to our parent since it has a device tree node
>>> +     * with operating points.
>>> +     */
>>> +    if (dev_pm_opp_of_add_table(dev)) {
>>> +        dev_err(dev, "Invalid operating-points in device tree.\n");
>>> +        return -EINVAL;
>>> +    }
>>> +
>>> +    of_property_read_u32(np, "upthreshold",
>>> +                 &data->ondemand_data.upthreshold);
>>> +
>>> +    of_property_read_u32(np, "downdifferential",
>>> +                 &data->ondemand_data.downdifferential);
>>> +
>>> +    data->rate = clk_get_rate(data->dmc_clk);
>>> +    rk3399_devfreq_dmc_profile.initial_freq = data->rate;
>>> +    data->devfreq = devfreq_add_device(dev,
>>> +                       &rk3399_devfreq_dmc_profile,
>>> +                       "simple_ondemand",
>>> +                       &data->ondemand_data);
>>> +    if (IS_ERR(data->devfreq))
>>> +        return PTR_ERR(data->devfreq);
>>> +
>>> +    devfreq_register_opp_notifier(dev, data->devfreq);
>>> +
>>> +    data->dmc_nb.notifier_call = rk3399_dmc_enable_notify;
>>> +    dmc_register_notifier(&data->dmc_nb);
>>> +    data->irq = irq;
>>> +    ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
>>> +                   dev_name(dev), data);
>>> +    if (ret) {
>>> +        dev_err(dev, "failed to request dmc irq: %d\n", ret);
>>> +        return ret;
>>> +    }
>>> +
>>> +    /* get dram timing and pass it to bl31 */
>>> +    data->timing = of_get_ddr_timings(dev, np);
>>> +    if (data->timing) {
>>> +        timing = (uint32_t *)data->timing;
>>> +        for (index = 0; index < (sizeof(struct dram_timing) / 4);
>>> +             index++) {
>>> +            param = index;
>>> +            param = param << 32 | *timing++;
>>> +            sip_smc_set_ddr_param(param);
>>> +            param = 0;
>>> +        }
>>> +    }
>>> +    sip_smc_ddr_init();
>>> +
>>> +    init_waitqueue_head(&data->wait_dcf_queue);
>>> +    data->wait_dcf_flag = 0;
>>> +
>>> +    data->dev = dev;
>>> +    platform_set_drvdata(pdev, data);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int rk3399_dmcfreq_remove(struct platform_device *pdev)
>>> +{
>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>> +
>>> +    devfreq_remove_device(dmcfreq->devfreq);
>>> +    regulator_put(dmcfreq->vdd_center);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static const struct of_device_id rk3399dmc_devfreq_of_match[] = {
>>> +    { .compatible = "rockchip,rk3399-dmc" },
>>> +    { },
>>> +};
>>> +
>>> +static struct platform_driver rk3399_dmcfreq_driver = {
>>> +    .probe    = rk3399_dmcfreq_probe,
>>> +    .remove    = rk3399_dmcfreq_remove,
>>> +    .driver = {
>>> +        .name    = "rk3399-dmc-freq",
>>> +        .pm    = &rk3399_dmcfreq_pm,
>>> +        .of_match_table = rk3399dmc_devfreq_of_match,
>>> +    },
>>> +};
>>> +module_platform_driver(rk3399_dmcfreq_driver);
>>> +
>>> +MODULE_LICENSE("GPL v2");
>>> +MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework");
>>> diff --git a/drivers/devfreq/rockchip/rockchip_dmc.c b/drivers/devfreq/rockchip/rockchip_dmc.c
>>> new file mode 100644
>>> index 0000000..78baee5
>>> --- /dev/null
>>> +++ b/drivers/devfreq/rockchip/rockchip_dmc.c
>>> @@ -0,0 +1,143 @@
>>> +/*
>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>> + * Author: Lin Huang <hl@rock-chips.com>
>>> + * Base on: https://chromium-review.googlesource.com/#/c/231477/
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify it
>>> + * under the terms and conditions of the GNU General Public License,
>>> + * version 2, as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>> + * more details.
>>> + */
>>> +
>>> +#include <linux/mutex.h>
>>> +#include <soc/rockchip/rockchip_dmc.h>
>>> +
>>> +static int num_wait;
>>> +static int num_disable;
>>> +static BLOCKING_NOTIFIER_HEAD(dmc_notifier_list);
>>> +static DEFINE_MUTEX(dmc_en_lock);
>>> +static DEFINE_MUTEX(dmc_sync_lock);
>>> +
>>> +/**
>>> + * rockchip_dmc_enabled - Returns true if dmc freq is enabled, false otherwise.
>>> + */
>>> +bool rockchip_dmc_enabled(void)
>>> +{
>>> +    return num_disable <= 0 && num_wait <= 1;
>>> +}
>>> +
>>> +/**
>>> + * rockchip_dmc_enable - Enable dmc frequency scaling. Will only enable
>>> + * frequency scaling if there are 1 or fewer notifiers. Call to undo
>>> + * rockchip_dmc_disable.
>>> + */
>>> +void rockchip_dmc_enable(void)
>>> +{
>>> +    mutex_lock(&dmc_en_lock);
>>> +    num_disable--;
>>> +    WARN_ON(num_disable < 0);
>>> +    if (rockchip_dmc_enabled())
>>> +        dmc_event(DMC_ENABLE);
>>> +    mutex_unlock(&dmc_en_lock);
>>> +}
>>> +
>>> +/**
>>> + * rockchip_dmc_disable - Disable dmc frequency scaling. Call when something
>>> + * cannot coincide with dmc frequency scaling.
>>> + */
>>> +void rockchip_dmc_disable(void)
>>> +{
>>> +    mutex_lock(&dmc_en_lock);
>>> +    if (rockchip_dmc_enabled())
>>> +        dmc_event(DMC_DISABLE);
>>> +    num_disable++;
>>> +    mutex_unlock(&dmc_en_lock);
>>> +}
>>> +
>>> +void dmc_event(int event)
>>> +{
>>> +    mutex_lock(&dmc_sync_lock);
>>> +    blocking_notifier_call_chain(&dmc_notifier_list, event, NULL);
>>> +    mutex_unlock(&dmc_sync_lock);
>>> +}
>>> +
>>> +/**
>>> + * dmc_register_notifier - register a driver to dmc chain
>>> + * @nb: notifier function to register
>>> + */
>>> +int dmc_register_notifier(struct notifier_block *nb)
>>> +{
>>> +    int ret;
>>> +
>>> +    if (!nb)
>>> +        return -EINVAL;
>>> +
>>> +    ret = blocking_notifier_chain_register(&dmc_notifier_list, nb);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * dmc_unregister_notifier - unregister a driver from dmc chain
>>> + * @nb: remove notifier function
>>> + */
>>> +int dmc_unregister_notifier(struct notifier_block *nb)
>>> +{
>>> +    int ret;
>>> +
>>> +    if (!nb)
>>> +        return -EINVAL;
>>> +
>>> +    ret = blocking_notifier_chain_unregister(&dmc_notifier_list, nb);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * rockchip_dmc_get - Register the notifier block for the dmc chain.
>>> + * @nb The dmc notifier block to register
>>> + */
>>> +int rockchip_dmc_get(struct notifier_block *nb)
>>> +{
>>> +    if (!nb)
>>> +        return -EINVAL;
>>> +
>>> +    mutex_lock(&dmc_en_lock);
>>> +
>>> +    /*
>>> +     * if have two notifier(enable two vop etc),
>>> +     * need to disable dmc
>>> +     */
>>> +    if (num_wait == 1 && num_disable <= 0)
>>> +        dmc_event(DMC_DISABLE);
>>> +    num_wait++;
>>> +    dmc_register_notifier(nb);
>>> +    mutex_unlock(&dmc_en_lock);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +/**
>>> + * rockchip_dmc_put - Remove the notifier block from the dmc chain.
>>> + * @nb The dmc notifier block to unregister
>>> + */
>>> +int rockchip_dmc_put(struct notifier_block *nb)
>>> +{
>>> +    if (!nb)
>>> +        return -EINVAL;
>>> +
>>> +    mutex_lock(&dmc_en_lock);
>>> +    num_wait--;
>>> +
>>> +    /* if notifier from 2 back to 1, enable dmc again */
>>> +    if (num_wait == 1 && num_disable <= 0)
>>> +        dmc_event(DMC_ENABLE);
>>> +    dmc_unregister_notifier(nb);
>>> +    mutex_unlock(&dmc_en_lock);
>>> +
>>> +    return 0;
>>> +}
>>> diff --git a/include/soc/rockchip/rockchip_dmc.h b/include/soc/rockchip/rockchip_dmc.h
>>> new file mode 100644
>>> index 0000000..3f69cbf
>>> --- /dev/null
>>> +++ b/include/soc/rockchip/rockchip_dmc.h
>>> @@ -0,0 +1,45 @@
>>> +/*
>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify it
>>> + * under the terms and conditions of the GNU General Public License,
>>> + * version 2, as published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>> + * more details.
>>> + */
>>> +
>>> +#ifndef __SOC_ROCKCHIP_DMC_H
>>> +#define __SOC_ROCKCHIP_DMC_H
>>> +
>>> +#include <linux/notifier.h>
>>> +
>>> +#define DMC_ENABLE    0
>>> +#define DMC_DISABLE    1
>>> +#define DMCFREQ_ADJUST    2
>>> +#define DMCFREQ_FINISH    3
>>> +
>>> +#if IS_ENABLED(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)
>>> +int rockchip_dmc_get(struct notifier_block *nb);
>>> +int rockchip_dmc_put(struct notifier_block *nb);
>>> +#else
>>> +static inline int rockchip_dmc_get(struct notifier_block *nb)
>>> +{
>>> +    return 0;
>>> +}
>>> +
>>> +static inline int rockchip_dmc_put(struct notifier_block *nb)
>>> +{
>>> +    return 0;
>>> +}
>>> +#endif
>>> +
>>> +void dmc_event(int event);
>>> +int dmc_register_notifier(struct notifier_block *nb);
>>> +int dmc_unregister_notifier(struct notifier_block *nb);
>>> +void rockchip_dmc_enable(void);
>>> +void rockchip_dmc_disable(void);
>>> +bool rockchip_dmc_enabled(void);
>>> +#endif
>>>
>> Regards,
>> Chanwoo Choi
>>
>>
>> _______________________________________________
>> Linux-rockchip mailing list
>> Linux-rockchip at lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-rockchip
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22 20:50   ` Heiko Stübner
  2016-07-24  8:00     ` hl
@ 2016-07-25 17:36     ` Sudeep Holla
  2016-07-26  1:13       ` hl
  1 sibling, 1 reply; 24+ messages in thread
From: Sudeep Holla @ 2016-07-25 17:36 UTC (permalink / raw)
  To: linux-arm-kernel



On 22/07/16 21:50, Heiko St?bner wrote:
> Hi again,
>
> one bigger thing I noticed only now.
>
> Am Freitag, 22. Juli 2016, 17:07:14 schrieben Sie:
>> diff --git a/drivers/firmware/rockchip_sip.c
>> b/drivers/firmware/rockchip_sip.c new file mode 100644
>> index 0000000..7756af9
>> --- /dev/null
>> +++ b/drivers/firmware/rockchip_sip.c
>> @@ -0,0 +1,64 @@
>> +/*
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + * Copyright (C) 2016 ARM Limited
>> + */
>> +#include <linux/errno.h>
>> +#include <linux/linkage.h>
>> +#include <linux/of.h>
>> +#include <linux/pm.h>
>> +#include <linux/printk.h>
>> +#include "rockchip_sip.h"
>> +
>> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
>> +				unsigned long, unsigned long);
>> +asmlinkage psci_fn __invoke_psci_fn_smc;
>> +
>> +#define CONFIG_DRAM_INIT	0x00
>> +#define CONFIG_DRAM_SET_RATE	0x01
>> +#define CONFIG_DRAM_ROUND_RATE	0x02
>> +#define CONFIG_DRAM_SET_AT_SR	0x03
>> +#define CONFIG_DRAM_GET_BW	0x04
>> +#define CONFIG_DRAM_GET_RATE	0x05
>> +#define CONFIG_DRAM_CLR_IRQ	0x06
>> +#define CONFIG_DRAM_SET_PARAM   0x07
>> +
>> +uint64_t sip_smc_ddr_init(void)
>> +{
>> +	return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
>> +				    0, CONFIG_DRAM_INIT);
>
> I don't think that is legal to use. For one this function itself is declared
> static in the psci code - most likely for a specific reason.
>
> And also if anything invoke_psci_fn would hold the correct pointer depending
> on the calling method.
>
> But as said above, accessing psci static stuff is most likely wrong. Maybe the
> two psci people I've included can tell us how this is to be accessed.
>

Thanks Heiko for looping us in this thread.

The feature being added in this series is completely out of scope of
PSCI specification and hence PSCI can't be used. Firstly we need to
audit if these are need in Linux and why they can't be handled within
the existing PSCI APIs. But yes, this series is misuse of PSCI.

I also see to know that ARM Trusted Firmware community has not accepted
this PSCI approach, so this patches are useless without that.

If they are still needed they need to make use of SMC Calling Convention
(arm_smccc_smc). Either make these smc function identifiers standard on
their platforms and use them directly in the driver. If they tend to
change too much across their platforms, use the DT approach with
appropriate bindings.

-- 
Regards,
Sudeep

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-25 17:36     ` Sudeep Holla
@ 2016-07-26  1:13       ` hl
  0 siblings, 0 replies; 24+ messages in thread
From: hl @ 2016-07-26  1:13 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Sudeep Holla,

On 2016?07?26? 01:36, Sudeep Holla wrote:
>
>
> On 22/07/16 21:50, Heiko St?bner wrote:
>> Hi again,
>>
>> one bigger thing I noticed only now.
>>
>> Am Freitag, 22. Juli 2016, 17:07:14 schrieben Sie:
>>> diff --git a/drivers/firmware/rockchip_sip.c
>>> b/drivers/firmware/rockchip_sip.c new file mode 100644
>>> index 0000000..7756af9
>>> --- /dev/null
>>> +++ b/drivers/firmware/rockchip_sip.c
>>> @@ -0,0 +1,64 @@
>>> +/*
>>> + * This program is free software; you can redistribute it and/or 
>>> modify
>>> + * it under the terms of the GNU General Public License version 2 as
>>> + * published by the Free Software Foundation.
>>> + *
>>> + * This program is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> + * GNU General Public License for more details.
>>> + *
>>> + * Copyright (C) 2016 ARM Limited
>>> + */
>>> +#include <linux/errno.h>
>>> +#include <linux/linkage.h>
>>> +#include <linux/of.h>
>>> +#include <linux/pm.h>
>>> +#include <linux/printk.h>
>>> +#include "rockchip_sip.h"
>>> +
>>> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
>>> +                unsigned long, unsigned long);
>>> +asmlinkage psci_fn __invoke_psci_fn_smc;
>>> +
>>> +#define CONFIG_DRAM_INIT    0x00
>>> +#define CONFIG_DRAM_SET_RATE    0x01
>>> +#define CONFIG_DRAM_ROUND_RATE    0x02
>>> +#define CONFIG_DRAM_SET_AT_SR    0x03
>>> +#define CONFIG_DRAM_GET_BW    0x04
>>> +#define CONFIG_DRAM_GET_RATE    0x05
>>> +#define CONFIG_DRAM_CLR_IRQ    0x06
>>> +#define CONFIG_DRAM_SET_PARAM   0x07
>>> +
>>> +uint64_t sip_smc_ddr_init(void)
>>> +{
>>> +    return __invoke_psci_fn_smc(SIP_DDR_FREQ, 0,
>>> +                    0, CONFIG_DRAM_INIT);
>>
>> I don't think that is legal to use. For one this function itself is 
>> declared
>> static in the psci code - most likely for a specific reason.
>>
>> And also if anything invoke_psci_fn would hold the correct pointer 
>> depending
>> on the calling method.
>>
>> But as said above, accessing psci static stuff is most likely wrong. 
>> Maybe the
>> two psci people I've included can tell us how this is to be accessed.
>>
>
> Thanks Heiko for looping us in this thread.
>
> The feature being added in this series is completely out of scope of
> PSCI specification and hence PSCI can't be used. Firstly we need to
> audit if these are need in Linux and why they can't be handled within
> the existing PSCI APIs. But yes, this series is misuse of PSCI.
>
> I also see to know that ARM Trusted Firmware community has not accepted
> this PSCI approach, so this patches are useless without that.
>
> If they are still needed they need to make use of SMC Calling Convention
> (arm_smccc_smc). Either make these smc function identifiers standard on
> their platforms and use them directly in the driver. If they tend to
> change too much across their platforms, use the DT approach with
> appropriate bindings.
Thanks for your suggestion, i will update the code in next version.

-- 
Lin Huang

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc
  2016-07-25  9:45       ` Chanwoo Choi
@ 2016-07-26  1:15         ` hl
  0 siblings, 0 replies; 24+ messages in thread
From: hl @ 2016-07-26  1:15 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Chanwoo Choi,

On 2016?07?25? 17:45, Chanwoo Choi wrote:
> Hi Lin,
>
> On 2016? 07? 25? 17:47, hl wrote:
>> Hi Chanwoo Choi,
>>
>> On 2016?07?25? 14:01, Chanwoo Choi wrote:
>>> Hi Lin,
>>>
>>> I'm glad to support the for dmc ddr clock scaling with devfreq/devfreq-event.
>>>
>>> But, I think that you have to use the standard interface.
>>> As I already mentioned[1] on previous mail, devfreq fwk support
>>> the standard DEVFREQ_TRANSITION_NOTIFIER notifier which has the two notifications.
>>> - DEVFREQ_PRECHANGE
>>> - DEVFREQ_POSTCHANGE
>>> [1] https://patchwork.kernel.org/patch/9194305/
>>>
>>> You can use the DEVFREQ_PRECHANGE instead of DMCFREQ_ADJUST
>>> and use the DEVFREQ_POSTCHANGE instead of DMCFREQ_FINISH.
>>>
>>> On 2016? 07? 22? 18:07, Lin Huang wrote:
>>>> base on dfi result, we do ddr frequency scaling, register
>>>> dmc driver to devfreq framework, and use simple-ondemand
>>>> policy.
>>>>
>>>> Signed-off-by: Lin Huang <hl@rock-chips.com>
>>>> ---
>>>> Changes in v3:
>>>> - operate dram setting through sip call
>>>> - imporve set rate flow
>>>>
>>>> Changes in v2:
>>>> - None
>>>>    Changes in v1:
>>>> - move dfi controller to event
>>>> - fix set voltage sequence when set rate fail
>>>> - change Kconfig type from tristate to bool
>>>> - move unuse EXPORT_SYMBOL_GPL()
>>>>
>>>>    drivers/devfreq/Kconfig                 |   1 +
>>>>    drivers/devfreq/Makefile                |   1 +
>>>>    drivers/devfreq/rockchip/Kconfig        |  15 +
>>>>    drivers/devfreq/rockchip/Makefile       |   2 +
>>>>    drivers/devfreq/rockchip/rk3399_dmc.c   | 482 ++++++++++++++++++++++++++++++++
>>>>    drivers/devfreq/rockchip/rockchip_dmc.c | 143 ++++++++++
>>>>    include/soc/rockchip/rockchip_dmc.h     |  45 +++
>>>>    7 files changed, 689 insertions(+)
>>>>    create mode 100644 drivers/devfreq/rockchip/Kconfig
>>>>    create mode 100644 drivers/devfreq/rockchip/Makefile
>>>>    create mode 100644 drivers/devfreq/rockchip/rk3399_dmc.c
>>>>    create mode 100644 drivers/devfreq/rockchip/rockchip_dmc.c
>>>>    create mode 100644 include/soc/rockchip/rockchip_dmc.h
>>>>
>>>> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>>> index a5be56e..cb67246 100644
>>>> --- a/drivers/devfreq/Kconfig
>>>> +++ b/drivers/devfreq/Kconfig
>>>> @@ -101,5 +101,6 @@ config ARM_TEGRA_DEVFREQ
>>>>             operating frequencies and voltages with OPP support.
>>>>      source "drivers/devfreq/event/Kconfig"
>>>> +source "drivers/devfreq/rockchip/Kconfig"
>>>>      endif # PM_DEVFREQ
>>>> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>>> index 09f11d9..48e2ae6 100644
>>>> --- a/drivers/devfreq/Makefile
>>>> +++ b/drivers/devfreq/Makefile
>>>> @@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
>>>>    # DEVFREQ Drivers
>>>>    obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)    += exynos-bus.o
>>>>    obj-$(CONFIG_ARM_TEGRA_DEVFREQ)        += tegra-devfreq.o
>>>> +obj-$(CONFIG_ARCH_ROCKCHIP)        += rockchip/
>>>>      # DEVFREQ Event Drivers
>>>>    obj-$(CONFIG_PM_DEVFREQ_EVENT)        += event/
>>>> diff --git a/drivers/devfreq/rockchip/Kconfig b/drivers/devfreq/rockchip/Kconfig
>>>> new file mode 100644
>>>> index 0000000..7fb1cff
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/rockchip/Kconfig
>>>> @@ -0,0 +1,15 @@
>>>> +config ARM_ROCKCHIP_DMC_DEVFREQ
>>>> +    bool "ARM ROCKCHIP DMC DEVFREQ Driver"
>>>> +    depends on ARCH_ROCKCHIP
>>>> +    help
>>>> +      This adds the DEVFREQ driver framework for the rockchip dmc.
>>>> +
>>>> +config ARM_RK3399_DMC_DEVFREQ
>>>> +    bool "ARM RK3399 DMC DEVFREQ Driver"
>>>> +    depends on ARM_ROCKCHIP_DMC_DEVFREQ
>>>> +    select PM_OPP
>>>> +    select DEVFREQ_GOV_SIMPLE_ONDEMAND
>>>> +    help
>>>> +          This adds the DEVFREQ driver for the RK3399 dmc. It sets the frequency
>>>> +          for the memory controller and reads the usage counts from hardware.
>>>> +
>>>> diff --git a/drivers/devfreq/rockchip/Makefile b/drivers/devfreq/rockchip/Makefile
>>>> new file mode 100644
>>>> index 0000000..caca525
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/rockchip/Makefile
>>>> @@ -0,0 +1,2 @@
>>>> +obj-$(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)    += rockchip_dmc.o
>>>> +obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ)    += rk3399_dmc.o
>>>> diff --git a/drivers/devfreq/rockchip/rk3399_dmc.c b/drivers/devfreq/rockchip/rk3399_dmc.c
>>>> new file mode 100644
>>>> index 0000000..f1d6120
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/rockchip/rk3399_dmc.c
>>>> @@ -0,0 +1,482 @@
>>>> +/*
>>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>>> + * Author: Lin Huang <hl@rock-chips.com>
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify it
>>>> + * under the terms and conditions of the GNU General Public License,
>>>> + * version 2, as published by the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>>> + * more details.
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/completion.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/devfreq.h>
>>>> +#include <linux/devfreq-event.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_opp.h>
>>>> +#include <linux/regulator/consumer.h>
>>>> +#include <linux/rwsem.h>
>>>> +#include <linux/suspend.h>
>>>> +#include <linux/syscore_ops.h>
>>>> +
>>>> +#include <soc/rockchip/rockchip_dmc.h>
>>>> +#include "../../firmware/rockchip_sip.h"
>>>> +
>>>> +struct dram_timing {
>>>> +    unsigned int ddr3_speed_bin;
>>>> +    unsigned int pd_idle;
>>>> +    unsigned int sr_idle;
>>>> +    unsigned int sr_mc_gate_idle;
>>>> +    unsigned int srpd_lite_idle;
>>>> +    unsigned int standby_idle;
>>>> +    unsigned int dram_dll_dis_freq;
>>>> +    unsigned int phy_dll_dis_freq;
>>>> +    unsigned int ddr3_odt_dis_freq;
>>>> +    unsigned int ddr3_drv;
>>>> +    unsigned int ddr3_odt;
>>>> +    unsigned int phy_ddr3_ca_drv;
>>>> +    unsigned int phy_ddr3_dq_drv;
>>>> +    unsigned int phy_ddr3_odt;
>>>> +    unsigned int lpddr3_odt_dis_freq;
>>>> +    unsigned int lpddr3_drv;
>>>> +    unsigned int lpddr3_odt;
>>>> +    unsigned int phy_lpddr3_ca_drv;
>>>> +    unsigned int phy_lpddr3_dq_drv;
>>>> +    unsigned int phy_lpddr3_odt;
>>>> +    unsigned int lpddr4_odt_dis_freq;
>>>> +    unsigned int lpddr4_drv;
>>>> +    unsigned int lpddr4_dq_odt;
>>>> +    unsigned int lpddr4_ca_odt;
>>>> +    unsigned int phy_lpddr4_ca_drv;
>>>> +    unsigned int phy_lpddr4_ck_cs_drv;
>>>> +    unsigned int phy_lpddr4_dq_drv;
>>>> +    unsigned int phy_lpddr4_odt;
>>>> +};
>>>> +
>>>> +struct rk3399_dmcfreq {
>>>> +    struct device *dev;
>>>> +    struct devfreq *devfreq;
>>>> +    struct devfreq_simple_ondemand_data ondemand_data;
>>>> +    struct clk *dmc_clk;
>>>> +    struct devfreq_event_dev *edev;
>>>> +    struct mutex lock;
>>>> +    struct notifier_block dmc_nb;
>>>> +    struct dram_timing *timing;
>>>> +    wait_queue_head_t    wait_dcf_queue;
>>>> +    int irq;
>>>> +    int wait_dcf_flag;
>>>> +    struct regulator *vdd_center;
>>>> +    unsigned long rate, target_rate;
>>>> +    unsigned long volt, target_volt;
>>>> +};
>>>> +
>>>> +static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
>>>> +                 u32 flags)
>>>> +{
>>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>>> +                            dev);
>>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>>> +    struct dev_pm_opp *opp;
>>>> +    unsigned long old_clk_rate = dmcfreq->rate;
>>>> +    unsigned long target_volt, target_rate;
>>>> +    int err;
>>>> +
>>>> +    rcu_read_lock();
>>>> +    opp = devfreq_recommended_opp(dev, freq, flags);
>>>> +    if (IS_ERR(opp)) {
>>>> +        rcu_read_unlock();
>>>> +        return PTR_ERR(opp);
>>>> +    }
>>>> +    target_rate = dev_pm_opp_get_freq(opp);
>>>> +    target_volt = dev_pm_opp_get_voltage(opp);
>>>> +    opp = devfreq_recommended_opp(dev, &dmcfreq->rate, flags);
>>>> +    if (IS_ERR(opp)) {
>>>> +        rcu_read_unlock();
>>>> +        return PTR_ERR(opp);
>>>> +    }
>>>> +    dmcfreq->volt = dev_pm_opp_get_voltage(opp);
>>>> +    rcu_read_unlock();
>>>> +
>>>> +    if (dmcfreq->rate == target_rate)
>>>> +        return 0;
>>>> +
>>>> +    mutex_lock(&dmcfreq->lock);
>>>> +    /*
>>>> +     * if frequency scaling from low to high, adjust voltage first;
>>>> +     * if frequency scaling from high to low, adjuset frequency first;
>>>> +     */
>>>> +    if (old_clk_rate < target_rate) {
>>>> +        err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>>>> +                        target_volt);
>>>> +        if (err) {
>>>> +            dev_err(dev, "Unable to set vol %lu\n", target_volt);
>>>> +            goto out;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    dmc_event(DMCFREQ_ADJUST);
>>> You don't need to send the notification in target function.
>>> When you use the DEVFREQ_TRANSITION_NOTIFIER notifier,
>>> devfreq send the notification before calling the target() and after calling the target().
>> I checked DEVFREQ_TRANSISTION_NOTIFIER notifier before, but i have some question:
>> Now i want to use
>>      DEVFREQ_PRECHANGE and DEVFREQ_POSTCHANGE in vop driver, but it hard to get the *devfreq
>> in vop driver, or i can register a DEVFREQ_TRANSITION_NOTIFIER notifier in rk3399_dmc.c and
>> when get message, post DEVFREQ_PRECHANGE or DEVFREQ_POSTCHANGE to vop driver through dmc_event(),
>> do you have a better way to do it?
> Firstly,
> The devfreq has the devfreq_get_devfreq_by_phandle() function
> to get the devfreq pointer in device tree. The vop_bind()
> get the IORESOURCE_MEM and interrupt. So, I think that you
> would get the devfreq pointer in the vop_bind() function.
> So, DEVFREQ_TRANSITION_NOTIFIER can alter the following two notifications:
> - dmc_event(DMCFREQ_ADJUST);
> - dmc_event(DMCFREQ_FINISH);
>
>
> Also,
> devfreq-event fwk provide the devfreq_event_get_edev_by_phandle() funtcion
> to get the devfreq-event pointer in the device tree.
>
> You could call the devfreq_event_[enable/disable[_edev()
> and devfreq_[resume/suspend]_device in the drivers/gpu/drm/rockchip/rockchip_drm_vop.c
> instead of dmc_event(DMC_ENABLE) / dmc_event(DMC_DISABLE).
>
> dmc_event(DMC_ENABLE);
> 	devfreq_event_enable_edev(dmcfreq->edev);
> 	devfreq_resume_device(dmcfreq->devfreq);
>
> dmc_event(DMC_DISABLE);
> 	devfreq_event_disable_edev(dmcfreq->edev);
> 	devfreq_suspend_device(dmcfreq->devfreq);
>
> I think that we have to use the standard interfaces and
> functions providing by framework. It is not appropriate way
> to define the specific functions for only one device driver
> between other framework.

Thanks for you suggestion, this may be a huge update, i will try to 
implement it.
>
> Regards,
> Chanwoo Choi
>
>>>> +    dmcfreq->wait_dcf_flag = 1;
>>>> +
>>>> +    err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
>>>> +    if (err) {
>>>> +        dev_err(dev,
>>>> +            "Unable to set freq %lu. Current freq %lu. Error %d\n",
>>>> +            target_rate, old_clk_rate, err);
>>>> +        regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>>>> +                      dmcfreq->volt);
>>>> +        dmc_event(DMCFREQ_FINISH);
>>> ditto.
>>>
>>>> +        goto out;
>>>> +    }
>>>> +
>>>> +    /*
>>>> +     * wait until bcf irq happen, it means freq scaling finish in bl31,
>>>> +     * use 100ms as timeout time
>>>> +     */
>>>> +    wait_event_timeout(dmcfreq->wait_dcf_queue,
>>>> +               !dmcfreq->wait_dcf_flag, HZ / 10);
>>>> +
>>>> +    dmc_event(DMCFREQ_FINISH);
>>> ditto.
>>>
>>>> +
>>>> +    /*
>>>> +     * check the dpll rate
>>>> +     * there only two result we will get,
>>>> +     * 1. ddr frequency scaling fail, we still get the old rate
>>>> +     * 2, ddr frequency scaling sucessful, we get the rate we set
>>>> +     */
>>>> +    dmcfreq->rate = clk_get_rate(dmcfreq->dmc_clk);
>>>> +
>>>> +    /* if get the incorrect rate, set voltage to old value */
>>>> +    if (dmcfreq->rate != target_rate) {
>>>> +        dev_err(dev, "get wrong ddr frequency, Request freq %lu,\
>>>> +            Current freq %lu\n", target_rate, dmcfreq->rate);
>>>> +        regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
>>>> +                      dmcfreq->volt);
>>>> +    } else if (old_clk_rate > target_rate)
>>>> +        err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
>>>> +                        target_volt);
>>>> +    if (err)
>>>> +        dev_err(dev, "Unable to set vol %lu\n", target_volt);
>>>> +
>>>> +out:
>>>> +    mutex_unlock(&dmcfreq->lock);
>>>> +    return err;
>>>> +}
>>>> +
>>>> +static int rk3399_dmcfreq_get_dev_status(struct device *dev,
>>>> +                     struct devfreq_dev_status *stat)
>>>> +{
>>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>>> +                            dev);
>>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>>> +    struct devfreq_event_data edata;
>>>> +
>>>> +    devfreq_event_get_event(dmcfreq->edev, &edata);
>>>> +
>>>> +    stat->current_frequency = dmcfreq->rate;
>>>> +    stat->busy_time = edata.load_count;
>>>> +    stat->total_time = edata.total_count;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int rk3399_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
>>>> +{
>>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>>> +                            dev);
>>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>>> +
>>>> +    *freq = dmcfreq->rate;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static void rk3399_dmcfreq_exit(struct device *dev)
>>>> +{
>>>> +    struct platform_device *pdev = container_of(dev, struct platform_device,
>>>> +                            dev);
>>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>>> +
>>>> +    devfreq_unregister_opp_notifier(dev, dmcfreq->devfreq);
>>>> +}
>>>> +
>>>> +static struct devfreq_dev_profile rk3399_devfreq_dmc_profile = {
>>>> +    .polling_ms    = 200,
>>>> +    .target        = rk3399_dmcfreq_target,
>>>> +    .get_dev_status    = rk3399_dmcfreq_get_dev_status,
>>>> +    .get_cur_freq    = rk3399_dmcfreq_get_cur_freq,
>>>> +    .exit        = rk3399_dmcfreq_exit,
>>>> +};
>>>> +
>>>> +static __maybe_unused int rk3399_dmcfreq_suspend(struct device *dev)
>>>> +{
>>>> +    rockchip_dmc_disable();
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static __maybe_unused int rk3399_dmcfreq_resume(struct device *dev)
>>>> +{
>>>> +    rockchip_dmc_enable();
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static SIMPLE_DEV_PM_OPS(rk3399_dmcfreq_pm, rk3399_dmcfreq_suspend,
>>>> +             rk3399_dmcfreq_resume);
>>>> +
>>>> +static int rk3399_dmc_enable_notify(struct notifier_block *nb,
>>>> +                    unsigned long event, void *data)
>>>> +{
>>>> +    struct rk3399_dmcfreq *dmcfreq =
>>>> +                  container_of(nb, struct rk3399_dmcfreq, dmc_nb);
>>>> +    unsigned long freq = ULONG_MAX;
>>>> +
>>>> +    if (event == DMC_ENABLE) {
>>>> +        devfreq_event_enable_edev(dmcfreq->edev);
>>>> +        devfreq_resume_device(dmcfreq->devfreq);
>>>> +        return NOTIFY_OK;
>>>> +    } else if (event == DMC_DISABLE) {
>>>> +        devfreq_event_disable_edev(dmcfreq->edev);
>>>> +        devfreq_suspend_device(dmcfreq->devfreq);
>>>> +
>>>> +        /* when disable dmc, set sdram to max frequency */
>>>> +        rk3399_dmcfreq_target(dmcfreq->dev, &freq, 0);
>>>> +        return NOTIFY_OK;
>>>> +    }
>>>> +
>>>> +    return NOTIFY_DONE;
>>>> +}
>>>> +
>>>> +static irqreturn_t rk3399_dmc_irq(int irq, void *dev_id)
>>>> +{
>>>> +    struct rk3399_dmcfreq *dmcfreq = dev_id;
>>>> +
>>>> +    dmcfreq->wait_dcf_flag = 0;
>>>> +    wake_up(&dmcfreq->wait_dcf_queue);
>>>> +
>>>> +    /* clr dcf irq */
>>>> +    sip_smc_clr_ddr_irq();
>>>> +
>>>> +    return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int of_do_get_timing(struct device_node *np,
>>>> +        struct dram_timing *timing)
>>>> +{
>>>> +    int ret;
>>>> +
>>>> +    ret = of_property_read_u32(np, "ddr3_speed_bin",
>>>> +                   &timing->ddr3_speed_bin);
>>>> +    ret |= of_property_read_u32(np, "pd_idle", &timing->pd_idle);
>>>> +    ret |= of_property_read_u32(np, "sr_idle", &timing->sr_idle);
>>>> +    ret |= of_property_read_u32(np, "sr_mc_gate_idle",
>>>> +                    &timing->sr_mc_gate_idle);
>>>> +    ret |= of_property_read_u32(np, "srpd_lite_idle",
>>>> +                    &timing->srpd_lite_idle);
>>>> +    ret |= of_property_read_u32(np, "standby_idle", &timing->standby_idle);
>>>> +    ret |= of_property_read_u32(np, "dram_dll_dis_freq",
>>>> +                    &timing->dram_dll_dis_freq);
>>>> +    ret |= of_property_read_u32(np, "phy_dll_dis_freq",
>>>> +                    &timing->phy_dll_dis_freq);
>>>> +    ret |= of_property_read_u32(np, "ddr3_odt_dis_freq",
>>>> +                    &timing->ddr3_odt_dis_freq);
>>>> +    ret |= of_property_read_u32(np, "ddr3_drv", &timing->ddr3_drv);
>>>> +    ret |= of_property_read_u32(np, "ddr3_odt", &timing->ddr3_odt);
>>>> +    ret |= of_property_read_u32(np, "phy_ddr3_ca_drv",
>>>> +                    &timing->phy_ddr3_ca_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_ddr3_dq_drv",
>>>> +                    &timing->phy_ddr3_dq_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_ddr3_odt", &timing->phy_ddr3_odt);
>>>> +    ret |= of_property_read_u32(np, "lpddr3_odt_dis_freq",
>>>> +                    &timing->lpddr3_odt_dis_freq);
>>>> +    ret |= of_property_read_u32(np, "lpddr3_drv", &timing->lpddr3_drv);
>>>> +    ret |= of_property_read_u32(np, "lpddr3_odt", &timing->lpddr3_odt);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_ca_drv",
>>>> +                    &timing->phy_lpddr3_ca_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_dq_drv",
>>>> +                    &timing->phy_lpddr3_dq_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr3_odt",
>>>> +                    &timing->phy_lpddr3_odt);
>>>> +    ret |= of_property_read_u32(np, "lpddr4_odt_dis_freq",
>>>> +                    &timing->lpddr4_odt_dis_freq);
>>>> +    ret |= of_property_read_u32(np, "lpddr4_drv",
>>>> +                    &timing->lpddr4_drv);
>>>> +    ret |= of_property_read_u32(np, "lpddr4_dq_odt",
>>>> +                    &timing->lpddr4_dq_odt);
>>>> +    ret |= of_property_read_u32(np, "lpddr4_ca_odt",
>>>> +                    &timing->lpddr4_ca_odt);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_ca_drv",
>>>> +                    &timing->phy_lpddr4_ca_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_ck_cs_drv",
>>>> +                    &timing->phy_lpddr4_ck_cs_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_dq_drv",
>>>> +                    &timing->phy_lpddr4_dq_drv);
>>>> +    ret |= of_property_read_u32(np, "phy_lpddr4_odt",
>>>> +                    &timing->phy_lpddr4_odt);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static struct dram_timing *of_get_ddr_timings(struct device *dev,
>>>> +                          struct device_node *np)
>>>> +{
>>>> +    struct dram_timing    *timing = NULL;
>>>> +    struct device_node    *np_tim;
>>>> +
>>>> +    np_tim = of_parse_phandle(np, "ddr_timing", 0);
>>>> +    if (np_tim) {
>>>> +        timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
>>>> +        if (!timing)
>>>> +            goto err;
>>>> +        if (of_do_get_timing(np_tim, timing)) {
>>>> +            devm_kfree(dev, timing);
>>>> +            goto err;
>>>> +        }
>>>> +        return timing;
>>>> +    }
>>>> +
>>>> +err:
>>>> +    if (timing) {
>>>> +        devm_kfree(dev, timing);
>>>> +        timing = NULL;
>>>> +    }
>>>> +
>>>> +    return timing;
>>>> +}
>>>> +
>>>> +static int rk3399_dmcfreq_probe(struct platform_device *pdev)
>>>> +{
>>>> +    struct device *dev = &pdev->dev;
>>>> +    struct rk3399_dmcfreq *data;
>>>> +    int ret, irq, index;
>>>> +    uint64_t param = 0;
>>>> +    uint32_t *timing;
>>>> +    struct device_node *np = pdev->dev.of_node;
>>>> +
>>>> +    irq = platform_get_irq(pdev, 0);
>>>> +    if (irq < 0) {
>>>> +        dev_err(&pdev->dev, "no dmc irq resource\n");
>>>> +        return -EINVAL;
>>>> +    }
>>>> +
>>>> +    data = devm_kzalloc(dev, sizeof(struct rk3399_dmcfreq), GFP_KERNEL);
>>>> +    if (!data)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    mutex_init(&data->lock);
>>>> +
>>>> +    data->vdd_center = devm_regulator_get(dev, "center");
>>>> +    if (IS_ERR(data->vdd_center)) {
>>>> +        dev_err(dev, "Cannot get the regulator \"center\"\n");
>>>> +        return PTR_ERR(data->vdd_center);
>>>> +    }
>>>> +
>>>> +    data->dmc_clk = devm_clk_get(dev, "dmc_clk");
>>>> +    if (IS_ERR(data->dmc_clk)) {
>>>> +        dev_err(dev, "Cannot get the clk dmc_clk\n");
>>>> +        return PTR_ERR(data->dmc_clk);
>>>> +    };
>>>> +
>>>> +    data->edev = devfreq_event_get_edev_by_phandle(dev, 0);
>>>> +    if (IS_ERR(data->edev))
>>>> +        return -EPROBE_DEFER;
>>>> +
>>>> +    ret = devfreq_event_enable_edev(data->edev);
>>>> +    if (ret < 0) {
>>>> +        dev_err(dev, "failed to enable devfreq-event devices\n");
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    /*
>>>> +     * We add a devfreq driver to our parent since it has a device tree node
>>>> +     * with operating points.
>>>> +     */
>>>> +    if (dev_pm_opp_of_add_table(dev)) {
>>>> +        dev_err(dev, "Invalid operating-points in device tree.\n");
>>>> +        return -EINVAL;
>>>> +    }
>>>> +
>>>> +    of_property_read_u32(np, "upthreshold",
>>>> +                 &data->ondemand_data.upthreshold);
>>>> +
>>>> +    of_property_read_u32(np, "downdifferential",
>>>> +                 &data->ondemand_data.downdifferential);
>>>> +
>>>> +    data->rate = clk_get_rate(data->dmc_clk);
>>>> +    rk3399_devfreq_dmc_profile.initial_freq = data->rate;
>>>> +    data->devfreq = devfreq_add_device(dev,
>>>> +                       &rk3399_devfreq_dmc_profile,
>>>> +                       "simple_ondemand",
>>>> +                       &data->ondemand_data);
>>>> +    if (IS_ERR(data->devfreq))
>>>> +        return PTR_ERR(data->devfreq);
>>>> +
>>>> +    devfreq_register_opp_notifier(dev, data->devfreq);
>>>> +
>>>> +    data->dmc_nb.notifier_call = rk3399_dmc_enable_notify;
>>>> +    dmc_register_notifier(&data->dmc_nb);
>>>> +    data->irq = irq;
>>>> +    ret = devm_request_irq(dev, irq, rk3399_dmc_irq, 0,
>>>> +                   dev_name(dev), data);
>>>> +    if (ret) {
>>>> +        dev_err(dev, "failed to request dmc irq: %d\n", ret);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    /* get dram timing and pass it to bl31 */
>>>> +    data->timing = of_get_ddr_timings(dev, np);
>>>> +    if (data->timing) {
>>>> +        timing = (uint32_t *)data->timing;
>>>> +        for (index = 0; index < (sizeof(struct dram_timing) / 4);
>>>> +             index++) {
>>>> +            param = index;
>>>> +            param = param << 32 | *timing++;
>>>> +            sip_smc_set_ddr_param(param);
>>>> +            param = 0;
>>>> +        }
>>>> +    }
>>>> +    sip_smc_ddr_init();
>>>> +
>>>> +    init_waitqueue_head(&data->wait_dcf_queue);
>>>> +    data->wait_dcf_flag = 0;
>>>> +
>>>> +    data->dev = dev;
>>>> +    platform_set_drvdata(pdev, data);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int rk3399_dmcfreq_remove(struct platform_device *pdev)
>>>> +{
>>>> +    struct rk3399_dmcfreq *dmcfreq = platform_get_drvdata(pdev);
>>>> +
>>>> +    devfreq_remove_device(dmcfreq->devfreq);
>>>> +    regulator_put(dmcfreq->vdd_center);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id rk3399dmc_devfreq_of_match[] = {
>>>> +    { .compatible = "rockchip,rk3399-dmc" },
>>>> +    { },
>>>> +};
>>>> +
>>>> +static struct platform_driver rk3399_dmcfreq_driver = {
>>>> +    .probe    = rk3399_dmcfreq_probe,
>>>> +    .remove    = rk3399_dmcfreq_remove,
>>>> +    .driver = {
>>>> +        .name    = "rk3399-dmc-freq",
>>>> +        .pm    = &rk3399_dmcfreq_pm,
>>>> +        .of_match_table = rk3399dmc_devfreq_of_match,
>>>> +    },
>>>> +};
>>>> +module_platform_driver(rk3399_dmcfreq_driver);
>>>> +
>>>> +MODULE_LICENSE("GPL v2");
>>>> +MODULE_DESCRIPTION("RK3399 dmcfreq driver with devfreq framework");
>>>> diff --git a/drivers/devfreq/rockchip/rockchip_dmc.c b/drivers/devfreq/rockchip/rockchip_dmc.c
>>>> new file mode 100644
>>>> index 0000000..78baee5
>>>> --- /dev/null
>>>> +++ b/drivers/devfreq/rockchip/rockchip_dmc.c
>>>> @@ -0,0 +1,143 @@
>>>> +/*
>>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>>> + * Author: Lin Huang <hl@rock-chips.com>
>>>> + * Base on: https://chromium-review.googlesource.com/#/c/231477/
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify it
>>>> + * under the terms and conditions of the GNU General Public License,
>>>> + * version 2, as published by the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>>> + * more details.
>>>> + */
>>>> +
>>>> +#include <linux/mutex.h>
>>>> +#include <soc/rockchip/rockchip_dmc.h>
>>>> +
>>>> +static int num_wait;
>>>> +static int num_disable;
>>>> +static BLOCKING_NOTIFIER_HEAD(dmc_notifier_list);
>>>> +static DEFINE_MUTEX(dmc_en_lock);
>>>> +static DEFINE_MUTEX(dmc_sync_lock);
>>>> +
>>>> +/**
>>>> + * rockchip_dmc_enabled - Returns true if dmc freq is enabled, false otherwise.
>>>> + */
>>>> +bool rockchip_dmc_enabled(void)
>>>> +{
>>>> +    return num_disable <= 0 && num_wait <= 1;
>>>> +}
>>>> +
>>>> +/**
>>>> + * rockchip_dmc_enable - Enable dmc frequency scaling. Will only enable
>>>> + * frequency scaling if there are 1 or fewer notifiers. Call to undo
>>>> + * rockchip_dmc_disable.
>>>> + */
>>>> +void rockchip_dmc_enable(void)
>>>> +{
>>>> +    mutex_lock(&dmc_en_lock);
>>>> +    num_disable--;
>>>> +    WARN_ON(num_disable < 0);
>>>> +    if (rockchip_dmc_enabled())
>>>> +        dmc_event(DMC_ENABLE);
>>>> +    mutex_unlock(&dmc_en_lock);
>>>> +}
>>>> +
>>>> +/**
>>>> + * rockchip_dmc_disable - Disable dmc frequency scaling. Call when something
>>>> + * cannot coincide with dmc frequency scaling.
>>>> + */
>>>> +void rockchip_dmc_disable(void)
>>>> +{
>>>> +    mutex_lock(&dmc_en_lock);
>>>> +    if (rockchip_dmc_enabled())
>>>> +        dmc_event(DMC_DISABLE);
>>>> +    num_disable++;
>>>> +    mutex_unlock(&dmc_en_lock);
>>>> +}
>>>> +
>>>> +void dmc_event(int event)
>>>> +{
>>>> +    mutex_lock(&dmc_sync_lock);
>>>> +    blocking_notifier_call_chain(&dmc_notifier_list, event, NULL);
>>>> +    mutex_unlock(&dmc_sync_lock);
>>>> +}
>>>> +
>>>> +/**
>>>> + * dmc_register_notifier - register a driver to dmc chain
>>>> + * @nb: notifier function to register
>>>> + */
>>>> +int dmc_register_notifier(struct notifier_block *nb)
>>>> +{
>>>> +    int ret;
>>>> +
>>>> +    if (!nb)
>>>> +        return -EINVAL;
>>>> +
>>>> +    ret = blocking_notifier_chain_register(&dmc_notifier_list, nb);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/**
>>>> + * dmc_unregister_notifier - unregister a driver from dmc chain
>>>> + * @nb: remove notifier function
>>>> + */
>>>> +int dmc_unregister_notifier(struct notifier_block *nb)
>>>> +{
>>>> +    int ret;
>>>> +
>>>> +    if (!nb)
>>>> +        return -EINVAL;
>>>> +
>>>> +    ret = blocking_notifier_chain_unregister(&dmc_notifier_list, nb);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/**
>>>> + * rockchip_dmc_get - Register the notifier block for the dmc chain.
>>>> + * @nb The dmc notifier block to register
>>>> + */
>>>> +int rockchip_dmc_get(struct notifier_block *nb)
>>>> +{
>>>> +    if (!nb)
>>>> +        return -EINVAL;
>>>> +
>>>> +    mutex_lock(&dmc_en_lock);
>>>> +
>>>> +    /*
>>>> +     * if have two notifier(enable two vop etc),
>>>> +     * need to disable dmc
>>>> +     */
>>>> +    if (num_wait == 1 && num_disable <= 0)
>>>> +        dmc_event(DMC_DISABLE);
>>>> +    num_wait++;
>>>> +    dmc_register_notifier(nb);
>>>> +    mutex_unlock(&dmc_en_lock);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * rockchip_dmc_put - Remove the notifier block from the dmc chain.
>>>> + * @nb The dmc notifier block to unregister
>>>> + */
>>>> +int rockchip_dmc_put(struct notifier_block *nb)
>>>> +{
>>>> +    if (!nb)
>>>> +        return -EINVAL;
>>>> +
>>>> +    mutex_lock(&dmc_en_lock);
>>>> +    num_wait--;
>>>> +
>>>> +    /* if notifier from 2 back to 1, enable dmc again */
>>>> +    if (num_wait == 1 && num_disable <= 0)
>>>> +        dmc_event(DMC_ENABLE);
>>>> +    dmc_unregister_notifier(nb);
>>>> +    mutex_unlock(&dmc_en_lock);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> diff --git a/include/soc/rockchip/rockchip_dmc.h b/include/soc/rockchip/rockchip_dmc.h
>>>> new file mode 100644
>>>> index 0000000..3f69cbf
>>>> --- /dev/null
>>>> +++ b/include/soc/rockchip/rockchip_dmc.h
>>>> @@ -0,0 +1,45 @@
>>>> +/*
>>>> + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or modify it
>>>> + * under the terms and conditions of the GNU General Public License,
>>>> + * version 2, as published by the Free Software Foundation.
>>>> + *
>>>> + * This program is distributed in the hope it will be useful, but WITHOUT
>>>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>>>> + * more details.
>>>> + */
>>>> +
>>>> +#ifndef __SOC_ROCKCHIP_DMC_H
>>>> +#define __SOC_ROCKCHIP_DMC_H
>>>> +
>>>> +#include <linux/notifier.h>
>>>> +
>>>> +#define DMC_ENABLE    0
>>>> +#define DMC_DISABLE    1
>>>> +#define DMCFREQ_ADJUST    2
>>>> +#define DMCFREQ_FINISH    3
>>>> +
>>>> +#if IS_ENABLED(CONFIG_ARM_ROCKCHIP_DMC_DEVFREQ)
>>>> +int rockchip_dmc_get(struct notifier_block *nb);
>>>> +int rockchip_dmc_put(struct notifier_block *nb);
>>>> +#else
>>>> +static inline int rockchip_dmc_get(struct notifier_block *nb)
>>>> +{
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static inline int rockchip_dmc_put(struct notifier_block *nb)
>>>> +{
>>>> +    return 0;
>>>> +}
>>>> +#endif
>>>> +
>>>> +void dmc_event(int event);
>>>> +int dmc_register_notifier(struct notifier_block *nb);
>>>> +int dmc_unregister_notifier(struct notifier_block *nb);
>>>> +void rockchip_dmc_enable(void);
>>>> +void rockchip_dmc_disable(void);
>>>> +bool rockchip_dmc_enabled(void);
>>>> +#endif
>>>>
>>> Regards,
>>> Chanwoo Choi
>>>
>>>
>>> _______________________________________________
>>> Linux-rockchip mailing list
>>> Linux-rockchip at lists.infradead.org
>>> http://lists.infradead.org/mailman/listinfo/linux-rockchip
>
>
>

-- 
Lin Huang

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service
  2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
                     ` (2 preceding siblings ...)
  2016-07-22 21:32   ` kbuild test robot
@ 2016-07-26 18:29   ` Mark Rutland
  3 siblings, 0 replies; 24+ messages in thread
From: Mark Rutland @ 2016-07-26 18:29 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jul 22, 2016 at 05:07:14PM +0800, Lin Huang wrote:
> +config ROCKCHIP_SIP
> +	bool "Rockchip SIP interface"
> +	depends on ARM64 && ARM_PSCI_FW
> +	help
> +	  Say Y here if you want to enable SIP callbacks for Rockchip platforms
> +	  This option enables support for communicating with the ATF.

[...]

> +typedef unsigned long (psci_fn)(unsigned long, unsigned long,
> +				unsigned long, unsigned long);
> +asmlinkage psci_fn __invoke_psci_fn_smc;

Use the SMCCC wrappers (see include/linux/arm-smccc.h).

This is *not* PSCI, even if the same ATF FW provides both services.

NAK for using the PSCI functions.

Thanks,
Mark.

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2016-07-26 18:29 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-22  9:07 [PATCH v3 0/7] rk3399 support ddr frequency scaling Lin Huang
2016-07-22  9:07 ` [PATCH v3 1/7] firmware: rockchip: sip: Add rockchip SIP runtime service Lin Huang
2016-07-22 10:00   ` Heiko Stübner
2016-07-22 20:50   ` Heiko Stübner
2016-07-24  8:00     ` hl
2016-07-25 17:36     ` Sudeep Holla
2016-07-26  1:13       ` hl
2016-07-22 21:32   ` kbuild test robot
2016-07-26 18:29   ` Mark Rutland
2016-07-22  9:07 ` [PATCH v3 2/7] clk: rockchip: add new clock-type for the ddrclk Lin Huang
2016-07-24  9:09   ` Heiko Stübner
2016-07-22  9:07 ` [PATCH v3 3/7] clk: rockchip: rk3399: add SCLK_DDRCLK ID for ddrc Lin Huang
2016-07-22 10:08   ` Heiko Stübner
2016-07-22  9:07 ` [PATCH v3 4/7] clk: rockchip: rk3399: add ddrc clock support Lin Huang
2016-07-22  9:07 ` [PATCH v3 5/7] PM / devfreq: event: support rockchip dfi controller Lin Huang
2016-07-22 20:28   ` Paul Gortmaker
2016-07-22  9:07 ` [PATCH v3 6/7] PM / devfreq: rockchip: add devfreq driver for rk3399 dmc Lin Huang
2016-07-22 20:24   ` Paul Gortmaker
2016-07-24  7:54     ` hl
2016-07-25  6:01   ` Chanwoo Choi
2016-07-25  8:47     ` hl
2016-07-25  9:45       ` Chanwoo Choi
2016-07-26  1:15         ` hl
2016-07-22  9:07 ` [PATCH v3 7/7] drm/rockchip: Add dmc notifier in vop driver Lin Huang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).