build: upgrade to go 1.17 and dependencies
This commit is contained in:
878
vendor/periph.io/x/host/v3/ftdi/dev.go
generated
vendored
Normal file
878
vendor/periph.io/x/host/v3/ftdi/dev.go
generated
vendored
Normal file
@@ -0,0 +1,878 @@
|
||||
// Copyright 2017 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/i2c"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/spi"
|
||||
)
|
||||
|
||||
// Info is the information gathered about the connected FTDI device.
|
||||
//
|
||||
// The data is gathered from the USB descriptor.
|
||||
type Info struct {
|
||||
// Opened is true if the device was successfully opened.
|
||||
Opened bool
|
||||
// Type is the FTDI device type.
|
||||
//
|
||||
// The value can be "FT232H", "FT232R", etc.
|
||||
//
|
||||
// An empty string means the type is unknown.
|
||||
Type string
|
||||
// VenID is the vendor ID from the USB descriptor information. It is expected
|
||||
// to be 0x0403 (FTDI).
|
||||
VenID uint16
|
||||
// DevID is the product ID from the USB descriptor information. It is
|
||||
// expected to be one of 0x6001, 0x6006, 0x6010, 0x6014.
|
||||
DevID uint16
|
||||
}
|
||||
|
||||
// Dev represents one FTDI device.
|
||||
//
|
||||
// There can be multiple FTDI devices connected to a host.
|
||||
//
|
||||
// The device may also export one or multiple of I²C, SPI buses. You need to
|
||||
// either cast into the right hardware, but more simply use the i2creg / spireg
|
||||
// bus/port registries.
|
||||
type Dev interface {
|
||||
// conn.Resource
|
||||
String() string
|
||||
Halt() error
|
||||
|
||||
// Info returns information about an opened device.
|
||||
Info(i *Info)
|
||||
|
||||
// Header returns the GPIO pins exposed on the chip.
|
||||
Header() []gpio.PinIO
|
||||
|
||||
// SetSpeed sets the base clock for all I/O transactions.
|
||||
//
|
||||
// The device defaults to its fastest speed.
|
||||
SetSpeed(f physic.Frequency) error
|
||||
|
||||
// EEPROM returns the EEPROM content.
|
||||
EEPROM(ee *EEPROM) error
|
||||
// WriteEEPROM updates the EEPROM. Must be used carefully.
|
||||
WriteEEPROM(ee *EEPROM) error
|
||||
// EraseEEPROM erases the EEPROM. Must be used carefully.
|
||||
EraseEEPROM() error
|
||||
// UserArea reads and return the EEPROM part that can be used to stored user
|
||||
// defined values.
|
||||
UserArea() ([]byte, error)
|
||||
// WriteUserArea updates the user area in the EEPROM.
|
||||
//
|
||||
// If the length of ua is less than the available space, is it zero extended.
|
||||
WriteUserArea(ua []byte) error
|
||||
}
|
||||
|
||||
// broken represents a device that couldn't be opened correctly.
|
||||
//
|
||||
// It returns an error message to help the user diagnose issues.
|
||||
type broken struct {
|
||||
index int
|
||||
err error
|
||||
name string
|
||||
}
|
||||
|
||||
func (b *broken) String() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
func (b *broken) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *broken) Info(i *Info) {
|
||||
i.Opened = false
|
||||
}
|
||||
|
||||
func (b *broken) Header() []gpio.PinIO {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *broken) SetSpeed(f physic.Frequency) error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *broken) EEPROM(ee *EEPROM) error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *broken) WriteEEPROM(ee *EEPROM) error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *broken) EraseEEPROM() error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
func (b *broken) UserArea() ([]byte, error) {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
func (b *broken) WriteUserArea(ua []byte) error {
|
||||
return b.err
|
||||
}
|
||||
|
||||
// generic represents a generic FTDI device.
|
||||
//
|
||||
// It is used for the models that this package doesn't fully support yet.
|
||||
type generic struct {
|
||||
// Immutable after initialization.
|
||||
index int
|
||||
h *handle
|
||||
name string
|
||||
}
|
||||
|
||||
func (f *generic) String() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
//
|
||||
// This halts all operations going through this device.
|
||||
func (f *generic) Halt() error {
|
||||
return f.h.Reset()
|
||||
}
|
||||
|
||||
// Info returns information about an opened device.
|
||||
func (f *generic) Info(i *Info) {
|
||||
i.Opened = true
|
||||
i.Type = f.h.t.String()
|
||||
i.VenID = f.h.venID
|
||||
i.DevID = f.h.devID
|
||||
}
|
||||
|
||||
// Header returns the GPIO pins exposed on the chip.
|
||||
func (f *generic) Header() []gpio.PinIO {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *generic) SetSpeed(freq physic.Frequency) error {
|
||||
// TODO(maruel): Doc says the actual speed is 16x, confirm.
|
||||
return f.h.SetBaudRate(freq)
|
||||
}
|
||||
|
||||
func (f *generic) EEPROM(ee *EEPROM) error {
|
||||
return f.h.ReadEEPROM(ee)
|
||||
/*
|
||||
if f.ee.Raw == nil {
|
||||
if err := f.h.readEEPROM(&f.ee); err != nil {
|
||||
return nil
|
||||
}
|
||||
if f.ee.Raw == nil {
|
||||
// It's a fresh new device. Devices bought via Adafruit already have
|
||||
// their EEPROM programmed with Adafruit branding but devices sold by
|
||||
// CJMCU are not. Since d2xxGetDeviceInfo() above succeeded, we know the
|
||||
// device type via the USB descriptor, which is sufficient to load the
|
||||
// driver, which permits to program the EEPROM to "bootstrap" it.
|
||||
f.ee.Raw = []byte{}
|
||||
}
|
||||
}
|
||||
*ee = f.ee
|
||||
return nil
|
||||
*/
|
||||
}
|
||||
|
||||
func (f *generic) WriteEEPROM(ee *EEPROM) error {
|
||||
// TODO(maruel): Compare with the cached EEPROM, and only update the
|
||||
// different values if needed so reduce the EEPROM wear.
|
||||
// f.h.h.d2xxWriteEE()
|
||||
return f.h.WriteEEPROM(ee)
|
||||
}
|
||||
|
||||
func (f *generic) EraseEEPROM() error {
|
||||
return f.h.EraseEEPROM()
|
||||
}
|
||||
|
||||
func (f *generic) UserArea() ([]byte, error) {
|
||||
return f.h.ReadUA()
|
||||
}
|
||||
|
||||
func (f *generic) WriteUserArea(ua []byte) error {
|
||||
return f.h.WriteUA(ua)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func newFT232H(g generic) (*FT232H, error) {
|
||||
f := &FT232H{
|
||||
generic: g,
|
||||
cbus: gpiosMPSSE{h: g.h, cbus: true},
|
||||
dbus: gpiosMPSSE{h: g.h},
|
||||
c8: invalidPin{num: 16, n: g.name + ".C8"}, // , dp: gpio.PullUp
|
||||
c9: invalidPin{num: 17, n: g.name + ".C9"}, // , dp: gpio.PullUp
|
||||
}
|
||||
f.cbus.init(f.name)
|
||||
f.dbus.init(f.name)
|
||||
|
||||
for i := range f.dbus.pins {
|
||||
f.hdr[i] = &f.dbus.pins[i]
|
||||
}
|
||||
for i := range f.cbus.pins {
|
||||
f.hdr[i+8] = &f.cbus.pins[i]
|
||||
}
|
||||
// TODO(maruel): C8 and C9 can be used when their mux in the EEPROM is set to
|
||||
// ft232hCBusIOMode.
|
||||
f.hdr[16] = &f.c8
|
||||
f.hdr[17] = &f.c9
|
||||
f.D0 = f.hdr[0]
|
||||
f.D1 = f.hdr[1]
|
||||
f.D2 = f.hdr[2]
|
||||
f.D3 = f.hdr[3]
|
||||
f.D4 = f.hdr[4]
|
||||
f.D5 = f.hdr[5]
|
||||
f.D6 = f.hdr[6]
|
||||
f.D7 = f.hdr[7]
|
||||
f.C0 = f.hdr[8]
|
||||
f.C1 = f.hdr[9]
|
||||
f.C2 = f.hdr[10]
|
||||
f.C3 = f.hdr[11]
|
||||
f.C4 = f.hdr[12]
|
||||
f.C5 = f.hdr[13]
|
||||
f.C6 = f.hdr[14]
|
||||
f.C7 = f.hdr[15]
|
||||
f.C8 = f.hdr[16]
|
||||
f.C9 = f.hdr[17]
|
||||
|
||||
// This function forces all pins as inputs.
|
||||
if err := f.h.InitMPSSE(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.s.c.f = f
|
||||
f.i.f = f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// FT232H represents a FT232H device.
|
||||
//
|
||||
// It implements Dev.
|
||||
//
|
||||
// The FT232H has 1024 bytes output buffer and 1024 bytes input buffer. It
|
||||
// supports 512 bytes USB packets.
|
||||
//
|
||||
// The device can be used in a few different modes, two modes are supported:
|
||||
//
|
||||
// - D0~D3 as a serial protocol (MPSEE), supporting I²C and SPI (and eventually
|
||||
// UART), In this mode, D4~D7 and C0~C7 can be used as synchronized GPIO.
|
||||
//
|
||||
// - D0~D7 as a synchronous 8 bits bit-bang port. In this mode, only a few pins
|
||||
// on CBus are usable in slow mode.
|
||||
//
|
||||
// Each group of pins D0~D7 and C0~C7 can be changed at once in one pass via
|
||||
// DBus() or CBus().
|
||||
//
|
||||
// This enables usage as an 8 bit parallel port.
|
||||
//
|
||||
// Pins C8 and C9 can only be used in 'slow' mode via EEPROM and are currently
|
||||
// not implemented.
|
||||
//
|
||||
// Datasheet
|
||||
//
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf
|
||||
type FT232H struct {
|
||||
generic
|
||||
|
||||
D0 gpio.PinIO // Clock output
|
||||
D1 gpio.PinIO // Data out
|
||||
D2 gpio.PinIO // Data in
|
||||
D3 gpio.PinIO // Chip select
|
||||
D4 gpio.PinIO
|
||||
D5 gpio.PinIO
|
||||
D6 gpio.PinIO
|
||||
D7 gpio.PinIO
|
||||
C0 gpio.PinIO
|
||||
C1 gpio.PinIO
|
||||
C2 gpio.PinIO
|
||||
C3 gpio.PinIO
|
||||
C4 gpio.PinIO
|
||||
C5 gpio.PinIO
|
||||
C6 gpio.PinIO
|
||||
C7 gpio.PinIO
|
||||
C8 gpio.PinIO // Not implemented
|
||||
C9 gpio.PinIO // Not implemented
|
||||
|
||||
hdr [18]gpio.PinIO
|
||||
cbus gpiosMPSSE
|
||||
dbus gpiosMPSSE
|
||||
c8 invalidPin // gpio.PullUp
|
||||
c9 invalidPin // gpio.PullUp
|
||||
|
||||
mu sync.Mutex
|
||||
usingI2C bool
|
||||
usingSPI bool
|
||||
i i2cBus
|
||||
s spiMPSEEPort
|
||||
// TODO(maruel): Technically speaking, a SPI port could be hacked up too in
|
||||
// sync bit-bang but there's less point when MPSEE is available.
|
||||
}
|
||||
|
||||
// Header returns the GPIO pins exposed on the chip.
|
||||
func (f *FT232H) Header() []gpio.PinIO {
|
||||
out := make([]gpio.PinIO, len(f.hdr))
|
||||
copy(out, f.hdr[:])
|
||||
return out
|
||||
}
|
||||
|
||||
func (f *FT232H) SetSpeed(freq physic.Frequency) error {
|
||||
// TODO(maruel): When using MPSEE, use the MPSEE command. If using sync
|
||||
// bit-bang, use SetBaudRate().
|
||||
|
||||
// TODO(maruel): Doc says the actual speed is 16x, confirm.
|
||||
return f.h.SetBaudRate(freq)
|
||||
}
|
||||
|
||||
// CBus sets the values of C0 to C7 in the specified direction and value.
|
||||
//
|
||||
// 0 direction means input, 1 means output.
|
||||
func (f *FT232H) CBus(direction, value byte) error {
|
||||
return f.h.MPSSECBus(direction, value)
|
||||
}
|
||||
|
||||
// DBus sets the values of D0 to d7 in the specified direction and value.
|
||||
//
|
||||
// 0 direction means input, 1 means output.
|
||||
//
|
||||
// This function must be used to set Clock idle level.
|
||||
func (f *FT232H) DBus(direction, value byte) error {
|
||||
return f.h.MPSSEDBus(direction, value)
|
||||
}
|
||||
|
||||
// CBusRead reads the values of C0 to C7.
|
||||
func (f *FT232H) CBusRead() (byte, error) {
|
||||
return f.h.MPSSECBusRead()
|
||||
}
|
||||
|
||||
// DBusRead reads the values of D0 to D7.
|
||||
func (f *FT232H) DBusRead() (byte, error) {
|
||||
return f.h.MPSSEDBusRead()
|
||||
}
|
||||
|
||||
// I2C returns an I²C bus over the AD bus.
|
||||
//
|
||||
// pull can be either gpio.PullUp or gpio.Float. The recommended pull up
|
||||
// resistors are 10kΩ for 100kHz and 2kΩ for 400kHz when using Float. The
|
||||
// GPIO's pull up is 75kΩ, which may require using a lower speed for signal
|
||||
// reliability. Optimal pull up resistor calculation depends on the capacitance.
|
||||
//
|
||||
// It uses D0, D1 and D2.
|
||||
//
|
||||
// D0 is SCL. It must to be pulled up externally.
|
||||
//
|
||||
// D1 and D2 are used for SDA. D1 is the output using open drain, D2 is the
|
||||
// input. D1 and D2 must be wired together and must be pulled up externally.
|
||||
//
|
||||
// It is recommended to set the mode to ‘245 FIFO’ in the EEPROM of the FT232H.
|
||||
//
|
||||
// The FIFO mode is recommended because it allows the ADbus lines to start as
|
||||
// tristate. If the chip starts in the default UART mode, then the ADbus lines
|
||||
// will be in the default UART idle states until the application opens the port
|
||||
// and configures it as MPSSE. Care should also be taken that the RD# input on
|
||||
// ACBUS is not asserted in this initial state as this can cause the FIFO lines
|
||||
// to drive out.
|
||||
func (f *FT232H) I2C(pull gpio.Pull) (i2c.BusCloser, error) {
|
||||
if pull != gpio.PullUp && pull != gpio.Float {
|
||||
return nil, errors.New("d2xx: I²C pull can only be PullUp or Float")
|
||||
}
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingI2C {
|
||||
return nil, errors.New("d2xx: already using I²C")
|
||||
}
|
||||
if f.usingSPI {
|
||||
return nil, errors.New("d2xx: already using SPI")
|
||||
}
|
||||
if err := f.i.setupI2C(pull == gpio.PullUp); err != nil {
|
||||
_ = f.i.stopI2C()
|
||||
return nil, err
|
||||
}
|
||||
return &f.i, nil
|
||||
}
|
||||
|
||||
// SPI returns a SPI port over the AD bus.
|
||||
//
|
||||
// It uses D0, D1, D2 and D3. D0 is the clock, D1 the output (MOSI), D2 is the
|
||||
// input (MISO) and D3 is CS line.
|
||||
func (f *FT232H) SPI() (spi.PortCloser, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingI2C {
|
||||
return nil, errors.New("d2xx: already using I²C")
|
||||
}
|
||||
if f.usingSPI {
|
||||
return nil, errors.New("d2xx: already using SPI")
|
||||
}
|
||||
// Don't mark it as being used yet. It only become used once Connect() is
|
||||
// called.
|
||||
return &f.s, nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func newFT232R(g generic) (*FT232R, error) {
|
||||
f := &FT232R{
|
||||
generic: g,
|
||||
dbus: [...]dbusPinSync{{num: 0}, {num: 1}, {num: 2}, {num: 3}, {num: 4}, {num: 5}, {num: 6}, {num: 7}},
|
||||
cbus: [...]cbusPin{{num: 8, p: gpio.PullUp}, {num: 9, p: gpio.PullUp}, {num: 10, p: gpio.PullUp}, {num: 11, p: gpio.Float}},
|
||||
}
|
||||
// Use the UART names, as this is how all FT232R boards are marked.
|
||||
dnames := [...]string{"TX", "RX", "RTS", "CTS", "DTR", "DSR", "DCD", "RI"}
|
||||
for i := range f.dbus {
|
||||
f.dbus[i].n = f.name + "." + dnames[i]
|
||||
f.dbus[i].bus = f
|
||||
f.hdr[i] = &f.dbus[i]
|
||||
}
|
||||
for i := range f.cbus {
|
||||
f.cbus[i].n = f.name + ".C" + strconv.Itoa(i)
|
||||
f.cbus[i].bus = f
|
||||
f.hdr[i+8] = &f.cbus[i]
|
||||
}
|
||||
f.D0 = f.hdr[0]
|
||||
f.D1 = f.hdr[1]
|
||||
f.D2 = f.hdr[2]
|
||||
f.D3 = f.hdr[3]
|
||||
f.D4 = f.hdr[4]
|
||||
f.D5 = f.hdr[5]
|
||||
f.D6 = f.hdr[6]
|
||||
f.D7 = f.hdr[7]
|
||||
f.TX = f.hdr[0]
|
||||
f.RX = f.hdr[1]
|
||||
f.RTS = f.hdr[2]
|
||||
f.CTS = f.hdr[3]
|
||||
f.DTR = f.hdr[4]
|
||||
f.DSR = f.hdr[5]
|
||||
f.DCD = f.hdr[6]
|
||||
f.RI = f.hdr[7]
|
||||
f.C0 = f.hdr[8]
|
||||
f.C1 = f.hdr[9]
|
||||
f.C2 = f.hdr[10]
|
||||
f.C3 = f.hdr[11]
|
||||
|
||||
// Default to 3MHz.
|
||||
if err := f.h.SetBaudRate(3 * physic.MegaHertz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set all CBus pins as input.
|
||||
if err := f.h.SetBitMode(0, bitModeCbusBitbang); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// And read their value.
|
||||
// TODO(maruel): Sadly this is impossible to know which pin is input or
|
||||
// output, but we could try to guess, as the call above may generate noise on
|
||||
// the line which could interfere with the device connected.
|
||||
var err error
|
||||
if f.cbusnibble, err = f.h.GetBitMode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set all DBus as asynchronous bitbang, everything as input.
|
||||
if err := f.h.SetBitMode(0, bitModeAsyncBitbang); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// And read their value.
|
||||
var b [1]byte
|
||||
if _, err := f.h.ReadAll(context.Background(), b[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.dvalue = b[0]
|
||||
f.s.c.f = f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// FT232R represents a FT232RL/FT232RQ device.
|
||||
//
|
||||
// It implements Dev.
|
||||
//
|
||||
// Not all pins may be physically connected on the header!
|
||||
//
|
||||
// Adafruit's version only has the following pins connected: RX, TX, RTS and
|
||||
// CTS.
|
||||
//
|
||||
// SparkFun's version exports all pins *except* (inexplicably) the CBus ones.
|
||||
//
|
||||
// The FT232R has 128 bytes output buffer and 256 bytes input buffer.
|
||||
//
|
||||
// Pin C4 can only be used in 'slow' mode via EEPROM and is currently not
|
||||
// implemented.
|
||||
//
|
||||
// Datasheet
|
||||
//
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf
|
||||
type FT232R struct {
|
||||
generic
|
||||
|
||||
// Pin and their alias to the Dn pins for user convenience. Each pair points
|
||||
// to the exact same pin.
|
||||
D0, TX gpio.PinIO // Transmit; SPI_MOSI
|
||||
D1, RX gpio.PinIO // Receive; SPI_MISO
|
||||
D2, RTS gpio.PinIO // Request To Send Control Output / Handshake signal; SPI_CLK
|
||||
D3, CTS gpio.PinIO // Clear to Send Control input / Handshake signal; SPI_CS
|
||||
D4, DTR gpio.PinIO // Data Terminal Ready Control Output / Handshake signal
|
||||
D5, DSR gpio.PinIO // Data Set Ready Control Input / Handshake signal
|
||||
D6, DCD gpio.PinIO // Data Carrier Detect Control input
|
||||
D7, RI gpio.PinIO // Ring Indicator Control Input. When remote wake up is enabled in the internal EEPROM taking RI# low can be used to resume the PC USB host controller from suspend.
|
||||
|
||||
// The CBus pins are slower to use, but can drive an high load, like a LED.
|
||||
C0 gpio.PinIO
|
||||
C1 gpio.PinIO
|
||||
C2 gpio.PinIO
|
||||
C3 gpio.PinIO
|
||||
|
||||
dbus [8]dbusPinSync
|
||||
cbus [4]cbusPin
|
||||
hdr [12]gpio.PinIO
|
||||
|
||||
// Mutable.
|
||||
mu sync.Mutex
|
||||
usingSPI bool
|
||||
usingCBus bool
|
||||
s spiSyncPort
|
||||
dmask uint8 // 0 input, 1 output
|
||||
dvalue uint8
|
||||
cbusnibble uint8 // upper nibble is I/O control, lower nibble is values.
|
||||
}
|
||||
|
||||
// Header returns the GPIO pins exposed on the chip.
|
||||
func (f *FT232R) Header() []gpio.PinIO {
|
||||
out := make([]gpio.PinIO, len(f.hdr))
|
||||
copy(out, f.hdr[:])
|
||||
return out
|
||||
}
|
||||
|
||||
// SetDBusMask sets all D0~D7 input or output mode at once.
|
||||
//
|
||||
// mask is the input/output pins to use. A bit value of 0 sets the
|
||||
// corresponding pin to an input, a bit value of 1 sets the corresponding pin
|
||||
// to an output.
|
||||
//
|
||||
// It should be called before calling Tx().
|
||||
func (f *FT232R) SetDBusMask(mask uint8) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingSPI {
|
||||
return errors.New("d2xx: already using SPI")
|
||||
}
|
||||
return f.setDBusMaskLocked(mask)
|
||||
}
|
||||
|
||||
// Tx does synchronized read-then-write on all the D0~D7 GPIOs.
|
||||
//
|
||||
// SetSpeed() determines the pace at which the I/O is done.
|
||||
//
|
||||
// SetDBusMask() determines which bits are interpreted in the w and r byte
|
||||
// slice. w has its significant value masked by 'mask' and r has its
|
||||
// significant value masked by '^mask'.
|
||||
//
|
||||
// Input sample is done *before* updating outputs. So r[0] is sampled before
|
||||
// w[0] is used. The last w byte should be duplicated if an addition read is
|
||||
// desired.
|
||||
//
|
||||
// On the Adafruit cable, only the first 4 bits D0(TX), D1(RX), D2(RTS) and
|
||||
// D3(CTS) are connected. This is just enough to create a full duplex SPI bus!
|
||||
func (f *FT232R) Tx(w, r []byte) error {
|
||||
if len(w) != 0 {
|
||||
if len(r) != 0 && len(w) != len(r) {
|
||||
return errors.New("d2xx: length of buffer w and r must match")
|
||||
}
|
||||
} else if len(r) == 0 {
|
||||
return errors.New("d2xx: at least one of w or r must be passed")
|
||||
}
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingSPI {
|
||||
return errors.New("d2xx: already using SPI")
|
||||
}
|
||||
return f.txLocked(w, r)
|
||||
}
|
||||
|
||||
// SPI returns a SPI port over the first 4 pins.
|
||||
//
|
||||
// It uses D0(TX), D1(RX), D2(RTS) and D3(CTS). D2(RTS) is the clock, D0(TX)
|
||||
// the output (MOSI), D1(RX) is the input (MISO) and D3(CTS) is CS line.
|
||||
func (f *FT232R) SPI() (spi.PortCloser, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingSPI {
|
||||
return nil, errors.New("d2xx: already using SPI")
|
||||
}
|
||||
// Don't mark it as being used yet. It only become used once Connect() is
|
||||
// called.
|
||||
return &f.s, nil
|
||||
}
|
||||
|
||||
// setDBusMaskLocked is the locked version of SetDBusMask.
|
||||
func (f *FT232R) setDBusMaskLocked(mask uint8) error {
|
||||
if mask != f.dmask {
|
||||
if err := f.h.SetBitMode(mask, bitModeAsyncBitbang); err != nil {
|
||||
return err
|
||||
}
|
||||
f.dmask = mask
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FT232R) txLocked(w, r []byte) error {
|
||||
// Investigate FT232R clock issue:
|
||||
// http://developer.intra2net.com/mailarchive/html/libftdi/2010/msg00240.html
|
||||
|
||||
// The FT232R has 128 bytes TX buffer and 256 bytes RX buffer. Chunk into 64
|
||||
// bytes chunks. That's half the buffer size of the TX buffer and permits
|
||||
// pipelining and removes the risk of buffer overrun. This is important
|
||||
// otherwise there's huge gaps due to the USB transmit overhead.
|
||||
// TODO(maruel): Determine what's optimal via experimentation.
|
||||
chunk := 64
|
||||
var scratch [128]byte
|
||||
if len(w) == 0 {
|
||||
// Read only.
|
||||
for i := range scratch {
|
||||
scratch[i] = f.dvalue
|
||||
}
|
||||
for len(r) != 0 {
|
||||
// TODO(maruel): Optimize.
|
||||
c := len(r)
|
||||
if c > chunk {
|
||||
c = chunk
|
||||
}
|
||||
if _, err := f.h.Write(scratch[:c]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.h.ReadAll(context.Background(), r[:c]); err != nil {
|
||||
return err
|
||||
}
|
||||
r = r[c:]
|
||||
}
|
||||
} else if len(r) == 0 {
|
||||
// Write only.
|
||||
// The first write is 128 bytes to fill the buffer.
|
||||
chunk = 128
|
||||
for len(w) != 0 {
|
||||
c := len(w)
|
||||
if c > chunk {
|
||||
c = chunk
|
||||
}
|
||||
if _, err := f.h.Write(w[:c]); err != nil {
|
||||
return err
|
||||
}
|
||||
w = w[c:]
|
||||
chunk = 64
|
||||
}
|
||||
/*
|
||||
// Let the USB drive pace it.
|
||||
if _, err := f.h.Write(w); err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
// R/W.
|
||||
// Always write one 'w' ahead.
|
||||
// The first write is 128 bytes to fill the buffer.
|
||||
chunk = 128
|
||||
cw := len(w)
|
||||
if cw > chunk {
|
||||
cw = chunk
|
||||
}
|
||||
if _, err := f.h.Write(w[:cw]); err != nil {
|
||||
return err
|
||||
}
|
||||
w = w[cw:]
|
||||
chunk = 64
|
||||
for len(r) != 0 {
|
||||
// Read then write.
|
||||
cr := len(r)
|
||||
if cr > chunk {
|
||||
cr = chunk
|
||||
}
|
||||
if _, err := f.h.ReadAll(context.Background(), r[:cr]); err != nil {
|
||||
return err
|
||||
}
|
||||
r = r[cr:]
|
||||
|
||||
cw = len(w)
|
||||
if cw > chunk {
|
||||
cw = chunk
|
||||
}
|
||||
if cw != 0 {
|
||||
if _, err := f.h.Write(w[:cw]); err != nil {
|
||||
return err
|
||||
}
|
||||
w = w[cw:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dbusSyncGPIOFunc implements dbusSync. It returns the function of a GPIO
|
||||
// pin.
|
||||
func (f *FT232R) dbusSyncGPIOFunc(n int) string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.usingSPI {
|
||||
switch n {
|
||||
case 0:
|
||||
return "SPI_MOSI" // TX
|
||||
case 1:
|
||||
return "SPI_MISO" // RX
|
||||
case 2:
|
||||
return "SPI_CLK" // RTS
|
||||
case 3:
|
||||
return "SPI_CS" // CTS
|
||||
}
|
||||
}
|
||||
mask := uint8(1 << uint(n))
|
||||
if f.dmask&mask != 0 {
|
||||
return "Out/" + gpio.Level(f.dvalue&mask != 0).String()
|
||||
}
|
||||
return "In/" + f.dbusSyncReadLocked(n).String()
|
||||
}
|
||||
|
||||
// dbusSyncGPIOIn implements dbusSync.
|
||||
func (f *FT232R) dbusSyncGPIOIn(n int) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
// TODO(maruel): if f.usingSPI && n < 4.
|
||||
mask := uint8(1 << uint(n))
|
||||
if f.dmask&mask == 0 {
|
||||
// Already input.
|
||||
return nil
|
||||
}
|
||||
v := f.dmask &^ mask
|
||||
if err := f.h.SetBitMode(v, bitModeAsyncBitbang); err != nil {
|
||||
return err
|
||||
}
|
||||
f.dmask = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// dbusSyncGPIORead implements dbusSync.
|
||||
func (f *FT232R) dbusSyncGPIORead(n int) gpio.Level {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.dbusSyncReadLocked(n)
|
||||
}
|
||||
|
||||
func (f *FT232R) dbusSyncReadLocked(n int) gpio.Level {
|
||||
// In synchronous mode, to read we must write first to for a sample.
|
||||
b := [1]byte{f.dvalue}
|
||||
if _, err := f.h.Write(b[:]); err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
mask := uint8(1 << uint(n))
|
||||
if _, err := f.h.ReadAll(context.Background(), b[:]); err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
f.dvalue = b[0]
|
||||
return f.dvalue&mask != 0
|
||||
}
|
||||
|
||||
// dbusSyncGPIOOut implements dbusSync.
|
||||
func (f *FT232R) dbusSyncGPIOOut(n int, l gpio.Level) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
mask := uint8(1 << uint(n))
|
||||
if f.dmask&mask != 1 {
|
||||
// Was input.
|
||||
v := f.dmask | mask
|
||||
if err := f.h.SetBitMode(v, bitModeAsyncBitbang); err != nil {
|
||||
return err
|
||||
}
|
||||
f.dmask = v
|
||||
}
|
||||
return f.dbusSyncGPIOOutLocked(n, l)
|
||||
}
|
||||
|
||||
func (f *FT232R) dbusSyncGPIOOutLocked(n int, l gpio.Level) error {
|
||||
b := [1]byte{f.dvalue}
|
||||
if _, err := f.h.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
f.dvalue = b[0]
|
||||
// In synchronous mode, we must read after writing to flush the buffer.
|
||||
if _, err := f.h.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cBusGPIOFunc implements cBusGPIO.
|
||||
func (f *FT232R) cBusGPIOFunc(n int) string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
fmask := uint8(0x10 << uint(n))
|
||||
vmask := uint8(1 << uint(n))
|
||||
if f.cbusnibble&fmask != 0 {
|
||||
return "Out/" + gpio.Level(f.cbusnibble&vmask != 0).String()
|
||||
}
|
||||
return "In/" + f.cBusReadLocked(n).String()
|
||||
}
|
||||
|
||||
// cBusGPIOIn implements cBusGPIO.
|
||||
func (f *FT232R) cBusGPIOIn(n int) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
fmask := uint8(0x10 << uint(n))
|
||||
if f.cbusnibble&fmask == 0 {
|
||||
// Already input.
|
||||
return nil
|
||||
}
|
||||
v := f.cbusnibble &^ fmask
|
||||
if err := f.h.SetBitMode(v, bitModeCbusBitbang); err != nil {
|
||||
return err
|
||||
}
|
||||
f.cbusnibble = v
|
||||
return nil
|
||||
}
|
||||
|
||||
// cBusGPIORead implements cBusGPIO.
|
||||
func (f *FT232R) cBusGPIORead(n int) gpio.Level {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.cBusReadLocked(n)
|
||||
}
|
||||
|
||||
func (f *FT232R) cBusReadLocked(n int) gpio.Level {
|
||||
v, err := f.h.GetBitMode()
|
||||
if err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
f.cbusnibble = v
|
||||
vmask := uint8(1 << uint(n))
|
||||
return f.cbusnibble&vmask != 0
|
||||
}
|
||||
|
||||
// cBusGPIOOut implements cBusGPIO.
|
||||
func (f *FT232R) cBusGPIOOut(n int, l gpio.Level) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
fmask := uint8(0x10 << uint(n))
|
||||
vmask := uint8(1 << uint(n))
|
||||
v := f.cbusnibble | fmask
|
||||
if l {
|
||||
v |= vmask
|
||||
} else {
|
||||
v &^= vmask
|
||||
}
|
||||
if f.cbusnibble == v {
|
||||
// Was already in the right mode.
|
||||
return nil
|
||||
}
|
||||
if err := f.h.SetBitMode(v, bitModeCbusBitbang); err != nil {
|
||||
return err
|
||||
}
|
||||
f.cbusnibble = v
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var _ conn.Resource = Dev(nil)
|
20
vendor/periph.io/x/host/v3/ftdi/doc.go
generated
vendored
Normal file
20
vendor/periph.io/x/host/v3/ftdi/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017 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 implements support for popular FTDI devices.
|
||||
//
|
||||
// The supported devices (FT232h/FT232r) implement support for various
|
||||
// protocols like the GPIO, I²C, SPI, UART, JTAG.
|
||||
//
|
||||
// More details
|
||||
//
|
||||
// See https://periph.io/device/ftdi/ for more details, and how to configure
|
||||
// the host to be able to use this driver.
|
||||
//
|
||||
// Datasheets
|
||||
//
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf
|
||||
//
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232H.pdf
|
||||
package ftdi
|
213
vendor/periph.io/x/host/v3/ftdi/driver.go
generated
vendored
Normal file
213
vendor/periph.io/x/host/v3/ftdi/driver.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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
|
368
vendor/periph.io/x/host/v3/ftdi/eeprom.go
generated
vendored
Normal file
368
vendor/periph.io/x/host/v3/ftdi/eeprom.go
generated
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// EEPROM is the unprocessed EEPROM content.
|
||||
//
|
||||
// The EEPROM is in 3 parts: the defined struct, the 4 strings and the rest
|
||||
// which is used as an 'user area'. The size of the user area depends on the
|
||||
// length of the strings. The user area content is not included in this struct.
|
||||
type EEPROM struct {
|
||||
// Raw is the raw EEPROM content. It excludes the strings.
|
||||
Raw []byte
|
||||
|
||||
// The following condition must be true: len(Manufacturer) + len(Desc) <= 40.
|
||||
Manufacturer string
|
||||
ManufacturerID string
|
||||
Desc string
|
||||
Serial string
|
||||
}
|
||||
|
||||
// Validate checks that the data is good.
|
||||
func (e *EEPROM) Validate() error {
|
||||
// Verify that the values are set correctly.
|
||||
if len(e.Manufacturer) > 40 {
|
||||
return errors.New("ftdi: Manufacturer is too long")
|
||||
}
|
||||
if len(e.ManufacturerID) > 40 {
|
||||
return errors.New("ftdi: ManufacturerID is too long")
|
||||
}
|
||||
if len(e.Desc) > 40 {
|
||||
return errors.New("ftdi: Desc is too long")
|
||||
}
|
||||
if len(e.Serial) > 40 {
|
||||
return errors.New("ftdi: Serial is too long")
|
||||
}
|
||||
if len(e.Manufacturer)+len(e.Desc) > 40 {
|
||||
return errors.New("ftdi: length of Manufacturer plus Desc is too long")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EEPROM) AsHeader() *EEPROMHeader {
|
||||
// sizeof(EEPROMHeader)
|
||||
if len(e.Raw) < 16 {
|
||||
return nil
|
||||
}
|
||||
return (*EEPROMHeader)(unsafe.Pointer(&e.Raw[0]))
|
||||
}
|
||||
|
||||
// AsFT232H returns the Raw data aliased as EEPROMFT232H.
|
||||
func (e *EEPROM) AsFT232H() *EEPROMFT232H {
|
||||
// sizeof(EEPROMFT232H)
|
||||
if len(e.Raw) < 44 {
|
||||
return nil
|
||||
}
|
||||
return (*EEPROMFT232H)(unsafe.Pointer(&e.Raw[0]))
|
||||
}
|
||||
|
||||
// AsFT2232H returns the Raw data aliased as EEPROMFT2232H.
|
||||
func (e *EEPROM) AsFT2232H() *EEPROMFT2232H {
|
||||
// sizeof(EEPROMFT2232H)
|
||||
if len(e.Raw) < 40 {
|
||||
return nil
|
||||
}
|
||||
return (*EEPROMFT2232H)(unsafe.Pointer(&e.Raw[0]))
|
||||
}
|
||||
|
||||
// AsFT232R returns the Raw data aliased as EEPROMFT232R.
|
||||
func (e *EEPROM) AsFT232R() *EEPROMFT232R {
|
||||
// sizeof(EEPROMFT232R)
|
||||
if len(e.Raw) < 32 {
|
||||
return nil
|
||||
}
|
||||
return (*EEPROMFT232R)(unsafe.Pointer(&e.Raw[0]))
|
||||
}
|
||||
|
||||
// FT232hCBusMux is stored in the FT232H EEPROM to control each CBus pin.
|
||||
type FT232hCBusMux uint8
|
||||
|
||||
const (
|
||||
// TriSt-PU; Sets in Tristate (pull up) (C0~C6, C8, C9) on 75kΩ.
|
||||
FT232hCBusTristatePullUp FT232hCBusMux = 0x00
|
||||
// TXLED#; Pulses low when transmitting data (C0~C6, C8, C9).
|
||||
FT232hCBusTxLED FT232hCBusMux = 0x01
|
||||
// RXLED#; Pulses low when receiving data (C0~C6, C8, C9).
|
||||
FT232hCBusRxLED FT232hCBusMux = 0x02
|
||||
// TX&RXLED#; Pulses low when either receiving or transmitting data (C0~C6,
|
||||
// C8, C9).
|
||||
FT232hCBusTxRxLED FT232hCBusMux = 0x03
|
||||
// PWREN#; Output is low after the device has been configured by USB, then
|
||||
// high during USB suspend mode (C0~C6, C8, C9).
|
||||
//
|
||||
// Must be used with an external 10kΩ pull up.
|
||||
FT232hCBusPwrEnable FT232hCBusMux = 0x04
|
||||
// SLEEP#; Goes low during USB suspend mode (C0~C6, C8, C9).
|
||||
FT232hCBusSleep FT232hCBusMux = 0x05
|
||||
// DRIVE1; Drives pin to logic 0 (C0~C6, C8, C9).
|
||||
FT232hCBusDrive0 FT232hCBusMux = 0x06
|
||||
// DRIVE1; Drives pin to logic 1 (C0, C5, C6, C8, C9).
|
||||
FT232hCBusDrive1 FT232hCBusMux = 0x07
|
||||
// I/O Mode; CBus bit-bang mode option (C5, C6, C8, C9).
|
||||
FT232hCBusIOMode FT232hCBusMux = 0x08
|
||||
// TXDEN; Tx Data Enable. Used with RS485 level converters to enable the line
|
||||
// driver during data transmit. It is active one bit time before the start
|
||||
// bit up to until the end of the stop bit (C0~C6, C8, C9).
|
||||
FT232hCBusTxdEnable FT232hCBusMux = 0x09
|
||||
// CLK30 30MHz clock output (C0, C5, C6, C8, C9).
|
||||
FT232hCBusClk30 FT232hCBusMux = 0x0A
|
||||
// CLK15 15MHz clock output (C0, C5, C6, C8, C9).
|
||||
FT232hCBusClk15 FT232hCBusMux = 0x0B
|
||||
// CLK7.5 7.5MHz clock output (C0, C5, C6, C8, C9).
|
||||
FT232hCBusClk7_5 FT232hCBusMux = 0x0C
|
||||
)
|
||||
|
||||
const ft232hCBusMuxName = "FT232hCBusTristatePullUpFT232hCBusTxLEDFT232hCBusRxLEDFT232hCBusTxRxLEDFT232hCBusPwrEnableFT232hCBusSleepFT232hCBusDrive0FT232hCBusDrive1FT232hCBusIOModeFT232hCBusTxdEnableFT232hCBusClk30FT232hCBusClk15FT232hCBusClk7_5"
|
||||
|
||||
var fr232hCBusMuxIndex = [...]uint8{0, 24, 39, 54, 71, 90, 105, 121, 137, 153, 172, 187, 202, 218}
|
||||
|
||||
func (f FT232hCBusMux) String() string {
|
||||
if f >= FT232hCBusMux(len(fr232hCBusMuxIndex)-1) {
|
||||
return fmt.Sprintf("FT232hCBusMux(%d)", f)
|
||||
}
|
||||
return ft232hCBusMuxName[fr232hCBusMuxIndex[f]:fr232hCBusMuxIndex[f+1]]
|
||||
}
|
||||
|
||||
// FT232rCBusMux is stored in the FT232R EEPROM to control each CBus pin.
|
||||
type FT232rCBusMux uint8
|
||||
|
||||
const (
|
||||
// TXDEN; Tx Data Enable. Used with RS485 level converters to enable the line
|
||||
// driver during data transmit. It is active one bit time before the start
|
||||
// bit up to until the end of the stop bit (C0~C4).
|
||||
FT232rCBusTxdEnable FT232rCBusMux = 0x00
|
||||
// PWREN#; Output is low after the device has been configured by USB, then
|
||||
// high during USB suspend mode (C0~C4).
|
||||
//
|
||||
// Must be used with an external 10kΩ pull up.
|
||||
FT232rCBusPwrEnable FT232rCBusMux = 0x01
|
||||
// RXLED#; Pulses low when receiving data (C0~C4).
|
||||
FT232rCBusRxLED FT232rCBusMux = 0x02
|
||||
// TXLED#; Pulses low when transmitting data (C0~C4).
|
||||
FT232rCBusTxLED FT232rCBusMux = 0x03
|
||||
// TX&RXLED#; Pulses low when either receiving or transmitting data (C0~C4).
|
||||
FT232rCBusTxRxLED FT232rCBusMux = 0x04
|
||||
// SLEEP# Goes low during USB suspend mode (C0~C4).
|
||||
FT232rCBusSleep FT232rCBusMux = 0x05
|
||||
// CLK48 48Mhz +/-0.7% clock output (C0~C4).
|
||||
FT232rCBusClk48 FT232rCBusMux = 0x06
|
||||
// CLK24 24Mhz clock output (C0~C4).
|
||||
FT232rCBusClk24 FT232rCBusMux = 0x07
|
||||
// CLK12 12Mhz clock output (C0~C4).
|
||||
FT232rCBusClk12 FT232rCBusMux = 0x08
|
||||
// CLK6 6Mhz +/-0.7% clock output (C0~C4).
|
||||
FT232rCBusClk6 FT232rCBusMux = 0x09
|
||||
// CBitBangI/O; CBus bit-bang mode option (C0~C3).
|
||||
FT232rCBusIOMode FT232rCBusMux = 0x0A
|
||||
// BitBangWRn; CBus WR# strobe output (C0~C3).
|
||||
FT232rCBusBitBangWR FT232rCBusMux = 0x0B
|
||||
// BitBangRDn; CBus RD# strobe output (C0~C3).
|
||||
FT232rCBusBitBangRD FT232rCBusMux = 0x0C
|
||||
)
|
||||
|
||||
const ft232rCBusMuxName = "FT232rCBusTxdEnableFT232rCBusPwrEnableFT232rCBusRxLEDFT232rCBusTxLEDFT232rCBusTxRxLEDFT232rCBusSleepFT232rCBusClk48FT232rCBusClk24FT232rCBusClk12FT232rCBusClk6FT232rCBusIOModeFT232rCBusBitBangWRFT232rCBusBitBangRD"
|
||||
|
||||
var ft232rCBusMuxIndex = [...]uint8{0, 19, 38, 53, 68, 85, 100, 115, 130, 145, 159, 175, 194, 213}
|
||||
|
||||
func (f FT232rCBusMux) String() string {
|
||||
if f >= FT232rCBusMux(len(ft232rCBusMuxIndex)-1) {
|
||||
return fmt.Sprintf("FT232rCBusMux(%d)", f)
|
||||
}
|
||||
return ft232rCBusMuxName[ft232rCBusMuxIndex[f]:ft232rCBusMuxIndex[f+1]]
|
||||
}
|
||||
|
||||
// EEPROMHeader is the common header found on FTDI devices.
|
||||
//
|
||||
// It is 16 bytes long.
|
||||
type EEPROMHeader struct {
|
||||
DeviceType DevType // 0x00 FTxxxx device type to be programmed
|
||||
VendorID uint16 // 0x04 Defaults to 0x0403; can be changed
|
||||
ProductID uint16 // 0x06 Defaults to 0x6001 for FT232R, 0x6014 for FT232H, relevant value
|
||||
SerNumEnable uint8 // 0x07 bool Non-zero if serial number to be used
|
||||
Unused0 uint8 // 0x08 For alignment.
|
||||
MaxPower uint16 // 0x0A 0mA < MaxPower <= 500mA
|
||||
SelfPowered uint8 // 0x0C bool 0 = bus powered, 1 = self powered
|
||||
RemoteWakeup uint8 // 0x0D bool 0 = not capable, 1 = capable; RI# low will wake host in 20ms.
|
||||
PullDownEnable uint8 // 0x0E bool Non zero if pull down in suspend enabled
|
||||
Unused1 uint8 // 0x0F For alignment.
|
||||
}
|
||||
|
||||
// EEPROMFT232H is the EEPROM layout of a FT232H device.
|
||||
//
|
||||
// It is 44 bytes long.
|
||||
type EEPROMFT232H struct {
|
||||
EEPROMHeader
|
||||
|
||||
// FT232H specific.
|
||||
ACSlowSlew uint8 // 0x10 bool Non-zero if AC bus pins have slow slew
|
||||
ACSchmittInput uint8 // 0x11 bool Non-zero if AC bus pins are Schmitt input
|
||||
ACDriveCurrent uint8 // 0x12 Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
ADSlowSlew uint8 // 0x13 bool Non-zero if AD bus pins have slow slew
|
||||
ADSchmittInput uint8 // 0x14 bool Non-zero if AD bus pins are Schmitt input
|
||||
ADDriveCurrent uint8 // 0x15 Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
Cbus0 FT232hCBusMux // 0x16
|
||||
Cbus1 FT232hCBusMux // 0x17
|
||||
Cbus2 FT232hCBusMux // 0x18
|
||||
Cbus3 FT232hCBusMux // 0x19
|
||||
Cbus4 FT232hCBusMux // 0x1A
|
||||
Cbus5 FT232hCBusMux // 0x1B
|
||||
Cbus6 FT232hCBusMux // 0x1C
|
||||
Cbus7 FT232hCBusMux // 0x1D C7 is limited a sit can only do 'suspend on C7 low'. Defaults pull down.
|
||||
Cbus8 FT232hCBusMux // 0x1E
|
||||
Cbus9 FT232hCBusMux // 0x1F
|
||||
FT1248Cpol uint8 // 0x20 bool FT1248 clock polarity - clock idle high (true) or clock idle low (false)
|
||||
FT1248Lsb uint8 // 0x21 bool FT1248 data is LSB (true), or MSB (false)
|
||||
FT1248FlowControl uint8 // 0x22 bool FT1248 flow control enable
|
||||
IsFifo uint8 // 0x23 bool Non-zero if Interface is 245 FIFO
|
||||
IsFifoTar uint8 // 0x24 bool Non-zero if Interface is 245 FIFO CPU target
|
||||
IsFastSer uint8 // 0x25 bool Non-zero if Interface is Fast serial
|
||||
IsFT1248 uint8 // 0x26 bool Non-zero if Interface is FT1248
|
||||
PowerSaveEnable uint8 // 0x27 bool Suspect on ACBus7 low.
|
||||
DriverType uint8 // 0x28 bool 0 is D2XX, 1 is VCP
|
||||
Unused2 uint8 // 0x29
|
||||
Unused3 uint16 // 0x30
|
||||
}
|
||||
|
||||
func (e *EEPROMFT232H) Defaults() {
|
||||
// As found on Adafruit device.
|
||||
e.ACDriveCurrent = 4
|
||||
e.ADDriveCurrent = 4
|
||||
e.Cbus0 = FT232hCBusTristatePullUp
|
||||
e.Cbus1 = FT232hCBusTristatePullUp
|
||||
e.Cbus2 = FT232hCBusTristatePullUp
|
||||
e.Cbus3 = FT232hCBusTristatePullUp
|
||||
e.Cbus4 = FT232hCBusTristatePullUp
|
||||
e.Cbus5 = FT232hCBusTristatePullUp
|
||||
e.Cbus6 = FT232hCBusTristatePullUp
|
||||
e.Cbus7 = FT232hCBusTristatePullUp
|
||||
e.Cbus8 = FT232hCBusDrive1
|
||||
e.Cbus9 = FT232hCBusDrive0
|
||||
}
|
||||
|
||||
// EEPROMFT2232H is the EEPROM layout of a FT2232H device.
|
||||
//
|
||||
// It is 40 bytes long.
|
||||
type EEPROMFT2232H struct {
|
||||
EEPROMHeader
|
||||
|
||||
// FT232H specific.
|
||||
ALSlowSlew uint8 // 0x10 bool non-zero if AL pins have slow slew
|
||||
ALSchmittInput uint8 // 0x11 bool non-zero if AL pins are Schmitt input
|
||||
ALDriveCurrent uint8 // 0x12 Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
AHSlowSlew uint8 // 0x13 bool non-zero if AH pins have slow slew
|
||||
AHSchmittInput uint8 // 0x14 bool non-zero if AH pins are Schmitt input
|
||||
AHDriveCurrent uint8 // 0x15 Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
BLSlowSlew uint8 // 0x16 bool non-zero if BL pins have slow slew
|
||||
BLSchmittInput uint8 // 0x17 bool non-zero if BL pins are Schmitt input
|
||||
BLDriveCurrent uint8 // 0x18 Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
BHSlowSlew uint8 // 0x19 bool non-zero if BH pins have slow slew
|
||||
BHSchmittInput uint8 // 0x1A bool non-zero if BH pins are Schmitt input
|
||||
BHDriveCurrent uint8 // 0x1B Valid values are 4mA, 8mA, 12mA, 16mA in 2mA units
|
||||
AIsFifo uint8 // 0x1C bool non-zero if interface is 245 FIFO
|
||||
AIsFifoTar uint8 // 0x1D bool non-zero if interface is 245 FIFO CPU target
|
||||
AIsFastSer uint8 // 0x1E bool non-zero if interface is Fast serial
|
||||
BIsFifo uint8 // 0x1F bool non-zero if interface is 245 FIFO
|
||||
BIsFifoTar uint8 // 0x20 bool non-zero if interface is 245 FIFO CPU target
|
||||
BIsFastSer uint8 // 0x21 bool non-zero if interface is Fast serial
|
||||
PowerSaveEnable uint8 // 0x22 bool non-zero if using BCBUS7 to save power for self-powered designs
|
||||
ADriverType uint8 // 0x23 bool
|
||||
BDriverType uint8 // 0x24 bool
|
||||
Unused2 uint8 // 0x25
|
||||
Unused3 uint16 // 0x26
|
||||
}
|
||||
|
||||
// EEPROMFT232R is the EEPROM layout of a FT232R device.
|
||||
//
|
||||
// It is 32 bytes long.
|
||||
type EEPROMFT232R struct {
|
||||
EEPROMHeader
|
||||
|
||||
// FT232R specific.
|
||||
IsHighCurrent uint8 // 0x10 bool High Drive I/Os; 3mA instead of 1mA (@3.3V)
|
||||
UseExtOsc uint8 // 0x11 bool Use external oscillator
|
||||
InvertTXD uint8 // 0x12 bool
|
||||
InvertRXD uint8 // 0x13 bool
|
||||
InvertRTS uint8 // 0x14 bool
|
||||
InvertCTS uint8 // 0x15 bool
|
||||
InvertDTR uint8 // 0x16 bool
|
||||
InvertDSR uint8 // 0x17 bool
|
||||
InvertDCD uint8 // 0x18 bool
|
||||
InvertRI uint8 // 0x19 bool
|
||||
Cbus0 FT232rCBusMux // 0x1A Default ft232rCBusTxLED
|
||||
Cbus1 FT232rCBusMux // 0x1B Default ft232rCBusRxLED
|
||||
Cbus2 FT232rCBusMux // 0x1C Default ft232rCBusTxdEnable
|
||||
Cbus3 FT232rCBusMux // 0x1D Default ft232rCBusPwrEnable
|
||||
Cbus4 FT232rCBusMux // 0x1E Default ft232rCBusSleep
|
||||
DriverType uint8 // 0x1F bool 0 is D2XX, 1 is VCP
|
||||
}
|
||||
|
||||
func (e *EEPROMFT232R) Defaults() {
|
||||
// As found on Adafruit device.
|
||||
e.Cbus0 = FT232rCBusTxLED
|
||||
e.Cbus1 = FT232rCBusRxLED
|
||||
e.Cbus2 = FT232rCBusTxdEnable
|
||||
e.Cbus3 = FT232rCBusPwrEnable
|
||||
e.Cbus4 = FT232rCBusSleep
|
||||
e.DriverType = 1
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// DevType is the FTDI device type.
|
||||
type DevType uint32
|
||||
|
||||
const (
|
||||
DevTypeFTBM DevType = iota // 0
|
||||
DevTypeFTAM
|
||||
DevTypeFT100AX
|
||||
DevTypeUnknown // 3
|
||||
DevTypeFT2232C
|
||||
DevTypeFT232R // 5
|
||||
DevTypeFT2232H
|
||||
DevTypeFT4232H
|
||||
DevTypeFT232H // 8
|
||||
DevTypeFTXSeries
|
||||
DevTypeFT4222H0
|
||||
DevTypeFT4222H1_2
|
||||
DevTypeFT4222H3
|
||||
DevTypeFT4222Prog
|
||||
DevTypeFT900
|
||||
DevTypeFT930
|
||||
DevTypeFTUMFTPD3A
|
||||
)
|
||||
|
||||
// EEPROMSize returns the size of the EEPROM for this device.
|
||||
func (d DevType) EEPROMSize() int {
|
||||
switch d {
|
||||
case DevTypeFT232H:
|
||||
// sizeof(EEPROMFT232H)
|
||||
return 44
|
||||
case DevTypeFT2232H:
|
||||
// sizeof(EEPROMFT2232H)
|
||||
return 40
|
||||
case DevTypeFT232R:
|
||||
// sizeof(EEPROMFT232R)
|
||||
return 32
|
||||
default:
|
||||
return 256
|
||||
}
|
||||
}
|
||||
|
||||
const devTypeName = "FTBMFTAMFT100AXUnknownFT2232CFT232RFT2232HFT4232HFT232HFTXSeriesFT4222H0FT4222H1/2FT4222H3FT4222ProgFT900FT930FTUMFTPD3A"
|
||||
|
||||
var devTypeIndex = [...]uint8{0, 4, 8, 15, 22, 29, 35, 42, 49, 55, 64, 72, 82, 90, 100, 105, 110, 120}
|
||||
|
||||
func (d DevType) String() string {
|
||||
if d >= DevType(len(devTypeIndex)-1) {
|
||||
d = DevTypeUnknown
|
||||
}
|
||||
return devTypeName[devTypeIndex[d]:devTypeIndex[d+1]]
|
||||
}
|
5
vendor/periph.io/x/host/v3/ftdi/ftdi.go
generated
vendored
Normal file
5
vendor/periph.io/x/host/v3/ftdi/ftdi.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2021 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
|
241
vendor/periph.io/x/host/v3/ftdi/gpio.go
generated
vendored
Normal file
241
vendor/periph.io/x/host/v3/ftdi/gpio.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// Emulate independent GPIOs.
|
||||
|
||||
package ftdi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
// dbusSync is the handler of a synchronous bitbang on DBus.
|
||||
//
|
||||
// More details at:
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_232R-01_Bit_Bang_Mode_Available_For_FT232R_and_Ft245R.pdf
|
||||
type dbusSync interface {
|
||||
dbusSyncGPIOFunc(n int) string
|
||||
dbusSyncGPIOIn(n int) error
|
||||
dbusSyncGPIORead(n int) gpio.Level
|
||||
dbusSyncGPIOOut(n int, l gpio.Level) error
|
||||
}
|
||||
|
||||
// dbusPinSync represents a GPIO on a synchronous bitbang DBus.
|
||||
//
|
||||
// It is immutable and stateless.
|
||||
type dbusPinSync struct {
|
||||
n string
|
||||
num int
|
||||
bus dbusSync
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (s *dbusPinSync) String() string {
|
||||
return s.n
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
func (s *dbusPinSync) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (s *dbusPinSync) Name() string {
|
||||
return s.n
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (s *dbusPinSync) Number() int {
|
||||
return s.num
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (s *dbusPinSync) Function() string {
|
||||
return s.bus.dbusSyncGPIOFunc(s.num)
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (s *dbusPinSync) In(pull gpio.Pull, e gpio.Edge) error {
|
||||
if e != gpio.NoEdge {
|
||||
// We could support it on D5.
|
||||
return errors.New("d2xx: edge triggering is not supported")
|
||||
}
|
||||
if pull != gpio.PullUp && pull != gpio.PullNoChange {
|
||||
// EEPROM has a PullDownEnable flag.
|
||||
return errors.New("d2xx: pull is not supported")
|
||||
}
|
||||
return s.bus.dbusSyncGPIOIn(s.num)
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (s *dbusPinSync) Read() gpio.Level {
|
||||
return s.bus.dbusSyncGPIORead(s.num)
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (s *dbusPinSync) WaitForEdge(t time.Duration) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
func (s *dbusPinSync) DefaultPull() gpio.Pull {
|
||||
// 200kΩ
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf
|
||||
// p. 24
|
||||
return gpio.PullUp
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn.
|
||||
func (s *dbusPinSync) Pull() gpio.Pull {
|
||||
return gpio.PullUp
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (s *dbusPinSync) Out(l gpio.Level) error {
|
||||
return s.bus.dbusSyncGPIOOut(s.num, l)
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
func (s *dbusPinSync) PWM(d gpio.Duty, f physic.Frequency) error {
|
||||
return errors.New("d2xx: not implemented")
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *dbusPinSync) Drive() physic.ElectricCurrent {
|
||||
// optionally 3
|
||||
//return s.bus.ee.DDriveCurrent * physic.MilliAmpere
|
||||
return physic.MilliAmpere
|
||||
}
|
||||
|
||||
func (s *dbusPinSync) SlewLimit() bool {
|
||||
//return s.bus.ee.DSlowSlew
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *dbusPinSync) Hysteresis() bool {
|
||||
//return s.bus.ee.DSchmittInput
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
//
|
||||
|
||||
// cBusGPIO is the handler of a CBus bitbang bus.
|
||||
//
|
||||
// This is an asynchronous mode.
|
||||
//
|
||||
// More details at:
|
||||
// http://www.ftdichip.com/Support/Knowledgebase/index.html?cbusbitbangmode.htm
|
||||
type cBusGPIO interface {
|
||||
cBusGPIOFunc(n int) string
|
||||
cBusGPIOIn(n int) error
|
||||
cBusGPIORead(n int) gpio.Level
|
||||
cBusGPIOOut(n int, l gpio.Level) error
|
||||
}
|
||||
|
||||
// cbusPin represents a GPIO on a CBus bitbang bus.
|
||||
//
|
||||
// It is immutable and stateless.
|
||||
type cbusPin struct {
|
||||
n string
|
||||
num int
|
||||
p gpio.Pull
|
||||
bus cBusGPIO
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (c *cbusPin) String() string {
|
||||
return c.n
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
func (c *cbusPin) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (c *cbusPin) Name() string {
|
||||
return c.n
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (c *cbusPin) Number() int {
|
||||
return c.num
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (c *cbusPin) Function() string {
|
||||
return c.bus.cBusGPIOFunc(c.num)
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (c *cbusPin) In(pull gpio.Pull, e gpio.Edge) error {
|
||||
if e != gpio.NoEdge {
|
||||
// We could support it on D5.
|
||||
return errors.New("d2xx: edge triggering is not supported")
|
||||
}
|
||||
if pull != c.p && pull != gpio.PullNoChange {
|
||||
// EEPROM has a PullDownEnable flag.
|
||||
return errors.New("d2xx: pull is not supported")
|
||||
}
|
||||
return c.bus.cBusGPIOIn(c.num)
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (c *cbusPin) Read() gpio.Level {
|
||||
return c.bus.cBusGPIORead(c.num)
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (c *cbusPin) WaitForEdge(t time.Duration) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
func (c *cbusPin) DefaultPull() gpio.Pull {
|
||||
// 200kΩ
|
||||
// http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf
|
||||
// p. 24
|
||||
return c.p
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn.
|
||||
func (c *cbusPin) Pull() gpio.Pull {
|
||||
return c.p
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (c *cbusPin) Out(l gpio.Level) error {
|
||||
return c.bus.cBusGPIOOut(c.num, l)
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
func (c *cbusPin) PWM(d gpio.Duty, f physic.Frequency) error {
|
||||
return errors.New("d2xx: not implemented")
|
||||
}
|
||||
|
||||
/*
|
||||
func (c *cbusPin) Drive() physic.ElectricCurrent {
|
||||
// optionally 3
|
||||
//return c.bus.ee.CDriveCurrent * physic.MilliAmpere
|
||||
return physic.MilliAmpere
|
||||
}
|
||||
|
||||
func (c *cbusPin) SlewLimit() bool {
|
||||
//return c.bus.ee.CSlowSlew
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *cbusPin) Hysteresis() bool {
|
||||
//return c.bus.ee.CSchmittInput
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
var _ gpio.PinIO = &dbusPinSync{}
|
||||
var _ gpio.PinIO = &cbusPin{}
|
382
vendor/periph.io/x/host/v3/ftdi/handle.go
generated
vendored
Normal file
382
vendor/periph.io/x/host/v3/ftdi/handle.go
generated
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
// Copyright 2021 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/d2xx"
|
||||
)
|
||||
|
||||
//
|
||||
|
||||
// bitMode is used by SetBitMode to change the chip behavior.
|
||||
type bitMode uint8
|
||||
|
||||
const (
|
||||
// Resets all Pins to their default value
|
||||
bitModeReset bitMode = 0x00
|
||||
// Sets the DBus to asynchronous bit-bang.
|
||||
bitModeAsyncBitbang bitMode = 0x01
|
||||
// Switch to MPSSE mode (FT2232, FT2232H, FT4232H and FT232H).
|
||||
bitModeMpsse bitMode = 0x02
|
||||
// Sets the DBus to synchronous bit-bang (FT232R, FT245R, FT2232, FT2232H,
|
||||
// FT4232H and FT232H).
|
||||
bitModeSyncBitbang bitMode = 0x04
|
||||
// Switch to MCU host bus emulation (FT2232, FT2232H, FT4232H and FT232H).
|
||||
bitModeMcuHost bitMode = 0x08
|
||||
// Switch to fast opto-isolated serial mode (FT2232, FT2232H, FT4232H and
|
||||
// FT232H).
|
||||
bitModeFastSerial bitMode = 0x10
|
||||
// Sets the CBus in 4 bits bit-bang mode (FT232R and FT232H)
|
||||
// In this case, upper nibble controls which pin is output/input, lower
|
||||
// controls which of outputs are high and low.
|
||||
bitModeCbusBitbang bitMode = 0x20
|
||||
// Single Channel Synchronous 245 FIFO mode (FT2232H and FT232H).
|
||||
bitModeSyncFifo bitMode = 0x40
|
||||
)
|
||||
|
||||
// numDevices returns the number of detected devices.
|
||||
func numDevices() (int, error) {
|
||||
num, e := d2xx.CreateDeviceInfoList()
|
||||
if e != 0 {
|
||||
return 0, toErr("GetNumDevices initialization failed", e)
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func openHandle(opener func(i int) (d2xx.Handle, d2xx.Err), i int) (*handle, error) {
|
||||
h, e := opener(i)
|
||||
if e != 0 {
|
||||
return nil, toErr("Open", e)
|
||||
}
|
||||
d := &handle{h: h}
|
||||
t, vid, did, e := h.GetDeviceInfo()
|
||||
if e != 0 {
|
||||
_ = d.Close()
|
||||
return nil, toErr("GetDeviceInfo", e)
|
||||
}
|
||||
d.t = DevType(t)
|
||||
d.venID = vid
|
||||
d.devID = did
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// handle is a thin wrapper around the low level d2xx device handle to make it
|
||||
// more go-idiomatic.
|
||||
//
|
||||
// It also implements many utility functions to help with initialization and
|
||||
// device management.
|
||||
type handle struct {
|
||||
// It is just above 'handle' which directly maps to D2XX function calls.
|
||||
//
|
||||
// Dev converts the int error type into Go native error and handles higher
|
||||
// level functionality like reading and writing to the USB connection.
|
||||
//
|
||||
// The content of the struct is immutable after initialization.
|
||||
h d2xx.Handle
|
||||
t DevType
|
||||
venID uint16
|
||||
devID uint16
|
||||
}
|
||||
|
||||
func (h *handle) Close() error {
|
||||
// Not yet called.
|
||||
return toErr("Close", h.h.Close())
|
||||
}
|
||||
|
||||
// Init is the general setup for common devices.
|
||||
//
|
||||
// It tries first the 'happy path' which doesn't reset the device. By doing so,
|
||||
// the goal is to reduce the amount of glitches on the GPIO pins, on a best
|
||||
// effort basis. On all devices, the GPIOs are still reset as inputs, since
|
||||
// there is no way to determine if each GPIO is an input or output.
|
||||
func (h *handle) Init() error {
|
||||
// Driver: maximum packet size. Note that this clears any data in the buffer,
|
||||
// so it is good to do it immediately after a reset. The 'out' parameter is
|
||||
// ignored.
|
||||
// TODO(maruel): The FT232H doc claims a 512 byte packets support in hi-speed
|
||||
// mode, which means that this would likely be better to use this value.
|
||||
if e := h.h.SetUSBParameters(65536, 0); e != 0 {
|
||||
return toErr("SetUSBParameters", e)
|
||||
}
|
||||
// Driver: Set I/O timeouts to 15 sec. The reason is that we want the
|
||||
// timeouts to be very visible, at least as the driver is being developed.
|
||||
if e := h.h.SetTimeouts(15000, 15000); e != 0 {
|
||||
return toErr("SetTimeouts", e)
|
||||
}
|
||||
// Not sure: Disable event/error characters.
|
||||
if e := h.h.SetChars(0, false, 0, false); e != 0 {
|
||||
return toErr("SetChars", e)
|
||||
}
|
||||
// Not sure: Latency timer at 1ms.
|
||||
if e := h.h.SetLatencyTimer(1); e != 0 {
|
||||
return toErr("SetLatencyTimer", e)
|
||||
}
|
||||
// Not sure: Turn on flow control to synchronize IN requests.
|
||||
if e := h.h.SetFlowControl(); e != 0 {
|
||||
return toErr("SetFlowControl", e)
|
||||
}
|
||||
// Just in case. It's a very small cost.
|
||||
return h.Flush()
|
||||
}
|
||||
|
||||
// Reset resets the device.
|
||||
func (h *handle) Reset() error {
|
||||
if e := h.h.ResetDevice(); e != 0 {
|
||||
return toErr("Reset", e)
|
||||
}
|
||||
if err := h.SetBitMode(0, bitModeReset); err != nil {
|
||||
return err
|
||||
}
|
||||
// USB/driver: Flush any pending read buffer that had been sent by the device
|
||||
// before it reset. Do not return any error there, as the device may spew a
|
||||
// read error right after being initialized.
|
||||
_ = h.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBitMode returns the current bit mode.
|
||||
//
|
||||
// This is device-dependent.
|
||||
func (h *handle) GetBitMode() (byte, error) {
|
||||
l, e := h.h.GetBitMode()
|
||||
if e != 0 {
|
||||
return 0, toErr("GetBitMode", e)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// SetBitMode change the mode of operation of the device.
|
||||
//
|
||||
// mask sets which pins are inputs and outputs for bitModeCbusBitbang.
|
||||
func (h *handle) SetBitMode(mask byte, mode bitMode) error {
|
||||
return toErr("SetBitMode", h.h.SetBitMode(mask, byte(mode)))
|
||||
}
|
||||
|
||||
// Flush flushes any data left in the read buffer.
|
||||
func (h *handle) Flush() error {
|
||||
var buf [128]byte
|
||||
for {
|
||||
p, err := h.Read(buf[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read returns as much as available in the read buffer without blocking.
|
||||
func (h *handle) Read(b []byte) (int, error) {
|
||||
// GetQueueStatus() 60µs is relatively slow compared to Read() 4µs,
|
||||
// but surprisingly if GetQueueStatus() is *not* called, Read()
|
||||
// becomes largely slower (800µs).
|
||||
//
|
||||
// TODO(maruel): This asks for more perf testing before settling on the best
|
||||
// solution.
|
||||
// TODO(maruel): Investigate FT_GetStatus().
|
||||
p, e := h.h.GetQueueStatus()
|
||||
if p == 0 || e != 0 {
|
||||
return int(p), toErr("Read/GetQueueStatus", e)
|
||||
}
|
||||
v := int(p)
|
||||
if v > len(b) {
|
||||
v = len(b)
|
||||
}
|
||||
n, e := h.h.Read(b[:v])
|
||||
return n, toErr("Read", e)
|
||||
}
|
||||
|
||||
// ReadAll blocks to return all the data.
|
||||
//
|
||||
// Similar to ioutil.ReadAll() except that it will stop if the context is
|
||||
// canceled.
|
||||
func (h *handle) ReadAll(ctx context.Context, b []byte) (int, error) {
|
||||
// TODO(maruel): Use FT_SetEventNotification() instead of looping when
|
||||
// waiting for bytes.
|
||||
for offset := 0; offset != len(b); {
|
||||
if ctx.Err() != nil {
|
||||
return offset, io.EOF
|
||||
}
|
||||
chunk := len(b) - offset
|
||||
if chunk > 4096 {
|
||||
chunk = 4096
|
||||
}
|
||||
n, err := h.Read(b[offset : offset+chunk])
|
||||
if offset += n; err != nil {
|
||||
return offset, err
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// WriteFast writes to the USB device.
|
||||
//
|
||||
// In practice this takes at least 0.1ms, which limits the effective rate.
|
||||
//
|
||||
// There's no guarantee that the data is all written, so it is important to
|
||||
// check the return value.
|
||||
func (h *handle) WriteFast(b []byte) (int, error) {
|
||||
n, e := h.h.Write(b)
|
||||
return n, toErr("Write", e)
|
||||
}
|
||||
|
||||
// Write blocks until all data is written.
|
||||
func (h *handle) Write(b []byte) (int, error) {
|
||||
for offset := 0; offset != len(b); {
|
||||
chunk := len(b) - offset
|
||||
if chunk > 4096 {
|
||||
chunk = 4096
|
||||
}
|
||||
p, err := h.WriteFast(b[offset : offset+chunk])
|
||||
if err != nil {
|
||||
return offset + p, err
|
||||
}
|
||||
if p != 0 {
|
||||
offset += p
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// ReadEEPROM reads the EEPROM.
|
||||
func (h *handle) ReadEEPROM(ee *EEPROM) error {
|
||||
// The raw data size must be exactly what the device contains.
|
||||
eepromSize := h.t.EEPROMSize()
|
||||
if len(ee.Raw) < eepromSize {
|
||||
ee.Raw = make([]byte, eepromSize)
|
||||
} else if len(ee.Raw) > eepromSize {
|
||||
ee.Raw = ee.Raw[:eepromSize]
|
||||
}
|
||||
ee2 := d2xx.EEPROM{Raw: ee.Raw}
|
||||
e := h.h.EEPROMRead(uint32(h.t), &ee2)
|
||||
ee.Manufacturer = ee2.Manufacturer
|
||||
ee.ManufacturerID = ee2.ManufacturerID
|
||||
ee.Desc = ee2.Desc
|
||||
ee.Serial = ee2.Serial
|
||||
if e != 0 {
|
||||
// 15 == FT_EEPROM_NOT_PROGRAMMED
|
||||
if e != 15 {
|
||||
return toErr("EEPROMRead", e)
|
||||
}
|
||||
// It's a fresh new device. Devices bought via Adafruit already have
|
||||
// their EEPROM programmed with Adafruit branding but fake devices sold by
|
||||
// CJMCU are not. Since GetDeviceInfo() above succeeded, we know the
|
||||
// device type via the USB descriptor, which is sufficient to load the
|
||||
// driver, which permits to program the EEPROM to "bootstrap" it.
|
||||
//
|
||||
// Fill it with an empty yet valid EEPROM content. We don't want to set
|
||||
// VenID or DevID to 0! Nobody would do that, right?
|
||||
ee.Raw = make([]byte, h.t.EEPROMSize())
|
||||
hdr := ee.AsHeader()
|
||||
hdr.DeviceType = h.t
|
||||
hdr.VendorID = h.venID
|
||||
hdr.ProductID = h.devID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEEPROM programs the EEPROM.
|
||||
func (h *handle) WriteEEPROM(ee *EEPROM) error {
|
||||
if err := ee.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ee.Raw) != 0 {
|
||||
hdr := ee.AsHeader()
|
||||
if hdr == nil {
|
||||
return errors.New("ftdi: unexpected EEPROM header size")
|
||||
}
|
||||
if hdr.DeviceType != h.t {
|
||||
return errors.New("ftdi: unexpected device type set while programming EEPROM")
|
||||
}
|
||||
if hdr.VendorID != h.venID {
|
||||
return errors.New("ftdi: unexpected VenID set while programming EEPROM")
|
||||
}
|
||||
if hdr.ProductID != h.devID {
|
||||
return errors.New("ftdi: unexpected DevID set while programming EEPROM")
|
||||
}
|
||||
}
|
||||
ee2 := d2xx.EEPROM{
|
||||
Raw: ee.Raw,
|
||||
Manufacturer: ee.Manufacturer,
|
||||
ManufacturerID: ee.ManufacturerID,
|
||||
Desc: ee.Desc,
|
||||
Serial: ee.Serial,
|
||||
}
|
||||
return toErr("EEPROMWrite", h.h.EEPROMProgram(&ee2))
|
||||
}
|
||||
|
||||
// EraseEEPROM erases all the EEPROM.
|
||||
//
|
||||
// Will fail on FT232R and FT245R.
|
||||
func (h *handle) EraseEEPROM() error {
|
||||
return toErr("EraseEE", h.h.EraseEE())
|
||||
}
|
||||
|
||||
// ReadUA reads the EEPROM user area.
|
||||
//
|
||||
// May return nil when there's nothing programmed yet.
|
||||
func (h *handle) ReadUA() ([]byte, error) {
|
||||
size, e := h.h.EEUASize()
|
||||
if e != 0 {
|
||||
return nil, toErr("EEUASize", e)
|
||||
}
|
||||
if size == 0 {
|
||||
// Happens on uninitialized EEPROM.
|
||||
return nil, nil
|
||||
}
|
||||
b := make([]byte, size)
|
||||
if e := h.h.EEUARead(b); e != 0 {
|
||||
return nil, toErr("EEUARead", e)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// WriteUA writes to the EEPROM user area.
|
||||
func (h *handle) WriteUA(ua []byte) error {
|
||||
size, e := h.h.EEUASize()
|
||||
if e != 0 {
|
||||
return toErr("EEUASize", e)
|
||||
}
|
||||
if size == 0 {
|
||||
return errors.New("ftdi: please program EEPROM first")
|
||||
}
|
||||
if size < len(ua) {
|
||||
return fmt.Errorf("ftdi: maximum user area size is %d bytes", size)
|
||||
}
|
||||
if size != len(ua) {
|
||||
b := make([]byte, size)
|
||||
copy(b, ua)
|
||||
ua = b
|
||||
}
|
||||
if e := h.h.EEUAWrite(ua); e != 0 {
|
||||
return toErr("EEUAWrite", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBaudRate sets the baud rate.
|
||||
func (h *handle) SetBaudRate(f physic.Frequency) error {
|
||||
if f >= physic.GigaHertz {
|
||||
return errors.New("ftdi: baud rate too high")
|
||||
}
|
||||
v := uint32(f / physic.Hertz)
|
||||
return toErr("SetBaudRate", h.h.SetBaudRate(v))
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func toErr(s string, e d2xx.Err) error {
|
||||
if e == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New("ftdi: " + s + ": " + e.String())
|
||||
}
|
295
vendor/periph.io/x/host/v3/ftdi/i2c.go
generated
vendored
Normal file
295
vendor/periph.io/x/host/v3/ftdi/i2c.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// This functionality requires MPSSE.
|
||||
//
|
||||
// Interfacing I²C:
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_113_FTDI_Hi_Speed_USB_To_I2C_Example.pdf
|
||||
//
|
||||
// Implementation based on
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_255_USB%20to%20I2C%20Example%20using%20the%20FT232H%20and%20FT201X%20devices.pdf
|
||||
//
|
||||
// Page 18: MPSSE does not automatically support clock stretching for I²C.
|
||||
|
||||
package ftdi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/i2c"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
const i2cSCL = 1 // D0
|
||||
const i2cSDAOut = 2 // D1
|
||||
const i2cSDAIn = 4 // D2
|
||||
|
||||
type i2cBus struct {
|
||||
f *FT232H
|
||||
pullUp bool
|
||||
}
|
||||
|
||||
// Close stops I²C mode, returns to high speed mode, disable tri-state.
|
||||
func (d *i2cBus) Close() error {
|
||||
d.f.mu.Lock()
|
||||
err := d.stopI2C()
|
||||
d.f.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplex implements conn.Conn.
|
||||
func (d *i2cBus) Duplex() conn.Duplex {
|
||||
return conn.Half
|
||||
}
|
||||
|
||||
func (d *i2cBus) String() string {
|
||||
return d.f.String()
|
||||
}
|
||||
|
||||
// SetSpeed implements i2c.Bus.
|
||||
func (d *i2cBus) SetSpeed(f physic.Frequency) error {
|
||||
if f > 10*physic.MegaHertz {
|
||||
return fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 10MHz", f)
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.KiloHertz?", f)
|
||||
}
|
||||
d.f.mu.Lock()
|
||||
defer d.f.mu.Unlock()
|
||||
_, err := d.f.h.MPSSEClock(f * 2 / 3)
|
||||
return err
|
||||
}
|
||||
|
||||
// Tx implements i2c.Bus.
|
||||
func (d *i2cBus) Tx(addr uint16, w, r []byte) error {
|
||||
d.f.mu.Lock()
|
||||
defer d.f.mu.Unlock()
|
||||
if err := d.setI2CStart(); err != nil {
|
||||
return err
|
||||
}
|
||||
a := [1]byte{byte(addr)}
|
||||
if err := d.writeBytes(a[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w) != 0 {
|
||||
if err := d.writeBytes(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
if err := d.readBytes(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.setI2CStop(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.setI2CLinesIdle()
|
||||
}
|
||||
|
||||
// SCL implements i2c.Pins.
|
||||
func (d *i2cBus) SCL() gpio.PinIO {
|
||||
return d.f.D0
|
||||
}
|
||||
|
||||
// SDA implements i2c.Pins.
|
||||
func (d *i2cBus) SDA() gpio.PinIO {
|
||||
return d.f.D1
|
||||
}
|
||||
|
||||
// setupI2C initializes the MPSSE to the state to run an I²C transaction.
|
||||
//
|
||||
// Defaults to 400kHz.
|
||||
//
|
||||
// When pullUp is true; output alternates between Out(Low) and In(PullUp).
|
||||
//
|
||||
// when pullUp is false; pins are set in Tristate so Out(High) becomes float
|
||||
// instead of drive High. Low still drives low. That's called open collector.
|
||||
func (d *i2cBus) setupI2C(pullUp bool) error {
|
||||
if pullUp {
|
||||
return errors.New("d2xx: PullUp will soon be implemented")
|
||||
}
|
||||
// TODO(maruel): We could set these only *during* the I²C operation, which
|
||||
// would make more sense.
|
||||
f := 400 * physic.KiloHertz
|
||||
clk := ((30 * physic.MegaHertz / f) - 1) * 2 / 3
|
||||
|
||||
buf := [4 + 3]byte{
|
||||
clock3Phase,
|
||||
clock30MHz, byte(clk), byte(clk >> 8),
|
||||
}
|
||||
cmd := buf[:4]
|
||||
if !d.pullUp {
|
||||
// TODO(maruel): Do not mess with other GPIOs tristate.
|
||||
cmd = append(cmd, dataTristate, 7, 0)
|
||||
}
|
||||
if _, err := d.f.h.Write(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
d.f.usingI2C = true
|
||||
d.pullUp = pullUp
|
||||
return d.setI2CLinesIdle()
|
||||
}
|
||||
|
||||
// stopI2C resets the MPSSE to a more "normal" state.
|
||||
func (d *i2cBus) stopI2C() error {
|
||||
// Resets to 30MHz.
|
||||
buf := [4 + 3]byte{
|
||||
clock2Phase,
|
||||
clock30MHz, 0, 0,
|
||||
}
|
||||
cmd := buf[:4]
|
||||
if !d.pullUp {
|
||||
// TODO(maruel): Do not mess with other GPIOs tristate.
|
||||
cmd = append(cmd, dataTristate, 0, 0)
|
||||
}
|
||||
_, err := d.f.h.Write(cmd)
|
||||
d.f.usingI2C = false
|
||||
return err
|
||||
}
|
||||
|
||||
// setI2CLinesIdle sets all D0 and D1 lines high.
|
||||
//
|
||||
// Does not touch D3~D7.
|
||||
func (d *i2cBus) setI2CLinesIdle() error {
|
||||
const mask = 0xFF &^ (i2cSCL | i2cSDAOut | i2cSDAIn)
|
||||
// TODO(maruel): d.pullUp
|
||||
d.f.dbus.direction = d.f.dbus.direction&mask | i2cSCL | i2cSDAOut
|
||||
d.f.dbus.value = d.f.dbus.value & mask
|
||||
cmd := [...]byte{gpioSetD, d.f.dbus.value | i2cSCL | i2cSDAOut, d.f.dbus.direction}
|
||||
_, err := d.f.h.Write(cmd[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// setI2CStart starts an I²C transaction.
|
||||
//
|
||||
// Does not touch D3~D7.
|
||||
func (d *i2cBus) setI2CStart() error {
|
||||
// TODO(maruel): d.pullUp
|
||||
dir := d.f.dbus.direction
|
||||
v := d.f.dbus.value
|
||||
// Assumes last setup was d.setI2CLinesIdle(), e.g. D0 and D1 are high, so
|
||||
// skip this.
|
||||
//
|
||||
// Runs the command 4 times as a way to delay execution.
|
||||
cmd := [...]byte{
|
||||
// SCL high, SDA low for 600ns
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
// SCL low, SDA low
|
||||
gpioSetD, v, dir,
|
||||
gpioSetD, v, dir,
|
||||
gpioSetD, v, dir,
|
||||
}
|
||||
_, err := d.f.h.Write(cmd[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// setI2CStop completes an I²C transaction.
|
||||
//
|
||||
// Does not touch D3~D7.
|
||||
func (d *i2cBus) setI2CStop() error {
|
||||
// TODO(maruel): d.pullUp
|
||||
dir := d.f.dbus.direction
|
||||
v := d.f.dbus.value
|
||||
// Runs the command 4 times as a way to delay execution.
|
||||
cmd := [...]byte{
|
||||
// SCL low, SDA low
|
||||
gpioSetD, v, dir,
|
||||
gpioSetD, v, dir,
|
||||
gpioSetD, v, dir,
|
||||
gpioSetD, v, dir,
|
||||
// SCL high, SDA low
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
gpioSetD, v | i2cSCL, dir,
|
||||
// SCL high, SDA high
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
}
|
||||
_, err := d.f.h.Write(cmd[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// writeBytes writes multiple bytes within an I²C transaction.
|
||||
//
|
||||
// Does not touch D3~D7.
|
||||
func (d *i2cBus) writeBytes(w []byte) error {
|
||||
// TODO(maruel): d.pullUp
|
||||
dir := d.f.dbus.direction
|
||||
v := d.f.dbus.value
|
||||
// TODO(maruel): WAT?
|
||||
if err := d.f.h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(maruel): Implement both with and without NAK check.
|
||||
var r [1]byte
|
||||
cmd := [...]byte{
|
||||
// Data out, the 0 will be replaced with the byte.
|
||||
dataOut | dataOutFall, 0, 0, 0,
|
||||
// Set back to idle.
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
// Read ACK/NAK.
|
||||
dataIn | dataBit, 0,
|
||||
flush,
|
||||
}
|
||||
for _, c := range w {
|
||||
cmd[3] = c
|
||||
if _, err := d.f.h.Write(cmd[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := d.f.h.ReadAll(context.Background(), r[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if r[0]&1 == 0 {
|
||||
return errors.New("got NAK")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readBytes reads multiple bytes within an I²C transaction.
|
||||
//
|
||||
// Does not touch D3~D7.
|
||||
func (d *i2cBus) readBytes(r []byte) error {
|
||||
// TODO(maruel): d.pullUp
|
||||
dir := d.f.dbus.direction
|
||||
v := d.f.dbus.value
|
||||
|
||||
cmd := [...]byte{
|
||||
// Read 8 bits.
|
||||
dataIn | dataBit, 7,
|
||||
// Send ACK/NAK.
|
||||
dataOut | dataOutFall | dataBit, 0, 0,
|
||||
// Set back to idle.
|
||||
gpioSetD, v | i2cSCL | i2cSDAOut, dir,
|
||||
// Force read buffer flush. This is only necessary if NAK are not ignored.
|
||||
flush,
|
||||
}
|
||||
for i := range r {
|
||||
if i == len(r)-1 {
|
||||
// NAK.
|
||||
cmd[4] = 0x80
|
||||
}
|
||||
if _, err := d.f.h.Write(cmd[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := d.f.h.ReadAll(context.Background(), r[i:1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ i2c.BusCloser = &i2cBus{}
|
||||
var _ i2c.Pins = &i2cBus{}
|
452
vendor/periph.io/x/host/v3/ftdi/mpsse.go
generated
vendored
Normal file
452
vendor/periph.io/x/host/v3/ftdi/mpsse.go
generated
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// MPSSE is Multi-Protocol Synchronous Serial Engine
|
||||
//
|
||||
// MPSSE basics:
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf
|
||||
//
|
||||
// MPSSE and MCU emulation modes:
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf
|
||||
|
||||
package ftdi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
const (
|
||||
// TDI/TDO serial operation synchronised on clock edges.
|
||||
//
|
||||
// Long streams (default):
|
||||
// - [1, 65536] bytes (length is sent minus one, requires 8 bits multiple)
|
||||
// <op>, <LengthLow-1>, <LengthHigh-1>, <byte0>, ..., <byteN>
|
||||
//
|
||||
// Short streams (dataBit is specified):
|
||||
// - [1, 8] bits
|
||||
// <op>, <Length-1>, <byte>
|
||||
//
|
||||
// When both dataOut and dataIn are specified, one of dataOutFall or
|
||||
// dataInFall should be specified, at least for most sane protocols.
|
||||
//
|
||||
// Flags:
|
||||
dataOut byte = 0x10 // Enable output, default on +VE (Rise)
|
||||
dataIn byte = 0x20 // Enable input, default on +VE (Rise)
|
||||
dataOutFall byte = 0x01 // instead of Rise
|
||||
dataInFall byte = 0x04 // instead of Rise
|
||||
dataLSBF byte = 0x08 // instead of MSBF
|
||||
dataBit byte = 0x02 // instead of Byte
|
||||
|
||||
// Data line drives low when the data is 0 and tristates on data 1. This is
|
||||
// used with I²C.
|
||||
// <op>, <ADBus pins>, <ACBus pins>
|
||||
dataTristate byte = 0x9E
|
||||
|
||||
// TSM operation (for JTAG).
|
||||
//
|
||||
// - Send bits 6 to 0 to the TMS pin using LSB or MSB.
|
||||
// - Bit 7 is passed to TDI/DO before the first clock of TMS and is held
|
||||
// static for the duration of TMS clocking.
|
||||
//
|
||||
// <op>, <Length>, <byte>
|
||||
tmsOutLSBFRise byte = 0x4A
|
||||
tmsOutLSBFFall byte = 0x4B
|
||||
tmsIOLSBInRise byte = 0x6A
|
||||
tmsIOLSBInFall byte = 0x6B
|
||||
// Unclear: 0x6E and 0x6F
|
||||
|
||||
// GPIO operation.
|
||||
//
|
||||
// - Operates on 8 GPIOs at a time, e.g. C0~C7 or D0~D7.
|
||||
// - Direction 1 means output, 0 means input.
|
||||
//
|
||||
// <op>, <value>, <direction>
|
||||
gpioSetD byte = 0x80
|
||||
gpioSetC byte = 0x82
|
||||
// <op>, returns <value>
|
||||
gpioReadD byte = 0x81
|
||||
gpioReadC byte = 0x83
|
||||
|
||||
// Internal loopback.
|
||||
//
|
||||
// Connects TDI and TDO together.
|
||||
internalLoopbackEnable byte = 0x84
|
||||
internalLoopbackDisable byte = 0x85
|
||||
|
||||
// Clock.
|
||||
//
|
||||
// The TCK/SK has a 50% duty cycle.
|
||||
//
|
||||
// The inactive clock state can be set via the gpioSetD command and control
|
||||
// bit 0.
|
||||
//
|
||||
// By default, the base clock is 6MHz via a 5x divisor. On
|
||||
// FT232H/FT2232H/FT4232H, the 5x divisor can be disabled.
|
||||
clock30MHz byte = 0x8A
|
||||
clock6MHz byte = 0x8B
|
||||
// Sets clock divisor.
|
||||
//
|
||||
// The effective value depends if clock30MHz was sent or not.
|
||||
//
|
||||
// - 0(1) 6MHz / 30MHz
|
||||
// - 1(2) 3MHz / 15MHz
|
||||
// - 2(3) 2MHz / 10MHz
|
||||
// - 3(4) 1.5MHz / 7.5MHz
|
||||
// - 4(5) 1.25MHz / 6MHz
|
||||
// - ...
|
||||
// - 0xFFFF(65536) 91.553Hz / 457.763Hz
|
||||
//
|
||||
// <op>, <valueL-1>, <valueH-1>
|
||||
clockSetDivisor byte = 0x86
|
||||
// Uses 3 phases data clocking: data is valid on both clock edges. Needed
|
||||
// for I²C.
|
||||
clock3Phase byte = 0x8C
|
||||
// Uses normal 2 phases data clocking.
|
||||
clock2Phase byte = 0x8D
|
||||
// Enables clock even while not doing any operation. Used with JTAG.
|
||||
// Enables the clock between [1, 8] pulses.
|
||||
// <op>, <length-1>
|
||||
clockOnShort byte = 0x8E
|
||||
// Enables the clock between [8, 524288] pulses in 8 multiples.
|
||||
// <op>, <lengthL-1>, <lengthH-1>
|
||||
clockOnLong byte = 0x8F
|
||||
// Enables clock until D5 is high or low. Used with JTAG.
|
||||
clockUntilHigh byte = 0x94
|
||||
clockUntilLow byte = 0x95
|
||||
// <op>, <lengthL-1>, <lengthH-1> in 8 multiples.
|
||||
clockUntilHighLong byte = 0x9C
|
||||
clockUntilLowLong byte = 0x9D
|
||||
// Enables adaptive clocking. Used with JTAG.
|
||||
//
|
||||
// This causes the controller to wait for D7 signal state as an ACK.
|
||||
clockAdaptive byte = 0x96
|
||||
// Disables adaptive clocking.
|
||||
clockNormal byte = 0x97
|
||||
|
||||
// CPU mode.
|
||||
//
|
||||
// Access the device registers like a memory mapped device.
|
||||
//
|
||||
// <op>, <addrLow>
|
||||
cpuReadShort byte = 0x90
|
||||
// <op>, <addrHi>, <addrLow>
|
||||
cpuReadFar byte = 0x91
|
||||
// <op>, <addrLow>, <data>
|
||||
cpuWriteShort byte = 0x92
|
||||
// <op>, <addrHi>, <addrLow>, <data>
|
||||
cpuWriteFar byte = 0x91
|
||||
|
||||
// Buffer operations.
|
||||
//
|
||||
// Flush the buffer back to the host.
|
||||
flush byte = 0x87
|
||||
// Wait until D5 (JTAG) or I/O1 (CPU) is high. Once it is detected as
|
||||
// high, the MPSSE engine moves on to process the next instruction.
|
||||
waitHigh byte = 0x88
|
||||
waitLow byte = 0x89
|
||||
)
|
||||
|
||||
// InitMPSSE sets the device into MPSSE mode.
|
||||
//
|
||||
// This requires a f232h, ft2232, ft2232h or a ft4232h.
|
||||
//
|
||||
// Use only one of Init or InitMPSSE.
|
||||
func (h *handle) InitMPSSE() error {
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_255_USB%20to%20I2C%20Example%20using%20the%20FT232H%20and%20FT201X%20devices.pdf
|
||||
// Pre-state:
|
||||
// - Write EEPROM i.IsFifo = true so the device DBus is started in tristate.
|
||||
|
||||
// Try to verify the MPSSE controller without initializing it first. This is
|
||||
// the 'happy path', which enables reusing the device is its current state
|
||||
// without affecting current GPIO state.
|
||||
if h.mpsseVerify() != nil {
|
||||
// Do a full reset. Just trying to set the MPSSE controller will
|
||||
// likely not work. That's a layering violation (since the retry with reset
|
||||
// is done in driver.go) but we've survived worse things...
|
||||
//
|
||||
// TODO(maruel): This is not helping in practice, this need to be fine
|
||||
// tuned.
|
||||
if err := h.Reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.SetBitMode(0, bitModeMpsse); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.mpsseVerify(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize MPSSE to a known state.
|
||||
// Reset the clock since it is impossible to read back the current clock rate.
|
||||
// Reset all the GPIOs are inputs since it is impossible to read back the
|
||||
// state of each GPIO (if they are input or output).
|
||||
cmd := []byte{
|
||||
clock30MHz, clockNormal, clock2Phase, internalLoopbackDisable,
|
||||
gpioSetC, 0x00, 0x00,
|
||||
gpioSetD, 0x00, 0x00,
|
||||
}
|
||||
if _, err := h.Write(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
// Success!!
|
||||
return nil
|
||||
}
|
||||
|
||||
// mpsseVerify sends an invalid MPSSE command and verifies the returned value
|
||||
// is incorrect.
|
||||
//
|
||||
// In practice this takes around 2ms.
|
||||
func (h *handle) mpsseVerify() error {
|
||||
var b [16]byte
|
||||
for _, v := range []byte{0xAA, 0xAB} {
|
||||
// Write a bad command and ensure it returned correctly.
|
||||
// Unlike what the application note proposes, include a flush op right
|
||||
// after. Without the flush, the device will only flush after the delay
|
||||
// specified to SetLatencyTimer. The flush removes this unneeded wait,
|
||||
// which enables increasing the delay specified to SetLatencyTimer.
|
||||
b[0] = v
|
||||
b[1] = flush
|
||||
if _, err := h.Write(b[:2]); err != nil {
|
||||
return fmt.Errorf("d2xx: mpsseVerify: %v", err)
|
||||
}
|
||||
// Sometimes, especially right after a reset, the device spews a few bytes.
|
||||
// Discard them. This significantly increases the odds of a successful
|
||||
// initialization.
|
||||
p, e := h.h.GetQueueStatus()
|
||||
if e != 0 {
|
||||
return toErr("Read/GetQueueStatus", e)
|
||||
}
|
||||
for p > 2 {
|
||||
l := int(p) - 2
|
||||
if l > len(b) {
|
||||
l = len(b)
|
||||
}
|
||||
// Discard the overflow bytes.
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
if _, err := h.ReadAll(ctx, b[:l]); err != nil {
|
||||
return fmt.Errorf("d2xx: mpsseVerify: %v", err)
|
||||
}
|
||||
p -= uint32(l)
|
||||
}
|
||||
// Custom implementation, as we want to flush any stray byte.
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
if _, err := h.ReadAll(ctx, b[:2]); err != nil {
|
||||
return fmt.Errorf("d2xx: mpsseVerify: %v", err)
|
||||
}
|
||||
// 0xFA means invalid command, 0xAA is the command echoed back.
|
||||
if b[0] != 0xFA || b[1] != v {
|
||||
return fmt.Errorf("d2xx: mpsseVerify: failed test for byte %#x: %#x", v, b)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// MPSSERegRead reads the memory mapped registers from the device.
|
||||
func (h *handle) MPSSERegRead(addr uint16) (byte, error) {
|
||||
// Unlike most other operations, the uint16 byte order is <hi>, <lo>.
|
||||
b := [...]byte{cpuReadFar, byte(addr >> 8), byte(addr), flush}
|
||||
if _, err := h.Write(b[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
_, err := h.ReadAll(ctx, b[:1])
|
||||
return b[0], err
|
||||
}
|
||||
|
||||
// MPSSEClock sets the clock at the closest value and returns it.
|
||||
func (h *handle) MPSSEClock(f physic.Frequency) (physic.Frequency, error) {
|
||||
// TODO(maruel): Memory clock and skip if the same value.
|
||||
clk := clock30MHz
|
||||
base := 30 * physic.MegaHertz
|
||||
div := base / f
|
||||
if div >= 65536 {
|
||||
clk = clock6MHz
|
||||
base /= 5
|
||||
div = base / f
|
||||
if div >= 65536 {
|
||||
return 0, errors.New("d2xx: clock frequency is too low")
|
||||
}
|
||||
}
|
||||
b := [...]byte{clk, clockSetDivisor, byte(div - 1), byte((div - 1) >> 8)}
|
||||
_, err := h.Write(b[:])
|
||||
return base / div, err
|
||||
}
|
||||
|
||||
// mpsseTxOp returns the right MPSSE command byte for the stream.
|
||||
func mpsseTxOp(w, r bool, ew, er gpio.Edge, lsbf bool) byte {
|
||||
op := byte(0)
|
||||
if lsbf {
|
||||
op |= dataLSBF
|
||||
}
|
||||
if w {
|
||||
op |= dataOut
|
||||
if ew == gpio.FallingEdge {
|
||||
op |= dataOutFall
|
||||
}
|
||||
}
|
||||
if r {
|
||||
op |= dataIn
|
||||
if er == gpio.FallingEdge {
|
||||
op |= dataInFall
|
||||
}
|
||||
}
|
||||
return op
|
||||
}
|
||||
|
||||
// MPSSETx runs a transaction on the clock on pins D0, D1 and D2.
|
||||
//
|
||||
// It can only do it on a multiple of 8 bits.
|
||||
func (h *handle) MPSSETx(w, r []byte, ew, er gpio.Edge, lsbf bool) error {
|
||||
l := len(w)
|
||||
if len(w) != 0 {
|
||||
// TODO(maruel): This is easy to fix by daisy chaining operations.
|
||||
if len(w) > 65536 {
|
||||
return errors.New("d2xx: write buffer too long; max 65536")
|
||||
}
|
||||
}
|
||||
if len(r) != 0 {
|
||||
if len(r) > 65536 {
|
||||
return errors.New("d2xx: read buffer too long; max 65536")
|
||||
}
|
||||
if l != 0 && len(r) != l {
|
||||
return errors.New("d2xx: mismatched buffer lengths")
|
||||
}
|
||||
l = len(r)
|
||||
}
|
||||
// The FT232H has 1Kb Tx and Rx buffers. So partial writes should be done.
|
||||
// TODO(maruel): Test.
|
||||
|
||||
// Flush can be useful if rbits != 0.
|
||||
op := mpsseTxOp(len(w) != 0, len(r) != 0, ew, er, lsbf)
|
||||
cmd := []byte{op, byte(l - 1), byte((l - 1) >> 8)}
|
||||
cmd = append(cmd, w...)
|
||||
if len(r) != 0 {
|
||||
cmd = append(cmd, flush)
|
||||
}
|
||||
if _, err := h.Write(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(r) != 0 {
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
_, err := h.ReadAll(ctx, r)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MPSSETxShort runs a transaction on the clock pins D0, D1 and D2 for a byte
|
||||
// or less: between 1 and 8 bits.
|
||||
func (h *handle) MPSSETxShort(w byte, wbits, rbits int, ew, er gpio.Edge, lsbf bool) (byte, error) {
|
||||
op := byte(dataBit)
|
||||
if lsbf {
|
||||
op |= dataLSBF
|
||||
}
|
||||
l := wbits
|
||||
if wbits != 0 {
|
||||
if wbits > 8 {
|
||||
return 0, errors.New("d2xx: write buffer too long; max 8")
|
||||
}
|
||||
op |= dataOut
|
||||
if ew == gpio.FallingEdge {
|
||||
op |= dataOutFall
|
||||
}
|
||||
}
|
||||
if rbits != 0 {
|
||||
if rbits > 8 {
|
||||
return 0, errors.New("d2xx: read buffer too long; max 8")
|
||||
}
|
||||
op |= dataIn
|
||||
if er == gpio.FallingEdge {
|
||||
op |= dataInFall
|
||||
}
|
||||
if l != 0 && rbits != l {
|
||||
return 0, errors.New("d2xx: mismatched buffer lengths")
|
||||
}
|
||||
l = rbits
|
||||
}
|
||||
b := [3]byte{op, byte(l - 1)}
|
||||
cmd := b[:2]
|
||||
if wbits != 0 {
|
||||
cmd = append(cmd, w)
|
||||
}
|
||||
if rbits != 0 {
|
||||
cmd = append(cmd, flush)
|
||||
}
|
||||
if _, err := h.Write(cmd); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if rbits != 0 {
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
_, err := h.ReadAll(ctx, b[:1])
|
||||
return b[0], err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// MPSSECBus operates on 8 GPIOs at a time C0~C7.
|
||||
//
|
||||
// Direction 1 means output, 0 means input.
|
||||
func (h *handle) MPSSECBus(mask, value byte) error {
|
||||
b := [...]byte{gpioSetC, value, mask}
|
||||
_, err := h.Write(b[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// MPSSEDBus operates on 8 GPIOs at a time D0~D7.
|
||||
//
|
||||
// Direction 1 means output, 0 means input.
|
||||
func (h *handle) MPSSEDBus(mask, value byte) error {
|
||||
b := [...]byte{gpioSetD, value, mask}
|
||||
_, err := h.Write(b[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// MPSSECBusRead reads all the CBus pins C0~C7.
|
||||
func (h *handle) MPSSECBusRead() (byte, error) {
|
||||
b := [...]byte{gpioReadC, flush}
|
||||
if _, err := h.Write(b[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
if _, err := h.ReadAll(ctx, b[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
// MPSSEDBusRead reads all the DBus pins D0~D7.
|
||||
func (h *handle) MPSSEDBusRead() (byte, error) {
|
||||
b := [...]byte{gpioReadD, flush}
|
||||
if _, err := h.Write(b[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ctx, cancel := context200ms()
|
||||
defer cancel()
|
||||
if _, err := h.ReadAll(ctx, b[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return b[0], nil
|
||||
}
|
||||
|
||||
func context200ms() (context.Context, func()) {
|
||||
return context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||
}
|
205
vendor/periph.io/x/host/v3/ftdi/mpsse_gpio.go
generated
vendored
Normal file
205
vendor/periph.io/x/host/v3/ftdi/mpsse_gpio.go
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright 2017 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
// gpiosMPSSE is a slice of 8 GPIO pins driven via MPSSE.
|
||||
//
|
||||
// This permits keeping a cache.
|
||||
type gpiosMPSSE struct {
|
||||
// Immutable.
|
||||
h *handle
|
||||
cbus bool // false if D bus
|
||||
pins [8]gpioMPSSE
|
||||
|
||||
// Cache of values
|
||||
direction byte
|
||||
value byte
|
||||
}
|
||||
|
||||
func (g *gpiosMPSSE) init(name string) {
|
||||
s := "D"
|
||||
if g.cbus {
|
||||
s = "C"
|
||||
}
|
||||
// Configure pulls; pull ups are 75kΩ.
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_184%20FTDI%20Device%20Input%20Output%20Pin%20States.pdf
|
||||
// has a good table.
|
||||
// D0, D2 and D4 go in high impedance before going into pull up.
|
||||
// TODO(maruel): The pull on CBus depends on EEPROM!
|
||||
for i := range g.pins {
|
||||
g.pins[i].a = g
|
||||
g.pins[i].n = name + "." + s + strconv.Itoa(i)
|
||||
g.pins[i].num = i
|
||||
g.pins[i].dp = gpio.PullUp
|
||||
}
|
||||
if g.cbus {
|
||||
// That's just the default EEPROM value.
|
||||
g.pins[7].dp = gpio.PullDown
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gpiosMPSSE) in(n int) error {
|
||||
if g.h == nil {
|
||||
return errors.New("d2xx: device not open")
|
||||
}
|
||||
g.direction = g.direction & ^(1 << uint(n))
|
||||
if g.cbus {
|
||||
return g.h.MPSSECBus(g.direction, g.value)
|
||||
}
|
||||
return g.h.MPSSEDBus(g.direction, g.value)
|
||||
}
|
||||
|
||||
func (g *gpiosMPSSE) read() (byte, error) {
|
||||
if g.h == nil {
|
||||
return 0, errors.New("d2xx: device not open")
|
||||
}
|
||||
var err error
|
||||
if g.cbus {
|
||||
g.value, err = g.h.MPSSECBusRead()
|
||||
} else {
|
||||
g.value, err = g.h.MPSSEDBusRead()
|
||||
}
|
||||
return g.value, err
|
||||
}
|
||||
|
||||
func (g *gpiosMPSSE) out(n int, l gpio.Level) error {
|
||||
if g.h == nil {
|
||||
return errors.New("d2xx: device not open")
|
||||
}
|
||||
g.direction = g.direction | (1 << uint(n))
|
||||
if l {
|
||||
g.value |= 1 << uint(n)
|
||||
} else {
|
||||
g.value &^= 1 << uint(n)
|
||||
}
|
||||
if g.cbus {
|
||||
return g.h.MPSSECBus(g.direction, g.value)
|
||||
}
|
||||
return g.h.MPSSEDBus(g.direction, g.value)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// gpioMPSSE is a GPIO pin on a FTDI device driven via MPSSE.
|
||||
//
|
||||
// gpioMPSSE implements gpio.PinIO.
|
||||
//
|
||||
// It is immutable and stateless.
|
||||
type gpioMPSSE struct {
|
||||
a *gpiosMPSSE
|
||||
n string
|
||||
num int
|
||||
dp gpio.Pull
|
||||
}
|
||||
|
||||
// String implements pin.Pin.
|
||||
func (g *gpioMPSSE) String() string {
|
||||
return g.n
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (g *gpioMPSSE) Name() string {
|
||||
return g.n
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (g *gpioMPSSE) Number() int {
|
||||
return g.num
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (g *gpioMPSSE) Function() string {
|
||||
s := "Out/"
|
||||
m := byte(1 << uint(g.num))
|
||||
if g.a.direction&m == 0 {
|
||||
s = "In/"
|
||||
_, _ = g.a.read()
|
||||
}
|
||||
return s + gpio.Level(g.a.value&m != 0).String()
|
||||
}
|
||||
|
||||
// Halt implements gpio.PinIO.
|
||||
func (g *gpioMPSSE) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (g *gpioMPSSE) In(pull gpio.Pull, e gpio.Edge) error {
|
||||
if e != gpio.NoEdge {
|
||||
// We could support it on D5.
|
||||
return errors.New("d2xx: edge triggering is not supported")
|
||||
}
|
||||
if pull != g.dp && pull != gpio.PullNoChange {
|
||||
// TODO(maruel): This needs to be redone:
|
||||
// - EEPROM values FT232hCBusTristatePullUp and FT232hCBusPwrEnable can be
|
||||
// used to control individual CBus pins.
|
||||
// - dataTristate enables gpio.Float when set to output High, but I don't
|
||||
// know if it will enable reading the value (?). This needs to be
|
||||
// confirmed.
|
||||
return fmt.Errorf("d2xx: pull %s is not supported; try %s", pull, g.dp)
|
||||
}
|
||||
return g.a.in(g.num)
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (g *gpioMPSSE) Read() gpio.Level {
|
||||
v, _ := g.a.read()
|
||||
return gpio.Level(v&(1<<uint(g.num)) != 0)
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (g *gpioMPSSE) WaitForEdge(t time.Duration) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
func (g *gpioMPSSE) DefaultPull() gpio.Pull {
|
||||
return g.dp
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn. The resistor is 75kΩ.
|
||||
func (g *gpioMPSSE) Pull() gpio.Pull {
|
||||
// See In() for the challenges.
|
||||
return g.dp
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (g *gpioMPSSE) Out(l gpio.Level) error {
|
||||
return g.a.out(g.num, l)
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
func (g *gpioMPSSE) PWM(d gpio.Duty, f physic.Frequency) error {
|
||||
return errors.New("d2xx: not implemented")
|
||||
}
|
||||
|
||||
/*
|
||||
func (g *gpioMPSSE) Drive() physic.ElectricCurrent {
|
||||
//return g.a.ee.CDriveCurrent * physic.MilliAmpere
|
||||
return 2 * physic.MilliAmpere
|
||||
}
|
||||
|
||||
func (g *gpioMPSSE) SlewLimit() bool {
|
||||
//return g.a.ee.CSlowSlew
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *gpioMPSSE) Hysteresis() bool {
|
||||
//return g.a.ee.DSchmittInput
|
||||
return true
|
||||
}
|
||||
*/
|
||||
|
||||
var _ gpio.PinIO = &gpioMPSSE{}
|
85
vendor/periph.io/x/host/v3/ftdi/pin.go
generated
vendored
Normal file
85
vendor/periph.io/x/host/v3/ftdi/pin.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// Emulate independent GPIOs.
|
||||
|
||||
package ftdi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
// invalidPin is a non-working (not implemented) pin on a FTDI device.
|
||||
//
|
||||
// invalidPin implements gpio.PinIO.
|
||||
type invalidPin struct {
|
||||
n string
|
||||
num int
|
||||
}
|
||||
|
||||
// String implements pin.Pin.
|
||||
func (p *invalidPin) String() string {
|
||||
return p.n
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (p *invalidPin) Name() string {
|
||||
return p.n
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (p *invalidPin) Number() int {
|
||||
return p.num
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (p *invalidPin) Function() string {
|
||||
return "N/A"
|
||||
}
|
||||
|
||||
// Halt implements gpio.PinIO.
|
||||
func (p *invalidPin) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (p *invalidPin) In(pull gpio.Pull, e gpio.Edge) error {
|
||||
return errors.New("d2xx: to be implemented")
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (p *invalidPin) Read() gpio.Level {
|
||||
return gpio.Low
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (p *invalidPin) WaitForEdge(t time.Duration) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn.
|
||||
func (p *invalidPin) Pull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
func (p *invalidPin) DefaultPull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (p *invalidPin) Out(l gpio.Level) error {
|
||||
return errors.New("d2xx: to be implemented")
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
func (p *invalidPin) PWM(d gpio.Duty, f physic.Frequency) error {
|
||||
return errors.New("d2xx: to be implemented")
|
||||
}
|
||||
|
||||
var _ gpio.PinIO = &invalidPin{}
|
697
vendor/periph.io/x/host/v3/ftdi/spi.go
generated
vendored
Normal file
697
vendor/periph.io/x/host/v3/ftdi/spi.go
generated
vendored
Normal file
@@ -0,0 +1,697 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// This functionality requires MPSSE.
|
||||
//
|
||||
// Interfacing SPI:
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_114_FTDI_Hi_Speed_USB_To_SPI_Example.pdf
|
||||
//
|
||||
// Implementation based on
|
||||
// http://www.ftdichip.com/Support/Documents/AppNotes/AN_180_FT232H%20MPSSE%20Example%20-%20USB%20Current%20Meter%20using%20the%20SPI%20interface.pdf
|
||||
|
||||
package ftdi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/spi"
|
||||
)
|
||||
|
||||
// spiMPSEEPort is an SPI port over a FTDI device in MPSSE mode using the data
|
||||
// command on the AD bus.
|
||||
type spiMPSEEPort struct {
|
||||
c spiMPSEEConn
|
||||
|
||||
// Mutable.
|
||||
maxFreq physic.Frequency
|
||||
}
|
||||
|
||||
func (s *spiMPSEEPort) Close() error {
|
||||
s.c.f.mu.Lock()
|
||||
s.c.f.usingSPI = false
|
||||
s.maxFreq = 0
|
||||
s.c.edgeInvert = false
|
||||
s.c.clkActiveLow = false
|
||||
s.c.noCS = false
|
||||
s.c.lsbFirst = false
|
||||
s.c.halfDuplex = false
|
||||
s.c.f.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spiMPSEEPort) String() string {
|
||||
return s.c.f.String()
|
||||
}
|
||||
|
||||
// Connect implements spi.Port.
|
||||
func (s *spiMPSEEPort) Connect(f physic.Frequency, m spi.Mode, bits int) (spi.Conn, error) {
|
||||
if f > physic.GigaHertz {
|
||||
return nil, fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 30MHz", f)
|
||||
}
|
||||
if f > 30*physic.MegaHertz {
|
||||
// TODO(maruel): Figure out a way to communicate that the speed was lowered.
|
||||
// https://github.com/google/periph/issues/255
|
||||
f = 30 * physic.MegaHertz
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return nil, fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f)
|
||||
}
|
||||
if bits&7 != 0 {
|
||||
return nil, errors.New("d2xx: bits must be multiple of 8")
|
||||
}
|
||||
if bits != 8 {
|
||||
return nil, errors.New("d2xx: implement bits per word above 8")
|
||||
}
|
||||
|
||||
s.c.f.mu.Lock()
|
||||
defer s.c.f.mu.Unlock()
|
||||
s.c.noCS = m&spi.NoCS != 0
|
||||
s.c.halfDuplex = m&spi.HalfDuplex != 0
|
||||
s.c.lsbFirst = m&spi.LSBFirst != 0
|
||||
m &^= spi.NoCS | spi.HalfDuplex | spi.LSBFirst
|
||||
if s.c.halfDuplex {
|
||||
return nil, errors.New("d2xx: spi.HalfDuplex is not yet supported (implementing wouldn't be too hard, please submit a PR")
|
||||
}
|
||||
if m < 0 || m > 3 {
|
||||
return nil, errors.New("d2xx: unknown spi mode")
|
||||
}
|
||||
s.c.edgeInvert = m&1 != 0
|
||||
s.c.clkActiveLow = m&2 != 0
|
||||
if s.maxFreq == 0 || f < s.maxFreq {
|
||||
// TODO(maruel): We could set these only *during* the SPI operation, which
|
||||
// would make more sense.
|
||||
if _, err := s.c.f.h.MPSSEClock(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.maxFreq = f
|
||||
}
|
||||
s.c.resetIdle()
|
||||
if err := s.c.f.h.MPSSEDBus(s.c.f.dbus.direction, s.c.f.dbus.value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.c.f.usingSPI = true
|
||||
return &s.c, nil
|
||||
}
|
||||
|
||||
// LimitSpeed implements spi.Port.
|
||||
func (s *spiMPSEEPort) LimitSpeed(f physic.Frequency) error {
|
||||
if f > physic.GigaHertz {
|
||||
return fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 30MHz", f)
|
||||
}
|
||||
if f > 30*physic.MegaHertz {
|
||||
f = 30 * physic.MegaHertz
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return errors.New("d2xx: minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?")
|
||||
}
|
||||
s.c.f.mu.Lock()
|
||||
defer s.c.f.mu.Unlock()
|
||||
if s.maxFreq != 0 && s.maxFreq <= f {
|
||||
return nil
|
||||
}
|
||||
s.maxFreq = f
|
||||
// TODO(maruel): We could set these only *during* the SPI operation, which
|
||||
// would make more sense.
|
||||
_, err := s.c.f.h.MPSSEClock(s.maxFreq)
|
||||
return err
|
||||
}
|
||||
|
||||
// CLK returns the SCK (clock) pin.
|
||||
func (s *spiMPSEEPort) CLK() gpio.PinOut {
|
||||
return s.c.CLK()
|
||||
}
|
||||
|
||||
// MOSI returns the SDO (master out, slave in) pin.
|
||||
func (s *spiMPSEEPort) MOSI() gpio.PinOut {
|
||||
return s.c.MOSI()
|
||||
}
|
||||
|
||||
// MISO returns the SDI (master in, slave out) pin.
|
||||
func (s *spiMPSEEPort) MISO() gpio.PinIn {
|
||||
return s.c.MISO()
|
||||
}
|
||||
|
||||
// CS returns the CSN (chip select) pin.
|
||||
func (s *spiMPSEEPort) CS() gpio.PinOut {
|
||||
return s.c.CS()
|
||||
}
|
||||
|
||||
type spiMPSEEConn struct {
|
||||
// Immutable.
|
||||
f *FT232H
|
||||
|
||||
// Initialized at Connect().
|
||||
edgeInvert bool // CPHA=1
|
||||
clkActiveLow bool // CPOL=1
|
||||
noCS bool // CS line is not changed
|
||||
lsbFirst bool // Default is MSB first
|
||||
halfDuplex bool // 3 wire mode
|
||||
}
|
||||
|
||||
func (s *spiMPSEEConn) String() string {
|
||||
return s.f.String()
|
||||
}
|
||||
|
||||
func (s *spiMPSEEConn) Tx(w, r []byte) error {
|
||||
var p = [1]spi.Packet{{W: w, R: r}}
|
||||
return s.TxPackets(p[:])
|
||||
}
|
||||
|
||||
func (s *spiMPSEEConn) Duplex() conn.Duplex {
|
||||
// TODO(maruel): Support half if there's a need.
|
||||
return conn.Full
|
||||
}
|
||||
|
||||
func (s *spiMPSEEConn) TxPackets(pkts []spi.Packet) error {
|
||||
// Verification.
|
||||
for _, p := range pkts {
|
||||
if p.KeepCS {
|
||||
return errors.New("d2xx: implement spi.Packet.KeepCS")
|
||||
}
|
||||
if p.BitsPerWord&7 != 0 {
|
||||
return errors.New("d2xx: bits must be a multiple of 8")
|
||||
}
|
||||
if p.BitsPerWord != 0 && p.BitsPerWord != 8 {
|
||||
return errors.New("d2xx: implement spi.Packet.BitsPerWord")
|
||||
}
|
||||
if err := verifyBuffers(p.W, p.R); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.f.mu.Lock()
|
||||
defer s.f.mu.Unlock()
|
||||
const clk = byte(1) << 0
|
||||
const mosi = byte(1) << 1
|
||||
const miso = byte(1) << 2
|
||||
const cs = byte(1) << 3
|
||||
s.resetIdle()
|
||||
idle := s.f.dbus.value
|
||||
start1 := idle
|
||||
if !s.noCS {
|
||||
start1 &^= cs
|
||||
}
|
||||
// In mode 0 and 2, start2 is not needed.
|
||||
start2 := start1
|
||||
stop := idle
|
||||
if s.edgeInvert {
|
||||
// This is needed to 'prime' the clock.
|
||||
start2 ^= clk
|
||||
// With mode 1 and 3, keep the clock steady while CS is being deasserted to
|
||||
// not create a spurious clock.
|
||||
stop ^= clk
|
||||
}
|
||||
ew := gpio.FallingEdge
|
||||
er := gpio.RisingEdge
|
||||
if s.edgeInvert {
|
||||
ew, er = er, ew
|
||||
}
|
||||
if s.clkActiveLow {
|
||||
// TODO(maruel): Not sure.
|
||||
ew, er = er, ew
|
||||
}
|
||||
|
||||
// FT232H claims 512 USB packet support, so to reduce the chatter over USB,
|
||||
// try to make all I/O be aligned on this amount. This also removes the need
|
||||
// for heap usage. The idea is to always trail reads by one buffer. This is
|
||||
// fine as the device has 1024 byte read buffer. Operations look like this:
|
||||
// W, W, R, W, R, W, R, R
|
||||
// This enables reducing the I/O gaps between USB packets as the device is
|
||||
// always busy with operations.
|
||||
var buf [512]byte
|
||||
cmd := buf[:0]
|
||||
keptCS := false
|
||||
|
||||
// Loop, without increasing the index.
|
||||
for _, p := range pkts {
|
||||
if len(p.W) == 0 && len(p.R) == 0 {
|
||||
continue
|
||||
}
|
||||
// TODO(maruel): s.halfDuplex.
|
||||
|
||||
if !keptCS {
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = append(cmd, gpioSetD, idle, s.f.dbus.direction)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = append(cmd, gpioSetD, start1, s.f.dbus.direction)
|
||||
}
|
||||
}
|
||||
if s.edgeInvert {
|
||||
// This is needed to 'prime' the clock.
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = append(cmd, gpioSetD, start2, s.f.dbus.direction)
|
||||
}
|
||||
}
|
||||
op := mpsseTxOp(len(p.W) != 0, len(p.R) != 0, ew, er, s.lsbFirst)
|
||||
|
||||
// Do an I/O loop. We can mutate p here because it is a copy.
|
||||
// TODO(maruel): Have the pipeline cross the packet boundary.
|
||||
if len(p.W) == 0 {
|
||||
// Have the write buffer point to the read one. This saves from
|
||||
// allocating memory. The side effect is that it will write whatever
|
||||
// happened to be in the read buffer.
|
||||
p.W = p.R[:]
|
||||
}
|
||||
pendingRead := 0
|
||||
for len(p.W) != 0 {
|
||||
// op, sizelo, sizehi.
|
||||
chunk := len(buf) - 3 - len(cmd)
|
||||
if l := len(p.W); chunk > l {
|
||||
chunk = l
|
||||
}
|
||||
cmd = append(cmd, op, byte(chunk-1), byte((chunk-1)>>8))
|
||||
cmd = append(cmd, p.W[:chunk]...)
|
||||
p.W = p.W[chunk:]
|
||||
if _, err := s.f.h.WriteFast(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = buf[:0]
|
||||
|
||||
// TODO(maruel): Read 62 bytes at a time?
|
||||
// Delay reading by 512 bytes.
|
||||
if pendingRead >= 512 {
|
||||
if len(p.R) != 0 {
|
||||
// Align reads on 512 bytes exactly, aligned on USB packet size.
|
||||
if _, err := s.f.h.ReadAll(context.Background(), p.R[:512]); err != nil {
|
||||
return err
|
||||
}
|
||||
p.R = p.R[512:]
|
||||
pendingRead -= 512
|
||||
}
|
||||
}
|
||||
pendingRead += chunk
|
||||
}
|
||||
// Do not forget to read whatever is pending.
|
||||
// TODO(maruel): Investigate if a flush helps.
|
||||
if len(p.R) != 0 {
|
||||
// Send a flush to not wait for data.
|
||||
cmd = append(cmd, flush)
|
||||
if _, err := s.f.h.WriteFast(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = buf[:0]
|
||||
if _, err := s.f.h.ReadAll(context.Background(), p.R); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO(maruel): Inject this in the write if it fits (it will generally
|
||||
// do). That will save one USB I/O, which is not insignificant.
|
||||
keptCS = p.KeepCS
|
||||
if !keptCS {
|
||||
cmd = append(cmd, flush)
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = append(cmd, gpioSetD, stop, s.f.dbus.direction)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = append(cmd, gpioSetD, idle, s.f.dbus.direction)
|
||||
}
|
||||
if _, err := s.f.h.WriteFast(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = buf[:0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CLK returns the SCK (clock) pin.
|
||||
func (s *spiMPSEEConn) CLK() gpio.PinOut {
|
||||
return s.f.D0
|
||||
}
|
||||
|
||||
// MOSI returns the SDO (master out, slave in) pin.
|
||||
func (s *spiMPSEEConn) MOSI() gpio.PinOut {
|
||||
return s.f.D1
|
||||
}
|
||||
|
||||
// MISO returns the SDI (master in, slave out) pin.
|
||||
func (s *spiMPSEEConn) MISO() gpio.PinIn {
|
||||
return s.f.D2
|
||||
}
|
||||
|
||||
// CS returns the CSN (chip select) pin.
|
||||
func (s *spiMPSEEConn) CS() gpio.PinOut {
|
||||
return s.f.D3
|
||||
}
|
||||
|
||||
// resetIdle sets D0~D3. D0, D1 and D3 are output but only touch D3 is CS is
|
||||
// used.
|
||||
func (s *spiMPSEEConn) resetIdle() {
|
||||
const clk = byte(1) << 0
|
||||
const mosi = byte(1) << 1
|
||||
const miso = byte(1) << 2
|
||||
const cs = byte(1) << 3
|
||||
if !s.noCS {
|
||||
s.f.dbus.direction &= 0xF0
|
||||
s.f.dbus.direction |= cs
|
||||
s.f.dbus.value &= 0xF0
|
||||
s.f.dbus.value |= cs
|
||||
} else {
|
||||
s.f.dbus.value &= 0xF8
|
||||
s.f.dbus.direction &= 0xF8
|
||||
}
|
||||
s.f.dbus.direction |= mosi | clk
|
||||
if s.clkActiveLow {
|
||||
// Clock idles high.
|
||||
s.f.dbus.value |= clk
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// spiSyncPort is an SPI port over a FTDI device in synchronous bit-bang mode.
|
||||
type spiSyncPort struct {
|
||||
c spiSyncConn
|
||||
|
||||
// Mutable.
|
||||
maxFreq physic.Frequency
|
||||
}
|
||||
|
||||
func (s *spiSyncPort) Close() error {
|
||||
s.c.f.mu.Lock()
|
||||
s.c.f.usingSPI = false
|
||||
s.maxFreq = 0
|
||||
s.c.edgeInvert = false
|
||||
s.c.clkActiveLow = false
|
||||
s.c.noCS = false
|
||||
s.c.lsbFirst = false
|
||||
s.c.halfDuplex = false
|
||||
s.c.f.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spiSyncPort) String() string {
|
||||
return s.c.f.String()
|
||||
}
|
||||
|
||||
const ft232rMaxSpeed = 3 * physic.MegaHertz
|
||||
|
||||
// Connect implements spi.Port.
|
||||
func (s *spiSyncPort) Connect(f physic.Frequency, m spi.Mode, bits int) (spi.Conn, error) {
|
||||
if f > physic.GigaHertz {
|
||||
return nil, fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 1.5MHz", f)
|
||||
}
|
||||
if f > ft232rMaxSpeed/2 {
|
||||
// TODO(maruel): Figure out a way to communicate that the speed was lowered.
|
||||
// https://github.com/google/periph/issues/255
|
||||
f = ft232rMaxSpeed / 2
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return nil, fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f)
|
||||
}
|
||||
if bits&7 != 0 {
|
||||
return nil, errors.New("d2xx: bits must be multiple of 8")
|
||||
}
|
||||
if bits != 8 {
|
||||
return nil, errors.New("d2xx: implement bits per word above 8")
|
||||
}
|
||||
|
||||
s.c.f.mu.Lock()
|
||||
defer s.c.f.mu.Unlock()
|
||||
s.c.noCS = m&spi.NoCS != 0
|
||||
s.c.halfDuplex = m&spi.HalfDuplex != 0
|
||||
s.c.lsbFirst = m&spi.LSBFirst != 0
|
||||
m &^= spi.NoCS | spi.HalfDuplex | spi.LSBFirst
|
||||
if s.c.halfDuplex {
|
||||
return nil, errors.New("d2xx: spi.HalfDuplex is not yet supported (implementing wouldn't be too hard, please submit a PR")
|
||||
}
|
||||
if m < 0 || m > 3 {
|
||||
return nil, errors.New("d2xx: unknown spi mode")
|
||||
}
|
||||
s.c.edgeInvert = m&1 != 0
|
||||
s.c.clkActiveLow = m&2 != 0
|
||||
if s.maxFreq == 0 || f < s.maxFreq {
|
||||
if err := s.c.f.SetSpeed(f * 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.maxFreq = f
|
||||
}
|
||||
// D0, D2 and D3 are output. D4~D7 are kept as-is.
|
||||
const mosi = byte(1) << 0 // TX
|
||||
const miso = byte(1) << 1 // RX
|
||||
const clk = byte(1) << 2 // RTS
|
||||
const cs = byte(1) << 3 // CTS
|
||||
mask := mosi | clk | cs | (s.c.f.dmask & 0xF0)
|
||||
if err := s.c.f.setDBusMaskLocked(mask); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(maruel): Combine both following calls if possible. We'd shave off a
|
||||
// few ms.
|
||||
if !s.c.noCS {
|
||||
// CTS/SPI_CS is active low.
|
||||
if err := s.c.f.dbusSyncGPIOOutLocked(3, gpio.High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if s.c.clkActiveLow {
|
||||
// RTS/SPI_CLK is active low.
|
||||
if err := s.c.f.dbusSyncGPIOOutLocked(2, gpio.High); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s.c.f.usingSPI = true
|
||||
return &s.c, nil
|
||||
}
|
||||
|
||||
// LimitSpeed implements spi.Port.
|
||||
func (s *spiSyncPort) LimitSpeed(f physic.Frequency) error {
|
||||
if f > physic.GigaHertz {
|
||||
return fmt.Errorf("d2xx: invalid speed %s; maximum supported clock is 1.5MHz", f)
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return fmt.Errorf("d2xx: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f)
|
||||
}
|
||||
s.c.f.mu.Lock()
|
||||
defer s.c.f.mu.Unlock()
|
||||
if s.maxFreq != 0 && s.maxFreq <= f {
|
||||
return nil
|
||||
}
|
||||
if err := s.c.f.SetSpeed(f * 2); err == nil {
|
||||
s.maxFreq = f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CLK returns the SCK (clock) pin.
|
||||
func (s *spiSyncPort) CLK() gpio.PinOut {
|
||||
return s.c.CLK()
|
||||
}
|
||||
|
||||
// MOSI returns the SDO (master out, slave in) pin.
|
||||
func (s *spiSyncPort) MOSI() gpio.PinOut {
|
||||
return s.c.MOSI()
|
||||
}
|
||||
|
||||
// MISO returns the SDI (master in, slave out) pin.
|
||||
func (s *spiSyncPort) MISO() gpio.PinIn {
|
||||
return s.c.MISO()
|
||||
}
|
||||
|
||||
// CS returns the CSN (chip select) pin.
|
||||
func (s *spiSyncPort) CS() gpio.PinOut {
|
||||
return s.c.CS()
|
||||
}
|
||||
|
||||
type spiSyncConn struct {
|
||||
// Immutable.
|
||||
f *FT232R
|
||||
|
||||
// Initialized at Connect().
|
||||
edgeInvert bool // CPHA=1
|
||||
clkActiveLow bool // CPOL=1
|
||||
noCS bool // CS line is not changed
|
||||
lsbFirst bool // Default is MSB first
|
||||
halfDuplex bool // 3 wire mode
|
||||
}
|
||||
|
||||
func (s *spiSyncConn) String() string {
|
||||
return s.f.String()
|
||||
}
|
||||
|
||||
func (s *spiSyncConn) Tx(w, r []byte) error {
|
||||
var p = [1]spi.Packet{{W: w, R: r}}
|
||||
return s.TxPackets(p[:])
|
||||
}
|
||||
|
||||
func (s *spiSyncConn) Duplex() conn.Duplex {
|
||||
// TODO(maruel): Support half if there's a need.
|
||||
return conn.Full
|
||||
}
|
||||
|
||||
func (s *spiSyncConn) TxPackets(pkts []spi.Packet) error {
|
||||
// We need to 'expand' each bit 2 times * 8 bits, which leads
|
||||
// to a 16x memory usage increase. Adds 5 samples before and after.
|
||||
totalW := 0
|
||||
totalR := 0
|
||||
for _, p := range pkts {
|
||||
if p.KeepCS {
|
||||
return errors.New("d2xx: implement spi.Packet.KeepCS")
|
||||
}
|
||||
if p.BitsPerWord&7 != 0 {
|
||||
return errors.New("d2xx: bits must be a multiple of 8")
|
||||
}
|
||||
if p.BitsPerWord != 0 && p.BitsPerWord != 8 {
|
||||
return errors.New("d2xx: implement spi.Packet.BitsPerWord")
|
||||
}
|
||||
if err := verifyBuffers(p.W, p.R); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(maruel): Correctly calculate offsets.
|
||||
if len(p.W) != 0 {
|
||||
totalW += 2 * 8 * len(p.W)
|
||||
}
|
||||
if len(p.R) != 0 {
|
||||
totalR += 2 * 8 * len(p.R)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a large, single chunk.
|
||||
var we, re []byte
|
||||
if totalW != 0 {
|
||||
totalW += 10
|
||||
we = make([]byte, 0, totalW)
|
||||
}
|
||||
if totalR != 0 {
|
||||
totalR += 10
|
||||
re = make([]byte, totalR)
|
||||
}
|
||||
const mosi = byte(1) << 0 // TX
|
||||
const miso = byte(1) << 1 // RX
|
||||
const clk = byte(1) << 2 // RTS
|
||||
const cs = byte(1) << 3 // CTS
|
||||
|
||||
s.f.mu.Lock()
|
||||
defer s.f.mu.Unlock()
|
||||
|
||||
// https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Data_transmission
|
||||
|
||||
csActive := s.f.dvalue & s.f.dmask & 0xF0
|
||||
csIdle := csActive
|
||||
if !s.noCS {
|
||||
csIdle = csActive | cs
|
||||
}
|
||||
clkIdle := csActive
|
||||
clkActive := clkIdle | clk
|
||||
if s.clkActiveLow {
|
||||
clkActive, clkIdle = clkIdle, clkActive
|
||||
csIdle |= clk
|
||||
}
|
||||
// Start of tx; assert CS if needed.
|
||||
we = append(we, csIdle, clkIdle, clkIdle, clkIdle, clkIdle)
|
||||
for _, p := range pkts {
|
||||
if len(p.W) == 0 && len(p.R) == 0 {
|
||||
continue
|
||||
}
|
||||
// TODO(maruel): s.halfDuplex.
|
||||
for _, b := range p.W {
|
||||
for j := uint(0); j < 8; j++ {
|
||||
// For each bit, handle clock phase and data phase.
|
||||
bit := byte(0)
|
||||
if !s.lsbFirst {
|
||||
// MSBF
|
||||
if b&(0x80>>j) != 0 {
|
||||
bit = mosi
|
||||
}
|
||||
} else {
|
||||
// LSBF
|
||||
if b&(1<<j) != 0 {
|
||||
bit = mosi
|
||||
}
|
||||
}
|
||||
if !s.edgeInvert {
|
||||
// Mode0/2; CPHA=0
|
||||
we = append(we, clkIdle|bit, clkActive|bit)
|
||||
} else {
|
||||
// Mode1/3; CPHA=1
|
||||
we = append(we, clkActive|bit, clkIdle|bit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// End of tx; deassert CS.
|
||||
we = append(we, clkIdle, clkIdle, clkIdle, clkIdle, csIdle)
|
||||
|
||||
if err := s.f.txLocked(we, re); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract data from re into r.
|
||||
for _, p := range pkts {
|
||||
// TODO(maruel): Correctly calculate offsets.
|
||||
if len(p.W) == 0 && len(p.R) == 0 {
|
||||
continue
|
||||
}
|
||||
// TODO(maruel): halfDuplex.
|
||||
for i := range p.R {
|
||||
// For each bit, read at the right data phase.
|
||||
b := byte(0)
|
||||
for j := 0; j < 8; j++ {
|
||||
if re[5+i*8*2+j*2+1]&byte(1)<<1 != 0 {
|
||||
if !s.lsbFirst {
|
||||
// MSBF
|
||||
b |= 0x80 >> uint(j)
|
||||
} else {
|
||||
// LSBF
|
||||
b |= 1 << uint(j)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.R[i] = b
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CLK returns the SCK (clock) pin.
|
||||
func (s *spiSyncConn) CLK() gpio.PinOut {
|
||||
return s.f.D2 // RTS
|
||||
}
|
||||
|
||||
// MOSI returns the SDO (master out, slave in) pin.
|
||||
func (s *spiSyncConn) MOSI() gpio.PinOut {
|
||||
return s.f.D0 // TX
|
||||
}
|
||||
|
||||
// MISO returns the SDI (master in, slave out) pin.
|
||||
func (s *spiSyncConn) MISO() gpio.PinIn {
|
||||
return s.f.D1 // RX
|
||||
}
|
||||
|
||||
// CS returns the CSN (chip select) pin.
|
||||
func (s *spiSyncConn) CS() gpio.PinOut {
|
||||
return s.f.D3 // CTS
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func verifyBuffers(w, r []byte) error {
|
||||
if len(w) != 0 {
|
||||
if len(r) != 0 {
|
||||
if len(w) != len(r) {
|
||||
return errors.New("d2xx: both buffers must have the same size")
|
||||
}
|
||||
}
|
||||
// TODO(maruel): When the buffer is >64Kb, cut it in parts and do not
|
||||
// request a flush. Still try to read though.
|
||||
if len(w) > 65536 {
|
||||
return errors.New("d2xx: maximum buffer size is 64Kb")
|
||||
}
|
||||
} else if len(r) != 0 {
|
||||
// TODO(maruel): Remove, this is not a problem.
|
||||
if len(r) > 65536 {
|
||||
return errors.New("d2xx: maximum buffer size is 64Kb")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ spi.PortCloser = &spiMPSEEPort{}
|
||||
var _ spi.Conn = &spiMPSEEConn{}
|
||||
var _ spi.PortCloser = &spiSyncPort{}
|
||||
var _ spi.Conn = &spiSyncConn{}
|
Reference in New Issue
Block a user