; i2c_expander.asm
;
; Copyright (C) 2006,2007 Nuno Sucena Almeida
;
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program; if not, write to the Free Software
; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
;

; simple support for I2C communication with
; the MCP23016 I/O Expanders

	include	<proc.inc>
	include <i2c.inc>
	include	<i2c_expander.inc>

	udata
; expander control byte
i2c_ioe_control		res	1
; expander command and data LOW and HIGH (see fig 1-3,page 13) bytes.
i2c_ioe_command		res	1
i2c_ioe_data_1		res	1
i2c_ioe_data_2		res	1
i2c_send_cmd_tries	res	1
i2c_ioe_rotate_1	res	1
;; temp variables that will hold the values of data_1 and data_2
;; during the vertical cycle
i2c_ioe_data_temp	res	1
;; We have a 16x16 matrix, 16 vertical lines and 16 horizontal lines,
;; so if we are doing a vertical scan, we will need 2 bytes * 16 lines
;; for the complete matrix, ie 32 bytes.
i2c_ioe_matrix		res	MATRIX_SIZE


;--------------------------------------------------------------------
; external functions
	extern	i2c_send
	extern	i2c_debug
	extern	delay10
	extern	uart_receive

;--------------------------------------------------------------------
; external symbols
	extern	i2c_status


	code
;--------------------------------------------------------------------
;	set I/O Expander address to 0100 000 0
;	0100 - control code
;	 000 - chip select
;	   0 - R/W
;	see page 4 of MCP23016
i2c_ioe_setup:
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control

	; *************************************************
	; set all ports as output by unsetting the
	; IODIR0 and IODIR1 Expander registers
	banksel	i2c_ioe_data_1
	clrf	i2c_ioe_data_1
	clrf	i2c_ioe_data_2
	; set the command byte value for IODIR0 register
	movlw	IODIR0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command

	; setup I/O expander at address 000
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	call	i2c_ioe_send_cmd

 	; setup I/O expander at address 001
	movlw	B'01000010'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	call	i2c_ioe_send_cmd

	; *************************************************
	; setup I/O expander at address 000
	; to have all outputs set to HIGH
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	movlw	H'FF'
	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	movwf	i2c_ioe_data_2
	movlw	GP0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command
	call	i2c_ioe_send_cmd

	; *************************************************
	; setup I/O expander at address 001
	; to have all outputs set to LOW
	movlw	B'01000010'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	banksel	i2c_ioe_data_1
	clrf	i2c_ioe_data_1
	clrf	i2c_ioe_data_2
	movlw	GP0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command
	call	i2c_ioe_send_cmd

	; *************************************************
	; setup Test Point pins to have the 1MHz
	; clock signal
	movlw	D'1'
	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	movwf	i2c_ioe_data_2
	movlw	IOCON0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command

	; setup at address 000
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	call	i2c_ioe_send_cmd

	; setup at address 001
	movlw	B'01000010'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	call	i2c_ioe_send_cmd

	banksel	i2c_ioe_data_1
	clrf	i2c_ioe_data_1
	clrf	i2c_ioe_data_2

	return

;--------------------------------------------------------------------
; write to a register in the I/O Expander.
;	see table 1-3,page 4 for commands or the include file.
;	see sec 1.9.2, page 13 for details and figures 1-3ff
i2c_ioe_send_cmd:
	;; we only allow this many errors,ie
	;; data without receiving ACK
	movlw	I2C_RETRIES
	banksel	i2c_send_cmd_tries
	movwf	i2c_send_cmd_tries
i2c_ioe_send_cmd_start:
	banksel	i2c_send_cmd_tries
	decfsz	i2c_send_cmd_tries,F
	goto	i2c_ioe_send_cmd_cont
	goto	i2c_ioe_send_cmd_end
i2c_ioe_send_cmd_cont:
	; wait for clock line to be free
 	i2c_SCL_WAIT
	; send Start
	i2c_START
	; unset R/W control bit
	banksel	i2c_ioe_control
	bcf	i2c_ioe_control,0
	; send Address byte:
	movf	i2c_ioe_control,W
	call	i2c_send
	; check ACK and if error, jump to START
 	i2c_CHECK_ACK	i2c_ioe_send_cmd_start
	; send Command Byte
	banksel	i2c_ioe_command
	movf	i2c_ioe_command,W
	call	i2c_send
	; check ACK and if error, jump to START
 	i2c_CHECK_ACK	i2c_ioe_send_cmd_start
	; send Data 1
	banksel	i2c_ioe_data_1
	movf	i2c_ioe_data_1,W
	call	i2c_send
	; check ACK and if error, jump to START
 	i2c_CHECK_ACK	i2c_ioe_send_cmd_start
	; send Data 2
	banksel	i2c_ioe_data_2
	movf	i2c_ioe_data_2,W
	call	i2c_send
	; check ACK and if error, jump to START
 	i2c_CHECK_ACK	i2c_ioe_send_cmd_start
i2c_ioe_send_cmd_end:
	; all done, send STOP
	i2c_STOP
	return

;--------------------------------------------------------------------
; this routine
i2c_ioe_rotate:
	;; expander at address 000
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	banksel	i2c_ioe_data_1
	incf	i2c_ioe_data_1,F
	incf	i2c_ioe_data_2,F
	movlw	GP0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command
	call	i2c_ioe_send_cmd
	return



;--------------------------------------------------------------------
; this routine cycles the value of the expander at address 000 so that
; we have a vertical scanning.
; It starts at 1 then, then we do a left bit rotation
; until the carrier status bit (STATUS,C) is 1 and then we go to the
; higher byte. In between it reads, using indirect addressing, the
; horizontal byte values (expander at address 001) from RAM.
i2c_ioe_cycle_macro	macro data_1,data_2
	local	i2c_ioe_cycle_macro_loop
i2c_ioe_cycle_macro_loop:
	;; save data values to temporary location
	banksel	data_1
	movf	data_1,W
	movwf	i2c_ioe_data_temp

	;; we read the 2 bytes from RAM and send it to the
 	;; expander at address 001
 	movlw	B'01000010'
 	banksel	i2c_ioe_control
 	movwf	i2c_ioe_control
	;; read LOW byte value from RAM
	banksel	i2c_ioe_matrix
	movf	INDF,W
	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	;; read HIGH byte value from RAM
	banksel	i2c_ioe_matrix
	incf	FSR,F
	movf	INDF,W
	banksel	i2c_ioe_data_2
	movwf	i2c_ioe_data_2
	incf	FSR,F
	;; send byte values to expander at 001
 	call	i2c_ioe_send_cmd

	;; get back the saved data values
	banksel	data_1
	movf	i2c_ioe_data_temp,W
	movwf	data_1
	clrf	data_2

 	;; expander at address 000 which will do the vertical cycle
 	movlw	B'01000000'
 	banksel	i2c_ioe_control
 	movwf	i2c_ioe_control
	;; send data values to expander at 000
 	call	i2c_ioe_send_cmd

	;; left rotate and check if we have 'overflow'
	banksel	data_1
 	rlf	data_1,F
	btfss	STATUS,C
	goto	i2c_ioe_cycle_macro_loop
	endm

i2c_ioe_cycle_vertical:
	;; set command:
 	movlw	GP0
 	banksel	i2c_ioe_command
 	movwf	i2c_ioe_command
	;; initialize the indirect addressing register
	movlw	i2c_ioe_matrix
	movwf	FSR

i2c_ioe_cycle_vertical_loop_1:
	;; initialize lower byte at 1 and higher byte at 0.
 	movlw	D'1'
 	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	i2c_ioe_cycle_macro i2c_ioe_data_1,i2c_ioe_data_2

i2c_ioe_cycle_vertical_loop_2:
	;; we are done with the lower first 8 bits, so lets go to the
	;; next higher byte.
	;; initialize higher byte at 1 and higher byte at 0.
 	movlw	D'1'
 	banksel	i2c_ioe_data_2
	movwf	i2c_ioe_data_2
	i2c_ioe_cycle_macro i2c_ioe_data_2,i2c_ioe_data_1
	return

;--------------------------------------------------------------------
; table for the vertical cycle, so that we mimic a shift-register
; i2c_ioe_cycle_table1:
; 	addwf	PCL,F
; 	dt	D'1',D'2',D'4',D'8',D'16',D'32',D'64',D'128'
; 	dt	D'0',D'0',D'0',D'0',D'0' ,D'0' ,D'0' ,D'0'

;--------------------------------------------------------------------
; this routine gets 4 values from the uart, which correspond to the
; expanders output values and then sends them through the i2c bus.
i2c_ioe_set_outputs:
	movlw	GP0
	banksel	i2c_ioe_command
	movwf	i2c_ioe_command
	;; expander at address 000
	movlw	B'01000000'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	;; get first data
	call	uart_receive
	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	;; get second data
	call	uart_receive
	banksel	i2c_ioe_data_2
	movwf	i2c_ioe_data_2
	call	i2c_ioe_send_cmd
	;; expander at address 001
	movlw	B'01000010'
	banksel	i2c_ioe_control
	movwf	i2c_ioe_control
	;; get first data
	call	uart_receive
	banksel	i2c_ioe_data_1
	movwf	i2c_ioe_data_1
	call	uart_receive
	;; get second data
	banksel	i2c_ioe_data_2
	movwf	i2c_ioe_data_2
	call	i2c_ioe_send_cmd
	return

	global	i2c_ioe_setup
	global	i2c_ioe_send_cmd
	global	i2c_ioe_rotate
	global	i2c_ioe_set_outputs
	global  i2c_ioe_cycle_vertical

	global	i2c_ioe_matrix
	end
