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