diff options
author | Michael Turquette <mturquette@baylibre.com> | 2017-04-12 18:53:16 +0200 |
---|---|---|
committer | Michael Turquette <mturquette@baylibre.com> | 2017-04-12 18:53:16 +0200 |
commit | a507c57dd1ddcb39a06856aed83b6c69b483aa6a (patch) | |
tree | 545fea648933e80430fd7cd4f0c83402a79c6c4a /drivers/clk | |
parent | 0d7a5328dba0d3e299d7d611e4ec059b8cab4f38 (diff) | |
parent | b609338b26f5653aa211fc7af83477e2df6e3f0b (diff) |
Merge tag 'meson-clk-for-4.12' of git://github.com/BayLibre/clk-meson into clk-next
Pull AmLogic clk driver updates from Jerome Brunet:
2nd Amlogic clock driver update for 4.12:
* Protect against holes in onecell_data
* Fix divison by zero and overflow in the mpll driver
* Add audio clock divider driver for i2s clocks
* Add i2s and spdif master clocks
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/meson/Makefile | 2 | ||||
-rw-r--r-- | drivers/clk/meson/clk-audio-divider.c | 144 | ||||
-rw-r--r-- | drivers/clk/meson/clk-mpll.c | 26 | ||||
-rw-r--r-- | drivers/clk/meson/clkc.h | 10 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb.c | 144 | ||||
-rw-r--r-- | drivers/clk/meson/gxbb.h | 9 |
6 files changed, 322 insertions, 13 deletions
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 349583405b7c..83b6d9d65aa1 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -2,6 +2,6 @@ # Makefile for Meson specific clk # -obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o +obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o diff --git a/drivers/clk/meson/clk-audio-divider.c b/drivers/clk/meson/clk-audio-divider.c new file mode 100644 index 000000000000..6c07db06642d --- /dev/null +++ b/drivers/clk/meson/clk-audio-divider.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017 AmLogic, Inc. + * Author: Jerome Brunet <jbrunet@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * i2s master clock divider: The algorithm of the generic clk-divider used with + * a very precise clock parent such as the mpll tends to select a low divider + * factor. This gives poor results with this particular divider, especially with + * high frequencies (> 100 MHz) + * + * This driver try to select the maximum possible divider with the rate the + * upstream clock can provide. + */ + +#include <linux/clk-provider.h> +#include "clkc.h" + +#define to_meson_clk_audio_divider(_hw) container_of(_hw, \ + struct meson_clk_audio_divider, hw) + +static int _div_round(unsigned long parent_rate, unsigned long rate, + unsigned long flags) +{ + if (flags & CLK_DIVIDER_ROUND_CLOSEST) + return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, rate); + + return DIV_ROUND_UP_ULL((u64)parent_rate, rate); +} + +static int _get_val(unsigned long parent_rate, unsigned long rate) +{ + return DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1; +} + +static int _valid_divider(struct clk_hw *hw, int divider) +{ + struct meson_clk_audio_divider *adiv = + to_meson_clk_audio_divider(hw); + int max_divider; + u8 width; + + width = adiv->div.width; + max_divider = 1 << width; + + return clamp(divider, 1, max_divider); +} + +static unsigned long audio_divider_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct meson_clk_audio_divider *adiv = + to_meson_clk_audio_divider(hw); + struct parm *p; + unsigned long reg, divider; + + p = &adiv->div; + reg = readl(adiv->base + p->reg_off); + divider = PARM_GET(p->width, p->shift, reg) + 1; + + return DIV_ROUND_UP_ULL((u64)parent_rate, divider); +} + +static long audio_divider_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + struct meson_clk_audio_divider *adiv = + to_meson_clk_audio_divider(hw); + unsigned long max_prate; + int divider; + + if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) { + divider = _div_round(*parent_rate, rate, adiv->flags); + divider = _valid_divider(hw, divider); + return DIV_ROUND_UP_ULL((u64)*parent_rate, divider); + } + + /* Get the maximum parent rate */ + max_prate = clk_hw_round_rate(clk_hw_get_parent(hw), ULONG_MAX); + + /* Get the corresponding rounded down divider */ + divider = max_prate / rate; + divider = _valid_divider(hw, divider); + + /* Get actual rate of the parent */ + *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), + divider * rate); + + return DIV_ROUND_UP_ULL((u64)*parent_rate, divider); +} + +static int audio_divider_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct meson_clk_audio_divider *adiv = + to_meson_clk_audio_divider(hw); + struct parm *p; + unsigned long reg, flags = 0; + int val; + + val = _get_val(parent_rate, rate); + + if (adiv->lock) + spin_lock_irqsave(adiv->lock, flags); + else + __acquire(adiv->lock); + + p = &adiv->div; + reg = readl(adiv->base + p->reg_off); + reg = PARM_SET(p->width, p->shift, reg, val); + writel(reg, adiv->base + p->reg_off); + + if (adiv->lock) + spin_unlock_irqrestore(adiv->lock, flags); + else + __release(adiv->lock); + + return 0; +} + +const struct clk_ops meson_clk_audio_divider_ro_ops = { + .recalc_rate = audio_divider_recalc_rate, + .round_rate = audio_divider_round_rate, +}; + +const struct clk_ops meson_clk_audio_divider_ops = { + .recalc_rate = audio_divider_recalc_rate, + .round_rate = audio_divider_round_rate, + .set_rate = audio_divider_set_rate, +}; diff --git a/drivers/clk/meson/clk-mpll.c b/drivers/clk/meson/clk-mpll.c index 540dabe5adad..39eab69fe51a 100644 --- a/drivers/clk/meson/clk-mpll.c +++ b/drivers/clk/meson/clk-mpll.c @@ -65,18 +65,21 @@ #include "clkc.h" #define SDM_DEN 16384 -#define SDM_MIN 1 -#define SDM_MAX 16383 #define N2_MIN 4 #define N2_MAX 511 #define to_meson_clk_mpll(_hw) container_of(_hw, struct meson_clk_mpll, hw) -static unsigned long rate_from_params(unsigned long parent_rate, +static long rate_from_params(unsigned long parent_rate, unsigned long sdm, unsigned long n2) { - return (parent_rate * SDM_DEN) / ((SDM_DEN * n2) + sdm); + unsigned long divisor = (SDM_DEN * n2) + sdm; + + if (n2 < N2_MIN) + return -EINVAL; + + return DIV_ROUND_UP_ULL((u64)parent_rate * SDM_DEN, divisor); } static void params_from_rate(unsigned long requested_rate, @@ -89,17 +92,13 @@ static void params_from_rate(unsigned long requested_rate, if (div < N2_MIN) { *n2 = N2_MIN; - *sdm = SDM_MIN; + *sdm = 0; } else if (div > N2_MAX) { *n2 = N2_MAX; - *sdm = SDM_MAX; + *sdm = SDM_DEN - 1; } else { *n2 = div; *sdm = DIV_ROUND_UP(rem * SDM_DEN, requested_rate); - if (*sdm < SDM_MIN) - *sdm = SDM_MIN; - else if (*sdm > SDM_MAX) - *sdm = SDM_MAX; } } @@ -109,6 +108,7 @@ static unsigned long mpll_recalc_rate(struct clk_hw *hw, struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw); struct parm *p; unsigned long reg, sdm, n2; + long rate; p = &mpll->sdm; reg = readl(mpll->base + p->reg_off); @@ -118,7 +118,11 @@ static unsigned long mpll_recalc_rate(struct clk_hw *hw, reg = readl(mpll->base + p->reg_off); n2 = PARM_GET(p->width, p->shift, reg); - return rate_from_params(parent_rate, sdm, n2); + rate = rate_from_params(parent_rate, sdm, n2); + if (rate < 0) + return 0; + + return rate; } static long mpll_round_rate(struct clk_hw *hw, diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h index b0c9999d03de..d6feafe8bd6c 100644 --- a/drivers/clk/meson/clkc.h +++ b/drivers/clk/meson/clkc.h @@ -121,6 +121,14 @@ struct meson_clk_mpll { spinlock_t *lock; }; +struct meson_clk_audio_divider { + struct clk_hw hw; + void __iomem *base; + struct parm div; + u8 flags; + spinlock_t *lock; +}; + #define MESON_GATE(_name, _reg, _bit) \ struct clk_gate _name = { \ .reg = (void __iomem *) _reg, \ @@ -141,5 +149,7 @@ extern const struct clk_ops meson_clk_pll_ops; extern const struct clk_ops meson_clk_cpu_ops; extern const struct clk_ops meson_clk_mpll_ro_ops; extern const struct clk_ops meson_clk_mpll_ops; +extern const struct clk_ops meson_clk_audio_divider_ro_ops; +extern const struct clk_ops meson_clk_audio_divider_ops; #endif /* __CLKC_H */ diff --git a/drivers/clk/meson/gxbb.c b/drivers/clk/meson/gxbb.c index 75197664a7ee..ad5f027af1a2 100644 --- a/drivers/clk/meson/gxbb.c +++ b/drivers/clk/meson/gxbb.c @@ -845,6 +845,112 @@ static struct clk_mux gxbb_mali = { }, }; +static struct clk_mux gxbb_cts_amclk_sel = { + .reg = (void *) HHI_AUD_CLK_CNTL, + .mask = 0x3, + .shift = 9, + /* Default parent unknown (register reset value: 0) */ + .table = (u32[]){ 1, 2, 3 }, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_amclk_sel", + .ops = &clk_mux_ops, + .parent_names = (const char *[]){ "mpll0", "mpll1", "mpll2" }, + .num_parents = 3, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct meson_clk_audio_divider gxbb_cts_amclk_div = { + .div = { + .reg_off = HHI_AUD_CLK_CNTL, + .shift = 0, + .width = 8, + }, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_amclk_div", + .ops = &meson_clk_audio_divider_ops, + .parent_names = (const char *[]){ "cts_amclk_sel" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT | CLK_DIVIDER_ROUND_CLOSEST, + }, +}; + +static struct clk_gate gxbb_cts_amclk = { + .reg = (void *) HHI_AUD_CLK_CNTL, + .bit_idx = 8, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_amclk", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "cts_amclk_div" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_mux gxbb_cts_mclk_i958_sel = { + .reg = (void *)HHI_AUD_CLK_CNTL2, + .mask = 0x3, + .shift = 25, + /* Default parent unknown (register reset value: 0) */ + .table = (u32[]){ 1, 2, 3 }, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mclk_i958_sel", + .ops = &clk_mux_ops, + .parent_names = (const char *[]){ "mpll0", "mpll1", "mpll2" }, + .num_parents = 3, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_divider gxbb_cts_mclk_i958_div = { + .reg = (void *)HHI_AUD_CLK_CNTL2, + .shift = 16, + .width = 8, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mclk_i958_div", + .ops = &clk_divider_ops, + .parent_names = (const char *[]){ "cts_mclk_i958_sel" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT | CLK_DIVIDER_ROUND_CLOSEST, + }, +}; + +static struct clk_gate gxbb_cts_mclk_i958 = { + .reg = (void *)HHI_AUD_CLK_CNTL2, + .bit_idx = 24, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_mclk_i958", + .ops = &clk_gate_ops, + .parent_names = (const char *[]){ "cts_mclk_i958_div" }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_mux gxbb_cts_i958 = { + .reg = (void *)HHI_AUD_CLK_CNTL2, + .mask = 0x1, + .shift = 27, + .lock = &clk_lock, + .hw.init = &(struct clk_init_data){ + .name = "cts_i958", + .ops = &clk_mux_ops, + .parent_names = (const char *[]){ "cts_amclk", "cts_mclk_i958" }, + .num_parents = 2, + /* + *The parent is specific to origin of the audio data. Let the + * consumer choose the appropriate parent + */ + .flags = CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT, + }, +}; + /* Everything Else (EE) domain gates */ static MESON_GATE(gxbb_ddr, HHI_GCLK_MPEG0, 0); static MESON_GATE(gxbb_dos, HHI_GCLK_MPEG0, 1); @@ -1045,6 +1151,13 @@ static struct clk_hw_onecell_data gxbb_hw_onecell_data = { [CLKID_MALI_1_DIV] = &gxbb_mali_1_div.hw, [CLKID_MALI_1] = &gxbb_mali_1.hw, [CLKID_MALI] = &gxbb_mali.hw, + [CLKID_CTS_AMCLK] = &gxbb_cts_amclk.hw, + [CLKID_CTS_AMCLK_SEL] = &gxbb_cts_amclk_sel.hw, + [CLKID_CTS_AMCLK_DIV] = &gxbb_cts_amclk_div.hw, + [CLKID_CTS_MCLK_I958] = &gxbb_cts_mclk_i958.hw, + [CLKID_CTS_MCLK_I958_SEL] = &gxbb_cts_mclk_i958_sel.hw, + [CLKID_CTS_MCLK_I958_DIV] = &gxbb_cts_mclk_i958_div.hw, + [CLKID_CTS_I958] = &gxbb_cts_i958.hw, }, .num = NR_CLKS, }; @@ -1158,6 +1271,13 @@ static struct clk_hw_onecell_data gxl_hw_onecell_data = { [CLKID_MALI_1_DIV] = &gxbb_mali_1_div.hw, [CLKID_MALI_1] = &gxbb_mali_1.hw, [CLKID_MALI] = &gxbb_mali.hw, + [CLKID_CTS_AMCLK] = &gxbb_cts_amclk.hw, + [CLKID_CTS_AMCLK_SEL] = &gxbb_cts_amclk_sel.hw, + [CLKID_CTS_AMCLK_DIV] = &gxbb_cts_amclk_div.hw, + [CLKID_CTS_MCLK_I958] = &gxbb_cts_mclk_i958.hw, + [CLKID_CTS_MCLK_I958_SEL] = &gxbb_cts_mclk_i958_sel.hw, + [CLKID_CTS_MCLK_I958_DIV] = &gxbb_cts_mclk_i958_div.hw, + [CLKID_CTS_I958] = &gxbb_cts_i958.hw, }, .num = NR_CLKS, }; @@ -1270,6 +1390,8 @@ static struct clk_gate *const gxbb_clk_gates[] = { &gxbb_sar_adc_clk, &gxbb_mali_0, &gxbb_mali_1, + &gxbb_cts_amclk, + &gxbb_cts_mclk_i958, }; static struct clk_mux *const gxbb_clk_muxes[] = { @@ -1278,6 +1400,9 @@ static struct clk_mux *const gxbb_clk_muxes[] = { &gxbb_mali_0_sel, &gxbb_mali_1_sel, &gxbb_mali, + &gxbb_cts_amclk_sel, + &gxbb_cts_mclk_i958_sel, + &gxbb_cts_i958, }; static struct clk_divider *const gxbb_clk_dividers[] = { @@ -1285,6 +1410,11 @@ static struct clk_divider *const gxbb_clk_dividers[] = { &gxbb_sar_adc_clk_div, &gxbb_mali_0_div, &gxbb_mali_1_div, + &gxbb_cts_mclk_i958_div, +}; + +static struct meson_clk_audio_divider *const gxbb_audio_dividers[] = { + &gxbb_cts_amclk_div, }; struct clkc_data { @@ -1298,6 +1428,8 @@ struct clkc_data { unsigned int clk_muxes_count; struct clk_divider *const *clk_dividers; unsigned int clk_dividers_count; + struct meson_clk_audio_divider *const *clk_audio_dividers; + unsigned int clk_audio_dividers_count; struct meson_clk_cpu *cpu_clk; struct clk_hw_onecell_data *hw_onecell_data; }; @@ -1313,6 +1445,8 @@ static const struct clkc_data gxbb_clkc_data = { .clk_muxes_count = ARRAY_SIZE(gxbb_clk_muxes), .clk_dividers = gxbb_clk_dividers, .clk_dividers_count = ARRAY_SIZE(gxbb_clk_dividers), + .clk_audio_dividers = gxbb_audio_dividers, + .clk_audio_dividers_count = ARRAY_SIZE(gxbb_audio_dividers), .cpu_clk = &gxbb_cpu_clk, .hw_onecell_data = &gxbb_hw_onecell_data, }; @@ -1328,6 +1462,8 @@ static const struct clkc_data gxl_clkc_data = { .clk_muxes_count = ARRAY_SIZE(gxbb_clk_muxes), .clk_dividers = gxbb_clk_dividers, .clk_dividers_count = ARRAY_SIZE(gxbb_clk_dividers), + .clk_audio_dividers = gxbb_audio_dividers, + .clk_audio_dividers_count = ARRAY_SIZE(gxbb_audio_dividers), .cpu_clk = &gxbb_cpu_clk, .hw_onecell_data = &gxl_hw_onecell_data, }; @@ -1384,10 +1520,18 @@ static int gxbb_clkc_probe(struct platform_device *pdev) clkc_data->clk_dividers[i]->reg = clk_base + (u64)clkc_data->clk_dividers[i]->reg; + /* Populate base address for the audio dividers */ + for (i = 0; i < clkc_data->clk_audio_dividers_count; i++) + clkc_data->clk_audio_dividers[i]->base = clk_base; + /* * register all clks */ for (clkid = 0; clkid < clkc_data->hw_onecell_data->num; clkid++) { + /* array might be sparse */ + if (!clkc_data->hw_onecell_data->hws[clkid]) + continue; + ret = devm_clk_hw_register(dev, clkc_data->hw_onecell_data->hws[clkid]); if (ret) diff --git a/drivers/clk/meson/gxbb.h b/drivers/clk/meson/gxbb.h index 9d949244b104..1d8d13f5e813 100644 --- a/drivers/clk/meson/gxbb.h +++ b/drivers/clk/meson/gxbb.h @@ -277,8 +277,15 @@ #define CLKID_MALI_1_DIV 104 /* CLKID_MALI_1 */ /* CLKID_MALI */ +#define CLKID_CTS_AMCLK 107 +#define CLKID_CTS_AMCLK_SEL 108 +#define CLKID_CTS_AMCLK_DIV 109 +#define CLKID_CTS_MCLK_I958 110 +#define CLKID_CTS_MCLK_I958_SEL 111 +#define CLKID_CTS_MCLK_I958_DIV 112 +#define CLKID_CTS_I958 113 -#define NR_CLKS 107 +#define NR_CLKS 114 /* include the CLKIDs that have been made part of the stable DT binding */ #include <dt-bindings/clock/gxbb-clkc.h> |