diff options
Diffstat (limited to 'drivers/gpu/drm/arm/hdlcd_crtc.c')
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_crtc.c | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c new file mode 100644 index 000000000000..6c0e0a5fe447 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_helper.h> +#include "hdlcd_fb_helper.h" +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/of_graph.h> +#include <linux/platform_data/simplefb.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +/* + * The HDLCD controller is a dumb RGB streamer that gets connected to + * a single HDMI transmitter or in the case of the ARM Models it gets + * emulated by the software that does the actual rendering. + * + */ +static void hdlcd_crtc_destroy(struct drm_crtc *crtc) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + of_node_put(hdlcd->crtc.port); + drm_crtc_cleanup(crtc); +} + +void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd, bool wait) +{ + struct drm_framebuffer *fb = hdlcd->crtc.primary->fb; + struct drm_gem_cma_object *gem; + unsigned int depth, bpp; + dma_addr_t scanout_start; + + drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); + gem = hdlcd_fb_get_gem_obj(fb, 0); + + scanout_start = gem->paddr + fb->offsets[0] + + (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + if (wait && hdlcd->dpms == DRM_MODE_DPMS_ON) { + drm_vblank_get(fb->dev, 0); + /* Clear any interrupt that may be from before we changed scanout */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + /* Wait for next interrupt so we know scanout change is live */ + reinit_completion(&hdlcd->frame_completion); + wait_for_completion_interruptible(&hdlcd->frame_completion); + + drm_vblank_put(fb->dev, 0); + } +} + +static int hdlcd_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t flags) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + /* don't schedule any page flipping if one is in progress */ + if (hdlcd->event) + return -EBUSY; + + hdlcd->event = event; + drm_vblank_get(crtc->dev, 0); + } + + crtc->primary->fb = fb; + + if (hdlcd->dpms == DRM_MODE_DPMS_ON) { + hdlcd_set_scanout(hdlcd, true); + } else { + unsigned long flags; + + /* not active, update registers immediately */ + hdlcd_set_scanout(hdlcd, false); + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (event) + drm_send_vblank_event(crtc->dev, 0, event); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } + + return 0; +} + +static const struct drm_crtc_funcs hdlcd_crtc_funcs = { + .destroy = hdlcd_crtc_destroy, + .set_config = drm_crtc_helper_set_config, + .page_flip = hdlcd_crtc_page_flip, +}; + +static void hdlcd_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + + hdlcd->dpms = mode; + if (mode == DRM_MODE_DPMS_ON) + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 1); + else + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); +} + +static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void hdlcd_crtc_prepare(struct drm_crtc *crtc) +{ + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void hdlcd_crtc_commit(struct drm_crtc *crtc) +{ + drm_vblank_post_modeset(crtc->dev, 0); + hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static struct simplefb_format supported_formats[] = SIMPLEFB_FORMATS; + +static int hdlcd_crtc_colour_set(struct hdlcd_drm_private *hdlcd, + uint32_t pixel_format) +{ + unsigned int depth, bpp; + unsigned int default_color = 0x00000000; + bool swap_red_blue = false; + u32 hbi; + struct simplefb_format *format = NULL; + int i; + +#ifdef HDLCD_SHOW_UNDERRUN + default_color = 0x00ff0000; /* show underruns in red */ +#endif + + /* Calculate each colour's number of bits */ + drm_fb_get_bpp_depth(pixel_format, &depth, &bpp); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (!format) { + DRM_ERROR("Format not supported: 0x%x\n", pixel_format); + return -EINVAL; + } + + /* HDLCD uses 'bytes per pixel' */ + bpp = (bpp + 7) / 8; + hdlcd_write(hdlcd, HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3); + + /* + * The format of the HDLCD_REG_<color>_SELECT register is: + * - bits[23:16] - default value for that color component + * - bits[11:8] - number of bits to extract for each color component + * - bits[4:0] - index of the lowest bit to extract + * + * The default color value is used when bits[11:8] read zero, when the + * pixel is outside the visible frame area or when there is a + * buffer underrun. + */ + if (of_property_read_u32(of_root, "arm,hbi", &hbi) == 0) { + /* + * This is a hack to swap read and blue when building for some + * Versatile Express CoreTiles because they seem to be wired up + * differently. + */ + if (hbi == 0x249) /* TC2 */ + swap_red_blue = true; + } + if(!swap_red_blue) { + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + format->red.offset | (format->red.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + format->green.offset | (format->green.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + format->blue.offset | (format->blue.length & 0xf) << 8); + } else { + /* + * This is a hack to swap read and blue when building for + * 32-bit ARM, because Versatile Express motherboard seems + * to be wired up differently. + */ + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, default_color | + format->red.offset | (format->red.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, default_color | + format->green.offset | (format->green.length & 0xf) << 8); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, default_color | + format->blue.offset | (format->blue.length & 0xf) << 8); + } + return 0; +} + +static int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + hdlcd_set_scanout(hdlcd, true); + return 0; +} + + +static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + unsigned int depth, bpp, polarities, line_length, err; + + drm_vblank_pre_modeset(crtc->dev, 0); + + polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_DATA; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + polarities |= HDLCD_POLARITY_HSYNC; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + polarities |= HDLCD_POLARITY_VSYNC; + + drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp); + /* switch to the more useful 'bytes per pixel' HDLCD needs */ + bpp = (bpp + 7) / 8; + line_length = crtc->primary->fb->width * bpp; + + /* Allow max number of outstanding requests and largest burst size */ + hdlcd_write(hdlcd, HDLCD_REG_BUS_OPTIONS, + HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_LENGTH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_COUNT, + crtc->primary->fb->height - 1); + hdlcd_write(hdlcd, HDLCD_REG_FB_LINE_PITCH, line_length); + hdlcd_write(hdlcd, HDLCD_REG_V_BACK_PORCH, + mode->vtotal - mode->vsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_FRONT_PORCH, + mode->vsync_start - mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_SYNC, + mode->vsync_end - mode->vsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_V_DATA, mode->vdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_BACK_PORCH, + mode->htotal - mode->hsync_end - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_FRONT_PORCH, + mode->hsync_start - mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_SYNC, + mode->hsync_end - mode->hsync_start - 1); + hdlcd_write(hdlcd, HDLCD_REG_H_DATA, mode->hdisplay - 1); + hdlcd_write(hdlcd, HDLCD_REG_POLARITIES, polarities); + + err = hdlcd_crtc_colour_set(hdlcd, crtc->primary->fb->pixel_format); + if (err) + return err; + + clk_prepare(hdlcd->clk); + clk_set_rate(hdlcd->clk, mode->crtc_clock * 1000); + clk_enable(hdlcd->clk); + + hdlcd_set_scanout(hdlcd, false); + + return 0; +} + +static void hdlcd_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = { + .dpms = hdlcd_crtc_dpms, + .mode_fixup = hdlcd_crtc_mode_fixup, + .prepare = hdlcd_crtc_prepare, + .commit = hdlcd_crtc_commit, + .mode_set = hdlcd_crtc_mode_set, + .mode_set_base = hdlcd_crtc_mode_set_base, + .load_lut = hdlcd_crtc_load_lut, +}; + +int hdlcd_setup_crtc(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + int ret; + + hdlcd->crtc.port = of_graph_get_next_endpoint(dev->platformdev->dev.of_node, NULL); + if (!hdlcd->crtc.port) + return -ENXIO; + + ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs); + if (ret < 0) { + of_node_put(hdlcd->crtc.port); + return ret; + } + + drm_crtc_helper_add(&hdlcd->crtc, &hdlcd_crtc_helper_funcs); + return 0; +} + |