;++
;
; USB C power-delivery manager.
;
; By John Wilson.
;
; Copyright (C) 2022 by Digby's Bitpile, Inc.
;
; 12/28/2022	JMBW	Created.
; 12/28/2022	JMBW	Works.
;
; The logic is very simple.  The CC1/CC2 pins on the USB connector each have a
; 22 K resistor (Rp) at our end, pulling up to +5 V.  The other end of one of
; them (which would tell us which pair of data pins is in use with this cable,
; if we cared) has a 5.1 K pull-down resistor (Rd).  The one that's not in use
; may have a 1 K pull-down (Ra) inside the cable.
;
; So, basically, the possible voltages on CC1 and CC2 are:
; >1.65 V	floating high (no connection -- power = off)
; 0.45-1.60 V	Rp/Rd (power = on, and this is the side that's in use)
; 0-0.40 V	Rp/Ra (power = off, since it's just the cable)
;
; Normally exactly one of the CC1/CC2 pins will have Rp/Rd, and that tells us
; to turn on the power.  If neither does, no power, and if both do, well that's
; enough reason for us (I would think things that plug in direction w/no cable
; would do this -- they deserve power too!).
;
; The TPS2001D switch turns on when we drive ENA high, and it does its own 2 A
; current-limiting, and it drives the /FLT pin to tell us if that's not working
; out, with 9 ms of de-glitching.  So if we see that, we should turn off again,
; and then re-assess.
;
;-
#include <xc.inc>		;knows CPU type from Makefile-defined symbol
;
config OSC=IntRC, WDT=OFF, MCLRE=ON, CP=OFF, IOSCFS=OFF
;
; Port usage:
;
; GP0/AN0 = CC1 \ these are also the ICSP pins so ... good luck debugging!
; GP1/AN1 = CC2 /
; GP4 = /FLT (over-current fault input from TPS2001D switch)
; GP5 = ENA (output enable pin to TPS2001D switch)
;
; The PIC12F510 has two pages in the register file.  00h-0Fh each contain the
; same SFRs, and 10h-1Fh have two sets of GPRs for us to use.
;
tpsflt	equ	10h		;/FLT = over-current fault (input)
bitflt	equ	4		;(as a bit number)
tpsena	equ	20h		;ENA = enable power (output)
bitena	equ	5		;(as a bit number)
;
; 8-bit ADC thresholds:
;
fltng	equ	165*255/500	;.GE. FLTNG => pin is floating high
rpra	equ	40*255/500	;.LE. RPRA  => pin is pulled down (Rp/Ra)
				;in between => device is present (Rp/Rd)
;
psect	udata_bank0		;regular variables (in bank 0 please)
scr:	ds	1		;scratch location (few immediate opcodes)
pwron:	ds	1		;NZ => power on
	global	scr		;for debugger
;
psect	code,abs
start:	; init CPU
	; set up I/O pins
	clrf	GPIO		;drive ENA low when enabled
	movlw	tpsflt+03h	;/FLT, AN1, and AN0 = inputs, ENA = output
	tris	GPIO
	; set up ADC
	movlw	0F1h		;ANS1:0 = 11 (AN0-3 are all analog inputs)
				;ADCS1:0 = 11 (clock = INTOSC/4)
				;CHS1:0 = 00 (selected channel = AN0)
				;GO(/DONE) = 0 (don't go)
	movwf	ADCON0		;ADON = enabled
	; set up timer 0
	clrw			;T0CS=0 => source=Fosc/4
	option			;PSA=0 => use prescaler
				;PS2:0=000 => prescaler=/256
;
; Main loop
;
idle:	; wait for something to get plugged in
	call	onoff		;valid voltage on at least one CCn pin?
	btfsc	ZERO
	 goto	idle		;spin if not
	; turn on power
	bsf	GPIO,bitena	;turn on power if so
stayon:	call	delay		;delay
	; see if we're overloaded
	btfss	GPIO,bitflt	;fault?  (active low)
	 goto	bad		;yes
	; see if they've unplugged
	call	onoff		;valid voltage on at least one CCn pin?
	btfss	ZERO
	 goto	stayon
	bcf	GPIO,bitena	;turn power off
	goto	idle		;go wait for more plugging
;
bad:	; over-current fault
	; kill the device until they unplug it
	bcf	GPIO,bitena	;turn power off
bad1:	call	onoff		;valid voltage on at least one CCn pin?
	btfsc	ZERO
	 goto	bad1		;wait until they unplug
	goto	idle
;+
;
; Decide whether we want the power on.
;
; Z	0 => yes, 1 => no
;
;-
onoff:	; read voltages on both CC pins
	movlw	0F1h		;ANS1:0 = 11 (AN0-3 are all analog inputs)
				;ADCS1:0 = 11 (clock = INTOSC/4)
				;CHS1:0 = 00 (selected channel = AN0)
				;GO(/DONE) = (don't go)
				;ADON = enabled
	call	adc		;read CC1
	movwf	pwron		;save result
	movlw	0F5h		;ANS1:0 = 11 (AN0-3 are all analog inputs)
				;ADCS1:0 = 11 (clock = INTOSC/4)
				;CHS1:0 = 01 (selected channel = AN1)
				;GO(/DONE) = 0 (don't go)
				;ADON = enabled
	call	adc		;read CC2
	iorwf	pwron		;combine with CC1's result
	retlw	0
;+
;
; Read ADC.
;
; w	ADCON0 value to start conversion for the channel we want
;
; Returns:
; w	NZ if we vote for turning power on
;	0 if not (but other CCn pin still gets a vote)
;
;-
adc:	movwf	ADCON0		;set up next conversion
	bsf	ADCON0,1	;start it
adc1:	btfsc	ADCON0,1	;has GO cleared (i.e. /DONE asserted)?
	 goto	adc1		;spin until it does
	movf	ADRES,W		;fetch result
	movwf	scr		;save in scratch location
	movlw	fltng		;.GE. FLTNG?
	subwf	scr		;C=0 if so
	btfsc	CARRY
	 retlw	0		;no power
	movlw	rpra-1-fltng	;happy range?
	subwf	scr		;CF=0 if so
	btfsc	CARRY
	 retlw	1		;power on
	retlw	0		;no power
;+
;
; Delay.
;
; TMR0 has been set with clk = Fosc/4 (and Fosc is 4 MHz), and prescale = /256.
; So it increments every 256 usec.
;
; So, we wait for it to go all the way around, or 65.536 msec, plus a little
; overhead.
;
;-
delay:	clrf	TMR0		;clear it
delay1:	movf	TMR0,w		;fetch it
	btfsc	ZERO		;has it incremented away from 0?
	 goto	delay1		;spin until it does
delay2:	movf	TMR0,w		;fetch it
	btfss	ZERO		;has it incremented *back* to 0?
	 goto	delay2		;spin until it does
	retlw	0
;
	end	start
