mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-16 15:35:42 +02:00
chore: refactore bcm2711 hardware abstraction layer
Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com>
This commit is contained in:
3
pkg/computeblade/computeblade.go
Normal file
3
pkg/computeblade/computeblade.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package computeblade
|
||||
|
||||
|
||||
20
pkg/computeblade/cover.cov
Normal file
20
pkg/computeblade/cover.cov
Normal file
@@ -0,0 +1,20 @@
|
||||
mode: set
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:40.56,46.2 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:49.84,62.2 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:65.89,74.2 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:86.54,94.2 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:96.64,97.30 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:97.30,99.3 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:101.2,105.12 4 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:109.56,111.6 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:111.6,113.69 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:113.69,115.4 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:117.2,118.44 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:118.44,119.11 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:121.21,122.22 1 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:124.22,126.21 2 1
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:128.32,129.43 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:130.5,131.19 2 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:131.19,133.6 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:134.5,134.57 1 0
|
||||
github.com/xvzf/computeblade-agent/pkg/computeblade/ledengine.go:134.57,136.6 1 0
|
||||
108
pkg/computeblade/hal/hal.go
Normal file
108
pkg/computeblade/hal/hal.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
type FanUnit uint8
|
||||
type ComputeModule uint8
|
||||
type PowerStatus uint8
|
||||
|
||||
var (
|
||||
fanSpeedTargetPercent = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "fan_speed_target_percent",
|
||||
Help: "Target fanspeed in percent",
|
||||
})
|
||||
fanSpeed = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "fan_speed",
|
||||
Help: "Fan speed in RPM",
|
||||
})
|
||||
computeModule = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "compute_modul_present",
|
||||
Help: "Compute module type",
|
||||
}, []string{"type"})
|
||||
ledColorChangeEventCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "led_color_change_event_count",
|
||||
Help: "Led color change event_count",
|
||||
})
|
||||
powerStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "power_status",
|
||||
Help: "Power status of the blade",
|
||||
}, []string{"type"})
|
||||
stealthModeEnabled = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "stealth_mode_enabled",
|
||||
Help: "Stealth mode enabled",
|
||||
})
|
||||
fanUnit = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "fan_unit",
|
||||
Help: "Fan unit",
|
||||
}, []string{"type"})
|
||||
edgeButtonEventCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "computeblade",
|
||||
Name: "edge_button_event_count",
|
||||
Help: "Number of edge button presses",
|
||||
})
|
||||
)
|
||||
|
||||
func (p PowerStatus) String() string {
|
||||
switch p {
|
||||
case PowerPoe802at:
|
||||
return "poe+"
|
||||
case PowerPoeOrUsbC:
|
||||
return "poeOrUsbC"
|
||||
default:
|
||||
return "undefined"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
FanUnitStandard = iota
|
||||
FanUnitSmart
|
||||
)
|
||||
|
||||
const (
|
||||
PowerPoeOrUsbC = iota
|
||||
PowerPoe802at
|
||||
)
|
||||
|
||||
const (
|
||||
LedTop = iota
|
||||
LedEdge
|
||||
)
|
||||
|
||||
type LedColor struct {
|
||||
Red uint8
|
||||
Green uint8
|
||||
Blue uint8
|
||||
}
|
||||
|
||||
type ComputeBladeHalOpts struct {
|
||||
FanUnit FanUnit
|
||||
}
|
||||
|
||||
// ComputeBladeHal abstracts hardware details of the Compute Blade and provides a simple interface
|
||||
type ComputeBladeHal interface {
|
||||
Close() error
|
||||
// SetFanSpeed sets the fan speed in percent
|
||||
SetFanSpeed(speed uint8) error
|
||||
// GetFanSpeed returns the current fan speed in percent (based on moving average)
|
||||
GetFanRPM() (float64, error)
|
||||
// SetStealthMode enables/disables stealth mode of the blade (turning on/off the LEDs)
|
||||
SetStealthMode(enabled bool) error
|
||||
// SetLEDs sets the color of the LEDs
|
||||
SetLed(idx uint, color LedColor) error
|
||||
// GetPowerStatus returns the current power status of the blade
|
||||
GetPowerStatus() (PowerStatus, error)
|
||||
// GetEdgeButtonPressChan returns a channel emitting edge button press events
|
||||
WaitForEdgeButtonPress(ctx context.Context) error
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package bcm2711
|
||||
//go:build linux
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -10,44 +12,43 @@ import (
|
||||
|
||||
"github.com/warthog618/gpiod"
|
||||
"github.com/warthog618/gpiod/device/rpi"
|
||||
"github.com/xvzf/computeblade-agent/pkg/hal"
|
||||
)
|
||||
|
||||
const (
|
||||
bcm2711PeripheryBaseAddr = 0xFE000000
|
||||
bcm2711RegPwmAddr = bcm2711PeripheryBaseAddr + 0x20C000
|
||||
bcm2711GpioAddr = bcm2711PeripheryBaseAddr + 0x200000
|
||||
bcm2711ClkAddr = bcm2711PeripheryBaseAddr + 0x101000
|
||||
bcm2711ClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs
|
||||
bcm2711PageSize = 4096 // theoretical page size
|
||||
bcm283xPeripheryBaseAddr = 0xFE000000
|
||||
bcm283xRegPwmAddr = bcm283xPeripheryBaseAddr + 0x20C000
|
||||
bcm283xGpioAddr = bcm283xPeripheryBaseAddr + 0x200000
|
||||
bcm283xClkAddr = bcm283xPeripheryBaseAddr + 0x101000
|
||||
bcm283xClkManagerPwd = (0x5A << 24) //(31 - 24) on CM_GP0CTL/CM_GP1CTL/CM_GP2CTL regs
|
||||
bcm283xPageSize = 4096 // theoretical page size
|
||||
|
||||
bcm2711FrontButtonPin = 20
|
||||
bcm2711StealthPin = 21
|
||||
bcm2711RegPwmTachPin = 13
|
||||
bcm283xFrontButtonPin = 20
|
||||
bcm283xStealthPin = 21
|
||||
bcm283xRegPwmTachPin = 13
|
||||
|
||||
bcm2711RegGpfsel1 = 0x01
|
||||
bcm283xRegGpfsel1 = 0x01
|
||||
|
||||
bcm2711RegPwmCtl = 0x00
|
||||
bcm2711RegPwmRng1 = 0x04
|
||||
bcm2711RegPwmFif1 = 0x06
|
||||
bcm283xRegPwmCtl = 0x00
|
||||
bcm283xRegPwmRng1 = 0x04
|
||||
bcm283xRegPwmFif1 = 0x06
|
||||
|
||||
bcm2711RegPwmCtlBitPwen2 = 8 // Enable (pwm2)
|
||||
bcm2711RegPwmCtlBitClrf1 = 6 // Clear FIFO
|
||||
bcm2711RegPwmCtlBitUsef1 = 5 // Use FIFO
|
||||
bcm2711RegPwmCtlBitSbit1 = 3 // Line level when not transmitting
|
||||
bcm2711RegPwmCtlBitRptl1 = 2 // Repeat last data when FIFO is empty
|
||||
bcm2711RegPwmCtlBitMode1 = 1 // Mode; 0: PWM, 1: Serializer
|
||||
bcm2711RegPwmCtlBitPwen1 = 0 // Enable (pwm1)
|
||||
bcm283xRegPwmCtlBitPwen2 = 8 // Enable (pwm2)
|
||||
bcm283xRegPwmCtlBitClrf1 = 6 // Clear FIFO
|
||||
bcm283xRegPwmCtlBitUsef1 = 5 // Use FIFO
|
||||
bcm283xRegPwmCtlBitSbit1 = 3 // Line level when not transmitting
|
||||
bcm283xRegPwmCtlBitRptl1 = 2 // Repeat last data when FIFO is empty
|
||||
bcm283xRegPwmCtlBitMode1 = 1 // Mode; 0: PWM, 1: Serializer
|
||||
bcm283xRegPwmCtlBitPwen1 = 0 // Enable (pwm1)
|
||||
|
||||
bcm2711RegPwmclkCntrl = 0x28
|
||||
bcm2711RegPwmclkDiv = 0x29
|
||||
bcm2711RegPwmclkCntrlBitSrcOsc = 0
|
||||
bcm2711RegPwmclkCntrlBitEnable = 4
|
||||
bcm283xRegPwmclkCntrl = 0x28
|
||||
bcm283xRegPwmclkDiv = 0x29
|
||||
bcm283xRegPwmclkCntrlBitSrcOsc = 0
|
||||
bcm283xRegPwmclkCntrlBitEnable = 4
|
||||
)
|
||||
|
||||
type bcm2711 struct {
|
||||
type bcm283x struct {
|
||||
// Config options
|
||||
opts hal.ComputeBladeHalOpts
|
||||
opts ComputeBladeHalOpts
|
||||
|
||||
wrMutex sync.Mutex
|
||||
|
||||
@@ -63,11 +64,15 @@ type bcm2711 struct {
|
||||
clkMem []uint32
|
||||
gpioChip0 *gpiod.Chip
|
||||
|
||||
// Save LED colors so the pixels can be updated individually
|
||||
leds [2]LedColor
|
||||
|
||||
// Stealth mode output
|
||||
stealthModeLine *gpiod.Line
|
||||
|
||||
// Edge button input
|
||||
edgeButtonLine *gpiod.Line
|
||||
edgeButtonLine *gpiod.Line
|
||||
edgeButtonWatchChan chan struct{}
|
||||
|
||||
// PoE detection input
|
||||
poeLine *gpiod.Line
|
||||
@@ -78,7 +83,7 @@ type bcm2711 struct {
|
||||
fanRpm float64
|
||||
}
|
||||
|
||||
func New(opts hal.ComputeBladeHalOpts) (*bcm2711, error) {
|
||||
func NewCm4Hal(opts ComputeBladeHalOpts) (ComputeBladeHal, error) {
|
||||
// /dev/gpiomem doesn't allow complex operations for PWM fan control or WS281x
|
||||
devmem, err := os.OpenFile("/dev/mem", os.O_RDWR|os.O_SYNC, os.ModePerm)
|
||||
if err != nil {
|
||||
@@ -91,36 +96,39 @@ func New(opts hal.ComputeBladeHalOpts) (*bcm2711, error) {
|
||||
}
|
||||
|
||||
// Setup memory mappings
|
||||
gpioMem, gpioMem8, err := mmap(devmem, bcm2711GpioAddr, bcm2711PageSize)
|
||||
gpioMem, gpioMem8, err := mmap(devmem, bcm283xGpioAddr, bcm283xPageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pwmMem, pwmMem8, err := mmap(devmem, bcm2711RegPwmAddr, bcm2711PageSize)
|
||||
pwmMem, pwmMem8, err := mmap(devmem, bcm283xRegPwmAddr, bcm283xPageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clkMem, clkMem8, err := mmap(devmem, bcm2711ClkAddr, bcm2711PageSize)
|
||||
clkMem, clkMem8, err := mmap(devmem, bcm283xClkAddr, bcm283xPageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bcm := &bcm2711{
|
||||
devmem: devmem,
|
||||
gpioMem: gpioMem,
|
||||
gpioMem8: gpioMem8,
|
||||
pwmMem: pwmMem,
|
||||
pwmMem8: pwmMem8,
|
||||
clkMem: clkMem,
|
||||
clkMem8: clkMem8,
|
||||
gpioChip0: gpioChip0,
|
||||
opts: opts,
|
||||
bcm := &bcm283x{
|
||||
devmem: devmem,
|
||||
gpioMem: gpioMem,
|
||||
gpioMem8: gpioMem8,
|
||||
pwmMem: pwmMem,
|
||||
pwmMem8: pwmMem8,
|
||||
clkMem: clkMem,
|
||||
clkMem8: clkMem8,
|
||||
gpioChip0: gpioChip0,
|
||||
opts: opts,
|
||||
edgeButtonWatchChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
computeModule.WithLabelValues("cm4").Set(1)
|
||||
|
||||
return bcm, bcm.setup()
|
||||
}
|
||||
|
||||
// Close cleans all memory mappings
|
||||
func (bcm *bcm2711) Close() error {
|
||||
func (bcm *bcm283x) Close() error {
|
||||
errs := errors.Join(
|
||||
syscall.Munmap(bcm.gpioMem8),
|
||||
syscall.Munmap(bcm.pwmMem8),
|
||||
@@ -140,7 +148,7 @@ func (bcm *bcm2711) Close() error {
|
||||
|
||||
// handleFanEdge handles an edge event on the fan tach input for the standard fan unite.
|
||||
// Exponential moving average is used to smooth out the fan speed.
|
||||
func (bcm *bcm2711) handleFanEdge(evt gpiod.LineEvent) {
|
||||
func (bcm *bcm283x) handleFanEdge(evt gpiod.LineEvent) {
|
||||
// Ensure we're always storing the last event
|
||||
defer func() {
|
||||
bcm.lastFanEdgeEvent = &evt
|
||||
@@ -158,14 +166,28 @@ func (bcm *bcm2711) handleFanEdge(evt gpiod.LineEvent) {
|
||||
|
||||
// Simple moving average to smooth out the fan speed
|
||||
bcm.fanRpm = (rpm * 0.1) + (bcm.fanRpm * 0.9)
|
||||
fanSpeed.Set(bcm.fanRpm)
|
||||
}
|
||||
|
||||
func (bcm *bcm2711) handleEdgeButtonEdge(evt gpiod.LineEvent) {
|
||||
fmt.Printf("button pressed\n")
|
||||
func (bcm *bcm283x) handleEdgeButtonEdge(evt gpiod.LineEvent) {
|
||||
edgeButtonEventCount.Inc()
|
||||
close(bcm.edgeButtonWatchChan)
|
||||
bcm.edgeButtonWatchChan = make(chan struct{})
|
||||
}
|
||||
|
||||
// GetEdgeButtonPressChan returns a channel that can be used to watch for edge button presses
|
||||
func (bcm *bcm283x) GetEdgeButtonPressChan(ctx context.Context) error {
|
||||
// Either wait for the context to be cancelled or the edge button to be pressed
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-bcm.edgeButtonWatchChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Init initialises GPIOs and sets sane defaults
|
||||
func (bcm *bcm2711) setup() error {
|
||||
func (bcm *bcm283x) setup() error {
|
||||
var err error = nil
|
||||
|
||||
// Register edge event handler for edge button
|
||||
@@ -177,7 +199,7 @@ func (bcm *bcm2711) setup() error {
|
||||
}
|
||||
|
||||
// Register input for PoE detection
|
||||
bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput)
|
||||
bcm.poeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO23, gpiod.AsInput, gpiod.WithPullUp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -189,40 +211,48 @@ func (bcm *bcm2711) setup() error {
|
||||
}
|
||||
|
||||
// standard fan unit
|
||||
if bcm.opts.FanUnit == hal.FanUnitStandard {
|
||||
if bcm.opts.FanUnit == FanUnitStandard {
|
||||
fanUnit.WithLabelValues("standard").Set(1)
|
||||
// FAN PWM output for standard fan unit (GPIO 12)
|
||||
// -> bcm2711RegGpfsel1 8:6, alt0
|
||||
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6)
|
||||
// -> bcm283xRegGpfsel1 8:6, alt0
|
||||
bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6)
|
||||
// Register edge event handler for fan tach input
|
||||
bcm.fanEdgeLine, err = bcm.gpioChip0.RequestLine(rpi.GPIO13, gpiod.WithEventHandler(bcm.handleFanEdge), gpiod.WithFallingEdge, gpiod.WithPullUp)
|
||||
bcm.fanEdgeLine, err = bcm.gpioChip0.RequestLine(
|
||||
rpi.GPIO13,
|
||||
gpiod.WithEventHandler(bcm.handleFanEdge),
|
||||
gpiod.WithFallingEdge,
|
||||
gpiod.WithPullUp,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Setup defaults
|
||||
|
||||
bcm.SetFanSpeed(bcm.opts.Defaults.FanSpeed)
|
||||
bcm.SetStealthMode(bcm.opts.Defaults.StealthModeEnabled)
|
||||
bcm.SetLEDs(bcm.opts.Defaults.TopLedColor, bcm.opts.Defaults.EdgeLedColor)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (bcm2711 *bcm2711) GetFanSpeed() int {
|
||||
return int(bcm2711.fanRpm)
|
||||
func (bcm283x *bcm283x) GetFanRPM() (float64, error) {
|
||||
return bcm283x.fanRpm, nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2711) GetPowerStatus() hal.PowerStatus {
|
||||
|
||||
func (bcm *bcm283x) GetPowerStatus() (PowerStatus, error) {
|
||||
// GPIO 23 is used for PoE detection
|
||||
if val, err := bcm.poeLine.Value(); err != nil && val == 1 {
|
||||
return hal.PowerPoe802at
|
||||
val, err := bcm.poeLine.Value()
|
||||
if err != nil {
|
||||
return PowerPoeOrUsbC, err
|
||||
}
|
||||
return hal.PowerPoeOrUsbC
|
||||
|
||||
if val > 0 {
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(1)
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(0)
|
||||
return PowerPoe802at, nil
|
||||
}
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoe802at)).Set(0)
|
||||
powerStatus.WithLabelValues(fmt.Sprint(PowerPoeOrUsbC)).Set(1)
|
||||
return PowerPoeOrUsbC, nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error {
|
||||
func (bcm *bcm283x) setPwm0Freq(targetFrequency uint64) error {
|
||||
// Calculate PWM divisor based on target frequency
|
||||
divisor := 54000000 / targetFrequency
|
||||
realDivisor := divisor & 0xfff // 12 bits
|
||||
@@ -231,42 +261,44 @@ func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error {
|
||||
}
|
||||
|
||||
// Stop pwm for both channels; this is required to set the new configuration
|
||||
bcm.pwmMem[bcm2711RegPwmCtl] &^= (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitPwen2)
|
||||
bcm.pwmMem[bcm283xRegPwmCtl] &^= (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitPwen2)
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
// Stop clock w/o any changes, they cannot be made in the same step
|
||||
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (bcm.clkMem[bcm2711RegPwmclkCntrl] &^ (1 << 4))
|
||||
bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (bcm.clkMem[bcm283xRegPwmclkCntrl] &^ (1 << 4))
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
// Wait for the clock to not be busy so we can perform the changes
|
||||
for bcm.clkMem[bcm2711RegPwmclkCntrl]&(1<<7) != 0 {
|
||||
for bcm.clkMem[bcm283xRegPwmclkCntrl]&(1<<7) != 0 {
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
}
|
||||
|
||||
// passwd, disabled, source (oscillator)
|
||||
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (0 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
|
||||
bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (0 << bcm283xRegPwmclkCntrlBitEnable) | (1 << bcm283xRegPwmclkCntrlBitSrcOsc)
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
bcm.clkMem[bcm2711RegPwmclkDiv] = bcm2711ClkManagerPwd | (uint32(divisor) << 12)
|
||||
bcm.clkMem[bcm283xRegPwmclkDiv] = bcm283xClkManagerPwd | (uint32(divisor) << 12)
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
// Start clock (passwd, enable, source)
|
||||
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (1 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
|
||||
bcm.clkMem[bcm283xRegPwmclkCntrl] = bcm283xClkManagerPwd | (1 << bcm283xRegPwmclkCntrlBitEnable) | (1 << bcm283xRegPwmclkCntrlBitSrcOsc)
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
// Start pwm for both channels again
|
||||
bcm.pwmMem[bcm2711RegPwmCtl] &= (1 << bcm2711RegPwmCtlBitPwen1)
|
||||
bcm.pwmMem[bcm283xRegPwmCtl] &= (1 << bcm283xRegPwmCtlBitPwen1)
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFanSpeed sets the fanspeed of a blade in percent (standard fan unit)
|
||||
func (bcm *bcm2711) SetFanSpeed(speed uint8) {
|
||||
func (bcm *bcm283x) SetFanSpeed(speed uint8) error {
|
||||
fanSpeedTargetPercent.Set(float64(speed))
|
||||
bcm.setFanSpeedPWM(speed)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bcm *bcm2711) setFanSpeedPWM(speed uint8) {
|
||||
func (bcm *bcm283x) setFanSpeedPWM(speed uint8) {
|
||||
// Noctua fans are expecting a 25khz signal, where duty cycle controls fan on/speed/off
|
||||
// With the usage of the FIFO, we can alter the duty cycle by the number of bits set in the FIFO, maximum of 32.
|
||||
// We therefore need a frequency of 32*25khz = 800khz, which is a divisor of 67.5 (thus we'll use 68).
|
||||
@@ -290,21 +322,23 @@ func (bcm *bcm2711) setFanSpeedPWM(speed uint8) {
|
||||
}
|
||||
|
||||
// Use fifo, repeat, ...
|
||||
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (1 << bcm2711RegPwmCtlBitUsef1)
|
||||
bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (1 << bcm283xRegPwmCtlBitUsef1)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
bcm.pwmMem[bcm2711RegPwmRng1] = 32
|
||||
bcm.pwmMem[bcm283xRegPwmRng1] = 32
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = targetvalue
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = targetvalue
|
||||
|
||||
// Store fan speed for later use
|
||||
bcm.currFanSpeed = speed
|
||||
}
|
||||
|
||||
func (bcm *bcm2711) SetStealthMode(enable bool) {
|
||||
func (bcm *bcm283x) SetStealthMode(enable bool) error {
|
||||
if enable {
|
||||
_ = bcm.stealthModeLine.SetValue(1)
|
||||
stealthModeEnabled.Set(1)
|
||||
return bcm.stealthModeLine.SetValue(1)
|
||||
} else {
|
||||
_ = bcm.stealthModeLine.SetValue(0)
|
||||
stealthModeEnabled.Set(0)
|
||||
return bcm.stealthModeLine.SetValue(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,53 +358,67 @@ func serializePwmDataFrame(data uint8) uint32 {
|
||||
return result
|
||||
}
|
||||
|
||||
// SetLEDs sets the color of the WS281x LEDs
|
||||
func (bcm *bcm2711) SetLEDs(top hal.LedColor, edge hal.LedColor) {
|
||||
func (bcm *bcm283x) SetLed(idx uint, color LedColor) error {
|
||||
if idx >= 2 {
|
||||
return fmt.Errorf("invalid led index %d, supported: [0, 1]", idx)
|
||||
}
|
||||
|
||||
bcm.leds[idx] = color
|
||||
|
||||
return bcm.updateLEDs()
|
||||
}
|
||||
|
||||
// updateLEDs sets the color of the WS281x LEDs
|
||||
func (bcm *bcm283x) updateLEDs() error {
|
||||
bcm.wrMutex.Lock()
|
||||
defer bcm.wrMutex.Unlock()
|
||||
|
||||
ledColorChangeEventCount.Inc()
|
||||
|
||||
// Set frequency to 3*800khz.
|
||||
// we'll bit-bang the data, so we'll need to send 3 bits per bit of data.
|
||||
bcm.setPwm0Freq(3 * 800000)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// WS281x Output (GPIO 18)
|
||||
// -> bcm2711RegGpfsel1 24:26, regular output; it's configured as alt5 whenever pixel data is sent.
|
||||
// -> bcm283xRegGpfsel1 24:26, regular output; it's configured as alt5 whenever pixel data is sent.
|
||||
// This is not optimal but required as the pwm0 peripheral is shared between fan and data line for the LEDs.
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b010 << 24)
|
||||
bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 24)) | (0b010 << 24)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
defer func() {
|
||||
// Set to regular output again so the PWM signal doesn't confuse the WS2812
|
||||
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24)
|
||||
bcm.gpioMem[bcm283xRegGpfsel1] = (bcm.gpioMem[bcm283xRegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24)
|
||||
bcm.setFanSpeedPWM(bcm.currFanSpeed)
|
||||
}()
|
||||
|
||||
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1) | (1 << bcm2711RegPwmCtlBitClrf1)
|
||||
bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (0 << bcm283xRegPwmCtlBitSbit1) | (1 << bcm283xRegPwmCtlBitUsef1) | (1 << bcm283xRegPwmCtlBitClrf1)
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
// bcm.pwmMem[bcm2711RegPwmRng1] = 32
|
||||
bcm.pwmMem[bcm2711RegPwmRng1] = 24 // we only need 24 bits per LED
|
||||
// bcm.pwmMem[bcm283xRegPwmRng1] = 32
|
||||
bcm.pwmMem[bcm283xRegPwmRng1] = 24 // we only need 24 bits per LED
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
|
||||
// Add sufficient padding to clear 50us of silence with ~412.5ns per bit -> at least 121 bits -> let's be safe and send 6*24=144 bits of silence
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0
|
||||
// Write top LED data
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Red) << 8
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Green) << 8
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(top.Blue) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Red) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Green) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[0].Blue) << 8
|
||||
// Write edge LED data
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Red) << 8
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Green) << 8
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = serializePwmDataFrame(edge.Blue) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Red) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Green) << 8
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = serializePwmDataFrame(bcm.leds[1].Blue) << 8
|
||||
// make sure there's >50us of silence
|
||||
bcm.pwmMem[bcm2711RegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further.
|
||||
bcm.pwmMem[bcm283xRegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further.
|
||||
|
||||
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1)
|
||||
bcm.pwmMem[bcm283xRegPwmCtl] = (1 << bcm283xRegPwmCtlBitPwen1) | (1 << bcm283xRegPwmCtlBitMode1) | (1 << bcm283xRegPwmCtlBitRptl1) | (0 << bcm283xRegPwmCtlBitSbit1) | (1 << bcm283xRegPwmCtlBitUsef1)
|
||||
// sleep for 4*50us to ensure the data is sent. This is probably a bit too gracious but does not have a significant impact, so let's be safe data gets out.
|
||||
time.Sleep(200 * time.Microsecond)
|
||||
|
||||
return nil
|
||||
}
|
||||
50
pkg/computeblade/hal/hal_mock.go
Normal file
50
pkg/computeblade/hal/hal_mock.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package hal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// fails if ComputeBladeHalMock does not implement ComputeBladeHal
|
||||
var _ ComputeBladeHal = &ComputeBladeHalMock{}
|
||||
|
||||
// ComputeBladeMock implements a mock for the ComputeBladeHal interface
|
||||
type ComputeBladeHalMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) Close() error {
|
||||
args := m.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) SetFanSpeed(percent uint8) error {
|
||||
args := m.Called(percent)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) GetFanRPM() (float64, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(float64), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) SetStealthMode(enabled bool) error {
|
||||
args := m.Called(enabled)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) GetPowerStatus() (PowerStatus, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(PowerStatus), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) WaitForEdgeButtonPress(ctx context.Context) error {
|
||||
args := m.Called(ctx)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *ComputeBladeHalMock) SetLed(idx uint, color LedColor) error {
|
||||
args := m.Called(idx, color)
|
||||
return args.Error(0)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package bcm2711
|
||||
//go:build linux
|
||||
package hal
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,54 +0,0 @@
|
||||
package hal
|
||||
|
||||
type FanUnit uint8
|
||||
type ComputeModule uint8
|
||||
type PowerStatus uint8
|
||||
|
||||
func (p PowerStatus) String() string {
|
||||
switch p {
|
||||
case PowerPoe802at:
|
||||
return "poe802at"
|
||||
case PowerPoeOrUsbC:
|
||||
return "poeOrUsbC"
|
||||
default:
|
||||
return "undefined"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
FanUnitStandard = iota
|
||||
FanUnitSmart
|
||||
)
|
||||
|
||||
const (
|
||||
PowerPoeOrUsbC = iota
|
||||
PowerPoe802at
|
||||
)
|
||||
|
||||
type LedColor struct {
|
||||
Red uint8
|
||||
Green uint8
|
||||
Blue uint8
|
||||
}
|
||||
|
||||
type ComputeBladeHalOptsDefault struct {
|
||||
StealthModeEnabled bool
|
||||
FanSpeed uint8
|
||||
TopLedColor LedColor
|
||||
EdgeLedColor LedColor
|
||||
}
|
||||
|
||||
type ComputeBladeHalOpts struct {
|
||||
FanUnit FanUnit
|
||||
Defaults ComputeBladeHalOptsDefault
|
||||
}
|
||||
|
||||
// COmputeBladeHal abstracts hardware details of the Compute Blade and provides a simple interface
|
||||
type ComputeBladeHal interface {
|
||||
Init()
|
||||
Close() error
|
||||
SetFanSpeed(speed uint8)
|
||||
SetStealthMode(enabled bool)
|
||||
GetPowerStatus() PowerStatus
|
||||
SetLEDs(top, edge LedColor)
|
||||
}
|
||||
Reference in New Issue
Block a user