Files
IronOS/source/Core/Drivers/USBPD.cpp
Ben V. Brown 3c83b59b53 Expose Vbus
2022-02-06 13:14:53 +11:00

247 lines
9.8 KiB
C++

#include "USBPD.h"
#include "configuration.h"
#if POW_PD
#include "BSP_PD.h"
#include "FreeRTOS.h"
#include "fusb302b.h"
#include "main.hpp"
#include "pd.h"
#include "policy_engine.h"
#ifndef USB_PD_VMAX
#error Max PD Voltage must be defined
#endif
#ifndef TIP_RESISTANCE
#error Tip resistance must be defined
#endif
void ms_delay(uint32_t delayms) {
// Convert ms -> ticks
TickType_t ticks = delayms / portTICK_PERIOD_MS;
vTaskDelay(ticks ? ticks : 1); /* Minimum delay = 1 tick */
}
uint32_t get_ms_timestamp() {
// Convert ticks -> ms
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
bool pdbs_dpm_evaluate_capability(const pd_msg *capabilities, pd_msg *request);
void pdbs_dpm_get_sink_capability(pd_msg *cap, const bool isPD3);
FUSB302 fusb((0x22 << 1), fusb_read_buf, fusb_write_buf, ms_delay); // Create FUSB driver
PolicyEngine pe(fusb, get_ms_timestamp, ms_delay, pdbs_dpm_get_sink_capability, pdbs_dpm_evaluate_capability);
int USBPowerDelivery::detectionState = 0;
uint16_t requested_voltage_mv = 0;
/* The current draw when the output is disabled */
#define DPM_MIN_CURRENT PD_MA2PDI(100)
// Start processing
bool USBPowerDelivery::start() {
if (fusbPresent() && fusb.fusb_setup()) {
setupFUSBIRQ();
return true;
}
return false;
}
void USBPowerDelivery::IRQOccured() { pe.IRQOccured(); }
bool USBPowerDelivery::negotiationHasWorked() { return pe.pdHasNegotiated(); }
uint8_t USBPowerDelivery::getStateNumber() { return pe.currentStateCode(true); }
void USBPowerDelivery::step() {
while (pe.thread()) {}
}
void USBPowerDelivery::PPSTimerCallback() { pe.PPSTimerCallback(); }
bool USBPowerDelivery::negotiationComplete() {
if (!fusbPresent()) {
return true;
}
return pe.setupCompleteOrTimedOut(getSettingValue(SettingsOptions::PDNegTimeout));
}
bool USBPowerDelivery::fusbPresent() {
if (detectionState == 0) {
if (fusb.fusb_read_id()) {
detectionState = 1;
}
}
return detectionState == 1;
}
bool USBPowerDelivery::isVBUSConnected() {
// Check the status reg
FUSB302::fusb_status status;
if (fusb.fusb_get_status(&status)) {
return status.status0 & 0x80;
}
// As we are using this to detect the mod fail safe
return true;
}
bool pdbs_dpm_evaluate_capability(const pd_msg *capabilities, pd_msg *request) {
/* Get the number of PDOs */
uint8_t numobj = PD_NUMOBJ_GET(capabilities);
/* Make sure we have configuration */
/* Look at the PDOs to see if one matches our desires */
// Look against USB_PD_Desired_Levels to select in order of preference
uint8_t bestIndex = 0xFF;
int bestIndexVoltage = 0;
int bestIndexCurrent = 0;
bool bestIsPPS = false;
powerSupplyWattageLimit = 0;
for (uint8_t i = 0; i < numobj; i++) {
/* If we have a fixed PDO, its V equals our desired V, and its I is
* at least our desired I */
if ((capabilities->obj[i] & PD_PDO_TYPE) == PD_PDO_TYPE_FIXED) {
// This is a fixed PDO entry
// Evaluate if it can produve sufficient current based on the TIP_RESISTANCE (ohms*10)
// V=I*R -> V/I => minimum resistance, if our tip resistance is >= this then we can use this supply
int voltage_mv = PD_PDV2MV(PD_PDO_SRC_FIXED_VOLTAGE_GET(capabilities->obj[i])); // voltage in mV units
int current_a_x100 = PD_PDO_SRC_FIXED_CURRENT_GET(capabilities->obj[i]); // current in 10mA units
int min_resistance_ohmsx10 = voltage_mv / current_a_x100;
if (voltage_mv <= (USB_PD_VMAX * 1000)) {
#ifdef MODEL_HAS_DCDC
// If this device has step down DC/DC inductor to smooth out current spikes
// We can instead ignore resistance and go for max voltage we can accept
min_resistance_ohmsx10 = TIP_RESISTANCE;
#endif
// Fudge of 0.5 ohms to round up a little to account for other losses
if (min_resistance_ohmsx10 <= (TIP_RESISTANCE + 5)) {
// This is a valid power source we can select as
if ((voltage_mv > bestIndexVoltage) || bestIndex == 0xFF) {
// Higher voltage and valid, select this instead
bestIndex = i;
bestIndexVoltage = voltage_mv;
bestIndexCurrent = current_a_x100;
bestIsPPS = false;
#ifdef MODEL_HAS_DCDC
// set limiter for wattage
powerSupplyWattageLimit = ((voltage_mv * current_a_x100) / 100 / 1000);
#endif
}
}
}
} else if ((capabilities->obj[i] & PD_PDO_TYPE) == PD_PDO_TYPE_AUGMENTED && (capabilities->obj[i] & PD_APDO_TYPE) == PD_APDO_TYPE_PPS) {
// If this is a PPS slot, calculate the max voltage in the PPS range that can we be used and maintain
uint16_t max_voltage = PD_PAV2MV(PD_APDO_PPS_MAX_VOLTAGE_GET(capabilities->obj[i]));
// uint16_t min_voltage = PD_PAV2MV(PD_APDO_PPS_MIN_VOLTAGE_GET(capabilities->obj[i]));
uint16_t max_current = PD_PAI2CA(PD_APDO_PPS_CURRENT_GET(capabilities->obj[i])); // max current in 10mA units
// Using the current and tip resistance, calculate the ideal max voltage
// if this is range, then we will work with this voltage
// if this is not in range; then max_voltage can be safely selected
int ideal_voltage_mv = (TIP_RESISTANCE * max_current);
if (ideal_voltage_mv > max_voltage) {
ideal_voltage_mv = max_voltage; // constrain
}
if (ideal_voltage_mv > (USB_PD_VMAX * 1000)) {
ideal_voltage_mv = (USB_PD_VMAX * 1000); // constrain to model max
}
if (ideal_voltage_mv > bestIndexVoltage || bestIndex == 0xFF) {
bestIndex = i;
bestIndexVoltage = ideal_voltage_mv;
bestIndexCurrent = max_current;
bestIsPPS = true;
#ifdef MODEL_HAS_DCDC
// set limiter for wattage
powerSupplyWattageLimit = ((ideal_voltage_mv * max_current) / 100 / 1000);
#endif
}
}
}
if (bestIndex != 0xFF) {
/* We got what we wanted, so build a request for that */
request->hdr = PD_MSGTYPE_REQUEST | PD_NUMOBJ(1);
if (bestIsPPS) {
request->obj[0] = PD_RDO_PROG_CURRENT_SET(PD_CA2PAI(bestIndexCurrent)) | PD_RDO_PROG_VOLTAGE_SET(PD_MV2PRV(bestIndexVoltage)) | PD_RDO_NO_USB_SUSPEND | PD_RDO_OBJPOS_SET(bestIndex + 1);
} else {
request->obj[0] = PD_RDO_FV_MAX_CURRENT_SET(bestIndexCurrent) | PD_RDO_FV_CURRENT_SET(bestIndexCurrent) | PD_RDO_NO_USB_SUSPEND | PD_RDO_OBJPOS_SET(bestIndex + 1);
}
// We dont do usb
// request->obj[0] |= PD_RDO_USB_COMMS;
/* Update requested voltage */
requested_voltage_mv = bestIndexVoltage;
} else {
/* Nothing matched (or no configuration), so get 5 V at low current */
request->hdr = PD_MSGTYPE_REQUEST | PD_NUMOBJ(1);
request->obj[0] = PD_RDO_FV_MAX_CURRENT_SET(DPM_MIN_CURRENT) | PD_RDO_FV_CURRENT_SET(DPM_MIN_CURRENT) | PD_RDO_NO_USB_SUSPEND | PD_RDO_OBJPOS_SET(1);
// We dont do usb
// request->obj[0] |= PD_RDO_USB_COMMS;
/* Update requested voltage */
requested_voltage_mv = 5000;
}
// Even if we didnt match, we return true as we would still like to handshake on 5V at the minimum
return true;
}
void pdbs_dpm_get_sink_capability(pd_msg *cap, const bool isPD3) {
/* Keep track of how many PDOs we've added */
// int numobj = 0;
// /* If we have no configuration or want something other than 5 V, add a PDO
// * for vSafe5V */
// /* Minimum current, 5 V, and higher capability. */
// cap->obj[numobj++] = PD_PDO_TYPE_FIXED | PD_PDO_SNK_FIXED_VOLTAGE_SET(PD_MV2PDV(5000)) | PD_PDO_SNK_FIXED_CURRENT_SET(DPM_MIN_CURRENT);
// /* Get the current we want */
// uint16_t voltage = USB_PD_VMAX * 1000; // in mv
// if (requested_voltage_mv != 5000) {
// voltage = requested_voltage_mv;
// }
// uint16_t current = (voltage) / TIP_RESISTANCE; // In centi-amps
// /* Add a PDO for the desired power. */
// cap->obj[numobj++] = PD_PDO_TYPE_FIXED | PD_PDO_SNK_FIXED_VOLTAGE_SET(PD_MV2PDV(voltage)) | PD_PDO_SNK_FIXED_CURRENT_SET(current);
// /* Get the PDO from the voltage range */
// int8_t i = dpm_get_range_fixed_pdo_index(cap);
// /* If it's vSafe5V, set our vSafe5V's current to what we want */
// if (i == 0) {
// cap->obj[0] &= ~PD_PDO_SNK_FIXED_CURRENT;
// cap->obj[0] |= PD_PDO_SNK_FIXED_CURRENT_SET(current);
// } else {
// /* If we want more than 5 V, set the Higher Capability flag */
// if (PD_MV2PDV(voltage) != PD_MV2PDV(5000)) {
// cap->obj[0] |= PD_PDO_SNK_FIXED_HIGHER_CAP;
// }
// /* If the range PDO is a different voltage than the preferred
// * voltage, add it to the array. */
// if (i > 0 && PD_PDO_SRC_FIXED_VOLTAGE_GET(cap->obj[i]) != PD_MV2PDV(voltage)) {
// cap->obj[numobj++] = PD_PDO_TYPE_FIXED | PD_PDO_SNK_FIXED_VOLTAGE_SET(PD_PDO_SRC_FIXED_VOLTAGE_GET(cap->obj[i])) | PD_PDO_SNK_FIXED_CURRENT_SET(PD_PDO_SRC_FIXED_CURRENT_GET(cap->obj[i]));
// }
// /* If we have three PDOs at this point, make sure the last two are
// * sorted by voltage. */
// if (numobj == 3 && (cap->obj[1] & PD_PDO_SNK_FIXED_VOLTAGE) > (cap->obj[2] & PD_PDO_SNK_FIXED_VOLTAGE)) {
// cap->obj[1] ^= cap->obj[2];
// cap->obj[2] ^= cap->obj[1];
// cap->obj[1] ^= cap->obj[2];
// }
// /* If we're using PD 3.0, add a PPS APDO for our desired voltage */
// if ((hdr_template & PD_HDR_SPECREV) >= PD_SPECREV_3_0) {
// cap->obj[numobj++]
// = PD_PDO_TYPE_AUGMENTED | PD_APDO_TYPE_PPS | PD_APDO_PPS_MAX_VOLTAGE_SET(PD_MV2PAV(voltage)) | PD_APDO_PPS_MIN_VOLTAGE_SET(PD_MV2PAV(voltage)) |
// PD_APDO_PPS_CURRENT_SET(PD_CA2PAI(current));
// }
// }
// /* Set the unconstrained power flag. */
// if (_unconstrained_power) {
// cap->obj[0] |= PD_PDO_SNK_FIXED_UNCONSTRAINED;
// }
// /* Set the USB communications capable flag. */
// cap->obj[0] |= PD_PDO_SNK_FIXED_USB_COMMS;
// /* Set the Sink_Capabilities message header */
// cap->hdr = hdr_template | PD_MSGTYPE_SINK_CAPABILITIES | PD_NUMOBJ(numobj);
}
#endif