1 // SPDX-License-Identifier: GPL-2.0+
2 /* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
5 #include <linux/bitfield.h>
6 #include <linux/component.h>
7 #include <linux/device.h>
9 #include <linux/module.h>
10 #include <linux/of_device.h>
11 #include <linux/of_graph.h>
12 #include <linux/platform_device.h>
14 #include <dt-bindings/clock/sun8i-tcon-top.h>
16 #include "sun8i_tcon_top.h"
18 struct sun8i_tcon_top_quirks
{
23 static bool sun8i_tcon_top_node_is_tcon_top(struct device_node
*node
)
25 return !!of_match_node(sun8i_tcon_top_of_table
, node
);
28 int sun8i_tcon_top_set_hdmi_src(struct device
*dev
, int tcon
)
30 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
34 if (!sun8i_tcon_top_node_is_tcon_top(dev
->of_node
)) {
35 dev_err(dev
, "Device is not TCON TOP!\n");
39 if (tcon
< 2 || tcon
> 3) {
40 dev_err(dev
, "TCON index must be 2 or 3!\n");
44 spin_lock_irqsave(&tcon_top
->reg_lock
, flags
);
46 val
= readl(tcon_top
->regs
+ TCON_TOP_GATE_SRC_REG
);
47 val
&= ~TCON_TOP_HDMI_SRC_MSK
;
48 val
|= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK
, tcon
- 1);
49 writel(val
, tcon_top
->regs
+ TCON_TOP_GATE_SRC_REG
);
51 spin_unlock_irqrestore(&tcon_top
->reg_lock
, flags
);
55 EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src
);
57 int sun8i_tcon_top_de_config(struct device
*dev
, int mixer
, int tcon
)
59 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
63 if (!sun8i_tcon_top_node_is_tcon_top(dev
->of_node
)) {
64 dev_err(dev
, "Device is not TCON TOP!\n");
69 dev_err(dev
, "Mixer index is too high!\n");
74 dev_err(dev
, "TCON index is too high!\n");
78 spin_lock_irqsave(&tcon_top
->reg_lock
, flags
);
80 reg
= readl(tcon_top
->regs
+ TCON_TOP_PORT_SEL_REG
);
82 reg
&= ~TCON_TOP_PORT_DE0_MSK
;
83 reg
|= FIELD_PREP(TCON_TOP_PORT_DE0_MSK
, tcon
);
85 reg
&= ~TCON_TOP_PORT_DE1_MSK
;
86 reg
|= FIELD_PREP(TCON_TOP_PORT_DE1_MSK
, tcon
);
88 writel(reg
, tcon_top
->regs
+ TCON_TOP_PORT_SEL_REG
);
90 spin_unlock_irqrestore(&tcon_top
->reg_lock
, flags
);
94 EXPORT_SYMBOL(sun8i_tcon_top_de_config
);
97 static struct clk_hw
*sun8i_tcon_top_register_gate(struct device
*dev
,
101 u8 bit
, int name_index
)
103 const char *clk_name
, *parent_name
;
106 index
= of_property_match_string(dev
->of_node
, "clock-names", parent
);
108 return ERR_PTR(index
);
110 parent_name
= of_clk_get_parent_name(dev
->of_node
, index
);
112 ret
= of_property_read_string_index(dev
->of_node
,
113 "clock-output-names", name_index
,
118 return clk_hw_register_gate(dev
, clk_name
, parent_name
,
120 regs
+ TCON_TOP_GATE_SRC_REG
,
124 static int sun8i_tcon_top_bind(struct device
*dev
, struct device
*master
,
127 struct platform_device
*pdev
= to_platform_device(dev
);
128 struct clk_hw_onecell_data
*clk_data
;
129 struct sun8i_tcon_top
*tcon_top
;
130 const struct sun8i_tcon_top_quirks
*quirks
;
131 struct resource
*res
;
135 quirks
= of_device_get_match_data(&pdev
->dev
);
137 tcon_top
= devm_kzalloc(dev
, sizeof(*tcon_top
), GFP_KERNEL
);
141 clk_data
= devm_kzalloc(dev
, struct_size(clk_data
, hws
, CLK_NUM
),
145 tcon_top
->clk_data
= clk_data
;
147 spin_lock_init(&tcon_top
->reg_lock
);
149 tcon_top
->rst
= devm_reset_control_get(dev
, NULL
);
150 if (IS_ERR(tcon_top
->rst
)) {
151 dev_err(dev
, "Couldn't get our reset line\n");
152 return PTR_ERR(tcon_top
->rst
);
155 tcon_top
->bus
= devm_clk_get(dev
, "bus");
156 if (IS_ERR(tcon_top
->bus
)) {
157 dev_err(dev
, "Couldn't get the bus clock\n");
158 return PTR_ERR(tcon_top
->bus
);
161 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
162 regs
= devm_ioremap_resource(dev
, res
);
163 tcon_top
->regs
= regs
;
165 return PTR_ERR(regs
);
167 ret
= reset_control_deassert(tcon_top
->rst
);
169 dev_err(dev
, "Could not deassert ctrl reset control\n");
173 ret
= clk_prepare_enable(tcon_top
->bus
);
175 dev_err(dev
, "Could not enable bus clock\n");
176 goto err_assert_reset
;
180 * At least on H6, some registers have some bits set by default
181 * which may cause issues. Clear them here.
183 writel(0, regs
+ TCON_TOP_PORT_SEL_REG
);
184 writel(0, regs
+ TCON_TOP_GATE_SRC_REG
);
187 * TCON TOP has two muxes, which select parent clock for each TCON TV
188 * channel clock. Parent could be either TCON TV or TVE clock. For now
189 * we leave this fixed to TCON TV, since TVE driver for R40 is not yet
190 * implemented. Once it is, graph needs to be traversed to determine
191 * if TVE is active on each TCON TV. If it is, mux should be switched
192 * to TVE clock parent.
194 clk_data
->hws
[CLK_TCON_TOP_TV0
] =
195 sun8i_tcon_top_register_gate(dev
, "tcon-tv0", regs
,
197 TCON_TOP_TCON_TV0_GATE
, 0);
199 if (quirks
->has_tcon_tv1
)
200 clk_data
->hws
[CLK_TCON_TOP_TV1
] =
201 sun8i_tcon_top_register_gate(dev
, "tcon-tv1", regs
,
203 TCON_TOP_TCON_TV1_GATE
, 1);
206 clk_data
->hws
[CLK_TCON_TOP_DSI
] =
207 sun8i_tcon_top_register_gate(dev
, "dsi", regs
,
209 TCON_TOP_TCON_DSI_GATE
, 2);
211 for (i
= 0; i
< CLK_NUM
; i
++)
212 if (IS_ERR(clk_data
->hws
[i
])) {
213 ret
= PTR_ERR(clk_data
->hws
[i
]);
214 goto err_unregister_gates
;
217 clk_data
->num
= CLK_NUM
;
219 ret
= of_clk_add_hw_provider(dev
->of_node
, of_clk_hw_onecell_get
,
222 goto err_unregister_gates
;
224 dev_set_drvdata(dev
, tcon_top
);
228 err_unregister_gates
:
229 for (i
= 0; i
< CLK_NUM
; i
++)
230 if (!IS_ERR_OR_NULL(clk_data
->hws
[i
]))
231 clk_hw_unregister_gate(clk_data
->hws
[i
]);
232 clk_disable_unprepare(tcon_top
->bus
);
234 reset_control_assert(tcon_top
->rst
);
239 static void sun8i_tcon_top_unbind(struct device
*dev
, struct device
*master
,
242 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
243 struct clk_hw_onecell_data
*clk_data
= tcon_top
->clk_data
;
246 of_clk_del_provider(dev
->of_node
);
247 for (i
= 0; i
< CLK_NUM
; i
++)
248 if (clk_data
->hws
[i
])
249 clk_hw_unregister_gate(clk_data
->hws
[i
]);
251 clk_disable_unprepare(tcon_top
->bus
);
252 reset_control_assert(tcon_top
->rst
);
255 static const struct component_ops sun8i_tcon_top_ops
= {
256 .bind
= sun8i_tcon_top_bind
,
257 .unbind
= sun8i_tcon_top_unbind
,
260 static int sun8i_tcon_top_probe(struct platform_device
*pdev
)
262 return component_add(&pdev
->dev
, &sun8i_tcon_top_ops
);
265 static int sun8i_tcon_top_remove(struct platform_device
*pdev
)
267 component_del(&pdev
->dev
, &sun8i_tcon_top_ops
);
272 static const struct sun8i_tcon_top_quirks sun8i_r40_tcon_top_quirks
= {
273 .has_tcon_tv1
= true,
277 static const struct sun8i_tcon_top_quirks sun50i_h6_tcon_top_quirks
= {
278 /* Nothing special */
281 /* sun4i_drv uses this list to check if a device node is a TCON TOP */
282 const struct of_device_id sun8i_tcon_top_of_table
[] = {
284 .compatible
= "allwinner,sun8i-r40-tcon-top",
285 .data
= &sun8i_r40_tcon_top_quirks
288 .compatible
= "allwinner,sun50i-h6-tcon-top",
289 .data
= &sun50i_h6_tcon_top_quirks
293 MODULE_DEVICE_TABLE(of
, sun8i_tcon_top_of_table
);
294 EXPORT_SYMBOL(sun8i_tcon_top_of_table
);
296 static struct platform_driver sun8i_tcon_top_platform_driver
= {
297 .probe
= sun8i_tcon_top_probe
,
298 .remove
= sun8i_tcon_top_remove
,
300 .name
= "sun8i-tcon-top",
301 .of_match_table
= sun8i_tcon_top_of_table
,
304 module_platform_driver(sun8i_tcon_top_platform_driver
);
306 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
307 MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
308 MODULE_LICENSE("GPL");