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