Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.19-rc4 193 lines 5.4 kB view raw
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (c) 2017 BayLibre, SAS. 4 * Author: Neil Armstrong <narmstrong@baylibre.com> 5 */ 6 7#include <linux/clk-provider.h> 8#include <linux/bitfield.h> 9#include <linux/regmap.h> 10#include "gxbb-aoclk.h" 11 12/* 13 * The AO Domain embeds a dual/divider to generate a more precise 14 * 32,768KHz clock for low-power suspend mode and CEC. 15 * ______ ______ 16 * | | | | 17 * ______ | Div1 |-| Cnt1 | ______ 18 * | | /|______| |______|\ | | 19 * Xtal-->| Gate |---| ______ ______ X-X--| Gate |--> 20 * |______| | \| | | |/ | |______| 21 * | | Div2 |-| Cnt2 | | 22 * | |______| |______| | 23 * |_______________________| 24 * 25 * The dividing can be switched to single or dual, with a counter 26 * for each divider to set when the switching is done. 27 * The entire dividing mechanism can be also bypassed. 28 */ 29 30#define CLK_CNTL0_N1_MASK GENMASK(11, 0) 31#define CLK_CNTL0_N2_MASK GENMASK(23, 12) 32#define CLK_CNTL0_DUALDIV_EN BIT(28) 33#define CLK_CNTL0_OUT_GATE_EN BIT(30) 34#define CLK_CNTL0_IN_GATE_EN BIT(31) 35 36#define CLK_CNTL1_M1_MASK GENMASK(11, 0) 37#define CLK_CNTL1_M2_MASK GENMASK(23, 12) 38#define CLK_CNTL1_BYPASS_EN BIT(24) 39#define CLK_CNTL1_SELECT_OSC BIT(27) 40 41#define PWR_CNTL_ALT_32K_SEL GENMASK(13, 10) 42 43struct cec_32k_freq_table { 44 unsigned long parent_rate; 45 unsigned long target_rate; 46 bool dualdiv; 47 unsigned int n1; 48 unsigned int n2; 49 unsigned int m1; 50 unsigned int m2; 51}; 52 53static const struct cec_32k_freq_table aoclk_cec_32k_table[] = { 54 [0] = { 55 .parent_rate = 24000000, 56 .target_rate = 32768, 57 .dualdiv = true, 58 .n1 = 733, 59 .n2 = 732, 60 .m1 = 8, 61 .m2 = 11, 62 }, 63}; 64 65/* 66 * If CLK_CNTL0_DUALDIV_EN == 0 67 * - will use N1 divider only 68 * If CLK_CNTL0_DUALDIV_EN == 1 69 * - hold M1 cycles of N1 divider then changes to N2 70 * - hold M2 cycles of N2 divider then changes to N1 71 * Then we can get more accurate division. 72 */ 73static unsigned long aoclk_cec_32k_recalc_rate(struct clk_hw *hw, 74 unsigned long parent_rate) 75{ 76 struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw); 77 unsigned long n1; 78 u32 reg0, reg1; 79 80 regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, &reg0); 81 regmap_read(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, &reg1); 82 83 if (reg1 & CLK_CNTL1_BYPASS_EN) 84 return parent_rate; 85 86 if (reg0 & CLK_CNTL0_DUALDIV_EN) { 87 unsigned long n2, m1, m2, f1, f2, p1, p2; 88 89 n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1; 90 n2 = FIELD_GET(CLK_CNTL0_N2_MASK, reg0) + 1; 91 92 m1 = FIELD_GET(CLK_CNTL1_M1_MASK, reg1) + 1; 93 m2 = FIELD_GET(CLK_CNTL1_M2_MASK, reg1) + 1; 94 95 f1 = DIV_ROUND_CLOSEST(parent_rate, n1); 96 f2 = DIV_ROUND_CLOSEST(parent_rate, n2); 97 98 p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2)); 99 p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2)); 100 101 return DIV_ROUND_UP(100000000, p1 + p2); 102 } 103 104 n1 = FIELD_GET(CLK_CNTL0_N1_MASK, reg0) + 1; 105 106 return DIV_ROUND_CLOSEST(parent_rate, n1); 107} 108 109static const struct cec_32k_freq_table *find_cec_32k_freq(unsigned long rate, 110 unsigned long prate) 111{ 112 int i; 113 114 for (i = 0 ; i < ARRAY_SIZE(aoclk_cec_32k_table) ; ++i) 115 if (aoclk_cec_32k_table[i].parent_rate == prate && 116 aoclk_cec_32k_table[i].target_rate == rate) 117 return &aoclk_cec_32k_table[i]; 118 119 return NULL; 120} 121 122static long aoclk_cec_32k_round_rate(struct clk_hw *hw, unsigned long rate, 123 unsigned long *prate) 124{ 125 const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate, 126 *prate); 127 128 /* If invalid return first one */ 129 if (!freq) 130 return aoclk_cec_32k_table[0].target_rate; 131 132 return freq->target_rate; 133} 134 135/* 136 * From the Amlogic init procedure, the IN and OUT gates needs to be handled 137 * in the init procedure to avoid any glitches. 138 */ 139 140static int aoclk_cec_32k_set_rate(struct clk_hw *hw, unsigned long rate, 141 unsigned long parent_rate) 142{ 143 const struct cec_32k_freq_table *freq = find_cec_32k_freq(rate, 144 parent_rate); 145 struct aoclk_cec_32k *cec_32k = to_aoclk_cec_32k(hw); 146 u32 reg = 0; 147 148 if (!freq) 149 return -EINVAL; 150 151 /* Disable clock */ 152 regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, 153 CLK_CNTL0_IN_GATE_EN | CLK_CNTL0_OUT_GATE_EN, 0); 154 155 reg = FIELD_PREP(CLK_CNTL0_N1_MASK, freq->n1 - 1); 156 if (freq->dualdiv) 157 reg |= CLK_CNTL0_DUALDIV_EN | 158 FIELD_PREP(CLK_CNTL0_N2_MASK, freq->n2 - 1); 159 160 regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, reg); 161 162 reg = FIELD_PREP(CLK_CNTL1_M1_MASK, freq->m1 - 1); 163 if (freq->dualdiv) 164 reg |= FIELD_PREP(CLK_CNTL1_M2_MASK, freq->m2 - 1); 165 166 regmap_write(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL1, reg); 167 168 /* Enable clock */ 169 regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, 170 CLK_CNTL0_IN_GATE_EN, CLK_CNTL0_IN_GATE_EN); 171 172 udelay(200); 173 174 regmap_update_bits(cec_32k->regmap, AO_RTC_ALT_CLK_CNTL0, 175 CLK_CNTL0_OUT_GATE_EN, CLK_CNTL0_OUT_GATE_EN); 176 177 regmap_update_bits(cec_32k->regmap, AO_CRT_CLK_CNTL1, 178 CLK_CNTL1_SELECT_OSC, CLK_CNTL1_SELECT_OSC); 179 180 /* Select 32k from XTAL */ 181 regmap_update_bits(cec_32k->regmap, 182 AO_RTI_PWR_CNTL_REG0, 183 PWR_CNTL_ALT_32K_SEL, 184 FIELD_PREP(PWR_CNTL_ALT_32K_SEL, 4)); 185 186 return 0; 187} 188 189const struct clk_ops meson_aoclk_cec_32k_ops = { 190 .recalc_rate = aoclk_cec_32k_recalc_rate, 191 .round_rate = aoclk_cec_32k_round_rate, 192 .set_rate = aoclk_cec_32k_set_rate, 193};