// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. * Author: Pierre Lee */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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]; 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_con1 = REG_ADDR(apmixed_base, reg_con0_offs + 0x04); 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); } /* 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]); /* 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, "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 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 char * const mt6761_pll_names[] = { "armpll", "mainpll", "msdcpll", "mfgpll", "mempll", "mpll", "mmpll"}; static const u16 mt6761_pll_regs[] = { 0x003C, 0x0050, 0x0064, 0x0078, 0x008C, 0x00A0, 0x00B4}; static const u16 mt6761_pll_con0_regs[] = { 0x030C, 0x0228, 0x0350, 0x0218, 0xdead, 0x0340, 0x0330}; static const char * const mt6765_pll_names[] = { "armpll", "mainpll", "msdcpll", "mfgpll", "mempll", "mpll", "mmpll", "armpll_l", "ccipll"}; static const u16 mt6765_pll_regs[] = { 0x003C, 0x0050, 0x0064, 0x0078, 0x008C, 0x00A0, 0x00B4, 0x00C8, 0x00DC}; static const u16 mt6765_pll_con0_regs[] = { 0x020C, 0x023C, 0x027C, 0x024C, 0xdead, 0x02A0, 0x025C, 0x021C, 0x022C}; 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_regs = mt6779_pll_regs, .pll_con0_regs = mt6779_pll_con0_regs, .pll_slope0_reg_setting = 0x6003c97, .pll_slope1_reg_setting = 0x6003c97, }; static const struct mtk_fhctl_compatible mt6761_fhctl_compat = { .common_regs = mt_fhctl_regs_v2, .pll_num = 7, .pll_names = mt6761_pll_names, .pll_dds_reg_field_size = 22, .pll_regs = mt6761_pll_regs, .pll_con0_regs = mt6761_pll_con0_regs, .pll_slope0_reg_setting = 0x6003c97, .pll_slope1_reg_setting = 0x6003c97, }; static const struct mtk_fhctl_compatible mt6765_fhctl_compat = { .common_regs = mt_fhctl_regs_v2, .pll_num = 9, .pll_names = mt6765_pll_names, .pll_dds_reg_field_size = 22, .pll_regs = mt6765_pll_regs, .pll_con0_regs = mt6765_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,mt6761-fhctl", .data = &mt6761_fhctl_compat }, { .compatible = "mediatek,mt6765-fhctl", .data = &mt6765_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 ");