1 // license:LGPL-2.1+
2 // copyright-holders:Michael Zapf
3 /****************************************************************************
4
5 Horizon Ramdisk
6
7 This emulation realizes the latest development, the HRD 4000, which could
8 host up to 16 MiB of SRAM. Real cards rarely had more than 1.5 MiB since
9 the SRAM used on the card is rather expensive.
10
11 The SRAM is buffered with a battery pack. Also, there is an option for
12 an additional 32 KiB of unbuffered memory.
13
14 The driver (ROS) of the ramdisk is stored in another buffered 8 KiB SRAM.
15
16 The Horizon RAMdisk comes with a disk containing the ROS and a configuration
17 program (CFG). The latest version is ROS 8.14.
18
19 Technical details:
20
21 In the tradition (Horizon) mode, memory is organized as 2 KiB pages. The
22 pages are selected via CRU bits and visible in the address area 5800 - 5fff.
23 The area 4000-57ff is occupied by the ROS. As with all peripheral cards,
24 the 4000-5fff area requires a CRU bit to be set (usually bit 0 of this
25 card's CRU base).
26
27 Next releases of the HRD included new modes. The RAMBO (RAM Block operator)
28 mode gathers four pages to a single 8 KiB page that is visible in the
29 area 6000-7fff (cartridge space). Note that due to a possible design glitch,
30 each RAMBO page n covers Horizon pages 4n, 4n+2, 4n+1, 4n+3 in this sequence.
31 We emulate this by swapping two CRU lines.
32
33 The RAMDisk may be split in two separate drives, which is called the
34 Phoenix extension. This is particularly important for use in the Geneve.
35 As a bootable drive, the RAMdisk must not
36 exceed 256 KiB; consequently, the RAM area is split, and one part realizes
37 the boot drive while the other is still available for data. Also, there
38 is a mechanism for selecting the parts of the card: The TI setting allows
39 to select two CRU addresses, one for each part. In the Geneve mode, only
40 one CRU address is used (1400 or 1600), and the part is selected by the
41 fact that one disk uses CRU bits higher than 8, while the other uses the
42 bits lower than 8.
43
44 The card is able to handle 128K*8 and 512K*8 SRAM chips, allowing a total
45 of 16 MiB memory space. Unfortunately, a bug causes the configuration
46 program to crash when used with more than 2 MiB. Although the card was
47 quite popular, this bug was not found because most cards were sold with
48 less than 2 MiB onboard. As the community is still alive we can hope for
49 a fix for this problem; so we make the size configurable.
50
51 According to the Genmod setup instructions, the Horizon Ramdisks do not
52 decode the AMA/AMB/AMC lines, so it is important to modify them when
53 running with the Genmod system. This can be done with the configuration
54 setting "Genmod fix".
55
56 *****************************************************************************/
57
58 #include "emu.h"
59 #include "horizon.h"
60
61 #define LOG_WARN (1U<<1) // Warnings
62 #define LOG_CONFIG (1U<<2) // Configuration
63 #define LOG_READ (1U<<3)
64 #define LOG_WRITE (1U<<4)
65 #define LOG_CRU (1U<<5)
66
67 #define VERBOSE ( LOG_CONFIG | LOG_WARN )
68
69 #include "logmacro.h"
70
71 DEFINE_DEVICE_TYPE_NS(TI99_HORIZON, bus::ti99::peb, horizon_ramdisk_device, "ti99_horizon", "Horizon 4000 Ramdisk")
72
73 namespace bus { namespace ti99 { namespace peb {
74
75 #define RAMREGION "ram32k"
76 #define ROSREGION "ros8k"
77 #define NVRAMREGION "nvram"
78
79 #define MAXSIZE 16777216
80 #define ROSSIZE 8192
81
horizon_ramdisk_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)82 horizon_ramdisk_device::horizon_ramdisk_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock):
83 device_t(mconfig, TI99_HORIZON, tag, owner, clock),
84 device_ti99_peribox_card_interface(mconfig, *this),
85 device_nvram_interface(mconfig, *this),
86 m_ram(*this, RAMREGION),
87 m_nvram(*this, NVRAMREGION),
88 m_ros(*this, ROSREGION),
89 m_page(0),
90 m_cru_horizon(0),
91 m_cru_phoenix(0),
92 m_timode(false),
93 m_32k_installed(false),
94 m_split_mode(false),
95 m_rambo_mode(false),
96 m_hideswitch(false),
97 m_use_rambo(false),
98 m_genmod_fix(false)
99 {
100 }
101
102 //-------------------------------------------------
103 // nvram_default - called to initialize NVRAM to
104 // its default state
105 //-------------------------------------------------
106
nvram_default()107 void horizon_ramdisk_device::nvram_default()
108 {
109 int size = 2097152*(1 << ioport("HORIZONSIZE")->read());
110 memset(m_nvram->pointer(), 0, size);
111 memset(m_ros->pointer(), 0, ROSSIZE);
112 }
113
114 //-------------------------------------------------
115 // nvram_read - called to read NVRAM from the
116 // .nv file
117 //-------------------------------------------------
118
nvram_read(emu_file & file)119 void horizon_ramdisk_device::nvram_read(emu_file &file)
120 {
121 int size = 2097152*(1 << ioport("HORIZONSIZE")->read());
122
123 // NVRAM plus ROS
124 auto buffer = make_unique_clear<uint8_t []>(MAXSIZE + ROSSIZE);
125
126 memset(m_nvram->pointer(), 0, size);
127 memset(m_ros->pointer(), 0, ROSSIZE);
128
129 // We assume the last 8K is ROS
130 int filesize = file.read(&buffer[0], MAXSIZE+ROSSIZE);
131 int nvramsize = filesize - ROSSIZE;
132
133 // If there is a reasonable size
134 if (nvramsize >= 0)
135 {
136 // Copy from buffer to NVRAM and ROS
137 memcpy(m_nvram->pointer(), &buffer[0], nvramsize);
138 memcpy(m_ros->pointer(), &buffer[nvramsize], ROSSIZE);
139 }
140 }
141
142 //-------------------------------------------------
143 // nvram_write - called to write NVRAM to the
144 // .nv file
145 //-------------------------------------------------
146
nvram_write(emu_file & file)147 void horizon_ramdisk_device::nvram_write(emu_file &file)
148 {
149 int nvramsize = 2097152*(1 << ioport("HORIZONSIZE")->read());
150
151 auto buffer = make_unique_clear<uint8_t []>(nvramsize + ROSSIZE);
152 memcpy(&buffer[0], m_nvram->pointer(), nvramsize);
153 memcpy(&buffer[nvramsize], m_ros->pointer(), ROSSIZE);
154
155 file.write(buffer.get(), nvramsize + ROSSIZE);
156 }
157
readz(offs_t offset,uint8_t * value)158 void horizon_ramdisk_device::readz(offs_t offset, uint8_t *value)
159 {
160 // 32K expansion
161 // According to the manual, "this memory is not affected by the HIDE switch"
162 if (m_32k_installed)
163 {
164 switch((offset & 0xe000)>>13)
165 {
166 case 1: // 2000-3fff
167 *value = m_ram->pointer()[offset & 0x1fff];
168 return;
169 case 5: // a000-bfff
170 *value = m_ram->pointer()[(offset & 0x1fff) | 0x2000];
171 return;
172 case 6: // c000-dfff
173 *value = m_ram->pointer()[(offset & 0x1fff) | 0x4000];
174 return;
175 case 7: // e000-ffff
176 *value = m_ram->pointer()[(offset & 0x1fff) | 0x6000];
177 return;
178 default:
179 break;
180 }
181 }
182
183 if (m_hideswitch) return;
184
185 // I think RAMBO mode does not need the card to be selected
186 if (!m_selected && !m_rambo_mode) return;
187
188 if (!m_rambo_mode)
189 {
190 if (in_dsr_space(offset, m_genmod_fix))
191 {
192 if ((offset & 0x1800) == 0x1800)
193 {
194 // NVRAM page of size 2 KiB
195 *value = m_nvram->pointer()[(m_page << 11)|(offset & 0x07ff)];
196 LOGMASKED(LOG_READ, "offset=%04x, page=%04x -> %02x\n", offset&0xffff, m_page, *value);
197 }
198 else
199 {
200 // ROS
201 *value = m_ros->pointer()[offset & 0x1fff];
202 LOGMASKED(LOG_READ, "offset=%04x (ROS) -> %02x\n", offset&0xffff, *value);
203 }
204 }
205 }
206 else
207 {
208 if (in_dsr_space(offset, m_genmod_fix))
209 {
210 *value = m_ros->pointer()[offset & 0x1fff];
211 LOGMASKED(LOG_READ, "offset=%04x (Rambo) -> %02x\n", offset&0xffff, *value);
212 }
213 if (in_cart_space(offset, m_genmod_fix))
214 {
215 // In RAMBO mode the page numbers are multiples of 4
216 // (encompassing 4 Horizon pages)
217 // We clear away the rightmost two bits
218 *value = m_nvram->pointer()[((m_page&0xfffc)<<11) | (offset & 0x1fff)];
219 LOGMASKED(LOG_READ, "offset=%04x, page=%04x (Rambo) -> %02x\n", offset&0xffff, m_page, *value);
220 }
221 }
222 }
223
write(offs_t offset,uint8_t data)224 void horizon_ramdisk_device::write(offs_t offset, uint8_t data)
225 {
226 // 32K expansion
227 // According to the manual, "this memory is not affected by the HIDE switch"
228 if (m_32k_installed)
229 {
230 switch((offset & 0xe000)>>13)
231 {
232 case 1: // 2000-3fff
233 m_ram->pointer()[offset & 0x1fff] = data;
234 return;
235 case 5: // a000-bfff
236 m_ram->pointer()[(offset & 0x1fff) | 0x2000] = data;
237 return;
238 case 6: // c000-dfff
239 m_ram->pointer()[(offset & 0x1fff) | 0x4000] = data;
240 return;
241 case 7: // e000-ffff
242 m_ram->pointer()[(offset & 0x1fff) | 0x6000] = data;
243 return;
244 default:
245 break;
246 }
247 }
248
249 if (m_hideswitch) return;
250
251 // I think RAMBO mode does not need the card to be selected
252 if (!m_selected && !m_rambo_mode) return;
253
254 if (!m_rambo_mode)
255 {
256 if (in_dsr_space(offset, m_genmod_fix))
257 {
258 if ((offset & 0x1800) == 0x1800)
259 {
260 // NVRAM page of size 2 KiB
261 m_nvram->pointer()[(m_page << 11)|(offset & 0x07ff)] = data;
262 LOGMASKED(LOG_WRITE, "offset=%04x, page=%04x <- %02x\n", offset&0xffff, m_page, data);
263 }
264 else
265 {
266 // ROS
267 m_ros->pointer()[offset & 0x1fff] = data;
268 LOGMASKED(LOG_WRITE, "offset=%04x (ROS) <- %02x\n", offset&0xffff, data);
269 }
270 }
271 }
272 else
273 {
274 if (in_dsr_space(offset, m_genmod_fix))
275 {
276 m_ros->pointer()[offset & 0x1fff] = data;
277 LOGMASKED(LOG_WRITE, "offset=%04x (Rambo) <- %02x\n", offset&0xffff, data);
278 }
279 if (in_cart_space(offset, m_genmod_fix))
280 {
281 // In RAMBO mode the page numbers are multiples of 4
282 // (encompassing 4 Horizon pages)
283 // We clear away the rightmost two bits
284 m_nvram->pointer()[((m_page&0xfffc)<<11) | (offset & 0x1fff)] = data;
285 LOGMASKED(LOG_WRITE, "offset=%04x, page=%04x (Rambo) <- %02x\n", offset&0xffff, m_page, data);
286 }
287 }
288 }
289
crureadz(offs_t offset,uint8_t * value)290 void horizon_ramdisk_device::crureadz(offs_t offset, uint8_t *value)
291 {
292 // There is no CRU read operation for the Horizon.
293 return;
294 }
295
setbit(int & page,int pattern,bool set)296 void horizon_ramdisk_device::setbit(int& page, int pattern, bool set)
297 {
298 if (set)
299 {
300 page |= pattern;
301 }
302 else
303 {
304 page &= ~pattern;
305 }
306 }
307
cruwrite(offs_t offset,uint8_t data)308 void horizon_ramdisk_device::cruwrite(offs_t offset, uint8_t data)
309 {
310 int size = ioport("HORIZONSIZE")->read();
311 int split_bit = size + 10;
312 int splitpagebit = 0x0200 << size;
313
314 if (((offset & 0xff00)==m_cru_horizon)||((offset & 0xff00)==m_cru_phoenix))
315 {
316 int bit = (offset >> 1) & 0x0f;
317 LOGMASKED(LOG_CRU, "CRU write bit %d <- %d\n", bit, data);
318 switch (bit)
319 {
320 case 0:
321 m_selected = (data!=0);
322 LOGMASKED(LOG_CRU, "Activate ROS = %d\n", m_selected);
323 break;
324 case 1:
325 // Swap the lines so that the access with RAMBO is consistent
326 if (!m_rambo_mode) setbit(m_page, 0x0002, data!=0);
327 break;
328 case 2:
329 // Swap the lines so that the access with RAMBO is consistent
330 if (!m_rambo_mode) setbit(m_page, 0x0001, data!=0);
331 break;
332 case 3:
333 case 4:
334 case 5:
335 case 6:
336 case 7:
337 case 8:
338 case 9:
339 setbit(m_page, 0x0001 << (bit-1), data!=0);
340 break;
341 case 14:
342 break;
343 case 15:
344 if (m_use_rambo)
345 {
346 m_rambo_mode = (data != 0);
347 LOGMASKED(LOG_CRU, "RAMBO = %d\n", m_rambo_mode);
348 }
349 break;
350
351 default: // bits 10-13
352 if (bit != split_bit || !m_split_mode)
353 {
354 if (bit <= split_bit) setbit(m_page, 0x0200<<(bit-10), data!=0);
355 }
356 break;
357 }
358
359 if (m_split_mode)
360 {
361 if (m_timode)
362 {
363 // In TI mode, switch between both RAMDisks using the CRU address
364 setbit(m_page, splitpagebit, ((offset & 0xff00)==m_cru_phoenix));
365 }
366 else
367 {
368 // In Geneve mode, switch between both RAMdisks by
369 // using the bit number of the last CRU access
370 setbit(m_page, splitpagebit, (bit>7));
371 }
372 }
373 }
374 }
375
device_start(void)376 void horizon_ramdisk_device::device_start(void)
377 {
378 m_cru_horizon = 0;
379 m_cru_phoenix = 0;
380
381 save_item(NAME(m_page));
382 save_item(NAME(m_cru_horizon));
383 save_item(NAME(m_cru_phoenix));
384 save_item(NAME(m_timode));
385 save_item(NAME(m_32k_installed));
386 save_item(NAME(m_split_mode));
387 save_item(NAME(m_rambo_mode));
388 save_item(NAME(m_hideswitch));
389 save_item(NAME(m_use_rambo));
390 }
391
device_reset(void)392 void horizon_ramdisk_device::device_reset(void)
393 {
394 m_cru_horizon = ioport("CRUHOR")->read();
395 m_cru_phoenix = ioport("CRUPHOE")->read();
396
397 m_32k_installed = (ioport("HORIZON32")->read()!=0);
398
399 m_split_mode = (ioport("HORIZONDUAL")->read()!=0);
400 m_timode = (ioport("HORIZONDUAL")->read()==1);
401
402 m_rambo_mode = false;
403 m_hideswitch = (ioport("HORIZONACT")->read()!=0);
404
405 m_use_rambo = (ioport("RAMBO")->read()!=0);
406
407 m_genmod_fix = (ioport("GENMODFIX")->read()!=0);
408
409 m_page = 0;
410 m_selected = false;
411 }
412
INPUT_CHANGED_MEMBER(horizon_ramdisk_device::hs_changed)413 INPUT_CHANGED_MEMBER( horizon_ramdisk_device::hs_changed )
414 {
415 LOGMASKED(LOG_CONFIG, "hideswitch changed %d\n", newval);
416 m_hideswitch = (newval!=0);
417 }
418
419 /*
420 Input ports for the Horizon
421 */
422 INPUT_PORTS_START( horizon )
423 PORT_START( "CRUHOR" )
424 PORT_DIPNAME( 0x1f00, 0x1200, "Horizon CRU base" )
DEF_STR(Off)425 PORT_DIPSETTING( 0x0000, DEF_STR( Off ) )
426 PORT_DIPSETTING( 0x1000, "1000" )
427 PORT_DIPSETTING( 0x1200, "1200" )
428 PORT_DIPSETTING( 0x1400, "1400" )
429 PORT_DIPSETTING( 0x1500, "1500" )
430 PORT_DIPSETTING( 0x1600, "1600" )
431 PORT_DIPSETTING( 0x1700, "1700" )
432
433 PORT_START( "CRUPHOE" )
434 PORT_DIPNAME( 0x1f00, 0x0000, "Phoenix CRU base" )
435 PORT_DIPSETTING( 0x0000, DEF_STR( Off ) )
436 PORT_DIPSETTING( 0x1400, "1400" )
437 PORT_DIPSETTING( 0x1600, "1600" )
438
439 PORT_START( "HORIZONDUAL" )
440 PORT_DIPNAME( 0x03, 0x00, "Horizon ramdisk split" )
441 PORT_DIPSETTING( 0x00, DEF_STR( Off ) )
442 PORT_DIPSETTING( 0x01, "TI mode" )
443 PORT_DIPSETTING( 0x02, "Geneve mode" )
444
445 PORT_START( "HORIZONACT" )
446 PORT_DIPNAME( 0x01, 0x00, "Horizon hideswitch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, horizon_ramdisk_device, hs_changed, 0)
447 PORT_DIPSETTING( 0x00, DEF_STR( Off ) )
448 PORT_DIPSETTING( 0x01, DEF_STR( On ) )
449
450 PORT_START( "HORIZON32" )
451 PORT_CONFNAME( 0x01, 0x00, "Horizon 32 KiB upgrade" )
452 PORT_CONFSETTING( 0x00, DEF_STR( Off ))
453 PORT_CONFSETTING( 0x01, DEF_STR( On ))
454
455 PORT_START( "RAMBO" )
456 PORT_CONFNAME( 0x01, 0x01, "Horizon RAMBO" )
457 PORT_CONFSETTING( 0x00, DEF_STR( Off ))
458 PORT_CONFSETTING( 0x01, DEF_STR( On ))
459
460 PORT_START( "HORIZONSIZE" )
461 PORT_CONFNAME( 0x03, 0x00, "Horizon size" )
462 PORT_CONFSETTING( 0x00, "2 MiB")
463 PORT_CONFSETTING( 0x01, "4 MiB")
464 PORT_CONFSETTING( 0x02, "8 MiB")
465 PORT_CONFSETTING( 0x03, "16 MiB")
466
467 PORT_START( "GENMODFIX" )
468 PORT_CONFNAME( 0x01, 0x00, "Horizon Genmod fix" )
469 PORT_CONFSETTING( 0x00, DEF_STR( Off ))
470 PORT_CONFSETTING( 0x01, DEF_STR( On ))
471
472 INPUT_PORTS_END
473
474 void horizon_ramdisk_device::device_add_mconfig(machine_config &config)
475 {
476 RAM(config, NVRAMREGION).set_default_size("16M");
477 RAM(config, ROSREGION).set_default_size("8K");
478 RAM(config, RAMREGION).set_default_size("32K").set_default_value(0);
479 }
480
device_input_ports() const481 ioport_constructor horizon_ramdisk_device::device_input_ports() const
482 {
483 return INPUT_PORTS_NAME(horizon);
484 }
485
486 } } } // end namespace bus::ti99::peb
487