;
; i2c.asm: flash memory routines
;          over the i2c serial bus
;
; uses the picmicro MSSP module
;
; developed and tested to work with
; atmel 24c128B 128k (16384 * 8) B serial eeprom
;
; 24c128B memory is organized as 256 pages (FADDRH) of 64 bytes (FADDRL)
;
; Note that the B-version of the chip was used in development
;  prototype. B-version has 5ms write cycle instead of the regular
;  10ms write cycle.
;
; author: Marko Kanala, raato ]a t[ mulletronic.com
;
; LICENSE
; Creative Commons Attribution-NonCommercial-ShareALike 2.5
;
; You are free:
;
; * to copy, distribute, display, and perform the work
; * to make derivative works
;
; Under the following conditions:
;	
; 1. Attribution. You must attribute the work in the manner specified by the 
;    author or licensor.
; 2. Noncommercial. You may not use this work for commercial purposes.
; 3. Share Alike. If you alter, transform, or build upon this work, you may 
;    distribute the resulting work only under a license identical to this one.
;
; * For any reuse or distribution, you must make clear to others the license 
;   terms of this work.
; * Any of these conditions can be waived if you get permission from the 
;   copyright holder.
;
; Your fair use and other rights are in no way affected by the above.
;
; This is a human-readable summary of the Legal Code.
; See full license at http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode
;
; Copyright: Marko Kanala (raato@mulletronic.com).
;

I2C_DEVWRITE	equ		b'10100000'	; 0xa0, address pins to gnd
I2C_DEVREAD		equ		b'10100001'	; 0xa1, address pins to gnd
I2C_FOSC		equ		40000000	; 18f4x2 @ 40mhz
I2C_CLOCK		equ		1000000		; desired clock rate of 1mhz
; baud rate generator value (0x0a)
SSPADD_VALUE	equ		(((I2C_FOSC/I2C_CLOCK)/4) - 1)

;;; macros for I2C routines

; waits for SSPIF interrupt
I2CWAIT		MACRO
	btfss	PIR1, SSPIF
	bra		$-2
	ENDM

; sends a byte from WREG to i2c bus
I2CSEND 	MACRO
	bcf		PIR1, SSPIF
	movwf	SSPBUF
	I2CWAIT		
	; TODO: should probably handle 
	;        a missed ack here
	;btfsc	SSPCON2, ACKSTAT
	;bra	ackfailed
	ENDM

; receives a byte from i2c to WREG
I2CRECEIVE	MACRO
	bcf		PIR1, SSPIF
	; enable receive mode
	bsf		SSPCON2, RCEN
	I2CWAIT
	movf	SSPBUF, W
	ENDM

; sends ack to i2c bus
I2CACK		MACRO
	bcf		PIR1, SSPIF
	; setup for ACK and send
	bcf		SSPCON2, ACKDT
	bsf		SSPCON2, ACKEN
	I2CWAIT
	ENDM

; sends nak to i2c bus
I2CNAK		MACRO
	bcf		PIR1, SSPIF
	; setup for nak and send
	bsf		SSPCON2, ACKDT
	bsf		SSPCON2, ACKEN
	I2CWAIT
	ENDM

; sends i2c start
I2CSTART	MACRO
	bcf		PIR1, SSPIF
	bsf		SSPCON2, SEN
	I2CWAIT
	ENDM

; sends i2c restart
I2CRESTART	MACRO
	bcf		PIR1, SSPIF
	bsf		SSPCON2, RSEN
	I2CWAIT
	ENDM

; sends i2c stop
I2CSTOP	MACRO
	bcf		PIR1, SSPIF
	bsf		SSPCON2, PEN
	I2CWAIT
	ENDM

;
; reads pattern data from i2c flash memory
; note: PATTERN_SIZE_FLASH bytes are read.
;
; address is calculated from nextbank / nextpattern
;  values.
;
; pattern_data is used as destination address.
;
i2c_flash_read:
	lfsr	FSR1, pattern_data
	movlw	16
	mulwf	nextbank			; bank * 16	
	movf	PRODL, W
	addwf	nextpattern, W		; + pattern number
	; note: address is clocked in as xxHHHHHH HHLLLLLL
	; so bank * 16 + pattern automatically produces the FADDRH
	movwf	FADDRH
	clrf	FADDRL
	bcf		T2CON, TMR2ON		; disable key/led timer on i2c 
	movlw	PATTERN_SIZE_FLASH
	call	i2c_flash_read_bytes
	bsf		T2CON, TMR2ON		; enable key/led timer 
	SETEVENT	EVENT_FLASHREADOK
	return

;
; writes a full pattern to i2c flash memory
;
; address is calculated from currentbank / currentpattern
;  values.
;
; pattern_data is used as a source address.
;
; note that this function uses page writes
;  to write a full pattern to the flash memory.
i2c_flash_save:
	; note: FSR1 is postincremented automatically
	lfsr	FSR1, pattern_data
	movlw	16
	mulwf	currentbank				; bank * 16	
	movf	PRODL, W
	addwf	currentpattern, W		; + pattern number
	; note: address is clocked in as xxHHHHHH HHLLLLLL
	; so bank * 16 + pattern automatically produces the FADDRH
	movwf	FADDRH
	clrf	FADDRL
	movlw	64
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	call	i2c_flash_save_page		; first 64 bytes
	movlw	b'01000000'
	addwf	FADDRL, F				; address += 64
	call	i2c_poll				; poll for write cycle
	movlw	64
	call	i2c_flash_save_page		; bytes 64 - 128
	movlw	b'01000000'
	addwf	FADDRL, F				; address += 64
	call	i2c_poll				; poll for write cycle
	movlw	PATTERN_SIZE_FLASH-128	; bytes 128 - 164
	call	i2c_flash_save_page
	call	i2c_poll				; poll for write cycle
	bsf		T2CON, TMR2ON			; enable key/led timer 
	return

;
; reads data from i2c flash memory
; note: 256 bytes are read.
;
; address is calculated from nextbank / nextpattern
;  values.
;
; sysexdata is used as destination address.
;
i2c_flash_read_sysex_256:
	lfsr	FSR1, sysexdata
	movlw	16
	mulwf	nextbank			; bank * 16	
	movf	PRODL, W
	addwf	nextpattern, W		; + pattern number
	; note: address is clocked in as xxHHHHHH HHLLLLLL
	; so bank * 16 + pattern automatically produces the FADDRH
	movwf	FADDRH
	clrf	FADDRL
	bcf		T2CON, TMR2ON		; disable key/led timer on i2c 
	movlw	0					; 256 bytes!
	call	i2c_flash_read_bytes
	bsf		T2CON, TMR2ON		; enable key/led timer 
	return

;
; writes 128 bytes of sysex data to i2c flash memory
;
; dest address is calculated from currentbank / currentpattern
;  values.
;
; sysexdata is used as a source address.
;
; WREG is the offset the data is written with
;  so this function can be used to write the full 256
;  bytes of data in two halves
i2c_flash_save_sysex_128:
	movwf	FADDRL
	; note: FSR1 is postincremented automatically
	lfsr	FSR1, sysexdata
	addwf	FSR1L
	movlw	16
	mulwf	currentbank				; bank * 16	
	movf	PRODL, W
	addwf	currentpattern, W		; + pattern number
	; note: address is clocked in as xxHHHHHH HHLLLLLL
	; so bank * 16 + pattern automatically produces the FADDRH
	movwf	FADDRH
	movlw	64
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	call	i2c_flash_save_page		; first 64 bytes
	call	i2c_poll				; poll for write cycle
	movlw	b'01000000'
	addwf	FADDRL, F				; address += 64
	movlw	64
	call	i2c_flash_save_page		; bytes 64 - 128
	call	i2c_poll				; poll for write cycle
	bsf		T2CON, TMR2ON			; enable key/led timer 
	return

;
; reads track name and length from the flash memory
;
; data is read for currenttrack
i2c_flash_read_trackmeta:
	rlncf	currenttrack, W			; w = 2 * currenttrack
	addwf	WREG					; currenttrack * 4
	; track name at the end of page 4
	addlw	3
	movwf	FADDRH
	movlw	0xf2					; name at offset 0x3f2
	movwf	FADDRL
	lfsr	FSR1, track_name
	movlw	12
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	call	i2c_flash_read_bytes
	; track length at offset 0x3fe
	movlw	0xfe
	movwf	FADDRL
	lfsr	FSR1, currenttracklen
	movlw	2						; len == 2 bytes
	call	i2c_flash_read_bytes
	bsf		T2CON, TMR2ON
	return

;
; reads pattern name for track editor
;
; bank & pattern name is decoded from editedmeasuredata
i2c_flash_read_trackpattern_name:
	; calculate address for pattern name
	movf	editedmeasuredata, W
	DECODE_BANK_FROM_MEASURE
	mullw	16						; bank * 16
	movf	editedmeasuredata, W
	DECODE_PATTERN_FROM_MEASURE
	addwf	PRODL, W
	movwf	FADDRH
	clrf	FADDRL
	lfsr	FSR1, pat_name
	movlw	12
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	call	i2c_flash_read_bytes
	bsf		T2CON, TMR2ON
	return

;
; saves track name to the flash memory
;
; address is calculated from currenttrack
;
i2c_flash_save_trackname:
	rlncf	currenttrack, W			; w = 2 * currenttrack
	addwf	WREG					; currenttrack * 4
	; track name at the end of page 4
	addlw	3
	movwf	FADDRH
	movlw	0xf2					; name at offset 0x3f2
	movwf	FADDRL
	lfsr	FSR1, track_name
	movlw	12
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	call	i2c_flash_save_page
	call	i2c_poll				; poll for write cycle
	bsf		T2CON, TMR2ON
	return

;
; saves current track length to the flash memory
;
; address is calculated from currenttrack
;
i2c_flash_save_tracklength:
	rlncf	currenttrack, W			; w = 2 * currenttrack
	addwf	WREG					; currenttrack * 4
	addlw	3
	movwf	FADDRH
	movlw	0xfe					; len at offset 0x3fe
	movwf	FADDRL
	lfsr	FSR1, currenttracklen
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	; 2 bytes
	movlw	2
	call	i2c_flash_save_page
	call	i2c_poll				; poll for write cycle
	bsf		T2CON, TMR2ON
	return

;
; reads 2 a track measures from the flash memory
; 
; the next measure data is set to "nextmeasuredata"
;
; measure position is read from memory location
;  nextmeasure
;
; data is set to currentmeasuredata AND
;  editedmeasuredata
;
i2c_flash_read_measure:
	lfsr	FSR1, currentmeasuredata
i2c_flash_read_measure_fsr1:
	call	i2c_calc_measure_address
	; FADDRx now set
	bcf		T2CON, TMR2ON			; disable key/led timer on i2c 
	; one byte / measure
	movlw	1
	call	i2c_flash_read_bytes
	bsf		T2CON, TMR2ON			; enable key/led timer
	; when current is read discard edited data
	movf	POSTDEC1, W				; fsr1 - 1
	movff	INDF1, editedmeasuredata
	return

;
; saves a measure to the flash memory
;
; measure position is read from memory location
;  currentmeasure
;
; data is saved from editedmeasuredata
i2c_flash_save_measure:
	; i2c_calc_measure_address uses nextmeasure for calculation
	;  so hoax hoax a bit
	movff	nextmeasure, i2c_temp
	movff	nextmeasure + 1, i2c_temp2
	movff	currentmeasure, nextmeasure
	movff	currentmeasure + 1, nextmeasure + 1
	call	i2c_calc_measure_address
	movff	i2c_temp, nextmeasure
	movff	i2c_temp2, nextmeasure + 1
	lfsr	FSR1, editedmeasuredata		; source
	movlw	1					; count
	bcf		T2CON, TMR2ON		; disable key/led timer on i2c 
	call	i2c_flash_save_page
	call	i2c_poll			; poll for write cycle
	bsf		T2CON, TMR2ON 
	; set current also
	movff	editedmeasuredata, currentmeasuredata
	return	

; calculates a measure address in flash memory
;  according to values in nextmeasure & nextmeasure+1
;  and currenttrack
;
; track data in flash:
;
; offset 0x0a4: first 92 bytes of measure data
; offset 0x1a4: 92 bytes of measure data
; offset 0x2a4: 92 bytes of measure data
; offset 0x3a4: 78 bytes of measure data
; offset 0x3f2: 12 bytes of track name
; offset 0x3fe: 2 bytes of track length
;
; measure boundaries:
;
;  < 92 (0x005c), page 1
; < 184 (0x00b8), page 2
; < 276 (0x0114), page 3
; < 354 (0x0162), page 4
i2c_calc_measure_address:
	rlncf	currenttrack, W			; w = 2 * currenttrack
	addwf	WREG					; currenttrack * 4
	movwf	FADDRH
	clrf	FADDRL
	; calculate FADDRx
	tstfsz	nextmeasure
	bra		i2c_calc_measure_address_34
	; pages 1 or 2
	movlw	183
	cpfsgt	nextmeasure + 1
	bra		i2c_calc_measure_address_12
	; over 183 but less than 256, must be page 3
	incf	FADDRH
	incf	FADDRH
	movlw	184
	subwf	nextmeasure + 1, W
	addlw	0xa4
	movwf	FADDRL	; 0x2a4 + measure - 184
	return
i2c_calc_measure_address_12:
	movlw	91
	cpfsgt	nextmeasure + 1
	bra		i2c_calc_measure_address_1
	; over 91 but less than 184, must be page 2
	incf	FADDRH
	movlw	92
	subwf	nextmeasure + 1, W
	addlw	0xa4
	movwf	FADDRL	; 0x1a4 + measure - 92
	return
i2c_calc_measure_address_1:
	; less than 92, must be page 1
	movlw	0xa4
	addwf	nextmeasure + 1, W
	movwf	FADDRL	; 0x0a4 + measure
	return
; measures >= 0x0100
i2c_calc_measure_address_34:
	movlw	0x13
	cpfsgt	nextmeasure + 1
	bra		i2c_calc_measure_address_3
	; over 276, must be page 4
	movlw	3
	addwf	FADDRH	; += 276
	movlw	0xa4 - (276 - 256)
	addwf	nextmeasure + 1, W
	movwf	FADDRL
	return	
i2c_calc_measure_address_3:
	; over 255 but less than 276, must be page 3
	incf	FADDRH	
	incf	FADDRH	; += 184
	movlw	0xa4 + 256 - 184
	addwf	nextmeasure + 1, W
	movwf	FADDRL
	return







; i2c generic -->

;
; writes bytes to i2c flash memory
;
; FADDRH = high address byte in flash 
; FADDRL = low address byte in flash
; WREG = byte count (less or equal than 64 bytes)
; FSR1 = source address
;
i2c_flash_save_page:
	movwf	i2c_counter
	; generate start condition
	I2CSTART
	; send device address to bus
	movlw	I2C_DEVWRITE
	I2CSEND
	; send flash address to bus
	movf	FADDRH, W
	I2CSEND
	movf	FADDRL, W
	I2CSEND
i2c_flash_save_loop:
	; send byte from FSR1 to bus
	movf	POSTINC1, W
	I2CSEND
	decfsz	i2c_counter
	bra		i2c_flash_save_loop
	I2CSTOP
	; be sure to poll the ACK bit from the device
	;  or wait 5 ms before calling this function again!
	return

;
; reads WREG bytes from FADDR:FADDL and stores
;  the results to POSTINC1.
;
i2c_flash_read_bytes:
	movwf	i2c_counter
	; generate start condition
	I2CSTART
	; send device address to bus
	movlw	I2C_DEVWRITE
	I2CSEND
	; send flash address to bus
	movf	FADDRH, W
	I2CSEND
	movf	FADDRL, W
	I2CSEND
	I2CRESTART
	; send device read address
	movlw	I2C_DEVREAD
	I2CSEND
	bra		i2c_flash_read_loop_skipack
i2c_flash_read_loop:
	; send ack
	I2CACK
i2c_flash_read_loop_skipack:
	; receive and store byte
	I2CRECEIVE
	movwf	POSTINC1
	decfsz	i2c_counter
	bra		i2c_flash_read_loop
	; send nak after
	I2CNAK
	I2CSTOP
	return

; Polls the flash device for an ACK bit
;  which indicates the internal write cycle
;  has finished.
i2c_poll:
	I2CRESTART
	movlw	I2C_DEVWRITE
	I2CSEND
	btfsc	SSPCON2, ACKSTAT		; is ACK low ?
	bra		i2c_poll
	; ACK was low, write cycle has finished
	I2CSTOP
	return
;
; initializes the i2c bus
;
i2c_init:
	; baud rate value
	movlw	SSPADD_VALUE
	movwf	SSPADD
	; slew rate control disabled for 1mhz mode
	bsf		SSPSTAT, SMP
	; select i2c input levels
	bcf		SSPSTAT, CKE
	; make SDA and SCL as inputs
	movlw	b'00011000'
	iorwf	TRISC, F
	; serial port enabled, master mode
	movlw	b'00101000'
	movwf	SSPCON1
	clrf	SSPCON2
	return


External Labels :

i2c_flash_read_bytes
i2c_flash_save_page
i2c_poll
i2c_calc_measure_address