| /* |
| * |
| * (C) COPYRIGHT 2012-2014 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU licence. |
| * |
| * A copy of the licence is included with the program, and can also be obtained |
| * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| |
| |
| /** |
| * pl111_drm_pl111.c |
| * PL111 specific functions for PL111 DRM |
| */ |
| #include <linux/amba/bus.h> |
| #include <linux/amba/clcd.h> |
| #include <linux/version.h> |
| #include <linux/shmem_fs.h> |
| #include <linux/dma-buf.h> |
| #include <linux/module.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_crtc_helper.h> |
| |
| #include "pl111_drm.h" |
| |
| /* This can't be called from IRQ context, due to clk_get() and board->enable */ |
| static int clcd_enable(struct drm_framebuffer *fb) |
| { |
| __u32 cntl; |
| struct clcd_board *board; |
| |
| pr_info("DRM %s\n", __func__); |
| |
| clk_prepare_enable(priv.clk); |
| |
| /* Enable and Power Up */ |
| cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1); |
| DRM_DEBUG_KMS("fb->bits_per_pixel = %d\n", fb->bits_per_pixel); |
| if (fb->bits_per_pixel == 16) |
| cntl |= CNTL_LCDBPP16_565; |
| else if (fb->bits_per_pixel == 32 && fb->depth == 24) |
| cntl |= CNTL_LCDBPP24; |
| else |
| BUG_ON(1); |
| |
| cntl |= CNTL_BGR; |
| |
| writel(cntl, priv.regs + CLCD_PL111_CNTL); |
| |
| if (priv.amba_dev->dev.platform_data) { |
| board = priv.amba_dev->dev.platform_data; |
| |
| if (board->enable) |
| board->enable(NULL); |
| } |
| |
| /* Enable Interrupts */ |
| writel(CLCD_IRQ_NEXTBASE_UPDATE, priv.regs + CLCD_PL111_IENB); |
| |
| return 0; |
| } |
| |
| int clcd_disable(struct drm_crtc *crtc) |
| { |
| struct clcd_board *board; |
| struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(crtc); |
| #ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS |
| unsigned long flags; |
| #endif |
| |
| pr_info("DRM %s\n", __func__); |
| |
| /* Disable Interrupts */ |
| writel(0x00000000, priv.regs + CLCD_PL111_IENB); |
| |
| if (priv.amba_dev->dev.platform_data) { |
| board = priv.amba_dev->dev.platform_data; |
| |
| if (board->disable) |
| board->disable(NULL); |
| } |
| |
| /* Disable and Power Down */ |
| writel(0, priv.regs + CLCD_PL111_CNTL); |
| |
| /* Disable clock */ |
| clk_disable_unprepare(priv.clk); |
| |
| pl111_crtc->last_bpp = 0; |
| #ifdef CONFIG_DMA_SHARED_BUFFER_USES_KDS |
| spin_lock_irqsave(&pl111_crtc->current_displaying_lock, flags); |
| /* Release the previous buffers */ |
| if (pl111_crtc->old_kds_res_set != NULL) |
| kds_resource_set_release(&pl111_crtc->old_kds_res_set); |
| |
| pl111_crtc->old_kds_res_set = NULL; |
| spin_unlock_irqrestore(&pl111_crtc->current_displaying_lock, flags); |
| #endif |
| return 0; |
| } |
| |
| /* |
| * To avoid a possible race where "pl111_crtc->current_update_res" has |
| * been updated (non NULL) but the corresponding scanout buffer has not been |
| * written to the base registers we must always call this function holding |
| * the "base_update_lock" spinlock with IRQs disabled (spin_lock_irqsave()). |
| */ |
| void do_flip_to_res(struct pl111_drm_flip_resource *flip_res) |
| { |
| struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc); |
| struct drm_framebuffer *fb; |
| struct pl111_gem_bo *bo; |
| size_t min_size; |
| fb = flip_res->fb; |
| bo = PL111_BO_FROM_FRAMEBUFFER(fb); |
| |
| |
| |
| min_size = (fb->height - 1) * fb->pitches[0] |
| + fb->width * (fb->bits_per_pixel >> 3); |
| |
| BUG_ON(bo->gem_object.size < min_size); |
| |
| /* Don't even attempt PL111_BOT_SHM, it's not contiguous */ |
| BUG_ON(bo->type != PL111_BOT_DMA); |
| |
| /* |
| * Note the buffer for releasing after IRQ, and don't allow any more |
| * updates until then. |
| * |
| * This clcd controller latches the new address on next vsync. Address |
| * latching is indicated by CLCD_IRQ_NEXTBASE_UPDATE, and so we must |
| * wait for that before releasing the previous buffer's kds |
| * resources. Otherwise, we'll allow writers to write to the old buffer |
| * whilst it is still being displayed |
| */ |
| pl111_crtc->current_update_res = flip_res; |
| |
| DRM_DEBUG_KMS("Displaying fb 0x%p, dumb_bo 0x%p, physaddr %.8x\n", |
| fb, bo, bo->backing_data.dma.fb_dev_addr); |
| |
| if (drm_vblank_get(pl111_crtc->crtc.dev, pl111_crtc->crtc_index) < 0) |
| DRM_ERROR("Could not get vblank reference for crtc %d\n", |
| pl111_crtc->crtc_index); |
| |
| /* Set the scanout buffer */ |
| writel(bo->backing_data.dma.fb_dev_addr, priv.regs + CLCD_UBAS); |
| writel(bo->backing_data.dma.fb_dev_addr + |
| ((fb->height - 1) * fb->pitches[0]), priv.regs + CLCD_LBAS); |
| } |
| |
| void |
| show_framebuffer_on_crtc_cb_internal(struct pl111_drm_flip_resource *flip_res, |
| struct drm_framebuffer *fb) |
| { |
| unsigned long irq_flags; |
| struct pl111_drm_crtc *pl111_crtc = to_pl111_crtc(flip_res->crtc); |
| |
| spin_lock_irqsave(&pl111_crtc->base_update_lock, irq_flags); |
| if (list_empty(&pl111_crtc->update_queue) && |
| !pl111_crtc->current_update_res) { |
| do_flip_to_res(flip_res); |
| } else { |
| /* |
| * Enqueue the update to occur on a future IRQ |
| * This only happens on triple-or-greater buffering |
| */ |
| DRM_DEBUG_KMS("Deferring 3+ buffered flip to fb %p to IRQ\n", |
| fb); |
| list_add_tail(&flip_res->link, &pl111_crtc->update_queue); |
| } |
| spin_unlock_irqrestore(&pl111_crtc->base_update_lock, irq_flags); |
| |
| if (!flip_res->page_flip && (pl111_crtc->last_bpp == 0 || |
| pl111_crtc->last_bpp != fb->bits_per_pixel || |
| !drm_mode_equal(pl111_crtc->new_mode, |
| pl111_crtc->current_mode))) { |
| struct clcd_regs timing; |
| |
| pl111_convert_drm_mode_to_timing(pl111_crtc->new_mode, &timing); |
| |
| DRM_DEBUG_KMS("Set timing: %08X:%08X:%08X:%08X clk=%ldHz\n", |
| timing.tim0, timing.tim1, timing.tim2, |
| timing.tim3, timing.pixclock); |
| |
| /* This is the actual mode setting part */ |
| clk_set_rate(priv.clk, timing.pixclock); |
| |
| writel(timing.tim0, priv.regs + CLCD_TIM0); |
| writel(timing.tim1, priv.regs + CLCD_TIM1); |
| writel(timing.tim2, priv.regs + CLCD_TIM2); |
| writel(timing.tim3, priv.regs + CLCD_TIM3); |
| |
| clcd_enable(fb); |
| pl111_crtc->last_bpp = fb->bits_per_pixel; |
| } |
| |
| if (!flip_res->page_flip) { |
| drm_mode_destroy(flip_res->crtc->dev, pl111_crtc->current_mode); |
| pl111_crtc->current_mode = pl111_crtc->new_mode; |
| pl111_crtc->new_mode = NULL; |
| } |
| |
| BUG_ON(!pl111_crtc->current_mode); |
| |
| /* |
| * If IRQs weren't enabled before, they are now. This will eventually |
| * cause flip_res to be released via pl111_common_irq, which updates |
| * every time the Base Address is latched (i.e. every frame, regardless |
| * of whether we update the base address or not) |
| */ |
| } |
| |
| irqreturn_t pl111_irq(int irq, void *data) |
| { |
| u32 irq_stat; |
| struct pl111_drm_crtc *pl111_crtc = priv.pl111_crtc; |
| |
| irq_stat = readl(priv.regs + CLCD_PL111_MIS); |
| |
| if (!irq_stat) |
| return IRQ_NONE; |
| |
| if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) |
| pl111_common_irq(pl111_crtc); |
| |
| /* Clear the interrupt once done */ |
| writel(irq_stat, priv.regs + CLCD_PL111_ICR); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int pl111_device_init(struct drm_device *dev) |
| { |
| struct pl111_drm_dev_private *priv = dev->dev_private; |
| int ret; |
| |
| if (priv == NULL) { |
| pr_err("%s no private data\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (priv->amba_dev == NULL) { |
| pr_err("%s no amba device found\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* set up MMIO for register access */ |
| priv->mmio_start = priv->amba_dev->res.start; |
| priv->mmio_len = resource_size(&priv->amba_dev->res); |
| |
| DRM_DEBUG_KMS("mmio_start=%lu, mmio_len=%u\n", priv->mmio_start, |
| priv->mmio_len); |
| |
| priv->regs = ioremap(priv->mmio_start, priv->mmio_len); |
| if (priv->regs == NULL) { |
| pr_err("%s failed mmio\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* turn off interrupts */ |
| writel(0, priv->regs + CLCD_PL111_IENB); |
| |
| ret = request_irq(priv->amba_dev->irq[0], pl111_irq, 0, |
| "pl111_irq_handler", NULL); |
| if (ret != 0) { |
| pr_err("%s failed %d\n", __func__, ret); |
| goto out_mmio; |
| } |
| |
| goto finish; |
| |
| out_mmio: |
| iounmap(priv->regs); |
| finish: |
| DRM_DEBUG_KMS("pl111_device_init returned %d\n", ret); |
| return ret; |
| } |
| |
| void pl111_device_fini(struct drm_device *dev) |
| { |
| struct pl111_drm_dev_private *priv = dev->dev_private; |
| u32 cntl; |
| |
| if (priv == NULL || priv->regs == NULL) |
| return; |
| |
| free_irq(priv->amba_dev->irq[0], NULL); |
| |
| cntl = readl(priv->regs + CLCD_PL111_CNTL); |
| |
| cntl &= ~CNTL_LCDEN; |
| writel(cntl, priv->regs + CLCD_PL111_CNTL); |
| |
| cntl &= ~CNTL_LCDPWR; |
| writel(cntl, priv->regs + CLCD_PL111_CNTL); |
| |
| iounmap(priv->regs); |
| } |
| |
| int pl111_amba_probe(struct amba_device *dev, const struct amba_id *id) |
| { |
| struct clcd_board *board = dev->dev.platform_data; |
| int ret; |
| pr_info("DRM %s\n", __func__); |
| |
| if (!board) |
| dev_warn(&dev->dev, "board data not available\n"); |
| |
| ret = amba_request_regions(dev, NULL); |
| if (ret != 0) { |
| DRM_ERROR("CLCD: unable to reserve regs region\n"); |
| goto out; |
| } |
| |
| priv.amba_dev = dev; |
| |
| priv.clk = clk_get(&priv.amba_dev->dev, NULL); |
| if (IS_ERR(priv.clk)) { |
| DRM_ERROR("CLCD: unable to get clk.\n"); |
| ret = PTR_ERR(priv.clk); |
| goto clk_err; |
| } |
| |
| return 0; |
| |
| clk_err: |
| amba_release_regions(dev); |
| out: |
| return ret; |
| } |
| |
| int pl111_amba_remove(struct amba_device *dev) |
| { |
| DRM_DEBUG_KMS("DRM %s\n", __func__); |
| |
| clk_put(priv.clk); |
| |
| amba_release_regions(dev); |
| |
| priv.amba_dev = NULL; |
| |
| return 0; |
| } |
| |
| void pl111_convert_drm_mode_to_timing(struct drm_display_mode *mode, |
| struct clcd_regs *timing) |
| { |
| unsigned int ppl, hsw, hfp, hbp; |
| unsigned int lpp, vsw, vfp, vbp; |
| unsigned int cpl; |
| |
| memset(timing, 0, sizeof(struct clcd_regs)); |
| |
| ppl = (mode->hdisplay / 16) - 1; |
| hsw = mode->hsync_end - mode->hsync_start - 1; |
| hfp = mode->hsync_start - mode->hdisplay - 1; |
| hbp = mode->htotal - mode->hsync_end - 1; |
| |
| lpp = mode->vdisplay - 1; |
| vsw = mode->vsync_end - mode->vsync_start - 1; |
| vfp = mode->vsync_start - mode->vdisplay; |
| vbp = mode->vtotal - mode->vsync_end; |
| |
| cpl = mode->hdisplay - 1; |
| |
| timing->tim0 = (ppl << 2) | (hsw << 8) | (hfp << 16) | (hbp << 24); |
| timing->tim1 = lpp | (vsw << 10) | (vfp << 16) | (vbp << 24); |
| timing->tim2 = TIM2_IVS | TIM2_IHS | TIM2_IPC | TIM2_BCD | (cpl << 16); |
| timing->tim3 = 0; |
| |
| timing->pixclock = mode->clock * 1000; |
| } |
| |
| void pl111_convert_timing_to_drm_mode(struct clcd_regs *timing, |
| struct drm_display_mode *mode) |
| { |
| unsigned int ppl, hsw, hfp, hbp; |
| unsigned int lpp, vsw, vfp, vbp; |
| |
| ppl = (timing->tim0 >> 2) & 0x3f; |
| hsw = (timing->tim0 >> 8) & 0xff; |
| hfp = (timing->tim0 >> 16) & 0xff; |
| hbp = (timing->tim0 >> 24) & 0xff; |
| |
| lpp = timing->tim1 & 0x3ff; |
| vsw = (timing->tim1 >> 10) & 0x3f; |
| vfp = (timing->tim1 >> 16) & 0xff; |
| vbp = (timing->tim1 >> 24) & 0xff; |
| |
| mode->hdisplay = (ppl + 1) * 16; |
| mode->hsync_start = ((ppl + 1) * 16) + hfp + 1; |
| mode->hsync_end = ((ppl + 1) * 16) + hfp + hsw + 2; |
| mode->htotal = ((ppl + 1) * 16) + hfp + hsw + hbp + 3; |
| mode->hskew = 0; |
| |
| mode->vdisplay = lpp + 1; |
| mode->vsync_start = lpp + vfp + 1; |
| mode->vsync_end = lpp + vfp + vsw + 2; |
| mode->vtotal = lpp + vfp + vsw + vbp + 2; |
| |
| mode->flags = 0; |
| |
| mode->width_mm = 0; |
| mode->height_mm = 0; |
| |
| mode->clock = timing->pixclock / 1000; |
| mode->hsync = timing->pixclock / mode->htotal; |
| mode->vrefresh = mode->hsync / mode->vtotal; |
| } |