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 16 #include <drm/drmP.h> 17 #include <drm/drm_crtc_helper.h> 18 #include <drm/drm_fb_cma_helper.h> 19 #include <drm/drm_gem_cma_helper.h> 20 21 #include "sun4i_crtc.h" 22 #include "sun4i_drv.h" 23 #include "sun4i_framebuffer.h" 24 #include "sun4i_layer.h" 25 #include "sun4i_tcon.h" 26 27 static int sun4i_drv_connector_plug_all(struct drm_device *drm) 28 { 29 struct drm_connector *connector, *failed; 30 int ret; 31 32 mutex_lock(&drm->mode_config.mutex); 33 list_for_each_entry(connector, &drm->mode_config.connector_list, head) { 34 ret = drm_connector_register(connector); 35 if (ret) { 36 failed = connector; 37 goto err; 38 } 39 } 40 mutex_unlock(&drm->mode_config.mutex); 41 return 0; 42 43 err: 44 list_for_each_entry(connector, &drm->mode_config.connector_list, head) { 45 if (failed == connector) 46 break; 47 48 drm_connector_unregister(connector); 49 } 50 mutex_unlock(&drm->mode_config.mutex); 51 52 return ret; 53 } 54 55 static int sun4i_drv_enable_vblank(struct drm_device *drm, unsigned int pipe) 56 { 57 struct sun4i_drv *drv = drm->dev_private; 58 struct sun4i_tcon *tcon = drv->tcon; 59 60 DRM_DEBUG_DRIVER("Enabling VBLANK on pipe %d\n", pipe); 61 62 sun4i_tcon_enable_vblank(tcon, true); 63 64 return 0; 65 } 66 67 static void sun4i_drv_disable_vblank(struct drm_device *drm, unsigned int pipe) 68 { 69 struct sun4i_drv *drv = drm->dev_private; 70 struct sun4i_tcon *tcon = drv->tcon; 71 72 DRM_DEBUG_DRIVER("Disabling VBLANK on pipe %d\n", pipe); 73 74 sun4i_tcon_enable_vblank(tcon, false); 75 } 76 77 static const struct file_operations sun4i_drv_fops = { 78 .owner = THIS_MODULE, 79 .open = drm_open, 80 .release = drm_release, 81 .unlocked_ioctl = drm_ioctl, 82 #ifdef CONFIG_COMPAT 83 .compat_ioctl = drm_compat_ioctl, 84 #endif 85 .poll = drm_poll, 86 .read = drm_read, 87 .llseek = no_llseek, 88 .mmap = drm_gem_cma_mmap, 89 }; 90 91 static struct drm_driver sun4i_drv_driver = { 92 .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, 93 94 /* Generic Operations */ 95 .fops = &sun4i_drv_fops, 96 .name = "sun4i-drm", 97 .desc = "Allwinner sun4i Display Engine", 98 .date = "20150629", 99 .major = 1, 100 .minor = 0, 101 102 /* GEM Operations */ 103 .dumb_create = drm_gem_cma_dumb_create, 104 .dumb_destroy = drm_gem_dumb_destroy, 105 .dumb_map_offset = drm_gem_cma_dumb_map_offset, 106 .gem_free_object = drm_gem_cma_free_object, 107 .gem_vm_ops = &drm_gem_cma_vm_ops, 108 109 /* PRIME Operations */ 110 .prime_handle_to_fd = drm_gem_prime_handle_to_fd, 111 .prime_fd_to_handle = drm_gem_prime_fd_to_handle, 112 .gem_prime_import = drm_gem_prime_import, 113 .gem_prime_export = drm_gem_prime_export, 114 .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, 115 .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, 116 .gem_prime_vmap = drm_gem_cma_prime_vmap, 117 .gem_prime_vunmap = drm_gem_cma_prime_vunmap, 118 .gem_prime_mmap = drm_gem_cma_prime_mmap, 119 120 /* Frame Buffer Operations */ 121 122 /* VBlank Operations */ 123 .get_vblank_counter = drm_vblank_count, 124 .enable_vblank = sun4i_drv_enable_vblank, 125 .disable_vblank = sun4i_drv_disable_vblank, 126 }; 127 128 static int sun4i_drv_bind(struct device *dev) 129 { 130 struct drm_device *drm; 131 struct sun4i_drv *drv; 132 int ret; 133 134 drm = drm_dev_alloc(&sun4i_drv_driver, dev); 135 if (!drm) 136 return -ENOMEM; 137 138 ret = drm_dev_set_unique(drm, dev_name(drm->dev)); 139 if (ret) 140 goto free_drm; 141 142 drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); 143 if (!drv) { 144 ret = -ENOMEM; 145 goto free_drm; 146 } 147 drm->dev_private = drv; 148 149 drm_vblank_init(drm, 1); 150 drm_mode_config_init(drm); 151 152 ret = component_bind_all(drm->dev, drm); 153 if (ret) { 154 dev_err(drm->dev, "Couldn't bind all pipelines components\n"); 155 goto free_drm; 156 } 157 158 /* Create our layers */ 159 drv->layers = sun4i_layers_init(drm); 160 if (!drv->layers) { 161 dev_err(drm->dev, "Couldn't create the planes\n"); 162 ret = -EINVAL; 163 goto free_drm; 164 } 165 166 /* Create our CRTC */ 167 drv->crtc = sun4i_crtc_init(drm); 168 if (!drv->crtc) { 169 dev_err(drm->dev, "Couldn't create the CRTC\n"); 170 ret = -EINVAL; 171 goto free_drm; 172 } 173 drm->irq_enabled = true; 174 175 /* Create our framebuffer */ 176 drv->fbdev = sun4i_framebuffer_init(drm); 177 if (IS_ERR(drv->fbdev)) { 178 dev_err(drm->dev, "Couldn't create our framebuffer\n"); 179 ret = PTR_ERR(drv->fbdev); 180 goto free_drm; 181 } 182 183 /* Enable connectors polling */ 184 drm_kms_helper_poll_init(drm); 185 186 ret = drm_dev_register(drm, 0); 187 if (ret) 188 goto free_drm; 189 190 ret = sun4i_drv_connector_plug_all(drm); 191 if (ret) 192 goto unregister_drm; 193 194 return 0; 195 196 unregister_drm: 197 drm_dev_unregister(drm); 198 free_drm: 199 drm_dev_unref(drm); 200 return ret; 201 } 202 203 static void sun4i_drv_unbind(struct device *dev) 204 { 205 struct drm_device *drm = dev_get_drvdata(dev); 206 207 drm_dev_unregister(drm); 208 drm_kms_helper_poll_fini(drm); 209 sun4i_framebuffer_free(drm); 210 drm_vblank_cleanup(drm); 211 drm_dev_unref(drm); 212 } 213 214 static const struct component_master_ops sun4i_drv_master_ops = { 215 .bind = sun4i_drv_bind, 216 .unbind = sun4i_drv_unbind, 217 }; 218 219 static bool sun4i_drv_node_is_frontend(struct device_node *node) 220 { 221 return of_device_is_compatible(node, 222 "allwinner,sun5i-a13-display-frontend"); 223 } 224 225 static bool sun4i_drv_node_is_tcon(struct device_node *node) 226 { 227 return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon"); 228 } 229 230 static int compare_of(struct device *dev, void *data) 231 { 232 DRM_DEBUG_DRIVER("Comparing of node %s with %s\n", 233 of_node_full_name(dev->of_node), 234 of_node_full_name(data)); 235 236 return dev->of_node == data; 237 } 238 239 static int sun4i_drv_add_endpoints(struct device *dev, 240 struct component_match **match, 241 struct device_node *node) 242 { 243 struct device_node *port, *ep, *remote; 244 int count = 0; 245 246 /* 247 * We don't support the frontend for now, so we will never 248 * have a device bound. Just skip over it, but we still want 249 * the rest our pipeline to be added. 250 */ 251 if (!sun4i_drv_node_is_frontend(node) && 252 !of_device_is_available(node)) 253 return 0; 254 255 if (!sun4i_drv_node_is_frontend(node)) { 256 /* Add current component */ 257 DRM_DEBUG_DRIVER("Adding component %s\n", 258 of_node_full_name(node)); 259 component_match_add(dev, match, compare_of, node); 260 count++; 261 } 262 263 /* Inputs are listed first, then outputs */ 264 port = of_graph_get_port_by_id(node, 1); 265 if (!port) { 266 DRM_DEBUG_DRIVER("No output to bind\n"); 267 return count; 268 } 269 270 for_each_available_child_of_node(port, ep) { 271 remote = of_graph_get_remote_port_parent(ep); 272 if (!remote) { 273 DRM_DEBUG_DRIVER("Error retrieving the output node\n"); 274 of_node_put(remote); 275 continue; 276 } 277 278 /* 279 * If the node is our TCON, the first port is used for our 280 * panel, and will not be part of the 281 * component framework. 282 */ 283 if (sun4i_drv_node_is_tcon(node)) { 284 struct of_endpoint endpoint; 285 286 if (of_graph_parse_endpoint(ep, &endpoint)) { 287 DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); 288 continue; 289 } 290 291 if (!endpoint.id) { 292 DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); 293 continue; 294 } 295 } 296 297 /* Walk down our tree */ 298 count += sun4i_drv_add_endpoints(dev, match, remote); 299 300 of_node_put(remote); 301 } 302 303 return count; 304 } 305 306 static int sun4i_drv_probe(struct platform_device *pdev) 307 { 308 struct component_match *match = NULL; 309 struct device_node *np = pdev->dev.of_node; 310 int i, count = 0; 311 312 for (i = 0;; i++) { 313 struct device_node *pipeline = of_parse_phandle(np, 314 "allwinner,pipelines", 315 i); 316 if (!pipeline) 317 break; 318 319 count += sun4i_drv_add_endpoints(&pdev->dev, &match, 320 pipeline); 321 322 DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", 323 count, i); 324 } 325 326 if (count) 327 return component_master_add_with_match(&pdev->dev, 328 &sun4i_drv_master_ops, 329 match); 330 else 331 return 0; 332 } 333 334 static int sun4i_drv_remove(struct platform_device *pdev) 335 { 336 return 0; 337 } 338 339 static const struct of_device_id sun4i_drv_of_table[] = { 340 { .compatible = "allwinner,sun5i-a13-display-engine" }, 341 { } 342 }; 343 MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); 344 345 static struct platform_driver sun4i_drv_platform_driver = { 346 .probe = sun4i_drv_probe, 347 .remove = sun4i_drv_remove, 348 .driver = { 349 .name = "sun4i-drm", 350 .of_match_table = sun4i_drv_of_table, 351 }, 352 }; 353 module_platform_driver(sun4i_drv_platform_driver); 354 355 MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); 356 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 357 MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); 358 MODULE_LICENSE("GPL"); 359