1 /* 2 * Copyright (C) 2015 Free Electrons 3 * Copyright (C) 2015 NextThing Co 4 * 5 * Maxime Ripard <maxime.ripard@free-electrons.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation; either version 2 of 10 * the License, or (at your option) any later version. 11 */ 12 13 #include <linux/component.h> 14 #include <linux/of_graph.h> 15 #include <linux/of_reserved_mem.h> 16 17 #include <drm/drmP.h> 18 #include <drm/drm_crtc_helper.h> 19 #include <drm/drm_fb_cma_helper.h> 20 #include <drm/drm_gem_cma_helper.h> 21 #include <drm/drm_fb_helper.h> 22 #include <drm/drm_of.h> 23 24 #include "sun4i_drv.h" 25 #include "sun4i_framebuffer.h" 26 #include "sun4i_tcon.h" 27 28 static void sun4i_drv_lastclose(struct drm_device *dev) 29 { 30 struct sun4i_drv *drv = dev->dev_private; 31 32 drm_fbdev_cma_restore_mode(drv->fbdev); 33 } 34 35 DEFINE_DRM_GEM_CMA_FOPS(sun4i_drv_fops); 36 37 static struct drm_driver sun4i_drv_driver = { 38 .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, 39 40 /* Generic Operations */ 41 .lastclose = sun4i_drv_lastclose, 42 .fops = &sun4i_drv_fops, 43 .name = "sun4i-drm", 44 .desc = "Allwinner sun4i Display Engine", 45 .date = "20150629", 46 .major = 1, 47 .minor = 0, 48 49 /* GEM Operations */ 50 .dumb_create = drm_gem_cma_dumb_create, 51 .gem_free_object_unlocked = drm_gem_cma_free_object, 52 .gem_vm_ops = &drm_gem_cma_vm_ops, 53 54 /* PRIME Operations */ 55 .prime_handle_to_fd = drm_gem_prime_handle_to_fd, 56 .prime_fd_to_handle = drm_gem_prime_fd_to_handle, 57 .gem_prime_import = drm_gem_prime_import, 58 .gem_prime_export = drm_gem_prime_export, 59 .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, 60 .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, 61 .gem_prime_vmap = drm_gem_cma_prime_vmap, 62 .gem_prime_vunmap = drm_gem_cma_prime_vunmap, 63 .gem_prime_mmap = drm_gem_cma_prime_mmap, 64 65 /* Frame Buffer Operations */ 66 }; 67 68 static void sun4i_remove_framebuffers(void) 69 { 70 struct apertures_struct *ap; 71 72 ap = alloc_apertures(1); 73 if (!ap) 74 return; 75 76 /* The framebuffer can be located anywhere in RAM */ 77 ap->ranges[0].base = 0; 78 ap->ranges[0].size = ~0; 79 80 drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false); 81 kfree(ap); 82 } 83 84 static int sun4i_drv_bind(struct device *dev) 85 { 86 struct drm_device *drm; 87 struct sun4i_drv *drv; 88 int ret; 89 90 drm = drm_dev_alloc(&sun4i_drv_driver, dev); 91 if (IS_ERR(drm)) 92 return PTR_ERR(drm); 93 94 drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); 95 if (!drv) { 96 ret = -ENOMEM; 97 goto free_drm; 98 } 99 drm->dev_private = drv; 100 INIT_LIST_HEAD(&drv->engine_list); 101 INIT_LIST_HEAD(&drv->tcon_list); 102 103 ret = of_reserved_mem_device_init(dev); 104 if (ret && ret != -ENODEV) { 105 dev_err(drm->dev, "Couldn't claim our memory region\n"); 106 goto free_drm; 107 } 108 109 /* drm_vblank_init calls kcalloc, which can fail */ 110 ret = drm_vblank_init(drm, 1); 111 if (ret) 112 goto free_mem_region; 113 114 drm_mode_config_init(drm); 115 116 ret = component_bind_all(drm->dev, drm); 117 if (ret) { 118 dev_err(drm->dev, "Couldn't bind all pipelines components\n"); 119 goto cleanup_mode_config; 120 } 121 122 drm->irq_enabled = true; 123 124 /* Remove early framebuffers (ie. simplefb) */ 125 sun4i_remove_framebuffers(); 126 127 /* Create our framebuffer */ 128 drv->fbdev = sun4i_framebuffer_init(drm); 129 if (IS_ERR(drv->fbdev)) { 130 dev_err(drm->dev, "Couldn't create our framebuffer\n"); 131 ret = PTR_ERR(drv->fbdev); 132 goto cleanup_mode_config; 133 } 134 135 /* Enable connectors polling */ 136 drm_kms_helper_poll_init(drm); 137 138 ret = drm_dev_register(drm, 0); 139 if (ret) 140 goto finish_poll; 141 142 return 0; 143 144 finish_poll: 145 drm_kms_helper_poll_fini(drm); 146 sun4i_framebuffer_free(drm); 147 cleanup_mode_config: 148 drm_mode_config_cleanup(drm); 149 free_mem_region: 150 of_reserved_mem_device_release(dev); 151 free_drm: 152 drm_dev_unref(drm); 153 return ret; 154 } 155 156 static void sun4i_drv_unbind(struct device *dev) 157 { 158 struct drm_device *drm = dev_get_drvdata(dev); 159 160 drm_dev_unregister(drm); 161 drm_kms_helper_poll_fini(drm); 162 sun4i_framebuffer_free(drm); 163 drm_mode_config_cleanup(drm); 164 of_reserved_mem_device_release(dev); 165 drm_dev_unref(drm); 166 } 167 168 static const struct component_master_ops sun4i_drv_master_ops = { 169 .bind = sun4i_drv_bind, 170 .unbind = sun4i_drv_unbind, 171 }; 172 173 static bool sun4i_drv_node_is_connector(struct device_node *node) 174 { 175 return of_device_is_compatible(node, "hdmi-connector"); 176 } 177 178 static bool sun4i_drv_node_is_frontend(struct device_node *node) 179 { 180 return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") || 181 of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") || 182 of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend"); 183 } 184 185 static bool sun4i_drv_node_is_tcon(struct device_node *node) 186 { 187 return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") || 188 of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") || 189 of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") || 190 of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") || 191 of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon"); 192 } 193 194 static int compare_of(struct device *dev, void *data) 195 { 196 DRM_DEBUG_DRIVER("Comparing of node %pOF with %pOF\n", 197 dev->of_node, 198 data); 199 200 return dev->of_node == data; 201 } 202 203 static int sun4i_drv_add_endpoints(struct device *dev, 204 struct component_match **match, 205 struct device_node *node) 206 { 207 struct device_node *port, *ep, *remote; 208 int count = 0; 209 210 /* 211 * We don't support the frontend for now, so we will never 212 * have a device bound. Just skip over it, but we still want 213 * the rest our pipeline to be added. 214 */ 215 if (!sun4i_drv_node_is_frontend(node) && 216 !of_device_is_available(node)) 217 return 0; 218 219 /* 220 * The connectors will be the last nodes in our pipeline, we 221 * can just bail out. 222 */ 223 if (sun4i_drv_node_is_connector(node)) 224 return 0; 225 226 if (!sun4i_drv_node_is_frontend(node)) { 227 /* Add current component */ 228 DRM_DEBUG_DRIVER("Adding component %pOF\n", node); 229 drm_of_component_match_add(dev, match, compare_of, node); 230 count++; 231 } 232 233 /* Inputs are listed first, then outputs */ 234 port = of_graph_get_port_by_id(node, 1); 235 if (!port) { 236 DRM_DEBUG_DRIVER("No output to bind\n"); 237 return count; 238 } 239 240 for_each_available_child_of_node(port, ep) { 241 remote = of_graph_get_remote_port_parent(ep); 242 if (!remote) { 243 DRM_DEBUG_DRIVER("Error retrieving the output node\n"); 244 of_node_put(remote); 245 continue; 246 } 247 248 /* 249 * If the node is our TCON, the first port is used for 250 * panel or bridges, and will not be part of the 251 * component framework. 252 */ 253 if (sun4i_drv_node_is_tcon(node)) { 254 struct of_endpoint endpoint; 255 256 if (of_graph_parse_endpoint(ep, &endpoint)) { 257 DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); 258 continue; 259 } 260 261 if (!endpoint.id) { 262 DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); 263 continue; 264 } 265 } 266 267 /* Walk down our tree */ 268 count += sun4i_drv_add_endpoints(dev, match, remote); 269 270 of_node_put(remote); 271 } 272 273 return count; 274 } 275 276 static int sun4i_drv_probe(struct platform_device *pdev) 277 { 278 struct component_match *match = NULL; 279 struct device_node *np = pdev->dev.of_node; 280 int i, count = 0; 281 282 for (i = 0;; i++) { 283 struct device_node *pipeline = of_parse_phandle(np, 284 "allwinner,pipelines", 285 i); 286 if (!pipeline) 287 break; 288 289 count += sun4i_drv_add_endpoints(&pdev->dev, &match, 290 pipeline); 291 of_node_put(pipeline); 292 293 DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", 294 count, i); 295 } 296 297 if (count) 298 return component_master_add_with_match(&pdev->dev, 299 &sun4i_drv_master_ops, 300 match); 301 else 302 return 0; 303 } 304 305 static int sun4i_drv_remove(struct platform_device *pdev) 306 { 307 return 0; 308 } 309 310 static const struct of_device_id sun4i_drv_of_table[] = { 311 { .compatible = "allwinner,sun5i-a10s-display-engine" }, 312 { .compatible = "allwinner,sun5i-a13-display-engine" }, 313 { .compatible = "allwinner,sun6i-a31-display-engine" }, 314 { .compatible = "allwinner,sun6i-a31s-display-engine" }, 315 { .compatible = "allwinner,sun8i-a33-display-engine" }, 316 { .compatible = "allwinner,sun8i-v3s-display-engine" }, 317 { } 318 }; 319 MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); 320 321 static struct platform_driver sun4i_drv_platform_driver = { 322 .probe = sun4i_drv_probe, 323 .remove = sun4i_drv_remove, 324 .driver = { 325 .name = "sun4i-drm", 326 .of_match_table = sun4i_drv_of_table, 327 }, 328 }; 329 module_platform_driver(sun4i_drv_platform_driver); 330 331 MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); 332 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 333 MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); 334 MODULE_LICENSE("GPL"); 335