diff --git a/package/kernel/rockchip-drivers/Makefile b/package/kernel/rockchip-drivers/Makefile index 6bb43d62cc..65a7473d60 100644 --- a/package/kernel/rockchip-drivers/Makefile +++ b/package/kernel/rockchip-drivers/Makefile @@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=rockchip-drivers PKG_VERSION:=24.11.2 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_HASH:=92a837b8ea38adbd32f2879561322f247fa6606e3d0abe4dd73eb738200a1897 PKG_SOURCE_URL_FILE:=v$(PKG_VERSION).tar.gz @@ -11,6 +11,7 @@ PKG_SOURCE:=rockchip-drivers-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/jjm2473/rockchip-drivers/archive/refs/tags/ PKG_BUILD_PARALLEL:=1 +PKG_EXTMOD_SUBDIRS:=drivers PKG_MAINTAINER:=jjm2473 @@ -20,6 +21,48 @@ include $(INCLUDE_DIR)/package.mk TAR_OPTIONS+= --strip-components 1 TAR_CMD=$(HOST_TAR) -C $(1) $(TAR_OPTIONS) +define KernelPackage/rockchip-sip + TITLE:=Rockchip SIP interface + HIDDEN:=1 + FILES:= \ + $(PKG_BUILD_DIR)/drivers/firmware/rockchip_sip.ko +endef + +define KernelPackage/rockchip-pvtm + TITLE:=Rockchip PVTM support + HIDDEN:=1 + FILES:= \ + $(PKG_BUILD_DIR)/drivers/soc/rockchip/rockchip_pvtm.ko + AUTOLOAD:=$(call AutoProbe,rockchip_pvtm) +endef + +define KernelPackage/rockchip-opp-select + TITLE:=Rockchip OPP select support + HIDDEN:=1 + DEPENDS:=+kmod-rockchip-pvtm +kmod-rockchip-sip + KCONFIG:=CONFIG_PM_DEVFREQ=y + FILES:= \ + $(PKG_BUILD_DIR)/drivers/soc/rockchip/rockchip_opp_select.ko +endef + +define KernelPackage/rockchip-system-monitor + TITLE:=Rockchip system monitor support + DEPENDS:=+kmod-rockchip-opp-select + HIDDEN:=1 + FILES:= \ + $(PKG_BUILD_DIR)/drivers/soc/rockchip/rockchip_system_monitor.ko + AUTOLOAD:=$(call AutoProbe,rockchip_system_monitor) +endef + +# rockchip-ipa is only for linux before 6.1.0 +define KernelPackage/rockchip-ipa + TITLE:=Rockchip IPA support + DEPENDS:=+kmod-rockchip-opp-select + HIDDEN:=1 + FILES:= \ + $(PKG_BUILD_DIR)/drivers/soc/rockchip/rockchip_ipa.ko +endef + define KernelPackage/rga2 SUBMENU:=Video Support TITLE:=RK RGA2 @@ -51,7 +94,7 @@ endef define KernelPackage/rk_vcodec SUBMENU:=Video Support TITLE:=RK mpp service framework (rk_vcodec) - DEPENDS:=@TARGET_rockchip + DEPENDS:=@TARGET_rockchip +kmod-rockchip-system-monitor FILES:= \ $(PKG_BUILD_DIR)/drivers/video/rockchip/mpp/rk_vcodec.ko AUTOLOAD:=$(call AutoProbe,rk_vcodec) @@ -64,7 +107,7 @@ endef define KernelPackage/rknpu SUBMENU:=Other modules TITLE:=Rockchip RKNPU driver - DEPENDS:=@TARGET_rockchip +kmod-drm + DEPENDS:=@TARGET_rockchip +kmod-drm +kmod-rockchip-system-monitor KCONFIG:=CONFIG_SYNC_FILE=y FILES:= \ $(PKG_BUILD_DIR)/drivers/rknpu/rknpu.ko @@ -81,7 +124,11 @@ config-y:= CPU_RK3568 \ CPU_RK3576 \ CPU_RK3562 \ CPU_RK3368 -config-m:= +config-m:= ROCKCHIP_PVTM \ + ROCKCHIP_IPA \ + ROCKCHIP_SIP \ + ROCKCHIP_SYSTEM_MONITOR \ + ROCKCHIP_OPP ifdef CONFIG_PACKAGE_kmod-rga2 config-m += ROCKCHIP_RGA2 @@ -138,18 +185,16 @@ define Build/Compile +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \ $(KERNEL_MAKE_FLAGS) \ $(PKG_MAKE_FLAGS) \ - M="$(PKG_BUILD_DIR)/drivers/video/rockchip" \ - EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ - modules - - +$(MAKE) $(PKG_JOBS) -C "$(LINUX_DIR)" \ - $(KERNEL_MAKE_FLAGS) \ - $(PKG_MAKE_FLAGS) \ - M="$(PKG_BUILD_DIR)/drivers/rknpu" \ + M="$(PKG_BUILD_DIR)/drivers" \ EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ modules endef +$(eval $(call KernelPackage,rockchip-sip)) +$(eval $(call KernelPackage,rockchip-ipa)) +$(eval $(call KernelPackage,rockchip-pvtm)) +$(eval $(call KernelPackage,rockchip-opp-select)) +$(eval $(call KernelPackage,rockchip-system-monitor)) $(eval $(call KernelPackage,rga2)) $(eval $(call KernelPackage,rga3)) $(eval $(call KernelPackage,rk_vcodec)) diff --git a/package/kernel/rockchip-drivers/patches/117-fix-rockchip_opp_set_low_length-undefined.patch b/package/kernel/rockchip-drivers/patches/117-fix-rockchip_opp_set_low_length-undefined.patch deleted file mode 100644 index 9910a2061c..0000000000 --- a/package/kernel/rockchip-drivers/patches/117-fix-rockchip_opp_set_low_length-undefined.patch +++ /dev/null @@ -1,34 +0,0 @@ -From f621cf43274a3ba9a644ed02cd38f09919244e41 Mon Sep 17 00:00:00 2001 -From: jjm2473 -Date: Wed, 3 Sep 2025 15:05:32 +0800 -Subject: [PATCH] fix rockchip_opp_set_low_length undefined - ---- - include/soc/rockchip/rockchip_opp_select.h | 9 +++++++++ - 1 file changed, 9 insertions(+) - ---- a/include/soc/rockchip/rockchip_opp_select.h -+++ b/include/soc/rockchip/rockchip_opp_select.h -@@ -166,6 +166,8 @@ int rockchip_set_intermediate_rate(struc - struct clk *clk, unsigned long old_freq, - unsigned long new_freq, bool is_scaling_up, - bool is_set_clk); -+int rockchip_opp_set_low_length(struct device *dev, struct device_node *np, -+ struct rockchip_opp_info *opp_info); - int rockchip_opp_config_regulators(struct device *dev, - struct dev_pm_opp *old_opp, - struct dev_pm_opp *new_opp, -@@ -252,6 +254,13 @@ rockchip_set_intermediate_rate(struct de - { - return -EOPNOTSUPP; - } -+ -+static inline int -+rockchip_opp_set_low_length(struct device *dev, struct device_node *np, -+ struct rockchip_opp_info *opp_info) -+{ -+ return -EOPNOTSUPP; -+} - - static inline int - rockchip_opp_config_regulators(struct device *dev, diff --git a/package/kernel/rockchip-drivers/patches/117-include-soc-rockchip-sync-6.6.patch b/package/kernel/rockchip-drivers/patches/117-include-soc-rockchip-sync-6.6.patch new file mode 100644 index 0000000000..f15cb6bdb4 --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/117-include-soc-rockchip-sync-6.6.patch @@ -0,0 +1,111 @@ +From b53308643ce75bd4b1ffb47572d19c8c2aed8529 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 3 Sep 2025 15:05:32 +0800 +Subject: [PATCH] include/soc/rockchip: sync from develop-6.6 + +--- + include/soc/rockchip/rockchip_opp_select.h | 17 +++++++++++++++++ + include/soc/rockchip/rockchip_system_monitor.h | 14 +++++++++++++- + 2 files changed, 30 insertions(+), 1 deletion(-) + +--- a/include/soc/rockchip/rockchip_opp_select.h ++++ b/include/soc/rockchip/rockchip_opp_select.h +@@ -44,6 +44,7 @@ struct volt_rm_table { + struct rockchip_opp_data { + config_clks_t config_clks; + config_regulators_t config_regulators; ++ bool is_use_pvtpll; + + int (*get_soc_info)(struct device *dev, struct device_node *np, + int *bin, int *process); +@@ -97,6 +98,7 @@ struct pvtpll_opp_table { + * @init_freq: Set the initial frequency when init opp table. + * @is_rate_volt_checked: Marks if device has checked initial rate and voltage. + * @pvtpll_clk_id: Device's clock id. ++ * @pvtpll_smc: Marks if smc call of pvtpll is available. + * @pvtpll_low_temp: Marks if device has low temperature pvtpll config. + */ + struct rockchip_opp_info { +@@ -138,9 +140,15 @@ struct rockchip_opp_info { + bool is_rate_volt_checked; + + u32 pvtpll_clk_id; ++ bool pvtpll_smc; + bool pvtpll_low_temp; + }; + ++static inline bool rockchip_opp_is_use_pvtpll(struct rockchip_opp_info *info) ++{ ++ return (info->is_scmi_clk || (info->data && info->data->is_use_pvtpll)); ++} ++ + #if IS_ENABLED(CONFIG_ROCKCHIP_OPP) + int rockchip_of_get_leakage(struct device *dev, char *lkg_name, int *leakage); + int rockchip_nvmem_cell_read_u8(struct device_node *np, const char *cell_id, +@@ -166,6 +174,8 @@ int rockchip_set_intermediate_rate(struc + struct clk *clk, unsigned long old_freq, + unsigned long new_freq, bool is_scaling_up, + bool is_set_clk); ++int rockchip_opp_set_low_length(struct device *dev, struct device_node *np, ++ struct rockchip_opp_info *opp_info); + int rockchip_opp_config_regulators(struct device *dev, + struct dev_pm_opp *old_opp, + struct dev_pm_opp *new_opp, +@@ -252,6 +262,13 @@ rockchip_set_intermediate_rate(struct de + { + return -EOPNOTSUPP; + } ++ ++static inline int ++rockchip_opp_set_low_length(struct device *dev, struct device_node *np, ++ struct rockchip_opp_info *opp_info) ++{ ++ return -EOPNOTSUPP; ++} + + static inline int + rockchip_opp_config_regulators(struct device *dev, +--- a/include/soc/rockchip/rockchip_system_monitor.h ++++ b/include/soc/rockchip/rockchip_system_monitor.h +@@ -80,7 +80,8 @@ struct temp_opp_table { + * @low_temp_min_volt: Minimum voltage of OPPs when low temperature, in + * microvolt + * @high_temp_max_volt: Maximum voltage when high temperature, in microvolt +- * @video_4k_freq: Maximum frequency when paly 4k video, in KHz ++ * @video_1080p_freq: Maximum frequency when play 1080p video, in KHz ++ * @video_4k_freq: Maximum frequency when play 4k video, in KHz + * @reboot_freq: Limit maximum and minimum frequency when reboot, in KHz + * @status_min_limit: Minimum frequency of some status frequency, in KHz + * @status_max_limit: Minimum frequency of all status frequency, in KHz +@@ -111,6 +112,7 @@ struct monitor_dev_info { + unsigned long low_temp_min_volt; + unsigned long high_temp_max_volt; + unsigned int early_suspend_freq; ++ unsigned int video_1080p_freq; + unsigned int video_4k_freq; + unsigned int reboot_freq; + unsigned int status_min_limit; +@@ -149,6 +151,8 @@ int rockchip_monitor_dev_low_temp_adjust + int rockchip_monitor_dev_high_temp_adjust(struct monitor_dev_info *info, + bool is_high); + int rockchip_monitor_suspend_low_temp_adjust(int cpu); ++void rockchip_monitor_remove_cpu_limit(int cpu); ++void rockchip_monitor_restore_cpu_limit(int cpu); + int rockchip_system_monitor_register_notifier(struct notifier_block *nb); + void rockchip_system_monitor_unregister_notifier(struct notifier_block *nb); + #else +@@ -201,6 +205,14 @@ static inline int rockchip_monitor_suspe + return 0; + }; + ++static inline void rockchip_monitor_remove_cpu_limit(int cpu) ++{ ++}; ++ ++static inline void rockchip_monitor_restore_cpu_limit(int cpu) ++{ ++}; ++ + static inline int + rockchip_system_monitor_register_notifier(struct notifier_block *nb) + { diff --git a/package/kernel/rockchip-drivers/patches/118-include-rockchip_sip-sync-6.6.patch b/package/kernel/rockchip-drivers/patches/118-include-rockchip_sip-sync-6.6.patch new file mode 100644 index 0000000000..45a6da64cc --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/118-include-rockchip_sip-sync-6.6.patch @@ -0,0 +1,130 @@ +From afa7e4065ba4f577a516feeca495e4ecc527524e Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 10 Sep 2025 13:31:37 +0800 +Subject: [PATCH] include/rockchip_sip.h: sync from develop-6.6 + +--- + include/linux/rockchip/rockchip_sip.h | 56 +++++++++++++++++++++++++++ + 1 file changed, 56 insertions(+) + +--- a/include/linux/rockchip/rockchip_sip.h ++++ b/include/linux/rockchip/rockchip_sip.h +@@ -33,6 +33,7 @@ + #define PSCI_SIP_VPU_RESET 0x8200000c + #define SIP_BUS_CFG 0x8200000d + #define SIP_LAST_LOG 0x8200000e ++#define SIP_ACCESS_MEM_OS_REG 0x8200000f + #define SIP_SCMI_AGENT0 0x82000010 + #define SIP_SCMI_AGENT1 0x82000011 + #define SIP_SCMI_AGENT2 0x82000012 +@@ -58,6 +59,8 @@ + #define SIP_HDMIRX_CFG 0x82000027 + #define SIP_MCU_CFG 0x82000028 + #define SIP_PVTPLL_CFG 0x82000029 ++#define SIP_GPIO_CFG 0x8200002c ++#define SIP_CPU_PM_CFG 0x8200002d + + #define TRUSTED_OS_HDCPKEY_INIT 0xB7000003 + +@@ -110,6 +113,13 @@ + #define LINUX_PM_STATE 0x09 + #define SUSPEND_IO_RET_CONFIG 0x0a + #define SLEEP_PIN_CONFIG 0x0b ++#define SLEEP_IO_CONFIG 0x0c ++ ++enum { ++ RK_PM_SLEEP_IO_CFG_CNT = 0, ++ RK_PM_SLEEP_IO_CFG_VAL = 1, ++ RK_PM_SLEEP_IO_CFG_MAX, ++}; + + /* SIP_REMOTECTL_CFG call types */ + #define REMOTECTL_SET_IRQ 0xf0 +@@ -120,6 +130,12 @@ + /* wakeup state */ + #define REMOTECTL_PWRKEY_WAKEUP 0xdeadbeaf + ++/* SIP_ACCESS_MEM_OS_REG child configs */ ++enum { ++ RK_MEM_OS_REG_READ = 0, ++ RK_MEM_OS_REG_WRITE, ++}; ++ + /* SIP_MCU_CFG child configs, MCU ID */ + enum { + RK_BUS_MCU, +@@ -232,6 +248,22 @@ enum { + PVTPLL_GET_INFO = 0, + PVTPLL_ADJUST_TABLE = 1, + PVTPLL_LOW_TEMP = 2, ++ PVTPLL_VOLT_SEL = 3, ++}; ++ ++/* SIP_GPIO_CFG child configs */ ++enum { ++ GPIO_GET_GROUP_INFO = 0, ++ GPIO_SET_GROUP_INFO = 1, ++ GPIO_GET_VIRT_EN = 2, ++ GPIO_SET_VIRT_EN = 3, ++ GPIO_SET_STORE_ST = 4, ++ GPIO_CLEAR_STORE_ST = 5, ++}; ++ ++/* SIP_CPU_PM_CFG child configs */ ++enum { ++ CPU_PM_CLUST_AUTO_PD_EN = 0, + }; + + struct pt_regs; +@@ -262,6 +294,7 @@ int sip_smc_secure_reg_write(u32 addr_ph + u32 sip_smc_secure_reg_read(u32 addr_phy); + struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2); + struct dram_addrmap_info *sip_smc_get_dram_map(void); ++int sip_smc_access_mem_os_reg(u32 func, u32 id, u32 *val); + int sip_smc_amp_config(u32 sub_func_id, u32 arg1, u32 arg2, u32 arg3); + struct arm_smccc_res sip_smc_get_amp_info(u32 sub_func_id, u32 arg1); + struct arm_smccc_res sip_smc_get_pvtpll_info(u32 sub_func_id, u32 arg1); +@@ -270,6 +303,9 @@ struct arm_smccc_res sip_smc_pvtpll_conf + + void __iomem *sip_hdcp_request_share_memory(int id); + struct arm_smccc_res sip_hdcp_config(u32 arg0, u32 arg1, u32 arg2); ++struct arm_smccc_res sip_smc_gpio_config(u32 sub_func_id, u32 arg1, u32 arg2, ++ u32 arg3); ++int sip_smc_cpu_pm_config(u32 func, u32 id, u32 cfg); + ulong sip_cpu_logical_map_mpidr(u32 cpu); + /***************************fiq debugger **************************************/ + void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu); +@@ -360,6 +396,13 @@ static inline struct dram_addrmap_info * + return NULL; + } + ++static inline int sip_smc_access_mem_os_reg(u32 func, ++ u32 id, ++ u32 *val) ++{ ++ return 0; ++} ++ + static inline int sip_smc_amp_config(u32 sub_func_id, + u32 arg1, + u32 arg2, +@@ -406,6 +449,19 @@ static inline struct arm_smccc_res sip_h + return tmp; + } + ++static inline struct arm_smccc_res sip_smc_gpio_config(u32 sub_func_id, u32 arg1, ++ u32 arg2, u32 arg3) ++{ ++ struct arm_smccc_res tmp = { .a0 = SIP_RET_NOT_SUPPORTED, }; ++ ++ return tmp; ++} ++ ++static inline int sip_smc_cpu_pm_config(u32 func, u32 id, u32 cfg) ++{ ++ return SIP_RET_NOT_SUPPORTED; ++} ++ + static inline ulong sip_cpu_logical_map_mpidr(u32 cpu) { return 0; } + + /***************************fiq debugger **************************************/ diff --git a/package/kernel/rockchip-drivers/patches/119-rknpu_devfreq-sync-6.6.patch b/package/kernel/rockchip-drivers/patches/119-rknpu_devfreq-sync-6.6.patch new file mode 100644 index 0000000000..a322835d89 --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/119-rknpu_devfreq-sync-6.6.patch @@ -0,0 +1,64 @@ +From 98feb9ac47f8aeae0e7d8cbe911ff56c27fa4f1e Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 10 Sep 2025 13:32:17 +0800 +Subject: [PATCH] rknpu_devfreq: sync from develop-6.6 + +--- + drivers/rknpu/rknpu_devfreq.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +--- a/drivers/rknpu/rknpu_devfreq.c ++++ b/drivers/rknpu/rknpu_devfreq.c +@@ -1,6 +1,6 @@ + // SPDX-License-Identifier: GPL-2.0 + /* +- * Copyright (C) Rockchip Electronics Co.Ltd ++ * Copyright (C) Rockchip Electronics Co., Ltd. + * Author: Finley Xiao + */ + +@@ -120,6 +120,15 @@ static int rk3588_npu_get_soc_info(struc + else if (value == 0xa) + *bin = 2; + } ++ if (of_property_match_string(np, "nvmem-cell-names", "customer_demand") >= 0) { ++ ret = rockchip_nvmem_cell_read_u8(np, "customer_demand", &value); ++ if (ret) { ++ dev_err(dev, "Failed to get customer_demand\n"); ++ return ret; ++ } ++ if (value == 0x3) ++ *bin = 4; ++ } + if (*bin < 0) + *bin = 0; + LOG_DEV_INFO(dev, "bin=%d\n", *bin); +@@ -264,6 +273,10 @@ static const struct of_device_id rockchi + .data = (void *)&rk3576_npu_opp_data, + }, + { ++ .compatible = "rockchip,rk3576s", ++ .data = (void *)&rk3576_npu_opp_data, ++ }, ++ { + .compatible = "rockchip,rk3588", + .data = (void *)&rk3588_npu_opp_data, + }, +@@ -422,7 +435,7 @@ int rknpu_devfreq_runtime_suspend(struct + struct rknpu_device *rknpu_dev = dev_get_drvdata(dev); + struct rockchip_opp_info *opp_info = &rknpu_dev->opp_info; + +- if (opp_info->is_scmi_clk) { ++ if (rockchip_opp_is_use_pvtpll(opp_info)) { + if (clk_set_rate(opp_info->clk, POWER_DOWN_FREQ)) + LOG_DEV_ERROR(dev, "failed to restore clk rate\n"); + } +@@ -450,7 +463,7 @@ int rknpu_devfreq_runtime_resume(struct + if (opp_info->data && opp_info->data->set_read_margin) + opp_info->data->set_read_margin(dev, opp_info, + opp_info->target_rm); +- if (opp_info->is_scmi_clk) { ++ if (rockchip_opp_is_use_pvtpll(opp_info)) { + if (clk_set_rate(opp_info->clk, rknpu_dev->current_freq)) + LOG_DEV_ERROR(dev, "failed to set power down rate\n"); + } diff --git a/package/kernel/rockchip-drivers/patches/120-soc-rockchip-fix-include-path-on-OOT-build.patch b/package/kernel/rockchip-drivers/patches/120-soc-rockchip-fix-include-path-on-OOT-build.patch new file mode 100644 index 0000000000..9a4c3d2829 --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/120-soc-rockchip-fix-include-path-on-OOT-build.patch @@ -0,0 +1,40 @@ +From debe29461df32518661b42bd119ddd68a54a5dc5 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Mon, 8 Sep 2025 12:27:39 +0800 +Subject: [PATCH] soc/rockchip: fix include path on OOT build + +--- + drivers/soc/rockchip/rockchip_opp_select.c | 6 +++--- + drivers/soc/rockchip/rockchip_system_monitor.c | 6 +++--- + 2 files changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/soc/rockchip/rockchip_opp_select.c ++++ b/drivers/soc/rockchip/rockchip_opp_select.c +@@ -20,9 +20,9 @@ + #include + #include + +-#include "../../clk/rockchip/clk.h" +-#include "../../opp/opp.h" +-#include "../../devfreq/governor.h" ++#include <../drivers/clk/rockchip/clk.h> ++#include <../drivers/opp/opp.h> ++#include <../drivers/devfreq/governor.h> + + #define MAX_PROP_NAME_LEN 6 + #define SEL_TABLE_END ~1 +--- a/drivers/soc/rockchip/rockchip_system_monitor.c ++++ b/drivers/soc/rockchip/rockchip_system_monitor.c +@@ -40,9 +40,9 @@ + #include + #endif + +-#include "../../opp/opp.h" +-#include "../../regulator/internal.h" +-#include "../../thermal/thermal_core.h" ++#include <../drivers/opp/opp.h> ++#include <../drivers/regulator/internal.h> ++#include <../drivers/thermal/thermal_core.h> + + #define CPU_REBOOT_FREQ 816000 /* kHz */ + #define VIDEO_1080P_SIZE (1920 * 1080) diff --git a/package/kernel/rockchip-drivers/patches/121-soc-rockchip-fix-CE.patch b/package/kernel/rockchip-drivers/patches/121-soc-rockchip-fix-CE.patch new file mode 100644 index 0000000000..d7934fae8b --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/121-soc-rockchip-fix-CE.patch @@ -0,0 +1,46 @@ +From 79a10b75fdfefceaf7b4f2f5e0d425c5947c8bd8 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 10 Sep 2025 13:17:06 +0800 +Subject: [PATCH] soc/rockchip: fix ce + +--- + drivers/soc/rockchip/rockchip_opp_select.c | 7 +++++++ + drivers/soc/rockchip/rockchip_system_monitor.c | 2 ++ + 2 files changed, 9 insertions(+) + +--- a/drivers/soc/rockchip/rockchip_opp_select.c ++++ b/drivers/soc/rockchip/rockchip_opp_select.c +@@ -2090,8 +2090,15 @@ static int rockchip_pvtpll_set_volt_sel( + return 0; + + if (!info->pvtpll_smc) ++#if IS_REACHABLE(CONFIG_ROCKCHIP_CLK_PVTPLL) + return rockchip_pvtpll_volt_sel_adjust(info->pvtpll_clk_id, + info->volt_sel); ++#else ++ { ++ WARN_ONCE(1, "CONFIG_ROCKCHIP_CLK_PVTPLL not enabled, should not going to here\n"); ++ return 0; ++ } ++#endif + + res = sip_smc_pvtpll_config(PVTPLL_VOLT_SEL, info->pvtpll_clk_id, + (u32)info->volt_sel, 0, 0, 0, 0); +--- a/drivers/soc/rockchip/rockchip_system_monitor.c ++++ b/drivers/soc/rockchip/rockchip_system_monitor.c +@@ -1922,6 +1922,7 @@ static void system_monitor_early_min_vol + static DECLARE_DELAYED_WORK(system_monitor_early_min_volt_work, + system_monitor_early_min_volt_function); + ++#if IS_ENABLED(CONFIG_CPU_RK3506) + static int system_monitor_ddr_trefi_update(u32 ref_mode) + { + struct arm_smccc_res res; +@@ -1942,6 +1943,7 @@ static __maybe_unused int rk3506_system_ + + return 0; + } ++#endif + + static const struct of_device_id rockchip_system_monitor_of_match[] = { + { .compatible = "rockchip,system-monitor", .data = NULL }, diff --git a/package/kernel/rockchip-drivers/patches/122-firmware-fix-CE.patch b/package/kernel/rockchip-drivers/patches/122-firmware-fix-CE.patch new file mode 100644 index 0000000000..2d09a3453d --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/122-firmware-fix-CE.patch @@ -0,0 +1,27 @@ +From 174713a3cc2b01324ed3cb772642fe79b1cabdfd Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 10 Sep 2025 13:27:07 +0800 +Subject: [PATCH] firmware: fix ce + +--- + drivers/firmware/rockchip_sip.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/firmware/rockchip_sip.c ++++ b/drivers/firmware/rockchip_sip.c +@@ -207,6 +207,7 @@ struct arm_smccc_res sip_smc_bus_config( + } + EXPORT_SYMBOL_GPL(sip_smc_bus_config); + ++#ifdef ROCKCHIP_SIP_CONFIG_DRAM_ADDRMAP_GET + struct dram_addrmap_info *sip_smc_get_dram_map(void) + { + struct arm_smccc_res res; +@@ -236,6 +237,7 @@ struct dram_addrmap_info *sip_smc_get_dr + return map->bank_bit_first ? map : NULL; + } + EXPORT_SYMBOL_GPL(sip_smc_get_dram_map); ++#endif + + struct arm_smccc_res sip_smc_lastlog_request(void) + { diff --git a/package/kernel/rockchip-drivers/patches/123-opp-abi-compatible.patch b/package/kernel/rockchip-drivers/patches/123-opp-abi-compatible.patch new file mode 100644 index 0000000000..992b4b90e6 --- /dev/null +++ b/package/kernel/rockchip-drivers/patches/123-opp-abi-compatible.patch @@ -0,0 +1,30 @@ +From 6ec703b634fe473982fef3f3b5726d501679dde9 Mon Sep 17 00:00:00 2001 +From: jjm2473 +Date: Wed, 10 Sep 2025 14:31:13 +0800 +Subject: [PATCH] rockchip_opp_select.h: remove is_use_pvtpll for abi + compatible + +is_use_pvtpll is only used by rv1126b_npu_opp_data +--- + include/soc/rockchip/rockchip_opp_select.h | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +--- a/include/soc/rockchip/rockchip_opp_select.h ++++ b/include/soc/rockchip/rockchip_opp_select.h +@@ -44,7 +44,6 @@ struct volt_rm_table { + struct rockchip_opp_data { + config_clks_t config_clks; + config_regulators_t config_regulators; +- bool is_use_pvtpll; + + int (*get_soc_info)(struct device *dev, struct device_node *np, + int *bin, int *process); +@@ -146,7 +145,7 @@ struct rockchip_opp_info { + + static inline bool rockchip_opp_is_use_pvtpll(struct rockchip_opp_info *info) + { +- return (info->is_scmi_clk || (info->data && info->data->is_use_pvtpll)); ++ return (info->is_scmi_clk); + } + + #if IS_ENABLED(CONFIG_ROCKCHIP_OPP) diff --git a/package/kernel/rockchip-drivers/src/drivers/Makefile b/package/kernel/rockchip-drivers/src/drivers/Makefile new file mode 100644 index 0000000000..42f95d1c26 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/Makefile @@ -0,0 +1,4 @@ +obj-y += firmware/ +obj-y += soc/rockchip/ +obj-y += video/rockchip/ +obj-y += rknpu/ diff --git a/package/kernel/rockchip-drivers/src/drivers/firmware/Kconfig b/package/kernel/rockchip-drivers/src/drivers/firmware/Kconfig new file mode 100644 index 0000000000..3489c3d2a3 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/firmware/Kconfig @@ -0,0 +1,326 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# For a description of the syntax of this configuration file, +# see Documentation/kbuild/kconfig-language.rst. +# + +menu "Firmware Drivers" + +source "drivers/firmware/arm_scmi/Kconfig" + +config ARM_SCPI_PROTOCOL + tristate "ARM System Control and Power Interface (SCPI) Message Protocol" + depends on ARM || ARM64 || COMPILE_TEST + depends on MAILBOX + help + System Control and Power Interface (SCPI) Message Protocol is + defined for the purpose of communication between the Application + Cores(AP) and the System Control Processor(SCP). The MHU peripheral + provides a mechanism for inter-processor communication between SCP + and AP. + + SCP controls most of the power management on the Application + Processors. It offers control and management of: the core/cluster + power states, various power domain DVFS including the core/cluster, + certain system clocks configuration, thermal sensors and many + others. + + This protocol library provides interface for all the client drivers + making use of the features offered by the SCP. + +config ARM_SCPI_POWER_DOMAIN + tristate "SCPI power domain driver" + depends on ARM_SCPI_PROTOCOL || (COMPILE_TEST && OF) + default y + select PM_GENERIC_DOMAINS if PM + help + This enables support for the SCPI power domains which can be + enabled or disabled via the SCP firmware + +config ARM_SDE_INTERFACE + bool "ARM Software Delegated Exception Interface (SDEI)" + depends on ARM64 + depends on ACPI_APEI_GHES + help + The Software Delegated Exception Interface (SDEI) is an ARM + standard for registering callbacks from the platform firmware + into the OS. This is typically used to implement RAS notifications. + +config EDD + tristate "BIOS Enhanced Disk Drive calls determine boot disk" + depends on X86 + help + Say Y or M here if you want to enable BIOS Enhanced Disk Drive + Services real mode BIOS calls to determine which disk + BIOS tries boot from. This information is then exported via sysfs. + + This option is experimental and is known to fail to boot on some + obscure configurations. Most disk controller BIOS vendors do + not yet implement this feature. + +config EDD_OFF + bool "Sets default behavior for EDD detection to off" + depends on EDD + default n + help + Say Y if you want EDD disabled by default, even though it is compiled into the + kernel. Say N if you want EDD enabled by default. EDD can be dynamically set + using the kernel parameter 'edd={on|skipmbr|off}'. + +config FIRMWARE_MEMMAP + bool "Add firmware-provided memory map to sysfs" if EXPERT + default X86 + help + Add the firmware-provided (unmodified) memory map to /sys/firmware/memmap. + That memory map is used for example by kexec to set up parameter area + for the next kernel, but can also be used for debugging purposes. + + See also Documentation/ABI/testing/sysfs-firmware-memmap. + +config EFI_PCDP + bool "Console device selection via EFI PCDP or HCDP table" + depends on ACPI && EFI && IA64 + default y if IA64 + help + If your firmware supplies the PCDP table, and you want to + automatically use the primary console device it describes + as the Linux console, say Y here. + + If your firmware supplies the HCDP table, and you want to + use the first serial port it describes as the Linux console, + say Y here. If your EFI ConOut path contains only a UART + device, it will become the console automatically. Otherwise, + you must specify the "console=hcdp" kernel boot argument. + + Neither the PCDP nor the HCDP affects naming of serial devices, + so a serial console may be /dev/ttyS0, /dev/ttyS1, etc, depending + on how the driver discovers devices. + + You must also enable the appropriate drivers (serial, VGA, etc.) + + See DIG64_HCDPv20_042804.pdf available from + + +config DMIID + bool "Export DMI identification via sysfs to userspace" + depends on DMI + default y + help + Say Y here if you want to query SMBIOS/DMI system identification + information from userspace through /sys/class/dmi/id/ or if you want + DMI-based module auto-loading. + +config DMI_SYSFS + tristate "DMI table support in sysfs" + depends on SYSFS && DMI + default n + help + Say Y or M here to enable the exporting of the raw DMI table + data via sysfs. This is useful for consuming the data without + requiring any access to /dev/mem at all. Tables are found + under /sys/firmware/dmi when this option is enabled and + loaded. + +config DMI_SCAN_MACHINE_NON_EFI_FALLBACK + bool + +config ISCSI_IBFT_FIND + bool "iSCSI Boot Firmware Table Attributes" + depends on X86 && ISCSI_IBFT + default n + help + This option enables the kernel to find the region of memory + in which the ISCSI Boot Firmware Table (iBFT) resides. This + is necessary for iSCSI Boot Firmware Table Attributes module to work + properly. + +config ISCSI_IBFT + tristate "iSCSI Boot Firmware Table Attributes module" + select ISCSI_BOOT_SYSFS + select ISCSI_IBFT_FIND if X86 + depends on ACPI && SCSI && SCSI_LOWLEVEL + default n + help + This option enables support for detection and exposing of iSCSI + Boot Firmware Table (iBFT) via sysfs to userspace. If you wish to + detect iSCSI boot parameters dynamically during system boot, say Y. + Otherwise, say N. + +config RASPBERRYPI_FIRMWARE + tristate "Raspberry Pi Firmware Driver" + depends on BCM2835_MBOX + help + This option enables support for communicating with the firmware on the + Raspberry Pi. + +config FW_CFG_SYSFS + tristate "QEMU fw_cfg device support in sysfs" + depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || SPARC || X86) + depends on HAS_IOPORT_MAP + default n + help + Say Y or M here to enable the exporting of the QEMU firmware + configuration (fw_cfg) file entries via sysfs. Entries are + found under /sys/firmware/fw_cfg when this option is enabled + and loaded. + +config FW_CFG_SYSFS_CMDLINE + bool "QEMU fw_cfg device parameter parsing" + depends on FW_CFG_SYSFS + help + Allow the qemu_fw_cfg device to be initialized via the kernel + command line or using a module parameter. + WARNING: Using incorrect parameters (base address in particular) + may crash your system. + +config INTEL_STRATIX10_SERVICE + tristate "Intel Stratix10 Service Layer" + depends on ARCH_INTEL_SOCFPGA && ARM64 && HAVE_ARM_SMCCC + default n + help + Intel Stratix10 service layer runs at privileged exception level, + interfaces with the service providers (FPGA manager is one of them) + and manages secure monitor call to communicate with secure monitor + software at secure monitor exception level. + + Say Y here if you want Stratix10 service layer support. + +config INTEL_STRATIX10_RSU + tristate "Intel Stratix10 Remote System Update" + depends on INTEL_STRATIX10_SERVICE + help + The Intel Remote System Update (RSU) driver exposes interfaces + access through the Intel Service Layer to user space via sysfs + device attribute nodes. The RSU interfaces report/control some of + the optional RSU features of the Stratix 10 SoC FPGA. + + The RSU provides a way for customers to update the boot + configuration of a Stratix 10 SoC device with significantly reduced + risk of corrupting the bitstream storage and bricking the system. + + Enable RSU support if you are using an Intel SoC FPGA with the RSU + feature enabled and you want Linux user space control. + + Say Y here if you want Intel RSU support. + +config MTK_ADSP_IPC + tristate "MTK ADSP IPC Protocol driver" + depends on MTK_ADSP_MBOX + help + Say yes here to add support for the MediaTek ADSP IPC + between host AP (Linux) and the firmware running on ADSP. + ADSP exists on some mtk processors. + Client might use shared memory to exchange information with ADSP. + +config QCOM_SCM + tristate + +config QCOM_SCM_DOWNLOAD_MODE_DEFAULT + bool "Qualcomm download mode enabled by default" + depends on QCOM_SCM + help + A device with "download mode" enabled will upon an unexpected + warm-restart enter a special debug mode that allows the user to + "download" memory content over USB for offline postmortem analysis. + The feature can be enabled/disabled on the kernel command line. + + Say Y here to enable "download mode" by default. + +config ROCKCHIP_SIP + tristate "Rockchip SIP interface" + depends on HAVE_ARM_SMCCC && ARCH_ROCKCHIP + help + Say Y here if you want to enable SIP callbacks for Rockchip platforms + This option enables support for communicating with the ATF. + +config SYSFB + bool + select BOOT_VESA_SUPPORT + select SCREEN_INFO + +config SYSFB_SIMPLEFB + bool "Mark VGA/VBE/EFI FB as generic system framebuffer" + depends on X86 || EFI + select SYSFB + help + Firmwares often provide initial graphics framebuffers so the BIOS, + bootloader or kernel can show basic video-output during boot for + user-guidance and debugging. Historically, x86 used the VESA BIOS + Extensions and EFI-framebuffers for this, which are mostly limited + to x86 BIOS or EFI systems. + This option, if enabled, marks VGA/VBE/EFI framebuffers as generic + framebuffers so the new generic system-framebuffer drivers can be + used instead. If the framebuffer is not compatible with the generic + modes, it is advertised as fallback platform framebuffer so legacy + drivers like efifb, vesafb and uvesafb can pick it up. + If this option is not selected, all system framebuffers are always + marked as fallback platform framebuffers as usual. + + Note: Legacy fbdev drivers, including vesafb, efifb, uvesafb, will + not be able to pick up generic system framebuffers if this option + is selected. You are highly encouraged to enable simplefb as + replacement if you select this option. simplefb can correctly deal + with generic system framebuffers. But you should still keep vesafb + and others enabled as fallback if a system framebuffer is + incompatible with simplefb. + + If unsure, say Y. + +config TI_SCI_PROTOCOL + tristate "TI System Control Interface (TISCI) Message Protocol" + depends on TI_MESSAGE_MANAGER + help + TI System Control Interface (TISCI) Message Protocol is used to manage + compute systems such as ARM, DSP etc with the system controller in + complex System on Chip(SoC) such as those found on certain keystone + generation SoC from TI. + + System controller provides various facilities including power + management function support. + + This protocol library is used by client drivers to use the features + provided by the system controller. + +config TRUSTED_FOUNDATIONS + bool "Trusted Foundations secure monitor support" + depends on ARM && CPU_V7 + help + Some devices (including most early Tegra-based consumer devices on + the market) are booted with the Trusted Foundations secure monitor + active, requiring some core operations to be performed by the secure + monitor instead of the kernel. + + This option allows the kernel to invoke the secure monitor whenever + required on devices using Trusted Foundations. See the functions and + comments in linux/firmware/trusted_foundations.h or the device tree + bindings for "tlm,trusted-foundations" for details on how to use it. + + Choose N if you don't know what this is about. + +config TURRIS_MOX_RWTM + tristate "Turris Mox rWTM secure firmware driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on HAS_DMA && OF + depends on MAILBOX + select HW_RANDOM + select ARMADA_37XX_RWTM_MBOX + help + This driver communicates with the firmware on the Cortex-M3 secure + processor of the Turris Mox router. Enable if you are building for + Turris Mox, and you will be able to read the device serial number and + other manufacturing data and also utilize the Entropy Bit Generator + for hardware random number generation. + +source "drivers/firmware/arm_ffa/Kconfig" +source "drivers/firmware/broadcom/Kconfig" +source "drivers/firmware/cirrus/Kconfig" +source "drivers/firmware/google/Kconfig" +source "drivers/firmware/efi/Kconfig" +source "drivers/firmware/imx/Kconfig" +source "drivers/firmware/meson/Kconfig" +source "drivers/firmware/psci/Kconfig" +source "drivers/firmware/smccc/Kconfig" +source "drivers/firmware/tegra/Kconfig" +source "drivers/firmware/xilinx/Kconfig" + +endmenu diff --git a/package/kernel/rockchip-drivers/src/drivers/firmware/Makefile b/package/kernel/rockchip-drivers/src/drivers/firmware/Makefile new file mode 100644 index 0000000000..bab5e8c8dc --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/firmware/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux kernel. +# + +obj-$(CONFIG_ROCKCHIP_SIP) += rockchip_sip.o + diff --git a/package/kernel/rockchip-drivers/src/drivers/firmware/rockchip_sip.c b/package/kernel/rockchip-drivers/src/drivers/firmware/rockchip_sip.c new file mode 100644 index 0000000000..b6008d7c0c --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/firmware/rockchip_sip.c @@ -0,0 +1,772 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include +#include +#include +#ifdef CONFIG_ARM +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_64BIT +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name +#else +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name +#endif + +#define SIZE_PAGE(n) ((n) << 12) + +static struct arm_smccc_res __invoke_sip_fn_smc(unsigned long function_id, + unsigned long arg0, + unsigned long arg1, + unsigned long arg2) +{ + struct arm_smccc_res res; + + arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); + return res; +} + +struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2) +{ + return __invoke_sip_fn_smc(SIP_DRAM_CONFIG, arg0, arg1, arg2); +} +EXPORT_SYMBOL_GPL(sip_smc_dram); + +struct arm_smccc_res sip_smc_get_atf_version(void) +{ + return __invoke_sip_fn_smc(SIP_ATF_VERSION, 0, 0, 0); +} +EXPORT_SYMBOL_GPL(sip_smc_get_atf_version); + +struct arm_smccc_res sip_smc_get_sip_version(void) +{ + return __invoke_sip_fn_smc(SIP_SIP_VERSION, 0, 0, 0); +} +EXPORT_SYMBOL_GPL(sip_smc_get_sip_version); + +int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, ctrl, config1, config2); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_set_suspend_mode); + +struct arm_smccc_res sip_smc_get_suspend_info(u32 info) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, info, 0, 0); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_get_suspend_info); + +int sip_smc_virtual_poweroff(void) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), 0, 0, 0); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_virtual_poweroff); + +int sip_smc_remotectl_config(u32 func, u32 data) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_REMOTECTL_CFG, func, data, 0); + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_remotectl_config); + +u32 sip_smc_secure_reg_read(u32 addr_phy) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_ACCESS_REG, 0, addr_phy, SECURE_REG_RD); + if (res.a0) + pr_err("%s error: %d, addr phy: 0x%x\n", + __func__, (int)res.a0, addr_phy); + + return res.a1; +} +EXPORT_SYMBOL_GPL(sip_smc_secure_reg_read); + +int sip_smc_secure_reg_write(u32 addr_phy, u32 val) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_ACCESS_REG, val, addr_phy, SECURE_REG_WR); + if (res.a0) + pr_err("%s error: %d, addr phy: 0x%x\n", + __func__, (int)res.a0, addr_phy); + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_secure_reg_write); + +static void *sip_map(phys_addr_t start, size_t size) +{ + struct page **pages; + phys_addr_t page_start; + unsigned int page_count; + pgprot_t prot; + unsigned int i; + void *vaddr; + + if (!pfn_valid(__phys_to_pfn(start))) + return ioremap(start, size); + + page_start = start - offset_in_page(start); + page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); + + prot = pgprot_noncached(PAGE_KERNEL); + + pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL); + if (!pages) { + pr_err("%s: Failed to allocate array for %u pages\n", + __func__, page_count); + return NULL; + } + + for (i = 0; i < page_count; i++) + pages[i] = phys_to_page(page_start + i * PAGE_SIZE); + + vaddr = vmap(pages, page_count, VM_MAP, prot); + kfree(pages); + + /* + * Since vmap() uses page granularity, we must add the offset + * into the page here, to get the byte granularity address + * into the mapping to represent the actual "start" location. + */ + return vaddr + offset_in_page(start); +} + +struct arm_smccc_res sip_smc_request_share_mem(u32 page_num, + share_page_type_t page_type) +{ + struct arm_smccc_res res; + unsigned long share_mem_phy; + + res = __invoke_sip_fn_smc(SIP_SHARE_MEM, page_num, page_type, 0); + if (IS_SIP_ERROR(res.a0)) + goto error; + + share_mem_phy = res.a1; + res.a1 = (unsigned long)sip_map(share_mem_phy, SIZE_PAGE(page_num)); + +error: + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_request_share_mem); + +struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2) +{ + return __invoke_sip_fn_smc(SIP_MCU_EL3FIQ_CFG, arg0, arg1, arg2); +} +EXPORT_SYMBOL_GPL(sip_smc_mcu_el3fiq); + +struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(PSCI_SIP_VPU_RESET, arg0, arg1, arg2); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_vpu_reset); + +struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_BUS_CFG, arg0, arg1, arg2); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_bus_config); + +struct dram_addrmap_info *sip_smc_get_dram_map(void) +{ + struct arm_smccc_res res; + static struct dram_addrmap_info *map; + struct dram_addrmap_info *m; + + if (map) + return map->bank_bit_first ? map : NULL; + + /* Request share memory size 4KB */ + res = sip_smc_request_share_mem(1, SHARE_PAGE_TYPE_DDR_ADDRMAP); + if (res.a0 != 0) { + pr_err("%s: request share memory error!\n", __func__); + return NULL; + } + m = (struct dram_addrmap_info *)res.a1; + memset_io(m, 0x0, sizeof(*m)); + + res = sip_smc_dram(SHARE_PAGE_TYPE_DDR_ADDRMAP, 0, + ROCKCHIP_SIP_CONFIG_DRAM_ADDRMAP_GET); + if (res.a0) { + pr_err("rockchip_sip_config_dram_addrmap_get error:%lx\n", res.a0); + return NULL; + } + map = m; + + return map->bank_bit_first ? map : NULL; +} +EXPORT_SYMBOL_GPL(sip_smc_get_dram_map); + +struct arm_smccc_res sip_smc_lastlog_request(void) +{ + struct arm_smccc_res res; + void __iomem *addr1, *addr2; + + res = __invoke_sip_fn_smc(SIP_LAST_LOG, local_clock(), 0, 0); + if (IS_SIP_ERROR(res.a0)) + return res; + + addr1 = sip_map(res.a1, res.a3); + if (!addr1) { + pr_err("%s: share memory buffer0 ioremap failed\n", __func__); + res.a0 = SIP_RET_INVALID_ADDRESS; + return res; + } + addr2 = sip_map(res.a2, res.a3); + if (!addr2) { + pr_err("%s: share memory buffer1 ioremap failed\n", __func__); + res.a0 = SIP_RET_INVALID_ADDRESS; + return res; + } + + res.a1 = (unsigned long)addr1; + res.a2 = (unsigned long)addr2; + + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_lastlog_request); + +int sip_smc_access_mem_os_reg(u32 func, u32 id, u32 *val) +{ + struct arm_smccc_res res; + + if (val == NULL) + return SIP_RET_INVALID_PARAMS; + + res = __invoke_sip_fn_smc(SIP_ACCESS_MEM_OS_REG, func, id, *val); + + if (func == RK_MEM_OS_REG_READ) + *val = res.a1; + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_access_mem_os_reg); + +int sip_smc_amp_config(u32 sub_func_id, u32 arg1, u32 arg2, u32 arg3) +{ + struct arm_smccc_res res; + + arm_smccc_smc(RK_SIP_AMP_CFG, sub_func_id, arg1, arg2, arg3, + 0, 0, 0, &res); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_amp_config); + +struct arm_smccc_res sip_smc_get_amp_info(u32 sub_func_id, u32 arg1) +{ + struct arm_smccc_res res; + + arm_smccc_smc(RK_SIP_AMP_CFG, sub_func_id, arg1, 0, 0, 0, 0, 0, &res); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_get_amp_info); + +struct arm_smccc_res sip_smc_get_pvtpll_info(u32 sub_func_id, u32 arg1) +{ + struct arm_smccc_res res; + + /* + * res.a0: error code(0: success, !0: error). + * res.a1: low temp config flag(0: support, !0: don't support). + */ + arm_smccc_smc(SIP_PVTPLL_CFG, sub_func_id, arg1, 0, 0, 0, 0, 0, &res); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_get_pvtpll_info); + +struct arm_smccc_res sip_smc_pvtpll_config(u32 sub_func_id, u32 arg1, u32 arg2, + u32 arg3, u32 arg4, u32 arg5, u32 arg6) +{ + struct arm_smccc_res res; + + /* + * res.a0: error code(0: success, !0: error). + */ + arm_smccc_smc(SIP_PVTPLL_CFG, sub_func_id, arg1, arg2, arg3, arg4, arg5, + arg6, &res); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_pvtpll_config); + +void __iomem *sip_hdcp_request_share_memory(int id) +{ + static void __iomem *base; + struct arm_smccc_res res; + + if (id < 0 || id >= MAX_DEVICE) { + pr_err("%s: invalid device id\n", __func__); + return NULL; + } + + if (!base) { + /* request page share memory */ + res = sip_smc_request_share_mem(2, SHARE_PAGE_TYPE_HDCP); + if (IS_SIP_ERROR(res.a0)) + return NULL; + base = (void __iomem *)res.a1; + } + + return base + id * 1024; +} +EXPORT_SYMBOL_GPL(sip_hdcp_request_share_memory); + +struct arm_smccc_res sip_hdcp_config(u32 arg0, u32 arg1, u32 arg2) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_HDCP_CONFIG, arg0, arg1, arg2); + return res; +} +EXPORT_SYMBOL_GPL(sip_hdcp_config); + +struct arm_smccc_res sip_smc_gpio_config(u32 sub_func_id, u32 arg1, u32 arg2, + u32 arg3) +{ + struct arm_smccc_res res; + + /* + * res.a0: error code(0: success, !0: error). + */ + arm_smccc_smc(SIP_GPIO_CFG, sub_func_id, arg1, arg2, arg3, 0, 0, 0, &res); + return res; +} +EXPORT_SYMBOL_GPL(sip_smc_gpio_config); + +int sip_smc_cpu_pm_config(u32 func, u32 id, u32 cfg) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_CPU_PM_CFG, func, id, cfg); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_cpu_pm_config); + +/************************** fiq debugger **************************************/ +/* + * AArch32 is not allowed to call SMC64(ATF framework does not support), so we + * don't change SIP_UARTDBG_FN to SIP_UARTDBG_CFG64 even when cpu is AArch32 + * mode. Let ATF support SIP_UARTDBG_CFG, and we just initialize SIP_UARTDBG_FN + * depends on compile option(CONFIG_ARM or CONFIG_ARM64). + */ +#ifdef CONFIG_ARM64 +#define SIP_UARTDBG_FN SIP_UARTDBG_CFG64 +#define SIP_FIQ_DBG_STACK_SIZE IRQ_STACK_SIZE +#else +#define SIP_UARTDBG_FN SIP_UARTDBG_CFG +#define SIP_FIQ_DBG_STACK_SIZE SZ_8K + +static int firmware_64_32bit; +#endif + +static int fiq_sip_enabled; +static int fiq_target_cpu; +static unsigned long fiq_stack_top; +static phys_addr_t ft_fiq_mem_phy; +static void __iomem *ft_fiq_mem_base; +static sip_fiq_debugger_uart_irq_tf_cb_t sip_fiq_debugger_uart_irq_tf; +static struct pt_regs fiq_pt_regs; + +int sip_fiq_debugger_is_enabled(void) +{ + return fiq_sip_enabled; +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_is_enabled); + +static void sip_fiq_debugger_get_pt_regs(void *reg_base, + unsigned long sp_el1) +{ + __maybe_unused struct sm_nsec_ctx *nsec_ctx = reg_base; + __maybe_unused struct gp_regs_ctx *gp_regs = reg_base; + +#ifdef CONFIG_ARM64 + /* + * 64-bit ATF + 64-bit kernel + */ + /* copy cpu context: x0 ~ spsr_el3 */ + memcpy(&fiq_pt_regs, reg_base, 8 * 31); + + /* copy pstate: spsr_el3 */ + memcpy(&fiq_pt_regs.pstate, reg_base + 0x110, 8); + fiq_pt_regs.sp = sp_el1; + + /* copy pc: elr_el3 */ + memcpy(&fiq_pt_regs.pc, reg_base + 0x118, 8); +#else + if (firmware_64_32bit == FIRMWARE_ATF_64BIT) { + /* + * 64-bit ATF + 32-bit kernel + */ + fiq_pt_regs.ARM_r0 = gp_regs->x0; + fiq_pt_regs.ARM_r1 = gp_regs->x1; + fiq_pt_regs.ARM_r2 = gp_regs->x2; + fiq_pt_regs.ARM_r3 = gp_regs->x3; + fiq_pt_regs.ARM_r4 = gp_regs->x4; + fiq_pt_regs.ARM_r5 = gp_regs->x5; + fiq_pt_regs.ARM_r6 = gp_regs->x6; + fiq_pt_regs.ARM_r7 = gp_regs->x7; + fiq_pt_regs.ARM_r8 = gp_regs->x8; + fiq_pt_regs.ARM_r9 = gp_regs->x9; + fiq_pt_regs.ARM_r10 = gp_regs->x10; + fiq_pt_regs.ARM_fp = gp_regs->x11; + fiq_pt_regs.ARM_ip = gp_regs->x12; + fiq_pt_regs.ARM_sp = gp_regs->x19; /* aarch32 svc_r13 */ + fiq_pt_regs.ARM_lr = gp_regs->x18; /* aarch32 svc_r14 */ + fiq_pt_regs.ARM_cpsr = gp_regs->spsr_el3; + fiq_pt_regs.ARM_pc = gp_regs->elr_el3; + } else { + /* + * 32-bit tee firmware + 32-bit kernel + */ + fiq_pt_regs.ARM_r0 = nsec_ctx->r0; + fiq_pt_regs.ARM_r1 = nsec_ctx->r1; + fiq_pt_regs.ARM_r2 = nsec_ctx->r2; + fiq_pt_regs.ARM_r3 = nsec_ctx->r3; + fiq_pt_regs.ARM_r4 = nsec_ctx->r4; + fiq_pt_regs.ARM_r5 = nsec_ctx->r5; + fiq_pt_regs.ARM_r6 = nsec_ctx->r6; + fiq_pt_regs.ARM_r7 = nsec_ctx->r7; + fiq_pt_regs.ARM_r8 = nsec_ctx->r8; + fiq_pt_regs.ARM_r9 = nsec_ctx->r9; + fiq_pt_regs.ARM_r10 = nsec_ctx->r10; + fiq_pt_regs.ARM_fp = nsec_ctx->r11; + fiq_pt_regs.ARM_ip = nsec_ctx->r12; + fiq_pt_regs.ARM_sp = nsec_ctx->svc_sp; + fiq_pt_regs.ARM_lr = nsec_ctx->svc_lr; + fiq_pt_regs.ARM_cpsr = nsec_ctx->mon_spsr; + + /* + * 'nsec_ctx->mon_lr' is not the fiq break point's PC, because it will + * be override as 'psci_fiq_debugger_uart_irq_tf_cb' for optee-os to + * jump to fiq_debugger handler. + * + * As 'nsec_ctx->und_lr' is not used for kernel, so optee-os uses it to + * deliver fiq break point's PC. + * + */ + fiq_pt_regs.ARM_pc = nsec_ctx->und_lr; + } +#endif +} + +static void sip_fiq_debugger_uart_irq_tf_cb(unsigned long sp_el1, + unsigned long offset, + unsigned long cpu) +{ + char *cpu_context; + + /* calling fiq handler */ + if (ft_fiq_mem_base) { + cpu_context = (char *)ft_fiq_mem_base + offset; + sip_fiq_debugger_get_pt_regs(cpu_context, sp_el1); + sip_fiq_debugger_uart_irq_tf(&fiq_pt_regs, cpu); + } + + /* fiq handler done, return to EL3(then EL3 return to EL1 entry) */ + __invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, UARTDBG_CFG_OSHDL_TO_OS); +} + +int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, sip_fiq_debugger_uart_irq_tf_cb_t callback_fn) +{ + struct arm_smccc_res res; + + /* Alloc a page for fiq_debugger's stack */ + if (fiq_stack_top == 0) { + fiq_stack_top = __get_free_pages(GFP_KERNEL | __GFP_ZERO, + get_order(SIP_FIQ_DBG_STACK_SIZE)); + if (fiq_stack_top) { + fiq_stack_top += SIP_FIQ_DBG_STACK_SIZE; + } else { + pr_err("%s: alloc stack failed\n", __func__); + return -ENOMEM; + } + } + + /* init fiq debugger callback */ + sip_fiq_debugger_uart_irq_tf = callback_fn; + arm_smccc_smc(SIP_UARTDBG_FN, + irq_id, + (unsigned long)sip_fiq_debugger_uart_irq_tf_cb, + UARTDBG_CFG_INIT, + fiq_stack_top, 0, 0, 0, &res); + + if (IS_SIP_ERROR(res.a0)) { + pr_err("%s error: %d\n", __func__, (int)res.a0); + return res.a0; + } + + /* share memory ioremap */ + if (!ft_fiq_mem_base) { + ft_fiq_mem_phy = res.a1; + ft_fiq_mem_base = sip_map(ft_fiq_mem_phy, + FIQ_UARTDBG_SHARE_MEM_SIZE); + if (!ft_fiq_mem_base) { + pr_err("%s: share memory ioremap failed\n", __func__); + return -ENOMEM; + } + } + + fiq_sip_enabled = 1; + + return SIP_RET_SUCCESS; +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_uart_irq_tf_init); + +static ulong cpu_logical_map_mpidr(u32 cpu) +{ +#ifdef MODULE + /* Empirically, local "cpu_logical_map()" for rockchip platforms */ + ulong mpidr = read_cpuid_mpidr(); + + if (mpidr & MPIDR_MT_BITMASK) { + /* 0x100, 0x200, 0x300, 0x400 ... */ + mpidr = (cpu & 0xff) << 8; + } else { + if (cpu < 4) + /* 0x00, 0x01, 0x02, 0x03 */ + mpidr = cpu; + else if (cpu < 8) + /* 0x100, 0x101, 0x102, 0x103 */ + mpidr = 0x100 | (cpu - 4); + else + pr_err("Unsupported map cpu: %d\n", cpu); + } + + return mpidr; +#else + return cpu_logical_map(cpu); +#endif +} + +ulong sip_cpu_logical_map_mpidr(u32 cpu) +{ + return cpu_logical_map_mpidr(cpu); +} +EXPORT_SYMBOL_GPL(sip_cpu_logical_map_mpidr); + +int sip_fiq_debugger_switch_cpu(u32 cpu) +{ + struct arm_smccc_res res; + + fiq_target_cpu = cpu; + res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, cpu_logical_map_mpidr(cpu), + 0, UARTDBG_CFG_OSHDL_CPUSW); + return res.a0; +} + +int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_SWITCH_CPU, + cur_cpu, target_cpu, flag); + return res.a0; +} + +int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_GET_EVENT_ID, + 0, 0, 0); + *fiq = res.a1; + *sw_cpu = res.a2; + if (flag) + *flag = res.a3; + + return res.a0; +} + +EXPORT_SYMBOL_GPL(sip_fiq_debugger_switch_cpu); + +void sip_fiq_debugger_enable_debug(bool enable) +{ + unsigned long val; + + val = enable ? UARTDBG_CFG_OSHDL_DEBUG_ENABLE : + UARTDBG_CFG_OSHDL_DEBUG_DISABLE; + + __invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, val); +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_debug); + +int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, port_phyaddr, baudrate, + UARTDBG_CFG_PRINT_PORT); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_set_print_port); + +int sip_fiq_debugger_request_share_memory(void) +{ + struct arm_smccc_res res; + + /* request page share memory */ + res = sip_smc_request_share_mem(FIQ_UARTDBG_PAGE_NUMS, + SHARE_PAGE_TYPE_UARTDBG); + if (IS_SIP_ERROR(res.a0)) + return res.a0; + + return SIP_RET_SUCCESS; +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_request_share_memory); + +int sip_fiq_debugger_get_target_cpu(void) +{ + return fiq_target_cpu; +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_get_target_cpu); + +void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu) +{ + u32 en; + + fiq_target_cpu = tgt_cpu; + en = enable ? UARTDBG_CFG_FIQ_ENABEL : UARTDBG_CFG_FIQ_DISABEL; + __invoke_sip_fn_smc(SIP_UARTDBG_FN, tgt_cpu, 0, en); +} +EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_fiq); + +int sip_fiq_control(u32 sub_func, u32 irq, unsigned long data) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(RK_SIP_FIQ_CTRL, + sub_func, irq, data); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_fiq_control); + +int sip_wdt_config(u32 sub_func, u32 arg1, u32 arg2, u32 arg3) +{ + struct arm_smccc_res res; + + arm_smccc_smc(SIP_WDT_CFG, sub_func, arg1, arg2, arg3, + 0, 0, 0, &res); + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_wdt_config); + +int sip_hdmirx_config(u32 sub_func, u32 arg1, u32 arg2, u32 arg3) +{ + struct arm_smccc_res res; + + arm_smccc_smc(SIP_HDMIRX_CFG, sub_func, arg1, arg2, arg3, + 0, 0, 0, &res); + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_hdmirx_config); + +int sip_hdcpkey_init(u32 hdcp_id) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(TRUSTED_OS_HDCPKEY_INIT, hdcp_id, 0, 0); + + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_hdcpkey_init); + +int sip_smc_mcu_config(unsigned long mcu_id, + unsigned long func, + unsigned long arg2) +{ + struct arm_smccc_res res; + + res = __invoke_sip_fn_smc(SIP_MCU_CFG, mcu_id, func, arg2); + return res.a0; +} +EXPORT_SYMBOL_GPL(sip_smc_mcu_config); +/******************************************************************************/ +#ifdef CONFIG_ARM +static __init int sip_firmware_init(void) +{ + struct arm_smccc_res res; + + if (!psci_smp_available()) + return 0; + + /* + * 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(SIP_SIP_VERSION, SIP_IMPLEMENT_V2, + SECURE_REG_WR, 0); + if (IS_SIP_ERROR(res.a0)) + pr_err("%s: set rockchip sip version v2 failed\n", __func__); + + /* + * Currently, we support: + * + * 1. 64-bit ATF + 64-bit kernel; + * 2. 64-bit ATF + 32-bit kernel; + * 3. 32-bit TEE + 32-bit kernel; + * + * We need to detect which case of above and record in firmware_64_32bit + * We get info from cpuid and compare with all supported ARMv7 cpu. + */ + switch (read_cpuid_part()) { + case ARM_CPU_PART_CORTEX_A7: + case ARM_CPU_PART_CORTEX_A8: + case ARM_CPU_PART_CORTEX_A9: + case ARM_CPU_PART_CORTEX_A12: + case ARM_CPU_PART_CORTEX_A15: + case ARM_CPU_PART_CORTEX_A17: + firmware_64_32bit = FIRMWARE_TEE_32BIT; + break; + default: + firmware_64_32bit = FIRMWARE_ATF_64BIT; + break; + } + + return 0; +} +arch_initcall(sip_firmware_init); +#endif + +MODULE_DESCRIPTION("Rockchip SIP Call"); +MODULE_LICENSE("GPL"); diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig new file mode 100644 index 0000000000..aa94a31a8d --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig @@ -0,0 +1,373 @@ +# SPDX-License-Identifier: GPL-2.0-only +if ARCH_ROCKCHIP || COMPILE_TEST + +menu "Rockchip SoC drivers" + +source "drivers/soc/rockchip/Kconfig.cpu" + +# +# Rockchip Android Features +# + +config NO_GKI + bool "Disable Android GKI support" + default y if !GKI_HACKS_TO_FIX + help + Say y here to disable Android GKI support. Then + all the ABI compatibilities are not applicable. + + If unsure, say Y. + +config ROCKCHIP_DISABLE_UNUSED + tristate "Rockchip Disable Unused" + default m if GKI_HACKS_TO_FIX + help + Disable unused clk and power down after module init. + + If unsure, say "N". + +# +# Rockchip Soc drivers +# + +config ROCKCHIP_AMP + tristate "Rockchip AMP support" + help + Say y here to enable Rockchip AMP support. + This option protects resources used by AMP. + +config ROCKCHIP_ARM64_ALIGN_FAULT_FIX + bool "Rockchip align fault fix support" + depends on ARM64 && NO_GKI + help + Say y here to enable Rockchip align fault fix support. + +config ROCKCHIP_CPUINFO + tristate "Rockchip cpuinfo support" + depends on (NVMEM_ROCKCHIP_EFUSE || NVMEM_ROCKCHIP_OTP) && (ARM64 || ARM) + help + Say y here to enable Rockchip cpuinfo support. + Set system_serial_low/high from eFuse ID. + Serial can read from /proc/cpuinfo. + + If unsure, say N. + +config ROCKCHIP_CSU + tristate "Rockchip Clock Subunit Driver" + depends on ARCH_ROCKCHIP + help + This adds the clock subunit driver for Rockchip SoCs. + +config ROCKCHIP_GRF + tristate "Rockchip General Register Files support" + help + The General Register Files are a central component providing + special additional settings registers for a lot of soc-components. + In a lot of cases there also need to be default settings initialized + to make some of them conform to expectations of the kernel. + +config ROCKCHIP_HW_DECOMPRESS + bool "Rockchip HardWare Decompress Support" + help + This driver support Decompress IP built-in Rockchip SoC, support + LZ4, GZIP, ZLIB. + +config ROCKCHIP_HW_DECOMPRESS_TEST + bool "Rockchip Hw-Decompress test" + depends on ROCKCHIP_HW_DECOMPRESS + help + Say yes here to enable decompress test + +config ROCKCHIP_HW_DECOMPRESS_USER + tristate "Rockchip HardWare Decompress User Interface Support" + default n + select ROCKCHIP_HW_DECOMPRESS + help + This driver support user invokes the Decompress IP built-in Rockchip SoC, support + LZ4, GZIP, ZLIB. + +config ROCKCHIP_IODOMAIN + tristate "Rockchip IO domain support" + depends on OF + help + Say y here to enable support io domains on Rockchip SoCs. It is + necessary for the io domain setting of the SoC to match the + voltage supplied by the regulators. + +config ROCKCHIP_IOMUX + tristate "Rockchip IOMUX ioctl support" + depends on PINCTRL_ROCKCHIP + help + This is the debug option for rockchip pinctrl driver by add a device + /dev/iomux to set or get iomux. + + If unsure, say N. + +config ROCKCHIP_IPA + tristate "Rockchip IPA support" + depends on THERMAL && OF + help + Say y here to enable rockchip IPA. + Add a simple power model for ipa to calculate static power and + dynamic power. + + If unsure, say N. + +config ROCKCHIP_OPP + tristate "Rockchip OPP select support" + depends on PM_DEVFREQ + help + Say y here to enable rockchip OPP support. + +config ROCKCHIP_OPTIMIZE_RT_PRIO + bool "Rockchip optimize prio for kernel RT thread and kworker" + depends on NO_GKI + help + In some cases, there are too much userspace high priority RT threads, which + cause kernel RT threads or kworkers block too long time. This config separate + kernel and userspace RT threads into two priority regions, priority 0~49 for + kernel and priority 50~99 for userspace, so that kernel RT threads is always + higher priority than userspace. This config also set RT policy for kworkers. + +config ROCKCHIP_PERFORMANCE + bool "Rockchip performance configuration support" + depends on NO_GKI + help + This config aims to support different requests between power consumption + and performance. + +config ROCKCHIP_PERFORMANCE_LEVEL + int "Rockchip performance default level" + depends on ROCKCHIP_PERFORMANCE + range 0 2 + default 1 + help + Select default performance level: + + 0 for low-performance (powersave), + 1 for normal performance, + 2 for high-performance. + + This can also be changed at runtime (via the level module parameter). + +config ROCKCHIP_PM_DOMAINS + tristate "Rockchip generic power domain" + depends on PM + select PM_GENERIC_DOMAINS + help + Say y here to enable power domain support. + In order to meet high performance and low power requirements, a power + management unit is designed or saving power when RK3288 in low power + mode. The RK3288 PMU is dedicated for managing the power of the whole chip. + + If unsure, say N. + +config ROCKCHIP_DTPM + tristate "Rockchip DTPM hierarchy" + depends on DTPM && m + help + Describe the hierarchy for the Dynamic Thermal Power Management tree + on this platform. That will create all the power capping capable + devices. + +config ROCKCHIP_PVTM + tristate "Rockchip PVTM support" + help + Say y here to enable pvtm support. + The Process-Voltage-Temperature Monitor (PVTM) is used to monitor + the chip performance variance caused by chip process, voltage and + temperature. + +config ROCKCHIP_RAMDISK + bool "Rockchip RAM disk support" + help + Saying Y here will allow you to use reserved RAM memory as a block + device. + +config ROCKCHIP_LITE_ULTRA_SUSPEND + bool "Enable lite/ultra suspend" + depends on SUSPEND && NO_GKI + help + Add mem_sleep_states options lite/ultra for RK ebook project + +config ROCKCHIP_SUSPEND_MODE + tristate "Rockchip suspend mode config" + depends on SUSPEND + help + Say Y here if you want to set the suspend mode to the ATF. + +config ROCKCHIP_SUSPEND_DEBUG + bool "Rockchip suspend debug" + depends on ROCKCHIP_SUSPEND_MODE + help + Say y here to enable rockchip suspend debug support. + +config ROCKCHIP_SYSTEM_MONITOR + tristate "Rockchip system monitor support" + help + Say y here to enable rockchip system monitor support. + +config HAS_EARLYSUSPEND + bool + +config ROCKCHIP_EARLYSUSPEND + bool "Rockchip early suspend" + select HAS_EARLYSUSPEND + depends on !EARLYSUSPEND + depends on !FB_NOTIFY + depends on ROCKCHIP_SYSTEM_MONITOR + help + Call early suspend handlers when the user requested sleep state + changes. + +config ROCKCHIP_VENDOR_STORAGE + tristate "Rockchip vendor storage support" + help + Say y here to enable rockchip vendor storage support. + +config ROCKCHIP_MMC_VENDOR_STORAGE + tristate "Rockchip mmc vendor storage support" + depends on ROCKCHIP_VENDOR_STORAGE && MMC + help + Say y here to enable rockchip mmc vendor storage support. + +config ROCKCHIP_FLASH_VENDOR_STORAGE + tristate "Rockchip flash vendor storage support" + depends on ROCKCHIP_VENDOR_STORAGE && RK_FLASH + help + Say y here to enable rockchip flash vendor storage support. + +config ROCKCHIP_MTD_VENDOR_STORAGE + tristate "Rockchip mtd vendor storage support" + depends on ROCKCHIP_VENDOR_STORAGE && MTD + help + Say y here to enable rockchip mtd vendor storage support. + +config ROCKCHIP_RAM_VENDOR_STORAGE + tristate "Rockchip ram vendor storage support" + depends on ROCKCHIP_VENDOR_STORAGE + help + Say y here to enable rockchip ram vendor storage support. + +config ROCKCHIP_VENDOR_STORAGE_UPDATE_LOADER + bool "Rockchip vendor storage update loader support" + default n + help + Say y here to enable rockchip vendor storage update loader support. + The /dev/vendor_storage must set root user access only. + +menu "FIQ Debugger" +source "drivers/soc/rockchip/fiq_debugger/Kconfig" +endmenu + +config ROCKCHIP_DEBUG + tristate "Rockchip DEBUG" + help + Print dbgpcsr for every cpu when panic. + +config ROCKCHIP_MINI_KERNEL + bool "Rockchip Mini Kernel support" + select NO_GKI + default y if CPU_RV1103B || CPU_RV1106 || CPU_RV1126 + help + Say y here to enable Rockchip mini kernel support. + This option make the kernel size smaller. + +if ROCKCHIP_MINI_KERNEL + +config ROCKCHIP_KMALLOC_NO_USE_ARCH_DMA_MINALIGN + bool "Rockchip kmalloc no use ARCH_DMA_MINALIGN" + default y if CPU_RV1103B || CPU_RV1106 || CPU_RV1126 + help + Say y here to make ARCH_KMALLOC_MINALIGN != ARCH_DMA_MINALIGN + This option can save kmalloc memory but break drivers which + assume kmalloc returned memory is aligned for DMA. + If unsure, say N. + +endif + +config ROCKCHIP_THUNDER_BOOT + bool "Rockchip Thunder Boot support" + depends on NO_GKI + select ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK if SMP + help + Say y here to enable Rockchip thunder boot support. + This option make the kernel boot faster. + +config ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK + bool "Defer free large memblock to Buddy allocator" + depends on SMP + depends on NO_GKI + help + The physical memory of a system is divided into several types, like + memory reserved for device, for kernel pagetable, etc. The remaining + area is for Buddy allocator. Normally, The memory for Buddy is consist + of different size blocks, so, under meeting the memory request of kernel + booting, we can defer free the large block size to Buddy which can be + done later in work queue in parallel to other kernel threads, and the + size of the large block can be defined in kernel command line + by "defer_free_block_size" boot parameter. + +config ROCKCHIP_THUNDER_BOOT_MMC + bool "Rockchip Thunder Boot from MMC" + depends on ROCKCHIP_THUNDER_BOOT + help + Say y if boot from MMC. + +config ROCKCHIP_THUNDER_BOOT_SFC + bool "Rockchip Thunder Boot from SFC" + depends on ROCKCHIP_THUNDER_BOOT + help + Say y if boot from SPI Flash from SFC controller. + +config ROCKCHIP_THUNDER_BOOT_SERVICE + bool "Rockchip Thunder Boot Service" + depends on ROCKCHIP_THUNDER_BOOT + depends on MAILBOX + help + Say y if MCU need to notify AP. + +config ROCKCHIP_NPOR_POWERGOOD + bool "Rockchip NPOR Powergood" + help + Say y if SOC integrate ROCKCHIP NPOR Powergood. + +# +# Rockchip Procfs drivers +# + +config RK_CMA_PROCFS + tristate "CMA procfs interface" + depends on CMA && PROC_FS + help + Turns on the ProcFS interface for CMA, shows the bitmap in hex + format. + +config RK_DMABUF_PROCFS + tristate "DMABUF procfs support" + depends on DMA_SHARED_BUFFER + depends on PROC_FS + help + Turns on this to create a procfs debug interface for dma-buf, support + get information from db_list by dma_buf_get_each. + + If unsure, say "N". + +config RK_MEMBLOCK_PROCFS + bool "Memblock procfs for reserved memory" + depends on PROC_FS && ARCH_KEEP_MEMBLOCK + help + Extend memblock procfs to show size of each memblock, and shows the + result of total size by KiB format. + +config RK_ZONEINFO_PROCFS + bool "Zoneinfo procfs support" + depends on PROC_FS + help + Dump all zone information. + +source "drivers/soc/rockchip/minidump/Kconfig" + +endmenu + +endif diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig.cpu b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig.cpu new file mode 100644 index 0000000000..d3b8d2d318 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Kconfig.cpu @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: GPL-2.0 + +menu "Rockchip CPU selection" + depends on ARCH_ROCKCHIP + +config CPU_RK312X + bool "RK3126/8" + depends on ARM + +config CPU_RK3036 + bool "RK3036" + depends on ARM + +config CPU_RK30XX + bool "RK3066/8" + depends on ARM + +config CPU_RK3188 + bool "RK3188" + depends on ARM + +config CPU_RK3288 + bool "RK3288" + depends on ARM + +config CPU_RK322X + bool "RK3228/9" + depends on ARM + +config CPU_RV1103B + bool "RV1103B" + depends on ARM + +config CPU_RV1106 + bool "RV1103/6" + depends on ARM + +config CPU_RV1108 + bool "RV1107/8" + depends on ARM + +config CPU_RV1126 + bool "RV1109/26" + depends on ARM + +config CPU_RV1126B + bool "RV1126B" + +config CPU_PX30 + bool "PX30" + +config CPU_RK1808 + bool "RK1808" + +config CPU_RK3308 + bool "RK3308" + +config CPU_RK3328 + bool "RK3328" + +config CPU_RK3368 + bool "RK3368" + +config CPU_RK3399 + bool "RK3399" + +config CPU_RK3506 + bool "RK3506" + +config CPU_RK3528 + bool "RK3528" + +config CPU_RK3562 + bool "RK3562" + +config CPU_RK3568 + bool "RK3566/8" + +config CPU_RK3576 + bool "RK3576" + +config CPU_RK3588 + bool "RK3588" + +endmenu diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Makefile b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Makefile new file mode 100644 index 0000000000..4ba238e156 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/Makefile @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Rockchip Soc drivers +# +obj-$(CONFIG_ROCKCHIP_AMP) += rockchip_amp.o +obj-$(CONFIG_ROCKCHIP_CPUINFO) += rockchip-cpuinfo.o +obj-$(CONFIG_ROCKCHIP_CSU) += rockchip_csu.o +obj-$(CONFIG_ROCKCHIP_DISABLE_UNUSED) += rockchip_disable_unused.o +obj-$(CONFIG_ROCKCHIP_GRF) += grf.o +obj-$(CONFIG_ROCKCHIP_HW_DECOMPRESS) += rockchip_decompress.o +obj-$(CONFIG_ROCKCHIP_HW_DECOMPRESS_USER) += rockchip_decompress_user.o +obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o +obj-$(CONFIG_ROCKCHIP_IOMUX) += iomux.o +obj-$(CONFIG_ROCKCHIP_PM_DOMAINS) += pm_domains.o +obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o +obj-$(CONFIG_ROCKCHIP_FIQ_DEBUGGER) += fiq_debugger/ +obj-$(CONFIG_ROCKCHIP_VENDOR_STORAGE) += rk_vendor_storage.o +obj-$(CONFIG_ROCKCHIP_MMC_VENDOR_STORAGE) += sdmmc_vendor_storage.o +obj-$(CONFIG_ROCKCHIP_FLASH_VENDOR_STORAGE) += flash_vendor_storage.o +obj-$(CONFIG_ROCKCHIP_MTD_VENDOR_STORAGE) += mtd_vendor_storage.o +obj-$(CONFIG_ROCKCHIP_RAM_VENDOR_STORAGE) += ram_vendor_storage.o +obj-$(CONFIG_ROCKCHIP_IPA) += rockchip_ipa.o +obj-$(CONFIG_ROCKCHIP_OPP) += rockchip_opp_select.o +obj-$(CONFIG_ROCKCHIP_PERFORMANCE) += rockchip_performance.o +obj-$(CONFIG_ROCKCHIP_PVTM) += rockchip_pvtm.o +obj-$(CONFIG_ROCKCHIP_RAMDISK) += rockchip_ramdisk.o +obj-$(CONFIG_ROCKCHIP_SUSPEND_DEBUG) += rockchip_pm_debug.o +obj-$(CONFIG_ROCKCHIP_SUSPEND_MODE) += rockchip_pm_config.o +obj-$(CONFIG_ROCKCHIP_SYSTEM_MONITOR) += rockchip_system_monitor.o +obj-$(CONFIG_ROCKCHIP_THUNDER_BOOT_MMC) += rockchip_thunderboot_mmc.o +obj-$(CONFIG_ROCKCHIP_THUNDER_BOOT_SFC) += rockchip_thunderboot_sfc.o +obj-$(CONFIG_ROCKCHIP_THUNDER_BOOT_SERVICE) += rockchip_thunderboot_service.o +obj-$(CONFIG_ROCKCHIP_DEBUG) += rockchip_debug.o +obj-$(CONFIG_ROCKCHIP_NPOR_POWERGOOD) += rockchip_npor_powergood.o +obj-$(CONFIG_RK_CMA_PROCFS) += rk_cma_procfs.o +obj-$(CONFIG_RK_DMABUF_PROCFS) += rk_dmabuf_procfs.o +obj-$(CONFIG_RK_MEMBLOCK_PROCFS) += rk_memblock_procfs.o +obj-$(CONFIG_RK_ZONEINFO_PROCFS) += rk_zoneinfo_procfs.o +obj-$(CONFIG_ROCKCHIP_MINIDUMP) += minidump/ diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_ipa.c b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_ipa.c new file mode 100644 index 0000000000..83f81c9583 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_ipa.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd + */ +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +#define FALLBACK_STATIC_TEMPERATURE 55000 + +static void calculate_static_coefficient(struct ipa_power_model_data *data) +{ + s32 *ls = data->ls; + u32 lkg = data->leakage; + u32 ref_lkg = data->ref_leakage; + u32 min = data->lkg_range[0], max = data->lkg_range[1]; + u32 static_coeff = data->static_coefficient; + u32 lkg_scaling_factor; + + if (!lkg) { + if (ref_lkg) + lkg = ref_lkg; + else + lkg = (min + max) / 2; + } + if (ref_lkg) { + data->static_coefficient = static_coeff * lkg / ref_lkg; + return; + } + if (lkg < min) + lkg = min; + if (lkg > max) + lkg = max; + /* As ts have beed multiplied by 1000 in devicetree */ + lkg_scaling_factor = (ls[2] * lkg * lkg + ls[1] * lkg + ls[0]) / 1000; + data->static_coefficient = static_coeff * lkg_scaling_factor / 100; +} + +/** + * rockchip_ipa_power_model_init() - initialise ipa power model parameter + * @dev: device for which we do this operation + * @lkg_name: nvmem cell name from nvmem-cell-names property + * + * Return: a valid struct ipa_power_model_data pointer on success, and the onwer + * should use kfree to release the memory by itself. on failure, it returns a + * corresponding ERR_PTR(). + */ +struct ipa_power_model_data *rockchip_ipa_power_model_init(struct device *dev, + char *lkg_name) +{ + struct device_node *model_node; + struct ipa_power_model_data *model_data; + const char *tz_name; + int ret; + + model_data = kzalloc(sizeof(*model_data), GFP_KERNEL); + if (!model_data) + return ERR_PTR(-ENOMEM); + + model_node = of_get_compatible_child(dev->of_node, + "simple-power-model"); + if (!model_node) { + dev_err(dev, "failed to find power_model node\n"); + ret = -ENODEV; + goto err; + } + + if (of_property_read_string(model_node, "thermal-zone", &tz_name)) { + dev_err(dev, "ts in power_model not available\n"); + ret = -EINVAL; + goto err; + } + model_data->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR_OR_NULL(model_data->tz)) { + dev_err(dev, "failed to get thermal zone\n"); + model_data->tz = NULL; + ret = -EPROBE_DEFER; + goto err; + } + if (of_property_read_u32(model_node, "static-coefficient", + &model_data->static_coefficient)) { + dev_err(dev, "static-coefficient not available\n"); + ret = -EINVAL; + goto err; + } + /* cpu power model node doesn't contain dynamic-coefficient */ + of_property_read_u32(model_node, "dynamic-coefficient", + &model_data->dynamic_coefficient); + if (of_property_read_u32_array + (model_node, "ts", (u32 *)model_data->ts, 4)) { + dev_err(dev, "ts in power_model not available\n"); + ret = -EINVAL; + goto err; + } + rockchip_of_get_leakage(dev, lkg_name, &model_data->leakage); + if (!of_property_read_u32(model_node, "ref-leakage", + &model_data->ref_leakage)) + goto cal_static_coeff; + if (of_property_read_u32_array(model_node, "leakage-range", + (u32 *)model_data->lkg_range, 2)) { + dev_err(dev, "leakage-range isn't available\n"); + ret = -EINVAL; + goto err; + } + if (of_property_read_u32_array(model_node, "ls", + (u32 *)model_data->ls, 3)) { + dev_err(dev, "ls isn't available\n"); + ret = -EINVAL; + goto err; + } +cal_static_coeff: + calculate_static_coefficient(model_data); + + of_node_put(model_node); + + return model_data; +err: + of_node_put(model_node); + kfree(model_data); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL(rockchip_ipa_power_model_init); + +/** + * calculate_temp_scaling_factor() - Calculate temperature scaling coefficient + * @ts: Signed coefficients, in order t^0 to t^3, with units Deg^-N + * @t: Temperature, in mDeg C. Range: -40000 < t < 125000 + * + * Scale the temperature according to a cubic polynomial whose coefficients are + * provided in the device tree. The result is used to scale the static power + * coefficient, where 1000000 means no change. + * + * Return: Temperature scaling factor. + */ +static u32 calculate_temp_scaling_factor(s32 ts[4], s64 t) +{ + const s64 t2 = div_s64((t * t), 1000); + + const s64 t3 = div_s64((t * t2), 1000); + + /* + * Sum the parts. t^[1-3] are in m(Deg^N), but the coefficients are in + * Deg^-N, so we need to multiply the last coefficient by 1000. + */ + const s64 res_big = ts[3] * t3 + + ts[2] * t2 + + ts[1] * t + + ts[0] * 1000LL; + + /* ts has beed multiplied by 10 in devicetree */ + s64 res_unclamped = div_s64(res_big, 10000); + + /* Clamp to range of 0x to 10x the static power */ + return clamp(res_unclamped, (s64)0, (s64)10000000); +} + +/** + * calculate_volt_scaling_factor() - Calculate voltage scaling coefficient + * voltage_mv: Voltage, in mV. Range: 750 < voltage < 1350 + * + * Return: Voltage scaling factor. + */ +static u32 calculate_volt_scaling_factor(const u32 voltage_mv) +{ + const u32 v2 = (voltage_mv * voltage_mv) / 1000; + + const u32 v3_big = v2 * voltage_mv; + + const u32 v3 = v3_big / 1000; + + const u32 v4_big = v3 * voltage_mv; + + const u32 v4 = v4_big / 1000; + + return v4; +} + +/** + * rockchip_ipa_get_static_power() - Calculate static power + * @data: Pointer to IPA model + * voltage_mv: Voltage, in mV. Range: 750 < voltage < 1350 + * + * Return: Static power. + */ +unsigned long +rockchip_ipa_get_static_power(struct ipa_power_model_data *data, + unsigned long voltage_mv) +{ + u32 temp_scaling_factor, volt_scaling_factor, static_power; + u64 power_big; + int temp; + int ret; + + if (!data) + return 0; + + ret = thermal_zone_get_temp(data->tz, &temp); + if (ret) { + pr_err("%s:failed to read %s temp\n", + __func__, thermal_zone_device_type(data->tz)); + temp = FALLBACK_STATIC_TEMPERATURE; + } + + temp_scaling_factor = calculate_temp_scaling_factor(data->ts, temp); + volt_scaling_factor = calculate_volt_scaling_factor((u32)voltage_mv); + + power_big = (u64)data->static_coefficient * (u64)temp_scaling_factor; + static_power = div_u64(power_big, 1000000); + power_big = (u64)static_power * (u64)volt_scaling_factor; + static_power = div_u64(power_big, 1000000); + + trace_thermal_ipa_get_static_power(data->leakage, + data->static_coefficient, + temp, + temp_scaling_factor, + (u32)voltage_mv, + volt_scaling_factor, + static_power); + + return static_power; +} +EXPORT_SYMBOL(rockchip_ipa_get_static_power); + +MODULE_DESCRIPTION("Rockchip IPA driver"); +MODULE_AUTHOR("Finley Xiao "); +MODULE_LICENSE("GPL"); diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_opp_select.c b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_opp_select.c new file mode 100644 index 0000000000..53d80acaea --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_opp_select.c @@ -0,0 +1,2619 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../clk/rockchip/clk.h" +#include "../../opp/opp.h" +#include "../../devfreq/governor.h" + +#define MAX_PROP_NAME_LEN 6 +#define SEL_TABLE_END ~1 +#define AVS_DELETE_OPP 0 +#define AVS_SCALING_RATE 1 + +#define LEAKAGE_V1 1 +#define LEAKAGE_V2 2 +#define LEAKAGE_V3 3 + +#define to_thermal_opp_info(nb) container_of(nb, struct thermal_opp_info, \ + thermal_nb) + +struct sel_table { + int min; + int max; + int sel; +}; + +struct bin_sel_table { + int bin; + int sel; +}; + +struct pvtm_config { + unsigned int freq; + unsigned int volt; + unsigned int ch[2]; + unsigned int sample_time; + unsigned int num; + unsigned int err; + unsigned int ref_temp; + unsigned int offset; + int temp_prop[2]; + const char *tz_name; + struct thermal_zone_device *tz; +}; + +struct lkg_conversion_table { + int temp; + int conv; +}; + +struct otp_opp_info { + u16 min_freq; + u16 max_freq; + u8 volt; + u8 length; +} __packed; + +#define PVTM_CH_MAX 8 +#define PVTM_SUB_CH_MAX 8 + +#define FRAC_BITS 10 +#define int_to_frac(x) ((x) << FRAC_BITS) +#define frac_to_int(x) ((x) >> FRAC_BITS) + +static int pvtm_value[PVTM_CH_MAX][PVTM_SUB_CH_MAX]; +static int lkg_version; + +static int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + const char *reg_name); + +/* + * temp = temp * 10 + * conv = exp(-ln(1.2) / 5 * (temp - 23)) * 100 + */ +static const struct lkg_conversion_table conv_table[] = { + { 200, 111 }, + { 205, 109 }, + { 210, 107 }, + { 215, 105 }, + { 220, 103 }, + { 225, 101 }, + { 230, 100 }, + { 235, 98 }, + { 240, 96 }, + { 245, 94 }, + { 250, 92 }, + { 255, 91 }, + { 260, 89 }, + { 265, 88 }, + { 270, 86 }, + { 275, 84 }, + { 280, 83 }, + { 285, 81 }, + { 290, 80 }, + { 295, 78 }, + { 300, 77 }, + { 305, 76 }, + { 310, 74 }, + { 315, 73 }, + { 320, 72 }, + { 325, 70 }, + { 330, 69 }, + { 335, 68 }, + { 340, 66 }, + { 345, 65 }, + { 350, 64 }, + { 355, 63 }, + { 360, 62 }, + { 365, 61 }, + { 370, 60 }, + { 375, 58 }, + { 380, 57 }, + { 385, 56 }, + { 390, 55 }, + { 395, 54 }, + { 400, 53 }, +}; + +static int rockchip_nvmem_cell_read_common(struct device_node *np, + const char *cell_id, + void *val, size_t count) +{ + struct nvmem_cell *cell; + void *buf; + size_t len; + + cell = of_nvmem_cell_get(np, cell_id); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + if (IS_ERR(buf)) { + nvmem_cell_put(cell); + return PTR_ERR(buf); + } + if (len != count) { + kfree(buf); + nvmem_cell_put(cell); + return -EINVAL; + } + memcpy(val, buf, count); + kfree(buf); + nvmem_cell_put(cell); + + return 0; +} + +int rockchip_nvmem_cell_read_u8(struct device_node *np, const char *cell_id, + u8 *val) +{ + return rockchip_nvmem_cell_read_common(np, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL(rockchip_nvmem_cell_read_u8); + +int rockchip_nvmem_cell_read_u16(struct device_node *np, const char *cell_id, + u16 *val) +{ + return rockchip_nvmem_cell_read_common(np, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL(rockchip_nvmem_cell_read_u16); + +static int rockchip_get_sel_table(struct device_node *np, char *porp_name, + struct sel_table **table) +{ + struct sel_table *sel_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 3) + return -EINVAL; + + sel_table = kzalloc(sizeof(*sel_table) * (count / 3 + 1), GFP_KERNEL); + if (!sel_table) + return -ENOMEM; + + for (i = 0; i < count / 3; i++) { + of_property_read_u32_index(np, porp_name, 3 * i, + &sel_table[i].min); + of_property_read_u32_index(np, porp_name, 3 * i + 1, + &sel_table[i].max); + of_property_read_u32_index(np, porp_name, 3 * i + 2, + &sel_table[i].sel); + } + sel_table[i].min = 0; + sel_table[i].max = 0; + sel_table[i].sel = SEL_TABLE_END; + + *table = sel_table; + + return 0; +} + +static int rockchip_get_bin_sel_table(struct device_node *np, char *porp_name, + struct bin_sel_table **table) +{ + struct bin_sel_table *sel_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 2) + return -EINVAL; + + sel_table = kzalloc(sizeof(*sel_table) * (count / 2 + 1), GFP_KERNEL); + if (!sel_table) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &sel_table[i].bin); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &sel_table[i].sel); + } + + sel_table[i].bin = 0; + sel_table[i].sel = SEL_TABLE_END; + + *table = sel_table; + + return 0; +} + +static int rockchip_get_sel(struct device_node *np, char *name, + int value, int *sel) +{ + struct sel_table *table = NULL; + int i, ret = -EINVAL; + + if (!sel) + return -EINVAL; + + if (rockchip_get_sel_table(np, name, &table)) + return -EINVAL; + + for (i = 0; table[i].sel != SEL_TABLE_END; i++) { + if (value >= table[i].min) { + *sel = table[i].sel; + ret = 0; + } + } + kfree(table); + + return ret; +} + +static int rockchip_get_bin_sel(struct device_node *np, char *name, + int value, int *sel) +{ + struct bin_sel_table *table = NULL; + int i, ret = -EINVAL; + + if (!sel) + return -EINVAL; + + if (rockchip_get_bin_sel_table(np, name, &table)) + return -EINVAL; + + for (i = 0; table[i].sel != SEL_TABLE_END; i++) { + if (value == table[i].bin) { + *sel = table[i].sel; + ret = 0; + break; + } + } + kfree(table); + + return ret; +} + +static int rockchip_parse_pvtm_config(struct device_node *np, + struct pvtm_config *pvtm) +{ + if (of_property_read_u32(np, "rockchip,pvtm-freq", &pvtm->freq)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-sample-time", + &pvtm->sample_time)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp)) + return -EINVAL; + if (of_property_read_u32_array(np, "rockchip,pvtm-temp-prop", + pvtm->temp_prop, 2)) + return -EINVAL; + if (of_property_read_string(np, "rockchip,pvtm-thermal-zone", + &pvtm->tz_name)) { + if (of_property_read_string(np, "rockchip,thermal-zone", + &pvtm->tz_name)) + return -EINVAL; + } + pvtm->tz = thermal_zone_get_zone_by_name(pvtm->tz_name); + if (IS_ERR(pvtm->tz)) + return -EINVAL; + if (!pvtm->tz->ops->get_temp) + return -EINVAL; + if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) { + if (of_property_read_u32(np, "rockchip,pvtm-offset", + &pvtm->offset)) + return -EINVAL; + return 0; + } + if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) + return -EINVAL; + if (pvtm->ch[0] >= PVTM_CH_MAX || pvtm->ch[1] >= PVTM_SUB_CH_MAX) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err)) + return -EINVAL; + + return 0; +} + +static int rockchip_get_pvtm_specific_value(struct device *dev, + struct device_node *np, + struct clk *clk, + struct regulator *reg, + int *target_value) +{ + struct pvtm_config *pvtm; + unsigned long old_freq; + unsigned int old_volt; + int cur_temp, diff_temp; + int cur_value, total_value, avg_value, diff_value; + int min_value, max_value; + int ret = 0, i = 0, retry = 2; + + pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); + if (!pvtm) + return -ENOMEM; + + ret = rockchip_parse_pvtm_config(np, pvtm); + if (ret) + goto pvtm_value_out; + + old_freq = clk_get_rate(clk); + old_volt = regulator_get_voltage(reg); + + /* + * Set pvtm_freq to the lowest frequency in dts, + * so change frequency first. + */ + ret = clk_set_rate(clk, pvtm->freq * 1000); + if (ret) { + dev_err(dev, "Failed to set pvtm freq\n"); + goto pvtm_value_out; + } + + ret = regulator_set_voltage(reg, pvtm->volt, pvtm->volt); + if (ret) { + dev_err(dev, "Failed to set pvtm_volt\n"); + goto restore_clk; + } + + /* The first few values may be fluctuant, if error is too big, retry*/ + while (retry--) { + total_value = 0; + min_value = INT_MAX; + max_value = 0; + + for (i = 0; i < pvtm->num; i++) { + cur_value = rockchip_get_pvtm_value(pvtm->ch[0], + pvtm->ch[1], + pvtm->sample_time); + if (cur_value <= 0) { + ret = -EINVAL; + goto resetore_volt; + } + if (cur_value < min_value) + min_value = cur_value; + if (cur_value > max_value) + max_value = cur_value; + total_value += cur_value; + } + if (max_value - min_value < pvtm->err) + break; + } + if (!total_value || !pvtm->num) { + ret = -EINVAL; + goto resetore_volt; + } + avg_value = total_value / pvtm->num; + + /* + * As pvtm is influenced by temperature, compute difference between + * current temperature and reference temperature + */ + pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + diff_temp = (cur_temp / 1000 - pvtm->ref_temp); + diff_value = diff_temp * + (diff_temp < 0 ? pvtm->temp_prop[0] : pvtm->temp_prop[1]); + *target_value = avg_value + diff_value; + + pvtm_value[pvtm->ch[0]][pvtm->ch[1]] = *target_value; + + dev_info(dev, "temp=%d, pvtm=%d (%d + %d)\n", + cur_temp, *target_value, avg_value, diff_value); + +resetore_volt: + regulator_set_voltage(reg, old_volt, INT_MAX); +restore_clk: + clk_set_rate(clk, old_freq); +pvtm_value_out: + kfree(pvtm); + + return ret; +} + +/** + * mul_frac() - multiply two fixed-point numbers + * @x: first multiplicand + * @y: second multiplicand + * + * Return: the result of multiplying two fixed-point numbers. The + * result is also a fixed-point number. + */ +static inline s64 mul_frac(s64 x, s64 y) +{ + return (x * y) >> FRAC_BITS; +} + +static int temp_to_conversion_rate(int temp) +{ + int high, low, mid; + + low = 0; + high = ARRAY_SIZE(conv_table) - 1; + mid = (high + low) / 2; + + /* No temp available, return max conversion_rate */ + if (temp <= conv_table[low].temp) + return conv_table[low].conv; + if (temp >= conv_table[high].temp) + return conv_table[high].conv; + + while (low <= high) { + if (temp <= conv_table[mid].temp && temp > + conv_table[mid - 1].temp) { + return conv_table[mid - 1].conv + + (conv_table[mid].conv - conv_table[mid - 1].conv) * + (temp - conv_table[mid - 1].temp) / + (conv_table[mid].temp - conv_table[mid - 1].temp); + } else if (temp > conv_table[mid].temp) { + low = mid + 1; + } else { + high = mid - 1; + } + mid = (low + high) / 2; + } + + return 100; +} + +static int rockchip_adjust_leakage(struct device *dev, struct device_node *np, + int *leakage) +{ + struct nvmem_cell *cell; + u8 value = 0; + u32 temp; + int conversion; + int ret; + + cell = of_nvmem_cell_get(np, "leakage_temp"); + if (IS_ERR(cell)) + goto next; + nvmem_cell_put(cell); + ret = rockchip_nvmem_cell_read_u8(np, "leakage_temp", &value); + if (ret) { + dev_err(dev, "Failed to get leakage temp\n"); + return -EINVAL; + } + /* + * The ambient temperature range: 20C to 40C + * In order to improve the precision, we do a conversion. + * The temp in efuse : temp_efuse = (temp - 20) / (40 - 20) * 63 + * The ambient temp : temp = (temp_efuse / 63) * (40 - 20) + 20 + * Reserves a decimal point : temp = temp * 10 + */ + temp = value; + temp = mul_frac((int_to_frac(temp) / 63 * 20 + int_to_frac(20)), + int_to_frac(10)); + conversion = temp_to_conversion_rate(frac_to_int(temp)); + *leakage = *leakage * conversion / 100; + +next: + cell = of_nvmem_cell_get(np, "leakage_volt"); + if (IS_ERR(cell)) + return 0; + nvmem_cell_put(cell); + ret = rockchip_nvmem_cell_read_u8(np, "leakage_volt", &value); + if (ret) { + dev_err(dev, "Failed to get leakage volt\n"); + return -EINVAL; + } + /* + * if ft write leakage use 1.35v, need convert to 1v. + * leakage(1v) = leakage(1.35v) / 4 + */ + if (value) + *leakage = *leakage / 4; + + return 0; +} + +static int rockchip_get_leakage_version(int *version) +{ + if (*version) + return 0; + + if (of_machine_is_compatible("rockchip,rk3368")) + *version = LEAKAGE_V2; + else if (of_machine_is_compatible("rockchip,rv1126") || + of_machine_is_compatible("rockchip,rv1109")) + *version = LEAKAGE_V3; + else + *version = LEAKAGE_V1; + + return 0; +} + +static int rockchip_get_leakage_v1(struct device *dev, struct device_node *np, + char *lkg_name, int *leakage) +{ + struct nvmem_cell *cell; + int ret = 0; + u8 value = 0; + + cell = of_nvmem_cell_get(np, "leakage"); + if (IS_ERR(cell)) { + ret = rockchip_nvmem_cell_read_u8(np, lkg_name, &value); + } else { + nvmem_cell_put(cell); + ret = rockchip_nvmem_cell_read_u8(np, "leakage", &value); + } + if (ret) + dev_err(dev, "Failed to get %s\n", lkg_name); + else + *leakage = value; + + return ret; +} + +static int rockchip_get_leakage_v2(struct device *dev, struct device_node *np, + char *lkg_name, int *leakage) +{ + int lkg = 0, ret = 0; + + if (rockchip_get_leakage_v1(dev, np, lkg_name, &lkg)) + return -EINVAL; + + ret = rockchip_adjust_leakage(dev, np, &lkg); + if (ret) + dev_err(dev, "Failed to adjust leakage, value=%d\n", lkg); + else + *leakage = lkg; + + return ret; +} + +static int rockchip_get_leakage_v3(struct device *dev, struct device_node *np, + char *lkg_name, int *leakage) +{ + int lkg = 0; + + if (rockchip_get_leakage_v1(dev, np, lkg_name, &lkg)) + return -EINVAL; + + *leakage = (((lkg & 0xf8) >> 3) * 1000) + ((lkg & 0x7) * 125); + + return 0; +} + +int rockchip_of_get_leakage(struct device *dev, char *lkg_name, int *leakage) +{ + struct device_node *np; + int ret = -EINVAL; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + rockchip_get_leakage_version(&lkg_version); + + switch (lkg_version) { + case LEAKAGE_V1: + ret = rockchip_get_leakage_v1(dev, np, lkg_name, leakage); + break; + case LEAKAGE_V2: + ret = rockchip_get_leakage_v2(dev, np, lkg_name, leakage); + break; + case LEAKAGE_V3: + ret = rockchip_get_leakage_v3(dev, np, lkg_name, leakage); + if (!ret) { + /* + * round up to the nearest whole number for calculating + * static power, it does not need to be precise. + */ + if (*leakage % 1000 > 500) + *leakage = *leakage / 1000 + 1; + else + *leakage = *leakage / 1000; + } + break; + default: + break; + } + + of_node_put(np); + + return ret; +} +EXPORT_SYMBOL(rockchip_of_get_leakage); + +static void rockchip_of_get_lkg_sel(struct device *dev, struct device_node *np, + char *lkg_name, int process, + int *volt_sel, int *scale_sel) +{ + struct property *prop = NULL; + int leakage = -EINVAL, ret = 0; + char name[NAME_MAX]; + + rockchip_get_leakage_version(&lkg_version); + + switch (lkg_version) { + case LEAKAGE_V1: + ret = rockchip_get_leakage_v1(dev, np, lkg_name, &leakage); + if (ret) + return; + dev_info(dev, "leakage=%d\n", leakage); + break; + case LEAKAGE_V2: + ret = rockchip_get_leakage_v2(dev, np, lkg_name, &leakage); + if (ret) + return; + dev_info(dev, "leakage=%d\n", leakage); + break; + case LEAKAGE_V3: + ret = rockchip_get_leakage_v3(dev, np, lkg_name, &leakage); + if (ret) + return; + dev_info(dev, "leakage=%d.%d\n", leakage / 1000, + leakage % 1000); + break; + default: + return; + } + + if (!volt_sel) + goto next; + if (process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-leakage-voltage-sel", process); + prop = of_find_property(np, name, NULL); + } + if (!prop) + sprintf(name, "rockchip,leakage-voltage-sel"); + ret = rockchip_get_sel(np, name, leakage, volt_sel); + if (!ret) + dev_info(dev, "leakage-volt-sel=%d\n", *volt_sel); + +next: + if (!scale_sel) + return; + if (process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-leakage-scaling-sel", process); + prop = of_find_property(np, name, NULL); + } + if (!prop) + sprintf(name, "rockchip,leakage-scaling-sel"); + ret = rockchip_get_sel(np, name, leakage, scale_sel); + if (!ret) + dev_info(dev, "leakage-scale=%d\n", *scale_sel); +} + +static unsigned long rockchip_pvtpll_get_rate(struct rockchip_opp_info *info) +{ + unsigned int rate0, rate1, delta; + int i; + +#define MIN_STABLE_DELTA 3 + regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate0); + /* max delay 2ms */ + for (i = 0; i < 20; i++) { + udelay(100); + regmap_read(info->pvtpll_base, info->pvtpll_avg_offset, &rate1); + delta = abs(rate1 - rate0); + rate0 = rate1; + if (delta <= MIN_STABLE_DELTA) + break; + } + + if (delta > MIN_STABLE_DELTA) { + dev_err(info->dev, "%s: bad delta: %u\n", __func__, delta); + return 0; + } + + return rate0 * 1000000; +} + +static int rockchip_pvtpll_parse_dt(struct rockchip_opp_info *info) +{ + struct device_node *np; + int ret; + + np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(info->dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + ret = of_property_read_u32(np, "rockchip,pvtpll-avg-offset", &info->pvtpll_avg_offset); + if (ret) + goto out; + + ret = of_property_read_u32(np, "rockchip,pvtpll-min-rate", &info->pvtpll_min_rate); + if (ret) + goto out; + + ret = of_property_read_u32(np, "rockchip,pvtpll-volt-step", &info->pvtpll_volt_step); +out: + of_node_put(np); + + return ret; +} + +static int rockchip_init_pvtpll_info(struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + int i = 0, max_count, ret; + + ret = rockchip_pvtpll_parse_dt(info); + if (ret) + return ret; + + max_count = dev_pm_opp_get_opp_count(info->dev); + if (max_count <= 0) + return max_count ? max_count : -ENODATA; + + info->opp_table = kcalloc(max_count, sizeof(*info->opp_table), GFP_KERNEL); + if (!info->opp_table) + return -ENOMEM; + + opp_table = dev_pm_opp_get_opp_table(info->dev); + if (IS_ERR(opp_table)) { + kfree(info->opp_table); + info->opp_table = NULL; + return PTR_ERR(opp_table); + } + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + + info->opp_table[i].u_volt = opp->supplies[0].u_volt; + info->opp_table[i].u_volt_min = opp->supplies[0].u_volt_min; + info->opp_table[i].u_volt_max = opp->supplies[0].u_volt_max; + if (opp_table->regulator_count > 1) { + info->opp_table[i].u_volt_mem = opp->supplies[1].u_volt; + info->opp_table[i].u_volt_mem_min = opp->supplies[1].u_volt_min; + info->opp_table[i].u_volt_mem_max = opp->supplies[1].u_volt_max; + } + info->opp_table[i++].rate = opp->rates[0]; + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static int rockchip_pvtpll_set_volt(struct device *dev, struct regulator *reg, + int target_uV, int max_uV, char *reg_name) +{ + int ret = 0; + + ret = regulator_set_voltage(reg, target_uV, max_uV); + if (ret) + dev_err(dev, "%s: failed to set %s voltage (%d %d uV): %d\n", + __func__, reg_name, target_uV, max_uV, ret); + + return ret; +} + +static int rockchip_pvtpll_set_clk(struct device *dev, struct clk *clk, + unsigned long rate) +{ + int ret = 0; + + ret = clk_set_rate(clk, rate); + if (ret) + dev_err(dev, "%s: failed to set rate %lu Hz, ret:%d\n", + __func__, rate, ret); + + return ret; +} + +static void rockchip_pvtpll_calibrate_opp(struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + struct regulator *reg = NULL, *reg_mem = NULL; + unsigned long old_volt = 0, old_volt_mem = 0; + unsigned long volt = 0, volt_mem = 0; + unsigned long volt_min, volt_max, volt_mem_min, volt_mem_max; + unsigned long rate, pvtpll_rate, old_rate, cur_rate, delta0, delta1; + int i = 0, max_count, step, cur_step, ret; + + if (!info || !info->pvtpll_base) + return; + + dev_dbg(info->dev, "calibrating opp ...\n"); + ret = rockchip_init_pvtpll_info(info); + if (ret) + return; + + max_count = dev_pm_opp_get_opp_count(info->dev); + if (max_count <= 0) + return; + + opp_table = dev_pm_opp_get_opp_table(info->dev); + if (IS_ERR(opp_table)) + return; + + if (info->clocks) { + ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); + if (ret) { + dev_err(info->dev, "failed to enable opp clks\n"); + return; + } + } + + if ((!opp_table->regulators) || IS_ERR(opp_table->clk)) + goto out_put; + + reg = opp_table->regulators[0]; + old_volt = regulator_get_voltage(reg); + if (opp_table->regulator_count > 1) { + reg_mem = opp_table->regulators[1]; + old_volt_mem = regulator_get_voltage(reg_mem); + if (IS_ERR_VALUE(old_volt_mem)) + goto out_put; + } + old_rate = clk_get_rate(opp_table->clk); + if (IS_ERR_VALUE(old_volt) || IS_ERR_VALUE(old_rate)) + goto out_put; + cur_rate = old_rate; + + step = regulator_get_linear_step(reg); + if (!step || info->pvtpll_volt_step > step) + step = info->pvtpll_volt_step; + + if (old_rate > info->pvtpll_min_rate * 1000) { + if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, + info->pvtpll_min_rate * 1000)) + goto out_put; + } + + for (i = 0; i < max_count; i++) { + rate = info->opp_table[i].rate; + if (rate < 1000 * info->pvtpll_min_rate) + continue; + + volt = max(volt, info->opp_table[i].u_volt); + volt_min = info->opp_table[i].u_volt_min; + volt_max = info->opp_table[i].u_volt_max; + + if (opp_table->regulator_count > 1) { + volt_mem = max(volt_mem, info->opp_table[i].u_volt_mem); + volt_mem_min = info->opp_table[i].u_volt_mem_min; + volt_mem_max = info->opp_table[i].u_volt_mem_max; + if (rockchip_pvtpll_set_volt(info->dev, reg_mem, + volt_mem, volt_mem_max, "mem")) + goto out; + } + if (rockchip_pvtpll_set_volt(info->dev, reg, volt, volt_max, "vdd")) + goto out; + + if (rockchip_pvtpll_set_clk(info->dev, opp_table->clk, rate)) + goto out; + cur_rate = rate; + pvtpll_rate = rockchip_pvtpll_get_rate(info); + if (!pvtpll_rate) + goto out; + cur_step = (pvtpll_rate < rate) ? step : -step; + delta1 = abs(pvtpll_rate - rate); + do { + delta0 = delta1; + volt += cur_step; + if ((volt < volt_min) || (volt > volt_max)) + break; + if (opp_table->regulator_count > 1) { + if (volt > volt_mem_max) + break; + else if (volt < volt_mem_min) + volt_mem = volt_mem_min; + else + volt_mem = volt; + if (rockchip_pvtpll_set_volt(info->dev, reg_mem, + volt_mem, volt_mem_max, + "mem")) + break; + } + if (rockchip_pvtpll_set_volt(info->dev, reg, volt, + volt_max, "vdd")) + break; + pvtpll_rate = rockchip_pvtpll_get_rate(info); + if (!pvtpll_rate) + goto out; + delta1 = abs(pvtpll_rate - rate); + } while (delta1 < delta0); + + volt -= cur_step; + info->opp_table[i].u_volt = volt; + if (opp_table->regulator_count > 1) { + if (volt < volt_mem_min) + volt_mem = volt_mem_min; + else + volt_mem = volt; + info->opp_table[i].u_volt_mem = volt_mem; + } + } + + i = 0; + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + + opp->supplies[0].u_volt = info->opp_table[i].u_volt; + if (opp_table->regulator_count > 1) + opp->supplies[1].u_volt = info->opp_table[i].u_volt_mem; + i++; + } + mutex_unlock(&opp_table->lock); + dev_info(info->dev, "opp calibration done\n"); +out: + if (cur_rate > old_rate) + rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); + if (opp_table->regulator_count > 1) + rockchip_pvtpll_set_volt(info->dev, reg_mem, old_volt_mem, + INT_MAX, "mem"); + rockchip_pvtpll_set_volt(info->dev, reg, old_volt, INT_MAX, "vdd"); + if (cur_rate < old_rate) + rockchip_pvtpll_set_clk(info->dev, opp_table->clk, old_rate); +out_put: + if (info->clocks) + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + dev_pm_opp_put_opp_table(opp_table); +} + +static void rockchip_pvtpll_add_length(struct rockchip_opp_info *info) +{ + struct device_node *np; + struct opp_table *opp_table; + struct dev_pm_opp *opp; + unsigned long old_rate; + unsigned int min_rate = 0, max_rate = 0, margin = 0; + u32 opp_flag = 0; + int ret; + + if (!info) + return; + + np = of_parse_phandle(info->dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(info->dev, "OPP-v2 not supported\n"); + return; + } + + if (of_property_read_u32(np, "rockchip,pvtpll-len-min-rate", &min_rate)) + goto out; + if (of_property_read_u32(np, "rockchip,pvtpll-len-max-rate", &max_rate)) + goto out; + if (of_property_read_u32(np, "rockchip,pvtpll-len-margin", &margin)) + goto out; + + opp_table = dev_pm_opp_get_opp_table(info->dev); + if (IS_ERR(opp_table)) + goto out; + old_rate = clk_get_rate(opp_table->clk); + opp_flag = OPP_ADD_LENGTH | ((margin & OPP_LENGTH_MASK) << OPP_LENGTH_SHIFT); + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (opp->rates[0] < min_rate * 1000 || opp->rates[0] > max_rate * 1000) + continue; + ret = clk_set_rate(opp_table->clk, opp->rates[0] | opp_flag); + if (ret) { + dev_err(info->dev, + "failed to change %lu len margin %d\n", + opp->rates[0], margin); + break; + } + } + mutex_unlock(&opp_table->lock); + + clk_set_rate(opp_table->clk, old_rate); + + dev_pm_opp_put_opp_table(opp_table); +out: + of_node_put(np); +} + +static int rockchip_get_pvtm_pvtpll(struct device *dev, struct device_node *np, + struct rockchip_opp_info *info, + const char *reg_name) +{ + struct regulator *reg; + struct clk *clk; + struct pvtm_config *pvtm; + unsigned long old_freq; + unsigned int old_volt; + int cur_temp, diff_temp, prop_temp, diff_value; + int pvtm_value = 0; + int ret = 0; + + if (!rockchip_nvmem_cell_read_u16(np, "pvtm", (u16 *)&pvtm_value) && pvtm_value) { + dev_info(dev, "pvtm = %d, get from otp\n", pvtm_value); + return pvtm_value; + } + + if (!info || !info->pvtpll_base) + return -ENOMEM; + + pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); + if (!pvtm) + return -ENOMEM; + + ret = rockchip_parse_pvtm_config(np, pvtm); + if (ret) + goto out; + + clk = clk_get(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + dev_warn(dev, "Failed to get clk\n"); + goto out; + } + + reg = regulator_get_optional(dev, reg_name); + if (IS_ERR_OR_NULL(reg)) { + dev_warn(dev, "Failed to get reg\n"); + clk_put(clk); + goto out; + } + old_freq = clk_get_rate(clk); + old_volt = regulator_get_voltage(reg); + + ret = clk_set_rate(clk, pvtm->freq * 1000); + if (ret) { + dev_err(dev, "Failed to set pvtm freq\n"); + goto put_reg; + } + ret = regulator_set_voltage(reg, pvtm->volt, INT_MAX); + if (ret) { + dev_err(dev, "Failed to set pvtm_volt\n"); + goto restore_clk; + } + usleep_range(pvtm->sample_time, pvtm->sample_time + 100); + + ret = regmap_read(info->pvtpll_base, pvtm->offset, &pvtm_value); + if (ret < 0) { + dev_err(dev, "failed to get pvtm from 0x%x\n", pvtm->offset); + goto resetore_volt; + } + pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + diff_temp = (cur_temp / 1000 - pvtm->ref_temp); + if (diff_temp < 0) + prop_temp = pvtm->temp_prop[0]; + else + prop_temp = pvtm->temp_prop[1]; + diff_value = diff_temp * prop_temp / 1000; + pvtm_value += diff_value; + + dev_info(dev, "pvtm=%d\n", pvtm_value); + +resetore_volt: + regulator_set_voltage(reg, old_volt, INT_MAX); +restore_clk: + clk_set_rate(clk, old_freq); +put_reg: + regulator_put(reg); + clk_put(clk); +out: + kfree(pvtm); + + return pvtm_value; +} + +static int rockchip_get_pvtm(struct device *dev, struct device_node *np, + const char *reg_name) +{ + struct regulator *reg; + struct clk *clk; + unsigned int ch[2]; + int pvtm = 0; + u16 tmp = 0; + + if (!rockchip_nvmem_cell_read_u16(np, "pvtm", &tmp) && tmp) { + pvtm = 10 * tmp; + dev_info(dev, "pvtm = %d, from nvmem\n", pvtm); + return pvtm; + } + + if (of_property_read_u32_array(np, "rockchip,pvtm-ch", ch, 2)) + return -EINVAL; + + if (ch[0] >= PVTM_CH_MAX || ch[1] >= PVTM_SUB_CH_MAX) + return -EINVAL; + + if (pvtm_value[ch[0]][ch[1]]) { + dev_info(dev, "pvtm = %d, form pvtm_value\n", pvtm_value[ch[0]][ch[1]]); + return pvtm_value[ch[0]][ch[1]]; + } + + clk = clk_get(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + dev_warn(dev, "Failed to get clk\n"); + return PTR_ERR_OR_ZERO(clk); + } + + reg = regulator_get_optional(dev, reg_name); + if (IS_ERR_OR_NULL(reg)) { + dev_warn(dev, "Failed to get reg\n"); + clk_put(clk); + return PTR_ERR_OR_ZERO(reg); + } + + rockchip_get_pvtm_specific_value(dev, np, clk, reg, &pvtm); + + regulator_put(reg); + clk_put(clk); + + return pvtm; +} + +static void rockchip_of_get_pvtm_sel(struct device *dev, struct device_node *np, + struct rockchip_opp_info *info, + const char *reg_name, int *volt_sel, + int *scale_sel) +{ + struct property *prop = NULL; + char name[NAME_MAX]; + int pvtm, ret; + u32 hw = 0; + + if (of_property_read_bool(np, "rockchip,pvtm-pvtpll")) + pvtm = rockchip_get_pvtm_pvtpll(dev, np, info, reg_name); + else + pvtm = rockchip_get_pvtm(dev, np, reg_name); + if (pvtm <= 0) + return; + + if (!volt_sel) + goto next; + if (info->process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-pvtm-voltage-sel", info->process); + prop = of_find_property(np, name, NULL); + } else if (info->bin > 0) { + of_property_read_u32(np, "rockchip,pvtm-hw", &hw); + if (hw && (hw & BIT(info->bin))) { + sprintf(name, "rockchip,pvtm-voltage-sel-hw"); + prop = of_find_property(np, name, NULL); + } + if (!prop) { + snprintf(name, sizeof(name), + "rockchip,pvtm-voltage-sel-B%d", info->bin); + prop = of_find_property(np, name, NULL); + } + } + if (!prop) + sprintf(name, "rockchip,pvtm-voltage-sel"); + ret = rockchip_get_sel(np, name, pvtm, volt_sel); + if (!ret && volt_sel) + dev_info(dev, "pvtm-volt-sel=%d\n", *volt_sel); + +next: + if (!scale_sel) + return; + prop = NULL; + if (info->process >= 0) { + snprintf(name, sizeof(name), + "rockchip,p%d-pvtm-scaling-sel", info->process); + prop = of_find_property(np, name, NULL); + } + if (!prop) + sprintf(name, "rockchip,pvtm-scaling-sel"); + ret = rockchip_get_sel(np, name, pvtm, scale_sel); + if (!ret) + dev_info(dev, "pvtm-scale=%d\n", *scale_sel); +} + +static void rockchip_of_get_bin_sel(struct device *dev, struct device_node *np, + int bin, int *scale_sel) +{ + int ret = 0; + + if (!scale_sel || bin < 0) + return; + + ret = rockchip_get_bin_sel(np, "rockchip,bin-scaling-sel", + bin, scale_sel); + if (!ret) + dev_info(dev, "bin-scale=%d\n", *scale_sel); +} + +static void rockchip_of_get_bin_volt_sel(struct device *dev, struct device_node *np, + int bin, int *bin_volt_sel) +{ + int ret = 0; + + if (!bin_volt_sel || bin < 0) + return; + + ret = rockchip_get_bin_sel(np, "rockchip,bin-voltage-sel", + bin, bin_volt_sel); + if (!ret) + dev_info(dev, "bin-volt-sel=%d\n", *bin_volt_sel); +} + +void rockchip_get_opp_data(const struct of_device_id *matches, + struct rockchip_opp_info *info) +{ + const struct of_device_id *match; + struct device_node *node; + + node = of_find_node_by_path("/"); + match = of_match_node(matches, node); + if (match && match->data) + info->data = match->data; + of_node_put(node); +} +EXPORT_SYMBOL(rockchip_get_opp_data); + +static int rockchip_get_volt_rm_table(struct device *dev, struct device_node *np, + char *porp_name, struct volt_rm_table **table) +{ + struct volt_rm_table *rm_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 2) + return -EINVAL; + + rm_table = devm_kzalloc(dev, sizeof(*rm_table) * (count / 2 + 1), + GFP_KERNEL); + if (!rm_table) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &rm_table[i].volt); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &rm_table[i].rm); + } + + rm_table[i].volt = 0; + rm_table[i].rm = VOLT_RM_TABLE_END; + + *table = rm_table; + + return 0; +} + +static int rockchip_get_soc_info(struct device *dev, struct device_node *np, + int *bin, int *process) +{ + u8 value = 0; + int ret = 0; + + if (*bin >= 0 || *process >= 0) + return 0; + + if (of_property_match_string(np, "nvmem-cell-names", + "remark_spec_serial_number") >= 0) + rockchip_nvmem_cell_read_u8(np, "remark_spec_serial_number", &value); + + if (!value && of_property_match_string(np, "nvmem-cell-names", + "specification_serial_number") >= 0) { + ret = rockchip_nvmem_cell_read_u8(np, + "specification_serial_number", + &value); + if (ret) { + dev_err(dev, + "Failed to get specification_serial_number\n"); + return ret; + } + } + + /* M */ + if (value == 0xd) + *bin = 1; + /* J */ + else if (value == 0xa) + *bin = 2; + /* S */ + else if (value == 0x13) + *bin = 3; + + if (of_property_match_string(np, "nvmem-cell-names", "customer_demand") >= 0) { + ret = rockchip_nvmem_cell_read_u8(np, "customer_demand", &value); + if (ret) { + dev_err(dev, "Failed to get customer_demand\n"); + return ret; + } + if (value == 0x3) + *bin = 4; + } + + if (*bin < 0) + *bin = 0; + dev_info(dev, "bin=%d\n", *bin); + + return 0; +} + +static void rockchip_init_pvtpll_table(struct device *dev, + struct rockchip_opp_info *info) +{ + struct device_node *np = NULL; + struct property *prop = NULL; + struct of_phandle_args clkspec = { 0 }; + struct arm_smccc_res res; + char prop_name[NAME_MAX]; + u32 *value; + int count; + int ret, i; + + if (!info) + return; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return; + } + + ret = of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells", + 0, &clkspec); + if (ret) + goto out; + info->pvtpll_clk_id = clkspec.args[0]; + of_node_put(clkspec.np); + + res = sip_smc_get_pvtpll_info(PVTPLL_GET_INFO, info->pvtpll_clk_id); + if (res.a0) { + info->pvtpll_smc = false; + goto out; + } + if (!res.a1) + info->pvtpll_low_temp = true; + + if (info->bin > 0) { + snprintf(prop_name, sizeof(prop_name), + "rockchip,pvtpll-table-B%d", info->bin); + prop = of_find_property(np, prop_name, NULL); + } + if (!prop) { + sprintf(prop_name, "rockchip,pvtpll-table"); + prop = of_find_property(np, prop_name, NULL); + } + if (!prop) + goto out; + + count = of_property_count_u32_elems(np, prop_name); + if (count < 0) { + dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, + prop_name, count); + goto out; + } else if (count % 5) { + dev_err(dev, "Invalid count of %s\n", prop_name); + goto out; + } + + value = kmalloc_array(count, sizeof(*value), GFP_KERNEL); + if (!value) + goto out; + ret = of_property_read_u32_array(np, prop_name, value, count); + if (ret) { + dev_err(dev, "%s: error parsing %s: %d\n", __func__, + prop_name, ret); + goto free_value; + } + + for (i = 0; i < count; i += 5) { + res = sip_smc_pvtpll_config(PVTPLL_ADJUST_TABLE, + info->pvtpll_clk_id, value[i], + value[i + 1], value[i + 2], + value[i + 3], value[i + 4]); + if (res.a0) { + dev_err(dev, + "%s: error cfg clk_id=%u %u %u %u %u %u (%d)\n", + __func__, info->pvtpll_clk_id, value[i], + value[i + 1], value[i + 2], value[i + 3], + value[i + 4], (int)res.a0); + goto free_value; + } + } + +free_value: + kfree(value); +out: + of_node_put(np); +} + +static int rockchip_set_opp_supported_hw(struct device *dev, + struct device_node *np, + struct rockchip_opp_info *info) +{ + u32 version = 0, speed = 0; + + if (!of_property_read_bool(np, "rockchip,supported-hw")) + return 0; + if (info->supported_hw[0] || info->supported_hw[1]) + return 0; + + if (info->bin >= 0) + version = info->bin; + if (info->volt_sel >= 0) + speed = info->volt_sel; + /* SoC Version */ + info->supported_hw[0] = BIT(version); + /* Speed Grade */ + info->supported_hw[1] = BIT(speed); + + dev_info(dev, "soc version=%d, speed=%d\n", version, speed); + + return 0; +} + +static void rockchip_get_scale_volt_sel(struct device *dev, char *lkg_name, + const char *reg_name, + struct rockchip_opp_info *info) +{ + struct device_node *np; + int lkg_scale = 0, pvtm_scale = 0, bin_scale = 0; + int lkg_volt_sel = -EINVAL, pvtm_volt_sel = -EINVAL; + int bin_volt_sel = -EINVAL; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return; + } + + rockchip_of_get_lkg_sel(dev, np, lkg_name, info->process, + &lkg_volt_sel, &lkg_scale); + rockchip_of_get_pvtm_sel(dev, np, info, reg_name, &pvtm_volt_sel, + &pvtm_scale); + rockchip_of_get_bin_sel(dev, np, info->bin, &bin_scale); + rockchip_of_get_bin_volt_sel(dev, np, info->bin, &bin_volt_sel); + info->scale = max3(lkg_scale, pvtm_scale, bin_scale); + if (bin_volt_sel >= 0) + info->volt_sel = bin_volt_sel; + else + info->volt_sel = max(lkg_volt_sel, pvtm_volt_sel); + + of_node_put(np); +} + +static int rockchip_opp_set_regulator_helper(struct device *dev, + struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + + if (!info || !info->data || !info->data->config_regulators) + return 0; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return PTR_ERR(opp_table); + + opp_table->config_regulators = info->data->config_regulators; + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static int rockchip_opp_set_config(struct device *dev, struct rockchip_opp_info *info, + const char *clk_name, const char *reg_name) +{ + char name[MAX_PROP_NAME_LEN]; + struct dev_pm_opp_config config = {0}; + struct clk *clk = NULL; + const char *reg_names[] = {NULL, NULL, NULL}; + const char *clk_names[] = {NULL, NULL, NULL}; + int ret = 0; + + if (clk_name) { + clk = clk_get(dev, clk_name); + if (IS_ERR_OR_NULL(clk)) { + if (!of_property_read_string_index(dev->of_node, + "clock-names", + 0, &clk_name)) + clk = clk_get(dev, clk_name); + } + if (!IS_ERR_OR_NULL(clk)) { + if (strstr(__clk_get_name(clk), "scmi")) + info->is_scmi_clk = true; + clk_names[0] = clk_name; + config.clk_names = clk_names; + clk_put(clk); + if (info->data && info->data->config_clks) + config.config_clks = info->data->config_clks; + } + } + + if (info->process >= 0) { + if (info->volt_sel >= 0) + snprintf(name, MAX_PROP_NAME_LEN, "P%d-L%d", + info->process, info->volt_sel); + else + snprintf(name, MAX_PROP_NAME_LEN, "P%d", info->process); + config.prop_name = name; + } else if (info->volt_sel >= 0) { + snprintf(name, MAX_PROP_NAME_LEN, "L%d", info->volt_sel); + config.prop_name = name; + } + + if (info->data && info->data->config_regulators) + config.config_regulators = info->data->config_regulators; + + if (info->supported_hw[0] || info->supported_hw[1]) { + config.supported_hw = kmemdup(info->supported_hw, 2 * sizeof(u32), + GFP_KERNEL); + config.supported_hw_count = 2; + } + + if (reg_name) { + reg_names[0] = reg_name; + if (of_find_property(dev->of_node, "mem-supply", NULL)) + reg_names[1] = "mem"; + config.regulator_names = reg_names; + } + + info->opp_token = dev_pm_opp_set_config(dev, &config); + if (info->opp_token < 0) { + dev_err(dev, "failed to set opp config\n"); + ret = info->opp_token; + goto err; + } + + /* + * The dev_pm_opp_set_config() only support setting regulator helper + * for multiple regulators, but on some platforms, still need to set + * regulator helper for single regulator. + */ + if (rockchip_opp_set_regulator_helper(dev, info)) { + dev_err(dev, "failed to set opp regulator helper\n"); + dev_pm_opp_clear_config(info->opp_token); + info->opp_token = 0; + ret = -EINVAL; + } + +err: + kfree(config.supported_hw); + + return ret; +} + +void rockchip_opp_dvfs_lock(struct rockchip_opp_info *info) +{ + if (info) + mutex_lock(&info->dvfs_mutex); +} +EXPORT_SYMBOL(rockchip_opp_dvfs_lock); + +void rockchip_opp_dvfs_unlock(struct rockchip_opp_info *info) +{ + if (info) + mutex_unlock(&info->dvfs_mutex); +} +EXPORT_SYMBOL(rockchip_opp_dvfs_unlock); + +static int rockchip_get_opp_clk(struct device *dev, struct device_node *np, + struct rockchip_opp_info *info) +{ + struct clk_bulk_data *clocks; + struct of_phandle_args clkspec; + int ret = 0, nclocks = 0, i; + + if (of_find_property(np, "rockchip,opp-clocks", NULL)) { + nclocks = of_count_phandle_with_args(np, "rockchip,opp-clocks", + "#clock-cells"); + if (nclocks <= 0) + return 0; + clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL); + if (!clocks) + return -ENOMEM; + for (i = 0; i < nclocks; i++) { + ret = of_parse_phandle_with_args(np, + "rockchip,opp-clocks", + "#clock-cells", i, + &clkspec); + if (ret < 0) { + dev_err(dev, "%s: failed to parse opp clk %d\n", + np->name, i); + goto error; + } + clocks[i].clk = of_clk_get_from_provider(&clkspec); + of_node_put(clkspec.np); + if (IS_ERR(clocks[i].clk)) { + ret = PTR_ERR(clocks[i].clk); + clocks[i].clk = NULL; + dev_err(dev, "%s: failed to get opp clk %d\n", + np->name, i); + goto error; + } + } + } else { + nclocks = of_clk_get_parent_count(np); + if (nclocks <= 0) + return 0; + clocks = devm_kcalloc(dev, nclocks, sizeof(*clocks), GFP_KERNEL); + if (!clocks) + return -ENOMEM; + for (i = 0; i < nclocks; i++) { + clocks[i].clk = of_clk_get(np, i); + if (IS_ERR(clocks[i].clk)) { + ret = PTR_ERR(clocks[i].clk); + clocks[i].clk = NULL; + dev_err(dev, "%s: failed to get clk %d\n", + np->name, i); + goto error; + } + } + } + info->clocks = clocks; + info->nclocks = nclocks; + + return 0; +error: + while (--i >= 0) + clk_put(clocks[i].clk); + devm_kfree(dev, clocks); + + return ret; +} + +int rockchip_init_opp_info(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name) +{ + struct device_node *np; + int ret = 0; + u32 freq; + + /* Get OPP descriptor node */ + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_dbg(dev, "Failed to find operating-points-v2\n"); + return -ENOENT; + } + if (!info) + return -ENOMEM; + + info->dev = dev; + info->bin = -EINVAL; + info->process = -EINVAL; + info->volt_sel = -EINVAL; + info->pvtpll_clk_id = UINT_MAX; + info->pvtpll_smc = true; + info->is_runtime_active = true; + mutex_init(&info->dvfs_mutex); + + of_property_read_u32(np, "rockchip,init-freq", &info->init_freq); + + info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(info->grf)) + info->grf = NULL; + info->dsu_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf"); + if (IS_ERR(info->dsu_grf)) { + info->dsu_grf = NULL; + info->cci_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,cci-grf"); + if (IS_ERR(info->cci_grf)) + info->cci_grf = NULL; + } + + info->pvtpll_base = syscon_regmap_lookup_by_phandle(np, "rockchip,pvtpll"); + if (IS_ERR(info->pvtpll_base)) + info->pvtpll_base = info->grf; + + ret = rockchip_get_opp_clk(dev, np, info); + if (ret) + goto out; + if (info->clocks) { + ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); + if (ret) { + dev_err(dev, "failed to enable opp clks\n"); + goto out; + } + } + + if (info->data && info->data->set_read_margin) { + info->current_rm = UINT_MAX; + info->target_rm = UINT_MAX; + rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin", + &info->volt_rm_tbl); + of_property_read_u32(np, "low-volt-mem-read-margin", + &info->low_rm); + if (!of_property_read_u32(np, "intermediate-threshold-freq", + &freq)) + info->intermediate_threshold_freq = freq * 1000; + rockchip_init_read_margin(dev, info, reg_name); + } + + if (info->data && info->data->get_soc_info) + info->data->get_soc_info(dev, np, &info->bin, &info->process); + rockchip_get_soc_info(dev, np, &info->bin, &info->process); + + rockchip_init_pvtpll_table(dev, info); + rockchip_get_scale_volt_sel(dev, "leakage", reg_name, info); + + if (info && info->data && info->data->set_soc_info) + info->data->set_soc_info(dev, np, info); + rockchip_set_opp_supported_hw(dev, np, info); + + ret = rockchip_opp_set_config(dev, info, clk_name, reg_name); + + if (info->clocks) + clk_bulk_disable_unprepare(info->nclocks, info->clocks); +out: + of_node_put(np); + + return ret; +} +EXPORT_SYMBOL(rockchip_init_opp_info); + +void rockchip_uninit_opp_info(struct device *dev, struct rockchip_opp_info *info) +{ + dev_pm_opp_clear_config(info->opp_token); + info->opp_token = 0; +} +EXPORT_SYMBOL(rockchip_uninit_opp_info); + +static int rockchip_adjust_opp_by_irdrop(struct device *dev, + struct device_node *np, + unsigned long *safe_rate, + unsigned long *max_rate) +{ + struct sel_table *irdrop_table = NULL; + struct opp_table *opp_table; + struct dev_pm_opp *opp; + unsigned long tmp_safe_rate = 0; + int evb_irdrop = 0, board_irdrop, delta_irdrop; + int opp_rate, i, ret = 0; + u32 max_volt = UINT_MAX; + bool reach_max_volt = false; + + of_property_read_u32_index(np, "rockchip,max-volt", 0, &max_volt); + of_property_read_u32_index(np, "rockchip,evb-irdrop", 0, &evb_irdrop); + rockchip_get_sel_table(np, "rockchip,board-irdrop", &irdrop_table); + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) { + ret = PTR_ERR(opp_table); + goto out; + } + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + if (!irdrop_table) { + delta_irdrop = 0; + } else { + opp_rate = opp->rates[0] / 1000000; + board_irdrop = -EINVAL; + for (i = 0; irdrop_table[i].sel != SEL_TABLE_END; i++) { + if (opp_rate >= irdrop_table[i].min) + board_irdrop = irdrop_table[i].sel; + } + if (board_irdrop == -EINVAL) + delta_irdrop = 0; + else + delta_irdrop = board_irdrop - evb_irdrop; + } + if ((opp->supplies[0].u_volt + delta_irdrop) <= max_volt) { + opp->supplies[0].u_volt += delta_irdrop; + opp->supplies[0].u_volt_min += delta_irdrop; + if (opp->supplies[0].u_volt_max + delta_irdrop <= + max_volt) + opp->supplies[0].u_volt_max += delta_irdrop; + else + opp->supplies[0].u_volt_max = max_volt; + if (!reach_max_volt) + tmp_safe_rate = opp->rates[0]; + if (opp->supplies[0].u_volt == max_volt) + reach_max_volt = true; + } else { + opp->supplies[0].u_volt = max_volt; + opp->supplies[0].u_volt_min = max_volt; + opp->supplies[0].u_volt_max = max_volt; + } + if (max_rate) + *max_rate = opp->rates[0]; + if (safe_rate && tmp_safe_rate != opp->rates[0]) + *safe_rate = tmp_safe_rate; + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); +out: + kfree(irdrop_table); + + return ret; +} + +static void rockchip_adjust_opp_by_mbist_vmin(struct device *dev, + struct device_node *np) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + u32 vmin = 0; + u8 index = 0; + + if (rockchip_nvmem_cell_read_u8(np, "mbist-vmin", &index)) + return; + + if (!index) + return; + + if (of_property_read_u32_index(np, "mbist-vmin", index-1, &vmin)) + return; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return; + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + if (opp->supplies->u_volt < vmin) { + opp->supplies->u_volt = vmin; + opp->supplies->u_volt_min = vmin; + } + } + mutex_unlock(&opp_table->lock); +} + +static void rockchip_adjust_opp_by_otp(struct device *dev, + struct device_node *np) +{ + struct dev_pm_opp *opp; + struct opp_table *opp_table; + struct otp_opp_info opp_info = {}; + int ret; + + ret = rockchip_nvmem_cell_read_common(np, "opp-info", &opp_info, + sizeof(opp_info)); + if (ret || !opp_info.volt) + return; + + dev_info(dev, "adjust opp-table by otp: min=%uM, max=%uM, volt=%umV\n", + opp_info.min_freq, opp_info.max_freq, opp_info.volt); + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return; + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + if (opp->rates[0] < opp_info.min_freq * 1000000) + continue; + if (opp->rates[0] > opp_info.max_freq * 1000000) + continue; + + opp->supplies[0].u_volt += opp_info.volt * 1000; + if (opp->supplies[0].u_volt > opp->supplies[0].u_volt_max) + opp->supplies[0].u_volt = opp->supplies[0].u_volt_max; + if (opp_table->regulator_count > 1) { + opp->supplies[1].u_volt += opp_info.volt * 1000; + if (opp->supplies[1].u_volt > opp->supplies[1].u_volt_max) + opp->supplies[1].u_volt = opp->supplies[1].u_volt_max; + } + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); +} + +static int rockchip_adjust_opp_by_scale(struct device *dev, + unsigned long scale_rate) +{ + struct dev_pm_opp *opp; + unsigned long rate; + int i, count, ret = 0; + + count = dev_pm_opp_get_opp_count(dev); + if (count <= 0) { + ret = count ? count : -ENODATA; + goto out; + } + + for (i = 0, rate = 0; i < count; i++, rate++) { + /* find next rate */ + opp = dev_pm_opp_find_freq_ceil(dev, &rate); + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + goto out; + } + if (opp->rates[0] > scale_rate) + dev_pm_opp_disable(dev, opp->rates[0]); + dev_pm_opp_put(opp); + } +out: + return ret; +} + +static int rockchip_adjust_power_scale(struct device *dev, struct rockchip_opp_info *info) +{ + struct device_node *np; + struct clk *clk; + unsigned long safe_rate = 0, max_rate = 0; + int irdrop_scale = 0, opp_scale = 0; + u32 target_scale, avs = 0, avs_scale = 0; + long scale_rate = 0; + int ret = 0; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + of_property_read_u32(np, "rockchip,avs-enable", &avs); + of_property_read_u32(np, "rockchip,avs", &avs); + of_property_read_u32(np, "rockchip,avs-scale", &avs_scale); + rockchip_adjust_opp_by_otp(dev, np); + rockchip_adjust_opp_by_mbist_vmin(dev, np); + rockchip_adjust_opp_by_irdrop(dev, np, &safe_rate, &max_rate); + + dev_info(dev, "avs=%d\n", avs); + + if (!safe_rate && !info->scale) + goto out_np; + + clk = of_clk_get_by_name(np, NULL); + if (IS_ERR(clk)) { + if (!safe_rate) + goto out_np; + dev_dbg(dev, "Failed to get clk, safe_rate=%lu\n", safe_rate); + ret = rockchip_adjust_opp_by_scale(dev, safe_rate); + if (ret) + dev_err(dev, "Failed to adjust opp table\n"); + goto out_np; + } + + if (safe_rate) + irdrop_scale = rockchip_pll_clk_rate_to_scale(clk, safe_rate); + target_scale = max(irdrop_scale, info->scale); + if (target_scale <= 0) + goto out_clk; + dev_dbg(dev, "target_scale=%d, irdrop_scale=%d, scale=%d\n", + target_scale, irdrop_scale, info->scale); + + if (max_rate) + opp_scale = rockchip_pll_clk_rate_to_scale(clk, max_rate); + if (avs == AVS_SCALING_RATE) { + ret = rockchip_pll_clk_adaptive_scaling(clk, target_scale); + if (ret) + dev_err(dev, "Failed to adaptive scaling\n"); + if (opp_scale >= avs_scale) + goto out_clk; + dev_info(dev, "avs-scale=%d, opp-scale=%d\n", avs_scale, + opp_scale); + scale_rate = rockchip_pll_clk_scale_to_rate(clk, avs_scale); + if (scale_rate <= 0) { + dev_err(dev, "Failed to get avs scale rate, %d\n", + avs_scale); + goto out_clk; + } + dev_dbg(dev, "scale_rate=%lu\n", scale_rate); + ret = rockchip_adjust_opp_by_scale(dev, scale_rate); + if (ret) + dev_err(dev, "Failed to adjust opp table\n"); + } else if (avs == AVS_DELETE_OPP) { + if (opp_scale >= target_scale) + goto out_clk; + dev_info(dev, "target_scale=%d, opp-scale=%d\n", target_scale, + opp_scale); + scale_rate = rockchip_pll_clk_scale_to_rate(clk, target_scale); + if (scale_rate <= 0) { + dev_err(dev, "Failed to get scale rate, %d\n", + target_scale); + goto out_clk; + } + dev_dbg(dev, "scale_rate=%lu\n", scale_rate); + ret = rockchip_adjust_opp_by_scale(dev, scale_rate); + if (ret) + dev_err(dev, "Failed to adjust opp table\n"); + } + +out_clk: + clk_put(clk); +out_np: + of_node_put(np); + + return ret; +} + +static int rockchip_opp_parse_supplies(struct device *dev, + struct rockchip_opp_info *info) +{ + struct opp_table *opp_table; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return PTR_ERR(opp_table); + + if (opp_table->clk) + info->clk = opp_table->clk; + if (opp_table->regulators) + info->regulators = opp_table->regulators; + info->regulator_count = opp_table->regulator_count; + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static int rockchip_pvtpll_set_volt_sel(struct device *dev, + struct rockchip_opp_info *info) +{ + struct arm_smccc_res res; + + if (!info) + return 0; + if (info->volt_sel < 0) + return 0; + if (info->pvtpll_clk_id == UINT_MAX) + return 0; + + if (!info->pvtpll_smc) + return rockchip_pvtpll_volt_sel_adjust(info->pvtpll_clk_id, + info->volt_sel); + + res = sip_smc_pvtpll_config(PVTPLL_VOLT_SEL, info->pvtpll_clk_id, + (u32)info->volt_sel, 0, 0, 0, 0); + if (res.a0) + dev_err(dev, "%s: error cfg clk_id=%u voltsel (%d)\n", __func__, + info->pvtpll_clk_id, (int)res.a0); + + return 0; +} + +int rockchip_adjust_opp_table(struct device *dev, struct rockchip_opp_info *info) +{ + rockchip_opp_parse_supplies(dev, info); + rockchip_adjust_power_scale(dev, info); + rockchip_pvtpll_calibrate_opp(info); + rockchip_pvtpll_add_length(info); + rockchip_pvtpll_set_volt_sel(dev, info); + + return 0; +} +EXPORT_SYMBOL(rockchip_adjust_opp_table); + +int rockchip_get_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + unsigned long volt, u32 *target_rm) +{ + int i; + + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + + for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) { + if (volt >= opp_info->volt_rm_tbl[i].volt) { + opp_info->target_rm = opp_info->volt_rm_tbl[i].rm; + break; + } + } + *target_rm = opp_info->target_rm; + + return 0; +} +EXPORT_SYMBOL(rockchip_get_read_margin); + +int rockchip_set_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, u32 rm, + bool is_set_rm) +{ + if (!is_set_rm || !opp_info) + return 0; + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + if (!opp_info->data || !opp_info->data->set_read_margin) + return 0; + if (rm == opp_info->current_rm) + return 0; + + return opp_info->data->set_read_margin(dev, opp_info, rm); +} +EXPORT_SYMBOL(rockchip_set_read_margin); + +static int rockchip_init_read_margin(struct device *dev, + struct rockchip_opp_info *opp_info, + const char *reg_name) +{ + struct clk *clk; + struct regulator *reg; + unsigned long cur_rate; + int cur_volt, ret = 0; + u32 target_rm = UINT_MAX; + + reg = regulator_get_optional(dev, reg_name); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret != -EPROBE_DEFER) + dev_err(dev, "%s: no regulator (%s) found: %d\n", + __func__, reg_name, ret); + return ret; + } + cur_volt = regulator_get_voltage(reg); + if (cur_volt < 0) { + ret = cur_volt; + if (ret != -EPROBE_DEFER) + dev_err(dev, "%s: failed to get (%s) volt: %d\n", + __func__, reg_name, ret); + goto out; + } + + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev, "%s: failed to get clk: %d\n", __func__, ret); + goto out; + } + cur_rate = clk_get_rate(clk); + + rockchip_get_read_margin(dev, opp_info, cur_volt, &target_rm); + dev_dbg(dev, "cur_rate=%lu, threshold=%lu, cur_volt=%d, target_rm=%d\n", + cur_rate, opp_info->intermediate_threshold_freq, + cur_volt, target_rm); + if (opp_info->intermediate_threshold_freq && + cur_rate > opp_info->intermediate_threshold_freq) { + clk_set_rate(clk, opp_info->intermediate_threshold_freq); + rockchip_set_read_margin(dev, opp_info, target_rm, true); + clk_set_rate(clk, cur_rate); + } else { + rockchip_set_read_margin(dev, opp_info, target_rm, true); + } + + clk_put(clk); +out: + regulator_put(reg); + + return ret; +} + +int rockchip_set_intermediate_rate(struct device *dev, + struct rockchip_opp_info *opp_info, + struct clk *clk, unsigned long old_freq, + unsigned long new_freq, bool is_scaling_up, + bool is_set_clk) +{ + if (!is_set_clk) + return 0; + if (!opp_info || !opp_info->volt_rm_tbl) + return 0; + if (!opp_info->data || !opp_info->data->set_read_margin) + return 0; + if (opp_info->target_rm == opp_info->current_rm) + return 0; + /* + * There is no need to set intermediate rate if the new voltage + * and the current voltage are high voltage. + */ + if ((opp_info->target_rm < opp_info->low_rm) && + (opp_info->current_rm < opp_info->low_rm)) + return 0; + + if (is_scaling_up) { + /* + * If scaling up and the current frequency is less than + * or equal to intermediate threshold frequency, there is + * no need to set intermediate rate. + */ + if (opp_info->intermediate_threshold_freq && + old_freq <= opp_info->intermediate_threshold_freq) + return 0; + return clk_set_rate(clk, new_freq | OPP_SCALING_UP_INTER); + } + /* + * If scaling down and the new frequency is less than or equal to + * intermediate threshold frequency , there is no need to set + * intermediate rate and set the new frequency directly. + */ + if (opp_info->intermediate_threshold_freq && + new_freq <= opp_info->intermediate_threshold_freq) + return clk_set_rate(clk, new_freq); + + return clk_set_rate(clk, new_freq | OPP_SCALING_DOWN_INTER); +} +EXPORT_SYMBOL(rockchip_set_intermediate_rate); + +int rockchip_opp_set_low_length(struct device *dev, struct device_node *np, + struct rockchip_opp_info *opp_info) +{ + struct clk *clk; + unsigned long old_rate; + unsigned int low_len_sel; + u32 opp_flag = 0; + int ret = 0; + + if (opp_info->volt_sel < 0) + return 0; + + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) { + dev_warn(dev, "failed to get cpu clk\n"); + return PTR_ERR(clk); + } + + /* low speed grade should change to low length */ + if (of_property_read_u32(np, "rockchip,pvtm-low-len-sel", + &low_len_sel)) + goto out; + if (opp_info->volt_sel > low_len_sel) + goto out; + opp_flag = OPP_LENGTH_LOW; + + old_rate = clk_get_rate(clk); + ret = clk_set_rate(clk, old_rate | opp_flag); + if (ret) { + dev_err(dev, "failed to change length\n"); + goto out; + } + clk_set_rate(clk, old_rate); +out: + clk_put(clk); + + return ret; +} +EXPORT_SYMBOL(rockchip_opp_set_low_length); + +static int rockchip_opp_set_volt(struct device *dev, struct regulator *reg, + struct dev_pm_opp_supply *supply, char *reg_name) +{ + int ret = 0; + + ret = regulator_set_voltage_triplet(reg, supply->u_volt_min, + supply->u_volt, supply->u_volt_max); + if (ret) + dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n", + reg_name, supply->u_volt_min, supply->u_volt, + supply->u_volt_max, ret); + + return ret; +} + +int rockchip_opp_config_regulators(struct device *dev, + struct dev_pm_opp *old_opp, + struct dev_pm_opp *new_opp, + struct regulator **regulators, + unsigned int count, + struct rockchip_opp_info *info) +{ + struct regulator *vdd_reg = regulators[0]; + struct regulator *mem_reg; + struct dev_pm_opp_supply old_supplies[2] = { 0 }; + struct dev_pm_opp_supply new_supplies[2] = { 0 }; + unsigned long old_freq, freq; + u32 target_rm = UINT_MAX; + int ret = 0; + + if (count > 1) + mem_reg = regulators[1]; + + ret = dev_pm_opp_get_supplies(new_opp, new_supplies); + if (ret) + return ret; + ret = dev_pm_opp_get_supplies(old_opp, old_supplies); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); + if (ret) { + dev_err(dev, "failed to enable opp clks\n"); + return ret; + } + + rockchip_get_read_margin(dev, info, new_supplies[0].u_volt, &target_rm); + + old_freq = dev_pm_opp_get_freq(old_opp); + freq = dev_pm_opp_get_freq(new_opp); + + if (count > 1) + dev_dbg(dev, "%lu %lu -> %lu %lu (uV)\n", + old_supplies[0].u_volt, old_supplies[1].u_volt, + new_supplies[0].u_volt, new_supplies[1].u_volt); + else + dev_dbg(dev, "%lu -> %lu (uV)\n", old_supplies[0].u_volt, + new_supplies[0].u_volt); + + if (freq > old_freq) { + if (count > 1) { + ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem"); + if (ret) + goto restore_voltage; + } + ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd"); + if (ret) + goto restore_voltage; + rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); + } else { + rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); + ret = rockchip_opp_set_volt(dev, vdd_reg, &new_supplies[0], "vdd"); + if (ret) + goto restore_voltage; + if (count > 1) { + ret = rockchip_opp_set_volt(dev, mem_reg, &new_supplies[1], "mem"); + if (ret) + goto restore_voltage; + } + } + + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + + return 0; + +restore_voltage: + rockchip_get_read_margin(dev, info, old_supplies[0].u_volt, &target_rm); + rockchip_set_read_margin(dev, info, target_rm, info->is_runtime_active); + + if (old_supplies[0].u_volt) { + if (count > 1 && old_supplies[1].u_volt) { + ret = rockchip_opp_set_volt(dev, mem_reg, &old_supplies[1], "mem"); + if (ret) + goto dis_clks; + } + ret = rockchip_opp_set_volt(dev, vdd_reg, &old_supplies[0], "vdd"); + if (ret) + goto dis_clks; + } + +dis_clks: + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + + return ret; +} +EXPORT_SYMBOL(rockchip_opp_config_regulators); + +int rockchip_opp_config_clks(struct device *dev, struct opp_table *opp_table, + struct dev_pm_opp *opp, void *data, + bool scaling_down, struct rockchip_opp_info *info) +{ + unsigned long *target = data; + int ret; + + if (rockchip_opp_is_use_pvtpll(info) && !info->is_runtime_active) + return 0; + + ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); + if (ret) { + dev_err(dev, "failed to enable opp clks\n"); + return ret; + } + + dev_dbg(dev, "%lu -> %lu (Hz)\n", opp_table->rate_clk_single, *target); + ret = clk_set_rate(opp_table->clk, *target); + if (ret) + dev_err(dev, "failed to set clock rate: %lu\n", *target); + else + opp_table->rate_clk_single = *target; + + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + + return ret; +} +EXPORT_SYMBOL(rockchip_opp_config_clks); + +int rockchip_opp_check_rate_volt(struct device *dev, struct rockchip_opp_info *info) +{ + struct regulator *vdd_reg = NULL; + struct regulator *mem_reg = NULL; + struct dev_pm_opp *opp; + unsigned long old_rate = 0, new_rate = 0; + unsigned long new_volt = 0, new_volt_mem = 0; + int old_volt = 0, old_volt_mem = 0; + u32 target_rm = UINT_MAX; + bool is_set_clk = true; + bool is_set_rm = false; + int ret = 0; + + if (!info->regulators || !info->clk) + return 0; + + vdd_reg = info->regulators[0]; + old_rate = clk_get_rate(info->clk); + old_volt = regulator_get_voltage(vdd_reg); + if (info->regulator_count > 1) { + mem_reg = info->regulators[1]; + old_volt_mem = regulator_get_voltage(mem_reg); + } + + if (info->init_freq) { + new_rate = info->init_freq * 1000; + info->init_freq = 0; + } else { + new_rate = old_rate; + } + opp = dev_pm_opp_find_freq_ceil(dev, &new_rate); + if (IS_ERR(opp)) { + opp = dev_pm_opp_find_freq_floor(dev, &new_rate); + if (IS_ERR(opp)) + return PTR_ERR(opp); + } + new_volt = opp->supplies[0].u_volt; + if (info->regulator_count > 1) + new_volt_mem = opp->supplies[1].u_volt; + dev_pm_opp_put(opp); + + if (old_rate == new_rate && info->is_rate_volt_checked) { + if (info->regulator_count > 1) { + if (old_volt == new_volt && + new_volt_mem == old_volt_mem) + return 0; + } else if (old_volt == new_volt) { + return 0; + } + } + if (!new_volt || (info->regulator_count > 1 && !new_volt_mem)) + return 0; + + ret = clk_bulk_prepare_enable(info->nclocks, info->clocks); + if (ret) { + dev_err(dev, "failed to enable opp clks\n"); + return ret; + } + + if (rockchip_opp_is_use_pvtpll(info) && !info->is_runtime_active) + is_set_clk = false; + if (info->data && info->data->set_read_margin && info->is_runtime_active) + is_set_rm = true; + + rockchip_get_read_margin(dev, info, new_volt, &target_rm); + + dev_dbg(dev, "%s: %lu Hz --> %lu Hz, %lu %lu uV\n", __func__, + old_rate, new_rate, new_volt, new_volt_mem); + if (new_rate >= old_rate) { + if (old_volt > new_volt) { + ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX); + if (ret) { + dev_err(dev, "%s: failed to set volt: %lu\n", + __func__, new_volt); + goto restore_voltage; + } + } + if (info->regulator_count > 1) { + ret = regulator_set_voltage(mem_reg, new_volt_mem, + INT_MAX); + if (ret) { + dev_err(dev, "%s: failed to set volt: %lu\n", + __func__, new_volt_mem); + goto disable_clk; + } + } + if (old_volt <= new_volt) { + ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX); + if (ret) { + dev_err(dev, "%s: failed to set volt: %lu\n", + __func__, new_volt); + goto restore_voltage; + } + } + rockchip_set_read_margin(dev, info, target_rm, is_set_rm); + if (new_rate == old_rate) + goto out; + } + + if (is_set_clk) { + ret = clk_set_rate(info->clk, new_rate); + if (ret) { + dev_err(dev, "%s: failed to set clock rate: %lu\n", + __func__, new_rate); + goto restore_rm; + } + } + + if (new_rate < old_rate) { + rockchip_set_read_margin(dev, info, target_rm, is_set_rm); + ret = regulator_set_voltage(vdd_reg, new_volt, + INT_MAX); + if (ret) { + dev_err(dev, "%s: failed to set volt: %lu\n", + __func__, new_volt); + goto restore_freq; + } + if (info->regulator_count > 1) { + ret = regulator_set_voltage(mem_reg, new_volt_mem, + INT_MAX); + if (ret) { + dev_err(dev, "%s: failed to set volt: %lu\n", + __func__, new_volt_mem); + goto restore_freq; + } + } + } + +out: + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + + return 0; + +restore_freq: + if (is_set_clk && clk_set_rate(info->clk, old_rate)) + dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", + __func__, old_rate); +restore_rm: + rockchip_get_read_margin(dev, info, old_volt, &target_rm); + rockchip_set_read_margin(dev, info, target_rm, is_set_rm); +restore_voltage: + if (old_volt <= new_volt) + regulator_set_voltage(vdd_reg, old_volt, INT_MAX); + if (info->regulator_count > 1) + regulator_set_voltage(mem_reg, old_volt_mem, INT_MAX); + if (old_volt > new_volt) + regulator_set_voltage(vdd_reg, old_volt, INT_MAX); +disable_clk: + clk_bulk_disable_unprepare(info->nclocks, info->clocks); + + return ret; +} +EXPORT_SYMBOL(rockchip_opp_check_rate_volt); + +int rockchip_init_opp_table(struct device *dev, struct rockchip_opp_info *info, + char *clk_name, char *reg_name) +{ + int ret = 0; + + ret = rockchip_init_opp_info(dev, info, clk_name, reg_name); + if (ret) { + dev_err(dev, "failed to init opp info\n"); + return ret; + } + + ret = dev_pm_opp_of_add_table(dev); + if (ret) { + dev_err(dev, "failed to add opp table\n"); + rockchip_uninit_opp_info(dev, info); + return ret; + } + + rockchip_adjust_opp_table(dev, info); + + return 0; +} +EXPORT_SYMBOL(rockchip_init_opp_table); + +void rockchip_uninit_opp_table(struct device *dev, struct rockchip_opp_info *info) +{ + dev_pm_opp_of_remove_table(dev); + rockchip_uninit_opp_info(dev, info); +} +EXPORT_SYMBOL(rockchip_uninit_opp_table); + +MODULE_DESCRIPTION("ROCKCHIP OPP Select"); +MODULE_AUTHOR("Finley Xiao , Liang Chen "); +MODULE_LICENSE("GPL"); diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_pvtm.c b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_pvtm.c new file mode 100644 index 0000000000..846cc69c8e --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_pvtm.c @@ -0,0 +1,1047 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip PVTM support. + * + * Copyright (c) 2016 Rockchip Electronics Co., Ltd. + * Author: Finley Xiao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define wr_mask_bit(v, off, mask) ((v) << (off) | (mask) << (16 + off)) + +#define PVTM(_id, _name, _num_rings, _start, _en, _cal, _done, _freq) \ +{ \ + .id = _id, \ + .name = _name, \ + .num_rings = _num_rings, \ + .bit_start = _start, \ + .bit_en = _en, \ + .reg_cal = _cal, \ + .bit_freq_done = _done, \ + .reg_freq = _freq, \ +} + +struct rockchip_pvtm; + +struct rockchip_pvtm_ops { + u32 (*get_value)(struct rockchip_pvtm *pvtm, unsigned int ring_sel, + unsigned int time_us); + void (*set_ring_sel)(struct rockchip_pvtm *pvtm, unsigned int ring_sel); +}; + +struct rockchip_pvtm_info { + u32 reg_cal; + u32 reg_freq; + unsigned char id; + unsigned char *name; + unsigned int num_rings; + unsigned int bit_start; + unsigned int bit_en; + unsigned int bit_freq_done; +}; + +struct rockchip_pvtm_data { + u32 con; + u32 sta; + unsigned int num_pvtms; + const struct rockchip_pvtm_info *infos; + const struct rockchip_pvtm_ops ops; +}; + +struct rockchip_pvtm { + u32 con; + u32 sta; + struct list_head node; + struct device *dev; + struct regmap *grf; + void __iomem *base; + int num_clks; + struct clk_bulk_data *clks; + struct reset_control *rst; + struct thermal_zone_device *tz; + const struct rockchip_pvtm_info *info; + const struct rockchip_pvtm_ops *ops; + struct dentry *dentry; +}; + +static LIST_HEAD(pvtm_list); + +#ifdef CONFIG_DEBUG_FS +static struct dentry *rockchip_pvtm_debugfs_root; + +static int pvtm_value_show(struct seq_file *s, void *data) +{ + struct rockchip_pvtm *pvtm = (struct rockchip_pvtm *)s->private; + u32 value; + int i, ret, cur_temp; + + if (!pvtm || !pvtm->ops->get_value) { + seq_puts(s, "unsupported\n"); + return 0; + } + + if (pvtm->tz && pvtm->tz->ops && pvtm->tz->ops->get_temp) { + ret = pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + if (ret) + dev_err(pvtm->dev, "debug failed to get temp\n"); + else + seq_printf(s, "temp: %d ", cur_temp); + } + seq_puts(s, "pvtm: "); + for (i = 0; i < pvtm->info->num_rings; i++) { + value = pvtm->ops->get_value(pvtm, i, 1000); + seq_printf(s, "%d ", value); + } + seq_puts(s, "\n"); + + return 0; +} + +static int pvtm_value_open(struct inode *inode, struct file *file) +{ + return single_open(file, pvtm_value_show, inode->i_private); +} + +static const struct file_operations pvtm_value_fops = { + .open = pvtm_value_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rockchip_pvtm_debugfs_init(void) +{ + rockchip_pvtm_debugfs_root = debugfs_create_dir("pvtm", NULL); + if (IS_ERR_OR_NULL(rockchip_pvtm_debugfs_root)) { + pr_err("Failed to create pvtm debug directory\n"); + rockchip_pvtm_debugfs_root = NULL; + return -ENOMEM; + } + + return 0; +} + +static void rockchip_pvtm_debugfs_exit(void) +{ + debugfs_remove_recursive(rockchip_pvtm_debugfs_root); +} + +static int rockchip_pvtm_add_debugfs(struct rockchip_pvtm *pvtm) +{ + struct dentry *d; + + if (!rockchip_pvtm_debugfs_root) + return 0; + + pvtm->dentry = debugfs_create_dir(pvtm->info->name, + rockchip_pvtm_debugfs_root); + if (!pvtm->dentry) { + dev_err(pvtm->dev, "failed to create pvtm %s debug dir\n", + pvtm->info->name); + return -ENOMEM; + } + + d = debugfs_create_file("value", 0444, pvtm->dentry, + (void *)pvtm, &pvtm_value_fops); + if (!d) { + dev_err(pvtm->dev, "failed to pvtm %s value node\n", + pvtm->info->name); + debugfs_remove_recursive(pvtm->dentry); + return -ENOMEM; + } + + return 0; +} +#else +static inline int rockchip_pvtm_debugfs_init(void) +{ + return 0; +} + +static inline void rockchip_pvtm_debugfs_exit(void) +{ +} + +static inline int rockchip_pvtm_add_debugfs(struct rockchip_pvtm *pvtm) +{ + return 0; +} +#endif + +static int rockchip_pvtm_reset(struct rockchip_pvtm *pvtm) +{ + int ret; + + ret = reset_control_assert(pvtm->rst); + if (ret) { + dev_err(pvtm->dev, "failed to assert pvtm %d\n", ret); + return ret; + } + + udelay(2); + + ret = reset_control_deassert(pvtm->rst); + if (ret) { + dev_err(pvtm->dev, "failed to deassert pvtm %d\n", ret); + return ret; + } + + return 0; +} + +u32 rockchip_get_pvtm_value(unsigned int id, unsigned int ring_sel, + unsigned int time_us) +{ + struct rockchip_pvtm *p, *pvtm = NULL; + + if (list_empty(&pvtm_list)) { + pr_err("pvtm list NULL\n"); + return -EINVAL; + } + + list_for_each_entry(p, &pvtm_list, node) { + if (p->info->id == id) { + pvtm = p; + break; + } + } + + if (!pvtm) { + pr_err("invalid pvtm id %d\n", id); + return -EINVAL; + } + + if (ring_sel >= pvtm->info->num_rings) { + pr_err("invalid pvtm ring %d\n", ring_sel); + return -EINVAL; + } + + return pvtm->ops->get_value(pvtm, ring_sel, time_us); +} +EXPORT_SYMBOL(rockchip_get_pvtm_value); + +static void rockchip_pvtm_delay(unsigned int delay) +{ + unsigned int ms = delay / 1000; + unsigned int us = delay % 1000; + + if (ms > 0) { + if (ms < 20) + us += ms * 1000; + else + msleep(ms); + } + + if (us >= 10) + usleep_range(us, us + 100); + else + udelay(us); +} + +static void px30_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + unsigned int id = pvtm->info->id; + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, (id * 0x4 + 0x2), 0x3)); +} + +static void rk1808_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, 0x2, 0x7)); +} + +static void rk3399_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + unsigned int id = pvtm->info->id; + + if (id == 1) { + regmap_write(pvtm->grf, pvtm->con + 0x14, + wr_mask_bit(ring_sel >> 0x3, 0, 0x1)); + ring_sel &= 0x3; + } + if (id != 4) + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(ring_sel, (id * 0x4 + 0x2), 0x3)); +} + +static u32 rockchip_pvtm_get_value(struct rockchip_pvtm *pvtm, + unsigned int ring_sel, + unsigned int time_us) +{ + const struct rockchip_pvtm_info *info = pvtm->info; + unsigned int clk_cnt, check_cnt = 100; + u32 sta, val = 0; + int ret; + + ret = clk_bulk_prepare_enable(pvtm->num_clks, pvtm->clks); + if (ret < 0) { + dev_err(pvtm->dev, "failed to prepare/enable pvtm clks\n"); + return 0; + } + ret = rockchip_pvtm_reset(pvtm); + if (ret) { + dev_err(pvtm->dev, "failed to reset pvtm\n"); + goto disable_clks; + } + + /* if last status is enabled, stop calculating cycles first*/ + regmap_read(pvtm->grf, pvtm->con, &sta); + if (sta & BIT(info->bit_en)) + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_start, 0x1)); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0x1, info->bit_en, 0x1)); + + if (pvtm->ops->set_ring_sel) + pvtm->ops->set_ring_sel(pvtm, ring_sel); + + /* clk = 24 Mhz, T = 1 / 24 us */ + clk_cnt = time_us * 24; + regmap_write(pvtm->grf, pvtm->con + info->reg_cal, clk_cnt); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0x1, info->bit_start, 0x1)); + + rockchip_pvtm_delay(time_us); + + while (check_cnt) { + regmap_read(pvtm->grf, pvtm->sta, &sta); + if (sta & BIT(info->bit_freq_done)) + break; + udelay(4); + check_cnt--; + } + + if (check_cnt) { + regmap_read(pvtm->grf, pvtm->sta + info->reg_freq, &val); + } else { + dev_err(pvtm->dev, "wait pvtm_done timeout!\n"); + val = 0; + } + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_start, 0x1)); + + regmap_write(pvtm->grf, pvtm->con, + wr_mask_bit(0, info->bit_en, 0x1)); + +disable_clks: + clk_bulk_disable_unprepare(pvtm->num_clks, pvtm->clks); + + return val; +} + +static void rv1106_core_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + writel_relaxed(wr_mask_bit(ring_sel + 4, 0x2, 0x7), pvtm->base + pvtm->con); +} + +static void rv1126_pvtm_set_ring_sel(struct rockchip_pvtm *pvtm, + unsigned int ring_sel) +{ + writel_relaxed(wr_mask_bit(ring_sel, 0x2, 0x7), pvtm->base + pvtm->con); +} + +static u32 rv1126_pvtm_get_value(struct rockchip_pvtm *pvtm, + unsigned int ring_sel, + unsigned int time_us) +{ + const struct rockchip_pvtm_info *info = pvtm->info; + unsigned int clk_cnt, check_cnt = 100; + u32 sta, val = 0; + int ret; + + ret = clk_bulk_prepare_enable(pvtm->num_clks, pvtm->clks); + if (ret < 0) { + dev_err(pvtm->dev, "failed to prepare/enable pvtm clks\n"); + return 0; + } + ret = rockchip_pvtm_reset(pvtm); + if (ret) { + dev_err(pvtm->dev, "failed to reset pvtm\n"); + goto disable_clks; + } + + /* if last status is enabled, stop calculating cycles first*/ + sta = readl_relaxed(pvtm->base + pvtm->con); + if (sta & BIT(info->bit_en)) + writel_relaxed(wr_mask_bit(0, info->bit_start, 0x1), + pvtm->base + pvtm->con); + + writel_relaxed(wr_mask_bit(0x1, info->bit_en, 0x1), + pvtm->base + pvtm->con); + + if (pvtm->ops->set_ring_sel) + pvtm->ops->set_ring_sel(pvtm, ring_sel); + + /* clk = 24 Mhz, T = 1 / 24 us */ + clk_cnt = time_us * 24; + writel_relaxed(clk_cnt, pvtm->base + pvtm->con + info->reg_cal); + + writel_relaxed(wr_mask_bit(0x1, info->bit_start, 0x1), + pvtm->base + pvtm->con); + + rockchip_pvtm_delay(time_us); + + while (check_cnt) { + sta = readl_relaxed(pvtm->base + pvtm->sta); + if (sta & BIT(info->bit_freq_done)) + break; + udelay(4); + check_cnt--; + } + + if (check_cnt) { + val = readl_relaxed(pvtm->base + pvtm->sta + info->reg_freq); + } else { + dev_err(pvtm->dev, "wait pvtm_done timeout!\n"); + val = 0; + } + + writel_relaxed(wr_mask_bit(0, info->bit_start, 0x1), + pvtm->base + pvtm->con); + writel_relaxed(wr_mask_bit(0, info->bit_en, 0x1), + pvtm->base + pvtm->con); + +disable_clks: + clk_bulk_disable_unprepare(pvtm->num_clks, pvtm->clks); + + return val; +} + +static const struct rockchip_pvtm_info px30_pvtm_infos[] = { + PVTM(0, "core", 3, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data px30_pvtm = { + .con = 0x80, + .sta = 0x88, + .num_pvtms = ARRAY_SIZE(px30_pvtm_infos), + .infos = px30_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = px30_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info px30_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data px30_pmupvtm = { + .con = 0x180, + .sta = 0x190, + .num_pvtms = ARRAY_SIZE(px30_pmupvtm_infos), + .infos = px30_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk1808_pvtm_infos[] = { + PVTM(0, "core", 5, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_pvtm = { + .con = 0x80, + .sta = 0x88, + .num_pvtms = ARRAY_SIZE(rk1808_pvtm_infos), + .infos = rk1808_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk1808_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk1808_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_pmupvtm = { + .con = 0x180, + .sta = 0x190, + .num_pvtms = ARRAY_SIZE(rk1808_pmupvtm_infos), + .infos = rk1808_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk1808_npupvtm_infos[] = { + PVTM(2, "npu", 5, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk1808_npupvtm = { + .con = 0x780, + .sta = 0x788, + .num_pvtms = ARRAY_SIZE(rk1808_npupvtm_infos), + .infos = rk1808_npupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk1808_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3288_pvtm_infos[] = { + PVTM(0, "core", 1, 0, 1, 0x4, 1, 0x4), + PVTM(1, "gpu", 1, 8, 9, 0x8, 0, 0x8), +}; + +static const struct rockchip_pvtm_data rk3288_pvtm = { + .con = 0x368, + .sta = 0x374, + .num_pvtms = ARRAY_SIZE(rk3288_pvtm_infos), + .infos = rk3288_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_data rk3308_pmupvtm = { + .con = 0x440, + .sta = 0x448, + .num_pvtms = ARRAY_SIZE(px30_pmupvtm_infos), + .infos = px30_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk3399_pvtm_infos[] = { + PVTM(0, "core_l", 4, 0, 1, 0x4, 0, 0x4), + PVTM(1, "core_b", 6, 4, 5, 0x8, 1, 0x8), + PVTM(2, "ddr", 4, 8, 9, 0xc, 3, 0x10), + PVTM(3, "gpu", 4, 12, 13, 0x10, 2, 0xc), +}; + +static const struct rockchip_pvtm_data rk3399_pvtm = { + .con = 0xe600, + .sta = 0xe620, + .num_pvtms = ARRAY_SIZE(rk3399_pvtm_infos), + .infos = rk3399_pvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + .set_ring_sel = rk3399_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3399_pmupvtm_infos[] = { + PVTM(4, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3399_pmupvtm = { + .con = 0x240, + .sta = 0x248, + .num_pvtms = ARRAY_SIZE(rk3399_pmupvtm_infos), + .infos = rk3399_pmupvtm_infos, + .ops = { + .get_value = rockchip_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rk3568_corepvtm_infos[] = { + PVTM(0, "core", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_corepvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_corepvtm_infos), + .infos = rk3568_corepvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3568_gpupvtm_infos[] = { + PVTM(1, "gpu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_gpupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_gpupvtm_infos), + .infos = rk3568_gpupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3568_npupvtm_infos[] = { + PVTM(2, "npu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3568_npupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3568_npupvtm_infos), + .infos = rk3568_npupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_bigcore0_pvtm_infos[] = { + PVTM(0, "bigcore0", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_bigcore0_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_bigcore0_pvtm_infos), + .infos = rk3588_bigcore0_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_bigcore1_pvtm_infos[] = { + PVTM(1, "bigcore1", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_bigcore1_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_bigcore1_pvtm_infos), + .infos = rk3588_bigcore1_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_litcore_pvtm_infos[] = { + PVTM(2, "litcore", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_litcore_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_litcore_pvtm_infos), + .infos = rk3588_litcore_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_npu_pvtm_infos[] = { + PVTM(3, "npu", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_npu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_npu_pvtm_infos), + .infos = rk3588_npu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_gpu_pvtm_infos[] = { + PVTM(4, "gpu", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_gpu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_gpu_pvtm_infos), + .infos = rk3588_gpu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rk3588_pmu_pvtm_infos[] = { + PVTM(5, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rk3588_pmu_pvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rk3588_pmu_pvtm_infos), + .infos = rk3588_pmu_pvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rv1106_corepvtm_infos[] = { + PVTM(0, "core", 2, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1106_corepvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1106_corepvtm_infos), + .infos = rv1106_corepvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1106_core_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1106_pmupvtm_infos[] = { + PVTM(1, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1106_pmupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1106_pmupvtm_infos), + .infos = rv1106_pmupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct rockchip_pvtm_info rv1126_cpupvtm_infos[] = { + PVTM(0, "cpu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_cpupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_cpupvtm_infos), + .infos = rv1126_cpupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1126_npupvtm_infos[] = { + PVTM(1, "npu", 7, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_npupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_npupvtm_infos), + .infos = rv1126_npupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + .set_ring_sel = rv1126_pvtm_set_ring_sel, + }, +}; + +static const struct rockchip_pvtm_info rv1126_pmupvtm_infos[] = { + PVTM(2, "pmu", 1, 0, 1, 0x4, 0, 0x4), +}; + +static const struct rockchip_pvtm_data rv1126_pmupvtm = { + .con = 0x4, + .sta = 0x80, + .num_pvtms = ARRAY_SIZE(rv1126_pmupvtm_infos), + .infos = rv1126_pmupvtm_infos, + .ops = { + .get_value = rv1126_pvtm_get_value, + }, +}; + +static const struct of_device_id rockchip_pvtm_match[] = { +#ifdef CONFIG_CPU_PX30 + { + .compatible = "rockchip,px30-pvtm", + .data = (void *)&px30_pvtm, + }, + { + .compatible = "rockchip,px30-pmu-pvtm", + .data = (void *)&px30_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK1808 + { + .compatible = "rockchip,rk1808-pvtm", + .data = (void *)&rk1808_pvtm, + }, + { + .compatible = "rockchip,rk1808-pmu-pvtm", + .data = (void *)&rk1808_pmupvtm, + }, + { + .compatible = "rockchip,rk1808-npu-pvtm", + .data = (void *)&rk1808_npupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3288 + { + .compatible = "rockchip,rk3288-pvtm", + .data = (void *)&rk3288_pvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3308 + { + .compatible = "rockchip,rk3308-pvtm", + .data = (void *)&px30_pvtm, + }, + { + .compatible = "rockchip,rk3308-pmu-pvtm", + .data = (void *)&rk3308_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3399 + { + .compatible = "rockchip,rk3399-pvtm", + .data = (void *)&rk3399_pvtm, + }, + { + .compatible = "rockchip,rk3399-pmu-pvtm", + .data = (void *)&rk3399_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3568 + { + .compatible = "rockchip,rK3568-core-pvtm", + .data = (void *)&rk3568_corepvtm, + }, + { + .compatible = "rockchip,rk3568-gpu-pvtm", + .data = (void *)&rk3568_gpupvtm, + }, + { + .compatible = "rockchip,rk3568-npu-pvtm", + .data = (void *)&rk3568_npupvtm, + }, +#endif +#ifdef CONFIG_CPU_RK3588 + { + .compatible = "rockchip,rk3588-bigcore0-pvtm", + .data = (void *)&rk3588_bigcore0_pvtm, + }, + { + .compatible = "rockchip,rk3588-bigcore1-pvtm", + .data = (void *)&rk3588_bigcore1_pvtm, + }, + { + .compatible = "rockchip,rk3588-litcore-pvtm", + .data = (void *)&rk3588_litcore_pvtm, + }, + { + .compatible = "rockchip,rk3588-gpu-pvtm", + .data = (void *)&rk3588_gpu_pvtm, + }, + { + .compatible = "rockchip,rk3588-npu-pvtm", + .data = (void *)&rk3588_npu_pvtm, + }, + { + .compatible = "rockchip,rk3588-pmu-pvtm", + .data = (void *)&rk3588_pmu_pvtm, + }, +#endif +#ifdef CONFIG_CPU_RV1106 + { + .compatible = "rockchip,rv1106-core-pvtm", + .data = (void *)&rv1106_corepvtm, + }, + { + .compatible = "rockchip,rv1106-pmu-pvtm", + .data = (void *)&rv1106_pmupvtm, + }, +#endif +#ifdef CONFIG_CPU_RV1126 + { + .compatible = "rockchip,rv1126-cpu-pvtm", + .data = (void *)&rv1126_cpupvtm, + }, + { + .compatible = "rockchip,rv1126-npu-pvtm", + .data = (void *)&rv1126_npupvtm, + }, + { + .compatible = "rockchip,rv1126-pmu-pvtm", + .data = (void *)&rv1126_pmupvtm, + }, +#endif + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rockchip_pvtm_match); + +static int rockchip_pvtm_get_index(const struct rockchip_pvtm_data *data, + u32 ch, u32 *index) +{ + int i; + + for (i = 0; i < data->num_pvtms; i++) { + if (ch == data->infos[i].id) { + *index = i; + return 0; + } + } + + return -EINVAL; +} + +static struct rockchip_pvtm * +rockchip_pvtm_init(struct device *dev, struct device_node *node, + const struct rockchip_pvtm_data *data, + struct regmap *grf, void __iomem *base) +{ + struct rockchip_pvtm *pvtm; + const char *tz_name; + u32 id, index; + int i; + + if (of_property_read_u32(node, "reg", &id)) { + dev_err(dev, "%s: failed to retrieve pvtm id\n", node->name); + return NULL; + } + if (rockchip_pvtm_get_index(data, id, &index)) { + dev_err(dev, "%s: invalid pvtm id %d\n", node->name, id); + return NULL; + } + + pvtm = devm_kzalloc(dev, sizeof(*pvtm), GFP_KERNEL); + if (!pvtm) + return NULL; + + pvtm->dev = dev; + pvtm->grf = grf; + pvtm->base = base; + pvtm->con = data->con; + pvtm->sta = data->sta; + pvtm->ops = &data->ops; + pvtm->info = &data->infos[index]; + + if (!of_property_read_string(node, "thermal-zone", &tz_name)) { + pvtm->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(pvtm->tz)) { + dev_err(pvtm->dev, "failed to retrieve pvtm_tz\n"); + pvtm->tz = NULL; + } + } + + pvtm->num_clks = of_clk_get_parent_count(node); + if (pvtm->num_clks <= 0) { + dev_err(dev, "%s: does not have clocks\n", node->name); + goto clk_num_err; + } + pvtm->clks = devm_kcalloc(dev, pvtm->num_clks, sizeof(*pvtm->clks), + GFP_KERNEL); + if (!pvtm->clks) + goto clk_num_err; + for (i = 0; i < pvtm->num_clks; i++) { + pvtm->clks[i].clk = of_clk_get(node, i); + if (IS_ERR(pvtm->clks[i].clk)) { + dev_err(dev, "%s: failed to get clk at index %d\n", + node->name, i); + goto clk_err; + } + } + + pvtm->rst = devm_reset_control_array_get_optional_exclusive(dev); + if (IS_ERR(pvtm->rst)) + dev_dbg(dev, "%s: failed to get reset\n", node->name); + + rockchip_pvtm_add_debugfs(pvtm); + + return pvtm; + +clk_err: + while (--i >= 0) + clk_put(pvtm->clks[i].clk); + devm_kfree(dev, pvtm->clks); +clk_num_err: + devm_kfree(dev, pvtm); + + return NULL; +} + +static int rockchip_pvtm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct device_node *node; + const struct rockchip_pvtm_data *data; + struct rockchip_pvtm *pvtm; + struct regmap *grf = NULL; + void __iomem *base = NULL; + + data = device_get_match_data(dev); + if (!data) { + dev_err(dev, "missing pvtm data\n"); + return -EINVAL; + } + + if (dev->parent && dev->parent->of_node) { + grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(grf)) + return PTR_ERR(grf); + } else { + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + } + + for_each_available_child_of_node(np, node) { + pvtm = rockchip_pvtm_init(dev, node, data, grf, base); + if (!pvtm) { + dev_err(dev, "failed to handle node %s\n", + node->full_name); + continue; + } + list_add(&pvtm->node, &pvtm_list); + dev_info(dev, "%s probed\n", node->full_name); + } + + return 0; +} + +static struct platform_driver rockchip_pvtm_driver = { + .probe = rockchip_pvtm_probe, + .driver = { + .name = "rockchip-pvtm", + .of_match_table = rockchip_pvtm_match, + }, +}; + +static int __init rockchip_pvtm_module_init(void) +{ + rockchip_pvtm_debugfs_init(); + + return platform_driver_register(&rockchip_pvtm_driver); +} +module_init(rockchip_pvtm_module_init); + +static void __exit rockchip_pvtm_module_exit(void) +{ + rockchip_pvtm_debugfs_exit(); + platform_driver_unregister(&rockchip_pvtm_driver); +} +module_exit(rockchip_pvtm_module_exit); + +MODULE_DESCRIPTION("Rockchip PVTM driver"); +MODULE_AUTHOR("Finley Xiao "); +MODULE_LICENSE("GPL v2"); diff --git a/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_system_monitor.c b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_system_monitor.c new file mode 100644 index 0000000000..aed56d9ca0 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/drivers/soc/rockchip/rockchip_system_monitor.c @@ -0,0 +1,2031 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd + * Author: Finley Xiao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ROCKCHIP_EARLYSUSPEND +#include +#endif + +#include "../../opp/opp.h" +#include "../../regulator/internal.h" +#include "../../thermal/thermal_core.h" + +#define CPU_REBOOT_FREQ 816000 /* kHz */ +#define VIDEO_1080P_SIZE (1920 * 1080) +#define THERMAL_POLLING_DELAY 200 /* milliseconds */ + +struct video_info { + unsigned int width; + unsigned int height; + unsigned int ishevc; + unsigned int videoFramerate; + unsigned int streamBitrate; + struct list_head node; +}; + +struct system_monitor_attr { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n); +}; + +struct system_monitor { + struct device *dev; + struct cpumask early_suspend_offline_cpus; + struct cpumask video_4k_offline_cpus; + struct cpumask status_offline_cpus; + struct cpumask temp_offline_cpus; + struct cpumask offline_cpus; + struct notifier_block status_nb; + struct kobject *kobj; + + struct thermal_zone_device *tz; + struct delayed_work thermal_work; + struct temp_freq_table *temp_ddr_ref_mode; + int last_temp; + int offline_cpus_temp; + int temp_hysteresis; + unsigned int delay; + bool is_temp_offline; + + int (*ddr_trefi_update)(u32 ref_mode); +}; + +static unsigned long system_status; +static unsigned long ref_count[32] = {0}; + +static DEFINE_MUTEX(system_status_mutex); +static DEFINE_MUTEX(video_info_mutex); +static DEFINE_MUTEX(cpu_on_off_mutex); + +static DECLARE_RWSEM(mdev_list_sem); + +static LIST_HEAD(video_info_list); +static LIST_HEAD(monitor_dev_list); +static struct system_monitor *system_monitor; +static atomic_t monitor_in_suspend; + +static BLOCKING_NOTIFIER_HEAD(system_monitor_notifier_list); +static BLOCKING_NOTIFIER_HEAD(system_status_notifier_list); + +#ifdef CONFIG_ROCKCHIP_EARLYSUSPEND +static DEFINE_MUTEX(early_suspend_mutex); +static LIST_HEAD(early_suspend_list); +static bool is_early_suspend; + +static bool early_suspend_debug; +module_param(early_suspend_debug, bool, 0644); + +void register_early_suspend(struct early_suspend *handler) +{ + struct list_head *pos; + + if (!handler) + return; + + mutex_lock(&early_suspend_mutex); + list_for_each(pos, &early_suspend_list) { + struct early_suspend *tmp; + + tmp = list_entry(pos, struct early_suspend, link); + if (tmp->level > handler->level) + break; + } + list_add_tail(&handler->link, pos); + mutex_unlock(&early_suspend_mutex); +} +EXPORT_SYMBOL(register_early_suspend); + +void unregister_early_suspend(struct early_suspend *handler) +{ + if (!handler) + return; + + mutex_lock(&early_suspend_mutex); + list_del(&handler->link); + mutex_unlock(&early_suspend_mutex); +} +EXPORT_SYMBOL(unregister_early_suspend); + +void rockchip_request_early_suspend(void) +{ + struct early_suspend *pos; + + mutex_lock(&early_suspend_mutex); + if (is_early_suspend) + goto unlock; + list_for_each_entry(pos, &early_suspend_list, link) { + if (pos->suspend != NULL) { + bool debug = early_suspend_debug; + ktime_t calltime, rettime; + + if (debug) { + printk(KERN_DEBUG "early_suspend: calling %pS\n", pos->suspend); + calltime = ktime_get(); + } + pos->suspend(pos); + if (debug) { + rettime = ktime_get(); + printk(KERN_DEBUG "early_suspend: %pS returned after %lld usecs\n", + pos->suspend, ktime_us_delta(rettime, calltime)); + } + } + } + is_early_suspend = true; +unlock: + mutex_unlock(&early_suspend_mutex); +} +EXPORT_SYMBOL(rockchip_request_early_suspend); + +void rockchip_request_late_resume(void) +{ + struct early_suspend *pos; + + mutex_lock(&early_suspend_mutex); + if (!is_early_suspend) + goto unlock; + list_for_each_entry_reverse(pos, &early_suspend_list, link) { + if (pos->resume != NULL) { + bool debug = early_suspend_debug; + ktime_t calltime, rettime; + + if (debug) { + printk(KERN_DEBUG "late_resume: calling %pS\n", pos->resume); + calltime = ktime_get(); + } + pos->resume(pos); + if (debug) { + rettime = ktime_get(); + printk(KERN_DEBUG "late_resume: %pS returned after %lld usecs\n", + pos->resume, ktime_us_delta(rettime, calltime)); + } + } + } + is_early_suspend = false; +unlock: + mutex_unlock(&early_suspend_mutex); +} +EXPORT_SYMBOL(rockchip_request_late_resume); + +static ssize_t early_suspend_state_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", is_early_suspend); +} + +static ssize_t early_suspend_state_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (!n) + return -EINVAL; + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val > 1) + return -EINVAL; + + if (val) + rockchip_request_early_suspend(); + else + rockchip_request_late_resume(); + + return n; +} + +static struct system_monitor_attr early_suspend_state = + __ATTR(early_suspend, 0644, early_suspend_state_show, early_suspend_state_store); +#endif + +int rockchip_register_system_status_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&system_status_notifier_list, + nb); +} +EXPORT_SYMBOL(rockchip_register_system_status_notifier); + +int rockchip_unregister_system_status_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&system_status_notifier_list, + nb); +} +EXPORT_SYMBOL(rockchip_unregister_system_status_notifier); + +static int rockchip_system_status_notifier_call_chain(unsigned long val) +{ + int ret = blocking_notifier_call_chain(&system_status_notifier_list, + val, NULL); + + return notifier_to_errno(ret); +} + +void rockchip_set_system_status(unsigned long status) +{ + unsigned long old_system_status; + unsigned int single_status_offset; + + mutex_lock(&system_status_mutex); + + old_system_status = system_status; + + while (status) { + single_status_offset = fls(status) - 1; + status &= ~(1 << single_status_offset); + if (ref_count[single_status_offset] == 0) + system_status |= 1 << single_status_offset; + ref_count[single_status_offset]++; + } + + if (old_system_status != system_status) + rockchip_system_status_notifier_call_chain(system_status); + + mutex_unlock(&system_status_mutex); +} +EXPORT_SYMBOL(rockchip_set_system_status); + +void rockchip_clear_system_status(unsigned long status) +{ + unsigned long old_system_status; + unsigned int single_status_offset; + + mutex_lock(&system_status_mutex); + + old_system_status = system_status; + + while (status) { + single_status_offset = fls(status) - 1; + status &= ~(1 << single_status_offset); + if (ref_count[single_status_offset] == 0) { + continue; + } else { + if (ref_count[single_status_offset] == 1) + system_status &= ~(1 << single_status_offset); + ref_count[single_status_offset]--; + } + } + + if (old_system_status != system_status) + rockchip_system_status_notifier_call_chain(system_status); + + mutex_unlock(&system_status_mutex); +} +EXPORT_SYMBOL(rockchip_clear_system_status); + +unsigned long rockchip_get_system_status(void) +{ + return system_status; +} +EXPORT_SYMBOL(rockchip_get_system_status); + +int rockchip_add_system_status_interface(struct device *dev) +{ + if (!system_monitor || !system_monitor->kobj) { + pr_err("failed to get system status kobj\n"); + return -EINVAL; + } + + return compat_only_sysfs_link_entry_to_kobj(&dev->kobj, + system_monitor->kobj, + "system_status", NULL); +} +EXPORT_SYMBOL(rockchip_add_system_status_interface); + +static unsigned long rockchip_get_video_param(char **str) +{ + char *p; + unsigned long val = 0; + + strsep(str, "="); + p = strsep(str, ","); + if (p) { + if (kstrtoul(p, 10, &val)) + return 0; + } + + return val; +} + +/* + * format: + * 0,width=val,height=val,ishevc=val,videoFramerate=val,streamBitrate=val + * 1,width=val,height=val,ishevc=val,videoFramerate=val,streamBitrate=val + */ +static struct video_info *rockchip_parse_video_info(const char *buf) +{ + struct video_info *video_info; + const char *cp = buf; + char *str, *p; + int ntokens = 0; + + while ((cp = strpbrk(cp + 1, ","))) + ntokens++; + if (ntokens != 5) + return NULL; + + video_info = kzalloc(sizeof(*video_info), GFP_KERNEL); + if (!video_info) + return NULL; + + INIT_LIST_HEAD(&video_info->node); + + str = kstrdup(buf, GFP_KERNEL); + p = str; + strsep(&p, ","); + video_info->width = rockchip_get_video_param(&p); + video_info->height = rockchip_get_video_param(&p); + video_info->ishevc = rockchip_get_video_param(&p); + video_info->videoFramerate = rockchip_get_video_param(&p); + video_info->streamBitrate = rockchip_get_video_param(&p); + pr_debug("%c,width=%d,height=%d,ishevc=%d,videoFramerate=%d,streamBitrate=%d\n", + buf[0], + video_info->width, + video_info->height, + video_info->ishevc, + video_info->videoFramerate, + video_info->streamBitrate); + kfree(str); + + return video_info; +} + +static void rockchip_add_video_info(struct video_info *video_info) +{ + if (video_info) { + mutex_lock(&video_info_mutex); + list_add(&video_info->node, &video_info_list); + mutex_unlock(&video_info_mutex); + } +} + +static void rockchip_del_video_info(struct video_info *video_info) +{ + struct video_info *info, *tmp; + + if (!video_info) + return; + + mutex_lock(&video_info_mutex); + list_for_each_entry_safe(info, tmp, &video_info_list, node) { + if (info->width == video_info->width && + info->height == video_info->height && + info->ishevc == video_info->ishevc && + info->videoFramerate == video_info->videoFramerate && + info->streamBitrate == video_info->streamBitrate) { + list_del(&info->node); + kfree(info); + break; + } + } + kfree(video_info); + mutex_unlock(&video_info_mutex); +} + +static void rockchip_update_video_info(void) +{ + struct video_info *video_info; + unsigned int max_res = 0, max_stream_bitrate = 0, res = 0; + unsigned int max_video_framerate = 0; + + mutex_lock(&video_info_mutex); + if (list_empty(&video_info_list)) { + mutex_unlock(&video_info_mutex); + rockchip_clear_system_status(SYS_STATUS_VIDEO); + return; + } + + list_for_each_entry(video_info, &video_info_list, node) { + res = video_info->width * video_info->height; + if (res > max_res) + max_res = res; + if (video_info->streamBitrate > max_stream_bitrate) + max_stream_bitrate = video_info->streamBitrate; + if (video_info->videoFramerate > max_video_framerate) + max_video_framerate = video_info->videoFramerate; + } + mutex_unlock(&video_info_mutex); + + if (max_res <= VIDEO_1080P_SIZE) { + rockchip_set_system_status(SYS_STATUS_VIDEO_1080P); + } else { + if (max_stream_bitrate == 10) + rockchip_set_system_status(SYS_STATUS_VIDEO_4K_10B); + if (max_video_framerate == 60) + rockchip_set_system_status(SYS_STATUS_VIDEO_4K_60P); + rockchip_set_system_status(SYS_STATUS_VIDEO_4K); + } +} + +void rockchip_update_system_status(const char *buf) +{ + struct video_info *video_info; + + if (!buf) + return; + + switch (buf[0]) { + case '0': + /* clear video flag */ + video_info = rockchip_parse_video_info(buf); + if (video_info) { + rockchip_del_video_info(video_info); + rockchip_update_video_info(); + } + break; + case '1': + /* set video flag */ + video_info = rockchip_parse_video_info(buf); + if (video_info) { + rockchip_add_video_info(video_info); + rockchip_update_video_info(); + } + break; + case 'L': + /* clear low power flag */ + rockchip_clear_system_status(SYS_STATUS_LOW_POWER); + break; + case 'l': + /* set low power flag */ + rockchip_set_system_status(SYS_STATUS_LOW_POWER); + break; + case 'p': + /* set performance flag */ + rockchip_set_system_status(SYS_STATUS_PERFORMANCE); + break; + case 'n': + /* clear performance flag */ + rockchip_clear_system_status(SYS_STATUS_PERFORMANCE); + break; + case 'S': + /* set video svep flag */ + rockchip_set_system_status(SYS_STATUS_VIDEO_SVEP); + break; + case 's': + /* clear video svep flag */ + rockchip_clear_system_status(SYS_STATUS_VIDEO_SVEP); + break; + default: + break; + } +} +EXPORT_SYMBOL(rockchip_update_system_status); + +static ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + unsigned int status = rockchip_get_system_status(); + + return sprintf(buf, "0x%x\n", status); +} + +static ssize_t status_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + if (!n) + return -EINVAL; + + rockchip_update_system_status(buf); + + return n; +} + +static struct system_monitor_attr status = + __ATTR(system_status, 0644, status_show, status_store); + +static int rockchip_get_temp_freq_table(struct device_node *np, + char *porp_name, + struct temp_freq_table **freq_table) +{ + struct temp_freq_table *table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 2) + return -EINVAL; + + table = kzalloc(sizeof(*table) * (count / 2 + 1), GFP_KERNEL); + if (!table) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &table[i].temp); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &table[i].freq); + } + table[i].freq = UINT_MAX; + *freq_table = table; + + return 0; +} + +static int rockchip_get_adjust_volt_table(struct device_node *np, + char *porp_name, + struct volt_adjust_table **table) +{ + struct volt_adjust_table *volt_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 3) + return -EINVAL; + + volt_table = kzalloc(sizeof(*volt_table) * (count / 3 + 1), GFP_KERNEL); + if (!volt_table) + return -ENOMEM; + + for (i = 0; i < count / 3; i++) { + of_property_read_u32_index(np, porp_name, 3 * i, + &volt_table[i].min); + of_property_read_u32_index(np, porp_name, 3 * i + 1, + &volt_table[i].max); + of_property_read_u32_index(np, porp_name, 3 * i + 2, + &volt_table[i].volt); + } + volt_table[i].min = 0; + volt_table[i].max = 0; + volt_table[i].volt = INT_MAX; + + *table = volt_table; + + return 0; +} + +static int rockchip_get_low_temp_volt(struct monitor_dev_info *info, + unsigned long rate, int *delta_volt) +{ + int i, ret = -EINVAL; + unsigned int _rate = (unsigned int)(rate / 1000000); + + if (!info->low_temp_adjust_table) + return ret; + + for (i = 0; info->low_temp_adjust_table[i].volt != INT_MAX; i++) { + if (_rate >= info->low_temp_adjust_table[i].min && + _rate <= info->low_temp_adjust_table[i].max) { + *delta_volt = info->low_temp_adjust_table[i].volt; + ret = 0; + } + } + + return ret; +} + +static int rockchip_init_temp_opp_table(struct monitor_dev_info *info) +{ + struct device *dev = info->dev; + struct opp_table *opp_table; + struct dev_pm_opp *opp; + int delta_volt = 0; + int i = 0, max_count; + unsigned long low_limit = 0, high_limit = 0; + unsigned long low_limit_mem = 0, high_limit_mem = 0; + bool reach_max_volt = false; + bool reach_max_mem_volt = false; + bool reach_high_temp_max_volt = false; + bool reach_high_temp_max_mem_volt = false; + + max_count = dev_pm_opp_get_opp_count(dev); + if (max_count <= 0) + return max_count ? max_count : -ENODATA; + info->opp_table = kzalloc(sizeof(*info->opp_table) * max_count, + GFP_KERNEL); + if (!info->opp_table) + return -ENOMEM; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) { + kfree(info->opp_table); + info->opp_table = NULL; + return PTR_ERR(opp_table); + } + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + info->opp_table[i].rate = opp->rates[0]; + info->opp_table[i].volt = opp->supplies[0].u_volt; + info->opp_table[i].max_volt = opp->supplies[0].u_volt_max; + + if (opp->supplies[0].u_volt <= info->high_temp_max_volt) { + if (!reach_high_temp_max_volt) + high_limit = opp->rates[0]; + if (opp->supplies[0].u_volt == info->high_temp_max_volt) + reach_high_temp_max_volt = true; + } + + if (rockchip_get_low_temp_volt(info, opp->rates[0], &delta_volt)) + delta_volt = 0; + if ((opp->supplies[0].u_volt + delta_volt) <= info->max_volt) { + info->opp_table[i].low_temp_volt = + opp->supplies[0].u_volt + delta_volt; + if (info->opp_table[i].low_temp_volt < + info->low_temp_min_volt) + info->opp_table[i].low_temp_volt = + info->low_temp_min_volt; + if (!reach_max_volt) + low_limit = opp->rates[0]; + if (info->opp_table[i].low_temp_volt == info->max_volt) + reach_max_volt = true; + } else { + info->opp_table[i].low_temp_volt = info->max_volt; + } + if (low_limit && low_limit != opp->rates[0]) + info->low_limit = low_limit; + if (high_limit && high_limit != opp->rates[0]) + info->high_limit = high_limit; + + if (opp_table->regulator_count > 1) { + info->opp_table[i].mem_volt = opp->supplies[1].u_volt; + info->opp_table[i].max_mem_volt = opp->supplies[1].u_volt_max; + + if (opp->supplies[1].u_volt <= info->high_temp_max_volt) { + if (!reach_high_temp_max_mem_volt) + high_limit_mem = opp->rates[0]; + if (opp->supplies[1].u_volt == info->high_temp_max_volt) + reach_high_temp_max_mem_volt = true; + } + + if ((opp->supplies[1].u_volt + delta_volt) <= info->max_volt) { + info->opp_table[i].low_temp_mem_volt = + opp->supplies[1].u_volt + delta_volt; + if (info->opp_table[i].low_temp_mem_volt < + info->low_temp_min_volt) + info->opp_table[i].low_temp_mem_volt = + info->low_temp_min_volt; + if (!reach_max_mem_volt) + low_limit_mem = opp->rates[0]; + if (info->opp_table[i].low_temp_mem_volt == info->max_volt) + reach_max_mem_volt = true; + } else { + info->opp_table[i].low_temp_mem_volt = info->max_volt; + } + + if (low_limit_mem && low_limit_mem != opp->rates[0]) { + if (info->low_limit > low_limit_mem) + info->low_limit = low_limit_mem; + } + if (high_limit_mem && high_limit_mem != opp->rates[0]) { + if (info->high_limit > high_limit_mem) + info->high_limit = high_limit_mem; + } + } + + dev_dbg(dev, "rate=%lu, volt=%lu %lu low_temp_volt=%lu %lu\n", + info->opp_table[i].rate, info->opp_table[i].volt, + info->opp_table[i].mem_volt, + info->opp_table[i].low_temp_volt, + info->opp_table[i].low_temp_mem_volt); + i++; + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static int monitor_device_parse_wide_temp_config(struct device_node *np, + struct monitor_dev_info *info) +{ + struct device *dev = info->dev; + unsigned long high_temp_max_freq; + int ret = 0; + u32 value; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) + return -EINVAL; + + if (of_property_read_u32(np, "rockchip,max-volt", &value)) + info->max_volt = ULONG_MAX; + else + info->max_volt = value; + of_property_read_u32(np, "rockchip,temp-hysteresis", + &info->temp_hysteresis); + if (of_property_read_u32(np, "rockchip,low-temp", &info->low_temp)) + info->low_temp = INT_MIN; + rockchip_get_adjust_volt_table(np, "rockchip,low-temp-adjust-volt", + &info->low_temp_adjust_table); + if (!of_property_read_u32(np, "rockchip,low-temp-min-volt", &value)) + info->low_temp_min_volt = value; + if (of_property_read_u32(np, "rockchip,high-temp", &info->high_temp)) + info->high_temp = INT_MAX; + if (of_property_read_u32(np, "rockchip,high-temp-max-volt", + &value)) + info->high_temp_max_volt = ULONG_MAX; + else + info->high_temp_max_volt = value; + rockchip_init_temp_opp_table(info); + rockchip_get_temp_freq_table(np, "rockchip,temp-freq-table", + &info->high_limit_table); + if (!info->high_limit_table) + rockchip_get_temp_freq_table(np, "rockchip,high-temp-limit-table", + &info->high_limit_table); + if (!info->high_limit_table) { + if (!of_property_read_u32(np, "rockchip,high-temp-max-freq", + &value)) { + high_temp_max_freq = value * 1000; + if (info->high_limit) + info->high_limit = min(high_temp_max_freq, + info->high_limit); + else + info->high_limit = high_temp_max_freq; + } + } else { + info->high_limit = 0; + } + dev_info(dev, "l=%d h=%d hyst=%d l_limit=%lu h_limit=%lu h_table=%d\n", + info->low_temp, info->high_temp, info->temp_hysteresis, + info->low_limit, info->high_limit, + info->high_limit_table ? true : false); + + if ((info->low_temp + info->temp_hysteresis) > info->high_temp) { + dev_err(dev, "Invalid temperature, low=%d high=%d hyst=%d\n", + info->low_temp, info->high_temp, + info->temp_hysteresis); + ret = -EINVAL; + goto err; + } + if (!info->low_temp_adjust_table && !info->low_temp_min_volt && + !info->low_limit && !info->high_limit && !info->high_limit_table) { + ret = -EINVAL; + goto err; + } + if (info->low_temp_adjust_table || info->low_temp_min_volt) + info->is_low_temp_enabled = true; + + return 0; +err: + kfree(info->low_temp_adjust_table); + info->low_temp_adjust_table = NULL; + kfree(info->opp_table); + info->opp_table = NULL; + + return ret; +} + +static int monitor_device_parse_status_config(struct device_node *np, + struct monitor_dev_info *info) +{ + int ret; + + ret = of_property_read_u32(np, "rockchip,early-suspend-freq", + &info->early_suspend_freq); + ret &= of_property_read_u32(np, "rockchip,video-1080p-freq", + &info->video_1080p_freq); + ret &= of_property_read_u32(np, "rockchip,video-4k-freq", + &info->video_4k_freq); + ret &= of_property_read_u32(np, "rockchip,reboot-freq", + &info->reboot_freq); + if (info->devp->type == MONITOR_TYPE_CPU) { + if (!info->reboot_freq) { + info->reboot_freq = CPU_REBOOT_FREQ; + ret = 0; + } + } + + return ret; +} + +static int monitor_device_parse_early_min_volt(struct device_node *np, + struct monitor_dev_info *info) +{ + const char *prop_name = "rockchip,early-min-microvolt"; + int count = 0, ret = 0; + + count = of_property_count_u32_elems(np, prop_name); + if (count <= 0) + return -EINVAL; + + if (count > 1) { + ret = of_property_read_u32_index(np, prop_name, 1, + &info->early_min_volt[1]); + if (ret) + return ret; + } + + return of_property_read_u32_index(np, prop_name, 0, + &info->early_min_volt[0]); +} + +static int monitor_device_parse_dt(struct device *dev, + struct monitor_dev_info *info) +{ + struct device_node *np; + int ret; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) + return -EINVAL; + + ret = monitor_device_parse_wide_temp_config(np, info); + ret &= monitor_device_parse_status_config(np, info); + ret &= monitor_device_parse_early_min_volt(np, info); + + of_node_put(np); + + return ret; +} + +int rockchip_monitor_cpu_low_temp_adjust(struct monitor_dev_info *info, + bool is_low) +{ + if (!info->low_limit) + return 0; + + if (!freq_qos_request_active(&info->max_temp_freq_req)) + return 0; + + if (is_low) + freq_qos_update_request(&info->max_temp_freq_req, + info->low_limit / 1000); + else + freq_qos_update_request(&info->max_temp_freq_req, + FREQ_QOS_MAX_DEFAULT_VALUE); + + return 0; +} +EXPORT_SYMBOL(rockchip_monitor_cpu_low_temp_adjust); + +int rockchip_monitor_cpu_high_temp_adjust(struct monitor_dev_info *info, + bool is_high) +{ + if (!freq_qos_request_active(&info->max_temp_freq_req)) + return 0; + + if (info->high_limit_table) { + if (info->high_limit) + freq_qos_update_request(&info->max_temp_freq_req, + info->high_limit / 1000); + else + freq_qos_update_request(&info->max_temp_freq_req, + FREQ_QOS_MAX_DEFAULT_VALUE); + return 0; + } + + if (!info->high_limit) + return 0; + + if (is_high) + freq_qos_update_request(&info->max_temp_freq_req, + info->high_limit / 1000); + else + freq_qos_update_request(&info->max_temp_freq_req, + FREQ_QOS_MAX_DEFAULT_VALUE); + + return 0; +} +EXPORT_SYMBOL(rockchip_monitor_cpu_high_temp_adjust); + +int rockchip_monitor_dev_low_temp_adjust(struct monitor_dev_info *info, + bool is_low) +{ + if (!dev_pm_qos_request_active(&info->dev_max_freq_req)) + return 0; + + if (!info->low_limit) + return 0; + + if (is_low) + dev_pm_qos_update_request(&info->dev_max_freq_req, + info->low_limit / 1000); + else + dev_pm_qos_update_request(&info->dev_max_freq_req, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); + + return 0; +} +EXPORT_SYMBOL(rockchip_monitor_dev_low_temp_adjust); + +int rockchip_monitor_dev_high_temp_adjust(struct monitor_dev_info *info, + bool is_high) +{ + if (!dev_pm_qos_request_active(&info->dev_max_freq_req)) + return 0; + + if (info->high_limit_table) { + if (info->high_limit) + dev_pm_qos_update_request(&info->dev_max_freq_req, + info->high_limit / 1000); + else + dev_pm_qos_update_request(&info->dev_max_freq_req, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); + return 0; + } + + if (!info->high_limit) + return 0; + + if (is_high) + dev_pm_qos_update_request(&info->dev_max_freq_req, + info->high_limit / 1000); + else + dev_pm_qos_update_request(&info->dev_max_freq_req, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); + + return 0; +} +EXPORT_SYMBOL(rockchip_monitor_dev_high_temp_adjust); + +static int rockchip_adjust_low_temp_opp_volt(struct monitor_dev_info *info, + bool is_low_temp) +{ + struct device *dev = info->dev; + struct opp_table *opp_table; + struct dev_pm_opp *opp; + int i = 0; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(opp_table)) + return PTR_ERR(opp_table); + + mutex_lock(&opp_table->lock); + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (!opp->available) + continue; + if (is_low_temp) { + if (opp->supplies[0].u_volt_max < + info->opp_table[i].low_temp_volt) + opp->supplies[0].u_volt_max = + info->opp_table[i].low_temp_volt; + opp->supplies[0].u_volt = + info->opp_table[i].low_temp_volt; + opp->supplies[0].u_volt_min = opp->supplies[0].u_volt; + if (opp_table->regulator_count > 1) { + if (opp->supplies[1].u_volt_max < + info->opp_table[i].low_temp_mem_volt) + opp->supplies[1].u_volt_max = + info->opp_table[i].low_temp_mem_volt; + opp->supplies[1].u_volt = + info->opp_table[i].low_temp_mem_volt; + opp->supplies[1].u_volt_min = + opp->supplies[1].u_volt; + } + } else { + opp->supplies[0].u_volt_min = info->opp_table[i].volt; + opp->supplies[0].u_volt = opp->supplies[0].u_volt_min; + opp->supplies[0].u_volt_max = + info->opp_table[i].max_volt; + if (opp_table->regulator_count > 1) { + opp->supplies[1].u_volt_min = + info->opp_table[i].mem_volt; + opp->supplies[1].u_volt = + opp->supplies[1].u_volt_min; + opp->supplies[1].u_volt_max = + info->opp_table[i].max_mem_volt; + } + } + i++; + } + mutex_unlock(&opp_table->lock); + + dev_pm_opp_put_opp_table(opp_table); + + return 0; +} + +static void rockchip_low_temp_adjust(struct monitor_dev_info *info, + bool is_low) +{ + struct monitor_dev_profile *devp = info->devp; + struct arm_smccc_res res; + int ret = 0; + + dev_dbg(info->dev, "low_temp %d\n", is_low); + + if (info->opp_table) + rockchip_adjust_low_temp_opp_volt(info, is_low); + + if (devp->low_temp_adjust) + ret = devp->low_temp_adjust(info, is_low); + if (!ret) + info->is_low_temp = is_low; + + if (devp->check_rate_volt) + devp->check_rate_volt(info); + + if (devp->opp_info && devp->opp_info->pvtpll_low_temp) { + res = sip_smc_pvtpll_config(PVTPLL_LOW_TEMP, + devp->opp_info->pvtpll_clk_id, + is_low, 0, 0, 0, 0); + if (res.a0) + dev_err(info->dev, + "%s: error cfg id=%u low temp %d (%d)\n", + __func__, devp->opp_info->pvtpll_clk_id, + is_low, (int)res.a0); + } +} + +static void rockchip_high_temp_adjust(struct monitor_dev_info *info, + bool is_high) +{ + struct monitor_dev_profile *devp = info->devp; + int ret = 0; + + if (!devp->high_temp_adjust) + return; + + if (info->high_limit_table) { + devp->high_temp_adjust(info, is_high); + } else { + dev_dbg(info->dev, "high_temp %d\n", is_high); + ret = devp->high_temp_adjust(info, is_high); + if (!ret) + info->is_high_temp = is_high; + } +} + +static int rockchip_monitor_low_temp_adjust(struct monitor_dev_info *info) +{ + if (!info || !info->is_low_temp_enabled) + return 0; + + if (info->high_limit_table) { + info->high_limit = 0; + rockchip_high_temp_adjust(info, true); + } else if (info->is_high_temp) { + rockchip_high_temp_adjust(info, false); + } + if (!info->is_low_temp) + rockchip_low_temp_adjust(info, true); + + return 0; +} + +int rockchip_monitor_suspend_low_temp_adjust(int cpu) +{ + struct monitor_dev_info *info = NULL, *tmp; + + down_read(&mdev_list_sem); + list_for_each_entry(tmp, &monitor_dev_list, node) { + if (tmp->devp->type != MONITOR_TYPE_CPU) + continue; + if (cpumask_test_cpu(cpu, &tmp->devp->allowed_cpus)) { + info = tmp; + break; + } + } + up_read(&mdev_list_sem); + + return rockchip_monitor_low_temp_adjust(info); +} +EXPORT_SYMBOL(rockchip_monitor_suspend_low_temp_adjust); + +void rockchip_monitor_remove_cpu_limit(int cpu) +{ + struct monitor_dev_info *info; + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) { + if (info->devp->type != MONITOR_TYPE_CPU) + continue; + if (cpumask_test_cpu(cpu, &info->devp->allowed_cpus)) { + if (info->status_max_limit) + freq_qos_update_request(&info->max_sta_freq_req, + FREQ_QOS_MAX_DEFAULT_VALUE); + break; + } + } + up_read(&mdev_list_sem); +} +EXPORT_SYMBOL(rockchip_monitor_remove_cpu_limit); + +void rockchip_monitor_restore_cpu_limit(int cpu) +{ + struct monitor_dev_info *info; + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) { + if (info->devp->type != MONITOR_TYPE_CPU) + continue; + if (cpumask_test_cpu(cpu, &info->devp->allowed_cpus)) { + if (info->status_max_limit) + freq_qos_update_request(&info->max_sta_freq_req, + info->status_max_limit); + break; + } + } + up_read(&mdev_list_sem); +} +EXPORT_SYMBOL(rockchip_monitor_restore_cpu_limit); + +static int +rockchip_system_monitor_wide_temp_adjust(struct monitor_dev_info *info, + int temp) +{ + unsigned long target_freq = 0; + int i; + + if (temp < info->low_temp) { + if (!info->is_low_temp) + rockchip_low_temp_adjust(info, true); + } else if (temp > (info->low_temp + info->temp_hysteresis)) { + if (info->is_low_temp) + rockchip_low_temp_adjust(info, false); + } + + if (info->high_limit_table) { + for (i = 0; info->high_limit_table[i].freq != UINT_MAX; i++) { + if (temp > info->high_limit_table[i].temp) + target_freq = + info->high_limit_table[i].freq * 1000; + } + if (target_freq != info->high_limit) { + info->high_limit = target_freq; + rockchip_high_temp_adjust(info, true); + } + } else { + if (temp > info->high_temp) { + if (!info->is_high_temp) + rockchip_high_temp_adjust(info, true); + } else if (temp < (info->high_temp - info->temp_hysteresis)) { + if (info->is_high_temp) + rockchip_high_temp_adjust(info, false); + } + } + + return 0; +} + +static void +rockchip_system_monitor_wide_temp_init(struct monitor_dev_info *info) +{ + int ret, temp; + + if (!info->opp_table) + return; + if (!system_monitor->tz) + return; + + /* + * set the init state to low temperature that the voltage will be enough + * when cpu up at low temperature. + */ + if (!info->is_low_temp) { + if (info->opp_table) + rockchip_adjust_low_temp_opp_volt(info, true); + info->is_low_temp = true; + } + + ret = thermal_zone_get_temp(system_monitor->tz, &temp); + if (ret || temp == THERMAL_TEMP_INVALID) { + dev_err(info->dev, + "failed to read out thermal zone (%d)\n", ret); + return; + } + + if (temp > info->high_temp) { + if (info->opp_table) + rockchip_adjust_low_temp_opp_volt(info, false); + info->is_low_temp = false; + info->is_high_temp = true; + } else if (temp > (info->low_temp + info->temp_hysteresis)) { + if (info->opp_table) + rockchip_adjust_low_temp_opp_volt(info, false); + info->is_low_temp = false; + } +} + +static const char *get_rdev_name(struct regulator_dev *rdev) +{ + if (rdev->constraints && rdev->constraints->name) + return rdev->constraints->name; + else if (rdev->desc->name) + return rdev->desc->name; + else + return ""; +} + +static void +rockchip_system_monitor_early_regulator_init(struct monitor_dev_info *info) +{ + struct monitor_dev_profile *devp = info->devp; + struct rockchip_opp_info *opp_info = devp->opp_info; + struct regulator *reg; + struct regulator_dev *rdev; + int i; + + if (!opp_info || !opp_info->regulators) + return; + + for (i = 0; i < opp_info->regulator_count; i++) { + if (!info->early_min_volt[i] || i >= 2) + continue; + rdev = opp_info->regulators[i]->rdev; + reg = regulator_get(NULL, get_rdev_name(rdev)); + if (!IS_ERR_OR_NULL(reg)) { + info->early_reg[i] = reg; + reg->voltage[PM_SUSPEND_ON].min_uV = info->early_min_volt[i]; + reg->voltage[PM_SUSPEND_ON].max_uV = rdev->constraints->max_uV; + } + } +} + +static void +rockchip_system_monitor_early_regulator_uninit(struct monitor_dev_info *info) +{ + struct monitor_dev_profile *devp = info->devp; + struct rockchip_opp_info *opp_info = devp->opp_info; + struct regulator_dev *rdev; + int min_uV, max_uV; + int ret, i; + + if (!opp_info || !opp_info->regulators) + return; + + for (i = 0; i < opp_info->regulator_count; i++) { + if (!info->early_reg[i] || i >= 2) + continue; + rdev = info->early_reg[i]->rdev; + min_uV = rdev->constraints->min_uV; + max_uV = rdev->constraints->max_uV; + ret = regulator_set_voltage(info->early_reg[i], min_uV, max_uV); + if (ret) + dev_err(&rdev->dev, + "%s: failed to set volt\n", __func__); + regulator_put(info->early_reg[i]); + info->early_reg[i] = NULL; + } +} + +static int +rockchip_system_monitor_freq_qos_requset(struct monitor_dev_info *info) +{ + struct devfreq *devfreq; + struct cpufreq_policy *policy; + int max_default_value = FREQ_QOS_MAX_DEFAULT_VALUE; + int ret; + + if (!info->devp->data) + return 0; + + if (info->is_low_temp && info->low_limit) + max_default_value = info->low_limit / 1000; + else if (info->is_high_temp && info->high_limit) + max_default_value = info->high_limit / 1000; + + if (info->devp->type == MONITOR_TYPE_CPU) { + policy = (struct cpufreq_policy *)info->devp->data; + ret = freq_qos_add_request(&policy->constraints, + &info->max_temp_freq_req, + FREQ_QOS_MAX, + max_default_value); + if (ret < 0) { + dev_info(info->dev, + "failed to add temp freq constraint\n"); + return ret; + } + ret = freq_qos_add_request(&policy->constraints, + &info->min_sta_freq_req, + FREQ_QOS_MIN, + FREQ_QOS_MIN_DEFAULT_VALUE); + if (ret < 0) { + dev_info(info->dev, + "failed to add sta freq constraint\n"); + freq_qos_remove_request(&info->max_temp_freq_req); + return ret; + } + ret = freq_qos_add_request(&policy->constraints, + &info->max_sta_freq_req, + FREQ_QOS_MAX, + FREQ_QOS_MAX_DEFAULT_VALUE); + if (ret < 0) { + dev_info(info->dev, + "failed to add sta freq constraint\n"); + freq_qos_remove_request(&info->max_temp_freq_req); + freq_qos_remove_request(&info->min_sta_freq_req); + return ret; + } + } else if (info->devp->type == MONITOR_TYPE_DEV) { + devfreq = (struct devfreq *)info->devp->data; + ret = dev_pm_qos_add_request(devfreq->dev.parent, + &info->dev_max_freq_req, + DEV_PM_QOS_MAX_FREQUENCY, + max_default_value); + if (ret < 0) { + dev_info(info->dev, "failed to add freq constraint\n"); + return ret; + } + } + + return 0; +} + +int rockchip_monitor_check_rate_volt(struct monitor_dev_info *info) +{ + struct device *dev = info->dev; + struct monitor_dev_profile *devp = info->devp; + struct rockchip_opp_info *opp_info = devp->opp_info; + + rockchip_opp_dvfs_lock(opp_info); + + if (devp->type == MONITOR_TYPE_DEV) { + if (pm_runtime_active(dev)) + opp_info->is_runtime_active = true; + else + opp_info->is_runtime_active = false; + } + rockchip_opp_check_rate_volt(dev, opp_info); + opp_info->is_rate_volt_checked = true; + + rockchip_opp_dvfs_unlock(opp_info); + + return 0; +} +EXPORT_SYMBOL(rockchip_monitor_check_rate_volt); + +static void rockchip_system_monitor_check_rate_volt(struct monitor_dev_info *info) +{ + if (info->devp->check_rate_volt) + info->devp->check_rate_volt(info); + else + rockchip_monitor_check_rate_volt(info); +} + +struct monitor_dev_info * +rockchip_system_monitor_register(struct device *dev, + struct monitor_dev_profile *devp) +{ + struct monitor_dev_info *info; + + if (!system_monitor) + return ERR_PTR(-ENOMEM); + + if (!devp) + return ERR_PTR(-EINVAL); + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + info->dev = dev; + info->devp = devp; + + if (monitor_device_parse_dt(dev, info)) { + rockchip_system_monitor_check_rate_volt(info); + kfree(info); + return ERR_PTR(-EINVAL); + } + + rockchip_system_monitor_early_regulator_init(info); + rockchip_system_monitor_wide_temp_init(info); + rockchip_system_monitor_check_rate_volt(info); + rockchip_system_monitor_freq_qos_requset(info); + + down_write(&mdev_list_sem); + list_add(&info->node, &monitor_dev_list); + up_write(&mdev_list_sem); + + return info; +} +EXPORT_SYMBOL(rockchip_system_monitor_register); + +void rockchip_system_monitor_unregister(struct monitor_dev_info *info) +{ + if (!info) + return; + + down_write(&mdev_list_sem); + rockchip_system_monitor_early_regulator_uninit(info); + list_del(&info->node); + up_write(&mdev_list_sem); + + if (info->devp->type == MONITOR_TYPE_CPU) { + if (freq_qos_request_active(&info->max_temp_freq_req)) + freq_qos_remove_request(&info->max_temp_freq_req); + if (freq_qos_request_active(&info->min_sta_freq_req)) + freq_qos_remove_request(&info->min_sta_freq_req); + if (freq_qos_request_active(&info->max_sta_freq_req)) + freq_qos_remove_request(&info->max_sta_freq_req); + } else { + if (dev_pm_qos_request_active(&info->dev_max_freq_req)) + dev_pm_qos_remove_request(&info->dev_max_freq_req); + } + + kfree(info->low_temp_adjust_table); + kfree(info->opp_table); + kfree(info); +} +EXPORT_SYMBOL(rockchip_system_monitor_unregister); + +int rockchip_system_monitor_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&system_monitor_notifier_list, nb); +} +EXPORT_SYMBOL(rockchip_system_monitor_register_notifier); + +void rockchip_system_monitor_unregister_notifier(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&system_monitor_notifier_list, nb); +} +EXPORT_SYMBOL(rockchip_system_monitor_unregister_notifier); + +static int rockchip_system_monitor_temp_notify(int temp) +{ + struct system_monitor_event_data event_data; + int ret; + + event_data.temp = temp; + ret = blocking_notifier_call_chain(&system_monitor_notifier_list, + SYSTEM_MONITOR_CHANGE_TEMP, + (void *)&event_data); + + return notifier_to_errno(ret); +} + +static int notify_dummy(struct thermal_zone_device *tz, int trip) +{ + return 0; +} + +static struct thermal_governor thermal_gov_dummy = { + .name = "dummy", + .throttle = notify_dummy, +}; + +static int rockchip_system_monitor_parse_dt(struct system_monitor *monitor) +{ + struct device_node *np = monitor->dev->of_node; + const char *tz_name, *buf = NULL; + + if (of_property_read_string(np, "rockchip,early-suspend-offline-cpus", &buf)) + cpumask_clear(&monitor->early_suspend_offline_cpus); + else + cpulist_parse(buf, &monitor->early_suspend_offline_cpus); + + if (of_property_read_string(np, "rockchip,video-4k-offline-cpus", &buf)) + cpumask_clear(&monitor->video_4k_offline_cpus); + else + cpulist_parse(buf, &monitor->video_4k_offline_cpus); + + if (of_property_read_string(np, "rockchip,thermal-zone", &tz_name)) + goto out; + monitor->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(monitor->tz)) { + monitor->tz = NULL; + goto out; + } + if (of_property_read_u32(np, "rockchip,polling-delay", + &monitor->delay)) + monitor->delay = THERMAL_POLLING_DELAY; + + if (of_property_read_string(np, "rockchip,temp-offline-cpus", + &buf)) + cpumask_clear(&system_monitor->temp_offline_cpus); + else + cpulist_parse(buf, &system_monitor->temp_offline_cpus); + + if (of_property_read_u32(np, "rockchip,offline-cpu-temp", + &system_monitor->offline_cpus_temp)) + system_monitor->offline_cpus_temp = INT_MAX; + of_property_read_u32(np, "rockchip,temp-hysteresis", + &system_monitor->temp_hysteresis); + rockchip_get_temp_freq_table(np, "rockchip,temp-ddr-ref-mode", + &system_monitor->temp_ddr_ref_mode); + + if (of_find_property(np, "rockchip,thermal-governor-dummy", NULL)) { + if (monitor->tz->governor->unbind_from_tz) + monitor->tz->governor->unbind_from_tz(monitor->tz); + monitor->tz->governor = &thermal_gov_dummy; + } + +out: + return 0; +} + +#ifdef CONFIG_HOTPLUG_CPU +static void rockchip_system_monitor_first_cpu_online(struct cpumask *online_cpus) +{ + struct monitor_dev_info *tmp; + struct cpumask tmp_mask; + + down_read(&mdev_list_sem); + list_for_each_entry(tmp, &monitor_dev_list, node) { + if (tmp->devp->type != MONITOR_TYPE_CPU) + continue; + /* Check if all allowed cpus of the cluster are offline */ + cpumask_and(&tmp_mask, &tmp->devp->allowed_cpus, cpu_online_mask); + if (!cpumask_empty(&tmp_mask)) + continue; + /* Check if the online cpus contain one allowed cpu of the cluster */ + cpumask_and(&tmp_mask, &tmp->devp->allowed_cpus, online_cpus); + if (cpumask_empty(&tmp_mask)) + continue; + rockchip_monitor_low_temp_adjust(tmp); + } + up_read(&mdev_list_sem); +} +#endif + +static void rockchip_system_monitor_cpu_on_off(void) +{ +#ifdef CONFIG_HOTPLUG_CPU + struct cpumask online_cpus, offline_cpus; + unsigned int cpu; + + mutex_lock(&cpu_on_off_mutex); + + cpumask_clear(&offline_cpus); + if (system_monitor->is_temp_offline) { + cpumask_or(&offline_cpus, &system_monitor->status_offline_cpus, + &system_monitor->temp_offline_cpus); + } else { + cpumask_copy(&offline_cpus, + &system_monitor->status_offline_cpus); + } + if (cpumask_equal(&offline_cpus, &system_monitor->offline_cpus)) + goto out; + cpumask_copy(&system_monitor->offline_cpus, &offline_cpus); + for_each_cpu(cpu, &system_monitor->offline_cpus) { + if (cpu_online(cpu)) + remove_cpu(cpu); + } + + cpumask_clear(&online_cpus); + cpumask_andnot(&online_cpus, cpu_possible_mask, + &system_monitor->offline_cpus); + cpumask_xor(&online_cpus, cpu_online_mask, &online_cpus); + if (cpumask_empty(&online_cpus)) + goto out; + rockchip_system_monitor_first_cpu_online(&online_cpus); + for_each_cpu(cpu, &online_cpus) + add_cpu(cpu); + +out: + mutex_unlock(&cpu_on_off_mutex); +#endif +} + +static void rockchip_system_monitor_temp_cpu_on_off(int temp) +{ + bool is_temp_offline; + + if (cpumask_empty(&system_monitor->temp_offline_cpus)) + return; + + if (temp > system_monitor->offline_cpus_temp) + is_temp_offline = true; + else if (temp < system_monitor->offline_cpus_temp - + system_monitor->temp_hysteresis) + is_temp_offline = false; + else + return; + + if (system_monitor->is_temp_offline == is_temp_offline) + return; + system_monitor->is_temp_offline = is_temp_offline; + rockchip_system_monitor_cpu_on_off(); +} + +static void rockchip_system_monitor_temp_ddr_ref_mode(int temp) +{ + unsigned int i, new_ref_mode = 0; + static unsigned int last_ref_mode; + + if (!system_monitor->temp_ddr_ref_mode) + return; + if (!system_monitor->ddr_trefi_update) + return; + + for (i = 0; system_monitor->temp_ddr_ref_mode[i].freq != UINT_MAX; i++) { + if (temp > system_monitor->temp_ddr_ref_mode[i].temp) + new_ref_mode = system_monitor->temp_ddr_ref_mode[i].freq; + } + if (new_ref_mode && (new_ref_mode != last_ref_mode)) { + system_monitor->ddr_trefi_update(new_ref_mode); + last_ref_mode = new_ref_mode; + } +} + +static void rockchip_system_monitor_thermal_update(void) +{ + int temp, ret; + struct monitor_dev_info *info; + + ret = thermal_zone_get_temp(system_monitor->tz, &temp); + if (ret || temp == THERMAL_TEMP_INVALID) + goto out; + + dev_dbg(system_monitor->dev, "temperature=%d\n", temp); + + if (temp < system_monitor->last_temp && + system_monitor->last_temp - temp <= 2000) + goto out; + system_monitor->last_temp = temp; + + rockchip_system_monitor_temp_notify(temp); + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) + rockchip_system_monitor_wide_temp_adjust(info, temp); + up_read(&mdev_list_sem); + + rockchip_system_monitor_temp_cpu_on_off(temp); + rockchip_system_monitor_temp_ddr_ref_mode(temp); + +out: + mod_delayed_work(system_freezable_wq, &system_monitor->thermal_work, + msecs_to_jiffies(system_monitor->delay)); +} + +static void rockchip_system_monitor_thermal_check(struct work_struct *work) +{ + if (atomic_read(&monitor_in_suspend)) + return; + + rockchip_system_monitor_thermal_update(); +} + +static void rockchip_system_status_cpu_limit_freq(struct monitor_dev_info *info, + unsigned long status) +{ + unsigned int target_freq = 0; + + if (!freq_qos_request_active(&info->min_sta_freq_req)) + return; + if (!freq_qos_request_active(&info->max_sta_freq_req)) + return; + + if (status & SYS_STATUS_REBOOT) { + freq_qos_update_request(&info->max_sta_freq_req, + info->reboot_freq); + freq_qos_update_request(&info->min_sta_freq_req, + info->reboot_freq); + return; + } + + if (info->early_suspend_freq && (status & SYS_STATUS_SUSPEND)) + target_freq = info->early_suspend_freq; + if (info->video_1080p_freq && (status & SYS_STATUS_VIDEO_1080P)) { + if (!target_freq || target_freq > info->video_1080p_freq) + target_freq = info->video_1080p_freq; + } + if (info->video_4k_freq && (status & SYS_STATUS_VIDEO_4K)) { + if (!target_freq || target_freq > info->video_4k_freq) + target_freq = info->video_4k_freq; + } + + if (target_freq == info->status_max_limit) + return; + info->status_max_limit = target_freq; + if (info->status_max_limit) + freq_qos_update_request(&info->max_sta_freq_req, + info->status_max_limit); + else + freq_qos_update_request(&info->max_sta_freq_req, + FREQ_QOS_MAX_DEFAULT_VALUE); +} + +static void rockchip_system_status_limit_freq(unsigned long status) +{ + struct monitor_dev_info *info; + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) { + if (info->devp->type == MONITOR_TYPE_CPU) + rockchip_system_status_cpu_limit_freq(info, status); + } + up_read(&mdev_list_sem); +} + +static void rockchip_system_status_cpu_on_off(unsigned long status) +{ + struct cpumask offline_cpus; + + if (cpumask_empty(&system_monitor->video_4k_offline_cpus) && + cpumask_empty(&system_monitor->early_suspend_offline_cpus)) + return; + + cpumask_clear(&offline_cpus); + if (status & SYS_STATUS_SUSPEND) + cpumask_copy(&offline_cpus, + &system_monitor->early_suspend_offline_cpus); + else if (status & SYS_STATUS_VIDEO_4K) + cpumask_copy(&offline_cpus, + &system_monitor->video_4k_offline_cpus); + if (cpumask_equal(&offline_cpus, &system_monitor->status_offline_cpus)) + return; + cpumask_copy(&system_monitor->status_offline_cpus, &offline_cpus); + rockchip_system_monitor_cpu_on_off(); +} + +static int rockchip_system_status_notifier(struct notifier_block *nb, + unsigned long status, + void *ptr) +{ + rockchip_system_status_limit_freq(status); + + rockchip_system_status_cpu_on_off(status); + + return NOTIFY_OK; +} + +static int rockchip_system_monitor_set_cpu_uevent_suppress(bool is_suppress) +{ + struct monitor_dev_info *info; + struct cpufreq_policy *policy; + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) { + if (info->devp->type != MONITOR_TYPE_CPU) + continue; + policy = (struct cpufreq_policy *)info->devp->data; + if (!policy || !policy->cdev) + continue; + if (is_suppress) + dev_set_uevent_suppress(&policy->cdev->device, 1); + else + dev_set_uevent_suppress(&policy->cdev->device, 0); + } + up_read(&mdev_list_sem); + + return 0; +} + +static int monitor_pm_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + case PM_SUSPEND_PREPARE: + atomic_set(&monitor_in_suspend, 1); + rockchip_system_monitor_set_cpu_uevent_suppress(true); + break; + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + if (system_monitor->tz) + rockchip_system_monitor_thermal_update(); + atomic_set(&monitor_in_suspend, 0); + rockchip_system_monitor_set_cpu_uevent_suppress(false); + system_monitor->last_temp = INT_MAX; + break; + default: + break; + } + return 0; +} + +static struct notifier_block monitor_pm_nb = { + .notifier_call = monitor_pm_notify, +}; + +static int rockchip_monitor_reboot_notifier(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + rockchip_set_system_status(SYS_STATUS_REBOOT); + if (system_monitor->tz) + cancel_delayed_work_sync(&system_monitor->thermal_work); + + return NOTIFY_OK; +} + +static struct notifier_block rockchip_monitor_reboot_nb = { + .notifier_call = rockchip_monitor_reboot_notifier, +}; + +#ifdef CONFIG_FB +static int rockchip_monitor_fb_notifier(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + struct fb_event *event = ptr; + + if (action != FB_EVENT_BLANK) + return NOTIFY_OK; + + switch (*((int *)event->data)) { + case FB_BLANK_UNBLANK: + rockchip_clear_system_status(SYS_STATUS_SUSPEND); + break; + case FB_BLANK_POWERDOWN: + rockchip_set_system_status(SYS_STATUS_SUSPEND); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block rockchip_monitor_fb_nb = { + .notifier_call = rockchip_monitor_fb_notifier, +}; +#elif defined(CONFIG_ROCKCHIP_EARLYSUSPEND) +static void rockchip_monitor_early_suspend(struct early_suspend *h) +{ + rockchip_set_system_status(SYS_STATUS_SUSPEND); +} + +static void rockchip_monitor_resume(struct early_suspend *h) +{ + rockchip_clear_system_status(SYS_STATUS_SUSPEND); +} + +static struct early_suspend monitor_early_suspend_handler = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN, + .suspend = rockchip_monitor_early_suspend, + .resume = rockchip_monitor_resume, +}; +#endif + +static int rockchip_eink_devfs_notifier(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + switch (action) { + case EBC_ON: + rockchip_set_system_status(SYS_STATUS_EBC); + break; + case EBC_OFF: + rockchip_clear_system_status(SYS_STATUS_EBC); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block rockchip_monitor_ebc_nb = { + .notifier_call = rockchip_eink_devfs_notifier, +}; + +static void system_monitor_early_min_volt_function(struct work_struct *work) +{ + struct monitor_dev_info *info; + + down_read(&mdev_list_sem); + list_for_each_entry(info, &monitor_dev_list, node) + rockchip_system_monitor_early_regulator_uninit(info); + up_read(&mdev_list_sem); +} + +static DECLARE_DELAYED_WORK(system_monitor_early_min_volt_work, + system_monitor_early_min_volt_function); + +static int system_monitor_ddr_trefi_update(u32 ref_mode) +{ + struct arm_smccc_res res; + + res = sip_smc_dram(0, ref_mode, + ROCKCHIP_SIP_CONFIG_DRAM_TREFI_UPD); + if (res.a0 || res.a1) { + pr_err("rockchip_sip_config_dram_trefi_upd error:%lx\n", res.a0); + return -ENOMEM; + } + + return 0; +} + +static __maybe_unused int rk3506_system_monitor_init(struct platform_device *pdev) +{ + system_monitor->ddr_trefi_update = system_monitor_ddr_trefi_update; + + return 0; +} + +static const struct of_device_id rockchip_system_monitor_of_match[] = { + { .compatible = "rockchip,system-monitor", .data = NULL }, +#if IS_ENABLED(CONFIG_CPU_RK3506) + { .compatible = "rk3506,system-monitor", .data = rk3506_system_monitor_init }, +#endif + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rockchip_system_monitor_of_match); + +static int rockchip_system_monitor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int (*init)(struct platform_device *pdev); + + system_monitor = devm_kzalloc(dev, sizeof(struct system_monitor), + GFP_KERNEL); + if (!system_monitor) + return -ENOMEM; + system_monitor->dev = dev; + + system_monitor->kobj = kobject_create_and_add("system_monitor", NULL); + if (!system_monitor->kobj) + return -ENOMEM; + if (sysfs_create_file(system_monitor->kobj, &status.attr)) + dev_err(dev, "failed to create system status sysfs\n"); +#ifdef CONFIG_ROCKCHIP_EARLYSUSPEND + if (sysfs_create_file(system_monitor->kobj, &early_suspend_state.attr)) + dev_err(dev, "failed to create early suspend state sysfs\n"); +#endif + + cpumask_clear(&system_monitor->status_offline_cpus); + cpumask_clear(&system_monitor->offline_cpus); + + rockchip_system_monitor_parse_dt(system_monitor); + + init = device_get_match_data(dev); + if (init) + init(pdev); + + if (system_monitor->tz) { + system_monitor->last_temp = INT_MAX; + INIT_DELAYED_WORK(&system_monitor->thermal_work, + rockchip_system_monitor_thermal_check); + mod_delayed_work(system_freezable_wq, + &system_monitor->thermal_work, + msecs_to_jiffies(system_monitor->delay)); + } + + system_monitor->status_nb.notifier_call = + rockchip_system_status_notifier; + rockchip_register_system_status_notifier(&system_monitor->status_nb); + + if (register_pm_notifier(&monitor_pm_nb)) + dev_err(dev, "failed to register suspend notifier\n"); + + register_reboot_notifier(&rockchip_monitor_reboot_nb); + +#ifdef CONFIG_FB + if (fb_register_client(&rockchip_monitor_fb_nb)) + dev_err(dev, "failed to register fb nb\n"); +#elif defined(CONFIG_ROCKCHIP_EARLYSUSPEND) + register_early_suspend(&monitor_early_suspend_handler); +#endif + + ebc_register_notifier(&rockchip_monitor_ebc_nb); + + schedule_delayed_work(&system_monitor_early_min_volt_work, + msecs_to_jiffies(30000)); + + dev_info(dev, "system monitor probe\n"); + + return 0; +} + +static struct platform_driver rockchip_system_monitor_driver = { + .probe = rockchip_system_monitor_probe, + .driver = { + .name = "rockchip-system-monitor", + .of_match_table = rockchip_system_monitor_of_match, + }, +}; +module_platform_driver(rockchip_system_monitor_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Finley Xiao "); +MODULE_DESCRIPTION("rockchip system monitor driver"); diff --git a/package/kernel/rockchip-drivers/src/include/linux/ebc.h b/package/kernel/rockchip-drivers/src/include/linux/ebc.h new file mode 100644 index 0000000000..6067902e6d --- /dev/null +++ b/package/kernel/rockchip-drivers/src/include/linux/ebc.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2024 Rockchip Electronics Co., Ltd. + * + * Author: Zorro Liu + */ + +#ifndef __LINUX_EBC_H__ +#define __LINUX_EBC_H__ + +#include + +/* + * ebc status notify + */ +#define EBC_OFF (0) +#define EBC_ON (1) +#define EBC_FB_BLANK (2) +#define EBC_FB_UNBLANK (3) + +#if IS_REACHABLE(CONFIG_ROCKCHIP_EBC_DEV) +int ebc_register_notifier(struct notifier_block *nb); +int ebc_unregister_notifier(struct notifier_block *nb); +int ebc_notify(unsigned long event); +#else +static inline int ebc_register_notifier(struct notifier_block *nb) +{ + return 0; +} + +static inline int ebc_unregister_notifier(struct notifier_block *nb) +{ + return 0; +} + +static inline int ebc_notify(unsigned long event) +{ + return 0; +} +#endif + +#endif diff --git a/package/kernel/rockchip-drivers/src/include/linux/soc/rockchip/pvtm.h b/package/kernel/rockchip-drivers/src/include/linux/soc/rockchip/pvtm.h new file mode 100644 index 0000000000..3d2495cfd6 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/include/linux/soc/rockchip/pvtm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SOC_ROCKCHIP_PVTM_H +#define __SOC_ROCKCHIP_PVTM_H + +#if IS_ENABLED(CONFIG_ROCKCHIP_PVTM) +u32 rockchip_get_pvtm_value(unsigned int id, unsigned int ring_sel, + unsigned int time_us); +#else +static inline u32 rockchip_get_pvtm_value(unsigned int id, + unsigned int ring_sel, + unsigned int time_us) +{ + return 0; +} +#endif + +#endif /* __SOC_ROCKCHIP_PVTM_H */ diff --git a/package/kernel/rockchip-drivers/src/include/soc/rockchip/rockchip-system-status.h b/package/kernel/rockchip-drivers/src/include/soc/rockchip/rockchip-system-status.h new file mode 100644 index 0000000000..5eb53ff2f3 --- /dev/null +++ b/package/kernel/rockchip-drivers/src/include/soc/rockchip/rockchip-system-status.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (C) 2019, Fuzhou Rockchip Electronics Co., Ltd + */ + +#ifndef __SOC_ROCKCHIP_SYSTEM_STATUS_H +#define __SOC_ROCKCHIP_SYSTEM_STATUS_H + +#include + +#if IS_REACHABLE(CONFIG_ROCKCHIP_SYSTEM_MONITOR) +int rockchip_register_system_status_notifier(struct notifier_block *nb); +int rockchip_unregister_system_status_notifier(struct notifier_block *nb); +void rockchip_set_system_status(unsigned long status); +void rockchip_clear_system_status(unsigned long status); +unsigned long rockchip_get_system_status(void); +int rockchip_add_system_status_interface(struct device *dev); +void rockchip_update_system_status(const char *buf); +#else +static inline int +rockchip_register_system_status_notifier(struct notifier_block *nb) +{ + return -ENOTSUPP; +}; + +static inline int +rockchip_unregister_system_status_notifier(struct notifier_block *nb) +{ + return -ENOTSUPP; +}; + +static inline void rockchip_set_system_status(unsigned long status) +{ +}; + +static inline void rockchip_clear_system_status(unsigned long status) +{ +}; + +static inline unsigned long rockchip_get_system_status(void) +{ + return 0; +}; + +static inline int rockchip_add_system_status_interface(struct device *dev) +{ + return -ENOTSUPP; +}; + +static inline void rockchip_update_system_status(const char *buf) +{ +}; +#endif /* CONFIG_ROCKCHIP_SYSTEM_MONITOR */ + +#endif diff --git a/package/kernel/rockchip-drivers/src/include/trace/events/thermal_ipa_power.h b/package/kernel/rockchip-drivers/src/include/trace/events/thermal_ipa_power.h new file mode 100644 index 0000000000..a3a932c75d --- /dev/null +++ b/package/kernel/rockchip-drivers/src/include/trace/events/thermal_ipa_power.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM thermal_ipa_power + +#if !defined(_TRACE_THERMAL_IPA_POWER_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_THERMAL_IPA_POWER_H + +#include + +TRACE_EVENT(thermal_ipa_get_static_power, + TP_PROTO(u32 leakage, u32 coefficient, s32 temp, + u32 temp_scaling_factor, u32 volt, u32 volt_scaling_factor, + u32 static_power), + + TP_ARGS(leakage, coefficient, temp, temp_scaling_factor, volt, + volt_scaling_factor, static_power), + + TP_STRUCT__entry( + __field(u32, leakage) + __field(u32, coefficient) + __field(s32, temp) + __field(u32, temp_scaling_factor) + __field(u32, volt) + __field(u32, volt_scaling_factor) + __field(u32, static_power) + ), + + TP_fast_assign( + __entry->leakage = leakage; + __entry->coefficient = coefficient; + __entry->temp = temp; + __entry->temp_scaling_factor = temp_scaling_factor; + __entry->volt = volt; + __entry->volt_scaling_factor = volt_scaling_factor; + __entry->static_power = static_power; + ), + TP_printk("lkg=%u c=%u t=%d ts=%u v=%u vs=%u static_power=%u", + __entry->leakage, __entry->coefficient, __entry->temp, + __entry->temp_scaling_factor, __entry->volt, + __entry->volt_scaling_factor, __entry->static_power) +); + + +#endif /* _TRACE_THERMAL_IPA_POWER_H */ + +/* This part must be outside protection */ +#include