[Feature] add GA346 baseline version

Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl.c b/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl.c
new file mode 100644
index 0000000..d1bdfa3
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/clk/mediatek/clk-fhctl.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ * Author: Pierre Lee <pierre.lee@mediatek.com>
+ */
+
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include "clk-fhctl.h"
+#include "clk-fhctl-debug.h"
+#include "clk-mtk.h"
+
+
+/************************************************
+ **********      register base addr    **********
+ ************************************************/
+#define REG_ADDR(base, x) (void __iomem *)((unsigned long)base + (x))
+
+/************************************************
+ **********         Variable           **********
+ ************************************************/
+
+/* spinlock for fhctl */
+static DEFINE_SPINLOCK(fhctl_lock);
+static LIST_HEAD(clk_mt_fhctl_list);
+
+static struct mtk_fhctl *g_p_fhctl;
+
+/*****************************************************************
+ * Global variable operation
+ ****************************************************************/
+static void __set_fhctl(struct mtk_fhctl *pfhctl)
+{
+	g_p_fhctl = pfhctl;
+}
+
+static struct mtk_fhctl *__get_fhctl(void)
+{
+	return g_p_fhctl;
+}
+
+
+/*****************************************************************
+ * OF Info init
+ ****************************************************************/
+static int mtk_fhctl_parse_dt(struct mtk_fhctl *fhctl)
+{
+
+	unsigned int pll_num;
+	struct device *dev;
+	struct device_node *child;
+	struct device_node *node;
+
+	pll_num = fhctl->pll_num;
+	dev = fhctl->dev;
+	node = dev->of_node;
+
+	for_each_child_of_node(node, child) {
+		struct clk_mt_fhctl_pll_data *pll_data;
+		unsigned int id, pll_id, ssc;
+		int err, tbl_size;
+		bool ret;
+
+		/* search for fhctl id */
+		err = of_property_read_u32(child, "mediatek,fh-id", &id);
+		if (err) {
+			dev_info(dev, "miss fh-id property: %s", child->name);
+			return err;
+		}
+
+		if (id >= pll_num) {
+			dev_info(dev, "invalid %s fh-id:%d", child->name, id);
+			return -EINVAL;
+		}
+
+		pll_data = fhctl->fh_tbl[id]->pll_data;
+
+		/* Search for pll type */
+		pll_data->pll_type = FH_PLL_TYPE_FORCE;
+
+		/* Search for freqhopping table */
+		tbl_size = of_property_count_u32_elems(child,
+						"mediatek,fh-tbl");
+		if (tbl_size > 0) {
+			pll_data->hp_tbl_size = tbl_size;
+			pll_data->hp_tbl = devm_kzalloc(dev,
+					sizeof(u32)*tbl_size,
+					GFP_KERNEL);
+
+			if (!pll_data->hp_tbl)
+				return -ENOMEM;
+
+			err = of_property_read_u32_array(child,
+							"mediatek,fh-tbl",
+							pll_data->hp_tbl,
+							tbl_size);
+			if (err) {
+				dev_info(dev, "invalid fh-tbl property of %s",
+								child->name);
+				return err;
+			}
+
+			/* Parse successfully. Set pll type */
+			pll_data->pll_type = FH_PLL_TYPE_GENERAL;
+		}
+
+		/* Search for cpu pll type property */
+		ret = of_property_read_bool(child, "mediatek,fh-cpu-pll");
+		if (ret)
+			pll_data->pll_type = FH_PLL_TYPE_CPU;
+
+		/* Search for fh-pll-id */
+		err = of_property_read_u32(child, "mediatek,fh-pll-id",
+								&pll_id);
+		if (!err)
+			fhctl->idmap[id] = pll_id;
+
+		/* Search for default ssc rate */
+		err = of_property_read_u32(child, "mediatek,fh-ssc-rate",
+								&ssc);
+		if (!err)
+			pll_data->pll_default_ssc_rate = ssc;
+	}
+
+	return 0;
+}
+
+
+static int __add_fh_obj_tbl(struct mtk_fhctl *pfhctl, int posi,
+			struct clk_mt_fhctl *pfh)
+{
+	if (pfhctl == NULL) {
+		pr_info("Error: null pointer pfhctl");
+		return -EFAULT;
+	}
+
+	if (posi >= pfhctl->pll_num)
+		return -EINVAL;
+
+	pfhctl->fh_tbl[posi] = pfh;
+	return 0;
+}
+
+struct clk_mt_fhctl *mtk_fh_get_fh_obj_tbl(struct mtk_fhctl *pfhctl, int posi)
+{
+	int size;
+	struct clk_mt_fhctl *pfh;
+
+	if (pfhctl == NULL) {
+		pr_info("Error: null pointer pfhctl");
+		return ERR_PTR(-EFAULT);
+	}
+
+	size = pfhctl->pll_num;
+
+	if (posi >= size) {
+		dev_info(pfhctl->dev, "Error: size:%d posi:%d", size, posi);
+		return ERR_PTR(-EINVAL);
+	}
+
+	pfh = pfhctl->fh_tbl[posi];
+
+	dev_dbg(pfhctl->dev, "get fh:0x%p pll_id:%d", pfh, posi);
+
+	return pfh;
+}
+EXPORT_SYMBOL(mtk_fh_get_fh_obj_tbl);
+
+/*********************************************************
+ * For clock driver control
+ ********************************************************/
+bool _mtk_fh_set_rate(int pll_id, unsigned long dds, int postdiv)
+{
+	struct mtk_fhctl *fhctl;
+	struct clk_mt_fhctl *fh;
+	int fhctl_pll_id;
+
+	int i, tbl_size, ret;
+
+	pr_debug("check pll_id:0x%x dds:0x%lx", pll_id, dds);
+
+	fhctl = __get_fhctl();
+	if (fhctl == NULL) {
+		pr_info("ERROR: fhctl is not initialized");
+		return false;
+	}
+
+	/* Lookup table */
+	if (unlikely(pll_id < 0))
+		return false;
+
+	fhctl_pll_id = -1;
+	for (i = 0; i < fhctl->pll_num; i++)
+		if (fhctl->idmap[i] == pll_id) {
+			fhctl_pll_id = i;
+			break;
+		}
+
+	if (fhctl_pll_id == -1) {
+		pr_debug("pll not supportted by fhctl");
+		return false;
+	}
+
+	pr_debug("found fhctl_pll_id:%d", fhctl_pll_id);
+
+	fh = mtk_fh_get_fh_obj_tbl(fhctl, fhctl_pll_id);
+
+	if (IS_ERR_OR_NULL(fh))
+		return false;
+
+	if (fh->pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) {
+		pr_info("ERROR: pll not support");
+		return false;
+	}
+
+	if (fh->pll_data->pll_type == FH_PLL_TYPE_CPU) {
+		pr_info("ERROR: CPU hopping not support in AP side");
+		return false;
+	}
+
+	if (fh->pll_data->pll_type == FH_PLL_TYPE_FORCE) {
+		/* Force hopping by FHCTL. */
+		ret = fh->hal_ops->pll_hopping(fh, dds, postdiv);
+		return (ret == 0);
+	}
+
+	/* Look up hopping support table */
+	if (fh->pll_data->hp_tbl == NULL) {
+		pr_info("ERROR: fh->pll_data->hp_tbl NULL!");
+		return false;
+	}
+
+	tbl_size = fh->pll_data->hp_tbl_size;
+	for (i = 0; i < tbl_size; i++) {
+		if (fh->pll_data->hp_tbl[i] == dds) {
+			pr_debug("%s dds:0x%lx by fhctl hopping",
+				fh->pll_data->pll_name, dds);
+			ret = fh->hal_ops->pll_hopping(fh, dds, postdiv);
+			return (ret == 0);
+		}
+	}
+
+	return false;
+}
+
+/****************************************************
+ * CLK FHCTL reg init
+ ***************************************************/
+
+static struct clk_mt_fhctl_regs *__mt_fhctl_fh_regs_init(
+				struct mtk_fhctl *fhctl, unsigned int pll_id)
+{
+	struct clk_mt_fhctl_regs *fh_regs;
+	void *fhctl_base = fhctl->fhctl_base;
+	void *apmixed_base = fhctl->apmixed_base;
+	unsigned int reg_cfg_offs = fhctl->dev_comp->pll_regs[pll_id];
+	unsigned int reg_con0_offs = fhctl->dev_comp->pll_con0_regs[pll_id];
+	unsigned int reg_pcw_offs = fhctl->dev_comp->pll_apmix_pcw_offs;
+
+	fh_regs = devm_kmalloc(fhctl->dev, sizeof(struct clk_mt_fhctl_regs),
+			GFP_KERNEL);
+	if (!fh_regs)
+		return ERR_PTR(-ENOMEM);
+
+	/* fhctl common regs */
+	fh_regs->reg_unitslope_en = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_UNITSLOPE_EN]);
+	fh_regs->reg_hp_en = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_HP_EN]);
+	fh_regs->reg_clk_con = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_CLK_CON]);
+	fh_regs->reg_rst_con = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_RST_CON]);
+	fh_regs->reg_slope0 = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_SLOPE0]);
+	fh_regs->reg_slope1 = REG_ADDR(fhctl_base,
+		fhctl->dev_comp->common_regs[OFFSET_SLOPE1]);
+
+	/* fhctl PLL specific regs */
+	fh_regs->reg_cfg = REG_ADDR(fhctl_base, reg_cfg_offs);
+	fh_regs->reg_updnlmt = REG_ADDR(fhctl_base, reg_cfg_offs + 0x04);
+	fh_regs->reg_dds = REG_ADDR(fhctl_base, reg_cfg_offs + 0x08);
+	fh_regs->reg_dvfs = REG_ADDR(fhctl_base, reg_cfg_offs + 0xC);
+	fh_regs->reg_mon = REG_ADDR(fhctl_base, reg_cfg_offs + 0x10);
+
+	fh_regs->reg_con0 = REG_ADDR(apmixed_base, reg_con0_offs);
+	fh_regs->reg_con_pcw = REG_ADDR(apmixed_base, reg_con0_offs + reg_pcw_offs);
+
+	return fh_regs;
+}
+
+
+static struct clk_mt_fhctl *clk_register_fhctl_pll(
+			struct device *dev,
+			const struct clk_mt_fhctl_hal_ops *hal_ops,
+			struct clk_mt_fhctl_pll_data *pll_data,
+			struct clk_mt_fhctl_regs *fh_regs)
+{
+	struct clk_mt_fhctl *fh;
+
+	fh = devm_kmalloc(dev, sizeof(struct clk_mt_fhctl), GFP_KERNEL);
+	if (!fh)
+		return ERR_PTR(-ENOMEM);
+
+	fh->pll_data = pll_data;
+	fh->fh_regs = fh_regs;
+	fh->hal_ops = hal_ops;
+	fh->lock = &fhctl_lock;
+
+	return fh;
+}
+
+
+static int mt_fh_plt_drv_probe(struct platform_device *pdev)
+{
+	int i, err, pll_num;
+	int dds_mask_size;
+	struct mtk_fhctl *fhctl;
+	struct resource *res;
+	struct device_node *apmixed_node;
+
+	dev_info(&pdev->dev, "FHCTL driver probe start");
+
+	fhctl = devm_kmalloc(&pdev->dev, sizeof(*fhctl), GFP_KERNEL);
+	if (!fhctl)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	fhctl->fhctl_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(fhctl->fhctl_base))
+		return PTR_ERR(fhctl->fhctl_base);
+
+	/* Init APMIXED base address */
+	apmixed_node = of_parse_phandle(pdev->dev.of_node,
+					"mediatek,apmixed", 0);
+	if (!apmixed_node) {
+		dev_info(&pdev->dev, "fhctl: missing mediatek,apmixed node");
+		return -ENODEV;
+	}
+	fhctl->apmixed_base = of_iomap(apmixed_node, 0);
+
+
+	fhctl->dev = &pdev->dev;
+	fhctl->dev_comp = of_device_get_match_data(&pdev->dev);
+	fhctl->pll_num = fhctl->dev_comp->pll_num;
+	dds_mask_size = fhctl->dev_comp->pll_dds_reg_field_size;
+
+	pll_num = fhctl->pll_num;
+
+	fhctl->idmap = devm_kmalloc(&pdev->dev,
+					sizeof(int)*pll_num, GFP_KERNEL);
+	if (!fhctl->idmap)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, fhctl);
+
+	fhctl->fh_tbl = devm_kmalloc(&pdev->dev,
+		sizeof(struct clk_mt_fhctl *) * pll_num, GFP_KERNEL);
+	if (!fhctl->fh_tbl)
+		return -ENOMEM;
+
+	/* register all fhctl pll */
+	for (i = 0; i < pll_num; i++) {
+		struct clk_mt_fhctl *fh;
+		struct clk_mt_fhctl_pll_data *pll_data;
+		struct clk_mt_fhctl_regs *fh_regs;
+
+		pll_data = devm_kmalloc(&pdev->dev,
+			sizeof(struct clk_mt_fhctl_pll_data), GFP_KERNEL);
+		if (!pll_data)
+			return -ENOMEM;
+
+		fhctl->idmap[i] = -1;
+
+		/* Set pll data */
+		pll_data->pll_id = i;
+		pll_data->pll_name = fhctl->dev_comp->pll_names[i];
+		pll_data->pll_type = FH_PLL_TYPE_NOT_SUPPORT;
+		pll_data->dds_mask = GENMASK(dds_mask_size-1, 0);
+		pll_data->pll_default_ssc_rate = 0;
+		pll_data->slope0_value =
+			fhctl->dev_comp->pll_slope0_reg_setting;
+		pll_data->slope1_value =
+			fhctl->dev_comp->pll_slope1_reg_setting;
+		pll_data->hp_tbl = NULL;
+		pll_data->hp_tbl_size = 0;
+
+		/* Init fhctl PLL regs */
+		fh_regs = __mt_fhctl_fh_regs_init(fhctl, i);
+		if (IS_ERR_OR_NULL(fh_regs)) {
+			dev_info(&pdev->dev, "ERROR: init fh_regs fail.");
+			return PTR_ERR(fh_regs);
+		}
+
+		fh = clk_register_fhctl_pll(&pdev->dev, &mt_fhctl_hal_ops,
+				pll_data, fh_regs);
+		if (IS_ERR(fh)) {
+			dev_info(&pdev->dev,
+				"register clk fhctl failed: %s",
+				pll_data->pll_name);
+			return PTR_ERR(fh);
+		}
+
+		list_add(&fh->node, &clk_mt_fhctl_list);
+
+		/* Add fh object to table */
+		err = __add_fh_obj_tbl(fhctl, i, fh);
+		if (err)
+			dev_info(&pdev->dev,
+				"add fh object %d to table failed", i);
+
+	}
+
+	fhctl->ipi_ops_p = &ipi_ops;
+	err = fhctl->ipi_ops_p->ipi_init();
+	if (err){
+		dev_info(&pdev->dev, "ERROR fhctl->mt_fh_hal_init() fail");
+		return err;
+	}
+
+	/* Read fhctl setting by device tree */
+	err = mtk_fhctl_parse_dt(fhctl);
+	if (err) {
+		dev_info(&pdev->dev, "ERROR mtk_fhctl_parse_dt fail");
+		return err;
+	}
+
+	for (i = 0; i < pll_num ; i++)
+		fhctl->fh_tbl[i]->hal_ops->pll_init(fhctl->fh_tbl[i]);
+
+	__set_fhctl(fhctl);
+
+	mt_fhctl_init_debugfs(fhctl);
+
+	mtk_fh_set_rate = _mtk_fh_set_rate;
+
+	dev_info(&pdev->dev, "FHCTL Init Done");
+
+	for (i = 0; i < fhctl->pll_num; i++)
+		dev_info(&pdev->dev, "pllid_map[%d]=%d", i, fhctl->idmap[i]);
+
+	fhctl->reg_tr = fhctl->fhctl_base + (uintptr_t)FH_REG_TR;
+	/* show setting value */
+	dev_dbg(&pdev->dev, "pll_num:%d", fhctl->pll_num);
+	dev_dbg(&pdev->dev, "apmixed_base:0x%lx",
+		(unsigned long)fhctl->apmixed_base);
+	dev_dbg(&pdev->dev, "fhctl_base:0x%lx",
+		(unsigned long)fhctl->fhctl_base);
+	dev_dbg(&pdev->dev, "reg_tr:0x%lx",
+		(unsigned long)fhctl->reg_tr);
+	dev_dbg(&pdev->dev, "pll_dds_reg_field_size:%d",
+		fhctl->dev_comp->pll_dds_reg_field_size);
+	dev_dbg(&pdev->dev, "pll_reg_offs:0x%x",
+		fhctl->dev_comp->pll_regs[0]);
+	dev_dbg(&pdev->dev, "pll-type[0]:%d",
+		fhctl->fh_tbl[0]->pll_data->pll_type);
+	dev_dbg(&pdev->dev, "pll_default_enable_ssc[0]:%d",
+		fhctl->fh_tbl[0]->pll_data->pll_default_ssc_rate);
+	dev_dbg(&pdev->dev, "pll_con0_regs[0]:0x%x",
+		fhctl->dev_comp->pll_con0_regs[0]);
+	dev_dbg(&pdev->dev, "pll_slope0_reg_settings[0]:0x%x",
+		fhctl->dev_comp->pll_slope0_reg_setting);
+	dev_dbg(&pdev->dev, "pll_slope1_reg_settings[0]:0x%x",
+		fhctl->dev_comp->pll_slope1_reg_setting);
+	dev_dbg(&pdev->dev, "pll_names[0]:%s",
+		fhctl->fh_tbl[0]->pll_data->pll_name);
+
+	return 0;
+}
+
+static int mt_fh_plt_drv_remove(struct platform_device *pdev)
+{
+	struct mtk_fhctl *fhctl = platform_get_drvdata(pdev);
+
+	mtk_fh_set_rate = NULL;
+	mt_fhctl_exit_debugfs(fhctl);
+	return 0;
+}
+
+static void mt_fh_plt_drv_shutdown(struct platform_device *pdev)
+{
+	struct clk_mt_fhctl *fh;
+
+	dev_dbg(&pdev->dev, "%s!", __func__);
+
+	list_for_each_entry(fh, &clk_mt_fhctl_list, node) {
+		if (fh->pll_data->pll_default_ssc_rate > 0) {
+			dev_info(&pdev->dev, "Shutdown to Disable SSC => PLL:%s ",
+					fh->pll_data->pll_name);
+			fh->hal_ops->pll_ssc_disable(fh);
+		}
+	}
+	dev_dbg(&pdev->dev, "%s Done!", __func__);
+}
+
+
+static const u16 mt_fhctl_regs_v1[] = {
+	[OFFSET_HP_EN] = 0x0,
+	[OFFSET_CLK_CON] = 0x4,
+	[OFFSET_RST_CON] = 0x8,
+	[OFFSET_SLOPE0] = 0xc,
+	[OFFSET_SLOPE1] = 0x10,
+	[OFFSET_FHCTL_DSSC_CFG] = 0x14,
+};
+
+static const u16 mt_fhctl_regs_v2[] = {
+	[OFFSET_UNITSLOPE_EN] = 0x0,
+	[OFFSET_HP_EN] = 0x4,
+	[OFFSET_CLK_CON] = 0x8,
+	[OFFSET_RST_CON] = 0xc,
+	[OFFSET_SLOPE0] = 0x10,
+	[OFFSET_SLOPE1] = 0x14,
+	[OFFSET_FHCTL_DSSC_CFG] = 0x18,
+};
+
+static const u16 mt_fhctl_regs_v3[] = {
+	[OFFSET_UNITSLOPE_EN] = 0x4,
+	[OFFSET_HP_EN] = 0x0,
+	[OFFSET_CLK_CON] = 0x8,
+	[OFFSET_RST_CON] = 0xc,
+	[OFFSET_SLOPE0] = 0x10,
+	[OFFSET_SLOPE1] = 0x14,
+	[OFFSET_FHCTL_DSSC_CFG] = 0x18,
+};
+
+
+
+static const char * const mt6779_pll_names[] = {
+			"armpll_ll", "armpll_bl", "armpll_bb", "ccipll",
+			"mfgpll", "mpll", "mempll", "mainpll",
+			"msdcpll", "mmpll", "adsppll", "tvdpll"};
+
+
+static const u16 mt6779_pll_regs[] = {
+			0x0038, 0x004C, 0xdead, 0x0074,
+			0x088, 0x009C, 0x00B0, 0x00C4,
+			0x00D8, 0x00EC, 0x0100, 0x0114};
+
+static const u16 mt6779_pll_con0_regs[] = {
+			0x200, 0x210, 0x0220, 0x02A0,
+			0x0250, 0x0290, 0xdead, 0x0230,
+			0x0260, 0x0280, 0x02b0, 0x0270};
+
+
+static const struct mtk_fhctl_compatible mt6779_fhctl_compat = {
+	.common_regs = mt_fhctl_regs_v1,
+	.pll_num = 12,
+	.pll_names = mt6779_pll_names,
+	.pll_dds_reg_field_size = 22,
+	.pll_apmix_pcw_offs = CON1_OFFS,
+	.pll_regs = mt6779_pll_regs,
+	.pll_con0_regs = mt6779_pll_con0_regs,
+	.pll_slope0_reg_setting = 0x6003c97,
+	.pll_slope1_reg_setting = 0x6003c97,
+};
+
+
+static const char * const mt6880_pll_names[] = {
+			"armpll_ll", "mainpll", "mpll", "ccipll",
+			"msdcpll", "mfgpll", "mmpll", "net1pll",
+			"net2pll", "wedmcupll", "mempll"};
+
+
+static const u16 mt6880_pll_regs[] = {
+			0x003C, 0x0050, 0x0064, 0x0078,
+			0x008C, 0x00A0, 0x00B4, 0x00C8,
+			0x00DC, 0x00F0, 0x0104};
+
+static const u16 mt6880_pll_con0_regs[] = {
+			0x0204, 0x0404, 0x0604, 0x0218,
+			0x022C, 0x0618, 0x042C, 0x0804,
+			0x0818, 0x082C, 0xdead};
+
+
+static const struct mtk_fhctl_compatible mt6880_fhctl_compat = {
+	.common_regs = mt_fhctl_regs_v3,
+	.pll_num = 11,
+	.pll_names = mt6880_pll_names,
+	.pll_dds_reg_field_size = 22,
+	.pll_apmix_pcw_offs = CON2_OFFS,
+	.pll_regs = mt6880_pll_regs,
+	.pll_con0_regs = mt6880_pll_con0_regs,
+	.pll_slope0_reg_setting = 0x6003c97,
+	.pll_slope1_reg_setting = 0x6003c97,
+};
+
+
+static const struct of_device_id mtk_fhctl_of_match[] = {
+	{ .compatible = "mediatek,mt6779-fhctl", .data = &mt6779_fhctl_compat },
+	{ .compatible = "mediatek,mt6880-fhctl", .data = &mt6880_fhctl_compat },
+	{}
+};
+MODULE_DEVICE_TABLE(of, mtk_fhctl_of_match);
+
+
+static struct platform_driver fhctl_driver = {
+	.probe = mt_fh_plt_drv_probe,
+	.remove = mt_fh_plt_drv_remove,
+	.shutdown = mt_fh_plt_drv_shutdown,
+	.driver = {
+		.name = "mt-freqhopping",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(mtk_fhctl_of_match),
+	},
+};
+
+module_platform_driver(fhctl_driver);
+
+
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MediaTek FHCTL Driver");
+MODULE_AUTHOR("Pierre Lee <pierre.lee@mediatek.com>");
+