Linux Audio

Check our new training course

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