! SMD disc controller module, speaking to RWT-style/NEC interface.
! Included before disc independent part.

%externalstring(47) copyright smd %alias "GDMR_(C)_DISC.SMD" = %c
   "Copyright (C) 1987 George D.M. Ross"

%constinteger size = heads * sectors * cylinders

%constinteger read    bit = 16_01
%constinteger write   bit = 16_02
%constinteger intwait bit = 16_04
%constinteger int     bit = 16_08
%constinteger doing   bit = 16_10
%constinteger adding  bit = 16_20

%owninteger disc reads  = 0
%owninteger disc writes = 0
%owninteger disc HW     = 0

%ownrecord(semaphore fm) disc interrupt = 0
%ownrecord(semaphore fm) disc initialised = 0
%ownrecord(semaphore fm) seek complete = 0

%constinteger unit shift = 28
%constinteger block mask = \(16_FFFFFFFF << unit shift)

%owninteger current cylinder = -1


! Board control

%constinteger bc reset      = -16_8000
%constinteger bc int enable =  16_4000
%constinteger bc dma out    =  16_2000
%constinteger bc dma in     =  16_1000
%constinteger bc idle       =        0

@16_71002 %writeonly %short bcr

! Controller commands

%constinteger dc sense int status   = 16_10
%constinteger dc specify            = 16_20
%constinteger dc sense unit status  = 16_30
%constinteger dc detect error       = 16_40
%constinteger dc recalibrate        = 16_50
%constinteger dc seek               = 16_60
!constinteger dc write ID           = 16_70
!constinteger dc verify ID          = 16_80
!constinteger dc read ID            = 16_90
!constinteger dc read diagnostic    = 16_A0
%constinteger dc read data          = 16_B0
!constinteger dc check              = 16_C0
!constinteger dc scan               = 16_D0
%constinteger dc verify data        = 16_E0
%constinteger dc write data         = 16_F0

%constinteger dc clce = 16_08
%constinteger dc hsrq = 16_04
%constinteger dc clb  = 16_02
%constinteger dc rst  = 16_01

@16_71001 %writeonly %byte dcr

! Controller status

%constinteger ds busy                 = 16_80
%constinteger ds command end          = 16_60
%constinteger ds command end abnormal = 16_20
%constinteger ds command end normal   = 16_40
%constinteger ds command end invalid  = 16_60
%constinteger ds sense request        = 16_10
%constinteger ds reset request        = 16_08
%constinteger ds ID error             = 16_04
%constinteger ds not coincident       = 16_02
%constinteger ds data request         = 16_01

%constinteger busy bit = 7
%constinteger CEH  bit = 6
%constinteger CEL  bit = 5
%constinteger SRQ  bit = 4

@16_71001 %readonly %volatile %byte dsr

! Disc data register.  Note that we split this into the read
! half and the write half in order to prevent "optimisations"
! such as clr.b being used.

@16_71000 %readonly %volatile %byte rdr
@16_71000 %writeonly          %byte wdr

! Interrupt status bits

%constinteger is seek error = 16_20
%constinteger seek done bit = 7

! Error status bits

%constinteger es enc = 16_80
%constinteger es ovr = 16_40
%constinteger es der = 16_20
%constinteger es eqc = 16_10
%constinteger es nr  = 16_08
%constinteger es nd  = 16_04
%constinteger es nwr = 16_02
%constinteger es mdm = 16_01

! Resultant status bytes

%ownbyte controller status = 0
%ownbyte interrupt  status = 0

! Disc buffer

%constinteger disc buffer address = 16_70000

! Parameter & result records

%recordformat parameter fm((%byte pcnh, pcnl) %c
                       %or (%byte phn, flag, lcnh, lcnl, lhn, lsn, scnt))

%recordformat result fm((%byte est, phn, flag, lcnh, lcnl, lhn, lsn, scnt) %c
                    %or %byte ust)
                    
%ownrecord(result fm) results = 0
%owninteger expecting = -1


! Error codes

%constinteger transfer size      error = -11
%constinteger off cylinder end   error = -12
%constinteger seek failed        error = -13
%constinteger read failed        error = -14
%constinteger write failed       error = -15
%constinteger verify failed      error = -16

! Interrupt handler

%constinteger interrupts off = 16_0400

%routine initialise interrupt handler
   %ownrecord(interrupt handler fm) handler = 0
   %ownbyte sensing = 0
   %label h, CE, rl, no r, sense done, seek done, SRQ, do SRQ
      ! Semaphores set up in 'initialise disc'
      setup interrupt handler(handler, addr(h))
      add interrupt handler(handler, 4)
      %return

      ! Interrupt handler
h:    ! Get the controller status
      *move.b dsr, D0
      *btst.b #CEH bit, D0
      *bne CE
      *btst.b #CEL bit, D0
      *bne CE
      *btst.b #SRQ bit, D0
      *bne SRQ
      ! Else not us, so....
      return from interrupt

CE:   ! Command ended.
      ! Was it a sense int status?
      *move.b sensing, D1
      *bne sense done
      ! Stop any DMA, save the status.
      *move.w #bc int enable, bcr
      *move.b D0, controller status
      ! Save the results, if any expected.
      *move.l expecting, D1
      *blt no r
      *lea results, A0
rl:   *move.b rdr, (A0)+
      *dbra D1, rl
      *move.l D1, expecting
no r: ! Signal done
      int signal semaphore(disc interrupt)
      ! Sense int status needed?
      !? *move.b controller status, D0
      *btst.b #SRQ bit, D0
      *bne do SRQ
      *move.b #dc clce, dcr
      return from interrupt

sense done:
      ! Sense interrupt has finished.  Read the result
      *move.b rdr, D1
      *move.b D1, interrupt status
      *clr.b sensing
      *move.b #dc clce, dcr
      ! Check if it's a seek completed
      *btst.b #seek done bit, D1
      *bne seek done
      return from interrupt

seek done:
      ! A seek operation has terminated.  Signal the
      ! semaphore, then return.
      int signal semaphore(seek complete)
      return from interrupt

SRQ:  ! Request to sense interrupt status.  Hush it if we're
      ! busy -- it'll come back again.
      *btst.b #busy bit, D0
      *beq do SRQ
      ! We're busy, so hush it.
      *move.b #dc hsrq, dcr
      return from interrupt

do SRQ:
      ! Controller is idle now, so issue the request
      *move.b #dc sense int status, dcr
      *move.b #1, sensing
      return from interrupt

      ! HMD-trap
      *move.l D0, D0
%end


! Command execution

%predicate command(%integer op,
                   %record(parameter fm)%name parameters, %integer n parameters,
                   %integer n results, board control)
   %integer old SR, status, i
   %label pl
      ! Interrupts off for most of this, to lock out the SRQ section
      ! of the interrupt handler....
      ! First wait for the controller to go idle
      %cycle
         old SR = or to SR(interrupts off)
         status = dsr
         %exit %if status & ds busy = 0
         ! Still busy, ints on again and wait a bit
         old SR = set SR(old SR)
         %for i = 1, 1, 1000 %cycle;  %repeat
      %repeat
      ! The controller is idle and interrupts are off.  Set up
      ! the parameters
      %if n parameters > 0 %start
         A0 = addr(parameters)
         D0 = n parameters - 1
   pl:   *move.b (A0)+, wdr
         *dbra D0, pl
      %finish
      ! Note how many results we're expecting
      expecting = n results - 1
      ! Now issue the command, re-enable interrupts, and wait...
      dcr = op
      bcr = board control
      old SR = set SR(old SR)
      semaphore wait(disc interrupt)
      !! printstring("Done ");  phex2(op)
      !! printstring(", status ");  phex2(controller status)
      !! newline
      %if controller status & ds command end = ds command end normal %start
         %true
      %else
         %if controller status & (ds reset request ! %c
                                  ds ID error      ! %c
                                  ds not coincident) # 0 %start
            printstring("SMD controller: ")
   printstring(" reset request")  %if controller status & ds reset request  # 0
   printstring(" ID error")       %if controller status & ds ID error       # 0
   printstring(" not coincident") %if controller status & ds not coincident # 0
            newline
         %finish
         %false
      %finish
%end


! Reset

%routine reset everything
   %integer i
      current cylinder = -1;  ! Force a seek
      dcr = dc rst
      %for i = 1, 1, 10 000 %cycle;  %repeat
      %if command(dc recalibrate, nil, 0, 0, bc int enable) %start
         semaphore wait(seek complete)
      %else 
         printstring("Recalibrate failed");  newline
      %finish
%end


! Initialisation

%routine initialise controller
   %integer i
      bcr = bc reset
      %for i = 1, 1, 10 000 %cycle;  %repeat
      bcr = bc int enable
      %for i = 1, 1, 10 000 %cycle;  %repeat
      wdr = 16_40
      wdr = 16_82
      wdr = 0
      wdr = heads - 1
      wdr = sectors - 1
      wdr = 16
      wdr = 10
      dcr = dc specify
      semaphore wait(disc interrupt)
      !! printstring("Specify: ");  phex2(controller status);  newline
      %for i = 1, 1, 300 000 %cycle;  %repeat;  ! Wait for drives to wake...
%end


! Disc status enquiry

%routine disc enquiry
   %if command(dc sense unit status, nil, 0, 1, bc int enable) %start
      printstring("SMD drive: ")
      write(sectors, 0);  printstring("s ")
      write(heads, 0);  printstring("h ")
      write(cylinders, 0);  print symbol('c')
      printstring(" selected")  %if results_ust & 16_80 # 0
      printstring(" sk-end")    %if results_ust & 16_40 # 0
      printstring(" protected") %if results_ust & 16_20 # 0
      printstring(" (am)")      %if results_ust & 16_10 # 0
      printstring(" ready")     %if results_ust & 16_08 # 0
      printstring(" on-cyl")    %if results_ust & 16_04 # 0
      printstring(" sk-err")    %if results_ust & 16_02 # 0
      printstring(" fault")     %if results_ust & 16_01 # 0
      !! space;  phex2(results_ust)
      newline
   %else
      reset everything %if controller status & ds reset request # 0
   %finish
%end


! Address resolution

%routine split address(%integer block, %integername cylinder,
                       %record(parameter fm)%name p)
   %integer c, h, s
      h = block // sectors;  s = block - h * sectors
      c = h // heads;  h = h - c * heads
      !! printstring("Split address ");  write(block, 0)
      !! printstring(" -> ")
      !! write(c, 0);  printstring("c ")
      !! write(h, 0);  printstring("h ")
      !! write(s, 0);  print symbol('s');  newline
      cylinder = c
      p_phn = h;  p_lhn = h
      p_lcnh = cylinder >> 8
      p_lcnl = cylinder & 255
      p_lsn = s
      p_flag = 0
%end


! Address reporting

%routine show address(%record(result fm)%name r)
   write(r_lcnh << 8 + r_lcnl, 0);  printstring("c ")
   write(r_lhn, 0);  printstring("h ")
   write(r_lsn, 0);  print symbol('s')
%end


! Disc transfers

%routine show error(%string(31) doing)
   printstring("SMD: ");  printstring(doing)
   printstring(": ");  show address(results)
   printstring(" ENC") %if results_est & es enc # 0
   printstring(" OVR") %if results_est & es ovr # 0
   printstring(" DER") %if results_est & es der # 0
   printstring(" EQC") %if results_est & es eqc # 0
   printstring(" NR")  %if results_est & es nr  # 0
   printstring(" ND")  %if results_est & es nd  # 0
   printstring(" NWR") %if results_est & es nwr # 0
   printstring(" MDM") %if results_est & es mdm # 0
   newline
   reset everything %if controller status & ds reset request # 0
%end

%predicate do seek(%integer cylinder)
   %record(parameter fm) seek p
      current cylinder = -1
      seek p_pcnh = cylinder >> 8
      seek p_pcnl = cylinder & 255
      %unless command(dc seek, seek p, 2, 0, bc int enable) %start
         show error("initiate seek")
         %false
      %finish
!L!   lights or B(lights seek)
      semaphore wait(seek complete)
!L!   lights and B(\ lights seek)
      !! printstring("Seek done status: ")
      !! phex2(interrupt status);  newline
      %if interrupt status & is seek error # 0 %start
         show error("seek")
         %false
      %finish
      current cylinder = cylinder
      %true
%end

%predicate do verify(%record(parameter fm)%name p)
   %true %if command(dc verify data, p, 7, 8, bc int enable ! bc dma out)
   show error("verify")
   %false
%end

%integerfn do disc read(%integer block, n, %bytename buffer)
   %record(parameter fm) seek p, transfer p
   %integer cylinder
      %result = transfer size error %unless 0 < n <= 8
      split address(block, cylinder, transfer p)
      ! Check we won't be crossing heads
      %result = off cylinder end error %if transfer p_lsn + n > sectors
      disc reads = disc reads + 1
      %result = seek failed error %unless cylinder = current cylinder %or do seek(cylinder)
      ! Do the transfer
      transfer p_scnt = n
      %unless command(dc read data, transfer p, 7, 8,
                      bc int enable ! bc dma in) %start
         show error("read transfer")
         %result = read failed error
      %finish
      ! Verify, if required
      %if read verify # 0 %start
         %result = verify failed error %unless do verify(transfer p)
      %finish
      ! Copy the resulting data
      D0 = n << 8 - 1
      A0 = addr(buffer)
      A1 = disc buffer address
cl:   *move.w (A1)+, (A0)+
      *dbra D0, cl
      %result = 0
%end

%integerfn do disc write(%integer block, n, %bytename buffer)
   %record(parameter fm) seek p, transfer p
   %integer cylinder
      %result = transfer size error %unless 0 < n <= 8
      split address(block, cylinder, transfer p)
      ! Check we won't be crossing heads
      %result = off cylinder end error %if transfer p_lsn + n > sectors
      disc writes = disc writes + 1
      %result = seek failed error %unless cylinder = current cylinder %or do seek(cylinder)
      ! Copy in the data
      D0 = n << 8 - 1
      A0 = addr(buffer)
      A1 = disc buffer address
cl:   *move.w (A0)+, (A1)+
      *dbra D0, cl
      ! Do the transfer
      transfer p_scnt = n
      %unless command(dc write data, transfer p, 7, 8,
                      bc int enable ! bc dma out) %start
         show error("write transfer")
         %result = write failed error
      %finish
      ! Verify, if required
      %if write verify # 0 %start
         %result = verify failed error %unless do verify(transfer p)
      %finish
      %result = 0
%end

%routine initialise disc
   setup semaphore(disc interrupt)
   setup semaphore(disc initialised)
   setup semaphore(seek complete)
   initialise interrupt handler
   initialise controller
   disc enquiry
%end

%end %of %file
