xref: /linux/drivers/gpu/drm/sun4i/sun8i_tcon_top.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1 // SPDX-License-Identifier: GPL-2.0+
2 /* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
3 
4 
5 #include <linux/bitfield.h>
6 #include <linux/component.h>
7 #include <linux/device.h>
8 #include <linux/io.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/of_graph.h>
12 #include <linux/platform_device.h>
13 
14 #include <dt-bindings/clock/sun8i-tcon-top.h>
15 
16 #include "sun8i_tcon_top.h"
17 
18 struct sun8i_tcon_top_quirks {
19 	bool has_tcon_tv1;
20 	bool has_dsi;
21 };
22 
23 static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node)
24 {
25 	return !!of_match_node(sun8i_tcon_top_of_table, node);
26 }
27 
28 static unsigned int sun8i_tcon_top_get_tcon_map(struct device_node *node)
29 {
30 	static const u32 out_ports[] = {
31 		TCON_TOP_MIXER0_OUT_PORT,
32 		TCON_TOP_MIXER1_OUT_PORT,
33 	};
34 	unsigned int i, map = 0;
35 
36 	for (i = 0; i < ARRAY_SIZE(out_ports); i++) {
37 		struct device_node *port;
38 
39 		port = of_graph_get_port_by_id(node, out_ports[i]);
40 		if (!port)
41 			continue;
42 
43 		for_each_of_graph_port_endpoint(port, ep) {
44 			struct of_endpoint endpoint;
45 
46 			if (of_graph_parse_endpoint(ep, &endpoint))
47 				continue;
48 
49 			if (endpoint.id < TCON_TOP_PORT_TCON_NUM)
50 				map |= BIT(endpoint.id);
51 		}
52 
53 		of_node_put(port);
54 	}
55 
56 	return map;
57 }
58 
59 static unsigned int sun8i_tcon_top_park_index(struct sun8i_tcon_top *tcon_top,
60 					      int tcon)
61 {
62 	unsigned int candidates;
63 
64 	candidates = tcon_top->tcon_map & ~BIT(tcon);
65 	if (!candidates)
66 		candidates = GENMASK(TCON_TOP_PORT_TCON_NUM - 1, 0) & ~BIT(tcon);
67 
68 	return ffs(candidates) - 1;
69 }
70 
71 int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon)
72 {
73 	struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
74 	unsigned long flags;
75 	u32 val;
76 
77 	if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
78 		dev_err(dev, "Device is not TCON TOP!\n");
79 		return -EINVAL;
80 	}
81 
82 	if (tcon < 2 || tcon > 3) {
83 		dev_err(dev, "TCON index must be 2 or 3!\n");
84 		return -EINVAL;
85 	}
86 
87 	spin_lock_irqsave(&tcon_top->reg_lock, flags);
88 
89 	val = readl(tcon_top->regs + TCON_TOP_GATE_SRC_REG);
90 	val &= ~TCON_TOP_HDMI_SRC_MSK;
91 	val |= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, tcon - 1);
92 	writel(val, tcon_top->regs + TCON_TOP_GATE_SRC_REG);
93 
94 	spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
95 
96 	return 0;
97 }
98 EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src);
99 
100 int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon)
101 {
102 	struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
103 	u32 mixer_msk, other_msk;
104 	unsigned long flags;
105 	u32 reg;
106 
107 	if (!sun8i_tcon_top_node_is_tcon_top(dev->of_node)) {
108 		dev_err(dev, "Device is not TCON TOP!\n");
109 		return -EINVAL;
110 	}
111 
112 	if (mixer > 1) {
113 		dev_err(dev, "Mixer index is too high!\n");
114 		return -EINVAL;
115 	}
116 
117 	if (tcon < 0 || tcon >= TCON_TOP_PORT_TCON_NUM) {
118 		dev_err(dev, "TCON index is invalid!\n");
119 		return -EINVAL;
120 	}
121 
122 	mixer_msk = mixer ? TCON_TOP_PORT_DE1_MSK : TCON_TOP_PORT_DE0_MSK;
123 	other_msk = mixer ? TCON_TOP_PORT_DE0_MSK : TCON_TOP_PORT_DE1_MSK;
124 
125 	spin_lock_irqsave(&tcon_top->reg_lock, flags);
126 
127 	reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG);
128 
129 	reg &= ~mixer_msk;
130 	reg |= field_prep(mixer_msk, tcon);
131 
132 	if (field_get(other_msk, reg) == tcon) {
133 		reg &= ~other_msk;
134 		reg |= field_prep(other_msk,
135 				  sun8i_tcon_top_park_index(tcon_top, tcon));
136 	}
137 
138 	writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG);
139 
140 	spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
141 
142 	return 0;
143 }
144 EXPORT_SYMBOL(sun8i_tcon_top_de_config);
145 
146 
147 static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev,
148 						   const char *parent,
149 						   void __iomem *regs,
150 						   spinlock_t *lock,
151 						   u8 bit, int name_index)
152 {
153 	const char *clk_name, *parent_name;
154 	int ret, index;
155 
156 	index = of_property_match_string(dev->of_node, "clock-names", parent);
157 	if (index < 0)
158 		return ERR_PTR(index);
159 
160 	parent_name = of_clk_get_parent_name(dev->of_node, index);
161 
162 	ret = of_property_read_string_index(dev->of_node,
163 					    "clock-output-names", name_index,
164 					    &clk_name);
165 	if (ret)
166 		return ERR_PTR(ret);
167 
168 	return clk_hw_register_gate(dev, clk_name, parent_name,
169 				    CLK_SET_RATE_PARENT,
170 				    regs + TCON_TOP_GATE_SRC_REG,
171 				    bit, 0, lock);
172 };
173 
174 static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
175 			       void *data)
176 {
177 	struct platform_device *pdev = to_platform_device(dev);
178 	struct clk_hw_onecell_data *clk_data;
179 	struct sun8i_tcon_top *tcon_top;
180 	const struct sun8i_tcon_top_quirks *quirks;
181 	void __iomem *regs;
182 	int ret, i;
183 
184 	quirks = of_device_get_match_data(&pdev->dev);
185 
186 	tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL);
187 	if (!tcon_top)
188 		return -ENOMEM;
189 
190 	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, CLK_NUM),
191 				GFP_KERNEL);
192 	if (!clk_data)
193 		return -ENOMEM;
194 	clk_data->num = CLK_NUM;
195 	tcon_top->clk_data = clk_data;
196 	tcon_top->tcon_map = sun8i_tcon_top_get_tcon_map(dev->of_node);
197 
198 	spin_lock_init(&tcon_top->reg_lock);
199 
200 	tcon_top->rst = devm_reset_control_get(dev, NULL);
201 	if (IS_ERR(tcon_top->rst)) {
202 		dev_err(dev, "Couldn't get our reset line\n");
203 		return PTR_ERR(tcon_top->rst);
204 	}
205 
206 	tcon_top->bus = devm_clk_get(dev, "bus");
207 	if (IS_ERR(tcon_top->bus)) {
208 		dev_err(dev, "Couldn't get the bus clock\n");
209 		return PTR_ERR(tcon_top->bus);
210 	}
211 
212 	regs = devm_platform_ioremap_resource(pdev, 0);
213 	tcon_top->regs = regs;
214 	if (IS_ERR(regs))
215 		return PTR_ERR(regs);
216 
217 	ret = reset_control_deassert(tcon_top->rst);
218 	if (ret) {
219 		dev_err(dev, "Could not deassert ctrl reset control\n");
220 		return ret;
221 	}
222 
223 	ret = clk_prepare_enable(tcon_top->bus);
224 	if (ret) {
225 		dev_err(dev, "Could not enable bus clock\n");
226 		goto err_assert_reset;
227 	}
228 
229 	writel(0, regs + TCON_TOP_PORT_SEL_REG);
230 	writel(0, regs + TCON_TOP_GATE_SRC_REG);
231 
232 	/*
233 	 * TCON TOP has two muxes, which select parent clock for each TCON TV
234 	 * channel clock. Parent could be either TCON TV or TVE clock. For now
235 	 * we leave this fixed to TCON TV, since TVE driver for R40 is not yet
236 	 * implemented. Once it is, graph needs to be traversed to determine
237 	 * if TVE is active on each TCON TV. If it is, mux should be switched
238 	 * to TVE clock parent.
239 	 */
240 	i = 0;
241 	clk_data->hws[CLK_TCON_TOP_TV0] =
242 		sun8i_tcon_top_register_gate(dev, "tcon-tv0", regs,
243 					     &tcon_top->reg_lock,
244 					     TCON_TOP_TCON_TV0_GATE, i++);
245 
246 	if (quirks->has_tcon_tv1)
247 		clk_data->hws[CLK_TCON_TOP_TV1] =
248 			sun8i_tcon_top_register_gate(dev, "tcon-tv1", regs,
249 						     &tcon_top->reg_lock,
250 						     TCON_TOP_TCON_TV1_GATE, i++);
251 
252 	if (quirks->has_dsi)
253 		clk_data->hws[CLK_TCON_TOP_DSI] =
254 			sun8i_tcon_top_register_gate(dev, "dsi", regs,
255 						     &tcon_top->reg_lock,
256 						     TCON_TOP_TCON_DSI_GATE, i++);
257 
258 	for (i = 0; i < CLK_NUM; i++)
259 		if (IS_ERR(clk_data->hws[i])) {
260 			ret = PTR_ERR(clk_data->hws[i]);
261 			goto err_unregister_gates;
262 		}
263 
264 	ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
265 				     clk_data);
266 	if (ret)
267 		goto err_unregister_gates;
268 
269 	dev_set_drvdata(dev, tcon_top);
270 
271 	return 0;
272 
273 err_unregister_gates:
274 	for (i = 0; i < CLK_NUM; i++)
275 		if (!IS_ERR_OR_NULL(clk_data->hws[i]))
276 			clk_hw_unregister_gate(clk_data->hws[i]);
277 	clk_disable_unprepare(tcon_top->bus);
278 err_assert_reset:
279 	reset_control_assert(tcon_top->rst);
280 
281 	return ret;
282 }
283 
284 static void sun8i_tcon_top_unbind(struct device *dev, struct device *master,
285 				  void *data)
286 {
287 	struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
288 	struct clk_hw_onecell_data *clk_data = tcon_top->clk_data;
289 	int i;
290 
291 	of_clk_del_provider(dev->of_node);
292 	for (i = 0; i < CLK_NUM; i++)
293 		if (clk_data->hws[i])
294 			clk_hw_unregister_gate(clk_data->hws[i]);
295 
296 	clk_disable_unprepare(tcon_top->bus);
297 	reset_control_assert(tcon_top->rst);
298 }
299 
300 static const struct component_ops sun8i_tcon_top_ops = {
301 	.bind	= sun8i_tcon_top_bind,
302 	.unbind	= sun8i_tcon_top_unbind,
303 };
304 
305 static int sun8i_tcon_top_probe(struct platform_device *pdev)
306 {
307 	return component_add(&pdev->dev, &sun8i_tcon_top_ops);
308 }
309 
310 static void sun8i_tcon_top_remove(struct platform_device *pdev)
311 {
312 	component_del(&pdev->dev, &sun8i_tcon_top_ops);
313 }
314 
315 static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks = {
316 	.has_tcon_tv1	= true,
317 	.has_dsi	= true,
318 };
319 
320 static const struct sun8i_tcon_top_quirks sun20i_d1_tcon_top_quirks = {
321 	.has_dsi	= true,
322 };
323 
324 static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks = {
325 	/* Nothing special */
326 };
327 
328 /* sun4i_drv uses this list to check if a device node is a TCON TOP */
329 const struct of_device_id sun8i_tcon_top_of_table[] = {
330 	{
331 		.compatible = "allwinner,sun8i-r40-tcon-top",
332 		.data = &sun8i_r40_tcon_top_quirks
333 	},
334 	{
335 		.compatible = "allwinner,sun20i-d1-tcon-top",
336 		.data = &sun20i_d1_tcon_top_quirks
337 	},
338 	{
339 		.compatible = "allwinner,sun50i-h6-tcon-top",
340 		.data = &sun50i_h6_tcon_top_quirks
341 	},
342 	{ /* sentinel */ }
343 };
344 MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table);
345 EXPORT_SYMBOL(sun8i_tcon_top_of_table);
346 
347 static struct platform_driver sun8i_tcon_top_platform_driver = {
348 	.probe		= sun8i_tcon_top_probe,
349 	.remove		= sun8i_tcon_top_remove,
350 	.driver		= {
351 		.name		= "sun8i-tcon-top",
352 		.of_match_table	= sun8i_tcon_top_of_table,
353 	},
354 };
355 module_platform_driver(sun8i_tcon_top_platform_driver);
356 
357 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
358 MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
359 MODULE_LICENSE("GPL");
360