subsurface/core/serial_cp2130.c
Anton Lundin 93ef223a31 Add a simple cp2130 libusb driver
This adds a simple cp2130 userspace driver. Its probably unusable in the
real world but its a great base to build upon.

Signed-off-by: Anton Lundin <glance@acc.umu.se>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2016-09-18 07:12:16 -07:00

283 lines
9.7 KiB
C

/*
* This is code from and inspired by https://www.silabs.com/Support%20Documents/TechnicalDocs/AN792.pdf
*/
#include <string.h> // memset
#include <stdlib.h> // malloc, free
#include <stdbool.h> // bool
/*
#include <errno.h> // errno
#include <sys/time.h> // gettimeofday
#include <time.h> // nanosleep
#include <stdio.h>
*/
#include <libusb.h>
#include <ftdi.h>
#include <libdivecomputer/custom_serial.h>
typedef struct cp2130_serial_t {
/** libusb's context */
struct libusb_context *context;
/** libusb's usb_dev_handle */
struct libusb_device_handle *cp2130Handle;
/** Should we re-attach native driver? */
bool kernelAttached;
long timeout;
} cp2130_serial_t;
static dc_status_t cp2130_serial_close (void **userdata);
/*
8.1.3. Initialization and Device Discovery
The sample application shows the calls necessary to initialize and discover a device.
The steps that need to be taken to get a handle to the CP2130 device are:
1. Initialize LibUSB using libusb_init().
2. Get the device list using libusb_get_device_list() and find a device to connect to.
3. Open the device with LibUSB using libusb_open().
4. Detach any existing kernel connection by checking libusb_kernel_driver_active() and using
libusb_detach_kernel_driver() if it is connected to the kernel.
5. Claim the interface using libusb_claim_interface().
Here is the program listing from the sample application with comments for reference:
*/
static dc_status_t cp2130_serial_open (void **userdata, const char* name) {
// Allocate memory.
cp2130_serial_t *device = (cp2130_serial_t*) malloc (sizeof (cp2130_serial_t));
libusb_device **deviceList = NULL;
struct libusb_device_descriptor deviceDescriptor;
libusb_device *usb_device = NULL;
dc_status_t rc = DC_STATUS_SUCCESS;
if (device == NULL)
return DC_STATUS_NOMEMORY;
memset(device, 0, sizeof (cp2130_serial_t));
// Default to blocking io
device->timeout = -1;
// Initialize libusb
if (libusb_init(&device->context) != 0)
goto exit;
// Search the connected devices to find and open a handle to the CP2130
size_t deviceCount = libusb_get_device_list(device->context, &deviceList);
if (deviceCount <= 0)
goto exit;
for (int i = 0; i < deviceCount; i++) {
if (libusb_get_device_descriptor(deviceList[i], &deviceDescriptor) == 0) {
if ((deviceDescriptor.idVendor == 0x10C4) && (deviceDescriptor.idProduct == 0x87A0)) {
usb_device = deviceList[i];
break;
}
}
}
if (usb_device == NULL) {
rc = DC_STATUS_NODEVICE;
goto exit;
}
// If a device is found, then open it
if (libusb_open(usb_device, &device->cp2130Handle) != 0) {
rc = DC_STATUS_IO;
goto exit;
}
// See if a kernel driver is active already, if so detach it and store a
// flag so we can reattach when we are done
if (libusb_kernel_driver_active(device->cp2130Handle, 0) != 0) {
libusb_detach_kernel_driver(device->cp2130Handle, 0);
device->kernelAttached = true;
}
// Finally, claim the interface
if (libusb_claim_interface(device->cp2130Handle, 0) != 0) {
rc = DC_STATUS_IO;
goto exit;
}
*userdata = device;
if (deviceList)
libusb_free_device_list(deviceList, 1);
return rc;
exit:
if (deviceList)
libusb_free_device_list(deviceList, 1);
(void)cp2130_serial_close((void**)&device);
return rc;
}
/*
8.1.4. Uninitialization
The sample code also shows the calls necessary to uninitialize a device.
The steps need to be taken to disconnect from the CP2130 device are:
1. Release the interface using libusb_release_interface().
2. Reattach from the kernel using libusb_attach_kernel_driver() (only if the device was connected to the
kernel previously).
3. Close the LibUSB handle using libusb_close().
4. Free the device list we obtained originaly using libusb_free_device_list().
5. Uninitialize LibUSB using libusb_exit().
Here is the program listing from the sample application for reference:
*/
static dc_status_t cp2130_serial_close (void **userdata) {
cp2130_serial_t *device = (cp2130_serial_t*) *userdata;
if (device == NULL)
return DC_STATUS_SUCCESS;
if (device->cp2130Handle)
libusb_release_interface(device->cp2130Handle, 0);
if (device->kernelAttached)
libusb_attach_kernel_driver(device->cp2130Handle, 0);
if (device->cp2130Handle)
libusb_close(device->cp2130Handle);
if (device->context)
libusb_exit(device->context);
free(device);
return DC_STATUS_SUCCESS;
}
/*
8.1.5.1. Control Requests
The example GPIO function will get/set the GPIO values with a control request. Each of the commands defined in
section "6. Configuration and Control Commands (Control Transfers)" can be used with the LibUSB control
request function. Each of the paramaters will map to the LibUSB function. In this example we will refer to section
"6.6. Get_GPIO_Values (Command ID 0x20)" .
In this command there is a bmRequestType, bRequest and wLength. In this case the other paramaters, wValue
and wIndex, are set to 0. These parameters are used directly with the libusb_control_transfer_function and it will
return the number of bytes transferred. Here is the definition:
int libusb_control_transfer(libusb_device_handle* dev_handle, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char* data, uint16_t wLength, unsigned int timeout)
After putting the defined values from section "6.6.2. Setup Stage (OUT Transfer)" in the function, this is the resulting call to get the GPIO
unsigned char control_buf_out[2];
libusb_control_transfer(cp2130Handle, 0xC0, 0x20, 0x0000, 0x0000, control_buf_out, sizeof(control_buf_out), usbTimeout);
*/
/*
8.1.5.2. Bulk OUT Requests
The example write function will send data to the SPI MOSI line. To perform writes, use the description in section
"5.2. Write (Command ID 0x01)" to transmit data with the LibUSB bulk transfer function. Here is the definition:
int libusb_bulk_transfer(struct libusb_device_handle* dev_handle, unsigned char endpoint,
unsigned char* data, int length, int * transferred, unsigned int timeout)
To perform a write to the MOSI line, pack a buffer with the specified data and payload then send the entire packet.
Here is an example from the sample application that will write 6 bytes to endpoint 1:
*/
static dc_status_t cp2130_serial_write(void **userdata, const void *data, size_t size, size_t *actual) {
cp2130_serial_t *device = (cp2130_serial_t*) *userdata;
int libusb_status;
int bytesWritten;
if (device == NULL)
return DC_STATUS_SUCCESS;
unsigned char write_command_buf[14] = {
0x00, 0x00, // Reserved
0x01, // Write command
0x00, // Reserved
// Number of bytes, little-endian
size & 0xFF,
(size >> 8) & 0xFF,
(size >> 16) & 0xFF,
(size >> 24) & 0xFF,
};
libusb_status = libusb_bulk_transfer(device->cp2130Handle, 0x01, write_command_buf, sizeof(write_command_buf), &bytesWritten, device->timeout);
if (libusb_status != 0 || bytesWritten != sizeof(write_command_buf))
return DC_STATUS_IO; // Simplified for now.
libusb_status = libusb_bulk_transfer(device->cp2130Handle, 0x01, (unsigned char*) data, size, &bytesWritten, device->timeout);
if (actual)
*actual = bytesWritten;
if (libusb_status == 0)
return DC_STATUS_SUCCESS;
else
return DC_STATUS_IO; // Simplified for now.
}
/*
The function will return 0 upon success, otherwise it will return an error code to check for the failure reason.
*/
/*
8.1.5.3. Bulk IN Requests
Note: Because there is no input to the SPI the read is commented out. The code itself demonstrates reading 6 bytes, but will
not succeed since there is nothing to send this data to the host. This code is meant to serve as an example of how to
perform a read in a developed system.
The example read function will send a request to read data from the SPI MISO line. To perform reads, use the
description in section "5.1. Read (Command ID 0x00)" to request data with the LibUSB bulk transfer function (see
definition in "8.1.5.2. Bulk OUT Requests" , or the LibUSB documentation).
To perform a read from the MISO line, pack a buffer with the specified read command then send the entire packet.
Immediately after that, perform another bulk request to get the response. Here is an example from the sample
application that will try to read 6 bytes from endpoint 1:
*/
static dc_status_t cp2130_serial_read (void **userdata, void *data, size_t size, size_t *actual) {
cp2130_serial_t *device = (cp2130_serial_t*) *userdata;
int libusb_status;
int bytesWritten, bytesRead;
if (device == NULL)
return DC_STATUS_SUCCESS;
// This example shows how to issue a bulk read request to the SPI MISO line
unsigned char read_command_buf[14] = {
0x00, 0x00, // Reserved
0x00, // Read command
0x00, // Reserved
// Read number of bytes, little-endian
size & 0xFF,
(size >> 8) & 0xFF,
(size >> 16) & 0xFF,
(size >> 24) & 0xFF,
};
libusb_status = libusb_bulk_transfer(device->cp2130Handle, 0x01, read_command_buf, sizeof(read_command_buf), &bytesWritten, device->timeout);
if (libusb_status != 0 || bytesWritten != sizeof(read_command_buf))
return DC_STATUS_IO; // Simplified for now.
libusb_status = libusb_bulk_transfer(device->cp2130Handle, 0x01, (unsigned char*) data, size, &bytesRead, device->timeout);
if (actual)
*actual = bytesRead;
if (libusb_status == 0)
return DC_STATUS_SUCCESS;
else
return DC_STATUS_IO; // Simplified for now.
}
/*
The bulk transfer function will return 0 upon success, otherwise it will return an error code to check for the failure
reason. In this case make sure to check that the bytesWritten is the same as the command buffer size as well as a
successful transfer.
*/
dc_custom_serial_t cp2130_serial_ops = {
.userdata = NULL,
.open = cp2130_serial_open,
.close = cp2130_serial_close,
.read = cp2130_serial_read,
.write = cp2130_serial_write,
// NULL means NOP
.purge = NULL,
.get_available = NULL,
.set_timeout = NULL,
.configure = NULL,
.set_dtr = NULL,
.set_rts = NULL,
.set_halfduplex = NULL,
.set_break = NULL
};