1d3ff9728SAbel Vesa // SPDX-License-Identifier: GPL-2.0
2d3ff9728SAbel Vesa /*
3d3ff9728SAbel Vesa  * Copyright 2018 NXP
4d3ff9728SAbel Vesa  */
5d3ff9728SAbel Vesa 
6d3ff9728SAbel Vesa #include <linux/clk-provider.h>
762e59c4eSStephen Boyd #include <linux/errno.h>
862e59c4eSStephen Boyd #include <linux/io.h>
962e59c4eSStephen Boyd #include <linux/slab.h>
10d3ff9728SAbel Vesa 
11d3ff9728SAbel Vesa #include "clk.h"
12d3ff9728SAbel Vesa 
13d3ff9728SAbel Vesa #define PCG_PREDIV_SHIFT	16
14d3ff9728SAbel Vesa #define PCG_PREDIV_WIDTH	3
15d3ff9728SAbel Vesa #define PCG_PREDIV_MAX		8
16d3ff9728SAbel Vesa 
17d3ff9728SAbel Vesa #define PCG_DIV_SHIFT		0
1862668b68SPeng Fan #define PCG_CORE_DIV_WIDTH	3
19d3ff9728SAbel Vesa #define PCG_DIV_WIDTH		6
20d3ff9728SAbel Vesa #define PCG_DIV_MAX		64
21d3ff9728SAbel Vesa 
22d3ff9728SAbel Vesa #define PCG_PCS_SHIFT		24
23d3ff9728SAbel Vesa #define PCG_PCS_MASK		0x7
24d3ff9728SAbel Vesa 
25d3ff9728SAbel Vesa #define PCG_CGC_SHIFT		28
26d3ff9728SAbel Vesa 
27d3ff9728SAbel Vesa static unsigned long imx8m_clk_composite_divider_recalc_rate(struct clk_hw *hw,
28d3ff9728SAbel Vesa 						unsigned long parent_rate)
29d3ff9728SAbel Vesa {
30d3ff9728SAbel Vesa 	struct clk_divider *divider = to_clk_divider(hw);
31d3ff9728SAbel Vesa 	unsigned long prediv_rate;
32d3ff9728SAbel Vesa 	unsigned int prediv_value;
33d3ff9728SAbel Vesa 	unsigned int div_value;
34d3ff9728SAbel Vesa 
35d3ff9728SAbel Vesa 	prediv_value = readl(divider->reg) >> divider->shift;
36d3ff9728SAbel Vesa 	prediv_value &= clk_div_mask(divider->width);
37d3ff9728SAbel Vesa 
38d3ff9728SAbel Vesa 	prediv_rate = divider_recalc_rate(hw, parent_rate, prediv_value,
39d3ff9728SAbel Vesa 						NULL, divider->flags,
40d3ff9728SAbel Vesa 						divider->width);
41d3ff9728SAbel Vesa 
42d3ff9728SAbel Vesa 	div_value = readl(divider->reg) >> PCG_DIV_SHIFT;
43d3ff9728SAbel Vesa 	div_value &= clk_div_mask(PCG_DIV_WIDTH);
44d3ff9728SAbel Vesa 
45d3ff9728SAbel Vesa 	return divider_recalc_rate(hw, prediv_rate, div_value, NULL,
46d3ff9728SAbel Vesa 				   divider->flags, PCG_DIV_WIDTH);
47d3ff9728SAbel Vesa }
48d3ff9728SAbel Vesa 
49d3ff9728SAbel Vesa static int imx8m_clk_composite_compute_dividers(unsigned long rate,
50d3ff9728SAbel Vesa 						unsigned long parent_rate,
51d3ff9728SAbel Vesa 						int *prediv, int *postdiv)
52d3ff9728SAbel Vesa {
53d3ff9728SAbel Vesa 	int div1, div2;
54d3ff9728SAbel Vesa 	int error = INT_MAX;
55d3ff9728SAbel Vesa 	int ret = -EINVAL;
56d3ff9728SAbel Vesa 
57d3ff9728SAbel Vesa 	*prediv = 1;
58d3ff9728SAbel Vesa 	*postdiv = 1;
59d3ff9728SAbel Vesa 
60d3ff9728SAbel Vesa 	for (div1 = 1; div1 <= PCG_PREDIV_MAX; div1++) {
61d3ff9728SAbel Vesa 		for (div2 = 1; div2 <= PCG_DIV_MAX; div2++) {
62d3ff9728SAbel Vesa 			int new_error = ((parent_rate / div1) / div2) - rate;
63d3ff9728SAbel Vesa 
64d3ff9728SAbel Vesa 			if (abs(new_error) < abs(error)) {
65d3ff9728SAbel Vesa 				*prediv = div1;
66d3ff9728SAbel Vesa 				*postdiv = div2;
67d3ff9728SAbel Vesa 				error = new_error;
68d3ff9728SAbel Vesa 				ret = 0;
69d3ff9728SAbel Vesa 			}
70d3ff9728SAbel Vesa 		}
71d3ff9728SAbel Vesa 	}
72d3ff9728SAbel Vesa 	return ret;
73d3ff9728SAbel Vesa }
74d3ff9728SAbel Vesa 
75d3ff9728SAbel Vesa static long imx8m_clk_composite_divider_round_rate(struct clk_hw *hw,
76d3ff9728SAbel Vesa 						unsigned long rate,
77d3ff9728SAbel Vesa 						unsigned long *prate)
78d3ff9728SAbel Vesa {
79d3ff9728SAbel Vesa 	int prediv_value;
80d3ff9728SAbel Vesa 	int div_value;
81d3ff9728SAbel Vesa 
82d3ff9728SAbel Vesa 	imx8m_clk_composite_compute_dividers(rate, *prate,
83d3ff9728SAbel Vesa 						&prediv_value, &div_value);
84d3ff9728SAbel Vesa 	rate = DIV_ROUND_UP(*prate, prediv_value);
85d3ff9728SAbel Vesa 
86d3ff9728SAbel Vesa 	return DIV_ROUND_UP(rate, div_value);
87d3ff9728SAbel Vesa 
88d3ff9728SAbel Vesa }
89d3ff9728SAbel Vesa 
90d3ff9728SAbel Vesa static int imx8m_clk_composite_divider_set_rate(struct clk_hw *hw,
91d3ff9728SAbel Vesa 					unsigned long rate,
92d3ff9728SAbel Vesa 					unsigned long parent_rate)
93d3ff9728SAbel Vesa {
94d3ff9728SAbel Vesa 	struct clk_divider *divider = to_clk_divider(hw);
95d3ff9728SAbel Vesa 	unsigned long flags = 0;
96d3ff9728SAbel Vesa 	int prediv_value;
97d3ff9728SAbel Vesa 	int div_value;
9833e7a842SColin Ian King 	int ret;
99d3ff9728SAbel Vesa 	u32 val;
100d3ff9728SAbel Vesa 
101d3ff9728SAbel Vesa 	ret = imx8m_clk_composite_compute_dividers(rate, parent_rate,
102d3ff9728SAbel Vesa 						&prediv_value, &div_value);
103d3ff9728SAbel Vesa 	if (ret)
104d3ff9728SAbel Vesa 		return -EINVAL;
105d3ff9728SAbel Vesa 
106d3ff9728SAbel Vesa 	spin_lock_irqsave(divider->lock, flags);
107d3ff9728SAbel Vesa 
108d3ff9728SAbel Vesa 	val = readl(divider->reg);
109d3ff9728SAbel Vesa 	val &= ~((clk_div_mask(divider->width) << divider->shift) |
110d3ff9728SAbel Vesa 			(clk_div_mask(PCG_DIV_WIDTH) << PCG_DIV_SHIFT));
111d3ff9728SAbel Vesa 
112d3ff9728SAbel Vesa 	val |= (u32)(prediv_value  - 1) << divider->shift;
113d3ff9728SAbel Vesa 	val |= (u32)(div_value - 1) << PCG_DIV_SHIFT;
114d3ff9728SAbel Vesa 	writel(val, divider->reg);
115d3ff9728SAbel Vesa 
116d3ff9728SAbel Vesa 	spin_unlock_irqrestore(divider->lock, flags);
117d3ff9728SAbel Vesa 
118d3ff9728SAbel Vesa 	return ret;
119d3ff9728SAbel Vesa }
120d3ff9728SAbel Vesa 
121d3ff9728SAbel Vesa static const struct clk_ops imx8m_clk_composite_divider_ops = {
122d3ff9728SAbel Vesa 	.recalc_rate = imx8m_clk_composite_divider_recalc_rate,
123d3ff9728SAbel Vesa 	.round_rate = imx8m_clk_composite_divider_round_rate,
124d3ff9728SAbel Vesa 	.set_rate = imx8m_clk_composite_divider_set_rate,
125d3ff9728SAbel Vesa };
126d3ff9728SAbel Vesa 
127a4b431f8SPeng Fan struct clk_hw *imx8m_clk_hw_composite_flags(const char *name,
12865a6b7c5SAbel Vesa 					const char * const *parent_names,
129d3ff9728SAbel Vesa 					int num_parents, void __iomem *reg,
13062668b68SPeng Fan 					u32 composite_flags,
131d3ff9728SAbel Vesa 					unsigned long flags)
132d3ff9728SAbel Vesa {
133d3ff9728SAbel Vesa 	struct clk_hw *hw = ERR_PTR(-ENOMEM), *mux_hw;
134d3ff9728SAbel Vesa 	struct clk_hw *div_hw, *gate_hw;
135d3ff9728SAbel Vesa 	struct clk_divider *div = NULL;
136d3ff9728SAbel Vesa 	struct clk_gate *gate = NULL;
137d3ff9728SAbel Vesa 	struct clk_mux *mux = NULL;
13862668b68SPeng Fan 	const struct clk_ops *divider_ops;
139d3ff9728SAbel Vesa 
140d3ff9728SAbel Vesa 	mux = kzalloc(sizeof(*mux), GFP_KERNEL);
141d3ff9728SAbel Vesa 	if (!mux)
142d3ff9728SAbel Vesa 		goto fail;
143d3ff9728SAbel Vesa 
144d3ff9728SAbel Vesa 	mux_hw = &mux->hw;
145d3ff9728SAbel Vesa 	mux->reg = reg;
146d3ff9728SAbel Vesa 	mux->shift = PCG_PCS_SHIFT;
147d3ff9728SAbel Vesa 	mux->mask = PCG_PCS_MASK;
148073a01e8SPeng Fan 	mux->lock = &imx_ccm_lock;
149d3ff9728SAbel Vesa 
150d3ff9728SAbel Vesa 	div = kzalloc(sizeof(*div), GFP_KERNEL);
151d3ff9728SAbel Vesa 	if (!div)
152d3ff9728SAbel Vesa 		goto fail;
153d3ff9728SAbel Vesa 
154d3ff9728SAbel Vesa 	div_hw = &div->hw;
155d3ff9728SAbel Vesa 	div->reg = reg;
15662668b68SPeng Fan 	if (composite_flags & IMX_COMPOSITE_CORE) {
15762668b68SPeng Fan 		div->shift = PCG_DIV_SHIFT;
15862668b68SPeng Fan 		div->width = PCG_CORE_DIV_WIDTH;
15962668b68SPeng Fan 		divider_ops = &clk_divider_ops;
16062668b68SPeng Fan 	} else {
161d3ff9728SAbel Vesa 		div->shift = PCG_PREDIV_SHIFT;
162d3ff9728SAbel Vesa 		div->width = PCG_PREDIV_WIDTH;
16362668b68SPeng Fan 		divider_ops = &imx8m_clk_composite_divider_ops;
16462668b68SPeng Fan 	}
16562668b68SPeng Fan 
166d3ff9728SAbel Vesa 	div->lock = &imx_ccm_lock;
167d3ff9728SAbel Vesa 	div->flags = CLK_DIVIDER_ROUND_CLOSEST;
168d3ff9728SAbel Vesa 
169d3ff9728SAbel Vesa 	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
170d3ff9728SAbel Vesa 	if (!gate)
171d3ff9728SAbel Vesa 		goto fail;
172d3ff9728SAbel Vesa 
173d3ff9728SAbel Vesa 	gate_hw = &gate->hw;
174d3ff9728SAbel Vesa 	gate->reg = reg;
175d3ff9728SAbel Vesa 	gate->bit_idx = PCG_CGC_SHIFT;
176073a01e8SPeng Fan 	gate->lock = &imx_ccm_lock;
177d3ff9728SAbel Vesa 
178d3ff9728SAbel Vesa 	hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
179d3ff9728SAbel Vesa 			mux_hw, &clk_mux_ops, div_hw,
18062668b68SPeng Fan 			divider_ops, gate_hw, &clk_gate_ops, flags);
181d3ff9728SAbel Vesa 	if (IS_ERR(hw))
182d3ff9728SAbel Vesa 		goto fail;
183d3ff9728SAbel Vesa 
184a4b431f8SPeng Fan 	return hw;
185d3ff9728SAbel Vesa 
186d3ff9728SAbel Vesa fail:
187d3ff9728SAbel Vesa 	kfree(gate);
188d3ff9728SAbel Vesa 	kfree(div);
189d3ff9728SAbel Vesa 	kfree(mux);
190d3ff9728SAbel Vesa 	return ERR_CAST(hw);
191d3ff9728SAbel Vesa }
192