162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2022 MediaTek Inc. 462306a36Sopenharmony_ci * Author: Edward-JW Yang <edward-jw.yang@mediatek.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/io.h> 862306a36Sopenharmony_ci#include <linux/iopoll.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "clk-mtk.h" 1162306a36Sopenharmony_ci#include "clk-pllfh.h" 1262306a36Sopenharmony_ci#include "clk-fhctl.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define PERCENT_TO_DDSLMT(dds, percent_m10) \ 1562306a36Sopenharmony_ci ((((dds) * (percent_m10)) >> 5) / 100) 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic const struct fhctl_offset fhctl_offset_v1 = { 1862306a36Sopenharmony_ci .offset_hp_en = 0x0, 1962306a36Sopenharmony_ci .offset_clk_con = 0x4, 2062306a36Sopenharmony_ci .offset_rst_con = 0x8, 2162306a36Sopenharmony_ci .offset_slope0 = 0xc, 2262306a36Sopenharmony_ci .offset_slope1 = 0x10, 2362306a36Sopenharmony_ci .offset_cfg = 0x0, 2462306a36Sopenharmony_ci .offset_updnlmt = 0x4, 2562306a36Sopenharmony_ci .offset_dds = 0x8, 2662306a36Sopenharmony_ci .offset_dvfs = 0xc, 2762306a36Sopenharmony_ci .offset_mon = 0x10, 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic const struct fhctl_offset fhctl_offset_v2 = { 3162306a36Sopenharmony_ci .offset_hp_en = 0x0, 3262306a36Sopenharmony_ci .offset_clk_con = 0x8, 3362306a36Sopenharmony_ci .offset_rst_con = 0xc, 3462306a36Sopenharmony_ci .offset_slope0 = 0x10, 3562306a36Sopenharmony_ci .offset_slope1 = 0x14, 3662306a36Sopenharmony_ci .offset_cfg = 0x0, 3762306a36Sopenharmony_ci .offset_updnlmt = 0x4, 3862306a36Sopenharmony_ci .offset_dds = 0x8, 3962306a36Sopenharmony_ci .offset_dvfs = 0xc, 4062306a36Sopenharmony_ci .offset_mon = 0x10, 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ciconst struct fhctl_offset *fhctl_get_offset_table(enum fhctl_variant v) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci switch (v) { 4662306a36Sopenharmony_ci case FHCTL_PLLFH_V1: 4762306a36Sopenharmony_ci return &fhctl_offset_v1; 4862306a36Sopenharmony_ci case FHCTL_PLLFH_V2: 4962306a36Sopenharmony_ci return &fhctl_offset_v2; 5062306a36Sopenharmony_ci default: 5162306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 5262306a36Sopenharmony_ci }; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic void dump_hw(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, 5662306a36Sopenharmony_ci const struct fh_pll_data *data) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci pr_info("hp_en<%x>,clk_con<%x>,slope0<%x>,slope1<%x>\n", 5962306a36Sopenharmony_ci readl(regs->reg_hp_en), readl(regs->reg_clk_con), 6062306a36Sopenharmony_ci readl(regs->reg_slope0), readl(regs->reg_slope1)); 6162306a36Sopenharmony_ci pr_info("cfg<%x>,lmt<%x>,dds<%x>,dvfs<%x>,mon<%x>\n", 6262306a36Sopenharmony_ci readl(regs->reg_cfg), readl(regs->reg_updnlmt), 6362306a36Sopenharmony_ci readl(regs->reg_dds), readl(regs->reg_dvfs), 6462306a36Sopenharmony_ci readl(regs->reg_mon)); 6562306a36Sopenharmony_ci pr_info("pcw<%x>\n", readl(pll->pcw_addr)); 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic int fhctl_set_ssc_regs(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, 6962306a36Sopenharmony_ci const struct fh_pll_data *data, u32 rate) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci u32 updnlmt_val, r; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci writel((readl(regs->reg_cfg) & ~(data->frddsx_en)), regs->reg_cfg); 7462306a36Sopenharmony_ci writel((readl(regs->reg_cfg) & ~(data->sfstrx_en)), regs->reg_cfg); 7562306a36Sopenharmony_ci writel((readl(regs->reg_cfg) & ~(data->fhctlx_en)), regs->reg_cfg); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (rate > 0) { 7862306a36Sopenharmony_ci /* Set the relative parameter registers (dt/df/upbnd/downbnd) */ 7962306a36Sopenharmony_ci r = readl(regs->reg_cfg); 8062306a36Sopenharmony_ci r &= ~(data->msk_frddsx_dys); 8162306a36Sopenharmony_ci r |= (data->df_val << (ffs(data->msk_frddsx_dys) - 1)); 8262306a36Sopenharmony_ci writel(r, regs->reg_cfg); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci r = readl(regs->reg_cfg); 8562306a36Sopenharmony_ci r &= ~(data->msk_frddsx_dts); 8662306a36Sopenharmony_ci r |= (data->dt_val << (ffs(data->msk_frddsx_dts) - 1)); 8762306a36Sopenharmony_ci writel(r, regs->reg_cfg); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci writel((readl(pll->pcw_addr) & data->dds_mask) | data->tgl_org, 9062306a36Sopenharmony_ci regs->reg_dds); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* Calculate UPDNLMT */ 9362306a36Sopenharmony_ci updnlmt_val = PERCENT_TO_DDSLMT((readl(regs->reg_dds) & 9462306a36Sopenharmony_ci data->dds_mask), rate) << 9562306a36Sopenharmony_ci data->updnlmt_shft; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci writel(updnlmt_val, regs->reg_updnlmt); 9862306a36Sopenharmony_ci writel(readl(regs->reg_hp_en) | BIT(data->fh_id), 9962306a36Sopenharmony_ci regs->reg_hp_en); 10062306a36Sopenharmony_ci /* Enable SSC */ 10162306a36Sopenharmony_ci writel(readl(regs->reg_cfg) | data->frddsx_en, regs->reg_cfg); 10262306a36Sopenharmony_ci /* Enable Hopping control */ 10362306a36Sopenharmony_ci writel(readl(regs->reg_cfg) | data->fhctlx_en, regs->reg_cfg); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci } else { 10662306a36Sopenharmony_ci /* Switch to APMIXEDSYS control */ 10762306a36Sopenharmony_ci writel(readl(regs->reg_hp_en) & ~BIT(data->fh_id), 10862306a36Sopenharmony_ci regs->reg_hp_en); 10962306a36Sopenharmony_ci /* Wait for DDS to be stable */ 11062306a36Sopenharmony_ci udelay(30); 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci return 0; 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic int hopping_hw_flow(struct mtk_clk_pll *pll, struct fh_pll_regs *regs, 11762306a36Sopenharmony_ci const struct fh_pll_data *data, 11862306a36Sopenharmony_ci struct fh_pll_state *state, unsigned int new_dds) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci u32 dds_mask = data->dds_mask; 12162306a36Sopenharmony_ci u32 mon_dds = 0; 12262306a36Sopenharmony_ci u32 con_pcw_tmp; 12362306a36Sopenharmony_ci int ret; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (state->ssc_rate) 12662306a36Sopenharmony_ci fhctl_set_ssc_regs(pll, regs, data, 0); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci writel((readl(pll->pcw_addr) & dds_mask) | data->tgl_org, 12962306a36Sopenharmony_ci regs->reg_dds); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci writel(readl(regs->reg_cfg) | data->sfstrx_en, regs->reg_cfg); 13262306a36Sopenharmony_ci writel(readl(regs->reg_cfg) | data->fhctlx_en, regs->reg_cfg); 13362306a36Sopenharmony_ci writel(data->slope0_value, regs->reg_slope0); 13462306a36Sopenharmony_ci writel(data->slope1_value, regs->reg_slope1); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci writel(readl(regs->reg_hp_en) | BIT(data->fh_id), regs->reg_hp_en); 13762306a36Sopenharmony_ci writel((new_dds) | (data->dvfs_tri), regs->reg_dvfs); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Wait 1000 us until DDS stable */ 14062306a36Sopenharmony_ci ret = readl_poll_timeout_atomic(regs->reg_mon, mon_dds, 14162306a36Sopenharmony_ci (mon_dds & dds_mask) == new_dds, 14262306a36Sopenharmony_ci 10, 1000); 14362306a36Sopenharmony_ci if (ret) { 14462306a36Sopenharmony_ci pr_warn("%s: FHCTL hopping timeout\n", pll->data->name); 14562306a36Sopenharmony_ci dump_hw(pll, regs, data); 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci con_pcw_tmp = readl(pll->pcw_addr) & (~dds_mask); 14962306a36Sopenharmony_ci con_pcw_tmp = (con_pcw_tmp | (readl(regs->reg_mon) & dds_mask) | 15062306a36Sopenharmony_ci data->pcwchg); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci writel(con_pcw_tmp, pll->pcw_addr); 15362306a36Sopenharmony_ci writel(readl(regs->reg_hp_en) & ~BIT(data->fh_id), regs->reg_hp_en); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (state->ssc_rate) 15662306a36Sopenharmony_ci fhctl_set_ssc_regs(pll, regs, data, state->ssc_rate); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return ret; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic unsigned int __get_postdiv(struct mtk_clk_pll *pll) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci unsigned int regval; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci regval = readl(pll->pd_addr) >> pll->data->pd_shift; 16662306a36Sopenharmony_ci regval &= POSTDIV_MASK; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return BIT(regval); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void __set_postdiv(struct mtk_clk_pll *pll, unsigned int postdiv) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci unsigned int regval; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci regval = readl(pll->pd_addr); 17662306a36Sopenharmony_ci regval &= ~(POSTDIV_MASK << pll->data->pd_shift); 17762306a36Sopenharmony_ci regval |= (ffs(postdiv) - 1) << pll->data->pd_shift; 17862306a36Sopenharmony_ci writel(regval, pll->pd_addr); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic int fhctl_hopping(struct mtk_fh *fh, unsigned int new_dds, 18262306a36Sopenharmony_ci unsigned int postdiv) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci const struct fh_pll_data *data = &fh->pllfh_data->data; 18562306a36Sopenharmony_ci struct fh_pll_state *state = &fh->pllfh_data->state; 18662306a36Sopenharmony_ci struct fh_pll_regs *regs = &fh->regs; 18762306a36Sopenharmony_ci struct mtk_clk_pll *pll = &fh->clk_pll; 18862306a36Sopenharmony_ci spinlock_t *lock = fh->lock; 18962306a36Sopenharmony_ci unsigned int pll_postdiv; 19062306a36Sopenharmony_ci unsigned long flags = 0; 19162306a36Sopenharmony_ci int ret; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (postdiv) { 19462306a36Sopenharmony_ci pll_postdiv = __get_postdiv(pll); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (postdiv > pll_postdiv) 19762306a36Sopenharmony_ci __set_postdiv(pll, postdiv); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci spin_lock_irqsave(lock, flags); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci ret = hopping_hw_flow(pll, regs, data, state, new_dds); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci spin_unlock_irqrestore(lock, flags); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci if (postdiv && postdiv < pll_postdiv) 20762306a36Sopenharmony_ci __set_postdiv(pll, postdiv); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return ret; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int fhctl_ssc_enable(struct mtk_fh *fh, u32 rate) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci const struct fh_pll_data *data = &fh->pllfh_data->data; 21562306a36Sopenharmony_ci struct fh_pll_state *state = &fh->pllfh_data->state; 21662306a36Sopenharmony_ci struct fh_pll_regs *regs = &fh->regs; 21762306a36Sopenharmony_ci struct mtk_clk_pll *pll = &fh->clk_pll; 21862306a36Sopenharmony_ci spinlock_t *lock = fh->lock; 21962306a36Sopenharmony_ci unsigned long flags = 0; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci spin_lock_irqsave(lock, flags); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci fhctl_set_ssc_regs(pll, regs, data, rate); 22462306a36Sopenharmony_ci state->ssc_rate = rate; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci spin_unlock_irqrestore(lock, flags); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return 0; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic const struct fh_operation fhctl_ops = { 23262306a36Sopenharmony_ci .hopping = fhctl_hopping, 23362306a36Sopenharmony_ci .ssc_enable = fhctl_ssc_enable, 23462306a36Sopenharmony_ci}; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ciconst struct fh_operation *fhctl_get_ops(void) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci return &fhctl_ops; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_civoid fhctl_hw_init(struct mtk_fh *fh) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci const struct fh_pll_data data = fh->pllfh_data->data; 24462306a36Sopenharmony_ci struct fh_pll_state state = fh->pllfh_data->state; 24562306a36Sopenharmony_ci struct fh_pll_regs regs = fh->regs; 24662306a36Sopenharmony_ci u32 val; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* initial hw register */ 24962306a36Sopenharmony_ci val = readl(regs.reg_clk_con) | BIT(data.fh_id); 25062306a36Sopenharmony_ci writel(val, regs.reg_clk_con); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci val = readl(regs.reg_rst_con) & ~BIT(data.fh_id); 25362306a36Sopenharmony_ci writel(val, regs.reg_rst_con); 25462306a36Sopenharmony_ci val = readl(regs.reg_rst_con) | BIT(data.fh_id); 25562306a36Sopenharmony_ci writel(val, regs.reg_rst_con); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci writel(0x0, regs.reg_cfg); 25862306a36Sopenharmony_ci writel(0x0, regs.reg_updnlmt); 25962306a36Sopenharmony_ci writel(0x0, regs.reg_dds); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci /* enable ssc if needed */ 26262306a36Sopenharmony_ci if (state.ssc_rate) 26362306a36Sopenharmony_ci fh->ops->ssc_enable(fh, state.ssc_rate); 26462306a36Sopenharmony_ci} 265