Sharp MZ80A updates and performance tuning
This commit is contained in:
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
@@ -1 +1 @@
|
||||
2.07
|
||||
2.18
|
||||
|
||||
@@ -814,19 +814,48 @@ bool SDCard::storeRP2350Info(const t_IpcFrameHdr &frame, FSPI &fspi)
|
||||
{
|
||||
memcpy(rp2350Header, infoBuf, sizeof(t_FlashPartitionHeader));
|
||||
|
||||
// Extended payload includes cpufreq/psramfreq/voltage/flashSize/psramSize after the header.
|
||||
if (payloadSize >= sizeof(t_FlashInfoPayload))
|
||||
// Extended payload fields — extract via WiFi's wifiCtrl.run fields directly.
|
||||
// The rp2350Header pointer points to wifiCtrl.run.rp2350FlashHeader, and the
|
||||
// fields after it (rp2350CpuFreq, rp2350PsramFreq, etc.) are at known offsets.
|
||||
// Backwards-compatible: only extract fields present in the payload.
|
||||
if (payloadSize > sizeof(t_FlashPartitionHeader))
|
||||
{
|
||||
t_FlashInfoPayload *info = (t_FlashInfoPayload *) infoBuf;
|
||||
// Store in the fields immediately after rp2350FlashHeader in wifiCtrl.run.
|
||||
// Layout must match: int32_t cpufreq, psramfreq, voltage; uint32_t flashSize, psramSize;
|
||||
int32_t *extra = (int32_t *) (rp2350Header + 1);
|
||||
extra[0] = info->cpufreq;
|
||||
extra[1] = info->psramfreq;
|
||||
extra[2] = info->voltage;
|
||||
uint32_t *extra32 = (uint32_t *) &extra[3];
|
||||
extra32[0] = info->flashSize;
|
||||
extra32[1] = info->psramSize;
|
||||
|
||||
// Core config fields (cpufreq..psramSize) — present in all extended payloads.
|
||||
const size_t coreExtSize = offsetof(t_FlashInfoPayload, hostClkHz);
|
||||
if (payloadSize >= coreExtSize)
|
||||
{
|
||||
int32_t *extra = (int32_t *) (rp2350Header + 1);
|
||||
extra[0] = info->cpufreq;
|
||||
extra[1] = info->psramfreq;
|
||||
extra[2] = info->voltage;
|
||||
uint32_t *extra32 = (uint32_t *) &extra[3];
|
||||
extra32[0] = info->flashSize;
|
||||
extra32[1] = info->psramSize;
|
||||
}
|
||||
|
||||
// Host clock + emulation speed — added after psramSize.
|
||||
if (payloadSize >= offsetof(t_FlashInfoPayload, driverSummary))
|
||||
{
|
||||
int32_t *extra = (int32_t *) (rp2350Header + 1);
|
||||
uint32_t *extra32 = (uint32_t *) &extra[3];
|
||||
extra32[2] = info->hostClkHz; // rp2350HostClkHz
|
||||
extra32[3] = info->emulSpeedHz; // rp2350EmulSpeedHz
|
||||
}
|
||||
|
||||
// Driver summary — last field, variable-length content.
|
||||
uint32_t *extra32base = (uint32_t *) ((int32_t *)(rp2350Header + 1) + 3);
|
||||
char *drvSummary = (char *) &extra32base[4]; // After flashSize, psramSize, hostClkHz, emulSpeedHz
|
||||
if (payloadSize >= sizeof(t_FlashInfoPayload))
|
||||
{
|
||||
memcpy(drvSummary, info->driverSummary, FLASHHDR_DRIVER_SUMMARY_SIZE);
|
||||
drvSummary[FLASHHDR_DRIVER_SUMMARY_SIZE - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
drvSummary[0] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
free(infoBuf);
|
||||
|
||||
@@ -476,39 +476,41 @@ bool WiFi::stringReplace(std::string &str, const std::string &from, const std::s
|
||||
//
|
||||
bool WiFi::setFloppyDiskFile(const std::string ¶m1, int diskNo)
|
||||
{
|
||||
// Check for out of bounds values.
|
||||
if (diskNo > WIFI_MAX_FLOPPY_DISK_IMAGES)
|
||||
return (false);
|
||||
if (diskNo < 0 || diskNo >= WIFI_MAX_FLOPPY_DISK_IMAGES)
|
||||
return false;
|
||||
|
||||
// Save the floppy disk image name for later use.
|
||||
this->wifiCtrl.run.floppyDiskImage[diskNo] = param1;
|
||||
return (true);
|
||||
if (this->wifiCtrl.run.floppyDiskImage[diskNo] != param1)
|
||||
{
|
||||
this->wifiCtrl.run.floppyDiskImage[diskNo] = param1;
|
||||
ESP_LOGI(WIFITAG, "Floppy %d: %s", diskNo, param1.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method to change the name of the quick disk image indexed by 'diskNo'.
|
||||
//
|
||||
bool WiFi::setQuickDiskFile(const std::string ¶m1, int diskNo)
|
||||
{
|
||||
// Check for out of bounds values.
|
||||
if (diskNo > WIFI_MAX_QUICK_DISK_IMAGES)
|
||||
return (false);
|
||||
if (diskNo < 0 || diskNo >= WIFI_MAX_QUICK_DISK_IMAGES)
|
||||
return false;
|
||||
|
||||
// Save the floppy disk image name for later use.
|
||||
this->wifiCtrl.run.quickDiskImage[diskNo] = param1;
|
||||
return (true);
|
||||
if (this->wifiCtrl.run.quickDiskImage[diskNo] != param1)
|
||||
{
|
||||
this->wifiCtrl.run.quickDiskImage[diskNo] = param1;
|
||||
ESP_LOGI(WIFITAG, "Quick Disk %d: %s", diskNo, param1.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method to change the name of the ramfile backup image indexed by 'ramfileNo'.
|
||||
//
|
||||
bool WiFi::setRamFile(const std::string ¶m1, int ramfileNo)
|
||||
{
|
||||
// Check for out of bounds values.
|
||||
if (ramfileNo > WIFI_MAX_RAMFILE_IMAGES)
|
||||
return (false);
|
||||
if (ramfileNo < 0 || ramfileNo >= WIFI_MAX_RAMFILE_IMAGES)
|
||||
return false;
|
||||
|
||||
// Save the floppy disk image name for later use.
|
||||
this->wifiCtrl.run.ramFileImage[ramfileNo] = param1;
|
||||
return (true);
|
||||
if (this->wifiCtrl.run.ramFileImage[ramfileNo] != param1)
|
||||
{
|
||||
this->wifiCtrl.run.ramFileImage[ramfileNo] = param1;
|
||||
ESP_LOGI(WIFITAG, "RAMFILE %d: %s", ramfileNo, param1.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method to unpack a gzipped and/or tar file. The extension of the srcFile determines the action taken.
|
||||
@@ -739,14 +741,16 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_USBNCMVISIBLE%";
|
||||
#if !defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
keyValue.value = "";
|
||||
#else
|
||||
keyValue.value = "style=\"display:none\"";
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_NETPANELTITLE%";
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
keyValue.value = "Network Configuration";
|
||||
#elif defined(CONFIG_IF_WIFI_ENABLED)
|
||||
keyValue.value = "WiFi Configuration";
|
||||
#else
|
||||
keyValue.value = "Network Configuration";
|
||||
@@ -910,6 +914,23 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
|
||||
keyValue.value = (ai && ai->description[0]) ? std::string(ai->description) : "N/A";
|
||||
pairs.push_back(keyValue);
|
||||
|
||||
keyValue.name = "%SK_FWAUTHOR%";
|
||||
keyValue.value = (ai && ai->author[0]) ? std::string(ai->author) : "N/A";
|
||||
pairs.push_back(keyValue);
|
||||
|
||||
keyValue.name = "%SK_FWLICENSE%";
|
||||
keyValue.value = (ai && ai->license[0]) ? std::string(ai->license) : "N/A";
|
||||
pairs.push_back(keyValue);
|
||||
|
||||
keyValue.name = "%SK_FWCOPYRIGHT%";
|
||||
keyValue.value = (ai && ai->copyright[0]) ? std::string(ai->copyright) : "N/A";
|
||||
pairs.push_back(keyValue);
|
||||
|
||||
// Active drivers — populated by RP2350 via INF IPC command.
|
||||
keyValue.name = "%SK_ACTIVEDRIVERS%";
|
||||
keyValue.value = (wifiCtrl.run.rp2350DriverSummary[0]) ? std::string(wifiCtrl.run.rp2350DriverSummary) : "N/A";
|
||||
pairs.push_back(keyValue);
|
||||
|
||||
keyValue.name = "%SK_CPUCLOCK%";
|
||||
keyValue.value = (wifiCtrl.run.rp2350CpuFreq > 0) ?
|
||||
std::to_string(wifiCtrl.run.rp2350CpuFreq / 1000000) + " MHz" : "N/A";
|
||||
@@ -1072,6 +1093,12 @@ esp_err_t WiFi::versionedRename(const std::string &src, const std::string &dst,
|
||||
}
|
||||
|
||||
// dst is now free — rename src into place.
|
||||
// If src == dst this is a backup-only operation (the dst was already versioned
|
||||
// above), so skip the rename — there's nothing to move.
|
||||
if (src == dst)
|
||||
{
|
||||
return ESP_OK;
|
||||
}
|
||||
if (rename(src.c_str(), dst.c_str()) != 0)
|
||||
{
|
||||
errMsg = "Failed to rename " + src + " -> " + dst;
|
||||
@@ -1109,6 +1136,9 @@ esp_err_t WiFi::expandAndSendFile(httpd_req_t *req, const char *basePath, const
|
||||
inFile.close();
|
||||
ESP_LOGI(WIFITAG, "expandAndSendFile: %s loaded (%d bytes)", fileName.c_str(), (int)contents.length());
|
||||
|
||||
// Close connection after response — see defaultFileHandler for rationale.
|
||||
httpd_resp_set_hdr(req, "Connection", "close");
|
||||
|
||||
if (contents.find("%SK_") == std::string::npos)
|
||||
{
|
||||
// No template variables — send entire file as a single HTTP chunk.
|
||||
@@ -1292,6 +1322,15 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
|
||||
std::string disposition = "";
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
// Per-request local variables — the shared wifiCtrl.session struct must NOT be
|
||||
// used here because multiple requests are handled concurrently by esp_http_server.
|
||||
// Using the shared session caused a race condition where parallel requests
|
||||
// overwrote each other's filePath/fileName/gzip fields, leaving some responses
|
||||
// (typically jquery.min.js) stuck forever in "pending" state.
|
||||
std::string localFilePath;
|
||||
std::string localFileName;
|
||||
bool localGzip = false;
|
||||
|
||||
// Retrieve pointer to object in order to access data.
|
||||
WiFi *pThis = (WiFi *) req->user_ctx;
|
||||
|
||||
@@ -1312,18 +1351,17 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
|
||||
buf.reset(new char[bufLen]);
|
||||
if (buf != nullptr && httpd_req_get_hdr_value_str(req, "Accept-Encoding", buf.get(), bufLen) == ESP_OK)
|
||||
{
|
||||
pThis->wifiCtrl.session.gzip = (strstr(buf.get(), "gzip") != NULL);
|
||||
pThis->wifiCtrl.session.deflate = (strstr(buf.get(), "deflate") != NULL);
|
||||
localGzip = (strstr(buf.get(), "gzip") != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a filename in the URI and construct the file path returning both.
|
||||
result = pThis->getPathFromURI(pThis->wifiCtrl.session.filePath, pThis->wifiCtrl.session.fileName, pThis->wifiCtrl.run.basePath, req->uri);
|
||||
result = pThis->getPathFromURI(localFilePath, localFileName, pThis->wifiCtrl.run.basePath, req->uri);
|
||||
if (result == ESP_FAIL)
|
||||
{
|
||||
if (strlen(req->uri) == 1 && req->uri[0] == '/')
|
||||
{
|
||||
pThis->wifiCtrl.session.fileName = "/";
|
||||
localFileName = "/";
|
||||
result = ESP_OK;
|
||||
}
|
||||
else
|
||||
@@ -1336,20 +1374,20 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
|
||||
if (result == ESP_OK)
|
||||
{
|
||||
// See if the provided name matches a static handler, such as root.
|
||||
if (pThis->wifiCtrl.session.fileName == "/" || pThis->wifiCtrl.session.fileName == "index.html" || pThis->wifiCtrl.session.fileName == "index.htm")
|
||||
if (localFileName == "/" || localFileName == "index.html" || localFileName == "index.htm")
|
||||
{
|
||||
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, "index.htm");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get details of the file.
|
||||
if (stat(pThis->wifiCtrl.session.filePath.c_str(), &fileStat) == -1)
|
||||
if (stat(localFilePath.c_str(), &fileStat) == -1)
|
||||
{
|
||||
if (pThis->wifiCtrl.session.gzip)
|
||||
if (localGzip)
|
||||
{
|
||||
gzipFile = pThis->wifiCtrl.session.filePath + ".gz";
|
||||
gzipFile = localFilePath + ".gz";
|
||||
}
|
||||
if (pThis->wifiCtrl.session.gzip && stat(gzipFile.c_str(), &fileStat) == -1)
|
||||
if (localGzip && stat(gzipFile.c_str(), &fileStat) == -1)
|
||||
{
|
||||
result = ESP_FAIL;
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
|
||||
@@ -1367,22 +1405,22 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Opened file : %s ", pThis->wifiCtrl.session.filePath.c_str());
|
||||
ESP_LOGI(WIFITAG, "Opened file : %s ", localFilePath.c_str());
|
||||
}
|
||||
|
||||
if (result == ESP_OK)
|
||||
{
|
||||
// If the file is HTML, JS or CSS then process externally.
|
||||
if ((pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".html") || pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".htm") ||
|
||||
(pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".js") && !pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".min.js")) ||
|
||||
(pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".css") && !pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".min.css"))) &&
|
||||
if ((pThis->isFileExt(localFileName, ".html") || pThis->isFileExt(localFileName, ".htm") ||
|
||||
(pThis->isFileExt(localFileName, ".js") && !pThis->isFileExt(localFileName, ".min.js")) ||
|
||||
(pThis->isFileExt(localFileName, ".css") && !pThis->isFileExt(localFileName, ".min.css"))) &&
|
||||
gzipFile.empty())
|
||||
{
|
||||
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, pThis->wifiCtrl.session.fileName);
|
||||
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, localFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
fd = fopen(gzipFile.empty() ? pThis->wifiCtrl.session.filePath.c_str() : gzipFile.c_str(), "r");
|
||||
fd = fopen(gzipFile.empty() ? localFilePath.c_str() : gzipFile.c_str(), "r");
|
||||
if (!fd)
|
||||
{
|
||||
result = ESP_FAIL;
|
||||
@@ -1393,9 +1431,17 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
|
||||
ESP_LOGI(WIFITAG,
|
||||
"Sending %sfile : %s (%ld bytes)...",
|
||||
gzipFile.empty() ? "" : "gzip ",
|
||||
pThis->wifiCtrl.session.fileName.c_str(),
|
||||
localFileName.c_str(),
|
||||
fileStat.st_size);
|
||||
result = pThis->setContentTypeFromFileType(req, pThis->wifiCtrl.session.fileName);
|
||||
// Close connection after each file response. The server is
|
||||
// single-threaded — while sending a large file (e.g. 98KB font),
|
||||
// all other requests are blocked. With keep-alive, the browser
|
||||
// queues subsequent requests on existing connections that may be
|
||||
// stalled behind a large transfer, leaving them "pending" forever.
|
||||
// With Connection:close, the browser opens fresh connections for
|
||||
// each resource. With 20 sockets available this works well.
|
||||
httpd_resp_set_hdr(req, "Connection", "close");
|
||||
result = pThis->setContentTypeFromFileType(req, localFileName);
|
||||
if (result == ESP_OK)
|
||||
{
|
||||
std::unique_ptr<char[]> chunk(new char[MAX_CHUNK_SIZE]);
|
||||
@@ -1525,36 +1571,112 @@ esp_err_t WiFi::defaultDataGETHandler(httpd_req_t *req)
|
||||
// Match URI and execute required data retrieval and return.
|
||||
if (uriStr == "wifistatus")
|
||||
{
|
||||
// Lightweight JSON endpoint for AJAX polling of WiFi signal + system status.
|
||||
char json[256];
|
||||
// JSON endpoint for AJAX polling of WiFi + system + RP2350 status.
|
||||
// All fields that can change at runtime are included so the dashboard
|
||||
// updates live without requiring a page refresh.
|
||||
int8_t txPwr = 0;
|
||||
int rssi = 0;
|
||||
bool connected = pThis->wifiCtrl.client.connected;
|
||||
const char *ip = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
|
||||
? pThis->wifiCtrl.ap.ip : pThis->wifiCtrl.client.ip;
|
||||
const char *nm = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
|
||||
? pThis->wifiCtrl.ap.netmask : pThis->wifiCtrl.client.netmask;
|
||||
const char *gw = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
|
||||
? pThis->wifiCtrl.ap.gateway : pThis->wifiCtrl.client.gateway;
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
esp_wifi_get_max_tx_power(&txPwr);
|
||||
wifi_ap_record_t apInfo;
|
||||
if (connected && esp_wifi_sta_get_ap_info(&apInfo) == ESP_OK)
|
||||
rssi = apInfo.rssi;
|
||||
#endif
|
||||
// Compute uptime string
|
||||
// Compute uptime string.
|
||||
int64_t us = esp_timer_get_time();
|
||||
int secs = (int) (us / 1000000);
|
||||
int days = secs / 86400;
|
||||
secs %= 86400;
|
||||
int hrs = secs / 3600;
|
||||
secs %= 3600;
|
||||
int mins = secs / 60;
|
||||
secs %= 60;
|
||||
int days = secs / 86400; secs %= 86400;
|
||||
int hrs = secs / 3600; secs %= 3600;
|
||||
int mins = secs / 60; secs %= 60;
|
||||
char uptimeBuf[32];
|
||||
if (days > 0)
|
||||
snprintf(uptimeBuf, sizeof(uptimeBuf), "%dd %02d:%02d:%02d", days, hrs, mins, secs);
|
||||
else
|
||||
snprintf(uptimeBuf, sizeof(uptimeBuf), "%02d:%02d:%02d", hrs, mins, secs);
|
||||
snprintf(json, sizeof(json),
|
||||
"{\"rssi\":%d,\"txPower\":%.1f,\"connected\":%s,\"uptime\":\"%s\"}",
|
||||
rssi, txPwr * 0.25f, connected ? "true" : "false", uptimeBuf);
|
||||
|
||||
// RP2350 partition header data (populated by INF IPC from RP2350).
|
||||
uint8_t activeApp = pThis->wifiCtrl.run.rp2350FlashHeader.activeApp;
|
||||
bool haveInfo = (pThis->wifiCtrl.run.rp2350FlashHeader.configured == FLASH_APP_CONFIGURED_FLAG &&
|
||||
activeApp >= 1 && activeApp < FLASH_APP_MAX_INSTANCES);
|
||||
t_FlashPartitionInstance *ai = haveInfo ? &pThis->wifiCtrl.run.rp2350FlashHeader.config[activeApp] : NULL;
|
||||
|
||||
const char *fwVer = (ai && ai->version[0]) ? ai->version : "";
|
||||
const char *fwDate = (ai && ai->versionDate[0]) ? ai->versionDate : "";
|
||||
const char *persona = (ai && ai->description[0]) ? ai->description : "";
|
||||
const char *drvSummary = pThis->wifiCtrl.run.rp2350DriverSummary[0] ? pThis->wifiCtrl.run.rp2350DriverSummary : "";
|
||||
int cpuMHz = pThis->wifiCtrl.run.rp2350CpuFreq / 1000000;
|
||||
int psramMHz = pThis->wifiCtrl.run.rp2350PsramFreq / 1000000;
|
||||
int flashMB = pThis->wifiCtrl.run.rp2350FlashSize / (1024 * 1024);
|
||||
int psramMB = pThis->wifiCtrl.run.rp2350PsramSize / (1024 * 1024);
|
||||
uint32_t hostClkHz = pThis->wifiCtrl.run.rp2350HostClkHz;
|
||||
uint32_t emulSpeedHz = pThis->wifiCtrl.run.rp2350EmulSpeedHz;
|
||||
|
||||
// Build RP2350 partition table HTML for live update.
|
||||
std::ostringstream rpParts;
|
||||
for (int idx = 0; idx < FLASH_APP_MAX_INSTANCES; idx++)
|
||||
{
|
||||
const t_FlashPartitionInstance *pi = &pThis->wifiCtrl.run.rp2350FlashHeader.config[idx];
|
||||
std::string active = (idx == activeApp && haveInfo) ? "Yes" : "No";
|
||||
rpParts << "<tr>"
|
||||
<< "<td>" << idx << "</td>"
|
||||
<< "<td>" << to_str(pi->addr, 0, 16) << "</td>"
|
||||
<< "<td>" << to_str(pi->size, 0, 16) << "</td>"
|
||||
<< "<td>" << to_str(pi->chksum, 0, 16) << "</td>"
|
||||
<< "<td>" << active << "</td>"
|
||||
<< "<td>" << pi->license << "</td>"
|
||||
<< "<td>" << pi->author << "</td>"
|
||||
<< "<td>" << pi->description << "</td>"
|
||||
<< "<td>" << pi->version << "</td>"
|
||||
<< "<td>" << pi->versionDate << "</td>"
|
||||
<< "<td>" << pi->copyright << "</td>"
|
||||
<< "</tr>";
|
||||
}
|
||||
// Escape the HTML for safe embedding in JSON string value.
|
||||
std::string rpPartsHtml = rpParts.str();
|
||||
std::string rpPartsEsc;
|
||||
rpPartsEsc.reserve(rpPartsHtml.size() + 64);
|
||||
for (char c : rpPartsHtml)
|
||||
{
|
||||
if (c == '"') rpPartsEsc += "\\\"";
|
||||
else if (c == '\\') rpPartsEsc += "\\\\";
|
||||
else if (c == '\n') rpPartsEsc += "\\n";
|
||||
else rpPartsEsc += c;
|
||||
}
|
||||
|
||||
// Build JSON response using std::string (partition HTML can be large).
|
||||
std::ostringstream json;
|
||||
json << "{\"rssi\":" << rssi
|
||||
<< ",\"txPower\":" << (txPwr * 0.25f)
|
||||
<< ",\"connected\":" << (connected ? "true" : "false")
|
||||
<< ",\"uptime\":\"" << uptimeBuf << "\""
|
||||
<< ",\"ip\":\"" << (ip[0] ? ip : "") << "\""
|
||||
<< ",\"netmask\":\"" << (nm[0] ? nm : "") << "\""
|
||||
<< ",\"gateway\":\"" << (gw[0] ? gw : "") << "\""
|
||||
<< ",\"fwVersion\":\"" << fwVer << "\""
|
||||
<< ",\"fwDate\":\"" << fwDate << "\""
|
||||
<< ",\"activePartition\":" << (haveInfo ? std::to_string(activeApp) : "null")
|
||||
<< ",\"persona\":\"" << persona << "\""
|
||||
<< ",\"drivers\":\"" << drvSummary << "\""
|
||||
<< ",\"rp2350Clock\":" << (cpuMHz > 0 ? std::to_string(cpuMHz) : "null")
|
||||
<< ",\"psramClock\":" << (psramMHz > 0 ? std::to_string(psramMHz) : "null")
|
||||
<< ",\"rp2350Flash\":" << (flashMB > 0 ? std::to_string(flashMB) : "null")
|
||||
<< ",\"rp2350Psram\":" << (psramMB > 0 ? std::to_string(psramMB) : "null")
|
||||
<< ",\"hostClkHz\":" << (hostClkHz > 0 ? std::to_string(hostClkHz) : "null")
|
||||
<< ",\"emulSpeedHz\":" << (emulSpeedHz > 0 ? std::to_string(emulSpeedHz) : "null")
|
||||
<< ",\"rpPartitions\":\"" << rpPartsEsc << "\""
|
||||
<< ",\"floppy1\":\"" << (pThis->wifiCtrl.run.floppyDiskImage[0].empty() ? "none" : pThis->wifiCtrl.run.floppyDiskImage[0]) << "\""
|
||||
<< ",\"floppy2\":\"" << (pThis->wifiCtrl.run.floppyDiskImage[1].empty() ? "none" : pThis->wifiCtrl.run.floppyDiskImage[1]) << "\""
|
||||
<< ",\"qdisk\":\"" << (pThis->wifiCtrl.run.quickDiskImage[0].empty() ? "none" : pThis->wifiCtrl.run.quickDiskImage[0]) << "\""
|
||||
<< "}";
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, json);
|
||||
httpd_resp_sendstr(req, json.str().c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
else if (uriStr.substr(0, 6) == "rename")
|
||||
@@ -3475,6 +3597,12 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
|
||||
htmlStr.append("<button id=\"mkdir\" class=\"fa fa-folder-o wm-button-small\" aria-hidden=\"true\" title=\"Create Directory\" type=\"button\" "
|
||||
"onclick=\"mkdir()\"></button>");
|
||||
htmlStr.append("</td></tr>\n");
|
||||
htmlStr.append("<tr id=\"uploadProgressRow\" style=\"display:none;\">"
|
||||
"<td colspan=\"4\">"
|
||||
"<div style=\"background:#444;border-radius:4px;height:22px;width:100%;position:relative;\">"
|
||||
"<div id=\"uploadProgressBar\" style=\"background:#5cb85c;height:100%;border-radius:4px;width:0%;transition:width 0.2s;\"></div>"
|
||||
"<span id=\"uploadProgressText\" style=\"position:absolute;top:0;left:0;width:100%;text-align:center;line-height:22px;color:#fff;font-size:12px;\"></span>"
|
||||
"</div></td></tr>\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3493,6 +3621,12 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
|
||||
htmlStr.append("<button id=\"mkdir\" class=\"fa fa-folder-o wm-button-small\" aria-hidden=\"true\" title=\"Create Directory\" type=\"button\" "
|
||||
"onclick=\"mkdir()\"></button>");
|
||||
htmlStr.append("</td></tr>\n");
|
||||
htmlStr.append("<tr id=\"uploadProgressRow\" style=\"display:none;\">"
|
||||
"<td colspan=\"4\">"
|
||||
"<div style=\"background:#444;border-radius:4px;height:22px;width:100%;position:relative;\">"
|
||||
"<div id=\"uploadProgressBar\" style=\"background:#5cb85c;height:100%;border-radius:4px;width:0%;transition:width 0.2s;\"></div>"
|
||||
"<span id=\"uploadProgressText\" style=\"position:absolute;top:0;left:0;width:100%;text-align:center;line-height:22px;color:#fff;font-size:12px;\"></span>"
|
||||
"</div></td></tr>\n");
|
||||
}
|
||||
|
||||
int entryCnt = 1;
|
||||
@@ -4351,10 +4485,12 @@ bool WiFi::startWebserver(void)
|
||||
// Keep keep-alive enabled (HTTP/1.1 default) so the browser can multiplex
|
||||
// multiple resource requests over one TCP connection. USB NCM at Full Speed
|
||||
// (12 Mbps) is much slower than WiFi, so active transfers hold sockets longer.
|
||||
// 20 sockets with a 5s idle timeout gives enough headroom for pages that load
|
||||
// 12+ resources in parallel without LRU-purging active transfers.
|
||||
config.recv_wait_timeout = 5;
|
||||
config.send_wait_timeout = 10;
|
||||
// Short recv_wait_timeout aggressively recycles idle keep-alive connections.
|
||||
// With 20 sockets available, the browser can always open fresh connections.
|
||||
// Longer timeouts (5-15s) cause stale keep-alive connections from previous
|
||||
// page loads to hold sockets, triggering LRU purge of active transfers.
|
||||
config.recv_wait_timeout = 1;
|
||||
config.send_wait_timeout = 15;
|
||||
|
||||
// Setup the required paths and descriptors then register them with the server.
|
||||
const httpd_uri_t dataPOST = {.uri = "/data", .method = HTTP_POST, .handler = defaultDataPOSTHandler, .user_ctx = this};
|
||||
@@ -5046,6 +5182,14 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
|
||||
strncpy(wifiCtrl.run.fsPath, fsPath, FILE_PATH_MAX);
|
||||
wifiCtrl.run.versionList = versionList;
|
||||
memset(&wifiCtrl.run.rp2350FlashHeader, 0, sizeof(t_FlashPartitionHeader));
|
||||
wifiCtrl.run.rp2350CpuFreq = 0;
|
||||
wifiCtrl.run.rp2350PsramFreq = 0;
|
||||
wifiCtrl.run.rp2350Voltage = 0;
|
||||
wifiCtrl.run.rp2350FlashSize = 0;
|
||||
wifiCtrl.run.rp2350PsramSize = 0;
|
||||
wifiCtrl.run.rp2350HostClkHz = 0;
|
||||
wifiCtrl.run.rp2350EmulSpeedHz = 0;
|
||||
wifiCtrl.run.rp2350DriverSummary[0] = '\0';
|
||||
// Wire SDCard's rp2350Header pointer directly to our rp2350FlashHeader so
|
||||
// SDCard::storeRP2350Info() can populate it when the INF IPC command arrives.
|
||||
if (sdcard)
|
||||
@@ -5128,6 +5272,15 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
|
||||
wifiConfig.params.txPower = 0;
|
||||
}
|
||||
|
||||
// Sync the runtime wifiMode from the persisted config so that template
|
||||
// expansion shows the correct WiFi mode (AP vs Client) even before
|
||||
// wifi->run() is called. Without this, pages loaded via USB NCM before
|
||||
// WiFi connects would show AP config instead of Client config.
|
||||
if (wifiConfig.params.wifiMode == WIFI_CONFIG_CLIENT || wifiConfig.params.wifiMode == WIFI_CONFIG_AP)
|
||||
{
|
||||
wifiCtrl.run.wifiMode = wifiConfig.params.wifiMode;
|
||||
}
|
||||
|
||||
// Process JSON and override any setting specified in config.
|
||||
// Check we have a valid handle.
|
||||
if (config == NULL)
|
||||
|
||||
@@ -155,8 +155,10 @@ class CommandProcessor : public WiFi, FSPI, SDCard
|
||||
// WiFi tracking for floppy/QD:
|
||||
if (frame.command == IPCF_CMD_RFD)
|
||||
wifi.setFloppyDiskFile(std::string(frame.filename), frame.diskNo);
|
||||
else if (frame.command == IPCF_CMD_RQD || frame.command == IPCF_CMD_RRF)
|
||||
else if (frame.command == IPCF_CMD_RQD)
|
||||
wifi.setQuickDiskFile(std::string(frame.filename), frame.diskNo);
|
||||
else if (frame.command == IPCF_CMD_RRF)
|
||||
wifi.setRamFile(std::string(frame.filename), frame.diskNo);
|
||||
sdcard.readFileViaSPI(frame, fspi);
|
||||
}
|
||||
|
||||
|
||||
3
projects/tzpuPico/esp32/main/include/WiFi.h
vendored
3
projects/tzpuPico/esp32/main/include/WiFi.h
vendored
@@ -324,6 +324,9 @@ class WiFi
|
||||
int32_t rp2350Voltage; // Core voltage (from INF)
|
||||
uint32_t rp2350FlashSize; // RP2350 Flash size in bytes (from INF)
|
||||
uint32_t rp2350PsramSize; // RP2350 PSRAM size in bytes (from INF)
|
||||
uint32_t rp2350HostClkHz; // Measured host clock frequency in Hz (from INF)
|
||||
uint32_t rp2350EmulSpeedHz; // Effective Z80 emulation speed in Hz (from INF)
|
||||
char rp2350DriverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers (from INF)
|
||||
|
||||
// Active Floppy Disk images.
|
||||
std::string floppyDiskImage[WIFI_MAX_FLOPPY_DISK_IMAGES];
|
||||
|
||||
@@ -152,6 +152,9 @@ typedef struct
|
||||
} t_FlashPartitionHeader;
|
||||
|
||||
// Extended INF payload — partition header + runtime core config.
|
||||
// Maximum size of the active driver summary string sent via INF IPC.
|
||||
#define FLASHHDR_DRIVER_SUMMARY_SIZE 128
|
||||
|
||||
// Sent by RP2350 ESP_sendVersionInfo, received by ESP32 storeRP2350Info.
|
||||
typedef struct
|
||||
{
|
||||
@@ -161,6 +164,9 @@ typedef struct
|
||||
int32_t voltage; // Core voltage setting (VREG enum)
|
||||
uint32_t flashSize; // RP2350 Flash size in bytes
|
||||
uint32_t psramSize; // RP2350 PSRAM size in bytes
|
||||
uint32_t hostClkHz; // Measured host clock frequency in Hz
|
||||
uint32_t emulSpeedHz; // Effective Z80 emulation speed in Hz
|
||||
char driverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers and interfaces
|
||||
} t_FlashInfoPayload;
|
||||
|
||||
// Structure to describe a single file stored in ROM. Indexed by its filename (matching a filename appearing in JSON including path)
|
||||
|
||||
@@ -319,6 +319,7 @@ void buildVersionList(WiFi::t_versionList *versionList, NVS &nvs, SDCard &sdcard
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
|
||||
static esp_netif_t *s_usb_ncm_netif = NULL;
|
||||
static volatile bool g_usbReady = false; // Set by USB task when setup completes.
|
||||
|
||||
static void usb_ncm_l2_free(void *h, void *buffer)
|
||||
{
|
||||
@@ -357,13 +358,16 @@ static esp_err_t usb_ncm_recv_callback(void *buffer, uint16_t len, void *ctx)
|
||||
// For this reason, esp_netif_init() and esp_event_loop_create_default() are
|
||||
// called here (both are idempotent / tolerate double-init) so the netif can
|
||||
// be created immediately after the TinyUSB NCM class is initialized.
|
||||
bool setupUSBConsole(void)
|
||||
// USB setup task — runs asynchronously so the main task can start the
|
||||
// CommandProcessor (SPI slave) without waiting for USB delays. The 2-second
|
||||
// disconnect/reconnect cycle that macOS needs would otherwise block SPI
|
||||
// slave init, causing the RP2350's first sector reads to fail.
|
||||
void setupUSBTask(void *pvParameters)
|
||||
{
|
||||
bool cdcOk = false;
|
||||
bool ncmOk = false;
|
||||
|
||||
// 0. Initialise the TCP/IP stack and event loop early so we can create
|
||||
// the esp_netif before the host starts probing.
|
||||
// 0. Initialise the TCP/IP stack and event loop (idempotent / tolerates double-init).
|
||||
esp_netif_init();
|
||||
esp_event_loop_create_default();
|
||||
|
||||
@@ -372,19 +376,18 @@ bool setupUSBConsole(void)
|
||||
tusb_cfg.external_phy = false;
|
||||
esp_err_t ret = tinyusb_driver_install(&tusb_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
return false;
|
||||
ESP_LOGE(MAINTAG, "USB: TinyUSB driver install failed");
|
||||
g_usbReady = true; // Signal ready (even on failure) so main loop doesn't wait forever.
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Force a clean USB disconnect/reconnect cycle. After a software reboot
|
||||
// (esp_restart), the USB peripheral may not fully reset — the host sees a
|
||||
// glitch rather than a proper disconnect. macOS then tries to resume the
|
||||
// previous NCM session state, which fails and leaves the link "inactive".
|
||||
// By explicitly pulling D+ low (tud_disconnect) and then re-enabling it
|
||||
// (tud_connect), the host sees a clean device removal followed by a fresh
|
||||
// enumeration, regardless of whether this was a power cycle or soft reboot.
|
||||
// 2. Force a clean USB disconnect/reconnect cycle. These delays run in this
|
||||
// background task so the main task is free to start the CommandProcessor.
|
||||
tud_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
tud_connect();
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
// 3. Initialise CDC-ACM for serial logging.
|
||||
tinyusb_config_cdcacm_t acm_cfg = {};
|
||||
@@ -400,8 +403,7 @@ bool setupUSBConsole(void)
|
||||
ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
|
||||
ncmOk = (ret == ESP_OK);
|
||||
|
||||
// 5. Create the lwIP netif IMMEDIATELY so received packets are handled
|
||||
// as soon as the host starts probing (ARP, NDP, DHCP).
|
||||
// 5. Create the lwIP netif so received packets are handled immediately.
|
||||
if (ncmOk) {
|
||||
esp_netif_ip_info_t ip_info = {};
|
||||
ip_info.ip.addr = ipaddr_addr(CONFIG_IF_USB_NCM_IP);
|
||||
@@ -434,19 +436,21 @@ bool setupUSBConsole(void)
|
||||
s_usb_ncm_netif = esp_netif_new(&cfg);
|
||||
if (s_usb_ncm_netif != NULL) {
|
||||
esp_netif_set_mac(s_usb_ncm_netif, lwip_mac);
|
||||
uint32_t lease_opt = 120; // DHCP lease time in minutes
|
||||
uint32_t lease_opt = 120;
|
||||
esp_netif_dhcps_option(s_usb_ncm_netif, ESP_NETIF_OP_SET,
|
||||
ESP_NETIF_IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
|
||||
esp_netif_action_start(s_usb_ncm_netif, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Redirect stdout/stderr/stdin to the TinyUSB CDC-ACM port.
|
||||
// 6. Redirect console to TinyUSB CDC-ACM.
|
||||
if (cdcOk) {
|
||||
esp_tusb_init_console(TINYUSB_CDC_ACM_0);
|
||||
}
|
||||
|
||||
return cdcOk;
|
||||
ESP_LOGI(MAINTAG, "USB setup complete (CDC:%s NCM:%s)", cdcOk ? "OK" : "FAIL", ncmOk ? "OK" : "FAIL");
|
||||
g_usbReady = true;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
#endif // CONFIG_IF_USB_NCM_ENABLED
|
||||
|
||||
@@ -811,12 +815,11 @@ extern "C"
|
||||
IO_init(LOGGING_FRAMED);
|
||||
#endif
|
||||
|
||||
// Install TinyUSB composite device (CDC-ACM + NCM) and redirect console
|
||||
// to CDC-ACM as early as possible so all subsequent ESP_LOG output is visible.
|
||||
// Launch USB setup (TinyUSB + CDC + NCM + netif) as a background task.
|
||||
// The 2-second disconnect/reconnect cycle runs asynchronously so the main
|
||||
// task can proceed immediately to start the CommandProcessor (SPI slave).
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
if (setupUSBConsole()) {
|
||||
ESP_LOGW(MAINTAG, "USB CDC-ACM console active — log output on TinyUSB serial port.");
|
||||
}
|
||||
xTaskCreate(setupUSBTask, "usbSetup", 8192, NULL, 5, NULL);
|
||||
#endif
|
||||
|
||||
// Phase 1: fast setup — eFUSE, NVS, SPI slave (~60 ms).
|
||||
@@ -937,10 +940,17 @@ extern "C"
|
||||
if (!wifiStarted && loopCount > 100)
|
||||
{
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
// USB NCM network + lwIP netif were already created in setupUSBConsole().
|
||||
// Start the webserver now so USB NCM is immediately browsable.
|
||||
// The HTTP server binds to INADDR_ANY:80 so it will also serve on
|
||||
// WiFi once that connection is established.
|
||||
// Wait for the async USB setup task to complete before starting
|
||||
// the webserver. This is non-blocking in the sense that the SPI
|
||||
// slave and SD card are already running while we wait here.
|
||||
if (!g_usbReady)
|
||||
{
|
||||
// USB task still running — skip this iteration, try next loop.
|
||||
++loopCount;
|
||||
vTaskDelay(10);
|
||||
continue;
|
||||
}
|
||||
// USB NCM network + lwIP netif are now ready.
|
||||
ESP_LOGW(MAINTAG, "Starting webserver on USB NCM.");
|
||||
wifi->startWebserver();
|
||||
#endif
|
||||
|
||||
12
projects/tzpuPico/esp32/sdkconfig
vendored
12
projects/tzpuPico/esp32/sdkconfig
vendored
@@ -588,7 +588,13 @@ CONFIG_SD_CDDETECT=21
|
||||
#
|
||||
# WiFi
|
||||
#
|
||||
# CONFIG_IF_WIFI_ENABLED is not set
|
||||
CONFIG_IF_WIFI_ENABLED=y
|
||||
CONFIG_IF_WIFI_SSID="pZ80"
|
||||
CONFIG_IF_WIFI_DEFAULT_SSID_PWD="pZ80pZ80"
|
||||
CONFIG_IF_WIFI_MAX_RETRIES=10
|
||||
CONFIG_IF_WIFI_AP_CHANNEL=7
|
||||
CONFIG_IF_WIFI_SSID_HIDDEN=0
|
||||
CONFIG_IF_WIFI_MAX_CONNECTIONS=5
|
||||
# end of WiFi
|
||||
|
||||
#
|
||||
@@ -1951,8 +1957,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
#
|
||||
# TCP
|
||||
#
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=16
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=16
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=24
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=24
|
||||
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
|
||||
CONFIG_LWIP_TCP_MAXRTX=12
|
||||
CONFIG_LWIP_TCP_SYNMAXRTX=12
|
||||
|
||||
@@ -1956,8 +1956,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
#
|
||||
# TCP
|
||||
#
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=16
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=16
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=24
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=24
|
||||
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
|
||||
CONFIG_LWIP_TCP_MAXRTX=12
|
||||
CONFIG_LWIP_TCP_SYNMAXRTX=12
|
||||
|
||||
@@ -1962,8 +1962,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
#
|
||||
# TCP
|
||||
#
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=16
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=16
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=24
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=24
|
||||
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
|
||||
CONFIG_LWIP_TCP_MAXRTX=12
|
||||
CONFIG_LWIP_TCP_SYNMAXRTX=12
|
||||
|
||||
@@ -1960,8 +1960,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
#
|
||||
# TCP
|
||||
#
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=16
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=16
|
||||
CONFIG_LWIP_MAX_ACTIVE_TCP=24
|
||||
CONFIG_LWIP_MAX_LISTENING_TCP=24
|
||||
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
|
||||
CONFIG_LWIP_TCP_MAXRTX=12
|
||||
CONFIG_LWIP_TCP_SYNMAXRTX=12
|
||||
|
||||
2
projects/tzpuPico/esp32/tzpuPico_version.txt
vendored
2
projects/tzpuPico/esp32/tzpuPico_version.txt
vendored
@@ -1 +1 @@
|
||||
2.0
|
||||
2.01
|
||||
|
||||
2
projects/tzpuPico/esp32/version.txt
vendored
2
projects/tzpuPico/esp32/version.txt
vendored
@@ -1 +1 @@
|
||||
2.27
|
||||
2.46
|
||||
|
||||
8
projects/tzpuPico/esp32/webserver/config.htm
vendored
8
projects/tzpuPico/esp32/webserver/config.htm
vendored
@@ -95,9 +95,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
</ul>
|
||||
@@ -117,7 +117,7 @@
|
||||
</ol>
|
||||
<div class="alert alert-success alert-dismissable justify">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<p>This is the JSON configuration editor to define the %SK_PROCESSOR% and ESP32 configuration. Please read the documentation if you need help.</p>
|
||||
<p>This is the JSON configuration editor to define the %SK_PROCESSOR% and ESP32 configuration.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
@@ -95,9 +95,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
</ul>
|
||||
|
||||
119
projects/tzpuPico/esp32/webserver/index.htm
vendored
119
projects/tzpuPico/esp32/webserver/index.htm
vendored
@@ -95,9 +95,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (<span class="rp-floppy1">%SK_FLOPPY1%</span>)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (<span class="rp-floppy2">%SK_FLOPPY2%</span>)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (<span class="rp-qdisk">%SK_QDDISK%</span>)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
</ul>
|
||||
@@ -115,10 +115,12 @@
|
||||
<ol class="breadcrumb">
|
||||
<li class="active"><i class="fa fa-dashboard"></i> Status</li>
|
||||
</ol>
|
||||
<!--
|
||||
<div class="alert alert-success alert-dismissable justify">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<p>This built-in web app allows configuration of the host, emulated processor, virtual interfaces, <u>O</u>ver <u>T</u>he <u>A</u>ir firmware updates and more.<br>Please read the documentation if you need help.</p>
|
||||
<p>This built-in web app allows configuration of the host, emulated processor, virtual interfaces, <u>O</u>ver <u>T</u>he <u>A</u>ir firmware updates and more.</p>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
@@ -141,9 +143,9 @@
|
||||
<td>Password:</td><td><span style="color: blue;">%SK_APPWD%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP (AP):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td>IP (AP):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -160,14 +162,14 @@
|
||||
<td>DHCP:</td><td><span style="color: blue;">Enabled</span></td>
|
||||
</tr>
|
||||
<tr id="wifiCfg3%SK_CLIENTDHCPON%">
|
||||
<td>IP (assigned):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td>IP (assigned):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
<tr id="wifiCfg3%SK_CLIENTDHCPOFF%">
|
||||
<td>IP (fixed):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td>IP (fixed):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -200,27 +202,23 @@
|
||||
<table class="table table-borderless table-sm" style="margin-bottom:0; width:auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:140px; padding-right:4px;">Firmware Version:</td><td style="width:200px;"><span style="color:#5bf;">%SK_FWVERSION%</span></td>
|
||||
<td style="width:140px; padding-right:4px;">Firmware Version:</td><td style="width:200px;"><span class="rp-fwversion" style="color:#5bf;">%SK_FWVERSION%</span></td>
|
||||
<td style="width:140px; padding-right:4px;">ESP32 Version:</td><td style="width:200px;"><span style="color:#5bf;">%SK_ESPVERSION%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Active Partition:</td><td><span style="color:#5bf;">%SK_ACTIVEPARTITION%</span></td>
|
||||
<td>Active Persona:</td><td><span style="color:#5bf;">%SK_PERSONA%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RP2350 Clock:</td><td><span style="color:#5bf;">%SK_CPUCLOCK%</span></td>
|
||||
<td>RP2350 Clock:</td><td><span class="rp-clock" style="color:#5bf;">%SK_CPUCLOCK%</span></td>
|
||||
<td>ESP32 Clock:</td><td><span style="color:#5bf;">%SK_ESP32CLOCK%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RP2350 Flash:</td><td><span style="color:#5bf;">%SK_RP2350FLASH%</span></td>
|
||||
<td>RP2350 Flash:</td><td><span class="rp-flash" style="color:#5bf;">%SK_RP2350FLASH%</span></td>
|
||||
<td>ESP32 Flash:</td><td><span style="color:#5bf;">%SK_ESP32FLASH%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RP2350 PSRAM:</td><td><span style="color:#5bf;">%SK_RP2350PSRAM%</span></td>
|
||||
<td>RP2350 PSRAM:</td><td><span class="rp-psram" style="color:#5bf;">%SK_RP2350PSRAM%</span></td>
|
||||
<td>ESP32 PSRAM:</td><td><span style="color:#5bf;">%SK_ESP32PSRAM%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PSRAM Clock:</td><td><span style="color:#5bf;">%SK_PSRAMCLOCK%</span></td>
|
||||
<td>PSRAM Clock:</td><td><span class="rp-psramclk" style="color:#5bf;">%SK_PSRAMCLOCK%</span></td>
|
||||
<td>SD Card:</td><td><span style="color:#5bf;">%SK_SDCARD%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -234,6 +232,41 @@
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="fa fa-microchip"></i> Active Personality</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-borderless table-sm" style="margin-bottom:0; width:auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:140px; padding-right:4px;">Personality:</td><td style="width:200px;"><span class="rp-persona" style="color:#5bf;">%SK_PERSONA%</span></td>
|
||||
<td style="padding-left:20px; width:140px;">Processor:</td><td style="width:200px;"><span style="color:#5bf;">%SK_PROCESSOR%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Active Partition:</td><td><span class="rp-partition" style="color:#5bf;">%SK_ACTIVEPARTITION%</span></td>
|
||||
<td style="padding-left:20px;">Host Clock:</td><td><span class="rp-hostclk" style="color:#5bf;">N/A</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emulation Speed:</td><td><span class="rp-emulspeed" style="color:#5bf;">N/A</span></td>
|
||||
<td style="padding-left:20px;">Drivers:</td><td><span class="rp-drivers" style="color:#5bf;">%SK_ACTIVEDRIVERS%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Floppy 1:</td><td><span class="rp-floppy1" style="color:#5bf;">%SK_FLOPPY1%</span></td>
|
||||
<td style="padding-left:20px;">Floppy 2:</td><td><span class="rp-floppy2" style="color:#5bf;">%SK_FLOPPY2%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Quick Disk:</td><td><span class="rp-qdisk" style="color:#5bf;">%SK_QDDISK%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-primary">
|
||||
@@ -281,7 +314,7 @@
|
||||
<th><b>Copyright</b></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="rp-partitions-body">
|
||||
%SK_RP_PARTITIONS%
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -309,7 +342,7 @@
|
||||
<script src="js/actions.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<script>
|
||||
// Live WiFi signal strength polling (updates every 3 seconds).
|
||||
// Live WiFi/network status polling (updates every 3 seconds).
|
||||
(function pollWifiStatus() {
|
||||
$.getJSON("/data/wifistatus", function(data) {
|
||||
$(".rssi-live").text(data.rssi + " dBm");
|
||||
@@ -321,10 +354,46 @@
|
||||
} else {
|
||||
$(".rssi-live").text("N/A").css("color", "gray");
|
||||
}
|
||||
// Update uptime if present in response
|
||||
// Update IP/Netmask/Gateway dynamically (WiFi may connect after page load).
|
||||
if (data.ip) {
|
||||
$(".wifi-ip-live").text(data.ip || "");
|
||||
$(".wifi-nm-live").text(data.netmask || "");
|
||||
$(".wifi-gw-live").text(data.gateway || "");
|
||||
}
|
||||
// Update uptime.
|
||||
if (data.uptime) {
|
||||
$("#sys-uptime").text(data.uptime);
|
||||
}
|
||||
// Update RP2350 fields (populated by INF IPC, may arrive after page load).
|
||||
var na = "N/A";
|
||||
$(".rp-fwversion").text(data.fwVersion ? data.fwVersion + " (" + data.fwDate + ")" : na);
|
||||
$(".rp-clock").text(data.rp2350Clock !== null ? data.rp2350Clock + " MHz" : na);
|
||||
$(".rp-psramclk").text(data.psramClock !== null ? data.psramClock + " MHz" : na);
|
||||
$(".rp-flash").text(data.rp2350Flash !== null ? data.rp2350Flash + " MB" : na);
|
||||
$(".rp-psram").text(data.rp2350Psram !== null ? data.rp2350Psram + " MB" : na);
|
||||
$(".rp-persona").text(data.persona || na);
|
||||
$(".rp-partition").text(data.activePartition !== null ? data.activePartition : na);
|
||||
$(".rp-drivers").text(data.drivers || na);
|
||||
if (data.hostClkHz !== null && data.hostClkHz > 0) {
|
||||
var mhz = (data.hostClkHz / 1000000).toFixed(3);
|
||||
$(".rp-hostclk").text(mhz + " MHz");
|
||||
} else {
|
||||
$(".rp-hostclk").text(na);
|
||||
}
|
||||
if (data.emulSpeedHz !== null && data.emulSpeedHz > 0) {
|
||||
var emhz = (data.emulSpeedHz / 1000000).toFixed(3);
|
||||
$(".rp-emulspeed").text(emhz + " MHz");
|
||||
} else {
|
||||
$(".rp-emulspeed").text(na);
|
||||
}
|
||||
// Update RP2350 partition table.
|
||||
if (data.rpPartitions) {
|
||||
$("#rp-partitions-body").html(data.rpPartitions);
|
||||
}
|
||||
// Update disk image names.
|
||||
if (data.floppy1) $(".rp-floppy1").text(data.floppy1);
|
||||
if (data.floppy2) $(".rp-floppy2").text(data.floppy2);
|
||||
if (data.qdisk) $(".rp-qdisk").text(data.qdisk);
|
||||
}).always(function() {
|
||||
setTimeout(pollWifiStatus, 3000);
|
||||
});
|
||||
|
||||
@@ -101,53 +101,74 @@ function uploadFile() {
|
||||
} else if (filePath.indexOf(' ') >= 0)
|
||||
{
|
||||
alert("File path on server cannot have spaces!");
|
||||
} else if (filePath[filePath.length-1] == '/')
|
||||
} else if (filePath[filePath.length-1] == '/')
|
||||
{
|
||||
alert("File name not specified after path!");
|
||||
} else if (fileInput[0].size > MAX_FILE_SIZE)
|
||||
} else if (fileInput[0].size > MAX_FILE_SIZE)
|
||||
{
|
||||
alert("File size must be less than "+MAX_FILE_SIZE_STR+"!");
|
||||
} else
|
||||
} else
|
||||
{
|
||||
document.getElementById("newfile").disabled = true;
|
||||
document.getElementById("filepath").disabled = true;
|
||||
document.getElementById("upload").disabled = true;
|
||||
//document.getElementById("download").disabled = true;
|
||||
document.getElementById("mkdir").disabled = true;
|
||||
|
||||
// Show progress bar.
|
||||
var progressRow = document.getElementById("uploadProgressRow");
|
||||
var progressBar = document.getElementById("uploadProgressBar");
|
||||
var progressText = document.getElementById("uploadProgressText");
|
||||
if (progressRow) progressRow.style.display = "";
|
||||
if (progressBar) { progressBar.style.width = "0%"; progressBar.style.background = "#5cb85c"; }
|
||||
if (progressText) progressText.textContent = "Uploading... 0%";
|
||||
|
||||
var file = fileInput[0];
|
||||
var xhttp;
|
||||
if(window.XMLHttpRequest)
|
||||
var fileSize = file.size;
|
||||
var xhttp = new XMLHttpRequest();
|
||||
|
||||
// Track upload progress.
|
||||
xhttp.upload.addEventListener("progress", function(e) {
|
||||
if (e.lengthComputable && progressBar && progressText) {
|
||||
var pct = Math.round((e.loaded / e.total) * 100);
|
||||
progressBar.style.width = pct + "%";
|
||||
var loaded = (e.loaded / 1024).toFixed(0);
|
||||
var total = (e.total / 1024).toFixed(0);
|
||||
if (e.total > 1048576) {
|
||||
loaded = (e.loaded / 1048576).toFixed(1);
|
||||
total = (e.total / 1048576).toFixed(1);
|
||||
progressText.textContent = "Uploading... " + pct + "% (" + loaded + " / " + total + " MB)";
|
||||
} else {
|
||||
progressText.textContent = "Uploading... " + pct + "% (" + loaded + " / " + total + " KB)";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
xhttp.onreadystatechange = function()
|
||||
{
|
||||
xhttp = new XMLHttpRequest();
|
||||
} else
|
||||
{
|
||||
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
xhttp.onreadystatechange = function()
|
||||
{
|
||||
if (xhttp.readyState == 4)
|
||||
if (xhttp.readyState == 4)
|
||||
{
|
||||
if (xhttp.status == 200)
|
||||
{
|
||||
document.open();
|
||||
document.write(xhttp.responseText);
|
||||
document.close();
|
||||
alert("File upload successful!");
|
||||
location.reload()
|
||||
} else if (xhttp.status == 0)
|
||||
{
|
||||
if (xhttp.status == 200)
|
||||
{
|
||||
if (progressBar) progressBar.style.width = "100%";
|
||||
if (progressText) progressText.textContent = "Upload complete!";
|
||||
setTimeout(function() { location.reload(); }, 1000);
|
||||
} else if (xhttp.status == 0)
|
||||
{
|
||||
if (progressBar) { progressBar.style.width = "100%"; progressBar.style.background = "#d9534f"; }
|
||||
if (progressText) progressText.textContent = "Upload failed — connection lost";
|
||||
alert("Upload failed, server closed the connection abruptly!");
|
||||
location.reload()
|
||||
} else
|
||||
{
|
||||
location.reload();
|
||||
} else
|
||||
{
|
||||
if (progressBar) { progressBar.style.width = "100%"; progressBar.style.background = "#d9534f"; }
|
||||
if (progressText) progressText.textContent = "Upload failed — error " + xhttp.status;
|
||||
alert(xhttp.status + " Error!\n" + xhttp.responseText);
|
||||
location.reload()
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", upload_path+window.location.search, true);
|
||||
xhttp.send(fileInput[0]);
|
||||
xhttp.send(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,9 +96,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
</ul>
|
||||
@@ -118,7 +118,7 @@
|
||||
</ol>
|
||||
<div class="alert alert-success alert-dismissable justify">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<p>This page allows you to upgrade the esp32 firmware/filepack. Please read the documentation if you need help.</p>
|
||||
<p>This page allows you to upgrade the esp32 firmware/filepack.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
@@ -96,9 +96,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
</ul>
|
||||
@@ -118,7 +118,7 @@
|
||||
</ol>
|
||||
<div class="alert alert-success alert-dismissable justify">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<p>This page allows you to upgrade the rp2350 firmware. Please read the documentation if you need help.</p>
|
||||
<p>This page allows you to upgrade the rp2350 firmware.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="tasks/reloadcfg"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.07
|
||||
2.18
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
<ul class="dropdown-menu">
|
||||
<li style="margin-left: 1em;"><b>Action Menu</b></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
|
||||
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
|
||||
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
|
||||
<hr style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<li><a href="tasks/reloadcfg"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
#include "pico/binary_info.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "debug.h"
|
||||
#include "cJSON.h"
|
||||
#include "rp2350.h"
|
||||
#include "FSPI.h"
|
||||
#include "Z80CPU.h"
|
||||
#include "ESP.h"
|
||||
#include "flash_ram.h"
|
||||
#include "usb_bridge.h"
|
||||
@@ -113,9 +115,11 @@ bool ESP_pollNop(char *cmdBuf, size_t cmdBufLen)
|
||||
// ESP_sendVersionInfo — sends the flash partition header to ESP32.
|
||||
// Uses the binary SPI IPC protocol (IPCF_CMD_INF).
|
||||
// ---------------------------------------------------------------------------
|
||||
bool ESP_sendVersionInfo(void)
|
||||
bool ESP_sendVersionInfo(const void *cpuPtr)
|
||||
{
|
||||
uint32_t payloadLen = 0;
|
||||
const t_Z80CPU *cpu = (const t_Z80CPU *) cpuPtr;
|
||||
const t_drivers *drivers = cpu ? &cpu->_drivers : NULL;
|
||||
|
||||
// Build extended INF payload: partition header + runtime core config.
|
||||
static t_FlashInfoPayload infPayload;
|
||||
@@ -126,13 +130,17 @@ bool ESP_sendVersionInfo(void)
|
||||
(FLASH_APP_CONFIG_SIZE * (infPayload.header.activeApp - 1)));
|
||||
if (appCfg->s.configured == FLASH_APP_CONFIGURED_FLAG)
|
||||
{
|
||||
infPayload.cpufreq = appCfg->s.cpufreq;
|
||||
// Report the actual running frequency, not the configured value.
|
||||
// The PLL may apply a divider so the real frequency can differ from
|
||||
// what was requested in the JSON config (e.g. 300MHz requested may
|
||||
// yield 302MHz or 296MHz depending on the PLL VCO constraints).
|
||||
infPayload.cpufreq = (int32_t) clock_get_hz(clk_sys);
|
||||
infPayload.psramfreq = appCfg->s.psramfreq;
|
||||
infPayload.voltage = appCfg->s.voltage;
|
||||
}
|
||||
else
|
||||
{
|
||||
infPayload.cpufreq = 0;
|
||||
infPayload.cpufreq = (int32_t) clock_get_hz(clk_sys);
|
||||
infPayload.psramfreq = 0;
|
||||
infPayload.voltage = 0;
|
||||
}
|
||||
@@ -140,6 +148,44 @@ bool ESP_sendVersionInfo(void)
|
||||
// Hardware sizes — always available regardless of config.
|
||||
infPayload.flashSize = PICO_FLASH_SIZE_BYTES;
|
||||
infPayload.psramSize = psramSize; // Set during psram_init() at boot
|
||||
infPayload.hostClkHz = cpu ? cpu->hostClkHz : 0;
|
||||
infPayload.emulSpeedHz = cpu ? cpu->emulSpeedHz : 0;
|
||||
|
||||
// Build active driver summary string: "Driver(V/P) IF1(V/P) IF2(V/P), ..."
|
||||
infPayload.driverSummary[0] = '\0';
|
||||
if (drivers && drivers->drvCount > 0)
|
||||
{
|
||||
char *p = infPayload.driverSummary;
|
||||
size_t remain = sizeof(infPayload.driverSummary);
|
||||
|
||||
for (int d = 0; d < drivers->drvCount && remain > 1; d++)
|
||||
{
|
||||
const t_drvConfig *drv = &drivers->driver[d];
|
||||
if (!drv->name)
|
||||
continue;
|
||||
|
||||
// Add separator between drivers.
|
||||
if (p != infPayload.driverSummary)
|
||||
{
|
||||
int n = snprintf(p, remain, ", ");
|
||||
p += n; remain -= n;
|
||||
}
|
||||
|
||||
// Driver name with type indicator.
|
||||
int n = snprintf(p, remain, "%s(%s)", drv->name, drv->isPhysical ? "P" : "V");
|
||||
p += n; remain -= n;
|
||||
|
||||
// Append enabled interfaces.
|
||||
for (int i = 0; i < drv->ifCount && remain > 1; i++)
|
||||
{
|
||||
const t_drvIFConfig *ifc = &drv->ifConfig[i];
|
||||
if (!ifc->name)
|
||||
continue;
|
||||
n = snprintf(p, remain, " %s(%s)", ifc->name, ifc->isPhysical ? "P" : "V");
|
||||
p += n; remain -= n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send INF command — payload is the extended flash info.
|
||||
// Only attempt if ESP32 HS is already HIGH (CommandProcessor ready).
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "drivers/Sharp/MZ80A.h" // Sharp MZ80A Persona driver.
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------
|
||||
// .----._.----.
|
||||
// A11 <-01--|1 o|--40-> A10
|
||||
@@ -125,6 +126,9 @@ const t_doubleValuePair voltageMap[] = {
|
||||
{1.20, VREG_VOLTAGE_1_20}, ///< 1.20 V
|
||||
{1.25, VREG_VOLTAGE_1_25}, ///< 1.25 V
|
||||
{1.30, VREG_VOLTAGE_1_30}, ///< 1.30 V
|
||||
{1.35, VREG_VOLTAGE_1_35}, ///< 1.35 V (overclocking — requires vreg_disable_voltage_limit)
|
||||
{1.40, VREG_VOLTAGE_1_40}, ///< 1.40 V (overclocking — requires vreg_disable_voltage_limit)
|
||||
{1.50, VREG_VOLTAGE_1_50}, ///< 1.50 V (overclocking — requires vreg_disable_voltage_limit)
|
||||
};
|
||||
const size_t voltageMapSize = sizeof(voltageMap) / sizeof(voltageMap[0]);
|
||||
|
||||
@@ -402,7 +406,8 @@ bool Z80CPU_configMemoryFromJSON(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfi
|
||||
// physical VRAM and vis-a-viz.
|
||||
for (uint16_t idx = 0; idx < blockSize; idx++)
|
||||
{
|
||||
cpu->_z80PSRAM->memioPtr[blockAddr + idx] = ((memType == MEMBANK_TYPE_PHYSICAL || memType == MEMBANK_TYPE_PHYSICAL_HW) ? NULL : thisFunc);
|
||||
t_MemoryFunc func = ((memType == MEMBANK_TYPE_PHYSICAL || memType == MEMBANK_TYPE_PHYSICAL_HW) ? NULL : thisFunc);
|
||||
MEMIO_SET(cpu, blockAddr + idx, func);
|
||||
}
|
||||
switch (memType)
|
||||
{
|
||||
@@ -2040,57 +2045,6 @@ void Z80CPU_taskMirrorInternalRAMToPhysical(t_Z80CPU *cpu, uint32_t intAddr, uin
|
||||
return;
|
||||
}
|
||||
|
||||
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
|
||||
// any motherboard hosted DRAM is refreshed.
|
||||
// This function can be attached to any Memory region or can be globally enabled (refreshEnabled = 1) for all
|
||||
// virtual memory opcode fetches.
|
||||
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(void *context, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
static uint16_t r_reg = 0; // Cant use the Z80 refresh register as it is running at a higher speed in memory.
|
||||
t_Z80CPU *cpu = (t_Z80CPU *) context;
|
||||
|
||||
// If a bus transaction is underway, we dont wait, virtual memory processing is much faster.
|
||||
if(GET_IRQ_STATE(pio_1, 0) == 0)
|
||||
return(0);
|
||||
|
||||
// The refresh address is a combination of the I register and the R register.
|
||||
r_reg = ((uint8_t)cpu->_Z80.i << 8) | r_reg;
|
||||
|
||||
PUSH_FIFO(pio_0, sm_addr, (addr << 16) | 0xFFFF); // Preload Fifo with address.
|
||||
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) r_reg) << 16) | 0xFFFF); // Pre-load refresh address.
|
||||
|
||||
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
|
||||
// No waiting, we just push 8 instructions which form T1/T2 and exit.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0e, // set pins, 14 side 3 Set /M1 low
|
||||
0x2013); // T1 wait 0 gpio, 35
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
|
||||
(0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
(0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
|
||||
//0xfc0d); // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0x2013, // T3 wait 0 gpio, 35
|
||||
// (0x0000 + offsetCycle + 13)); // jmp offset+12, execute refresh T3/T4.
|
||||
|
||||
// 0x2013, // T3 wait 0 gpio, 35
|
||||
// (0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
|
||||
|
||||
r_reg = ++r_reg & 0x7f; // R register only 7 bits incrementing.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
@@ -2472,6 +2426,8 @@ t_Z80CPU *Z80CPU_init(void)
|
||||
cpu._memAttr[bank][idx].tCycSync = false;
|
||||
}
|
||||
}
|
||||
// Clear the memioPtr bitmap and arrays.
|
||||
memset(cpu.memioMap, 0, sizeof(cpu.memioMap));
|
||||
for (int idx = 0; idx < MEMORY_PAGE_SIZE; idx++)
|
||||
{
|
||||
cpu._z80PSRAM->memPtr[idx] = 0x00000000;
|
||||
@@ -2712,6 +2668,7 @@ void __func_in_RAM(Z80CPU_cpu_init)(t_Z80CPU *cpu)
|
||||
void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
static int pollCnt = 0;
|
||||
|
||||
// Notify core 0 than we are configured and running.
|
||||
multicore_fifo_push_blocking(1);
|
||||
@@ -2728,17 +2685,22 @@ void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
|
||||
cpu->holdAck = false;
|
||||
}
|
||||
|
||||
// Call any driver poll handler periodically.
|
||||
for (int idx = 0; idx < cpu->_drivers.drvCount; idx++)
|
||||
// Call any driver poll handler periodically (approx 256ms).
|
||||
if(pollCnt++ > 9999)
|
||||
{
|
||||
if (cpu->_drivers.driver[idx].pollPtr != NULL)
|
||||
pollCnt = 0;
|
||||
for (int idx = 0; idx < cpu->_drivers.drvCount; idx++)
|
||||
{
|
||||
cpu->_drivers.driver[idx].pollPtr(cpu);
|
||||
if (cpu->_drivers.driver[idx].pollPtr != NULL)
|
||||
{
|
||||
cpu->_drivers.driver[idx].pollPtr(cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step the CPU, giving a break to check signals.
|
||||
z80_run(&cpu->_Z80, 2048);
|
||||
z80_run(&cpu->_Z80, 64);
|
||||
cpu->emulLoopCount++;
|
||||
|
||||
// Check for hardware reset, if enabled, process and clear.
|
||||
if (cpu->forceReset || (pio_2->irq & (1u << 3)) != 0)
|
||||
@@ -2772,58 +2734,62 @@ uint8_t __func_in_RAM(Z80CPU_readMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t mem
|
||||
{
|
||||
// Locals.
|
||||
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1)); // Isolate the lower 10bit of the address.
|
||||
uint32_t RAMaddr = (membankptr & 0x7fffff); // Isolate the full RAM address.
|
||||
//uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
|
||||
uint8_t data = 0x00;
|
||||
uint32_t RAMaddr = (membankptr & 0x7fffff); // Isolate the full RAM address.
|
||||
uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
|
||||
uint8_t bank = (membankptr >> 16);
|
||||
uint8_t data = 0x00;
|
||||
|
||||
switch (membankptr >> 24) // Top 6 bits stores the memory type.
|
||||
{
|
||||
// Underlying is a physical device on the motherboard, probably RAM/ROM.
|
||||
case MEMBANK_TYPE_PHYSICAL:
|
||||
case MEMBANK_TYPE_PHYSICAL_VRAM:
|
||||
case MEMBANK_TYPE_PHYSICAL_HW:
|
||||
data = Z80CPU_readPhysicalMem(cpu, addr);
|
||||
break;
|
||||
// Underlying is a physical device on the motherboard, probably RAM/ROM.
|
||||
case MEMBANK_TYPE_PHYSICAL:
|
||||
case MEMBANK_TYPE_PHYSICAL_VRAM:
|
||||
case MEMBANK_TYPE_PHYSICAL_HW:
|
||||
data = Z80CPU_readPhysicalMem(cpu, addr);
|
||||
break;
|
||||
|
||||
// Underlying is a space within the PSRAM allocated area (4MB).
|
||||
case MEMBANK_TYPE_RAM:
|
||||
case MEMBANK_TYPE_VRAM:
|
||||
case MEMBANK_TYPE_ROM:
|
||||
// Execute any associated function with this location. Returned data is taken in preference to underlying RAM if a function is given.
|
||||
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
|
||||
if (cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
// Call the function passing in the data from the current RAM address. If the function doesnt provide or
|
||||
// override the data, then return what is in the RAM.
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, cpu->_z80PSRAM->RAM[RAMaddr + blockaddr]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read direct from RAM, no physical access.
|
||||
data = cpu->_z80PSRAM->RAM[RAMaddr + blockaddr];
|
||||
}
|
||||
// Underlying is a space within the PSRAM allocated area (4MB).
|
||||
case MEMBANK_TYPE_RAM:
|
||||
case MEMBANK_TYPE_VRAM:
|
||||
case MEMBANK_TYPE_ROM:
|
||||
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, cpu->_z80PSRAM->RAM[RAMaddr + blockaddr]);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = cpu->_z80PSRAM->RAM[RAMaddr + blockaddr];
|
||||
}
|
||||
|
||||
// Any wait states setup, wait given number of T cycles.
|
||||
if (waitStates)
|
||||
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
|
||||
break;
|
||||
// If T-Cycle sync enabled for this memory block, wait for the rising edge of T1.
|
||||
if(cpu->_memAttr[bank][block].tCycSync && waitStates == 0)
|
||||
{
|
||||
Z80CPU_syncPhysical(cpu);
|
||||
} else
|
||||
// Sync to host clock with wait states.
|
||||
if(cpu->_memAttr[bank][block].tCycSync && waitStates > 0)
|
||||
{
|
||||
Z80CPU_syncPhysicalStates(cpu, waitStates);
|
||||
} else
|
||||
// Any wait states setup, wait given number of T cycles.
|
||||
if (waitStates)
|
||||
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
|
||||
break;
|
||||
|
||||
// Underlying is provided by execution of a function which returns a byte.
|
||||
case MEMBANK_TYPE_FUNC:
|
||||
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
|
||||
if (cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, 0);
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, 0);
|
||||
}
|
||||
break;
|
||||
// Underlying is provided by execution of a function which returns a byte.
|
||||
case MEMBANK_TYPE_FUNC:
|
||||
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
|
||||
case MEMBANK_TYPE_PTR:
|
||||
// Recursion to follow the pointer to the actual RAM bank.
|
||||
//data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates);
|
||||
data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates);
|
||||
break;
|
||||
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
|
||||
case MEMBANK_TYPE_PTR:
|
||||
// Recursion to follow the pointer to the actual RAM bank.
|
||||
//data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates);
|
||||
data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates);
|
||||
break;
|
||||
}
|
||||
|
||||
// Return the byte deduced from resolution of the <Memory Type>.
|
||||
@@ -2836,72 +2802,62 @@ uint8_t __func_in_RAM(Z80CPU_readMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t mem
|
||||
bool __func_in_RAM(Z80CPU_writeMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t membankptr, uint8_t waitStates, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1)); // Get lower bits of the address, ie. address within a block.
|
||||
uint32_t RAMaddr =
|
||||
(membankptr & 0x7fffff); // Isolate the RAM address. RAM is in banks, ie. <8 bit bank> <16 bit addr> so combined they form a 24bit RAM array address.
|
||||
//uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
|
||||
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1));
|
||||
uint32_t RAMaddr = (membankptr & 0x7fffff);
|
||||
bool result = true;
|
||||
|
||||
switch (membankptr >> 24) // Top 6 bits stores the memory type.
|
||||
switch (membankptr >> 24)
|
||||
{
|
||||
// Underlying is a physical device on the motherboard, probably RAM/ROM.
|
||||
case MEMBANK_TYPE_PHYSICAL:
|
||||
case MEMBANK_TYPE_PHYSICAL_VRAM:
|
||||
case MEMBANK_TYPE_PHYSICAL_HW:
|
||||
result = Z80CPU_writePhysicalMem(cpu, addr, data);
|
||||
break;
|
||||
case MEMBANK_TYPE_PHYSICAL:
|
||||
case MEMBANK_TYPE_PHYSICAL_VRAM:
|
||||
case MEMBANK_TYPE_PHYSICAL_HW:
|
||||
result = Z80CPU_writePhysicalMem(cpu, addr, data);
|
||||
break;
|
||||
|
||||
// Underlying is a space within the PSRAM allocated area (4MB).
|
||||
case MEMBANK_TYPE_RAM:
|
||||
case MEMBANK_TYPE_VRAM:
|
||||
// Execute any associated function with this location. Data is sent to function and not RAM if provided.
|
||||
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
|
||||
if (cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, data);
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = data; // Index is the [bank][block][lower bits of address]
|
||||
}
|
||||
case MEMBANK_TYPE_RAM:
|
||||
case MEMBANK_TYPE_VRAM:
|
||||
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = data;
|
||||
}
|
||||
|
||||
// Any wait states setup, wait given number of T cycles.
|
||||
if (waitStates)
|
||||
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
|
||||
break;
|
||||
// Any wait states setup, wait given number of T cycles.
|
||||
if (waitStates)
|
||||
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
|
||||
break;
|
||||
|
||||
// Underlying resolves to ROM which isnt writable, just call any attached memio handler.
|
||||
case MEMBANK_TYPE_ROM:
|
||||
if (cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, data);
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
result = false;
|
||||
break;
|
||||
|
||||
// Underlying is provided by execution of a function which returns a byte.
|
||||
case MEMBANK_TYPE_FUNC:
|
||||
// Check if a handler function has been installed, call if exists otherwise do nothing.
|
||||
//if (cpu->_z80PSRAM->memioPtr[block | blockaddr])
|
||||
if (cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, false, addr, data);
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Underlying resolves to ROM which isnt writable, just call any attached memio handler.
|
||||
case MEMBANK_TYPE_ROM:
|
||||
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
|
||||
case MEMBANK_TYPE_PTR:
|
||||
// Recursion to follow the pointer to the actual RAM bank.
|
||||
//result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates, data);
|
||||
result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates, data);
|
||||
break;
|
||||
// Underlying is provided by execution of a function which returns a byte.
|
||||
case MEMBANK_TYPE_FUNC:
|
||||
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
|
||||
{
|
||||
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, false, addr, data);
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
break;
|
||||
|
||||
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
|
||||
case MEMBANK_TYPE_PTR:
|
||||
// Recursion to follow the pointer to the actual RAM bank.
|
||||
//result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates, data);
|
||||
result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates, data);
|
||||
break;
|
||||
}
|
||||
|
||||
// Return success code.
|
||||
@@ -2959,6 +2915,7 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
|
||||
|
||||
// Check membank type, call physical reads direct.
|
||||
uint32_t membankptr = cpu->_membankPtr[block];
|
||||
|
||||
//if((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL|MEMBANK_TYPE_PHYSICAL_VRAM|MEMBANK_TYPE_PHYSICAL_HW))
|
||||
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM))
|
||||
{
|
||||
@@ -2966,17 +2923,18 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
|
||||
// membankptr.
|
||||
uint8_t bank = (membankptr >> 16);
|
||||
|
||||
// If T-Cycle sync enabled for this memory block, wait for the rising edge of T1.
|
||||
if (cpu->_memAttr[bank][block].tCycSync)
|
||||
{
|
||||
Z80CPU_syncPhysical(cpu);
|
||||
}
|
||||
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
|
||||
|
||||
// M1 cycles take 4 T-states vs 3 for normal reads. readMem with waitStates=2
|
||||
// gives sync+2 = 3T (correct for memory reads). Add 1 extra wait here for
|
||||
// M1's 4th T-state so the opcode fetch takes exactly 4T — matching the real Z80.
|
||||
// This makes delay loop timing fully deterministic at the host clock speed.
|
||||
if (cpu->_memAttr[bank][block].tCycSync && cpu->_memAttr[bank][block].waitStates > 0)
|
||||
{
|
||||
Z80CPU_waitPhysicalStates(cpu, 1);
|
||||
}
|
||||
|
||||
// DRAM refresh: when fetching opcodes from virtual memory, the physical M1 cycle
|
||||
// (which includes a RFSH bus cycle) is not generated. Physical DRAM on the host
|
||||
// motherboard will decay without a refresh. Trigger a full M1 cycle but ignore
|
||||
@@ -3010,10 +2968,7 @@ uint8_t __func_in_RAM(Z80CPU_fetchByte)(void *context, uint16_t address)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
|
||||
// membankptr.
|
||||
uint8_t bank = (membankptr >> 16);
|
||||
|
||||
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
|
||||
}
|
||||
|
||||
@@ -3041,24 +2996,18 @@ uint8_t __func_in_RAM(Z80CPU_readByte)(void *context, uint16_t address)
|
||||
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM | MEMBANK_TYPE_PHYSICAL_HW))
|
||||
{
|
||||
// Some hardware needs to be virtualised or assisted, so if a hook is installed, call it rather than the hardware.
|
||||
// The purpose, rather than using FUNC, is hardware is byte level not block, so most hardware will be physical
|
||||
// except the odd address.
|
||||
if (cpu->_z80PSRAM->memioPtr[address])
|
||||
if (MEMIO_BIT_TEST(cpu, address) && cpu->_z80PSRAM->memioPtr[address])
|
||||
{
|
||||
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[address](cpu, true, address, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read direct from hardware.
|
||||
data = Z80CPU_readPhysicalMem(cpu, address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
|
||||
// membankptr.
|
||||
uint8_t bank = (membankptr >> 16);
|
||||
|
||||
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
|
||||
}
|
||||
|
||||
@@ -3080,10 +3029,7 @@ void __func_in_RAM(Z80CPU_writeByte)(void *context, uint16_t address, uint8_t va
|
||||
uint32_t membankptr = cpu->_membankPtr[block];
|
||||
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM | MEMBANK_TYPE_PHYSICAL_HW))
|
||||
{
|
||||
// Some hardware needs to be virtualised or assisted, so if a hook is installed, call it rather than the hardware.
|
||||
// The purpose, rather than using FUNC, is hardware is byte level not block, so most hardware will be physical
|
||||
// except the odd address.
|
||||
if (cpu->_z80PSRAM->memioPtr[address])
|
||||
if (MEMIO_BIT_TEST(cpu, address) && cpu->_z80PSRAM->memioPtr[address])
|
||||
{
|
||||
cpu->_z80PSRAM->memioPtr[address](cpu, false, address, value);
|
||||
}
|
||||
@@ -3095,11 +3041,7 @@ void __func_in_RAM(Z80CPU_writeByte)(void *context, uint16_t address, uint8_t va
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
|
||||
// membankptr.
|
||||
uint8_t bank = (membankptr >> 16);
|
||||
|
||||
// Default to membankptr from SRAM table
|
||||
Z80CPU_writeMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates, value);
|
||||
}
|
||||
|
||||
@@ -3251,8 +3193,12 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
|
||||
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) cpu->_Z80.r) << 16) | 0xFFFF); // pre-load refresh address.
|
||||
|
||||
WAIT_IRQ_SET(pio_1, 0); // Wait until state machine at start of sequence.
|
||||
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
|
||||
// Enable read state machine.
|
||||
CLEAR_IRQ(pio_1, 0);
|
||||
|
||||
// Enable address load.
|
||||
CLEAR_IRQ(pio_0, 0);
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
@@ -3271,30 +3217,17 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0d, // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
(0x0000 + offsetCycle + 12)); // jmp offset+13, execute refresh T3/T4.
|
||||
// 0x2013); // T3 wait 0 gpio, 35
|
||||
(0x0000 + offsetCycle + 13)); // jmp offset+13, execute refresh T3/T4.
|
||||
|
||||
// Wait for the data to arrive before changing to the Refresh address.
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
|
||||
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
|
||||
|
||||
// Retrieve opcode during lull period.
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
|
||||
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
|
||||
data = POP_FIFO(pio_1, smCycle);
|
||||
|
||||
// REFRESH Cycle during T3/T4 is performed withn the PIO cycle_program program.
|
||||
|
||||
// // Enter refresh cycle.
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xfc09, // set pins, 9 side 3 Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
// 0x2093); // T4 wait 1 gpio, 35
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0x2013, // T4 wait 0 gpio, 35
|
||||
// 0xfd0d); // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low delay 1
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xfc0f, // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
// (0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
#ifdef DEBUG_PIO
|
||||
debugf("Fetch %04x -> %02x\r\n", addr, (uint8_t) data);
|
||||
#endif
|
||||
@@ -3314,31 +3247,38 @@ uint8_t __func_in_RAM(Z80CPU_readPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
|
||||
|
||||
PUSH_FIFO(pio_0, sm_addr, (addr << 16) | 0xFFFF); // Preload Fifo with address.
|
||||
|
||||
WAIT_IRQ_SET(pio_1, 0); // Wait until state machine at start of sequence.
|
||||
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
// Wait until state machine at start of sequence.
|
||||
WAIT_IRQ_SET(pio_1, 0);
|
||||
|
||||
// Enable read state machine.
|
||||
CLEAR_IRQ(pio_1, 0);
|
||||
|
||||
// Enable address load.
|
||||
CLEAR_IRQ(pio_0, 0);
|
||||
|
||||
// Instructions to implement Read Physical.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0xf80b); // set pins, 11 /MREQ=0 side 2 /RD=0
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0xf80b); // set pins, 11 /MREQ=0 side 2 /RD=0
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0f, // set pins, 15 side 3
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
0xfc0f, // set pins, 15 side 3
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle); // Wait until last instruction flushed.
|
||||
data = POP_FIFO(pio_1, smCycle); // Grab input data from FIFO
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle); // Wait until last instruction flushed.
|
||||
data = POP_FIFO(pio_1, smCycle); // Grab input data from FIFO
|
||||
|
||||
#ifdef DEBUG_PIO
|
||||
debugf("Read %04x -> %02x\r\n", addr, (uint8_t) data);
|
||||
@@ -3360,35 +3300,40 @@ bool __func_in_RAM(Z80CPU_writePhysicalMem)(t_Z80CPU *cpu, uint16_t addr, uint8_
|
||||
PUSH_FIFO(pio_0, sm_data, 0x000000 | (data << 8) | 0x00FF); // Load Fifo with data ready to be loaded.
|
||||
|
||||
WAIT_IRQ_SET(pio_1, 0); // T1 - Wait until state machine at start of sequence.
|
||||
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
|
||||
// Enable read state machine.
|
||||
CLEAR_IRQ(pio_1, 0);
|
||||
|
||||
// Enable address load.
|
||||
CLEAR_IRQ(pio_0, 0);
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0xfc0b); // set pins, 11 /MREQ=0 side 3 /RD=1,/WR=1
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0xfc0b); // set pins, 11 /MREQ=0 side 3 /RD=1,/WR=1
|
||||
|
||||
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
|
||||
CLEAR_IRQ(pio_0, 1); // Enable data load.
|
||||
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
|
||||
CLEAR_IRQ(pio_0, 1); // Enable data load.
|
||||
|
||||
// Instructions to implement Write Physical.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T2 wait 1 gpio, 35
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T2 wait 1 gpio, 35
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
0xf40b, // set pins, 11 /MREQ=0 side 1 /WR=0
|
||||
(0x0000 + offsetCycle + 8));// jmp offset+8, wait state loop eval WAIT stage.
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf40b, // set pins, 11 /MREQ=0 side 1 /WR=0
|
||||
(0x0000 + offsetCycle + 8)); // jmp offset+8, wait state loop eval WAIT stage.
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
0x2013); // T3 wait 0 gpio, 35
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
0x2013); // T3 wait 0 gpio, 35
|
||||
|
||||
PUSH_INSTR_W32(pio_1,
|
||||
smCycle,
|
||||
0xfc0f, // set pins, 15 /IORQ=1,/MREQ=1/RFSH=1/M1=1 side 3 /RD=1,/WR=1
|
||||
0xfc0f, // set pins, 15 /IORQ=1,/MREQ=1/RFSH=1/M1=1 side 3 /RD=1,/WR=1
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
#ifdef DEBUG_PIO
|
||||
@@ -3414,26 +3359,26 @@ uint8_t __func_in_RAM(Z80CPU_readPhysicalIO)(t_Z80CPU *cpu, uint16_t addr)
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0x2093); // T2 wait 1 gpio, 35
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0x2093); // T2 wait 1 gpio, 35
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf807, // set pins, 7 side 2 Set /IORQ low, /RD low
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
0xf807, // set pins, 7 side 2 Set /IORQ low, /RD low
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
(0x0000 + offsetCycle + 6),// jmp offset+6, wait state loop.
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0f, // set pins, 15 side 3 Set /IORQ high, /RD high
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
|
||||
data = POP_FIFO(pio_1, smCycle);
|
||||
@@ -3465,31 +3410,31 @@ bool __func_in_RAM(Z80CPU_writePhysicalIO)(t_Z80CPU *cpu, uint16_t addr, uint8_t
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0x2093); // T2 wait 1 gpio, 35
|
||||
0x2013, // T1 wait 0 gpio, 35
|
||||
0x2093); // T2 wait 1 gpio, 35
|
||||
|
||||
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
|
||||
CLEAR_IRQ(pio_0, 1); // Enable data load.
|
||||
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
|
||||
CLEAR_IRQ(pio_0, 1); // Enable data load.
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf407, // set pins, 7 side 1 Set /IORQ low, /WR low
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
0xf407, // set pins, 7 side 1 Set /IORQ low, /WR low
|
||||
0x2013); // T2 wait 0 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
(0x0000 + offsetCycle + 6),// jmp offset+6, wait state loop.
|
||||
0x2093); // T3 wait 1 gpio, 35
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0xfc0f); // set pins, 15 side 3 Set /IORQ high, /WR high
|
||||
0x2013, // T3 wait 0 gpio, 35
|
||||
0xfc0f); // set pins, 15 side 3 Set /IORQ high, /WR high
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xa021, // nop
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
// BUSRQ/BUSACK State, INT, NMI? Inline single test for speed expanding if any IRQ is set.
|
||||
SERVICE_REQUESTS();
|
||||
@@ -3614,6 +3559,53 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalIntVector)(t_Z80CPU *cpu, uint16_t add
|
||||
return ((uint8_t) data);
|
||||
}
|
||||
|
||||
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
|
||||
// any motherboard hosted DRAM is refreshed.
|
||||
// This function can be attached to any Memory region or can be globally enabled (refreshEnabled = 1) for all
|
||||
// virtual memory opcode fetches.
|
||||
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(void *context, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint16_t r_reg = 0;
|
||||
t_Z80CPU *cpu = (t_Z80CPU *) context;
|
||||
|
||||
// The refresh address is a combination of the I register and the R register.
|
||||
r_reg = ((uint8_t)cpu->_Z80.i << 8) | (uint8_t)cpu->_Z80.r;
|
||||
|
||||
// Only need to place the refresh address on the bus, we are not performing T1/T2 M1 cycle.
|
||||
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) r_reg) << 16) | 0xFFFF); // Pre-load refresh address.
|
||||
|
||||
// Wait until the previous cycle completes before commencing new cycle.
|
||||
WAIT_IRQ_SET(pio_1, smCycle);
|
||||
|
||||
// // No waiting, we just push 8 instructions which form T1/T2 and exit.
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xfc0e, // set pins, 14 side 3 Set /M1 low
|
||||
// 0x2013); // T1 wait 0 gpio, 35
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
|
||||
// (0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
|
||||
|
||||
// Push initial instructions to setup pin inactive state then execute the Refresh T3/T4 cycles.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
//0x2093, // T3 wait 1 gpio, 35
|
||||
0xfc0f, // /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
(0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
|
||||
|
||||
// Enable refresh address load.
|
||||
CLEAR_IRQ(pio_0, 0);
|
||||
|
||||
// Enable smcycle state machine.
|
||||
CLEAR_IRQ(pio_1, 0);
|
||||
|
||||
r_reg = ++r_reg & 0x7f; // R register only 7 bits incrementing.
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Available SDK calls.
|
||||
//
|
||||
// static uint pio_get_gpio_base (PIO pio)
|
||||
|
||||
421
projects/tzpuPico/src/drivers/PIT8253.c
Normal file
421
projects/tzpuPico/src/drivers/PIT8253.c
Normal file
@@ -0,0 +1,421 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Name: PIT8253.c
|
||||
// Created: April 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Comprehensive Intel 8253 Programmable Interval Timer emulation.
|
||||
//
|
||||
// Supports all 6 counter modes:
|
||||
// Mode 0: Interrupt on Terminal Count
|
||||
// Mode 1: Hardware Retriggerable One-Shot
|
||||
// Mode 2: Rate Generator
|
||||
// Mode 3: Square Wave Generator
|
||||
// Mode 4: Software Triggered Strobe
|
||||
// Mode 5: Hardware Triggered Strobe
|
||||
//
|
||||
// Supports BCD/binary counting, counter latch, LSB/MSB/LSB-then-MSB
|
||||
// read/load modes, and proper output pin state tracking.
|
||||
//
|
||||
// This module is standalone — each driver allocates its own t_PIT8253
|
||||
// context so multiple instances (e.g. MZ-700 and MZ-80A) can coexist.
|
||||
//
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: April 2026 - Extracted from MZ700.c / MZ80A.c into
|
||||
// standalone reusable module.
|
||||
// -----------------------------------------------------------------------
|
||||
// This source file is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "drivers/PIT8253.h"
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Decrement a 16-bit value by 1 in BCD mode (4-digit BCD: 0000-9999).
|
||||
static uint16_t pitBcdDecrement(uint16_t val)
|
||||
{
|
||||
uint16_t d0 = (val) & 0xF;
|
||||
uint16_t d1 = (val >> 4) & 0xF;
|
||||
uint16_t d2 = (val >> 8) & 0xF;
|
||||
uint16_t d3 = (val >> 12) & 0xF;
|
||||
uint16_t bin = d0 + d1 * 10 + d2 * 100 + d3 * 1000;
|
||||
if (bin == 0)
|
||||
bin = 10000; // Wrap: 0 → 10000 (BCD 0000 → 9999 after decrement).
|
||||
bin--;
|
||||
d0 = bin % 10;
|
||||
d1 = (bin / 10) % 10;
|
||||
d2 = (bin / 100) % 10;
|
||||
d3 = (bin / 1000) % 10;
|
||||
return (uint16_t)(d0 | (d1 << 4) | (d2 << 8) | (d3 << 12));
|
||||
}
|
||||
|
||||
// Get the effective count value for terminal count detection.
|
||||
// A programmed count of 0 means 0x10000 (65536) in binary or 10000 in BCD.
|
||||
static uint32_t pitEffectiveCount(t_PIT8253Counter *ctr)
|
||||
{
|
||||
if (ctr->reload == 0)
|
||||
return ctr->bcd ? 10000 : 0x10000;
|
||||
return ctr->reload;
|
||||
}
|
||||
|
||||
// Update a single counter based on elapsed wall-clock time.
|
||||
static void pitUpdateCounter(t_PIT8253 *pit, int ch)
|
||||
{
|
||||
t_PIT8253Counter *ctr = &pit->counter[ch];
|
||||
|
||||
if (!ctr->counting)
|
||||
return;
|
||||
|
||||
// Gate must be high for counting in modes 0, 2, 3, 4 (modes 1, 5 are edge-triggered,
|
||||
// but gate is assumed high, so they also count).
|
||||
if (!ctr->gate && (ctr->mode == 0 || ctr->mode == 2 || ctr->mode == 3 || ctr->mode == 4))
|
||||
return;
|
||||
|
||||
absolute_time_t now = get_absolute_time();
|
||||
int64_t elapsedUs = absolute_time_diff_us(ctr->lastUpdate, now);
|
||||
if (elapsedUs <= 0)
|
||||
return;
|
||||
|
||||
// Calculate clock ticks elapsed.
|
||||
uint32_t ticks = (uint32_t)((elapsedUs * (int64_t)pit->clockHz) / 1000000);
|
||||
if (ticks == 0)
|
||||
return;
|
||||
|
||||
ctr->lastUpdate = now;
|
||||
|
||||
if (ctr->bcd)
|
||||
{
|
||||
// BCD counting — decrement one tick at a time (slow but correct for BCD).
|
||||
// Limit to prevent excessive CPU usage if many ticks elapsed.
|
||||
if (ticks > 20000)
|
||||
ticks = 20000;
|
||||
for (uint32_t t = 0; t < ticks; t++)
|
||||
{
|
||||
ctr->count = pitBcdDecrement(ctr->count);
|
||||
if (ctr->count == 0)
|
||||
{
|
||||
switch (ctr->mode)
|
||||
{
|
||||
case 0: // Interrupt on TC — output goes HIGH, counter keeps counting (wraps).
|
||||
ctr->output = true;
|
||||
break;
|
||||
case 1: // One-shot — output goes HIGH at TC.
|
||||
ctr->output = true;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
case 2: // Rate generator — reload, output pulse LOW for 1 tick then HIGH.
|
||||
ctr->count = ctr->reload ? ctr->reload : 0x9999;
|
||||
ctr->output = true;
|
||||
break;
|
||||
case 3: // Square wave — toggle output at half count.
|
||||
ctr->count = ctr->reload ? ctr->reload : 0x9999;
|
||||
ctr->output = !ctr->output;
|
||||
break;
|
||||
case 4: // SW strobe — output LOW for 1 tick at TC, then HIGH.
|
||||
ctr->output = false;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
case 5: // HW strobe — same as mode 4 but hardware triggered.
|
||||
ctr->output = false;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Binary counting.
|
||||
uint32_t c = ctr->count;
|
||||
|
||||
switch (ctr->mode)
|
||||
{
|
||||
case 0: // Interrupt on Terminal Count.
|
||||
// Counter counts down from loaded value. Output goes HIGH when count reaches 0.
|
||||
// Counter continues to count (wraps through 0xFFFF).
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
uint32_t rem = ticks - c;
|
||||
ctr->count = (uint16_t)(0x10000 - (rem % 0x10000));
|
||||
if (ctr->count == 0x10000)
|
||||
ctr->count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // Hardware Retriggerable One-Shot.
|
||||
// Output goes LOW on gate trigger, HIGH at terminal count.
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Rate Generator.
|
||||
// Counts down from N to 1. Output is HIGH for N-1, goes LOW for 1 tick, then reloads.
|
||||
{
|
||||
uint32_t rl = pitEffectiveCount(ctr);
|
||||
if (ticks >= c)
|
||||
{
|
||||
uint32_t rem = ticks - c;
|
||||
ctr->count = (uint16_t)(rl - (rem % rl));
|
||||
if (ctr->count == 0)
|
||||
ctr->count = (uint16_t)rl;
|
||||
ctr->output = true; // Output is HIGH except for 1 tick at TC.
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = (ctr->count != 1); // Output LOW for 1 clock before reload.
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // Square Wave Generator.
|
||||
// Output toggles at half the count value. Effective period = reload value.
|
||||
{
|
||||
uint32_t rl = pitEffectiveCount(ctr);
|
||||
uint32_t halfPeriod = rl / 2;
|
||||
if (halfPeriod == 0)
|
||||
halfPeriod = 1;
|
||||
|
||||
uint32_t totalTicks;
|
||||
if (ticks >= c)
|
||||
totalTicks = ticks - c;
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
break;
|
||||
}
|
||||
// Number of half-period toggles.
|
||||
uint32_t toggles = 1 + (totalTicks / halfPeriod);
|
||||
if (toggles & 1)
|
||||
ctr->output = !ctr->output;
|
||||
uint32_t rem = totalTicks % halfPeriod;
|
||||
ctr->count = (uint16_t)(halfPeriod - rem);
|
||||
if (ctr->count == 0)
|
||||
ctr->count = (uint16_t)halfPeriod;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // Software Triggered Strobe.
|
||||
// Counts down from loaded value. Output goes LOW for 1 tick at TC, then HIGH.
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // Hardware Triggered Strobe.
|
||||
// Same as mode 4 but requires gate rising edge to start.
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Public API
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void PIT8253_init(t_PIT8253 *pit, uint32_t clockHz)
|
||||
{
|
||||
memset(pit, 0, sizeof(t_PIT8253));
|
||||
pit->clockHz = clockHz;
|
||||
pit->initialized = false;
|
||||
}
|
||||
|
||||
void PIT8253_reset(t_PIT8253 *pit)
|
||||
{
|
||||
pit->initialized = false;
|
||||
}
|
||||
|
||||
uint8_t PIT8253_IO(t_PIT8253 *pit, bool read, uint8_t port, uint8_t data)
|
||||
{
|
||||
if (!pit->initialized)
|
||||
{
|
||||
for (int i = 0; i < PIT8253_NUM_COUNTERS; i++)
|
||||
{
|
||||
memset(&pit->counter[i], 0, sizeof(t_PIT8253Counter));
|
||||
pit->counter[i].gate = true;
|
||||
pit->counter[i].output = true; // Output starts HIGH for most modes.
|
||||
pit->counter[i].readLsbNext = true;
|
||||
pit->counter[i].writeLsbNext = true;
|
||||
pit->counter[i].rlMode = 3; // Default LSB then MSB.
|
||||
pit->counter[i].lastUpdate = get_absolute_time();
|
||||
}
|
||||
pit->initialized = true;
|
||||
}
|
||||
|
||||
// Control register (port 3, write only — reads return 0xFF).
|
||||
if (port == 3)
|
||||
{
|
||||
if (!read)
|
||||
{
|
||||
uint8_t ch = (data >> 6) & 0x03;
|
||||
uint8_t rl = (data >> 4) & 0x03;
|
||||
|
||||
if (ch == 3)
|
||||
{
|
||||
// Read-back command (8254 only, not 8253). Ignore silently.
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
if (rl == 0)
|
||||
{
|
||||
// Counter Latch Command — snapshot current counter value.
|
||||
if (!pit->counter[ch].latched)
|
||||
{
|
||||
pitUpdateCounter(pit, ch);
|
||||
pit->counter[ch].latchVal = pit->counter[ch].count;
|
||||
pit->counter[ch].latched = true;
|
||||
pit->counter[ch].readLsbNext = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Control Word — configure counter mode.
|
||||
pit->counter[ch].rlMode = rl;
|
||||
pit->counter[ch].mode = (data >> 1) & 0x07;
|
||||
pit->counter[ch].bcd = (data & 0x01) != 0;
|
||||
pit->counter[ch].readLsbNext = true;
|
||||
pit->counter[ch].writeLsbNext = true;
|
||||
pit->counter[ch].latched = false;
|
||||
pit->counter[ch].nullCount = true;
|
||||
pit->counter[ch].counting = false;
|
||||
|
||||
// Set initial output state per mode.
|
||||
switch (pit->counter[ch].mode)
|
||||
{
|
||||
case 0:
|
||||
pit->counter[ch].output = false; // Mode 0: output starts LOW.
|
||||
break;
|
||||
default:
|
||||
pit->counter[ch].output = true; // Modes 1-5: output starts HIGH.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// Counter data ports (0-2).
|
||||
if (port >= PIT8253_NUM_COUNTERS)
|
||||
return 0xFF;
|
||||
|
||||
t_PIT8253Counter *ctr = &pit->counter[port];
|
||||
|
||||
if (read)
|
||||
{
|
||||
// Read counter value. If latched, return latched value; otherwise return live count.
|
||||
uint16_t val;
|
||||
if (ctr->latched)
|
||||
val = ctr->latchVal;
|
||||
else
|
||||
{
|
||||
pitUpdateCounter(pit, port);
|
||||
val = ctr->count;
|
||||
}
|
||||
|
||||
uint8_t result;
|
||||
switch (ctr->rlMode)
|
||||
{
|
||||
case 1: // LSB only.
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
case 2: // MSB only.
|
||||
result = (uint8_t)((val >> 8) & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
case 3: // LSB then MSB.
|
||||
if (ctr->readLsbNext)
|
||||
{
|
||||
ctr->readLsbNext = false;
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->readLsbNext = true;
|
||||
result = (uint8_t)((val >> 8) & 0xFF);
|
||||
ctr->latched = false; // Both bytes read — release latch.
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write counter reload value.
|
||||
switch (ctr->rlMode)
|
||||
{
|
||||
case 1: // LSB only.
|
||||
ctr->reload = (ctr->reload & 0xFF00) | data;
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
break;
|
||||
case 2: // MSB only.
|
||||
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
break;
|
||||
case 3: // LSB then MSB.
|
||||
if (ctr->writeLsbNext)
|
||||
{
|
||||
ctr->reload = (ctr->reload & 0xFF00) | data;
|
||||
ctr->writeLsbNext = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
ctr->writeLsbNext = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
345
projects/tzpuPico/src/drivers/PPI8255.c
Normal file
345
projects/tzpuPico/src/drivers/PPI8255.c
Normal file
@@ -0,0 +1,345 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Name: PPI8255.c
|
||||
// Created: April 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Intel 8255 Programmable Peripheral Interface emulation.
|
||||
//
|
||||
// Full Mode 0 (simple I/O) implementation with:
|
||||
// - Independent direction for PA, PB, PC upper, PC lower
|
||||
// - Bit Set/Reset (BSR) for Port C
|
||||
// - Output latching (ports retain last written value)
|
||||
// - Per-port input callbacks (driver provides live values)
|
||||
// - Output change callback (driver notified on writes)
|
||||
// - Input injection (driver pushes values for input ports)
|
||||
// - Modes 1 & 2 accepted but operate as Mode 0
|
||||
//
|
||||
// Register map (accent offset from base):
|
||||
// +0 Port A data
|
||||
// +1 Port B data
|
||||
// +2 Port C data
|
||||
// +3 Control register
|
||||
//
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: April 2026 - New standalone reusable module.
|
||||
// -----------------------------------------------------------------------
|
||||
// This source file is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#include <string.h>
|
||||
#include "drivers/PPI8255.h"
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Decode the control word (D7=1) and update direction/mode fields.
|
||||
static void ppiDecodeControlWord(t_PPI8255 *ppi, uint8_t cw)
|
||||
{
|
||||
ppi->controlWord = cw;
|
||||
ppi->groupAMode = (cw >> 5) & 0x03; // D6:D5
|
||||
ppi->portAInput = (cw & 0x10) != 0; // D4
|
||||
ppi->portCUpperInput = (cw & 0x08) != 0; // D3
|
||||
ppi->groupBMode = (cw >> 2) & 0x01; // D2
|
||||
ppi->portBInput = (cw & 0x02) != 0; // D1
|
||||
ppi->portCLowerInput = (cw & 0x01) != 0; // D0
|
||||
|
||||
// On control word write, all output latches are cleared to 0.
|
||||
ppi->portA = 0;
|
||||
ppi->portB = 0;
|
||||
ppi->portC = 0;
|
||||
}
|
||||
|
||||
// Build the Port C read value, combining output latch bits with input bits.
|
||||
static uint8_t ppiBuildPortCRead(t_PPI8255 *ppi)
|
||||
{
|
||||
uint8_t value = 0;
|
||||
|
||||
// Upper nibble (PC7-PC4).
|
||||
if (ppi->portCUpperInput)
|
||||
{
|
||||
// Input: get value from callback or injected state.
|
||||
if (ppi->inputCallbackC)
|
||||
{
|
||||
uint8_t ext = ppi->inputCallbackC(PPI_PORT_C, ppi->callbackCtx);
|
||||
value |= (ext & 0xF0);
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= (ppi->portC & 0xF0); // Use injected value.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Output: return latched output.
|
||||
value |= (ppi->portC & 0xF0);
|
||||
}
|
||||
|
||||
// Lower nibble (PC3-PC0).
|
||||
if (ppi->portCLowerInput)
|
||||
{
|
||||
if (ppi->inputCallbackC)
|
||||
{
|
||||
uint8_t ext = ppi->inputCallbackC(PPI_PORT_C, ppi->callbackCtx);
|
||||
value |= (ext & 0x0F);
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= (ppi->portC & 0x0F);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= (ppi->portC & 0x0F);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Public API
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void PPI8255_init(t_PPI8255 *ppi)
|
||||
{
|
||||
memset(ppi, 0, sizeof(t_PPI8255));
|
||||
|
||||
// Default: Mode 0, all ports input (control word 0x9B).
|
||||
ppiDecodeControlWord(ppi, 0x9B);
|
||||
ppi->initialized = true;
|
||||
}
|
||||
|
||||
void PPI8255_reset(t_PPI8255 *ppi)
|
||||
{
|
||||
// Preserve callbacks across reset.
|
||||
t_PPIOutputCallback outCb = ppi->outputCallback;
|
||||
t_PPIInputCallback inA = ppi->inputCallbackA;
|
||||
t_PPIInputCallback inB = ppi->inputCallbackB;
|
||||
t_PPIInputCallback inC = ppi->inputCallbackC;
|
||||
void *ctx = ppi->callbackCtx;
|
||||
|
||||
memset(ppi, 0, sizeof(t_PPI8255));
|
||||
ppiDecodeControlWord(ppi, 0x9B);
|
||||
|
||||
ppi->outputCallback = outCb;
|
||||
ppi->inputCallbackA = inA;
|
||||
ppi->inputCallbackB = inB;
|
||||
ppi->inputCallbackC = inC;
|
||||
ppi->callbackCtx = ctx;
|
||||
ppi->initialized = true;
|
||||
}
|
||||
|
||||
void PPI8255_setOutputCallback(t_PPI8255 *ppi, t_PPIOutputCallback cb, void *ctx)
|
||||
{
|
||||
ppi->outputCallback = cb;
|
||||
ppi->callbackCtx = ctx;
|
||||
}
|
||||
|
||||
void PPI8255_setInputCallback(t_PPI8255 *ppi, t_PPIPort port, t_PPIInputCallback cb)
|
||||
{
|
||||
switch (port)
|
||||
{
|
||||
case PPI_PORT_A: ppi->inputCallbackA = cb; break;
|
||||
case PPI_PORT_B: ppi->inputCallbackB = cb; break;
|
||||
case PPI_PORT_C: ppi->inputCallbackC = cb; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PPI8255_IO(t_PPI8255 *ppi, bool read, uint8_t port, uint8_t data)
|
||||
{
|
||||
switch (port)
|
||||
{
|
||||
// -------------------------------------------------------------------
|
||||
// Port A (offset +0)
|
||||
// -------------------------------------------------------------------
|
||||
case 0:
|
||||
if (read)
|
||||
{
|
||||
if (ppi->portAInput)
|
||||
{
|
||||
// Input port: return live value from callback or injected state.
|
||||
if (ppi->inputCallbackA)
|
||||
return ppi->inputCallbackA(PPI_PORT_A, ppi->callbackCtx);
|
||||
return ppi->portA; // Injected value.
|
||||
}
|
||||
// Output port: return latched output value.
|
||||
return ppi->portA;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ppi->portAInput)
|
||||
{
|
||||
// Output port: latch the value and notify.
|
||||
ppi->portA = data;
|
||||
if (ppi->outputCallback)
|
||||
ppi->outputCallback(PPI_PORT_A, data, -1, -1, ppi->callbackCtx);
|
||||
}
|
||||
// Writes to input-configured ports are ignored (data is lost).
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Port B (offset +1)
|
||||
// -------------------------------------------------------------------
|
||||
case 1:
|
||||
if (read)
|
||||
{
|
||||
if (ppi->portBInput)
|
||||
{
|
||||
if (ppi->inputCallbackB)
|
||||
return ppi->inputCallbackB(PPI_PORT_B, ppi->callbackCtx);
|
||||
return ppi->portB;
|
||||
}
|
||||
return ppi->portB;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ppi->portBInput)
|
||||
{
|
||||
ppi->portB = data;
|
||||
if (ppi->outputCallback)
|
||||
ppi->outputCallback(PPI_PORT_B, data, -1, -1, ppi->callbackCtx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Port C (offset +2)
|
||||
// -------------------------------------------------------------------
|
||||
case 2:
|
||||
if (read)
|
||||
{
|
||||
return ppiBuildPortCRead(ppi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Write only affects output-direction bits.
|
||||
uint8_t mask = 0;
|
||||
if (!ppi->portCUpperInput) mask |= 0xF0;
|
||||
if (!ppi->portCLowerInput) mask |= 0x0F;
|
||||
|
||||
ppi->portC = (ppi->portC & ~mask) | (data & mask);
|
||||
|
||||
if (ppi->outputCallback && mask)
|
||||
ppi->outputCallback(PPI_PORT_C, ppi->portC, -1, -1, ppi->callbackCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Control Register (offset +3)
|
||||
// -------------------------------------------------------------------
|
||||
case 3:
|
||||
if (read)
|
||||
{
|
||||
// Control register is write-only on the 8255.
|
||||
// Some implementations return the last control word; others return 0xFF.
|
||||
return ppi->controlWord;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data & 0x80)
|
||||
{
|
||||
// I/O Mode Control Word (D7=1).
|
||||
// Reconfigure port directions and modes. Output latches cleared.
|
||||
uint8_t oldPortC = ppi->portC;
|
||||
ppiDecodeControlWord(ppi, data);
|
||||
|
||||
// Notify output callback that ports were reset.
|
||||
if (ppi->outputCallback)
|
||||
{
|
||||
ppi->outputCallback(PPI_PORT_A, 0, -1, -1, ppi->callbackCtx);
|
||||
ppi->outputCallback(PPI_PORT_B, 0, -1, -1, ppi->callbackCtx);
|
||||
if (oldPortC != 0)
|
||||
ppi->outputCallback(PPI_PORT_C, 0, -1, -1, ppi->callbackCtx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bit Set/Reset (BSR) Mode (D7=0).
|
||||
// Affects a single Port C bit without changing other bits or modes.
|
||||
uint8_t bitNum = (data >> 1) & 0x07; // D3:D1 = bit number (0-7).
|
||||
uint8_t setVal = data & 0x01; // D0 = 1=set, 0=reset.
|
||||
uint8_t bitMask = (1 << bitNum);
|
||||
|
||||
// BSR only works on output-direction bits.
|
||||
bool isOutput;
|
||||
if (bitNum >= 4)
|
||||
isOutput = !ppi->portCUpperInput;
|
||||
else
|
||||
isOutput = !ppi->portCLowerInput;
|
||||
|
||||
if (isOutput)
|
||||
{
|
||||
if (setVal)
|
||||
ppi->portC |= bitMask;
|
||||
else
|
||||
ppi->portC &= ~bitMask;
|
||||
|
||||
if (ppi->outputCallback)
|
||||
ppi->outputCallback(PPI_PORT_C, ppi->portC, bitNum, setVal, ppi->callbackCtx);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void PPI8255_injectInput(t_PPI8255 *ppi, t_PPIPort port, uint8_t value)
|
||||
{
|
||||
switch (port)
|
||||
{
|
||||
case PPI_PORT_A:
|
||||
if (ppi->portAInput)
|
||||
ppi->portA = value;
|
||||
break;
|
||||
case PPI_PORT_B:
|
||||
if (ppi->portBInput)
|
||||
ppi->portB = value;
|
||||
break;
|
||||
case PPI_PORT_C:
|
||||
{
|
||||
// Only inject into input-direction nibbles.
|
||||
uint8_t mask = 0;
|
||||
if (ppi->portCUpperInput) mask |= 0xF0;
|
||||
if (ppi->portCLowerInput) mask |= 0x0F;
|
||||
ppi->portC = (ppi->portC & ~mask) | (value & mask);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PPI8255_getOutput(t_PPI8255 *ppi, t_PPIPort port)
|
||||
{
|
||||
switch (port)
|
||||
{
|
||||
case PPI_PORT_A:
|
||||
return ppi->portAInput ? 0 : ppi->portA;
|
||||
case PPI_PORT_B:
|
||||
return ppi->portBInput ? 0 : ppi->portB;
|
||||
case PPI_PORT_C:
|
||||
{
|
||||
uint8_t value = 0;
|
||||
if (!ppi->portCUpperInput) value |= (ppi->portC & 0xF0);
|
||||
if (!ppi->portCLowerInput) value |= (ppi->portC & 0x0F);
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PPI8255_getPortCBit(t_PPI8255 *ppi, uint8_t bit)
|
||||
{
|
||||
if (bit > 7)
|
||||
return 0;
|
||||
return (ppi->portC >> bit) & 0x01;
|
||||
}
|
||||
@@ -145,9 +145,15 @@ uint8_t MZ1E05_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ1E05 Floppy Disk Interface.
|
||||
uint8_t MZ1E05_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ1E05_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ1E05_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
// Drain any pending WD1773 responses from the shared intercore queue.
|
||||
// Guard avoids calling flash-resident SDK queue functions when idle.
|
||||
if (mz1e05Ctrl && (mz1e05Ctrl->fdc.opState.loadPending || mz1e05Ctrl->fdc.opState.writePending))
|
||||
{
|
||||
wd1773_processResponses(&mz1e05Ctrl->fdc);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
@@ -181,7 +187,7 @@ uint8_t MZ1E05_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ1E05_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E05_IO_WD1773)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -198,7 +204,7 @@ uint8_t MZ1E05_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ1E05_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E05_IO_DriveSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -215,7 +221,7 @@ uint8_t MZ1E05_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ1E05_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E05_IO_SideSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -226,7 +232,7 @@ uint8_t MZ1E05_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// DDEN select (0 = MFM, 1 = FM)
|
||||
uint8_t MZ1E05_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E05_IO_DDENSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
@@ -142,7 +142,8 @@ uint8_t MZ1E14_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ1E14 ROM-Less QD MZ-1E14 interface.
|
||||
uint8_t MZ1E14_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ1E14_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ1E14_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
|
||||
@@ -182,7 +183,7 @@ uint8_t MZ1E14_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
|
||||
}
|
||||
|
||||
// I/O Handler for the Quick Disk SIO registers.
|
||||
uint8_t MZ1E14_IO_QD(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E14_IO_QD)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
@@ -145,7 +145,7 @@ uint8_t MZ1E19_Reset(t_Z80CPU *cpu)
|
||||
|
||||
// Read a byte from Z80 address space (for diagnostics — no side effects on FUNC/memioPtr).
|
||||
// Uses _membankPtr[block] like the real Z80 fetch path, NOT memPtr[addr].
|
||||
static uint8_t MZ1E19_peekZ80(t_Z80CPU *cpu, uint16_t addr)
|
||||
static uint8_t __func_in_RAM(MZ1E19_peekZ80)(t_Z80CPU *cpu, uint16_t addr)
|
||||
{
|
||||
uint8_t block = addr / MEMORY_BLOCK_SIZE;
|
||||
uint32_t membankptr = cpu->_membankPtr[block];
|
||||
@@ -192,7 +192,8 @@ static void MZ1E19_dumpContext(t_Z80CPU *cpu, const char *tag)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ1E19 ROM-Less QD MZ-1E19 interface.
|
||||
uint8_t MZ1E19_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ1E19_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ1E19_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
static uint32_t stallPollCount = 0;
|
||||
@@ -270,7 +271,7 @@ uint8_t MZ1E19_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
|
||||
}
|
||||
|
||||
// I/O Handler for the Quick Disk SIO registers.
|
||||
uint8_t MZ1E19_IO_QD(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1E19_IO_QD)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
@@ -156,9 +156,22 @@ uint8_t MZ1R12_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ1R12 S-RAM Board.
|
||||
uint8_t MZ1R12_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ1R12_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ1R12_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
// Drain any pending MZ-1R12 responses from the shared intercore queue.
|
||||
// Guard avoids calling flash-resident SDK queue functions when idle.
|
||||
if (mz1r12Ctrl)
|
||||
{
|
||||
for (int i = 0; i < mz1r12Count; i++)
|
||||
{
|
||||
if (mz1r12Ctrl[i].loadPending || mz1r12Ctrl[i].writePending)
|
||||
{
|
||||
MZ1R12_ProcessQueueResponses(cpu, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
@@ -244,7 +257,7 @@ int MZ1R12_ProcessQueueResponses(t_Z80CPU *cpu, int boardNo)
|
||||
|
||||
// MZ-1R12 S-Ram Register Handler.
|
||||
// 0xF8 - MZ-1R12 S-Ram Set High Address (W) or Reset (R)
|
||||
uint8_t MZ1R12_IO_Reg1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1R12_IO_Reg1)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -278,7 +291,7 @@ uint8_t MZ1R12_IO_Reg1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
|
||||
// MZ-1R12 S-Ram Register Handler.
|
||||
// 0xF9 - MZ-1R12 S-Ram Set Low Address (W) or Read Data (R)
|
||||
uint8_t MZ1R12_IO_Reg2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1R12_IO_Reg2)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -315,7 +328,7 @@ uint8_t MZ1R12_IO_Reg2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
|
||||
// MZ-1R12 S-Ram Register Handler.
|
||||
// 0xFA - MZ-1R12 S-Ram Write Date (W).
|
||||
uint8_t MZ1R12_IO_Reg3(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1R12_IO_Reg3)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
@@ -102,7 +102,8 @@ uint8_t MZ1R18_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ1R18 Ram File Board.
|
||||
uint8_t MZ1R18_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ1R18_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ1R18_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
|
||||
@@ -134,7 +135,7 @@ uint8_t MZ1R18_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
|
||||
|
||||
// MZ-1R18 Ram File Data Register Handler.
|
||||
// 0xEA - MZ-1R18 Ram File Data Register.
|
||||
uint8_t MZ1R18_DataReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1R18_DataReg)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -154,7 +155,7 @@ uint8_t MZ1R18_DataReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
|
||||
// MZ-1R18 Ram File Control Register Handler.
|
||||
// 0xEB - MZ-1R18 Ram File Control Register.
|
||||
uint8_t MZ1R18_CtrlReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ1R18_CtrlReg)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t ctrl = 0;
|
||||
|
||||
@@ -599,7 +599,7 @@ void MZ700_readROMData(void *ctx, void *cfg, char *buf, int len)
|
||||
// I/O handler for unconnected SIO ports (RS-232C at 0xB0-0xB3, etc.)
|
||||
// Returns idle Z80-SIO status when read: TX Buffer Empty (bit 2), no Rx data (bit 0 = 0).
|
||||
// This prevents BASIC from spinning on SIO status polls when the RS-232C board is not present.
|
||||
uint8_t MZ700_IO_SIO_NotPresent(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ700_IO_SIO_NotPresent)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
if (read)
|
||||
{
|
||||
@@ -618,7 +618,7 @@ uint8_t MZ700_IO_SIO_NotPresent(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t MZ700_IO_MemoryBankPorts(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ700_IO_MemoryBankPorts)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t port = (uint8_t) addr & 0xff;
|
||||
@@ -698,7 +698,7 @@ uint8_t MZ700_IO_MemoryBankPorts(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_
|
||||
}
|
||||
|
||||
// Debug handler, any byte output to this port will be sent to the debugf channel.
|
||||
uint8_t MZ700_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ700_IO_Debug)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t port = (uint8_t) addr & 0xff;
|
||||
@@ -798,7 +798,8 @@ uint8_t MZ700_Reset(t_Z80CPU *cpu)
|
||||
// I/O access. The code should be absolute minimum in time as it will affect Z80 emulation timing accuracy,
|
||||
// but needed as core 0 running the task could clash with core 1 and use of mutex will add similar delay.
|
||||
// Any long running tasks should be assigned to core 0.
|
||||
uint8_t MZ700_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ700_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ700_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -955,6 +956,7 @@ uint8_t MZ700_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
|
||||
{
|
||||
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
|
||||
}
|
||||
memset(cpu->memioMap, 0, MEMIO_BITMAP_SIZE);
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;
|
||||
|
||||
@@ -62,6 +62,13 @@ static t_InterfaceFuncMap interfaceFuncMap[] = {
|
||||
};
|
||||
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
|
||||
|
||||
// MZ-80A driver state for MEMSW handling.
|
||||
static bool mz80aIsPhysical = false; // True when the MZ80A driver is in PHYSICAL mode.
|
||||
static bool mz80aMemSwitch = false; // True when the memory swap (0x0000<->0xC000) is active.
|
||||
|
||||
// RFS control structure — needed to keep rfsCtrl->memSwitch in sync for RFS bank select offset.
|
||||
extern t_RFSCtrl *rfsCtrl;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Comprehensive Intel 8253 PIT (Programmable Interval Timer) emulation.
|
||||
// The MZ-80A uses an 8253 PIT at memory-mapped addresses 0xE004-0xE007
|
||||
@@ -522,7 +529,7 @@ void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
|
||||
idx < (config->romAddr[loadIdx].addr + config->romAddr[loadIdx].size) / MEMORY_BLOCK_SIZE;
|
||||
idx++)
|
||||
{
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_ROM << 24) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_RAM << 24) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = config->romAddr[loadIdx].wait;
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].tCycSync = config->romAddr[loadIdx].sync == 0 ? false : true;
|
||||
}
|
||||
@@ -535,8 +542,99 @@ void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
|
||||
return;
|
||||
}
|
||||
|
||||
// MZ-80A Memory Swap handler.
|
||||
// On the MZ-80A, a read or write to 0xE00C toggles a hardware latch that swaps the Monitor ROM
|
||||
// at 0x0000:0x0FFF with RAM at 0xC000:0xCFFF. This puts RAM at 0x0000 so CP/M can use the full
|
||||
// low memory area. In PHYSICAL mode we must also trigger the hardware latch via a physical bus cycle;
|
||||
// in VIRTUAL mode only the internal memory map needs updating.
|
||||
uint8_t __func_in_RAM(MZ80A_IO_MEMSW)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t result = data;
|
||||
|
||||
// Already switched — nothing to do.
|
||||
if (mz80aMemSwitch)
|
||||
return (result);
|
||||
|
||||
// In PHYSICAL mode, trigger the hardware swap latch via the physical bus.
|
||||
if (mz80aIsPhysical)
|
||||
{
|
||||
Z80CPU_readPhysicalMem(cpu, MEMSW);
|
||||
}
|
||||
|
||||
// Swap the internal memory map: 0x0000:0x0FFF <-> 0xC000:0xCFFF.
|
||||
uint8_t offset = 0xC000 / MEMORY_BLOCK_SIZE;
|
||||
for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
|
||||
{
|
||||
uint32_t memptr = cpu->_membankPtr[idx];
|
||||
uint8_t waitStates = cpu->_memAttr[MROM_BANK][idx].waitStates;
|
||||
bool tCycSync = cpu->_memAttr[MROM_BANK][idx].tCycSync;
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
debugf("MZ80A_MEMSW: %08x -> %08x (phys=%d)\r\n",
|
||||
cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset], mz80aIsPhysical);
|
||||
sleep_ms(1);
|
||||
}
|
||||
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
|
||||
cpu->_memAttr[MROM_BANK][idx].waitStates = cpu->_memAttr[MROM_BANK][idx + offset].waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx].tCycSync = cpu->_memAttr[MROM_BANK][idx + offset].tCycSync;
|
||||
cpu->_membankPtr[idx + offset] = memptr;
|
||||
cpu->_memAttr[MROM_BANK][idx + offset].waitStates = waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx + offset].tCycSync = tCycSync;
|
||||
}
|
||||
mz80aMemSwitch = true;
|
||||
if (rfsCtrl)
|
||||
rfsCtrl->memSwitch = true;
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// MZ-80A Memory Swap Reset handler.
|
||||
// A read or write to 0xE010 reverses the swap: ROM returns to 0x0000, RAM returns to 0xC000.
|
||||
uint8_t __func_in_RAM(MZ80A_IO_MEMSWR)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t result = data;
|
||||
|
||||
// Not switched — nothing to do.
|
||||
if (!mz80aMemSwitch)
|
||||
return (result);
|
||||
|
||||
// In PHYSICAL mode, trigger the hardware reverse-swap latch via the physical bus.
|
||||
if (mz80aIsPhysical)
|
||||
{
|
||||
Z80CPU_readPhysicalMem(cpu, MEMSWR);
|
||||
}
|
||||
|
||||
// Reverse the swap: 0x0000:0x0FFF <-> 0xC000:0xCFFF.
|
||||
uint8_t offset = 0xC000 / MEMORY_BLOCK_SIZE;
|
||||
for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
|
||||
{
|
||||
uint32_t memptr = cpu->_membankPtr[idx];
|
||||
uint8_t waitStates = cpu->_memAttr[MROM_BANK][idx].waitStates;
|
||||
bool tCycSync = cpu->_memAttr[MROM_BANK][idx].tCycSync;
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
debugf("MZ80A_MEMSWR: %08x -> %08x (phys=%d)\r\n",
|
||||
cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset], mz80aIsPhysical);
|
||||
sleep_ms(1);
|
||||
}
|
||||
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
|
||||
cpu->_memAttr[MROM_BANK][idx].waitStates = cpu->_memAttr[MROM_BANK][idx + offset].waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx].tCycSync = cpu->_memAttr[MROM_BANK][idx + offset].tCycSync;
|
||||
cpu->_membankPtr[idx + offset] = memptr;
|
||||
cpu->_memAttr[MROM_BANK][idx + offset].waitStates = waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx + offset].tCycSync = tCycSync;
|
||||
}
|
||||
mz80aMemSwitch = false;
|
||||
if (rfsCtrl)
|
||||
rfsCtrl->memSwitch = false;
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Debug handler, any byte output to this port will be sent to the debugf channel.
|
||||
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80A_IO_Debug)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t port = (uint8_t) addr & 0xff;
|
||||
(void)port;
|
||||
@@ -587,6 +685,7 @@ uint8_t MZ80A_Reset(t_Z80CPU *cpu)
|
||||
Z80CPU_writePhysicalMem(cpu, 0xE003, 0x04);
|
||||
|
||||
// Call interface reset handlers if board installed.
|
||||
// RFS_Reset will swap-back _membankPtr and clear rfsCtrl->memSwitch if MEMSW was active.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
@@ -596,11 +695,15 @@ uint8_t MZ80A_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset MEMSW state. The hardware latch is cleared by the /RESET signal; the internal
|
||||
// _membankPtr swap-back was handled by RFS_Reset above (if RFS interface is active).
|
||||
mz80aMemSwitch = false;
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Poll Callback handler.
|
||||
uint8_t MZ80A_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ80A_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -688,61 +791,61 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
|
||||
bool tCycSync = false;
|
||||
switch (idx)
|
||||
{
|
||||
// 0x0000:0x0FFF = Monitor ROM.
|
||||
case 0 ... 7:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
// 0x0000:0x0FFF = Monitor ROM.
|
||||
case 0 ... 7:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_RAM;
|
||||
// waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0x1000:0xCFFF = RAM (includes 0xC000:0xCFFF MEMSW swap target)
|
||||
case 8 ... 103:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_RAM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
// 0x1000:0xCFFF = RAM (includes 0xC000:0xCFFF MEMSW swap target)
|
||||
case 8 ... 103:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_RAM;
|
||||
waitStates = 0;
|
||||
tCycSync = false;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0xD000:0xD7FF = VRAM (2K character RAM, A0-A9)
|
||||
// Must remain physical as it controls underlying video hardware.
|
||||
case 104 ... 107:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_VRAM;
|
||||
break;
|
||||
// 0xD000:0xD7FF = VRAM (2K character RAM, A0-A9)
|
||||
// Must remain physical as it controls underlying video hardware.
|
||||
case 104 ... 107:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_VRAM;
|
||||
break;
|
||||
|
||||
// 0xD800:0xDFFF = Unused (open bus on MZ-80A, no colour RAM)
|
||||
case 108 ... 111:
|
||||
break;
|
||||
// 0xD800:0xDFFF = Unused (open bus on MZ-80A, no colour RAM)
|
||||
case 108 ... 111:
|
||||
break;
|
||||
|
||||
// 0xE000:0xE7FF = Physical IO (8255, 8253, LS367, MEMSW/MEMSWR)
|
||||
// Must remain physical as it controls underlying hardware.
|
||||
case 112 ... 115:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_HW;
|
||||
break;
|
||||
// 0xE000:0xE7FF = Physical IO (8255, 8253, LS367, MEMSW/MEMSWR)
|
||||
// Must remain physical as it controls underlying hardware.
|
||||
case 112 ... 115:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_HW;
|
||||
break;
|
||||
|
||||
// 0xE800:0xEFFF = User ROM / RFS
|
||||
case 116 ... 119:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
// 0xE800:0xEFFF = User ROM / RFS
|
||||
case 116 ... 119:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
waitStates = 0;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
|
||||
case 120 ... 127:
|
||||
if (!isPhysical)
|
||||
{
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
}
|
||||
break;
|
||||
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
|
||||
case 120 ... 127:
|
||||
if (!isPhysical)
|
||||
{
|
||||
waitStates = 0;
|
||||
tCycSync = true;
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
cpu->_membankPtr[idx] = (memType << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = waitStates;
|
||||
@@ -753,6 +856,7 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
|
||||
{
|
||||
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
|
||||
}
|
||||
memset(cpu->memioMap, 0, MEMIO_BITMAP_SIZE);
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;
|
||||
@@ -830,6 +934,31 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_PHYSICAL << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
// Install MZ-80A MEMSW/MEMSWR handlers, overriding any installed by RFS_Init.
|
||||
// The MZ-80A has hardware MEMSW support — in PHYSICAL mode the handler must also
|
||||
// trigger the hardware latch via a physical bus cycle. RFS_Init installs its own
|
||||
// MEMSW handlers for use on machines without hardware MEMSW (e.g. MZ-700).
|
||||
mz80aIsPhysical = config->isPhysical;
|
||||
mz80aMemSwitch = false;
|
||||
MEMIO_SET(cpu, MEMSW, (t_MemoryFunc) MZ80A_IO_MEMSW);
|
||||
MEMIO_SET(cpu, MEMSWR, (t_MemoryFunc) MZ80A_IO_MEMSWR);
|
||||
/*
|
||||
// Debug: dump memory attributes for key blocks to verify tCycSync and waitStates.
|
||||
debugf("MZ80A_Init: Memory attributes (bank 0):\r\n");
|
||||
for (int idx = 0; idx < MEMORY_PAGE_BLOCKS; idx++)
|
||||
{
|
||||
uint32_t mbp = cpu->_membankPtr[idx];
|
||||
uint8_t ws = cpu->_memAttr[0][idx].waitStates;
|
||||
bool ts = cpu->_memAttr[0][idx].tCycSync;
|
||||
uint8_t type = mbp >> 24;
|
||||
if (type != MEMBANK_TYPE_PHYSICAL || ws != 0 || ts)
|
||||
{
|
||||
debugf(" [%3d] %04X: type=%d ws=%d sync=%d mbp=%08X\r\n",
|
||||
idx, idx * MEMORY_BLOCK_SIZE, type, ws, ts, mbp);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,8 +133,8 @@ uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
// fetches. The FDC driver places JP (IX) at F3FE and JP (IY) at F7FE.
|
||||
// When DRQ is low, Z80 fetches from F3FE (spin loop). When DRQ goes high,
|
||||
// A10 is forced high so F3FE fetch returns F7FE data (exit to INI handler).
|
||||
cpu->_z80PSRAM->memioPtr[0xF3FE] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
|
||||
cpu->_z80PSRAM->memioPtr[0xF3FF] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
|
||||
MEMIO_SET(cpu, 0xF3FE, (t_MemoryFunc) MZ80AFI_IO_A10Toggle);
|
||||
MEMIO_SET(cpu, 0xF3FF, (t_MemoryFunc) MZ80AFI_IO_A10Toggle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +160,15 @@ uint8_t MZ80AFI_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the MZ80AFI Floppy Disk Interface.
|
||||
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(MZ80AFI_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
// Drain any pending WD1773 responses from the shared intercore queue.
|
||||
// Guard avoids calling flash-resident SDK queue functions when idle.
|
||||
if (mz80afiCtrl && (mz80afiCtrl->fdc.opState.loadPending || mz80afiCtrl->fdc.opState.writePending))
|
||||
{
|
||||
wd1773_processResponses(&mz80afiCtrl->fdc);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
@@ -200,7 +206,7 @@ uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *p
|
||||
// On the real MZ-80A, the FDC interface ORs the WD1773 DRQ signal with address line A10
|
||||
// during ROM fetches. This allows a tight spin loop at F3FE (JP (IX)) to automatically
|
||||
// exit to F7FE (JP (IY)) when data is ready, without polling the status register.
|
||||
uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80AFI_IO_A10Toggle)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
if (!read)
|
||||
return data;
|
||||
@@ -219,7 +225,7 @@ uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t da
|
||||
// wd1773_write() does val=~val, wd1773_read() does return ~val.
|
||||
// No additional inversion needed in the handler.
|
||||
|
||||
uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80AFI_IO_WD1773)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -233,10 +239,10 @@ uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
if (reg == 0 && statusReadCount < 5)
|
||||
{
|
||||
int64_t elapsed = absolute_time_diff_us(mz80afiCtrl->fdc.motorStartTime, get_absolute_time());
|
||||
debugf("FDC R[0]=%02X mot=%d loaded=%d lpend=%d rpend=%d spinUp=%llu elapsed=%lld\r\n",
|
||||
result, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded,
|
||||
mz80afiCtrl->fdc.opState.loadPending, mz80afiCtrl->fdc.opState.readPending,
|
||||
mz80afiCtrl->fdc.spinUpUs, elapsed);
|
||||
//debugf("FDC R[0]=%02X mot=%d loaded=%d lpend=%d rpend=%d spinUp=%llu elapsed=%lld\r\n",
|
||||
// result, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded,
|
||||
// mz80afiCtrl->fdc.opState.loadPending, mz80afiCtrl->fdc.opState.readPending,
|
||||
// mz80afiCtrl->fdc.spinUpUs, elapsed);
|
||||
statusReadCount++;
|
||||
}
|
||||
}
|
||||
@@ -253,7 +259,7 @@ uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80AFI_IO_DriveSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -270,7 +276,7 @@ uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t dat
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80AFI_IO_SideSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -281,7 +287,7 @@ uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data
|
||||
}
|
||||
|
||||
// DDEN select (0 = MFM, 1 = FM)
|
||||
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(MZ80AFI_IO_DDENSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ static int qdDiagDataReadCount; // Reset in qdReset to ensure diagnostics afte
|
||||
// SIO Control Write (ports 0xF6 / 0xF7)
|
||||
// Channel A = data channel (spiral track)
|
||||
// Channel B = motor/status channel
|
||||
void qdSioWriteControl(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChannelB)
|
||||
void __not_in_flash_func(qdSioWriteControl)(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChannelB)
|
||||
{
|
||||
// First write after reset = WR0 (command or pointer)
|
||||
if (ch->pointer == 0)
|
||||
@@ -240,7 +240,7 @@ void qdSioWriteControl(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChan
|
||||
void qdUpdateStatus(t_QdDrive *qd);
|
||||
|
||||
// SIO Control Read
|
||||
uint8_t qdSioReadControl(t_QdDrive *qd, t_sioChannel *ch, bool isChannelB)
|
||||
uint8_t __not_in_flash_func(qdSioReadControl)(t_QdDrive *qd, t_sioChannel *ch, bool isChannelB)
|
||||
{
|
||||
qdUpdateStatus(qd);
|
||||
uint8_t reg = ch->pointer;
|
||||
@@ -295,7 +295,7 @@ int qdGetDiskSize(t_QdDrive *qd, int machineType)
|
||||
}
|
||||
|
||||
// Process intercore message response.
|
||||
int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
|
||||
int __not_in_flash_func(qdProcessDeviceResponses)(t_QdDrive *qd, queue_t *responseQueue)
|
||||
{
|
||||
int processedCount = 0;
|
||||
int skipped = 0;
|
||||
@@ -321,26 +321,26 @@ int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
|
||||
|
||||
switch (response.type)
|
||||
{
|
||||
case MSG_LOAD_COMPLETE:
|
||||
qd->opState.loadPending = false;
|
||||
qd->diskLoaded = response.response.success;
|
||||
if (!response.response.success)
|
||||
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
|
||||
qd->intrq = true;
|
||||
break;
|
||||
|
||||
case MSG_WRITE_COMPLETE:
|
||||
if (response.requestId == qd->opState.pendingWriteId)
|
||||
{
|
||||
qd->opState.writePending = false;
|
||||
case MSG_LOAD_COMPLETE:
|
||||
qd->opState.loadPending = false;
|
||||
qd->diskLoaded = response.response.success;
|
||||
if (!response.response.success)
|
||||
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
|
||||
qd->intrq = true;
|
||||
}
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
case MSG_WRITE_COMPLETE:
|
||||
if (response.requestId == qd->opState.pendingWriteId)
|
||||
{
|
||||
qd->opState.writePending = false;
|
||||
if (!response.response.success)
|
||||
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
|
||||
qd->intrq = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
processedCount++;
|
||||
}
|
||||
@@ -348,13 +348,19 @@ int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
|
||||
}
|
||||
|
||||
// Process all intercore message responses.
|
||||
void qdProcessResponses(t_QdDrive *qd)
|
||||
// Only call qdProcessDeviceResponses when a response is actually expected.
|
||||
// This prevents non-deterministic timing from SDK queue functions (in flash)
|
||||
// being called on the Z80 emulation hot path when no operations are pending.
|
||||
void __not_in_flash_func(qdProcessResponses)(t_QdDrive *qd)
|
||||
{
|
||||
qdProcessDeviceResponses(qd, qd->responseQueue);
|
||||
if (qd->opState.loadPending || qd->opState.writePending)
|
||||
{
|
||||
qdProcessDeviceResponses(qd, qd->responseQueue);
|
||||
}
|
||||
}
|
||||
|
||||
// Status Register (RR0/RR1) update.
|
||||
void qdUpdateStatus(t_QdDrive *qd)
|
||||
void __not_in_flash_func(qdUpdateStatus)(t_QdDrive *qd)
|
||||
{
|
||||
// Locals.
|
||||
absolute_time_t now = get_absolute_time();
|
||||
@@ -526,9 +532,10 @@ void qdReset(t_QdDrive *qd)
|
||||
qd->lastRotation = 0;
|
||||
qd->huntStartTime = get_absolute_time();
|
||||
|
||||
// Clear any pending async operations — stale responses will be harmlessly processed.
|
||||
qd->opState.loadPending = false;
|
||||
qd->opState.writePending = false;
|
||||
// Preserve loadPending/writePending — in-flight intercore responses must still
|
||||
// be consumed by qdProcessDeviceResponses. Clearing them here causes the guard
|
||||
// to block queue processing, leaving responses (and those of other devices sharing
|
||||
// the queue) unprocessed, eventually filling the queue and stalling Core 0.
|
||||
}
|
||||
|
||||
// QD Initialisation.
|
||||
@@ -611,7 +618,7 @@ void qdChangeDisk(t_QdDrive *qd, const char *newFilename, int diskNo)
|
||||
}
|
||||
|
||||
// Set status flag.
|
||||
void qdSetStatusFlag(t_QdDrive *qd, uint8_t flag)
|
||||
void __not_in_flash_func(qdSetStatusFlag)(t_QdDrive *qd, uint8_t flag)
|
||||
{
|
||||
qd->statusFlags |= flag;
|
||||
}
|
||||
@@ -623,7 +630,7 @@ void qdClearStatusFlags(t_QdDrive *qd)
|
||||
}
|
||||
|
||||
// SIO Data Read.
|
||||
uint8_t qdRead(t_QdDrive *qd, uint8_t offset)
|
||||
uint8_t __not_in_flash_func(qdRead)(t_QdDrive *qd, uint8_t offset)
|
||||
{
|
||||
uint8_t value = 0xFF;
|
||||
|
||||
@@ -737,7 +744,7 @@ uint8_t qdRead(t_QdDrive *qd, uint8_t offset)
|
||||
}
|
||||
|
||||
// SIO Data port write.
|
||||
void qdWrite(t_QdDrive *qd, uint8_t offset, uint8_t val)
|
||||
void __not_in_flash_func(qdWrite)(t_QdDrive *qd, uint8_t offset, uint8_t val)
|
||||
{
|
||||
qdProcessResponses(qd);
|
||||
|
||||
|
||||
@@ -98,16 +98,45 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
// Setup the initial state of the latch up-counter, used to enable access to the programmable registers.
|
||||
rfsCtrl->upCntr = ((rfsCtrl->regCtrl & 0x20) >> 2) | ((rfsCtrl->regCtrl & 0x10) >> 2) | ((rfsCtrl->regCtrl & 0x08) >> 2);
|
||||
|
||||
// Install hardware one-shot timers on I/O ports 0x51, 0x52, 0x53 (all upper-byte variants).
|
||||
// Timer delays = body-only T-states at 2MHz (excluding CALL/RET overhead
|
||||
// which still executes at emulation speed).
|
||||
// DLY3: NEG(8)+NEG(8)+LD(7)+JP(10)+42×(DEC(4)+JP(10))+RET(10) = 631T = 315µs
|
||||
// DLY2: LD(7)+13×(DEC(4)+JP(10))+RET(10) = 199T = 100µs
|
||||
// DLY1: LD(7)+14×(DEC(4)+JP(10))+RET(10) = 213T = 107µs
|
||||
rfsCtrl->timer[0].delayUs = 315; // DLY3: 631 T-states = 315.5µs at 2MHz.
|
||||
rfsCtrl->timer[0].running = false;
|
||||
rfsCtrl->timer[1].delayUs = 100; // DLY2/L0760: 199 T-states = 99.5µs at 2MHz.
|
||||
rfsCtrl->timer[1].running = false;
|
||||
rfsCtrl->timer[2].delayUs = 107; // DLY1/L0759: 213 T-states = 106.5µs at 2MHz.
|
||||
rfsCtrl->timer[2].running = false;
|
||||
for (int hi = 0; hi < 256; hi++)
|
||||
{
|
||||
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER0_PORT] = (t_MemoryFunc) RFS_IO_Timer0;
|
||||
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER1_PORT] = (t_MemoryFunc) RFS_IO_Timer1;
|
||||
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER2_PORT] = (t_MemoryFunc) RFS_IO_Timer2;
|
||||
}
|
||||
|
||||
// Set waitStates=2 for the Monitor ROM (0x0000:0x0FFF) so each memory access
|
||||
// syncs to T1 + waits 2 additional clocks = 3 T-states (correct for Z80 memory
|
||||
// read M-cycles). Combined with the +1 M1 extra wait in fetchOpcode, this gives
|
||||
// 4T per M1 cycle — matching native 2MHz Z80 bus timing exactly.
|
||||
// This makes DLY3/DLY1/DLY2 delay loops deterministic for CMT tape timing.
|
||||
// for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
|
||||
// {
|
||||
// cpu->_memAttr[0][idx].waitStates = 1;
|
||||
// }
|
||||
|
||||
// Install Memory I/O handlers.
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRLRST] = (t_MemoryFunc) RFS_IO_BNKCTRLRST;
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRLDIS] = (t_MemoryFunc) RFS_IO_BNKCTRLDIS;
|
||||
cpu->_z80PSRAM->memioPtr[HWSPIDATA] = (t_MemoryFunc) RFS_IO_HWSPIDATA;
|
||||
cpu->_z80PSRAM->memioPtr[HWSPISTART] = (t_MemoryFunc) RFS_IO_HWSPISTART;
|
||||
cpu->_z80PSRAM->memioPtr[BNKSELMROM] = (t_MemoryFunc) RFS_IO_BNKSELMROM;
|
||||
cpu->_z80PSRAM->memioPtr[BNKSELUSER] = (t_MemoryFunc) RFS_IO_BNKSELUSER;
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRL] = (t_MemoryFunc) RFS_IO_BNKCTRL;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSW] = (t_MemoryFunc) RFS_IO_MEMSW;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSWR] = (t_MemoryFunc) RFS_IO_MEMSWR;
|
||||
MEMIO_SET(cpu, BNKCTRLRST, (t_MemoryFunc) RFS_IO_BNKCTRLRST);
|
||||
MEMIO_SET(cpu, BNKCTRLDIS, (t_MemoryFunc) RFS_IO_BNKCTRLDIS);
|
||||
MEMIO_SET(cpu, HWSPIDATA, (t_MemoryFunc) RFS_IO_HWSPIDATA);
|
||||
MEMIO_SET(cpu, HWSPISTART, (t_MemoryFunc) RFS_IO_HWSPISTART);
|
||||
MEMIO_SET(cpu, BNKSELMROM, (t_MemoryFunc) RFS_IO_BNKSELMROM);
|
||||
MEMIO_SET(cpu, BNKSELUSER, (t_MemoryFunc) RFS_IO_BNKSELUSER);
|
||||
MEMIO_SET(cpu, BNKCTRL, (t_MemoryFunc) RFS_IO_BNKCTRL);
|
||||
MEMIO_SET(cpu, MEMSW, (t_MemoryFunc) RFS_IO_MEMSW);
|
||||
MEMIO_SET(cpu, MEMSWR, (t_MemoryFunc) RFS_IO_MEMSWR);
|
||||
|
||||
// Invoke the reset handler as it sets the initial state of the RFS board.
|
||||
RFS_Reset(cpu);
|
||||
@@ -171,38 +200,39 @@ uint8_t RFS_Reset(t_Z80CPU *cpu)
|
||||
bool tCycSync = true;
|
||||
switch (idx)
|
||||
{
|
||||
// 0x0000:0x0FFF = Monitor ROM.
|
||||
case 0 ... 7:
|
||||
cpu->_membankPtr[idx] = (memType << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_memAttr[MROM_BANK][idx].waitStates = waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx].tCycSync = tCycSync;
|
||||
break;
|
||||
// 0x0000:0x0FFF = Monitor ROM.
|
||||
case 0 ... 7:
|
||||
//cpu->_membankPtr[idx] = (memType << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_RAM << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
//cpu->_memAttr[MROM_BANK][idx].waitStates = waitStates;
|
||||
cpu->_memAttr[MROM_BANK][idx].tCycSync = tCycSync;
|
||||
break;
|
||||
|
||||
// 0x1000:0xCFFF = RAM
|
||||
case 8 ... 103:
|
||||
// Leave as defined in config, physical or virtual.
|
||||
break;
|
||||
// 0x1000:0xCFFF = RAM
|
||||
case 8 ... 103:
|
||||
// Leave as defined in config, physical or virtual.
|
||||
break;
|
||||
|
||||
// 0xD000:0xDFFF = VRAM
|
||||
case 104 ... 111:
|
||||
// This region needs to be left physical as it controls underlying hardware.
|
||||
break;
|
||||
// 0xD000:0xDFFF = VRAM
|
||||
case 104 ... 111:
|
||||
// This region needs to be left physical as it controls underlying hardware.
|
||||
break;
|
||||
|
||||
// 0xE000:0xE7FF = Physical IO
|
||||
case 112 ... 115:
|
||||
// This region needs to be left physical as it controls underlying hardware.
|
||||
break;
|
||||
// 0xE000:0xE7FF = Physical IO
|
||||
case 112 ... 115:
|
||||
// This region needs to be left physical as it controls underlying hardware.
|
||||
break;
|
||||
|
||||
// 0xE800:0xEFFF = RFS
|
||||
case 116 ... 119:
|
||||
cpu->_membankPtr[idx] = (memType << 24) | (USER_ROM_I_BANK << 16) | (UROM_RFS_BANK_OFFSET + ((idx - 116) * MEMORY_BLOCK_SIZE));
|
||||
cpu->_memAttr[USER_ROM_I_BANK][idx].waitStates = 0;
|
||||
cpu->_memAttr[USER_ROM_I_BANK][idx].tCycSync = false;
|
||||
break;
|
||||
// 0xE800:0xEFFF = RFS
|
||||
case 116 ... 119:
|
||||
cpu->_membankPtr[idx] = (memType << 24) | (USER_ROM_I_BANK << 16) | (UROM_RFS_BANK_OFFSET + ((idx - 116) * MEMORY_BLOCK_SIZE));
|
||||
cpu->_memAttr[USER_ROM_I_BANK][idx].waitStates = 0;
|
||||
cpu->_memAttr[USER_ROM_I_BANK][idx].tCycSync = false;
|
||||
break;
|
||||
|
||||
// 0xF000:0xFFFF = Empty
|
||||
case 120 ... 127:
|
||||
break;
|
||||
// 0xF000:0xFFFF = Empty
|
||||
case 120 ... 127:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,9 +240,15 @@ uint8_t RFS_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the RFS S-RAM Board.
|
||||
uint8_t RFS_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t RFS_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(RFS_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
// Drain any pending RFS responses from the shared intercore queue.
|
||||
// Guard avoids calling flash-resident SDK queue functions when idle.
|
||||
if (rfsCtrl && rfsCtrl->loadPending)
|
||||
{
|
||||
RFS_ProcessQueueResponses(cpu);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
@@ -296,7 +332,7 @@ int RFS_ProcessQueueResponses(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Bank control reset, returns all registers to power up default.
|
||||
uint8_t RFS_IO_BNKCTRLRST(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_BNKCTRLRST)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = data;
|
||||
@@ -316,7 +352,7 @@ uint8_t RFS_IO_BNKCTRLRST(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Disable bank control registers by resetting the coded latch.
|
||||
uint8_t RFS_IO_BNKCTRLDIS(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_BNKCTRLDIS)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -340,7 +376,7 @@ uint8_t RFS_IO_BNKCTRLDIS(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Hardware SPI Data register (read/write).
|
||||
uint8_t RFS_IO_HWSPIDATA(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_HWSPIDATA)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = data;
|
||||
@@ -368,7 +404,7 @@ uint8_t RFS_IO_HWSPIDATA(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Start an SPI transfer.
|
||||
uint8_t RFS_IO_HWSPISTART(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_HWSPISTART)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = data;
|
||||
@@ -394,7 +430,7 @@ uint8_t RFS_IO_HWSPISTART(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Select RFS Bank1 (MROM)
|
||||
uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_BNKSELMROM)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -436,7 +472,7 @@ uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
// Monitor ROM is located at 0x0000 (+offset if MZ80A and memory switch invoked).
|
||||
uint8_t bank = (uint8_t) (rfsCtrl->mromAddr / MEMORY_PAGE_SIZE) + MROM_BANK;
|
||||
uint16_t bankAddr = (rfsCtrl->mromAddr & 0xF000) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_membankPtr[idx + memSwitchOffset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
|
||||
cpu->_membankPtr[idx + memSwitchOffset] = (MEMBANK_TYPE_RAM << 24) | (bank << 16) | (bankAddr);
|
||||
cpu->_memAttr[bank][idx + memSwitchOffset].waitStates = 1;
|
||||
cpu->_memAttr[bank][idx + memSwitchOffset].tCycSync = true;
|
||||
}
|
||||
@@ -447,7 +483,7 @@ uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Select RFS Bank2 (User ROM)
|
||||
uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_BNKSELUSER)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -507,7 +543,7 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Bank Control register (read/write).
|
||||
uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_BNKCTRL)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
@@ -562,7 +598,7 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Function to perform a memory swap aka MZ-80A, where RAM at C000 is swapped to 0000 and ROM is swapped to C000.
|
||||
uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_MEMSW)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = data;
|
||||
@@ -581,7 +617,7 @@ uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
// debugf("MEMSW: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
|
||||
debugf("MEMSW: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
|
||||
sleep_ms(1);
|
||||
}
|
||||
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
|
||||
@@ -597,7 +633,7 @@ uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
}
|
||||
|
||||
// Function to perform a memory swap reset aka MZ-80A, where RAM at 0000 is swapped to C000 and ROM is swapped to 0000.
|
||||
uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
uint8_t __func_in_RAM(RFS_IO_MEMSWR)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = data;
|
||||
@@ -616,7 +652,7 @@ uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
// debugf("MEMSWR: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
|
||||
debugf("MEMSWR: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
|
||||
sleep_ms(1);
|
||||
}
|
||||
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
|
||||
@@ -636,7 +672,7 @@ uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
//
|
||||
// All SD commands are added to the processor but logic (apart from a standard response) is only added for commands which RFS uses.
|
||||
//
|
||||
void rfsSDCard(t_Z80CPU *cpu)
|
||||
void __func_in_RAM(rfsSDCard)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
//
|
||||
@@ -736,11 +772,11 @@ void rfsSDCard(t_Z80CPU *cpu)
|
||||
//debugf("Writing %d bytes, CRC(%02x,%02x)\r\n", noBytes, rfsCtrl->sd.cmdBuf[SD_SECSIZE+2], rfsCtrl->sd.cmdBuf[SD_SECSIZE+3]);
|
||||
|
||||
// Write the sector to the SD card image.
|
||||
debugf("SD WR LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
|
||||
//debugf("SD WR LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
|
||||
if (ESP_writeSector(SD_CARD_FILENAME, rfsCtrl->sd.lbaAddr * SD_SECSIZE, &rfsCtrl->sd.cmdBuf[1], SD_SECSIZE) == false)
|
||||
{
|
||||
sdSendResponse[0] = 0x06; // Write error.
|
||||
debugf("SD WR FAIL LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
|
||||
//debugf("SD WR FAIL LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
|
||||
}
|
||||
|
||||
// Send response.
|
||||
@@ -757,7 +793,7 @@ void rfsSDCard(t_Z80CPU *cpu)
|
||||
{
|
||||
rfsCtrl->sd.rcvCntr = 0;
|
||||
|
||||
debugf("SD CMD:%02x LBA:%02x%02x%02x%02x\r\n", rfsCtrl->sd.cmdBuf[0],rfsCtrl->sd.cmdBuf[1],rfsCtrl->sd.cmdBuf[2],rfsCtrl->sd.cmdBuf[3],rfsCtrl->sd.cmdBuf[4]);
|
||||
//debugf("SD CMD:%02x LBA:%02x%02x%02x%02x\r\n", rfsCtrl->sd.cmdBuf[0],rfsCtrl->sd.cmdBuf[1],rfsCtrl->sd.cmdBuf[2],rfsCtrl->sd.cmdBuf[3],rfsCtrl->sd.cmdBuf[4]);
|
||||
switch (rfsCtrl->sd.cmdBuf[0])
|
||||
{
|
||||
case CMD0: // GO_IDLE_STATE
|
||||
@@ -853,7 +889,7 @@ void rfsSDCard(t_Z80CPU *cpu)
|
||||
// Assemble response buffer including SD card sector.
|
||||
memset(sdRcvResponse, 0, sizeof(sdRcvResponse));
|
||||
int readBytes = ESP_readSector(SD_CARD_FILENAME, rfsCtrl->sd.lbaAddr * SD_SECSIZE, &sdRcvResponse[2], SD_SECSIZE);
|
||||
debugf("SD RD LBA:%08x bytes:%d\r\n", rfsCtrl->sd.lbaAddr, readBytes);
|
||||
//debugf("SD RD LBA:%08x bytes:%d\r\n", rfsCtrl->sd.lbaAddr, readBytes);
|
||||
if (readBytes == SD_SECSIZE)
|
||||
{
|
||||
sdRcvResponse[0] = 0x00; // Response R1
|
||||
@@ -972,3 +1008,59 @@ void rfsSDCard(t_Z80CPU *cpu)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RFS_IO_Timer0 / RFS_IO_Timer1 — One-shot microsecond timers.
|
||||
//
|
||||
// Write (OUT (C),A): Configure the delay. addr[15:8] = B (high byte),
|
||||
// data = A (low byte). Combined 16-bit value = delay in µs.
|
||||
//
|
||||
// Read (IN A,(port)):
|
||||
// - First read after expiry (or initial): starts the countdown, returns 0.
|
||||
// - While counting: returns 0.
|
||||
// - When expired: returns 1 and auto-resets (next read starts a new cycle).
|
||||
//
|
||||
// Used by sa1510.asm CMT routines:
|
||||
// Port 0x51 (timer[0]): DLY3 — 317µs bit-sampling delay.
|
||||
// Port 0x52 (timer[1]): L0760 — 101µs write timing delay.
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint8_t __func_in_RAM(rfs_timer_handler)(int idx, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
if(read)
|
||||
{
|
||||
if (!rfsCtrl->timer[idx].running)
|
||||
{
|
||||
rfsCtrl->timer[idx].targetTime = time_us_64() + rfsCtrl->timer[idx].delayUs;
|
||||
rfsCtrl->timer[idx].running = true;
|
||||
return 0;
|
||||
}
|
||||
if (time_us_64() >= rfsCtrl->timer[idx].targetTime)
|
||||
{
|
||||
rfsCtrl->timer[idx].running = false;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
debugf("Timer%d Write: %02x, %04x, %04x\r\n", idx, data, addr, rfsCtrl->timer[idx].delayUs);
|
||||
rfsCtrl->timer[idx].delayUs = (addr & 0xFF00) | data;
|
||||
rfsCtrl->timer[idx].running = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t __func_in_RAM(RFS_IO_Timer0)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
return rfs_timer_handler(0, read, addr, data);
|
||||
}
|
||||
|
||||
uint8_t __func_in_RAM(RFS_IO_Timer1)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
return rfs_timer_handler(1, read, addr, data);
|
||||
}
|
||||
|
||||
uint8_t __func_in_RAM(RFS_IO_Timer2)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
return rfs_timer_handler(2, read, addr, data);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,8 @@ uint8_t TZFS_Reset(t_Z80CPU *cpu)
|
||||
}
|
||||
|
||||
// Poll handler for the TZFS S-RAM Board.
|
||||
uint8_t TZFS_PollCB(t_Z80CPU *cpu)
|
||||
//uint8_t TZFS_PollCB(t_Z80CPU *cpu)
|
||||
uint8_t __func_in_RAM(TZFS_PollCB)(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ void wd1773_changeDisk(t_WD1773 *wd, const char *name, int diskNo)
|
||||
/**
|
||||
* @brief Select head
|
||||
*/
|
||||
void wd1773_setHead(t_WD1773 *wd, uint8_t head)
|
||||
void __not_in_flash_func(wd1773_setHead)(t_WD1773 *wd, uint8_t head)
|
||||
{
|
||||
wd->currentHead = head & 1;
|
||||
}
|
||||
@@ -420,7 +420,7 @@ void wd1773_setHead(t_WD1773 *wd, uint8_t head)
|
||||
/**
|
||||
* @brief Set density mode
|
||||
*/
|
||||
void wd1773_setDensity(t_WD1773 *wd, uint8_t single)
|
||||
void __not_in_flash_func(wd1773_setDensity)(t_WD1773 *wd, uint8_t single)
|
||||
{
|
||||
wd->densityMfm = !single;
|
||||
}
|
||||
@@ -481,7 +481,7 @@ uint8_t wd1773_getStatus(t_WD1773 *wd)
|
||||
/**
|
||||
* @brief Read FDC register (Z80 sees inverted data)
|
||||
*/
|
||||
uint8_t wd1773_read(t_WD1773 *wd, uint8_t offset)
|
||||
uint8_t __not_in_flash_func(wd1773_read)(t_WD1773 *wd, uint8_t offset)
|
||||
{
|
||||
wd1773_processResponses(wd);
|
||||
uint8_t val = 0;
|
||||
@@ -547,7 +547,7 @@ uint8_t wd1773_read(t_WD1773 *wd, uint8_t offset)
|
||||
/**
|
||||
* @brief Write FDC register (Z80 sends inverted data)
|
||||
*/
|
||||
void wd1773_write(t_WD1773 *wd, uint8_t offset, uint8_t val)
|
||||
void __not_in_flash_func(wd1773_write)(t_WD1773 *wd, uint8_t offset, uint8_t val)
|
||||
{
|
||||
wd1773_processResponses(wd);
|
||||
val = ~val;
|
||||
|
||||
3
projects/tzpuPico/src/include/ESP.h
vendored
3
projects/tzpuPico/src/include/ESP.h
vendored
@@ -39,6 +39,7 @@
|
||||
#include "hardware/dma.h"
|
||||
#include "cJSON.h"
|
||||
#include "ipc_protocol.h"
|
||||
#include "flash_ram.h"
|
||||
|
||||
// Constants.
|
||||
#define ESP_MAX_CMDBUF_LEN 128
|
||||
@@ -71,7 +72,7 @@ typedef struct
|
||||
bool ESP_init(void);
|
||||
void ESP_deinit(void);
|
||||
bool ESP_pollNop(char *cmdBuf, size_t cmdBufLen);
|
||||
bool ESP_sendVersionInfo(void);
|
||||
bool ESP_sendVersionInfo(const void *cpuPtr);
|
||||
int ESP_readFloppyDiskFile(const char *fileName,
|
||||
void *ctx,
|
||||
void *cfg,
|
||||
|
||||
5
projects/tzpuPico/src/include/FSPI.h
vendored
5
projects/tzpuPico/src/include/FSPI.h
vendored
@@ -69,9 +69,10 @@
|
||||
|
||||
// Peripheral and SPI settings
|
||||
#define FSPI_PERIPHERAL spi1
|
||||
// At 50MHz the SPI RX FIFO (4 bytes deep) can overflow when core-1 Z80/PSRAM
|
||||
// At 50MHz the SPI RX FIFO (4 bytes deep) can overflow when core-1 Z80/PSRAM
|
||||
// accesses stall the AHB crossbar, causing dropped bytes mid-transfer and CRC mismatches.
|
||||
// If too many CRC errors are seen in the log, reduce the frequency.
|
||||
// 25MHz is reliable at sys_clk <= 320MHz. If overclocking above 320MHz, reduce to
|
||||
// 12MHz (48MHz clk_peri / 4 = 2.67µs per FIFO fill) to prevent AHB contention overflow.
|
||||
#define FSPI_CLK_FREQ 25 * 1000 * 1000
|
||||
#define FSPI_DATABITS_PER_XFER 8
|
||||
#define FSPI_DATA_POLARITY SPI_CPOL_1
|
||||
|
||||
26
projects/tzpuPico/src/include/Z80CPU.h
vendored
26
projects/tzpuPico/src/include/Z80CPU.h
vendored
@@ -280,12 +280,13 @@ typedef struct
|
||||
{
|
||||
uint8_t RAM[MEMORY_PAGE_BANKS * MEMORY_PAGE_SIZE];
|
||||
uint32_t memPtr[MEMORY_PAGE_SIZE];
|
||||
t_MemoryFunc memioPtr[MEMORY_PAGE_SIZE];
|
||||
t_MemoryFunc memioPtr[MEMORY_PAGE_SIZE]; // Function pointer array in PSRAM (64K × 4 bytes).
|
||||
t_MemoryFunc ioPtr[IO_PAGE_SIZE];
|
||||
//uint8_t (*memioPtr[MEMORY_PAGE_SIZE])(bool read, uint16_t addr, uint8_t data);
|
||||
//uint8_t (*ioPtr[IO_PAGE_SIZE])(uint16_t addr, uint8_t data);
|
||||
} t_Z80PSRAM;
|
||||
|
||||
// Size of the SRAM bitmap for memioPtr: 1 bit per Z80 address = 64K/8 = 8KB.
|
||||
#define MEMIO_BITMAP_SIZE (MEMORY_PAGE_SIZE / 8)
|
||||
|
||||
// Struct for string-value pair
|
||||
typedef struct
|
||||
{
|
||||
@@ -398,14 +399,18 @@ struct Z80CPU
|
||||
uint32_t _membankPtr[MEMORY_PAGE_BLOCKS]; // Fast RAM - quickest RAM which is RP2350's 'limited' main memory used for indexing CPU address to address in RAM array.
|
||||
t_memAttr _memAttr[MEMORY_PAGE_BANKS][MEMORY_PAGE_BLOCKS]; // Fast RAM - attributes associated with each RAM block.
|
||||
t_Z80PSRAM *_z80PSRAM; // PSRAM - Slower RAM as it is accessed via SPI, used for banked RAM and general storage.
|
||||
uint8_t memioMap[MEMIO_BITMAP_SIZE]; // SRAM bitmap: 1 bit per Z80 addr, set if memioPtr[addr] != NULL.
|
||||
t_drivers _drivers; // Handle to array of structures to specify active drivers and their configuration.
|
||||
queue_t requestQueue; // Inter core command request queue. Used to dispatch commands from the Z80 to core0 for processing.
|
||||
queue_t responseQueue; // Inter core command wresponse queue. Used to dispatch responses to commands received from the Z80 on core0.
|
||||
bool halt; // Halt state, true = active.
|
||||
bool refreshEnable; // Enable DRAM refresh during virtual memory fetches. Set via JSON config "refresh" parameter.
|
||||
bool refreshEnable; // Enable DRAM refresh during virtual memory fetches. Set via JSON config "z80refresh" parameter.
|
||||
volatile bool hold; // Hold CPU processing, used by Core 0 when access to physical is needed.
|
||||
volatile bool holdAck; // Hold confirmation flag.
|
||||
volatile bool forceReset; // Force a CPU reset asynchronously.
|
||||
uint32_t hostClkHz; // Measured host clock frequency in Hz (GPIO35, measured at boot).
|
||||
uint32_t emulSpeedHz; // Effective emulation speed in Hz (computed from Z80 cycle counter).
|
||||
volatile uint32_t emulLoopCount; // Incremented by Core 1 after each z80_run() call (32-bit, atomic on ARM).
|
||||
|
||||
// Pending task from Core 0 — processed by Core 1 in the emulation loop.
|
||||
// Core 0 writes these, Core 1 reads and clears pendingTask.
|
||||
@@ -414,6 +419,19 @@ struct Z80CPU
|
||||
char pendingTaskParam[128];// Parameter string for the task.
|
||||
};
|
||||
|
||||
// Bitmap helpers for memioMap — 1 bit per Z80 address in fast SRAM.
|
||||
// Check before accessing memioPtr in PSRAM to avoid slow SPI reads on the hot path.
|
||||
#define MEMIO_BIT_SET(cpu, addr) ((cpu)->memioMap[(addr) >> 3] |= (1 << ((addr) & 7)))
|
||||
#define MEMIO_BIT_CLR(cpu, addr) ((cpu)->memioMap[(addr) >> 3] &= ~(1 << ((addr) & 7)))
|
||||
#define MEMIO_BIT_TEST(cpu, addr) ((cpu)->memioMap[(addr) >> 3] & (1 << ((addr) & 7)))
|
||||
|
||||
// Set a memioPtr entry and update the SRAM bitmap.
|
||||
#define MEMIO_SET(cpu, addr, func) do { \
|
||||
(cpu)->_z80PSRAM->memioPtr[(addr)] = (t_MemoryFunc)(func); \
|
||||
if ((func) != NULL) MEMIO_BIT_SET(cpu, addr); \
|
||||
else MEMIO_BIT_CLR(cpu, addr); \
|
||||
} while(0)
|
||||
|
||||
// Struct for double-value pair
|
||||
typedef struct
|
||||
{
|
||||
|
||||
71
projects/tzpuPico/src/include/drivers/PIT8253.h
vendored
Normal file
71
projects/tzpuPico/src/include/drivers/PIT8253.h
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Name: PIT8253.h
|
||||
// Created: April 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Intel 8253 Programmable Interval Timer emulation.
|
||||
// Standalone module usable by any Sharp MZ series driver.
|
||||
// Supports all 6 counter modes, BCD/binary counting,
|
||||
// counter latching, and LSB/MSB/LSB-then-MSB read/load modes.
|
||||
//
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: April 2026 - Extracted from MZ700.c / MZ80A.c into
|
||||
// standalone reusable module.
|
||||
// -----------------------------------------------------------------------
|
||||
// This source file is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#ifndef PIT8253_H
|
||||
#define PIT8253_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "pico/time.h"
|
||||
#include "Z80CPU.h"
|
||||
|
||||
#define PIT8253_NUM_COUNTERS 3
|
||||
|
||||
// Individual counter state.
|
||||
typedef struct
|
||||
{
|
||||
uint16_t count; // Current counter value (CE = Counting Element).
|
||||
uint16_t reload; // Reload value (CR = Count Register, written by CPU).
|
||||
uint16_t latchVal; // Latched counter value for reading.
|
||||
bool latched; // True if counter value has been latched (counter latch cmd).
|
||||
uint8_t mode; // Counter mode (0-5).
|
||||
uint8_t rlMode; // Read/Load mode: 1=LSB only, 2=MSB only, 3=LSB then MSB.
|
||||
bool bcd; // True = BCD counting, false = binary.
|
||||
bool readLsbNext; // For RL mode 3 reads: true = next read byte is LSB.
|
||||
bool writeLsbNext; // For RL mode 3 writes: true = next write byte is LSB.
|
||||
bool gate; // Gate input state (active high for modes 0,2,3,4; edge for 1,5).
|
||||
bool output; // Output pin state.
|
||||
bool counting; // Counter has been loaded and is actively counting.
|
||||
bool nullCount; // True while new count written but not yet loaded into CE.
|
||||
bool reloadPending; // For modes 2/3: reload pending on next terminal count.
|
||||
absolute_time_t lastUpdate; // Last time counter was updated.
|
||||
} t_PIT8253Counter;
|
||||
|
||||
// PIT context — one instance per driver.
|
||||
typedef struct
|
||||
{
|
||||
t_PIT8253Counter counter[PIT8253_NUM_COUNTERS];
|
||||
uint32_t clockHz; // Clock input frequency (e.g., 895000 for MZ-700/MZ-80A).
|
||||
bool initialized;
|
||||
} t_PIT8253;
|
||||
|
||||
// Initialise a PIT context. Call once at driver init time.
|
||||
// clockHz = input clock frequency in Hz (typically 895000 for Sharp MZ series).
|
||||
void PIT8253_init(t_PIT8253 *pit, uint32_t clockHz);
|
||||
|
||||
// Reset a PIT context. Forces re-initialisation on next I/O access.
|
||||
void PIT8253_reset(t_PIT8253 *pit);
|
||||
|
||||
// I/O handler. addr bits 1:0 select the port (0-2 = counter data, 3 = control).
|
||||
// read = true for IN, false for OUT. data = byte written (ignored on read).
|
||||
// Returns the byte read (or 0 for writes).
|
||||
uint8_t PIT8253_IO(t_PIT8253 *pit, bool read, uint8_t port, uint8_t data);
|
||||
|
||||
#endif // PIT8253_H
|
||||
123
projects/tzpuPico/src/include/drivers/PPI8255.h
vendored
Normal file
123
projects/tzpuPico/src/include/drivers/PPI8255.h
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// Name: PPI8255.h
|
||||
// Created: April 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Intel 8255 Programmable Peripheral Interface emulation.
|
||||
// Standalone module usable by any Sharp MZ series driver.
|
||||
//
|
||||
// Supports:
|
||||
// - Mode 0 (simple I/O) for all ports
|
||||
// - Modes 1 & 2 (strobed/bidirectional) — stubbed
|
||||
// - Bit Set/Reset (BSR) for Port C
|
||||
// - Independent direction control for PA, PB, PCU, PCL
|
||||
// - Per-bit callbacks for output changes
|
||||
// - Per-port input injection for reads
|
||||
//
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: April 2026 - New standalone reusable module.
|
||||
// -----------------------------------------------------------------------
|
||||
// This source file is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#ifndef PPI8255_H
|
||||
#define PPI8255_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Port identifiers.
|
||||
typedef enum
|
||||
{
|
||||
PPI_PORT_A = 0,
|
||||
PPI_PORT_B = 1,
|
||||
PPI_PORT_C = 2,
|
||||
PPI_CONTROL = 3
|
||||
} t_PPIPort;
|
||||
|
||||
// Callback for output changes.
|
||||
// port = PPI_PORT_A / B / C
|
||||
// value = new 8-bit output value for the port
|
||||
// bit = specific bit that changed (0-7), or -1 if full-port write
|
||||
// state = bit value (0 or 1) for BSR, or -1 for full-port write
|
||||
// ctx = user context pointer registered at init time
|
||||
typedef void (*t_PPIOutputCallback)(t_PPIPort port, uint8_t value, int bit, int state, void *ctx);
|
||||
|
||||
// Callback for input reads.
|
||||
// port = PPI_PORT_A / B / C
|
||||
// ctx = user context pointer
|
||||
// Returns the 8-bit value to present on the port.
|
||||
typedef uint8_t (*t_PPIInputCallback)(t_PPIPort port, void *ctx);
|
||||
|
||||
// PPI context — one instance per emulated 8255.
|
||||
typedef struct
|
||||
{
|
||||
// Latched output values.
|
||||
uint8_t portA;
|
||||
uint8_t portB;
|
||||
uint8_t portC;
|
||||
|
||||
// Control word (last I/O mode control word written with D7=1).
|
||||
uint8_t controlWord;
|
||||
|
||||
// Direction flags derived from control word.
|
||||
// true = input, false = output.
|
||||
bool portAInput; // Port A direction.
|
||||
bool portBInput; // Port B direction.
|
||||
bool portCUpperInput; // PC7-PC4 direction.
|
||||
bool portCLowerInput; // PC3-PC0 direction.
|
||||
|
||||
// Group modes (from control word).
|
||||
uint8_t groupAMode; // 0, 1, or 2.
|
||||
uint8_t groupBMode; // 0 or 1.
|
||||
|
||||
// Callbacks.
|
||||
t_PPIOutputCallback outputCallback; // Called when an output port changes.
|
||||
t_PPIInputCallback inputCallbackA; // Called when Port A is read (if input).
|
||||
t_PPIInputCallback inputCallbackB; // Called when Port B is read (if input).
|
||||
t_PPIInputCallback inputCallbackC; // Called when Port C is read (if input).
|
||||
void *callbackCtx; // User context passed to all callbacks.
|
||||
|
||||
bool initialized;
|
||||
} t_PPI8255;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// API
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Initialise a PPI context. All ports default to Mode 0 input, values = 0.
|
||||
void PPI8255_init(t_PPI8255 *ppi);
|
||||
|
||||
// Reset a PPI context. Clears all ports and sets Mode 0 all-input.
|
||||
void PPI8255_reset(t_PPI8255 *ppi);
|
||||
|
||||
// Register the output callback (called whenever an output port value changes).
|
||||
void PPI8255_setOutputCallback(t_PPI8255 *ppi, t_PPIOutputCallback cb, void *ctx);
|
||||
|
||||
// Register per-port input callbacks (called when a port configured as input is read).
|
||||
void PPI8255_setInputCallback(t_PPI8255 *ppi, t_PPIPort port, t_PPIInputCallback cb);
|
||||
|
||||
// I/O handler. port = 0 (PA), 1 (PB), 2 (PC), 3 (Control).
|
||||
// read = true for read, false for write. data = byte written (ignored on read).
|
||||
// Returns the byte read (or 0 for writes).
|
||||
uint8_t PPI8255_IO(t_PPI8255 *ppi, bool read, uint8_t port, uint8_t data);
|
||||
|
||||
// Inject a value into an input port (for use by the driver to provide external state).
|
||||
// Only affects ports configured as input. For Port C, the value is masked by
|
||||
// the input bits (upper/lower nibble direction).
|
||||
void PPI8255_injectInput(t_PPI8255 *ppi, t_PPIPort port, uint8_t value);
|
||||
|
||||
// Read the current output latch value of a port (for use by the driver to
|
||||
// query what the Z80 last wrote). For Port C, returns the combined
|
||||
// output bits with input bits masked to 0.
|
||||
uint8_t PPI8255_getOutput(t_PPI8255 *ppi, t_PPIPort port);
|
||||
|
||||
// Read the current state of a specific Port C bit (0-7).
|
||||
// Returns the output latch bit for output-direction bits, or the injected
|
||||
// input bit for input-direction bits.
|
||||
uint8_t PPI8255_getPortCBit(t_PPI8255 *ppi, uint8_t bit);
|
||||
|
||||
#endif // PPI8255_H
|
||||
@@ -43,6 +43,8 @@ void MZ80A_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len);
|
||||
void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len);
|
||||
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ80A_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName);
|
||||
|
||||
@@ -92,6 +92,18 @@ extern "C"
|
||||
|
||||
#define BNKCTRLDEF BBMOSI + SDCS + BBCLK // Default on startup for the Bank Control register.
|
||||
|
||||
// picoZ80 hardware microsecond timer — I/O port accessible from Z80.
|
||||
// Write: two bytes (low then high) = delay in microseconds, starts on high-byte write.
|
||||
// Read: returns 0 while counting, 1 when expired.
|
||||
// Provides CPU-speed-independent delays for CMT tape timing etc.
|
||||
// picoZ80 hardware one-shot timers — two I/O ports for CMT tape timing.
|
||||
// Write (OUT (C),A): sets delay in µs. addr[15:8]=B (high byte), data=A (low byte).
|
||||
// Read (IN A,(port)): first read starts countdown & returns 0.
|
||||
// subsequent reads return 0 while counting, 1 when expired (auto-resets).
|
||||
#define RFS_TIMER0_PORT 0x51 // DLY3 timer (~324µs for CMT bit sampling)
|
||||
#define RFS_TIMER1_PORT 0x52 // DLY2/L0760 timer (~101µs for CMT write timing)
|
||||
#define RFS_TIMER2_PORT 0x53 // DLY1/L0759 timer (~107µs for CMT timing)
|
||||
|
||||
// RFS Board ROM rom load and size definitions.
|
||||
#define ROM_MROM_LOAD_ADDR 0x000000
|
||||
#define ROM_USER_I_LOAD_ADDR 0x080000
|
||||
@@ -170,6 +182,11 @@ extern "C"
|
||||
uint8_t memSwitch; // MZ-80A can relocate the lower 4K ROM by swapping RAM at 0xC000.
|
||||
bool loadPending; // A queued read is pending receipt when true.
|
||||
t_SDCtrl sd; // SD Control.
|
||||
struct {
|
||||
uint16_t delayUs; // Configured delay in microseconds.
|
||||
uint64_t targetTime; // Absolute target time (time_us_64).
|
||||
bool running; // True while countdown is active.
|
||||
} timer[3]; // timer[0]=port 0x51, timer[1]=port 0x52, timer[2]=port 0x53.
|
||||
} t_RFSCtrl;
|
||||
|
||||
// Public prototypes.
|
||||
@@ -189,6 +206,9 @@ extern "C"
|
||||
uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t RFS_IO_Timer0(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t RFS_IO_Timer1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t RFS_IO_Timer2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
void rfsSDCard(t_Z80CPU *cpu);
|
||||
|
||||
|
||||
|
||||
9
projects/tzpuPico/src/include/flash_ram.h
vendored
9
projects/tzpuPico/src/include/flash_ram.h
vendored
@@ -153,7 +153,11 @@ typedef struct
|
||||
t_FlashPartitionInstance config[FLASH_APP_MAX_INSTANCES];
|
||||
} t_FlashPartitionHeader;
|
||||
|
||||
// Extended INF payload — partition header + runtime core config.
|
||||
// Maximum size of the active driver summary string sent via INF IPC.
|
||||
// Format: "DriverName(V/P) InterfaceName(V/P) ..." e.g. "MZ700(V) RFS(V)"
|
||||
#define FLASHHDR_DRIVER_SUMMARY_SIZE 128
|
||||
|
||||
// Extended INF payload — partition header + runtime core config + active drivers.
|
||||
// Sent by RP2350 ESP_sendVersionInfo, received by ESP32 storeRP2350Info.
|
||||
typedef struct
|
||||
{
|
||||
@@ -163,6 +167,9 @@ typedef struct
|
||||
int32_t voltage; // Core voltage setting (VREG enum)
|
||||
uint32_t flashSize; // RP2350 Flash size in bytes (from PICO_FLASH_SIZE_BYTES)
|
||||
uint32_t psramSize; // RP2350 PSRAM size in bytes (from psram_init)
|
||||
uint32_t hostClkHz; // Measured host clock frequency in Hz (GPIO35 at boot)
|
||||
uint32_t emulSpeedHz; // Effective Z80 emulation speed in Hz
|
||||
char driverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers and interfaces (human-readable)
|
||||
} t_FlashInfoPayload;
|
||||
|
||||
// Structure to describe a single file stored in ROM. Indexed by its filename (matching a filename appearing in JSON including path)
|
||||
|
||||
1
projects/tzpuPico/src/include/psram.h
vendored
1
projects/tzpuPico/src/include/psram.h
vendored
@@ -34,6 +34,7 @@
|
||||
#define PSRAM_LOCATION _u(0x11000000)
|
||||
|
||||
extern void psram_set_qmi_timing();
|
||||
extern void psram_set_flash_timing_for_freq(uint32_t target_sys_hz);
|
||||
extern void psram_timing(int freq);
|
||||
extern size_t psram_init(uint cs_pin, int freq);
|
||||
|
||||
|
||||
@@ -759,7 +759,7 @@ void processSPICommands(const char *cmd)
|
||||
if (strcmp(cmd, "INF") == 0)
|
||||
{
|
||||
// Send the version information to the ESP32.
|
||||
ESP_sendVersionInfo();
|
||||
ESP_sendVersionInfo(z80CPU);
|
||||
}
|
||||
|
||||
// Hardware RESET, HRST
|
||||
@@ -1376,14 +1376,42 @@ int main(void)
|
||||
if (flashAppConfigHeader->s.configured == FLASH_APP_CONFIGURED_FLAG && flashAppConfigHeader->s.voltage != 0)
|
||||
voltage = flashAppConfigHeader->s.voltage;
|
||||
|
||||
// Guard against psramFreq == 0 from uninitialized or legacy config data.
|
||||
if (psramFreq <= 0)
|
||||
psramFreq = 133000000;
|
||||
|
||||
// Setup runtime Voltage / Frequency. Overclock to maximise emulation accuracy.
|
||||
//voltage = VREG_VOLTAGE_1_25;
|
||||
//cpuFreq = 300000000;
|
||||
//psramFreq = 109000000;
|
||||
bootStage(BOOTP_CLK_SET);
|
||||
vreg_disable_voltage_limit(); // Allow voltages above 1.30V for overclocking
|
||||
vreg_set_voltage(voltage);
|
||||
sleep_ms(5);
|
||||
clkInit = set_sys_clock_hz(cpuFreq, false);
|
||||
// Pre-set flash QMI timing for the target CPU frequency BEFORE raising the clock.
|
||||
// set_sys_clock_hz() does NOT update QMI M0 timing — without this, the flash SPI
|
||||
// clock exceeds 133MHz spec when sys_clk goes above ~266MHz, causing a hang.
|
||||
psram_set_flash_timing_for_freq(cpuFreq);
|
||||
// The PLL can only produce frequencies that are VCO/(postdiv1*postdiv2) where
|
||||
// VCO is 750-1600 MHz and postdiv values are 1-7. Not all frequencies are
|
||||
// achievable. Search downward in 1 MHz steps from the requested frequency
|
||||
// to find the nearest one the PLL can lock to.
|
||||
clkInit = false;
|
||||
{
|
||||
uint32_t tryFreq = cpuFreq;
|
||||
uint32_t minFreq = (cpuFreq > 10000000) ? cpuFreq - 10000000 : 50000000;
|
||||
while (tryFreq >= minFreq)
|
||||
{
|
||||
if (set_sys_clock_hz(tryFreq, false))
|
||||
{
|
||||
psram_set_qmi_timing(); // Optimize flash M0 timing for actual clock — runs from RAM
|
||||
clkInit = true;
|
||||
cpuFreq = tryFreq;
|
||||
break;
|
||||
}
|
||||
tryFreq -= 1000000; // Step down 1 MHz
|
||||
}
|
||||
}
|
||||
// clkInit result checked later after debug is online.
|
||||
|
||||
// Initialise the PSRAM - all heap space is stored in PSRAM (see main_memmap.ld).
|
||||
@@ -1428,9 +1456,75 @@ int main(void)
|
||||
// Delayed message as debug was not online.
|
||||
if (!clkInit)
|
||||
{
|
||||
// Fallback to safe 133 MHz
|
||||
// No achievable frequency found within 10 MHz of the requested value.
|
||||
set_sys_clock_khz(133000, true);
|
||||
debugf("Setting cpufreq to %dKHz failed, defaulting to 133MHz.\r\n", cpuFreq / 1000);
|
||||
debugf("Setting cpufreq to %dMHz failed (no PLL solution within 10MHz), defaulting to 133MHz.\r\n", cpuFreq / 1000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
debugf("CPU clock set to %lu MHz (actual %lu Hz)\r\n",
|
||||
(unsigned long)(cpuFreq / 1000000), (unsigned long)clock_get_hz(clk_sys));
|
||||
}
|
||||
|
||||
// Measure the host clock frequency on GPIO 35 before PIO takes over the pin.
|
||||
// Uses the PWM counter in input mode — the hardware counts rising edges on the
|
||||
// GPIO pin while we time a 100ms window. Accurate and reliable even at MHz rates.
|
||||
uint32_t hostClkHz = 0;
|
||||
{
|
||||
const uint HOST_CLK_PIN = 35;
|
||||
const uint32_t MEASURE_US = 100000; // 100ms measurement window
|
||||
uint slice = pwm_gpio_to_slice_num(HOST_CLK_PIN);
|
||||
uint chan = pwm_gpio_to_channel(HOST_CLK_PIN);
|
||||
|
||||
// Configure GPIO as PWM input (B channel counts rising edges).
|
||||
gpio_set_function(HOST_CLK_PIN, GPIO_FUNC_PWM);
|
||||
pwm_config cfg = pwm_get_default_config();
|
||||
pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING); // Count rising edges on channel B
|
||||
pwm_config_set_clkdiv(&cfg, 1); // No clock divider
|
||||
pwm_init(slice, &cfg, false); // Don't start yet
|
||||
|
||||
// Clear counter then run for the measurement window.
|
||||
pwm_set_counter(slice, 0);
|
||||
pwm_set_enabled(slice, true);
|
||||
uint64_t startUs = time_us_64();
|
||||
while ((time_us_64() - startUs) < MEASURE_US)
|
||||
tight_loop_contents();
|
||||
pwm_set_enabled(slice, false);
|
||||
|
||||
uint16_t count = pwm_get_counter(slice);
|
||||
// PWM counter is 16-bit (max 65535). Use a 10ms window for good accuracy.
|
||||
// At 3.5 MHz that's ~35,468 edges (within range). At 6.5 MHz (max for 16-bit
|
||||
// in 10ms) it would be ~65,000. For clocks > 6.5 MHz use 1ms window instead.
|
||||
uint32_t windowUs = 10000; // 10ms default
|
||||
pwm_set_counter(slice, 0);
|
||||
pwm_set_enabled(slice, true);
|
||||
startUs = time_us_64();
|
||||
while ((time_us_64() - startUs) < windowUs)
|
||||
tight_loop_contents();
|
||||
pwm_set_enabled(slice, false);
|
||||
count = pwm_get_counter(slice);
|
||||
|
||||
// If counter is near overflow, reduce window and re-measure.
|
||||
if (count > 60000)
|
||||
{
|
||||
windowUs = 1000; // 1ms window for higher frequencies
|
||||
pwm_set_counter(slice, 0);
|
||||
pwm_set_enabled(slice, true);
|
||||
startUs = time_us_64();
|
||||
while ((time_us_64() - startUs) < windowUs)
|
||||
tight_loop_contents();
|
||||
pwm_set_enabled(slice, false);
|
||||
count = pwm_get_counter(slice);
|
||||
}
|
||||
|
||||
hostClkHz = (uint32_t)count * (1000000 / windowUs);
|
||||
debugf("Host clock (GPIO35): %u edges in %lums = %lu.%03lu MHz\r\n",
|
||||
(unsigned)count, (unsigned long)(windowUs / 1000),
|
||||
(unsigned long)(hostClkHz / 1000000),
|
||||
(unsigned long)((hostClkHz % 1000000) / 1000));
|
||||
|
||||
// Release the pin — PIO will reconfigure it.
|
||||
gpio_deinit(HOST_CLK_PIN);
|
||||
}
|
||||
|
||||
// Initialise PIO to perform low level CPU interface.
|
||||
@@ -1441,6 +1535,8 @@ int main(void)
|
||||
// Initialise the Z80 CPU.
|
||||
bootStage(BOOTP_Z80_INIT);
|
||||
z80CPU = Z80CPU_init();
|
||||
if (z80CPU)
|
||||
z80CPU->hostClkHz = hostClkHz;
|
||||
// Z80CPU result checked below.
|
||||
|
||||
if (pioInitResult || // PIO not initialised?
|
||||
@@ -1748,11 +1844,31 @@ int main(void)
|
||||
bootStage(BOOTP_ML_SPI_CMD);
|
||||
processSPICommands(spiCmd);
|
||||
}
|
||||
if (lastInfoMs == 0 && nowMs >= 5000)
|
||||
if (nowMs - lastInfoMs >= 5000)
|
||||
{
|
||||
bootStage(BOOTP_ML_INF);
|
||||
|
||||
// Compute effective Z80 emulation speed from the loop counter.
|
||||
// emulLoopCount is a 32-bit counter incremented on Core 1 after each
|
||||
// z80_run(64) call — a single aligned 32-bit read is atomic on ARM.
|
||||
// Each loop iteration executes ~64 Z80 T-states.
|
||||
if (z80CPU)
|
||||
{
|
||||
static uint32_t prevLoops = 0;
|
||||
static uint32_t prevMs = 0;
|
||||
uint32_t curLoops = z80CPU->emulLoopCount;
|
||||
if (prevMs > 0 && nowMs > prevMs)
|
||||
{
|
||||
uint32_t deltaLoops = curLoops - prevLoops; // handles 32-bit wrap
|
||||
uint32_t deltaMs = nowMs - prevMs;
|
||||
z80CPU->emulSpeedHz = (uint32_t)((uint64_t)deltaLoops * 64ULL * 1000ULL / deltaMs);
|
||||
}
|
||||
prevLoops = curLoops;
|
||||
prevMs = nowMs;
|
||||
}
|
||||
|
||||
lastInfoMs = nowMs;
|
||||
ESP_sendVersionInfo();
|
||||
ESP_sendVersionInfo(z80CPU);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.083
|
||||
2.221
|
||||
|
||||
@@ -47,12 +47,16 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)()
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the minimum divisor assuming a 133MHz flash.
|
||||
// RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the
|
||||
// falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips.
|
||||
const int max_flash_freq = 133000000;
|
||||
const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq;
|
||||
const int rxdelay = divisor;
|
||||
// At sys_clk <= 320MHz, use the full 133MHz flash spec (divisor=3, SCK=106.7MHz,
|
||||
// rxdelay=3 — sample point at the SCK period boundary, safe with PCB round-trip
|
||||
// margin). Above 320MHz, the shorter system-clock period makes rxdelay=divisor
|
||||
// marginal at divisor=3, so fall back to 100MHz limit (divisor=4, more headroom).
|
||||
const int clock_hz = clock_get_hz(clk_sys);
|
||||
const int max_flash_freq = (clock_hz <= 320000000) ? 133000000 : 100000000;
|
||||
const int divisor = (clock_hz + max_flash_freq - 1) / max_flash_freq;
|
||||
int rxdelay = divisor;
|
||||
if (rxdelay > 7)
|
||||
rxdelay = 7;
|
||||
qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | (rxdelay << QMI_M0_TIMING_RXDELAY_LSB) | (divisor << QMI_M0_TIMING_CLKDIV_LSB);
|
||||
|
||||
// Force a read through XIP to ensure the timing is applied
|
||||
@@ -60,6 +64,40 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)()
|
||||
(void) *ptr;
|
||||
}
|
||||
|
||||
void __no_inline_not_in_flash_func(psram_set_flash_timing_for_freq)(uint32_t target_sys_hz)
|
||||
{
|
||||
// Pre-set flash QMI M0 timing conservatively for a target system clock frequency.
|
||||
// Called BEFORE set_sys_clock_hz() — the timing must be safe at BOTH the current
|
||||
// boot frequency AND the target frequency, because set_sys_clock_pll() runs from
|
||||
// flash and the clock transitions mid-function.
|
||||
//
|
||||
// Uses 100MHz max flash SCK so the divisor is high enough to keep flash safe
|
||||
// during the entire transition. psram_set_qmi_timing() is called immediately
|
||||
// AFTER set_sys_clock_hz() to apply the final optimized timing.
|
||||
int timeout = 10000;
|
||||
while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS)
|
||||
{
|
||||
timeout--;
|
||||
if (!timeout)
|
||||
break;
|
||||
}
|
||||
|
||||
// Conservative 100MHz limit ensures safe flash access at all points during the
|
||||
// clock transition (boot freq → 48MHz USB PLL → target freq).
|
||||
const int max_flash_freq = 100000000;
|
||||
const uint32_t current_hz = clock_get_hz(clk_sys);
|
||||
const uint32_t max_hz = (target_sys_hz > current_hz) ? target_sys_hz : current_hz;
|
||||
const int divisor = (max_hz + max_flash_freq - 1) / max_flash_freq;
|
||||
int rxdelay = divisor;
|
||||
if (rxdelay > 7)
|
||||
rxdelay = 7;
|
||||
qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | (rxdelay << QMI_M0_TIMING_RXDELAY_LSB) | (divisor << QMI_M0_TIMING_CLKDIV_LSB);
|
||||
|
||||
// Force a read through XIP to ensure the timing is applied.
|
||||
volatile uint32_t *ptr = (volatile uint32_t *) 0x14000000;
|
||||
(void) *ptr;
|
||||
}
|
||||
|
||||
// Busy-wait helper with timeout. Returns true if condition cleared, false on timeout.
|
||||
// Timeout is in iterations (~10ns each at 150MHz), not wall-clock.
|
||||
#define QMI_BUSY_TIMEOUT 100000
|
||||
@@ -201,9 +239,9 @@ void __no_inline_not_in_flash_func(psram_timing)(int freq)
|
||||
//
|
||||
// Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz.
|
||||
// So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late),
|
||||
// and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz).
|
||||
//const int max_psram_freq = 133000000;
|
||||
//const int max_psram_freq = 100000000;
|
||||
// and add an extra 1 to the rxdelay if the divided clock is >= 100MHz (i.e. sys clock >= 200MHz).
|
||||
if (freq <= 0)
|
||||
freq = 133000000;
|
||||
const int max_psram_freq = freq;
|
||||
const int clock_hz = clock_get_hz(clk_sys);
|
||||
int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq;
|
||||
@@ -212,15 +250,19 @@ void __no_inline_not_in_flash_func(psram_timing)(int freq)
|
||||
divisor = 2;
|
||||
}
|
||||
int rxdelay = divisor;
|
||||
if (clock_hz / divisor > 100000000)
|
||||
if (clock_hz / divisor >= 100000000)
|
||||
{
|
||||
rxdelay += 1;
|
||||
}
|
||||
if (rxdelay > 7)
|
||||
rxdelay = 7;
|
||||
|
||||
// - Max select must be <= 8us. The value is given in multiples of 64 system clocks.
|
||||
// - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2).
|
||||
const int clock_period_fs = 1000000000000000ll / clock_hz;
|
||||
const int maxSelect = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64
|
||||
int maxSelect = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64
|
||||
if (maxSelect > 63)
|
||||
maxSelect = 63;
|
||||
const int minDeselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2;
|
||||
|
||||
qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB |
|
||||
|
||||
5
projects/tzpuPico/src/z80.pio
vendored
5
projects/tzpuPico/src/z80.pio
vendored
@@ -207,13 +207,12 @@ cycle_nowait:
|
||||
out exec, 16
|
||||
jmp cycle_nowait ; Loop until jmp start executed.
|
||||
cycle_refresh:
|
||||
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
wait 0 gpio Z80_PIN_CLK [3] ; T4: GPIO35 low
|
||||
set pins, 0b1101 side 0b11 [3]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
.wrap
|
||||
|
||||
|
||||
2
projects/tzpuPico/version.txt
vendored
2
projects/tzpuPico/version.txt
vendored
@@ -1 +1 @@
|
||||
2.079
|
||||
2.208
|
||||
|
||||
Reference in New Issue
Block a user