Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 * Copyright (C) 2016 Free Electrons
  3 * Copyright (C) 2016 NextThing Co
  4 *
  5 * Maxime Ripard <maxime.ripard@free-electrons.com>
  6 *
  7 * This program is free software; you can redistribute it and/or
  8 * modify it under the terms of the GNU General Public License as
  9 * published by the Free Software Foundation; either version 2 of
 10 * the License, or (at your option) any later version.
 11 */
 12
 13#include <linux/clk-provider.h>
 14#include <linux/regmap.h>
 15
 16#include "sun4i_tcon.h"
 17#include "sun4i_dotclock.h"
 18
 19struct sun4i_dclk {
 20	struct clk_hw		hw;
 21	struct regmap		*regmap;
 22	struct sun4i_tcon	*tcon;
 23};
 24
 25static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
 26{
 27	return container_of(hw, struct sun4i_dclk, hw);
 28}
 29
 30static void sun4i_dclk_disable(struct clk_hw *hw)
 31{
 32	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 33
 34	regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 35			   BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
 36}
 37
 38static int sun4i_dclk_enable(struct clk_hw *hw)
 39{
 40	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 41
 42	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 43				  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
 44				  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
 45}
 46
 47static int sun4i_dclk_is_enabled(struct clk_hw *hw)
 48{
 49	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 50	u32 val;
 51
 52	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 53
 54	return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
 55}
 56
 57static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
 58					    unsigned long parent_rate)
 59{
 60	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 61	u32 val;
 62
 63	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 64
 65	val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
 66	val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
 67
 68	if (!val)
 69		val = 1;
 70
 71	return parent_rate / val;
 72}
 73
 74static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
 75				  unsigned long *parent_rate)
 76{
 77	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 78	struct sun4i_tcon *tcon = dclk->tcon;
 79	unsigned long best_parent = 0;
 80	u8 best_div = 1;
 81	int i;
 82
 83	for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
 84		unsigned long ideal = rate * i;
 85		unsigned long rounded;
 86
 87		rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
 88					    ideal);
 89
 90		if (rounded == ideal) {
 91			best_parent = rounded;
 92			best_div = i;
 93			goto out;
 94		}
 95
 96		if (abs(rate - rounded / i) <
 97		    abs(rate - best_parent / best_div)) {
 98			best_parent = rounded;
 99			best_div = i;
100		}
101	}
102
103out:
104	*parent_rate = best_parent;
105
106	return best_parent / best_div;
107}
108
109static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
110			       unsigned long parent_rate)
111{
112	struct sun4i_dclk *dclk = hw_to_dclk(hw);
113	u8 div = parent_rate / rate;
114
115	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
116				  GENMASK(6, 0), div);
117}
118
119static int sun4i_dclk_get_phase(struct clk_hw *hw)
120{
121	struct sun4i_dclk *dclk = hw_to_dclk(hw);
122	u32 val;
123
124	regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
125
126	val >>= 28;
127	val &= 3;
128
129	return val * 120;
130}
131
132static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
133{
134	struct sun4i_dclk *dclk = hw_to_dclk(hw);
135	u32 val = degrees / 120;
136
137	val <<= 28;
138
139	regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
140			   GENMASK(29, 28),
141			   val);
142
143	return 0;
144}
145
146static const struct clk_ops sun4i_dclk_ops = {
147	.disable	= sun4i_dclk_disable,
148	.enable		= sun4i_dclk_enable,
149	.is_enabled	= sun4i_dclk_is_enabled,
150
151	.recalc_rate	= sun4i_dclk_recalc_rate,
152	.round_rate	= sun4i_dclk_round_rate,
153	.set_rate	= sun4i_dclk_set_rate,
154
155	.get_phase	= sun4i_dclk_get_phase,
156	.set_phase	= sun4i_dclk_set_phase,
157};
158
159int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
160{
161	const char *clk_name, *parent_name;
162	struct clk_init_data init;
163	struct sun4i_dclk *dclk;
164	int ret;
165
166	parent_name = __clk_get_name(tcon->sclk0);
167	ret = of_property_read_string_index(dev->of_node,
168					    "clock-output-names", 0,
169					    &clk_name);
170	if (ret)
171		return ret;
172
173	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
174	if (!dclk)
175		return -ENOMEM;
176	dclk->tcon = tcon;
177
178	init.name = clk_name;
179	init.ops = &sun4i_dclk_ops;
180	init.parent_names = &parent_name;
181	init.num_parents = 1;
182	init.flags = CLK_SET_RATE_PARENT;
183
184	dclk->regmap = tcon->regs;
185	dclk->hw.init = &init;
186
187	tcon->dclk = clk_register(dev, &dclk->hw);
188	if (IS_ERR(tcon->dclk))
189		return PTR_ERR(tcon->dclk);
190
191	return 0;
192}
193EXPORT_SYMBOL(sun4i_dclk_create);
194
195int sun4i_dclk_free(struct sun4i_tcon *tcon)
196{
197	clk_unregister(tcon->dclk);
198	return 0;
199}
200EXPORT_SYMBOL(sun4i_dclk_free);