lcd.c 8.17 KB
Newer Older
Rahix's avatar
Rahix committed
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
#include "os/core.h"

#include "MAX77650-Arduino-Library.h"
#include "gpio.h"
#include "mxc_delay.h"
#include "portexpander.h"
#include "spi.h"

#include <machine/endian.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* HAL Interfaces {{{ */
static const gpio_cfg_t GPIO_PIN_DC = {
	PORT_1, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE
};

static void lcd_hw_init(void)
{
	GPIO_Config(&GPIO_PIN_DC);

	/* for the reset pin */
	if (!portexpander_detected()) {
		/* Open-drain */
		MAX77650_setDRV(false);
		/* Output */
		MAX77650_setDIR(false);
	}
}

static void lcd_set_dc(bool state)
{
	if (state) {
		GPIO_OutSet(&GPIO_PIN_DC);
	} else {
		GPIO_OutClr(&GPIO_PIN_DC);
	}
}

static void lcd_set_rst(bool state)
{
	if (!portexpander_detected()) {
		MAX77650_setDO(state ? true : false);
	} else {
		portexpander_out_put(PIN_4, state ? 0xFF : 0);
	}
}

/** Bit Rate. Display has 15 MHz limit */
#define SPI_SPEED (15 * 1000 * 1000)

static void lcd_spi_write(const uint8_t *data, size_t count)
{
	const sys_cfg_spi_t spi_master_cfg = {
		.map    = MAP_A,
		.ss0    = Enable,
		.ss1    = Disable,
		.ss2    = Disable,
		.num_io = 2,
	};
	spi_req_t request = {
		.ssel     = 0,
		.deass    = 1,
		.ssel_pol = SPI17Y_POL_LOW,
		.tx_data  = data,
		.rx_data  = NULL,
		.width    = SPI17Y_WIDTH_1,
		.len      = count,
		.bits     = 8,
		.rx_num   = 0,
		.tx_num   = 0,
		.callback = NULL,
	};
	if (SPI_Init(SPI2, 0, SPI_SPEED, spi_master_cfg) != 0) {
		panic("Error configuring display SPI");
	}
	SPI_MasterTrans(SPI2, &request);
}

static void lcd_delay(size_t millis)
{
	// TODO: Is this what we want?
	mxc_delay(millis * 1000);
}
/* HAL Interfaces }}} */

enum lcd_commands {
89
90
	/** Sleep In */
	LCD_SLPIN = 0x10,
Rahix's avatar
Rahix committed
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
	/** Sleep Out */
	LCD_SLPOUT = 0x11,
	/** Display Inversion On */
	LCD_INVON = 0x21,
	/** Display On */
	LCD_DISPON = 0x29,
	/** Column Address Set */
	LCD_CASET = 0x2A,
	/** Row Address Set */
	LCD_RASET = 0x2B,
	/** Memory Write */
	LCD_RAMWR = 0x2C,
	/** Memory Data Access Control */
	LCD_MADCTL = 0x36,
	/** Interface Pixel Format */
	LCD_COLMOD = 0x3A,
	/** Frame Rate Control (In normal mode/ Full colors) */
	LCD_FRMCTR1 = 0xB1,
	/** Frame Rate Control (In Idle mode/ 8-colors) */
	LCD_FRMCTR2 = 0xB2,
	/** Frame Rate Control (In Partial mode/ full colors) */
	LCD_FRMCTR3 = 0xB3,
	/** Display Inversion Control */
	LCD_INVCTR = 0xB4,
	/** Power Control 1 */
	LCD_PWCTR1 = 0xC0,
	/** Power Control 2 */
	LCD_PWCTR2 = 0xC1,
	/** Power Control 3 (in Normal mode/ Full colors) */
	LCD_PWCTR3 = 0xC2,
	/** Power Control 4 (in Idle mode/ 8-colors) */
	LCD_PWCTR4 = 0xC3,
	/** Power Control 5 (in Partial mode/ full-colors) */
	LCD_PWCTR5 = 0xC4,
	/** VCOM Control 1 */
	LCD_VMCTR1 = 0xC5,
	/** Gamma (+ polarity) Correction Characteristics Setting */
	LCD_GMCTRP1 = 0xE0,
	/** Gamma (- polarity) Correction Characteristics Setting */
	LCD_GMCTRN1 = 0xE1,
};

enum madctl_bits {
	MADCTL_MY  = 0x80,
	MADCTL_MX  = 0x40,
	MADCTL_MV  = 0x20,
	MADCTL_ML  = 0x10,
	MADCTL_RGB = 0x08,
	MADCTL_MH  = 0x04,
};

static void
lcd_send_command(enum lcd_commands cmd, const uint8_t *args, size_t count)
{
	lcd_set_dc(false);
	lcd_spi_write((uint8_t *)&cmd, 1);
	if (args != NULL && count != 0) {
		lcd_set_dc(true);
		lcd_spi_write(args, count);
	}
}

static void lcd_hard_reset(void)
{
	lcd_delay(20);
	lcd_set_rst(false);
	lcd_delay(20);
	lcd_set_rst(true);
	lcd_delay(20);
}

162
163
164
165
166
167
168
169
170
void lcd_set_sleep(bool sleep)
{
	if (sleep) {
		lcd_send_command(LCD_SLPIN, NULL, 0);
	} else {
		lcd_send_command(LCD_SLPOUT, NULL, 0);
	}
}

Rahix's avatar
Rahix committed
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/**
 * Perform a minimal initialization under the assumption that the bootloader has
 * already turned on the display.  This is faster and prevents visible
 * reinitialization artifacts.
 */
void lcd_reconfigure(void)
{
	/* Invert Display (twice for unknown reasons ...). */
	lcd_send_command(LCD_INVON, NULL, 0);
	lcd_send_command(LCD_INVON, NULL, 0);

	/* Set framerate control values for all modes to the same values. */
	const uint8_t frmctr[] = { 0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A };
	lcd_send_command(LCD_FRMCTR1, frmctr, 3);
	lcd_send_command(LCD_FRMCTR2, frmctr, 3);
	lcd_send_command(LCD_FRMCTR3, frmctr, 6);

	/* Set display inversion control (unsure what this does?). */
	const uint8_t invctr[] = { 0x03 };
	lcd_send_command(LCD_INVCTR, invctr, sizeof(invctr));

	/* Configure GVDD voltage to 4.7V. */
	const uint8_t pwctr1[] = { 0x62, 0x02, 0x04 };
	lcd_send_command(LCD_PWCTR1, pwctr1, sizeof(pwctr1));

	/* Configure only ignored bits? */
	const uint8_t pwctr2[] = { 0xC0 };
	lcd_send_command(LCD_PWCTR2, pwctr2, sizeof(pwctr2));

	/*
	 * Configure "large" amount of current in operational amplifier and
	 * booster step-ups for all modes.
	 */
	const uint8_t pwctr3[] = { 0x0D, 0x00 }, pwctr4[] = { 0x8D, 0x6A },
		      pwctr5[] = { 0x8D, 0xEE };
	lcd_send_command(LCD_PWCTR3, pwctr3, sizeof(pwctr3));
	lcd_send_command(LCD_PWCTR4, pwctr4, sizeof(pwctr4));
	lcd_send_command(LCD_PWCTR5, pwctr5, sizeof(pwctr5));

	/* Configure VCOMH voltage to 2.850V. */
	const uint8_t vmctr1[] = { 0x0E };
	lcd_send_command(LCD_VMCTR1, vmctr1, sizeof(vmctr1));

	/* Write positive and negative gamma correction values. */
	const uint8_t gmctrp1[] = {
		0x10, 0x0E, 0x02, 0x03, 0x0E, 0x07, 0x02, 0x07,
		0x0A, 0x12, 0x27, 0x37, 0x00, 0x0D, 0x0E, 0x10,
	};
	const uint8_t gmctrn1[] = {
		0x10, 0x0E, 0x03, 0x03, 0x0F, 0x06, 0x02, 0x08,
		0x0A, 0x13, 0x26, 0x36, 0x00, 0x0D, 0x0E, 0x10,
	};
	lcd_send_command(LCD_GMCTRP1, gmctrp1, sizeof(gmctrp1));
	lcd_send_command(LCD_GMCTRN1, gmctrn1, sizeof(gmctrn1));

	/* Configure 16-bit pixel format. */
	const uint8_t colmod[] = { 0x05 };
	lcd_send_command(LCD_COLMOD, colmod, sizeof(colmod));

	/*
	 * Configure "MADCTL", which defines the pixel and color access order.
	 */
	const uint8_t madctl[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
	/*
	 * Waveshare Driver:
	 * const uint8_t madctl[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
	 */
	lcd_send_command(LCD_MADCTL, madctl, sizeof(madctl));

	/* Turn the display on. */
	lcd_send_command(LCD_DISPON, NULL, 0);
}

/**
 * Perform a full initialization of the display.  This will ensure the display
 * is in a deterministic state.
 */
void lcd_initialize(void)
{
	lcd_hw_init();

	lcd_hard_reset();

	lcd_send_command(LCD_SLPOUT, NULL, 0);
	lcd_delay(120);

	lcd_reconfigure();
}

/**
 * Write a partial display update.
 *
 * The rectangle from column ``xstart`` to ``xend`` (inclusive) and row
 * ``ystart`` to ``yend`` (inclusive) will be updated with the contents of
 * ``fb``.
 *
 * ``fb`` **must** have a size of
 * ``(xend - xstart + 1) * (yend - ystart + 1) * 2`` bytes.
 */
void lcd_write_fb_partial(
	uint16_t xstart,
	uint16_t ystart,
	uint16_t xend,
	uint16_t yend,
	const uint8_t *fb
) {
	uint16_t param_buffer[2];

	/* Column start and end are offset by 1. */
	param_buffer[0] = __htons(xstart + 1);
	param_buffer[1] = __htons(xend + 1);
	lcd_send_command(
		LCD_CASET, (uint8_t *)param_buffer, sizeof(param_buffer)
	);

	/* Row start and end are offset by a magic 26. */
	param_buffer[0] = __htons(ystart + 26);
	param_buffer[1] = __htons(yend + 26);
	lcd_send_command(
		LCD_RASET, (uint8_t *)param_buffer, sizeof(param_buffer)
	);

	/* Now write out the actual framebuffer contents. */
	size_t fb_size = (xend - xstart + 1) * (yend - ystart + 1) * 2;
	lcd_send_command(LCD_RAMWR, fb, fb_size);
}

/**
 * Write out a full framebuffer update.
 *
 * ``fb`` **must** be 160 * 80 * 2 = **25600** bytes in size.  The pixels are
 * ordered in rows starting at the top left of the screen.  Each pixel must have
 * its bytes laid out as big endian (while the CPU is little endian!).
 */
void lcd_write_fb(const uint8_t *fb)
{
	lcd_write_fb_partial(0, 0, 159, 79, fb);
}

/**
 * Flip the screen orientation upside down.
 *
 * Historically we had software perform a flip of the framebuffer before
 * sending it out.  This function provides a way to make the hardware accept
 * such a flipped framebuffer.  This exists mostly to support the
 * :c:func:`epic_disp_framebuffer()` API call for legacy l0dables.
 */
void lcd_set_screenflip(bool flipped)
{
	const uint8_t madctl_upright[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
	const uint8_t madctl_flipped[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
	if (flipped) {
		lcd_send_command(LCD_MADCTL, madctl_flipped, 1);
	} else {
		lcd_send_command(LCD_MADCTL, madctl_upright, 1);
	}
}