1937 lines
49 KiB
Diff
1937 lines
49 KiB
Diff
From e7c97c57e2d5040e90662459239bc28c8ea89be5 Mon Sep 17 00:00:00 2001
|
|
From: Paolo Sabatino <paolo.sabatino@gmail.com>
|
|
Date: Wed, 7 Jul 2021 19:27:03 +0000
|
|
Subject: [PATCH] rk3228/rk3328: add dmc driver
|
|
|
|
---
|
|
arch/arm/boot/dts/rk322x.dtsi | 69 +-
|
|
drivers/devfreq/Kconfig | 24 +
|
|
drivers/devfreq/Makefile | 2 +
|
|
drivers/devfreq/rk3228_dmc.c | 623 ++++++++++++++
|
|
drivers/devfreq/rk3328_dmc.c | 846 +++++++++++++++++++
|
|
include/dt-bindings/clock/rockchip-ddr.h | 63 ++
|
|
include/dt-bindings/memory/rockchip,rk322x.h | 90 ++
|
|
7 files changed, 1714 insertions(+), 3 deletions(-)
|
|
create mode 100644 drivers/devfreq/rk3228_dmc.c
|
|
create mode 100644 drivers/devfreq/rk3328_dmc.c
|
|
create mode 100644 include/dt-bindings/clock/rockchip-ddr.h
|
|
create mode 100644 include/dt-bindings/memory/rockchip,rk322x.h
|
|
|
|
diff --git a/arch/arm/boot/dts/rk322x.dtsi b/arch/arm/boot/dts/rk322x.dtsi
|
|
index 88e33eb11..22da2e3cd 100644
|
|
--- a/arch/arm/boot/dts/rk322x.dtsi
|
|
+++ b/arch/arm/boot/dts/rk322x.dtsi
|
|
@@ -7,6 +7,8 @@
|
|
#include <dt-bindings/clock/rk3228-cru.h>
|
|
#include <dt-bindings/thermal/thermal.h>
|
|
#include <dt-bindings/power/rk3228-power.h>
|
|
+#include <dt-bindings/clock/rockchip-ddr.h>
|
|
+#include <dt-bindings/memory/rockchip,rk322x.h>
|
|
|
|
/ {
|
|
#address-cells = <1>;
|
|
@@ -106,6 +106,68 @@ dfi: dfi@11210000 {
|
|
status = "okay";
|
|
};
|
|
|
|
+ dmc: dmc@11200000 {
|
|
+ compatible = "rockchip,rk3228-dmc", "rockchip,rk322x-dram";
|
|
+ reg = <0x11200000 0x400>;
|
|
+ clocks = <&cru SCLK_DDRCLK>;
|
|
+ clock-names = "ddr_sclk";
|
|
+ operating-points-v2 = <&dmc_opp_table>;
|
|
+ rockchip,dram_timing = <&dram_timing>;
|
|
+ rockchip,grf = <&grf>;
|
|
+ devfreq-events = <&dfi>;
|
|
+ upthreshold = <15>;
|
|
+ downdifferential = <10>;
|
|
+ #cooling-cells = <2>;
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
+ dmc_opp_table: dmc-opp-table {
|
|
+ compatible = "operating-points-v2";
|
|
+
|
|
+ opp-330000000 {
|
|
+ opp-hz = /bits/ 64 <330000000>;
|
|
+ opp-microvolt = <1050000 1000000 1200000>;
|
|
+ };
|
|
+ opp-534000000 {
|
|
+ opp-hz = /bits/ 64 <534000000>;
|
|
+ opp-microvolt = <1050000 1000000 1200000>;
|
|
+ };
|
|
+ opp-660000000 {
|
|
+ opp-hz = /bits/ 64 <660000000>;
|
|
+ opp-microvolt = <1100000 1000000 1200000>;
|
|
+ };
|
|
+ opp-786000000 {
|
|
+ opp-hz = /bits/ 64 <786000000>;
|
|
+ opp-microvolt = <1150000 1000000 1200000>;
|
|
+ status = "disabled";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ dram_timing: dram-timing {
|
|
+ compatible = "rockchip,dram-timing";
|
|
+ dram_spd_bin = <DDR3_DEFAULT>;
|
|
+ sr_idle = <0x18>;
|
|
+ pd_idle = <0x20>;
|
|
+ dram_dll_disb_freq = <300>;
|
|
+ phy_dll_disb_freq = <400>;
|
|
+ dram_odt_disb_freq = <333>;
|
|
+ phy_odt_disb_freq = <333>;
|
|
+ ddr3_drv = <DDR3_DS_40ohm>;
|
|
+ ddr3_odt = <DDR3_ODT_120ohm>;
|
|
+ lpddr3_drv = <LP3_DS_34ohm>;
|
|
+ lpddr3_odt = <LP3_ODT_240ohm>;
|
|
+ lpddr2_drv = <LP2_DS_34ohm>;
|
|
+ /* lpddr2 not supported odt */
|
|
+ phy_ddr3_clk_drv = <PHY_DDR3_RON_RTT_45ohm>;
|
|
+ phy_ddr3_cmd_drv = <PHY_DDR3_RON_RTT_45ohm>;
|
|
+ phy_ddr3_dqs_drv = <PHY_DDR3_RON_RTT_34ohm>;
|
|
+ phy_ddr3_odt = <PHY_DDR3_RON_RTT_225ohm>;
|
|
+ phy_lp23_clk_drv = <PHY_LP23_RON_RTT_43ohm>;
|
|
+ phy_lp23_cmd_drv = <PHY_LP23_RON_RTT_34ohm>;
|
|
+ phy_lp23_dqs_drv = <PHY_LP23_RON_RTT_34ohm>;
|
|
+ phy_lp3_odt = <PHY_LP23_RON_RTT_240ohm>;
|
|
+ };
|
|
+
|
|
arm-pmu {
|
|
compatible = "arm,cortex-a7-pmu";
|
|
interrupts = <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>,
|
|
@@ -673,17 +736,17 @@ gpu_opp_table: opp-table2 {
|
|
|
|
opp-200000000 {
|
|
opp-hz = /bits/ 64 <200000000>;
|
|
- opp-microvolt = <1050000>;
|
|
+ opp-microvolt = <1050000 1000000 1200000>;
|
|
};
|
|
|
|
opp-300000000 {
|
|
opp-hz = /bits/ 64 <300000000>;
|
|
- opp-microvolt = <1050000>;
|
|
+ opp-microvolt = <1050000 1000000 1200000>;
|
|
};
|
|
|
|
opp-500000000 {
|
|
opp-hz = /bits/ 64 <500000000>;
|
|
- opp-microvolt = <1150000>;
|
|
+ opp-microvolt = <1150000 1000000 1200000>;
|
|
};
|
|
};
|
|
|
|
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
|
|
index 37dc40d1f..5f864a855 100644
|
|
--- a/drivers/devfreq/Kconfig
|
|
+++ b/drivers/devfreq/Kconfig
|
|
@@ -131,6 +131,30 @@ config ARM_TEGRA20_DEVFREQ
|
|
It reads Memory Controller counters and adjusts the operating
|
|
frequencies and voltages with OPP support.
|
|
|
|
+config ARM_RK3328_DMC_DEVFREQ
|
|
+ tristate "ARM RK3328 DMC DEVFREQ Driver"
|
|
+ depends on ARCH_ROCKCHIP
|
|
+ select DEVFREQ_EVENT_ROCKCHIP_DFI
|
|
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
|
+ select PM_DEVFREQ_EVENT
|
|
+ select PM_OPP
|
|
+ help
|
|
+ This adds the DEVFREQ driver for the RK3328 DMC(Dynamic Memory Controller).
|
|
+ It sets the frequency for the memory controller and reads the usage counts
|
|
+ from hardware.
|
|
+
|
|
+config ARM_RK3228_DMC_DEVFREQ
|
|
+ tristate "ARM RK3228 DMC DEVFREQ Driver"
|
|
+ depends on ARCH_ROCKCHIP
|
|
+ select DEVFREQ_EVENT_ROCKCHIP_DFI
|
|
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
|
+ select PM_DEVFREQ_EVENT
|
|
+ select PM_OPP
|
|
+ help
|
|
+ This adds the DEVFREQ driver for the RK3228 DMC(Dynamic Memory Controller).
|
|
+ It sets the frequency for the memory controller and reads the usage counts
|
|
+ from hardware.
|
|
+
|
|
config ARM_RK3399_DMC_DEVFREQ
|
|
tristate "ARM RK3399 DMC DEVFREQ Driver"
|
|
depends on (ARCH_ROCKCHIP && HAVE_ARM_SMCCC) || \
|
|
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
|
|
index 3ca1ad0ec..7d6d7be25 100644
|
|
--- a/drivers/devfreq/Makefile
|
|
+++ b/drivers/devfreq/Makefile
|
|
@@ -12,6 +12,8 @@ obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
|
|
obj-$(CONFIG_ARM_IMX_BUS_DEVFREQ) += imx-bus.o
|
|
obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ) += imx8m-ddrc.o
|
|
obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o
|
|
+obj-$(CONFIG_ARM_RK3328_DMC_DEVFREQ) += rk3328_dmc.o
|
|
+obj-$(CONFIG_ARM_RK3228_DMC_DEVFREQ) += rk3228_dmc.o
|
|
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o
|
|
obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o
|
|
|
|
diff --git a/drivers/devfreq/rk3328_dmc.c b/drivers/devfreq/rk3328_dmc.c
|
|
new file mode 100644
|
|
index 000000000..5dcea91cf
|
|
--- /dev/null
|
|
+++ b/drivers/devfreq/rk3328_dmc.c
|
|
@@ -0,0 +1,846 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd.
|
|
+ * Author: Lin Huang <hl@rock-chips.com>
|
|
+ */
|
|
+
|
|
+#include <linux/arm-smccc.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/devfreq.h>
|
|
+#include <linux/devfreq-event.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_opp.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/rwsem.h>
|
|
+#include <linux/suspend.h>
|
|
+
|
|
+#include <soc/rockchip/rockchip_sip.h>
|
|
+
|
|
+#define DTS_PAR_OFFSET (4096)
|
|
+
|
|
+struct share_params {
|
|
+ u32 hz;
|
|
+ u32 lcdc_type;
|
|
+ u32 vop;
|
|
+ u32 vop_dclk_mode;
|
|
+ u32 sr_idle_en;
|
|
+ u32 addr_mcu_el3;
|
|
+ /*
|
|
+ * 1: need to wait flag1
|
|
+ * 0: never wait flag1
|
|
+ */
|
|
+ u32 wait_flag1;
|
|
+ /*
|
|
+ * 1: need to wait flag1
|
|
+ * 0: never wait flag1
|
|
+ */
|
|
+ u32 wait_flag0;
|
|
+ u32 complt_hwirq;
|
|
+ /* if need, add parameter after */
|
|
+};
|
|
+
|
|
+static struct share_params *ddr_psci_param;
|
|
+
|
|
+/* hope this define can adapt all future platform */
|
|
+static const char * const rk3328_dts_timing[] = {
|
|
+ "ddr3_speed_bin",
|
|
+ "ddr4_speed_bin",
|
|
+ "pd_idle",
|
|
+ "sr_idle",
|
|
+ "sr_mc_gate_idle",
|
|
+ "srpd_lite_idle",
|
|
+ "standby_idle",
|
|
+
|
|
+ "auto_pd_dis_freq",
|
|
+ "auto_sr_dis_freq",
|
|
+ "ddr3_dll_dis_freq",
|
|
+ "ddr4_dll_dis_freq",
|
|
+ "phy_dll_dis_freq",
|
|
+
|
|
+ "ddr3_odt_dis_freq",
|
|
+ "phy_ddr3_odt_dis_freq",
|
|
+ "ddr3_drv",
|
|
+ "ddr3_odt",
|
|
+ "phy_ddr3_ca_drv",
|
|
+ "phy_ddr3_ck_drv",
|
|
+ "phy_ddr3_dq_drv",
|
|
+ "phy_ddr3_odt",
|
|
+
|
|
+ "lpddr3_odt_dis_freq",
|
|
+ "phy_lpddr3_odt_dis_freq",
|
|
+ "lpddr3_drv",
|
|
+ "lpddr3_odt",
|
|
+ "phy_lpddr3_ca_drv",
|
|
+ "phy_lpddr3_ck_drv",
|
|
+ "phy_lpddr3_dq_drv",
|
|
+ "phy_lpddr3_odt",
|
|
+
|
|
+ "lpddr4_odt_dis_freq",
|
|
+ "phy_lpddr4_odt_dis_freq",
|
|
+ "lpddr4_drv",
|
|
+ "lpddr4_dq_odt",
|
|
+ "lpddr4_ca_odt",
|
|
+ "phy_lpddr4_ca_drv",
|
|
+ "phy_lpddr4_ck_cs_drv",
|
|
+ "phy_lpddr4_dq_drv",
|
|
+ "phy_lpddr4_odt",
|
|
+
|
|
+ "ddr4_odt_dis_freq",
|
|
+ "phy_ddr4_odt_dis_freq",
|
|
+ "ddr4_drv",
|
|
+ "ddr4_odt",
|
|
+ "phy_ddr4_ca_drv",
|
|
+ "phy_ddr4_ck_drv",
|
|
+ "phy_ddr4_dq_drv",
|
|
+ "phy_ddr4_odt",
|
|
+};
|
|
+
|
|
+static const char * const rk3328_dts_ca_timing[] = {
|
|
+ "ddr3a1_ddr4a9_de-skew",
|
|
+ "ddr3a0_ddr4a10_de-skew",
|
|
+ "ddr3a3_ddr4a6_de-skew",
|
|
+ "ddr3a2_ddr4a4_de-skew",
|
|
+ "ddr3a5_ddr4a8_de-skew",
|
|
+ "ddr3a4_ddr4a5_de-skew",
|
|
+ "ddr3a7_ddr4a11_de-skew",
|
|
+ "ddr3a6_ddr4a7_de-skew",
|
|
+ "ddr3a9_ddr4a0_de-skew",
|
|
+ "ddr3a8_ddr4a13_de-skew",
|
|
+ "ddr3a11_ddr4a3_de-skew",
|
|
+ "ddr3a10_ddr4cs0_de-skew",
|
|
+ "ddr3a13_ddr4a2_de-skew",
|
|
+ "ddr3a12_ddr4ba1_de-skew",
|
|
+ "ddr3a15_ddr4odt0_de-skew",
|
|
+ "ddr3a14_ddr4a1_de-skew",
|
|
+ "ddr3ba1_ddr4a15_de-skew",
|
|
+ "ddr3ba0_ddr4bg0_de-skew",
|
|
+ "ddr3ras_ddr4cke_de-skew",
|
|
+ "ddr3ba2_ddr4ba0_de-skew",
|
|
+ "ddr3we_ddr4bg1_de-skew",
|
|
+ "ddr3cas_ddr4a12_de-skew",
|
|
+ "ddr3ckn_ddr4ckn_de-skew",
|
|
+ "ddr3ckp_ddr4ckp_de-skew",
|
|
+ "ddr3cke_ddr4a16_de-skew",
|
|
+ "ddr3odt0_ddr4a14_de-skew",
|
|
+ "ddr3cs0_ddr4act_de-skew",
|
|
+ "ddr3reset_ddr4reset_de-skew",
|
|
+ "ddr3cs1_ddr4cs1_de-skew",
|
|
+ "ddr3odt1_ddr4odt1_de-skew",
|
|
+};
|
|
+
|
|
+static const char * const rk3328_dts_cs0_timing[] = {
|
|
+ "cs0_dm0_rx_de-skew",
|
|
+ "cs0_dm0_tx_de-skew",
|
|
+ "cs0_dq0_rx_de-skew",
|
|
+ "cs0_dq0_tx_de-skew",
|
|
+ "cs0_dq1_rx_de-skew",
|
|
+ "cs0_dq1_tx_de-skew",
|
|
+ "cs0_dq2_rx_de-skew",
|
|
+ "cs0_dq2_tx_de-skew",
|
|
+ "cs0_dq3_rx_de-skew",
|
|
+ "cs0_dq3_tx_de-skew",
|
|
+ "cs0_dq4_rx_de-skew",
|
|
+ "cs0_dq4_tx_de-skew",
|
|
+ "cs0_dq5_rx_de-skew",
|
|
+ "cs0_dq5_tx_de-skew",
|
|
+ "cs0_dq6_rx_de-skew",
|
|
+ "cs0_dq6_tx_de-skew",
|
|
+ "cs0_dq7_rx_de-skew",
|
|
+ "cs0_dq7_tx_de-skew",
|
|
+ "cs0_dqs0_rx_de-skew",
|
|
+ "cs0_dqs0p_tx_de-skew",
|
|
+ "cs0_dqs0n_tx_de-skew",
|
|
+
|
|
+ "cs0_dm1_rx_de-skew",
|
|
+ "cs0_dm1_tx_de-skew",
|
|
+ "cs0_dq8_rx_de-skew",
|
|
+ "cs0_dq8_tx_de-skew",
|
|
+ "cs0_dq9_rx_de-skew",
|
|
+ "cs0_dq9_tx_de-skew",
|
|
+ "cs0_dq10_rx_de-skew",
|
|
+ "cs0_dq10_tx_de-skew",
|
|
+ "cs0_dq11_rx_de-skew",
|
|
+ "cs0_dq11_tx_de-skew",
|
|
+ "cs0_dq12_rx_de-skew",
|
|
+ "cs0_dq12_tx_de-skew",
|
|
+ "cs0_dq13_rx_de-skew",
|
|
+ "cs0_dq13_tx_de-skew",
|
|
+ "cs0_dq14_rx_de-skew",
|
|
+ "cs0_dq14_tx_de-skew",
|
|
+ "cs0_dq15_rx_de-skew",
|
|
+ "cs0_dq15_tx_de-skew",
|
|
+ "cs0_dqs1_rx_de-skew",
|
|
+ "cs0_dqs1p_tx_de-skew",
|
|
+ "cs0_dqs1n_tx_de-skew",
|
|
+
|
|
+ "cs0_dm2_rx_de-skew",
|
|
+ "cs0_dm2_tx_de-skew",
|
|
+ "cs0_dq16_rx_de-skew",
|
|
+ "cs0_dq16_tx_de-skew",
|
|
+ "cs0_dq17_rx_de-skew",
|
|
+ "cs0_dq17_tx_de-skew",
|
|
+ "cs0_dq18_rx_de-skew",
|
|
+ "cs0_dq18_tx_de-skew",
|
|
+ "cs0_dq19_rx_de-skew",
|
|
+ "cs0_dq19_tx_de-skew",
|
|
+ "cs0_dq20_rx_de-skew",
|
|
+ "cs0_dq20_tx_de-skew",
|
|
+ "cs0_dq21_rx_de-skew",
|
|
+ "cs0_dq21_tx_de-skew",
|
|
+ "cs0_dq22_rx_de-skew",
|
|
+ "cs0_dq22_tx_de-skew",
|
|
+ "cs0_dq23_rx_de-skew",
|
|
+ "cs0_dq23_tx_de-skew",
|
|
+ "cs0_dqs2_rx_de-skew",
|
|
+ "cs0_dqs2p_tx_de-skew",
|
|
+ "cs0_dqs2n_tx_de-skew",
|
|
+
|
|
+ "cs0_dm3_rx_de-skew",
|
|
+ "cs0_dm3_tx_de-skew",
|
|
+ "cs0_dq24_rx_de-skew",
|
|
+ "cs0_dq24_tx_de-skew",
|
|
+ "cs0_dq25_rx_de-skew",
|
|
+ "cs0_dq25_tx_de-skew",
|
|
+ "cs0_dq26_rx_de-skew",
|
|
+ "cs0_dq26_tx_de-skew",
|
|
+ "cs0_dq27_rx_de-skew",
|
|
+ "cs0_dq27_tx_de-skew",
|
|
+ "cs0_dq28_rx_de-skew",
|
|
+ "cs0_dq28_tx_de-skew",
|
|
+ "cs0_dq29_rx_de-skew",
|
|
+ "cs0_dq29_tx_de-skew",
|
|
+ "cs0_dq30_rx_de-skew",
|
|
+ "cs0_dq30_tx_de-skew",
|
|
+ "cs0_dq31_rx_de-skew",
|
|
+ "cs0_dq31_tx_de-skew",
|
|
+ "cs0_dqs3_rx_de-skew",
|
|
+ "cs0_dqs3p_tx_de-skew",
|
|
+ "cs0_dqs3n_tx_de-skew",
|
|
+};
|
|
+
|
|
+static const char * const rk3328_dts_cs1_timing[] = {
|
|
+ "cs1_dm0_rx_de-skew",
|
|
+ "cs1_dm0_tx_de-skew",
|
|
+ "cs1_dq0_rx_de-skew",
|
|
+ "cs1_dq0_tx_de-skew",
|
|
+ "cs1_dq1_rx_de-skew",
|
|
+ "cs1_dq1_tx_de-skew",
|
|
+ "cs1_dq2_rx_de-skew",
|
|
+ "cs1_dq2_tx_de-skew",
|
|
+ "cs1_dq3_rx_de-skew",
|
|
+ "cs1_dq3_tx_de-skew",
|
|
+ "cs1_dq4_rx_de-skew",
|
|
+ "cs1_dq4_tx_de-skew",
|
|
+ "cs1_dq5_rx_de-skew",
|
|
+ "cs1_dq5_tx_de-skew",
|
|
+ "cs1_dq6_rx_de-skew",
|
|
+ "cs1_dq6_tx_de-skew",
|
|
+ "cs1_dq7_rx_de-skew",
|
|
+ "cs1_dq7_tx_de-skew",
|
|
+ "cs1_dqs0_rx_de-skew",
|
|
+ "cs1_dqs0p_tx_de-skew",
|
|
+ "cs1_dqs0n_tx_de-skew",
|
|
+
|
|
+ "cs1_dm1_rx_de-skew",
|
|
+ "cs1_dm1_tx_de-skew",
|
|
+ "cs1_dq8_rx_de-skew",
|
|
+ "cs1_dq8_tx_de-skew",
|
|
+ "cs1_dq9_rx_de-skew",
|
|
+ "cs1_dq9_tx_de-skew",
|
|
+ "cs1_dq10_rx_de-skew",
|
|
+ "cs1_dq10_tx_de-skew",
|
|
+ "cs1_dq11_rx_de-skew",
|
|
+ "cs1_dq11_tx_de-skew",
|
|
+ "cs1_dq12_rx_de-skew",
|
|
+ "cs1_dq12_tx_de-skew",
|
|
+ "cs1_dq13_rx_de-skew",
|
|
+ "cs1_dq13_tx_de-skew",
|
|
+ "cs1_dq14_rx_de-skew",
|
|
+ "cs1_dq14_tx_de-skew",
|
|
+ "cs1_dq15_rx_de-skew",
|
|
+ "cs1_dq15_tx_de-skew",
|
|
+ "cs1_dqs1_rx_de-skew",
|
|
+ "cs1_dqs1p_tx_de-skew",
|
|
+ "cs1_dqs1n_tx_de-skew",
|
|
+
|
|
+ "cs1_dm2_rx_de-skew",
|
|
+ "cs1_dm2_tx_de-skew",
|
|
+ "cs1_dq16_rx_de-skew",
|
|
+ "cs1_dq16_tx_de-skew",
|
|
+ "cs1_dq17_rx_de-skew",
|
|
+ "cs1_dq17_tx_de-skew",
|
|
+ "cs1_dq18_rx_de-skew",
|
|
+ "cs1_dq18_tx_de-skew",
|
|
+ "cs1_dq19_rx_de-skew",
|
|
+ "cs1_dq19_tx_de-skew",
|
|
+ "cs1_dq20_rx_de-skew",
|
|
+ "cs1_dq20_tx_de-skew",
|
|
+ "cs1_dq21_rx_de-skew",
|
|
+ "cs1_dq21_tx_de-skew",
|
|
+ "cs1_dq22_rx_de-skew",
|
|
+ "cs1_dq22_tx_de-skew",
|
|
+ "cs1_dq23_rx_de-skew",
|
|
+ "cs1_dq23_tx_de-skew",
|
|
+ "cs1_dqs2_rx_de-skew",
|
|
+ "cs1_dqs2p_tx_de-skew",
|
|
+ "cs1_dqs2n_tx_de-skew",
|
|
+
|
|
+ "cs1_dm3_rx_de-skew",
|
|
+ "cs1_dm3_tx_de-skew",
|
|
+ "cs1_dq24_rx_de-skew",
|
|
+ "cs1_dq24_tx_de-skew",
|
|
+ "cs1_dq25_rx_de-skew",
|
|
+ "cs1_dq25_tx_de-skew",
|
|
+ "cs1_dq26_rx_de-skew",
|
|
+ "cs1_dq26_tx_de-skew",
|
|
+ "cs1_dq27_rx_de-skew",
|
|
+ "cs1_dq27_tx_de-skew",
|
|
+ "cs1_dq28_rx_de-skew",
|
|
+ "cs1_dq28_tx_de-skew",
|
|
+ "cs1_dq29_rx_de-skew",
|
|
+ "cs1_dq29_tx_de-skew",
|
|
+ "cs1_dq30_rx_de-skew",
|
|
+ "cs1_dq30_tx_de-skew",
|
|
+ "cs1_dq31_rx_de-skew",
|
|
+ "cs1_dq31_tx_de-skew",
|
|
+ "cs1_dqs3_rx_de-skew",
|
|
+ "cs1_dqs3p_tx_de-skew",
|
|
+ "cs1_dqs3n_tx_de-skew",
|
|
+};
|
|
+
|
|
+struct rk3328_ddr_dts_config_timing {
|
|
+ unsigned int ddr3_speed_bin;
|
|
+ unsigned int ddr4_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 auto_pd_dis_freq;
|
|
+ unsigned int auto_sr_dis_freq;
|
|
+ /* for ddr3 only */
|
|
+ unsigned int ddr3_dll_dis_freq;
|
|
+ /* for ddr4 only */
|
|
+ unsigned int ddr4_dll_dis_freq;
|
|
+ unsigned int phy_dll_dis_freq;
|
|
+
|
|
+ unsigned int ddr3_odt_dis_freq;
|
|
+ unsigned int phy_ddr3_odt_dis_freq;
|
|
+ unsigned int ddr3_drv;
|
|
+ unsigned int ddr3_odt;
|
|
+ unsigned int phy_ddr3_ca_drv;
|
|
+ unsigned int phy_ddr3_ck_drv;
|
|
+ unsigned int phy_ddr3_dq_drv;
|
|
+ unsigned int phy_ddr3_odt;
|
|
+
|
|
+ unsigned int lpddr3_odt_dis_freq;
|
|
+ unsigned int phy_lpddr3_odt_dis_freq;
|
|
+ unsigned int lpddr3_drv;
|
|
+ unsigned int lpddr3_odt;
|
|
+ unsigned int phy_lpddr3_ca_drv;
|
|
+ unsigned int phy_lpddr3_ck_drv;
|
|
+ unsigned int phy_lpddr3_dq_drv;
|
|
+ unsigned int phy_lpddr3_odt;
|
|
+
|
|
+ unsigned int lpddr4_odt_dis_freq;
|
|
+ unsigned int phy_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;
|
|
+
|
|
+ unsigned int ddr4_odt_dis_freq;
|
|
+ unsigned int phy_ddr4_odt_dis_freq;
|
|
+ unsigned int ddr4_drv;
|
|
+ unsigned int ddr4_odt;
|
|
+ unsigned int phy_ddr4_ca_drv;
|
|
+ unsigned int phy_ddr4_ck_drv;
|
|
+ unsigned int phy_ddr4_dq_drv;
|
|
+ unsigned int phy_ddr4_odt;
|
|
+
|
|
+ unsigned int ca_skew[15];
|
|
+ unsigned int cs0_skew[44];
|
|
+ unsigned int cs1_skew[44];
|
|
+
|
|
+ unsigned int available;
|
|
+};
|
|
+
|
|
+struct rk3328_ddr_de_skew_setting {
|
|
+ unsigned int ca_de_skew[30];
|
|
+ unsigned int cs0_de_skew[84];
|
|
+ unsigned int cs1_de_skew[84];
|
|
+};
|
|
+
|
|
+struct rk3328_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 regulator *vdd_center;
|
|
+ unsigned long rate, target_rate;
|
|
+ unsigned long volt, target_volt;
|
|
+
|
|
+ int (*set_auto_self_refresh)(u32 en);
|
|
+};
|
|
+
|
|
+static void
|
|
+rk3328_de_skew_setting_2_register(struct rk3328_ddr_de_skew_setting *de_skew,
|
|
+ struct rk3328_ddr_dts_config_timing *tim)
|
|
+{
|
|
+ u32 n;
|
|
+ u32 offset;
|
|
+ u32 shift;
|
|
+
|
|
+ memset_io(tim->ca_skew, 0, sizeof(tim->ca_skew));
|
|
+ memset_io(tim->cs0_skew, 0, sizeof(tim->cs0_skew));
|
|
+ memset_io(tim->cs1_skew, 0, sizeof(tim->cs1_skew));
|
|
+
|
|
+ /* CA de-skew */
|
|
+ for (n = 0; n < ARRAY_SIZE(de_skew->ca_de_skew); n++) {
|
|
+ offset = n / 2;
|
|
+ shift = n % 2;
|
|
+ /* 0 => 4; 1 => 0 */
|
|
+ shift = (shift == 0) ? 4 : 0;
|
|
+ tim->ca_skew[offset] &= ~(0xf << shift);
|
|
+ tim->ca_skew[offset] |= (de_skew->ca_de_skew[n] << shift);
|
|
+ }
|
|
+
|
|
+ /* CS0 data de-skew */
|
|
+ for (n = 0; n < ARRAY_SIZE(de_skew->cs0_de_skew); n++) {
|
|
+ offset = ((n / 21) * 11) + ((n % 21) / 2);
|
|
+ shift = ((n % 21) % 2);
|
|
+ if ((n % 21) == 20)
|
|
+ shift = 0;
|
|
+ else
|
|
+ /* 0 => 4; 1 => 0 */
|
|
+ shift = (shift == 0) ? 4 : 0;
|
|
+ tim->cs0_skew[offset] &= ~(0xf << shift);
|
|
+ tim->cs0_skew[offset] |= (de_skew->cs0_de_skew[n] << shift);
|
|
+ }
|
|
+
|
|
+ /* CS1 data de-skew */
|
|
+ for (n = 0; n < ARRAY_SIZE(de_skew->cs1_de_skew); n++) {
|
|
+ offset = ((n / 21) * 11) + ((n % 21) / 2);
|
|
+ shift = ((n % 21) % 2);
|
|
+ if ((n % 21) == 20)
|
|
+ shift = 0;
|
|
+ else
|
|
+ /* 0 => 4; 1 => 0 */
|
|
+ shift = (shift == 0) ? 4 : 0;
|
|
+ tim->cs1_skew[offset] &= ~(0xf << shift);
|
|
+ tim->cs1_skew[offset] |= (de_skew->cs1_de_skew[n] << shift);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void of_get_rk3328_timings(struct device *dev,
|
|
+ struct device_node *np, uint32_t *timing)
|
|
+{
|
|
+ struct device_node *np_tim;
|
|
+ u32 *p;
|
|
+ struct rk3328_ddr_dts_config_timing *dts_timing;
|
|
+ struct rk3328_ddr_de_skew_setting *de_skew;
|
|
+ int ret = 0;
|
|
+ u32 i;
|
|
+
|
|
+ dts_timing =
|
|
+ (struct rk3328_ddr_dts_config_timing *)(timing +
|
|
+ DTS_PAR_OFFSET / 4);
|
|
+
|
|
+ np_tim = of_parse_phandle(np, "ddr_timing", 0);
|
|
+ if (!np_tim) {
|
|
+ ret = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+ de_skew = kmalloc(sizeof(*de_skew), GFP_KERNEL);
|
|
+ if (!de_skew) {
|
|
+ ret = -ENOMEM;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ p = (u32 *)dts_timing;
|
|
+ for (i = 0; i < ARRAY_SIZE(rk3328_dts_timing); i++) {
|
|
+ ret |= of_property_read_u32(np_tim, rk3328_dts_timing[i],
|
|
+ p + i);
|
|
+ }
|
|
+ p = (u32 *)de_skew->ca_de_skew;
|
|
+ for (i = 0; i < ARRAY_SIZE(rk3328_dts_ca_timing); i++) {
|
|
+ ret |= of_property_read_u32(np_tim, rk3328_dts_ca_timing[i],
|
|
+ p + i);
|
|
+ }
|
|
+ p = (u32 *)de_skew->cs0_de_skew;
|
|
+ for (i = 0; i < ARRAY_SIZE(rk3328_dts_cs0_timing); i++) {
|
|
+ ret |= of_property_read_u32(np_tim, rk3328_dts_cs0_timing[i],
|
|
+ p + i);
|
|
+ }
|
|
+ p = (u32 *)de_skew->cs1_de_skew;
|
|
+ for (i = 0; i < ARRAY_SIZE(rk3328_dts_cs1_timing); i++) {
|
|
+ ret |= of_property_read_u32(np_tim, rk3328_dts_cs1_timing[i],
|
|
+ p + i);
|
|
+ }
|
|
+ if (!ret)
|
|
+ rk3328_de_skew_setting_2_register(de_skew, dts_timing);
|
|
+
|
|
+ kfree(de_skew);
|
|
+end:
|
|
+ if (!ret) {
|
|
+ dts_timing->available = 1;
|
|
+ } else {
|
|
+ dts_timing->available = 0;
|
|
+ dev_err(dev, "of_get_ddr_timings: fail\n");
|
|
+ }
|
|
+
|
|
+ of_node_put(np_tim);
|
|
+}
|
|
+
|
|
+static int rockchip_ddr_set_auto_self_refresh(uint32_t en)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ ddr_psci_param->sr_idle_en = en;
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ,
|
|
+ SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_SET_AT_SR,
|
|
+ 0, 0, 0, 0, &res);
|
|
+
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+static int rk3328_dmc_init(struct platform_device *pdev,
|
|
+ struct rk3328_dmcfreq *dmcfreq)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+ u32 size, page_num;
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ,
|
|
+ 0, 0, ROCKCHIP_SIP_CONFIG_DRAM_GET_VERSION,
|
|
+ 0, 0, 0, 0, &res);
|
|
+ if (res.a0 || (res.a1 < 0x101)) {
|
|
+ dev_err(&pdev->dev,
|
|
+ "trusted firmware need to update or is invalid\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ dev_notice(&pdev->dev, "current ATF version 0x%lx\n", res.a1);
|
|
+
|
|
+ /*
|
|
+ * first 4KB is used for interface parameters
|
|
+ * after 4KB * N is dts parameters
|
|
+ */
|
|
+ size = sizeof(struct rk3328_ddr_dts_config_timing);
|
|
+ page_num = DIV_ROUND_UP(size, 4096) + 1;
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_SHARE_MEM,
|
|
+ page_num, SHARE_PAGE_TYPE_DDR, 0,
|
|
+ 0, 0, 0, 0, &res);
|
|
+ if (res.a0 != 0) {
|
|
+ dev_err(&pdev->dev, "no ATF memory for init\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ ddr_psci_param = ioremap(res.a1, page_num << 12);
|
|
+ of_get_rk3328_timings(&pdev->dev, pdev->dev.of_node,
|
|
+ (uint32_t *)ddr_psci_param);
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ,
|
|
+ SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_INIT,
|
|
+ 0, 0, 0, 0, &res);
|
|
+ if (res.a0) {
|
|
+ dev_err(&pdev->dev, "Rockchip dram init error %lx\n", res.a0);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ dmcfreq->set_auto_self_refresh = rockchip_ddr_set_auto_self_refresh;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rk3328_dmcfreq_target(struct device *dev, unsigned long *freq,
|
|
+ u32 flags)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(dev);
|
|
+ struct dev_pm_opp *opp;
|
|
+ unsigned long old_clk_rate = dmcfreq->rate;
|
|
+ unsigned long target_volt, target_rate;
|
|
+ int err;
|
|
+
|
|
+ opp = devfreq_recommended_opp(dev, freq, flags);
|
|
+ if (IS_ERR(opp))
|
|
+ return PTR_ERR(opp);
|
|
+
|
|
+ target_rate = dev_pm_opp_get_freq(opp);
|
|
+ target_volt = dev_pm_opp_get_voltage(opp);
|
|
+ dev_pm_opp_put(opp);
|
|
+
|
|
+ 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, adjust frequency first.
|
|
+ */
|
|
+ if (old_clk_rate < target_rate) {
|
|
+ err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
|
|
+ target_volt);
|
|
+ if (err) {
|
|
+ dev_err(dev, "Cannot set voltage %lu uV\n",
|
|
+ target_volt);
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = clk_set_rate(dmcfreq->dmc_clk, target_rate);
|
|
+ if (err) {
|
|
+ dev_err(dev, "Cannot set frequency %lu (%d)\n", target_rate,
|
|
+ err);
|
|
+ regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
|
|
+ dmcfreq->volt);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * 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, "Got wrong frequency, Request %lu, Current %lu\n",
|
|
+ target_rate, dmcfreq->rate);
|
|
+ regulator_set_voltage(dmcfreq->vdd_center, dmcfreq->volt,
|
|
+ dmcfreq->volt);
|
|
+ goto out;
|
|
+ } else if (old_clk_rate > target_rate)
|
|
+ err = regulator_set_voltage(dmcfreq->vdd_center, target_volt,
|
|
+ target_volt);
|
|
+ if (err)
|
|
+ dev_err(dev, "Cannot set voltage %lu uV\n", target_volt);
|
|
+
|
|
+ dmcfreq->rate = target_rate;
|
|
+ dmcfreq->volt = target_volt;
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&dmcfreq->lock);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int rk3328_dmcfreq_get_dev_status(struct device *dev,
|
|
+ struct devfreq_dev_status *stat)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(dev);
|
|
+ struct devfreq_event_data edata;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_get_event(dmcfreq->edev, &edata);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ stat->current_frequency = dmcfreq->rate;
|
|
+ stat->busy_time = edata.load_count;
|
|
+ stat->total_time = edata.total_count;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int rk3328_dmcfreq_get_cur_freq(struct device *dev, unsigned long *freq)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(dev);
|
|
+
|
|
+ *freq = dmcfreq->rate;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct devfreq_dev_profile rk3328_devfreq_dmc_profile = {
|
|
+ .polling_ms = 200,
|
|
+ .target = rk3328_dmcfreq_target,
|
|
+ .get_dev_status = rk3328_dmcfreq_get_dev_status,
|
|
+ .get_cur_freq = rk3328_dmcfreq_get_cur_freq,
|
|
+};
|
|
+
|
|
+static __maybe_unused int rk3328_dmcfreq_suspend(struct device *dev)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(dev);
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_disable_edev(dmcfreq->edev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to disable the devfreq-event devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devfreq_suspend_device(dmcfreq->devfreq);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to suspend the devfreq devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int rk3328_dmcfreq_resume(struct device *dev)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(dev);
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_enable_edev(dmcfreq->edev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable the devfreq-event devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devfreq_resume_device(dmcfreq->devfreq);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to resume the devfreq devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(rk3328_dmcfreq_pm, rk3328_dmcfreq_suspend,
|
|
+ rk3328_dmcfreq_resume);
|
|
+
|
|
+static int rk3328_dmcfreq_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct rk3328_dmcfreq *data;
|
|
+ struct dev_pm_opp *opp;
|
|
+ int ret;
|
|
+
|
|
+ data = devm_kzalloc(dev, sizeof(struct rk3328_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)) {
|
|
+ if (PTR_ERR(data->vdd_center) == -EPROBE_DEFER)
|
|
+ return -EPROBE_DEFER;
|
|
+
|
|
+ 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)) {
|
|
+ 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, "devfreq-events", 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;
|
|
+ }
|
|
+
|
|
+ ret = rk3328_dmc_init(pdev, data);
|
|
+ if (ret)
|
|
+ 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);
|
|
+
|
|
+ opp = devfreq_recommended_opp(dev, &data->rate, 0);
|
|
+ if (IS_ERR(opp)) {
|
|
+ ret = PTR_ERR(opp);
|
|
+ goto err_free_opp;
|
|
+ }
|
|
+
|
|
+ data->rate = dev_pm_opp_get_freq(opp);
|
|
+ data->volt = dev_pm_opp_get_voltage(opp);
|
|
+ dev_pm_opp_put(opp);
|
|
+
|
|
+ rk3328_devfreq_dmc_profile.initial_freq = data->rate;
|
|
+
|
|
+ data->devfreq = devm_devfreq_add_device(dev,
|
|
+ &rk3328_devfreq_dmc_profile,
|
|
+ DEVFREQ_GOV_SIMPLE_ONDEMAND,
|
|
+ &data->ondemand_data);
|
|
+ if (IS_ERR(data->devfreq)) {
|
|
+ ret = PTR_ERR(data->devfreq);
|
|
+ goto err_free_opp;
|
|
+ }
|
|
+
|
|
+ devm_devfreq_register_opp_notifier(dev, data->devfreq);
|
|
+
|
|
+ data->dev = dev;
|
|
+ platform_set_drvdata(pdev, data);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_free_opp:
|
|
+ dev_pm_opp_of_remove_table(&pdev->dev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int rk3328_dmcfreq_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct rk3328_dmcfreq *dmcfreq = dev_get_drvdata(&pdev->dev);
|
|
+
|
|
+ /*
|
|
+ * Before remove the opp table we need to unregister the opp notifier.
|
|
+ */
|
|
+ devm_devfreq_unregister_opp_notifier(dmcfreq->dev, dmcfreq->devfreq);
|
|
+ dev_pm_opp_of_remove_table(dmcfreq->dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id rk3328dmc_devfreq_of_match[] = {
|
|
+ { .compatible = "rockchip,rk3328-dmc" },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, rk3328dmc_devfreq_of_match);
|
|
+
|
|
+static struct platform_driver rk3328_dmcfreq_driver = {
|
|
+ .probe = rk3328_dmcfreq_probe,
|
|
+ .remove = rk3328_dmcfreq_remove,
|
|
+ .driver = {
|
|
+ .name = "rk3328-dmc-freq",
|
|
+ .pm = &rk3328_dmcfreq_pm,
|
|
+ .of_match_table = rk3328dmc_devfreq_of_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(rk3328_dmcfreq_driver);
|
|
+
|
|
+MODULE_LICENSE("GPL v2");
|
|
+MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
|
|
+MODULE_DESCRIPTION("RK3328 dmcfreq driver with devfreq framework");
|
|
diff --git a/include/dt-bindings/clock/rockchip-ddr.h b/include/dt-bindings/clock/rockchip-ddr.h
|
|
new file mode 100644
|
|
index 000000000..b065432e7
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/clock/rockchip-ddr.h
|
|
@@ -0,0 +1,63 @@
|
|
+/*
|
|
+ *
|
|
+ * Copyright (C) 2017 ROCKCHIP, Inc.
|
|
+ *
|
|
+ * This software is licensed under the terms of the GNU General Public
|
|
+ * License version 2, as published by the Free Software Foundation, and
|
|
+ * may be copied, distributed, and modified under those terms.
|
|
+ *
|
|
+ * 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 _DT_BINDINGS_CLOCK_ROCKCHIP_DDR_H
|
|
+#define _DT_BINDINGS_CLOCK_ROCKCHIP_DDR_H
|
|
+
|
|
+#define DDR2_DEFAULT (0)
|
|
+
|
|
+#define DDR3_800D (0) /* 5-5-5 */
|
|
+#define DDR3_800E (1) /* 6-6-6 */
|
|
+#define DDR3_1066E (2) /* 6-6-6 */
|
|
+#define DDR3_1066F (3) /* 7-7-7 */
|
|
+#define DDR3_1066G (4) /* 8-8-8 */
|
|
+#define DDR3_1333F (5) /* 7-7-7 */
|
|
+#define DDR3_1333G (6) /* 8-8-8 */
|
|
+#define DDR3_1333H (7) /* 9-9-9 */
|
|
+#define DDR3_1333J (8) /* 10-10-10 */
|
|
+#define DDR3_1600G (9) /* 8-8-8 */
|
|
+#define DDR3_1600H (10) /* 9-9-9 */
|
|
+#define DDR3_1600J (11) /* 10-10-10 */
|
|
+#define DDR3_1600K (12) /* 11-11-11 */
|
|
+#define DDR3_1866J (13) /* 10-10-10 */
|
|
+#define DDR3_1866K (14) /* 11-11-11 */
|
|
+#define DDR3_1866L (15) /* 12-12-12 */
|
|
+#define DDR3_1866M (16) /* 13-13-13 */
|
|
+#define DDR3_2133K (17) /* 11-11-11 */
|
|
+#define DDR3_2133L (18) /* 12-12-12 */
|
|
+#define DDR3_2133M (19) /* 13-13-13 */
|
|
+#define DDR3_2133N (20) /* 14-14-14 */
|
|
+#define DDR3_DEFAULT (21)
|
|
+#define DDR_DDR2 (22)
|
|
+#define DDR_LPDDR (23)
|
|
+#define DDR_LPDDR2 (24)
|
|
+
|
|
+#define DDR4_1600J (0) /* 10-10-10 */
|
|
+#define DDR4_1600K (1) /* 11-11-11 */
|
|
+#define DDR4_1600L (2) /* 12-12-12 */
|
|
+#define DDR4_1866L (3) /* 12-12-12 */
|
|
+#define DDR4_1866M (4) /* 13-13-13 */
|
|
+#define DDR4_1866N (5) /* 14-14-14 */
|
|
+#define DDR4_2133N (6) /* 14-14-14 */
|
|
+#define DDR4_2133P (7) /* 15-15-15 */
|
|
+#define DDR4_2133R (8) /* 16-16-16 */
|
|
+#define DDR4_2400P (9) /* 15-15-15 */
|
|
+#define DDR4_2400R (10) /* 16-16-16 */
|
|
+#define DDR4_2400U (11) /* 18-18-18 */
|
|
+#define DDR4_DEFAULT (12)
|
|
+
|
|
+#define PAUSE_CPU_STACK_SIZE 16
|
|
+
|
|
+#endif
|
|
diff --git a/include/dt-bindings/memory/rockchip,rk322x.h b/include/dt-bindings/memory/rockchip,rk322x.h
|
|
new file mode 100644
|
|
index 000000000..1ab3317d7
|
|
--- /dev/null
|
|
+++ b/include/dt-bindings/memory/rockchip,rk322x.h
|
|
@@ -0,0 +1,90 @@
|
|
+/*
|
|
+ * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
|
|
+ *
|
|
+ * SPDX-License-Identifier: GPL-2.0+
|
|
+ */
|
|
+
|
|
+#ifndef _DT_BINDINGS_DRAM_ROCKCHIP_RK322X_H
|
|
+#define _DT_BINDINGS_DRAM_ROCKCHIP_RK322X_H
|
|
+
|
|
+#define DDR3_DS_34ohm (1 << 1)
|
|
+#define DDR3_DS_40ohm (0x0)
|
|
+
|
|
+#define LP2_DS_34ohm (0x1)
|
|
+#define LP2_DS_40ohm (0x2)
|
|
+#define LP2_DS_48ohm (0x3)
|
|
+#define LP2_DS_60ohm (0x4)
|
|
+#define LP2_DS_68_6ohm (0x5)/* optional */
|
|
+#define LP2_DS_80ohm (0x6)
|
|
+#define LP2_DS_120ohm (0x7)/* optional */
|
|
+
|
|
+#define LP3_DS_34ohm (0x1)
|
|
+#define LP3_DS_40ohm (0x2)
|
|
+#define LP3_DS_48ohm (0x3)
|
|
+#define LP3_DS_60ohm (0x4)
|
|
+#define LP3_DS_80ohm (0x6)
|
|
+#define LP3_DS_34D_40U (0x9)
|
|
+#define LP3_DS_40D_48U (0xa)
|
|
+#define LP3_DS_34D_48U (0xb)
|
|
+
|
|
+#define DDR3_ODT_DIS (0)
|
|
+#define DDR3_ODT_40ohm ((1 << 2) | (1 << 6))
|
|
+#define DDR3_ODT_60ohm (1 << 2)
|
|
+#define DDR3_ODT_120ohm (1 << 6)
|
|
+
|
|
+#define LP3_ODT_DIS (0)
|
|
+#define LP3_ODT_60ohm (1)
|
|
+#define LP3_ODT_120ohm (2)
|
|
+#define LP3_ODT_240ohm (3)
|
|
+
|
|
+#define PHY_DDR3_RON_RTT_DISABLE (0)
|
|
+#define PHY_DDR3_RON_RTT_451ohm (1)
|
|
+#define PHY_DDR3_RON_RTT_225ohm (2)
|
|
+#define PHY_DDR3_RON_RTT_150ohm (3)
|
|
+#define PHY_DDR3_RON_RTT_112ohm (4)
|
|
+#define PHY_DDR3_RON_RTT_90ohm (5)
|
|
+#define PHY_DDR3_RON_RTT_75ohm (6)
|
|
+#define PHY_DDR3_RON_RTT_64ohm (7)
|
|
+#define PHY_DDR3_RON_RTT_56ohm (16)
|
|
+#define PHY_DDR3_RON_RTT_50ohm (17)
|
|
+#define PHY_DDR3_RON_RTT_45ohm (18)
|
|
+#define PHY_DDR3_RON_RTT_41ohm (19)
|
|
+#define PHY_DDR3_RON_RTT_37ohm (20)
|
|
+#define PHY_DDR3_RON_RTT_34ohm (21)
|
|
+#define PHY_DDR3_RON_RTT_33ohm (22)
|
|
+#define PHY_DDR3_RON_RTT_30ohm (23)
|
|
+#define PHY_DDR3_RON_RTT_28ohm (24)
|
|
+#define PHY_DDR3_RON_RTT_26ohm (25)
|
|
+#define PHY_DDR3_RON_RTT_25ohm (26)
|
|
+#define PHY_DDR3_RON_RTT_23ohm (27)
|
|
+#define PHY_DDR3_RON_RTT_22ohm (28)
|
|
+#define PHY_DDR3_RON_RTT_21ohm (29)
|
|
+#define PHY_DDR3_RON_RTT_20ohm (30)
|
|
+#define PHY_DDR3_RON_RTT_19ohm (31)
|
|
+
|
|
+#define PHY_LP23_RON_RTT_DISABLE (0)
|
|
+#define PHY_LP23_RON_RTT_480ohm (1)
|
|
+#define PHY_LP23_RON_RTT_240ohm (2)
|
|
+#define PHY_LP23_RON_RTT_160ohm (3)
|
|
+#define PHY_LP23_RON_RTT_120ohm (4)
|
|
+#define PHY_LP23_RON_RTT_96ohm (5)
|
|
+#define PHY_LP23_RON_RTT_80ohm (6)
|
|
+#define PHY_LP23_RON_RTT_68ohm (7)
|
|
+#define PHY_LP23_RON_RTT_60ohm (16)
|
|
+#define PHY_LP23_RON_RTT_53ohm (17)
|
|
+#define PHY_LP23_RON_RTT_48ohm (18)
|
|
+#define PHY_LP23_RON_RTT_43ohm (19)
|
|
+#define PHY_LP23_RON_RTT_40ohm (20)
|
|
+#define PHY_LP23_RON_RTT_37ohm (21)
|
|
+#define PHY_LP23_RON_RTT_34ohm (22)
|
|
+#define PHY_LP23_RON_RTT_32ohm (23)
|
|
+#define PHY_LP23_RON_RTT_30ohm (24)
|
|
+#define PHY_LP23_RON_RTT_28ohm (25)
|
|
+#define PHY_LP23_RON_RTT_26ohm (26)
|
|
+#define PHY_LP23_RON_RTT_25ohm (27)
|
|
+#define PHY_LP23_RON_RTT_24ohm (28)
|
|
+#define PHY_LP23_RON_RTT_22ohm (29)
|
|
+#define PHY_LP23_RON_RTT_21ohm (30)
|
|
+#define PHY_LP23_RON_RTT_20ohm (31)
|
|
+
|
|
+#endif /* _DT_BINDINGS_DRAM_ROCKCHIP_RK322X_H */
|
|
|
|
diff --git a/drivers/devfreq/rk3228_dmc.c b/drivers/devfreq/rk3228_dmc.c
|
|
new file mode 100644
|
|
index 000000000..3a34cb2b5
|
|
--- /dev/null
|
|
+++ b/drivers/devfreq/rk3228_dmc.c
|
|
@@ -0,0 +1,738 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd.
|
|
+ * Author: Lin Huang <hl@rock-chips.com>
|
|
+ */
|
|
+
|
|
+#include <linux/arm-smccc.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/devfreq.h>
|
|
+#include <linux/devfreq-event.h>
|
|
+#include <linux/devfreq_cooling.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm_opp.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/rwsem.h>
|
|
+#include <linux/suspend.h>
|
|
+
|
|
+#include <soc/rockchip/rockchip_sip.h>
|
|
+
|
|
+#define DTS_PAR_OFFSET (4096)
|
|
+
|
|
+#define RK3228_GRF_OS_REG2 0x5d0
|
|
+#define DDR_PCTL_MCFG 0x80
|
|
+#define DDR_PCTL_TCL 0xe8
|
|
+#define DDR_PCTL_TRAS 0xf0
|
|
+#define DDR_PCTL_TRCD 0xf8
|
|
+#define DDR_PCTL_TRP 0xdc
|
|
+
|
|
+/* MCFG */
|
|
+#define MDDR_LPDDR2_CLK_STOP_IDLE_SHIFT 24
|
|
+#define PD_IDLE_SHIFT 8
|
|
+#define MDDR_EN (2 << 22)
|
|
+#define LPDDR2_EN (3 << 22)
|
|
+#define LPDDR3_EN (1 << 22)
|
|
+#define DDR2_EN (0 << 5)
|
|
+#define DDR3_EN (1 << 5)
|
|
+#define LPDDR2_S2 (0 << 6)
|
|
+#define LPDDR2_S4 (1 << 6)
|
|
+#define MDDR_LPDDR2_BL_2 (0 << 20)
|
|
+#define MDDR_LPDDR2_BL_4 (1 << 20)
|
|
+#define MDDR_LPDDR2_BL_8 (2 << 20)
|
|
+#define MDDR_LPDDR2_BL_16 (3 << 20)
|
|
+#define DDR2_DDR3_BL_4 0
|
|
+#define DDR2_DDR3_BL_8 1
|
|
+#define TFAW_SHIFT 18
|
|
+#define PD_EXIT_SLOW (0 << 17)
|
|
+#define PD_EXIT_FAST (1 << 17)
|
|
+#define PD_TYPE_SHIFT 16
|
|
+#define BURSTLENGTH_SHIFT 20
|
|
+
|
|
+#define MCFG_CR_2T_BIT(x) ((x & (1 << 3)) >> 3)
|
|
+#define MCFG_DDR_MASK 0x60
|
|
+#define MCFG_DDR_SHIFT 5
|
|
+#define MCFG_LPDDR_MASK 0xC00000
|
|
+#define MCFG_LPDDR_SHIFT 22
|
|
+
|
|
+#define MCFG_LPDDR2_S2 0x0
|
|
+#define MCFG_DDR3 0x1
|
|
+#define MCFG_LPDDR2_S4 0x2
|
|
+
|
|
+#define READ_DRAMTYPE_INFO(n) (((n) >> 13) & 0x7)
|
|
+
|
|
+enum {
|
|
+ DDR4 = 0,
|
|
+ DDR2 = 2,
|
|
+ DDR3 = 3,
|
|
+ LPDDR2 = 5,
|
|
+ LPDDR3 = 6,
|
|
+ LPDDR4 = 7,
|
|
+ UNUSED = 0xFF
|
|
+};
|
|
+
|
|
+struct share_params {
|
|
+ u32 hz;
|
|
+ u32 lcdc_type;
|
|
+ u32 vop;
|
|
+ u32 vop_dclk_mode;
|
|
+ u32 sr_idle_en;
|
|
+ u32 addr_mcu_el3;
|
|
+ /*
|
|
+ * 1: need to wait flag1
|
|
+ * 0: never wait flag1
|
|
+ */
|
|
+ u32 wait_flag1;
|
|
+ /*
|
|
+ * 1: need to wait flag1
|
|
+ * 0: never wait flag1
|
|
+ */
|
|
+ u32 wait_flag0;
|
|
+ u32 complt_hwirq;
|
|
+ /* if need, add parameter after */
|
|
+};
|
|
+
|
|
+static struct share_params *ddr_psci_param = NULL;
|
|
+
|
|
+static const char * const rk3228_dts_timing[] = {
|
|
+ "dram_spd_bin",
|
|
+ "sr_idle",
|
|
+ "pd_idle",
|
|
+ "dram_dll_disb_freq",
|
|
+ "phy_dll_disb_freq",
|
|
+ "dram_odt_disb_freq",
|
|
+ "phy_odt_disb_freq",
|
|
+ "ddr3_drv",
|
|
+ "ddr3_odt",
|
|
+ "lpddr3_drv",
|
|
+ "lpddr3_odt",
|
|
+ "lpddr2_drv",
|
|
+ "phy_ddr3_clk_drv",
|
|
+ "phy_ddr3_cmd_drv",
|
|
+ "phy_ddr3_dqs_drv",
|
|
+ "phy_ddr3_odt",
|
|
+ "phy_lp23_clk_drv",
|
|
+ "phy_lp23_cmd_drv",
|
|
+ "phy_lp23_dqs_drv",
|
|
+ "phy_lp3_odt"
|
|
+};
|
|
+
|
|
+struct rk3228_ddr_dts_config_timing {
|
|
+ u32 dram_spd_bin;
|
|
+ u32 sr_idle;
|
|
+ u32 pd_idle;
|
|
+ u32 dram_dll_dis_freq;
|
|
+ u32 phy_dll_dis_freq;
|
|
+ u32 dram_odt_dis_freq;
|
|
+ u32 phy_odt_dis_freq;
|
|
+ u32 ddr3_drv;
|
|
+ u32 ddr3_odt;
|
|
+ u32 lpddr3_drv;
|
|
+ u32 lpddr3_odt;
|
|
+ u32 lpddr2_drv;
|
|
+ u32 phy_ddr3_clk_drv;
|
|
+ u32 phy_ddr3_cmd_drv;
|
|
+ u32 phy_ddr3_dqs_drv;
|
|
+ u32 phy_ddr3_odt;
|
|
+ u32 phy_lp23_clk_drv;
|
|
+ u32 phy_lp23_cmd_drv;
|
|
+ u32 phy_lp23_dqs_drv;
|
|
+ u32 phy_lp3_odt;
|
|
+};
|
|
+
|
|
+struct rk3228_devfreq {
|
|
+ struct devfreq *devfreq;
|
|
+ struct opp_table *clkname_opp_table;
|
|
+ struct opp_table *regulators_opp_table;
|
|
+ struct thermal_cooling_device *cooling;
|
|
+ bool opp_of_table_added;
|
|
+};
|
|
+
|
|
+struct rk3228_dmc {
|
|
+ struct device *dev;
|
|
+ void __iomem *iomem;
|
|
+
|
|
+ int rate;
|
|
+ struct devfreq_simple_ondemand_data ondemand_data;
|
|
+ struct devfreq_event_dev *edev;
|
|
+ struct clk *dmc_clk;
|
|
+ struct rk3228_devfreq devfreq;
|
|
+
|
|
+ uint32_t dram_type;
|
|
+
|
|
+ //struct mutex lock;
|
|
+
|
|
+ int (*set_auto_self_refresh)(u32 en);
|
|
+};
|
|
+
|
|
+static uint32_t of_get_rk3228_timings(struct device *dev,
|
|
+ struct device_node *np, uint32_t *timing)
|
|
+{
|
|
+ struct device_node *np_tim;
|
|
+ uint32_t offset;
|
|
+ int ret = 0;
|
|
+ u32 idx;
|
|
+
|
|
+ // first 4kb page is reserved for interface parameters, we calculate an offset
|
|
+ // after which the timing parameters start
|
|
+ offset = DTS_PAR_OFFSET / sizeof(uint32_t);
|
|
+
|
|
+ np_tim = of_parse_phandle(np, "rockchip,dram_timing", 0);
|
|
+
|
|
+ if (!np_tim) {
|
|
+ ret = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ for (idx = 0; idx < ARRAY_SIZE(rk3228_dts_timing); idx++)
|
|
+ ret |= of_property_read_u32(np_tim, rk3228_dts_timing[idx], &timing[offset + idx]);
|
|
+
|
|
+end:
|
|
+ if (ret)
|
|
+ dev_err(dev, "of_get_ddr_timings: fail\n");
|
|
+
|
|
+ of_node_put(np_tim);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+static int rockchip_ddr_set_auto_self_refresh(uint32_t en)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ ddr_psci_param->sr_idle_en = en;
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ,
|
|
+ SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_SET_AT_SR,
|
|
+ 0, 0, 0, 0, &res);
|
|
+
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_target(struct device *dev, unsigned long *freq,
|
|
+ u32 flags)
|
|
+{
|
|
+
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(dev);
|
|
+ struct dev_pm_opp *opp;
|
|
+ int err;
|
|
+
|
|
+ opp = devfreq_recommended_opp(dev, freq, flags);
|
|
+ if (IS_ERR(opp))
|
|
+ return PTR_ERR(opp);
|
|
+ dev_pm_opp_put(opp);
|
|
+
|
|
+ err = dev_pm_opp_set_rate(dev, *freq);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ rdev->rate = *freq;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_get_dev_status(struct device *dev,
|
|
+ struct devfreq_dev_status *stat)
|
|
+{
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(dev);
|
|
+ struct devfreq_event_data edata;
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_get_event(rdev->edev, &edata);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ stat->current_frequency = rdev->rate;
|
|
+ stat->busy_time = edata.load_count;
|
|
+ stat->total_time = edata.total_count;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_get_cur_freq(struct device *dev, unsigned long *freq)
|
|
+{
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(dev);
|
|
+
|
|
+ *freq = rdev->rate;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct devfreq_dev_profile rk3228_devfreq_profile = {
|
|
+ .polling_ms = 50,
|
|
+ .target = rk3228_dmc_target,
|
|
+ .get_dev_status = rk3228_dmc_get_dev_status,
|
|
+ .get_cur_freq = rk3228_dmc_get_cur_freq,
|
|
+};
|
|
+
|
|
+void rk3228_devfreq_fini(struct rk3228_dmc *rdev)
|
|
+{
|
|
+ struct rk3228_devfreq *devfreq = &rdev->devfreq;
|
|
+
|
|
+ if (devfreq->cooling) {
|
|
+ devfreq_cooling_unregister(devfreq->cooling);
|
|
+ devfreq->cooling = NULL;
|
|
+ }
|
|
+
|
|
+ if (devfreq->devfreq) {
|
|
+ devm_devfreq_remove_device(rdev->dev, devfreq->devfreq);
|
|
+ devfreq->devfreq = NULL;
|
|
+ }
|
|
+
|
|
+ if (devfreq->opp_of_table_added) {
|
|
+ dev_pm_opp_of_remove_table(rdev->dev);
|
|
+ devfreq->opp_of_table_added = false;
|
|
+ }
|
|
+
|
|
+ if (devfreq->regulators_opp_table) {
|
|
+ dev_pm_opp_put_regulators(devfreq->regulators_opp_table);
|
|
+ devfreq->regulators_opp_table = NULL;
|
|
+ }
|
|
+
|
|
+ if (devfreq->clkname_opp_table) {
|
|
+ dev_pm_opp_put_clkname(devfreq->clkname_opp_table);
|
|
+ devfreq->clkname_opp_table = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+int rk3228_devfreq_init(struct rk3228_dmc *rdev)
|
|
+{
|
|
+ struct thermal_cooling_device *cooling;
|
|
+ struct device *dev = rdev->dev;
|
|
+ struct opp_table *opp_table;
|
|
+ struct devfreq *devfreq;
|
|
+ struct rk3228_devfreq *rdevfreq = &rdev->devfreq;
|
|
+
|
|
+ struct dev_pm_opp *opp;
|
|
+ unsigned long cur_freq;
|
|
+ int ret;
|
|
+
|
|
+ if (!device_property_present(dev, "operating-points-v2"))
|
|
+ /* Optional, continue without devfreq */
|
|
+ return 0;
|
|
+
|
|
+ opp_table = dev_pm_opp_set_clkname(dev, "ddr_sclk");
|
|
+ if (IS_ERR(opp_table)) {
|
|
+ ret = PTR_ERR(opp_table);
|
|
+ goto err_fini;
|
|
+ }
|
|
+
|
|
+ rdevfreq->clkname_opp_table = opp_table;
|
|
+
|
|
+ opp_table = dev_pm_opp_set_regulators(dev,
|
|
+ (const char *[]){ "logic" },
|
|
+ 1);
|
|
+ if (IS_ERR(opp_table)) {
|
|
+ ret = PTR_ERR(opp_table);
|
|
+
|
|
+ /* Continue if the optional regulator is missing */
|
|
+ if (ret != -ENODEV)
|
|
+ goto err_fini;
|
|
+ } else {
|
|
+ rdevfreq->regulators_opp_table = opp_table;
|
|
+ }
|
|
+
|
|
+ ret = dev_pm_opp_of_add_table(dev);
|
|
+ if (ret)
|
|
+ goto err_fini;
|
|
+ rdevfreq->opp_of_table_added = true;
|
|
+
|
|
+ cur_freq = 0;
|
|
+
|
|
+ opp = devfreq_recommended_opp(dev, &cur_freq, 0);
|
|
+ if (IS_ERR(opp)) {
|
|
+ ret = PTR_ERR(opp);
|
|
+ goto err_fini;
|
|
+ }
|
|
+
|
|
+ rk3228_devfreq_profile.initial_freq = cur_freq;
|
|
+ dev_pm_opp_put(opp);
|
|
+
|
|
+ rdev->ondemand_data.upthreshold = 30;
|
|
+ rdev->ondemand_data.downdifferential = 5;
|
|
+
|
|
+ devfreq = devm_devfreq_add_device(dev, &rk3228_devfreq_profile,
|
|
+ DEVFREQ_GOV_SIMPLE_ONDEMAND, &rdev->ondemand_data);
|
|
+ if (IS_ERR(devfreq)) {
|
|
+ dev_err(dev, "Couldn't initialize GPU devfreq\n");
|
|
+ ret = PTR_ERR(devfreq);
|
|
+ goto err_fini;
|
|
+ }
|
|
+
|
|
+ rdevfreq->devfreq = devfreq;
|
|
+
|
|
+ cooling = of_devfreq_cooling_register(dev->of_node, devfreq);
|
|
+ if (IS_ERR(cooling))
|
|
+ dev_warn(dev, "Failed to register cooling device\n");
|
|
+ else
|
|
+ rdevfreq->cooling = cooling;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_fini:
|
|
+ rk3228_devfreq_fini(rdev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_init(struct platform_device *pdev,
|
|
+ struct rk3228_dmc *rdev)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+ u32 page_num;
|
|
+
|
|
+ // Count of pages to request to trust os, in pages of 4kb
|
|
+ page_num = DIV_ROUND_UP(sizeof(struct rk3228_ddr_dts_config_timing), PAGE_SIZE) + 1;
|
|
+
|
|
+ dev_dbg(&pdev->dev, "trying to allocate %d pages\n", page_num);
|
|
+
|
|
+ // Do request to trust OS. res.a0 contains error code, res.a1 the *physical*
|
|
+ // initial location of pages
|
|
+ arm_smccc_smc(
|
|
+ ROCKCHIP_SIP_SHARE_MEM,
|
|
+ page_num, SHARE_PAGE_TYPE_DDR, 0,
|
|
+ 0, 0, 0, 0, &res
|
|
+ );
|
|
+
|
|
+ if (res.a0) {
|
|
+ dev_err(&pdev->dev, "no ATF memory for init\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ dev_dbg(&pdev->dev, "allocated %d shared memory pages\n", page_num);
|
|
+
|
|
+ // Remap the physical location to kernel space using ioremap
|
|
+ ddr_psci_param = (struct share_params *)ioremap(res.a1, page_num << PAGE_SHIFT);
|
|
+
|
|
+ if (of_get_rk3228_timings(&pdev->dev, pdev->dev.of_node,
|
|
+ (uint32_t *)ddr_psci_param))
|
|
+ return -ENOMEM;
|
|
+
|
|
+ // Reset Hz value
|
|
+ ddr_psci_param->hz = 0;
|
|
+
|
|
+ arm_smccc_smc(
|
|
+ ROCKCHIP_SIP_DRAM_FREQ,
|
|
+ SHARE_PAGE_TYPE_DDR, 0, ROCKCHIP_SIP_CONFIG_DRAM_INIT,
|
|
+ 0, 0, 0, 0, &res
|
|
+ );
|
|
+
|
|
+ if (res.a0) {
|
|
+ dev_err(&pdev->dev, "rockchip_sip_config_dram_init error:%lx\n",
|
|
+ res.a0);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ dev_notice(&pdev->dev, "TEE DRAM configuration initialized\n");
|
|
+
|
|
+ rdev->set_auto_self_refresh = rockchip_ddr_set_auto_self_refresh;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static __maybe_unused int rk3228_dmc_suspend(struct device *dev)
|
|
+{
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(dev);
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_disable_edev(rdev->edev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to disable the devfreq-event devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devfreq_suspend_device(rdev->devfreq.devfreq);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to suspend the devfreq devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int rk3228_dmc_resume(struct device *dev)
|
|
+{
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(dev);
|
|
+ int ret = 0;
|
|
+
|
|
+ ret = devfreq_event_enable_edev(rdev->edev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable the devfreq-event devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = devfreq_resume_device(rdev->devfreq.devfreq);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to resume the devfreq devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static uint32_t rk3228_get_dram_type(struct device *dev, struct device_node *node_grf, struct rk3228_dmc *data)
|
|
+{
|
|
+
|
|
+ struct regmap *regmap_grf;
|
|
+ uint32_t dram_type;
|
|
+ uint32_t val;
|
|
+
|
|
+ dram_type = UNUSED;
|
|
+
|
|
+ regmap_grf = syscon_node_to_regmap(node_grf);
|
|
+
|
|
+ if (IS_ERR(regmap_grf)) {
|
|
+ dev_err(dev, "Cannot map rockchip,grf\n");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ regmap_read(regmap_grf, RK3228_GRF_OS_REG2, &val);
|
|
+ dram_type = READ_DRAMTYPE_INFO(val);
|
|
+
|
|
+err:
|
|
+
|
|
+ return dram_type;
|
|
+
|
|
+}
|
|
+
|
|
+static SIMPLE_DEV_PM_OPS(rk3228_dmc_pm, rk3228_dmc_suspend,
|
|
+ rk3228_dmc_resume);
|
|
+
|
|
+static int rk3328_dmc_print_info(struct rk3228_dmc *rdev)
|
|
+{
|
|
+
|
|
+ u32 tcl;
|
|
+ u32 tras;
|
|
+ u32 trp;
|
|
+ u32 trcd;
|
|
+
|
|
+ u32 mcfg;
|
|
+ u32 reg_ddr_type1;
|
|
+ u32 reg_ddr_type2;
|
|
+
|
|
+ u32 dram_type;
|
|
+ u32 cr;
|
|
+
|
|
+ const char * const dram_types[] = {
|
|
+ "LPDDR2 S2",
|
|
+ "LPDDR2 S4",
|
|
+ "DDR3",
|
|
+ "LPDDR3",
|
|
+ "Unknown"
|
|
+ };
|
|
+
|
|
+ const char * const cr_types[] = {
|
|
+ "1T",
|
|
+ "2T"
|
|
+ };
|
|
+
|
|
+
|
|
+ tcl = readl(rdev->iomem + DDR_PCTL_TCL) & 0xf;
|
|
+ tras = readl(rdev->iomem + DDR_PCTL_TRAS) & 0x3f;
|
|
+ trp = readl(rdev->iomem + DDR_PCTL_TRP) & 0xf;
|
|
+ trcd = readl(rdev->iomem + DDR_PCTL_TRCD) & 0xf;
|
|
+
|
|
+ mcfg = readl(rdev->iomem + DDR_PCTL_MCFG);
|
|
+
|
|
+ reg_ddr_type1 = (mcfg & MCFG_DDR_MASK) >> MCFG_DDR_SHIFT;
|
|
+ reg_ddr_type2 = (mcfg & MCFG_LPDDR_MASK) >> MCFG_LPDDR_SHIFT;
|
|
+ cr = MCFG_CR_2T_BIT(mcfg);
|
|
+
|
|
+ switch (reg_ddr_type1) {
|
|
+ case MCFG_LPDDR2_S2:
|
|
+ dram_type = 0;
|
|
+ break;
|
|
+ case MCFG_LPDDR2_S4:
|
|
+ dram_type = 1;
|
|
+ break;
|
|
+ case MCFG_DDR3:
|
|
+ dram_type = reg_ddr_type2 == LPDDR3_EN ? 3 : 2;
|
|
+ break;
|
|
+ default:
|
|
+ dram_type = 4;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ dev_info(rdev->dev,
|
|
+ "memory type %s, timings (tCL, tRCD, tRP, tRAS): CL%d-%d-%d-%d command rate: %s (mcfg register: 0x%x)\n",
|
|
+ dram_types[dram_type], tcl, trcd, trp, tras, cr_types[cr], mcfg);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct rk3228_dmc *data;
|
|
+ struct device_node *node_grf;
|
|
+ int ret;
|
|
+
|
|
+ data = devm_kzalloc(dev, sizeof(struct rk3228_dmc), GFP_KERNEL);
|
|
+ if (!data)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ //mutex_init(&data->lock);
|
|
+
|
|
+ data->dmc_clk = devm_clk_get(dev, "ddr_sclk");
|
|
+ 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, "devfreq-events", 0);
|
|
+ if (IS_ERR(data->edev))
|
|
+ return -EPROBE_DEFER;
|
|
+
|
|
+ data->iomem = devm_platform_ioremap_resource(pdev, 0);
|
|
+ if (IS_ERR(data->iomem)) {
|
|
+ dev_err(dev, "fail to ioremap iomem\n");
|
|
+ ret = PTR_ERR(data->iomem);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ data->dev = dev;
|
|
+
|
|
+ rk3328_dmc_print_info(data);
|
|
+
|
|
+ node_grf = of_parse_phandle(np, "rockchip,grf", 0);
|
|
+ if (node_grf) {
|
|
+
|
|
+ data->dram_type = rk3228_get_dram_type(dev, node_grf, data);
|
|
+
|
|
+ if (data->dram_type == LPDDR2) {
|
|
+ dev_warn(dev, "detected LPDDR2 memory\n");
|
|
+ } else if (data->dram_type == DDR2) {
|
|
+ dev_warn(dev, "detected DDR2 memory\n");
|
|
+ } else if (data->dram_type == DDR3) {
|
|
+ dev_info(dev, "detected DDR3 memory\n");
|
|
+ } else if (data->dram_type == LPDDR3) {
|
|
+ dev_info(dev, "detected LPDDR3 memory\n");
|
|
+ } else if (data->dram_type == DDR4) {
|
|
+ dev_info(dev, "detected DDR4 memory\n");
|
|
+ } else if (data->dram_type == LPDDR4) {
|
|
+ dev_info(dev, "detected LPDDR4 memory\n");
|
|
+ } else if (data->dram_type == UNUSED) {
|
|
+ dev_info(dev, "memory type not detected\n");
|
|
+ } else {
|
|
+ dev_info(dev, "unknown memory type: 0x%x\n", data->dram_type);
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+
|
|
+ dev_warn(dev, "Cannot get rockchip,grf\n");
|
|
+ data->dram_type = UNUSED;
|
|
+
|
|
+ }
|
|
+
|
|
+ if (data->dram_type == DDR3 ||
|
|
+ data->dram_type == LPDDR3 ||
|
|
+ data->dram_type == DDR4 ||
|
|
+ data->dram_type == LPDDR4) {
|
|
+
|
|
+ ret = devfreq_event_enable_edev(data->edev);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dev, "failed to enable devfreq-event devices\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = rk3228_dmc_init(pdev, data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+
|
|
+
|
|
+ ret = rk3228_devfreq_init(data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ } else {
|
|
+
|
|
+ dev_warn(dev, "detected memory type does not support clock scaling\n");
|
|
+
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, data);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static int rk3228_dmc_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct rk3228_dmc *rdev = dev_get_drvdata(&pdev->dev);
|
|
+
|
|
+ /*
|
|
+ * Before remove the opp table we need to unregister the opp notifier.
|
|
+ */
|
|
+ rk3228_devfreq_fini(rdev);
|
|
+
|
|
+ if (ddr_psci_param)
|
|
+ iounmap(ddr_psci_param);
|
|
+
|
|
+ if (rdev->iomem)
|
|
+ iounmap(rdev->iomem);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id rk3228_dmc_of_match[] = {
|
|
+ { .compatible = "rockchip,rk3228-dmc" },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, rk3228_dmc_of_match);
|
|
+
|
|
+static struct platform_driver rk3228_dmc_driver = {
|
|
+ .probe = rk3228_dmc_probe,
|
|
+ .remove = rk3228_dmc_remove,
|
|
+ .driver = {
|
|
+ .name = "rk3228-dmc",
|
|
+ .pm = &rk3228_dmc_pm,
|
|
+ .of_match_table = rk3228_dmc_of_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(rk3228_dmc_driver);
|
|
+
|
|
+#ifdef CONFIG_ARM
|
|
+static __init int sip_firmware_init(void)
|
|
+{
|
|
+ struct arm_smccc_res res;
|
|
+
|
|
+ /*
|
|
+ * OP-TEE works on kernel 3.10 and 4.4 and we have different sip
|
|
+ * implement. We should tell OP-TEE the current rockchip sip version.
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ *
|
|
+ * res = __invoke_sip_fn_smc(ROCKCHIP_SIP_SIP_VERSION, ROCKCHIP_SIP_IMPLEMENT_V2,
|
|
+ SECURE_REG_WR, 0);
|
|
+
|
|
+ arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
|
|
+ */
|
|
+
|
|
+ arm_smccc_smc(ROCKCHIP_SIP_SIP_VERSION, ROCKCHIP_SIP_IMPLEMENT_V2, SECURE_REG_WR, 0, 0, 0, 0, 0, &res);
|
|
+
|
|
+ if (res.a0)
|
|
+ pr_err("%s: set rockchip sip version v2 failed\n", __func__);
|
|
+
|
|
+ pr_notice("Rockchip SIP initialized, version 0x%lx\n", res.a1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+arch_initcall(sip_firmware_init);
|
|
+#endif
|
|
+
|
|
+MODULE_LICENSE("GPL v2");
|
|
+MODULE_AUTHOR("Lin Huang <hl@rock-chips.com>");
|
|
+MODULE_DESCRIPTION("RK3228 dmcfreq driver with devfreq framework");
|
|
--
|
|
2.25.1
|
|
|
|
|