summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/udl/udl_transfer.c
blob: 5ff1037a34536d0de6380a2b31c3d5549312dbc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2012 Red Hat
 * based in parts on udlfb.c:
 * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
 * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
 * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
 */

#include <asm/unaligned.h>

#include "udl_drv.h"
#include "udl_proto.h"

#define MAX_CMD_PIXELS		255

#define RLX_HEADER_BYTES	7
#define MIN_RLX_PIX_BYTES       4
#define MIN_RLX_CMD_BYTES	(RLX_HEADER_BYTES + MIN_RLX_PIX_BYTES)

#define RLE_HEADER_BYTES	6
#define MIN_RLE_PIX_BYTES	3
#define MIN_RLE_CMD_BYTES	(RLE_HEADER_BYTES + MIN_RLE_PIX_BYTES)

#define RAW_HEADER_BYTES	6
#define MIN_RAW_PIX_BYTES	2
#define MIN_RAW_CMD_BYTES	(RAW_HEADER_BYTES + MIN_RAW_PIX_BYTES)

static inline u16 pixel32_to_be16(const uint32_t pixel)
{
	return (((pixel >> 3) & 0x001f) |
		((pixel >> 5) & 0x07e0) |
		((pixel >> 8) & 0xf800));
}

static inline u16 get_pixel_val16(const uint8_t *pixel, int log_bpp)
{
	u16 pixel_val16;
	if (log_bpp == 1)
		pixel_val16 = *(const uint16_t *)pixel;
	else
		pixel_val16 = pixel32_to_be16(*(const uint32_t *)pixel);
	return pixel_val16;
}

/*
 * Render a command stream for an encoded horizontal line segment of pixels.
 *
 * A command buffer holds several commands.
 * It always begins with a fresh command header
 * (the protocol doesn't require this, but we enforce it to allow
 * multiple buffers to be potentially encoded and sent in parallel).
 * A single command encodes one contiguous horizontal line of pixels
 *
 * The function relies on the client to do all allocation, so that
 * rendering can be done directly to output buffers (e.g. USB URBs).
 * The function fills the supplied command buffer, providing information
 * on where it left off, so the client may call in again with additional
 * buffers if the line will take several buffers to complete.
 *
 * A single command can transmit a maximum of 256 pixels,
 * regardless of the compression ratio (protocol design limit).
 * To the hardware, 0 for a size byte means 256
 *
 * Rather than 256 pixel commands which are either rl or raw encoded,
 * the rlx command simply assumes alternating raw and rl spans within one cmd.
 * This has a slightly larger header overhead, but produces more even results.
 * It also processes all data (read and write) in a single pass.
 * Performance benchmarks of common cases show it having just slightly better
 * compression than 256 pixel raw or rle commands, with similar CPU consumpion.
 * But for very rl friendly data, will compress not quite as well.
 */
static void udl_compress_hline16(
	const u8 **pixel_start_ptr,
	const u8 *const pixel_end,
	uint32_t *device_address_ptr,
	uint8_t **command_buffer_ptr,
	const uint8_t *const cmd_buffer_end, int log_bpp)
{
	const int bpp = 1 << log_bpp;
	const u8 *pixel = *pixel_start_ptr;
	uint32_t dev_addr  = *device_address_ptr;
	uint8_t *cmd = *command_buffer_ptr;

	while ((pixel_end > pixel) &&
	       (cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) {
		uint8_t *raw_pixels_count_byte = NULL;
		uint8_t *cmd_pixels_count_byte = NULL;
		const u8 *raw_pixel_start = NULL;
		const u8 *cmd_pixel_start, *cmd_pixel_end = NULL;
		uint16_t pixel_val16;

		*cmd++ = UDL_MSG_BULK;
		*cmd++ = UDL_CMD_WRITERLX16;
		*cmd++ = (uint8_t) ((dev_addr >> 16) & 0xFF);
		*cmd++ = (uint8_t) ((dev_addr >> 8) & 0xFF);
		*cmd++ = (uint8_t) ((dev_addr) & 0xFF);

		cmd_pixels_count_byte = cmd++; /*  we'll know this later */
		cmd_pixel_start = pixel;

		raw_pixels_count_byte = cmd++; /*  we'll know this later */
		raw_pixel_start = pixel;

		cmd_pixel_end = pixel + (min3(MAX_CMD_PIXELS + 1UL,
					(unsigned long)(pixel_end - pixel) >> log_bpp,
					(unsigned long)(cmd_buffer_end - 1 - cmd) / 2) << log_bpp);

		pixel_val16 = get_pixel_val16(pixel, log_bpp);

		while (pixel < cmd_pixel_end) {
			const u8 *const start = pixel;
			const uint16_t repeating_pixel_val16 = pixel_val16;

			put_unaligned_be16(pixel_val16, cmd);

			cmd += 2;
			pixel += bpp;

			while (pixel < cmd_pixel_end) {
				pixel_val16 = get_pixel_val16(pixel, log_bpp);
				if (pixel_val16 != repeating_pixel_val16)
					break;
				pixel += bpp;
			}

			if (unlikely(pixel > start + bpp)) {
				/* go back and fill in raw pixel count */
				*raw_pixels_count_byte = (((start -
						raw_pixel_start) >> log_bpp) + 1) & 0xFF;

				/* immediately after raw data is repeat byte */
				*cmd++ = (((pixel - start) >> log_bpp) - 1) & 0xFF;

				/* Then start another raw pixel span */
				raw_pixel_start = pixel;
				raw_pixels_count_byte = cmd++;
			}
		}

		if (pixel > raw_pixel_start) {
			/* finalize last RAW span */
			*raw_pixels_count_byte = ((pixel - raw_pixel_start) >> log_bpp) & 0xFF;
		} else {
			/* undo unused byte */
			cmd--;
		}

		*cmd_pixels_count_byte = ((pixel - cmd_pixel_start) >> log_bpp) & 0xFF;
		dev_addr += ((pixel - cmd_pixel_start) >> log_bpp) * 2;
	}

	if (cmd_buffer_end <= MIN_RLX_CMD_BYTES + cmd) {
		/* Fill leftover bytes with no-ops */
		if (cmd_buffer_end > cmd)
			memset(cmd, UDL_MSG_BULK, cmd_buffer_end - cmd);
		cmd = (uint8_t *) cmd_buffer_end;
	}

	*command_buffer_ptr = cmd;
	*pixel_start_ptr = pixel;
	*device_address_ptr = dev_addr;

	return;
}

/*
 * There are 3 copies of every pixel: The front buffer that the fbdev
 * client renders to, the actual framebuffer across the USB bus in hardware
 * (that we can only write to, slowly, and can never read), and (optionally)
 * our shadow copy that tracks what's been sent to that hardware buffer.
 */
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)
{
	const u8 *line_start, *line_end, *next_pixel;
	u32 base16 = 0 + (device_byte_offset >> log_bpp) * 2;
	struct urb *urb = *urb_ptr;
	u8 *cmd = *urb_buf_ptr;
	u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length;

	if (WARN_ON(!(log_bpp == 1 || log_bpp == 2))) {
		/* need to finish URB at error from this function */
		udl_urb_completion(urb);
		return -EINVAL;
	}

	line_start = (u8 *) (front + byte_offset);
	next_pixel = line_start;
	line_end = next_pixel + byte_width;

	while (next_pixel < line_end) {

		udl_compress_hline16(&next_pixel,
			     line_end, &base16,
			     (u8 **) &cmd, (u8 *) cmd_end, log_bpp);

		if (cmd >= cmd_end) {
			int len = cmd - (u8 *) urb->transfer_buffer;
			int ret = udl_submit_urb(dev, urb, len);
			if (ret)
				return ret;
			urb = udl_get_urb(dev);
			if (!urb)
				return -EAGAIN;
			*urb_ptr = urb;
			cmd = urb->transfer_buffer;
			cmd_end = &cmd[urb->transfer_buffer_length];
		}
	}

	*urb_buf_ptr = cmd;

	return 0;
}