1 // SPDX-License-Identifier: GPL-2.0+
2 /* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */
6 #include <dt-bindings/clock/sun8i-tcon-top.h>
8 #include <linux/bitfield.h>
9 #include <linux/component.h>
10 #include <linux/device.h>
11 #include <linux/module.h>
12 #include <linux/of_graph.h>
13 #include <linux/platform_device.h>
15 #include "sun8i_tcon_top.h"
17 static bool sun8i_tcon_top_node_is_tcon_top(struct device_node
*node
)
19 return !!of_match_node(sun8i_tcon_top_of_table
, node
);
22 int sun8i_tcon_top_set_hdmi_src(struct device
*dev
, int tcon
)
24 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
28 if (!sun8i_tcon_top_node_is_tcon_top(dev
->of_node
)) {
29 dev_err(dev
, "Device is not TCON TOP!\n");
33 if (tcon
< 2 || tcon
> 3) {
34 dev_err(dev
, "TCON index must be 2 or 3!\n");
38 spin_lock_irqsave(&tcon_top
->reg_lock
, flags
);
40 val
= readl(tcon_top
->regs
+ TCON_TOP_GATE_SRC_REG
);
41 val
&= ~TCON_TOP_HDMI_SRC_MSK
;
42 val
|= FIELD_PREP(TCON_TOP_HDMI_SRC_MSK
, tcon
- 1);
43 writel(val
, tcon_top
->regs
+ TCON_TOP_GATE_SRC_REG
);
45 spin_unlock_irqrestore(&tcon_top
->reg_lock
, flags
);
49 EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src
);
51 int sun8i_tcon_top_de_config(struct device
*dev
, int mixer
, int tcon
)
53 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
57 if (!sun8i_tcon_top_node_is_tcon_top(dev
->of_node
)) {
58 dev_err(dev
, "Device is not TCON TOP!\n");
63 dev_err(dev
, "Mixer index is too high!\n");
68 dev_err(dev
, "TCON index is too high!\n");
72 spin_lock_irqsave(&tcon_top
->reg_lock
, flags
);
74 reg
= readl(tcon_top
->regs
+ TCON_TOP_PORT_SEL_REG
);
76 reg
&= ~TCON_TOP_PORT_DE0_MSK
;
77 reg
|= FIELD_PREP(TCON_TOP_PORT_DE0_MSK
, tcon
);
79 reg
&= ~TCON_TOP_PORT_DE1_MSK
;
80 reg
|= FIELD_PREP(TCON_TOP_PORT_DE1_MSK
, tcon
);
82 writel(reg
, tcon_top
->regs
+ TCON_TOP_PORT_SEL_REG
);
84 spin_unlock_irqrestore(&tcon_top
->reg_lock
, flags
);
88 EXPORT_SYMBOL(sun8i_tcon_top_de_config
);
91 static struct clk_hw
*sun8i_tcon_top_register_gate(struct device
*dev
,
95 u8 bit
, int name_index
)
97 const char *clk_name
, *parent_name
;
100 index
= of_property_match_string(dev
->of_node
, "clock-names", parent
);
102 return ERR_PTR(index
);
104 parent_name
= of_clk_get_parent_name(dev
->of_node
, index
);
106 ret
= of_property_read_string_index(dev
->of_node
,
107 "clock-output-names", name_index
,
112 return clk_hw_register_gate(dev
, clk_name
, parent_name
,
114 regs
+ TCON_TOP_GATE_SRC_REG
,
118 static int sun8i_tcon_top_bind(struct device
*dev
, struct device
*master
,
121 struct platform_device
*pdev
= to_platform_device(dev
);
122 struct clk_hw_onecell_data
*clk_data
;
123 struct sun8i_tcon_top
*tcon_top
;
124 struct resource
*res
;
128 tcon_top
= devm_kzalloc(dev
, sizeof(*tcon_top
), GFP_KERNEL
);
132 clk_data
= devm_kzalloc(dev
, sizeof(*clk_data
) +
133 sizeof(*clk_data
->hws
) * CLK_NUM
,
137 tcon_top
->clk_data
= clk_data
;
139 spin_lock_init(&tcon_top
->reg_lock
);
141 tcon_top
->rst
= devm_reset_control_get(dev
, NULL
);
142 if (IS_ERR(tcon_top
->rst
)) {
143 dev_err(dev
, "Couldn't get our reset line\n");
144 return PTR_ERR(tcon_top
->rst
);
147 tcon_top
->bus
= devm_clk_get(dev
, "bus");
148 if (IS_ERR(tcon_top
->bus
)) {
149 dev_err(dev
, "Couldn't get the bus clock\n");
150 return PTR_ERR(tcon_top
->bus
);
153 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
154 regs
= devm_ioremap_resource(dev
, res
);
155 tcon_top
->regs
= regs
;
157 return PTR_ERR(regs
);
159 ret
= reset_control_deassert(tcon_top
->rst
);
161 dev_err(dev
, "Could not deassert ctrl reset control\n");
165 ret
= clk_prepare_enable(tcon_top
->bus
);
167 dev_err(dev
, "Could not enable bus clock\n");
168 goto err_assert_reset
;
172 * TCON TOP has two muxes, which select parent clock for each TCON TV
173 * channel clock. Parent could be either TCON TV or TVE clock. For now
174 * we leave this fixed to TCON TV, since TVE driver for R40 is not yet
175 * implemented. Once it is, graph needs to be traversed to determine
176 * if TVE is active on each TCON TV. If it is, mux should be switched
177 * to TVE clock parent.
179 clk_data
->hws
[CLK_TCON_TOP_TV0
] =
180 sun8i_tcon_top_register_gate(dev
, "tcon-tv0", regs
,
182 TCON_TOP_TCON_TV0_GATE
, 0);
184 clk_data
->hws
[CLK_TCON_TOP_TV1
] =
185 sun8i_tcon_top_register_gate(dev
, "tcon-tv1", regs
,
187 TCON_TOP_TCON_TV1_GATE
, 1);
189 clk_data
->hws
[CLK_TCON_TOP_DSI
] =
190 sun8i_tcon_top_register_gate(dev
, "dsi", regs
,
192 TCON_TOP_TCON_DSI_GATE
, 2);
194 for (i
= 0; i
< CLK_NUM
; i
++)
195 if (IS_ERR(clk_data
->hws
[i
])) {
196 ret
= PTR_ERR(clk_data
->hws
[i
]);
197 goto err_unregister_gates
;
200 clk_data
->num
= CLK_NUM
;
202 ret
= of_clk_add_hw_provider(dev
->of_node
, of_clk_hw_onecell_get
,
205 goto err_unregister_gates
;
207 dev_set_drvdata(dev
, tcon_top
);
211 err_unregister_gates
:
212 for (i
= 0; i
< CLK_NUM
; i
++)
213 if (clk_data
->hws
[i
])
214 clk_hw_unregister_gate(clk_data
->hws
[i
]);
215 clk_disable_unprepare(tcon_top
->bus
);
217 reset_control_assert(tcon_top
->rst
);
222 static void sun8i_tcon_top_unbind(struct device
*dev
, struct device
*master
,
225 struct sun8i_tcon_top
*tcon_top
= dev_get_drvdata(dev
);
226 struct clk_hw_onecell_data
*clk_data
= tcon_top
->clk_data
;
229 of_clk_del_provider(dev
->of_node
);
230 for (i
= 0; i
< CLK_NUM
; i
++)
231 clk_hw_unregister_gate(clk_data
->hws
[i
]);
233 clk_disable_unprepare(tcon_top
->bus
);
234 reset_control_assert(tcon_top
->rst
);
237 static const struct component_ops sun8i_tcon_top_ops
= {
238 .bind
= sun8i_tcon_top_bind
,
239 .unbind
= sun8i_tcon_top_unbind
,
242 static int sun8i_tcon_top_probe(struct platform_device
*pdev
)
244 return component_add(&pdev
->dev
, &sun8i_tcon_top_ops
);
247 static int sun8i_tcon_top_remove(struct platform_device
*pdev
)
249 component_del(&pdev
->dev
, &sun8i_tcon_top_ops
);
254 /* sun4i_drv uses this list to check if a device node is a TCON TOP */
255 const struct of_device_id sun8i_tcon_top_of_table
[] = {
258 MODULE_DEVICE_TABLE(of
, sun8i_tcon_top_of_table
);
259 EXPORT_SYMBOL(sun8i_tcon_top_of_table
);
261 static struct platform_driver sun8i_tcon_top_platform_driver
= {
262 .probe
= sun8i_tcon_top_probe
,
263 .remove
= sun8i_tcon_top_remove
,
265 .name
= "sun8i-tcon-top",
266 .of_match_table
= sun8i_tcon_top_of_table
,
269 module_platform_driver(sun8i_tcon_top_platform_driver
);
271 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
272 MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver");
273 MODULE_LICENSE("GPL");