Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 1 | /* |
| 2 | * shmob_drm_drv.c -- SH Mobile DRM driver |
| 3 | * |
| 4 | * Copyright (C) 2012 Renesas Corporation |
| 5 | * |
| 6 | * Laurent Pinchart (laurent.pinchart@ideasonboard.com) |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License as published by |
| 10 | * the Free Software Foundation; either version 2 of the License, or |
| 11 | * (at your option) any later version. |
| 12 | */ |
| 13 | |
| 14 | #include <linux/clk.h> |
| 15 | #include <linux/io.h> |
| 16 | #include <linux/mm.h> |
| 17 | #include <linux/module.h> |
| 18 | #include <linux/platform_device.h> |
| 19 | #include <linux/pm.h> |
| 20 | #include <linux/slab.h> |
| 21 | |
| 22 | #include <drm/drmP.h> |
| 23 | #include <drm/drm_crtc_helper.h> |
| 24 | #include <drm/drm_gem_cma_helper.h> |
| 25 | |
| 26 | #include "shmob_drm_crtc.h" |
| 27 | #include "shmob_drm_drv.h" |
| 28 | #include "shmob_drm_kms.h" |
| 29 | #include "shmob_drm_plane.h" |
| 30 | #include "shmob_drm_regs.h" |
| 31 | |
| 32 | /* ----------------------------------------------------------------------------- |
| 33 | * Hardware initialization |
| 34 | */ |
| 35 | |
Greg Kroah-Hartman | 56550d9 | 2012-12-21 15:09:25 -0800 | [diff] [blame] | 36 | static int shmob_drm_init_interface(struct shmob_drm_device *sdev) |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 37 | { |
| 38 | static const u32 ldmt1r[] = { |
| 39 | [SHMOB_DRM_IFACE_RGB8] = LDMT1R_MIFTYP_RGB8, |
| 40 | [SHMOB_DRM_IFACE_RGB9] = LDMT1R_MIFTYP_RGB9, |
| 41 | [SHMOB_DRM_IFACE_RGB12A] = LDMT1R_MIFTYP_RGB12A, |
| 42 | [SHMOB_DRM_IFACE_RGB12B] = LDMT1R_MIFTYP_RGB12B, |
| 43 | [SHMOB_DRM_IFACE_RGB16] = LDMT1R_MIFTYP_RGB16, |
| 44 | [SHMOB_DRM_IFACE_RGB18] = LDMT1R_MIFTYP_RGB18, |
| 45 | [SHMOB_DRM_IFACE_RGB24] = LDMT1R_MIFTYP_RGB24, |
| 46 | [SHMOB_DRM_IFACE_YUV422] = LDMT1R_MIFTYP_YCBCR, |
| 47 | [SHMOB_DRM_IFACE_SYS8A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8A, |
| 48 | [SHMOB_DRM_IFACE_SYS8B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8B, |
| 49 | [SHMOB_DRM_IFACE_SYS8C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8C, |
| 50 | [SHMOB_DRM_IFACE_SYS8D] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS8D, |
| 51 | [SHMOB_DRM_IFACE_SYS9] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS9, |
| 52 | [SHMOB_DRM_IFACE_SYS12] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS12, |
| 53 | [SHMOB_DRM_IFACE_SYS16A] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16A, |
| 54 | [SHMOB_DRM_IFACE_SYS16B] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16B, |
| 55 | [SHMOB_DRM_IFACE_SYS16C] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS16C, |
| 56 | [SHMOB_DRM_IFACE_SYS18] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS18, |
| 57 | [SHMOB_DRM_IFACE_SYS24] = LDMT1R_IFM | LDMT1R_MIFTYP_SYS24, |
| 58 | }; |
| 59 | |
| 60 | if (sdev->pdata->iface.interface >= ARRAY_SIZE(ldmt1r)) { |
| 61 | dev_err(sdev->dev, "invalid interface type %u\n", |
| 62 | sdev->pdata->iface.interface); |
| 63 | return -EINVAL; |
| 64 | } |
| 65 | |
| 66 | sdev->ldmt1r = ldmt1r[sdev->pdata->iface.interface]; |
| 67 | return 0; |
| 68 | } |
| 69 | |
Greg Kroah-Hartman | 56550d9 | 2012-12-21 15:09:25 -0800 | [diff] [blame] | 70 | static int shmob_drm_setup_clocks(struct shmob_drm_device *sdev, |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 71 | enum shmob_drm_clk_source clksrc) |
| 72 | { |
| 73 | struct clk *clk; |
| 74 | char *clkname; |
| 75 | |
| 76 | switch (clksrc) { |
| 77 | case SHMOB_DRM_CLK_BUS: |
| 78 | clkname = "bus_clk"; |
| 79 | sdev->lddckr = LDDCKR_ICKSEL_BUS; |
| 80 | break; |
| 81 | case SHMOB_DRM_CLK_PERIPHERAL: |
| 82 | clkname = "peripheral_clk"; |
| 83 | sdev->lddckr = LDDCKR_ICKSEL_MIPI; |
| 84 | break; |
| 85 | case SHMOB_DRM_CLK_EXTERNAL: |
| 86 | clkname = NULL; |
| 87 | sdev->lddckr = LDDCKR_ICKSEL_HDMI; |
| 88 | break; |
| 89 | default: |
| 90 | return -EINVAL; |
| 91 | } |
| 92 | |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 93 | clk = devm_clk_get(sdev->dev, clkname); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 94 | if (IS_ERR(clk)) { |
| 95 | dev_err(sdev->dev, "cannot get dot clock %s\n", clkname); |
| 96 | return PTR_ERR(clk); |
| 97 | } |
| 98 | |
| 99 | sdev->clock = clk; |
| 100 | return 0; |
| 101 | } |
| 102 | |
| 103 | /* ----------------------------------------------------------------------------- |
| 104 | * DRM operations |
| 105 | */ |
| 106 | |
| 107 | static int shmob_drm_unload(struct drm_device *dev) |
| 108 | { |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 109 | drm_kms_helper_poll_fini(dev); |
| 110 | drm_mode_config_cleanup(dev); |
| 111 | drm_vblank_cleanup(dev); |
| 112 | drm_irq_uninstall(dev); |
| 113 | |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 114 | dev->dev_private = NULL; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 115 | |
| 116 | return 0; |
| 117 | } |
| 118 | |
| 119 | static int shmob_drm_load(struct drm_device *dev, unsigned long flags) |
| 120 | { |
| 121 | struct shmob_drm_platform_data *pdata = dev->dev->platform_data; |
| 122 | struct platform_device *pdev = dev->platformdev; |
| 123 | struct shmob_drm_device *sdev; |
| 124 | struct resource *res; |
| 125 | unsigned int i; |
| 126 | int ret; |
| 127 | |
| 128 | if (pdata == NULL) { |
| 129 | dev_err(dev->dev, "no platform data\n"); |
| 130 | return -EINVAL; |
| 131 | } |
| 132 | |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 133 | sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 134 | if (sdev == NULL) { |
| 135 | dev_err(dev->dev, "failed to allocate private data\n"); |
| 136 | return -ENOMEM; |
| 137 | } |
| 138 | |
| 139 | sdev->dev = &pdev->dev; |
| 140 | sdev->pdata = pdata; |
| 141 | spin_lock_init(&sdev->irq_lock); |
| 142 | |
| 143 | sdev->ddev = dev; |
| 144 | dev->dev_private = sdev; |
| 145 | |
| 146 | /* I/O resources and clocks */ |
| 147 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 148 | if (res == NULL) { |
| 149 | dev_err(&pdev->dev, "failed to get memory resource\n"); |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 150 | return -EINVAL; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 151 | } |
| 152 | |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 153 | sdev->mmio = devm_ioremap_nocache(&pdev->dev, res->start, |
| 154 | resource_size(res)); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 155 | if (sdev->mmio == NULL) { |
| 156 | dev_err(&pdev->dev, "failed to remap memory resource\n"); |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 157 | return -ENOMEM; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 158 | } |
| 159 | |
| 160 | ret = shmob_drm_setup_clocks(sdev, pdata->clk_source); |
| 161 | if (ret < 0) |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 162 | return ret; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 163 | |
| 164 | ret = shmob_drm_init_interface(sdev); |
| 165 | if (ret < 0) |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 166 | return ret; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 167 | |
| 168 | ret = shmob_drm_modeset_init(sdev); |
| 169 | if (ret < 0) { |
| 170 | dev_err(&pdev->dev, "failed to initialize mode setting\n"); |
Laurent Pinchart | 16ad3b2 | 2013-04-25 12:12:33 +0200 | [diff] [blame] | 171 | return ret; |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | for (i = 0; i < 4; ++i) { |
| 175 | ret = shmob_drm_plane_create(sdev, i); |
| 176 | if (ret < 0) { |
| 177 | dev_err(&pdev->dev, "failed to create plane %u\n", i); |
| 178 | goto done; |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | ret = drm_vblank_init(dev, 1); |
| 183 | if (ret < 0) { |
| 184 | dev_err(&pdev->dev, "failed to initialize vblank\n"); |
| 185 | goto done; |
| 186 | } |
| 187 | |
| 188 | ret = drm_irq_install(dev); |
| 189 | if (ret < 0) { |
| 190 | dev_err(&pdev->dev, "failed to install IRQ handler\n"); |
| 191 | goto done; |
| 192 | } |
| 193 | |
Thierry Reding | a16d4f8 | 2012-10-15 18:03:42 +0000 | [diff] [blame] | 194 | platform_set_drvdata(pdev, sdev); |
| 195 | |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 196 | done: |
| 197 | if (ret) |
| 198 | shmob_drm_unload(dev); |
| 199 | |
| 200 | return ret; |
| 201 | } |
| 202 | |
| 203 | static void shmob_drm_preclose(struct drm_device *dev, struct drm_file *file) |
| 204 | { |
| 205 | struct shmob_drm_device *sdev = dev->dev_private; |
| 206 | |
| 207 | shmob_drm_crtc_cancel_page_flip(&sdev->crtc, file); |
| 208 | } |
| 209 | |
| 210 | static irqreturn_t shmob_drm_irq(int irq, void *arg) |
| 211 | { |
| 212 | struct drm_device *dev = arg; |
| 213 | struct shmob_drm_device *sdev = dev->dev_private; |
| 214 | unsigned long flags; |
| 215 | u32 status; |
| 216 | |
| 217 | /* Acknowledge interrupts. Putting interrupt enable and interrupt flag |
| 218 | * bits in the same register is really brain-dead design and requires |
| 219 | * taking a spinlock. |
| 220 | */ |
| 221 | spin_lock_irqsave(&sdev->irq_lock, flags); |
| 222 | status = lcdc_read(sdev, LDINTR); |
| 223 | lcdc_write(sdev, LDINTR, status ^ LDINTR_STATUS_MASK); |
| 224 | spin_unlock_irqrestore(&sdev->irq_lock, flags); |
| 225 | |
| 226 | if (status & LDINTR_VES) { |
| 227 | drm_handle_vblank(dev, 0); |
| 228 | shmob_drm_crtc_finish_page_flip(&sdev->crtc); |
| 229 | } |
| 230 | |
| 231 | return IRQ_HANDLED; |
| 232 | } |
| 233 | |
| 234 | static int shmob_drm_enable_vblank(struct drm_device *dev, int crtc) |
| 235 | { |
| 236 | struct shmob_drm_device *sdev = dev->dev_private; |
| 237 | |
| 238 | shmob_drm_crtc_enable_vblank(sdev, true); |
| 239 | |
| 240 | return 0; |
| 241 | } |
| 242 | |
| 243 | static void shmob_drm_disable_vblank(struct drm_device *dev, int crtc) |
| 244 | { |
| 245 | struct shmob_drm_device *sdev = dev->dev_private; |
| 246 | |
| 247 | shmob_drm_crtc_enable_vblank(sdev, false); |
| 248 | } |
| 249 | |
| 250 | static const struct file_operations shmob_drm_fops = { |
| 251 | .owner = THIS_MODULE, |
| 252 | .open = drm_open, |
| 253 | .release = drm_release, |
| 254 | .unlocked_ioctl = drm_ioctl, |
| 255 | #ifdef CONFIG_COMPAT |
| 256 | .compat_ioctl = drm_compat_ioctl, |
| 257 | #endif |
| 258 | .poll = drm_poll, |
| 259 | .read = drm_read, |
| 260 | .fasync = drm_fasync, |
| 261 | .llseek = no_llseek, |
| 262 | .mmap = drm_gem_cma_mmap, |
| 263 | }; |
| 264 | |
| 265 | static struct drm_driver shmob_drm_driver = { |
Laurent Pinchart | 416c390 | 2013-06-08 09:33:27 +0200 | [diff] [blame] | 266 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET |
| 267 | | DRIVER_PRIME, |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 268 | .load = shmob_drm_load, |
| 269 | .unload = shmob_drm_unload, |
| 270 | .preclose = shmob_drm_preclose, |
| 271 | .irq_handler = shmob_drm_irq, |
| 272 | .get_vblank_counter = drm_vblank_count, |
| 273 | .enable_vblank = shmob_drm_enable_vblank, |
| 274 | .disable_vblank = shmob_drm_disable_vblank, |
| 275 | .gem_free_object = drm_gem_cma_free_object, |
| 276 | .gem_vm_ops = &drm_gem_cma_vm_ops, |
Laurent Pinchart | 416c390 | 2013-06-08 09:33:27 +0200 | [diff] [blame] | 277 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, |
| 278 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, |
Laurent Pinchart | 58cbd3a | 2013-07-10 15:28:15 +0200 | [diff] [blame] | 279 | .gem_prime_import = drm_gem_prime_import, |
| 280 | .gem_prime_export = drm_gem_prime_export, |
| 281 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, |
| 282 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, |
| 283 | .gem_prime_vmap = drm_gem_cma_prime_vmap, |
| 284 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, |
| 285 | .gem_prime_mmap = drm_gem_cma_prime_mmap, |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 286 | .dumb_create = drm_gem_cma_dumb_create, |
| 287 | .dumb_map_offset = drm_gem_cma_dumb_map_offset, |
| 288 | .dumb_destroy = drm_gem_cma_dumb_destroy, |
| 289 | .fops = &shmob_drm_fops, |
| 290 | .name = "shmob-drm", |
| 291 | .desc = "Renesas SH Mobile DRM", |
| 292 | .date = "20120424", |
| 293 | .major = 1, |
| 294 | .minor = 0, |
| 295 | }; |
| 296 | |
| 297 | /* ----------------------------------------------------------------------------- |
| 298 | * Power management |
| 299 | */ |
| 300 | |
| 301 | #if CONFIG_PM_SLEEP |
| 302 | static int shmob_drm_pm_suspend(struct device *dev) |
| 303 | { |
Thierry Reding | a16d4f8 | 2012-10-15 18:03:42 +0000 | [diff] [blame] | 304 | struct shmob_drm_device *sdev = dev_get_drvdata(dev); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 305 | |
Thierry Reding | a16d4f8 | 2012-10-15 18:03:42 +0000 | [diff] [blame] | 306 | drm_kms_helper_poll_disable(sdev->ddev); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 307 | shmob_drm_crtc_suspend(&sdev->crtc); |
| 308 | |
| 309 | return 0; |
| 310 | } |
| 311 | |
| 312 | static int shmob_drm_pm_resume(struct device *dev) |
| 313 | { |
Thierry Reding | a16d4f8 | 2012-10-15 18:03:42 +0000 | [diff] [blame] | 314 | struct shmob_drm_device *sdev = dev_get_drvdata(dev); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 315 | |
Daniel Vetter | b13f598 | 2012-12-02 01:36:59 +0100 | [diff] [blame] | 316 | drm_modeset_lock_all(sdev->ddev); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 317 | shmob_drm_crtc_resume(&sdev->crtc); |
Daniel Vetter | b13f598 | 2012-12-02 01:36:59 +0100 | [diff] [blame] | 318 | drm_modeset_unlock_all(sdev->ddev); |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 319 | |
| 320 | drm_kms_helper_poll_enable(sdev->ddev); |
| 321 | return 0; |
| 322 | } |
| 323 | #endif |
| 324 | |
| 325 | static const struct dev_pm_ops shmob_drm_pm_ops = { |
| 326 | SET_SYSTEM_SLEEP_PM_OPS(shmob_drm_pm_suspend, shmob_drm_pm_resume) |
| 327 | }; |
| 328 | |
| 329 | /* ----------------------------------------------------------------------------- |
| 330 | * Platform driver |
| 331 | */ |
| 332 | |
Greg Kroah-Hartman | 56550d9 | 2012-12-21 15:09:25 -0800 | [diff] [blame] | 333 | static int shmob_drm_probe(struct platform_device *pdev) |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 334 | { |
| 335 | return drm_platform_init(&shmob_drm_driver, pdev); |
| 336 | } |
| 337 | |
Greg Kroah-Hartman | 56550d9 | 2012-12-21 15:09:25 -0800 | [diff] [blame] | 338 | static int shmob_drm_remove(struct platform_device *pdev) |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 339 | { |
| 340 | drm_platform_exit(&shmob_drm_driver, pdev); |
| 341 | |
| 342 | return 0; |
| 343 | } |
| 344 | |
| 345 | static struct platform_driver shmob_drm_platform_driver = { |
| 346 | .probe = shmob_drm_probe, |
Greg Kroah-Hartman | 56550d9 | 2012-12-21 15:09:25 -0800 | [diff] [blame] | 347 | .remove = shmob_drm_remove, |
Laurent Pinchart | 51c1327 | 2012-04-26 13:53:59 +0200 | [diff] [blame] | 348 | .driver = { |
| 349 | .owner = THIS_MODULE, |
| 350 | .name = "shmob-drm", |
| 351 | .pm = &shmob_drm_pm_ops, |
| 352 | }, |
| 353 | }; |
| 354 | |
| 355 | module_platform_driver(shmob_drm_platform_driver); |
| 356 | |
| 357 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); |
| 358 | MODULE_DESCRIPTION("Renesas SH Mobile DRM Driver"); |
| 359 | MODULE_LICENSE("GPL"); |