/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ch341a-cfi-device.h" #include "fu-ch341a-device.h" struct _FuCh341aDevice { FuUsbDevice parent_instance; guint8 speed; }; G_DEFINE_TYPE(FuCh341aDevice, fu_ch341a_device, FU_TYPE_USB_DEVICE) #define CH341A_USB_TIMEOUT 1000 #define CH341A_EP_OUT 0x02 /* host to device (write) */ #define CH341A_EP_IN 0x82 /* device to host (read) */ #define CH341A_EP_SIZE 0x20 #define CH341A_CMD_SET_OUTPUT 0xA1 #define CH341A_CMD_IO_ADDR 0xA2 #define CH341A_CMD_PRINT_OUT 0xA3 #define CH341A_CMD_SPI_STREAM 0xA8 #define CH341A_CMD_SIO_STREAM 0xA9 #define CH341A_CMD_I2C_STREAM 0xAA #define CH341A_CMD_UIO_STREAM 0xAB #define CH341A_CMD_I2C_STM_START 0x74 #define CH341A_CMD_I2C_STM_STOP 0x75 #define CH341A_CMD_I2C_STM_OUT 0x80 #define CH341A_CMD_I2C_STM_IN 0xC0 #define CH341A_CMD_I2C_STM_SET 0x60 #define CH341A_CMD_I2C_STM_US 0x40 #define CH341A_CMD_I2C_STM_MS 0x50 #define CH341A_CMD_I2C_STM_DLY 0x0F #define CH341A_CMD_I2C_STM_END 0x00 #define CH341A_CMD_UIO_STM_IN 0x00 #define CH341A_CMD_UIO_STM_DIR 0x40 #define CH341A_CMD_UIO_STM_OUT 0x80 #define CH341A_CMD_UIO_STM_US 0xC0 #define CH341A_CMD_UIO_STM_END 0x20 #define CH341A_STM_I2C_SPEED_LOW 0x00 #define CH341A_STM_I2C_SPEED_STANDARD 0x01 #define CH341A_STM_I2C_SPEED_FAST 0x02 #define CH341A_STM_I2C_SPEED_HIGH 0x03 #define CH341A_STM_SPI_MODUS_STANDARD 0x00 #define CH341A_STM_SPI_MODUS_DOUBLE 0x04 #define CH341A_STM_SPI_ENDIAN_BIG 0x0 #define CH341A_STM_SPI_ENDIAN_LITTLE 0x80 static const gchar * fu_ch341a_device_speed_to_string(guint8 speed) { if (speed == CH341A_STM_I2C_SPEED_LOW) return "20kHz"; if (speed == CH341A_STM_I2C_SPEED_STANDARD) return "100kHz"; if (speed == CH341A_STM_I2C_SPEED_FAST) return "400kHz"; if (speed == CH341A_STM_I2C_SPEED_HIGH) return "750kHz"; if (speed == (CH341A_STM_I2C_SPEED_LOW | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*20kHz"; if (speed == (CH341A_STM_I2C_SPEED_STANDARD | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*100kHz"; if (speed == (CH341A_STM_I2C_SPEED_FAST | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*400kHz"; if (speed == (CH341A_STM_I2C_SPEED_HIGH | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*750kHz"; return NULL; } static void fu_ch341a_device_to_string(FuDevice *device, guint idt, GString *str) { FuCh341aDevice *self = FU_CH341A_DEVICE(device); /* FuUsbDevice->to_string */ FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Speed", fu_ch341a_device_speed_to_string(self->speed)); } static gboolean fu_ch341a_device_write(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; /* debug */ if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL) fu_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz); if (!g_usb_device_bulk_transfer(usb_device, CH341A_EP_OUT, buf, bufsz, &actual_length, CH341A_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write 0x%x bytes:", (guint)bufsz); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "only wrote 0x%x of 0x%x", (guint)actual_length, (guint)bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_ch341a_device_read(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; if (!g_usb_device_bulk_transfer(usb_device, CH341A_EP_IN, buf, bufsz, &actual_length, CH341A_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read 0x%x bytes: ", (guint)bufsz); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "only read 0x%x of 0x%x", (guint)actual_length, (guint)bufsz); return FALSE; } /* debug */ if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL) fu_dump_raw(G_LOG_DOMAIN, "read", buf, bufsz); /* success */ return TRUE; } /** * fu_ch341a_reverse_uint8: * @value: integer * * Calculates the reverse bit order for a single byte. * * Returns: the @value, reversed **/ static guint8 fu_ch341a_reverse_uint8(guint8 value) { guint8 tmp = 0; if (value & 0x01) tmp = 0x80; if (value & 0x02) tmp |= 0x40; if (value & 0x04) tmp |= 0x20; if (value & 0x08) tmp |= 0x10; if (value & 0x10) tmp |= 0x08; if (value & 0x20) tmp |= 0x04; if (value & 0x40) tmp |= 0x02; if (value & 0x80) tmp |= 0x01; return tmp; } gboolean fu_ch341a_device_spi_transfer(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { gsize buf2sz = bufsz + 1; g_autofree guint8 *buf2 = g_malloc0(buf2sz); /* requires LSB first */ buf2[0] = CH341A_CMD_SPI_STREAM; for (gsize i = 0; i < bufsz; i++) buf2[i + 1] = fu_ch341a_reverse_uint8(buf[i]); /* debug */ if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL) fu_dump_raw(G_LOG_DOMAIN, "SPIwrite", buf, bufsz); if (!fu_ch341a_device_write(self, buf2, buf2sz, error)) return FALSE; if (!fu_ch341a_device_read(self, buf, bufsz, error)) return FALSE; /* requires LSB first */ for (gsize i = 0; i < bufsz; i++) buf[i] = fu_ch341a_reverse_uint8(buf[i]); /* debug */ if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL) fu_dump_raw(G_LOG_DOMAIN, "SPIread", buf, bufsz); /* success */ return TRUE; } static gboolean fu_ch341a_device_configure_stream(FuCh341aDevice *self, GError **error) { guint8 buf[] = {CH341A_CMD_I2C_STREAM, CH341A_CMD_I2C_STM_SET | self->speed, CH341A_CMD_I2C_STM_END}; if (!fu_ch341a_device_write(self, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to configure stream: "); return FALSE; } /* success */ return TRUE; } gboolean fu_ch341a_device_chip_select(FuCh341aDevice *self, gboolean val, GError **error) { guint8 buf[] = { CH341A_CMD_UIO_STREAM, CH341A_CMD_UIO_STM_OUT | (val ? 0x36 : 0x37), /* CS* high, SCK=0, DOUBT*=1 */ CH341A_CMD_UIO_STM_DIR | (val ? 0x3F : 0x00), /* pin direction */ CH341A_CMD_UIO_STM_END, }; return fu_ch341a_device_write(self, buf, sizeof(buf), error); } static gboolean fu_ch341a_device_setup(FuDevice *device, GError **error) { FuCh341aDevice *self = FU_CH341A_DEVICE(device); g_autoptr(FuCh341aCfiDevice) cfi_device = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->setup(device, error)) return FALSE; /* set speed */ if (!fu_ch341a_device_configure_stream(self, error)) return FALSE; /* setup SPI chip */ cfi_device = g_object_new(FU_TYPE_CH341A_CFI_DEVICE, "context", fu_device_get_context(FU_DEVICE(self)), "proxy", FU_DEVICE(self), "logical-id", "SPI", NULL); if (!fu_device_setup(FU_DEVICE(cfi_device), error)) return FALSE; fu_device_add_child(device, FU_DEVICE(cfi_device)); /* success */ return TRUE; } static void fu_ch341a_device_init(FuCh341aDevice *self) { self->speed = CH341A_STM_I2C_SPEED_STANDARD; fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x0); fu_device_set_name(FU_DEVICE(self), "CH341A"); fu_device_set_vendor(FU_DEVICE(self), "WinChipHead"); } static void fu_ch341a_device_class_init(FuCh341aDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_ch341a_device_setup; klass_device->to_string = fu_ch341a_device_to_string; }