/** * usb2517.c * * Low level control of the Microchip USB2517 7-port USB hub controller over I2C. * * Copyright (c) 2024 Bigscreen, Inc. */ #include #include #include "stdbool.h" #include "i2c.h" #include "usb2517.h" bool hub_usb2517_is_initialized = false; static uint8_t usb2517_write_reg(I2C_Handle* hi2c, uint8_t reg_addr, uint8_t data) { // Write a single data byte to a USB2517 register I2C_Transaction_Request i2ctxn; uint8_t temp_bytes[2]; temp_bytes[0] = 1; // number of bytes to send temp_bytes[1] = data; uint8_t retval = pdPASS; i2ctxn.dev_addr = USB2517_I2C_ADDR; i2ctxn.cb = NULL; i2ctxn.internal_addr = reg_addr; i2ctxn.data = temp_bytes; i2ctxn.len = 2; // length byte and data byte i2ctxn.shift = I2C_Unshifted; i2ctxn.ttype = I2C_INT_ADDR_WRITE; retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); return retval; } static uint8_t usb2517_write_reg_burst(I2C_Handle* hi2c, uint8_t reg_addr, uint8_t data_len, const uint8_t* data) { // Write up to 32 bytes to USB2517 registers (register address will auto-increment) I2C_Transaction_Request i2ctxn; uint8_t retval = pdPASS; uint8_t temp_bytes[33]; // Big enough to hold the maximum 32 byte transfer, plus the byte for length temp_bytes[0] = data_len; memcpy(&(temp_bytes[1]), data, data_len); i2ctxn.dev_addr = USB2517_I2C_ADDR; i2ctxn.cb = NULL; i2ctxn.internal_addr = reg_addr; i2ctxn.data = temp_bytes; i2ctxn.len = data_len + 1; // 1 extra for the length byte i2ctxn.shift = I2C_Unshifted; i2ctxn.ttype = I2C_INT_ADDR_WRITE; retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); return retval; } uint8_t usb2517_init(I2C_Handle* hi2c) { uint8_t retval; ioport_set_pin_level(PIN_USBHUB_RESET, true); // RESET_N released, hub starts up vTaskDelay(1); // datasheet says it needs at least 500us after reset release before // SMBUS (I2C) is active i2c_lock(hi2c); // set vendor id = 0x35BD, product id = 0x2517, device rev number = 0x0100 retval = usb2517_write_reg_burst(hi2c, USB2517_REG_VID_LSB, 6, (const uint8_t[]){0xBD,0x35, 0x17,0x25, 0x00, 0x01}); // disable current sense, enable multi-transaction-translator, set self-powered if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_CFG1, USB2517_CFG1_CURRENT_SENS_DISABLE | USB2517_CFG1_MTT_ENABLE | USB2517_CFG1_SELF_BUS_PWR/* | USB2517_CFG1_HS_DISABLE*/); } // set this as a compound device (permanently attached devices) if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_CFG2, USB2517_CFG2_COMPOUND); } // optional - enable strings // usb2517_write_reg(hi2c, USB2517_REG_CFG3, USB2517_CFG3_STRING_EN); // all devices except aux USB (port 1) are non removeable. port 7 is unused. bit 0 is reserved. if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_NONREMOVDEV, 0x7C); // 0x7C = 0b01111100 } // disable port 7 if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_PORTDIS_SELF, 0x80); } if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_PORTDIS_BUS, 0x80); } // set all max power to minimum if(pdPASS == retval) { retval = usb2517_write_reg_burst(hi2c, USB2517_REG_MAX_POWER_SELF, 4, (const uint8_t[]){0, 0, 0, 0}); } // Optional - strings. // usb2517_write_reg(hi2c, USB2517_REG_MFR_STR_LEN, hub_mfr_str_len); // usb2517_write_reg(hi2c, USB2517_REG_PRD_STR_LEN, hub_prd_str_len); // usb2517_write_reg(hi2c, USB2517_REG_SER_STR_LEN, hub_ser_str_len); // usb2517_write_reg_burst(hi2c, USB2517_REG_MFR_STR, hub_mfr_str_len, hub_mfr_str); // usb2517_write_reg_burst(hi2c, USB2517_REG_PRD_STR, hub_prd_str_len, hub_prd_str); // usb2517_write_reg_burst(hi2c, USB2517_REG_SER_STR, hub_ser_str_len, hub_ser_str); // Optional - set upstream boost if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_BOOST_UP, USB2517_BOOST_UP(USB2517_BOOST_DRIVE_NORMAL)); // retval = usb2517_write_reg(hi2c, USB2517_REG_BOOST_UP, USB2517_BOOST_UP(USB2517_BOOST_DRIVE_LOW)); //retval = usb2517_write_reg(hi2c, USB2517_REG_BOOST_UP, USB2517_BOOST_UP(USB2517_BOOST_DRIVE_MEDIUM)); // retval = usb2517_write_reg(hi2c, USB2517_REG_BOOST_UP, USB2517_BOOST_UP(USB2517_BOOST_DRIVE_HIGH)); } // Okay ready to start! Set the hub attach if(pdPASS == retval) { retval = usb2517_write_reg(hi2c, USB2517_REG_STATUS_CMD, USB2517_STATUS_USB_ATTACH); } i2c_unlock(hi2c); if(pdPASS == retval) { hub_usb2517_is_initialized = true; } return retval; } bool usb2517_is_initialized(void) { return hub_usb2517_is_initialized; } // Note this should only be used at startup. // Uses the reset GPIO, so any hub settings previously applied will be lost // Assumes reset is active (held low) before running this function bool usb2517_check_if_present(I2C_Handle* hi2c) { I2C_Transaction_Request i2ctxn; uint8_t temp_i2c_bytes[2]; uint8_t retval; i2c_lock(hi2c); ioport_set_pin_level(PIN_USBHUB_RESET, true); // RESET_N released, hub starts up vTaskDelay(2); // more than 500usec i2ctxn.dev_addr = USB2517_I2C_ADDR; i2ctxn.cb = NULL; i2ctxn.data = temp_i2c_bytes; i2ctxn.len = 2; i2ctxn.shift = I2C_Unshifted; i2ctxn.internal_addr = USB2517_REG_STATUS_CMD; i2ctxn.ttype = I2C_INT_ADDR_WRITE; temp_i2c_bytes[0] = 1; // only sending 1 byte after this (length byte) temp_i2c_bytes[1] = USB2517_STATUS_RESET; // just command a reset // we only really need to check if the I2C address is replied (ack'ed) retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); i2c_unlock(hi2c); ioport_set_pin_level(PIN_USBHUB_RESET, false); // assert RESET_N again, ready for regular bootup routine return (pdPASS == retval); }