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 }