summaryrefslogtreecommitdiff
path: root/drivers/clk
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/Makefile2
-rw-r--r--drivers/clk/clk.c18
-rw-r--r--drivers/clk/msm/clock-local2.c37
-rw-r--r--drivers/clk/msm/clock-osm.c180
-rw-r--r--drivers/clk/msm/mdss/mdss-hdmi-pll-8998.c240
-rw-r--r--drivers/clk/msm/mdss/mdss-hdmi-pll.h16
-rw-r--r--drivers/clk/msm/mdss/mdss-pll.c14
-rw-r--r--drivers/clk/msm/mdss/mdss-pll.h5
-rw-r--r--drivers/clk/qcom/Makefile1
-rw-r--r--drivers/clk/qcom/clk-cpu-osm.c24
-rw-r--r--drivers/clk/qcom/clk-regmap-mux-div.c263
-rw-r--r--drivers/clk/qcom/clk-regmap-mux-div.h66
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