// 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