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