You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/mcp23xxx/group.go

218 lines
5.8 KiB
Go

// Copyright 2025 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 mcp23xxx
import (
"fmt"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/pin"
)
// The internal structure for a group of pins.
type pinGroup struct {
dev *Dev
port int
pins []*portpin
defaultMask gpio.GPIOValue
}
// Group returns a gpio.Group that is made up of the specified pins.
func (dev *Dev) Group(port int, pins []int) *gpio.Group {
grouppins := make([]*portpin, len(pins))
for ix, number := range pins {
pp, ok := dev.Pins[port][number].(*portpin)
if !ok {
return nil
}
grouppins[ix] = pp
}
defMask := gpio.GPIOValue((1 << len(pins)) - 1)
var pgif interface{} = &pinGroup{dev: dev, port: port, pins: grouppins, defaultMask: defMask}
if gpiogroup, ok := pgif.(gpio.Group); ok {
return &gpiogroup
}
return nil
}
// Pins returns the set of pin.Pin that make up that group.
func (pg *pinGroup) Pins() []pin.Pin {
pins := make([]pin.Pin, len(pg.pins))
for ix, p := range pg.pins {
pins[ix] = p
}
return pins
}
// Given the offset within the group, return the corresponding GPIO pin.
func (pg *pinGroup) ByOffset(offset int) pin.Pin {
return pg.pins[offset]
}
// Given the specific name of a pin, return it. If it can't be found, nil is
// returned.
func (pg *pinGroup) ByName(name string) pin.Pin {
for _, pin := range pg.pins {
if pin.Name() == name {
return pin
}
}
return nil
}
// Given the GPIO pin number, return that pin from the set.
func (pg *pinGroup) ByNumber(number int) pin.Pin {
for _, pin := range pg.pins {
if pin.Number() == number {
return pin
}
}
return nil
}
// Out writes value to the specified pins of the device/port. If mask is 0,
// the default mask of all pins in the group is used.
func (pg *pinGroup) Out(value, mask gpio.GPIOValue) error {
if mask == 0 {
mask = pg.defaultMask
} else {
mask &= pg.defaultMask
}
value &= mask
// Convert the write value which is relative to the pins to the
// absolute value for the port.
wr := uint8(0)
wrMask := uint8(0)
for bit := range len(pg.pins) {
if (mask & (1 << bit)) > 0 {
if (value & 0x01) == 0x01 {
wr |= 1 << pg.pins[bit].Number()
}
wrMask |= 1 << pg.pins[bit].Number()
}
value = value >> 1
}
port := pg.pins[0].port
// Verify pins are set for output
outputPins, err := port.iodir.readValue(true)
if err != nil {
return err
}
if ((outputPins ^ 0xff) & wrMask) != wrMask {
outputPins &= (wrMask ^ 0xff)
err = port.iodir.writeValue(outputPins, false)
if err != nil {
return err
}
}
// Read the current value
currentValue, err := port.olat.readValue(true)
if err != nil {
return err
}
// Apply the mask to clear bits we're writing.
currentValue &= (0xff ^ wrMask)
// Or the value with the bits to modify
currentValue |= wr
// And, write the value out the port.
return port.olat.writeValue(currentValue, true)
}
// Read reads from the device and port and returns the state of the GPIO
// pins in the group. If a pin specified by mask is not configured for
// input, it is transparently re-configured.
func (pg *pinGroup) Read(mask gpio.GPIOValue) (result gpio.GPIOValue, err error) {
if mask == 0 {
mask = pg.defaultMask
} else {
mask &= pg.defaultMask
}
// Compute the read mask
rmask := uint8(0)
for bit := range 8 {
if (mask & (1 << bit)) > 0 {
rmask |= (1 << pg.pins[bit].Number())
}
}
// Make sure the direction for the pins involved in this write read is
// Input.
port := pg.pins[0].port
currentIn, err := port.iodir.readValue(true)
if err != nil {
return
}
// We need to make some pins Input. Write the value to the iodir register.
if (currentIn & rmask) != rmask {
err = port.iodir.writeValue(currentIn|rmask, false)
if err != nil {
return
}
}
// Now, perform the read itself.
v, err := port.gpio.readValue(false)
if err != nil {
return
}
// Now convert the set pins into the Group value
for ix, pin := range pg.pins {
if (v & (1 << pin.Number())) > 0 {
result |= 1 << ix
}
}
return
}
// WaitForEdge listens for a GPIO pin change event. The MCP23XXXX devices
// can't directly signal an edge event. To do this, you must call
// Dev.SetEdgePin() with a HOST GPIO pin configured for falling edge
// detection. That pin should be connected to the MCP23XXX INT pin. When
// a falling edge is detected on the supplied host GPIO pin, the code
// will return the GPIO Pin number on the device that changed.
//
// Note that the MCP23XXX devices only detect change. You can't configure
// falling or rising edge. Consequently, the returned edge will always be
// gpio.NoEdge.
//
// For a change event to be detected, the pin must be configured for input.
// This function will NOT set pins for input. Additionally, the calling
// code must set the INTCON register appropriately. Refer to the datasheet.
//
// In the event that the changed pin is NOT part of the io group, the
// triggering pin number will be returned, along with the error
// ErrPinNotInGroup
func (pg *pinGroup) WaitForEdge(timeout time.Duration) (number int, edge gpio.Edge, err error) {
return -1, gpio.NoEdge, gpio.ErrGroupFeatureNotImplemented
}
// Halt() interrupts a pending WaitForEdge() call if one is in process.
func (pg *pinGroup) Halt() error {
if pg.dev.edgePin != nil {
var ifpin interface{} = pg.dev.edgePin
if r, ok := ifpin.(conn.Resource); ok {
return r.Halt()
}
}
// TODO: I think we want to call Dev.Halt()
return nil
}
// String returns the device variant name and configured pins for the group.
func (pg *pinGroup) String() string {
s := fmt.Sprintf("%s - [ ", pg.dev)
for ix := range len(pg.pins) {
s += fmt.Sprintf("%d ", pg.pins[ix].Number())
}
s += "]"
return s
}
var _ gpio.Group = &pinGroup{}