Mercurial > repos > blastem
comparison cdd_mcu.c @ 2061:7c1760b5b3e5 segacd
Implemented basic TOC functionality of CDD MCU
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 27 Jan 2022 00:33:41 -0800 |
parents | |
children | 07ed42bd7b4c |
comparison
equal
deleted
inserted
replaced
2060:f1c2415f4d1d | 2061:7c1760b5b3e5 |
---|---|
1 #include "cdd_mcu.h" | |
2 #include "backend.h" | |
3 | |
4 #define SCD_MCLKS 50000000 | |
5 #define CD_BLOCK_CLKS 16934400 | |
6 #define CDD_MCU_DIVIDER 8 | |
7 #define SECTOR_CLOCKS (CD_BLOCK_CLKS/75) | |
8 #define NIBBLE_CLOCKS (CDD_MCU_DIVIDER * 77) | |
9 | |
10 //lead in start max diameter 46 mm | |
11 //program area start max diameter 50 mm | |
12 //difference 4 mm = 4000 um | |
13 //radius difference 2 mm = 2000 um | |
14 //track pitch 1.6 um | |
15 //1250 physical tracks in between | |
16 //linear speed 1.2 m/s - 1.4 m/s | |
17 // 1.3 m = 1300 mm | |
18 // circumference at 46 mm ~ 144.51 mm | |
19 // circumference at 50 mm ~ 157.08 mm | |
20 // avg is 150.795 | |
21 // 75 sectors per second | |
22 // 17.3333 mm "typical" length of a sector | |
23 // ~8.7 sectors per track in lead-in area | |
24 #define LEADIN_SECTORS 10875 | |
25 | |
26 static uint32_t cd_block_to_mclks(uint32_t cycles) | |
27 { | |
28 return ((uint64_t)cycles) * ((uint64_t)SCD_MCLKS) / ((uint64_t)CD_BLOCK_CLKS); | |
29 } | |
30 | |
31 static uint32_t mclks_to_cd_block(uint32_t cycles) | |
32 { | |
33 return ((uint64_t)cycles) * ((uint64_t)CD_BLOCK_CLKS) / ((uint64_t)SCD_MCLKS); | |
34 } | |
35 | |
36 void cdd_mcu_init(cdd_mcu *context, system_media *media) | |
37 { | |
38 context->next_int_cycle = CYCLE_NEVER; | |
39 context->last_subcode_cycle = CYCLE_NEVER; | |
40 context->last_nibble_cycle = CYCLE_NEVER; | |
41 context->requested_format = SF_NOTREADY; | |
42 context->media = media; | |
43 context->current_status_nibble = -1; | |
44 context->current_cmd_nibble = -1; | |
45 } | |
46 | |
47 enum { | |
48 GAO_CDD_CTRL, | |
49 GAO_CDD_STATUS, | |
50 GAO_CDD_CMD = GAO_CDD_STATUS+5 | |
51 }; | |
52 | |
53 static uint8_t checksum(uint8_t *vbuffer) | |
54 { | |
55 uint8_t *buffer = vbuffer; | |
56 uint8_t sum = 0; | |
57 for (int i = 0; i < 9; i++) | |
58 { | |
59 sum += buffer[i]; | |
60 } | |
61 return (~sum) & 0xF; | |
62 } | |
63 #define SEEK_SPEED 2200 //made up number | |
64 static void handle_seek(cdd_mcu *context) | |
65 { | |
66 if (context->seeking) { | |
67 if (context->seek_pba == context->head_pba) { | |
68 context->seeking = 0; | |
69 } else if (context->seek_pba > context->head_pba) { | |
70 context->head_pba += SEEK_SPEED; | |
71 if (context->head_pba > context->seek_pba) { | |
72 context->head_pba = context->seek_pba; | |
73 } | |
74 } else { | |
75 context->head_pba -= SEEK_SPEED; | |
76 if (context->head_pba < context->seek_pba) { | |
77 context->head_pba = context->seek_pba; | |
78 } | |
79 } | |
80 } | |
81 } | |
82 | |
83 static void lba_to_status(cdd_mcu *context, uint32_t lba) | |
84 { | |
85 uint32_t seconds = lba / 75; | |
86 uint32_t frames = lba % 75; | |
87 uint32_t minutes = seconds / 60; | |
88 seconds = seconds % 60; | |
89 context->status_buffer.b.time.min_high = minutes / 10; | |
90 context->status_buffer.b.time.min_low = minutes % 10; | |
91 context->status_buffer.b.time.sec_high = seconds / 10; | |
92 context->status_buffer.b.time.sec_low = seconds % 10; | |
93 context->status_buffer.b.time.frame_high = frames / 10; | |
94 context->status_buffer.b.time.frame_low = frames % 10; | |
95 } | |
96 | |
97 static void update_status(cdd_mcu *context) | |
98 { | |
99 switch (context->status) | |
100 { | |
101 case DS_PLAY: | |
102 handle_seek(context); | |
103 if (!context->seeking) { | |
104 context->head_pba++; | |
105 } | |
106 break; | |
107 case DS_PAUSE: | |
108 handle_seek(context); | |
109 break; | |
110 case DS_TOC_READ: | |
111 handle_seek(context); | |
112 if (!context->seeking) { | |
113 context->head_pba++; | |
114 if (context->media && context->media->type == MEDIA_CDROM && context->media->num_tracks) { | |
115 if (context->head_pba > 3*context->media->num_tracks + 1) { | |
116 context->toc_valid = 1; | |
117 context->seeking = 1; | |
118 context->seek_pba = LEADIN_SECTORS + context->media->tracks[0].start_lba + context->media->tracks[0].fake_pregap; | |
119 context->status = DS_PAUSE; | |
120 } | |
121 | |
122 } else { | |
123 context->status = DS_NO_DISC; | |
124 } | |
125 } | |
126 break; | |
127 | |
128 } | |
129 switch (context->requested_format) | |
130 { | |
131 case SF_ABSOLUTE: | |
132 if (context->toc_valid) { | |
133 lba_to_status(context, context->head_pba - LEADIN_SECTORS); | |
134 context->status_buffer.format = SF_ABSOLUTE; | |
135 } else { | |
136 context->status_buffer.format = SF_NOTREADY; | |
137 } | |
138 break; | |
139 case SF_RELATIVE: | |
140 if (context->toc_valid) { | |
141 uint32_t lba =context->head_pba - LEADIN_SECTORS; | |
142 for (uint32_t i = 0; i < context->media->num_tracks; i++) | |
143 { | |
144 if (lba < context->media->tracks[i].end_lba) { | |
145 if (context->media->tracks[i].fake_pregap) { | |
146 if (lba > context->media->tracks[i].fake_pregap) { | |
147 lba -= context->media->tracks[i].fake_pregap; | |
148 } else { | |
149 //relative time counts down to 0 in pregap | |
150 lba = context->media->tracks[i].fake_pregap - lba; | |
151 break; | |
152 } | |
153 } | |
154 if (lba < context->media->tracks[i].start_lba) { | |
155 //relative time counts down to 0 in pregap | |
156 lba = context->media->tracks[i].start_lba - lba; | |
157 } else { | |
158 lba -= context->media->tracks[i].start_lba; | |
159 } | |
160 break; | |
161 } else if (context->media->tracks[i].fake_pregap) { | |
162 lba -= context->media->tracks[i].fake_pregap; | |
163 } | |
164 } | |
165 lba_to_status(context, lba); | |
166 context->status_buffer.format = SF_ABSOLUTE; | |
167 } else { | |
168 context->status_buffer.format = SF_NOTREADY; | |
169 } | |
170 break; | |
171 case SF_TOCO: | |
172 if (context->toc_valid) { | |
173 lba_to_status(context, context->media->tracks[context->media->num_tracks - 1].end_lba + context->media->tracks[0].fake_pregap); | |
174 context->status_buffer.format = SF_TOCO; | |
175 } else { | |
176 context->status_buffer.format = SF_NOTREADY; | |
177 } | |
178 break; | |
179 case SF_TOCT: | |
180 if (context->toc_valid) { | |
181 context->status_buffer.b.toct.first_track_high = 0; | |
182 context->status_buffer.b.toct.first_track_low = 1; | |
183 context->status_buffer.b.toct.last_track_high = (context->media->num_tracks + 1) / 10; | |
184 context->status_buffer.b.toct.last_track_low = (context->media->num_tracks + 1) % 10; | |
185 context->status_buffer.b.toct.version = 0; | |
186 context->status_buffer.format = SF_TOCT; | |
187 } else { | |
188 context->status_buffer.format = SF_NOTREADY; | |
189 } | |
190 break; | |
191 case SF_TOCN: | |
192 if (context->toc_valid) { | |
193 lba_to_status(context, context->media->tracks[context->requested_track].start_lba + context->media->tracks[0].fake_pregap); | |
194 context->status_buffer.b.tocn.track_low = context->requested_track % 10; | |
195 context->status_buffer.format = SF_TOCN; | |
196 } else { | |
197 context->status_buffer.format = SF_NOTREADY; | |
198 } | |
199 break; | |
200 case SF_NOTREADY: | |
201 memset(&context->status_buffer, 0, sizeof(context->status_buffer) - 1); | |
202 context->status_buffer.format = SF_NOTREADY; | |
203 break; | |
204 } | |
205 if (context->error_status == DS_STOP) { | |
206 context->status_buffer.status = context->status; | |
207 } else { | |
208 context->status_buffer.status = context->error_status; | |
209 context->error_status = DS_STOP; | |
210 } | |
211 if (context->requested_format != SF_TOCN) { | |
212 context->status_buffer.b.time.flags = 0; //TODO: populate these | |
213 } | |
214 context->status_buffer.checksum = checksum((uint8_t *)&context->status_buffer); | |
215 if (context->status_buffer.format != SF_NOTREADY) { | |
216 printf("CDD Status %d%d.%d%d%d%d%d%d.%d%d\n", | |
217 context->status_buffer.status, context->status_buffer.format, | |
218 context->status_buffer.b.time.min_high, context->status_buffer.b.time.min_low, | |
219 context->status_buffer.b.time.sec_high, context->status_buffer.b.time.sec_low, | |
220 context->status_buffer.b.time.frame_high, context->status_buffer.b.time.frame_low, | |
221 context->status_buffer.b.time.flags, context->status_buffer.checksum | |
222 ); | |
223 } | |
224 } | |
225 | |
226 static void run_command(cdd_mcu *context) | |
227 { | |
228 uint8_t check = checksum((uint8_t*)&context->cmd_buffer); | |
229 if (check != context->cmd_buffer.checksum) { | |
230 context->error_status = DS_SUM_ERROR; | |
231 return; | |
232 } | |
233 if (context->cmd_buffer.must_be_zero) { | |
234 context->error_status = DS_CMD_ERROR; | |
235 return; | |
236 } | |
237 switch (context->cmd_buffer.cmd_type) | |
238 { | |
239 case CMD_NOP: | |
240 break; | |
241 case CMD_STOP: | |
242 puts("CDD CMD: STOP"); | |
243 context->status = DS_STOP; | |
244 break; | |
245 case CMD_REPORT_REQUEST: | |
246 switch (context->cmd_buffer.b.format.status_type) | |
247 { | |
248 case SF_ABSOLUTE: | |
249 case SF_RELATIVE: | |
250 case SF_TRACK: | |
251 context->requested_format = context->cmd_buffer.b.format.status_type; | |
252 break; | |
253 case SF_TOCO: | |
254 if (context->toc_valid) { | |
255 context->requested_format = SF_TOCO; | |
256 } else { | |
257 context->error_status = DS_CMD_ERROR; | |
258 context->requested_format = SF_ABSOLUTE; | |
259 } | |
260 break; | |
261 case SF_TOCT: | |
262 if (context->toc_valid) { | |
263 if (context->status == DS_STOP) { | |
264 context->status = DS_TOC_READ; | |
265 context->seeking = 1; | |
266 context->seek_pba = 0; | |
267 } | |
268 } else { | |
269 context->status = DS_TOC_READ; | |
270 context->seeking = 1; | |
271 context->seek_pba = 0; | |
272 } | |
273 context->requested_format = SF_TOCT; | |
274 break; | |
275 case SF_TOCN: | |
276 context->requested_track = context->cmd_buffer.b.format.track_high * 10; | |
277 context->requested_track += context->cmd_buffer.b.format.track_low; | |
278 context->status = DS_TOC_READ; | |
279 context->seeking = 1; | |
280 context->seek_pba = 0; | |
281 context->requested_format = SF_TOCN; | |
282 break; | |
283 } | |
284 printf("CDD CMD: REPORT REQUEST(%d), format set to %d\n", context->cmd_buffer.b.format.status_type, context->requested_format); | |
285 break; | |
286 default: | |
287 printf("CDD CMD: Unimplemented(%d)\n", context->cmd_buffer.cmd_type); | |
288 } | |
289 } | |
290 | |
291 #define BIT_HOCK 0x4 | |
292 #define BIT_DRS 0x2 | |
293 #define BIT_DTS 0x1 | |
294 | |
295 void cdd_mcu_run(cdd_mcu *context, uint32_t cycle, uint16_t *gate_array) | |
296 { | |
297 uint32_t cd_cycle = mclks_to_cd_block(cycle); | |
298 if (!(gate_array[GAO_CDD_CTRL] & BIT_HOCK)) { | |
299 //it's a little unclear if this gates the actual cd block clock or just handshaking | |
300 //assum it's actually the clock for now | |
301 context->cycle = cycle; | |
302 return; | |
303 } | |
304 uint32_t next_subcode = context->last_subcode_cycle + SECTOR_CLOCKS; | |
305 uint32_t next_nibble = context->current_status_nibble >= 0 ? context->last_nibble_cycle + NIBBLE_CLOCKS : CYCLE_NEVER; | |
306 uint32_t next_cmd_nibble = context->current_cmd_nibble >= 0 ? context->last_nibble_cycle + NIBBLE_CLOCKS : CYCLE_NEVER; | |
307 for (; context->cycle < cd_cycle; context->cycle += CDD_MCU_DIVIDER) | |
308 { | |
309 if (context->cycle >= next_subcode) { | |
310 context->last_subcode_cycle = context->cycle; | |
311 next_subcode = context->cycle + SECTOR_CLOCKS; | |
312 update_status(context); | |
313 next_nibble = context->cycle; | |
314 context->current_status_nibble = 0; | |
315 gate_array[GAO_CDD_STATUS] |= BIT_DRS; | |
316 } | |
317 if (context->cycle >= next_nibble) { | |
318 if (context->current_status_nibble == sizeof(cdd_status)) { | |
319 context->current_status_nibble = -1; | |
320 gate_array[GAO_CDD_STATUS] &= ~BIT_DRS; | |
321 if (context->cmd_recv_pending) { | |
322 context->cmd_recv_pending = 0; | |
323 context->current_cmd_nibble = 0; | |
324 gate_array[GAO_CDD_STATUS] |= BIT_DTS; | |
325 next_nibble = context->cycle + NIBBLE_CLOCKS; | |
326 } else { | |
327 context->cmd_recv_wait = 1; | |
328 next_nibble = CYCLE_NEVER; | |
329 } | |
330 } else { | |
331 uint8_t value = ((uint8_t *)&context->status_buffer)[context->current_status_nibble]; | |
332 int ga_index = GAO_CDD_STATUS + (context->current_status_nibble >> 1); | |
333 if (context->current_status_nibble & 1) { | |
334 gate_array[ga_index] = value | (gate_array[ga_index] & 0xFF00); | |
335 } else { | |
336 gate_array[ga_index] = (value << 8) | (gate_array[ga_index] & 0x00FF); | |
337 } | |
338 if (context->current_status_nibble == 7) { | |
339 context->int_pending = 1; | |
340 context->next_int_cycle = cd_block_to_mclks(cycle + SECTOR_CLOCKS); | |
341 } | |
342 context->current_status_nibble++; | |
343 context->last_nibble_cycle = context->cycle; | |
344 next_nibble = context->cycle + NIBBLE_CLOCKS; | |
345 } | |
346 } else if (context->cycle >= next_cmd_nibble) { | |
347 if (context->current_cmd_nibble == sizeof(cdd_cmd)) { | |
348 next_cmd_nibble = CYCLE_NEVER; | |
349 context->current_cmd_nibble = -1; | |
350 gate_array[GAO_CDD_STATUS] &= ~BIT_DTS; | |
351 run_command(context); | |
352 } else { | |
353 int ga_index = GAO_CDD_CMD + (context->current_cmd_nibble >> 1); | |
354 uint8_t value = (context->current_cmd_nibble & 1) ? gate_array[ga_index] : gate_array[ga_index] >> 8; | |
355 ((uint8_t *)&context->cmd_buffer)[context->current_cmd_nibble] = value; | |
356 context->current_cmd_nibble++; | |
357 context->last_nibble_cycle = context->cycle; | |
358 next_cmd_nibble = context->cycle + NIBBLE_CLOCKS; | |
359 } | |
360 } | |
361 } | |
362 } | |
363 | |
364 void cdd_mcu_start_cmd_recv(cdd_mcu *context, uint16_t *gate_array) | |
365 { | |
366 if (context->cmd_recv_wait) { | |
367 context->current_cmd_nibble = 0; | |
368 gate_array[GAO_CDD_STATUS] |= BIT_DTS; | |
369 context->last_nibble_cycle = context->cycle; | |
370 context->cmd_recv_wait = 0; | |
371 } else { | |
372 context->cmd_recv_pending = 1; | |
373 } | |
374 } | |
375 | |
376 void cdd_hock_enabled(cdd_mcu *context) | |
377 { | |
378 context->last_subcode_cycle = context->cycle; | |
379 context->next_int_cycle = cd_block_to_mclks(context->cycle + SECTOR_CLOCKS + 7 * NIBBLE_CLOCKS); | |
380 } | |
381 | |
382 void cdd_hock_disabled(cdd_mcu *context) | |
383 { | |
384 context->last_subcode_cycle = CYCLE_NEVER; | |
385 context->next_int_cycle = CYCLE_NEVER; | |
386 context->last_nibble_cycle = CYCLE_NEVER; | |
387 context->current_status_nibble = -1; | |
388 context->current_cmd_nibble = -1; | |
389 } | |
390 | |
391 void cdd_mcu_adjust_cycle(cdd_mcu *context, uint32_t deduction) | |
392 { | |
393 uint32_t cd_deduction = mclks_to_cd_block(deduction); | |
394 if (context->next_int_cycle != CYCLE_NEVER) { | |
395 context->next_int_cycle -= deduction; | |
396 } | |
397 if (context->last_subcode_cycle != CYCLE_NEVER) { | |
398 context->last_subcode_cycle -= cd_deduction; | |
399 } | |
400 if (context->last_nibble_cycle != CYCLE_NEVER) { | |
401 context->last_nibble_cycle -= cd_deduction; | |
402 } | |
403 } |