robocar-led/vendor/periph.io/x/host/v3/ftdi/dev.go

879 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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