;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;   PSX-Pad for KolibriOS
;   Copyright (C) Jeffrey Amelynck 2008. All rights reserved.
;
;   hidnplayr@kolibrios.org
;
;   v0.1
;   date: 4/09/2008
;   type: private beta
;   functions implemented: Read raw data from Digital controller and Analog controller with red led.
;
;   v0.2:
;   date: 5/09/2008
;   type: public beta
;   functions implemented: Same as above plus converting keycodes from keypad do keyboard scancodes.
;                        : To use this function you need a kernel wich can input scancodes using function 18,23.
;                        ; I also did some cleanup and speedup
;
;
;   v0.2.1
;   by O. Bogomaz aka Albom, albom85@yandex.ru
;   using of standart kernel function 72.1
;
;
;   TODO: - Multiple controllers
;         - Analog controller(s)
;
;
;   More info about PSX/PS2 gamepad protocol:
;   http://curiousinventor.com/guides/ps2
;   http://www.geocities.com/digitan000/Hardware/22/e22_page.html
;
;   How to connect your PSX pad to the PC:
;   http://www.emulatronia.com/reportajes/directpad/psxeng/print.htm
;
;
;   PSX-Pad for KolibriOS is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY.
;   No author or distributor accepts responsibility to anyone for the
;   consequences of using it or for whether it serves any particular purpose or
;   works at all, unless he says so in writing. Refer to the GNU General Public
;   License (the "GPL") for full details.
;
;   Everyone is granted permission to copy, modify and redistribute KolibriOS,
;   but only under the conditions described in the GPL. A copy of this license
;   is supposed to have been given to you along with KolibriOS so you can know
;   your rights and responsibilities. It should be in a file named COPYING.
;   Among other things, the copyright notice and this notice must be preserved
;   on all copies.
;

use32
   
		org    0x0
   
		db     'MENUET01'	       ; 8 byte id
		dd     0x01		           ; header version
		dd     START		       ; start of code
		dd     I_END		       ; size of image
		dd     0x100000 	       ; memory for app
		dd     0x100000 	       ; esp
		dd     0x0 , 0x0	       ; I_Param , I_Icon

; Bits on Data Port (outputs for pc)
command 	equ 0
attention	equ 1
clock		equ 2
vcc		equ (1 shl 3 + 1 shl 4 + 1 shl 5 + 1 shl 6 + 1 shl 7)

; Bits on Status Port (inputs for PC)
data		equ 6
ack		equ 5

__DEBUG__ equ 1
__DEBUG_LEVEL__ equ 2

include '../../macros.inc'
;include 'fdo.inc'

START:
  mov	eax, 40       ; Disable notification of all events
  xor	ebx, ebx
  int	0x40

;  DEBUGF 2,"\nPSX-Pad for KolibriOS v0.2\n\n"

  mov	eax, 46       ; Ask the kernel if wse may use the LPT port
  mov	ebx, 0
  movzx ecx, [BASE]
  movzx edx, [CONTROL]
  int	0x40
  test	eax, eax
  jz	@f

;  DEBUGF 2,"Could not reserve port!\n"
  jmp	exit
@@:

  mov	dx, [CONTROL] ; disable bi-directional data port
  in	al, dx
  and	al, 0xdf
  out	dx, al

  mov	eax, 18       ; read CPU-speed, we'll need it for 100us delay
  mov	ebx, 5
  int	0x40	      ; now we've got the cpuspeed in hz, we need it in Mhz
  xor	edx, edx
  mov	ecx, 1000000
  div	ecx
  mov	[CPUSPEED], eax
;  DEBUGF 2,"CPUspeed: %u\n",eax

;  DEBUGF 1,"Raising attention line\n"
  call	raise_att

;  DEBUGF 1,"Raising Clock\n"
  call	raise_clk

;  DEBUGF 1,"Powering Up controller\n"
  call	raise_vcc

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; All things are ready to go, enter mainloop!
;
; This loop constantly poll's the PSX-pad for data
;

mainloop:

  mov	eax, 5	    ; Lets start by giving the other applications some cpu time, we'll take ours later.
  mov	ebx, 5
  int	0x40

;  DEBUGF 1,"Lowering attention line\n"
  call	lower_att   ; We've got the attention from the PSX-Pad now :) (yes, it's active low..)

;  DEBUGF 1,"Sending Startup byte.. "
  mov	ah, 0x01    ; Startup code
  call	tx_rx
  call	wait_for_ack
;  DEBUGF 1,"Rx: %x\n",bl

;  DEBUGF 1,"Request for data.. "
  mov	ah, 0x42    ; Request for data
  call	tx_rx
  call	wait_for_ack
;  DEBUGF 1,"Rx: %x\n",bl

  cmp	bl, 0x41
  je	digital_controller

  cmp	bl, 0x73
  je	analog_red_controller

;  cmp   ah, 0x23
;  je    negcon_controller

;  cmp   ah, 0x53
;  je    analog_green_controller

;  cmp   ah, 0x12
;  je    psx_mouse


;  DEBUGF 2,"Unsupported controller/mode:%x !\n",bl
  jmp	exit




digital_controller:
  call	command_idle
  call	wait_for_ack
  ; Right now, we receive 0x5a from the controller, wich means: sending data!
;  DEBUGF 1,"Receiving data.. "

  call	command_idle
  call	wait_for_ack
  mov	byte [digital+1], bl

  call	command_idle
  mov	byte [digital+0], bl

;  DEBUGF 1,"Digital data: %x\n",[digital]:4

  mov	ax, word [digital_]
  xor	ax, word [digital]
  mov	cx, word [digital]

  bt	ax, 6	  ; X
  jnc	@f
  pusha
  and	cx, 1 shl 6
  shl	cx, 1
  add	cl, 29
  call	sendkey
  popa
@@:

  bt	ax, 5	  ; O
  jnc	@f
  pusha
  mov	cx, word [digital]
  and	cx, 1 shl 5
  shl	cx, 2
  add	cl, 56
  call	sendkey
  popa
@@:

  bt	ax, 11	  ; Start
  jnc	@f
  pusha
  mov	cx, word [digital]
  and	cx, 1 shl 11
  shr	cx, 4
  add	cl, 28
  call	sendkey
  popa
@@:

  bt	ax, 8	 ; Select
  jnc	@f
  pusha
  mov	cx, word [digital]
  and	cx, 1 shl 8
  shr	cx, 1
  add	cl, 14
  call	sendkey
  popa
@@:

  bt	ax, 12	  ; up
  jnc	@f
  pusha
  mov	cl, 224
  call	sendkey
  mov	cx, word [digital]
  and	cx, 1 shl 12
  shr	cx, 5
  add	cl, 72
  call	sendkey
  popa
@@:

  bt	ax, 13	  ; right
  jnc	@f
  pusha
  mov	cl, 224
  call	sendkey
  mov	cx, word [digital]
  and	cx, 1 shl 13
  shr	cx, 6
  add	cl, 77
  call	sendkey
  popa
@@:

  bt	ax, 14	  ; down
  jnc	@f
  pusha
  mov	cl, 224
  call	sendkey
  mov	cx, word [digital]
  and	cx, 1 shl 14
  shr	cx, 7
  add	cl, 80
  call	sendkey
  popa
@@:

  bt	ax, 15	  ; left
  jnc	@f
  pusha
  mov	cl, 224   ; extended key
  call	sendkey
  mov	cx, word [digital]
  and	cx, 1 shl 15
  shr	cx, 8
  add	cl, 75	  ; left
  call	sendkey
  popa
@@:

  mov	ax, word [digital]
  mov	word [digital_],ax

  call	raise_att
  jmp	mainloop


analog_red_controller:
  call	command_idle
  call	wait_for_ack
  ; Right now, we receive 0x5a from the controller, wich means: sending data!
;  DEBUGF 1,"Receiving data.. "

  call	command_idle
  call	wait_for_ack
  mov	byte [analog_red+5], bl

  call	command_idle
  call	wait_for_ack
  mov	byte [analog_red+4], bl

  call	command_idle
  call	wait_for_ack
  mov	byte [analog_red+3], bl

  call	command_idle
  call	wait_for_ack
  mov	byte [analog_red+2], bl

  call	command_idle
  call	wait_for_ack
  mov	byte [analog_red+1], bl

  call	command_idle
  mov	byte [analog_red+0], bl


;  DEBUGF 2,"Analog data: %x%x\n",[analog_red]:8,[analog_red+4]:4
  call	raise_att
  jmp	mainloop




exit:
  mov	eax, -1
  int	0x40



sendkey:	 ; This function inserts Keyboard Scan-codes into the kernel's queue
		 ; Scancode is in cl
;  mov	eax, 18
;  mov	ebx, 23
;  int	0x40

  pushad

  mov   eax, 72  ; <-- standart function (by Albom)
  mov   ebx, 1
  mov   edx, ecx
  and   edx, 0xff
  mov   ecx, 2
  int   0x40
 
  popad
  ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;  Low-level code starts here
;


raise_att:
    mov  al, [PORT_DATA]
    bts  ax, attention
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret

lower_att:
    mov  al, [PORT_DATA]
    btr  ax, attention
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret




raise_clk:
    mov  al, [PORT_DATA]
    bts  ax, clock
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret

lower_clk:
    mov  al, [PORT_DATA]
    btr  ax, clock
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret




raise_vcc:
    mov  al, [PORT_DATA]
    or	 al, vcc
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret

lower_vcc:
    mov  al, [PORT_DATA]
    and  al, 0xff - vcc
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    ret




wait_for_ack:
    mov  dx, [STATUS]
    mov  ecx, 10000
.loop:
    in	 al, dx
    bt	 ax, ack
    jnc  .ack
    loop .loop

;    DEBUGF 2,"ACK timeout!\n"

;    pop  eax      ; balance the stack, we're not doing a ret like we should..
;    jmp  mainloop

.ack:
;    DEBUGF 1,"ACK !\n"

    ret



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This code comes from Serge's audio driver.
; If you know a better way to do 100 us wait, please tell me.
; This RDTSC stuff is know to have a bug in the newer AMD processors.
delay:

    push ecx
    push edx
    push ebx
    push eax

    mov  eax, 100
    mov  ecx, [CPUSPEED]
    mul  ecx
    mov  ebx, eax	;low
    mov  ecx, edx	;high
    rdtsc
    add  ebx, eax
    adc  ecx,edx
  @@:
    rdtsc
    sub  eax, ebx
    sbb  edx, ecx
    js	 @B

    pop  eax
    pop  ebx
    pop  edx
    pop  ecx

    ret







tx_rx:
    ; ah = byte to send
    ; bl = received byte
    mov  ecx, 8
    mov  bl, 0

tx_rx_loop:
    call delay
    call lower_clk

    mov  dl, ah
    and  dl, 1
;    DEBUGF 1,"OUTb:%u ", dl

    mov  al, [PORT_DATA]
    and  al, 0xfe
    or	 al, dl
    mov  [PORT_DATA], al

    mov  dx, [BASE]
    out  dx, al

    shr  ah, 1

    call delay
    call raise_clk

    mov  dx, [STATUS]
    in	 al, dx

    shl  al, 1
    and  al, 1 shl 7
;    DEBUGF 1,"INb:%x\n", al
    shr  bl, 1
    or	 bl, al

    loop tx_rx_loop

ret



command_idle:
    ; bl = received byte
    mov  bl, 0
    mov  ecx, 8

command_idle_loop:
    call delay
    call lower_clk

    call delay
    call raise_clk

    mov  dx, [STATUS]
    in	 al, dx

    shl  al, 1
    and  al, 1 shl 7

    shr  bl, 1
    or	 bl, al

    loop  command_idle_loop

ret
   
   
; DATA AREA

;include_debug_strings ; ALWAYS present in data section

; Addresses to PORT
BASE	    dw 0x378
STATUS	    dw 0x379
CONTROL     dw 0x37a
; Buffer for data port
PORT_DATA   db 0

; hmm, what would this be...
CPUSPEED    dd ?

; buffers for data from controller
digital     rb 2
digital_    rb 2   ; this buffer is used to find keychanges (if somebody just pressed/released a key)
analog_red  rb 6
analog_red_ rb 6

I_END: