Dear all,
The purpose of that RFC is to enable de DMC device for the Rockchip
based GRU Chromebooks, and to add a mean of synchronization between the
Rockchip DRM driver and that DMC devfreq driver.
The DMC device is responsible for updating the DDR frequency according
to the load of the DDR memory. Changing that clock rate within the
display scanout leads to glitches. Thus, the devfreq framework needs a
mean to synchronize the rate change with other devices.
In that case, the DRM driver display has to synchronize a change rate
within the VBLANK.
The first patch adds a locking API to the devfreq framework. The users
of a devfreq device can control the frequency change by locking and
unlocking the devfreq device whenever they want. When a change rate is
requested, and the device is locked, the drivers that hold a lock are
called back, and the change will be applied as soon as the device is
unlocked.
The second patch adds the devfreq support in the Rockchip DRM drivers
and uses the devfreq lock API to defer the change of the DDR frequency
within the next VBLANK. The DRM driver locks the devfreq device and gets
notified when a change is wanted then. Next, it enables the VBLANK
interrupt, releases the lock on interrupt and starts a timer that
relocks the devfreq device at the end of the vertical blanking interval.
Also, the DRM driver disables the devfreq device if more than one CRTC
becomes active.
The third patch merges the Rockchip DDR clock code to the Rockchip DMC
devfreq driver. These drivers both perform SMC calls to the
Trusted-Firmware A to run SiP services that are related to the DDR
memory. This merge puts the code at the same place. It avoids the
contention in the Common Clock Framework that may cause to miss the
deadline during which the rate can be changing without making glitches.
The fourth patch tells display-subsystem to use the DMC devfreq device.
I am waiting for your feedback.
Note: This RFC needs patchset[1]. Its purpose is to addresses the review
that was made in v1[2]. I dropped some patches in v2 to address things
separately (in that RFC).
[1]: https://patchwork.kernel.org/cover/10901577/
[2]: https://lkml.org/lkml/fancy/2018/8/2/7
Best regards,
Gaël PORTAY (3):
PM / devfreq: add devfreq_lock/unlock() functions
drm: rockchip: Add DDR devfreq support.
clk: rockchip: merge clk-ddr in dmc devfreq driver
arm64: dts: rockchip: Set the display-subsystem devfreq
arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi | 4 +
arch/arm64/boot/dts/rockchip/rk3399.dtsi | 2 +-
drivers/clk/rockchip/Makefile | 1 -
drivers/clk/rockchip/clk-ddr.c | 147 --------------
drivers/clk/rockchip/clk-rk3399.c | 2 -
drivers/clk/rockchip/clk.c | 9 -
drivers/clk/rockchip/clk.h | 33 ---
drivers/devfreq/devfreq.c | 200 ++++++++++++++++++-
drivers/devfreq/rk3399_dmc.c | 42 ++--
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 51 ++++-
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 6 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 177 +++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 3 +-
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 82 ++++++++
include/linux/devfreq.h | 64 ++++++
15 files changed, 611 insertions(+), 212 deletions(-)
delete mode 100644 drivers/clk/rockchip/clk-ddr.c
--
2.21.0
This patch adds the implementation for lock/unlock functions in the
devfreq framework.
In some situations, changing the clock rate affect other devices, and
the devfreq framework needs a mean to synchronize all the drivers
together.
This locking API allows third-party drivers that use a devfreq device to
have control on whether it can change the rate or not.
Those drivers can lock the devfreq device to prevent it from changing
the rate, and then can release the lock to let it change the rate again.
When a change rate is triggered, and the devfreq device is locked, the
device informs the drivers that hold a lock through a callback that a
change rate is wanted. This change is not applied. It is marked as
pending.
When a pending rate change exists, and all the locks are released, the
devfreq device resumes the rate change that is pending after the last
lock is released.
Signed-off-by: Gaël PORTAY <[email protected]>
---
drivers/devfreq/devfreq.c | 200 +++++++++++++++++++++++++++++++++++++-
include/linux/devfreq.h | 64 ++++++++++++
2 files changed, 263 insertions(+), 1 deletion(-)
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index 0ae3de76833b..a655e14a28f6 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -323,6 +323,60 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
return err;
}
+static void devfreq_set_target_work(struct work_struct *work)
+{
+ struct devfreq *devfreq = container_of(work, struct devfreq,
+ set_target_work);
+ struct devfreq_dev_userlock *userlock, *userlock_safe;
+ unsigned long freq, lock_flags;
+ bool deadline = false;
+ ktime_t now;
+ bool locked;
+ u32 flags;
+ int err;
+
+ /* Suspended, skip. */
+ if (atomic_read(&devfreq->suspend_count) > 0)
+ return;
+
+ spin_lock_irqsave(&devfreq->lockers_spinlock, lock_flags);
+ locked = !list_empty(&devfreq->userlocks.locked);
+ /* Not locked, check if deadline of unlockers was not reached. */
+ if (!locked) {
+ now = ktime_get();
+ freq = devfreq->new_freq;
+ flags = devfreq->new_flags;
+
+ list_for_each_entry_safe(userlock, userlock_safe,
+ &devfreq->userlocks.unlocked,
+ node) {
+ if (ktime_after(userlock->deadline, now))
+ continue;
+
+ if (!userlock->deadline)
+ continue;
+
+ deadline = true;
+ list_move_tail(&userlock->node,
+ &devfreq->userlocks.locked);
+ }
+
+ if (!deadline)
+ devfreq->set_target_pending = false;
+ }
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, lock_flags);
+
+ if (locked || deadline)
+ return;
+
+ mutex_lock(&devfreq->lock);
+ err = devfreq_set_target(devfreq, freq, flags);
+ if (err)
+ dev_err(&devfreq->dev, "failed to set target with (%d) error\n",
+ err);
+ mutex_unlock(&devfreq->lock);
+}
+
/* Load monitoring helper functions for governors use */
/**
@@ -334,7 +388,9 @@ static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
*/
int update_devfreq(struct devfreq *devfreq)
{
- unsigned long freq, min_freq, max_freq;
+ unsigned long freq, min_freq, max_freq, lock_flags;
+ struct devfreq_dev_userlock *userlock;
+ bool locked = false;
int err = 0;
u32 flags = 0;
@@ -370,6 +426,28 @@ int update_devfreq(struct devfreq *devfreq)
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
}
+ spin_lock_irqsave(&devfreq->lockers_spinlock, lock_flags);
+ locked = !list_empty(&devfreq->userlocks.locked);
+ /* Device is locked, tell lockers that a change rate is wanted */
+ if (locked) {
+ devfreq->set_target_pending = true;
+ devfreq->new_freq = freq;
+ devfreq->new_flags = flags;
+
+ list_for_each_entry(userlock, &devfreq->userlocks.locked,
+ node) {
+ if (userlock->want_to_change_rate)
+ userlock->want_to_change_rate(userlock,
+ devfreq);
+ }
+ }
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, lock_flags);
+
+ if (locked) {
+ dev_warn(&devfreq->dev, "Locked!\n");
+ return 0;
+ }
+
return devfreq_set_target(devfreq, freq, flags);
}
@@ -646,6 +724,9 @@ struct devfreq *devfreq_add_device(struct device *dev,
devfreq->last_status.current_frequency = profile->initial_freq;
devfreq->data = data;
devfreq->nb.notifier_call = devfreq_notifier_call;
+ INIT_WORK(&devfreq->set_target_work, devfreq_set_target_work);
+ INIT_LIST_HEAD(&devfreq->userlocks.locked);
+ INIT_LIST_HEAD(&devfreq->userlocks.unlocked);
if (!devfreq->profile->max_state && !devfreq->profile->freq_table) {
mutex_unlock(&devfreq->lock);
@@ -1682,3 +1763,120 @@ void devm_devfreq_unregister_notifier(struct device *dev,
devm_devfreq_dev_match, devfreq));
}
EXPORT_SYMBOL(devm_devfreq_unregister_notifier);
+
+/**
+ * devfreq_register_dev_user_lock() - Helper function to register a user lock
+ * to devfreq
+ * @userlock: The devfreq dev user lock object.
+ * @devfreq: The devfreq object.
+ *
+ * Helper function to register the @userlock to the @devfreq device.
+ *
+ * The function moves the @userlock to the list of unlockers.
+ *
+ * Return: 0 on success or -EINVAL if arguments are invalid.
+ */
+int devfreq_register_dev_user_lock(struct devfreq_dev_userlock *userlock,
+ struct devfreq *devfreq)
+{
+ unsigned long flags;
+
+ if (!devfreq || !userlock)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devfreq->lockers_spinlock, flags);
+ userlock->deadline = 0;
+ list_add_tail(&userlock->node, &devfreq->userlocks.unlocked);
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(devfreq_register_dev_user_lock);
+
+/**
+ * devfreq_unregister_dev_user_lock() - Helper function to unregister a user
+ * lock
+ * @userlock: The devfreq dev user lock object.
+ * @devfreq: The devfreq object.
+ *
+ * Helper function to unregister the @userlock from the @devfreq device.
+ *
+ * The function removes the @userlock.
+ */
+void devfreq_unregister_dev_user_lock(struct devfreq_dev_userlock *userlock,
+ struct devfreq *devfreq)
+{
+ unsigned long flags;
+
+ if (!devfreq || !userlock)
+ return;
+
+ spin_lock_irqsave(&devfreq->lockers_spinlock, flags);
+ list_del(&userlock->node);
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, flags);
+}
+EXPORT_SYMBOL(devfreq_unregister_dev_user_lock);
+
+/**
+ * devfreq_lock_device() - Helper function to lock devfreq
+ * @devfreq: The devfreq object.
+ * @userlock: The devfreq dev user lock object.
+ *
+ * Helper function to lock the @devfreq device from changing the frequency rate.
+ * The function moves the @userlock to the list of lockers.
+ *
+ * The @userlock should be registered.
+ *
+ * Return: 0 on success or -EINVAL if arguments are invalid.
+ */
+int devfreq_lock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock)
+{
+ unsigned long flags;
+
+ if (!devfreq || !userlock)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devfreq->lockers_spinlock, flags);
+ list_move_tail(&userlock->node, &devfreq->userlocks.locked);
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(devfreq_lock_device);
+
+/**
+ * devfreq_unlock_device() - Helper function to unlock devfreq
+ * @devfreq: The devfreq object.
+ * @userlock: The devfreq dev user lock object.
+ * @deadline: The deadline.
+ *
+ * Helper function to unlock the @devfreq device. The function moves the
+ * @userlock to the list of unlockers.
+ *
+ * If @devfreq device has no more lockers in its list, it triggers the pending
+ * rate change.
+ *
+ * The @userlock should be registered.
+ */
+void devfreq_unlock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock,
+ ktime_t deadline)
+{
+ unsigned long flags;
+
+ if (!devfreq || !userlock)
+ return;
+
+ if (atomic_read(&devfreq->suspend_count) > 0)
+ return;
+
+ spin_lock_irqsave(&devfreq->lockers_spinlock, flags);
+ userlock->deadline = deadline;
+ list_move_tail(&userlock->node, &devfreq->userlocks.unlocked);
+ if (list_empty(&devfreq->userlocks.locked) &&
+ devfreq->set_target_pending)
+ queue_work(system_highpri_wq, &devfreq->set_target_work);
+ spin_unlock_irqrestore(&devfreq->lockers_spinlock, flags);
+}
+EXPORT_SYMBOL(devfreq_unlock_device);
diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h
index fbffa74bfc1b..0a577a776235 100644
--- a/include/linux/devfreq.h
+++ b/include/linux/devfreq.h
@@ -35,6 +35,7 @@
struct devfreq;
struct devfreq_governor;
+struct devfreq_dev_userlock;
/**
* struct devfreq_dev_status - Data given from devfreq user device to
@@ -109,6 +110,21 @@ struct devfreq_dev_profile {
unsigned int max_state;
};
+/**
+ * struct devfreq_dev_userlock - Devfreq's user device lock
+ * @node: list node - contains the userlocks that have been locked or
+ * unlocked.
+ * @deadline: The timestamp while the unlock is valid.
+ * @want_to_change_rate: The callback that is called for every lockers,
+ * when devfreq wants to change the frequency rate.
+ */
+struct devfreq_dev_userlock {
+ struct list_head node;
+ ktime_t deadline;
+ void (*want_to_change_rate)(struct devfreq_dev_userlock *user,
+ struct devfreq *devfreq);
+};
+
/**
* struct devfreq - Device devfreq structure
* @node: list node - contains the devices with devfreq that have been
@@ -138,6 +154,12 @@ struct devfreq_dev_profile {
* @trans_table: Statistics of devfreq transitions
* @time_in_state: Statistics of devfreq states
* @last_stat_updated: The last time stat updated
+ * @new_freq: the last frequency change before lock.
+ * @new_flags: the last flag change before lock.
+ * @set_target_pending: is a set target pending.
+ * @set_target_work: work for resuming set target.
+ * @userlocks: the lists of user locks (unlocked and locked).
+ * @lock_spinlock: a spinlock to protect accessing to lock attributes.
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
*
* This structure stores the devfreq information for a give device.
@@ -180,6 +202,16 @@ struct devfreq {
unsigned long *time_in_state;
unsigned long last_stat_updated;
+ unsigned long new_freq;
+ u32 new_flags;
+ bool set_target_pending;
+ struct work_struct set_target_work;
+ struct {
+ struct list_head unlocked;
+ struct list_head locked;
+ } userlocks;
+ spinlock_t lockers_spinlock;
+
struct srcu_notifier_head transition_notifier_list;
};
@@ -243,6 +275,15 @@ extern void devm_devfreq_unregister_notifier(struct device *dev,
unsigned int list);
extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
int index);
+extern int devfreq_register_dev_user_lock(
+ struct devfreq_dev_userlock *userlock, struct devfreq *devfreq);
+extern void devfreq_unregister_dev_user_lock(
+ struct devfreq_dev_userlock *userlock, struct devfreq *devfreq);
+extern int devfreq_lock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock);
+extern void devfreq_unlock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock,
+ ktime_t deadline);
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
/**
@@ -405,6 +446,29 @@ static inline int devfreq_update_stats(struct devfreq *df)
{
return -EINVAL;
}
+
+static inline int devfreq_register_dev_user_lock(
+ struct devfreq_dev_userlock *userlock, struct devfreq *devfreq)
+{
+ return -EINVAL;
+}
+
+static inline void devfreq_unregister_dev_user_unlock(
+ struct devfreq_dev_userlock *userlock, struct devfreq *devfreq)
+{
+}
+
+static inline int devfreq_lock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock)
+{
+ return -EINVAL;
+}
+
+static inline void devfreq_unlock_device(struct devfreq *devfreq,
+ struct devfreq_dev_userlock *userlock,
+ ktime_t deadline)
+{
+}
#endif /* CONFIG_PM_DEVFREQ */
#endif /* __LINUX_DEVFREQ_H__ */
--
2.21.0
Use the Dynamic Memory Controller as the display-subsystem's devfreq
device.
Signed-off-by: Gaël PORTAY <[email protected]>
---
arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi | 4 ++++
arch/arm64/boot/dts/rockchip/rk3399.dtsi | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi b/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
index 40e78186560b..a5cbbc08f7e1 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3399-gru.dtsi
@@ -375,6 +375,10 @@
<200000000>;
};
+&display_subsystem {
+ devfreq = <&dmc>;
+};
+
&emmc_phy {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/rockchip/rk3399.dtsi b/arch/arm64/boot/dts/rockchip/rk3399.dtsi
index 87ee084fac89..253b476163fd 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3399.dtsi
@@ -155,7 +155,7 @@
};
};
- display-subsystem {
+ display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
ports = <&vopl_out>, <&vopb_out>;
};
--
2.21.0
The Rockchip DMC devfreq driver is the only user of the Rockchip DDR
clock.
Both drivers perform SMC calls to the Trusted-Firmware A to run SiP
services related to the DDR memory.
This commit centralizes the SiP services in the DMC devfreq driver and
removes the DDR clock which becomes useless.
Signed-off-by: Gaël PORTAY <[email protected]>
---
drivers/clk/rockchip/Makefile | 1 -
drivers/clk/rockchip/clk-ddr.c | 147 ------------------------------
drivers/clk/rockchip/clk-rk3399.c | 2 -
drivers/clk/rockchip/clk.c | 9 --
drivers/clk/rockchip/clk.h | 33 -------
drivers/devfreq/rk3399_dmc.c | 42 ++++++---
6 files changed, 28 insertions(+), 206 deletions(-)
delete mode 100644 drivers/clk/rockchip/clk-ddr.c
diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile
index ff35ab463a6f..25014216c6d7 100644
--- a/drivers/clk/rockchip/Makefile
+++ b/drivers/clk/rockchip/Makefile
@@ -10,7 +10,6 @@ obj-y += clk-half-divider.o
obj-y += clk-inverter.o
obj-y += clk-mmc-phase.o
obj-y += clk-muxgrf.o
-obj-y += clk-ddr.o
obj-$(CONFIG_RESET_CONTROLLER) += softrst.o
obj-y += clk-px30.o
diff --git a/drivers/clk/rockchip/clk-ddr.c b/drivers/clk/rockchip/clk-ddr.c
deleted file mode 100644
index ebce5260068b..000000000000
--- a/drivers/clk/rockchip/clk-ddr.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (c) 2016 Rockchip Electronics Co. Ltd.
- * Author: Lin Huang <[email protected]>
- *
- * 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/arm-smccc.h>
-#include <linux/clk.h>
-#include <linux/clk-provider.h>
-#include <linux/io.h>
-#include <linux/slab.h>
-#include <soc/rockchip/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 div_shift;
- int div_width;
- int ddr_flag;
- spinlock_t *lock;
-};
-
-#define to_rockchip_ddrclk_hw(hw) container_of(hw, struct rockchip_ddrclk, hw)
-
-static int rockchip_ddrclk_sip_set_rate(struct clk_hw *hw, unsigned long drate,
- unsigned long prate)
-{
- struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
- unsigned long flags;
- struct arm_smccc_res res;
-
- spin_lock_irqsave(ddrclk->lock, flags);
- arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, drate, 0,
- ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE,
- 0, 0, 0, 0, &res);
- spin_unlock_irqrestore(ddrclk->lock, flags);
-
- return res.a0;
-}
-
-static unsigned long
-rockchip_ddrclk_sip_recalc_rate(struct clk_hw *hw,
- unsigned long parent_rate)
-{
- struct arm_smccc_res res;
-
- arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
- ROCKCHIP_SIP_CONFIG_DRAM_GET_RATE,
- 0, 0, 0, 0, &res);
-
- return res.a0;
-}
-
-static long rockchip_ddrclk_sip_round_rate(struct clk_hw *hw,
- unsigned long rate,
- unsigned long *prate)
-{
- struct arm_smccc_res res;
-
- arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, rate, 0,
- ROCKCHIP_SIP_CONFIG_DRAM_ROUND_RATE,
- 0, 0, 0, 0, &res);
-
- return res.a0;
-}
-
-static u8 rockchip_ddrclk_get_parent(struct clk_hw *hw)
-{
- struct rockchip_ddrclk *ddrclk = to_rockchip_ddrclk_hw(hw);
- u32 val;
-
- val = clk_readl(ddrclk->reg_base +
- ddrclk->mux_offset) >> ddrclk->mux_shift;
- val &= GENMASK(ddrclk->mux_width - 1, 0);
-
- return val;
-}
-
-static const struct clk_ops rockchip_ddrclk_sip_ops = {
- .recalc_rate = rockchip_ddrclk_sip_recalc_rate,
- .set_rate = rockchip_ddrclk_sip_set_rate,
- .round_rate = rockchip_ddrclk_sip_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 div_shift, int div_width,
- int ddr_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.flags = flags;
- init.flags |= CLK_SET_RATE_NO_REPARENT;
-
- switch (ddr_flag) {
- case ROCKCHIP_DDRCLK_SIP:
- init.ops = &rockchip_ddrclk_sip_ops;
- break;
- default:
- pr_err("%s: unsupported ddrclk type %d\n", __func__, ddr_flag);
- kfree(ddrclk);
- return ERR_PTR(-EINVAL);
- }
-
- 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->div_shift = div_shift;
- ddrclk->div_width = div_width;
- ddrclk->ddr_flag = ddr_flag;
-
- clk = clk_register(NULL, &ddrclk->hw);
- if (IS_ERR(clk))
- kfree(ddrclk);
-
- return clk;
-}
diff --git a/drivers/clk/rockchip/clk-rk3399.c b/drivers/clk/rockchip/clk-rk3399.c
index 5a628148f3f0..9f01463ad62e 100644
--- a/drivers/clk/rockchip/clk-rk3399.c
+++ b/drivers/clk/rockchip/clk-rk3399.c
@@ -1396,8 +1396,6 @@ static struct rockchip_clk_branch rk3399_clk_branches[] __initdata = {
2, GFLAGS),
GATE(0, "clk_ddrc_gpll_src", "gpll", 0, RK3399_CLKGATE_CON(3),
3, GFLAGS),
- COMPOSITE_DDRCLK(SCLK_DDRC, "sclk_ddrc", mux_ddrclk_p, 0,
- RK3399_CLKSEL_CON(6), 4, 2, 0, 0, ROCKCHIP_DDRCLK_SIP),
};
static struct rockchip_clk_branch rk3399_clk_pmu_branches[] __initdata = {
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c
index c3ad92965823..971fdda693f7 100644
--- a/drivers/clk/rockchip/clk.c
+++ b/drivers/clk/rockchip/clk.c
@@ -544,15 +544,6 @@ void __init rockchip_clk_register_branches(
list->gate_offset, list->gate_shift,
list->gate_flags, flags, &ctx->lock);
break;
- case branch_ddrclk:
- 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->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 6b53fff4cc96..0a486fdffa6a 100644
--- a/drivers/clk/rockchip/clk.h
+++ b/drivers/clk/rockchip/clk.h
@@ -354,20 +354,6 @@ struct clk *rockchip_clk_register_mmc(const char *name,
const char *const *parent_names, u8 num_parents,
void __iomem *reg, int shift);
-/*
- * DDRCLK flags, including method of setting the rate
- * ROCKCHIP_DDRCLK_SIP: use SIP call to bl31 to change ddrclk rate.
- */
-#define ROCKCHIP_DDRCLK_SIP BIT(0)
-
-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 div_shift, int div_width,
- int ddr_flags, void __iomem *reg_base,
- spinlock_t *lock);
-
#define ROCKCHIP_INVERTER_HIWORD_MASK BIT(0)
struct clk *rockchip_clk_register_inverter(const char *name,
@@ -392,7 +378,6 @@ enum rockchip_clk_branch_type {
branch_mmc,
branch_inverter,
branch_factor,
- branch_ddrclk,
branch_half_divider,
};
@@ -583,24 +568,6 @@ struct rockchip_clk_branch {
.child = ch, \
}
-#define COMPOSITE_DDRCLK(_id, cname, pnames, f, mo, ms, mw, \
- ds, dw, df) \
- { \
- .id = _id, \
- .branch_type = branch_ddrclk, \
- .name = cname, \
- .parent_names = pnames, \
- .num_parents = ARRAY_SIZE(pnames), \
- .flags = f, \
- .muxdiv_offset = mo, \
- .mux_shift = ms, \
- .mux_width = mw, \
- .div_shift = ds, \
- .div_width = dw, \
- .div_flags = df, \
- .gate_offset = -1, \
- }
-
#define MUX(_id, cname, pnames, f, o, s, w, mf) \
{ \
.id = _id, \
diff --git a/drivers/devfreq/rk3399_dmc.c b/drivers/devfreq/rk3399_dmc.c
index daf19e121c99..7f9c8c0cf45d 100644
--- a/drivers/devfreq/rk3399_dmc.c
+++ b/drivers/devfreq/rk3399_dmc.c
@@ -13,7 +13,6 @@
*/
#include <linux/arm-smccc.h>
-#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/devfreq.h>
#include <linux/devfreq-event.h>
@@ -67,7 +66,6 @@ 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 dram_timing timing;
@@ -79,6 +77,31 @@ struct rk3399_dmcfreq {
int odt_pd_arg0, odt_pd_arg1;
};
+static int rk3399_ddrclk_set_rate(unsigned long rate)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, rate, 0,
+ ROCKCHIP_SIP_CONFIG_DRAM_SET_RATE,
+ 0, 0, 0, 0, &res);
+
+ if (rate != res.a0 * 1000000L)
+ return -1; /* TODO which errno?*/
+
+ return 0;
+}
+
+static unsigned long rk3399_ddrclk_get_rate(void)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
+ ROCKCHIP_SIP_CONFIG_DRAM_GET_RATE,
+ 0, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
@@ -130,7 +153,7 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
}
}
- err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
+ err = rk3399_ddrclk_set_rate(target_rate);
if (err) {
dev_err(dev, "Cannot set frequency %lu (%d)\n", target_rate,
err);
@@ -145,7 +168,7 @@ static int rk3399_dmcfreq_target(struct device *dev, unsigned long *freq,
* 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);
+ dmcfreq->rate = rk3399_ddrclk_get_rate();
/* If get the incorrect rate, set voltage to old value. */
if (dmcfreq->rate != target_rate) {
@@ -338,15 +361,6 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
return PTR_ERR(data->vdd_center);
}
- data->dmc_clk = devm_clk_get(dev, "dmc_clk");
- if (IS_ERR(data->dmc_clk)) {
- if (PTR_ERR(data->dmc_clk) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
- 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;
@@ -441,7 +455,7 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
of_property_read_u32(np, "downdifferential",
&data->ondemand_data.downdifferential);
- data->rate = clk_get_rate(data->dmc_clk);
+ data->rate = rk3399_ddrclk_get_rate();
opp = devfreq_recommended_opp(dev, &data->rate, 0);
if (IS_ERR(opp)) {
--
2.21.0
This commit adds the support for devfreq to control the DDR frequency
dynamically.
Glitches affect the display when the DMC devfreq device changes the DDR
frequency during the scanout. The DRM driver synchronizes the rate
change within the VBLANK.
The VOP locks the DMC devfreq device that causes it to notified when a
rate change is wanted. Then, the VOP enables the VBLANK interrupt,
releases the lock when the interrupt arises and locks the devfreq device
again after the vblank pulse ends using a timer.
The DRM driver disables the devfreq device if more than one CRTC becomes
active.
Signed-off-by: Gaël PORTAY <[email protected]>
---
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 51 +++++-
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 6 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 177 +++++++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 3 +-
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 82 +++++++++
5 files changed, 315 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
index d7fa17f12769..ef843568a7f8 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -19,6 +19,8 @@
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
+#include <linux/devfreq.h>
+#include <linux/devfreq-event.h>
#include <linux/dma-mapping.h>
#include <linux/dma-iommu.h>
#include <linux/pm_runtime.h>
@@ -78,6 +80,46 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
iommu_detach_device(domain, dev);
}
+#if IS_ENABLED(CONFIG_ARM_RK3399_DMC_DEVFREQ)
+static int rockchip_drm_init_devfreq(struct device *dev,
+ struct rockchip_drm_private *priv)
+{
+ struct devfreq *devfreq;
+ struct devfreq_event_dev *edev;
+ int ret;
+
+ devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
+ if (IS_ERR(devfreq)) {
+ ret = PTR_ERR(devfreq);
+ if (ret == -ENODEV) {
+ DRM_DEV_INFO(dev, "devfreq missing, skip\n");
+ return 0;
+ }
+ return ret;
+ }
+
+ edev = devfreq_event_get_edev_by_phandle(devfreq->dev.parent, 0);
+ if (IS_ERR(edev)) {
+ ret = PTR_ERR(edev);
+ if (ret == -ENODEV) {
+ DRM_DEV_INFO(dev, "devfreq edev missing, skip\n");
+ return 0;
+ }
+ return ret;
+ }
+
+ priv->devfreq = devfreq;
+ priv->devfreq_event_dev = edev;
+ return 0;
+}
+#else
+static int rockchip_drm_init_devfreq(struct device *dev,
+ struct rockchip_drm_private *priv)
+{
+ return 0;
+}
+#endif
+
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
@@ -137,13 +179,19 @@ static int rockchip_drm_bind(struct device *dev)
INIT_LIST_HEAD(&private->psr_list);
mutex_init(&private->psr_list_lock);
+ ret = rockchip_drm_init_devfreq(dev, private);
+ if (ret)
+ goto err_free;
+
ret = rockchip_drm_init_iommu(drm_dev);
if (ret)
goto err_free;
drm_mode_config_init(drm_dev);
- rockchip_drm_mode_config_init(drm_dev);
+ ret = rockchip_drm_mode_config_init(drm_dev);
+ if (ret)
+ goto err_free;
/* Try to bind all sub drivers. */
ret = component_bind_all(dev, drm_dev);
@@ -201,6 +249,7 @@ static void rockchip_drm_unbind(struct device *dev)
drm_atomic_helper_shutdown(drm_dev);
component_unbind_all(dev, drm_dev);
drm_mode_config_cleanup(drm_dev);
+ rockchip_drm_mode_config_fini(drm_dev);
rockchip_iommu_cleanup(drm_dev);
drm_dev->dev_private = NULL;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index ce48568ec8a0..0ac7e31b5605 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -18,6 +18,7 @@
#define _ROCKCHIP_DRM_DRV_H
#include <drm/drm_fb_helper.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_gem.h>
@@ -57,6 +58,10 @@ struct rockchip_drm_private {
struct drm_mm mm;
struct list_head psr_list;
struct mutex psr_list_lock;
+
+ struct devfreq *devfreq;
+ struct devfreq_event_dev *devfreq_event_dev;
+ struct drm_private_obj ddrfreq_lock_manager;
};
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
@@ -66,6 +71,7 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
+uint32_t rockchip_drm_get_vblank_ns(struct drm_display_mode *mode);
extern struct platform_driver cdn_dp_driver;
extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
extern struct platform_driver dw_mipi_dsi_rockchip_driver;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 97438bbbe389..613a99c20ed3 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -13,6 +13,7 @@
*/
#include <linux/kernel.h>
+#include <linux/devfreq.h>
#include <drm/drm.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
@@ -25,6 +26,56 @@
#include "rockchip_drm_gem.h"
#include "rockchip_drm_psr.h"
+struct rockchip_ddrfreq_lock_state {
+ struct drm_private_state base;
+ int lock_counter;
+};
+
+#define to_ddrfreq_lock_state(x) container_of(x, \
+ struct rockchip_ddrfreq_lock_state, base)
+
+struct drm_private_state *rockchip_atomic_duplicate_ddrfreq_lock_state(
+ struct drm_private_obj *obj)
+{
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+
+ ddrfreq_lock_state = kmemdup(obj->state, sizeof(*ddrfreq_lock_state),
+ GFP_KERNEL);
+ if (!ddrfreq_lock_state)
+ return NULL;
+
+ __drm_atomic_helper_private_obj_duplicate_state(obj,
+ &ddrfreq_lock_state->base);
+
+ return &ddrfreq_lock_state->base;
+}
+
+void rockchip_atomic_destroy_ddrfreq_lock_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+{
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state =
+ to_ddrfreq_lock_state(state);
+
+ kfree(ddrfreq_lock_state);
+}
+
+struct drm_private_state_funcs rockchip_ddrfreq_lock_state_funcs = {
+ .atomic_duplicate_state = rockchip_atomic_duplicate_ddrfreq_lock_state,
+ .atomic_destroy_state = rockchip_atomic_destroy_ddrfreq_lock_state,
+};
+
+static struct rockchip_ddrfreq_lock_state *rockchip_get_ddrfreq_lock_state(
+ struct drm_atomic_state *state, struct drm_private_obj *obj)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_private_obj_state(state, obj);
+ if (IS_ERR(priv_state))
+ return ERR_CAST(priv_state);
+
+ return to_ddrfreq_lock_state(priv_state);
+}
+
static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
@@ -127,10 +178,80 @@ rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
return ERR_PTR(ret);
}
+static void
+rockchip_drm_psr_inhibit_get_state(struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_encoder *encoder;
+ u32 encoder_mask = 0;
+ int i;
+
+ for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
+ encoder_mask |= crtc_state->encoder_mask;
+ encoder_mask |= crtc->state->encoder_mask;
+ }
+
+ drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
+ rockchip_drm_psr_inhibit_get(encoder);
+}
+
+uint32_t rockchip_drm_get_vblank_ns(struct drm_display_mode *mode)
+{
+ uint64_t vblank_time = mode->vtotal - mode->vdisplay;
+
+ vblank_time *= (uint64_t)NSEC_PER_SEC * mode->htotal;
+ do_div(vblank_time, mode->clock * 1000);
+
+ return vblank_time;
+}
+
+static void
+rockchip_drm_psr_inhibit_put_state(struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_encoder *encoder;
+ u32 encoder_mask = 0;
+ int i;
+
+ for_each_old_crtc_in_state(state, crtc, crtc_state, i) {
+ encoder_mask |= crtc_state->encoder_mask;
+ encoder_mask |= crtc->state->encoder_mask;
+ }
+
+ drm_for_each_encoder_mask(encoder, state->dev, encoder_mask)
+ rockchip_drm_psr_inhibit_put(encoder);
+}
+
static void
rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
{
struct drm_device *dev = old_state->dev;
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ if (old_crtc_state->enable == new_crtc_state->enable)
+ continue;
+
+ ddrfreq_lock_state = rockchip_get_ddrfreq_lock_state(old_state,
+ &priv->ddrfreq_lock_manager);
+ if (IS_ERR(ddrfreq_lock_state))
+ break;
+
+ /* TODO This logic based on the previous state looks weird :/ */
+ if (old_crtc_state->enable &&
+ ddrfreq_lock_state->lock_counter == 2)
+ devfreq_resume_device(priv->devfreq);
+ else if (!old_crtc_state->enable &&
+ ddrfreq_lock_state->lock_counter == 1)
+ devfreq_suspend_device(priv->devfreq);
+ }
rockchip_drm_psr_inhibit_get_state(old_state);
@@ -154,10 +275,41 @@ static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers =
.atomic_commit_tail = rockchip_atomic_helper_commit_tail_rpm,
};
+static int rockchip_drm_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc;
+ int ret;
+ int i;
+
+ ret = drm_atomic_helper_check(dev, state);
+ if (ret == 0) {
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+ if (!crtc_state->active_changed)
+ continue;
+
+ ddrfreq_lock_state = rockchip_get_ddrfreq_lock_state(
+ state, &priv->ddrfreq_lock_manager);
+ if (IS_ERR(ddrfreq_lock_state))
+ return PTR_ERR(ddrfreq_lock_state);
+
+ if (crtc_state->enable)
+ ddrfreq_lock_state->lock_counter++;
+ else
+ ddrfreq_lock_state->lock_counter--;
+ }
+ }
+
+ return ret;
+}
+
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_user_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
- .atomic_check = drm_atomic_helper_check,
+ .atomic_check = rockchip_drm_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
};
@@ -175,8 +327,11 @@ rockchip_drm_framebuffer_init(struct drm_device *dev,
return fb;
}
-void rockchip_drm_mode_config_init(struct drm_device *dev)
+int rockchip_drm_mode_config_init(struct drm_device *dev)
{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
@@ -190,4 +345,22 @@ void rockchip_drm_mode_config_init(struct drm_device *dev)
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
+
+ ddrfreq_lock_state = kzalloc(sizeof(*ddrfreq_lock_state),
+ GFP_KERNEL);
+ if (!ddrfreq_lock_state)
+ return -ENOMEM;
+
+ drm_atomic_private_obj_init(&priv->ddrfreq_lock_manager,
+ &ddrfreq_lock_state->base,
+ &rockchip_ddrfreq_lock_state_funcs);
+
+ return 0;
+}
+
+void rockchip_drm_mode_config_fini(struct drm_device *dev)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ drm_atomic_private_obj_fini(&priv->ddrfreq_lock_manager);
}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
index f1265cb1aee8..a81df4ef8b35 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
@@ -21,5 +21,6 @@ rockchip_drm_framebuffer_init(struct drm_device *dev,
struct drm_gem_object *obj);
void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
-void rockchip_drm_mode_config_init(struct drm_device *dev);
+int rockchip_drm_mode_config_init(struct drm_device *dev);
+void rockchip_drm_mode_config_fini(struct drm_device *dev);
#endif /* _ROCKCHIP_DRM_FB_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 0d4ade9d4722..e76a68ae3991 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -36,6 +36,8 @@
#include <linux/component.h>
#include <linux/overflow.h>
+#include <linux/devfreq.h>
+
#include <linux/reset.h>
#include <linux/delay.h>
@@ -141,6 +143,12 @@ struct vop {
struct completion line_flag_completion;
+ /* devfreq locking */
+ spinlock_t devfreq_lock;
+ bool want_to_change_rate;
+ struct devfreq_dev_userlock devfreq_userlock;
+ struct hrtimer vblank_timer;
+
const struct vop_data *data;
uint32_t *regsbak;
@@ -632,9 +640,12 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct vop *vop = to_vop(crtc);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
WARN_ON(vop->event);
+ devfreq_unlock_device(priv->devfreq, &vop->devfreq_userlock, 0);
+
mutex_lock(&vop->vop_lock);
drm_crtc_vblank_off(crtc);
@@ -1028,6 +1039,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
{
struct vop *vop = to_vop(crtc);
const struct vop_data *vop_data = vop->data;
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state);
struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
@@ -1123,6 +1135,11 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
VOP_REG_SET(vop, common, standby, 0);
mutex_unlock(&vop->vop_lock);
+
+ ret = devfreq_lock_device(priv->devfreq, &vop->devfreq_userlock);
+ if (ret)
+ DRM_DEV_ERROR(vop->dev,
+ "cannot lock devfreq - err %d\n", ret);
}
static bool vop_fs_irq_is_pending(struct vop *vop)
@@ -1349,10 +1366,44 @@ static void vop_handle_vblank(struct vop *vop)
drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq);
}
+static enum hrtimer_restart vblank_timer_function(struct hrtimer *timer)
+{
+ struct vop *vop = container_of(timer, struct vop, vblank_timer);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
+ struct devfreq *devfreq = priv->devfreq;
+
+ drm_crtc_vblank_put(&vop->crtc);
+
+ devfreq_lock_device(devfreq, &vop->devfreq_userlock);
+
+ spin_lock(&vop->devfreq_lock);
+ vop->want_to_change_rate = false;
+ spin_unlock(&vop->devfreq_lock);
+
+ return HRTIMER_NORESTART;
+}
+
+static void want_to_change_rate(struct devfreq_dev_userlock *userlock,
+ struct devfreq *devfreq)
+{
+ struct vop *vop = container_of(userlock, struct vop, devfreq_userlock);
+ int err;
+
+ spin_lock(&vop->devfreq_lock);
+ vop->want_to_change_rate = true;
+ spin_unlock(&vop->devfreq_lock);
+
+ err = drm_crtc_vblank_get(&vop->crtc);
+ if (err)
+ DRM_DEV_ERROR(vop->dev, "couldn't get vblank %d\n", err);
+}
+
static irqreturn_t vop_isr(int irq, void *data)
{
struct vop *vop = data;
struct drm_crtc *crtc = &vop->crtc;
+ struct rockchip_drm_private *priv = crtc->dev->dev_private;
+ ktime_t vblank_timeout;
uint32_t active_irqs;
int ret = IRQ_NONE;
@@ -1398,6 +1449,19 @@ static irqreturn_t vop_isr(int irq, void *data)
}
if (active_irqs & FS_INTR) {
+ spin_lock(&vop->devfreq_lock);
+ if (vop->want_to_change_rate) {
+ vop->want_to_change_rate = false;
+ vblank_timeout = ktime_add_ns(ktime_get(),
+ rockchip_drm_get_vblank_ns(&crtc->mode));
+ hrtimer_start(&vop->vblank_timer, vblank_timeout,
+ HRTIMER_MODE_ABS);
+ devfreq_unlock_device(priv->devfreq,
+ &vop->devfreq_userlock,
+ vblank_timeout);
+ }
+ spin_unlock(&vop->devfreq_lock);
+
drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop);
active_irqs &= ~FS_INTR;
@@ -1746,6 +1810,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
const struct vop_data *vop_data;
+ struct rockchip_drm_private *priv;
struct drm_device *drm_dev = data;
struct vop *vop;
struct resource *res;
@@ -1815,6 +1880,20 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
}
}
+ hrtimer_init(&vop->vblank_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+ vop->vblank_timer.function = vblank_timer_function;
+
+ INIT_LIST_HEAD(&vop->devfreq_userlock.node);
+ vop->devfreq_userlock.want_to_change_rate = want_to_change_rate;
+
+ priv = vop->crtc.dev->dev_private;
+ ret = devfreq_register_dev_user_lock(&vop->devfreq_userlock,
+ priv->devfreq);
+ if (ret)
+ DRM_DEV_ERROR(&pdev->dev,
+ "cannot register to devfreq user lock - err %d\n",
+ ret);
+
return 0;
err_disable_pm_runtime:
@@ -1826,6 +1905,9 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
static void vop_unbind(struct device *dev, struct device *master, void *data)
{
struct vop *vop = dev_get_drvdata(dev);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
+
+ devfreq_unregister_dev_user_lock(&vop->devfreq_userlock, priv->devfreq);
if (vop->rgb)
rockchip_rgb_fini(vop->rgb);
--
2.21.0
This commit adds the support for devfreq to control the DDR frequency
dynamically.
Glitches affect the display when the DMC devfreq device changes the DDR
frequency during the scanout. The DRM driver synchronizes the rate
change within the VBLANK.
The VOP locks the DMC devfreq device that causes it to notified when a
rate change is wanted. Then, the VOP enables the VBLANK interrupt,
releases the lock when the interrupt arises and locks the devfreq device
again after the vblank pulse ends using a timer.
The DRM driver disables the devfreq device if more than one CRTC becomes
active.
Signed-off-by: Ga?l PORTAY <[email protected]>
---
All,
I forgot to apply the fixup commit (that fixes the rebase on 5.1) when I
sent the patch.
Here is what the commit looks like.
Sorry for the noise.
Regards,
Ga?l
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 51 ++++++-
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 6 +
drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 142 +++++++++++++++++++-
drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 3 +-
drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 82 +++++++++++
5 files changed, 280 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
index d7fa17f12769..ef843568a7f8 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c
@@ -19,6 +19,8 @@
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
+#include <linux/devfreq.h>
+#include <linux/devfreq-event.h>
#include <linux/dma-mapping.h>
#include <linux/dma-iommu.h>
#include <linux/pm_runtime.h>
@@ -78,6 +80,46 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
iommu_detach_device(domain, dev);
}
+#if IS_ENABLED(CONFIG_ARM_RK3399_DMC_DEVFREQ)
+static int rockchip_drm_init_devfreq(struct device *dev,
+ struct rockchip_drm_private *priv)
+{
+ struct devfreq *devfreq;
+ struct devfreq_event_dev *edev;
+ int ret;
+
+ devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
+ if (IS_ERR(devfreq)) {
+ ret = PTR_ERR(devfreq);
+ if (ret == -ENODEV) {
+ DRM_DEV_INFO(dev, "devfreq missing, skip\n");
+ return 0;
+ }
+ return ret;
+ }
+
+ edev = devfreq_event_get_edev_by_phandle(devfreq->dev.parent, 0);
+ if (IS_ERR(edev)) {
+ ret = PTR_ERR(edev);
+ if (ret == -ENODEV) {
+ DRM_DEV_INFO(dev, "devfreq edev missing, skip\n");
+ return 0;
+ }
+ return ret;
+ }
+
+ priv->devfreq = devfreq;
+ priv->devfreq_event_dev = edev;
+ return 0;
+}
+#else
+static int rockchip_drm_init_devfreq(struct device *dev,
+ struct rockchip_drm_private *priv)
+{
+ return 0;
+}
+#endif
+
static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
{
struct rockchip_drm_private *private = drm_dev->dev_private;
@@ -137,13 +179,19 @@ static int rockchip_drm_bind(struct device *dev)
INIT_LIST_HEAD(&private->psr_list);
mutex_init(&private->psr_list_lock);
+ ret = rockchip_drm_init_devfreq(dev, private);
+ if (ret)
+ goto err_free;
+
ret = rockchip_drm_init_iommu(drm_dev);
if (ret)
goto err_free;
drm_mode_config_init(drm_dev);
- rockchip_drm_mode_config_init(drm_dev);
+ ret = rockchip_drm_mode_config_init(drm_dev);
+ if (ret)
+ goto err_free;
/* Try to bind all sub drivers. */
ret = component_bind_all(dev, drm_dev);
@@ -201,6 +249,7 @@ static void rockchip_drm_unbind(struct device *dev)
drm_atomic_helper_shutdown(drm_dev);
component_unbind_all(dev, drm_dev);
drm_mode_config_cleanup(drm_dev);
+ rockchip_drm_mode_config_fini(drm_dev);
rockchip_iommu_cleanup(drm_dev);
drm_dev->dev_private = NULL;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
index ce48568ec8a0..0ac7e31b5605 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h
@@ -18,6 +18,7 @@
#define _ROCKCHIP_DRM_DRV_H
#include <drm/drm_fb_helper.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_gem.h>
@@ -57,6 +58,10 @@ struct rockchip_drm_private {
struct drm_mm mm;
struct list_head psr_list;
struct mutex psr_list_lock;
+
+ struct devfreq *devfreq;
+ struct devfreq_event_dev *devfreq_event_dev;
+ struct drm_private_obj ddrfreq_lock_manager;
};
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
@@ -66,6 +71,7 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout);
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep);
+uint32_t rockchip_drm_get_vblank_ns(struct drm_display_mode *mode);
extern struct platform_driver cdn_dp_driver;
extern struct platform_driver dw_hdmi_rockchip_pltfm_driver;
extern struct platform_driver dw_mipi_dsi_rockchip_driver;
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
index 97438bbbe389..f508d5558e16 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c
@@ -13,6 +13,7 @@
*/
#include <linux/kernel.h>
+#include <linux/devfreq.h>
#include <drm/drm.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
@@ -25,6 +26,56 @@
#include "rockchip_drm_gem.h"
#include "rockchip_drm_psr.h"
+struct rockchip_ddrfreq_lock_state {
+ struct drm_private_state base;
+ int lock_counter;
+};
+
+#define to_ddrfreq_lock_state(x) container_of(x, \
+ struct rockchip_ddrfreq_lock_state, base)
+
+struct drm_private_state *rockchip_atomic_duplicate_ddrfreq_lock_state(
+ struct drm_private_obj *obj)
+{
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+
+ ddrfreq_lock_state = kmemdup(obj->state, sizeof(*ddrfreq_lock_state),
+ GFP_KERNEL);
+ if (!ddrfreq_lock_state)
+ return NULL;
+
+ __drm_atomic_helper_private_obj_duplicate_state(obj,
+ &ddrfreq_lock_state->base);
+
+ return &ddrfreq_lock_state->base;
+}
+
+void rockchip_atomic_destroy_ddrfreq_lock_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+{
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state =
+ to_ddrfreq_lock_state(state);
+
+ kfree(ddrfreq_lock_state);
+}
+
+struct drm_private_state_funcs rockchip_ddrfreq_lock_state_funcs = {
+ .atomic_duplicate_state = rockchip_atomic_duplicate_ddrfreq_lock_state,
+ .atomic_destroy_state = rockchip_atomic_destroy_ddrfreq_lock_state,
+};
+
+static struct rockchip_ddrfreq_lock_state *rockchip_get_ddrfreq_lock_state(
+ struct drm_atomic_state *state, struct drm_private_obj *obj)
+{
+ struct drm_private_state *priv_state;
+
+ priv_state = drm_atomic_get_private_obj_state(state, obj);
+ if (IS_ERR(priv_state))
+ return ERR_CAST(priv_state);
+
+ return to_ddrfreq_lock_state(priv_state);
+}
+
static int rockchip_drm_fb_dirty(struct drm_framebuffer *fb,
struct drm_file *file,
unsigned int flags, unsigned int color,
@@ -127,10 +178,44 @@ rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
return ERR_PTR(ret);
}
+uint32_t rockchip_drm_get_vblank_ns(struct drm_display_mode *mode)
+{
+ uint64_t vblank_time = mode->vtotal - mode->vdisplay;
+
+ vblank_time *= (uint64_t)NSEC_PER_SEC * mode->htotal;
+ do_div(vblank_time, mode->clock * 1000);
+
+ return vblank_time;
+}
+
static void
rockchip_atomic_helper_commit_tail_rpm(struct drm_atomic_state *old_state)
{
struct drm_device *dev = old_state->dev;
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ if (old_crtc_state->enable == new_crtc_state->enable)
+ continue;
+
+ ddrfreq_lock_state = rockchip_get_ddrfreq_lock_state(old_state,
+ &priv->ddrfreq_lock_manager);
+ if (IS_ERR(ddrfreq_lock_state))
+ break;
+
+ /* TODO This logic based on the previous state looks weird :/ */
+ if (old_crtc_state->enable &&
+ ddrfreq_lock_state->lock_counter == 2)
+ devfreq_resume_device(priv->devfreq);
+ else if (!old_crtc_state->enable &&
+ ddrfreq_lock_state->lock_counter == 1)
+ devfreq_suspend_device(priv->devfreq);
+ }
rockchip_drm_psr_inhibit_get_state(old_state);
@@ -154,10 +239,41 @@ static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers =
.atomic_commit_tail = rockchip_atomic_helper_commit_tail_rpm,
};
+static int rockchip_drm_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_crtc *crtc;
+ int ret;
+ int i;
+
+ ret = drm_atomic_helper_check(dev, state);
+ if (ret == 0) {
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+ if (!crtc_state->active_changed)
+ continue;
+
+ ddrfreq_lock_state = rockchip_get_ddrfreq_lock_state(
+ state, &priv->ddrfreq_lock_manager);
+ if (IS_ERR(ddrfreq_lock_state))
+ return PTR_ERR(ddrfreq_lock_state);
+
+ if (crtc_state->enable)
+ ddrfreq_lock_state->lock_counter++;
+ else
+ ddrfreq_lock_state->lock_counter--;
+ }
+ }
+
+ return ret;
+}
+
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_user_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
- .atomic_check = drm_atomic_helper_check,
+ .atomic_check = rockchip_drm_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
};
@@ -175,8 +291,11 @@ rockchip_drm_framebuffer_init(struct drm_device *dev,
return fb;
}
-void rockchip_drm_mode_config_init(struct drm_device *dev)
+int rockchip_drm_mode_config_init(struct drm_device *dev)
{
+ struct rockchip_drm_private *priv = dev->dev_private;
+ struct rockchip_ddrfreq_lock_state *ddrfreq_lock_state;
+
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
@@ -190,4 +309,23 @@ void rockchip_drm_mode_config_init(struct drm_device *dev)
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
dev->mode_config.helper_private = &rockchip_mode_config_helpers;
+
+ ddrfreq_lock_state = kzalloc(sizeof(*ddrfreq_lock_state),
+ GFP_KERNEL);
+ if (!ddrfreq_lock_state)
+ return -ENOMEM;
+
+ drm_atomic_private_obj_init(dev,
+ &priv->ddrfreq_lock_manager,
+ &ddrfreq_lock_state->base,
+ &rockchip_ddrfreq_lock_state_funcs);
+
+ return 0;
+}
+
+void rockchip_drm_mode_config_fini(struct drm_device *dev)
+{
+ struct rockchip_drm_private *priv = dev->dev_private;
+
+ drm_atomic_private_obj_fini(&priv->ddrfreq_lock_manager);
}
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
index f1265cb1aee8..a81df4ef8b35 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h
@@ -21,5 +21,6 @@ rockchip_drm_framebuffer_init(struct drm_device *dev,
struct drm_gem_object *obj);
void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb);
-void rockchip_drm_mode_config_init(struct drm_device *dev);
+int rockchip_drm_mode_config_init(struct drm_device *dev);
+void rockchip_drm_mode_config_fini(struct drm_device *dev);
#endif /* _ROCKCHIP_DRM_FB_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
index 0d4ade9d4722..e76a68ae3991 100644
--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -36,6 +36,8 @@
#include <linux/component.h>
#include <linux/overflow.h>
+#include <linux/devfreq.h>
+
#include <linux/reset.h>
#include <linux/delay.h>
@@ -141,6 +143,12 @@ struct vop {
struct completion line_flag_completion;
+ /* devfreq locking */
+ spinlock_t devfreq_lock;
+ bool want_to_change_rate;
+ struct devfreq_dev_userlock devfreq_userlock;
+ struct hrtimer vblank_timer;
+
const struct vop_data *data;
uint32_t *regsbak;
@@ -632,9 +640,12 @@ static void vop_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct vop *vop = to_vop(crtc);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
WARN_ON(vop->event);
+ devfreq_unlock_device(priv->devfreq, &vop->devfreq_userlock, 0);
+
mutex_lock(&vop->vop_lock);
drm_crtc_vblank_off(crtc);
@@ -1028,6 +1039,7 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
{
struct vop *vop = to_vop(crtc);
const struct vop_data *vop_data = vop->data;
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc->state);
struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode;
u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start;
@@ -1123,6 +1135,11 @@ static void vop_crtc_atomic_enable(struct drm_crtc *crtc,
VOP_REG_SET(vop, common, standby, 0);
mutex_unlock(&vop->vop_lock);
+
+ ret = devfreq_lock_device(priv->devfreq, &vop->devfreq_userlock);
+ if (ret)
+ DRM_DEV_ERROR(vop->dev,
+ "cannot lock devfreq - err %d\n", ret);
}
static bool vop_fs_irq_is_pending(struct vop *vop)
@@ -1349,10 +1366,44 @@ static void vop_handle_vblank(struct vop *vop)
drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq);
}
+static enum hrtimer_restart vblank_timer_function(struct hrtimer *timer)
+{
+ struct vop *vop = container_of(timer, struct vop, vblank_timer);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
+ struct devfreq *devfreq = priv->devfreq;
+
+ drm_crtc_vblank_put(&vop->crtc);
+
+ devfreq_lock_device(devfreq, &vop->devfreq_userlock);
+
+ spin_lock(&vop->devfreq_lock);
+ vop->want_to_change_rate = false;
+ spin_unlock(&vop->devfreq_lock);
+
+ return HRTIMER_NORESTART;
+}
+
+static void want_to_change_rate(struct devfreq_dev_userlock *userlock,
+ struct devfreq *devfreq)
+{
+ struct vop *vop = container_of(userlock, struct vop, devfreq_userlock);
+ int err;
+
+ spin_lock(&vop->devfreq_lock);
+ vop->want_to_change_rate = true;
+ spin_unlock(&vop->devfreq_lock);
+
+ err = drm_crtc_vblank_get(&vop->crtc);
+ if (err)
+ DRM_DEV_ERROR(vop->dev, "couldn't get vblank %d\n", err);
+}
+
static irqreturn_t vop_isr(int irq, void *data)
{
struct vop *vop = data;
struct drm_crtc *crtc = &vop->crtc;
+ struct rockchip_drm_private *priv = crtc->dev->dev_private;
+ ktime_t vblank_timeout;
uint32_t active_irqs;
int ret = IRQ_NONE;
@@ -1398,6 +1449,19 @@ static irqreturn_t vop_isr(int irq, void *data)
}
if (active_irqs & FS_INTR) {
+ spin_lock(&vop->devfreq_lock);
+ if (vop->want_to_change_rate) {
+ vop->want_to_change_rate = false;
+ vblank_timeout = ktime_add_ns(ktime_get(),
+ rockchip_drm_get_vblank_ns(&crtc->mode));
+ hrtimer_start(&vop->vblank_timer, vblank_timeout,
+ HRTIMER_MODE_ABS);
+ devfreq_unlock_device(priv->devfreq,
+ &vop->devfreq_userlock,
+ vblank_timeout);
+ }
+ spin_unlock(&vop->devfreq_lock);
+
drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop);
active_irqs &= ~FS_INTR;
@@ -1746,6 +1810,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
const struct vop_data *vop_data;
+ struct rockchip_drm_private *priv;
struct drm_device *drm_dev = data;
struct vop *vop;
struct resource *res;
@@ -1815,6 +1880,20 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
}
}
+ hrtimer_init(&vop->vblank_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+ vop->vblank_timer.function = vblank_timer_function;
+
+ INIT_LIST_HEAD(&vop->devfreq_userlock.node);
+ vop->devfreq_userlock.want_to_change_rate = want_to_change_rate;
+
+ priv = vop->crtc.dev->dev_private;
+ ret = devfreq_register_dev_user_lock(&vop->devfreq_userlock,
+ priv->devfreq);
+ if (ret)
+ DRM_DEV_ERROR(&pdev->dev,
+ "cannot register to devfreq user lock - err %d\n",
+ ret);
+
return 0;
err_disable_pm_runtime:
@@ -1826,6 +1905,9 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
static void vop_unbind(struct device *dev, struct device *master, void *data)
{
struct vop *vop = dev_get_drvdata(dev);
+ struct rockchip_drm_private *priv = vop->crtc.dev->dev_private;
+
+ devfreq_unregister_dev_user_lock(&vop->devfreq_userlock, priv->devfreq);
if (vop->rgb)
rockchip_rgb_fini(vop->rgb);
--
2.21.0