Files
compute-blade-agent/pkg/hal/bcm2711/hal_bcm2711.go
Matthias Riegler ddc1dbe1e8 chore: refactoring
Signed-off-by: Matthias Riegler <matthias.riegler@ankorstore.com>
2023-07-10 07:26:18 +02:00

322 lines
11 KiB
Go

package bcm2711
import (
"errors"
"os"
"sync"
"syscall"
"time"
"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
bcm2711FrontButtonPin = 20
bcm2711StealthPin = 21
bcm2711RegPwmTachPin = 13
bcm2711RegGpfsel0 = 0x00
bcm2711RegGpfsel1 = 0x01
bcm2711RegGpfsel2 = 0x02
bcm2711RegPwmCtl = 0x00
bcm2711RegPwmSta = 0x01
bcm2711RegPwmRng1 = 0x04
bcm2711RegPwmFif1 = 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)
bcm2711RegPwmclkCntrl = 0x28
bcm2711RegPwmclkDiv = 0x29
bcm2711RegPwmclkCntrlBitSrcOsc = 0
bcm2711RegPwmclkCntrlBitEnable = 4
)
type bcm2711 struct {
// Config options
opts hal.ComputeBladeHalOpts
wrMutex sync.Mutex
// Keep track of the currently set fanspeed so it can later be restored after setting the ws281x LEDs
currFanSpeed uint8
devmem *os.File
mbox *os.File
gpioMem8 []uint8
gpioMem []uint32
pwmMem8 []uint8
pwmMem []uint32
clkMem8 []uint8
clkMem []uint32
}
func New(opts hal.ComputeBladeHalOpts) (*bcm2711, 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 {
return nil, err
}
// /dev/vcio for ioctl with VC mailbox
mbox, err := os.OpenFile("/dev/vcio", os.O_RDWR|os.O_SYNC, os.ModePerm)
if err != nil {
return nil, err
}
// Setup memory mappings
gpioMem, gpioMem8, err := mmap(devmem, bcm2711GpioAddr, bcm2711PageSize)
if err != nil {
return nil, err
}
pwmMem, pwmMem8, err := mmap(devmem, bcm2711RegPwmAddr, bcm2711PageSize)
if err != nil {
return nil, err
}
clkMem, clkMem8, err := mmap(devmem, bcm2711ClkAddr, bcm2711PageSize)
if err != nil {
return nil, err
}
return &bcm2711{
devmem: devmem,
mbox: mbox,
gpioMem: gpioMem,
gpioMem8: gpioMem8,
pwmMem: pwmMem,
pwmMem8: pwmMem8,
clkMem: clkMem,
clkMem8: clkMem8,
opts: opts,
}, nil
}
// Close cleans all memory mappings
func (bcm *bcm2711) Close() error {
return errors.Join(
syscall.Munmap(bcm.gpioMem8),
syscall.Munmap(bcm.pwmMem8),
syscall.Munmap(bcm.clkMem8),
bcm.devmem.Close(),
bcm.mbox.Close(),
)
}
// Init initialises GPIOs and sets sane defaults
func (bcm *bcm2711) Init() {
bcm.InitGPIO()
bcm.SetFanSpeed(bcm.opts.Defaults.FanSpeed)
bcm.SetStealthMode(bcm.opts.Defaults.StealthModeEnabled)
bcm.SetLEDs(bcm.opts.Defaults.TopLedColor, bcm.opts.Defaults.EdgeLedColor)
}
// InitGPIO initalises GPIO configuration
func (bcm *bcm2711) InitGPIO() {
// based on https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf
bcm.wrMutex.Lock()
defer bcm.wrMutex.Unlock()
// Edge Button (GPIO 20)
// -> bcm2711RegGpfsel2 2:0, input
bcm.gpioMem[bcm2711RegGpfsel2] = (bcm.gpioMem[bcm2711RegGpfsel2] &^ (0b111 << 0)) | (0b000 << 0)
// PoE at detection (GPIO 23)
// -> bcm2711RegGpfsel2 2:0, input
bcm.gpioMem[bcm2711RegGpfsel2] = (bcm.gpioMem[bcm2711RegGpfsel2] &^ (0b111 << 9)) | (0b000 << 9)
// Stealth Mode Output (GPIO 21)
// -> bcm2711RegGpfsel2 5:3, output
bcm.gpioMem[bcm2711RegGpfsel2] = (bcm.gpioMem[bcm2711RegGpfsel2] &^ (0b111 << 3)) | (0b001 << 3)
// FAN PWM output for standard fan unit (GPIO 12)
if bcm.opts.FanUnit == hal.FanUnitStandard {
// -> bcm2711RegGpfsel1 8:6, alt0
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 6)) | (0b100 << 6)
}
// FAN TACH input for standard fan unit (GPIO 13)
if bcm.opts.FanUnit == hal.FanUnitStandard {
// -> bcm2711RegGpfsel1 11:9, input
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 9)) | (0b000 << 9)
}
// Set WS2812 output (GPIO 18)
// -> bcm2711RegGpfsel1 24:26, set as regular output by default. On-demand, it's mapped to pwm0
bcm.gpioMem[bcm2711RegGpfsel1] = (bcm.gpioMem[bcm2711RegGpfsel1] &^ (0b111 << 24)) | (0b001 << 24)
}
func (bcm *bcm2711) GetPoeStatus() {
}
func (bcm *bcm2711) setPwm0Freq(targetFrequency uint64) error {
// Calculate PWM divisor based on target frequency
divisor := 54000000 / targetFrequency
realDivisor := divisor & 0xfff // 12 bits
if divisor != realDivisor {
return errors.New("invalid frequency, max divisor is 4095, calculated divisor is " + string(divisor))
}
// Stop pwm for both channels; this is required to set the new configuration
bcm.pwmMem[bcm2711RegPwmCtl] &^= (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitPwen2)
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))
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 {
time.Sleep(time.Microsecond * 10)
}
// passwd, disabled, source (oscillator)
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (0 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
time.Sleep(time.Microsecond * 10)
bcm.clkMem[bcm2711RegPwmclkDiv] = bcm2711ClkManagerPwd | (uint32(divisor) << 12)
time.Sleep(time.Microsecond * 10)
// Start clock (passwd, enable, source)
bcm.clkMem[bcm2711RegPwmclkCntrl] = bcm2711ClkManagerPwd | (1 << bcm2711RegPwmclkCntrlBitEnable) | (1 << bcm2711RegPwmclkCntrlBitSrcOsc)
time.Sleep(time.Microsecond * 10)
// Start pwm for both channels again
bcm.pwmMem[bcm2711RegPwmCtl] &= (1 << bcm2711RegPwmCtlBitPwen1)
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) {
bcm.setFanSpeedPWM(speed)
}
func (bcm *bcm2711) 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).
// This results in an actual period frequency of 24.8khz, which is within the specifications of Noctua fans.
err := bcm.setPwm0Freq(800000)
if err != nil {
// we know it produces a valid divisor, so this should never happen
panic(err)
}
// Using hardware ticks would offer a better resultion, but this works for now.
var targetvalue uint32 = 0
if speed == 0 {
targetvalue = 0
} else if speed <= 100 {
for i := 0; i <= int((float64(speed)/100.0)*32.0); i++ {
targetvalue |= (1 << i)
}
} else {
targetvalue = ^(uint32(0))
}
// Use fifo, repeat, ...
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (1 << bcm2711RegPwmCtlBitUsef1)
time.Sleep(10 * time.Microsecond)
bcm.pwmMem[bcm2711RegPwmRng1] = 32
time.Sleep(10 * time.Microsecond)
bcm.pwmMem[bcm2711RegPwmFif1] = targetvalue
// Store fan speed for later use
bcm.currFanSpeed = speed
}
func (bcm *bcm2711) SetStealthMode(enable bool) {
bcm.wrMutex.Lock()
defer bcm.wrMutex.Unlock()
if enable {
// set high (bcm2711StealthPin == 21)
bcm.gpioMem[7] = 1 << (bcm2711StealthPin)
} else {
// clear high state (bcm2711StealthPin == 21)
bcm.gpioMem[10] = 1 << (bcm2711StealthPin)
}
}
// serializePwmDataFrame converts a byte to a 24 bit PWM data frame for WS281x LEDs
func serializePwmDataFrame(data uint8) uint32 {
var result uint32 = 0
for i := 7; i >= 0; i-- {
if i != 7 {
result <<= 3
}
if (uint32(data)&(1<<i))>>i == 0 {
result |= 0b100 // -__
} else {
result |= 0b110 // --_
}
}
return result
}
// SetLEDs sets the color of the WS281x LEDs
func (bcm *bcm2711) SetLEDs(top hal.LedColor, edge hal.LedColor) {
bcm.wrMutex.Lock()
defer bcm.wrMutex.Unlock()
// 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.
// 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)
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.setFanSpeedPWM(bcm.currFanSpeed)
}()
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1) | (1 << bcm2711RegPwmCtlBitClrf1)
time.Sleep(10 * time.Microsecond)
// bcm.pwmMem[bcm2711RegPwmRng1] = 32
bcm.pwmMem[bcm2711RegPwmRng1] = 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
// 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
// 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
// make sure there's >50us of silence
bcm.pwmMem[bcm2711RegPwmFif1] = 0 // auto-repeated, so no need to feed the FIFO further.
bcm.pwmMem[bcm2711RegPwmCtl] = (1 << bcm2711RegPwmCtlBitPwen1) | (1 << bcm2711RegPwmCtlBitMode1) | (1 << bcm2711RegPwmCtlBitRptl1) | (0 << bcm2711RegPwmCtlBitSbit1) | (1 << bcm2711RegPwmCtlBitUsef1)
// 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)
}