1/*
2 * DRA7 ATL (Audio Tracking Logic) clock driver
3 *
4 * Copyright (C) 2013 Texas Instruments, Inc.
5 *
6 * Peter Ujfalusi <peter.ujfalusi@ti.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
13 * kind, whether express or implied; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 */
17
18#include <linux/init.h>
19#include <linux/clk.h>
20#include <linux/clk-provider.h>
21#include <linux/slab.h>
22#include <linux/io.h>
23#include <linux/of.h>
24#include <linux/of_address.h>
25#include <linux/platform_device.h>
26#include <linux/pm_runtime.h>
27#include <linux/clk/ti.h>
28
29#include "clock.h"
30
31#define DRA7_ATL_INSTANCES	4
32
33#define DRA7_ATL_PPMR_REG(id)		(0x200 + (id * 0x80))
34#define DRA7_ATL_BBSR_REG(id)		(0x204 + (id * 0x80))
35#define DRA7_ATL_ATLCR_REG(id)		(0x208 + (id * 0x80))
36#define DRA7_ATL_SWEN_REG(id)		(0x210 + (id * 0x80))
37#define DRA7_ATL_BWSMUX_REG(id)		(0x214 + (id * 0x80))
38#define DRA7_ATL_AWSMUX_REG(id)		(0x218 + (id * 0x80))
39#define DRA7_ATL_PCLKMUX_REG(id)	(0x21c + (id * 0x80))
40
41#define DRA7_ATL_SWEN			BIT(0)
42#define DRA7_ATL_DIVIDER_MASK		(0x1f)
43#define DRA7_ATL_PCLKMUX		BIT(0)
44struct dra7_atl_clock_info;
45
46struct dra7_atl_desc {
47	struct clk *clk;
48	struct clk_hw hw;
49	struct dra7_atl_clock_info *cinfo;
50	int id;
51
52	bool probed;		/* the driver for the IP has been loaded */
53	bool valid;		/* configured */
54	bool enabled;
55	u32 bws;		/* Baseband Word Select Mux */
56	u32 aws;		/* Audio Word Select Mux */
57	u32 divider;		/* Cached divider value */
58};
59
60struct dra7_atl_clock_info {
61	struct device *dev;
62	void __iomem *iobase;
63
64	struct dra7_atl_desc *cdesc;
65};
66
67#define to_atl_desc(_hw)	container_of(_hw, struct dra7_atl_desc, hw)
68
69static inline void atl_write(struct dra7_atl_clock_info *cinfo, u32 reg,
70			     u32 val)
71{
72	__raw_writel(val, cinfo->iobase + reg);
73}
74
75static inline int atl_read(struct dra7_atl_clock_info *cinfo, u32 reg)
76{
77	return __raw_readl(cinfo->iobase + reg);
78}
79
80static int atl_clk_enable(struct clk_hw *hw)
81{
82	struct dra7_atl_desc *cdesc = to_atl_desc(hw);
83
84	if (!cdesc->probed)
85		goto out;
86
87	if (unlikely(!cdesc->valid))
88		dev_warn(cdesc->cinfo->dev, "atl%d has not been configured\n",
89			 cdesc->id);
90	pm_runtime_get_sync(cdesc->cinfo->dev);
91
92	atl_write(cdesc->cinfo, DRA7_ATL_ATLCR_REG(cdesc->id),
93		  cdesc->divider - 1);
94	atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), DRA7_ATL_SWEN);
95
96out:
97	cdesc->enabled = true;
98
99	return 0;
100}
101
102static void atl_clk_disable(struct clk_hw *hw)
103{
104	struct dra7_atl_desc *cdesc = to_atl_desc(hw);
105
106	if (!cdesc->probed)
107		goto out;
108
109	atl_write(cdesc->cinfo, DRA7_ATL_SWEN_REG(cdesc->id), 0);
110	pm_runtime_put_sync(cdesc->cinfo->dev);
111
112out:
113	cdesc->enabled = false;
114}
115
116static int atl_clk_is_enabled(struct clk_hw *hw)
117{
118	struct dra7_atl_desc *cdesc = to_atl_desc(hw);
119
120	return cdesc->enabled;
121}
122
123static unsigned long atl_clk_recalc_rate(struct clk_hw *hw,
124					 unsigned long parent_rate)
125{
126	struct dra7_atl_desc *cdesc = to_atl_desc(hw);
127
128	return parent_rate / cdesc->divider;
129}
130
131static long atl_clk_round_rate(struct clk_hw *hw, unsigned long rate,
132			       unsigned long *parent_rate)
133{
134	unsigned divider;
135
136	divider = (*parent_rate + rate / 2) / rate;
137	if (divider > DRA7_ATL_DIVIDER_MASK + 1)
138		divider = DRA7_ATL_DIVIDER_MASK + 1;
139
140	return *parent_rate / divider;
141}
142
143static int atl_clk_set_rate(struct clk_hw *hw, unsigned long rate,
144			    unsigned long parent_rate)
145{
146	struct dra7_atl_desc *cdesc;
147	u32 divider;
148
149	if (!hw || !rate)
150		return -EINVAL;
151
152	cdesc = to_atl_desc(hw);
153	divider = ((parent_rate + rate / 2) / rate) - 1;
154	if (divider > DRA7_ATL_DIVIDER_MASK)
155		divider = DRA7_ATL_DIVIDER_MASK;
156
157	cdesc->divider = divider + 1;
158
159	return 0;
160}
161
162static const struct clk_ops atl_clk_ops = {
163	.enable		= atl_clk_enable,
164	.disable	= atl_clk_disable,
165	.is_enabled	= atl_clk_is_enabled,
166	.recalc_rate	= atl_clk_recalc_rate,
167	.round_rate	= atl_clk_round_rate,
168	.set_rate	= atl_clk_set_rate,
169};
170
171static void __init of_dra7_atl_clock_setup(struct device_node *node)
172{
173	struct dra7_atl_desc *clk_hw = NULL;
174	struct clk_init_data init = { NULL };
175	const char **parent_names = NULL;
176	const char *name;
177	struct clk *clk;
178
179	clk_hw = kzalloc(sizeof(*clk_hw), GFP_KERNEL);
180	if (!clk_hw) {
181		pr_err("%s: could not allocate dra7_atl_desc\n", __func__);
182		return;
183	}
184
185	clk_hw->hw.init = &init;
186	clk_hw->divider = 1;
187	name = ti_dt_clk_name(node);
188	init.name = name;
189	init.ops = &atl_clk_ops;
190	init.flags = CLK_IGNORE_UNUSED;
191	init.num_parents = of_clk_get_parent_count(node);
192
193	if (init.num_parents != 1) {
194		pr_err("%s: atl clock %pOFn must have 1 parent\n", __func__,
195		       node);
196		goto cleanup;
197	}
198
199	parent_names = kzalloc(sizeof(char *), GFP_KERNEL);
200
201	if (!parent_names)
202		goto cleanup;
203
204	parent_names[0] = of_clk_get_parent_name(node, 0);
205
206	init.parent_names = parent_names;
207
208	clk = of_ti_clk_register(node, &clk_hw->hw, name);
209
210	if (!IS_ERR(clk)) {
211		of_clk_add_provider(node, of_clk_src_simple_get, clk);
212		kfree(parent_names);
213		return;
214	}
215cleanup:
216	kfree(parent_names);
217	kfree(clk_hw);
218}
219CLK_OF_DECLARE(dra7_atl_clock, "ti,dra7-atl-clock", of_dra7_atl_clock_setup);
220
221static int of_dra7_atl_clk_probe(struct platform_device *pdev)
222{
223	struct device_node *node = pdev->dev.of_node;
224	struct dra7_atl_clock_info *cinfo;
225	int i;
226	int ret = 0;
227
228	if (!node)
229		return -ENODEV;
230
231	cinfo = devm_kzalloc(&pdev->dev, sizeof(*cinfo), GFP_KERNEL);
232	if (!cinfo)
233		return -ENOMEM;
234
235	cinfo->iobase = of_iomap(node, 0);
236	cinfo->dev = &pdev->dev;
237	pm_runtime_enable(cinfo->dev);
238
239	pm_runtime_get_sync(cinfo->dev);
240	atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX);
241
242	for (i = 0; i < DRA7_ATL_INSTANCES; i++) {
243		struct device_node *cfg_node;
244		char prop[5];
245		struct dra7_atl_desc *cdesc;
246		struct of_phandle_args clkspec;
247		struct clk *clk;
248		int rc;
249
250		rc = of_parse_phandle_with_args(node, "ti,provided-clocks",
251						NULL, i, &clkspec);
252
253		if (rc) {
254			pr_err("%s: failed to lookup atl clock %d\n", __func__,
255			       i);
256			ret = -EINVAL;
257			goto pm_put;
258		}
259
260		clk = of_clk_get_from_provider(&clkspec);
261		if (IS_ERR(clk)) {
262			pr_err("%s: failed to get atl clock %d from provider\n",
263			       __func__, i);
264			ret = PTR_ERR(clk);
265			goto pm_put;
266		}
267
268		cdesc = to_atl_desc(__clk_get_hw(clk));
269		cdesc->cinfo = cinfo;
270		cdesc->id = i;
271
272		/* Get configuration for the ATL instances */
273		snprintf(prop, sizeof(prop), "atl%u", i);
274		cfg_node = of_get_child_by_name(node, prop);
275		if (cfg_node) {
276			ret = of_property_read_u32(cfg_node, "bws",
277						   &cdesc->bws);
278			ret |= of_property_read_u32(cfg_node, "aws",
279						    &cdesc->aws);
280			if (!ret) {
281				cdesc->valid = true;
282				atl_write(cinfo, DRA7_ATL_BWSMUX_REG(i),
283					  cdesc->bws);
284				atl_write(cinfo, DRA7_ATL_AWSMUX_REG(i),
285					  cdesc->aws);
286			}
287			of_node_put(cfg_node);
288		}
289
290		cdesc->probed = true;
291		/*
292		 * Enable the clock if it has been asked prior to loading the
293		 * hw driver
294		 */
295		if (cdesc->enabled)
296			atl_clk_enable(__clk_get_hw(clk));
297	}
298
299pm_put:
300	pm_runtime_put_sync(cinfo->dev);
301	return ret;
302}
303
304static const struct of_device_id of_dra7_atl_clk_match_tbl[] = {
305	{ .compatible = "ti,dra7-atl", },
306	{},
307};
308
309static struct platform_driver dra7_atl_clk_driver = {
310	.driver = {
311		.name = "dra7-atl",
312		.suppress_bind_attrs = true,
313		.of_match_table = of_dra7_atl_clk_match_tbl,
314	},
315	.probe = of_dra7_atl_clk_probe,
316};
317builtin_platform_driver(dra7_atl_clk_driver);
318