blob: 16883a8472ec0d4d656f4aef757bff3aa385d5cb [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ftdi-i2c.h"
#include <fuchsia/hardware/ftdi/c/fidl.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/metadata/i2c.h>
#include <inttypes.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include "ftdi.h"
namespace ftdi_mpsse {
zx_status_t FtdiI2c::Enable() {
if (!mpsse_.IsValid()) {
zxlogf(ERROR, "ftdi_i2c: mpsse is invalid!\n");
return ZX_ERR_INTERNAL;
}
zx_status_t status = mpsse_.Sync();
if (status != ZX_OK) {
zxlogf(ERROR, "ftdi_i2c: mpsse failed to sync %d\n", status);
return status;
}
status = mpsse_.FlushGpio();
if (status != ZX_OK) {
zxlogf(ERROR, "ftdi_i2c: mpsse failed flush GPIO\n");
return status;
}
status = mpsse_.SetClock(false, true, 100000);
if (status != ZX_OK) {
return status;
}
// Enable drive-zero mode -- this means sending 0 to GPIO drives outputs low
// and sending 1 drives them with tri-state. This matches the I2C protocol
// and lets multiple devices share the bus.
uint8_t buf[3] = {kFtdiCommandDriveZeroMode, 0x07, 0x00};
status = mpsse_.Write(buf, sizeof(buf));
if (status != ZX_OK) {
return status;
}
std::vector<uint8_t> buffer(6);
size_t bytes_written;
status = WriteIdleToBuf(0, &buffer, &bytes_written);
if (status != ZX_OK) {
return status;
}
status = mpsse_.Write(buffer.data(), buffer.size());
DdkMakeVisible();
return ZX_OK;
}
zx_status_t FtdiI2c::Bind() {
zx_status_t status = DdkAdd("ftdi-i2c", DEVICE_ADD_INVISIBLE);
if (status != ZX_OK) {
return status;
}
std::vector<i2c_channel_t> i2c_channels(i2c_devices_.size());
for (size_t i = 0; i < i2c_devices_.size(); i++) {
i2c_channel_t& chan = i2c_channels[i];
I2cDevice& dev = i2c_devices_[i];
chan.bus_id = 0;
chan.address = static_cast<uint16_t>(dev.address);
chan.vid = dev.vid;
chan.pid = dev.pid;
chan.did = dev.did;
}
status = DdkAddMetadata(DEVICE_METADATA_I2C_CHANNELS,
i2c_channels.data(), i2c_channels.size() * sizeof(i2c_channel_t));
if (status != ZX_OK) {
DdkRemove();
return status;
}
auto f = [](void* arg) -> int {
return reinterpret_cast<FtdiI2c*>(arg)->Enable();
};
int rc = thrd_create_with_name(&enable_thread_, f, this, "ftdi-i2c-enable-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
// This adds the command to set SCL and SDA high into buffer. It must be called
// at least once for initial setup.
zx_status_t FtdiI2c::WriteIdleToBuf(size_t index, std::vector<uint8_t>* buffer,
size_t* bytes_written) {
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.SetGpio(pin_layout_.sda_in, Mpsse::Direction::IN, Mpsse::Level::LOW);
mpsse_.GpioWriteCommandToBuffer(index, buffer, bytes_written);
return ZX_OK;
}
void FtdiI2c::DdkUnbind() {
thrd_join(enable_thread_, NULL);
DdkRemove();
}
zx_status_t FtdiI2c::Write(uint8_t bus_address, std::vector<uint8_t> data) {
std::vector<uint8_t> transaction(kI2cNumCommandBytesPerFullWrite
+ (kI2cNumCommandBytesPerWriteByte * data.size()));
size_t transaction_index = 0;
size_t bytes_written = 0;
zx_status_t status = WriteIdleToBuf(transaction_index, &transaction, &bytes_written);
if (status != ZX_OK) {
return status;
}
transaction_index += bytes_written;
status = WriteTransactionStartToBuf(transaction_index, &transaction, &bytes_written);
if (status != ZX_OK) {
return status;
}
transaction_index += bytes_written;
auto it = data.begin();
it = data.insert(it, static_cast<uint8_t>(bus_address << 1));
for (size_t i = 0; i < data.size(); i++) {
transaction[transaction_index++] = kI2cWriteCommandByte1;
transaction[transaction_index++] = kI2cWriteCommandByte2;
transaction[transaction_index++] = kI2cWriteCommandByte3;
transaction[transaction_index++] = data[i];
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.GpioWriteCommandToBuffer(transaction_index, &transaction, &bytes_written);
transaction_index += bytes_written;
// Read bit for ACK/NAK.
transaction[transaction_index++] = kI2cReadAckCommandByte1;
transaction[transaction_index++] = kI2cReadAckCommandByte2;
}
status = WriteTransactionEndToBuf(transaction_index, &transaction, &bytes_written);
if (status != ZX_OK) {
return status;
}
transaction_index += bytes_written;
// Ask for response immediately
transaction[transaction_index++] = kI2cCommandFinishTransaction;
if (transaction_index != transaction.size()) {
printf("Index (%ld) != size (%ld) \n", transaction_index, transaction.size());
return ZX_ERR_INTERNAL;
}
status = mpsse_.Write(transaction.data(), transaction.size());
if (status != ZX_OK) {
return status;
}
std::vector<uint8_t> response(data.size());
status = mpsse_.Read(response.data(), data.size());
if (status != ZX_OK) {
return status;
}
// Check each response byte to see if its an ACK (zero) or NACK (non-zero).
for (size_t i = 0; i < response.size(); i++) {
if ((response[i] & 0x1) != 0) {
return ZX_ERR_INTERNAL;
}
}
return ZX_OK;
}
zx_status_t FtdiI2c::Ping(uint8_t bus_address) {
std::vector<uint8_t> data(1);
data[0] = 0x00;
return Write(bus_address, data);
}
// This adds the command to transition SCL from high to low.
zx_status_t FtdiI2c::WriteTransactionStartToBuf(size_t index, std::vector<uint8_t>* buffer,
size_t* bytes_written) {
size_t sub_written = 0;
*bytes_written = 0;
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.GpioWriteCommandToBuffer(index + *bytes_written, buffer, &sub_written);
*bytes_written += sub_written;
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.GpioWriteCommandToBuffer(index + *bytes_written, buffer, &sub_written);
*bytes_written += sub_written;
return ZX_OK;
}
zx_status_t FtdiI2c::WriteTransactionEndToBuf(size_t index, std::vector<uint8_t>* buffer,
size_t* bytes_written) {
size_t sub_written = 0;
*bytes_written = 0;
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.GpioWriteCommandToBuffer(index + *bytes_written, buffer, &sub_written);
*bytes_written += sub_written;
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::LOW);
mpsse_.GpioWriteCommandToBuffer(index + *bytes_written, buffer, &sub_written);
*bytes_written += sub_written;
mpsse_.SetGpio(pin_layout_.scl, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.SetGpio(pin_layout_.sda_out, Mpsse::Direction::OUT, Mpsse::Level::HIGH);
mpsse_.GpioWriteCommandToBuffer(index + *bytes_written, buffer, &sub_written);
*bytes_written += sub_written;
return ZX_OK;
}
zx_status_t FtdiI2c::I2cImplTransact(uint32_t bus_id, const i2c_impl_op_t* op_list,
size_t op_count) {
zx_status_t status;
std::vector<uint8_t> write_data(kFtdiI2cMaxTransferSize);
size_t write_data_index = 0;
for (size_t i = 0; i < op_count; i++) {
if (op_list[i].is_read) {
// TODO(dgilhooley) - Hook up FTDI to an i2c device that supports Read, so
// Read can be tested and implemented
return ZX_ERR_NOT_SUPPORTED;
} else {
size_t copy_amt = op_list[i].data_size;
uint8_t* data_buffer = reinterpret_cast<uint8_t*>(op_list[i].data_buffer);
size_t data_buffer_index = 0;
while (copy_amt--) {
if (write_data_index == kFtdiI2cMaxTransferSize) {
return ZX_ERR_INTERNAL;
}
write_data[write_data_index++] = data_buffer[data_buffer_index++];
}
}
if (op_list[i].stop) {
write_data.resize(write_data_index);
status = Write(static_cast<uint8_t>(op_list[i].address), write_data);
if (status != ZX_OK) {
return status;
}
// Reset the write_data for the next transaction.
write_data.resize(kFtdiI2cMaxTransferSize);
write_data_index = 0;
}
}
return ZX_OK;
}
zx_status_t FtdiI2c::Create(zx_device_t* device,
const fuchsia_hardware_ftdi_I2cBusLayout* layout,
const fuchsia_hardware_ftdi_I2cDevice* i2c_dev) {
// TODO(dgilhooley: Support i2c on different sets of pins and then remove this check.
if (layout->scl != 0 && layout->sda_out != 1 && layout->sda_in != 2) {
return ZX_ERR_OUT_OF_RANGE;
}
ftdi_mpsse::FtdiI2c::I2cLayout i2c_layout;
i2c_layout.scl = layout->scl;
i2c_layout.sda_out = layout->sda_out;
i2c_layout.sda_in = layout->sda_in;
std::vector<ftdi_mpsse::FtdiI2c::I2cDevice> i2c_devices(1);
i2c_devices[0].address = i2c_dev->address;
i2c_devices[0].vid = i2c_dev->vid;
i2c_devices[0].pid = i2c_dev->pid;
i2c_devices[0].did = i2c_dev->did;
auto dev = std::make_unique<ftdi_mpsse::FtdiI2c>(device, i2c_layout, i2c_devices);
zx_status_t status = dev->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
dev.release();
}
return ZX_OK;
}
} // namespace ftdi_mpsse