diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/clk.c | 18 | ||||
-rw-r--r-- | drivers/clk/msm/clock-local2.c | 37 | ||||
-rw-r--r-- | drivers/clk/msm/clock-osm.c | 180 | ||||
-rw-r--r-- | drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c | 240 | ||||
-rw-r--r-- | drivers/clk/msm/mdss/mdss-hdmi-pll.h | 16 | ||||
-rw-r--r-- | drivers/clk/msm/mdss/mdss-pll.c | 14 | ||||
-rw-r--r-- | drivers/clk/msm/mdss/mdss-pll.h | 5 | ||||
-rw-r--r-- | drivers/clk/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-cpu-osm.c | 24 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-regmap-mux-div.c | 263 | ||||
-rw-r--r-- | drivers/clk/qcom/clk-regmap-mux-div.h | 66 |
12 files changed, 631 insertions, 235 deletions
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 37f67c77fe7c..0082b30a66c4 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -78,7 +78,9 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_ARCH_OMAP2PLUS) += ti/ obj-$(CONFIG_ARCH_U8500) += ux500/ obj-$(CONFIG_COMMON_CLK_VERSATILE) += versatile/ +ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_X86) += x86/ +endif obj-$(CONFIG_ARCH_ZX) += zte/ obj-$(CONFIG_ARCH_ZYNQ) += zynq/ obj-$(CONFIG_H8300) += h8300/ diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 4996f4f312f4..73d65813de8b 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -1751,6 +1751,15 @@ static int clk_change_rate(struct clk_core *core) else if (core->parent) best_parent_rate = core->parent->rate; + trace_clk_set_rate(core, core->new_rate); + + /* Enforce vdd requirements for new frequency. */ + if (core->prepare_count) { + rc = clk_vote_rate_vdd(core, core->new_rate); + if (rc) + goto out; + } + if (core->new_parent && core->new_parent != core->parent) { old_parent = __clk_set_parent_before(core, core->new_parent); trace_clk_set_parent(core, core->new_parent); @@ -1768,15 +1777,6 @@ static int clk_change_rate(struct clk_core *core) __clk_set_parent_after(core, core->new_parent, old_parent); } - trace_clk_set_rate(core, core->new_rate); - - /* Enforce vdd requirements for new frequency. */ - if (core->prepare_count) { - rc = clk_vote_rate_vdd(core, core->new_rate); - if (rc) - goto out; - } - if (!skip_set_rate && core->ops->set_rate) { rc = core->ops->set_rate(core->hw, core->new_rate, best_parent_rate); diff --git a/drivers/clk/msm/clock-local2.c b/drivers/clk/msm/clock-local2.c index 66be713dffea..40d8d12cda82 100644 --- a/drivers/clk/msm/clock-local2.c +++ b/drivers/clk/msm/clock-local2.c @@ -1339,34 +1339,6 @@ static struct frac_entry frac_table_810m[] = { /* Link rate of 162M */ {0, 0}, }; -static bool is_same_rcg_config(struct rcg_clk *rcg, struct clk_freq_tbl *freq, - bool has_mnd) -{ - u32 cfg; - - /* RCG update pending */ - if (readl_relaxed(CMD_RCGR_REG(rcg)) & CMD_RCGR_CONFIG_DIRTY_MASK) - return false; - if (has_mnd) - if (readl_relaxed(M_REG(rcg)) != freq->m_val || - readl_relaxed(N_REG(rcg)) != freq->n_val || - readl_relaxed(D_REG(rcg)) != freq->d_val) - return false; - /* - * Both 0 and 1 represent same divider value in HW. - * Always use 0 to simplify comparison. - */ - if ((freq->div_src_val & CFG_RCGR_DIV_MASK) == 1) - freq->div_src_val &= ~CFG_RCGR_DIV_MASK; - cfg = readl_relaxed(CFG_RCGR_REG(rcg)); - if ((cfg & CFG_RCGR_DIV_MASK) == 1) - cfg &= ~CFG_RCGR_DIV_MASK; - if (cfg != freq->div_src_val) - return false; - - return true; -} - static int set_rate_edp_pixel(struct clk *clk, unsigned long rate) { struct rcg_clk *rcg = to_rcg_clk(clk); @@ -1404,8 +1376,7 @@ static int set_rate_edp_pixel(struct clk *clk, unsigned long rate) pixel_freq->d_val = ~frac->den; } spin_lock_irqsave(&local_clock_reg_lock, flags); - if (!is_same_rcg_config(rcg, pixel_freq, true)) - __set_rate_mnd(rcg, pixel_freq); + __set_rate_mnd(rcg, pixel_freq); spin_unlock_irqrestore(&local_clock_reg_lock, flags); return 0; } @@ -1466,8 +1437,7 @@ static int set_rate_byte(struct clk *clk, unsigned long rate) byte_freq->div_src_val |= BVAL(4, 0, div); spin_lock_irqsave(&local_clock_reg_lock, flags); - if (!is_same_rcg_config(rcg, byte_freq, false)) - __set_rate_hid(rcg, byte_freq); + __set_rate_hid(rcg, byte_freq); spin_unlock_irqrestore(&local_clock_reg_lock, flags); return 0; @@ -1788,8 +1758,7 @@ static int rcg_clk_set_rate_dp(struct clk *clk, unsigned long rate) } spin_lock_irqsave(&local_clock_reg_lock, flags); - if (!is_same_rcg_config(rcg, freq_tbl, true)) - __set_rate_mnd(rcg, freq_tbl); + __set_rate_mnd(rcg, freq_tbl); spin_unlock_irqrestore(&local_clock_reg_lock, flags); return 0; } diff --git a/drivers/clk/msm/clock-osm.c b/drivers/clk/msm/clock-osm.c index 9d9aa61c480a..72a75873b810 100644 --- a/drivers/clk/msm/clock-osm.c +++ b/drivers/clk/msm/clock-osm.c @@ -606,6 +606,83 @@ static int clk_osm_acd_auto_local_write_reg(struct clk_osm *c, u32 mask) return 0; } +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); + + /* ACD has been initialized and enabled for this cluster */ + c->acd_init = false; + return 0; +} + static inline int clk_osm_count_ns(struct clk_osm *c, u64 nsec) { u64 temp; @@ -729,6 +806,17 @@ static int clk_osm_set_rate(struct clk *c, unsigned long rate) static int clk_osm_enable(struct clk *c) { struct clk_osm *cpuclk = to_clk_osm(c); + int rc; + + rc = clk_osm_acd_init(cpuclk); + if (rc) { + pr_err("Failed to initialize ACD for cluster %d, rc=%d\n", + cpuclk->cluster_num, rc); + return rc; + } + + /* Wait for 5 usecs before enabling OSM */ + udelay(5); clk_osm_write_reg(cpuclk, 1, ENABLE_REG); @@ -1541,8 +1629,8 @@ static int clk_osm_setup_hw_table(struct clk_osm *c) { struct osm_entry *entry = c->osm_table; int i; - u32 freq_val, volt_val, override_val, spare_val; - u32 table_entry_offset, last_spare, last_virtual_corner = 0; + u32 freq_val = 0, volt_val = 0, override_val = 0, spare_val = 0; + u32 table_entry_offset = 0, last_spare = 0, last_virtual_corner = 0; for (i = 0; i < OSM_TABLE_SIZE; i++) { if (i < c->num_entries) { @@ -2758,7 +2846,7 @@ static ssize_t debugfs_trace_method_get(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct clk_osm *c = file->private_data; - int len, rc; + int len = 0, rc; if (IS_ERR(file) || file == NULL) { pr_err("input error %ld\n", PTR_ERR(file)); @@ -3105,81 +3193,6 @@ 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; @@ -3362,17 +3375,6 @@ 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-hdmi-pll-8998.c b/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c index c60c4864442f..c4215f30acce 100644 --- a/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c +++ b/drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017, 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 @@ -29,6 +29,10 @@ #define _W(x, y, z) MDSS_PLL_REG_W(x, y, z) #define _R(x, y) MDSS_PLL_REG_R(x, y) +/* CONSTANTS */ +#define HDMI_VERSION_8998_3_3 1 +#define HDMI_VERSION_8998_1_8 2 + /* PLL REGISTERS */ #define FREQ_UPDATE (0x008) #define BIAS_EN_CLKBUFLR_EN (0x034) @@ -277,7 +281,7 @@ find_optimal_index: } static int hdmi_8998_config_phy(unsigned long rate, - struct hdmi_8998_reg_cfg *cfg) + struct hdmi_8998_reg_cfg *cfg, u32 ver) { u64 const high_freq_bit_clk_threshold = 3400000000UL; u64 const dig_freq_bit_clk_threshold = 1500000000UL; @@ -359,6 +363,7 @@ static int hdmi_8998_config_phy(unsigned long rate, pr_debug("INTEGLOOP_GAIN = %llu\n", integloop_gain); pr_debug("CMP_RNG = %llu\n", cmp_rng); pr_debug("PLL_CMP = %llu\n", pll_cmp); + pr_debug("VER=%d\n", ver); cfg->svs_mode_clk_sel = (digclk_divsel & 0xFF); cfg->hsclk_sel = (0x20 | hsclk_sel); @@ -382,82 +387,105 @@ static int hdmi_8998_config_phy(unsigned long rate, cfg->core_clk_en = 0x2C; cfg->coreclk_div_mode0 = 0x5; cfg->phy_mode = (tmds_bclk_ratio ? 0x5 : 0x4); + /* V1P8_SEL */ + if (ver == HDMI_VERSION_8998_1_8) + cfg->phy_mode |= 1 << 4; cfg->ssc_en_center = 0x0; - if (bclk > high_freq_bit_clk_threshold) { - cfg->l0_tx_drv_lvl = 0xA; - cfg->l0_tx_emp_post1_lvl = 0x3; - cfg->l1_tx_drv_lvl = 0xA; - cfg->l1_tx_emp_post1_lvl = 0x3; - cfg->l2_tx_drv_lvl = 0xA; - cfg->l2_tx_emp_post1_lvl = 0x3; - cfg->l3_tx_drv_lvl = 0x8; - cfg->l3_tx_emp_post1_lvl = 0x3; - cfg->l0_pre_driver_1 = 0x0; - cfg->l0_pre_driver_2 = 0x1C; - cfg->l1_pre_driver_1 = 0x0; - cfg->l1_pre_driver_2 = 0x1C; - cfg->l2_pre_driver_1 = 0x0; - cfg->l2_pre_driver_2 = 0x1C; - cfg->l3_pre_driver_1 = 0x0; - cfg->l3_pre_driver_2 = 0x0; - } else if (bclk > dig_freq_bit_clk_threshold) { - cfg->l0_tx_drv_lvl = 0x9; - cfg->l0_tx_emp_post1_lvl = 0x3; - cfg->l1_tx_drv_lvl = 0x9; - cfg->l1_tx_emp_post1_lvl = 0x3; - cfg->l2_tx_drv_lvl = 0x9; - cfg->l2_tx_emp_post1_lvl = 0x3; - cfg->l3_tx_drv_lvl = 0x8; - cfg->l3_tx_emp_post1_lvl = 0x3; - cfg->l0_pre_driver_1 = 0x0; - cfg->l0_pre_driver_2 = 0x16; - cfg->l1_pre_driver_1 = 0x0; - cfg->l1_pre_driver_2 = 0x16; - cfg->l2_pre_driver_1 = 0x0; - cfg->l2_pre_driver_2 = 0x16; - cfg->l3_pre_driver_1 = 0x0; - cfg->l3_pre_driver_2 = 0x0; - } else if (bclk > mid_freq_bit_clk_threshold) { - cfg->l0_tx_drv_lvl = 0x9; - cfg->l0_tx_emp_post1_lvl = 0x3; - cfg->l1_tx_drv_lvl = 0x9; - cfg->l1_tx_emp_post1_lvl = 0x3; - cfg->l2_tx_drv_lvl = 0x9; - cfg->l2_tx_emp_post1_lvl = 0x3; - cfg->l3_tx_drv_lvl = 0x8; - cfg->l3_tx_emp_post1_lvl = 0x3; - cfg->l0_pre_driver_1 = 0x0; - cfg->l0_pre_driver_2 = 0x0E; - cfg->l1_pre_driver_1 = 0x0; - cfg->l1_pre_driver_2 = 0x0E; - cfg->l2_pre_driver_1 = 0x0; - cfg->l2_pre_driver_2 = 0x0E; - cfg->l3_pre_driver_1 = 0x0; - cfg->l3_pre_driver_2 = 0x0; + if (ver == HDMI_VERSION_8998_3_3) { + if (bclk > high_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0xA; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0xA; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0xA; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x1C; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x1C; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x1C; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > dig_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x16; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x16; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x16; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > mid_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x0E; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x0E; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x0E; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else { + cfg->l0_tx_drv_lvl = 0x0; + cfg->l0_tx_emp_post1_lvl = 0x0; + cfg->l1_tx_drv_lvl = 0x0; + cfg->l1_tx_emp_post1_lvl = 0x0; + cfg->l2_tx_drv_lvl = 0x0; + cfg->l2_tx_emp_post1_lvl = 0x0; + cfg->l3_tx_drv_lvl = 0x0; + cfg->l3_tx_emp_post1_lvl = 0x0; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x01; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x01; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x01; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } } else { - cfg->l0_tx_drv_lvl = 0x0; - cfg->l0_tx_emp_post1_lvl = 0x0; - cfg->l1_tx_drv_lvl = 0x0; - cfg->l1_tx_emp_post1_lvl = 0x0; - cfg->l2_tx_drv_lvl = 0x0; - cfg->l2_tx_emp_post1_lvl = 0x0; - cfg->l3_tx_drv_lvl = 0x0; + cfg->l0_tx_drv_lvl = 0xF; + cfg->l0_tx_emp_post1_lvl = 0x5; + cfg->l1_tx_drv_lvl = 0xF; + cfg->l1_tx_emp_post1_lvl = 0x2; + cfg->l2_tx_drv_lvl = 0xF; + cfg->l2_tx_emp_post1_lvl = 0x2; + cfg->l3_tx_drv_lvl = 0xF; cfg->l3_tx_emp_post1_lvl = 0x0; cfg->l0_pre_driver_1 = 0x0; - cfg->l0_pre_driver_2 = 0x01; + cfg->l0_pre_driver_2 = 0x1E; cfg->l1_pre_driver_1 = 0x0; - cfg->l1_pre_driver_2 = 0x01; + cfg->l1_pre_driver_2 = 0x1E; cfg->l2_pre_driver_1 = 0x0; - cfg->l2_pre_driver_2 = 0x01; + cfg->l2_pre_driver_2 = 0x1E; cfg->l3_pre_driver_1 = 0x0; - cfg->l3_pre_driver_2 = 0x0; + cfg->l3_pre_driver_2 = 0x10; } return rc; } -static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate) +static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate, + u32 ver) { int rc = 0; struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); @@ -465,7 +493,7 @@ static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate) struct hdmi_8998_reg_cfg cfg = {0}; void __iomem *phy = io->phy_base, *pll = io->pll_base; - rc = hdmi_8998_config_phy(rate, &cfg); + rc = hdmi_8998_config_phy(rate, &cfg, ver); if (rc) { pr_err("rate calculation failed\n, rc=%d", rc); return rc; @@ -699,7 +727,7 @@ static int hdmi_8998_vco_get_lock_range(struct clk *c, } static int hdmi_8998_vco_rate_atomic_update(struct clk *c, - unsigned long rate) + unsigned long rate, u32 ver) { struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); struct mdss_pll_resources *io = vco->priv; @@ -707,7 +735,7 @@ static int hdmi_8998_vco_rate_atomic_update(struct clk *c, struct hdmi_8998_reg_cfg cfg = {0}; int rc = 0; - rc = hdmi_8998_config_phy(rate, &cfg); + rc = hdmi_8998_config_phy(rate, &cfg, ver); if (rc) { pr_err("rate calculation failed\n, rc=%d", rc); goto end; @@ -728,7 +756,7 @@ end: return rc; } -static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate) +static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate, u32 ver) { struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); struct mdss_pll_resources *io = vco->priv; @@ -767,9 +795,9 @@ static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate) set_power_dwn = 1; if (atomic_update) - rc = hdmi_8998_vco_rate_atomic_update(c, rate); + rc = hdmi_8998_vco_rate_atomic_update(c, rate, ver); else - rc = hdmi_8998_pll_set_clk_rate(c, rate); + rc = hdmi_8998_pll_set_clk_rate(c, rate, ver); if (rc) { pr_err("failed to set clk rate\n"); @@ -806,7 +834,7 @@ static long hdmi_8998_vco_round_rate(struct clk *c, unsigned long rate) return rrate; } -static int hdmi_8998_vco_prepare(struct clk *c) +static int hdmi_8998_vco_prepare(struct clk *c, u32 ver) { struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); struct mdss_pll_resources *io = vco->priv; @@ -824,7 +852,7 @@ static int hdmi_8998_vco_prepare(struct clk *c) } if (!vco->rate_set && vco->rate) { - rc = hdmi_8998_pll_set_clk_rate(c, vco->rate); + rc = hdmi_8998_pll_set_clk_rate(c, vco->rate, ver); if (rc) { pr_err("set rate failed, rc=%d\n", rc); goto error; @@ -902,10 +930,38 @@ static enum handoff hdmi_8998_vco_handoff(struct clk *c) return ret; } -static struct clk_ops hdmi_8998_vco_clk_ops = { - .set_rate = hdmi_8998_vco_set_rate, +static int hdmi_8998_3p3_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8998_vco_set_rate(c, rate, HDMI_VERSION_8998_3_3); +} + +static int hdmi_8998_1p8_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8998_vco_set_rate(c, rate, HDMI_VERSION_8998_1_8); +} + +static int hdmi_8998_3p3_vco_prepare(struct clk *c) +{ + return hdmi_8998_vco_prepare(c, HDMI_VERSION_8998_3_3); +} + +static int hdmi_8998_1p8_vco_prepare(struct clk *c) +{ + return hdmi_8998_vco_prepare(c, HDMI_VERSION_8998_1_8); +} + +static struct clk_ops hdmi_8998_3p3_vco_clk_ops = { + .set_rate = hdmi_8998_3p3_vco_set_rate, + .round_rate = hdmi_8998_vco_round_rate, + .prepare = hdmi_8998_3p3_vco_prepare, + .unprepare = hdmi_8998_vco_unprepare, + .handoff = hdmi_8998_vco_handoff, +}; + +static struct clk_ops hdmi_8998_1p8_vco_clk_ops = { + .set_rate = hdmi_8998_1p8_vco_set_rate, .round_rate = hdmi_8998_vco_round_rate, - .prepare = hdmi_8998_vco_prepare, + .prepare = hdmi_8998_1p8_vco_prepare, .unprepare = hdmi_8998_vco_unprepare, .handoff = hdmi_8998_vco_handoff, }; @@ -915,7 +971,7 @@ static struct hdmi_pll_vco_clk hdmi_vco_clk = { .max_rate = HDMI_VCO_MAX_RATE_HZ, .c = { .dbg_name = "hdmi_8998_vco_clk", - .ops = &hdmi_8998_vco_clk_ops, + .ops = &hdmi_8998_3p3_vco_clk_ops, CLK_INIT(hdmi_vco_clk.c), }, }; @@ -925,7 +981,7 @@ static struct clk_lookup hdmipllcc_8998[] = { }; int hdmi_8998_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res) + struct mdss_pll_resources *pll_res, u32 ver) { int rc = 0; @@ -936,8 +992,20 @@ int hdmi_8998_pll_clock_register(struct platform_device *pdev, hdmi_vco_clk.priv = pll_res; + switch (ver) { + case HDMI_VERSION_8998_3_3: + hdmi_vco_clk.c.ops = &hdmi_8998_3p3_vco_clk_ops; + break; + case HDMI_VERSION_8998_1_8: + hdmi_vco_clk.c.ops = &hdmi_8998_1p8_vco_clk_ops; + break; + default: + hdmi_vco_clk.c.ops = &hdmi_8998_3p3_vco_clk_ops; + break; + }; + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8998, - ARRAY_SIZE(hdmipllcc_8998)); + ARRAY_SIZE(hdmipllcc_8998)); if (rc) { pr_err("clock register failed, rc=%d\n", rc); return rc; @@ -945,3 +1013,17 @@ int hdmi_8998_pll_clock_register(struct platform_device *pdev, return rc; } + +int hdmi_8998_3p3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8998_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8998_3_3); +} + +int hdmi_8998_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8998_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8998_1_8); +} diff --git a/drivers/clk/msm/mdss/mdss-hdmi-pll.h b/drivers/clk/msm/mdss/mdss-hdmi-pll.h index 19f9b925644a..9e6a39481286 100644 --- a/drivers/clk/msm/mdss/mdss-hdmi-pll.h +++ b/drivers/clk/msm/mdss/mdss-hdmi-pll.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012-2017, 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 @@ -45,17 +45,19 @@ int hdmi_20nm_pll_clock_register(struct platform_device *pdev, struct mdss_pll_resources *pll_res); int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res); + struct mdss_pll_resources *pll_res); int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res); + struct mdss_pll_resources *pll_res); int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res); + struct mdss_pll_resources *pll_res); int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res); + struct mdss_pll_resources *pll_res); -int hdmi_8998_pll_clock_register(struct platform_device *pdev, - struct mdss_pll_resources *pll_res); +int hdmi_8998_3p3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +int hdmi_8998_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); #endif diff --git a/drivers/clk/msm/mdss/mdss-pll.c b/drivers/clk/msm/mdss/mdss-pll.c index 01ce2b1817f2..b5c98774ba92 100644 --- a/drivers/clk/msm/mdss/mdss-pll.c +++ b/drivers/clk/msm/mdss/mdss-pll.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2017, 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 @@ -149,7 +149,9 @@ static int mdss_pll_resource_parse(struct platform_device *pdev, "qcom,mdss_hdmi_pll_8996_v3_1p8")) { pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V3_1_8; } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8998")) { - pll_res->pll_interface_type = MDSS_HDMI_PLL_8998; + pll_res->pll_interface_type = MDSS_HDMI_PLL_8998_3_3; + } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8998_1p8")) { + pll_res->pll_interface_type = MDSS_HDMI_PLL_8998_1_8; } else { goto err; } @@ -193,8 +195,11 @@ static int mdss_pll_clock_register(struct platform_device *pdev, case MDSS_HDMI_PLL_8996_V3_1_8: rc = hdmi_8996_v3_1p8_pll_clock_register(pdev, pll_res); break; - case MDSS_HDMI_PLL_8998: - rc = hdmi_8998_pll_clock_register(pdev, pll_res); + case MDSS_HDMI_PLL_8998_3_3: + rc = hdmi_8998_3p3_pll_clock_register(pdev, pll_res); + break; + case MDSS_HDMI_PLL_8998_1_8: + rc = hdmi_8998_1p8_pll_clock_register(pdev, pll_res); break; case MDSS_UNKNOWN_PLL: default: @@ -401,6 +406,7 @@ static const struct of_device_id mdss_pll_dt_match[] = { {.compatible = "qcom,mdss_hdmi_pll_8996_v3_1p8"}, {.compatible = "qcom,mdss_dp_pll_8998"}, {.compatible = "qcom,mdss_hdmi_pll_8998"}, + {.compatible = "qcom,mdss_hdmi_pll_8998_1p8"}, {} }; diff --git a/drivers/clk/msm/mdss/mdss-pll.h b/drivers/clk/msm/mdss/mdss-pll.h index 8fffaf30d4ec..0120d71f0daf 100644 --- a/drivers/clk/msm/mdss/mdss-pll.h +++ b/drivers/clk/msm/mdss/mdss-pll.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2017, 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 @@ -37,7 +37,8 @@ enum { MDSS_HDMI_PLL_8996_V2, MDSS_HDMI_PLL_8996_V3, MDSS_HDMI_PLL_8996_V3_1_8, - MDSS_HDMI_PLL_8998, + MDSS_HDMI_PLL_8998_3_3, + MDSS_HDMI_PLL_8998_1_8, MDSS_UNKNOWN_PLL, }; diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 4c18181c047c..d3e88f40bdfd 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -9,6 +9,7 @@ clk-qcom-y += clk-rcg2.o clk-qcom-y += clk-branch.o clk-qcom-y += clk-regmap-divider.o clk-qcom-y += clk-regmap-mux.o +clk-qcom-y += clk-regmap-mux-div.o clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o clk-qcom-y += clk-hfpll.o clk-qcom-y += reset.o clk-voter.o diff --git a/drivers/clk/qcom/clk-cpu-osm.c b/drivers/clk/qcom/clk-cpu-osm.c index 8bf45f572c5e..d99e13817a29 100644 --- a/drivers/clk/qcom/clk-cpu-osm.c +++ b/drivers/clk/qcom/clk-cpu-osm.c @@ -719,9 +719,22 @@ static int clk_osm_set_rate(struct clk_hw *hw, unsigned long rate, return 0; } +static int clk_osm_acd_init(struct clk_osm *c); + static int clk_osm_enable(struct clk_hw *hw) { struct clk_osm *cpuclk = to_clk_osm(hw); + int rc; + + rc = clk_osm_acd_init(cpuclk); + if (rc) { + pr_err("Failed to initialize ACD for cluster %d, rc=%d\n", + cpuclk->cluster_num, rc); + return rc; + } + + /* Wait for 5 usecs before enabling OSM */ + udelay(5); clk_osm_write_reg(cpuclk, 1, ENABLE_REG); @@ -3272,17 +3285,6 @@ static int clk_cpu_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/qcom/clk-regmap-mux-div.c b/drivers/clk/qcom/clk-regmap-mux-div.c new file mode 100644 index 000000000000..942a68e2a650 --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2014, 2017, 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 + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/regmap.h> + +#include "clk-regmap-mux-div.h" + +#define CMD_RCGR 0x0 +#define CMD_RCGR_UPDATE BIT(0) +#define CMD_RCGR_DIRTY_CFG BIT(4) +#define CMD_RCGR_ROOT_OFF BIT(31) +#define CFG_RCGR 0x4 + +#define to_clk_regmap_mux_div(_hw) \ + container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr) + +int __mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src, u32 div) +{ + int ret, count; + u32 val, mask; + const char *name = clk_hw_get_name(&md->clkr.hw); + + val = (div << md->hid_shift) | (src << md->src_shift); + mask = ((BIT(md->hid_width) - 1) << md->hid_shift) | + ((BIT(md->src_width) - 1) << md->src_shift); + + ret = regmap_update_bits(md->clkr.regmap, CFG_RCGR + md->reg_offset, + mask, val); + if (ret) + return ret; + + ret = regmap_update_bits(md->clkr.regmap, CMD_RCGR + md->reg_offset, + CMD_RCGR_UPDATE, CMD_RCGR_UPDATE); + if (ret) + return ret; + + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, + &val); + if (ret) + return ret; + if (!(val & CMD_RCGR_UPDATE)) + return 0; + udelay(1); + } + + pr_err("%s: RCG did not update its configuration", name); + return -EBUSY; +} + +int mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, + u32 *div) +{ + int ret = 0; + u32 val, __div, __src; + const char *name = clk_hw_get_name(&md->clkr.hw); + + ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val); + if (ret) + return ret; + + if (val & CMD_RCGR_DIRTY_CFG) { + pr_err("%s: RCG configuration is pending\n", name); + return -EBUSY; + } + + ret = regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val); + if (ret) + return ret; + + __src = (val >> md->src_shift); + __src &= BIT(md->src_width) - 1; + *src = __src; + + __div = (val >> md->hid_shift); + __div &= BIT(md->hid_width) - 1; + *div = __div; + + return ret; +} + +static int mux_div_enable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_src_div(md, md->src, md->div); +} + +static inline bool is_better_rate(unsigned long req, unsigned long best, + unsigned long new) +{ + return (req <= new && new < best) || (best < req && best < new); +} + +static int mux_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + unsigned int i, div, max_div; + unsigned long actual_rate, best_rate = 0; + unsigned long req_rate = req->rate; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); + unsigned long parent_rate = clk_hw_get_rate(parent); + + max_div = BIT(md->hid_width) - 1; + for (div = 1; div < max_div; div++) { + parent_rate = mult_frac(req_rate, div, 2); + parent_rate = clk_hw_round_rate(parent, parent_rate); + actual_rate = mult_frac(parent_rate, 2, div); + + if (is_better_rate(req_rate, best_rate, actual_rate)) { + best_rate = actual_rate; + req->rate = best_rate; + req->best_parent_rate = parent_rate; + req->best_parent_hw = parent; + } + + if (actual_rate < req_rate || best_rate <= req_rate) + break; + } + } + + if (!best_rate) + return -EINVAL; + + return 0; +} + +static int __mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long prate, u32 src) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + int ret; + u32 div, max_div, best_src = 0, best_div = 0; + unsigned int i; + unsigned long actual_rate, best_rate = 0; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); + unsigned long parent_rate = clk_hw_get_rate(parent); + + max_div = BIT(md->hid_width) - 1; + for (div = 1; div < max_div; div++) { + parent_rate = mult_frac(rate, div, 2); + parent_rate = clk_hw_round_rate(parent, parent_rate); + actual_rate = mult_frac(parent_rate, 2, div); + + if (is_better_rate(rate, best_rate, actual_rate)) { + best_rate = actual_rate; + best_src = md->parent_map[i].cfg; + best_div = div - 1; + } + + if (actual_rate < rate || best_rate <= rate) + break; + } + } + + ret = __mux_div_set_src_div(md, best_src, best_div); + if (!ret) { + md->div = best_div; + md->src = best_src; + } + + return ret; +} + +static u8 mux_div_get_parent(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + const char *name = clk_hw_get_name(hw); + u32 i, div, src = 0; + + mux_div_get_src_div(md, &src, &div); + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) + if (src == md->parent_map[i].cfg) + return i; + + pr_err("%s: Can't find parent with src %d\n", name, src); + return 0; +} + +static int mux_div_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_src_div(md, md->parent_map[index].cfg, md->div); +} + +static int mux_div_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long prate) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_rate_and_parent(hw, rate, prate, md->src); +} + +static int mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long prate, u8 index) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_rate_and_parent(hw, rate, prate, + md->parent_map[index].cfg); +} + +static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + u32 div, src; + int i, num_parents = clk_hw_get_num_parents(hw); + const char *name = clk_hw_get_name(hw); + + mux_div_get_src_div(md, &src, &div); + for (i = 0; i < num_parents; i++) + if (src == md->parent_map[i].cfg) { + struct clk_hw *p = clk_hw_get_parent_by_index(hw, i); + unsigned long parent_rate = clk_hw_get_rate(p); + + return mult_frac(parent_rate, 2, div + 1); + } + + pr_err("%s: Can't find parent %d\n", name, src); + return 0; +} + +static void mux_div_disable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + __mux_div_set_src_div(md, md->safe_src, md->safe_div); +} + +const struct clk_ops clk_regmap_mux_div_ops = { + .enable = mux_div_enable, + .disable = mux_div_disable, + .get_parent = mux_div_get_parent, + .set_parent = mux_div_set_parent, + .set_rate = mux_div_set_rate, + .set_rate_and_parent = mux_div_set_rate_and_parent, + .determine_rate = mux_div_determine_rate, + .recalc_rate = mux_div_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_regmap_mux_div_ops); diff --git a/drivers/clk/qcom/clk-regmap-mux-div.h b/drivers/clk/qcom/clk-regmap-mux-div.h new file mode 100644 index 000000000000..63a696a96033 --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2014, 2017, 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 + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QCOM_CLK_REGMAP_MUX_DIV_H__ +#define __QCOM_CLK_REGMAP_MUX_DIV_H__ + +#include <linux/clk-provider.h> +#include "clk-rcg.h" +#include "clk-regmap.h" + +/** + * struct mux_div_clk - combined mux/divider clock + * @reg_offset: offset of the mux/divider register + * @hid_width: number of bits in half integer divider + * @hid_shift: lowest bit of hid value field + * @src_width: number of bits in source select + * @src_shift: lowest bit of source select field + * @div: the divider raw configuration value + * @src: the mux index which will be used if the clock is enabled + * @safe_src: the safe source mux value we switch to, while the main PLL is + * reconfigured + * @safe_div: the safe divider value that we set, while the main PLL is + * reconfigured + * @safe_freq: When switching rates from A to B, the mux div clock will + * instead switch from A -> safe_freq -> B. This allows the + * mux_div clock to change rates while enabled, even if this + * behavior is not supported by the parent clocks. + * If changing the rate of parent A also causes the rate of + * parent B to change, then safe_freq must be defined. + * safe_freq is expected to have a source clock which is always + * on and runs at only one rate. + * @parent_map: pointer to parent_map struct + * @clkr: handle between common and hardware-specific interfaces + */ + +struct clk_regmap_mux_div { + u32 reg_offset; + u32 hid_width; + u32 hid_shift; + u32 src_width; + u32 src_shift; + u32 div; + u32 src; + u32 safe_src; + u32 safe_div; + unsigned long safe_freq; + const struct parent_map *parent_map; + struct clk_regmap clkr; +}; + +extern const struct clk_ops clk_regmap_mux_div_ops; +int __mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src, u32 div); +int mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, u32 *div); + +#endif |