summaryrefslogtreecommitdiffstats
path: root/drivers/clk/meson/gxbb-aoclk-32k.c
diff options
context:
space:
mode:
authorLinus Torvalds2017-09-13 20:04:14 +0200
committerLinus Torvalds2017-09-13 20:04:14 +0200
commitf60a2abfdbf298a4722dfef331c38447fa18c4e3 (patch)
tree1ac56db32fb1909aedefc21b1c62d0bbe5a13e68 /drivers/clk/meson/gxbb-aoclk-32k.c
parentMerge tag 'rtc-4.14' of git://git.kernel.org/pub/scm/linux/kernel/git/abellon... (diff)
parentclk: si5351: fix PLL reset (diff)
downloadkernel-qcow2-linux-f60a2abfdbf298a4722dfef331c38447fa18c4e3.tar.gz
kernel-qcow2-linux-f60a2abfdbf298a4722dfef331c38447fa18c4e3.tar.xz
kernel-qcow2-linux-f60a2abfdbf298a4722dfef331c38447fa18c4e3.zip
Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
Pull clk updates from Stephen Boyd: "The diff is dominated by the Allwinner A10/A20 SoCs getting converted to the sunxi-ng framework. Otherwise, the heavy hitters are various drivers for SoCs like AT91, Amlogic, Renesas, and Rockchip. There are some other new clk drivers in here too but overall this is just a bunch of clk drivers for various different pieces of hardware and a collection of non-critical fixes for clk drivers. New Drivers: - Allwinner R40 SoCs - Renesas R-Car Gen3 USB 2.0 clock selector PHY - Atmel AT91 audio PLL - Uniphier PXs3 SoCs - ARC HSDK Board PLLs - AXS10X Board PLLs - STMicroelectronics STM32H743 SoCs Removed Drivers: - Non-compiling mb86s7x support Updates: - Allwinner A10/A20 SoCs converted to sunxi-ng framework - Allwinner H3 CPU clk fixes - Renesas R-Car D3 SoC - Renesas V2H and M3-W modules - Samsung Exynos5420/5422/5800 audio fixes - Rockchip fractional clk approximation fixes - Rockchip rk3126 SoC support within the rk3128 driver - Amlogic gxbb CEC32 and sd_emmc clks - Amlogic meson8b reset controller support - IDT VersaClock 5P49V5925/5P49V6901 support - Qualcomm MSM8996 SMMU clks - Various 'const' applications for struct clk_ops - si5351 PLL reset bugfix - Uniphier audio on LD11/LD20 and ethernet support on LD11/LD20/Pro4/PXs2 - Assorted Tegra clk driver fixes" * tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (120 commits) clk: si5351: fix PLL reset ASoC: atmel-classd: remove aclk clock ASoC: atmel-classd: remove aclk clock from DT binding clk: at91: clk-generated: make gclk determine audio_pll rate clk: at91: clk-generated: create function to find best_diff clk: at91: add audio pll clock drivers dt-bindings: clk: at91: add audio plls to the compatible list clk: at91: clk-generated: remove useless divisor loop clk: mb86s7x: Drop non-building driver clk: ti: check for null return in strrchr to avoid null dereferencing clk: Don't write error code into divider register clk: uniphier: add video input subsystem clock clk: uniphier: add audio system clock clk: stm32h7: Add stm32h743 clock driver clk: gate: expose clk_gate_ops::is_enabled clk: nxp: clk-lpc32xx: rename clk_gate_is_enabled() clk: uniphier: add PXs3 clock data clk: hi6220: change watchdog clock source clk: Kconfig: Name RK805 in Kconfig for COMMON_CLK_RK808 clk: cs2000: Add cs2000_set_saved_rate ...
Diffstat (limited to 'drivers/clk/meson/gxbb-aoclk-32k.c')
-rw-r--r--drivers/clk/meson/gxbb-aoclk-32k.c194
1 files changed, 194 insertions, 0 deletions
diff --git a/drivers/clk/meson/gxbb-aoclk-32k.c b/drivers/clk/meson/gxbb-aoclk-32k.c
new file mode 100644
index 000000000000..491634dbc985
--- /dev/null
+++ b/drivers/clk/meson/gxbb-aoclk-32k.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2017 BayLibre, SAS.
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/bitfield.h>
+#include <linux/regmap.h>
+#include "gxbb-aoclk.h"
+
+/*
+ * The AO Domain embeds a dual/divider to generate a more precise
+ * 32,768KHz clock for low-power suspend mode and CEC.
+ * ______ ______
+ * | | | |
+ * ______ | Div1 |-| Cnt1 | ______
+ * | | /|______| |______|\ | |
+ * Xtal-->| Gate |---| ______ ______ X-X--| Gate |-->
+ * |______| | \| | | |/ | |______|
+ * | | Div2 |-| Cnt2 | |
+ * | |______| |______| |
+ * |_______________________|
+ *
+ * The dividing can be switched to single or dual, with a counter
+ * for each divider to set when the switching is done.
+ * The entire dividing mechanism can be also bypassed.
+ */
+
+#define CLK_CNTL0_N1_MASK GENMASK(11, 0)
+#define CLK_CNTL0_N2_MASK GENMASK(23, 12)
+#define CLK_CNTL0_DUALDIV_EN BIT(28)
+#define CLK_CNTL0_OUT_GATE_EN BIT(30)
+#define CLK_CNTL0_IN_GATE_EN BIT(31)
+
+#define CLK_CNTL1_M1_MASK GENMASK(11, 0)
+#define CLK_CNTL1_M2_MASK GENMASK(23, 12)
+#define CLK_CNTL1_BYPASS_EN BIT(24)
+#define CLK_CNTL1_SELECT_OSC BIT(27)
+
+#define PWR_CNTL_ALT_32K_SEL GENMASK(13, 10)
+
+struct cec_32k_freq_table {
+ unsigned long parent_rate;
+ unsigned long target_rate;
+ bool dualdiv;
+ unsigned int n1;
+ unsigned int n2;
+ unsigned int m1;
+ unsigned int m2;
+};
+
+static const struct cec_32k_freq_table aoclk_cec_32k_table[] = {
+ [0] = {
+ .parent_rate = 24000000,
+ .target_rate = 32768,
+ .dualdiv = true,
+ .n1 = 733,
+ .n2 = 732,
+ .m1 = 8,
+ .m2 = 11,
+ },
+};
+
+/*
+ * If CLK_CNTL0_DUALDIV_EN == 0
+ * - will use N1 divider only
+ * If CLK_CNTL0_DUALDIV_EN == 1
+ * - hold M1 cycles of N1 divider then changes to N2
+ * - hold M2 cycles of N2 divider then changes to N1
+ * Then we can get more accurate division.
+ */
+static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
+ unsigned long n1;
+ u32 reg0, reg1;
+
+ regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, &reg0);
+ regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, &reg1);
+
+ if (reg1 & CLK_CNTL1_BYPASS_EN)
+ return parent_rate;
+
+ if (reg0 & CLK_CNTL0_DUALDIV_EN) {
+ unsigned long n2, m1, m2, f1, f2, p1, p2;
+
+ n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
+ n2 = FIELD_GET(CLK_CNTL0_N2_MASK, reg0) + 1;
+
+ m1 = FIELD_GET(CLK_CNTL1_M1_MASK, reg1) + 1;
+ m2 = FIELD_GET(CLK_CNTL1_M2_MASK, reg1) + 1;
+
+ f1 = DIV_ROUND_CLOSEST(parent_rate, n1);
+ f2 = DIV_ROUND_CLOSEST(parent_rate, n2);
+
+ p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2));
+ p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2));
+
+ return DIV_ROUND_UP(100000000, p1 + p2);
+ }
+
+ n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1;
+
+ return DIV_ROUND_CLOSEST(parent_rate, n1);
+}
+
+static const struct cec_32k_freq_table *find_cec_32k_freq(unsigned long rate,
+ unsigned long prate)
+{
+ int i;
+
+ for (i = 0 ; i < ARRAY_SIZE(aoclk_cec_32k_table) ; ++i)
+ if (aoclk_cec_32k_table[i].parent_rate == prate &&
+ aoclk_cec_32k_table[i].target_rate == rate)
+ return &aoclk_cec_32k_table[i];
+
+ return NULL;
+}
+
+static long aoclk_cec_32k_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate,
+ *prate);
+
+ /* If invalid return first one */
+ if (!freq)
+ return aoclk_cec_32k_table[0].target_rate;
+
+ return freq->target_rate;
+}
+
+/*
+ * From the Amlogic init procedure, the IN and OUT gates needs to be handled
+ * in the init procedure to avoid any glitches.
+ */
+
+static int aoclk_cec_32k_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate,
+ parent_rate);
+ struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw);
+ u32 reg = 0;
+
+ if (!freq)
+ return -EINVAL;
+
+ /* Disable clock */
+ regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
+ CLK_CNTL0_IN_GATE_EN | CLK_CNTL0_OUT_GATE_EN, 0);
+
+ reg = FIELD_PREP(CLK_CNTL0_N1_MASK, freq->n1 - 1);
+ if (freq->dualdiv)
+ reg |= CLK_CNTL0_DUALDIV_EN |
+ FIELD_PREP(CLK_CNTL0_N2_MASK, freq->n2 - 1);
+
+ regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, reg);
+
+ reg = FIELD_PREP(CLK_CNTL1_M1_MASK, freq->m1 - 1);
+ if (freq->dualdiv)
+ reg |= FIELD_PREP(CLK_CNTL1_M2_MASK, freq->m2 - 1);
+
+ regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, reg);
+
+ /* Enable clock */
+ regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
+ CLK_CNTL0_IN_GATE_EN, CLK_CNTL0_IN_GATE_EN);
+
+ udelay(200);
+
+ regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0,
+ CLK_CNTL0_OUT_GATE_EN, CLK_CNTL0_OUT_GATE_EN);
+
+ regmap_update_bits(cec_32k->regmap, AO_CRT_CLK_CNTL1,
+ CLK_CNTL1_SELECT_OSC, CLK_CNTL1_SELECT_OSC);
+
+ /* Select 32k from XTAL */
+ regmap_update_bits(cec_32k->regmap,
+ AO_RTI_PWR_CNTL_REG0,
+ PWR_CNTL_ALT_32K_SEL,
+ FIELD_PREP(PWR_CNTL_ALT_32K_SEL, 4));
+
+ return 0;
+}
+
+const struct clk_ops meson_aoclk_cec_32k_ops = {
+ .recalc_rate = aoclk_cec_32k_recalc_rate,
+ .round_rate = aoclk_cec_32k_round_rate,
+ .set_rate = aoclk_cec_32k_set_rate,
+};