summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/diserial.h
blob: 7d7717c515897f6b4b0cc5151c0e3c3a196a3082 (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
// license:BSD-3-Clause
// copyright-holders:Carl, Miodrag Milanovic, Vas Crabb
#ifndef MAME_EMU_DISERIAL_H
#define MAME_EMU_DISERIAL_H

#pragma once

#include <cassert>

// Windows headers are crap, let me count the ways
#undef PARITY_NONE
#undef PARITY_ODD
#undef PARITY_EVEN
#undef PARITY_MARK
#undef PARITY_SPACE

// ======================> device_serial_interface
class device_serial_interface : public device_interface
{
public:
	enum
	{
		/* receive is waiting for start bit. The transition from high-low indicates
		start of start bit. This is used to synchronise with the data being transferred */
		RECEIVE_REGISTER_WAITING_FOR_START_BIT = 0x01,

		/* receive is synchronised with data, data bits will be clocked in */
		RECEIVE_REGISTER_SYNCHRONISED = 0x02,

		/* set if receive register has been filled */
		RECEIVE_REGISTER_FULL = 0x04
	};

	enum
	{
		/* register is empty and ready to be filled with data */
		TRANSMIT_REGISTER_EMPTY = 0x0001
	};

	/* parity selections */
	/* if all the bits are added in a byte, if the result is:
	   even -> parity is even
	   odd -> parity is odd
	*/

	enum parity_t
	{
		PARITY_NONE,     /* no parity. a parity bit will not be in the transmitted/received data */
		PARITY_ODD,      /* odd parity */
		PARITY_EVEN,     /* even parity */
		PARITY_MARK,     /* one parity */
		PARITY_SPACE     /* zero parity */
	};

	enum stop_bits_t
	{
		STOP_BITS_0,
		STOP_BITS_1 = 1,
		STOP_BITS_1_5 = 2,
		STOP_BITS_2 = 3
	};

	/* Communication lines.  Beware, everything is active high */
	enum
	{
		CTS = 0x0001, /* Clear to Send.       (INPUT)  Other end of connection is ready to accept data */
		RTS = 0x0002, /* Request to Send.     (OUTPUT) This end is ready to send data, and requests if the other */
						/*                               end is ready to accept it */
		DSR = 0x0004, /* Data Set ready.      (INPUT)  Other end of connection has data */
		DTR = 0x0008, /* Data terminal Ready. (OUTPUT) TX contains new data. */
		RX  = 0x0010, /* Receive data.        (INPUT)  */
		TX  = 0x0020  /* TX = Transmit data.  (OUTPUT) */
	};

	// construction/destruction
	device_serial_interface(const machine_config &mconfig, device_t &device);
	virtual ~device_serial_interface();

	DECLARE_WRITE_LINE_MEMBER(rx_w);
	DECLARE_WRITE_LINE_MEMBER(tx_clock_w);
	DECLARE_WRITE_LINE_MEMBER(rx_clock_w);
	DECLARE_WRITE_LINE_MEMBER(clock_w);

protected:
	void set_data_frame(int start_bit_count, int data_bit_count, parity_t parity, stop_bits_t stop_bits);

	void receive_register_reset();
	void receive_register_update_bit(int bit);
	void receive_register_extract();

	void set_rcv_rate(const attotime &rate);
	void set_tra_rate(const attotime &rate);
	void set_rcv_rate(u32 clock, int div) { set_rcv_rate((clock && div) ? (attotime::from_hz(clock) * div) : attotime::never); }
	void set_tra_rate(u32 clock, int div) { set_tra_rate((clock && div) ? (attotime::from_hz(clock) * div) : attotime::never); }
	void set_rcv_rate(int baud) { set_rcv_rate(baud ? attotime::from_hz(baud) : attotime::never); }
	void set_tra_rate(int baud) { set_tra_rate(baud ? attotime::from_hz(baud) : attotime::never); }
	void set_rate(const attotime &rate) { set_rcv_rate(rate); set_tra_rate(rate); }
	void set_rate(u32 clock, int div) { set_rcv_rate(clock, div); set_tra_rate(clock, div); }
	void set_rate(int baud) { set_rcv_rate(baud); set_tra_rate(baud); }

	void transmit_register_reset();
	void transmit_register_add_bit(int bit);
	void transmit_register_setup(u8 data_byte);
	u8 transmit_register_get_data_bit();

	u8 serial_helper_get_parity(u8 data) { return m_serial_parity_table[data]; }

	bool is_receive_register_full() const { return m_rcv_flags & RECEIVE_REGISTER_FULL; }
	bool is_transmit_register_empty() const { return m_tra_flags & TRANSMIT_REGISTER_EMPTY; }
	bool is_receive_register_synchronized() const { return m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED; }
	bool is_receive_register_shifting() const { return m_rcv_bit_count_received > 0; }
	bool is_receive_framing_error() const { return m_rcv_framing_error; }
	bool is_receive_parity_error() const { return m_rcv_parity_error; }

	u8 get_received_char() const { return m_rcv_byte_received; }

	virtual void tra_callback() { }
	virtual void rcv_callback() { receive_register_update_bit(m_rcv_line); }
	virtual void tra_complete() { }
	virtual void rcv_complete() { }

	// interface-level overrides
	virtual void interface_pre_start() override;
	virtual void interface_post_start() override;

	bool m_start_bit_hack_for_external_clocks;

	const char *parity_tostring(parity_t stop_bits);
	const char *stop_bits_tostring(stop_bits_t stop_bits);

private:
	TIMER_CALLBACK_MEMBER(rcv_clock) { rx_clock_w(!m_rcv_clock_state); }
	TIMER_CALLBACK_MEMBER(tra_clock) { tx_clock_w(!m_tra_clock_state); }

	u8 m_serial_parity_table[256];

	// Data frame
	// number of start bits
	int m_df_start_bit_count;
	// length of word in bits
	u8 m_df_word_length;
	// parity state
	u8 m_df_parity;
	// number of stop bits
	u8 m_df_stop_bit_count;

	// Receive register
	/* data */
	u16 m_rcv_register_data;
	/* flags */
	u8 m_rcv_flags;
	/* bit count received */
	u8 m_rcv_bit_count_received;
	/* length of data to receive - includes data bits, parity bit and stop bit */
	u8 m_rcv_bit_count;
	/* the byte of data received */
	u8 m_rcv_byte_received;

	bool m_rcv_framing_error;
	bool m_rcv_parity_error;

	// Transmit register
	/* data */
	u16 m_tra_register_data;
	/* flags */
	u8 m_tra_flags;
	/* number of bits transmitted */
	u8 m_tra_bit_count_transmitted;
	/* length of data to send */
	u8 m_tra_bit_count;

	emu_timer *m_rcv_clock;
	emu_timer *m_tra_clock;
	attotime m_rcv_rate;
	attotime m_tra_rate;
	u8 m_rcv_line;

	int m_tra_clock_state, m_rcv_clock_state;

	void tra_edge();
	void rcv_edge();
};


template <u32 FIFO_LENGTH>
class device_buffered_serial_interface : public device_serial_interface
{
protected:
	using device_serial_interface::device_serial_interface;

	void interface_post_start() override
	{
		device_serial_interface::interface_post_start();

		device().save_item(NAME(m_fifo));
		device().save_item(NAME(m_head));
		device().save_item(NAME(m_tail));
		device().save_item(NAME(m_empty));
	}

	virtual void tra_complete() override
	{
		assert(!m_empty || (m_head == m_tail));
		assert(m_head < ARRAY_LENGTH(m_fifo));
		assert(m_tail < ARRAY_LENGTH(m_fifo));

		if (!m_empty)
		{
			transmit_register_setup(m_fifo[m_head]);
			m_head = (m_head + 1U) % FIFO_LENGTH;
			m_empty = (m_head == m_tail) ? 1U : 0U;
		}
	}

	virtual void rcv_complete() override
	{
		receive_register_extract();
		received_byte(get_received_char());
	}

	void clear_fifo()
	{
		m_head = m_tail = 0U;
		m_empty = 1U;
	}

	void transmit_byte(u8 byte)
	{
		assert(!m_empty || (m_head == m_tail));
		assert(m_head < ARRAY_LENGTH(m_fifo));
		assert(m_tail < ARRAY_LENGTH(m_fifo));

		if (m_empty && is_transmit_register_empty())
		{
			transmit_register_setup(byte);
		}
		else if (m_empty || (m_head != m_tail))
		{
			m_fifo[m_tail] = byte;
			m_tail = (m_tail + 1U) % FIFO_LENGTH;
			m_empty = 0U;
		}
		else
		{
			device().logerror("FIFO overrun (byte = 0x%02x)", byte);
		}
	}

	bool fifo_empty() const
	{
		return m_empty;
	}

	bool fifo_full() const
	{
		return !m_empty && (m_head == m_tail);
	}

private:
	virtual void received_byte(u8 byte) = 0;

	u8  m_fifo[FIFO_LENGTH];
	u32 m_head = 0U, m_tail = 0U;
	u8  m_empty = 1U;
};

#endif // MAME_EMU_DISERIAL_H