blob: 1d613d0592a3310698ecf39b0cde145a632a51d0 [file] [log] [blame]
/*
*
* (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;
}