diff --git a/drivers/gpu/drm/udl/Makefile b/drivers/gpu/drm/udl/Makefile
index 3f6db179455d..0abd04b0e829 100644
--- a/drivers/gpu/drm/udl/Makefile
+++ b/drivers/gpu/drm/udl/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
-udl-y := udl_drv.o udl_modeset.o udl_main.o udl_transfer.o
+udl-y := udl_drv.o udl_modeset.o udl_main.o udl_transfer.o udl_cursor.o
 
 obj-$(CONFIG_DRM_UDL) := udl.o
diff --git a/drivers/gpu/drm/udl/udl_cursor.c b/drivers/gpu/drm/udl/udl_cursor.c
new file mode 100644
index 000000000000..594bb3b6b056
--- /dev/null
+++ b/drivers/gpu/drm/udl/udl_cursor.c
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * udl_cursor.c
+ *
+ * Copyright (c) 2015 The Chromium OS Authors
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/iosys-map.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "udl_cursor.h"
+#include "udl_drv.h"
+
+void udl_cursor_get_hline(struct udl_cursor *cursor, int x, int y,
+		struct udl_cursor_hline *hline)
+{
+	if (!cursor || !cursor->enabled ||
+		x >= cursor->x + UDL_CURSOR_W ||
+		y < cursor->y || y >= cursor->y + UDL_CURSOR_H) {
+		hline->buffer = NULL;
+		return;
+	}
+
+	hline->buffer = &cursor->buffer[UDL_CURSOR_W * (y - cursor->y)];
+	hline->width = UDL_CURSOR_W;
+	hline->offset = x - cursor->x;
+}
+
+/*
+ * Return pre-computed cursor blend value defined as:
+ * R: 5 bits (bit 0:4)
+ * G: 6 bits (bit 5:10)
+ * B: 5 bits (bit 11:15)
+ * A: 7 bits (bit 16:22)
+ */
+static uint32_t cursor_blend_val32(uint32_t pix)
+{
+	/* range of alpha_scaled is 0..64 */
+	uint32_t alpha_scaled = ((pix >> 24) * 65) >> 8;
+
+	return ((pix >> 3) & 0x1f) |
+		((pix >> 5) & 0x7e0) |
+		((pix >> 8) & 0xf800) |
+		(alpha_scaled << 16);
+}
+
+int udl_cursor_download(struct udl_cursor *cursor,
+		const struct iosys_map *map)
+{
+	uint32_t *src_ptr, *dst_ptr;
+	size_t i;
+
+	src_ptr = map->vaddr;
+	dst_ptr = cursor->buffer;
+	for (i = 0; i < UDL_CURSOR_BUF; ++i)
+		dst_ptr[i] = cursor_blend_val32(le32_to_cpu(src_ptr[i]));
+	return 0;
+}
+
+
+int udl_cursor_move(struct udl_cursor *cursor, int x, int y)
+{
+	cursor->x = x;
+	cursor->y = y;
+	return 0;
+}
diff --git a/drivers/gpu/drm/udl/udl_cursor.h b/drivers/gpu/drm/udl/udl_cursor.h
new file mode 100644
index 000000000000..6a848accc106
--- /dev/null
+++ b/drivers/gpu/drm/udl/udl_cursor.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * udl_cursor.h
+ *
+ * Copyright (c) 2015 The Chromium OS Authors
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UDL_CURSOR_H_
+#define _UDL_CURSOR_H_
+
+#include <linux/module.h>
+#include <drm/drm_crtc.h>
+
+#define UDL_CURSOR_W 64
+#define UDL_CURSOR_H 64
+#define UDL_CURSOR_BUF (UDL_CURSOR_W * UDL_CURSOR_H)
+struct udl_cursor {
+	uint32_t buffer[UDL_CURSOR_BUF];
+	bool enabled;
+	int x;
+	int y;
+};
+struct udl_cursor_hline {
+	uint32_t *buffer;
+	int width;
+	int offset;
+};
+
+extern void udl_cursor_get_hline(struct udl_cursor *cursor, int x, int y,
+		struct udl_cursor_hline *hline);
+extern int udl_cursor_move(struct udl_cursor *cursor, int x, int y);
+extern int udl_cursor_download(struct udl_cursor *cursor, const struct iosys_map *map);
+
+#endif
diff --git a/drivers/gpu/drm/udl/udl_drv.h b/drivers/gpu/drm/udl/udl_drv.h
index 282ebd6c02fd..ccd813bec1a9 100644
--- a/drivers/gpu/drm/udl/udl_drv.h
+++ b/drivers/gpu/drm/udl/udl_drv.h
@@ -21,6 +21,7 @@
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_plane.h>
+#include "udl_cursor.h"
 
 struct drm_mode_create_dumb;
 
@@ -60,6 +61,7 @@ static inline struct udl_connector *to_udl_connector(struct drm_connector *conne
 	return container_of(connector, struct udl_connector, connector);
 }
 
+struct udl_cursor_hline;
 struct udl_device {
 	struct drm_device drm;
 	struct device *dev;
@@ -74,6 +76,7 @@ struct udl_device {
 	int sku_pixel_limit;
 
 	struct urb_list urbs;
+	struct udl_cursor cursor;
 };
 
 #define to_udl(x) container_of(x, struct udl_device, drm)
@@ -97,7 +100,8 @@ int udl_init(struct udl_device *udl);
 
 int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr,
 		     const char *front, char **urb_buf_ptr,
-		     u32 byte_offset, u32 device_byte_offset, u32 byte_width);
+		     u32 byte_offset, u32 device_byte_offset, u32 byte_width,
+		     struct udl_cursor_hline *cursor_hline);
 
 int udl_drop_usb(struct drm_device *dev);
 int udl_select_std_channel(struct udl_device *udl);
diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c
index 7702359c90c2..21594144fec5 100644
--- a/drivers/gpu/drm/udl/udl_modeset.c
+++ b/drivers/gpu/drm/udl/udl_modeset.c
@@ -26,6 +26,9 @@
 
 #include "udl_drv.h"
 #include "udl_proto.h"
+#include "udl_cursor.h"
+
+#define UDL_COLOR_DEPTH_16BPP	0
 
 /*
  * All DisplayLink bulk operations start with 0xaf (UDL_MSG_BULK), followed by
@@ -204,6 +207,7 @@ static int udl_handle_damage(struct drm_framebuffer *fb,
 			     const struct drm_rect *clip)
 {
 	struct drm_device *dev = fb->dev;
+	struct udl_device *udl = to_udl(dev);
 	void *vaddr = map->vaddr; /* TODO: Use mapping abstraction properly */
 	int i, ret;
 	char *cmd;
@@ -225,9 +229,12 @@ static int udl_handle_damage(struct drm_framebuffer *fb,
 		const int byte_offset = line_offset + (clip->x1 << log_bpp);
 		const int dev_byte_offset = (fb->width * i + clip->x1) << log_bpp;
 		const int byte_width = drm_rect_width(clip) << log_bpp;
+		struct udl_cursor_hline cursor_hline;
+
+		udl_cursor_get_hline(&udl->cursor, clip->x1, i, &cursor_hline);
 		ret = udl_render_hline(dev, log_bpp, &urb, (char *)vaddr,
 				       &cmd, byte_offset, dev_byte_offset,
-				       byte_width);
+				       byte_width, &cursor_hline);
 		if (ret)
 			return ret;
 	}
diff --git a/drivers/gpu/drm/udl/udl_transfer.c b/drivers/gpu/drm/udl/udl_transfer.c
index 5ff1037a3453..ba3a3ae08943 100644
--- a/drivers/gpu/drm/udl/udl_transfer.c
+++ b/drivers/gpu/drm/udl/udl_transfer.c
@@ -11,6 +11,7 @@
 
 #include "udl_drv.h"
 #include "udl_proto.h"
+#include "udl_cursor.h"
 
 #define MAX_CMD_PIXELS		255
 
@@ -43,6 +44,19 @@ static inline u16 get_pixel_val16(const uint8_t *pixel, int log_bpp)
 	return pixel_val16;
 }
 
+static inline u16 blend_alpha(const uint16_t pixel_val16, uint32_t blend_val32)
+{
+	uint32_t alpha = (blend_val32 >> 16);
+	uint32_t alpha_inv = 64 - alpha;
+
+	return (((pixel_val16 & 0x1f) * alpha_inv +
+		(blend_val32 & 0x1f) * alpha) >> 6) |
+		((((pixel_val16 & 0x7e0) * alpha_inv +
+		(blend_val32 & 0x7e0) * alpha) >> 6) & 0x7e0) |
+		((((pixel_val16 & 0xf800) * alpha_inv +
+		(blend_val32 & 0xf800) * alpha) >> 6) & 0xf800);
+}
+
 /*
  * Render a command stream for an encoded horizontal line segment of pixels.
  *
@@ -74,6 +88,7 @@ static void udl_compress_hline16(
 	const u8 **pixel_start_ptr,
 	const u8 *const pixel_end,
 	uint32_t *device_address_ptr,
+	struct udl_cursor_hline *cursor_hline,
 	uint8_t **command_buffer_ptr,
 	const uint8_t *const cmd_buffer_end, int log_bpp)
 {
@@ -81,6 +96,9 @@ static void udl_compress_hline16(
 	const u8 *pixel = *pixel_start_ptr;
 	uint32_t dev_addr  = *device_address_ptr;
 	uint8_t *cmd = *command_buffer_ptr;
+	const uint32_t *cursor_buf = cursor_hline ? cursor_hline->buffer : NULL;
+	int cursor_pos = cursor_buf ? cursor_hline->offset : 0;
+	int cursor_width = cursor_buf ? cursor_hline->width : 0;
 
 	while ((pixel_end > pixel) &&
 	       (cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) {
@@ -107,6 +125,11 @@ static void udl_compress_hline16(
 					(unsigned long)(cmd_buffer_end - 1 - cmd) / 2) << log_bpp);
 
 		pixel_val16 = get_pixel_val16(pixel, log_bpp);
+		if (cursor_buf && cursor_pos >= 0 &&
+			cursor_pos < cursor_width) {
+			pixel_val16 = blend_alpha(pixel_val16,
+				cursor_buf[cursor_pos]);
+		}
 
 		while (pixel < cmd_pixel_end) {
 			const u8 *const start = pixel;
@@ -116,12 +139,19 @@ static void udl_compress_hline16(
 
 			cmd += 2;
 			pixel += bpp;
+			cursor_pos++;
 
 			while (pixel < cmd_pixel_end) {
 				pixel_val16 = get_pixel_val16(pixel, log_bpp);
+				if (cursor_buf && cursor_pos >= 0 &&
+					cursor_pos < cursor_width) {
+					pixel_val16 = blend_alpha(pixel_val16,
+						cursor_buf[cursor_pos]);
+				}
 				if (pixel_val16 != repeating_pixel_val16)
 					break;
 				pixel += bpp;
+				cursor_pos++;
 			}
 
 			if (unlikely(pixel > start + bpp)) {
@@ -160,6 +190,8 @@ static void udl_compress_hline16(
 	*command_buffer_ptr = cmd;
 	*pixel_start_ptr = pixel;
 	*device_address_ptr = dev_addr;
+	if (cursor_buf)
+		cursor_hline->offset = cursor_pos;
 
 	return;
 }
@@ -173,7 +205,7 @@ static void udl_compress_hline16(
 int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr,
 		     const char *front, char **urb_buf_ptr,
 		     u32 byte_offset, u32 device_byte_offset,
-		     u32 byte_width)
+		     u32 byte_width, struct udl_cursor_hline *cursor_hline)
 {
 	const u8 *line_start, *line_end, *next_pixel;
 	u32 base16 = 0 + (device_byte_offset >> log_bpp) * 2;
@@ -194,7 +226,7 @@ int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr,
 	while (next_pixel < line_end) {
 
 		udl_compress_hline16(&next_pixel,
-			     line_end, &base16,
+			     line_end, &base16, cursor_hline,
 			     (u8 **) &cmd, (u8 *) cmd_end, log_bpp);
 
 		if (cmd >= cmd_end) {
