pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_config.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - config byte layout
4 *
5 * Copyright (c) 2021 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Purpose: THE single authoritative layout of the 256-byte config block,
12 * plus the portable semantics over it (field descriptors, validation,
13 * defaults, per-version migration, and the VDP-side apply).
14 *
15 * This header owns the config-byte ABI. It is shared by:
16 * - the library (core reset/write paths, GPU config-action keys)
17 * - the PICO9918 firmware (flash storage, apply, validation)
18 * - the configurator (generated from this header)
19 *
20 * Nothing else may declare a PICO9918_CONF_* byte index. If a new byte is needed, it
21 * is claimed here and nowhere else.
22 *
23 * This header must stay free of host dependencies - no firmware headers, no board
24 * headers, no SDK includes. The version a block is stamped with is this library's
25 * own, from the generated pico9918_build_config.h, because it is compared against
26 * the field table below and the two must come from one build. Only the board
27 * revision, which nothing here can measure, arrives as a parameter.
28 *
29 * -------------------------------------------------------------------------
30 * ABI FREEZE
31 * -------------------------------------------------------------------------
32 * The following bytes are deployed in flash on real units. Their indices are
33 * frozen and must NEVER be reassigned to a different meaning - doing so makes
34 * existing units come up with corrupted settings:
35 *
36 * 0-6, 8-14, 16-20, 128-160, 200-204, 252-255
37 *
38 * Free for future claims: 7, 15 (claimed below), 21-127, 161-199, 205-245.
39 *
40 * Byte 7 is deliberately left unused: it falls in the "not settable via
41 * registers" identity/version band (0-6) that the configurator may treat as
42 * reserved, and the firmware's version-match load path does not clear it.
43 *
44 * -------------------------------------------------------------------------
45 * CONFIGURATOR MENU-SENTINEL BAND: 246-255
46 * -------------------------------------------------------------------------
47 * The configurator uses 246-255 as menu-sentinel IDs in the SAME numeric
48 * space as these config indices (CONF_MENU_OUTPUT = 246 .. CONF_MENU_EMPTY =
49 * 255). The overlap at 252-255 is deliberate and already shipping. Do not
50 * claim 246-251 for a real config byte without first checking the
51 * configurator's sentinel list - a collision there breaks menu dispatch.
52 */
53
54#ifndef _PICO9918_CONFIG_H
55#define _PICO9918_CONFIG_H
56
57#include <stddef.h>
58#include <stdbool.h>
59#include <stdint.h>
60
61/* for PICO9918_INST_ONLY_ARG (single- vs multi-instance calling convention) */
62#include "pico9918.h"
63
64/** \brief size of the config block, in bytes */
65#define PICO9918_CONFIG_BYTES 256
66
67/**
68 * \brief the first config byte a guest may write through VR58/59
69 *
70 * Below it is the identity/version band the host stamps - see the ABI FREEZE note above.
71 * The register path enforces this same boundary.
72 */
73#define PICO9918_CONFIG_FIRST_SETTABLE 8
74
75/** \brief vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE */
76#define PICO9918_BASE_TMS9918 0x00 /**< the TMS9918A base, which the F18A unlock extends */
77#define PICO9918_BASE_V9938 0x01 /**< the V9938 base */
78
79/**
80 * \brief PICO9918_CONF_PICO_MODEL values, derived from the board revision
81 *
82 * A frozen ABI, and load-bearing beyond a label: the configurator selects which firmware
83 * image to flash from this byte, so a wrong value offers a unit the wrong image.
84 */
85typedef enum
86{
87 PICO9918_MODEL_RP2040 = 1,
88 PICO9918_MODEL_RP2350 = 2,
90
91/** \brief every claimed config byte, by index. The values are a frozen ABI */
92typedef enum
93{
94 // not settable via registers
95 PICO9918_CONF_PICO_MODEL = 0,
96 PICO9918_CONF_HW_VERSION = 1,
97 PICO9918_CONF_SW_VERSION = 2,
98 PICO9918_CONF_SW_PATCH_VERSION = 3,
99 PICO9918_CONF_CLOCK_TESTED = 4,
100 PICO9918_CONF_DISP_DRIVER = 5,
101 PICO9918_CONF_FLASH_STATUS = 6,
102
103 // 7: free (see ABI FREEZE note above)
104
105 // settable via registers
106 PICO9918_CONF_CRT_SCANLINES = 8,
107 PICO9918_CONF_SCANLINE_SPRITES = 9,
108 PICO9918_CONF_CLOCK_PRESET_ID = 10,
109 PICO9918_CONF_SCART_MODE = 11, // 0 = PAL 576i (default), 1 = NTSC 480i
110 PICO9918_CONF_VDP_DEVICE = 12, // emulated host-clock variant (GROMCLK/CPUCLK pins)
111 PICO9918_CONF_DISP_DRIVER_PREF = 13, // 0 = AUTO (detect dongle), 1 = force VGA, 2 = force SCART
112 PICO9918_CONF_VGA_MODE = 14, // 0 = 480p60 (extensible)
113 PICO9918_CONF_VDP_BASE = 15, // render base: PICO9918_BASE_TMS9918 / _V9938
114
115 PICO9918_CONF_DIAG = 16,
116 PICO9918_CONF_DIAG_REGISTERS = 17,
117 PICO9918_CONF_DIAG_PERFORMANCE = 18,
118 PICO9918_CONF_DIAG_PALETTE = 19,
119 PICO9918_CONF_DIAG_ADDRESS = 20,
120
121 // 21-127: free
122
123 PICO9918_CONF_PALETTE_IDX_0 = 128,
124 PICO9918_CONF_PALETTE_IDX_15 = PICO9918_CONF_PALETTE_IDX_0 + 32, // 16x 2 bytes
125
126 // 161-199: free
127
128 // pending-block mirror (read by configurator)
129 PICO9918_CONF_PENDING_STATE = 200,
130 PICO9918_CONF_PENDING_DRIVER_PREF = 201,
131 PICO9918_CONF_PENDING_VGA_MODE = 202,
132 PICO9918_CONF_PENDING_SCART_MODE = 203,
133 PICO9918_CONF_PENDING_CLOCK_PRESET = 204,
134
135 // 205-245: free (246-251 only after checking the configurator sentinels)
136
137 // commands (configurator writes 1 to trigger)
138 PICO9918_CONF_SAVE_FORCED = 252,
139 PICO9918_CONF_PENDING_CANCEL = 253,
140 PICO9918_CONF_PENDING_CONFIRM = 254,
141 PICO9918_CONF_SAVE_TO_FLASH = 255,
143
144/**
145 * \brief PICO9918_CONF_PENDING_STATE values, and the state byte of a host's stored
146 * pending record. A frozen ABI - the configurator reads it out of byte 200
147 *
148 * CONFIRMED -> PENDING (host saves) -> ARMED (host boots with it) -> CONFIRMED
149 * (the user accepts, or the next boot reverts)
150 */
151typedef enum
152{
153 PICO9918_PENDING_STATE_CONFIRMED = 0xC0,
154 PICO9918_PENDING_STATE_PENDING = 0x9E,
155 PICO9918_PENDING_STATE_ARMED = 0xA0,
157
158/**
159 * \brief bytes in a pending record: the state, then one slot per tracked field
160 *
161 * LOAD-BEARING: a tracked field's slot is its pendingMirror less
162 * PICO9918_CONF_PENDING_STATE, so a host's stored record and the in-RAM mirror band
163 * are the same layout. pico9918_config_pending_capture() and _restore() are built on
164 * it, as is every tool that writes the record, so claiming a new mirror byte means
165 * naming it here.
166 */
167#define PICO9918_PENDING_RECORD_BYTES \
168 (PICO9918_CONF_PENDING_CLOCK_PRESET - PICO9918_CONF_PENDING_STATE + 1)
169
170/* -------------------------------------------------------------------------
171 * Field descriptors
172 * -------------------------------------------------------------------------
173 * Drives validation, defaults, per-version migration, and the pending-block
174 * mirror. Adding a field: append one row in pico9918_config.c.
175 * Set pendingMirror to
176 * PICO9918_PENDING_MIRROR_NONE for fields that don't participate in the display-change
177 * confirmation flow (a host concept - the library only copies the bytes).
178 */
179/** \brief pendingMirror value for a field outside the confirmation flow */
180#define PICO9918_PENDING_MIRROR_NONE 0xFF
181
182/** \brief one config field's descriptor */
183typedef struct
184{
185 uint8_t offset; /**< the field's config byte index */
186 uint8_t max; /**< bounds-check is value > max */
187 uint8_t defaultValue; /**< what a reset or a migration writes */
188 uint8_t pendingMirror; /**< PICO9918_CONF_PENDING_* offset, or PICO9918_PENDING_MIRROR_NONE */
189 uint16_t introducedIn; /**< packed major(4) | minor(4) | patch(8) */
191
192/* The declarations below need C linkage under a C++ host, and this header carries no
193 PICO9918_ macro to supply it - see the dependency rule at the top. */
194#ifdef __cplusplus
195extern "C"
196{
197#endif
198
199/**
200 * \brief the descriptor table. The host save path reads pendingMirror/max/
201 * defaultValue from it
202 */
203PICO9918_DLLEXPORT_CONST const pico9918_config_field_t pico9918_config_fields[];
204
205/** \brief how many rows pico9918_config_fields has */
206PICO9918_DLLEXPORT_CONST const size_t pico9918_config_field_count;
207
208/**
209 * \brief the instance's PICO9918_CONFIG_BYTES settings block
210 *
211 * The same bytes pico9918_config_validate() checks and pico9918_config_apply_now() acts
212 * on, so a host reads its stored block into this and applies it. Persistence stays
213 * the host's - the library never reaches storage - and so does the decision to
214 * write, since these are settings a user chose rather than VDP state.
215 */
218
219/**
220 * \brief write a complete, valid settings block: every field at its default
221 *
222 * What a host wants when it has nothing stored, and the reason it should not simply
223 * zero the block: the field defaults happen to be zero today, but the palette's are
224 * not, and applying the block unpacks those bytes into the live palette. A zeroed
225 * block therefore renders black. This also sets the initialised marker that
226 * pico9918_config_validate() looks for, so a block from here survives it untouched.
227 *
228 * The identity bytes at 0-3 are cleared with the rest; pico9918_config_validate() and
229 * pico9918_config_prepare_save() are where they are stamped back in.
230 */
233
234/**
235 * \brief validate a config block just read from host storage, and stamp its identity
236 *
237 * Resets the block to defaults if it is uninitialised or holds an out-of-range field; then
238 * defaults the fields introduced since the stored version. Either way the identity bytes
239 * end up describing this build and the command bytes a host persisted are cleared.
240 *
241 * A block carrying another model's identity is NOT reset. The byte layout is the same on
242 * both, so the identity is corrected and the settings kept - which is what lets a host
243 * offering a runtime tier switch keep one stored block across it, instead of the user
244 * losing their settings on every toggle.
245 *
246 * Three of the four identity bytes are this library's own: the version pair is the version
247 * it was compiled at, which is the only number the field table's introducedIn can be
248 * compared against, and the model follows \p hwVersion. Passing them in is what let a host
249 * one release behind leave the fields added since at whatever the stored block held.
250 *
251 * \p hwVersion is the board revision, which only a host can measure - byte 1 as the
252 * configurator decodes it, major in the high nibble and minor in the low
253 * (0x03, 0x10, 0x20). A major of 2 or above is the PRO tier, and that is what byte 0 is
254 * derived from, so a value outside this encoding stamps the wrong model.
255 *
256 * Returns true if the block changed in a way the host should persist. A host running the
257 * configurator protocol can ignore that: PICO9918_CONF_SAVE_FORCED is set on the same
258 * path, which is the save request its GPU loop already dispatches.
259 */
261bool pico9918_config_validate(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t hwVersion);
262
263/**
264 * \brief stamp the identity and the initialised marker into a block about to be persisted
265 *
266 * The marker is how pico9918_config_validate() tells a stored block from an erased one,
267 * so a host that persists a block without this gets a factory reset on its next boot.
268 * Host storage is untouched - this only prepares the bytes. \p hwVersion is encoded as
269 * pico9918_config_validate() describes.
270 *
271 * The command bytes are cleared here too, including the forced-save one
272 * pico9918_config_validate() raises: a command is a request to the run that made it, never
273 * a stored setting. A host does not need to clear them itself before persisting.
274 */
276void pico9918_config_prepare_save(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t hwVersion);
277
278/** \brief copy live tracked fields into the in-RAM pending mirror with the given state */
280void pico9918_config_refresh_pending_mirror(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t state);
281
282/**
283 * \brief copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record
284 * \note record[0], the state, is the caller's - only the field slots are written
285 */
287void pico9918_config_pending_capture(const uint8_t config[PICO9918_CONFIG_BYTES], uint8_t* record);
288
289/** \brief copy a pending record's field slots back over the live config */
291void pico9918_config_pending_restore(uint8_t config[PICO9918_CONFIG_BYTES], const uint8_t* record);
292
293/**
294 * \brief ask for the block to be applied at the next end of frame
295 *
296 * The deferred form, and what a device wants: the apply seeds registers and republishes
297 * the palette, so doing it mid-frame would show on the line being scanned out. \p
298 * applyVdpEffects asks for that reseeding; without it the apply runs its host-side and
299 * derived effects only.
300 *
301 * A host that has no frame boundary to wait for wants pico9918_config_apply_now().
302 */
304void pico9918_config_schedule_apply(PICO9918_INST_ARG bool applyVdpEffects);
305
306/**
307 * \brief apply the block now, and cancel any apply already owed
308 *
309 * For a host that has just written the block itself - a configurator front end, or a
310 * consumer stepping the library a frame at a time - and would rather see the effects
311 * than wait for a boundary it does not have. The deferred request is cleared, so the
312 * next end of frame does not apply the same block a second time.
313 *
314 * Also where a host lands after pico9918_set_chip(), which schedules an apply rather than
315 * performing one.
316 */
318void pico9918_config_apply_now(PICO9918_INST_ARG bool applyVdpEffects);
319
320/**
321 * \brief register the host's config-applied hook
322 *
323 * Fires from the apply itself, which the frame module reaches where the
324 * configDirty flag is actually consumed - the end-of-frame interrupt, not the
325 * scanline body - so it is per-frame at worst and a function pointer is
326 * permitted. It exists so a host's own apply effects stay in lockstep with the
327 * library's register and palette effects, instead of the host having to watch
328 * configDirty itself.
329 *
330 * Called LAST, after the VDP-side effects, and on every personality: a host effect
331 * is the host's to gate, and one derived from a register has to read the value this
332 * call may just have seeded. NULL (the default) means the host has no such effects
333 * and nothing is called.
334 *
335 * Registered per instance in a multi-instance build - see pico9918.h for why the two
336 * builds take different shapes.
337 */
339void pico9918_config_set_applied_callback(PICO9918_INST_ARG pico9918_config_applied_fn cb, void* userdata);
340
341#ifdef __cplusplus
342}
343#endif
344
345#endif // _PICO9918_CONFIG_H
pico9918-core - core interface
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:83
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:84
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:50
void pico9918_config_pending_capture(const uint8_t config[PICO9918_CONFIG_BYTES], uint8_t *record)
copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record
pico9918_config_model_t
PICO9918_CONF_PICO_MODEL values, derived from the board revision.
uint8_t * pico9918_config(pico9918_t *tms9918)
the instance's PICO9918_CONFIG_BYTES settings block
void pico9918_config_schedule_apply(pico9918_t *tms9918, bool applyVdpEffects)
ask for the block to be applied at the next end of frame
pico9918_config_option_t
every claimed config byte, by index.
void pico9918_config_defaults(uint8_t config[PICO9918_CONFIG_BYTES])
write a complete, valid settings block: every field at its default
pico9918_pending_state_t
PICO9918_CONF_PENDING_STATE values, and the state byte of a host's stored pending record.
const size_t pico9918_config_field_count
how many rows pico9918_config_fields has
void pico9918_config_set_applied_callback(pico9918_t *tms9918, pico9918_config_applied_fn cb, void *userdata)
register the host's config-applied hook
bool pico9918_config_validate(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t hwVersion)
validate a config block just read from host storage, and stamp its identity
void pico9918_config_refresh_pending_mirror(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t state)
copy live tracked fields into the in-RAM pending mirror with the given state
void pico9918_config_prepare_save(uint8_t config[PICO9918_CONFIG_BYTES], uint8_t hwVersion)
stamp the identity and the initialised marker into a block about to be persisted
const pico9918_config_field_t pico9918_config_fields[]
the descriptor table.
void pico9918_config_apply_now(pico9918_t *tms9918, bool applyVdpEffects)
apply the block now, and cancel any apply already owed
void pico9918_config_pending_restore(uint8_t config[PICO9918_CONFIG_BYTES], const uint8_t *record)
copy a pending record's field slots back over the live config
#define PICO9918_CONFIG_BYTES
size of the config block, in bytes
one config field's descriptor
uint8_t offset
the field's config byte index
uint16_t introducedIn
packed major(4) | minor(4) | patch(8)
uint8_t max
bounds-check is value > max
uint8_t defaultValue
what a reset or a migration writes
uint8_t pendingMirror
PICO9918_CONF_PENDING_* offset, or PICO9918_PENDING_MIRROR_NONE.