OXYGENHUB
◆ main openmsx / src / I8255.cc
8.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "I8255.hh"
2
3
#include "I8255Interface.hh"
4
#include "serialize.hh"
5
6
#include "unreachable.hh"
7
8
namespace openmsx {
9
10
const int MODE_A       = 0x60;
11
const int MODEA_0      = 0x00;
12
const int MODEA_1      = 0x20;
13
const int MODEA_2      = 0x40;
14
const int MODEA_2_     = 0x60;
15
const int MODE_B       = 0x04;
16
const int MODEB_0      = 0x00;
17
const int MODEB_1      = 0x04;
18
const int DIRECTION_A  = 0x10;
19
const int DIRECTION_B  = 0x02;
20
const int DIRECTION_C0 = 0x01;
21
const int DIRECTION_C1 = 0x08;
22
const int SET_MODE     = 0x80;
23
const int BIT_NR       = 0x0E;
24
const int SET_RESET    = 0x01;
25
26
27
I8255::I8255(I8255Interface& interface_, EmuTime time,
28
             StringSetting& invalidPpiModeSetting)
29
	: interface(interface_)
30
	, ppiModeCallback(invalidPpiModeSetting)
31
{
32
	reset(time);
33
}
34
35
void I8255::reset(EmuTime time)
36
{
37
	latchPortA = 0;
38
	latchPortB = 0;
39
	latchPortC = 0;
40
	writeControlPort(SET_MODE | DIRECTION_A | DIRECTION_B |
41
	                            DIRECTION_C0 | DIRECTION_C1, time); // all input
42
}
43
44
uint8_t I8255::read(uint8_t port, EmuTime time)
45
{
46
	switch (port) {
47
	case 0:
48
		return readPortA(time);
49
	case 1:
50
		return readPortB(time);
51
	case 2:
52
		return readPortC(time);
53
	case 3:
54
		return readControlPort(time);
55
	default:
56
		UNREACHABLE;
57
	}
58
}
59
60
uint8_t I8255::peek(uint8_t port, EmuTime time) const
61
{
62
	switch (port) {
63
	case 0:
64
		return peekPortA(time);
65
	case 1:
66
		return peekPortB(time);
67
	case 2:
68
		return peekPortC(time);
69
	case 3:
70
		return readControlPort(time);
71
	default:
72
		UNREACHABLE;
73
	}
74
}
75
76
void I8255::write(uint8_t port, uint8_t value, EmuTime time)
77
{
78
	switch (port) {
79
	case 0:
80
		writePortA(value, time);
81
		break;
82
	case 1:
83
		writePortB(value, time);
84
		break;
85
	case 2:
86
		writePortC(value, time);
87
		break;
88
	case 3:
89
		writeControlPort(value, time);
90
		break;
91
	default:
92
		UNREACHABLE;
93
	}
94
}
95
96
uint8_t I8255::readPortA(EmuTime time)
97
{
98
	switch (control & MODE_A) {
99
	case MODEA_0:
100
		if (control & DIRECTION_A) {
101
			// input
102
			return interface.readA(time);	// input not latched
103
		} else {
104
			// output
105
			return latchPortA;		// output is latched
106
		}
107
	case MODEA_1: // TODO but not relevant for MSX
108
	case MODEA_2: case MODEA_2_:
109
	default:
110
		return 255; // avoid warning
111
	}
112
}
113
114
uint8_t I8255::peekPortA(EmuTime time) const
115
{
116
	switch (control & MODE_A) {
117
	case MODEA_0:
118
		if (control & DIRECTION_A) {
119
			return interface.peekA(time);	// input not latched
120
		} else {
121
			return latchPortA;		// output is latched
122
		}
123
	case MODEA_1: // TODO but not relevant for MSX
124
	case MODEA_2: case MODEA_2_:
125
	default:
126
		return 255;
127
	}
128
}
129
130
uint8_t I8255::readPortB(EmuTime time)
131
{
132
	switch (control & MODE_B) {
133
	case MODEB_0:
134
		if (control & DIRECTION_B) {
135
			// input
136
			return interface.readB(time);	// input not latched
137
		} else {
138
			// output
139
			return latchPortB;		// output is latched
140
		}
141
	case MODEB_1: // TODO but not relevant for MSX
142
	default:
143
		return 255; // avoid warning
144
	}
145
}
146
147
uint8_t I8255::peekPortB(EmuTime time) const
148
{
149
	switch (control & MODE_B) {
150
	case MODEB_0:
151
		if (control & DIRECTION_B) {
152
			return interface.peekB(time);	// input not latched
153
		} else {
154
			return latchPortB;		// output is latched
155
		}
156
	case MODEB_1: // TODO but not relevant for MSX
157
	default:
158
		return 255;
159
	}
160
}
161
162
uint8_t I8255::readPortC(EmuTime time)
163
{
164
	uint8_t tmp = readC1(time) | readC0(time);
165
	switch (control & MODE_A) {
166
	case MODEA_0:
167
		// do nothing
168
		break;
169
	case MODEA_1:
170
	case MODEA_2: case MODEA_2_:
171
		// TODO but not relevant for MSX
172
		break;
173
	}
174
	switch (control & MODE_B) {
175
	case MODEB_0:
176
		// do nothing
177
		break;
178
	case MODEB_1:
179
		// TODO but not relevant for MSX
180
		break;
181
	}
182
	return tmp;
183
}
184
185
uint8_t I8255::peekPortC(EmuTime time) const
186
{
187
	return peekC1(time) | peekC0(time);
188
}
189
190
uint8_t I8255::readC1(EmuTime time)
191
{
192
	if (control & DIRECTION_C1) {
193
		// input
194
		return uint8_t(interface.readC1(time) << 4); // input not latched
195
	} else {
196
		// output
197
		return latchPortC & 0xf0;		// output is latched
198
	}
199
}
200
201
uint8_t I8255::peekC1(EmuTime time) const
202
{
203
	if (control & DIRECTION_C1) {
204
		return uint8_t(interface.peekC1(time) << 4); // input not latched
205
	} else {
206
		return latchPortC & 0xf0;		// output is latched
207
	}
208
}
209
210
uint8_t I8255::readC0(EmuTime time)
211
{
212
	if (control & DIRECTION_C0) {
213
		// input
214
		return interface.readC0(time);		// input not latched
215
	} else {
216
		// output
217
		return latchPortC & 0x0f;		// output is latched
218
	}
219
}
220
221
uint8_t I8255::peekC0(EmuTime time) const
222
{
223
	if (control & DIRECTION_C0) {
224
		return interface.peekC0(time);		// input not latched
225
	} else {
226
		return latchPortC & 0x0f;		// output is latched
227
	}
228
}
229
230
uint8_t I8255::readControlPort(EmuTime /*time*/) const
231
{
232
	return control;
233
}
234
235
void I8255::writePortA(uint8_t value, EmuTime time)
236
{
237
	switch (control & MODE_A) {
238
	case MODEA_0:
239
		// do nothing
240
		break;
241
	case MODEA_1:
242
	case MODEA_2: case MODEA_2_:
243
		// TODO but not relevant for MSX
244
		break;
245
	}
246
	outputPortA(value, time);
247
}
248
249
void I8255::writePortB(uint8_t value, EmuTime time)
250
{
251
	switch (control & MODE_B) {
252
	case MODEB_0:
253
		// do nothing
254
		break;
255
	case MODEB_1:
256
		// TODO but not relevant for MSX
257
		break;
258
	}
259
	outputPortB(value, time);
260
}
261
262
void I8255::writePortC(uint8_t value, EmuTime time)
263
{
264
	switch (control & MODE_A) {
265
	case MODEA_0:
266
		// do nothing
267
		break;
268
	case MODEA_1:
269
	case MODEA_2: case MODEA_2_:
270
		// TODO but not relevant for MSX
271
		break;
272
	}
273
	switch (control & MODE_B) {
274
	case MODEB_0:
275
		// do nothing
276
		break;
277
	case MODEB_1:
278
		// TODO but not relevant for MSX
279
		break;
280
	}
281
	outputPortC(value, time);
282
}
283
284
void I8255::outputPortA(uint8_t value, EmuTime time)
285
{
286
	latchPortA = value;
287
	if (!(control & DIRECTION_A)) {
288
		// output
289
		interface.writeA(value, time);
290
	}
291
}
292
293
void I8255::outputPortB(uint8_t value, EmuTime time)
294
{
295
	latchPortB = value;
296
	if (!(control & DIRECTION_B)) {
297
		// output
298
		interface.writeB(value, time);
299
	}
300
}
301
302
void I8255::outputPortC(uint8_t value, EmuTime time)
303
{
304
	latchPortC = value;
305
	if (!(control & DIRECTION_C1)) {
306
		// output
307
		interface.writeC1(latchPortC >> 4, time);
308
	}
309
	if (!(control & DIRECTION_C0)) {
310
		// output
311
		interface.writeC0(latchPortC & 15, time);
312
	}
313
}
314
315
void I8255::writeControlPort(uint8_t value, EmuTime time)
316
{
317
	if (value & SET_MODE) {
318
		// set new control mode
319
		control = value;
320
		if (control & (MODE_A | MODE_B)) {
321
			ppiModeCallback.execute();
322
		}
323
		// Some PPI datasheets state that port A and C (and sometimes
324
		// also B) are reset to zero on a mode change. But the
325
		// documentation is not consistent.
326
		// TODO investigate this further.
327
		outputPortA(latchPortA, time);
328
		outputPortB(latchPortB, time);
329
		outputPortC(latchPortC, time);
330
	} else {
331
		// (re)set bit of port C
332
		auto bitmask = uint8_t(1 << ((value & BIT_NR) >> 1));
333
		if (value & SET_RESET) {
334
			// set
335
			latchPortC |= bitmask;
336
		} else {
337
			// reset
338
			latchPortC &= ~bitmask;
339
		}
340
		outputPortC(latchPortC, time);
341
		// check for special (re)set commands
342
		// not relevant for mode 0
343
		switch (control & MODE_A) {
344
		case MODEA_0:
345
			// do nothing
346
			break;
347
		case MODEA_1:
348
		case MODEA_2: case MODEA_2_:
349
			// TODO but not relevant for MSX
350
			break;
351
		}
352
		switch (control & MODE_B) {
353
		case MODEB_0:
354
			// do nothing
355
			break;
356
		case MODEB_1:
357
			// TODO but not relevant for MSX
358
			break;
359
		}
360
	}
361
}
362
363
// Returns the value that's current being output on port A.
364
// Or a floating value if port A is actually programmed as input.
365
uint8_t I8255::getPortA() const
366
{
367
	uint8_t result = latchPortA;
368
	if (control & DIRECTION_A) {
369
		// actually set as input -> return floating value
370
		result = 255; // real floating value not yet supported
371
	}
372
	return result;
373
}
374
375
uint8_t I8255::getPortB() const
376
{
377
	uint8_t result = latchPortB;
378
	if (control & DIRECTION_B) {
379
		// actually set as input -> return floating value
380
		result = 255; // real floating value not yet supported
381
	}
382
	return result;
383
}
384
385
uint8_t I8255::getPortC() const
386
{
387
	uint8_t result = latchPortC;
388
	if (control & DIRECTION_C0) {
389
		// actually set as input -> return floating value
390
		result |= 0x0f; // real floating value not yet supported
391
	}
392
	if (control & DIRECTION_C1) {
393
		// actually set as input -> return floating value
394
		result |= 0xf0; // real floating value not yet supported
395
	}
396
	return result;
397
}
398
399
template<typename Archive>
400
void I8255::serialize(Archive& ar, unsigned /*version*/)
401
{
402
	ar.serialize("latchPortA", latchPortA,
403
	             "latchPortB", latchPortB,
404
	             "latchPortC", latchPortC,
405
	             "control",    control);
406
407
	// note: don't write to any output ports (is handled elsewhere)
408
	//       don't serialize 'warningPrinted'
409
}
410
INSTANTIATE_SERIALIZE_METHODS(I8255);
411
412
} // namespace openmsx