# Library for interfacing with an MCP23008 chip, as used on the jeelabs
# expander and output plugs

namespace eval jeeio {
    package require piio
    namespace path ::twowire

    variable slaveaddr 0x20
    variable regs {
	iodir 0 ipol 1 gpinten 2 defval 3 
	intcon 4 iocon 5 gppu 6 intf 7
	intcap 8 gpio 9 olat 10
    }

    namespace ensemble create -subcommands {
	expanderplug outputplug setoutput setinput readpin writepin
	writeddr writegpio readgpio
    }

    proc readreg {fd reg} {
	variable regs
	return [readregbyte $fd [dict get $regs $reg]]
    }

    proc writereg {fd reg val} {
	variable regs
	writeregbyte $fd [dict get $regs $reg] $val
    }

    proc expanderplug {bus {num 0}} {
	variable slaveaddr
	return [twowire $bus [expr {$slaveaddr + $num}]]
    }

    proc outputplug {bus {num 0}} {
	variable slaveaddr
	set fd [twowire $bus [expr {$slaveaddr + 6 + $num}]]
	# Configure all pins as outputs
	writeddr $fd 0
	return $fd
    }

    # Configure the function of all 8 pins
    proc writeddr {fd val} {
	writereg $fd iodir $val
    }

    # Set all outputs at once
    proc writegpio {fd val} {
	writereg $fd olat $val
    }

    # Read the state of all pins
    proc readgpio {fd} {
	return [readreg $fd gpio]
    }

    # Configure a single pin as output
    proc setoutput {fd pin} {
	set ddr [readreg $fd iodir]
	writereg $fd iodir [expr {$ddr & ~(1 << $pin)}]
    }

    # Configure a single pin as input
    proc setinput {fd pin} {
	set ddr [readreg $fd iodir]
	writereg $fd iodir [expr {$ddr | (1 << $pin)}]
    }

    # Read the state of a single pin
    proc readpin {fd pin} {
	return [expr {([readreg $fd gpio] >> $pin) & 1}]
    }

    # Set the state of a single pin
    proc writepin {fd pin state} {
	set olat [readreg $fd olat]
	set new [expr {$state ? $olat | (1 << $pin) : $olat & ~(1 << $pin)}]
	writereg $fd gpio $new
    }
}
