1/* 2 * Copyright (C) 2014 Traphandler 3 * Copyright (C) 2014 Free Electrons 4 * Copyright (C) 2014 Atmel 5 * 6 * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> 7 * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> 8 * 9 * This program is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU General Public License version 2 as published by 11 * the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 16 * more details. 17 * 18 * You should have received a copy of the GNU General Public License along with 19 * this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 22#include <linux/clk.h> 23#include <linux/irq.h> 24#include <linux/irqchip.h> 25#include <linux/module.h> 26#include <linux/pm_runtime.h> 27 28#include "atmel_hlcdc_dc.h" 29 30#define ATMEL_HLCDC_LAYER_IRQS_OFFSET 8 31 32static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d3_layers[] = { 33 { 34 .name = "base", 35 .formats = &atmel_hlcdc_plane_rgb_formats, 36 .regs_offset = 0x40, 37 .id = 0, 38 .type = ATMEL_HLCDC_BASE_LAYER, 39 .nconfigs = 7, 40 .layout = { 41 .xstride = { 2 }, 42 .default_color = 3, 43 .general_config = 4, 44 .disc_pos = 5, 45 .disc_size = 6, 46 }, 47 }, 48 { 49 .name = "overlay1", 50 .formats = &atmel_hlcdc_plane_rgb_formats, 51 .regs_offset = 0x140, 52 .id = 1, 53 .type = ATMEL_HLCDC_OVERLAY_LAYER, 54 .nconfigs = 10, 55 .layout = { 56 .pos = 2, 57 .size = 3, 58 .xstride = { 4 }, 59 .pstride = { 5 }, 60 .default_color = 6, 61 .chroma_key = 7, 62 .chroma_key_mask = 8, 63 .general_config = 9, 64 }, 65 }, 66 { 67 .name = "overlay2", 68 .formats = &atmel_hlcdc_plane_rgb_formats, 69 .regs_offset = 0x240, 70 .id = 2, 71 .type = ATMEL_HLCDC_OVERLAY_LAYER, 72 .nconfigs = 10, 73 .layout = { 74 .pos = 2, 75 .size = 3, 76 .xstride = { 4 }, 77 .pstride = { 5 }, 78 .default_color = 6, 79 .chroma_key = 7, 80 .chroma_key_mask = 8, 81 .general_config = 9, 82 }, 83 }, 84 { 85 .name = "high-end-overlay", 86 .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, 87 .regs_offset = 0x340, 88 .id = 3, 89 .type = ATMEL_HLCDC_OVERLAY_LAYER, 90 .nconfigs = 42, 91 .layout = { 92 .pos = 2, 93 .size = 3, 94 .memsize = 4, 95 .xstride = { 5, 7 }, 96 .pstride = { 6, 8 }, 97 .default_color = 9, 98 .chroma_key = 10, 99 .chroma_key_mask = 11, 100 .general_config = 12, 101 .csc = 14, 102 }, 103 }, 104 { 105 .name = "cursor", 106 .formats = &atmel_hlcdc_plane_rgb_formats, 107 .regs_offset = 0x440, 108 .id = 4, 109 .type = ATMEL_HLCDC_CURSOR_LAYER, 110 .nconfigs = 10, 111 .max_width = 128, 112 .max_height = 128, 113 .layout = { 114 .pos = 2, 115 .size = 3, 116 .xstride = { 4 }, 117 .pstride = { 5 }, 118 .default_color = 6, 119 .chroma_key = 7, 120 .chroma_key_mask = 8, 121 .general_config = 9, 122 }, 123 }, 124}; 125 126static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { 127 .min_width = 0, 128 .min_height = 0, 129 .max_width = 2048, 130 .max_height = 2048, 131 .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), 132 .layers = atmel_hlcdc_sama5d3_layers, 133}; 134 135static const struct of_device_id atmel_hlcdc_of_match[] = { 136 { 137 .compatible = "atmel,sama5d3-hlcdc", 138 .data = &atmel_hlcdc_dc_sama5d3, 139 }, 140 { /* sentinel */ }, 141}; 142 143int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, 144 struct drm_display_mode *mode) 145{ 146 int vfront_porch = mode->vsync_start - mode->vdisplay; 147 int vback_porch = mode->vtotal - mode->vsync_end; 148 int vsync_len = mode->vsync_end - mode->vsync_start; 149 int hfront_porch = mode->hsync_start - mode->hdisplay; 150 int hback_porch = mode->htotal - mode->hsync_end; 151 int hsync_len = mode->hsync_end - mode->hsync_start; 152 153 if (hsync_len > 0x40 || hsync_len < 1) 154 return MODE_HSYNC; 155 156 if (vsync_len > 0x40 || vsync_len < 1) 157 return MODE_VSYNC; 158 159 if (hfront_porch > 0x200 || hfront_porch < 1 || 160 hback_porch > 0x200 || hback_porch < 1 || 161 mode->hdisplay < 1) 162 return MODE_H_ILLEGAL; 163 164 if (vfront_porch > 0x40 || vfront_porch < 1 || 165 vback_porch > 0x40 || vback_porch < 0 || 166 mode->vdisplay < 1) 167 return MODE_V_ILLEGAL; 168 169 return MODE_OK; 170} 171 172static irqreturn_t atmel_hlcdc_dc_irq_handler(int irq, void *data) 173{ 174 struct drm_device *dev = data; 175 struct atmel_hlcdc_dc *dc = dev->dev_private; 176 unsigned long status; 177 unsigned int imr, isr; 178 int i; 179 180 regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_IMR, &imr); 181 regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); 182 status = imr & isr; 183 if (!status) 184 return IRQ_NONE; 185 186 if (status & ATMEL_HLCDC_SOF) 187 atmel_hlcdc_crtc_irq(dc->crtc); 188 189 for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { 190 struct atmel_hlcdc_layer *layer = dc->layers[i]; 191 192 if (!(ATMEL_HLCDC_LAYER_STATUS(i) & status) || !layer) 193 continue; 194 195 atmel_hlcdc_layer_irq(layer); 196 } 197 198 return IRQ_HANDLED; 199} 200 201static struct drm_framebuffer *atmel_hlcdc_fb_create(struct drm_device *dev, 202 struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) 203{ 204 return drm_fb_cma_create(dev, file_priv, mode_cmd); 205} 206 207static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev) 208{ 209 struct atmel_hlcdc_dc *dc = dev->dev_private; 210 211 if (dc->fbdev) { 212 drm_fbdev_cma_hotplug_event(dc->fbdev); 213 } else { 214 dc->fbdev = drm_fbdev_cma_init(dev, 24, 215 dev->mode_config.num_crtc, 216 dev->mode_config.num_connector); 217 if (IS_ERR(dc->fbdev)) 218 dc->fbdev = NULL; 219 } 220} 221 222static const struct drm_mode_config_funcs mode_config_funcs = { 223 .fb_create = atmel_hlcdc_fb_create, 224 .output_poll_changed = atmel_hlcdc_fb_output_poll_changed, 225 .atomic_check = drm_atomic_helper_check, 226 .atomic_commit = drm_atomic_helper_commit, 227}; 228 229static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) 230{ 231 struct atmel_hlcdc_dc *dc = dev->dev_private; 232 struct atmel_hlcdc_planes *planes; 233 int ret; 234 int i; 235 236 drm_mode_config_init(dev); 237 238 ret = atmel_hlcdc_create_outputs(dev); 239 if (ret) { 240 dev_err(dev->dev, "failed to create panel: %d\n", ret); 241 return ret; 242 } 243 244 planes = atmel_hlcdc_create_planes(dev); 245 if (IS_ERR(planes)) { 246 dev_err(dev->dev, "failed to create planes\n"); 247 return PTR_ERR(planes); 248 } 249 250 dc->planes = planes; 251 252 dc->layers[planes->primary->layer.desc->id] = 253 &planes->primary->layer; 254 255 if (planes->cursor) 256 dc->layers[planes->cursor->layer.desc->id] = 257 &planes->cursor->layer; 258 259 for (i = 0; i < planes->noverlays; i++) 260 dc->layers[planes->overlays[i]->layer.desc->id] = 261 &planes->overlays[i]->layer; 262 263 ret = atmel_hlcdc_crtc_create(dev); 264 if (ret) { 265 dev_err(dev->dev, "failed to create crtc\n"); 266 return ret; 267 } 268 269 dev->mode_config.min_width = dc->desc->min_width; 270 dev->mode_config.min_height = dc->desc->min_height; 271 dev->mode_config.max_width = dc->desc->max_width; 272 dev->mode_config.max_height = dc->desc->max_height; 273 dev->mode_config.funcs = &mode_config_funcs; 274 275 return 0; 276} 277 278static int atmel_hlcdc_dc_load(struct drm_device *dev) 279{ 280 struct platform_device *pdev = to_platform_device(dev->dev); 281 const struct of_device_id *match; 282 struct atmel_hlcdc_dc *dc; 283 int ret; 284 285 match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node); 286 if (!match) { 287 dev_err(&pdev->dev, "invalid compatible string\n"); 288 return -ENODEV; 289 } 290 291 if (!match->data) { 292 dev_err(&pdev->dev, "invalid hlcdc description\n"); 293 return -EINVAL; 294 } 295 296 dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL); 297 if (!dc) 298 return -ENOMEM; 299 300 dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0); 301 if (!dc->wq) 302 return -ENOMEM; 303 304 dc->desc = match->data; 305 dc->hlcdc = dev_get_drvdata(dev->dev->parent); 306 dev->dev_private = dc; 307 308 ret = clk_prepare_enable(dc->hlcdc->periph_clk); 309 if (ret) { 310 dev_err(dev->dev, "failed to enable periph_clk\n"); 311 goto err_destroy_wq; 312 } 313 314 pm_runtime_enable(dev->dev); 315 316 ret = atmel_hlcdc_dc_modeset_init(dev); 317 if (ret < 0) { 318 dev_err(dev->dev, "failed to initialize mode setting\n"); 319 goto err_periph_clk_disable; 320 } 321 322 drm_mode_config_reset(dev); 323 324 ret = drm_vblank_init(dev, 1); 325 if (ret < 0) { 326 dev_err(dev->dev, "failed to initialize vblank\n"); 327 goto err_periph_clk_disable; 328 } 329 330 pm_runtime_get_sync(dev->dev); 331 ret = drm_irq_install(dev, dc->hlcdc->irq); 332 pm_runtime_put_sync(dev->dev); 333 if (ret < 0) { 334 dev_err(dev->dev, "failed to install IRQ handler\n"); 335 goto err_periph_clk_disable; 336 } 337 338 platform_set_drvdata(pdev, dev); 339 340 drm_kms_helper_poll_init(dev); 341 342 /* force connectors detection */ 343 drm_helper_hpd_irq_event(dev); 344 345 return 0; 346 347err_periph_clk_disable: 348 pm_runtime_disable(dev->dev); 349 clk_disable_unprepare(dc->hlcdc->periph_clk); 350 351err_destroy_wq: 352 destroy_workqueue(dc->wq); 353 354 return ret; 355} 356 357static void atmel_hlcdc_dc_unload(struct drm_device *dev) 358{ 359 struct atmel_hlcdc_dc *dc = dev->dev_private; 360 361 if (dc->fbdev) 362 drm_fbdev_cma_fini(dc->fbdev); 363 flush_workqueue(dc->wq); 364 drm_kms_helper_poll_fini(dev); 365 drm_mode_config_cleanup(dev); 366 drm_vblank_cleanup(dev); 367 368 pm_runtime_get_sync(dev->dev); 369 drm_irq_uninstall(dev); 370 pm_runtime_put_sync(dev->dev); 371 372 dev->dev_private = NULL; 373 374 pm_runtime_disable(dev->dev); 375 clk_disable_unprepare(dc->hlcdc->periph_clk); 376 destroy_workqueue(dc->wq); 377} 378 379static int atmel_hlcdc_dc_connector_plug_all(struct drm_device *dev) 380{ 381 struct drm_connector *connector, *failed; 382 int ret; 383 384 mutex_lock(&dev->mode_config.mutex); 385 list_for_each_entry(connector, &dev->mode_config.connector_list, head) { 386 ret = drm_connector_register(connector); 387 if (ret) { 388 failed = connector; 389 goto err; 390 } 391 } 392 mutex_unlock(&dev->mode_config.mutex); 393 return 0; 394 395err: 396 list_for_each_entry(connector, &dev->mode_config.connector_list, head) { 397 if (failed == connector) 398 break; 399 400 drm_connector_unregister(connector); 401 } 402 mutex_unlock(&dev->mode_config.mutex); 403 404 return ret; 405} 406 407static void atmel_hlcdc_dc_connector_unplug_all(struct drm_device *dev) 408{ 409 mutex_lock(&dev->mode_config.mutex); 410 drm_connector_unplug_all(dev); 411 mutex_unlock(&dev->mode_config.mutex); 412} 413 414static void atmel_hlcdc_dc_preclose(struct drm_device *dev, 415 struct drm_file *file) 416{ 417 struct drm_crtc *crtc; 418 419 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) 420 atmel_hlcdc_crtc_cancel_page_flip(crtc, file); 421} 422 423static void atmel_hlcdc_dc_lastclose(struct drm_device *dev) 424{ 425 struct atmel_hlcdc_dc *dc = dev->dev_private; 426 427 drm_fbdev_cma_restore_mode(dc->fbdev); 428} 429 430static int atmel_hlcdc_dc_irq_postinstall(struct drm_device *dev) 431{ 432 struct atmel_hlcdc_dc *dc = dev->dev_private; 433 unsigned int cfg = 0; 434 int i; 435 436 /* Enable interrupts on activated layers */ 437 for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { 438 if (dc->layers[i]) 439 cfg |= ATMEL_HLCDC_LAYER_STATUS(i); 440 } 441 442 regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, cfg); 443 444 return 0; 445} 446 447static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev) 448{ 449 struct atmel_hlcdc_dc *dc = dev->dev_private; 450 unsigned int isr; 451 452 regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff); 453 regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); 454} 455 456static int atmel_hlcdc_dc_enable_vblank(struct drm_device *dev, int crtc) 457{ 458 struct atmel_hlcdc_dc *dc = dev->dev_private; 459 460 /* Enable SOF (Start Of Frame) interrupt for vblank counting */ 461 regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, ATMEL_HLCDC_SOF); 462 463 return 0; 464} 465 466static void atmel_hlcdc_dc_disable_vblank(struct drm_device *dev, int crtc) 467{ 468 struct atmel_hlcdc_dc *dc = dev->dev_private; 469 470 regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, ATMEL_HLCDC_SOF); 471} 472 473static const struct file_operations fops = { 474 .owner = THIS_MODULE, 475 .open = drm_open, 476 .release = drm_release, 477 .unlocked_ioctl = drm_ioctl, 478#ifdef CONFIG_COMPAT 479 .compat_ioctl = drm_compat_ioctl, 480#endif 481 .poll = drm_poll, 482 .read = drm_read, 483 .llseek = no_llseek, 484 .mmap = drm_gem_cma_mmap, 485}; 486 487static struct drm_driver atmel_hlcdc_dc_driver = { 488 .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET, 489 .preclose = atmel_hlcdc_dc_preclose, 490 .lastclose = atmel_hlcdc_dc_lastclose, 491 .irq_handler = atmel_hlcdc_dc_irq_handler, 492 .irq_preinstall = atmel_hlcdc_dc_irq_uninstall, 493 .irq_postinstall = atmel_hlcdc_dc_irq_postinstall, 494 .irq_uninstall = atmel_hlcdc_dc_irq_uninstall, 495 .get_vblank_counter = drm_vblank_count, 496 .enable_vblank = atmel_hlcdc_dc_enable_vblank, 497 .disable_vblank = atmel_hlcdc_dc_disable_vblank, 498 .gem_free_object = drm_gem_cma_free_object, 499 .gem_vm_ops = &drm_gem_cma_vm_ops, 500 .dumb_create = drm_gem_cma_dumb_create, 501 .dumb_map_offset = drm_gem_cma_dumb_map_offset, 502 .dumb_destroy = drm_gem_dumb_destroy, 503 .fops = &fops, 504 .name = "atmel-hlcdc", 505 .desc = "Atmel HLCD Controller DRM", 506 .date = "20141504", 507 .major = 1, 508 .minor = 0, 509}; 510 511static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) 512{ 513 struct drm_device *ddev; 514 int ret; 515 516 ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, &pdev->dev); 517 if (!ddev) 518 return -ENOMEM; 519 520 ret = drm_dev_set_unique(ddev, dev_name(ddev->dev)); 521 if (ret) 522 goto err_unref; 523 524 ret = atmel_hlcdc_dc_load(ddev); 525 if (ret) 526 goto err_unref; 527 528 ret = drm_dev_register(ddev, 0); 529 if (ret) 530 goto err_unload; 531 532 ret = atmel_hlcdc_dc_connector_plug_all(ddev); 533 if (ret) 534 goto err_unregister; 535 536 return 0; 537 538err_unregister: 539 drm_dev_unregister(ddev); 540 541err_unload: 542 atmel_hlcdc_dc_unload(ddev); 543 544err_unref: 545 drm_dev_unref(ddev); 546 547 return ret; 548} 549 550static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) 551{ 552 struct drm_device *ddev = platform_get_drvdata(pdev); 553 554 atmel_hlcdc_dc_connector_unplug_all(ddev); 555 drm_dev_unregister(ddev); 556 atmel_hlcdc_dc_unload(ddev); 557 drm_dev_unref(ddev); 558 559 return 0; 560} 561 562#ifdef CONFIG_PM_SLEEP 563static int atmel_hlcdc_dc_drm_suspend(struct device *dev) 564{ 565 struct drm_device *drm_dev = dev_get_drvdata(dev); 566 struct drm_crtc *crtc; 567 568 if (pm_runtime_suspended(dev)) 569 return 0; 570 571 drm_modeset_lock_all(drm_dev); 572 list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) 573 atmel_hlcdc_crtc_suspend(crtc); 574 drm_modeset_unlock_all(drm_dev); 575 return 0; 576} 577 578static int atmel_hlcdc_dc_drm_resume(struct device *dev) 579{ 580 struct drm_device *drm_dev = dev_get_drvdata(dev); 581 struct drm_crtc *crtc; 582 583 if (pm_runtime_suspended(dev)) 584 return 0; 585 586 drm_modeset_lock_all(drm_dev); 587 list_for_each_entry(crtc, &drm_dev->mode_config.crtc_list, head) 588 atmel_hlcdc_crtc_resume(crtc); 589 drm_modeset_unlock_all(drm_dev); 590 return 0; 591} 592#endif 593 594static SIMPLE_DEV_PM_OPS(atmel_hlcdc_dc_drm_pm_ops, 595 atmel_hlcdc_dc_drm_suspend, atmel_hlcdc_dc_drm_resume); 596 597static const struct of_device_id atmel_hlcdc_dc_of_match[] = { 598 { .compatible = "atmel,hlcdc-display-controller" }, 599 { }, 600}; 601 602static struct platform_driver atmel_hlcdc_dc_platform_driver = { 603 .probe = atmel_hlcdc_dc_drm_probe, 604 .remove = atmel_hlcdc_dc_drm_remove, 605 .driver = { 606 .name = "atmel-hlcdc-display-controller", 607 .pm = &atmel_hlcdc_dc_drm_pm_ops, 608 .of_match_table = atmel_hlcdc_dc_of_match, 609 }, 610}; 611module_platform_driver(atmel_hlcdc_dc_platform_driver); 612 613MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); 614MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); 615MODULE_DESCRIPTION("Atmel HLCDC Display Controller DRM Driver"); 616MODULE_LICENSE("GPL"); 617MODULE_ALIAS("platform:atmel-hlcdc-dc"); 618