From 93ef223a3131db838abc7c390ddce3fae8be5f7c Mon Sep 17 00:00:00 2001 From: Anton Lundin Date: Sun, 18 Sep 2016 14:56:44 +0200 Subject: [PATCH] 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 Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 1 + android/res/xml/device_filter.xml | 3 + core/CMakeLists.txt | 7 + core/libdivecomputer.c | 4 + core/libdivecomputer.h | 1 + core/serial_cp2130.c | 283 ++++++++++++++++++++++++++++++ 6 files changed, 299 insertions(+) create mode 100644 core/serial_cp2130.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 095160c7d..104c32365 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(NO_USERMANUAL "don't include a viewer for the user manual" OFF) option(FBSUPPORT "allow posting to Facebook" ON) option(BTSUPPORT "enable support for QtBluetooth (requires Qt5.4 or newer)" ON) option(FTDISUPPORT "enable support for libftdi based serial" OFF) +option(CP2130SUPPORT "enable support for cp2130 based serial" OFF) # Options regarding What should we build on subsurface option(MAKE_TESTS "Make the tests" ON) diff --git a/android/res/xml/device_filter.xml b/android/res/xml/device_filter.xml index fc9333bdb..19e535e28 100644 --- a/android/res/xml/device_filter.xml +++ b/android/res/xml/device_filter.xml @@ -11,4 +11,7 @@ + + + diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5c22e2306..dff8361e5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -18,6 +18,12 @@ if(FTDISUPPORT) set(SERIAL_FTDI serial_ftdi.c) endif() +if(CP2130SUPPORT) + message(STATUS "building with cp2130 support") + add_definitions(-DSERIAL_CP2130) + set(SERIAL_CP2130 serial_cp2130.c) +endif() + if(BTSUPPORT) add_definitions(-DBT_SUPPORT) set(BT_SRC_FILES desktop-widgets/btdeviceselectiondialog.cpp) @@ -89,6 +95,7 @@ set(SUBSURFACE_CORE_LIB_SRCS subsurface-qt/CylinderObjectHelper.cpp subsurface-qt/SettingsObjectWrapper.cpp ${SERIAL_FTDI} + ${SERIAL_CP2130} ${PLATFORM_SRC} ${BT_CORE_SRC_FILES} ) diff --git a/core/libdivecomputer.c b/core/libdivecomputer.c index cd1b69bde..f76a654d4 100644 --- a/core/libdivecomputer.c +++ b/core/libdivecomputer.c @@ -1033,6 +1033,10 @@ const char *do_libdivecomputer_import(device_data_t *data) #ifdef SERIAL_FTDI } else if (!strcmp(data->devname, "ftdi")) { rc = dc_context_set_custom_serial(data->context, &serial_ftdi_ops); +#endif +#ifdef SERIAL_CP2130 + } else if (!strcmp(data->devname, "cp2130")) { + rc = dc_context_set_custom_serial(data->context, &cp2130_serial_ops); #endif } diff --git a/core/libdivecomputer.h b/core/libdivecomputer.h index f2894b0c4..8a91fcf86 100644 --- a/core/libdivecomputer.h +++ b/core/libdivecomputer.h @@ -65,6 +65,7 @@ extern char *dumpfile_name; // Thats why I've worked around it with a stupid helper returning it. dc_custom_serial_t* get_qt_serial_ops(); extern dc_custom_serial_t serial_ftdi_ops; +extern dc_custom_serial_t cp2130_serial_ops; #endif #ifdef __cplusplus diff --git a/core/serial_cp2130.c b/core/serial_cp2130.c new file mode 100644 index 000000000..e6fb659d1 --- /dev/null +++ b/core/serial_cp2130.c @@ -0,0 +1,283 @@ +/* + * This is code from and inspired by https://www.silabs.com/Support%20Documents/TechnicalDocs/AN792.pdf + */ + +#include // memset +#include // malloc, free +#include // bool +/* +#include // errno +#include // gettimeofday +#include // nanosleep +#include +*/ + +#include +#include + +#include + +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 +};