diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index f911606897b8d1dcdb23e7dfdafc012f0879211e..1e255210851e49bdff23c5db918609a5261d1690 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -189,6 +189,16 @@ config ARM_RASPBERRYPI_CPUFREQ
 
 	  If in doubt, say N.
 
+config ARM_ROCKCHIP_CPUFREQ
+	tristate "Rockchip CPUfreq driver"
+	depends on ARCH_ROCKCHIP && CPUFREQ_DT
+	select PM_OPP
+	help
+	  This adds the CPUFreq driver support for Rockchip SoCs,
+	  based on cpufreq-dt.
+
+	  If in doubt, say N.
+
 config ARM_S3C64XX_CPUFREQ
 	bool "Samsung S3C64XX"
 	depends on CPU_S3C6410
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 8d141c71b016bd311861d6e059245eef3383cbfa..14fb48863f0b7b7af69314f3c324b11db0b9ce8f 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_PXA3xx)			+= pxa3xx-cpufreq.o
 obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW)	+= qcom-cpufreq-hw.o
 obj-$(CONFIG_ARM_QCOM_CPUFREQ_NVMEM)	+= qcom-cpufreq-nvmem.o
 obj-$(CONFIG_ARM_RASPBERRYPI_CPUFREQ) 	+= raspberrypi-cpufreq.o
+obj-$(CONFIG_ARM_ROCKCHIP_CPUFREQ)	+= rockchip-cpufreq.o
 obj-$(CONFIG_ARM_S3C64XX_CPUFREQ)	+= s3c64xx-cpufreq.o
 obj-$(CONFIG_ARM_S5PV210_CPUFREQ)	+= s5pv210-cpufreq.o
 obj-$(CONFIG_ARM_SA1110_CPUFREQ)	+= sa1110-cpufreq.o
diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c
index bd1e1357cef8e2b9f232959e4488135cb2709136..cfd35aa520432ef9e324617f98a7e0351e99dcaa 100644
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -168,6 +168,8 @@ static const struct of_device_id blocklist[] __initconst = {
 	{ .compatible = "qcom,sm8450", },
 	{ .compatible = "qcom,sm8550", },
 
+	{ .compatible = "rockchip,rk3588", },
+
 	{ .compatible = "st,stih407", },
 	{ .compatible = "st,stih410", },
 	{ .compatible = "st,stih418", },
diff --git a/drivers/cpufreq/rockchip-cpufreq.c b/drivers/cpufreq/rockchip-cpufreq.c
new file mode 100644
index 0000000000000000000000000000000000000000..0bf57ac85e6081ba6e9e7dadc85ce4cb5171344f
--- /dev/null
+++ b/drivers/cpufreq/rockchip-cpufreq.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip CPUFreq Driver. This is similar to the generic DT
+ * cpufreq driver, but handles the following platform specific
+ * quirks:
+ *
+ *  * support for two regulators - one for the CPU core and one
+ *    for the memory interface
+ *  * reboot handler to setup the reboot frequency
+ *  * handling of read margin registers
+ *
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd
+ * Copyright (C) 2023 Collabora Ltd.
+ */
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include "cpufreq-dt.h"
+
+#define RK3588_MEMCFG_HSSPRF_LOW	0x20
+#define RK3588_MEMCFG_HSDPRF_LOW	0x28
+#define RK3588_MEMCFG_HSDPRF_HIGH	0x2c
+#define RK3588_CPU_CTRL			0x30
+
+#define VOLT_RM_TABLE_END		~1
+
+static struct platform_device *cpufreq_pdev;
+static LIST_HEAD(priv_list);
+
+struct volt_rm_table {
+	uint32_t volt;
+	uint32_t rm;
+};
+
+struct rockchip_opp_info {
+	const struct rockchip_opp_data *data;
+	struct volt_rm_table *volt_rm_tbl;
+	struct regmap *grf;
+	u32 current_rm;
+	u32 reboot_freq;
+};
+
+struct private_data {
+	struct list_head node;
+
+	cpumask_var_t cpus;
+	struct device *cpu_dev;
+	struct cpufreq_frequency_table *freq_table;
+};
+
+struct rockchip_opp_data {
+	int (*set_read_margin)(struct device *dev, struct rockchip_opp_info *opp_info,
+			       unsigned long volt);
+};
+
+struct cluster_info {
+	struct list_head list_head;
+	struct rockchip_opp_info opp_info;
+	cpumask_t cpus;
+};
+static LIST_HEAD(cluster_info_list);
+
+static int rk3588_cpu_set_read_margin(struct device *dev, struct rockchip_opp_info *opp_info,
+				      unsigned long volt)
+{
+	bool is_found = false;
+	u32 rm;
+	int i;
+
+	if (!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) {
+			rm = opp_info->volt_rm_tbl[i].rm;
+			is_found = true;
+			break;
+		}
+	}
+
+	if (!is_found)
+		return 0;
+	if (rm == opp_info->current_rm)
+		return 0;
+	if (!opp_info->grf)
+		return 0;
+
+	dev_dbg(dev, "set rm to %d\n", rm);
+	regmap_write(opp_info->grf, RK3588_MEMCFG_HSSPRF_LOW, 0x001c0000 | (rm << 2));
+	regmap_write(opp_info->grf, RK3588_MEMCFG_HSDPRF_LOW, 0x003c0000 | (rm << 2));
+	regmap_write(opp_info->grf, RK3588_MEMCFG_HSDPRF_HIGH, 0x003c0000 | (rm << 2));
+	regmap_write(opp_info->grf, RK3588_CPU_CTRL, 0x00200020);
+	udelay(1);
+	regmap_write(opp_info->grf, RK3588_CPU_CTRL, 0x00200000);
+
+	opp_info->current_rm = rm;
+
+	return 0;
+}
+
+static const struct rockchip_opp_data rk3588_cpu_opp_data = {
+	.set_read_margin = rk3588_cpu_set_read_margin,
+};
+
+static const struct of_device_id rockchip_cpufreq_of_match[] = {
+	{
+		.compatible = "rockchip,rk3588",
+		.data = (void *)&rk3588_cpu_opp_data,
+	},
+	{},
+};
+
+static struct cluster_info *rockchip_cluster_info_lookup(int cpu)
+{
+	struct cluster_info *cluster;
+
+	list_for_each_entry(cluster, &cluster_info_list, list_head) {
+		if (cpumask_test_cpu(cpu, &cluster->cpus))
+			return cluster;
+	}
+
+	return NULL;
+}
+
+static int rockchip_cpufreq_set_volt(struct device *dev,
+				     struct regulator *reg,
+				     struct dev_pm_opp_supply *supply)
+{
+	int ret;
+
+	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",
+			__func__, supply->u_volt_min, supply->u_volt,
+			supply->u_volt_max, ret);
+
+	return ret;
+}
+
+static int rockchip_cpufreq_set_read_margin(struct device *dev,
+					    struct rockchip_opp_info *opp_info,
+					    unsigned long volt)
+{
+	if (opp_info->data && opp_info->data->set_read_margin) {
+		opp_info->data->set_read_margin(dev, opp_info, volt);
+	}
+
+	return 0;
+}
+
+static int rk_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 dev_pm_opp_supply old_supplies[2];
+	struct dev_pm_opp_supply new_supplies[2];
+	struct regulator *vdd_reg = regulators[0];
+	struct regulator *mem_reg = regulators[1];
+	struct rockchip_opp_info *opp_info;
+	struct cluster_info *cluster;
+	int ret = 0;
+	unsigned long old_freq = dev_pm_opp_get_freq(old_opp);
+	unsigned long new_freq = dev_pm_opp_get_freq(new_opp);
+
+	/* We must have two regulators here */
+	WARN_ON(count != 2);
+
+	ret = dev_pm_opp_get_supplies(old_opp, old_supplies);
+	if (ret)
+		return ret;
+
+	ret = dev_pm_opp_get_supplies(new_opp, new_supplies);
+	if (ret)
+		return ret;
+
+	cluster = rockchip_cluster_info_lookup(dev->id);
+	if (!cluster)
+		return -EINVAL;
+	opp_info = &cluster->opp_info;
+
+	if (new_freq >= old_freq) {
+		ret = rockchip_cpufreq_set_volt(dev, mem_reg, &new_supplies[1]);
+		if (ret)
+			goto error;
+		ret = rockchip_cpufreq_set_volt(dev, vdd_reg, &new_supplies[0]);
+		if (ret)
+			goto error;
+		rockchip_cpufreq_set_read_margin(dev, opp_info, new_supplies[0].u_volt);
+	} else {
+		rockchip_cpufreq_set_read_margin(dev, opp_info, new_supplies[0].u_volt);
+		ret = rockchip_cpufreq_set_volt(dev, vdd_reg, &new_supplies[0]);
+		if (ret)
+			goto error;
+		ret = rockchip_cpufreq_set_volt(dev, mem_reg, &new_supplies[1]);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	rockchip_cpufreq_set_read_margin(dev, opp_info, old_supplies[0].u_volt);
+	rockchip_cpufreq_set_volt(dev, mem_reg, &old_supplies[1]);
+	rockchip_cpufreq_set_volt(dev, vdd_reg, &old_supplies[0]);
+	return ret;
+}
+
+static 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);
+}
+
+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_cpufreq_reboot(struct notifier_block *notifier, unsigned long event, void *cmd)
+{
+	struct cluster_info *cluster;
+	struct device *dev;
+	int freq, ret, cpu;
+
+	if (event != SYS_RESTART)
+		return NOTIFY_DONE;
+
+	for_each_possible_cpu(cpu) {
+		cluster = rockchip_cluster_info_lookup(cpu);
+		if (!cluster)
+			continue;
+
+		dev = get_cpu_device(cpu);
+		if (!dev)
+			continue;
+
+		freq = cluster->opp_info.reboot_freq;
+
+		if (freq) {
+			ret = dev_pm_opp_set_rate(dev, freq);
+			if (ret)
+				dev_err(dev, "Failed setting reboot freq for cpu %d to %d: %d\n",
+					cpu, freq, ret);
+			dev_pm_opp_remove_table(dev);
+		}
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster)
+{
+	struct rockchip_opp_info *opp_info = &cluster->opp_info;
+	int reg_table_token = -EINVAL;
+	int opp_table_token = -EINVAL;
+	struct device_node *np;
+	struct device *dev;
+	const char * const reg_names[] = { "cpu", "mem", NULL };
+	int ret = 0;
+
+	dev = get_cpu_device(cpu);
+	if (!dev)
+		return -ENODEV;
+
+	if (!of_find_property(dev->of_node, "cpu-supply", NULL))
+		return -ENOENT;
+
+	np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
+	if (!np) {
+		dev_warn(dev, "OPP-v2 not supported\n");
+		return -ENOENT;
+	}
+
+	reg_table_token = dev_pm_opp_set_regulators(dev, reg_names);
+	if (reg_table_token < 0) {
+		ret = reg_table_token;
+		dev_err_probe(dev, ret, "Failed to set opp regulators\n");
+		goto np_err;
+	}
+
+	ret = dev_pm_opp_of_get_sharing_cpus(dev, &cluster->cpus);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to get sharing cpus\n");
+		goto np_err;
+	}
+
+	rockchip_get_opp_data(rockchip_cpufreq_of_match, opp_info);
+	if (opp_info->data && opp_info->data->set_read_margin) {
+		opp_info->current_rm = UINT_MAX;
+		opp_info->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
+		if (IS_ERR(opp_info->grf))
+			opp_info->grf = NULL;
+		rockchip_get_volt_rm_table(dev, np, "rockchip,volt-mem-read-margin", &opp_info->volt_rm_tbl);
+
+		of_property_read_u32(np, "rockchip,reboot-freq", &opp_info->reboot_freq);
+	}
+
+	opp_table_token = dev_pm_opp_set_config_regulators(dev, rk_opp_config_regulators);
+	if (opp_table_token < 0) {
+		ret = opp_table_token;
+		dev_err(dev, "Failed to set opp config regulators\n");
+		goto reg_opp_table;
+	}
+
+	of_node_put(np);
+
+	return 0;
+
+reg_opp_table:
+	if (reg_table_token >= 0)
+		dev_pm_opp_put_regulators(reg_table_token);
+np_err:
+	of_node_put(np);
+
+	return ret;
+}
+
+static struct notifier_block rockchip_cpufreq_reboot_notifier = {
+	.notifier_call = rockchip_cpufreq_reboot,
+	.priority = 0,
+};
+
+static struct freq_attr *cpufreq_rockchip_attr[] = {
+	&cpufreq_freq_attr_scaling_available_freqs,
+	NULL,
+};
+
+static int cpufreq_online(struct cpufreq_policy *policy)
+{
+	/* We did light-weight tear down earlier, nothing to do here */
+	return 0;
+}
+
+static int cpufreq_offline(struct cpufreq_policy *policy)
+{
+	/*
+	 * Preserve policy->driver_data and don't free resources on light-weight
+	 * tear down.
+	 */
+	return 0;
+}
+
+static struct private_data *rockchip_cpufreq_find_data(int cpu)
+{
+	struct private_data *priv;
+
+	list_for_each_entry(priv, &priv_list, node) {
+		if (cpumask_test_cpu(cpu, priv->cpus))
+			return priv;
+	}
+
+	return NULL;
+}
+
+static int cpufreq_init(struct cpufreq_policy *policy)
+{
+	struct private_data *priv;
+	struct device *cpu_dev;
+	struct clk *cpu_clk;
+	unsigned int transition_latency;
+	int ret;
+
+	priv = rockchip_cpufreq_find_data(policy->cpu);
+	if (!priv) {
+		pr_err("failed to find data for cpu%d\n", policy->cpu);
+		return -ENODEV;
+	}
+	cpu_dev = priv->cpu_dev;
+
+	cpu_clk = clk_get(cpu_dev, NULL);
+	if (IS_ERR(cpu_clk)) {
+		ret = PTR_ERR(cpu_clk);
+		dev_err(cpu_dev, "%s: failed to get clk: %d\n", __func__, ret);
+		return ret;
+	}
+
+	transition_latency = dev_pm_opp_get_max_transition_latency(cpu_dev);
+	if (!transition_latency)
+		transition_latency = CPUFREQ_ETERNAL;
+
+	cpumask_copy(policy->cpus, priv->cpus);
+	policy->driver_data = priv;
+	policy->clk = cpu_clk;
+	policy->freq_table = priv->freq_table;
+	policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(cpu_dev) / 1000;
+	policy->cpuinfo.transition_latency = transition_latency;
+	policy->dvfs_possible_from_any_cpu = true;
+
+	return 0;
+}
+
+static int cpufreq_exit(struct cpufreq_policy *policy)
+{
+	clk_put(policy->clk);
+	return 0;
+}
+
+static int set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+	struct private_data *priv = policy->driver_data;
+	unsigned long freq = policy->freq_table[index].frequency;
+
+	return dev_pm_opp_set_rate(priv->cpu_dev, freq * 1000);
+}
+
+static struct cpufreq_driver rockchip_cpufreq_driver = {
+	.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
+		 CPUFREQ_IS_COOLING_DEV |
+		 CPUFREQ_HAVE_GOVERNOR_PER_POLICY,
+	.verify = cpufreq_generic_frequency_table_verify,
+	.target_index = set_target,
+	.get = cpufreq_generic_get,
+	.init = cpufreq_init,
+	.exit = cpufreq_exit,
+	.online = cpufreq_online,
+	.offline = cpufreq_offline,
+	.register_em = cpufreq_register_em_with_opp,
+	.name = "rockchip-cpufreq",
+	.attr = cpufreq_rockchip_attr,
+	.suspend = cpufreq_generic_suspend,
+};
+
+static int rockchip_cpufreq_init(struct device *dev, int cpu)
+{
+	struct private_data *priv;
+	struct device *cpu_dev;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	if (!alloc_cpumask_var(&priv->cpus, GFP_KERNEL))
+		return -ENOMEM;
+
+	cpumask_set_cpu(cpu, priv->cpus);
+
+	cpu_dev = get_cpu_device(cpu);
+	if (!cpu_dev)
+		return -EPROBE_DEFER;
+	priv->cpu_dev = cpu_dev;
+
+	ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, priv->cpus);
+	if (ret)
+		return ret;
+
+	ret = dev_pm_opp_of_cpumask_add_table(priv->cpus);
+	if (ret)
+		return ret;
+
+	ret = dev_pm_opp_get_opp_count(cpu_dev);
+	if (ret <= 0)
+		return dev_err_probe(cpu_dev, -ENODEV, "OPP table can't be empty\n");
+
+	ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &priv->freq_table);
+	if (ret)
+		return dev_err_probe(cpu_dev, ret, "failed to init cpufreq table\n");
+
+	list_add(&priv->node, &priv_list);
+
+	return 0;
+}
+
+static void rockchip_cpufreq_free_list(void *data)
+{
+	struct cluster_info *cluster, *pos;
+
+	list_for_each_entry_safe(cluster, pos, &cluster_info_list, list_head) {
+		list_del(&cluster->list_head);
+	}
+}
+
+static int rockchip_cpufreq_init_list(struct device *dev)
+{
+	struct cluster_info *cluster;
+	int cpu, ret;
+
+	for_each_possible_cpu(cpu) {
+		cluster = rockchip_cluster_info_lookup(cpu);
+		if (cluster)
+			continue;
+
+		cluster = devm_kzalloc(dev, sizeof(*cluster), GFP_KERNEL);
+		if (!cluster) {
+			ret = -ENOMEM;
+			goto release_cluster_info;
+		}
+
+		ret = rockchip_cpufreq_cluster_init(cpu, cluster);
+		if (ret) {
+			dev_err_probe(dev, ret, "Failed to initialize dvfs info cpu%d\n", cpu);
+			goto release_cluster_info;
+		}
+		list_add(&cluster->list_head, &cluster_info_list);
+	}
+
+	return 0;
+
+release_cluster_info:
+	rockchip_cpufreq_free_list(NULL);
+	return ret;
+}
+
+static void rockchip_cpufreq_unregister(void *data)
+{
+	cpufreq_unregister_driver(&rockchip_cpufreq_driver);
+}
+
+static int rockchip_cpufreq_probe(struct platform_device *pdev)
+{
+	int ret, cpu;
+
+	ret = rockchip_cpufreq_init_list(&pdev->dev);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(&pdev->dev, rockchip_cpufreq_free_list, NULL);
+	if (ret)
+		return ret;
+
+	ret = devm_register_reboot_notifier(&pdev->dev, &rockchip_cpufreq_reboot_notifier);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to register reboot handler\n");
+
+	for_each_possible_cpu(cpu) {
+		ret = rockchip_cpufreq_init(&pdev->dev, cpu);
+		if (ret)
+			return ret;
+	}
+
+	ret = cpufreq_register_driver(&rockchip_cpufreq_driver);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed register driver\n");
+
+	ret = devm_add_action_or_reset(&pdev->dev, rockchip_cpufreq_unregister, NULL);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static struct platform_driver rockchip_cpufreq_platdrv = {
+	.driver = {
+		.name	= "rockchip-cpufreq",
+	},
+	.probe		= rockchip_cpufreq_probe,
+};
+
+static int __init rockchip_cpufreq_driver_init(void)
+{
+	int ret;
+
+	if (!of_machine_is_compatible("rockchip,rk3588") &&
+	    !of_machine_is_compatible("rockchip,rk3588s")) {
+		return -ENODEV;
+	}
+
+	ret = platform_driver_register(&rockchip_cpufreq_platdrv);
+	if (ret)
+		return ret;
+
+	cpufreq_pdev = platform_device_register_data(NULL, "rockchip-cpufreq", -1,
+						     NULL, 0);
+	if (IS_ERR(cpufreq_pdev)) {
+		pr_err("failed to register rockchip-cpufreq platform device\n");
+		ret = PTR_ERR(cpufreq_pdev);
+		goto unregister_platform_driver;
+	}
+
+	return 0;
+
+unregister_platform_driver:
+	platform_driver_unregister(&rockchip_cpufreq_platdrv);
+	return ret;
+}
+module_init(rockchip_cpufreq_driver_init);
+
+static void __exit rockchip_cpufreq_driver_exit(void)
+{
+	platform_device_unregister(cpufreq_pdev);
+	platform_driver_unregister(&rockchip_cpufreq_platdrv);
+}
+module_exit(rockchip_cpufreq_driver_exit)
+
+MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>");
+MODULE_DESCRIPTION("Rockchip cpufreq driver");
+MODULE_LICENSE("GPL v2");