From becf123772a9ef15823a3f495478fe68e45b5028 Mon Sep 17 00:00:00 2001 From: Lori Hikichi Date: Mon, 14 Aug 2017 12:00:38 -0700 Subject: [PATCH 1/8] clk: iproc: Allow iproc pll to runtime calculate vco parameters Add the ability for the iproc pll to calculate the pll parameters at runtime instead of only using predefined tables. This ability allows the clock users to select from the full range of vco frequencies. The old method of table based programming is retained so that existing users will retain expected behavior. The flag IPROC_CLK_PLL_CALC_PARAM will need to be set to enable the new runtime calculation method. Currently, this is only being enabled for the audio pll. This feature also revealed a problem with the driver using the round_rate api. The round_rate api does not allow for frequencies larger than 2^31 to be returned. Those large frequencies are interpreted as an error code. Therefore, we are moving to the determine_rate api which solves this problem. Signed-off-by: Simran Rai Signed-off-by: Lori Hikichi Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-cygnus.c | 25 ++------- drivers/clk/bcm/clk-iproc-pll.c | 97 ++++++++++++++++++++++++++++----- drivers/clk/bcm/clk-iproc.h | 5 ++ 3 files changed, 92 insertions(+), 35 deletions(-) diff --git a/drivers/clk/bcm/clk-cygnus.c b/drivers/clk/bcm/clk-cygnus.c index 464fdc4bc66b..b8d073e4855f 100644 --- a/drivers/clk/bcm/clk-cygnus.c +++ b/drivers/clk/bcm/clk-cygnus.c @@ -269,23 +269,10 @@ static void __init cygnus_asiu_init(struct device_node *node) } CLK_OF_DECLARE(cygnus_asiu_clk, "brcm,cygnus-asiu-clk", cygnus_asiu_init); -/* - * AUDIO PLL VCO frequency parameter table - * - * PLL output frequency = ((ndiv_int + ndiv_frac / 2^20) * - * (parent clock rate / pdiv) - * - * On Cygnus, parent is the 25MHz oscillator - */ -static const struct iproc_pll_vco_param audiopll_vco_params[] = { - /* rate (Hz) ndiv_int ndiv_frac pdiv */ - { 1354750204UL, 54, 199238, 1 }, - { 1769470191UL, 70, 816639, 1 }, -}; - static const struct iproc_pll_ctrl audiopll = { .flags = IPROC_CLK_PLL_NEEDS_SW_CFG | IPROC_CLK_PLL_HAS_NDIV_FRAC | - IPROC_CLK_PLL_USER_MODE_ON | IPROC_CLK_PLL_RESET_ACTIVE_LOW, + IPROC_CLK_PLL_USER_MODE_ON | IPROC_CLK_PLL_RESET_ACTIVE_LOW | + IPROC_CLK_PLL_CALC_PARAM, .reset = RESET_VAL(0x5c, 0, 1), .dig_filter = DF_VAL(0x48, 0, 3, 6, 4, 3, 3), .sw_ctrl = SW_CTRL_VAL(0x4, 0), @@ -300,8 +287,7 @@ static const struct iproc_pll_ctrl audiopll = { static const struct iproc_clk_ctrl audiopll_clk[] = { [BCM_CYGNUS_AUDIOPLL_CH0] = { .channel = BCM_CYGNUS_AUDIOPLL_CH0, - .flags = IPROC_CLK_AON | - IPROC_CLK_MCLK_DIV_BY_2, + .flags = IPROC_CLK_AON | IPROC_CLK_MCLK_DIV_BY_2, .enable = ENABLE_VAL(0x14, 8, 10, 9), .mdiv = REG_VAL(0x14, 0, 8), }, @@ -321,9 +307,8 @@ static const struct iproc_clk_ctrl audiopll_clk[] = { static void __init cygnus_audiopll_clk_init(struct device_node *node) { - iproc_pll_clk_setup(node, &audiopll, audiopll_vco_params, - ARRAY_SIZE(audiopll_vco_params), audiopll_clk, - ARRAY_SIZE(audiopll_clk)); + iproc_pll_clk_setup(node, &audiopll, NULL, 0, + audiopll_clk, ARRAY_SIZE(audiopll_clk)); } CLK_OF_DECLARE(cygnus_audiopll, "brcm,cygnus-audiopll", cygnus_audiopll_clk_init); diff --git a/drivers/clk/bcm/clk-iproc-pll.c b/drivers/clk/bcm/clk-iproc-pll.c index 375d8dd80d45..9514ecf319b0 100644 --- a/drivers/clk/bcm/clk-iproc-pll.c +++ b/drivers/clk/bcm/clk-iproc-pll.c @@ -95,6 +95,39 @@ struct iproc_pll { #define to_iproc_clk(hw) container_of(hw, struct iproc_clk, hw) +static int pll_calc_param(unsigned long target_rate, + unsigned long parent_rate, + struct iproc_pll_vco_param *vco_out) +{ + u64 ndiv_int, ndiv_frac, residual; + + ndiv_int = target_rate / parent_rate; + + if (!ndiv_int || (ndiv_int > 255)) + return -EINVAL; + + residual = target_rate - (ndiv_int * parent_rate); + residual <<= 20; + + /* + * Add half of the divisor so the result will be rounded to closest + * instead of rounded down. + */ + residual += (parent_rate / 2); + ndiv_frac = div64_u64((u64)residual, (u64)parent_rate); + + vco_out->ndiv_int = ndiv_int; + vco_out->ndiv_frac = ndiv_frac; + vco_out->pdiv = 1; + + vco_out->rate = vco_out->ndiv_int * parent_rate; + residual = (u64)vco_out->ndiv_frac * (u64)parent_rate; + residual >>= 20; + vco_out->rate += residual; + + return 0; +} + /* * Based on the target frequency, find a match from the VCO frequency parameter * table and return its index @@ -252,11 +285,10 @@ static void __pll_bring_out_reset(struct iproc_pll *pll, unsigned int kp, iproc_pll_write(pll, pll->control_base, reset->offset, val); } -static int pll_set_rate(struct iproc_clk *clk, unsigned int rate_index, +static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, unsigned long parent_rate) { struct iproc_pll *pll = clk->pll; - const struct iproc_pll_vco_param *vco = &pll->vco_param[rate_index]; const struct iproc_pll_ctrl *ctrl = pll->ctrl; int ka = 0, ki, kp, ret; unsigned long rate = vco->rate; @@ -431,25 +463,50 @@ static unsigned long iproc_pll_recalc_rate(struct clk_hw *hw, return clk->rate; } -static long iproc_pll_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static int iproc_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { - unsigned i; + unsigned int i; struct iproc_clk *clk = to_iproc_clk(hw); struct iproc_pll *pll = clk->pll; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + unsigned long diff, best_diff; + unsigned int best_idx = 0; + int ret; - if (rate == 0 || *parent_rate == 0 || !pll->vco_param) + if (req->rate == 0 || req->best_parent_rate == 0) return -EINVAL; + if (ctrl->flags & IPROC_CLK_PLL_CALC_PARAM) { + struct iproc_pll_vco_param vco_param; + + ret = pll_calc_param(req->rate, req->best_parent_rate, + &vco_param); + if (ret) + return ret; + + req->rate = vco_param.rate; + return 0; + } + + if (!pll->vco_param) + return -EINVAL; + + best_diff = ULONG_MAX; for (i = 0; i < pll->num_vco_entries; i++) { - if (rate <= pll->vco_param[i].rate) + diff = abs(req->rate - pll->vco_param[i].rate); + if (diff <= best_diff) { + best_diff = diff; + best_idx = i; + } + /* break now if perfect match */ + if (diff == 0) break; } - if (i == pll->num_vco_entries) - i--; + req->rate = pll->vco_param[best_idx].rate; - return pll->vco_param[i].rate; + return 0; } static int iproc_pll_set_rate(struct clk_hw *hw, unsigned long rate, @@ -457,13 +514,23 @@ static int iproc_pll_set_rate(struct clk_hw *hw, unsigned long rate, { struct iproc_clk *clk = to_iproc_clk(hw); struct iproc_pll *pll = clk->pll; + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + struct iproc_pll_vco_param vco_param; int rate_index, ret; - rate_index = pll_get_rate_index(pll, rate); - if (rate_index < 0) - return rate_index; + if (ctrl->flags & IPROC_CLK_PLL_CALC_PARAM) { + ret = pll_calc_param(rate, parent_rate, &vco_param); + if (ret) + return ret; + } else { + rate_index = pll_get_rate_index(pll, rate); + if (rate_index < 0) + return rate_index; - ret = pll_set_rate(clk, rate_index, parent_rate); + vco_param = pll->vco_param[rate_index]; + } + + ret = pll_set_rate(clk, &vco_param, parent_rate); return ret; } @@ -471,7 +538,7 @@ static const struct clk_ops iproc_pll_ops = { .enable = iproc_pll_enable, .disable = iproc_pll_disable, .recalc_rate = iproc_pll_recalc_rate, - .round_rate = iproc_pll_round_rate, + .determine_rate = iproc_pll_determine_rate, .set_rate = iproc_pll_set_rate, }; diff --git a/drivers/clk/bcm/clk-iproc.h b/drivers/clk/bcm/clk-iproc.h index 2148b4ea9f28..a48ddd3e0b28 100644 --- a/drivers/clk/bcm/clk-iproc.h +++ b/drivers/clk/bcm/clk-iproc.h @@ -80,6 +80,11 @@ */ #define IPROC_CLK_PLL_RESET_ACTIVE_LOW BIT(9) +/* + * Calculate the PLL parameters are runtime, instead of using table + */ +#define IPROC_CLK_PLL_CALC_PARAM BIT(10) + /* * Parameters for VCO frequency configuration * From 85151a6b0b331f7bc4d9534d6138bfdd3e206b0d Mon Sep 17 00:00:00 2001 From: Lori Hikichi Date: Mon, 14 Aug 2017 12:00:39 -0700 Subject: [PATCH 2/8] clk: iproc: Fix error in the pll post divider rate calculation The pll post divider code was using DIV_ROUND_UP when determining the divider value best suited to produce the target frequency. Using DIV_ROUND_CLOSEST will give us better divider values when the division results in a small remainder. Also, change the post divider clock over to the determine_rate api instead of round_rate. Signed-off-by: Simran Rai Signed-off-by: Lori Hikichi Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-iproc-pll.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/drivers/clk/bcm/clk-iproc-pll.c b/drivers/clk/bcm/clk-iproc-pll.c index 9514ecf319b0..7df010b2edcd 100644 --- a/drivers/clk/bcm/clk-iproc-pll.c +++ b/drivers/clk/bcm/clk-iproc-pll.c @@ -602,25 +602,26 @@ static unsigned long iproc_clk_recalc_rate(struct clk_hw *hw, return clk->rate; } -static long iproc_clk_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static int iproc_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { - unsigned int div; + unsigned int bestdiv; - if (rate == 0 || *parent_rate == 0) + if (req->rate == 0) return -EINVAL; + if (req->rate == req->best_parent_rate) + return 0; - if (rate == *parent_rate) - return *parent_rate; + bestdiv = DIV_ROUND_CLOSEST(req->best_parent_rate, req->rate); + if (bestdiv < 2) + req->rate = req->best_parent_rate; - div = DIV_ROUND_UP(*parent_rate, rate); - if (div < 2) - return *parent_rate; + if (bestdiv > 256) + bestdiv = 256; - if (div > 256) - div = 256; + req->rate = req->best_parent_rate / bestdiv; - return *parent_rate / div; + return 0; } static int iproc_clk_set_rate(struct clk_hw *hw, unsigned long rate, @@ -635,10 +636,10 @@ static int iproc_clk_set_rate(struct clk_hw *hw, unsigned long rate, if (rate == 0 || parent_rate == 0) return -EINVAL; + div = DIV_ROUND_CLOSEST(parent_rate, rate); if (ctrl->flags & IPROC_CLK_MCLK_DIV_BY_2) - div = DIV_ROUND_UP(parent_rate, rate * 2); - else - div = DIV_ROUND_UP(parent_rate, rate); + div /= 2; + if (div > 256) return -EINVAL; @@ -662,7 +663,7 @@ static const struct clk_ops iproc_clk_ops = { .enable = iproc_clk_enable, .disable = iproc_clk_disable, .recalc_rate = iproc_clk_recalc_rate, - .round_rate = iproc_clk_round_rate, + .determine_rate = iproc_clk_determine_rate, .set_rate = iproc_clk_set_rate, }; From f3f739c93c42f50870a75af6c9bf001854313c25 Mon Sep 17 00:00:00 2001 From: Lori Hikichi Date: Mon, 14 Aug 2017 12:00:40 -0700 Subject: [PATCH 3/8] clk: iproc: Allow plls to do minor rate changes without reset The iproc plls are capable of doing small rate changes without the need for a full reset and re-lock procedure. This feature will allow for small tweaks to the PLL rate to occur smoothly. Signed-off-by: Lori Hikichi Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-iproc-pll.c | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/clk/bcm/clk-iproc-pll.c b/drivers/clk/bcm/clk-iproc-pll.c index 7df010b2edcd..ab10819e2c09 100644 --- a/drivers/clk/bcm/clk-iproc-pll.c +++ b/drivers/clk/bcm/clk-iproc-pll.c @@ -285,6 +285,40 @@ static void __pll_bring_out_reset(struct iproc_pll *pll, unsigned int kp, iproc_pll_write(pll, pll->control_base, reset->offset, val); } +/* + * Determines if the change to be applied to the PLL is minor (just an update + * or the fractional divider). If so, then we can avoid going through a + * disruptive reset and lock sequence. + */ +static bool pll_fractional_change_only(struct iproc_pll *pll, + struct iproc_pll_vco_param *vco) +{ + const struct iproc_pll_ctrl *ctrl = pll->ctrl; + u32 val; + u32 ndiv_int; + unsigned int pdiv; + + /* PLL needs to be locked */ + val = readl(pll->status_base + ctrl->status.offset); + if ((val & (1 << ctrl->status.shift)) == 0) + return false; + + val = readl(pll->control_base + ctrl->ndiv_int.offset); + ndiv_int = (val >> ctrl->ndiv_int.shift) & + bit_mask(ctrl->ndiv_int.width); + + if (ndiv_int != vco->ndiv_int) + return false; + + val = readl(pll->control_base + ctrl->pdiv.offset); + pdiv = (val >> ctrl->pdiv.shift) & bit_mask(ctrl->pdiv.width); + + if (pdiv != vco->pdiv) + return false; + + return true; +} + static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, unsigned long parent_rate) { @@ -333,6 +367,19 @@ static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, return ret; } + if (pll_fractional_change_only(clk->pll, vco)) { + /* program fractional part of NDIV */ + if (ctrl->flags & IPROC_CLK_PLL_HAS_NDIV_FRAC) { + val = readl(pll->control_base + ctrl->ndiv_frac.offset); + val &= ~(bit_mask(ctrl->ndiv_frac.width) << + ctrl->ndiv_frac.shift); + val |= vco->ndiv_frac << ctrl->ndiv_frac.shift; + iproc_pll_write(pll, pll->control_base, + ctrl->ndiv_frac.offset, val); + return 0; + } + } + /* put PLL in reset */ __pll_put_in_reset(pll); From b33db49783763e1b2a63b12fbe0e91fb7147a987 Mon Sep 17 00:00:00 2001 From: Lori Hikichi Date: Mon, 14 Aug 2017 12:00:41 -0700 Subject: [PATCH 4/8] clk: iproc: Minor tidy up of iproc pll data structures There were a few fields in the iproc pll data structures that were holding information that was not true state information. Using stack variables is sufficient and simplifies the structure. There are not any functional changes in this commit. Signed-off-by: Lori Hikichi Signed-off-by: Stephen Boyd --- drivers/clk/bcm/clk-iproc-pll.c | 83 ++++++++++++++------------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/drivers/clk/bcm/clk-iproc-pll.c b/drivers/clk/bcm/clk-iproc-pll.c index ab10819e2c09..43a58ae5a89d 100644 --- a/drivers/clk/bcm/clk-iproc-pll.c +++ b/drivers/clk/bcm/clk-iproc-pll.c @@ -69,16 +69,6 @@ enum vco_freq_range { VCO_MAX = 4000000000U, }; -struct iproc_pll; - -struct iproc_clk { - struct clk_hw hw; - const char *name; - struct iproc_pll *pll; - unsigned long rate; - const struct iproc_clk_ctrl *ctrl; -}; - struct iproc_pll { void __iomem *status_base; void __iomem *control_base; @@ -88,9 +78,12 @@ struct iproc_pll { const struct iproc_pll_ctrl *ctrl; const struct iproc_pll_vco_param *vco_param; unsigned int num_vco_entries; +}; - struct clk_hw_onecell_data *clk_data; - struct iproc_clk *clks; +struct iproc_clk { + struct clk_hw hw; + struct iproc_pll *pll; + const struct iproc_clk_ctrl *ctrl; }; #define to_iproc_clk(hw) container_of(hw, struct iproc_clk, hw) @@ -329,6 +322,7 @@ static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, u32 val; enum kp_band kp_index; unsigned long ref_freq; + const char *clk_name = clk_hw_get_name(&clk->hw); /* * reference frequency = parent frequency / PDIV @@ -351,19 +345,19 @@ static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, kp_index = KP_BAND_HIGH_HIGH; } else { pr_err("%s: pll: %s has invalid rate: %lu\n", __func__, - clk->name, rate); + clk_name, rate); return -EINVAL; } kp = get_kp(ref_freq, kp_index); if (kp < 0) { - pr_err("%s: pll: %s has invalid kp\n", __func__, clk->name); + pr_err("%s: pll: %s has invalid kp\n", __func__, clk_name); return kp; } ret = __pll_enable(pll); if (ret) { - pr_err("%s: pll: %s fails to enable\n", __func__, clk->name); + pr_err("%s: pll: %s fails to enable\n", __func__, clk_name); return ret; } @@ -433,7 +427,7 @@ static int pll_set_rate(struct iproc_clk *clk, struct iproc_pll_vco_param *vco, ret = pll_wait_for_lock(pll); if (ret < 0) { - pr_err("%s: pll: %s failed to lock\n", __func__, clk->name); + pr_err("%s: pll: %s failed to lock\n", __func__, clk_name); return ret; } @@ -469,16 +463,15 @@ static unsigned long iproc_pll_recalc_rate(struct clk_hw *hw, u32 val; u64 ndiv, ndiv_int, ndiv_frac; unsigned int pdiv; + unsigned long rate; if (parent_rate == 0) return 0; /* PLL needs to be locked */ val = readl(pll->status_base + ctrl->status.offset); - if ((val & (1 << ctrl->status.shift)) == 0) { - clk->rate = 0; + if ((val & (1 << ctrl->status.shift)) == 0) return 0; - } /* * PLL output frequency = @@ -500,14 +493,14 @@ static unsigned long iproc_pll_recalc_rate(struct clk_hw *hw, val = readl(pll->control_base + ctrl->pdiv.offset); pdiv = (val >> ctrl->pdiv.shift) & bit_mask(ctrl->pdiv.width); - clk->rate = (ndiv * parent_rate) >> 20; + rate = (ndiv * parent_rate) >> 20; if (pdiv == 0) - clk->rate *= 2; + rate *= 2; else - clk->rate /= pdiv; + rate /= pdiv; - return clk->rate; + return rate; } static int iproc_pll_determine_rate(struct clk_hw *hw, @@ -632,6 +625,7 @@ static unsigned long iproc_clk_recalc_rate(struct clk_hw *hw, struct iproc_pll *pll = clk->pll; u32 val; unsigned int mdiv; + unsigned long rate; if (parent_rate == 0) return 0; @@ -642,11 +636,11 @@ static unsigned long iproc_clk_recalc_rate(struct clk_hw *hw, mdiv = 256; if (ctrl->flags & IPROC_CLK_MCLK_DIV_BY_2) - clk->rate = parent_rate / (mdiv * 2); + rate = parent_rate / (mdiv * 2); else - clk->rate = parent_rate / mdiv; + rate = parent_rate / mdiv; - return clk->rate; + return rate; } static int iproc_clk_determine_rate(struct clk_hw *hw, @@ -698,10 +692,6 @@ static int iproc_clk_set_rate(struct clk_hw *hw, unsigned long rate, val |= div << ctrl->mdiv.shift; } iproc_pll_write(pll, pll->control_base, ctrl->mdiv.offset, val); - if (ctrl->flags & IPROC_CLK_MCLK_DIV_BY_2) - clk->rate = parent_rate / (div * 2); - else - clk->rate = parent_rate / div; return 0; } @@ -744,6 +734,8 @@ void iproc_pll_clk_setup(struct device_node *node, struct iproc_clk *iclk; struct clk_init_data init; const char *parent_name; + struct iproc_clk *iclk_array; + struct clk_hw_onecell_data *clk_data; if (WARN_ON(!pll_ctrl) || WARN_ON(!clk_ctrl)) return; @@ -752,14 +744,14 @@ void iproc_pll_clk_setup(struct device_node *node, if (WARN_ON(!pll)) return; - pll->clk_data = kzalloc(sizeof(*pll->clk_data->hws) * num_clks + - sizeof(*pll->clk_data), GFP_KERNEL); - if (WARN_ON(!pll->clk_data)) + clk_data = kzalloc(sizeof(*clk_data->hws) * num_clks + + sizeof(*clk_data), GFP_KERNEL); + if (WARN_ON(!clk_data)) goto err_clk_data; - pll->clk_data->num = num_clks; + clk_data->num = num_clks; - pll->clks = kcalloc(num_clks, sizeof(*pll->clks), GFP_KERNEL); - if (WARN_ON(!pll->clks)) + iclk_array = kcalloc(num_clks, sizeof(struct iproc_clk), GFP_KERNEL); + if (WARN_ON(!iclk_array)) goto err_clks; pll->control_base = of_iomap(node, 0); @@ -789,9 +781,8 @@ void iproc_pll_clk_setup(struct device_node *node, /* initialize and register the PLL itself */ pll->ctrl = pll_ctrl; - iclk = &pll->clks[0]; + iclk = &iclk_array[0]; iclk->pll = pll; - iclk->name = node->name; init.name = node->name; init.ops = &iproc_pll_ops; @@ -812,7 +803,7 @@ void iproc_pll_clk_setup(struct device_node *node, if (WARN_ON(ret)) goto err_pll_register; - pll->clk_data->hws[0] = &iclk->hw; + clk_data->hws[0] = &iclk->hw; /* now initialize and register all leaf clocks */ for (i = 1; i < num_clks; i++) { @@ -826,8 +817,7 @@ void iproc_pll_clk_setup(struct device_node *node, if (WARN_ON(ret)) goto err_clk_register; - iclk = &pll->clks[i]; - iclk->name = clk_name; + iclk = &iclk_array[i]; iclk->pll = pll; iclk->ctrl = &clk_ctrl[i]; @@ -842,11 +832,10 @@ void iproc_pll_clk_setup(struct device_node *node, if (WARN_ON(ret)) goto err_clk_register; - pll->clk_data->hws[i] = &iclk->hw; + clk_data->hws[i] = &iclk->hw; } - ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, - pll->clk_data); + ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data); if (WARN_ON(ret)) goto err_clk_register; @@ -854,7 +843,7 @@ void iproc_pll_clk_setup(struct device_node *node, err_clk_register: while (--i >= 0) - clk_hw_unregister(pll->clk_data->hws[i]); + clk_hw_unregister(clk_data->hws[i]); err_pll_register: if (pll->status_base != pll->control_base) @@ -871,10 +860,10 @@ void iproc_pll_clk_setup(struct device_node *node, iounmap(pll->control_base); err_pll_iomap: - kfree(pll->clks); + kfree(iclk_array); err_clks: - kfree(pll->clk_data); + kfree(clk_data); err_clk_data: kfree(pll); From 9713588f8cd96069716bd8daf720dc7ff921af89 Mon Sep 17 00:00:00 2001 From: "Gomonovych, Vasyl" Date: Wed, 29 Nov 2017 00:04:06 +0100 Subject: [PATCH 5/8] clk: mvebu: armada-37xx-periph: Use PTR_ERR_OR_ZERO() Fix ptr_ret.cocci warnings: drivers/clk/mvebu/armada-37xx-periph.c:362:1-3: WARNING: PTR_ERR_OR_ZERO can be used Use PTR_ERR_OR_ZERO rather than if(IS_ERR(...)) + PTR_ERR Generated by: scripts/coccinelle/api/ptr_ret.cocci Signed-off-by: Vasyl Gomonovych Signed-off-by: Stephen Boyd --- drivers/clk/mvebu/armada-37xx-periph.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/clk/mvebu/armada-37xx-periph.c b/drivers/clk/mvebu/armada-37xx-periph.c index cecb0fdfaef6..72e2ce46f4b0 100644 --- a/drivers/clk/mvebu/armada-37xx-periph.c +++ b/drivers/clk/mvebu/armada-37xx-periph.c @@ -359,10 +359,7 @@ static int armada_3700_add_composite_clk(const struct clk_periph_data *data, mux_ops, rate_hw, rate_ops, gate_hw, gate_ops, CLK_IGNORE_UNUSED); - if (IS_ERR(*hw)) - return PTR_ERR(*hw); - - return 0; + return PTR_ERR_OR_ZERO(*hw); } static int armada_3700_periph_clock_probe(struct platform_device *pdev) From 0c6ab1b8f8940d4ddbfff7ddff080cbfb5f32b02 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Tue, 5 Dec 2017 17:46:58 +0200 Subject: [PATCH 6/8] clk: qcom: Add A53 PLL support The CPUs on Qualcomm MSM8916-based platforms are clocked by two PLLs, a primary (A53) CPU PLL and a secondary fixed-rate GPLL0. These sources are connected to a mux and half-integer divider, which is feeding the CPU cores. This patch adds support for the primary CPU PLL which generates the higher range of frequencies above 1GHz. Signed-off-by: Georgi Djakov Acked-by: Bjorn Andersson Tested-by: Amit Kucheria [sboyd@codeaurora.org: Move to devm provider registration, NUL terminate frequency table, made tristate/modular] Signed-off-by: Stephen Boyd --- .../devicetree/bindings/clock/qcom,a53pll.txt | 22 ++++ drivers/clk/qcom/Kconfig | 10 ++ drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/a53-pll.c | 107 ++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/qcom,a53pll.txt create mode 100644 drivers/clk/qcom/a53-pll.c diff --git a/Documentation/devicetree/bindings/clock/qcom,a53pll.txt b/Documentation/devicetree/bindings/clock/qcom,a53pll.txt new file mode 100644 index 000000000000..e3fa8118eaee --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,a53pll.txt @@ -0,0 +1,22 @@ +Qualcomm MSM8916 A53 PLL Binding +-------------------------------- +The A53 PLL on MSM8916 platforms is the main CPU PLL used used for frequencies +above 1GHz. + +Required properties : +- compatible : Shall contain only one of the following: + + "qcom,msm8916-a53pll" + +- reg : shall contain base register location and length + +- #clock-cells : must be set to <0> + +Example: + + a53pll: clock@b016000 { + compatible = "qcom,msm8916-a53pll"; + reg = <0xb016000 0x40>; + #clock-cells = <0>; + }; + diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 9f6c278deead..49db9fda6548 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -12,6 +12,16 @@ config COMMON_CLK_QCOM select REGMAP_MMIO select RESET_CONTROLLER +config QCOM_A53PLL + tristate "MSM8916 A53 PLL" + depends on COMMON_CLK_QCOM + default ARCH_QCOM + help + Support for the A53 PLL on MSM8916 devices. It provides + the CPU with frequencies above 1GHz. + Say Y if you want to support higher CPU frequencies on MSM8916 + devices. + config QCOM_CLK_RPM tristate "RPM based Clock Controller" depends on COMMON_CLK_QCOM && MFD_QCOM_RPM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 26410d31446b..e767c60c24ec 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -32,5 +32,6 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o +obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o diff --git a/drivers/clk/qcom/a53-pll.c b/drivers/clk/qcom/a53-pll.c new file mode 100644 index 000000000000..45cfc57bff92 --- /dev/null +++ b/drivers/clk/qcom/a53-pll.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm A53 PLL driver + * + * Copyright (c) 2017, Linaro Limited + * Author: Georgi Djakov + */ + +#include +#include +#include +#include +#include + +#include "clk-pll.h" +#include "clk-regmap.h" + +static const struct pll_freq_tbl a53pll_freq[] = { + { 998400000, 52, 0x0, 0x1, 0 }, + { 1094400000, 57, 0x0, 0x1, 0 }, + { 1152000000, 62, 0x0, 0x1, 0 }, + { 1209600000, 63, 0x0, 0x1, 0 }, + { 1248000000, 65, 0x0, 0x1, 0 }, + { 1363200000, 71, 0x0, 0x1, 0 }, + { 1401600000, 73, 0x0, 0x1, 0 }, + { } +}; + +static const struct regmap_config a53pll_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x40, + .fast_io = true, +}; + +static int qcom_a53pll_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct resource *res; + struct clk_pll *pll; + void __iomem *base; + struct clk_init_data init = { }; + int ret; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(dev, base, &a53pll_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + pll->l_reg = 0x04; + pll->m_reg = 0x08; + pll->n_reg = 0x0c; + pll->config_reg = 0x14; + pll->mode_reg = 0x00; + pll->status_reg = 0x1c; + pll->status_bit = 16; + pll->freq_tbl = a53pll_freq; + + init.name = "a53pll"; + init.parent_names = (const char *[]){ "xo" }; + init.num_parents = 1; + init.ops = &clk_pll_sr2_ops; + init.flags = CLK_IS_CRITICAL; + pll->clkr.hw.init = &init; + + ret = devm_clk_register_regmap(dev, &pll->clkr); + if (ret) { + dev_err(dev, "failed to register regmap clock: %d\n", ret); + return ret; + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &pll->clkr.hw); + if (ret) { + dev_err(dev, "failed to add clock provider: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id qcom_a53pll_match_table[] = { + { .compatible = "qcom,msm8916-a53pll" }, + { } +}; + +static struct platform_driver qcom_a53pll_driver = { + .probe = qcom_a53pll_probe, + .driver = { + .name = "qcom-a53pll", + .of_match_table = qcom_a53pll_match_table, + }, +}; +module_platform_driver(qcom_a53pll_driver); + +MODULE_DESCRIPTION("Qualcomm A53 PLL Driver"); +MODULE_LICENSE("GPL v2"); From 081bfeed5f1b8394d993afa6b0ce20ed3e868960 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Tue, 5 Dec 2017 17:46:59 +0200 Subject: [PATCH 7/8] clk: qcom: Add regmap mux-div clocks support Add support for hardware that can switch both parent clock and divider at the same time. This avoids generating intermediate frequencies from either the old parent clock and new divider or new parent clock and old divider combinations. Signed-off-by: Georgi Djakov Tested-by: Amit Kucheria [sboyd@codeaurora.org: Change a comment style, drop parent_map in favor of a u32 array instead, export symbols for clk_ops and mux function] Signed-off-by: Stephen Boyd --- drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-regmap-mux-div.c | 231 ++++++++++++++++++++++++++ drivers/clk/qcom/clk-regmap-mux-div.h | 44 +++++ 3 files changed, 276 insertions(+) create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.c create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.h diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index e767c60c24ec..7c51d877f967 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -10,6 +10,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-y += reset.o clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o 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..6044839da85a --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Linaro Limited + * Author: Georgi Djakov + */ + +#include +#include +#include +#include + +#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; +} +EXPORT_SYMBOL_GPL(mux_div_set_src_div); + +static void mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src, + u32 *div) +{ + u32 val, d, s; + const char *name = clk_hw_get_name(&md->clkr.hw); + + regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val); + + if (val & CMD_RCGR_DIRTY_CFG) { + pr_err("%s: RCG configuration is pending\n", name); + return; + } + + regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val); + s = (val >> md->src_shift); + s &= BIT(md->src_width) - 1; + *src = s; + + d = (val >> md->hid_shift); + d &= BIT(md->hid_width) - 1; + *div = d; +} + +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]; + 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]) + 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], 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]); +} + +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]) { + 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; +} + +const struct clk_ops clk_regmap_mux_div_ops = { + .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..6cd6261be7ac --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Linaro Limited + * Author: Georgi Djakov + */ + +#ifndef __QCOM_CLK_REGMAP_MUX_DIV_H__ +#define __QCOM_CLK_REGMAP_MUX_DIV_H__ + +#include +#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 + * @parent_map: map from parent_names index to src_sel field + * @clkr: handle between common and hardware-specific interfaces + * @pclk: the input PLL clock + * @clk_nb: clock notifier for rate changes of the input PLL + */ +struct clk_regmap_mux_div { + u32 reg_offset; + u32 hid_width; + u32 hid_shift; + u32 src_width; + u32 src_shift; + u32 div; + u32 src; + const u32 *parent_map; + struct clk_regmap clkr; + struct clk *pclk; + struct notifier_block clk_nb; +}; + +extern const struct clk_ops clk_regmap_mux_div_ops; +extern int mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src, u32 div); + +#endif From 81ac38847a1d7fdd74a232cae195ff8f0fb4ab21 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Tue, 5 Dec 2017 17:47:01 +0200 Subject: [PATCH 8/8] clk: qcom: Add APCS clock controller support Add a driver for the APCS clock controller. It is part of the APCS hardware block, which among other things implements also a combined mux and half integer divider functionality. It can choose between a fixed-rate clock or the dedicated APCS (A53) PLL. The source and the divider can be set both at the same time. This is required for enabling CPU frequency scaling on MSM8916-based platforms. Signed-off-by: Georgi Djakov Acked-by: Bjorn Andersson Tested-by: Amit Kucheria [sboyd@codeaurora.org: Include rcg header for parent_map, drop multiple unneeded includes, add COMPILE_TEST to APCS depends, made tristate/modular] Signed-off-by: Stephen Boyd --- drivers/clk/qcom/Kconfig | 11 +++ drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/apcs-msm8916.c | 138 ++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 drivers/clk/qcom/apcs-msm8916.c diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 49db9fda6548..769e27353e1b 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -22,6 +22,17 @@ config QCOM_A53PLL Say Y if you want to support higher CPU frequencies on MSM8916 devices. +config QCOM_CLK_APCS_MSM8916 + tristate "MSM8916 APCS Clock Controller" + depends on COMMON_CLK_QCOM + depends on QCOM_APCS_IPC || COMPILE_TEST + default ARCH_QCOM + help + Support for the APCS Clock Controller on msm8916 devices. The + APCS is managing the mux and divider which feeds the CPUs. + Say Y if you want to support CPU frequency scaling on devices + such as msm8916. + config QCOM_CLK_RPM tristate "RPM based Clock Controller" depends on COMMON_CLK_QCOM && MFD_QCOM_RPM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 7c51d877f967..0408cebf38d4 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -34,5 +34,6 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o obj-$(CONFIG_QCOM_A53PLL) += a53-pll.o +obj-$(CONFIG_QCOM_CLK_APCS_MSM8916) += apcs-msm8916.o obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o diff --git a/drivers/clk/qcom/apcs-msm8916.c b/drivers/clk/qcom/apcs-msm8916.c new file mode 100644 index 000000000000..246957f1a413 --- /dev/null +++ b/drivers/clk/qcom/apcs-msm8916.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm APCS clock controller driver + * + * Copyright (c) 2017, Linaro Limited + * Author: Georgi Djakov + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "clk-regmap.h" +#include "clk-regmap-mux-div.h" + +static const u32 gpll0_a53cc_map[] = { 4, 5 }; + +static const char * const gpll0_a53cc[] = { + "gpll0_vote", + "a53pll", +}; + +/* + * We use the notifier function for switching to a temporary safe configuration + * (mux and divider), while the A53 PLL is reconfigured. + */ +static int a53cc_notifier_cb(struct notifier_block *nb, unsigned long event, + void *data) +{ + int ret = 0; + struct clk_regmap_mux_div *md = container_of(nb, + struct clk_regmap_mux_div, + clk_nb); + if (event == PRE_RATE_CHANGE) + /* set the mux and divider to safe frequency (400mhz) */ + ret = mux_div_set_src_div(md, 4, 3); + + return notifier_from_errno(ret); +} + +static int qcom_apcs_msm8916_clk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *parent = dev->parent; + struct clk_regmap_mux_div *a53cc; + struct regmap *regmap; + struct clk_init_data init = { }; + int ret; + + regmap = dev_get_regmap(parent, NULL); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(dev, "failed to get regmap: %d\n", ret); + return ret; + } + + a53cc = devm_kzalloc(dev, sizeof(*a53cc), GFP_KERNEL); + if (!a53cc) + return -ENOMEM; + + init.name = "a53mux"; + init.parent_names = gpll0_a53cc; + init.num_parents = ARRAY_SIZE(gpll0_a53cc); + init.ops = &clk_regmap_mux_div_ops; + init.flags = CLK_SET_RATE_PARENT; + + a53cc->clkr.hw.init = &init; + a53cc->clkr.regmap = regmap; + a53cc->reg_offset = 0x50; + a53cc->hid_width = 5; + a53cc->hid_shift = 0; + a53cc->src_width = 3; + a53cc->src_shift = 8; + a53cc->parent_map = gpll0_a53cc_map; + + a53cc->pclk = devm_clk_get(parent, NULL); + if (IS_ERR(a53cc->pclk)) { + ret = PTR_ERR(a53cc->pclk); + dev_err(dev, "failed to get clk: %d\n", ret); + return ret; + } + + a53cc->clk_nb.notifier_call = a53cc_notifier_cb; + ret = clk_notifier_register(a53cc->pclk, &a53cc->clk_nb); + if (ret) { + dev_err(dev, "failed to register clock notifier: %d\n", ret); + return ret; + } + + ret = devm_clk_register_regmap(dev, &a53cc->clkr); + if (ret) { + dev_err(dev, "failed to register regmap clock: %d\n", ret); + goto err; + } + + ret = of_clk_add_hw_provider(parent->of_node, of_clk_hw_simple_get, + &a53cc->clkr.hw); + if (ret) { + dev_err(dev, "failed to add clock provider: %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, a53cc); + + return 0; + +err: + clk_notifier_unregister(a53cc->pclk, &a53cc->clk_nb); + return ret; +} + +static int qcom_apcs_msm8916_clk_remove(struct platform_device *pdev) +{ + struct clk_regmap_mux_div *a53cc = platform_get_drvdata(pdev); + struct device *parent = pdev->dev.parent; + + clk_notifier_unregister(a53cc->pclk, &a53cc->clk_nb); + of_clk_del_provider(parent->of_node); + + return 0; +} + +static struct platform_driver qcom_apcs_msm8916_clk_driver = { + .probe = qcom_apcs_msm8916_clk_probe, + .remove = qcom_apcs_msm8916_clk_remove, + .driver = { + .name = "qcom-apcs-msm8916-clk", + }, +}; +module_platform_driver(qcom_apcs_msm8916_clk_driver); + +MODULE_AUTHOR("Georgi Djakov "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm MSM8916 APCS clock driver");