Skip to content

Commit

Permalink
bcm283x: Issue 62 Change bcm283x to use ioctl for GPIO pins, not sysf…
Browse files Browse the repository at this point in the history
…s. (#63)
  • Loading branch information
gsexton authored Oct 21, 2024
1 parent 7540d26 commit 75941b8
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 110 deletions.
6 changes: 5 additions & 1 deletion bcm283x/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Package bcm283x exposes the BCM283x GPIO functionality.
//
// This driver implements memory-mapped GPIO pin manipulation and leverages
// sysfs-gpio for edge detection.
// ioctl-gpio for edge detection.
//
// If you are looking for the actual implementation, open doc.go for further
// implementation details.
Expand All @@ -15,6 +15,10 @@
// Aliases for GPCLK0, GPCLK1, GPCLK2 are created for corresponding CLKn pins.
// Same for PWM0_OUT and PWM1_OUT, which point respectively to PWM0 and PWM1.
//
// For multi-pin IO, you should prefer using the /host/gpioioctl/GPIOChip.LineSet()
// functionality. It's chipset agnostic because it uses the ioctl interfaces,
// and it offers multi-pin WaitForEdge().
//
// # Datasheet
//
// https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
Expand Down
79 changes: 36 additions & 43 deletions bcm283x/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/pin"
"periph.io/x/host/v3/distro"
"periph.io/x/host/v3/gpioioctl"
"periph.io/x/host/v3/pmem"
"periph.io/x/host/v3/sysfs"
"periph.io/x/host/v3/videocore"
Expand Down Expand Up @@ -185,7 +186,7 @@ func PinsSetup28To45(drive physic.ElectricCurrent, slewLimit, hysteresis bool) e
return nil
}

// Pin is a GPIO number (GPIOnn) on BCM238(5|6|7).
// Pin is a GPIO number (GPIOnn) on BCM283(5|6|7).
//
// Pin implements gpio.PinIO.
type Pin struct {
Expand All @@ -195,7 +196,7 @@ type Pin struct {
defaultPull gpio.Pull // Default pull at system boot, as per datasheet.

// Immutable after driver initialization.
sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any.
ioctlPin *gpioioctl.GPIOLine // Set to the corresponding gpioioctl.GPIOLine, if any.

// Mutable.
usingEdge bool // Set when edge detection is enabled.
Expand All @@ -217,7 +218,7 @@ func (p *Pin) String() string {
// disabled.
func (p *Pin) Halt() error {
if p.usingEdge {
if err := p.sysfsPin.Halt(); err != nil {
if err := p.ioctlPin.Halt(); err != nil {
return p.wrap(err)
}
p.usingEdge = false
Expand Down Expand Up @@ -245,10 +246,10 @@ func (p *Pin) Function() string {
// Func implements pin.PinFunc.
func (p *Pin) Func() pin.Func {
if drvGPIO.gpioMemory == nil {
if p.sysfsPin == nil {
if p.ioctlPin == nil {
return pin.FuncNone
}
return p.sysfsPin.Func()
return p.ioctlPin.Func()
}
switch f := p.function(); f {
case in:
Expand Down Expand Up @@ -311,10 +312,10 @@ func (p *Pin) SupportedFuncs() []pin.Func {
// SetFunc implements pin.PinFunc.
func (p *Pin) SetFunc(f pin.Func) error {
if drvGPIO.gpioMemory == nil {
if p.sysfsPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
if p.ioctlPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
}
return p.sysfsPin.SetFunc(f)
return p.ioctlPin.SetFunc(f)
}
switch f {
case gpio.FLOAT:
Expand Down Expand Up @@ -369,29 +370,17 @@ func (p *Pin) SetFunc(f pin.Func) error {
// possible to 'read back' what value was specified for each pin.
//
// Will fail if requesting to change a pin that is set to special functionality.
//
// Using edge detection requires opening a gpio sysfs file handle. On Raspbian,
// make sure the user is member of group 'gpio'. The pin will be exported at
// /sys/class/gpio/gpio*/. Note that the pin will not be unexported at
// shutdown.
//
// For edge detection, the processor samples the input at its CPU clock rate
// and looks for '011' to rising and '100' for falling detection to avoid
// glitches. Because gpio sysfs is used, the latency is unpredictable.
func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
if p.usingEdge && edge == gpio.NoEdge {
if err := p.sysfsPin.Halt(); err != nil {
if err := p.ioctlPin.Halt(); err != nil {
return p.wrap(err)
}
}
if drvGPIO.gpioMemory == nil {
if p.sysfsPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
}
if pull != gpio.PullNoChange {
return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized"))
if p.ioctlPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
}
if err := p.sysfsPin.In(pull, edge); err != nil {
if err := p.ioctlPin.In(pull, edge); err != nil {
return p.wrap(err)
}
p.usingEdge = edge != gpio.NoEdge
Expand Down Expand Up @@ -457,11 +446,11 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
}
}
if edge != gpio.NoEdge {
if p.sysfsPin == nil {
return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.number))
if p.ioctlPin == nil {
return p.wrap(fmt.Errorf("pin %d is not exported by gpioioctl", p.number))
}
// This resets pending edges.
if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil {
if err := p.ioctlPin.In(gpio.PullNoChange, edge); err != nil {
return p.wrap(err)
}
p.usingEdge = true
Expand All @@ -474,10 +463,10 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
// This function is fast. It works even if the pin is set as output.
func (p *Pin) Read() gpio.Level {
if drvGPIO.gpioMemory == nil {
if p.sysfsPin == nil {
if p.ioctlPin == nil {
return gpio.Low
}
return p.sysfsPin.Read()
return p.ioctlPin.Read()
}
if p.number < 32 {
// Important: do not remove the &31 here even if not necessary. Testing
Expand All @@ -501,8 +490,8 @@ func (p *Pin) FastRead() gpio.Level {

// WaitForEdge implements gpio.PinIn.
func (p *Pin) WaitForEdge(timeout time.Duration) bool {
if p.sysfsPin != nil {
return p.sysfsPin.WaitForEdge(timeout)
if p.ioctlPin != nil {
return p.ioctlPin.WaitForEdge(timeout)
}
return false
}
Expand All @@ -512,7 +501,7 @@ func (p *Pin) WaitForEdge(timeout time.Duration) bool {
// bcm2711/bcm2838 support querying the pull resistor of all GPIO pins. Prior
// to it, bcm283x doesn't support querying the pull resistor of any GPIO pin.
func (p *Pin) Pull() gpio.Pull {
// sysfs does not have the capability to read pull resistor.
// gpioioctl does not have the capability to read pull resistor.
if drvGPIO.gpioMemory != nil {
if drvGPIO.useLegacyPull {
// TODO(maruel): The best that could be added is to cache the last set value
Expand Down Expand Up @@ -545,10 +534,10 @@ func (p *Pin) DefaultPull() gpio.Pull {
// Fails if requesting to change a pin that is set to special functionality.
func (p *Pin) Out(l gpio.Level) error {
if drvGPIO.gpioMemory == nil {
if p.sysfsPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
if p.ioctlPin == nil {
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
}
return p.sysfsPin.Out(l)
return p.ioctlPin.Out(l)
}
// TODO(maruel): This function call is very costly.
if err := p.Halt(); err != nil {
Expand Down Expand Up @@ -973,7 +962,7 @@ var cpuPins = []Pin{
// Mapping may be overridden during driverGPIO.Init().
var mapping = mapping283x

// BCM238x specific alternate function mapping (the default).
// BCM283x specific alternate function mapping (the default).
var mapping283x = [][6]pin.Func{
{"I2C0_SDA"}, // 0
{"I2C0_SCL"},
Expand Down Expand Up @@ -1331,17 +1320,20 @@ func (d *driverGPIO) String() string {
}

func (d *driverGPIO) Prerequisites() []string {
return nil
return []string{"ioctl-gpio"}
}

func (d *driverGPIO) After() []string {
return []string{"sysfs-gpio"}
return nil
}

func (d *driverGPIO) Init() (bool, error) {
if !Present() {
return false, errors.New("bcm283x CPU not detected")
}
if len(gpioioctl.Chips) == 0 {
return false, errors.New("gpioioctl not initialized")
}
// It's kind of messy, some report bcm283x while others show bcm27xx.
// Let's play safe here.
dTCompatible := strings.Join(distro.DTCompatible(), " ")
Expand Down Expand Up @@ -1381,17 +1373,18 @@ func (d *driverGPIO) Init() (bool, error) {
d.gpioBaseAddr = d.baseAddr + 0x200000

// Mark the right pins as available even if the memory map fails so they can
// callback to sysfs.Pins.
// callback to gpioioctl.Pins.
functions := map[pin.Func]struct{}{}
lines := gpioioctl.Chips[0].Lines()
for i := range cpuPins {
name := cpuPins[i].name
num := strconv.Itoa(cpuPins[i].number)

// Initializes the sysfs corresponding pin right away.
cpuPins[i].sysfsPin = sysfs.Pins[cpuPins[i].number]
// Initializes the ioctl corresponding pin right away.
cpuPins[i].ioctlPin = lines[cpuPins[i].number]

// Unregister the pin if already registered. This happens with sysfs-gpio.
// Do not error on it, since sysfs-gpio may have failed to load.
// Unregister the pin if already registered. This happens with ioctl-gpio.
// Do not error on it, since ioctl-gpio may have failed to load.
_ = gpioreg.Unregister(name)
_ = gpioreg.Unregister(num)

Expand Down
3 changes: 2 additions & 1 deletion bcm283x/gpio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"periph.io/x/conn/v3/pin"
"periph.io/x/conn/v3/spi"
"periph.io/x/conn/v3/uart"
_ "periph.io/x/host/v3/gpioioctl"
"periph.io/x/host/v3/pmem"
"periph.io/x/host/v3/videocore"
)
Expand Down Expand Up @@ -315,7 +316,7 @@ func TestDriver(t *testing.T) {
if s := drvGPIO.String(); s != "bcm283x-gpio" {
t.Fatal(s)
}
if s := drvGPIO.Prerequisites(); s != nil {
if s := drvGPIO.Prerequisites(); s == nil {
t.Fatal(s)
}
// It will fail to initialize on non-bcm.
Expand Down
8 changes: 1 addition & 7 deletions gpioioctl/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// periph.io/x/cmd/periph-smoketest/gpiosmoketest
// folder.

//go:build linux

package gpioioctl

import (
Expand All @@ -24,11 +22,7 @@ func init() {
var err error

if len(Chips) == 0 {
/*
During pipeline builds, GPIOChips may not be available, or
it may build on another OS. In that case, mock in enough
for a test to pass.
*/
makeDummyChip()
line := GPIOLine{
number: 0,
name: "DummyGPIOLine",
Expand Down
45 changes: 45 additions & 0 deletions gpioioctl/dummy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2024 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.
//
// Create a dummy chip for testing an non-Linux os.

package gpioioctl

import (
"log"

"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/gpio/gpioreg"
)

func makeDummyChip() {
/*
During pipeline builds, GPIOChips may not be available, or
it may build on another OS. In that case, mock in enough
for a test to pass.
*/

line := GPIOLine{
number: 0,
name: "DummyGPIOLine",
consumer: "",
edge: gpio.NoEdge,
pull: gpio.PullNoChange,
direction: LineDirNotSet,
}

chip := GPIOChip{name: "DummyGPIOChip",
path: "/dev/gpiochipdummy",
label: "Dummy GPIOChip for Testing Purposes",
lineCount: 1,
lines: []*GPIOLine{&line},
}
Chips = append(Chips, &chip)
Chips = append(Chips, &chip)
if err := gpioreg.Register(&line); err != nil {
nameStr := chip.Name()
lineStr := line.String()
log.Println("chip", nameStr, " gpioreg.Register(line) ", lineStr, " returned ", err)
}
}
2 changes: 0 additions & 2 deletions gpioioctl/example_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build linux

package gpioioctl_test

// Copyright 2024 The Periph Authors. All rights reserved.
Expand Down
Loading

0 comments on commit 75941b8

Please sign in to comment.