summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99/gromport/multiconn.cpp
blob: 191853f1029ff02bd1f60d4db542ad38d8df7dc8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/**************************************************************************
    The multi-cartridge extender

    This is a somewhat mythical device which was never available for the normal
    customer, but there are reports of the existence of such a device
    in development labs or demonstrations.

    The interesting thing about this is that the OS of the console
    fully supports this multi-cartridge extender, providing a selection
    option on the screen to switch between different plugged-in
    cartridges.

    The switching is possible by decoding address lines that are reserved
    for GROM access. GROMs are accessed via four separate addresses
    9800, 9802, 9C00, 9C02. The addressing scheme looks like this:

    1001 1Wxx xxxx xxM0        W = write(1), read(0), M = address(1), data(0)

    This leaves 8 bits (256 options) which are not decoded inside the
    console. As the complete address is routed to the port, some circuit
    just needs to decode the xxx lines and turn on the respective slot.

    One catch must be considered: Some cartridges contain ROMs which are
    directly accessed and not via ports. This means that the ROMs must
    be activated according to the slot that is selected.

    Another issue: Each GROM contains an own address counter and an ID.
    According to the ID the GROM only delivers data if the address counter
    is within the ID area (0 = 0000-1fff, 1=2000-3fff ... 7=e000-ffff).
    Thus it is essential that all GROMs stay in sync with their address
    counters. We have to route all address settings to all slots and their
    GROMs, even when the slot has not been selected before. The selected
    just shows its effect when data is read. In this case, only the
    data from the selected slot will be delivered.

    This may be considered as a design flaw within the complete cartridge system
    which eventually led to TI not manufacturing that device for the broad
    market.
***************************************************************************/

#include "emu.h"
#include "multiconn.h"

#define LOG_WARN             (1U<<1)
#define LOG_CHANGE          (1U<<2)
#define VERBOSE ( LOG_WARN )

#include "logmacro.h"

DEFINE_DEVICE_TYPE_NS(TI99_GROMPORT_MULTI,  bus::ti99::gromport, ti99_multi_cart_conn_device,  "ti99_mcartconn", "TI-99 Multi-cartridge extender")

namespace bus { namespace ti99 { namespace gromport {

#define AUTO -1

ti99_multi_cart_conn_device::ti99_multi_cart_conn_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: cartridge_connector_device(mconfig, TI99_GROMPORT_MULTI, tag, owner, clock),
	m_active_slot(0),
	m_fixed_slot(0),
	m_next_free_slot(0)
{
}

/*
    Activates a slot in the multi-cartridge extender.
    Setting the slot is done by accessing the GROM ports using a
    specific address:

    slot 0:  0x9800 (0x9802, 0x9c00, 0x9c02)   : cartridge1
    slot 1:  0x9804 (0x9806, 0x9c04, 0x9c06)   : cartridge2
    ...
    slot 15: 0x983c (0x983e, 0x9c3c, 0x9c3e)   : cartridge16

    Scheme:

    1001 1Wxx xxxx xxM0 (M=mode; M=0: data, M=1: address; W=write)

    The following addresses are theoretically available, but the
    built-in OS does not use them; i.e. cartridges will not be
    included in the selection list, and their features will not be
    found by lookup, but they could be accessed directly by user
    programs.
    slot 16: 0x9840 (0x9842, 0x9c40, 0x9c42)
    ...
    slot 255:  0x9bfc (0x9bfe, 0x9ffc, 0x9ffe)

    Setting the GROM base should select one cartridge, but the ROMs in the
    CPU space must also be switched. As there is no known special mechanism
    we assume that by switching the GROM base, the ROM is automatically
    switched.

    Caution: This means that cartridges which do not have at least one
    GROM cannot be switched with this mechanism.

    We assume that the slot number is already calculated in the caller:
    slotnumber>=0 && slotnumber<=255

    NOTE: The OS will stop searching when it finds slots 1 and 2 empty.
    Interestingly, cartridge subroutines are found nevertheless, even when
    the cartridge is plugged into a higher slot.
*/
void ti99_multi_cart_conn_device::set_slot(int slotnumber)
{
	if (m_active_slot != slotnumber)
		LOGMASKED(LOG_CHANGE, "Setting cartslot to %d\n", slotnumber);

	if (m_fixed_slot==AUTO)
		m_active_slot = slotnumber;
	else
		m_active_slot = m_fixed_slot;
}

int ti99_multi_cart_conn_device::get_active_slot(bool changebase, offs_t offset)
{
	int slot;
	if (changebase)
	{
		// GROM selected?
		if (m_grom_selected) set_slot((offset>>2) & 0x00ff);
	}
	slot = m_active_slot;
	return slot;
}

void ti99_multi_cart_conn_device::insert(int index, ti99_cartridge_device* cart)
{
	LOGMASKED(LOG_CHANGE, "Insert slot %d\n", index);
	m_cartridge[index] = cart;
	m_gromport->cartridge_inserted();
}

void ti99_multi_cart_conn_device::remove(int index)
{
	LOGMASKED(LOG_CHANGE, "Remove slot %d\n", index);
	m_cartridge[index] = nullptr;
}

WRITE_LINE_MEMBER(ti99_multi_cart_conn_device::romgq_line)
{
	m_readrom = state;

	// Propagate to all slots
	for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++)
	{
		if (m_cartridge[i] != nullptr)
		{
			m_cartridge[i]->romgq_line(state);
		}
	}
}

/*
    Combined select lines
*/
void ti99_multi_cart_conn_device::set_gromlines(line_state mline, line_state moline, line_state gsq)
{
	// GROM selected?
	m_grom_selected = (gsq == ASSERT_LINE);

	// Propagate to all slots
	for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++)
	{
		if (m_cartridge[i] != nullptr)
		{
			m_cartridge[i]->set_gromlines(mline, moline, gsq);
		}
	}
}

WRITE_LINE_MEMBER(ti99_multi_cart_conn_device::gclock_in)
{
	// Propagate to all slots
	for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++)
	{
		if (m_cartridge[i] != nullptr)
		{
			m_cartridge[i]->gclock_in(state);
		}
	}
}

READ8Z_MEMBER(ti99_multi_cart_conn_device::readz)
{
	int slot = get_active_slot(true, offset);

	// If we have a GROM access, we need to send the read request to all
	// attached cartridges so the slot is irrelevant here. Each GROM
	// contains an internal address counter, and we must make sure they all stay in sync.
	if (m_grom_selected)
	{
		for (int i=0; i < NUMBER_OF_CARTRIDGE_SLOTS; i++)
		{
			if (m_cartridge[i] != nullptr)
			{
				uint8_t newval = *value;
				m_cartridge[i]->readz(offset, &newval);
				if (i==slot)
				{
					*value = newval;
				}
			}
		}
	}
	else
	{
		if (slot < NUMBER_OF_CARTRIDGE_SLOTS && m_cartridge[slot] != nullptr)
		{
			m_cartridge[slot]->readz(offset, value);
		}
	}
}

void ti99_multi_cart_conn_device::write(offs_t offset, uint8_t data)
{
	// Same issue as above (read)
	// We don't have GRAM cartridges, anyway, so it's just used for setting the address.
	if (m_grom_selected)
	{
		for (auto & elem : m_cartridge)
		{
			if (elem != nullptr)
			{
				elem->write(offset, data);
			}
		}
	}
	else
	{
		int slot = get_active_slot(true, offset);
		if (slot < NUMBER_OF_CARTRIDGE_SLOTS && m_cartridge[slot] != nullptr)
		{
			// logerror("writing %04x (slot %d) <- %02x\n", offset, slot, data);
			m_cartridge[slot]->write(offset, data);
		}
	}
}

READ8Z_MEMBER(ti99_multi_cart_conn_device::crureadz)
{
	int slot = get_active_slot(false, offset);
	/* Sanity check. Higher slots are always empty. */
	if (slot >= NUMBER_OF_CARTRIDGE_SLOTS)
		return;

	if (m_cartridge[slot] != nullptr)
	{
		m_cartridge[slot]->crureadz(offset, value);
	}
}

void ti99_multi_cart_conn_device::cruwrite(offs_t offset, uint8_t data)
{
	int slot = get_active_slot(true, offset);

	/* Sanity check. Higher slots are always empty. */
	if (slot >= NUMBER_OF_CARTRIDGE_SLOTS)
		return;

	if (m_cartridge[slot] != nullptr)
	{
		m_cartridge[slot]->cruwrite(offset, data);
	}
}

/*
    Check whether the GROMs are idle. Just ask the currently
    active cartridge.
*/
bool ti99_multi_cart_conn_device::is_grom_idle()
{
	/* Sanity check. Higher slots are always empty. */
	if (m_active_slot >= NUMBER_OF_CARTRIDGE_SLOTS)
		return false;

	if (m_cartridge[m_active_slot] != nullptr)
		return m_cartridge[m_active_slot]->is_grom_idle();

	return false;
}

void ti99_multi_cart_conn_device::device_start()
{
	m_next_free_slot = 0;
	m_active_slot = 0;
	for (auto & elem : m_cartridge)
	{
		elem = nullptr;
	}
	save_item(NAME(m_readrom));
	save_item(NAME(m_active_slot));
	save_item(NAME(m_fixed_slot));
	save_item(NAME(m_next_free_slot));
}

void ti99_multi_cart_conn_device::device_reset(void)
{
	m_active_slot = 0;
	m_fixed_slot = ioport("CARTSLOT")->read() - 1;
	m_grom_selected = false;
}

void ti99_multi_cart_conn_device::device_add_mconfig(machine_config &config)
{
	TI99_CART(config, "cartridge1", 0);
	TI99_CART(config, "cartridge2", 0);
	TI99_CART(config, "cartridge3", 0);
	TI99_CART(config, "cartridge4", 0);
}

INPUT_CHANGED_MEMBER( ti99_multi_cart_conn_device::switch_changed )
{
	LOGMASKED(LOG_CHANGE, "Slot changed %d - %d\n", (int)((uint64_t)param & 0x07), newval);
	m_active_slot = m_fixed_slot = newval - 1;
}

INPUT_PORTS_START(multi_slot)
	PORT_START( "CARTSLOT" )
	PORT_DIPNAME( 0x0f, 0x00, "Multi-cartridge slot" ) PORT_CHANGED_MEMBER(DEVICE_SELF, ti99_multi_cart_conn_device, switch_changed, 0)
		PORT_DIPSETTING(    0x00, "Auto" )
		PORT_DIPSETTING(    0x01, "Slot 1" )
		PORT_DIPSETTING(    0x02, "Slot 2" )
		PORT_DIPSETTING(    0x03, "Slot 3" )
		PORT_DIPSETTING(    0x04, "Slot 4" )
INPUT_PORTS_END

ioport_constructor ti99_multi_cart_conn_device::device_input_ports() const
{
	return INPUT_PORTS_NAME(multi_slot);
}

} } } // end namespace bus::ti99::gromport