|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * Copyright (c) 2017 BayLibre, SAS. | 
|  | * Author: Neil Armstrong <narmstrong@baylibre.com> | 
|  | */ | 
|  |  | 
|  | #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, ®0); | 
|  | regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, ®1); | 
|  |  | 
|  | 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, | 
|  | }; |