Howto:Use The Sega CD In Mode 1

From The MegaDrive Wiki
Jump to: navigation, search

The Sega CD has a not so commonly known feature named "Mode 1," in which code is ran from the cartridge instead of the CD. but it still has the ability to utilize the Sega CD's added features such as CD audio, PCM, and much more. The mode is very sparsely documented, and quite a bit of research from various parties has gone into describing this mode. Therefore, this how to article will serve as a reference on how to use the Sega CD in Mode 1. Throughout this how to, we'll be making a very simple Mega Drive program that plays the first program on the disc in the Sega CD, without any interface to the VDP of any kind.

Contents

Hardware Basics

Before we can begin to use the Sega CD in Mode 1, we must first understand the additional restrictions and benefits the Sega CD gives one in Mode 1, as well as the added hardware available for use. With a Sega CD connected, the program has access to:

  • A CD drive for data and audio
  • An 8-channel stereo PCM chip
  • Video Scaling and Rotation chip (ASIC)
  • A 12.5 MHz Motorola 68000 with 512KB of Program RAM and 256 KB of Word RAM.

The memory map of the Sub-CPU is very different than the one of the Mega Drive's Main CPU. This memory map depends slightly upon what Word RAM mode has been chosen, but that will be pointed out below.

Start address End address Description
$000000 $07FFFF 4 Megabit Program RAM
$080000 $0BFFFF Word RAM (2M Mode)
$0C0000 $0D0000 Word RAM (1M Mode)
$0E0000 $FDFFFF Reserved/Prohibited
$FE0000 $FE3FFF Backup RAM (Odd bytes only)
$FE4000 $FEFFFF System Reserved
$FF0000 $FF3FFF PCM Sound Source (Odd bytes only)
$FF4000 $FF7FFF System Reserved
$FF8000 $FF81FF Sub-CPU Registers
$FF8200 $FFFFFF System Reserved

The Main CPU has no access to any of the Sega CD hardware besides some communications registers, as well as the Word and Program RAM. All Sega CD hardware access needs to be done through the Sub-CPU, and therefore a program is loaded into it from the Main CPU, after a BIOS has been loaded.

Initializing the Mega Drive

Before we have the Mega Drive in a state usable for initializing the Sega CD, we first must initialize our Mega Drive. For doing this, we use the code from this how to article. After writing our Mega Drive initialization code, we will be left with the following code:

  1. StartOfRom:
  2. Vectors:	dc.l $FFFE00,   EntryPoint, ErrorTrap, ErrorTrap
  3. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  4. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  5. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  6. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  7. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  8. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  9. 		dc.l HBlank,	ErrorTrap, VBlank,     ErrorTrap
  10. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  11. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  12. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  13. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  14. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  15. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  16. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  17. 		dc.l ErrorTrap,	ErrorTrap, ErrorTrap, ErrorTrap
  18. Console:	dc.b 'SEGA MEGA DRIVE ' 						; Hardware system ID
  19. Date:		dc.b '(C)XXXX YEAR.MON' 						; Release date
  20. Title_Local:	dc.b 'SEGA CD DEMO PROGRAM                            ' 		; Domestic name (Note: has to be 48 bytes long, pad it out with spaces if you do not have 48 characters in your title.
  21. Title_Int:	dc.b 'SEGA CD DEMO PROGRAM                            ' 		; International name (Note: has to be 48 bytes long)
  22. Serial:		dc.b 'GM 10101010-00'   						; Serial/version number
  23. Checksum:	dc.w 0
  24. 		dc.b 'J               '							; I/O support (Has to be 16 bytes long, so pad it out with spaces.)
  25. RomStartLoc:	dc.l StartOfRom								; ROM start
  26. RomEndLoc:	dc.l EndOfRom-1								; ROM end
  27. RamStartLoc:	dc.l $FF0000								; RAM start
  28. RamEndLoc:	dc.l $FFFFFF								; RAM end
  29. SRAMSupport:	dc.l $5241F820								; change to $5241F820 (NOT $5241E020) to create SRAM. 'RA' and $F8 also work.
  30. 		dc.l $200000								; SRAM start
  31. 		dc.l $200200								; SRAM end (Gives us $200  ($100 useable) bytes of SRAM)
  32. Notes:		dc.b '                                                    '             ; Anything can be put in this space, but it has to be 52 bytes.
  33. Region:		dc.b 'JUE             ' 						; Region (J=Japan, U=USA, E=Europe)
  34.  
  35. EntryPoint:
  36. 		tst.l	($A10008).l 							; Test port A control
  37. 		bne.s	PortA_Ok							; If so, magically branch
  38. 		tst.w	($A1000C).l 							; Test port C control
  39.  
  40. PortA_Ok:
  41. 		bne.w	PortC_Ok
  42. 		move.b	($A10001).l,d0 							; Get hardware version
  43. 		andi.b	#$F,d0								; Compare
  44. 		beq.s	SkipSecurity							; If the console has no TMSS, skip the security stuff.
  45. 		move.l	#'SEGA',($A14000).l						; Make the TMSS happy.
  46.  
  47. SkipSecurity:
  48. 		moveq	#0,d0								; Clear d0.
  49. 		move.l	#$C0000000,($C00004).l 						; Set VDP to CRAM write
  50. 		move.w	#$3F,d7								; Clear the entire CRAM.
  51.  
  52. VDP_ClrCRAM:
  53. 		move.w	d0,($C00000).l							; Write 0 to the data port.
  54. 		dbf	d7,VDP_ClrCRAM 							; Clear	the CRAM
  55. 		lea	($FFFF0000).l, a0						; Load start of RAM into a0.
  56. 		move.w	#$3FFF, d0							; Clear $3FFF longwords.
  57. 		moveq	#0, d1								; Clear d1.
  58.  
  59. @clrRamLoop:
  60. 		move.l	d1, (a0)+							; Clear a long of RAM.
  61. 		dbf	d0, @clrRamLoop							; Continue clearing RAM if there's anything left.
  62.  
  63. PortC_Ok:
  64. 		bsr.w	Init_Z80							; Initialize the Z80
  65. 		move	#$2300, sr							; Re-enable interrupts.
  66. 		bra.s	MainProgram
  67.  
  68. HInt:
  69. 		rte
  70.  
  71. VBlank:
  72. 		movem	d0-a6, -(sp)						; Back up registers to stack
  73. 		bsr.w	SCD_U_SubCPUInt2					; Generate an Level 2 interrupt on Sub-CPU
  74. 		movem	(sp)+, d0-a6						; Restore registers.
  75. 		rte
  76.  
  77. MainProgram:

Initialize the Sega CD

Now that our Mega Drive is initialized, it is time to initialize the Sega CD. Doing this requires a few things, such as setting the correct memory modes, resetting the Sub-CPU and taking it's bus, decompressing a BIOS and Sub-CPU code, and then allowing it to run again. All of this is accomplished by the following code (put directly after the MainProgram label above):

  1. 		move	#$2700, sr
  2.  
  3. 		cmp.w	#"BR", $400180						; Check to see if we have a Sega CD attached.
  4. 		bne.w	MainLoop							; If not, branch.
  5.  
  6. 		movem.l	d0-a6, -(sp)						; Back up regs to stack
  7.  
  8. 		lea		$A12000, a6							; Load gate array start to a6
  9.  
  10. 		moveq	#0, d0								; Clear d0
  11. 		move.b	1(a6), d0							; Load reset reg to d0.
  12. 		bset	#1, d0								; Request Sub-CPU Bus
  13. 		bclr	#0, d0								; Reset Sub-CPU
  14. 		move.b	d0, 1(a6)							; Restore reset reg.
  15.  
  16. 		moveq	#$7F, d0
  17.  
  18. @nopLoop:
  19. 		nop
  20. 		nop
  21. 		dbf		d0, @nopLoop
  22.  
  23. 		lea		($A12002).l,a3							; Word RAM Control to a3
  24. 		move.w	#$0009, (a3)								; Write mem mode
  25.  
  26. 		moveq	#0, d0								; Clear d0
  27.  
  28. 		cmp.w	#"EG", $41586E					; Western BIOS at $415800
  29. 		bne.s	@checkRegularBIOS					; If not, branch.
  30.  
  31. 		lea		$415800, a0							; BIOS location to a0
  32.  
  33.    		bra.s	@decompressSubCPU					; Branch to Sub-CPU Shenanigans.
  34.  
  35. @checkRegularBIOS:
  36.    		cmp.w	#"EG", $41606E					; Regular BIOS at $416000
  37.    		bne.s	@checkWondermegaBIOS				; If not, branch.
  38.  
  39. 		lea		$416000, a0							; BIOS location to a0
  40.  
  41.    		bra.s	@decompressSubCPU					; Branch to Sub-CPU Shenanigans.
  42.  
  43. @checkWondermegaBIOS:
  44.    		cmp.w	#"ON", $41606E					; WonderMega BIOS at $416000
  45.    		bne.s	@checkLaserActiveBIOS				; If not, branch.
  46.  
  47. 		lea		$416000, a0							; BIOS location to a0
  48.  
  49.    		bra.s	@decompressSubCPU					; Branch to Sub-CPU Shenanigans.
  50.  
  51. @checkLaserActiveBIOS:
  52.    		cmp.w	#"EG", $41AD6E					; LaserActive BIOS at $41AD00
  53. 		bne.w	@UnknownBIOSError					; If not, branch. (Unknown BIOS)
  54.  
  55. 		lea		$41AD00, a0							; BIOS location to a0
  56.  
  57. @decompressSubCPU:
  58. 		lea		$420000, a1							; Location to decompress to to a1
  59. 		bsr.w	KosDec								; Decompress
  60.  
  61. 		lea		SubCPU_Code, a0						; Sub-CPU code to a0
  62. 		lea		$426000, a1							; Location to load to
  63. 		moveq	#0, d0								; Clear d0
  64.  
  65. 		move.w	#SubCPU_End-SubCPU_Code, d0			; Move length to d0
  66.  
  67. @loadSubCPULoop:
  68. 		move.b	(a0)+, (a1)+						; Write a byte of Sub-CPU code
  69.     	 	dbf     d0, @loadSubCPULoop					; Keep looping until all is written.
  70.  
  71. 		move.b	#$2A,($A12002).l 					; Set 2M mode, and some unknown shit
  72. 		lea		($A12001).l,a3						; Reset reg to a3
  73. 		moveq	#1,d0								; Move 1 to d0
  74.  
  75.  
  76. @resetLoop:
  77. 		move.b	d0,(a3)								; Write d0 to reset reg
  78. 		cmp.b	(a3),d0								; Is the reset reg set to our value?
  79. 		bne.s	@resetLoop							; Keep looping until it is
  80.  
  81. 		nop
  82. 		nop
  83. 		nop
  84. 		nop
  85. 		nop
  86. 		nop
  87. 		nop
  88. 		nop
  89. 		nop
  90. 		nop
  91.  
  92. 		bsr.w	SCD_U_SubCPUInt2					; Generate an Level 2 interrupt on Sub-CPU
  93.  
  94. 		nop
  95. 		nop
  96. 		nop
  97. 		nop
  98. 		nop
  99. 		nop
  100. 		nop
  101. 		nop
  102. 		nop
  103. 		nop
  104.  
  105. 		bsr.w	SCD_U_SubCPUInt2					; Generate an Level 2 interrupt on Sub-CPU
  106. 		move.b	#$FF, $A12010						; Clear the function number.
  107. 		bra.w	MainLoop							; Jump to main loop.
  108.  
  109. @UnknownBIOSError:
  110. 		bra.s	        @UnknownBIOSError					; Keep looping
  111.  
  112. ; ===========================================================================
  113. ; SCD Utility method to cause a Level 2 interrupt on the Sub-CPU.
  114.  
  115. SCD_U_SubCPUInt2:
  116. 		moveq	#0, d0								; Clear d0
  117. 		move.w	$A12000, d0							; Move RESET reg to d0
  118. 		bset		#8, d0								; Set the L2 Int flag
  119. 		move.w	d0, $A12000							; Restore the thinger
  120.  
  121. 		rts
  122.  
  123. ; ===========================================================================
  124.  
  125. SubCPU_Code:
  126. 		incbin	"SubCPU.bin"
  127.  
  128. SubCPU_End:
  129. 		dc.w	$0000
  130. 		even
  131.  
  132. ; ---------------------------------------------------------------------------
  133. ; Kosinski decompression algorithm
  134. ; ---------------------------------------------------------------------------
  135.  
  136. ; ||||||||||||||| S U B	R O U T	I N E |||||||||||||||||||||||||||||||||||||||
  137.  
  138. KosDec:
  139.  
  140. var_2		= -2
  141. var_1		= -1
  142.  
  143. 		subq.l	#2,sp
  144. 		move.b	(a0)+,2+var_1(sp)
  145. 		move.b	(a0)+,(sp)
  146. 		move.w	(sp),d5
  147. 		moveq	#$F,d4
  148.  
  149. loc_18A8:
  150. 		lsr.w	#1,d5
  151. 		move	sr,d6
  152. 		dbf	d4,loc_18BA
  153. 		move.b	(a0)+,2+var_1(sp)
  154. 		move.b	(a0)+,(sp)
  155. 		move.w	(sp),d5
  156. 		moveq	#$F,d4
  157.  
  158. loc_18BA:
  159. 		move	d6,ccr
  160. 		bcc.s	loc_18C2
  161. 		move.b	(a0)+,(a1)+
  162. 		bra.s	loc_18A8
  163. ; ===========================================================================
  164.  
  165. loc_18C2:				; XREF: KosDec
  166. 		moveq	#0,d3
  167. 		lsr.w	#1,d5
  168. 		move	sr,d6
  169. 		dbf	d4,loc_18D6
  170. 		move.b	(a0)+,2+var_1(sp)
  171. 		move.b	(a0)+,(sp)
  172. 		move.w	(sp),d5
  173. 		moveq	#$F,d4
  174.  
  175. loc_18D6:
  176. 		move	d6,ccr
  177. 		bcs.s	loc_1906
  178. 		lsr.w	#1,d5
  179. 		dbf	d4,loc_18EA
  180. 		move.b	(a0)+,2+var_1(sp)
  181. 		move.b	(a0)+,(sp)
  182. 		move.w	(sp),d5
  183. 		moveq	#$F,d4
  184.  
  185. loc_18EA:
  186. 		roxl.w	#1,d3
  187. 		lsr.w	#1,d5
  188. 		dbf	d4,loc_18FC
  189. 		move.b	(a0)+,2+var_1(sp)
  190. 		move.b	(a0)+,(sp)
  191. 		move.w	(sp),d5
  192. 		moveq	#$F,d4
  193.  
  194. loc_18FC:
  195. 		roxl.w	#1,d3
  196. 		addq.w	#1,d3
  197. 		moveq	#-1,d2
  198. 		move.b	(a0)+,d2
  199. 		bra.s	loc_191C
  200. ; ===========================================================================
  201.  
  202. loc_1906:				; XREF: loc_18C2
  203. 		move.b	(a0)+,d0
  204. 		move.b	(a0)+,d1
  205. 		moveq	#-1,d2
  206. 		move.b	d1,d2
  207. 		lsl.w	#5,d2
  208. 		move.b	d0,d2
  209. 		andi.w	#7,d1
  210. 		beq.s	loc_1928
  211. 		move.b	d1,d3
  212. 		addq.w	#1,d3
  213.  
  214. loc_191C:
  215. 		move.b	(a1,d2.w),d0
  216. 		move.b	d0,(a1)+
  217. 		dbf	d3,loc_191C
  218. 		bra.s	loc_18A8
  219. ; ===========================================================================
  220.  
  221. loc_1928:				; XREF: loc_1906
  222. 		move.b	(a0)+,d1
  223. 		beq.s	loc_1938
  224. 		cmpi.b	#1,d1
  225. 		beq.w	loc_18A8
  226. 		move.b	d1,d3
  227. 		bra.s	loc_191C
  228. ; ===========================================================================
  229.  
  230. loc_1938:				; XREF: loc_1928
  231. 		addq.l	#2,sp
  232. 		rts	
  233. ; End of function KosDec

Whoa - that's quite a lot of code. Reading the comments will help greatly, as I have taken care to annotate my code as best as I can. The KosDec routine is a routine to decompress the Sub-CPU BIOS from the Sega CD BIOS EPROM, called Kosinski. It was a commonly used compression for Mega Drive games' code, as well as other things. The SubCPU_Code label includes a binary file called SubCPU.bin which is our Sub-CPU code, which will run on the Sega CD and allow the main CPU to play music from the CD, as well as a few other things. Note that Program RAM is accessed in 4 different banks of 128 KB, so if your Sub-CPU code exceeds that size, you need to load it in 128KB chunks and switch the bank, but that's out of the scope of this article. If your code is over 128KB anyways, you should really be using compression and not a simple byte loading loop.


Do note that as the Sub-CPU BIOS address is hard coded, it seems to fail to work on some specific versions of the Sega CD BIOS. See the below list for alternate addresses of the Sub-CPU BIOS code in some special versions of the BIOS:

 (Note: A lot of these are based from primarily Emulator tests, though some have been properly tested on Hardware and have matched.)
 Multi-Mega (Europe) (v2.21X) [b] ........ $016000
 CDX (USA) (v2.21X) ...................... $016000
 Mega-CD 2 (Europe) (v2.00) .............. $016000
 Mega-CD 2 (Europe) (v2.00W) ............. $016000
 Mega-CD 2 (Japan) (v2.00C) .............. $016000
 Sega CD 2 (USA) (v2.00) ................. $016000
 Sega CD 2 (USA) (v2.00W) ................ $016000
 Sega CD 2 (USA) (v2.11X) ................ $016000
 Mega-CD (Asia) (v1.00S) ................. $016000
 Mega-CD (Europe) (v1.00) ................ $015800
 Mega-CD (Japan) (1.00l) ................. $016000
 Mega-CD (Japan) (1.00S) ................. $016000
 Mega-CD (Japan) (v1.00P) ................ $016000
 Sega CD (USA) (v1.00) ................... $015800
 Sega CD (USA) (v1.10) ................... $015800
 WonderMega (Japan) (v1.00) (Sega) ....... $016000
 WonderMega M2 (Japan) (v2.00) ........... $016000
 X'Eye (USA) (v2.00) ..................... $016000
 LaserActive (Japan) (v1.02) ............. $01AD00/$00D500?
 LaserActive (USA) (v1.02) ............... $01AD00/$00D500?
 LaserActive (USA) (v1.04) ............... $01AD00/$00D500?


Sub-CPU Code

As you probably noticed above, we're including a binary file that's our assembled Sub-CPU code. The Sub-CPU code is the code that runs on the Sega CD's Motorola 68000 and interacts with the Sega CD hardware. The Sega CD provides a few registers for the Sub-CPU and the Mega Drive's Main CPU to communicate, and to set things such as commands.

This code is loaded directly after the Sega CD BIOS, and the BIOS will call into it once the hardware has been initialized. All Sub-CPU code needs a header, exactly like this:

  1. 	org		$6000
  2.  
  3. SPHeader:
  4. 	dc.b   'MAIN-SUBCPU', 0
  5.     	dc.w   $0001, $0000						; Version 0001, normal type
  6.         dc.l   $00000000							; No Link module
  7.         dc.l   SPEnd-SPHeader						; Size
  8.         dc.l   SPHeaderOffsets-SPHeader				; Jump table pointer
  9.         dc.l   $00000000							; Some Work RAM info
  10.  
  11. SPHeaderOffsets:
  12.         dc.w   SPInit-SPHeaderOffsets
  13.         dc.w   SPMain-SPHeaderOffsets
  14.         dc.w   SPInt2-SPHeaderOffsets
  15.         dc.w   SPNull-SPHeaderOffsets
  16.         dc.w   $0000

SPInit points to a function that initializes the state of the Sega CD and Sub-CPU code. We will set the hardware's faders to 100% volume. SPMain is the main code that is ran after the initialization routine has ran. SPInt2 is the routine that is ran once a Level 2 interrupt is received, which is caused by our MD code every VBlank. This interrupt is required to keep the Sub-CPU going. Then there is SPNull, which I believe has to do with the Sega CD timer the Sub-CPU can use.

Our initialization code looks like this:

  1. ; Called on a level 2 interrupt.
  2. SPInt2:
  3. 		rts
  4.  
  5. SPInit:	
  6. 		move.b	#'D', $FF800F						; Clear busy flag.
  7.  
  8. 		moveq	#0, d1							; Clear d1.
  9. 		move.w	#$0400, d1						; Set volume to max.
  10. 		move.w	#$0085, d0						; Set the fader.
  11. 		jsr 		$5F22							; Call into the BIOS.
  12.  
  13. 		move.w	#$8400, d1						; Set master volume to max.
  14. 		move.w	#$0085, d0						; Set the fader.
  15. 		jsr 		$5F22							; Call into the BIOS.
  16.  
  17. 		lea		DriveInit_Table(pc), a0				; Load the table to a0.
  18. 		move.w    #$0010, d0						; Call BIOS function to read the ToC.
  19. 		jsr 		$5F22							; Call into the BIOS.
  20.  
  21. 		move.w	#$0089, d0						; Stop the drive.
  22. 		jsr 		$5F22							; Call into the BIOS.
  23.  
  24. 		move.b	#'D', $FF800F						; Clear busy flag.		
  25. 		rts

It sets the fader and master volume up to max, and then uses the rts to return control to the BIOS, which then calls our main code. It also declares our Level 2 interrupt handler - in this case, we aren't doing anything in it, so it is a simple rts. Our Main loop, or SPMain routine, will be responsible for calling the appropriate functions for reading the CD's Table of Contents (which contains pointers to where tracks are located on the disc) and then playing the first track on the disc. Note that the functions above shouldn't really be needed, so they can be omitted if the need be.

  1. SPMain:			
  2. MainLoop:		
  3. 		move.b	#'D', $FF800F						; Clear busy flag.
  4.  
  5. 		cmp.b	#0, $FF8010							; Do we have a pending command?
  6. 		beq.s	MainLoop							; If not, keep checking.
  7.  
  8. 		move.b	#'B', $FF800F						; Set busy flag.
  9.  
  10. 		moveq	#0, d0								; Clear d0.
  11. 		move.b	$FF8010, d0							; Write the function ID to d0.
  12.  
  13. 		cmp.b	#$0, d0								; Read ToC
  14. 		beq.w	Func00
  15. 		cmp.b	#$1, d0								; Stop CD audio
  16. 		beq.w	Func01
  17. 		cmp.b	#$2, d0								; Play track in word arg.
  18. 		beq.w	Func02
  19. 		cmp.b	#$3, d0								; Pause playback.
  20. 		beq.w	Func03
  21. 		cmp.b	#$4, d0								; Continue pause.
  22. 		beq.w	Func04
  23.  
  24. 		bra.w	MainLoop							; Keep looping the main loop.
  25.  
  26. ; ===============================================================================		
  27.  
  28. DriveInit_Table:	
  29. 		dc.w	$01FF
  30. 		even
  31.  
  32. Func00:		; Read drive TOC		
  33. 		move.w    #$0002, d0						; Stop all CD playback.
  34. 		jsr 		$5F22							; Call into the BIOS.
  35.  
  36. 		lea		DriveInit_Table(pc), a0				; Load the table to a0.
  37. 		move.w    #$0010, d0						; Call BIOS function to read the ToC.
  38. 		jsr 		$5F22							; Call into the BIOS.
  39.  
  40. 		move.b	#'D', $FF800F						; Clear busy flag.
  41. 		moveq	#$1F, d0
  42. @loop:
  43. 		nop
  44. 		nop
  45. 		nop
  46. 		nop
  47. 		nop
  48. 		dbf		d0, @loop
  49. 		bra.w	MainLoop							; Jump back to main loop.
  50.  
  51. ; ===============================================================================	
  52. Func01:		; Stop music
  53. 		move.w    #$0002, d0						; Stop all CD playback.
  54. 		jsr 		$5F22							; Call into the BIOS.
  55.  
  56. 		move.b	#'D', $FF800F						; Clear busy flag.
  57. 		moveq	#$1F, d0
  58. @loop:
  59. 		nop
  60. 		nop
  61. 		nop
  62. 		nop
  63. 		nop
  64. 		dbf		d0, @loop
  65. 		bra.w	MainLoop							; Jump back to main loop.
  66.  
  67. ; ===============================================================================
  68.  
  69. TrackPlay_Table:
  70. 		dc.w	$0004
  71. 		even
  72.  
  73. Func02:		
  74. 		move.w	#$0089, d0						; Stop the drive from reading any data.
  75. 		jsr 		$5F22							; Call into the BIOS.
  76. 		move.w    #$0002, d0						; Stop current CD track playback.
  77. 		jsr 		$5F22							; Call into the BIOS.
  78.  
  79. 		lea		TrackPlay_Table(pc), a0				; Load table address to a0.
  80. 		move.w	$FF8012, (a0)						; Write track number from word parameter
  81.  
  82. 		move.w    #$0013, d0						; Set to play track repeatedly.
  83. 		jsr 		$5F22							; Call into the BIOS.
  84.  
  85. 		move.b	#'D', $FF800F						; Clear busy flag.
  86. 		moveq	#$1F, d0
  87. @loop:
  88. 		nop
  89. 		nop
  90. 		nop
  91. 		nop
  92. 		nop
  93. 		dbf		d0, @loop
  94. 		bra.w	MainLoop							; Jump back to main loop.
  95.  
  96. ; ===============================================================================	
  97. Func03:
  98. 		move.w    #$0003, d0						; Pause the drive.
  99. 		jsr 		$5F22							; Call into the BIOS.
  100.  
  101. 		move.b	#'D', $FF800F						; Clear busy flag.
  102. 		moveq	#$1F, d0
  103. @loop:
  104. 		nop
  105. 		nop
  106. 		nop
  107. 		nop
  108. 		nop
  109. 		dbf		d0, @loop
  110. 		bra.w	MainLoop							; Jump back to main loop.
  111.  
  112. ; ===============================================================================	
  113. Func04:
  114. 		move.w    #$0004, d0						; Unpause the drive.
  115. 		jsr 		$5F22							; Call into the BIOS.
  116.  
  117. 		move.b	#'D', $FF800F						; Clear busy flag.
  118. 		moveq	#$1F, d0
  119. @loop:
  120. 		nop
  121. 		nop
  122. 		nop
  123. 		nop
  124. 		nop
  125. 		dbf		d0, @loop
  126. 		bra.w	MainLoop							; Jump back to main loop.
  127. ; ===============================================================================
  128. SPNull:
  129. 		rts
  130. ; ===============================================================================
  131. SPEnd:
  132. 	END

The code above first defines two pointers to words. The first tells the BIOS to read the Table of Contents for all of the tracks, while the second tells the BIOS what track to play from the CD. The first BIOS call makes use of the first word, and reads the Table of Contents from the disc. As explained earlier, it contains the location of the tracks on the disc. The second call makes sure that the CD drive is not in data reading mode, which the Table of Contents call uses to read the ToC. The last call then makes use of the second word and plays the track over and over again, thus looping it. To finish it off, our main code then gets to an infinite loop.

Conclusion

This is just a small example of what the Sega CD is capable of - there are much more BIOS functions that aren't explained here, but are in Sega's official Sega CD documentation, which you should take a look at if you're at all interested in making more interesting things with the Sega CD. The code here doesn't use any sort of handshaking nor does it have facilities for the Main CPU to interact with Sega CD hardware, as this is left as an exercise to the reader to figure out.