mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 10:55:06 +00:00
172 lines
5.0 KiB
C++
172 lines
5.0 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#include <tactility/concurrent/mutex.h>
|
|
#include <tactility/device.h>
|
|
#include <tactility/driver.h>
|
|
#include <tactility/drivers/i2c_controller.h>
|
|
#include <tactility/drivers/posix_i2c.h>
|
|
#include <tactility/log.h>
|
|
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <new>
|
|
#include <unistd.h>
|
|
|
|
#ifdef __linux__
|
|
#include <linux/i2c-dev.h>
|
|
#include <linux/i2c.h>
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
|
|
#define TAG "posix_i2c"
|
|
|
|
namespace {
|
|
|
|
struct PosixI2cInternal {
|
|
Mutex mutex {};
|
|
int fd = -1;
|
|
|
|
PosixI2cInternal() {
|
|
mutex_construct(&mutex);
|
|
}
|
|
|
|
~PosixI2cInternal() {
|
|
mutex_destruct(&mutex);
|
|
}
|
|
};
|
|
|
|
#define GET_CONFIG(device) ((const PosixI2cConfig*)device->config)
|
|
#define GET_DATA(device) ((PosixI2cInternal*)device_get_driver_data(device))
|
|
|
|
#define lock(data) mutex_lock(&data->mutex)
|
|
#define unlock(data) mutex_unlock(&data->mutex)
|
|
|
|
#ifdef __linux__
|
|
|
|
// The kernel adapter applies its own timeout, so the TickType_t timeouts of the API are not used.
|
|
error_t transfer(Device* device, i2c_msg* messages, uint32_t message_count) {
|
|
auto* data = GET_DATA(device);
|
|
i2c_rdwr_ioctl_data request = { .msgs = messages, .nmsgs = message_count };
|
|
lock(data);
|
|
int result = ioctl(data->fd, I2C_RDWR, &request);
|
|
int error_number = errno;
|
|
unlock(data);
|
|
if (result >= 0) return ERROR_NONE;
|
|
if (error_number == ETIMEDOUT) return ERROR_TIMEOUT;
|
|
if (error_number == ENXIO || error_number == EREMOTEIO) return ERROR_NOT_FOUND;
|
|
return ERROR_RESOURCE;
|
|
}
|
|
|
|
i2c_msg write_message(uint8_t address, const uint8_t* data, uint16_t size) {
|
|
return { .addr = address, .flags = 0, .len = size, .buf = const_cast<uint8_t*>(data) };
|
|
}
|
|
|
|
i2c_msg read_message(uint8_t address, uint8_t* data, uint16_t size) {
|
|
return { .addr = address, .flags = I2C_M_RD, .len = size, .buf = data };
|
|
}
|
|
|
|
// ---- I2cControllerApi ----
|
|
|
|
error_t read(Device* device, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
|
|
if (dataSize > UINT16_MAX) return ERROR_INVALID_ARGUMENT;
|
|
i2c_msg messages[] = { read_message(address, data, dataSize) };
|
|
return transfer(device, messages, 1);
|
|
}
|
|
|
|
error_t write(Device* device, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
|
|
i2c_msg messages[] = { write_message(address, data, dataSize) };
|
|
return transfer(device, messages, 1);
|
|
}
|
|
|
|
error_t write_read(Device* device, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) {
|
|
if (writeDataSize > UINT16_MAX || readDataSize > UINT16_MAX) return ERROR_INVALID_ARGUMENT;
|
|
i2c_msg messages[] = {
|
|
write_message(address, writeData, writeDataSize),
|
|
read_message(address, readData, readDataSize)
|
|
};
|
|
return transfer(device, messages, 2);
|
|
}
|
|
|
|
error_t read_register(Device* device, uint8_t address, uint8_t reg, uint8_t* data, size_t dataSize, TickType_t timeout) {
|
|
return write_read(device, address, ®, 1, data, dataSize, timeout);
|
|
}
|
|
|
|
error_t write_register(Device* device, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
|
|
if (dataSize == UINT16_MAX) return ERROR_INVALID_ARGUMENT;
|
|
auto* buffer = new(std::nothrow) uint8_t[dataSize + 1];
|
|
if (buffer == nullptr) return ERROR_OUT_OF_MEMORY;
|
|
buffer[0] = reg;
|
|
memcpy(buffer + 1, data, dataSize);
|
|
error_t error = write(device, address, buffer, dataSize + 1, timeout);
|
|
delete[] buffer;
|
|
return error;
|
|
}
|
|
|
|
error_t probe(Device* device, uint8_t address, TickType_t timeout) {
|
|
uint8_t byte;
|
|
return read(device, address, &byte, 1, timeout);
|
|
}
|
|
|
|
#endif
|
|
|
|
const I2cControllerApi posix_i2c_api = {
|
|
#ifdef __linux__
|
|
.read = read,
|
|
.write = write,
|
|
.write_read = write_read,
|
|
.read_register = read_register,
|
|
.write_register = write_register,
|
|
.probe = probe
|
|
#endif
|
|
};
|
|
|
|
// ---- Driver lifecycle ----
|
|
|
|
error_t start_device(Device* device) {
|
|
#ifdef __linux__
|
|
const char* path = GET_CONFIG(device)->path;
|
|
int fd = open(path, O_RDWR | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
LOG_E(TAG, "%s failed to open %s: %s", device->name, path, strerror(errno));
|
|
return ERROR_RESOURCE;
|
|
}
|
|
|
|
auto* data = new(std::nothrow) PosixI2cInternal();
|
|
if (data == nullptr) {
|
|
close(fd);
|
|
return ERROR_OUT_OF_MEMORY;
|
|
}
|
|
data->fd = fd;
|
|
device_set_driver_data(device, data);
|
|
return ERROR_NONE;
|
|
#else
|
|
LOG_E(TAG, "%s: I2C requires Linux i2c-dev", device->name);
|
|
return ERROR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
error_t stop_device(Device* device) {
|
|
auto* data = GET_DATA(device);
|
|
device_set_driver_data(device, nullptr);
|
|
close(data->fd);
|
|
delete data;
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
Driver posix_i2c_driver = {
|
|
.name = "posix_i2c",
|
|
.compatible = (const char*[]) { "posix,i2c", nullptr },
|
|
.start_device = start_device,
|
|
.stop_device = stop_device,
|
|
.api = (const void*)&posix_i2c_api,
|
|
.device_type = &I2C_CONTROLLER_TYPE,
|
|
.internal = nullptr
|
|
};
|
|
|
|
} // extern "C"
|