[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>");
+