1 // SPDX-License-Identifier: GPL-2.0+ 2 /* Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net> */ 3 4 #include <drm/drmP.h> 5 6 #include <dt-bindings/clock/sun8i-tcon-top.h> 7 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> 14 15 #include "sun8i_tcon_top.h" 16 17 static int sun8i_tcon_top_get_connected_ep_id(struct device_node *node, 18 int port_id) 19 { 20 struct device_node *ep, *remote, *port; 21 struct of_endpoint endpoint; 22 23 port = of_graph_get_port_by_id(node, port_id); 24 if (!port) 25 return -ENOENT; 26 27 for_each_available_child_of_node(port, ep) { 28 remote = of_graph_get_remote_port_parent(ep); 29 if (!remote) 30 continue; 31 32 if (of_device_is_available(remote)) { 33 of_graph_parse_endpoint(ep, &endpoint); 34 35 of_node_put(remote); 36 37 return endpoint.id; 38 } 39 40 of_node_put(remote); 41 } 42 43 return -ENOENT; 44 } 45 46 static struct clk_hw *sun8i_tcon_top_register_gate(struct device *dev, 47 struct clk *parent, 48 void __iomem *regs, 49 spinlock_t *lock, 50 u8 bit, int name_index) 51 { 52 const char *clk_name, *parent_name; 53 int ret; 54 55 parent_name = __clk_get_name(parent); 56 ret = of_property_read_string_index(dev->of_node, 57 "clock-output-names", name_index, 58 &clk_name); 59 if (ret) 60 return ERR_PTR(ret); 61 62 return clk_hw_register_gate(dev, clk_name, parent_name, 63 CLK_SET_RATE_PARENT, 64 regs + TCON_TOP_GATE_SRC_REG, 65 bit, 0, lock); 66 }; 67 68 static int sun8i_tcon_top_bind(struct device *dev, struct device *master, 69 void *data) 70 { 71 struct platform_device *pdev = to_platform_device(dev); 72 struct clk *dsi, *tcon_tv0, *tcon_tv1, *tve0, *tve1; 73 struct clk_hw_onecell_data *clk_data; 74 struct sun8i_tcon_top *tcon_top; 75 bool mixer0_unused = false; 76 struct resource *res; 77 void __iomem *regs; 78 int ret, i, id; 79 u32 val; 80 81 tcon_top = devm_kzalloc(dev, sizeof(*tcon_top), GFP_KERNEL); 82 if (!tcon_top) 83 return -ENOMEM; 84 85 clk_data = devm_kzalloc(dev, sizeof(*clk_data) + 86 sizeof(*clk_data->hws) * CLK_NUM, 87 GFP_KERNEL); 88 if (!clk_data) 89 return -ENOMEM; 90 tcon_top->clk_data = clk_data; 91 92 spin_lock_init(&tcon_top->reg_lock); 93 94 tcon_top->rst = devm_reset_control_get(dev, NULL); 95 if (IS_ERR(tcon_top->rst)) { 96 dev_err(dev, "Couldn't get our reset line\n"); 97 return PTR_ERR(tcon_top->rst); 98 } 99 100 tcon_top->bus = devm_clk_get(dev, "bus"); 101 if (IS_ERR(tcon_top->bus)) { 102 dev_err(dev, "Couldn't get the bus clock\n"); 103 return PTR_ERR(tcon_top->bus); 104 } 105 106 dsi = devm_clk_get(dev, "dsi"); 107 if (IS_ERR(dsi)) { 108 dev_err(dev, "Couldn't get the dsi clock\n"); 109 return PTR_ERR(dsi); 110 } 111 112 tcon_tv0 = devm_clk_get(dev, "tcon-tv0"); 113 if (IS_ERR(tcon_tv0)) { 114 dev_err(dev, "Couldn't get the tcon-tv0 clock\n"); 115 return PTR_ERR(tcon_tv0); 116 } 117 118 tcon_tv1 = devm_clk_get(dev, "tcon-tv1"); 119 if (IS_ERR(tcon_tv1)) { 120 dev_err(dev, "Couldn't get the tcon-tv1 clock\n"); 121 return PTR_ERR(tcon_tv1); 122 } 123 124 tve0 = devm_clk_get(dev, "tve0"); 125 if (IS_ERR(tve0)) { 126 dev_err(dev, "Couldn't get the tve0 clock\n"); 127 return PTR_ERR(tve0); 128 } 129 130 tve1 = devm_clk_get(dev, "tve1"); 131 if (IS_ERR(tve1)) { 132 dev_err(dev, "Couldn't get the tve1 clock\n"); 133 return PTR_ERR(tve1); 134 } 135 136 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 137 regs = devm_ioremap_resource(dev, res); 138 if (IS_ERR(regs)) 139 return PTR_ERR(regs); 140 141 ret = reset_control_deassert(tcon_top->rst); 142 if (ret) { 143 dev_err(dev, "Could not deassert ctrl reset control\n"); 144 return ret; 145 } 146 147 ret = clk_prepare_enable(tcon_top->bus); 148 if (ret) { 149 dev_err(dev, "Could not enable bus clock\n"); 150 goto err_assert_reset; 151 } 152 153 val = 0; 154 155 /* check if HDMI mux output is connected */ 156 if (sun8i_tcon_top_get_connected_ep_id(dev->of_node, 5) >= 0) { 157 /* find HDMI input endpoint id, if it is connected at all*/ 158 id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 4); 159 if (id >= 0) 160 val = FIELD_PREP(TCON_TOP_HDMI_SRC_MSK, id + 1); 161 else 162 DRM_DEBUG_DRIVER("TCON TOP HDMI input is not connected\n"); 163 } else { 164 DRM_DEBUG_DRIVER("TCON TOP HDMI output is not connected\n"); 165 } 166 167 writel(val, regs + TCON_TOP_GATE_SRC_REG); 168 169 val = 0; 170 171 /* process mixer0 mux output */ 172 id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 1); 173 if (id >= 0) { 174 val = FIELD_PREP(TCON_TOP_PORT_DE0_MSK, id); 175 } else { 176 DRM_DEBUG_DRIVER("TCON TOP mixer0 output is not connected\n"); 177 mixer0_unused = true; 178 } 179 180 /* process mixer1 mux output */ 181 id = sun8i_tcon_top_get_connected_ep_id(dev->of_node, 3); 182 if (id >= 0) { 183 val |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, id); 184 185 /* 186 * mixer0 mux has priority over mixer1 mux. We have to 187 * make sure mixer0 doesn't overtake TCON from mixer1. 188 */ 189 if (mixer0_unused && id == 0) 190 val |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, 1); 191 } else { 192 DRM_DEBUG_DRIVER("TCON TOP mixer1 output is not connected\n"); 193 } 194 195 writel(val, regs + TCON_TOP_PORT_SEL_REG); 196 197 /* 198 * TCON TOP has two muxes, which select parent clock for each TCON TV 199 * channel clock. Parent could be either TCON TV or TVE clock. For now 200 * we leave this fixed to TCON TV, since TVE driver for R40 is not yet 201 * implemented. Once it is, graph needs to be traversed to determine 202 * if TVE is active on each TCON TV. If it is, mux should be switched 203 * to TVE clock parent. 204 */ 205 clk_data->hws[CLK_TCON_TOP_TV0] = 206 sun8i_tcon_top_register_gate(dev, tcon_tv0, regs, 207 &tcon_top->reg_lock, 208 TCON_TOP_TCON_TV0_GATE, 0); 209 210 clk_data->hws[CLK_TCON_TOP_TV1] = 211 sun8i_tcon_top_register_gate(dev, tcon_tv1, regs, 212 &tcon_top->reg_lock, 213 TCON_TOP_TCON_TV1_GATE, 1); 214 215 clk_data->hws[CLK_TCON_TOP_DSI] = 216 sun8i_tcon_top_register_gate(dev, dsi, regs, 217 &tcon_top->reg_lock, 218 TCON_TOP_TCON_DSI_GATE, 2); 219 220 for (i = 0; i < CLK_NUM; i++) 221 if (IS_ERR(clk_data->hws[i])) { 222 ret = PTR_ERR(clk_data->hws[i]); 223 goto err_unregister_gates; 224 } 225 226 clk_data->num = CLK_NUM; 227 228 ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, 229 clk_data); 230 if (ret) 231 goto err_unregister_gates; 232 233 dev_set_drvdata(dev, tcon_top); 234 235 return 0; 236 237 err_unregister_gates: 238 for (i = 0; i < CLK_NUM; i++) 239 if (clk_data->hws[i]) 240 clk_hw_unregister_gate(clk_data->hws[i]); 241 clk_disable_unprepare(tcon_top->bus); 242 err_assert_reset: 243 reset_control_assert(tcon_top->rst); 244 245 return ret; 246 } 247 248 static void sun8i_tcon_top_unbind(struct device *dev, struct device *master, 249 void *data) 250 { 251 struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev); 252 struct clk_hw_onecell_data *clk_data = tcon_top->clk_data; 253 int i; 254 255 of_clk_del_provider(dev->of_node); 256 for (i = 0; i < CLK_NUM; i++) 257 clk_hw_unregister_gate(clk_data->hws[i]); 258 259 clk_disable_unprepare(tcon_top->bus); 260 reset_control_assert(tcon_top->rst); 261 } 262 263 static const struct component_ops sun8i_tcon_top_ops = { 264 .bind = sun8i_tcon_top_bind, 265 .unbind = sun8i_tcon_top_unbind, 266 }; 267 268 static int sun8i_tcon_top_probe(struct platform_device *pdev) 269 { 270 return component_add(&pdev->dev, &sun8i_tcon_top_ops); 271 } 272 273 static int sun8i_tcon_top_remove(struct platform_device *pdev) 274 { 275 component_del(&pdev->dev, &sun8i_tcon_top_ops); 276 277 return 0; 278 } 279 280 /* sun4i_drv uses this list to check if a device node is a TCON TOP */ 281 const struct of_device_id sun8i_tcon_top_of_table[] = { 282 { .compatible = "allwinner,sun8i-r40-tcon-top" }, 283 { /* sentinel */ } 284 }; 285 MODULE_DEVICE_TABLE(of, sun8i_tcon_top_of_table); 286 EXPORT_SYMBOL(sun8i_tcon_top_of_table); 287 288 static struct platform_driver sun8i_tcon_top_platform_driver = { 289 .probe = sun8i_tcon_top_probe, 290 .remove = sun8i_tcon_top_remove, 291 .driver = { 292 .name = "sun8i-tcon-top", 293 .of_match_table = sun8i_tcon_top_of_table, 294 }, 295 }; 296 module_platform_driver(sun8i_tcon_top_platform_driver); 297 298 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>"); 299 MODULE_DESCRIPTION("Allwinner R40 TCON TOP driver"); 300 MODULE_LICENSE("GPL"); 301