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};
 23
 24static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
 25{
 26	return container_of(hw, struct sun4i_dclk, hw);
 27}
 28
 29static void sun4i_dclk_disable(struct clk_hw *hw)
 30{
 31	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 32
 33	regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 34			   BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
 35}
 36
 37static int sun4i_dclk_enable(struct clk_hw *hw)
 38{
 39	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 40
 41	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
 42				  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
 43				  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
 44}
 45
 46static int sun4i_dclk_is_enabled(struct clk_hw *hw)
 47{
 48	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 49	u32 val;
 50
 51	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 52
 53	return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
 54}
 55
 56static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
 57					    unsigned long parent_rate)
 58{
 59	struct sun4i_dclk *dclk = hw_to_dclk(hw);
 60	u32 val;
 61
 62	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
 63
 64	val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
 65	val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
 66
 67	if (!val)
 68		val = 1;
 69
 70	return parent_rate / val;
 71}
 72
 73static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
 74				  unsigned long *parent_rate)
 75{
 76	unsigned long best_parent = 0;
 77	u8 best_div = 1;
 78	int i;
 79
 80	for (i = 6; i <= 127; i++) {
 81		unsigned long ideal = rate * i;
 82		unsigned long rounded;
 83
 84		rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
 85					    ideal);
 86
 87		if (rounded == ideal) {
 88			best_parent = rounded;
 89			best_div = i;
 90			goto out;
 91		}
 92
 93		if (abs(rate - rounded / i) <
 94		    abs(rate - best_parent / best_div)) {
 95			best_parent = rounded;
 96			best_div = i;
 97		}
 98	}
 99
100out:
101	*parent_rate = best_parent;
102
103	return best_parent / best_div;
104}
105
106static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
107			       unsigned long parent_rate)
108{
109	struct sun4i_dclk *dclk = hw_to_dclk(hw);
110	u8 div = parent_rate / rate;
111
112	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
113				  GENMASK(6, 0), div);
114}
115
116static int sun4i_dclk_get_phase(struct clk_hw *hw)
117{
118	struct sun4i_dclk *dclk = hw_to_dclk(hw);
119	u32 val;
120
121	regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
122
123	val >>= 28;
124	val &= 3;
125
126	return val * 120;
127}
128
129static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
130{
131	struct sun4i_dclk *dclk = hw_to_dclk(hw);
132
133	regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
134			   GENMASK(29, 28),
135			   degrees / 120);
136
137	return 0;
138}
139
140static const struct clk_ops sun4i_dclk_ops = {
141	.disable	= sun4i_dclk_disable,
142	.enable		= sun4i_dclk_enable,
143	.is_enabled	= sun4i_dclk_is_enabled,
144
145	.recalc_rate	= sun4i_dclk_recalc_rate,
146	.round_rate	= sun4i_dclk_round_rate,
147	.set_rate	= sun4i_dclk_set_rate,
148
149	.get_phase	= sun4i_dclk_get_phase,
150	.set_phase	= sun4i_dclk_set_phase,
151};
152
153int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
154{
155	const char *clk_name, *parent_name;
156	struct clk_init_data init;
157	struct sun4i_dclk *dclk;
158	int ret;
159
160	parent_name = __clk_get_name(tcon->sclk0);
161	ret = of_property_read_string_index(dev->of_node,
162					    "clock-output-names", 0,
163					    &clk_name);
164	if (ret)
165		return ret;
166
167	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
168	if (!dclk)
169		return -ENOMEM;
170
171	init.name = clk_name;
172	init.ops = &sun4i_dclk_ops;
173	init.parent_names = &parent_name;
174	init.num_parents = 1;
175	init.flags = CLK_SET_RATE_PARENT;
176
177	dclk->regmap = tcon->regs;
178	dclk->hw.init = &init;
179
180	tcon->dclk = clk_register(dev, &dclk->hw);
181	if (IS_ERR(tcon->dclk))
182		return PTR_ERR(tcon->dclk);
183
184	return 0;
185}
186EXPORT_SYMBOL(sun4i_dclk_create);
187
188int sun4i_dclk_free(struct sun4i_tcon *tcon)
189{
190	clk_unregister(tcon->dclk);
191	return 0;
192}
193EXPORT_SYMBOL(sun4i_dclk_free);