From 7cffa7f1e553e3a985d1c2a112b54fde7cfda396 Mon Sep 17 00:00:00 2001 From: Claes Jakobsson Date: Fri, 10 Jul 2015 18:58:07 +0200 Subject: [PATCH 1/3] On some platform spidev devices has a name whos minor is > 255. Use an int instead of byte to set ID --- host/bbb/bbb.go | 2 +- host/generic/spibus.go | 4 ++-- host/rpi/rpi.go | 2 +- spidriver.go | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/host/bbb/bbb.go b/host/bbb/bbb.go index f909627..1f42d7d 100644 --- a/host/bbb/bbb.go +++ b/host/bbb/bbb.go @@ -95,7 +95,7 @@ var ledMap = embd.LEDMap{ "beaglebone:green:usr3": []string{"3", "USR3", "usr3"}, } -var spiDeviceMinor byte = 1 +var spiDeviceMinor int = 1 func ensureFeatureEnabled(id string) error { glog.V(3).Infof("bbb: enabling feature %v", id) diff --git a/host/generic/spibus.go b/host/generic/spibus.go index f682daa..0799b5c 100644 --- a/host/generic/spibus.go +++ b/host/generic/spibus.go @@ -41,7 +41,7 @@ type spiIOCTransfer struct { type spiBus struct { file *os.File - spiDevMinor byte + spiDevMinor int channel byte mode byte @@ -61,7 +61,7 @@ func spiIOCMessageN(n uint32) uint32 { return (spiIOCMessage0 + (n * spiIOCIncrementor)) } -func NewSPIBus(spiDevMinor, mode, channel byte, speed, bpw, delay int, i func() error) embd.SPIBus { +func NewSPIBus(spiDevMinor int, mode, channel byte, speed, bpw, delay int, i func() error) embd.SPIBus { return &spiBus{ spiDevMinor: spiDevMinor, mode: mode, diff --git a/host/rpi/rpi.go b/host/rpi/rpi.go index 0a82a4b..cdc862d 100644 --- a/host/rpi/rpi.go +++ b/host/rpi/rpi.go @@ -13,7 +13,7 @@ import ( "github.com/kidoman/embd/host/generic" ) -var spiDeviceMinor = byte(0) +var spiDeviceMinor int = 0 var rev1Pins = embd.PinMap{ &embd.PinDesc{ID: "P1_3", Aliases: []string{"0", "GPIO_0", "SDA", "I2C0_SDA"}, Caps: embd.CapDigital | embd.CapI2C, DigitalLogical: 0}, diff --git a/spidriver.go b/spidriver.go index cecc19d..a145155 100644 --- a/spidriver.go +++ b/spidriver.go @@ -2,10 +2,10 @@ package embd import "sync" -type spiBusFactory func(byte, byte, byte, int, int, int, func() error) SPIBus +type spiBusFactory func(int, byte, byte, int, int, int, func() error) SPIBus type spiDriver struct { - spiDevMinor byte + spiDevMinor int initializer func() error busMap map[byte]SPIBus @@ -16,7 +16,7 @@ type spiDriver struct { // NewSPIDriver returns a SPIDriver interface which allows control // over the SPI bus. -func NewSPIDriver(spiDevMinor byte, sbf spiBusFactory, i func() error) SPIDriver { +func NewSPIDriver(spiDevMinor int, sbf spiBusFactory, i func() error) SPIDriver { return &spiDriver{ spiDevMinor: spiDevMinor, sbf: sbf, From ae869cce5b41473214a0e66227c9a2eca07989b3 Mon Sep 17 00:00:00 2001 From: Claes Jakobsson Date: Tue, 8 Sep 2015 14:31:00 +0200 Subject: [PATCH 2/3] Driver for our hardware --- host/botanist_gw/botanist_gw.go | 43 +++++ host/botanist_gw/digitalpin.go | 290 ++++++++++++++++++++++++++++++++ host/botanist_gw/interrupt.go | 126 ++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 host/botanist_gw/botanist_gw.go create mode 100644 host/botanist_gw/digitalpin.go create mode 100644 host/botanist_gw/interrupt.go diff --git a/host/botanist_gw/botanist_gw.go b/host/botanist_gw/botanist_gw.go new file mode 100644 index 0000000..a5f6fa4 --- /dev/null +++ b/host/botanist_gw/botanist_gw.go @@ -0,0 +1,43 @@ +/* + Package botaist_gw provides Botanist GW support. + The following features are supported on Linux kernel 3.8+ + + GPIO (digital (rw)) + IC + LED +*/ +package botanist_gw + +import ( + "github.com/kidoman/embd" + "github.com/kidoman/embd/host/generic" +) + +const BotanistGwHost = "botanist_gw" + +var spiDeviceMinor = int(32766) + +var pins = embd.PinMap{ + &embd.PinDesc{ID: "PA27", Aliases: []string{"27", "PORT_ENABLE", }, Caps: embd.CapDigital, DigitalLogical: 27}, + &embd.PinDesc{ID: "PA28", Aliases: []string{"28", "PORT_FAULT", }, Caps: embd.CapDigital, DigitalLogical: 28}, + &embd.PinDesc{ID: "PA29", Aliases: []string{"29", "PORT_DETECT", }, Caps: embd.CapDigital, DigitalLogical: 29}, +} + +var ledMap = embd.LEDMap{ +} + +func init() { + embd.Register(BotanistGwHost, func(rev int) *embd.Descriptor { + return &embd.Descriptor{ + GPIODriver: func() embd.GPIODriver { + return embd.NewGPIODriver(pins, NewDigitalPin, nil, nil) + }, + I2CDriver: func() embd.I2CDriver { + return embd.NewI2CDriver(generic.NewI2CBus) + }, + SPIDriver: func() embd.SPIDriver { + return embd.NewSPIDriver(spiDeviceMinor, generic.NewSPIBus, nil) + }, + } + }) +} diff --git a/host/botanist_gw/digitalpin.go b/host/botanist_gw/digitalpin.go new file mode 100644 index 0000000..78d5505 --- /dev/null +++ b/host/botanist_gw/digitalpin.go @@ -0,0 +1,290 @@ +// Digital IO support. +// This driver requires kernel version 3.8+ and should work uniformly +// across all supported devices. + +package botanist_gw + +import ( + "errors" + "fmt" + "os" + "path" + "strconv" + "time" + + "github.com/kidoman/embd" +) + +type digitalPin struct { + id string + n int + + drv embd.GPIODriver + + dir *os.File + val *os.File + activeLow *os.File + + readBuf []byte + + initialized bool +} + +func NewDigitalPin(pd *embd.PinDesc, drv embd.GPIODriver) embd.DigitalPin { + return &digitalPin{id: pd.ID, n: pd.DigitalLogical, drv: drv, readBuf: make([]byte, 1)} +} + +func (p *digitalPin) N() int { + return p.n +} + +func (p *digitalPin) init() error { + if p.initialized { + return nil + } + + var err error + if err = p.export(); err != nil { + return err + } + if p.dir, err = p.directionFile(); err != nil { + return err + } + if p.val, err = p.valueFile(); err != nil { + return err + } + if p.activeLow, err = p.activeLowFile(); err != nil { + return err + } + + p.initialized = true + + return nil +} + +func (p *digitalPin) export() error { + exporter, err := os.OpenFile("/sys/class/gpio/export", os.O_WRONLY, os.ModeExclusive) + if err != nil { + return err + } + defer exporter.Close() + _, err = exporter.WriteString(strconv.Itoa(p.n)) + return err +} + +func (p *digitalPin) unexport() error { + unexporter, err := os.OpenFile("/sys/class/gpio/unexport", os.O_WRONLY, os.ModeExclusive) + if err != nil { + return err + } + defer unexporter.Close() + _, err = unexporter.WriteString(strconv.Itoa(p.n)) + return err +} + +func (p *digitalPin) basePath() string { + var ctrl string + if p.n < 32 { + ctrl = "A" + } else if p.n >= 32 && p.n < 64 { + ctrl = "B" + } else { + ctrl = "C" + } + + return fmt.Sprintf("/sys/class/gpio/pio%s%v", ctrl, p.n % 32) +} + +func (p *digitalPin) openFile(path string) (*os.File, error) { + return os.OpenFile(path, os.O_RDWR, os.ModeExclusive) +} + +func (p *digitalPin) directionFile() (*os.File, error) { + return p.openFile(path.Join(p.basePath(), "direction")) +} + +func (p *digitalPin) valueFile() (*os.File, error) { + return p.openFile(path.Join(p.basePath(), "value")) +} + +func (p *digitalPin) activeLowFile() (*os.File, error) { + return p.openFile(path.Join(p.basePath(), "active_low")) +} + +func (p *digitalPin) SetDirection(dir embd.Direction) error { + if err := p.init(); err != nil { + return err + } + + str := "in" + if dir == embd.Out { + str = "out" + } + _, err := p.dir.WriteString(str) + return err +} + +func (p *digitalPin) read() (int, error) { + if _, err := p.val.ReadAt(p.readBuf, 0); err != nil { + return 0, err + } + if p.readBuf[0] == 49 { + return 1, nil + } + return 0, nil +} + +func (p *digitalPin) Read() (int, error) { + if err := p.init(); err != nil { + return 0, err + } + + return p.read() +} + +var ( + lowBytes = []byte{48} + highBytes = []byte{49} +) + +func (p *digitalPin) write(val int) error { + bytes := lowBytes + if val == embd.High { + bytes = highBytes + } + _, err := p.val.Write(bytes) + return err +} + +func (p *digitalPin) Write(val int) error { + if err := p.init(); err != nil { + return err + } + + return p.write(val) +} + +func (p *digitalPin) TimePulse(state int) (time.Duration, error) { + if err := p.init(); err != nil { + return 0, err + } + + aroundState := embd.Low + if state == embd.Low { + aroundState = embd.High + } + + // Wait for any previous pulse to end + for { + v, err := p.read() + if err != nil { + return 0, err + } + + if v == aroundState { + break + } + } + + // Wait until ECHO goes high + for { + v, err := p.read() + if err != nil { + return 0, err + } + + if v == state { + break + } + } + + startTime := time.Now() // Record time when ECHO goes high + + // Wait until ECHO goes low + for { + v, err := p.read() + if err != nil { + return 0, err + } + + if v == aroundState { + break + } + } + + return time.Since(startTime), nil // Calculate time lapsed for ECHO to transition from high to low +} + +func (p *digitalPin) ActiveLow(b bool) error { + if err := p.init(); err != nil { + return err + } + + str := "0" + if b { + str = "1" + } + _, err := p.activeLow.WriteString(str) + return err +} + +func (p *digitalPin) PullUp() error { + return errors.New("gpio: not implemented") +} + +func (p *digitalPin) PullDown() error { + return errors.New("gpio: not implemented") +} + +func (p *digitalPin) Close() error { + if err := p.StopWatching(); err != nil { + return err + } + + if err := p.drv.Unregister(p.id); err != nil { + return err + } + + if !p.initialized { + return nil + } + + if err := p.dir.Close(); err != nil { + return err + } + if err := p.val.Close(); err != nil { + return err + } + if err := p.activeLow.Close(); err != nil { + return err + } + if err := p.unexport(); err != nil { + return err + } + + p.initialized = false + + return nil +} + +func (p *digitalPin) setEdge(edge embd.Edge) error { + file, err := p.openFile(path.Join(p.basePath(), "edge")) + if err != nil { + return err + } + defer file.Close() + + _, err = file.Write([]byte(edge)) + return err +} + +func (p *digitalPin) Watch(edge embd.Edge, handler func(embd.DigitalPin)) error { + if err := p.setEdge(edge); err != nil { + return err + } + return registerInterrupt(p, handler) +} + +func (p *digitalPin) StopWatching() error { + return unregisterInterrupt(p) +} diff --git a/host/botanist_gw/interrupt.go b/host/botanist_gw/interrupt.go new file mode 100644 index 0000000..07a6bed --- /dev/null +++ b/host/botanist_gw/interrupt.go @@ -0,0 +1,126 @@ +// Generic Interrupt Pins. + +package botanist_gw + +import ( + "errors" + "fmt" + "sync" + "syscall" + + "github.com/kidoman/embd" +) + +const ( + MaxGPIOInterrupt = 64 +) + +var ErrorPinAlreadyRegistered = errors.New("pin interrupt already registered") + +type interrupt struct { + pin embd.DigitalPin + initialTrigger bool + handler func(embd.DigitalPin) +} + +func (i *interrupt) Signal() { + if !i.initialTrigger { + i.initialTrigger = true + return + } + i.handler(i.pin) +} + +type epollListener struct { + mu sync.Mutex // Guards the following. + fd int + interruptablePins map[int]*interrupt +} + +var epollListenerInstance *epollListener + +func getEpollListenerInstance() *epollListener { + if epollListenerInstance == nil { + epollListenerInstance = initEpollListener() + } + return epollListenerInstance +} + +func initEpollListener() *epollListener { + fd, err := syscall.EpollCreate1(0) + if err != nil { + panic(fmt.Sprintf("Unable to create epoll: %v", err)) + } + listener := &epollListener{fd: fd, interruptablePins: make(map[int]*interrupt)} + + go func() { + var epollEvents [MaxGPIOInterrupt]syscall.EpollEvent + + for { + n, err := syscall.EpollWait(listener.fd, epollEvents[:], -1) + if err != nil { + panic(fmt.Sprintf("EpollWait error: %v", err)) + } + for i := 0; i < n; i++ { + if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok { + irq.Signal() + } + } + } + }() + return listener +} + +func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) error { + l := getEpollListenerInstance() + + pinFd := int(pin.val.Fd()) + + l.mu.Lock() + defer l.mu.Unlock() + + if _, ok := l.interruptablePins[pinFd]; ok { + return ErrorPinAlreadyRegistered + } + + var event syscall.EpollEvent + event.Events = syscall.EPOLLIN | (syscall.EPOLLET & 0xffffffff) | syscall.EPOLLPRI + + if err := syscall.SetNonblock(pinFd, true); err != nil { + return err + } + + event.Fd = int32(pinFd) + + if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_ADD, pinFd, &event); err != nil { + return err + } + + l.interruptablePins[pinFd] = &interrupt{pin: pin, handler: handler} + + return nil +} + +func unregisterInterrupt(pin *digitalPin) error { + l := getEpollListenerInstance() + + pinFd := int(pin.val.Fd()) + + l.mu.Lock() + defer l.mu.Unlock() + + if _, ok := l.interruptablePins[pinFd]; !ok { + return nil + } + + if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_DEL, pinFd, nil); err != nil { + return err + } + + if err := syscall.SetNonblock(pinFd, false); err != nil { + return err + } + + delete(l.interruptablePins, pinFd) + return nil +} From 7a1b198cf416415f926d3765288b4a28eca11aa7 Mon Sep 17 00:00:00 2001 From: Claes Jakobsson Date: Sun, 13 Sep 2015 12:33:35 +0200 Subject: [PATCH 3/3] htdp.Data is already an address so don't take address to that address --- host/generic/i2cbus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host/generic/i2cbus.go b/host/generic/i2cbus.go index dc8e74c..e416c9e 100644 --- a/host/generic/i2cbus.go +++ b/host/generic/i2cbus.go @@ -223,7 +223,7 @@ func (b *i2cBus) WriteToReg(addr, reg byte, value []byte) error { message.addr = uint16(addr) message.flags = 0 message.len = uint16(len(outbuf)) - message.buf = uintptr(unsafe.Pointer(&hdrp.Data)) + message.buf = uintptr(unsafe.Pointer(hdrp.Data)) var packets i2c_rdwr_ioctl_data