-
Notifications
You must be signed in to change notification settings - Fork 905
/
Copy pathdvi_out_hstx_encoder.c
264 lines (224 loc) · 9.43 KB
/
dvi_out_hstx_encoder.c
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
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
// Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
// Generate DVI output using the command expander and TMDS encoder in HSTX.
// This example requires an external digital video connector connected to
// GPIOs 12 through 19 (the HSTX-capable GPIOs) with appropriate
// current-limiting resistors, e.g. 270 ohms. The pinout used in this example
// matches the Pico DVI Sock board, which can be soldered onto a Pico 2:
// https://github.com/Wren6991/Pico-DVI-Sock
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include "hardware/structs/bus_ctrl.h"
#include "hardware/structs/hstx_ctrl.h"
#include "hardware/structs/hstx_fifo.h"
#include "hardware/structs/sio.h"
#include "pico/multicore.h"
#include "pico/sem.h"
#include "mountains_640x480_rgb332.h"
#define framebuf mountains_640x480
// ----------------------------------------------------------------------------
// DVI constants
#define TMDS_CTRL_00 0x354u
#define TMDS_CTRL_01 0x0abu
#define TMDS_CTRL_10 0x154u
#define TMDS_CTRL_11 0x2abu
#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define MODE_H_SYNC_POLARITY 0
#define MODE_H_FRONT_PORCH 16
#define MODE_H_SYNC_WIDTH 96
#define MODE_H_BACK_PORCH 48
#define MODE_H_ACTIVE_PIXELS 640
#define MODE_V_SYNC_POLARITY 0
#define MODE_V_FRONT_PORCH 10
#define MODE_V_SYNC_WIDTH 2
#define MODE_V_BACK_PORCH 33
#define MODE_V_ACTIVE_LINES 480
#define MODE_H_TOTAL_PIXELS ( \
MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \
MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \
)
#define MODE_V_TOTAL_LINES ( \
MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \
MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \
)
#define HSTX_CMD_RAW (0x0u << 12)
#define HSTX_CMD_RAW_REPEAT (0x1u << 12)
#define HSTX_CMD_TMDS (0x2u << 12)
#define HSTX_CMD_TMDS_REPEAT (0x3u << 12)
#define HSTX_CMD_NOP (0xfu << 12)
// ----------------------------------------------------------------------------
// HSTX command lists
// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly
// pingponging and tripping up the IRQs.
static uint32_t vblank_line_vsync_off[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V1_H1,
HSTX_CMD_NOP
};
static uint32_t vblank_line_vsync_on[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V0_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V0_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V0_H1,
HSTX_CMD_NOP
};
static uint32_t vactive_line[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH,
SYNC_V1_H1,
HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS
};
// ----------------------------------------------------------------------------
// DMA logic
#define DMACH_PING 0
#define DMACH_PONG 1
// First we ping. Then we pong. Then... we ping again.
static bool dma_pong = false;
// A ping and a pong are cued up initially, so the first time we enter this
// handler it is to cue up the second ping after the first ping has completed.
// This is the third scanline overall (-> =2 because zero-based).
static uint v_scanline = 2;
// During the vertical active period, we take two IRQs per scanline: one to
// post the command list, and another to post the pixels.
static bool vactive_cmdlist_posted = false;
void __scratch_x("") dma_irq_handler() {
// dma_pong indicates the channel that just finished, which is the one
// we're about to reload.
uint ch_num = dma_pong ? DMACH_PONG : DMACH_PING;
dma_channel_hw_t *ch = &dma_hw->ch[ch_num];
dma_hw->intr = 1u << ch_num;
dma_pong = !dma_pong;
if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) {
ch->read_addr = (uintptr_t)vblank_line_vsync_on;
ch->transfer_count = count_of(vblank_line_vsync_on);
} else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) {
ch->read_addr = (uintptr_t)vblank_line_vsync_off;
ch->transfer_count = count_of(vblank_line_vsync_off);
} else if (!vactive_cmdlist_posted) {
ch->read_addr = (uintptr_t)vactive_line;
ch->transfer_count = count_of(vactive_line);
vactive_cmdlist_posted = true;
} else {
ch->read_addr = (uintptr_t)&framebuf[(v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES)) * MODE_H_ACTIVE_PIXELS];
ch->transfer_count = MODE_H_ACTIVE_PIXELS / sizeof(uint32_t);
vactive_cmdlist_posted = false;
}
if (!vactive_cmdlist_posted) {
v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES;
}
}
// ----------------------------------------------------------------------------
// Main program
static __force_inline uint16_t colour_rgb565(uint8_t r, uint8_t g, uint8_t b) {
return ((uint16_t)r & 0xf8) >> 3 | ((uint16_t)g & 0xfc) << 3 | ((uint16_t)b & 0xf8) << 8;
}
static __force_inline uint8_t colour_rgb332(uint8_t r, uint8_t g, uint8_t b) {
return (r & 0xc0) >> 6 | (g & 0xe0) >> 3 | (b & 0xe0) >> 0;
}
void scroll_framebuffer(void);
int main(void) {
// Configure HSTX's TMDS encoder for RGB332
hstx_ctrl_hw->expand_tmds =
2 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB |
0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB |
2 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB |
29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB |
1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB |
26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB;
// Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an
// entire 32-bit word.
hstx_ctrl_hw->expand_shift =
4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB |
8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB |
1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB |
0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB;
// Serial output config: clock period of 5 cycles, pop from command
// expander every 5 cycles, shift the output shiftreg by 2 every cycle.
hstx_ctrl_hw->csr = 0;
hstx_ctrl_hw->csr =
HSTX_CTRL_CSR_EXPAND_EN_BITS |
5u << HSTX_CTRL_CSR_CLKDIV_LSB |
5u << HSTX_CTRL_CSR_N_SHIFTS_LSB |
2u << HSTX_CTRL_CSR_SHIFT_LSB |
HSTX_CTRL_CSR_EN_BITS;
// Note we are leaving the HSTX clock at the SDK default of 125 MHz; since
// we shift out two bits per HSTX clock cycle, this gives us an output of
// 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz).
// If we want the exact rate then we'll have to reconfigure PLLs.
// HSTX outputs 0 through 7 appear on GPIO 12 through 19.
// Pinout on Pico DVI sock:
//
// GP12 D0+ GP13 D0-
// GP14 CK+ GP15 CK-
// GP16 D2+ GP17 D2-
// GP18 D1+ GP19 D1-
// Assign clock pair to two neighbouring pins:
hstx_ctrl_hw->bit[2] = HSTX_CTRL_BIT0_CLK_BITS;
hstx_ctrl_hw->bit[3] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS;
for (uint lane = 0; lane < 3; ++lane) {
// For each TMDS lane, assign it to the correct GPIO pair based on the
// desired pinout:
static const int lane_to_output_bit[3] = {0, 6, 4};
int bit = lane_to_output_bit[lane];
// Output even bits during first half of each HSTX cycle, and odd bits
// during second half. The shifter advances by two bits each cycle.
uint32_t lane_data_sel_bits =
(lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB |
(lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB;
// The two halves of each pair get identical data, but one pin is inverted.
hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits;
hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS;
}
for (int i = 12; i <= 19; ++i) {
gpio_set_function(i, 0); // HSTX
}
// Both channels are set up identically, to transfer a whole scanline and
// then chain to the opposite channel. Each time a channel finishes, we
// reconfigure the one that just finished, meanwhile the opposite channel
// is already making progress.
dma_channel_config c;
c = dma_channel_get_default_config(DMACH_PING);
channel_config_set_chain_to(&c, DMACH_PONG);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PING,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
c = dma_channel_get_default_config(DMACH_PONG);
channel_config_set_chain_to(&c, DMACH_PING);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PONG,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
dma_hw->ints0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
dma_hw->inte0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
dma_channel_start(DMACH_PING);
while (1)
__wfi();
}