2020-03-25 21:35:35 -03:00
|
|
|
/*
|
|
|
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
|
|
|
* running old operating systems and software designed for IBM
|
|
|
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
|
|
|
* system designs based on the PCI bus.
|
|
|
|
|
*
|
|
|
|
|
* This file is part of the 86Box distribution.
|
|
|
|
|
*
|
|
|
|
|
* Emulation of SPD (Serial Presence Detect) devices.
|
|
|
|
|
*
|
2020-04-26 19:24:15 -03:00
|
|
|
*
|
2020-03-25 21:35:35 -03:00
|
|
|
*
|
|
|
|
|
* Authors: RichardG, <richardg867@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2020 RichardG.
|
|
|
|
|
*/
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <wchar.h>
|
|
|
|
|
#define HAVE_STDARG_H
|
2020-03-29 14:24:42 +02:00
|
|
|
#include <86box/86box.h>
|
|
|
|
|
#include <86box/device.h>
|
2020-11-20 01:22:04 -03:00
|
|
|
#include <86box/i2c.h>
|
2020-03-29 14:24:42 +02:00
|
|
|
#include <86box/spd.h>
|
2020-06-26 13:26:42 +02:00
|
|
|
#include <86box/version.h>
|
2020-06-26 21:03:46 -03:00
|
|
|
#include <86box/machine.h>
|
2020-03-25 21:35:35 -03:00
|
|
|
|
|
|
|
|
|
2020-05-16 20:32:28 -03:00
|
|
|
#define SPD_ROLLUP(x) ((x) >= 16 ? ((x) - 15) : (x))
|
2020-03-25 21:35:35 -03:00
|
|
|
|
|
|
|
|
|
2020-06-26 18:05:27 -03:00
|
|
|
int spd_present = 0;
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_t *spd_modules[SPD_MAX_SLOTS];
|
|
|
|
|
|
|
|
|
|
static const device_t spd_device;
|
2020-03-25 21:35:35 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_SPD_LOG
|
|
|
|
|
int spd_do_log = ENABLE_SPD_LOG;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
spd_log(const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
if (spd_do_log) {
|
2020-11-20 01:22:04 -03:00
|
|
|
va_start(ap, fmt);
|
|
|
|
|
pclog_ex(fmt, ap);
|
|
|
|
|
va_end(ap);
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
#define spd_log(fmt, ...)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
spd_close(void *priv)
|
|
|
|
|
{
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: close()\n");
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
for (uint8_t i = 0; i < SPD_MAX_SLOTS; i++) {
|
|
|
|
|
if (spd_modules[i])
|
|
|
|
|
i2c_eeprom_close(spd_modules[i]->eeprom);
|
|
|
|
|
}
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-06-26 18:24:15 -03:00
|
|
|
spd_present = 0;
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void *
|
|
|
|
|
spd_init(const device_t *info)
|
|
|
|
|
{
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: init()\n");
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
for (uint8_t i = 0; i < SPD_MAX_SLOTS; i++) {
|
|
|
|
|
if (spd_modules[i])
|
|
|
|
|
spd_modules[i]->eeprom = i2c_eeprom_init(i2c_smbus, SPD_BASE_ADDR + i, spd_modules[i]->data, sizeof(spd_modules[i]->data), 0);
|
|
|
|
|
}
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-06-26 18:24:15 -03:00
|
|
|
spd_present = 1;
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
return &spd_modules;
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
comp_ui16_rev(const void *elem1, const void *elem2)
|
|
|
|
|
{
|
2020-05-16 20:32:28 -03:00
|
|
|
uint16_t a = *((uint16_t *) elem1);
|
|
|
|
|
uint16_t b = *((uint16_t *) elem2);
|
2020-03-25 21:35:35 -03:00
|
|
|
return ((a > b) ? -1 : ((a < b) ? 1 : 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_populate(uint16_t *rows, uint8_t slot_count, uint16_t total_size, uint16_t min_module_size, uint16_t max_module_size, uint8_t enable_asym)
|
2020-03-25 21:35:35 -03:00
|
|
|
{
|
2020-11-23 14:49:49 -03:00
|
|
|
uint8_t row, next_empty_row, split, i;
|
2020-06-26 18:05:27 -03:00
|
|
|
uint16_t asym;
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Populate rows with modules in power-of-2 capacities. */
|
|
|
|
|
memset(rows, 0, SPD_MAX_SLOTS << 1);
|
|
|
|
|
for (row = 0; row < slot_count && total_size; row++) {
|
2020-11-20 01:22:04 -03:00
|
|
|
/* populate slot */
|
2020-11-30 20:16:04 -03:00
|
|
|
rows[row] = 1 << log2i(MIN(total_size, max_module_size));
|
2020-11-23 14:49:49 -03:00
|
|
|
if (total_size >= rows[row]) {
|
|
|
|
|
spd_log("SPD: Initial row %d = %d MB\n", row, rows[row]);
|
|
|
|
|
total_size -= rows[row];
|
2020-11-20 01:22:04 -03:00
|
|
|
} else {
|
2020-11-23 14:49:49 -03:00
|
|
|
rows[row] = 0;
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
|
|
|
|
}
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Did we populate all the RAM? */
|
2020-05-16 20:32:28 -03:00
|
|
|
if (total_size) {
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Work backwards to add the missing RAM as asymmetric modules if possible. */
|
2020-11-20 01:22:04 -03:00
|
|
|
if (enable_asym) {
|
2020-11-23 14:49:49 -03:00
|
|
|
row = slot_count - 1;
|
2020-11-20 01:22:04 -03:00
|
|
|
do {
|
2020-11-30 20:16:04 -03:00
|
|
|
asym = (1 << log2i(MIN(total_size, rows[row])));
|
2020-11-23 14:49:49 -03:00
|
|
|
if (rows[row] + asym <= max_module_size) {
|
|
|
|
|
rows[row] += asym;
|
2020-11-20 01:22:04 -03:00
|
|
|
total_size -= asym;
|
|
|
|
|
}
|
2020-11-23 14:49:49 -03:00
|
|
|
} while ((row-- > 0) && total_size);
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (total_size) /* still not enough */
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: Not enough RAM slots (%d) to cover memory (%d MB short)\n", slot_count, total_size);
|
2020-05-16 20:32:28 -03:00
|
|
|
}
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Populate empty rows by splitting modules... */
|
|
|
|
|
split = (total_size == 0); /* ...if possible. */
|
2020-03-25 21:35:35 -03:00
|
|
|
while (split) {
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Look for a module to split. */
|
2020-11-20 01:22:04 -03:00
|
|
|
split = 0;
|
2020-11-23 14:49:49 -03:00
|
|
|
for (row = 0; row < slot_count; row++) {
|
2020-11-30 20:16:04 -03:00
|
|
|
if ((rows[row] < (min_module_size << 1)) || (rows[row] != (1 << log2i(rows[row]))))
|
2020-11-20 01:22:04 -03:00
|
|
|
continue; /* no module here, module is too small to be split, or asymmetric module */
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Find next empty row. */
|
|
|
|
|
next_empty_row = 0;
|
|
|
|
|
for (i = row + 1; i < slot_count && !next_empty_row; i++) {
|
|
|
|
|
if (!rows[i])
|
|
|
|
|
next_empty_row = i;
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
2020-11-23 14:49:49 -03:00
|
|
|
if (!next_empty_row)
|
|
|
|
|
break; /* no empty rows left */
|
2020-11-20 01:22:04 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Split the module into its own row and the next empty row. */
|
|
|
|
|
spd_log("SPD: splitting row %d (%d MB) into %d and %d (%d MB each)\n", row, rows[row], row, next_empty_row, rows[row] >> 1);
|
|
|
|
|
rows[row] = rows[next_empty_row] = rows[row] >> 1;
|
2020-11-20 01:22:04 -03:00
|
|
|
split = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Sort rows by descending capacity if any were split. */
|
2020-11-20 01:22:04 -03:00
|
|
|
if (split)
|
2020-11-23 14:49:49 -03:00
|
|
|
qsort(rows, slot_count, sizeof(uint16_t), comp_ui16_rev);
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
2020-06-26 18:05:27 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-16 19:50:32 -03:00
|
|
|
static int
|
|
|
|
|
spd_write_part_no(char *part_no, char *type, uint16_t size)
|
|
|
|
|
{
|
|
|
|
|
char size_unit;
|
|
|
|
|
|
|
|
|
|
if (size >= 1024) {
|
|
|
|
|
size_unit = 'G';
|
|
|
|
|
size >>= 10;
|
|
|
|
|
} else {
|
|
|
|
|
size_unit = 'M';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sprintf(part_no, EMU_NAME "-%s-%03d%c", type, size, size_unit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-06-26 18:05:27 -03:00
|
|
|
void
|
|
|
|
|
spd_register(uint8_t ram_type, uint8_t slot_mask, uint16_t max_module_size)
|
|
|
|
|
{
|
2020-11-23 14:49:49 -03:00
|
|
|
uint8_t slot, slot_count, row, i;
|
|
|
|
|
uint16_t min_module_size, rows[SPD_MAX_SLOTS], asym;
|
2020-06-26 18:05:27 -03:00
|
|
|
spd_edo_t *edo_data;
|
|
|
|
|
spd_sdram_t *sdram_data;
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Determine the minimum module size for this RAM type. */
|
2020-06-26 18:05:27 -03:00
|
|
|
switch (ram_type) {
|
2020-11-20 01:22:04 -03:00
|
|
|
case SPD_TYPE_FPM:
|
|
|
|
|
case SPD_TYPE_EDO:
|
|
|
|
|
min_module_size = SPD_MIN_SIZE_EDO;
|
|
|
|
|
break;
|
2020-06-26 18:05:27 -03:00
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
case SPD_TYPE_SDRAM:
|
|
|
|
|
min_module_size = SPD_MIN_SIZE_SDRAM;
|
|
|
|
|
break;
|
2020-06-26 18:05:27 -03:00
|
|
|
|
|
|
|
|
default:
|
2020-11-20 01:22:04 -03:00
|
|
|
spd_log("SPD: unknown RAM type %02X\n", ram_type);
|
|
|
|
|
return;
|
2020-06-26 18:05:27 -03:00
|
|
|
}
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Count how many slots are enabled. */
|
2020-06-26 18:05:27 -03:00
|
|
|
slot_count = 0;
|
|
|
|
|
for (slot = 0; slot < SPD_MAX_SLOTS; slot++) {
|
2020-11-23 14:49:49 -03:00
|
|
|
rows[slot] = 0;
|
|
|
|
|
if (slot_mask & (1 << slot))
|
2020-11-20 01:22:04 -03:00
|
|
|
slot_count++;
|
2022-02-20 02:26:27 -05:00
|
|
|
}
|
2020-06-26 18:05:27 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Populate rows. */
|
|
|
|
|
spd_populate(rows, slot_count, (mem_size >> 10), min_module_size, max_module_size, 1);
|
2020-03-25 21:35:35 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
/* Register SPD devices and populate their data according to the rows. */
|
|
|
|
|
row = 0;
|
2021-10-16 19:50:32 -03:00
|
|
|
for (slot = 0; (slot < SPD_MAX_SLOTS) && rows[row]; slot++) {
|
2020-11-20 01:22:04 -03:00
|
|
|
if (!(slot_mask & (1 << slot)))
|
|
|
|
|
continue; /* slot disabled */
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_modules[slot] = (spd_t *) malloc(sizeof(spd_t));
|
|
|
|
|
memset(spd_modules[slot], 0, sizeof(spd_t));
|
|
|
|
|
spd_modules[slot]->slot = slot;
|
|
|
|
|
spd_modules[slot]->size = rows[row];
|
|
|
|
|
|
|
|
|
|
/* Determine the second row size, from which the first row size can be obtained. */
|
2020-11-30 20:16:04 -03:00
|
|
|
asym = rows[row] - (1 << log2i(rows[row])); /* separate the powers of 2 */
|
2020-11-20 01:22:04 -03:00
|
|
|
if (!asym) /* is the module asymmetric? */
|
2020-11-23 14:49:49 -03:00
|
|
|
asym = rows[row] >> 1; /* symmetric, therefore divide by 2 */
|
2020-11-20 01:22:04 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_modules[slot]->row1 = rows[row] - asym;
|
|
|
|
|
spd_modules[slot]->row2 = asym;
|
2020-11-20 01:22:04 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: Registering slot %d = row %d = %d MB (%d/%d)\n", slot, row, rows[row], spd_modules[slot]->row1, spd_modules[slot]->row2);
|
2020-11-20 01:22:04 -03:00
|
|
|
|
|
|
|
|
switch (ram_type) {
|
|
|
|
|
case SPD_TYPE_FPM:
|
|
|
|
|
case SPD_TYPE_EDO:
|
2021-04-17 16:27:38 -03:00
|
|
|
edo_data = &spd_modules[slot]->edo_data;
|
2020-11-20 01:22:04 -03:00
|
|
|
|
|
|
|
|
/* EDO SPD is specified by JEDEC and present in some modules, but
|
|
|
|
|
most utilities cannot interpret it correctly. SIV32 at least gets
|
|
|
|
|
the module capacities right, so it was used as a reference here. */
|
|
|
|
|
edo_data->bytes_used = 0x80;
|
|
|
|
|
edo_data->spd_size = 0x08;
|
|
|
|
|
edo_data->mem_type = ram_type;
|
2020-11-30 20:16:04 -03:00
|
|
|
edo_data->row_bits = SPD_ROLLUP(7 + log2i(spd_modules[slot]->row1)); /* first row */
|
2020-11-20 01:22:04 -03:00
|
|
|
edo_data->col_bits = 9;
|
2020-11-23 14:49:49 -03:00
|
|
|
if (spd_modules[slot]->row1 != spd_modules[slot]->row2) { /* the upper 4 bits of row_bits/col_bits should be 0 on a symmetric module */
|
2020-11-30 20:16:04 -03:00
|
|
|
edo_data->row_bits |= SPD_ROLLUP(7 + log2i(spd_modules[slot]->row2)) << 4; /* second row, if different from first */
|
2020-11-20 01:22:04 -03:00
|
|
|
edo_data->col_bits |= 9 << 4; /* same as first row, but just in case */
|
|
|
|
|
}
|
|
|
|
|
edo_data->banks = 2;
|
|
|
|
|
edo_data->data_width_lsb = 64;
|
|
|
|
|
edo_data->signal_level = SPD_SIGNAL_LVTTL;
|
|
|
|
|
edo_data->trac = 50;
|
|
|
|
|
edo_data->tcac = 13;
|
|
|
|
|
edo_data->refresh_rate = SPD_REFRESH_NORMAL;
|
|
|
|
|
edo_data->dram_width = 8;
|
|
|
|
|
|
|
|
|
|
edo_data->spd_rev = 0x12;
|
2021-10-16 19:50:32 -03:00
|
|
|
for (i = spd_write_part_no(edo_data->part_no, (ram_type == SPD_TYPE_FPM) ? "FPM" : "EDO", rows[row]);
|
|
|
|
|
i < sizeof(edo_data->part_no); i++)
|
2020-11-20 01:22:04 -03:00
|
|
|
edo_data->part_no[i] = ' '; /* part number should be space-padded */
|
|
|
|
|
edo_data->rev_code[0] = BCD8(EMU_VERSION_MAJ);
|
|
|
|
|
edo_data->rev_code[1] = BCD8(EMU_VERSION_MIN);
|
|
|
|
|
edo_data->mfg_year = 20;
|
|
|
|
|
edo_data->mfg_week = 17;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 63; i++)
|
2020-11-23 14:49:49 -03:00
|
|
|
edo_data->checksum += spd_modules[slot]->data[i];
|
2020-11-20 01:22:04 -03:00
|
|
|
for (i = 0; i < 129; i++)
|
2020-11-23 14:49:49 -03:00
|
|
|
edo_data->checksum2 += spd_modules[slot]->data[i];
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SPD_TYPE_SDRAM:
|
2021-04-17 16:27:38 -03:00
|
|
|
sdram_data = &spd_modules[slot]->sdram_data;
|
2020-11-20 01:22:04 -03:00
|
|
|
|
|
|
|
|
sdram_data->bytes_used = 0x80;
|
|
|
|
|
sdram_data->spd_size = 0x08;
|
|
|
|
|
sdram_data->mem_type = ram_type;
|
2020-11-30 20:16:04 -03:00
|
|
|
sdram_data->row_bits = SPD_ROLLUP(6 + log2i(spd_modules[slot]->row1)); /* first row */
|
2020-11-20 01:22:04 -03:00
|
|
|
sdram_data->col_bits = 9;
|
2020-11-23 14:49:49 -03:00
|
|
|
if (spd_modules[slot]->row1 != spd_modules[slot]->row2) { /* the upper 4 bits of row_bits/col_bits should be 0 on a symmetric module */
|
2020-11-30 20:16:04 -03:00
|
|
|
sdram_data->row_bits |= SPD_ROLLUP(6 + log2i(spd_modules[slot]->row2)) << 4; /* second row, if different from first */
|
2020-11-20 01:22:04 -03:00
|
|
|
sdram_data->col_bits |= 9 << 4; /* same as first row, but just in case */
|
|
|
|
|
}
|
|
|
|
|
sdram_data->rows = 2;
|
|
|
|
|
sdram_data->data_width_lsb = 64;
|
|
|
|
|
sdram_data->signal_level = SPD_SIGNAL_LVTTL;
|
|
|
|
|
sdram_data->tclk = 0x75; /* 7.5 ns = 133.3 MHz */
|
|
|
|
|
sdram_data->tac = 0x10;
|
|
|
|
|
sdram_data->refresh_rate = SPD_SDR_REFRESH_SELF | SPD_REFRESH_NORMAL;
|
|
|
|
|
sdram_data->sdram_width = 8;
|
|
|
|
|
sdram_data->tccd = 1;
|
|
|
|
|
sdram_data->burst = SPD_SDR_BURST_PAGE | 1 | 2 | 4 | 8;
|
|
|
|
|
sdram_data->banks = 4;
|
|
|
|
|
sdram_data->cas = 0x1c; /* CAS 5/4/3 supported */
|
|
|
|
|
sdram_data->cslat = sdram_data->we = 0x7f;
|
|
|
|
|
sdram_data->dev_attr = SPD_SDR_ATTR_EARLY_RAS | SPD_SDR_ATTR_AUTO_PC | SPD_SDR_ATTR_PC_ALL | SPD_SDR_ATTR_W1R_BURST;
|
|
|
|
|
sdram_data->tclk2 = 0xA0; /* 10 ns = 100 MHz */
|
|
|
|
|
sdram_data->tclk3 = 0xF0; /* 15 ns = 66.7 MHz */
|
|
|
|
|
sdram_data->tac2 = sdram_data->tac3 = 0x10;
|
|
|
|
|
sdram_data->trp = sdram_data->trrd = sdram_data->trcd = sdram_data->tras = 1;
|
2020-11-23 14:49:49 -03:00
|
|
|
if (spd_modules[slot]->row1 != spd_modules[slot]->row2) {
|
2020-11-20 01:22:04 -03:00
|
|
|
/* Utilities interpret bank_density a bit differently on asymmetric modules. */
|
2020-11-30 20:16:04 -03:00
|
|
|
sdram_data->bank_density = 1 << (log2i(spd_modules[slot]->row1 >> 1) - 2); /* first row */
|
|
|
|
|
sdram_data->bank_density |= 1 << (log2i(spd_modules[slot]->row2 >> 1) - 2); /* second row */
|
2020-11-20 01:22:04 -03:00
|
|
|
} else {
|
2020-11-30 20:16:04 -03:00
|
|
|
sdram_data->bank_density = 1 << (log2i(spd_modules[slot]->row1 >> 1) - 1); /* symmetric module = only one bit is set */
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
sdram_data->ca_setup = sdram_data->data_setup = 0x15;
|
|
|
|
|
sdram_data->ca_hold = sdram_data->data_hold = 0x08;
|
|
|
|
|
|
|
|
|
|
sdram_data->spd_rev = 0x12;
|
2021-10-16 19:50:32 -03:00
|
|
|
for (i = spd_write_part_no(sdram_data->part_no, "SDR", rows[row]);
|
|
|
|
|
i < sizeof(sdram_data->part_no); i++)
|
2020-11-20 01:22:04 -03:00
|
|
|
sdram_data->part_no[i] = ' '; /* part number should be space-padded */
|
|
|
|
|
sdram_data->rev_code[0] = BCD8(EMU_VERSION_MAJ);
|
|
|
|
|
sdram_data->rev_code[1] = BCD8(EMU_VERSION_MIN);
|
|
|
|
|
sdram_data->mfg_year = 20;
|
|
|
|
|
sdram_data->mfg_week = 13;
|
|
|
|
|
|
|
|
|
|
sdram_data->freq = 100;
|
|
|
|
|
sdram_data->features = 0xFF;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 63; i++)
|
2020-11-23 14:49:49 -03:00
|
|
|
sdram_data->checksum += spd_modules[slot]->data[i];
|
2020-11-20 01:22:04 -03:00
|
|
|
for (i = 0; i < 129; i++)
|
2020-11-23 14:49:49 -03:00
|
|
|
sdram_data->checksum2 += spd_modules[slot]->data[i];
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
row++;
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
2020-11-23 14:49:49 -03:00
|
|
|
|
|
|
|
|
device_add(&spd_device);
|
2020-03-25 21:35:35 -03:00
|
|
|
}
|
2020-06-26 21:03:46 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
spd_write_drbs(uint8_t *regs, uint8_t reg_min, uint8_t reg_max, uint8_t drb_unit)
|
|
|
|
|
{
|
2020-06-26 22:05:32 -03:00
|
|
|
uint8_t row, dimm, drb, apollo = 0;
|
2020-11-23 14:49:49 -03:00
|
|
|
uint16_t size, rows[SPD_MAX_SLOTS];
|
2020-06-26 21:03:46 -03:00
|
|
|
|
2020-06-26 22:05:32 -03:00
|
|
|
/* Special case for VIA Apollo Pro family, which jumps from 5F to 56. */
|
|
|
|
|
if (reg_max < reg_min) {
|
2020-11-20 01:22:04 -03:00
|
|
|
apollo = reg_max;
|
|
|
|
|
reg_max = reg_min + 7;
|
2020-06-26 22:05:32 -03:00
|
|
|
}
|
2020-06-26 21:03:46 -03:00
|
|
|
|
|
|
|
|
/* No SPD: split SIMMs into pairs as if they were "DIMM"s. */
|
|
|
|
|
if (!spd_present) {
|
2020-11-20 01:22:04 -03:00
|
|
|
dimm = ((reg_max - reg_min) + 1) >> 1; /* amount of "DIMM"s, also used to determine the maximum "DIMM" size */
|
2022-07-19 18:51:18 -04:00
|
|
|
spd_populate(rows, dimm, mem_size >> 10, drb_unit, 1 << (log2i((machines[machine].ram.max >> 10) / dimm)), 0);
|
2020-06-26 21:03:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Write DRBs for each row. */
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: Writing DRBs... regs=[%02X:%02X] unit=%d\n", reg_min, reg_max, drb_unit);
|
2020-06-26 21:03:46 -03:00
|
|
|
for (row = 0; row <= (reg_max - reg_min); row++) {
|
2020-11-20 01:22:04 -03:00
|
|
|
dimm = (row >> 1);
|
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
|
|
if (spd_present) {
|
|
|
|
|
/* SPD enabled: use SPD info for this slot, if present. */
|
2020-11-23 14:49:49 -03:00
|
|
|
if (spd_modules[dimm]) {
|
|
|
|
|
if (spd_modules[dimm]->row1 < drb_unit) /* hack within a hack: turn a double-sided DIMM that is too small into a single-sided one */
|
2020-11-20 01:22:04 -03:00
|
|
|
size = (row & 1) ? 0 : drb_unit;
|
|
|
|
|
else
|
2020-11-23 14:49:49 -03:00
|
|
|
size = (row & 1) ? spd_modules[dimm]->row2 : spd_modules[dimm]->row1;
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* No SPD: use the values calculated above. */
|
2020-11-23 14:49:49 -03:00
|
|
|
size = (rows[dimm] >> 1);
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Determine the DRB register to write. */
|
|
|
|
|
drb = reg_min + row;
|
|
|
|
|
if (apollo && ((drb & 0xf) < 0xa))
|
|
|
|
|
drb = apollo + (drb & 0xf);
|
|
|
|
|
|
|
|
|
|
/* Write DRB register, adding the previous DRB's value. */
|
|
|
|
|
if (row == 0)
|
|
|
|
|
regs[drb] = 0;
|
|
|
|
|
else if ((apollo) && (drb == apollo))
|
|
|
|
|
regs[drb] = regs[drb | 0xf]; /* 5F comes before 56 */
|
|
|
|
|
else
|
|
|
|
|
regs[drb] = regs[drb - 1];
|
|
|
|
|
if (size)
|
|
|
|
|
regs[drb] += size / drb_unit; /* this will intentionally overflow on 440GX with 2 GB */
|
2020-11-23 14:49:49 -03:00
|
|
|
spd_log("SPD: DRB[%d] = %d MB (%02Xh raw)\n", row, size, regs[drb]);
|
2020-06-26 21:03:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2020-11-23 14:49:49 -03:00
|
|
|
|
|
|
|
|
|
2022-02-06 00:32:23 +01:00
|
|
|
/* Needed for 430LX. */
|
|
|
|
|
void
|
|
|
|
|
spd_write_drbs_with_ext(uint8_t *regs, uint8_t reg_min, uint8_t reg_max, uint8_t drb_unit)
|
|
|
|
|
{
|
|
|
|
|
uint8_t row, dimm, drb;
|
2022-04-14 07:01:52 +05:00
|
|
|
uint16_t size, row_val = 0, rows[SPD_MAX_SLOTS];
|
2022-02-06 00:32:23 +01:00
|
|
|
int shift;
|
|
|
|
|
|
|
|
|
|
/* No SPD: split SIMMs into pairs as if they were "DIMM"s. */
|
|
|
|
|
if (!spd_present) {
|
|
|
|
|
dimm = ((reg_max - reg_min) + 1) >> 1; /* amount of "DIMM"s, also used to determine the maximum "DIMM" size */
|
2022-07-19 18:51:18 -04:00
|
|
|
spd_populate(rows, dimm, mem_size >> 10, drb_unit, 1 << (log2i((machines[machine].ram.max >> 10) / dimm)), 0);
|
2022-02-06 00:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Write DRBs for each row. */
|
|
|
|
|
spd_log("SPD: Writing DRBs... regs=[%02X:%02X] unit=%d\n", reg_min, reg_max, drb_unit);
|
|
|
|
|
for (row = 0; row <= (reg_max - reg_min); row++) {
|
|
|
|
|
dimm = (row >> 1);
|
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
|
|
if (spd_present) {
|
|
|
|
|
/* SPD enabled: use SPD info for this slot, if present. */
|
|
|
|
|
if (spd_modules[dimm]) {
|
|
|
|
|
if (spd_modules[dimm]->row1 < drb_unit) /* hack within a hack: turn a double-sided DIMM that is too small into a single-sided one */
|
|
|
|
|
size = (row & 1) ? 0 : drb_unit;
|
|
|
|
|
else
|
|
|
|
|
size = (row & 1) ? spd_modules[dimm]->row2 : spd_modules[dimm]->row1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* No SPD: use the values calculated above. */
|
|
|
|
|
size = (rows[dimm] >> 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Determine the DRB register to write. */
|
|
|
|
|
drb = reg_min + row;
|
|
|
|
|
|
|
|
|
|
/* Write DRB register, adding the previous DRB's value. */
|
|
|
|
|
if (row == 0)
|
|
|
|
|
row_val = 0;
|
|
|
|
|
if (size)
|
|
|
|
|
row_val += size / drb_unit; /* this will intentionally overflow on 440GX with 2 GB */
|
|
|
|
|
regs[drb] = row_val & 0xff;
|
|
|
|
|
drb = reg_min + 8 + (row >> 1);
|
|
|
|
|
shift = (row & 0x01) << 3;
|
|
|
|
|
regs[drb] = (((row_val & 0xfff) >> 8) << shift);
|
|
|
|
|
spd_log("SPD: DRB[%d] = %d MB (%02Xh raw)\n", row, size, regs[drb]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-07-12 05:56:06 +02:00
|
|
|
/* Used by ALi M1531 and M1541/2. */
|
2021-07-04 17:40:39 +02:00
|
|
|
void
|
|
|
|
|
spd_write_drbs_interleaved(uint8_t *regs, uint8_t reg_min, uint8_t reg_max, uint8_t drb_unit)
|
|
|
|
|
{
|
2021-07-12 05:56:06 +02:00
|
|
|
uint8_t row, dimm;
|
|
|
|
|
uint8_t drb;
|
2021-12-13 01:23:06 +01:00
|
|
|
uint16_t size, size_acc = 0;
|
2021-07-04 18:24:23 +02:00
|
|
|
uint16_t rows[SPD_MAX_SLOTS];
|
2021-07-04 17:40:39 +02:00
|
|
|
|
|
|
|
|
/* No SPD: split SIMMs into pairs as if they were "DIMM"s. */
|
|
|
|
|
if (!spd_present) {
|
|
|
|
|
dimm = ((reg_max - reg_min) + 1) >> 2; /* amount of "DIMM"s, also used to determine the maximum "DIMM" size */
|
2022-07-19 18:51:18 -04:00
|
|
|
spd_populate(rows, dimm, mem_size >> 10, drb_unit, 1 << (log2i((machines[machine].ram.max >> 10) / dimm)), 0);
|
2021-07-04 17:40:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Write DRBs for each row. */
|
|
|
|
|
spd_log("SPD: Writing DRBs... regs=[%02X:%02X] unit=%d\n", reg_min, reg_max, drb_unit);
|
|
|
|
|
for (row = 0; row <= (reg_max - reg_min); row += 2) {
|
|
|
|
|
dimm = (row >> 2);
|
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
|
|
if (spd_present) {
|
|
|
|
|
/* SPD enabled: use SPD info for this slot, if present. */
|
|
|
|
|
if (spd_modules[dimm]) {
|
|
|
|
|
if (spd_modules[dimm]->row1 < drb_unit) /* hack within a hack: turn a double-sided DIMM that is too small into a single-sided one */
|
|
|
|
|
size = ((row >> 1) & 1) ? 0 : drb_unit;
|
|
|
|
|
else
|
|
|
|
|
size = ((row >> 1) & 1) ? spd_modules[dimm]->row2 : spd_modules[dimm]->row1;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* No SPD: use the values calculated above. */
|
|
|
|
|
size = (rows[dimm] >> 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Determine the DRB register to write. */
|
|
|
|
|
drb = reg_min + row;
|
|
|
|
|
|
2021-07-04 18:24:23 +02:00
|
|
|
/* Calculate previous and new size. */
|
2021-07-04 17:40:39 +02:00
|
|
|
if (row == 0)
|
2021-07-04 18:24:23 +02:00
|
|
|
size_acc = 0;
|
2021-07-04 17:40:39 +02:00
|
|
|
else
|
2021-07-04 18:24:23 +02:00
|
|
|
size_acc += (size / drb_unit);
|
|
|
|
|
|
|
|
|
|
/* Write DRB register, adding the previous DRB's value. */
|
|
|
|
|
regs[drb] = size_acc & 0xff;
|
|
|
|
|
regs[drb + 1] = (regs[drb + 1] & 0xf0) | ((size_acc >> 8) & 0x0f);
|
|
|
|
|
|
2021-07-04 17:40:39 +02:00
|
|
|
spd_log("SPD: DRB[%d] = %d MB (%02Xh raw)\n", row >> 1, size, regs[drb]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-07-12 05:56:06 +02:00
|
|
|
/* This is needed because the ALi M1621 does this stuff completely differently,
|
|
|
|
|
as it has DRAM bank registers instead of DRAM row boundary registers. */
|
|
|
|
|
void
|
|
|
|
|
spd_write_drbs_ali1621(uint8_t *regs, uint8_t reg_min, uint8_t reg_max)
|
|
|
|
|
{
|
|
|
|
|
uint8_t dimm, drb;
|
|
|
|
|
uint16_t size;
|
|
|
|
|
uint16_t rows[SPD_MAX_SLOTS];
|
|
|
|
|
|
|
|
|
|
/* No SPD: split SIMMs into pairs as if they were "DIMM"s. */
|
|
|
|
|
if (!spd_present) {
|
|
|
|
|
dimm = ((reg_max - reg_min) + 1) >> 2; /* amount of "DIMM"s, also used to determine the maximum "DIMM" size */
|
2022-07-19 18:51:18 -04:00
|
|
|
spd_populate(rows, dimm, mem_size >> 10, 4, 1 << (log2i((machines[machine].ram.max >> 10) / dimm)), 0);
|
2021-07-12 05:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Write DRBs for each row. */
|
|
|
|
|
spd_log("SPD: Writing DRBs... regs=[%02X:%02X] unit=%d\n", reg_min, reg_max, drb_unit);
|
|
|
|
|
for (dimm = 0; dimm <= ((reg_max - reg_min) >> 2); dimm++) {
|
|
|
|
|
size = 0;
|
|
|
|
|
drb = reg_min + (dimm << 2);
|
|
|
|
|
|
|
|
|
|
regs[drb] = 0xff;
|
|
|
|
|
regs[drb + 1] = 0xff;
|
|
|
|
|
regs[drb + 2] = 0x00;
|
|
|
|
|
regs[drb + 3] = 0xf0;
|
|
|
|
|
|
|
|
|
|
if (spd_modules[dimm] == NULL)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (spd_present) {
|
|
|
|
|
/* SPD enabled: use SPD info for this slot, if present. */
|
|
|
|
|
size = (spd_modules[dimm]->row1 + spd_modules[dimm]->row2) >> 1;
|
|
|
|
|
} else {
|
|
|
|
|
/* No SPD: use the values calculated above. */
|
|
|
|
|
size = (rows[dimm] >> 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spd_modules[dimm]->row1)
|
|
|
|
|
regs[drb + 3] |= 0x06;
|
|
|
|
|
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 4:
|
|
|
|
|
default:
|
|
|
|
|
regs[drb + 2] = 0x00;
|
|
|
|
|
break;
|
|
|
|
|
case 8:
|
|
|
|
|
regs[drb + 2] = 0x10;
|
|
|
|
|
break;
|
|
|
|
|
case 16:
|
|
|
|
|
regs[drb + 2] = 0x20;
|
|
|
|
|
break;
|
|
|
|
|
case 32:
|
|
|
|
|
regs[drb + 2] = 0x30;
|
|
|
|
|
break;
|
|
|
|
|
case 64:
|
|
|
|
|
regs[drb + 2] = 0x40;
|
|
|
|
|
break;
|
|
|
|
|
case 128:
|
|
|
|
|
regs[drb + 2] = 0x50;
|
|
|
|
|
break;
|
|
|
|
|
case 256:
|
|
|
|
|
regs[drb + 2] = 0x60;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spd_modules[dimm]->row2) {
|
|
|
|
|
regs[drb + 3] |= 0x01;
|
|
|
|
|
regs[drb + 2] |= 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spd_log("SPD: DIMM %i: %02X %02X %02X %02X\n", regs[drb], regs[drb + 1], regs[drb + 2], regs[drb + 3]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
static const device_t spd_device = {
|
2022-03-13 09:47:11 -04:00
|
|
|
.name = "Serial Presence Detect ROMs",
|
|
|
|
|
.internal_name = "spd",
|
|
|
|
|
.flags = DEVICE_ISA,
|
|
|
|
|
.local = 0,
|
|
|
|
|
.init = spd_init,
|
|
|
|
|
.close = spd_close,
|
|
|
|
|
.reset = NULL,
|
|
|
|
|
{ .available = NULL },
|
|
|
|
|
.speed_changed = NULL,
|
|
|
|
|
.force_redraw = NULL,
|
|
|
|
|
.config = NULL
|
2020-11-23 14:49:49 -03:00
|
|
|
};
|