879 lines
22 KiB
Go
879 lines
22 KiB
Go
|
// 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)
|