diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2e86a9b..46bbf79 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -38,4 +38,5 @@ Matias Insaurralde Seán C McCord Stephan Sperber Thorsten von Eicken +Weston Schmidt diff --git a/tca95xx/example_test.go b/tca95xx/example_test.go new file mode 100644 index 0000000..e98fdd1 --- /dev/null +++ b/tca95xx/example_test.go @@ -0,0 +1,50 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package tca95xx_test + +import ( + "fmt" + "log" + + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/i2c/i2creg" + "periph.io/x/devices/v3/tca95xx" + "periph.io/x/host/v3" +) + +func Example() { + // Make sure periph is initialized. + if _, err := host.Init(); err != nil { + log.Fatal(err) + } + + // Open default I²C bus. + bus, err := i2creg.Open("") + if err != nil { + log.Fatalf("failed to open I²C: %v", err) + } + defer bus.Close() + + // Create a new I2C IO extender + extender, err := tca95xx.New(bus, tca95xx.TCA9534, 0x20) + if err != nil { + log.Fatalln(err) + } + + for _, port := range extender.Pins { + for _, pin := range port { + err = pin.In(gpio.Float, gpio.NoEdge) + if err != nil { + log.Fatalln(err) + } + level := pin.Read() + fmt.Printf("%s\t%s\n", pin.Name(), level.String()) + } + } + + if err != nil { + log.Fatalln(err) + } +} diff --git a/tca95xx/pins.go b/tca95xx/pins.go new file mode 100644 index 0000000..7722807 --- /dev/null +++ b/tca95xx/pins.go @@ -0,0 +1,205 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// This file is largely a copy of mcp23xxx/pins.go, but with a reduced feature +// set for controlling the chips. + +package tca95xx + +import ( + "errors" + "fmt" + "strconv" + "time" + + "periph.io/x/conn/v3" + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/physic" + "periph.io/x/conn/v3/pin" +) + +// Pin extends gpio.PinIO interface with features supported by tca95xx devices. +type Pin interface { + gpio.PinIO + // SetPolarityInverted if set to true, GPIO register bit reflects the same logic state of the input pin. + SetPolarityInverted(p bool) error + // IsPolarityInverted returns true if the value of the input pin reflects inverted logic state. + IsPolarityInverted() (bool, error) +} + +type port struct { + name string + + // GPIO basic registers + input registerCache // input at the pin + output registerCache // output control, or flipflop state if read + iodir registerCache // direction + ipol registerCache // polarity setting +} + +func (p *port) pins(count int) []Pin { + result := make([]Pin, count) + var i uint8 + for i = 0; i < uint8(count); i++ { + result[i] = &portpin{ + port: p, + pinbit: i, + } + } + return result +} + +// Tx takes bytes to either read or write. Only half duplex is supported so it +// is an error to pass 2 buffers at once. The bytes are written or read from +// the same connection sequentially. +func (p *port) Tx(w, r []byte) (err error) { + send := len(w) + get := len(r) + switch { + case send > 0 && get > 0: + return fmt.Errorf("tca95xx: only conn.Half duplex is supported") + case send > 0: + for i := 0; i < send; i++ { + err = p.output.writeValue(w[i], false) + if err != nil { + return err + } + } + case get > 0: + var in uint8 + for i := 0; i < get; i++ { + in, err = p.input.readValue(false) + if err != nil { + return err + } + r[i] = in + } + } + + return nil +} + +// Duplex returns that this is a half duplex connection. +func (p *port) Duplex() conn.Duplex { + return conn.Half +} + +// String provides the name of this connection. +func (p *port) String() string { + return p.name +} + +type portpin struct { + port *port + pinbit uint8 +} + +func (p *portpin) String() string { + return p.Name() +} + +func (p *portpin) Halt() error { + // To halt all drive, set to high-impedance input + return p.In(gpio.Float, gpio.NoEdge) +} + +func (p *portpin) Name() string { + return p.port.name + "_" + strconv.Itoa(int(p.pinbit)) +} + +func (p *portpin) Number() int { + return int(p.pinbit) +} + +func (p *portpin) Function() string { + return string(p.Func()) +} + +func (p *portpin) In(pull gpio.Pull, edge gpio.Edge) error { + // Set pullup + switch pull { + case gpio.PullDown: + // pull down is not supported by any device + return errors.New("tca95xx: PullDown is not supported") + case gpio.PullUp: + return errors.New("tca95xx: PullUp is not supported") + case gpio.Float, gpio.PullNoChange: + // Do nothing, supported. + } + + // Interrupts are not via I2C bus, so supporting them is less than + // ideal. + if edge != gpio.NoEdge { + return errors.New("tca95xx: edge detection not supported") + } + + // Set pin to input + return p.port.iodir.getAndSetBit(p.pinbit, true, true) +} + +func (p *portpin) Read() gpio.Level { + v, _ := p.port.input.getBit(p.pinbit, false) + if v { + return gpio.High + } + return gpio.Low +} + +func (p *portpin) WaitForEdge(timeout time.Duration) bool { + return false +} + +func (p *portpin) Pull() gpio.Pull { + return gpio.Float +} + +func (p *portpin) DefaultPull() gpio.Pull { + return gpio.Float +} + +func (p *portpin) Out(l gpio.Level) error { + err := p.port.iodir.getAndSetBit(p.pinbit, false, true) + if err != nil { + return err + } + return p.port.output.getAndSetBit(p.pinbit, l == gpio.High, true) +} + +func (p *portpin) PWM(duty gpio.Duty, f physic.Frequency) error { + return errors.New("tca95xx: PWM is not supported") +} + +func (p *portpin) Func() pin.Func { + v, _ := p.port.iodir.getBit(p.pinbit, true) + if v { + return gpio.IN + } + return gpio.OUT +} + +func (p *portpin) SupportedFuncs() []pin.Func { + return supportedFuncs[:] +} + +func (p *portpin) SetFunc(f pin.Func) error { + var v bool + switch f { + case gpio.IN: + v = true + case gpio.OUT: + v = false + default: + return errors.New("tca95xx: Function not supported: " + string(f)) + } + return p.port.iodir.getAndSetBit(p.pinbit, v, true) +} + +func (p *portpin) SetPolarityInverted(pol bool) error { + return p.port.ipol.getAndSetBit(p.pinbit, pol, true) +} +func (p *portpin) IsPolarityInverted() (bool, error) { + return p.port.ipol.getBit(p.pinbit, true) +} + +var supportedFuncs = [...]pin.Func{gpio.IN, gpio.OUT} diff --git a/tca95xx/registers.go b/tca95xx/registers.go new file mode 100644 index 0000000..cbd0352 --- /dev/null +++ b/tca95xx/registers.go @@ -0,0 +1,78 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// This file is largely a copy of mcp23xxx/registers.go without the spi interface. + +package tca95xx + +import "periph.io/x/conn/v3/i2c" + +type registerCache struct { + i2c *i2c.Dev + address uint8 + got bool + cache uint8 +} + +func newRegister(i2c *i2c.Dev, address uint8) registerCache { + return registerCache{ + i2c: i2c, + address: address, + got: false, + } +} + +func (r *registerCache) readRegister(address uint8) (uint8, error) { + rx := make([]byte, 1) + err := r.i2c.Tx([]byte{address}, rx) + return rx[0], err +} + +func (r *registerCache) writeRegister(address uint8, value uint8) error { + return r.i2c.Tx([]byte{address, value}, nil) +} + +func (r *registerCache) readValue(cached bool) (uint8, error) { + if cached && r.got { + return r.cache, nil + } + v, err := r.readRegister(r.address) + if err == nil { + r.got = true + r.cache = v + } + return v, err +} + +func (r *registerCache) writeValue(value uint8, cached bool) error { + if cached && r.got && value == r.cache { + return nil + } + + err := r.writeRegister(r.address, value) + if err != nil { + return err + } + r.got = true + r.cache = value + return nil +} + +func (r *registerCache) getAndSetBit(bit uint8, value bool, cached bool) error { + v, err := r.readValue(cached) + if err != nil { + return err + } + if value { + v |= 1 << bit + } else { + v &= ^(1 << bit) + } + return r.writeValue(v, cached) +} + +func (r *registerCache) getBit(bit uint8, cached bool) (bool, error) { + v, err := r.readValue(cached) + return (v & (1 << bit)) != 0, err +} diff --git a/tca95xx/tca9535_test.go b/tca95xx/tca9535_test.go new file mode 100644 index 0000000..d70a502 --- /dev/null +++ b/tca95xx/tca9535_test.go @@ -0,0 +1,291 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package tca95xx + +import ( + "reflect" + "testing" + "time" + + "periph.io/x/conn/v3" + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/gpio/gpioreg" + "periph.io/x/conn/v3/i2c/i2ctest" + "periph.io/x/conn/v3/physic" +) + +func TestTCA9535_out(t *testing.T) { + const address uint16 = 0x20 + scenario := &i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: address, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: address, W: []byte{0x07}, R: []byte{0xFF}}, + // iodir is set to output + {Addr: address, W: []byte{0x06, 0xFE}, R: nil}, + // output is read + {Addr: address, W: []byte{0x02}, R: []byte{0x00}}, + + // writing back unchanged value is omitted + // writing high output + {Addr: address, W: []byte{0x02, 0x01}, R: nil}, + // writing low output + {Addr: address, W: []byte{0x02, 0x00}, R: nil}, + }, + } + + dev, err := New(scenario, TCA9535, address) + if err != nil { + t.Fatal(err) + } + if dev == nil { + t.Fatal("dev is nil") + } + defer dev.Close() + + p0 := gpioreg.ByName("TCA9535_20_P0_0") + if nil == p0 { + t.Fatal("p0 is nil") + } + p0.Out(gpio.Low) + p0.Out(gpio.High) + p0.Out(gpio.Low) +} + +func TestTCA9535_in(t *testing.T) { + const address uint16 = 0x20 + scenario := &i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: address, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: address, W: []byte{0x07}, R: []byte{0xFF}}, + // not written, since it didn't change + // input is read + {Addr: address, W: []byte{0x00}, R: []byte{0x01}}, + }, + } + + dev, err := New(scenario, TCA9535, address) + if err != nil { + t.Fatal(err) + } + defer dev.Close() + + p0 := gpioreg.ByName("TCA9535_20_P0_0") + + p0.In(gpio.Float, gpio.NoEdge) + l := p0.Read() + if l != gpio.High { + t.Errorf("Input should be High") + } +} + +func TestTCA9535_inInverted(t *testing.T) { + const address uint16 = 0x20 + scenario := &i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: address, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: address, W: []byte{0x07}, R: []byte{0xFF}}, + // not written, since it didn't change + // polarity is set + {Addr: address, W: []byte{0x04}, R: []byte{0x01}}, + // gpio is read high + {Addr: address, W: []byte{0x00}, R: []byte{0x01}}, + // gpio is read low + {Addr: address, W: []byte{0x00}, R: []byte{0x00}}, + }, + } + + dev, err := New(scenario, TCA9535, address) + if err != nil { + t.Fatal(err) + } + defer dev.Close() + + p0 := gpioreg.ByName("TCA9535_20_P0_0").(Pin) + + p0.In(gpio.Float, gpio.NoEdge) + p0.SetPolarityInverted(true) + l := p0.Read() + if l != gpio.High { + t.Errorf("Input should be High") + } + l = p0.Read() + if l != gpio.Low { + t.Errorf("Input should be Low") + } + inverted, err := p0.IsPolarityInverted() + if inverted != true || err != nil { + t.Errorf("polarity should return as inverted") + } +} + +func TestTCA9535_Tx(t *testing.T) { + tests := []struct { + description string + scenario i2ctest.Playback + output bool + t []byte + r []byte + expectErr bool + }{ + { + description: "working write 2 characters", + output: true, + t: []byte{0xa5, 0x5a}, + scenario: i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}}, + // iodir is set to output + {Addr: 0x20, W: []byte{0x06, 0xFE}, R: nil}, + // output is read + {Addr: 0x20, W: []byte{0x02}, R: []byte{0x00}}, + {Addr: 0x20, W: []byte{0x06, 0xFC}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0xF8}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0xF0}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0xE0}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0xC0}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0x80}, R: nil}, + {Addr: 0x20, W: []byte{0x06, 0x00}, R: nil}, + + // output is set + {Addr: 0x20, W: []byte{0x02, 0xa5}, R: nil}, + // output is set + {Addr: 0x20, W: []byte{0x02, 0x5a}, R: nil}, + }, + }, + }, { + description: "working read 2 characters", + r: []byte{0xa5, 0x5a}, + scenario: i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}}, + // read the inputs + {Addr: 0x20, W: []byte{0x00}, R: []byte{0xa5}}, + {Addr: 0x20, W: []byte{0x00}, R: []byte{0x5a}}, + }, + }, + }, { + description: "Invalid, only r or w may be set.", + r: []byte{0xa5, 0x5a}, + t: []byte{0xa5, 0x5a}, + scenario: i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: 0x20, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: 0x20, W: []byte{0x07}, R: []byte{0xFF}}, + }, + }, + expectErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + dev, err := New(&tc.scenario, TCA9535, uint16(0x20)) + if err != nil { + t.Fatal(err) + } + if dev == nil { + t.Fatal("dev must not be nil") + } + defer dev.Close() + + if tc.output { + // Set the port for output + for _, pin := range dev.Pins[0] { + pin.Out(gpio.Low) + } + } else { + // Set the port for input + for _, pin := range dev.Pins[0] { + pin.In(gpio.Float, gpio.NoEdge) + } + } + + r := make([]byte, len(tc.r)) + err = dev.Conns[0].Tx(tc.t, r) + if tc.expectErr { + if err == nil { + t.Fatal(err) + } + return + } + + if err != nil { + t.Fatal(err) + } + + if len(tc.r) != len(r) || len(tc.r) > 0 { + if !reflect.DeepEqual(tc.r, r) { + t.Fatal("r buffers don't match") + } + } + }) + } +} + +func TestTCA9535_fixedValues(t *testing.T) { + const address uint16 = 0x20 + scenario := &i2ctest.Playback{ + Ops: []i2ctest.IO{ + // iodir is read on creation + {Addr: address, W: []byte{0x06}, R: []byte{0xFF}}, + {Addr: address, W: []byte{0x07}, R: []byte{0xFF}}, + }, + } + + dev, err := New(scenario, TCA9535, address) + if err != nil { + t.Fatal(err) + } + defer dev.Close() + + if conn.Half != dev.Conns[0].Duplex() { + t.Errorf("Duplex() should return conn.Half") + } + + if "TCA9535_20_P0" != dev.Conns[0].String() { + t.Errorf("String() should return 'TCA9535_20_P0'") + } + + if "TCA9535_20_P1" != dev.Conns[1].String() { + t.Errorf("String() should return 'TCA9535_20_P1'") + } + + if "TCA9535_20_P0_1" != dev.Pins[0][1].String() { + t.Errorf("String() should return 'TCA9535_20_P0_1'") + } + + if 1 != dev.Pins[0][1].Number() { + t.Errorf("Number() should return '1'") + } + + if 6 != dev.Pins[0][6].Number() { + t.Errorf("Number() should return '6'") + } + + if false != dev.Pins[0][6].WaitForEdge(10*time.Second) { + t.Errorf("WaitForEdge() should return 'false'") + } + + if gpio.Float != dev.Pins[0][5].Pull() { + t.Errorf("Pull() should return 'gpio.Float'") + } + + if gpio.Float != dev.Pins[0][5].DefaultPull() { + t.Errorf("DefaultPull() should return 'gpio.Float'") + } + + err = dev.Pins[0][0].PWM(gpio.DutyHalf, physic.Hertz) + if err == nil { + t.Errorf("PWM should return an error") + } +} diff --git a/tca95xx/tca95xx.go b/tca95xx/tca95xx.go new file mode 100644 index 0000000..128ad00 --- /dev/null +++ b/tca95xx/tca95xx.go @@ -0,0 +1,110 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package tca95xx provides an interface to the Texas Instruments TCA95 series +// of 8-bit I²C extenders. +// +// The following variants are supported: +// +// - PCA9536 - address: 0x41 +// - TCA6408A - addresses: 0x20, 0x21 +// - TCA6416 - addresses: 0x20, 0x21 +// - TCA6416A - addresses: 0x20, 0x21 +// - TCA9534 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 +// - TCA9534A - addresses: 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f +// - TCA9535 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 +// - TCA9537 - address: 0x49 +// - TCA9538 - address: 0x70, 0x71, 0x72, 0x73 +// - TCA9539 - address: 0x74, 0x75, 0x76, 0x77 +// - TCA9554 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 +// - TCA9555 - addresses: 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 +// +// Both gpio.Pin and conn.Conn interfaces are supported. +package tca95xx + +import ( + "fmt" + "strconv" + + "periph.io/x/conn/v3" + "periph.io/x/conn/v3/gpio/gpioreg" + "periph.io/x/conn/v3/i2c" +) + +// Dev is a TCA95xx series I²C extender with two ways to interact with the pins +// on the extender chip - as per pin gpio.Pin, or as per port conn.Conn +// connections. +type Dev struct { + Pins [][]Pin // Pins is a double array structured as: [port][pin]. + Conns []conn.Conn // Conns uses the same [port] array structure. +} + +// New returns a device object that communicates over I²C to the TCA95xx device +// family of I/O extenders. +func New(bus i2c.Bus, variant Variant, addr uint16) (*Dev, error) { + v, found := variants[variant] + if !found { + return nil, fmt.Errorf("%s: Unsupported variant", string(variant)) + } + if v.isAddrInvalid(addr) { + return nil, fmt.Errorf("tca95xx: address not supported by device type %s", string(variant)) + } + + i2c := i2c.Dev{ + Bus: bus, + Addr: addr, + } + + devicename := string(variant) + "_" + strconv.FormatInt(int64(addr), 16) + ports := v.getPorts(&i2c, devicename) + + // Map the register maps and ports into gpio.Pins. + pins := make([][]Pin, len(ports)) + pinsLeft := v.pins + for i := range ports { + // pre-cache iodir + _, err := ports[i].iodir.readValue(false) + if err != nil { + return nil, err + } + if pinsLeft > 8 { + pins[i] = ports[i].pins(8) + pinsLeft -= 8 + } else { + pins[i] = ports[i].pins(pinsLeft) + pinsLeft = 0 + } + for j := range pins[i] { + pin := pins[i][j] + // Ignore registration failure. + _ = gpioreg.Register(pin) + } + } + + // Convert to an array of Conn interfaces. + var conns []conn.Conn + for i := range ports { + conns = append(conns, ports[i]) + } + + d := Dev{ + Pins: pins, + Conns: conns, + } + + return &d, nil +} + +// Close removes any registration to the device. +func (d *Dev) Close() error { + for _, port := range d.Pins { + for _, pin := range port { + err := gpioreg.Unregister(pin.Name()) + if err != nil { + return err + } + } + } + return nil +} diff --git a/tca95xx/variants.go b/tca95xx/variants.go new file mode 100644 index 0000000..8cb3127 --- /dev/null +++ b/tca95xx/variants.go @@ -0,0 +1,89 @@ +// Copyright 2022 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package tca95xx + +import ( + "periph.io/x/conn/v3/i2c" +) + +// Variant is the type denoting a specific variant of the family. +type Variant string + +const ( + PCA9536 Variant = "PCA9536" // PCA9536 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/pca9536 + TCA6408A Variant = "TCA6408A" // TCA6408A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6408a + TCA6416 Variant = "TCA6416" // TCA6416 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6416 + TCA6416A Variant = "TCA6416A" // TCA6416A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca6416a + TCA9534 Variant = "TCA9534" // TCA9534 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9534 + TCA9534A Variant = "TCA9534A" // TCA9534A 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9534a + TCA9535 Variant = "TCA9535" // TCA9535 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9535 + TCA9537 Variant = "TCA9537" // TCA9537 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9537 + TCA9538 Variant = "TCA9538" // TCA9538 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9538 + TCA9539 Variant = "TCA9539" // TCA9539 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9539 + TCA9554 Variant = "TCA9554" // TCA9554 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9554 + TCA9555 Variant = "TCA9555" // TCA9555 8-bit I²C extender. Datasheet: https://www.ti.com/lit/gpn/tca9555 +) + +type variant struct { + addStart uint16 + addEnd uint16 + pins int +} + +var variants = map[Variant]variant{ + PCA9536: {addStart: 0x41, addEnd: 0x41, pins: 4}, + TCA6408A: {addStart: 0x20, addEnd: 0x21, pins: 8}, + TCA6416: {addStart: 0x20, addEnd: 0x21, pins: 16}, + TCA6416A: {addStart: 0x20, addEnd: 0x21, pins: 16}, + TCA9534: {addStart: 0x20, addEnd: 0x27, pins: 8}, + TCA9534A: {addStart: 0x38, addEnd: 0x3f, pins: 8}, + TCA9535: {addStart: 0x20, addEnd: 0x27, pins: 16}, + TCA9537: {addStart: 0x49, addEnd: 0x49, pins: 4}, + TCA9538: {addStart: 0x70, addEnd: 0x73, pins: 8}, + TCA9539: {addStart: 0x74, addEnd: 0x77, pins: 16}, + TCA9554: {addStart: 0x20, addEnd: 0x27, pins: 8}, + TCA9555: {addStart: 0x20, addEnd: 0x27, pins: 16}, +} + +// isAddrInvalid checks to see if the address is used by the chip. +func (v variant) isAddrInvalid(addr uint16) bool { + if addr < v.addStart || v.addEnd < addr { + return true + } + return false +} + +// getVariantRegMap returns the register map based on the number of pins the +// chip expands to. +func (v variant) getPorts(i2c *i2c.Dev, devicename string) []*port { + if v.pins == 16 { + return []*port{ + { + name: devicename + "_P0", + input: newRegister(i2c, 0x00), + output: newRegister(i2c, 0x02), + ipol: newRegister(i2c, 0x04), + iodir: newRegister(i2c, 0x06), + }, + { + name: devicename + "_P1", + input: newRegister(i2c, 0x01), + output: newRegister(i2c, 0x03), + ipol: newRegister(i2c, 0x05), + iodir: newRegister(i2c, 0x07), + }, + } + } + + return []*port{ + { + name: devicename + "_P0", + input: newRegister(i2c, 0x00), + output: newRegister(i2c, 0x01), + ipol: newRegister(i2c, 0x02), + iodir: newRegister(i2c, 0x03), + }, + } +}