1 /*
2  * Copyright 2018 Jared Boone
3  *
4  * This file is part of HackRF.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; see the file COPYING.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "portapack.h"
23 
24 #include "hackrf_core.h"
25 #include "gpio_lpc.h"
26 
27 #include <libopencm3/lpc43xx/scu.h>
28 
portapack_sleep_milliseconds(const uint32_t milliseconds)29 static void portapack_sleep_milliseconds(const uint32_t milliseconds) {
30 	/* NOTE: Naively assumes 204 MHz instruction cycle clock and five instructions per count */
31 	delay(milliseconds * 40800);
32 }
33 
34 static struct gpio_t gpio_io_stbx = GPIO(5,  0);	/* P2_0 */
35 static struct gpio_t gpio_addr    = GPIO(5,  1);	/* P2_1 */
36 __attribute__((unused)) static struct gpio_t gpio_lcd_te  = GPIO(5,  3);	/* P2_3 */
37 __attribute__((unused)) static struct gpio_t gpio_unused  = GPIO(5,  7);	/* P2_8 */
38 static struct gpio_t gpio_lcd_rdx = GPIO(5,  4);	/* P2_4 */
39 static struct gpio_t gpio_lcd_wrx = GPIO(1, 10);	/* P2_9 */
40 static struct gpio_t gpio_dir     = GPIO(1, 13);	/* P2_13 */
41 
42 typedef struct portapack_if_t {
43 	gpio_t gpio_dir;
44 	gpio_t gpio_lcd_rdx;
45 	gpio_t gpio_lcd_wrx;
46 	gpio_t gpio_io_stbx;
47 	gpio_t gpio_addr;
48 	gpio_port_t* const gpio_port_data;
49 	uint8_t io_reg;
50 } portapack_if_t;
51 
52 static portapack_if_t portapack_if = {
53 	.gpio_dir        = &gpio_dir,
54 	.gpio_lcd_rdx    = &gpio_lcd_rdx,
55 	.gpio_lcd_wrx    = &gpio_lcd_wrx,
56 	.gpio_io_stbx    = &gpio_io_stbx,
57 	.gpio_addr       = &gpio_addr,
58 	.gpio_port_data  = GPIO_LPC_PORT(3),
59 	.io_reg          = 0x03,
60 };
61 
62 /* NOTE: Code below assumes the shift value is "8". */
63 #define GPIO_DATA_SHIFT (8)
64 static const uint32_t gpio_data_mask = 0xFFU << GPIO_DATA_SHIFT;
65 
portapack_data_mask_set()66 static void portapack_data_mask_set() {
67 	portapack_if.gpio_port_data->mask = ~gpio_data_mask;
68 }
69 
portapack_data_write_low(const uint32_t value)70 static void portapack_data_write_low(const uint32_t value) {
71 	portapack_if.gpio_port_data->mpin = (value << GPIO_DATA_SHIFT);
72 }
73 
portapack_data_write_high(const uint32_t value)74 static void portapack_data_write_high(const uint32_t value) {
75 	/* NOTE: Assumes no other bits in the port are masked. */
76 	/* NOTE: Assumes that bits 15 through 8 are masked. */
77 	portapack_if.gpio_port_data->mpin = value;
78 }
79 
portapack_dir_read()80 static void portapack_dir_read() {
81 	portapack_if.gpio_port_data->dir &= ~gpio_data_mask;
82 	gpio_set(portapack_if.gpio_dir);
83 }
84 
portapack_dir_write()85 static void portapack_dir_write() {
86 	gpio_clear(portapack_if.gpio_dir);
87 	portapack_if.gpio_port_data->dir |= gpio_data_mask;
88 	/* TODO: Manipulating DIR[3] makes me queasy. The RFFC5072 DATA pin
89 	 * is also on port 3, and switches direction periodically...
90 	 * Time to resort to bit-banding to enforce atomicity? But then, how
91 	 * to change direction on eight bits efficiently? Or do I care, since
92 	 * the PortaPack data bus shouldn't change direction too frequently?
93 	 */
94 }
95 
portapack_lcd_rd_assert()96 __attribute__((unused)) static void portapack_lcd_rd_assert() {
97 	gpio_clear(portapack_if.gpio_lcd_rdx);
98 }
99 
portapack_lcd_rd_deassert()100 static void portapack_lcd_rd_deassert() {
101 	gpio_set(portapack_if.gpio_lcd_rdx);
102 }
103 
portapack_lcd_wr_assert()104 static void portapack_lcd_wr_assert() {
105 	gpio_clear(portapack_if.gpio_lcd_wrx);
106 }
107 
portapack_lcd_wr_deassert()108 static void portapack_lcd_wr_deassert() {
109 	gpio_set(portapack_if.gpio_lcd_wrx);
110 }
111 
portapack_io_stb_assert()112 static void portapack_io_stb_assert() {
113 	gpio_clear(portapack_if.gpio_io_stbx);
114 }
115 
portapack_io_stb_deassert()116 static void portapack_io_stb_deassert() {
117 	gpio_set(portapack_if.gpio_io_stbx);
118 }
119 
portapack_addr(const bool value)120 static void portapack_addr(const bool value) {
121 	gpio_write(portapack_if.gpio_addr, value);
122 }
123 
portapack_lcd_command(const uint32_t value)124 static void portapack_lcd_command(const uint32_t value) {
125 	portapack_data_write_high(0);		/* Drive high byte (with zero -- don't care) */
126 	portapack_dir_write();			/* Turn around data bus, MCU->CPLD */
127 	portapack_addr(0);				/* Indicate command */
128 	__asm__("nop");
129 	__asm__("nop");
130 	__asm__("nop");
131 	portapack_lcd_wr_assert();		/* Latch high byte */
132 
133 	portapack_data_write_low(value);	/* Drive low byte (pass-through) */
134 	__asm__("nop");
135 	__asm__("nop");
136 	__asm__("nop");
137 	portapack_lcd_wr_deassert();		/* Complete write operation */
138 
139 	portapack_addr(1);				/* Set up for data phase (most likely after a command) */
140 }
141 
portapack_lcd_write_data(const uint32_t value)142 static void portapack_lcd_write_data(const uint32_t value) {
143 	// NOTE: Assumes and DIR=0 and ADDR=1 from command phase.
144 	portapack_data_write_high(value);	/* Drive high byte */
145 	__asm__("nop");
146 	portapack_lcd_wr_assert();		/* Latch high byte */
147 
148 	portapack_data_write_low(value);	/* Drive low byte (pass-through) */
149 	__asm__("nop");
150 	__asm__("nop");
151 	__asm__("nop");
152 	portapack_lcd_wr_deassert();		/* Complete write operation */
153 }
154 
portapack_io_write(const bool address,const uint_fast16_t value)155 static void portapack_io_write(const bool address, const uint_fast16_t value) {
156 	portapack_data_write_low(value);
157 	portapack_dir_write();
158 	portapack_addr(address);
159 	__asm__("nop");
160 	__asm__("nop");
161 	__asm__("nop");
162 	portapack_io_stb_assert();
163 	__asm__("nop");
164 	__asm__("nop");
165 	__asm__("nop");
166 	portapack_io_stb_deassert();
167 }
168 
portapack_if_init()169 static void portapack_if_init() {
170 	portapack_data_mask_set();
171 	portapack_data_write_high(0);
172 
173 	portapack_dir_read();
174 	portapack_lcd_rd_deassert();
175 	portapack_lcd_wr_deassert();
176 	portapack_io_stb_deassert();
177 	portapack_addr(0);
178 
179 	gpio_output(portapack_if.gpio_dir);
180 	gpio_output(portapack_if.gpio_lcd_rdx);
181 	gpio_output(portapack_if.gpio_lcd_wrx);
182 	gpio_output(portapack_if.gpio_io_stbx);
183 	gpio_output(portapack_if.gpio_addr);
184 	/* gpio_input(portapack_if.gpio_rot_a); */
185 	/* gpio_input(portapack_if.gpio_rot_b); */
186 
187 	scu_pinmux(SCU_PINMUX_PP_D0, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
188 	scu_pinmux(SCU_PINMUX_PP_D1, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
189 	scu_pinmux(SCU_PINMUX_PP_D2, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
190 	scu_pinmux(SCU_PINMUX_PP_D3, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
191 	scu_pinmux(SCU_PINMUX_PP_D4, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
192 	scu_pinmux(SCU_PINMUX_PP_D5, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
193 	scu_pinmux(SCU_PINMUX_PP_D6, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
194 	scu_pinmux(SCU_PINMUX_PP_D7, SCU_CONF_FUNCTION0 | SCU_GPIO_PDN);
195 
196 	scu_pinmux(SCU_PINMUX_PP_DIR,     SCU_CONF_FUNCTION0 | SCU_GPIO_NOPULL);
197 	scu_pinmux(SCU_PINMUX_PP_LCD_RDX, SCU_CONF_FUNCTION4 | SCU_GPIO_NOPULL);
198 	scu_pinmux(SCU_PINMUX_PP_LCD_WRX, SCU_CONF_FUNCTION0 | SCU_GPIO_NOPULL);
199 	scu_pinmux(SCU_PINMUX_PP_IO_STBX, SCU_CONF_FUNCTION4 | SCU_GPIO_NOPULL);
200 	scu_pinmux(SCU_PINMUX_PP_ADDR,    SCU_CONF_FUNCTION4 | SCU_GPIO_NOPULL);
201 	/* scu_pinmux(SCU_PINMUX_PP_LCD_TE,   SCU_CONF_FUNCTION4 | SCU_GPIO_NOPULL); */
202 	/* scu_pinmux(SCU_PINMUX_PP_UNUSED,   SCU_CONF_FUNCTION4 | SCU_GPIO_NOPULL); */
203 }
204 
portapack_lcd_reset_state(const bool active)205 static void portapack_lcd_reset_state(const bool active) {
206 	portapack_if.io_reg = (portapack_if.io_reg & 0xfe) | (active ? (1 << 0) : 0);
207 	portapack_io_write(1, portapack_if.io_reg);
208 }
209 
portapack_lcd_data_write_command_and_data(const uint_fast8_t command,const uint8_t * data,const size_t data_count)210 static void portapack_lcd_data_write_command_and_data(
211 	const uint_fast8_t command,
212 	const uint8_t* data,
213 	const size_t data_count
214 ) {
215 	portapack_lcd_command(command);
216 	for(size_t i=0; i<data_count; i++) {
217 		portapack_lcd_write_data(data[i]);
218 	}
219 }
220 
portapack_lcd_sleep_out()221 static void portapack_lcd_sleep_out() {
222 	const uint8_t cmd_11[] = {};
223 	portapack_lcd_data_write_command_and_data(0x11, cmd_11, ARRAY_SIZEOF(cmd_11));
224 	// "It will be necessary to wait 120msec after sending Sleep Out
225 	// command (when in Sleep In Mode) before Sleep In command can be
226 	// sent."
227 	portapack_sleep_milliseconds(120);
228 }
229 
portapack_lcd_display_on()230 static void portapack_lcd_display_on() {
231 	const uint8_t cmd_29[] = {};
232 	portapack_lcd_data_write_command_and_data(0x29, cmd_29, ARRAY_SIZEOF(cmd_29));
233 }
234 
portapack_lcd_ramwr_start()235 static void portapack_lcd_ramwr_start() {
236 	const uint8_t cmd_2c[] = {};
237 	portapack_lcd_data_write_command_and_data(0x2c, cmd_2c, ARRAY_SIZEOF(cmd_2c));
238 }
239 
portapack_lcd_set(const uint_fast8_t command,const uint_fast16_t start,const uint_fast16_t end)240 static void portapack_lcd_set(const uint_fast8_t command, const uint_fast16_t start, const uint_fast16_t end) {
241 	const uint8_t data[] = {
242 		(start >> 8), (start & 0xff),
243 		(end   >> 8), (end   & 0xff)
244 	};
245 	portapack_lcd_data_write_command_and_data(command, data, ARRAY_SIZEOF(data));
246 }
247 
portapack_lcd_caset(const uint_fast16_t start_column,const uint_fast16_t end_column)248 static void portapack_lcd_caset(const uint_fast16_t start_column, const uint_fast16_t end_column) {
249 	portapack_lcd_set(0x2a, start_column, end_column);
250 }
251 
portapack_lcd_paset(const uint_fast16_t start_page,const uint_fast16_t end_page)252 static void portapack_lcd_paset(const uint_fast16_t start_page, const uint_fast16_t end_page) {
253 	portapack_lcd_set(0x2b, start_page, end_page);
254 }
255 
portapack_lcd_start_ram_write(const ui_rect_t rect)256 static void portapack_lcd_start_ram_write(
257 	const ui_rect_t rect
258 ) {
259 	portapack_lcd_caset(rect.point.x, rect.point.x + rect.size.width  - 1);
260 	portapack_lcd_paset(rect.point.y, rect.point.y + rect.size.height - 1);
261 	portapack_lcd_ramwr_start();
262 }
263 
portapack_lcd_write_pixel(const ui_color_t pixel)264 static void portapack_lcd_write_pixel(const ui_color_t pixel) {
265 	portapack_lcd_write_data(pixel.v);
266 }
267 
portapack_lcd_write_pixels_color(const ui_color_t c,size_t n)268 static void portapack_lcd_write_pixels_color(const ui_color_t c, size_t n) {
269 	while(n--) {
270 		portapack_lcd_write_data(c.v);
271 	}
272 }
273 
portapack_lcd_wake()274 static void portapack_lcd_wake() {
275 	portapack_lcd_sleep_out();
276 	portapack_lcd_display_on();
277 }
278 
portapack_lcd_reset()279 static void portapack_lcd_reset() {
280 	portapack_lcd_reset_state(false);
281 	portapack_sleep_milliseconds(1);
282 	portapack_lcd_reset_state(true);
283 	portapack_sleep_milliseconds(10);
284 	portapack_lcd_reset_state(false);
285 	portapack_sleep_milliseconds(120);
286 }
287 
portapack_lcd_init()288 static void portapack_lcd_init() {
289 	// LCDs are configured for IM[2:0] = 001
290 	// 8080-I system, 16-bit parallel bus
291 
292 	//
293 	// 0x3a: DBI[2:0] = 101
294 	// MDT[1:0] = XX (if not in 18-bit mode, right?)
295 
296 	// Power control B
297 	// 0
298 	// PCEQ=1, DRV_ena=0, Power control=3
299 	const uint8_t cmd_cf[] = { 0x00, 0xD9, 0x30 };
300 	portapack_lcd_data_write_command_and_data(0xCF, cmd_cf, ARRAY_SIZEOF(cmd_cf));
301 
302 	// Power on sequence control
303 	const uint8_t cmd_ed[] = { 0x64, 0x03, 0x12, 0x81 };
304 	portapack_lcd_data_write_command_and_data(0xED, cmd_ed, ARRAY_SIZEOF(cmd_ed));
305 
306 	// Driver timing control A
307 	const uint8_t cmd_e8[] = { 0x85, 0x10, 0x78 };
308 	portapack_lcd_data_write_command_and_data(0xE8, cmd_e8, ARRAY_SIZEOF(cmd_e8));
309 
310 	// Power control A
311 	const uint8_t cmd_cb[] = { 0x39, 0x2C, 0x00, 0x34, 0x02 };
312 	portapack_lcd_data_write_command_and_data(0xCB, cmd_cb, ARRAY_SIZEOF(cmd_cb));
313 
314 	// Pump ratio control
315 	const uint8_t cmd_f7[] = { 0x20 };
316 	portapack_lcd_data_write_command_and_data(0xF7, cmd_f7, ARRAY_SIZEOF(cmd_f7));
317 
318 	// Driver timing control B
319 	const uint8_t cmd_ea[] = { 0x00, 0x00 };
320 	portapack_lcd_data_write_command_and_data(0xEA, cmd_ea, ARRAY_SIZEOF(cmd_ea));
321 
322 	const uint8_t cmd_b1[] = { 0x00, 0x1B };
323 	portapack_lcd_data_write_command_and_data(0xB1, cmd_b1, ARRAY_SIZEOF(cmd_b1));
324 
325 	// Blanking Porch Control
326 	// VFP = 0b0000010 = 2 (number of HSYNC of vertical front porch)
327 	// VBP = 0b0000010 = 2 (number of HSYNC of vertical back porch)
328 	// HFP = 0b0001010 = 10 (number of DOTCLOCK of horizontal front porch)
329 	// HBP = 0b0010100 = 20 (number of DOTCLOCK of horizontal back porch)
330 	const uint8_t cmd_b5[] = { 0x02, 0x02, 0x0a, 0x14 };
331 	portapack_lcd_data_write_command_and_data(0xB5, cmd_b5, ARRAY_SIZEOF(cmd_b5));
332 
333 	// Display Function Control
334 	// PT[1:0] = 0b10
335 	// PTG[1:0] = 0b10
336 	// ISC[3:0] = 0b0010 (scan cycle interval of gate driver: 5 frames)
337 	// SM = 0 (gate driver pin arrangement in combination with GS)
338 	// SS = 1 (source output scan direction S720 -> S1)
339 	// GS = 0 (gate output scan direction G1 -> G320)
340 	// REV = 1 (normally white)
341 	// NL = 0b100111 (default)
342 	// PCDIV = 0b000000 (default?)
343 	const uint8_t cmd_b6[] = { 0x0A, 0xA2, 0x27, 0x00 };
344 	portapack_lcd_data_write_command_and_data(0xB6, cmd_b6, ARRAY_SIZEOF(cmd_b6));
345 
346 	// Power Control 1
347 	//VRH[5:0]
348 	const uint8_t cmd_c0[] = { 0x1B };
349 	portapack_lcd_data_write_command_and_data(0xC0, cmd_c0, ARRAY_SIZEOF(cmd_c0));
350 
351 	// Power Control 2
352 	//SAP[2:0];BT[3:0]
353 	const uint8_t cmd_c1[] = { 0x12 };
354 	portapack_lcd_data_write_command_and_data(0xC1, cmd_c1, ARRAY_SIZEOF(cmd_c1));
355 
356 	// VCOM Control 1
357 	const uint8_t cmd_c5[] = { 0x32, 0x3C };
358 	portapack_lcd_data_write_command_and_data(0xC5, cmd_c5, ARRAY_SIZEOF(cmd_c5));
359 
360 	// VCOM Control 2
361 	const uint8_t cmd_c7[] = { 0x9B };
362 	portapack_lcd_data_write_command_and_data(0xC7, cmd_c7, ARRAY_SIZEOF(cmd_c7));
363 
364 	// Memory Access Control
365 	// Invert X and Y memory access order, so upper-left of
366 	// screen is (0,0) when writing to display.
367 	const uint8_t cmd_36[] = {
368 		(1 << 7) |	// MY=1
369 		(1 << 6) |	// MX=1
370 		(0 << 5) |	// MV=0
371 		(1 << 4) |	// ML=1: reverse vertical refresh to simplify scrolling logic
372 		(1 << 3)	// BGR=1: For Kingtech LCD, BGR filter.
373 	};
374 	portapack_lcd_data_write_command_and_data(0x36, cmd_36, ARRAY_SIZEOF(cmd_36));
375 
376 	// COLMOD: Pixel Format Set
377 	// DPI=101 (16 bits/pixel), DBI=101 (16 bits/pixel)
378 	const uint8_t cmd_3a[] = { 0x55 };
379 	portapack_lcd_data_write_command_and_data(0x3A, cmd_3a, ARRAY_SIZEOF(cmd_3a));
380 
381 	//portapack_lcd_data_write_command_and_data(0xF6, { 0x01, 0x30 });
382 	// WEMODE=1 (reset column and page number on overflow)
383 	// MDT[1:0]
384 	// EPF[1:0]=00 (use channel MSB for LSB)
385 	// RIM=0 (If COLMOD[6:4]=101 (65k color), 16-bit RGB interface (1 transfer/pixel))
386 	// RM=0 (system interface/VSYNC interface)
387 	// DM[1:0]=00 (internal clock operation)
388 	// ENDIAN=0 (doesn't matter with 16-bit interface)
389 	const uint8_t cmd_f6[] = { 0x01, 0x30, 0x00 };
390 	portapack_lcd_data_write_command_and_data(0xF6, cmd_f6, ARRAY_SIZEOF(cmd_f6));
391 
392 	// 3Gamma Function Disable
393 	const uint8_t cmd_f2[] = { 0x00 };
394 	portapack_lcd_data_write_command_and_data(0xF2, cmd_f2, ARRAY_SIZEOF(cmd_f2));
395 
396 	// Gamma curve selected
397 	const uint8_t cmd_26[] = { 0x01 };
398 	portapack_lcd_data_write_command_and_data(0x26, cmd_26, ARRAY_SIZEOF(cmd_26));
399 
400 	// Set Gamma
401 	const uint8_t cmd_e0[] = {
402 		0x0F, 0x1D, 0x19, 0x0E, 0x10, 0x07, 0x4C, 0x63,
403 		0x3F, 0x03, 0x0D, 0x00, 0x26, 0x24, 0x04
404 	};
405 	portapack_lcd_data_write_command_and_data(0xE0, cmd_e0, ARRAY_SIZEOF(cmd_e0));
406 
407 	// Set Gamma
408 	const uint8_t cmd_e1[] = {
409 		0x00, 0x1C, 0x1F, 0x02, 0x0F, 0x03, 0x35, 0x25,
410 		0x47, 0x04, 0x0C, 0x0B, 0x29, 0x2F, 0x05
411 	};
412 	portapack_lcd_data_write_command_and_data(0xE1, cmd_e1, ARRAY_SIZEOF(cmd_e1));
413 
414 	portapack_lcd_wake();
415 
416 	// Turn on Tearing Effect Line (TE) output signal.
417 	const uint8_t cmd_35[] = { 0b00000000 };
418 	portapack_lcd_data_write_command_and_data(0x35, cmd_35, ARRAY_SIZEOF(cmd_35));
419 }
420 
portapack_backlight(const bool on)421 void portapack_backlight(const bool on) {
422 	portapack_if.io_reg = (portapack_if.io_reg & 0x7f) | (on ? (1 << 7) : 0);
423 	portapack_io_write(1, portapack_if.io_reg);
424 }
425 
portapack_reference_oscillator(const bool on)426 void portapack_reference_oscillator(const bool on) {
427 	const uint8_t mask = 1 << 6;
428 	portapack_if.io_reg = (portapack_if.io_reg & ~mask) | (on ? mask : 0);
429 	portapack_io_write(1, portapack_if.io_reg);
430 }
431 
portapack_fill_rectangle(const ui_rect_t rect,const ui_color_t color)432 void portapack_fill_rectangle(
433 	const ui_rect_t rect,
434 	const ui_color_t color
435 ) {
436 	portapack_lcd_start_ram_write(rect);
437 	portapack_lcd_write_pixels_color(color, rect.size.width * rect.size.height);
438 }
439 
portapack_clear_display(const ui_color_t color)440 void portapack_clear_display(const ui_color_t color) {
441 	const ui_rect_t rect_screen = { { 0, 0 }, { 240, 320 } };
442 	portapack_fill_rectangle(rect_screen, color);
443 }
444 
portapack_draw_bitmap(const ui_point_t point,const ui_bitmap_t bitmap,const ui_color_t foreground,const ui_color_t background)445 void portapack_draw_bitmap(
446 	const ui_point_t point,
447 	const ui_bitmap_t bitmap,
448 	const ui_color_t foreground,
449 	const ui_color_t background
450 ) {
451 	const ui_rect_t rect = {
452 		.point = point,
453 		.size = bitmap.size
454 	};
455 
456 	portapack_lcd_start_ram_write(rect);
457 
458 	const size_t count = bitmap.size.width * bitmap.size.height;
459 	for(size_t i=0; i<count; i++) {
460 		const uint8_t pixel = bitmap.data[i >> 3] & (1U << (i & 0x7));
461 		portapack_lcd_write_pixel(pixel ? foreground : background);
462 	}
463 }
464 
portapack_font_glyph(const ui_font_t * const font,const char c)465 ui_bitmap_t portapack_font_glyph(
466 	const ui_font_t* const font,
467 	const char c
468 ) {
469 	if( c >= font->c_start ) {
470 		const uint_fast8_t index = c - font->c_start;
471 		if( index < font->c_count ) {
472 			const ui_bitmap_t bitmap = {
473 				.size = font->glyph_size,
474 				.data = &font->data[index * font->data_stride]
475 			};
476 			return bitmap;
477 		}
478 	}
479 
480 	const ui_bitmap_t bitmap = {
481 		.size = font->glyph_size,
482 		.data = font->data,
483 	};
484 	return bitmap;
485 }
486 
jtag_pp_tck(const bool tms_value)487 static bool jtag_pp_tck(const bool tms_value) {
488 	gpio_write(jtag_cpld.gpio->gpio_pp_tms, tms_value);
489 
490 	// 8 ns TMS/TDI to TCK setup
491 	__asm__("nop");
492 	__asm__("nop");
493 	__asm__("nop");
494 
495 	gpio_set(jtag_cpld.gpio->gpio_tck);
496 
497 	// 15 ns TCK to TMS/TDI hold time
498 	// 20 ns TCK high time
499 	__asm__("nop");
500 	__asm__("nop");
501 	__asm__("nop");
502 	__asm__("nop");
503 	__asm__("nop");
504 
505 	gpio_clear(jtag_cpld.gpio->gpio_tck);
506 
507 	// 20 ns TCK low time
508 	// 25 ns TCK falling edge to TDO valid
509 	__asm__("nop");
510 	__asm__("nop");
511 	__asm__("nop");
512 	__asm__("nop");
513 	__asm__("nop");
514 	__asm__("nop");
515 	__asm__("nop");
516 
517 	return gpio_read(jtag_cpld.gpio->gpio_pp_tdo);
518 }
519 
jtag_pp_shift(const uint32_t tms_bits,const size_t count)520 static uint32_t jtag_pp_shift(const uint32_t tms_bits, const size_t count) {
521 	uint32_t result = 0;
522 	size_t bit_in_index = count - 1;
523 	size_t bit_out_index = 0;
524 	while(bit_out_index < count) {
525 		const uint32_t tdo = jtag_pp_tck((tms_bits >> bit_in_index) & 1) & 1;
526 		result |= (tdo << bit_out_index);
527 
528 		bit_in_index--;
529 		bit_out_index++;
530 	}
531 
532 	return result;
533 }
534 
jtag_pp_idcode(void)535 static uint32_t jtag_pp_idcode(void) {
536 	cpld_jtag_take(&jtag_cpld);
537 
538 	/* TODO: Check if PortaPack TMS is floating or driven by an external device. */
539 	gpio_output(jtag_cpld.gpio->gpio_pp_tms);
540 
541 	/* Test-Logic/Reset -> Run-Test/Idle -> Select-DR/Scan -> Capture-DR */
542 	jtag_pp_shift(0b11111010, 8);
543 
544 	/* Shift-DR */
545 	const uint32_t idcode = jtag_pp_shift(0, 32);
546 
547 	/* Exit1-DR -> Update-DR -> Run-Test/Idle -> ... -> Test-Logic/Reset */
548 	jtag_pp_shift(0b11011111, 8);
549 
550 	cpld_jtag_release(&jtag_cpld);
551 
552 	return idcode;
553 }
554 
portapack_detect(void)555 static bool portapack_detect(void) {
556 	return jtag_pp_idcode() == 0x020A50DD;
557 }
558 
559 static const portapack_t portapack_instance = {
560 };
561 
562 static const portapack_t* portapack_pointer = NULL;
563 
portapack(void)564 const portapack_t* portapack(void) {
565 	return portapack_pointer;
566 }
567 
portapack_init(void)568 void portapack_init(void) {
569 	if( portapack_detect() ) {
570 		portapack_if_init();
571 		portapack_lcd_reset();
572 		portapack_lcd_init();
573 		portapack_pointer = &portapack_instance;
574 	} else {
575 		portapack_pointer = NULL;
576 	}
577 }