[Feature] add GA346 baseline version

Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/cpufreq/mediatek-mt6880-cpufreq.c b/src/kernel/linux/v4.19/drivers/cpufreq/mediatek-mt6880-cpufreq.c
new file mode 100644
index 0000000..d5f82f2
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/cpufreq/mediatek-mt6880-cpufreq.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mediatek-mt6880-cpufreq.c - MT6880 CPUFreq Driver
+ *
+ * Copyright (c) 2020 MediaTek Inc.
+ * Wei-Chia Su <Wei-Chia.Su@mediatek.com>
+ */
+
+#include <asm-generic/delay.h>
+#include <clk-mtk.h>
+#include <mt6880-clk.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/energy_model.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+enum cpu_level {
+	CPU_LEVEL_0,
+
+	NUM_CPU_LEVEL
+};
+
+enum opp_idx_type {
+	CUR_OPP_IDX = 0,
+	TARGET_OPP_IDX = 1,
+
+	NR_OPP_IDX,
+};
+
+enum mt_cpu_dvfs_id {
+        MT_CPU_DVFS_LL,
+        MT_CPU_DVFS_CCI,
+
+        NR_MT_CPU_DVFS,
+};
+
+#define FHCTL (1)
+#define mt_reg_sync_writel(v, a) \
+        do {    \
+                __raw_writel((v), (void __force __iomem *)((a)));   \
+                mb();  \
+        } while (0)
+#define _BIT_(_bit_)	(unsigned)(1 << (_bit_))
+#define _BITS_(_bits_, _val_)	((((unsigned) -1 >> (31 - ((1) ? _bits_))) & ~((1U << ((0) ? _bits_)) - 1)) & ((_val_)<<((0) ? _bits_)))
+#define _BITMASK_(_bits_)	(((unsigned) -1 >> (31 - ((1) ? _bits_))) & ~((1U << ((0) ? _bits_)) - 1))
+#define _GET_BITS_VAL_(_bits_, _val_)	(((_val_) & (_BITMASK_(_bits_))) >> ((0) ? _bits_))
+#define IOMEM(a) ((void __force __iomem *)((a)))
+#define cpufreq_read(addr) __raw_readl(IOMEM(addr))
+#define cpufreq_write(addr, val) mt_reg_sync_writel((val), ((void *)addr))
+#define cpufreq_write_mask(addr, mask, val) cpufreq_write(addr, (cpufreq_read(addr) & ~(_BITMASK_(mask))) | _BITS_(mask, val))
+#define PLL_SETTLE_TIME	20
+#define POS_SETTLE_TIME	1
+#define APMIXED_NODE "mediatek,apmixed"
+#define MCUCFG_NODE "mediatek,mcucfg"
+#define ARMPLL_LL_CON2 (apmixed_base+ 0x20c)
+#define CCIPLL_CON2 (apmixed_base+ 0x220)
+#define CKDIV1_LL_CFG (mcucfg_base + 0xa2a0)
+#define CKDIV1_CCI_CFG (mcucfg_base + 0xa2e0)
+#define cpu_dvfs_is(p, id) (p == &cpu_dvfs[id])
+#define for_each_cpu_dvfs(i, p) for (i = 0, p = cpu_dvfs; i < NR_MT_CPU_DVFS; i++, p = &cpu_dvfs[i])
+#define for_each_cpu_dvfs_only(i, p) \
+        for (i = 0, p = cpu_dvfs; (i < NR_MT_CPU_DVFS) && (i != MT_CPU_DVFS_CCI); i++, p = &cpu_dvfs[i])
+#define FP(pos, clk) { \
+        .pos_div = pos, \
+        .clk_div = clk, \
+}
+
+struct mt_cpu_freq_info {
+	/*const*/ unsigned int cpufreq_khz;
+	unsigned int cpufreq_volt;
+};
+
+struct mt_cpu_dvfs {
+	const char *name;
+	const enum mt_cpu_dvfs_id id;
+	unsigned int *armpll_addr;
+	unsigned int *ckdiv_addr;
+	struct mt_cpu_freq_method *freq_tbl;
+};
+
+struct cpufreq_frequency_table *cci_freq_table;
+struct clk *cci_clk;
+struct mtk_cpu_dvfs_info {
+	struct cpumask cpus;
+	struct device *cpu_dev;
+	struct regulator *proc_reg;
+	struct clk *cpu_clk;
+	struct list_head list_head;
+};
+
+static LIST_HEAD(dvfs_info_list);
+
+static struct mt_cpu_dvfs cpu_dvfs[] = {
+	[MT_CPU_DVFS_LL] = {
+		.name = __stringify(MT_CPU_DVFS_LL),
+		.id = MT_CPU_DVFS_LL,
+	},
+	[MT_CPU_DVFS_CCI] = {
+		.name = __stringify(MT_CPU_DVFS_CCI),
+		.id = MT_CPU_DVFS_CCI,
+	},
+};
+
+static unsigned long apmixed_base;
+static unsigned long mcucfg_base;
+
+static struct mt_cpu_dvfs *id_to_cpu_dvfs(enum mt_cpu_dvfs_id id)
+{
+	return (id < NR_MT_CPU_DVFS) ? &cpu_dvfs[id] : NULL;
+}
+
+struct mt_cpu_freq_method {
+	const unsigned int pos_div;
+	const unsigned int clk_div;
+};
+
+struct opp_idx_tbl {
+	struct mt_cpu_dvfs *p;
+	struct mt_cpu_freq_method *slot;
+};
+
+static struct opp_idx_tbl opp_tbl_m[NR_OPP_IDX];
+static struct mt_cpu_freq_method opp_tbl_method_LL_FY[] = {
+	FP(4,   1),
+	FP(4,   1),
+	FP(4,   1),
+	FP(2,   1),
+	FP(2,   1),
+};
+
+static struct mt_cpu_freq_method opp_tbl_method_CCI_FY[] = {
+	FP(4,   1),
+	FP(4,   1),
+	FP(4,   1),
+	FP(4,   1),
+	FP(2,   1),
+};
+
+struct opp_tbl_info {
+	struct mt_cpu_freq_info *const opp_tbl;
+	const int size;
+};
+
+struct opp_tbl_m_info {
+	struct mt_cpu_freq_method *const opp_tbl_m;
+};
+
+static struct opp_tbl_m_info opp_tbls_m[NR_MT_CPU_DVFS][NUM_CPU_LEVEL] = {
+	{
+		[CPU_LEVEL_0] = { opp_tbl_method_LL_FY },
+	},
+	{
+		[CPU_LEVEL_0] = { opp_tbl_method_CCI_FY },
+	},
+};
+
+static unsigned int _cpu_dds_calc(unsigned int khz)
+{
+	unsigned int dds;
+
+	dds = ((khz / 1000) << 14) / 26;
+
+	return dds;
+}
+
+#if !FHCTL
+static void adjust_armpll_dds(struct mt_cpu_dvfs *p, unsigned int vco, unsigned int pos_div)
+{
+	unsigned int dds;
+	unsigned int val;
+
+	dds = _GET_BITS_VAL_(21:0, vco);
+	val = cpufreq_read(p->armpll_addr) & ~(_BITMASK_(21:0));
+	val |= dds;
+	cpufreq_write(p->armpll_addr, val | _BIT_(31) /* CHG */);
+	udelay(PLL_SETTLE_TIME);
+}
+#endif
+
+static void adjust_posdiv(struct mt_cpu_dvfs *p, unsigned int pos_div)
+{
+	unsigned int sel;
+
+	sel = (pos_div == 1 ? 0 :
+	       pos_div == 2 ? 1 :
+	       pos_div == 4 ? 2 : 0);
+	cpufreq_write_mask(p->armpll_addr, 26:24, sel);
+	udelay(POS_SETTLE_TIME);
+}
+
+static void adjust_clkdiv(struct mt_cpu_dvfs *p, unsigned int clk_div)
+{
+	unsigned int sel;
+
+	sel = (clk_div == 1 ? 8 :
+	       clk_div == 2 ? 10 :
+	       clk_div == 4 ? 11 : 8);
+	cpufreq_write_mask(p->ckdiv_addr, 21:17, sel);
+}
+
+static void set_cur_freq(struct mt_cpu_dvfs *p, unsigned int target_khz, int idx)
+{
+	unsigned int sel, cur_posdiv, cur_clkdiv, dds;
+
+	sel = _GET_BITS_VAL_(26:24, cpufreq_read(p->armpll_addr));
+	cur_posdiv = (sel == 0 ? 1 :
+		      sel == 1 ? 2 :
+		      sel == 2 ? 4 : 1);
+
+	sel = _GET_BITS_VAL_(21:17, cpufreq_read(p->ckdiv_addr));
+	cur_clkdiv = (sel == 8 ? 1 :
+		      sel == 10 ? 2 :
+		      sel == 11 ? 4 : 1);
+
+	opp_tbl_m[TARGET_OPP_IDX].p = p;
+	opp_tbl_m[TARGET_OPP_IDX].slot = &p->freq_tbl[idx];
+
+	/* post_div 1 -> 2 */
+	if (cur_posdiv < opp_tbl_m[TARGET_OPP_IDX].slot->pos_div)
+		adjust_posdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
+
+	/* armpll_div 1 -> 2 */
+	if (cur_clkdiv < opp_tbl_m[TARGET_OPP_IDX].slot->clk_div)
+		adjust_clkdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
+
+	dds = _cpu_dds_calc(target_khz *
+	      opp_tbl_m[TARGET_OPP_IDX].slot->pos_div * opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
+
+#if !FHCTL
+	adjust_armpll_dds(p, dds, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
+#else
+	if (cpu_dvfs_is(p, MT_CPU_DVFS_CCI))
+		mtk_fh_set_rate(CLK_TOP_CCIPLL_CK_VRPOC_CCI, dds,-1);
+	else if (cpu_dvfs_is(p, MT_CPU_DVFS_LL))
+		mtk_fh_set_rate(CLK_TOP_ARMPLL_LL_CK_VRPOC, dds,-1);
+#endif
+
+	/* armpll_div 2 -> 1 */
+	if (cur_clkdiv > opp_tbl_m[TARGET_OPP_IDX].slot->clk_div)
+		adjust_clkdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->clk_div);
+
+	/* post_div 2 -> 1 */
+	if (cur_posdiv > opp_tbl_m[TARGET_OPP_IDX].slot->pos_div)
+		adjust_posdiv(p, opp_tbl_m[TARGET_OPP_IDX].slot->pos_div);
+}
+
+static struct mtk_cpu_dvfs_info *mtk_cpu_dvfs_info_lookup(int cpu)
+{
+	struct mtk_cpu_dvfs_info *info;
+
+	list_for_each_entry(info, &dvfs_info_list, list_head) {
+		if (cpumask_test_cpu(cpu, &info->cpus))
+			return info;
+	}
+
+	return NULL;
+}
+
+static int mtk_cpufreq_set_voltage(struct mtk_cpu_dvfs_info *info, int vproc)
+{
+	return regulator_set_voltage(info->proc_reg, vproc, INT_MAX);
+}
+
+#if 0
+static unsigned int pll_to_clk(unsigned int pll_f, unsigned int ckdiv1)
+{
+	unsigned int freq = pll_f;
+
+	switch (ckdiv1) {
+	case 8:
+		break;
+	case 9:
+	
+	freq = freq * 3 / 4;
+		break;
+	case 10:
+		freq = freq * 2 / 4;
+		break;
+	case 11:
+		freq = freq * 1 / 4;
+		break;
+	case 16:
+		break;
+	case 17:
+		freq = freq * 4 / 5;
+		break;
+	case 18:
+		freq = freq * 3 / 5;
+		break;
+	case 19:
+		freq = freq * 2 / 5;
+		break;
+	case 20:
+		freq = freq * 1 / 5;
+		break;
+	case 24:
+		break;
+	case 25:
+		freq = freq * 5 / 6;
+		break;
+	case 26:
+		freq = freq * 4 / 6;
+		break;
+	case 27:
+		freq = freq * 3 / 6;
+		break;
+	case 28:
+		freq = freq * 2 / 6;
+		break;
+	case 29:
+		freq = freq * 1 / 6;
+		break;
+	default:
+		break;
+	}
+
+	return freq;
+}
+
+static unsigned int _cpu_freq_calc(unsigned int con1, unsigned int ckdiv1)
+{
+	unsigned int freq;
+	unsigned int posdiv;
+
+	posdiv = _GET_BITS_VAL_(26:24, con1);
+
+	con1 &= _BITMASK_(21:0);
+	freq = ((con1 * 26) >> 14) * 1000;
+
+	switch (posdiv) {
+	case 0:
+		break;
+	case 1:
+		freq = freq / 2;
+		break;
+	case 2:
+		freq = freq / 4;
+		break;
+	case 3:
+		freq = freq / 8;
+		break;
+	default:
+		freq = freq / 16;
+		break;
+	};
+
+	return pll_to_clk(freq, ckdiv1);
+}
+
+static unsigned int get_cur_phy_freq(struct mt_cpu_dvfs *p)
+{
+	unsigned int con1;
+	unsigned int ckdiv1;
+	unsigned int cur_khz;
+
+	con1 = cpufreq_read(p->armpll_addr);
+	ckdiv1 = cpufreq_read(p->ckdiv_addr);
+	ckdiv1 = _GET_BITS_VAL_(21:17, ckdiv1);
+
+	cur_khz = _cpu_freq_calc(con1, ckdiv1);
+
+	return cur_khz;
+}
+#endif
+
+static int mtk_cpufreq_set_target(struct cpufreq_policy *policy,
+				  unsigned int index)
+{
+	struct cpufreq_frequency_table *freq_table = policy->freq_table;
+	struct mtk_cpu_dvfs_info *info = policy->driver_data;
+	struct device *cpu_dev = info->cpu_dev;
+	struct dev_pm_opp *opp, *old_opp;
+	long freq_hz, old_freq_hz, freq_hz_cci;
+	int vproc, old_vproc, target_vproc, ret;
+
+	freq_hz_cci = cci_freq_table[index].frequency * 1000;
+	freq_hz = freq_table[index].frequency * 1000;
+
+	old_freq_hz = policy->cur * 1000;
+	old_opp = dev_pm_opp_find_freq_ceil(cpu_dev, &old_freq_hz);
+	if (IS_ERR(old_opp))
+		return PTR_ERR(old_opp);
+	old_vproc = dev_pm_opp_get_voltage(old_opp);
+	dev_pm_opp_put(old_opp);
+
+	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
+	if (IS_ERR(opp))
+		return PTR_ERR(opp);
+	vproc = dev_pm_opp_get_voltage(opp);
+	dev_pm_opp_put(opp);
+
+	target_vproc = vproc;
+	if (old_vproc < target_vproc) {
+		ret = mtk_cpufreq_set_voltage(info, target_vproc);
+		if (ret) {
+			mtk_cpufreq_set_voltage(info, old_vproc);
+			return ret;
+		}
+	}
+
+	set_cur_freq(id_to_cpu_dvfs(MT_CPU_DVFS_CCI), freq_hz_cci/1000, index);
+	set_cur_freq(id_to_cpu_dvfs(MT_CPU_DVFS_LL), freq_hz/1000, index);
+
+	if (vproc < old_vproc) {
+		ret = mtk_cpufreq_set_voltage(info, vproc);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int mtk_cpu_dvfs_info_init(struct mtk_cpu_dvfs_info *info, int cpu)
+{
+	struct device *cpu_dev;
+	struct regulator *proc_reg = ERR_PTR(-ENODEV);
+	struct clk *cpu_clk = ERR_PTR(-ENODEV);
+	struct platform_device *npdev;
+	struct device_node *node;
+	int ret;
+
+	cpu_dev = get_cpu_device(cpu);
+	if (!cpu_dev)
+		return -ENODEV;
+
+	cpu_clk = clk_get(cpu_dev, "cpu");
+	if (IS_ERR(cpu_clk))
+		return PTR_ERR(cpu_clk);
+
+	proc_reg = regulator_get_optional(cpu_dev, "proc");
+	if (IS_ERR(proc_reg)) {
+		ret = PTR_ERR(proc_reg);
+		goto out_free_resources;
+	}
+
+	node = of_find_node_by_name(NULL, "cci");
+	if (!node) {
+		ret = -ENODEV;
+		goto out_free_resources;
+	}
+
+	npdev = of_device_alloc(node, NULL, NULL);
+	if (!npdev) {
+		ret = -ENODEV;
+		goto out_free_resources;
+	}
+
+	if (of_device_is_compatible(node, "mediatek,mt6880-cci"))
+		npdev->dev.of_node = node;
+	else {
+		ret = -ENODEV;
+		goto out_free_resources;
+	}
+
+	ret = dev_pm_opp_of_add_table(&npdev->dev);
+	if (ret) {
+		ret = -ENODEV;
+		goto out_free_resources;
+	}
+
+        ret = dev_pm_opp_init_cpufreq_table(&npdev->dev, &cci_freq_table);
+	if (ret) {
+		ret = -ENODEV;
+		goto out_free_resources;
+	}
+
+	ret = dev_pm_opp_of_get_sharing_cpus(cpu_dev, &info->cpus);
+	if (ret) {
+		ret = -ENODEV;
+		goto out_free_cpufreq_table;
+	}
+
+	ret = dev_pm_opp_of_cpumask_add_table(&info->cpus);
+	if (ret) {
+		ret = -ENODEV;
+		goto out_free_cpufreq_table;
+	}
+
+	info->cpu_dev = cpu_dev;
+	info->proc_reg = proc_reg;
+	info->cpu_clk = cpu_clk;
+
+	if (npdev) {
+		of_platform_device_destroy(&npdev->dev, NULL);
+		of_dev_put(npdev);
+	}
+
+	return 0;
+
+out_free_cpufreq_table:
+	dev_pm_opp_free_cpufreq_table(cpu_dev, &cci_freq_table);
+out_free_resources:
+	if (npdev) {
+		of_platform_device_destroy(&npdev->dev, NULL);
+		of_dev_put(npdev);
+	}
+	if (!IS_ERR(proc_reg))
+		regulator_put(proc_reg);
+	if (!IS_ERR(cpu_clk))
+		clk_put(cpu_clk);
+
+	return ret;
+}
+
+static void mtk_cpu_dvfs_info_release(struct mtk_cpu_dvfs_info *info)
+{
+	if (!IS_ERR(info->proc_reg))
+		regulator_put(info->proc_reg);
+	if (!IS_ERR(info->cpu_clk))
+		clk_put(info->cpu_clk);
+	dev_pm_opp_of_cpumask_remove_table(&info->cpus);
+}
+
+static int mtk_cpufreq_init(struct cpufreq_policy *policy)
+{
+	struct mtk_cpu_dvfs_info *info;
+	struct cpufreq_frequency_table *freq_table;
+	struct em_data_callback em_cb = EM_DATA_CB(of_dev_pm_opp_get_cpu_power);
+	int ret;
+
+	info = mtk_cpu_dvfs_info_lookup(policy->cpu);
+	if (!info)
+		return -EINVAL;
+
+	ret = dev_pm_opp_get_opp_count(info->cpu_dev);
+	if (ret <= 0)
+		return -EINVAL;
+
+	ret = dev_pm_opp_init_cpufreq_table(info->cpu_dev, &freq_table);
+        if (ret)
+                return -EINVAL;
+
+	cpumask_copy(policy->cpus, &info->cpus);
+	em_register_perf_domain(policy->cpus, ret, &em_cb);
+	policy->freq_table = freq_table;
+	policy->driver_data = info;
+	policy->clk = info->cpu_clk;
+	return 0;
+}
+
+static int mtk_cpufreq_exit(struct cpufreq_policy *policy)
+{
+	struct mtk_cpu_dvfs_info *info = policy->driver_data;
+
+	dev_pm_opp_free_cpufreq_table(info->cpu_dev, &policy->freq_table);
+
+	return 0;
+}
+
+static struct cpufreq_driver mtk_cpufreq_driver = {
+	.flags = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK |
+		 CPUFREQ_HAVE_GOVERNOR_PER_POLICY | CPUFREQ_IS_COOLING_DEV,
+	.verify = cpufreq_generic_frequency_table_verify,
+	.target_index = mtk_cpufreq_set_target,
+	.get = cpufreq_generic_get,
+	.init = mtk_cpufreq_init,
+	.exit = mtk_cpufreq_exit,
+	.name = "mtk-cpufreq",
+	.attr = cpufreq_generic_attr,
+};
+
+static int mtk_cpufreq_probe(struct platform_device *pdev)
+{
+	struct mtk_cpu_dvfs_info *info, *tmp;
+	struct mt_cpu_dvfs *p;
+	int cpu, ret, j;
+	struct opp_tbl_m_info *opp_tbl_info;
+	struct device_node *node;
+
+	node = of_find_compatible_node(NULL, NULL, APMIXED_NODE);
+	if (!node)
+		return -ENODEV;
+
+	apmixed_base = (unsigned long)of_iomap(node, 0);
+	if (!apmixed_base)
+		return -ENODEV;
+
+	node = of_find_compatible_node(NULL, NULL, MCUCFG_NODE);
+	if (!node)
+		return -ENODEV;
+
+	mcucfg_base = (unsigned long)of_iomap(node, 0);
+	if (!mcucfg_base)
+		return -ENODEV;
+
+	for_each_cpu_dvfs(j, p) {
+		if (cpu_dvfs_is(p, MT_CPU_DVFS_LL)) {
+			p->armpll_addr = (unsigned int *)ARMPLL_LL_CON2;
+			p->ckdiv_addr = (unsigned int *)CKDIV1_LL_CFG;
+		} else {	/* CCI */
+			p->armpll_addr = (unsigned int *)CCIPLL_CON2;
+			p->ckdiv_addr = (unsigned int *)CKDIV1_CCI_CFG;
+		}
+		opp_tbl_info = &opp_tbls_m[j][0];
+		p->freq_tbl = opp_tbl_info->opp_tbl_m;
+	}
+
+	for_each_possible_cpu(cpu) {
+		info = mtk_cpu_dvfs_info_lookup(cpu);
+		if (info)
+			continue;
+
+		info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+		if (!info) {
+			ret = -ENOMEM;
+			goto release_dvfs_info_list;
+		}
+
+		ret = mtk_cpu_dvfs_info_init(info, cpu);
+		if (ret)
+			goto release_dvfs_info_list;
+
+		list_add(&info->list_head, &dvfs_info_list);
+	}
+
+	ret = cpufreq_register_driver(&mtk_cpufreq_driver);
+	if (ret)
+		goto release_dvfs_info_list;
+
+	return 0;
+
+release_dvfs_info_list:
+	list_for_each_entry_safe(info, tmp, &dvfs_info_list, list_head) {
+		mtk_cpu_dvfs_info_release(info);
+		list_del(&info->list_head);
+	}
+
+	return ret;
+}
+
+static struct platform_driver mtk_cpufreq_platdrv = {
+	.driver = {
+		.name	= "mtk-cpufreq",
+	},
+	.probe		= mtk_cpufreq_probe,
+};
+
+static const struct of_device_id mtk_cpufreq_machines[] __initconst = {
+	{ .compatible = "mediatek,mt6880", },
+	{ }
+};
+
+static int __init mtk_cpufreq_driver_init(void)
+{
+	struct device_node *np;
+	const struct of_device_id *match;
+	struct platform_device *pdev;
+	int err;
+
+	np = of_find_node_by_path("/");
+	if (!np)
+		return -ENODEV;
+
+	match = of_match_node(mtk_cpufreq_machines, np);
+	of_node_put(np);
+	if (!match)
+		return -ENODEV;
+
+	err = platform_driver_register(&mtk_cpufreq_platdrv);
+	if (err)
+		return err;
+
+	pdev = platform_device_register_simple("mtk-cpufreq", -1, NULL, 0);
+	if (IS_ERR(pdev))
+		return PTR_ERR(pdev);
+
+	return 0;
+}
+device_initcall(mtk_cpufreq_driver_init);
+
+MODULE_DESCRIPTION("MediaTek CPUFreq driver");
+MODULE_AUTHOR("Wei-Chia Su <wei-chia.su@mediatek.com>");
+MODULE_LICENSE("GPL v2");