/** * usb3803.c * * Low level control of the USB3803 USB hub over I2C. * * Copyright (c) 2022 Bigscreen, Inc. */ #include #include "stdbool.h" #include "i2c.h" #include "usb3803.h" bool hub1_is_initialized = false; bool hub2_is_initialized = false; bool hub3_is_initialized = false; static uint8_t set_usb3803_ilock(I2C_Handle* hi2c, uint8_t hub_i2c_addr) { I2C_Transaction_Request i2ctxn; uint8_t temp_byte; uint8_t retval = pdPASS; i2ctxn.dev_addr = hub_i2c_addr; i2ctxn.cb = NULL; i2ctxn.internal_addr = USB3803_SP_ILOCK; i2ctxn.data = &temp_byte; i2ctxn.len = 1; i2ctxn.shift = I2C_Unshifted; i2ctxn.ttype = I2C_INT_ADDR_READ; // read... retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); if(retval != pdPASS) {return retval;} // modify... temp_byte |= USB3803_ILOCK_CONFIG; // and write. i2ctxn.ttype = I2C_INT_ADDR_WRITE; return i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } static uint8_t set_usb3803_register_settings(I2C_Handle* hi2c, uint8_t hub_i2c_addr) { I2C_Transaction_Request i2ctxn; uint8_t temp_byte; uint8_t retval = pdPASS; i2ctxn.dev_addr = hub_i2c_addr; i2ctxn.cb = NULL; i2ctxn.data = &temp_byte; i2ctxn.len = 1; i2ctxn.shift = I2C_Unshifted; // Set the power mode. Self-powered, 0mA draw from bus i2ctxn.internal_addr = USB3803_CFG1; i2ctxn.ttype = I2C_INT_ADDR_READ; retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); temp_byte |= USB3803_CFG1_SELFBUS; // Self-powered i2ctxn.ttype = I2C_INT_ADDR_WRITE; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } // Set max power to zero everywhere temp_byte = 0; i2ctxn.internal_addr = USB3803_MAXPS; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } i2ctxn.internal_addr = USB3803_MAXPB; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } i2ctxn.internal_addr = USB3803_HCMCS; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } i2ctxn.internal_addr = USB3803_HCMCB; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } #if 0 // Increase the upstream signaling current i2ctxn.internal_addr = USB3803_BOOST_UP3; temp_byte = USB3803_BOOST_15_PCT << 4; if(retval == pdPASS) { retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } #endif return retval; } static uint8_t clear_usb3803_ilock(I2C_Handle* hi2c, uint8_t hub_i2c_addr) { I2C_Transaction_Request i2ctxn; uint8_t temp_byte; uint8_t retval = pdPASS; i2ctxn.dev_addr = hub_i2c_addr; i2ctxn.cb = NULL; i2ctxn.internal_addr = USB3803_SP_ILOCK; i2ctxn.data = &temp_byte; i2ctxn.len = 1; i2ctxn.shift = I2C_Unshifted; i2ctxn.ttype = I2C_INT_ADDR_READ; // read... retval = i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); if(retval != pdPASS) {return retval;} // modify... temp_byte &= (~USB3803_ILOCK_CONFIG); // and write. i2ctxn.ttype = I2C_INT_ADDR_WRITE; return i2c_transact_blocking(hi2c, &i2ctxn, DEFAULT_I2C_WAIT_TIME); } /* Performs initialization of the USB3803 hubs * returns pdFAIL if something went wrong (error in I2C) * or pdPASS if all is well. * This function only requires the I2C_Handle of the I2C * bus connected to the hub as an argument. */ board_ver_t usb3803_init(I2C_Handle* hi2c) { uint8_t retval_hub1 = pdPASS; uint8_t retval_hub2 = pdPASS; uint8_t retval_hub3 = pdPASS; ioport_set_pin_level(PIN_USBHUB_RESET, true); // RESET_N released, hubs start up // Now we have to wait at max 4ms for the hub to respond to I2C commands // but if we wait longer than 400ms, it will go straight to connection // and we will have missed our window to make changes to the USB descriptors // That's a generous window of time, but let's be sure to observe the // minimum time as well vTaskDelay(10); // 10ms is safely past the startup time and well before the end of connection delay i2c_lock(hi2c); // Set the hub's ILOCK.config bit to pause startup // Read-modify-write to prevent changing any other settings retval_hub1 = set_usb3803_ilock(hi2c, USB3803_I2C_ADDR); // Do the same for the other hubs retval_hub2 = set_usb3803_ilock(hi2c, USB3803_2ND_I2C_ADDR); retval_hub3 = set_usb3803_ilock(hi2c, USB3803_3RD_I2C_ADDR); if(pdPASS == retval_hub1) { retval_hub1 = set_usb3803_register_settings(hi2c, USB3803_I2C_ADDR); } if(pdPASS == retval_hub2) { retval_hub2 = set_usb3803_register_settings(hi2c, USB3803_2ND_I2C_ADDR); } if(pdPASS == retval_hub3) { retval_hub2 = set_usb3803_register_settings(hi2c, USB3803_3RD_I2C_ADDR); } if(pdPASS == retval_hub1) { retval_hub1 = clear_usb3803_ilock(hi2c, USB3803_I2C_ADDR); } if(pdPASS == retval_hub2) { retval_hub2 = clear_usb3803_ilock(hi2c, USB3803_2ND_I2C_ADDR); } if(pdPASS == retval_hub3) { retval_hub2 = clear_usb3803_ilock(hi2c, USB3803_3RD_I2C_ADDR); } i2c_unlock(hi2c); if(pdPASS == retval_hub1) { hub1_is_initialized = true; } if(pdPASS == retval_hub2) { hub2_is_initialized = true; } if(pdPASS == retval_hub3) { hub3_is_initialized = true; } if(hub1_is_initialized && hub2_is_initialized && (!hub3_is_initialized)) { return board_ver_BS1; } else if(hub1_is_initialized && hub2_is_initialized && hub3_is_initialized) { return board_ver_BS2; } else { return board_ver_unknown; } } bool usb3803_is_initialized(HUB_Select_T hubsel) { if(hubsel == USB_HUB_1) { return hub1_is_initialized; } else if(hubsel == USB_HUB_2) { return hub2_is_initialized; } else { return hub3_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 usb3803_check_if_present(I2C_Handle* hi2c, HUB_Select_T hubsel) { I2C_Transaction_Request i2ctxn; uint8_t temp_i2c_byte; uint8_t retval; i2c_lock(hi2c); ioport_set_pin_level(PIN_USBHUB_RESET, true); // RESET_N released, hub starts up vTaskDelay(10); // more than 4ms, waaaaay less than 400ms if(USB_HUB_1 == hubsel) { i2ctxn.dev_addr = USB3803_I2C_ADDR; } else { i2ctxn.dev_addr = USB3803_2ND_I2C_ADDR; } i2ctxn.cb = NULL; i2ctxn.data = &temp_i2c_byte; i2ctxn.len = 1; i2ctxn.shift = I2C_Unshifted; i2ctxn.internal_addr = USB3803_STCD; i2ctxn.ttype = I2C_INT_ADDR_WRITE; temp_i2c_byte = USB3803_STCD_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); } */