214 lines
4.9 KiB
Go
214 lines
4.9 KiB
Go
// Copyright 2018 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.
|
|
|
|
package ftdi
|
|
|
|
import (
|
|
"strconv"
|
|
"sync"
|
|
|
|
"periph.io/x/conn/v3/driver/driverreg"
|
|
"periph.io/x/conn/v3/gpio"
|
|
"periph.io/x/conn/v3/gpio/gpioreg"
|
|
"periph.io/x/conn/v3/i2c"
|
|
"periph.io/x/conn/v3/i2c/i2creg"
|
|
"periph.io/x/conn/v3/pin"
|
|
"periph.io/x/conn/v3/pin/pinreg"
|
|
"periph.io/x/conn/v3/spi/spireg"
|
|
"periph.io/x/d2xx"
|
|
)
|
|
|
|
// All enumerates all the connected FTDI devices.
|
|
func All() []Dev {
|
|
drv.mu.Lock()
|
|
defer drv.mu.Unlock()
|
|
out := make([]Dev, len(drv.all))
|
|
copy(out, drv.all)
|
|
return out
|
|
}
|
|
|
|
//
|
|
|
|
// open opens a FTDI device.
|
|
//
|
|
// Must be called with mu held.
|
|
func open(opener func(i int) (d2xx.Handle, d2xx.Err), i int) (Dev, error) {
|
|
h, err := openHandle(opener, i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := h.Init(); err != nil {
|
|
// setupCommon() takes the device in its previous state. It could be in an
|
|
// unexpected state, so try resetting it first.
|
|
if err := h.Reset(); err != nil {
|
|
_ = h.Close()
|
|
return nil, err
|
|
}
|
|
if err := h.Init(); err != nil {
|
|
_ = h.Close()
|
|
return nil, err
|
|
}
|
|
// The second attempt worked.
|
|
}
|
|
// Makes a copy of the handle.
|
|
g := generic{index: i, h: h, name: h.t.String()}
|
|
if i > 0 {
|
|
// When more than one device is present, add "(index)" suffix.
|
|
// TODO(maruel): Using the serial number would be nicer than a number.
|
|
g.name += "(" + strconv.Itoa(i) + ")"
|
|
}
|
|
// Makes a copy of the generic instance.
|
|
switch g.h.t {
|
|
case DevTypeFT232H:
|
|
f, err := newFT232H(g)
|
|
if err != nil {
|
|
_ = h.Close()
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
case DevTypeFT2232H:
|
|
f, err := newFT232H(g)
|
|
if err != nil {
|
|
_ = h.Close()
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
case DevTypeFT232R:
|
|
f, err := newFT232R(g)
|
|
if err != nil {
|
|
_ = h.Close()
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
default:
|
|
return &g, nil
|
|
}
|
|
}
|
|
|
|
// registerDev registers the header and supported buses and ports in the
|
|
// relevant registries.
|
|
func registerDev(d Dev, multi bool) error {
|
|
name := d.String()
|
|
hdr := d.Header()
|
|
|
|
// Register the GPIOs.
|
|
for _, p := range hdr {
|
|
if err := gpioreg.Register(p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !multi {
|
|
// Register shorthands.
|
|
// The "." used here vs the "_" used in pinreg is unfortunate. Investigate
|
|
// a better way.
|
|
prefix := len(name) + 1
|
|
for _, p := range hdr {
|
|
n := p.Name()
|
|
if err := gpioreg.RegisterAlias(n[prefix:], n); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register the header.
|
|
raw := make([][]pin.Pin, len(hdr))
|
|
for i := range hdr {
|
|
raw[i] = []pin.Pin{hdr[i]}
|
|
}
|
|
if err := pinreg.Register(name, raw); err != nil {
|
|
return err
|
|
}
|
|
switch t := d.(type) {
|
|
case *FT232H:
|
|
// Register I²C without pull up.
|
|
if err := i2creg.Register(name, nil, -1, func() (i2c.BusCloser, error) { return t.I2C(gpio.Float) }); err != nil {
|
|
return err
|
|
}
|
|
if err := spireg.Register(name, nil, -1, t.SPI); err != nil {
|
|
return err
|
|
}
|
|
// TODO(maruel): UART
|
|
case *FT232R:
|
|
// TODO(maruel): SPI, UART
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// driver implements driver.Impl.
|
|
type driver struct {
|
|
mu sync.Mutex
|
|
all []Dev
|
|
d2xxOpen func(i int) (d2xx.Handle, d2xx.Err)
|
|
numDevices func() (int, error)
|
|
}
|
|
|
|
func (d *driver) String() string {
|
|
return "ftdi"
|
|
}
|
|
|
|
func (d *driver) Prerequisites() []string {
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) After() []string {
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) Init() (bool, error) {
|
|
num, err := d.numDevices()
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
multi := num > 1
|
|
for i := 0; i < num; i++ {
|
|
// TODO(maruel): Close the device one day. :)
|
|
if dev, err1 := open(d.d2xxOpen, i); err1 == nil {
|
|
d.all = append(d.all, dev)
|
|
if err = registerDev(dev, multi); err != nil {
|
|
return true, err
|
|
}
|
|
} else {
|
|
// Create a shallow broken handle, so the user can learn how to fix the
|
|
// problem.
|
|
//
|
|
// TODO(maruel): On macOS with a FT232R, calling two processes in a row
|
|
// often results in a broken device on the second process. Figure out why
|
|
// and make it more resilient.
|
|
err = err1
|
|
// The serial number is not available so what can be listed is limited.
|
|
// TODO(maruel): Add VID/PID?
|
|
name := "broken#" + strconv.Itoa(i) + ": " + err.Error()
|
|
d.all = append(d.all, &broken{index: i, err: err, name: name})
|
|
}
|
|
}
|
|
return true, err
|
|
}
|
|
|
|
func (d *driver) reset() {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.all = nil
|
|
// open is mocked in tests.
|
|
d.d2xxOpen = d2xx.Open
|
|
// numDevices is mocked in tests.
|
|
d.numDevices = numDevices
|
|
|
|
// The d2xx can hang for up to the timeout under certain circumstances and the
|
|
// Go profiler is not very useful to find the source, so use manual logging
|
|
// to see where time it spent.
|
|
//d.d2xxOpen = func(i int) (d2xx.Handle, int) {
|
|
// h, e := d2xxOpen(i)
|
|
// return &d2xxLoggingHandle{h}, e
|
|
//}
|
|
}
|
|
|
|
func init() {
|
|
if d2xx.Available {
|
|
drv.reset()
|
|
driverreg.MustRegister(&drv)
|
|
}
|
|
}
|
|
|
|
var drv driver
|