pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_config.c File Reference

pico9918-core - Config semantics More...

#include "impl/pico9918_priv.h"
#include <string.h>
+ Include dependency graph for pico9918_config.c:

Go to the source code of this file.

Macros

#define CONFIG_RUNNING_VERSION   (((uint16_t)PICO9918_BUILD_SW_VERSION << 8) | PICO9918_BUILD_SW_PATCH)
 
#define CONFIG_APPLIED_CB   tms9918->configApplied
 

Functions

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_pending_capture (const uint8_t config[PICO9918_CONFIG_BYTES], uint8_t *record)
 copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record
 
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
 
PICO9918_INLINE_HOT uint16_t configStoredVersion (const uint8_t *config)
 
static uint8_t configPicoModel (uint8_t hwVersion)
 
static bool configOutOfRange (const uint8_t *config)
 
void pico9918_config_defaults (uint8_t config[PICO9918_CONFIG_BYTES])
 write a complete, valid settings block: every field at its default
 
static void migrateNewFields (uint8_t *config, uint16_t storedVer)
 
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_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
 
uint8_t * pico9918_config (pico9918_t *tms9918)
 the instance's PICO9918_CONFIG_BYTES settings block
 
void pico9918_config_set_applied_callback (pico9918_t *tms9918, pico9918_config_applied_fn cb, void *userdata)
 register the host's config-applied hook
 
static void configAppliedFire (pico9918_t *tms9918)
 
void pico9918_config_apply (pico9918_t *tms9918)
 apply the config block's VDP-side effects: registers 50 and 30, the palette unpack, and the derived PICO9918_CONF_DIAG summary byte
 
void pico9918_config_schedule_apply (pico9918_t *tms9918, bool applyVdpEffects)
 ask for the block to be applied at the next end of frame
 
void pico9918_config_apply_now (pico9918_t *tms9918, bool applyVdpEffects)
 apply the block now, and cancel any apply already owed
 

Variables

const pico9918_config_field_t pico9918_config_fields []
 
const size_t pico9918_config_field_count = sizeof(pico9918_config_fields) / sizeof(pico9918_config_fields[0])
 

Detailed Description

pico9918-core - Config semantics

Copyright (c) 2021 Troy Schrapel

This code is licensed under the MIT license

https://github.com/visrealm/pico9918-core

Portable half of the config block: the field descriptor table, the validation / defaults / migration driver, the pending mirror refresh, and the VDP-side apply. Host storage (flash), host detection (SCART, hardware version) and host-side apply effects (VGA scanlines) live in the host.

Definition in file pico9918_config.c.

Macro Definition Documentation

◆ CONFIG_RUNNING_VERSION

#define CONFIG_RUNNING_VERSION   (((uint16_t)PICO9918_BUILD_SW_VERSION << 8) | PICO9918_BUILD_SW_PATCH)

Definition at line 78 of file pico9918_config.c.

◆ CONFIG_APPLIED_CB

#define CONFIG_APPLIED_CB   tms9918->configApplied

Definition at line 199 of file pico9918_config.c.

Function Documentation

◆ pico9918_config_refresh_pending_mirror()

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

Definition at line 41 of file pico9918_config.c.

References pico9918_config_field_t::offset, pico9918_config_field_t::pendingMirror, and PICO9918_PENDING_MIRROR_NONE.

◆ pico9918_config_pending_capture()

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

Note
record[0], the state, is the caller's - only the field slots are written

Definition at line 51 of file pico9918_config.c.

References pico9918_config_field_t::offset, pico9918_config_field_t::pendingMirror, and PICO9918_PENDING_MIRROR_NONE.

◆ pico9918_config_pending_restore()

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

Definition at line 61 of file pico9918_config.c.

References pico9918_config_field_t::offset, pico9918_config_field_t::pendingMirror, and PICO9918_PENDING_MIRROR_NONE.

◆ configStoredVersion()

PICO9918_INLINE_HOT uint16_t configStoredVersion ( const uint8_t *  config)

Definition at line 71 of file pico9918_config.c.

◆ configPicoModel()

static uint8_t configPicoModel ( uint8_t  hwVersion)
static

Definition at line 82 of file pico9918_config.c.

◆ configOutOfRange()

static bool configOutOfRange ( const uint8_t *  config)
static

Definition at line 87 of file pico9918_config.c.

◆ pico9918_config_defaults()

void pico9918_config_defaults ( uint8_t  config[PICO9918_CONFIG_BYTES])

write a complete, valid settings block: every field at its default

What a host wants when it has nothing stored, and the reason it should not simply zero the block: the field defaults happen to be zero today, but the palette's are not, and applying the block unpacks those bytes into the live palette. A zeroed block therefore renders black. This also sets the initialised marker that pico9918_config_validate() looks for, so a block from here survives it untouched.

The identity bytes at 0-3 are cleared with the rest; pico9918_config_validate() and pico9918_config_prepare_save() are where they are stamped back in.

Definition at line 96 of file pico9918_config.c.

References pico9918_config_field_t::defaultValue, pico9918_config_field_t::offset, PICO9918_CONFIG_BYTES, and pico9918_default_palette().

Referenced by pico9918_config_validate().

◆ migrateNewFields()

static void migrateNewFields ( uint8_t *  config,
uint16_t  storedVer 
)
static

Definition at line 116 of file pico9918_config.c.

◆ pico9918_config_validate()

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

Resets the block to defaults if it is uninitialised or holds an out-of-range field; then defaults the fields introduced since the stored version. Either way the identity bytes end up describing this build and the command bytes a host persisted are cleared.

A block carrying another model's identity is NOT reset. The byte layout is the same on both, so the identity is corrected and the settings kept - which is what lets a host offering a runtime tier switch keep one stored block across it, instead of the user losing their settings on every toggle.

Three of the four identity bytes are this library's own: the version pair is the version it was compiled at, which is the only number the field table's introducedIn can be compared against, and the model follows hwVersion. Passing them in is what let a host one release behind leave the fields added since at whatever the stored block held.

hwVersion is the board revision, which only a host can measure - byte 1 as the configurator decodes it, major in the high nibble and minor in the low (0x03, 0x10, 0x20). A major of 2 or above is the PRO tier, and that is what byte 0 is derived from, so a value outside this encoding stamps the wrong model.

Returns true if the block changed in a way the host should persist. A host running the configurator protocol can ignore that: PICO9918_CONF_SAVE_FORCED is set on the same path, which is the save request its GPU loop already dispatches.

Definition at line 127 of file pico9918_config.c.

References pico9918_config_defaults().

◆ pico9918_config_prepare_save()

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

The marker is how pico9918_config_validate() tells a stored block from an erased one, so a host that persists a block without this gets a factory reset on its next boot. Host storage is untouched - this only prepares the bytes. hwVersion is encoded as pico9918_config_validate() describes.

The command bytes are cleared here too, including the forced-save one pico9918_config_validate() raises: a command is a request to the run that made it, never a stored setting. A host does not need to clear them itself before persisting.

Definition at line 162 of file pico9918_config.c.

◆ pico9918_config()

uint8_t * pico9918_config ( pico9918_t *  tms9918)

the instance's PICO9918_CONFIG_BYTES settings block

The same bytes pico9918_config_validate() checks and pico9918_config_apply_now() acts on, so a host reads its stored block into this and applies it. Persistence stays the host's - the library never reaches storage - and so does the decision to write, since these are settings a user chose rather than VDP state.

Definition at line 184 of file pico9918_config.c.

◆ pico9918_config_set_applied_callback()

void pico9918_config_set_applied_callback ( pico9918_t *  tms9918,
pico9918_config_applied_fn  cb,
void *  userdata 
)

register the host's config-applied hook

Fires from the apply itself, which the frame module reaches where the configDirty flag is actually consumed - the end-of-frame interrupt, not the scanline body - so it is per-frame at worst and a function pointer is permitted. It exists so a host's own apply effects stay in lockstep with the library's register and palette effects, instead of the host having to watch configDirty itself.

Called LAST, after the VDP-side effects, and on every personality: a host effect is the host's to gate, and one derived from a register has to read the value this call may just have seeded. NULL (the default) means the host has no such effects and nothing is called.

Registered per instance in a multi-instance build - see pico9918.h for why the two builds take different shapes.

Definition at line 202 of file pico9918_config.c.

◆ configAppliedFire()

static void configAppliedFire ( pico9918_t *  tms9918)
inlinestatic

Definition at line 210 of file pico9918_config.c.

◆ pico9918_config_apply()

void pico9918_config_apply ( pico9918_t *  tms9918)

apply the config block's VDP-side effects: registers 50 and 30, the palette unpack, and the derived PICO9918_CONF_DIAG summary byte

A settings block is a PICO9918 thing, so the effects land only on a personality that has the config port. On an F18A those registers and that palette are the guest's alone, and a block read from host storage must not touch them.

What it writes is a power-on default, not an owner: it runs when the block is loaded and after a reset has cleared the register file, and a later write to register 50 or 30 stands on every personality.

Host-side effects stay with the host.

Internal: it leaves configDirty set, so a host reaching it directly gets the block applied again at the next end of frame. Hosts want apply_now or schedule_apply.

Definition at line 215 of file pico9918_config.c.

References PICO9918_BASE_TMS9918, PICO9918_BASE_V9938, PICO9918_INST_ONLY, PICO9918_R50_VSCANLINES, PICO9918_REG_ENHANCED2, and PICO9918_REG_MAX_SCAN_SPRITES.

Referenced by pico9918_config_apply_now(), and pico9918_frame_raise_end_of_frame_int().

◆ pico9918_config_schedule_apply()

void pico9918_config_schedule_apply ( pico9918_t *  tms9918,
bool  applyVdpEffects 
)

ask for the block to be applied at the next end of frame

The deferred form, and what a device wants: the apply seeds registers and republishes the palette, so doing it mid-frame would show on the line being scanned out. applyVdpEffects asks for that reseeding; without it the apply runs its host-side and derived effects only.

A host that has no frame boundary to wait for wants pico9918_config_apply_now().

Definition at line 261 of file pico9918_config.c.

◆ pico9918_config_apply_now()

void pico9918_config_apply_now ( pico9918_t *  tms9918,
bool  applyVdpEffects 
)

apply the block now, and cancel any apply already owed

For a host that has just written the block itself - a configurator front end, or a consumer stepping the library a frame at a time - and would rather see the effects than wait for a boundary it does not have. The deferred request is cleared, so the next end of frame does not apply the same block a second time.

Also where a host lands after pico9918_set_chip(), which schedules an apply rather than performing one.

Definition at line 267 of file pico9918_config.c.

References pico9918_config_apply(), and PICO9918_INST_ONLY.

Variable Documentation

◆ pico9918_config_fields

const pico9918_config_field_t pico9918_config_fields[]
Initial value:
= {
{PICO9918_CONF_CRT_SCANLINES, 1, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_SCANLINE_SPRITES, 3, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_CLOCK_PRESET_ID, 2, 0, PICO9918_CONF_PENDING_CLOCK_PRESET, 0x1000},
{PICO9918_CONF_SCART_MODE, 1, 0, PICO9918_CONF_PENDING_SCART_MODE, 0x1200},
{PICO9918_CONF_VDP_DEVICE, 3, 0, PICO9918_PENDING_MIRROR_NONE, 0x1101},
{PICO9918_CONF_DISP_DRIVER_PREF, 2, 0, PICO9918_CONF_PENDING_DRIVER_PREF, 0x1200},
{PICO9918_CONF_VGA_MODE, 0, 0, PICO9918_CONF_PENDING_VGA_MODE, 0x1200},
{PICO9918_CONF_DIAG_REGISTERS, 1, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_DIAG_PERFORMANCE, 1, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_DIAG_PALETTE, 1, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_DIAG_ADDRESS, 1, 0, PICO9918_PENDING_MIRROR_NONE, 0x1000},
{PICO9918_CONF_VDP_BASE, 1, PICO9918_BASE_TMS9918, PICO9918_PENDING_MIRROR_NONE, 0x1300},
}
#define PICO9918_PENDING_MIRROR_NONE
pendingMirror value for a field outside the confirmation flow
#define PICO9918_BASE_TMS9918
vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE

Definition at line 21 of file pico9918_config.c.

◆ pico9918_config_field_count

const size_t pico9918_config_field_count = sizeof(pico9918_config_fields) / sizeof(pico9918_config_fields[0])

Definition at line 39 of file pico9918_config.c.