diff options
Diffstat (limited to 'drivers/clk')
| -rw-r--r-- | drivers/clk/msm/clock-gpu-cobalt.c | 3 | ||||
| -rw-r--r-- | drivers/clk/msm/clock-osm.c | 451 | ||||
| -rw-r--r-- | drivers/clk/msm/mdss/mdss-dp-pll-cobalt-util.c | 4 | ||||
| -rw-r--r-- | drivers/clk/msm/mdss/mdss-dsi-pll-cobalt.c | 18 | ||||
| -rw-r--r-- | drivers/clk/qcom/clk-alpha-pll.c | 247 | ||||
| -rw-r--r-- | drivers/clk/qcom/clk-alpha-pll.h | 23 | ||||
| -rw-r--r-- | drivers/clk/qcom/clk-pll.h | 9 | ||||
| -rw-r--r-- | drivers/clk/qcom/gpucc-msmfalcon.c | 14 |
8 files changed, 718 insertions, 51 deletions
diff --git a/drivers/clk/msm/clock-gpu-cobalt.c b/drivers/clk/msm/clock-gpu-cobalt.c index 9d93351a083e..7cec9be1f42c 100644 --- a/drivers/clk/msm/clock-gpu-cobalt.c +++ b/drivers/clk/msm/clock-gpu-cobalt.c @@ -173,6 +173,7 @@ static struct clk_freq_tbl ftbl_gfx3d_clk_src_v2[] = { F_SLEW( 515000000, 1030000000, gpu_pll0_pll_out_even, 1, 0, 0), F_SLEW( 596000000, 1192000000, gpu_pll0_pll_out_even, 1, 0, 0), F_SLEW( 670000000, 1340000000, gpu_pll0_pll_out_even, 1, 0, 0), + F_SLEW( 710000000, 1420000000, gpu_pll0_pll_out_even, 1, 0, 0), F_END }; @@ -611,7 +612,7 @@ static void msm_gfxcc_hamster_fixup(void) static void msm_gfxcc_cobalt_v2_fixup(void) { - gpu_pll0_pll.c.fmax[VDD_DIG_MIN] = 1340000500; + gpu_pll0_pll.c.fmax[VDD_DIG_MIN] = 1420000500; gfx3d_clk_src.freq_tbl = ftbl_gfx3d_clk_src_v2; } diff --git a/drivers/clk/msm/clock-osm.c b/drivers/clk/msm/clock-osm.c index 5391ef456aae..3e45aee1c0f7 100644 --- a/drivers/clk/msm/clock-osm.c +++ b/drivers/clk/msm/clock-osm.c @@ -49,6 +49,7 @@ enum clk_osm_bases { OSM_BASE, PLL_BASE, EFUSE_BASE, + ACD_BASE, NUM_BASES, }; @@ -228,11 +229,43 @@ enum clk_osm_trace_packet_id { #define MSMCOBALTV2_PWRCL_BOOT_RATE 1555200000 #define MSMCOBALTV2_PERFCL_BOOT_RATE 1728000000 +/* ACD registers */ +#define ACD_HW_VERSION 0x0 +#define ACDCR 0x4 +#define ACDTD 0x8 +#define ACDSSCR 0x28 +#define ACD_EXTINT_CFG 0x30 +#define ACD_DCVS_SW 0x34 +#define ACD_GFMUX_CFG 0x3c +#define ACD_READOUT_CFG 0x48 +#define ACD_AUTOXFER_CFG 0x80 +#define ACD_AUTOXFER 0x84 +#define ACD_AUTOXFER_CTL 0x88 +#define ACD_AUTOXFER_STATUS 0x8c +#define ACD_WRITE_CTL 0x90 +#define ACD_WRITE_STATUS 0x94 +#define ACD_READOUT 0x98 + +#define ACD_MASTER_ONLY_REG_ADDR 0x80 +#define ACD_WRITE_CTL_UPDATE_EN BIT(0) +#define ACD_WRITE_CTL_SELECT_SHIFT 1 +#define ACD_GFMUX_CFG_SELECT BIT(0) +#define ACD_AUTOXFER_START_CLEAR 0 +#define ACD_AUTOXFER_START_SET BIT(0) +#define AUTO_XFER_DONE_MASK BIT(0) +#define ACD_DCVS_SW_DCVS_IN_PRGR_SET BIT(0) +#define ACD_DCVS_SW_DCVS_IN_PRGR_CLEAR 0 +#define ACD_LOCAL_TRANSFER_TIMEOUT_NS 500 + static void __iomem *virt_base; static void __iomem *debug_base; #define lmh_lite_clk_src_source_val 1 +#define ACD_REG_RELATIVE_ADDR(addr) (addr / 4) +#define ACD_REG_RELATIVE_ADDR_BITMASK(addr) \ + (1 << (ACD_REG_RELATIVE_ADDR(addr))) + #define FIXDIV(div) (div ? (2 * (div) - 1) : (0)) #define F(f, s, div, m, n) \ @@ -341,6 +374,14 @@ struct clk_osm { u32 apm_ctrl_status; u32 osm_clk_rate; u32 xo_clk_rate; + u32 acd_td; + u32 acd_cr; + u32 acd_sscr; + u32 acd_extint0_cfg; + u32 acd_extint1_cfg; + u32 acd_autoxfer_ctl; + u32 acd_debugfs_addr; + bool acd_init; bool secure_init; bool red_fsm_en; bool boost_fsm_en; @@ -394,6 +435,161 @@ static inline int clk_osm_mb(struct clk_osm *c, int base) return readl_relaxed_no_log((char *)c->vbases[base] + VERSION_REG); } +static inline int clk_osm_acd_mb(struct clk_osm *c) +{ + return readl_relaxed_no_log((char *)c->vbases[ACD_BASE] + + ACD_HW_VERSION); +} + +static inline void clk_osm_acd_master_write_reg(struct clk_osm *c, + u32 val, u32 offset) +{ + writel_relaxed(val, (char *)c->vbases[ACD_BASE] + offset); +} + +static int clk_osm_acd_local_read_reg(struct clk_osm *c, u32 offset) +{ + u32 reg = 0; + int timeout; + + if (offset >= ACD_MASTER_ONLY_REG_ADDR) { + pr_err("ACD register at offset=0x%x not locally readable\n", + offset); + return -EINVAL; + } + + /* Set select field in read control register */ + writel_relaxed(ACD_REG_RELATIVE_ADDR(offset), + (char *)c->vbases[ACD_BASE] + ACD_READOUT_CFG); + + /* Clear write control register */ + writel_relaxed(reg, (char *)c->vbases[ACD_BASE] + ACD_WRITE_CTL); + + /* Set select and update_en fields in write control register */ + reg = (ACD_REG_RELATIVE_ADDR(ACD_READOUT_CFG) + << ACD_WRITE_CTL_SELECT_SHIFT) + | ACD_WRITE_CTL_UPDATE_EN; + writel_relaxed(reg, (char *)c->vbases[ACD_BASE] + ACD_WRITE_CTL); + + /* Ensure writes complete before polling */ + clk_osm_acd_mb(c); + + /* Poll write status register */ + for (timeout = ACD_LOCAL_TRANSFER_TIMEOUT_NS; timeout > 0; + timeout -= 100) { + reg = readl_relaxed((char *)c->vbases[ACD_BASE] + + ACD_WRITE_STATUS); + if ((reg & (ACD_REG_RELATIVE_ADDR_BITMASK(ACD_READOUT_CFG)))) + break; + ndelay(100); + } + + if (!timeout) { + pr_err("local read timed out, offset=0x%x status=0x%x\n", + offset, reg); + return -ETIMEDOUT; + } + + reg = readl_relaxed((char *)c->vbases[ACD_BASE] + + ACD_READOUT); + return reg; +} + +static int clk_osm_acd_local_write_reg(struct clk_osm *c, u32 val, u32 offset) +{ + u32 reg = 0; + int timeout; + + if (offset >= ACD_MASTER_ONLY_REG_ADDR) { + pr_err("ACD register at offset=0x%x not transferrable\n", + offset); + return -EINVAL; + } + + /* Clear write control register */ + writel_relaxed(reg, (char *)c->vbases[ACD_BASE] + ACD_WRITE_CTL); + + /* Set select and update_en fields in write control register */ + reg = (ACD_REG_RELATIVE_ADDR(offset) << ACD_WRITE_CTL_SELECT_SHIFT) + | ACD_WRITE_CTL_UPDATE_EN; + writel_relaxed(reg, (char *)c->vbases[ACD_BASE] + ACD_WRITE_CTL); + + /* Ensure writes complete before polling */ + clk_osm_acd_mb(c); + + /* Poll write status register */ + for (timeout = ACD_LOCAL_TRANSFER_TIMEOUT_NS; timeout > 0; + timeout -= 100) { + reg = readl_relaxed((char *)c->vbases[ACD_BASE] + + ACD_WRITE_STATUS); + if ((reg & (ACD_REG_RELATIVE_ADDR_BITMASK(offset)))) + break; + ndelay(100); + } + + if (!timeout) { + pr_err("local write timed out, offset=0x%x val=0x%x status=0x%x\n", + offset, val, reg); + return -ETIMEDOUT; + } + + return 0; +} + +static int clk_osm_acd_master_write_through_reg(struct clk_osm *c, + u32 val, u32 offset) +{ + writel_relaxed(val, (char *)c->vbases[ACD_BASE] + offset); + + /* Ensure writes complete before transfer to local copy */ + clk_osm_acd_mb(c); + + return clk_osm_acd_local_write_reg(c, val, offset); +} + +static int clk_osm_acd_auto_local_write_reg(struct clk_osm *c, u32 mask) +{ + u32 numregs, bitmask = mask; + u32 reg = 0; + int timeout; + + /* count number of bits set in register mask */ + for (numregs = 0; bitmask; numregs++) + bitmask &= bitmask - 1; + + /* Program auto-transfter mask */ + writel_relaxed(mask, (char *)c->vbases[ACD_BASE] + ACD_AUTOXFER_CFG); + + /* Clear start field in auto-transfer register */ + writel_relaxed(ACD_AUTOXFER_START_CLEAR, + (char *)c->vbases[ACD_BASE] + ACD_AUTOXFER); + + /* Set start field in auto-transfer register */ + writel_relaxed(ACD_AUTOXFER_START_SET, + (char *)c->vbases[ACD_BASE] + ACD_AUTOXFER); + + /* Ensure writes complete before polling */ + clk_osm_acd_mb(c); + + /* Poll auto-transfer status register */ + for (timeout = ACD_LOCAL_TRANSFER_TIMEOUT_NS * numregs; + timeout > 0; timeout -= 100) { + reg = readl_relaxed((char *)c->vbases[ACD_BASE] + + ACD_AUTOXFER_STATUS); + if (reg & AUTO_XFER_DONE_MASK) + break; + ndelay(100); + } + + if (!timeout) { + pr_err("local register auto-transfer timed out, mask=0x%x registers=%d status=0x%x\n", + mask, numregs, reg); + return -ETIMEDOUT; + } + + return 0; +} + static inline int clk_osm_count_ns(struct clk_osm *c, u64 nsec) { u64 temp; @@ -813,6 +1009,74 @@ static int clk_osm_parse_dt_configs(struct platform_device *pdev) LLM_SW_OVERRIDE_CNT + i, &perfcl_clk.llm_sw_overr[i]); + if (pwrcl_clk.acd_init || perfcl_clk.acd_init) { + rc = of_property_read_u32_array(of, "qcom,acdtd-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdtd-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_td = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_td = array[perfcl_clk.cluster_num]; + + rc = of_property_read_u32_array(of, "qcom,acdcr-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdcr-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_cr = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_cr = array[perfcl_clk.cluster_num]; + + rc = of_property_read_u32_array(of, "qcom,acdsscr-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdsscr-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_sscr = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_sscr = array[perfcl_clk.cluster_num]; + + rc = of_property_read_u32_array(of, "qcom,acdextint0-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdextint0-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_extint0_cfg = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_extint0_cfg = array[perfcl_clk.cluster_num]; + + rc = of_property_read_u32_array(of, "qcom,acdextint1-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdextint1-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_extint1_cfg = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_extint1_cfg = array[perfcl_clk.cluster_num]; + + rc = of_property_read_u32_array(of, "qcom,acdautoxfer-val", + array, MAX_CLUSTER_CNT); + if (rc) { + dev_err(&pdev->dev, "unable to find qcom,acdautoxfer-val property, rc=%d\n", + rc); + return -EINVAL; + } + + pwrcl_clk.acd_autoxfer_ctl = array[pwrcl_clk.cluster_num]; + perfcl_clk.acd_autoxfer_ctl = array[perfcl_clk.cluster_num]; + } + rc = of_property_read_u32(of, "qcom,xo-clk-rate", &pwrcl_clk.xo_clk_rate); if (rc) { @@ -1037,6 +1301,40 @@ static int clk_osm_resources_init(struct platform_device *pdev) perfcl_clk.vbases[EFUSE_BASE] = vbase; } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pwrcl_acd"); + if (res) { + pbase = (unsigned long)res->start; + vbase = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!vbase) { + dev_err(&pdev->dev, "Unable to map in pwrcl_acd base\n"); + return -ENOMEM; + } + pwrcl_clk.pbases[ACD_BASE] = pbase; + pwrcl_clk.vbases[ACD_BASE] = vbase; + pwrcl_clk.acd_init = true; + } else { + pwrcl_clk.acd_init = false; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "perfcl_acd"); + if (res) { + pbase = (unsigned long)res->start; + vbase = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!vbase) { + dev_err(&pdev->dev, "Unable to map in perfcl_acd base\n"); + return -ENOMEM; + } + perfcl_clk.pbases[ACD_BASE] = pbase; + perfcl_clk.vbases[ACD_BASE] = vbase; + perfcl_clk.acd_init = true; + } else { + perfcl_clk.acd_init = false; + } + vdd_pwrcl = devm_regulator_get(&pdev->dev, "vdd-pwrcl"); if (IS_ERR(vdd_pwrcl)) { rc = PTR_ERR(vdd_pwrcl); @@ -2402,6 +2700,55 @@ DEFINE_SIMPLE_ATTRIBUTE(debugfs_perf_state_deviation_corrected_irq_fops, debugfs_set_perf_state_deviation_corrected_irq, "%llu\n"); +static int debugfs_get_debug_reg(void *data, u64 *val) +{ + struct clk_osm *c = data; + + if (c->acd_debugfs_addr >= ACD_MASTER_ONLY_REG_ADDR) + *val = readl_relaxed((char *)c->vbases[ACD_BASE] + + c->acd_debugfs_addr); + else + *val = clk_osm_acd_local_read_reg(c, c->acd_debugfs_addr); + return 0; +} + +static int debugfs_set_debug_reg(void *data, u64 val) +{ + struct clk_osm *c = data; + + if (c->acd_debugfs_addr >= ACD_MASTER_ONLY_REG_ADDR) + clk_osm_acd_master_write_reg(c, val, c->acd_debugfs_addr); + else + clk_osm_acd_master_write_through_reg(c, val, + c->acd_debugfs_addr); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(debugfs_acd_debug_reg_fops, + debugfs_get_debug_reg, + debugfs_set_debug_reg, + "0x%llx\n"); + +static int debugfs_get_debug_reg_addr(void *data, u64 *val) +{ + struct clk_osm *c = data; + + *val = c->acd_debugfs_addr; + return 0; +} + +static int debugfs_set_debug_reg_addr(void *data, u64 val) +{ + struct clk_osm *c = data; + + c->acd_debugfs_addr = val; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(debugfs_acd_debug_reg_addr_fops, + debugfs_get_debug_reg_addr, + debugfs_set_debug_reg_addr, + "%llu\n"); + static void populate_debugfs_dir(struct clk_osm *c) { struct dentry *temp; @@ -2493,6 +2840,24 @@ static void populate_debugfs_dir(struct clk_osm *c) goto exit; } + temp = debugfs_create_file("acd_debug_reg", + S_IRUGO | S_IWUSR, + c->debugfs, c, + &debugfs_acd_debug_reg_fops); + if (IS_ERR_OR_NULL(temp)) { + pr_err("debugfs_acd_debug_reg_fops debugfs file creation failed\n"); + goto exit; + } + + temp = debugfs_create_file("acd_debug_reg_addr", + S_IRUGO | S_IWUSR, + c->debugfs, c, + &debugfs_acd_debug_reg_addr_fops); + if (IS_ERR_OR_NULL(temp)) { + pr_err("debugfs_acd_debug_reg_addr_fops debugfs file creation failed\n"); + goto exit; + } + exit: if (IS_ERR_OR_NULL(temp)) debugfs_remove_recursive(c->debugfs); @@ -2537,6 +2902,81 @@ static int clk_osm_panic_callback(struct notifier_block *nfb, return NOTIFY_OK; } +static int clk_osm_acd_init(struct clk_osm *c) +{ + + int rc = 0; + u32 auto_xfer_mask = 0; + + if (!c->acd_init) + return 0; + + c->acd_debugfs_addr = ACD_HW_VERSION; + + /* Program ACD tunable-length delay register */ + clk_osm_acd_master_write_reg(c, c->acd_td, ACDTD); + auto_xfer_mask |= ACD_REG_RELATIVE_ADDR_BITMASK(ACDTD); + + /* Program ACD control register */ + clk_osm_acd_master_write_reg(c, c->acd_cr, ACDCR); + auto_xfer_mask |= ACD_REG_RELATIVE_ADDR_BITMASK(ACDCR); + + /* Program ACD soft start control register */ + clk_osm_acd_master_write_reg(c, c->acd_sscr, ACDSSCR); + auto_xfer_mask |= ACD_REG_RELATIVE_ADDR_BITMASK(ACDSSCR); + + /* Program initial ACD external interface configuration register */ + clk_osm_acd_master_write_reg(c, c->acd_extint0_cfg, ACD_EXTINT_CFG); + auto_xfer_mask |= ACD_REG_RELATIVE_ADDR_BITMASK(ACD_EXTINT_CFG); + + /* Program ACD auto-register transfer control register */ + clk_osm_acd_master_write_reg(c, c->acd_autoxfer_ctl, ACD_AUTOXFER_CTL); + + /* Ensure writes complete before transfers to local copy */ + clk_osm_acd_mb(c); + + /* Transfer master copies */ + rc = clk_osm_acd_auto_local_write_reg(c, auto_xfer_mask); + if (rc) + return rc; + + /* Switch CPUSS clock source to ACD clock */ + rc = clk_osm_acd_master_write_through_reg(c, ACD_GFMUX_CFG_SELECT, + ACD_GFMUX_CFG); + if (rc) + return rc; + + /* Program ACD_DCVS_SW */ + rc = clk_osm_acd_master_write_through_reg(c, + ACD_DCVS_SW_DCVS_IN_PRGR_SET, + ACD_DCVS_SW); + if (rc) + return rc; + + rc = clk_osm_acd_master_write_through_reg(c, + ACD_DCVS_SW_DCVS_IN_PRGR_CLEAR, + ACD_DCVS_SW); + if (rc) + return rc; + + udelay(1); + + /* Program final ACD external interface configuration register */ + rc = clk_osm_acd_master_write_through_reg(c, c->acd_extint1_cfg, + ACD_EXTINT_CFG); + if (rc) + return rc; + + /* + * ACDCR, ACDTD, ACDSSCR, ACD_EXTINT_CFG, ACD_GFMUX_CFG + * must be copied from master to local copy on PC exit. + */ + auto_xfer_mask |= ACD_REG_RELATIVE_ADDR_BITMASK(ACD_GFMUX_CFG); + clk_osm_acd_master_write_reg(c, auto_xfer_mask, ACD_AUTOXFER_CFG); + + return 0; +} + static unsigned long init_rate = 300000000; static unsigned long osm_clk_init_rate = 200000000; @@ -2717,6 +3157,17 @@ static int cpu_clock_osm_driver_probe(struct platform_device *pdev) clk_osm_setup_cluster_pll(&perfcl_clk); } + rc = clk_osm_acd_init(&pwrcl_clk); + if (rc) { + pr_err("failed to initialize ACD for pwrcl, rc=%d\n", rc); + return rc; + } + rc = clk_osm_acd_init(&perfcl_clk); + if (rc) { + pr_err("failed to initialize ACD for perfcl, rc=%d\n", rc); + return rc; + } + spin_lock_init(&pwrcl_clk.lock); spin_lock_init(&perfcl_clk.lock); diff --git a/drivers/clk/msm/mdss/mdss-dp-pll-cobalt-util.c b/drivers/clk/msm/mdss/mdss-dp-pll-cobalt-util.c index a574a9cd2b5a..93bbcf5d40f5 100644 --- a/drivers/clk/msm/mdss/mdss-dp-pll-cobalt-util.c +++ b/drivers/clk/msm/mdss/mdss-dp-pll-cobalt-util.c @@ -275,7 +275,7 @@ int dp_config_vco_rate(struct dp_pll_vco_clk *vco, unsigned long rate) MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); MDSS_PLL_REG_W(dp_res->pll_base, - QSERDES_COM_DIV_FRAC_START3_MODE0, 0xa0); + QSERDES_COM_DIV_FRAC_START3_MODE0, 0x0a); MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CMN_CONFIG, 0x12); MDSS_PLL_REG_W(dp_res->pll_base, @@ -733,7 +733,7 @@ unsigned long dp_vco_get_rate(struct clk *c) { struct dp_pll_vco_clk *vco = mdss_dp_to_vco_clk(c); int rc; - u32 div, hsclk_div, link2xclk_div; + u32 div, hsclk_div, link2xclk_div = 0; u64 vco_rate; struct mdss_pll_resources *pll = vco->priv; diff --git a/drivers/clk/msm/mdss/mdss-dsi-pll-cobalt.c b/drivers/clk/msm/mdss/mdss-dsi-pll-cobalt.c index 4b2d8bba0940..299934c86d05 100644 --- a/drivers/clk/msm/mdss/mdss-dsi-pll-cobalt.c +++ b/drivers/clk/msm/mdss/mdss-dsi-pll-cobalt.c @@ -205,18 +205,34 @@ static void dsi_pll_calc_dec_frac(struct dsi_pll_cobalt *pll, struct dsi_pll_regs *regs = &pll->reg_setup; u64 target_freq; u64 fref = rsc->vco_ref_clk_rate; - u32 computed_output_div, div_log; + u32 computed_output_div, div_log = 0; u64 pll_freq; u64 divider; u64 dec, dec_multiple; u32 frac; u64 multiplier; + u32 i; target_freq = rsc->vco_current_rate; pr_debug("target_freq = %llu\n", target_freq); if (config->div_override) { computed_output_div = config->output_div; + + /* + * Computed_output_div = 2 ^ div_log + * To get div_log from output div just get the index of the + * 1 bit in the value. + * div_log ranges from 0-3. so check the 4 lsbs + */ + + for (i = 0; i < 4; i++) { + if (computed_output_div & (1 << i)) { + div_log = i; + break; + } + } + } else { if (target_freq < MHZ_375) { computed_output_div = 8; diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index 4d46d65898d1..085b9acfb9d5 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -20,28 +20,34 @@ #include "clk-alpha-pll.h" #define PLL_MODE 0x00 -# define PLL_OUTCTRL BIT(0) -# define PLL_BYPASSNL BIT(1) -# define PLL_RESET_N BIT(2) -# define PLL_LOCK_COUNT_SHIFT 8 -# define PLL_LOCK_COUNT_MASK 0x3f -# define PLL_BIAS_COUNT_SHIFT 14 -# define PLL_BIAS_COUNT_MASK 0x3f -# define PLL_VOTE_FSM_ENA BIT(20) -# define PLL_VOTE_FSM_RESET BIT(21) -# define PLL_ACTIVE_FLAG BIT(30) -# define PLL_LOCK_DET BIT(31) +#define PLL_OUTCTRL BIT(0) +#define PLL_BYPASSNL BIT(1) +#define PLL_RESET_N BIT(2) +#define PLL_LOCK_COUNT_SHIFT 8 +#define PLL_LOCK_COUNT_MASK 0x3f +#define PLL_LOCK_COUNT_VAL 0x0 +#define PLL_BIAS_COUNT_SHIFT 14 +#define PLL_BIAS_COUNT_MASK 0x3f +#define PLL_BIAS_COUNT_VAL 0x6 +#define PLL_LATCH_INTERFACE BIT(11) +#define PLL_VOTE_FSM_ENA BIT(20) +#define PLL_VOTE_FSM_RESET BIT(21) +#define PLL_UPDATE BIT(22) +#define PLL_HW_UPDATE_LOGIC_BYPASS BIT(23) +#define PLL_ALPHA_EN BIT(24) +#define PLL_ACTIVE_FLAG BIT(30) +#define PLL_LOCK_DET BIT(31) +#define PLL_ACK_LATCH BIT(29) #define PLL_L_VAL 0x04 #define PLL_ALPHA_VAL 0x08 #define PLL_ALPHA_VAL_U 0x0c #define PLL_USER_CTL 0x10 -# define PLL_POST_DIV_SHIFT 8 -# define PLL_POST_DIV_MASK 0xf -# define PLL_ALPHA_EN BIT(24) -# define PLL_VCO_SHIFT 20 -# define PLL_VCO_MASK 0x3 +#define PLL_POST_DIV_SHIFT 8 +#define PLL_POST_DIV_MASK 0xf +#define PLL_VCO_SHIFT 20 +#define PLL_VCO_MASK 0x3 #define PLL_USER_CTL_U 0x14 @@ -79,7 +85,7 @@ static int wait_for_pll(struct clk_alpha_pll *pll, u32 mask, bool inverse, ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val); if (ret) return ret; - if (inverse && (val & mask)) + if (inverse && !(val & mask)) return 0; else if ((val & mask) == mask) return 0; @@ -106,6 +112,10 @@ static int wait_for_pll_offline(struct clk_alpha_pll *pll, u32 mask) return wait_for_pll(pll, mask, 0, "offline"); } +static int wait_for_pll_latch_ack(struct clk_alpha_pll *pll, u32 mask) +{ + return wait_for_pll(pll, mask, 0, "latch_ack"); +} /* alpha pll with hwfsm support */ @@ -114,29 +124,103 @@ static int wait_for_pll_offline(struct clk_alpha_pll *pll, u32 mask) #define PLL_OFFLINE_ACK BIT(28) #define PLL_ACTIVE_FLAG BIT(30) +static void clk_alpha_set_fsm_mode(struct clk_alpha_pll *pll) +{ + u32 val; + + regmap_read(pll->clkr.regmap, pll->offset + PLL_MODE, &val); + + /* De-assert reset to FSM */ + val &= ~PLL_VOTE_FSM_RESET; + + /* Program bias count */ + val &= ~(PLL_BIAS_COUNT_MASK << PLL_BIAS_COUNT_SHIFT); + val |= PLL_BIAS_COUNT_VAL << PLL_BIAS_COUNT_SHIFT; + + /* Program lock count */ + val &= ~(PLL_LOCK_COUNT_MASK << PLL_LOCK_COUNT_SHIFT); + val |= PLL_LOCK_COUNT_VAL << PLL_LOCK_COUNT_SHIFT; + + /* Enable PLL FSM voting */ + val |= PLL_VOTE_FSM_ENA; + + regmap_write(pll->clkr.regmap, pll->offset + PLL_MODE, val); +} + void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap, - const struct pll_config *config) + const struct pll_config *config) { u32 val, mask; - regmap_write(regmap, pll->offset + PLL_CONFIG_CTL, - config->config_ctl_val); + if (config->l) + regmap_write(regmap, pll->offset + PLL_L_VAL, + config->l); + if (config->alpha) + regmap_write(regmap, pll->offset + PLL_ALPHA_VAL, + config->alpha); + if (config->alpha_u) + regmap_write(regmap, pll->offset + PLL_ALPHA_VAL_U, + config->alpha_u); + if (config->config_ctl_val) + regmap_write(regmap, pll->offset + PLL_CONFIG_CTL, + config->config_ctl_val); + + if (config->main_output_mask || config->aux_output_mask || + config->aux2_output_mask || config->early_output_mask || + config->vco_val || config->alpha_en_mask) { + + val = config->main_output_mask; + val |= config->aux_output_mask; + val |= config->aux2_output_mask; + val |= config->early_output_mask; + val |= config->vco_val; + val |= config->alpha_en_mask; + + mask = config->main_output_mask; + mask |= config->aux_output_mask; + mask |= config->aux2_output_mask; + mask |= config->early_output_mask; + mask |= config->vco_mask; + mask |= config->alpha_en_mask; + + regmap_update_bits(regmap, pll->offset + PLL_USER_CTL, + mask, val); + } - val = config->main_output_mask; - val |= config->aux_output_mask; - val |= config->aux2_output_mask; - val |= config->early_output_mask; - val |= config->post_div_val; + if (config->post_div_mask) { + mask = config->post_div_mask; + val = config->post_div_val; + regmap_update_bits(regmap, pll->offset + PLL_USER_CTL, + mask, val); + } - mask = config->main_output_mask; - mask |= config->aux_output_mask; - mask |= config->aux2_output_mask; - mask |= config->early_output_mask; - mask |= config->post_div_mask; + /* Do not bypass the latch interface */ + if (pll->flags & SUPPORTS_SLEW) + regmap_update_bits(regmap, pll->offset + PLL_USER_CTL_U, + PLL_LATCH_INTERFACE, (u32)~PLL_LATCH_INTERFACE); - regmap_update_bits(regmap, pll->offset + PLL_USER_CTL, mask, val); + if (pll->flags & SUPPORTS_DYNAMIC_UPDATE) { + regmap_update_bits(regmap, pll->offset + PLL_MODE, + PLL_HW_UPDATE_LOGIC_BYPASS, + PLL_HW_UPDATE_LOGIC_BYPASS); + } - return; + if (config->test_ctl_lo_mask) { + mask = config->test_ctl_lo_mask; + val = config->test_ctl_lo_val; + regmap_update_bits(regmap, pll->offset + PLL_TEST_CTL, + mask, val); + } + + if (config->test_ctl_hi_mask) { + mask = config->test_ctl_hi_mask; + val = config->test_ctl_hi_val; + regmap_update_bits(regmap, pll->offset + PLL_TEST_CTL_U, + mask, val); + } + + if (pll->flags & SUPPORTS_FSM_MODE) + clk_alpha_set_fsm_mode(pll); } static int clk_alpha_pll_hwfsm_enable(struct clk_hw *hw) @@ -190,8 +274,9 @@ static void clk_alpha_pll_hwfsm_disable(struct clk_hw *hw) PLL_FSM_ENA, 0); if (ret) return; + wait_for_pll_disable(pll, PLL_ACTIVE_FLAG); - return; + } static int clk_alpha_pll_enable(struct clk_hw *hw) @@ -201,7 +286,6 @@ static int clk_alpha_pll_enable(struct clk_hw *hw) u32 val, mask, off; off = pll->offset; - mask = PLL_OUTCTRL | PLL_RESET_N | PLL_BYPASSNL; ret = regmap_read(pll->clkr.regmap, off + PLL_MODE, &val); if (ret) @@ -339,7 +423,53 @@ clk_alpha_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) a >>= ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH; } - return alpha_pll_calc_rate(prate, l, a); + ctl >>= PLL_POST_DIV_SHIFT; + ctl &= PLL_POST_DIV_MASK; + + return alpha_pll_calc_rate(prate, l, a) >> fls(ctl); +} + +static int clk_alpha_pll_dynamic_update(struct clk_alpha_pll *pll) +{ + int ret; + + /* Latch the input to the PLL */ + regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_MODE, + PLL_UPDATE, PLL_UPDATE); + + /* Wait for 2 reference cycle before checking ACK bit */ + udelay(1); + + ret = wait_for_pll_latch_ack(pll, PLL_ACK_LATCH); + if (ret) + return ret; + + /* Return latch input to 0 */ + regmap_update_bits(pll->clkr.regmap, pll->offset + PLL_MODE, + PLL_UPDATE, (u32)~PLL_UPDATE); + + ret = wait_for_pll_enable(pll, PLL_LOCK_DET); + if (ret) + return ret; + + return 0; +} + +static const struct pll_vco_data + *find_vco_data(const struct pll_vco_data *data, + unsigned long rate, size_t size) +{ + int i; + + if (!data) + return NULL; + + for (i = 0; i < size; i++) { + if (rate == data[i].freq) + return &data[i]; + } + + return &data[i - 1]; } static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, @@ -347,16 +477,36 @@ static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, { struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); const struct pll_vco *vco; + const struct pll_vco_data *data; + bool is_enabled; u32 l, off = pll->offset; u64 a; + unsigned long rrate; + + rrate = alpha_pll_round_rate(rate, prate, &l, &a); - rate = alpha_pll_round_rate(rate, prate, &l, &a); - vco = alpha_pll_find_vco(pll, rate); + if (rrate != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + vco = alpha_pll_find_vco(pll, rrate); if (!vco) { pr_err("alpha pll not in a valid vco range\n"); return -EINVAL; } + is_enabled = clk_hw_is_enabled(hw); + + /* + * For PLLs that do not support dynamic programming (dynamic_update + * is not set), ensure PLL is off before changing rate. For + * optimization reasons, assume no downstream clock is actively + * using it. + */ + if (is_enabled && !(pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + hw->init->ops->disable(hw); + a <<= (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); regmap_write(pll->clkr.regmap, off + PLL_L_VAL, l); @@ -367,9 +517,27 @@ static int clk_alpha_pll_set_rate(struct clk_hw *hw, unsigned long rate, PLL_VCO_MASK << PLL_VCO_SHIFT, vco->val << PLL_VCO_SHIFT); + data = find_vco_data(pll->vco_data, rate, pll->num_vco_data); + if (data) { + if (data->freq == rate) + regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL, + PLL_POST_DIV_MASK << PLL_POST_DIV_SHIFT, + data->post_div_val << PLL_POST_DIV_SHIFT); + else + regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL, + PLL_POST_DIV_MASK << PLL_POST_DIV_SHIFT, + 0x0 << PLL_VCO_SHIFT); + } + regmap_update_bits(pll->clkr.regmap, off + PLL_USER_CTL, PLL_ALPHA_EN, PLL_ALPHA_EN); + if (is_enabled && (pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + clk_alpha_pll_dynamic_update(pll); + + if (is_enabled && !(pll->flags & SUPPORTS_DYNAMIC_UPDATE)) + hw->init->ops->enable(hw); + return 0; } @@ -381,6 +549,9 @@ static long clk_alpha_pll_round_rate(struct clk_hw *hw, unsigned long rate, u64 a; unsigned long min_freq, max_freq; + if (rate < pll->min_supported_freq) + return pll->min_supported_freq; + rate = alpha_pll_round_rate(rate, *prate, &l, &a); if (alpha_pll_find_vco(pll, rate)) return rate; diff --git a/drivers/clk/qcom/clk-alpha-pll.h b/drivers/clk/qcom/clk-alpha-pll.h index 7718bd5beefc..9b1d3ee61cac 100644 --- a/drivers/clk/qcom/clk-alpha-pll.h +++ b/drivers/clk/qcom/clk-alpha-pll.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -18,6 +18,11 @@ #include "clk-regmap.h" #include "clk-pll.h" +struct pll_vco_data { + unsigned long freq; + u8 post_div_val; +}; + struct pll_vco { unsigned long min_freq; unsigned long max_freq; @@ -28,21 +33,35 @@ struct pll_vco { * struct clk_alpha_pll - phase locked loop (PLL) * @offset: base address of registers * @vco_table: array of VCO settings + * @vco_data: array of VCO data settings like post div * @clkr: regmap clock handle */ struct clk_alpha_pll { u32 offset; + struct pll_config *config; const struct pll_vco *vco_table; size_t num_vco; + const struct pll_vco_data *vco_data; + size_t num_vco_data; + + u8 flags; +#define SUPPORTS_FSM_MODE BIT(0) + /* some PLLs support dynamically updating their rate + * without disabling the PLL first. Set this flag + * to enable this support. + */ +#define SUPPORTS_DYNAMIC_UPDATE BIT(1) +#define SUPPORTS_SLEW BIT(2) + struct clk_regmap clkr; - u32 config_ctl_val; #define PLLOUT_MAIN BIT(0) #define PLLOUT_AUX BIT(1) #define PLLOUT_AUX2 BIT(2) #define PLLOUT_EARLY BIT(3) u32 pllout_flags; + unsigned long min_supported_freq; }; /** diff --git a/drivers/clk/qcom/clk-pll.h b/drivers/clk/qcom/clk-pll.h index 1e64fe9326d6..4f779fda785b 100644 --- a/drivers/clk/qcom/clk-pll.h +++ b/drivers/clk/qcom/clk-pll.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2013, 2016 The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -70,6 +70,8 @@ struct pll_config { u16 l; u32 m; u32 n; + u32 alpha; + u32 alpha_u; u32 vco_val; u32 vco_mask; u32 pre_div_val; @@ -77,11 +79,16 @@ struct pll_config { u32 post_div_val; u32 post_div_mask; u32 mn_ena_mask; + u32 alpha_en_mask; u32 main_output_mask; u32 aux_output_mask; u32 aux2_output_mask; u32 early_output_mask; u32 config_ctl_val; + u32 test_ctl_lo_val; + u32 test_ctl_lo_mask; + u32 test_ctl_hi_val; + u32 test_ctl_hi_mask; }; void clk_pll_configure_sr(struct clk_pll *pll, struct regmap *regmap, diff --git a/drivers/clk/qcom/gpucc-msmfalcon.c b/drivers/clk/qcom/gpucc-msmfalcon.c index f194abb471cd..fe7cff443250 100644 --- a/drivers/clk/qcom/gpucc-msmfalcon.c +++ b/drivers/clk/qcom/gpucc-msmfalcon.c @@ -35,8 +35,8 @@ #define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } #define F_GFX(f, s, h, m, n, sf) { (f), (s), (2 * (h) - 1), (m), (n), (sf) } -static DEFINE_VDD_REGULATORS(vdd_dig, VDD_DIG_NUM, 1, vdd_corner, NULL); -static DEFINE_VDD_REGULATORS(vdd_mx, VDD_DIG_NUM, 1, vdd_corner, NULL); +static DEFINE_VDD_REGULATORS(vdd_dig, VDD_DIG_NUM, 1, vdd_corner); +static DEFINE_VDD_REGULATORS(vdd_mx, VDD_DIG_NUM, 1, vdd_corner); static DEFINE_VDD_REGS_INIT(vdd_gfx, 1); enum { @@ -181,6 +181,7 @@ static struct clk_init_data gpu_clks_init[] = { * | 465000000 | 930000000 | 1 | 2 | * | 588000000 | 1176000000 | 1 | 2 | * | 647000000 | 1294000000 | 1 | 2 | + * | 700000000 | 1400000000 | 1 | 2 | * | 750000000 | 1500000000 | 1 | 2 | * ==================================================== */ @@ -193,6 +194,7 @@ static const struct freq_tbl ftbl_gfx3d_clk_src[] = { F_GFX(465000000, 0, 2, 0, 0, 930000000), F_GFX(588000000, 0, 2, 0, 0, 1176000000), F_GFX(647000000, 0, 2, 0, 0, 1294000000), + F_GFX(700000000, 0, 2, 0, 0, 1400000000), F_GFX(750000000, 0, 2, 0, 0, 1500000000), { } }; @@ -376,9 +378,9 @@ static int of_get_fmax_vdd_class(struct platform_device *pdev, if (!vdd->vdd_uv) return -ENOMEM; - gpu_clks_init[index].fmax = devm_kzalloc(&pdev->dev, prop_len * + gpu_clks_init[index].rate_max = devm_kzalloc(&pdev->dev, prop_len * sizeof(unsigned long), GFP_KERNEL); - if (!gpu_clks_init[index].fmax) + if (!gpu_clks_init[index].rate_max) return -ENOMEM; array = devm_kzalloc(&pdev->dev, prop_len * sizeof(u32) * num, @@ -388,7 +390,7 @@ static int of_get_fmax_vdd_class(struct platform_device *pdev, of_property_read_u32_array(of, prop_name, array, prop_len * num); for (i = 0; i < prop_len; i++) { - gpu_clks_init[index].fmax[i] = array[num * i]; + gpu_clks_init[index].rate_max[i] = array[num * i]; for (j = 1; j < num; j++) { vdd->vdd_uv[(num - 1) * i + (j - 1)] = array[num * i + j]; @@ -398,7 +400,7 @@ static int of_get_fmax_vdd_class(struct platform_device *pdev, devm_kfree(&pdev->dev, array); vdd->num_levels = prop_len; vdd->cur_level = prop_len; - gpu_clks_init[index].num_fmax = prop_len; + gpu_clks_init[index].num_rate_max = prop_len; return 0; } |
