Skip to content

Commit

Permalink
bcm283x DMA improvements (google#209)
Browse files Browse the repository at this point in the history
- Enable DMA driven bitstream.
- Start DMA writing PWM FIFO.
- Round off PWM duty.
  • Loading branch information
simokawa authored and maruel committed Jan 4, 2018
1 parent 765e3e7 commit adee18c
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 23 deletions.
54 changes: 53 additions & 1 deletion host/bcm283x/bcm283xsmoketest/bcm283xsmoketest.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
package bcm283xsmoketest

import (
"encoding/hex"
"errors"
"flag"
"fmt"
"time"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpiostream"
"periph.io/x/periph/host/bcm283x"
)

Expand Down Expand Up @@ -59,7 +61,11 @@ func (s *SmokeTest) Run(f *flag.FlagSet, args []string) error {
if err := s.testPWMbyDMA(pClk, pPWM); err != nil {
return err
}
return s.testPWM(pPWM, pClk)
if err := s.testPWM(pPWM, pClk); err != nil {
return err
}
return s.testStreamIn(pPWM, pClk)
// TODO(simokawa): test StreamOut.
}

// waitForEdge returns a channel that will return one bool, true if a edge was
Expand Down Expand Up @@ -153,6 +159,42 @@ func (s *SmokeTest) testPWM(p1, p2 *loggingPin) error {
return p1.Halt()
}

// testStreamIn tests gpiostream.StreamIn and gpio.PWM.
func (s *SmokeTest) testStreamIn(p1, p2 *loggingPin) error {
const period = 200 * time.Microsecond
fmt.Printf("- Testing StreamIn\n")
defer p2.Halt()
if err := p2.PWM(gpio.DutyHalf, period); err != nil {
return err
}
// Gather 0.1 second of readings at 10kHz sampling rate.
// TODO(maruel): Support >64kb buffer.
b := &gpiostream.BitStreamLSB{
Bits: make(gpiostream.BitsLSB, 1000),
Res: period / 2,
}
if err := p1.StreamIn(gpio.PullDown, b); err != nil {
fmt.Printf("%s\n", hex.EncodeToString(b.Bits))
return err
}

// Sum the bits, it should be close to 50%.
v := 0
for _, x := range b.Bits {
for j := 0; j < 8; j++ {
v += int((x >> uint(j)) & 1)
}
}
fraction := (100 * v) / (8 * len(b.Bits))
fmt.Println("fraction", fraction)
if fraction < 45 || fraction > 55 {
return fmt.Errorf("reading clock lead to %d%% bits On, expected 50%%", fraction)
}

// TODO(maruel): There should be 10 streaks.
return nil
}

//

func printPin(p gpio.PinIO) {
Expand Down Expand Up @@ -192,6 +234,16 @@ func (p *loggingPin) PWM(duty gpio.Duty, period time.Duration) error {
return p.Pin.PWM(duty, period)
}

func (p *loggingPin) StreamIn(pull gpio.Pull, s gpiostream.Stream) error {
fmt.Printf(" %s %s.StreamIn(%s, %s)\n", since(p.start), p, pull, s)
return p.Pin.StreamIn(pull, s)
}

func (p *loggingPin) StreamOut(s gpiostream.Stream) error {
fmt.Printf(" %s %s.StreamOut(%s)\n", since(p.start), p, s)
return p.Pin.StreamOut(s)
}

// ensureConnectivity makes sure they are connected together.
func ensureConnectivity(p1, p2 *loggingPin) error {
if err := p1.In(gpio.PullDown, gpio.NoEdge); err != nil {
Expand Down
45 changes: 40 additions & 5 deletions host/bcm283x/dma.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ package bcm283x
import (
"errors"
"fmt"
"log"
"os"
"strings"
"time"
Expand All @@ -55,6 +56,7 @@ import (

var (
pcmBaseAddr uint32
pwmBaseAddr uint32
dmaMemory *dmaMap
dmaChannel15 *dmaChannel
dmaBufAllocator func(s int) (*videocore.Mem, error) = videocore.Alloc
Expand Down Expand Up @@ -713,6 +715,36 @@ func dmaWriteStreamPCM(p *Pin, w gpiostream.Stream) error {
return nil
}

func dmaWritePWMFIFO() (*dmaChannel, *videocore.Mem, error) {
if dmaMemory == nil {
return nil, nil, errors.New("bcm283x-dma is not initialized; try running as root?")
}
cb, buf, err := allocateCB(32 + 4) // CB + data
if err != nil {
return nil, nil, err
}
u := buf.Uint32()
offsetBytes := uint32(32)
u[offsetBytes/4] = 0x0
physBuf := uint32(buf.PhysAddr())
physBit := physBuf + offsetBytes
dest := pwmBaseAddr + 0x18 // PWM FIFO
if err := cb[0].initBlock(physBit, dest, 4, false, true, false, false, dmaPWM); err != nil {
_ = buf.Close()
return nil, nil, err
}
cb[0].nextCB = physBuf // Loop back to self.

_, ch := pickChannel()
if ch == nil {
_ = buf.Close()
return nil, nil, errors.New("bcm283x-dma: no channel available")
}
ch.startIO(physBuf)

return ch, buf, nil
}

func startPWMbyDMA(p *Pin, rng, data uint32) (*dmaChannel, *videocore.Mem, error) {
if dmaMemory == nil {
return nil, nil, errors.New("bcm283x-dma is not initialized; try running as root?")
Expand Down Expand Up @@ -967,7 +999,9 @@ func dmaWriteStreamDualChannel(p *Pin, w gpiostream.Stream) error {
return err
}

// The first channel must be a full bandwidth one.
// The first channel must be a full bandwidth one. The "light" ones are
// effectively a single one, which means that they are interleaved. If both
// are "light" then the jitter is largely increased.
x, chSet := pickChannel(6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
if chSet == nil {
return errors.New("bcm283x-dma: no channel available")
Expand Down Expand Up @@ -1096,7 +1130,8 @@ func (d *driverDMA) Init() (bool, error) {
if err := pmem.MapAsPOD(uint64(pcmBaseAddr), &pcmMemory); err != nil {
return true, err
}
if err := pmem.MapAsPOD(uint64(baseAddr+0x20C000), &pwmMemory); err != nil {
pwmBaseAddr = baseAddr + 0x20C000
if err := pmem.MapAsPOD(uint64(pwmBaseAddr), &pwmMemory); err != nil {
return true, err
}
if err := pmem.MapAsPOD(uint64(baseAddr+0x101000), &clockMemory); err != nil {
Expand All @@ -1115,12 +1150,12 @@ func (d *driverDMA) Close() error {

func debugDMA() {
for i, ch := range dmaMemory.channels {
fmt.Println(i, ch.cs.String())
log.Println(i, ch.cs.String())
if ch.cs&dmaActive != 0 {
fmt.Printf("%x: %s", ch.cbAddr, ch.GoString())
log.Printf("%x: %s", ch.cbAddr, ch.GoString())
}
}
fmt.Println(15, dmaChannel15.cs.String())
log.Println(15, dmaChannel15.cs.String())
}

func resetDMA(ch int) error {
Expand Down
79 changes: 64 additions & 15 deletions host/bcm283x/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"periph.io/x/periph"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/gpio/gpiostream"
"periph.io/x/periph/host/distro"
"periph.io/x/periph/host/pmem"
"periph.io/x/periph/host/sysfs"
Expand Down Expand Up @@ -407,10 +408,6 @@ func (p *Pin) PWM(duty gpio.Duty, period time.Duration) error {
} else if duty == gpio.DutyMax {
return p.Out(gpio.High)
}
if period < 500*time.Nanosecond {
// High clock rate tends to hang the RPi. Need to investigate more.
return p.wrap(errors.New("period must be at least 500ns"))
}
f := out
useDMA := false
switch p.number {
Expand All @@ -434,17 +431,19 @@ func (p *Pin) PWM(duty gpio.Duty, period time.Duration) error {
if pwmMemory == nil || clockMemory == nil {
return p.wrap(errors.New("bcm283x-dma not initialized; try again as root?"))
}
p.usingClock = true

if useDMA {
minPeriod := 2 * time.Second / time.Duration(pwmDMAFreq)
if period < minPeriod {
return p.wrap(fmt.Errorf("period must be at least %s", minPeriod))
}

// Total cycles in the period
rng := pwmDMAFreq * uint64(period) / uint64(time.Second)
// Pulse width cycles
dat := uint32(rng * uint64(duty) / uint64(gpio.DutyMax))

dat := uint32((rng*uint64(duty) + uint64(gpio.DutyHalf)) / uint64(gpio.DutyMax))
var err error
// TODO(simokawa): Reuse DMA buffer if possible.
if err := p.haltDMA(); err != nil {
if err = p.haltDMA(); err != nil {
return p.wrap(err)
}
// Start clock before DMA starts.
Expand All @@ -455,13 +454,15 @@ func (p *Pin) PWM(duty gpio.Duty, period time.Duration) error {
return p.wrap(err)
}
} else {
// TODO(maruel): Leverage oversampling.
minPeriod := 2 * time.Second / time.Duration(pwmBaseFreq)
if period < minPeriod {
return p.wrap(fmt.Errorf("period must be at least %s", minPeriod))
}
// Total cycles in the period
rng := pwmBaseFreq * uint64(period) / uint64(time.Second)
// Pulse width cycles
dat := uint32(rng * uint64(duty) / uint64(gpio.DutyMax))

if _, _, err := clockMemory.pwm.set(pwmBaseFreq, 1); err != nil {
dat := uint32((rng*uint64(duty) + uint64(gpio.DutyHalf)) / uint64(gpio.DutyMax))
if _, err := setPWMClockSource(); err != nil {
return p.wrap(err)
}
// Bit shift for PWM0 and PWM1
Expand All @@ -479,11 +480,57 @@ func (p *Pin) PWM(duty gpio.Duty, period time.Duration) error {
old := pwmMemory.ctl
pwmMemory.ctl = (old & ^(0xff << shift)) | ((pwm1Enable | pwm1MS) << shift)
}

p.usingClock = true
p.setFunction(f)
return nil
}

// StreamIn implements gpiostream.PinIn.
//
// DMA driven StreamOut is available for GPIO0 to GPIO31 pin and the maximum
// resolution is 200kHz.
func (p *Pin) StreamIn(pull gpio.Pull, s gpiostream.Stream) error {
b, ok := s.(*gpiostream.BitStreamLSB)
if !ok {
return errors.New("bcm283x: other Stream than BitStreamLSB are not implemented")
}
if err := p.In(pull, gpio.NoEdge); err != nil {
return err
}
if err := dmaReadStream(p, b); err != nil {
return p.wrap(err)
}
return nil
}

// StreamOut implements gpiostream.PinOut.
//
// PCM/I2S driven StreamOut is available for GPIO21 pin. The resolution is up to
// 250MHz.
//
// For GPIO0 to GPIO31 except GPIO21 pin, DMA driven StreamOut is available and
// the maximum resolution is 200kHz.
func (p *Pin) StreamOut(s gpiostream.Stream) error {
if err := p.Out(gpio.Low); err != nil {
return err
}
// If the pin is PCM_DOUT, use PCM for much nicer stream and lower memory
// usage.
if p.number == 21 || p.number == 31 {
alt := alt0
if p.number == 31 {
alt = alt2
}
p.setFunction(alt)
if err := dmaWriteStreamPCM(p, s); err != nil {
return p.wrap(err)
}
} else if err := dmaWriteStreamEdges(p, s); err != nil {
return p.wrap(err)
}
return nil
}

// DefaultPull returns the default pull for the pin.
//
// Implements gpio.PinDefaultPull.
Expand Down Expand Up @@ -540,7 +587,7 @@ func (p *Pin) haltClock() error {
return nil
}
}
_, _, err := clockMemory.pwm.set(0, 0)
err := resetPWMClockSource()
return err
}

Expand Down Expand Up @@ -974,3 +1021,5 @@ var _ gpio.PinIO = &Pin{}
var _ gpio.PinIn = &Pin{}
var _ gpio.PinOut = &Pin{}
var _ gpio.PinPWM = &Pin{}
var _ gpiostream.PinIn = &Pin{}
var _ gpiostream.PinOut = &Pin{}
4 changes: 2 additions & 2 deletions host/bcm283x/gpio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,12 @@ func TestPinPWM(t *testing.T) {

clockMemory = &clockMap{}
pwmMemory = &pwmMap{}
if err := p.PWM(gpio.DutyHalf, 499*time.Nanosecond); err == nil || err.Error() != "bcm283x-gpio (C1): period must be at least 500ns" {
if err := p.PWM(gpio.DutyHalf, 9*time.Microsecond); err == nil || err.Error() != "bcm283x-gpio (C1): period must be at least 10µs" {
t.Fatal(err)
}
// TODO(maruel): Fix test.
dmaMemory = &dmaMap{}
if err := p.PWM(gpio.DutyHalf, 500*time.Nanosecond); err == nil || err.Error() != "bcm283x-gpio (C1): can't write to clock divisor CPU register" {
if err := p.PWM(gpio.DutyHalf, 10*time.Microsecond); err == nil || err.Error() != "bcm283x-gpio (C1): can't write to clock divisor CPU register" {
t.Fatal(err)
}
}
Expand Down
28 changes: 28 additions & 0 deletions host/bcm283x/pwm.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"errors"
"fmt"
"time"

"periph.io/x/periph/host/videocore"
)

var (
Expand All @@ -25,6 +27,8 @@ var (
// These clocks are shared with hardware PWM, DMA driven PWM and BitStream.
pwmBaseFreq uint64 = 25 * 1000 * 1000 // 25MHz
pwmDMAFreq uint64 = 200 * 1000 // 200KHz
pwmDMACh *dmaChannel
pwmDMABuf *videocore.Mem
)

// PWENi is used to enable/disable the corresponding channel. Setting this bit
Expand Down Expand Up @@ -234,6 +238,10 @@ func setPWMClockSource() (uint64, error) {
if clockMemory == nil {
return 0, errors.New("subsystem Clock not initialized")
}
if pwmDMACh != nil {
// Already initialized
return pwmDMAFreq, nil
}

// divs * div must fit in rng1 registor.
div := uint32(pwmBaseFreq / pwmDMAFreq)
Expand All @@ -258,5 +266,25 @@ func setPWMClockSource() (uint64, error) {
old := pwmMemory.ctl
pwmMemory.ctl = (old & ^pwmControl(0xff)) | pwm1UseFIFO | pwm1Enable

// Start DMA
if pwmDMACh, pwmDMABuf, err = dmaWritePWMFIFO(); err != nil {
return 0, err
}

return pwmDMAFreq, nil
}

func resetPWMClockSource() error {
if pwmDMACh != nil {
pwmDMACh.reset()
pwmDMACh = nil
}
if pwmDMABuf != nil {
if err := pwmDMABuf.Close(); err != nil {
return err
}
pwmDMABuf = nil
}
_, _, err := clockMemory.pwm.set(0, 0)
return err
}

0 comments on commit adee18c

Please sign in to comment.