diff options
Diffstat (limited to 'drivers/gpu/drm/pl111/pl111_drm_cursor.c')
-rwxr-xr-x | drivers/gpu/drm/pl111/pl111_drm_cursor.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/drivers/gpu/drm/pl111/pl111_drm_cursor.c b/drivers/gpu/drm/pl111/pl111_drm_cursor.c new file mode 100755 index 000000000000..4bf20fec2464 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm_cursor.c @@ -0,0 +1,331 @@ +/* + * + * (C) COPYRIGHT 2012-2013 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_cursor.c + * Implementation of cursor 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_clcd_ext.h" +#include "pl111_drm.h" + +#define PL111_MAX_CURSOR_WIDTH (64) +#define PL111_MAX_CURSOR_HEIGHT (64) + +#define ARGB_2_LBBP_BINARY_THRESHOLD (1 << 7) +#define ARGB_ALPHA_SHIFT 24 +#define ARGB_ALPHA_MASK (0xff << ARGB_ALPHA_SHIFT) +#define ARGB_RED_SHIFT 16 +#define ARGB_RED_MASK (0xff << ARGB_RED_SHIFT) +#define ARGB_GREEN_SHIFT 8 +#define ARGB_GREEN_MASK (0xff << ARGB_GREEN_SHIFT) +#define ARGB_BLUE_SHIFT 0 +#define ARGB_BLUE_MASK (0xff << ARGB_BLUE_SHIFT) + + +void pl111_set_cursor_size(enum pl111_cursor_size size) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG); + + if (size == CURSOR_64X64) + reg_data |= CRSR_CONFIG_CRSR_SIZE; + else + reg_data &= ~CRSR_CONFIG_CRSR_SIZE; + + writel(reg_data, priv.regs + CLCD_CRSR_CONFIG); +} + +void pl111_set_cursor_sync(enum pl111_cursor_sync sync) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CONFIG); + + if (sync == CURSOR_SYNC_VSYNC) + reg_data |= CRSR_CONFIG_CRSR_FRAME_SYNC; + else + reg_data &= ~CRSR_CONFIG_CRSR_FRAME_SYNC; + + writel(reg_data, priv.regs + CLCD_CRSR_CONFIG); +} + +void pl111_set_cursor(u32 cursor) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL); + + reg_data &= ~(CRSR_CTRL_CRSR_MAX << CRSR_CTRL_CRSR_NUM_SHIFT); + reg_data |= (cursor & CRSR_CTRL_CRSR_MAX) << CRSR_CTRL_CRSR_NUM_SHIFT; + + writel(reg_data, priv.regs + CLCD_CRSR_CTRL); +} + +void pl111_set_cursor_enable(bool enable) +{ + u32 reg_data = readl(priv.regs + CLCD_CRSR_CTRL); + + if (enable) + reg_data |= CRSR_CTRL_CRSR_ON; + else + reg_data &= ~CRSR_CTRL_CRSR_ON; + + writel(reg_data, priv.regs + CLCD_CRSR_CTRL); +} + +void pl111_set_cursor_position(u32 x, u32 y) +{ + u32 reg_data = (x & CRSR_XY_MASK) | + ((y & CRSR_XY_MASK) << CRSR_XY_Y_SHIFT); + + writel(reg_data, priv.regs + CLCD_CRSR_XY); +} + +void pl111_set_cursor_clipping(u32 x, u32 y) +{ + u32 reg_data; + + /* + * Do not allow setting clipping values larger than + * the cursor size since the cursor is already fully hidden + * when x,y = PL111_MAX_CURSOR_WIDTH. + */ + if (x > PL111_MAX_CURSOR_WIDTH) + x = PL111_MAX_CURSOR_WIDTH; + if (y > PL111_MAX_CURSOR_WIDTH) + y = PL111_MAX_CURSOR_WIDTH; + + reg_data = (x & CRSR_CLIP_MASK) | + ((y & CRSR_CLIP_MASK) << CRSR_CLIP_Y_SHIFT); + + writel(reg_data, priv.regs + CLCD_CRSR_CLIP); +} + +void pl111_set_cursor_palette(u32 color0, u32 color1) +{ + writel(color0 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_0); + writel(color1 & CRSR_PALETTE_MASK, priv.regs + CLCD_CRSR_PALETTE_1); +} + +void pl111_cursor_enable(void) +{ + pl111_set_cursor_sync(CURSOR_SYNC_VSYNC); + pl111_set_cursor_size(CURSOR_64X64); + pl111_set_cursor_palette(0x0, 0x00ffffff); + pl111_set_cursor_enable(true); +} + +void pl111_cursor_disable(void) +{ + pl111_set_cursor_enable(false); +} + +/* shift required to locate pixel into the correct position in + * a cursor LBBP word, indexed by x mod 16. + */ +static const unsigned char +x_mod_16_to_value_shift[CLCD_CRSR_IMAGE_PIXELS_PER_WORD] = { + 6, 4, 2, 0, 14, 12, 10, 8, 22, 20, 18, 16, 30, 28, 26, 24 +}; + +/* Pack the pixel value into its correct position in the buffer as specified + * for LBBP */ +static inline void +set_lbbp_pixel(uint32_t *buffer, unsigned int x, unsigned int y, + uint32_t value) +{ + u32 *cursor_ram = priv.regs + CLCD_CRSR_IMAGE; + uint32_t shift; + uint32_t data; + + shift = x_mod_16_to_value_shift[x % CLCD_CRSR_IMAGE_PIXELS_PER_WORD]; + + /* Get the word containing this pixel */ + cursor_ram = cursor_ram + (x >> CLCD_CRSR_IMAGE_WORDS_PER_LINE) + (y << 2); + + /* Update pixel in cursor RAM */ + data = readl(cursor_ram); + data &= ~(CLCD_CRSR_LBBP_COLOR_MASK << shift); + data |= value << shift; + writel(data, cursor_ram); +} + +static u32 pl111_argb_to_lbbp(u32 argb_pix) +{ + u32 lbbp_pix = CLCD_CRSR_LBBP_TRANSPARENT; + u32 alpha = (argb_pix & ARGB_ALPHA_MASK) >> ARGB_ALPHA_SHIFT; + u32 red = (argb_pix & ARGB_RED_MASK) >> ARGB_RED_SHIFT; + u32 green = (argb_pix & ARGB_GREEN_MASK) >> ARGB_GREEN_SHIFT; + u32 blue = (argb_pix & ARGB_BLUE_MASK) >> ARGB_BLUE_SHIFT; + + /* + * Converting from 8 pixel transparency to binary transparency + * it's the best we can achieve. + */ + if (alpha & ARGB_2_LBBP_BINARY_THRESHOLD) { + u32 gray, max, min; + + /* + * Convert to gray using the lightness method: + * gray = [max(R,G,B) + min(R,G,B)]/2 + */ + min = min(red, green); + min = min(min, blue); + max = max(red, green); + max = max(max, blue); + gray = (min + max) >> 1; /* divide by 2 */ + /* Apply binary threshold to the gray value calculated */ + if (gray & ARGB_2_LBBP_BINARY_THRESHOLD) + lbbp_pix = CLCD_CRSR_LBBP_FOREGROUND; + else + lbbp_pix = CLCD_CRSR_LBBP_BACKGROUND; + } + + return lbbp_pix; +} + +/* + * The PL111 hardware cursor supports only LBBP which is a 2bpp format but + * the cursor format from userspace is ARGB8888 so we need to convert + * to LBBP here. + */ +static void pl111_set_cursor_image(u32 *data) +{ +#ifdef ARGB_LBBP_CONVERSION_DEBUG + /* Add 1 on width to insert trailing NULL */ + char string_cursor[PL111_MAX_CURSOR_WIDTH + 1]; +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + unsigned int x; + unsigned int y; + + for (y = 0; y < PL111_MAX_CURSOR_HEIGHT; y++) { + for (x = 0; x < PL111_MAX_CURSOR_WIDTH; x++) { + u32 value = pl111_argb_to_lbbp(*data); + +#ifdef ARGB_LBBP_CONVERSION_DEBUG + if (value == CLCD_CRSR_LBBP_TRANSPARENT) + string_cursor[x] = 'T'; + else if (value == CLCD_CRSR_LBBP_FOREGROUND) + string_cursor[x] = 'F'; + else if (value == CLCD_CRSR_LBBP_INVERSE) + string_cursor[x] = 'I'; + else + string_cursor[x] = 'B'; + +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + set_lbbp_pixel(data, x, y, value); + ++data; + } +#ifdef ARGB_LBBP_CONVERSION_DEBUG + string_cursor[PL111_MAX_CURSOR_WIDTH] = '\0'; + DRM_INFO("%s\n", string_cursor); +#endif /* ARGB_LBBP_CONVERSION_DEBUG */ + } +} + +int pl111_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file_priv, + uint32_t handle, + uint32_t width, + uint32_t height) +{ + struct drm_gem_object *obj; + struct pl111_gem_bo *bo; + + DRM_DEBUG_KMS("handle = %u, width = %u, height = %u\n", + handle, width, height); + + if (!handle) { + pl111_cursor_disable(); + return 0; + } + + if ((width != PL111_MAX_CURSOR_WIDTH) || + (height != PL111_MAX_CURSOR_HEIGHT)) + return -EINVAL; + + obj = drm_gem_object_lookup(crtc->dev, file_priv, handle); + if (!obj) { + DRM_ERROR("Cannot find cursor object for handle = %d\n", + handle); + return -ENOENT; + } + + /* + * We expect a PL111_MAX_CURSOR_WIDTH x PL111_MAX_CURSOR_HEIGHT + * ARGB888 buffer object in the input. + * + */ + if (obj->size < (PL111_MAX_CURSOR_WIDTH * PL111_MAX_CURSOR_HEIGHT * 4)) { + DRM_ERROR("Cannot set cursor with an obj size = %d\n", + obj->size); + drm_gem_object_unreference_unlocked(obj); + return -EINVAL; + } + + bo = PL111_BO_FROM_GEM(obj); + if (!(bo->type & PL111_BOT_DMA)) { + DRM_ERROR("Tried to set cursor with non DMA backed obj = %p\n", + obj); + drm_gem_object_unreference_unlocked(obj); + return -EINVAL; + } + + pl111_set_cursor_image(bo->backing_data.dma.fb_cpu_addr); + + /* + * Since we copy the contents of the buffer to the HW cursor internal + * memory this GEM object is not needed anymore. + */ + drm_gem_object_unreference_unlocked(obj); + + pl111_cursor_enable(); + + return 0; +} + +int pl111_crtc_cursor_move(struct drm_crtc *crtc, + int x, int y) +{ + int x_clip = 0; + int y_clip = 0; + + DRM_DEBUG("x %d y %d\n", x, y); + + /* + * The cursor image is clipped automatically at the screen limits when + * it extends beyond the screen image to the right or bottom but + * we must clip it using pl111 HW features for negative values. + */ + if (x < 0) { + x_clip = -x; + x = 0; + } + if (y < 0) { + y_clip = -y; + y = 0; + } + + pl111_set_cursor_clipping(x_clip, y_clip); + pl111_set_cursor_position(x, y); + + return 0; +} |