734 lines
19 KiB
C
734 lines
19 KiB
C
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "dma.h"
|
|
#include "hooks.h"
|
|
#include "i2c.h"
|
|
#include "link_defs.h"
|
|
#include "registers.h"
|
|
#include "timer.h"
|
|
#include "usb_descriptor.h"
|
|
#include "usb_power.h"
|
|
#include "util.h"
|
|
|
|
#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)
|
|
|
|
static int usb_power_init_inas(struct usb_power_config const *config);
|
|
static int usb_power_read(struct usb_power_config const *config);
|
|
static int usb_power_write_line(struct usb_power_config const *config);
|
|
|
|
void usb_power_deferred_rx(struct usb_power_config const *config)
|
|
{
|
|
int rx_count = rx_ep_pending(config->endpoint);
|
|
|
|
/* Handle an incoming command if available */
|
|
if (rx_count)
|
|
usb_power_read(config);
|
|
}
|
|
|
|
void usb_power_deferred_tx(struct usb_power_config const *config)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
|
|
if (!tx_ep_is_ready(config->endpoint))
|
|
return;
|
|
|
|
/* We've replied, set up the next read. */
|
|
if (!rx_ep_is_active(config->endpoint)) {
|
|
/* Remove any active dma region from output buffer */
|
|
state->reports_xmit_active = state->reports_tail;
|
|
|
|
/* Wait for the next command */
|
|
usb_read_ep(config->endpoint,
|
|
config->ep->out_databuffer_max,
|
|
config->ep->out_databuffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Reset stream */
|
|
void usb_power_event(struct usb_power_config const *config,
|
|
enum usb_ep_event evt)
|
|
{
|
|
if (evt != USB_EVENT_RESET)
|
|
return;
|
|
|
|
config->ep->out_databuffer = config->state->rx_buf;
|
|
config->ep->out_databuffer_max = sizeof(config->state->rx_buf);
|
|
config->ep->in_databuffer = config->state->tx_buf;
|
|
config->ep->in_databuffer_max = sizeof(config->state->tx_buf);
|
|
|
|
epN_reset(config->endpoint);
|
|
|
|
/* Flush any queued data */
|
|
hook_call_deferred(config->ep->rx_deferred, 0);
|
|
hook_call_deferred(config->ep->tx_deferred, 0);
|
|
}
|
|
|
|
|
|
/* Write one or more power records to USB */
|
|
static int usb_power_write_line(struct usb_power_config const *config)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
struct usb_power_report *r = (struct usb_power_report *)(
|
|
state->reports_data_area +
|
|
(USB_POWER_RECORD_SIZE(state->ina_count)
|
|
* state->reports_tail));
|
|
/* status + size + timestamps + power list */
|
|
size_t bytes = USB_POWER_RECORD_SIZE(state->ina_count);
|
|
|
|
/* Check if queue has active data. */
|
|
if (config->state->reports_head != config->state->reports_tail) {
|
|
int recordcount = 1;
|
|
|
|
/* We'll concatenate all the upcoming recrds. */
|
|
if (config->state->reports_tail < config->state->reports_head)
|
|
recordcount = config->state->reports_head -
|
|
config->state->reports_tail;
|
|
else
|
|
recordcount = state->max_cached -
|
|
config->state->reports_tail;
|
|
|
|
state->reports_xmit_active = state->reports_tail;
|
|
state->reports_tail = (state->reports_tail + recordcount) %
|
|
state->max_cached;
|
|
|
|
usb_write_ep(config->endpoint, bytes * recordcount, r);
|
|
return bytes;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int usb_power_state_reset(struct usb_power_config const *config)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
|
|
state->state = USB_POWER_STATE_OFF;
|
|
state->reports_head = 0;
|
|
state->reports_tail = 0;
|
|
state->reports_xmit_active = 0;
|
|
|
|
CPRINTS("[RESET] STATE -> OFF");
|
|
return USB_POWER_SUCCESS;
|
|
}
|
|
|
|
|
|
static int usb_power_state_stop(struct usb_power_config const *config)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
|
|
/* Only a valid transition from CAPTURING */
|
|
if (state->state != USB_POWER_STATE_CAPTURING) {
|
|
CPRINTS("[STOP] Error not capturing.");
|
|
return USB_POWER_ERROR_NOT_CAPTURING;
|
|
}
|
|
|
|
state->state = USB_POWER_STATE_OFF;
|
|
state->reports_head = 0;
|
|
state->reports_tail = 0;
|
|
state->reports_xmit_active = 0;
|
|
state->stride_bytes = 0;
|
|
CPRINTS("[STOP] STATE: CAPTURING -> OFF");
|
|
return USB_POWER_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static int usb_power_state_start(struct usb_power_config const *config,
|
|
union usb_power_command_data *cmd, int count)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
int integration_us = cmd->start.integration_us;
|
|
int ret;
|
|
|
|
if (state->state != USB_POWER_STATE_SETUP) {
|
|
CPRINTS("[START] Error not setup.");
|
|
return USB_POWER_ERROR_NOT_SETUP;
|
|
}
|
|
|
|
if (count != sizeof(struct usb_power_command_start)) {
|
|
CPRINTS("[START] Error count %d is not %d", (int)count,
|
|
sizeof(struct usb_power_command_start));
|
|
return USB_POWER_ERROR_READ_SIZE;
|
|
}
|
|
|
|
if (integration_us == 0) {
|
|
CPRINTS("[START] integration_us cannot be 0");
|
|
return USB_POWER_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Calculate the reports array */
|
|
state->stride_bytes = USB_POWER_RECORD_SIZE(state->ina_count);
|
|
state->max_cached = USB_POWER_MAX_CACHED(state->ina_count);
|
|
|
|
state->integration_us = integration_us;
|
|
ret = usb_power_init_inas(config);
|
|
|
|
if (ret)
|
|
return USB_POWER_ERROR_INVAL;
|
|
|
|
state->state = USB_POWER_STATE_CAPTURING;
|
|
CPRINTS("[START] STATE: SETUP -> CAPTURING %dus", integration_us);
|
|
|
|
/* Find our starting time. */
|
|
config->state->base_time = get_time().val;
|
|
|
|
hook_call_deferred(config->deferred_cap, state->integration_us);
|
|
return USB_POWER_SUCCESS;
|
|
}
|
|
|
|
|
|
static int usb_power_state_settime(struct usb_power_config const *config,
|
|
union usb_power_command_data *cmd, int count)
|
|
{
|
|
if (count != sizeof(struct usb_power_command_settime)) {
|
|
CPRINTS("[SETTIME] Error: count %d is not %d",
|
|
(int)count, sizeof(struct usb_power_command_settime));
|
|
return USB_POWER_ERROR_READ_SIZE;
|
|
}
|
|
|
|
/* Find the offset between microcontroller clock and host clock. */
|
|
if (cmd->settime.time)
|
|
config->state->wall_offset = cmd->settime.time - get_time().val;
|
|
else
|
|
config->state->wall_offset = 0;
|
|
|
|
return USB_POWER_SUCCESS;
|
|
}
|
|
|
|
|
|
static int usb_power_state_addina(struct usb_power_config const *config,
|
|
union usb_power_command_data *cmd, int count)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
struct usb_power_ina_cfg *ina;
|
|
int i;
|
|
|
|
/* Only valid from OFF or SETUP */
|
|
if ((state->state != USB_POWER_STATE_OFF) &&
|
|
(state->state != USB_POWER_STATE_SETUP)) {
|
|
CPRINTS("[ADDINA] Error incorrect state.");
|
|
return USB_POWER_ERROR_NOT_SETUP;
|
|
}
|
|
|
|
if (count != sizeof(struct usb_power_command_addina)) {
|
|
CPRINTS("[ADDINA] Error count %d is not %d",
|
|
(int)count, sizeof(struct usb_power_command_addina));
|
|
return USB_POWER_ERROR_READ_SIZE;
|
|
}
|
|
|
|
if (state->ina_count >= USB_POWER_MAX_READ_COUNT) {
|
|
CPRINTS("[ADDINA] Error INA list full");
|
|
return USB_POWER_ERROR_FULL;
|
|
}
|
|
|
|
/* Transition to SETUP state if necessary and clear INA data */
|
|
if (state->state == USB_POWER_STATE_OFF) {
|
|
state->state = USB_POWER_STATE_SETUP;
|
|
state->ina_count = 0;
|
|
}
|
|
|
|
if ((cmd->addina.type < USBP_INA231_POWER) ||
|
|
(cmd->addina.type > USBP_INA231_SHUNTV)) {
|
|
CPRINTS("[ADDINA] Error INA type 0x%x invalid",
|
|
(int)(cmd->addina.type));
|
|
return USB_POWER_ERROR_INVAL;
|
|
}
|
|
|
|
if (cmd->addina.rs == 0) {
|
|
CPRINTS("[ADDINA] Error INA resistance cannot be zero!");
|
|
return USB_POWER_ERROR_INVAL;
|
|
}
|
|
|
|
/* Select INA to configure */
|
|
ina = state->ina_cfg + state->ina_count;
|
|
|
|
ina->port = cmd->addina.port;
|
|
ina->addr_flags = cmd->addina.addr_flags;
|
|
ina->rs = cmd->addina.rs;
|
|
ina->type = cmd->addina.type;
|
|
|
|
/*
|
|
* INAs can be shared, in that they will have various values
|
|
* (and therefore registers) read from them each cycle, including
|
|
* power, voltage, current. If only a single value is read,
|
|
* we an use i2c_readagain for faster transactions as we don't
|
|
* have to respecify the address.
|
|
*/
|
|
ina->shared = 0;
|
|
#ifdef USB_POWER_VERBOSE
|
|
ina->shared = 1;
|
|
#endif
|
|
|
|
/* Check if shared with previously configured INAs. */
|
|
for (i = 0; i < state->ina_count; i++) {
|
|
struct usb_power_ina_cfg *tmp = state->ina_cfg + i;
|
|
|
|
if ((tmp->port == ina->port) &&
|
|
(tmp->addr_flags == ina->addr_flags)) {
|
|
ina->shared = 1;
|
|
tmp->shared = 1;
|
|
}
|
|
}
|
|
|
|
state->ina_count += 1;
|
|
return USB_POWER_SUCCESS;
|
|
}
|
|
|
|
static int usb_power_read(struct usb_power_config const *config)
|
|
{
|
|
/*
|
|
* If there is a USB packet waiting we process it and generate a
|
|
* response.
|
|
*/
|
|
uint8_t count = rx_ep_pending(config->endpoint);
|
|
uint8_t result = USB_POWER_SUCCESS;
|
|
union usb_power_command_data *cmd =
|
|
(union usb_power_command_data *)config->ep->out_databuffer;
|
|
|
|
struct usb_power_state *state = config->state;
|
|
struct dwc_usb_ep *ep = config->ep;
|
|
|
|
/* Bytes to return */
|
|
int in_msgsize = 1;
|
|
|
|
if (count < 2)
|
|
return EC_ERROR_INVAL;
|
|
|
|
/* State machine. */
|
|
switch (cmd->command) {
|
|
case USB_POWER_CMD_RESET:
|
|
result = usb_power_state_reset(config);
|
|
break;
|
|
|
|
case USB_POWER_CMD_STOP:
|
|
result = usb_power_state_stop(config);
|
|
break;
|
|
|
|
case USB_POWER_CMD_START:
|
|
result = usb_power_state_start(config, cmd, count);
|
|
if (result == USB_POWER_SUCCESS) {
|
|
/* Send back actual integration time. */
|
|
ep->in_databuffer[1] =
|
|
(state->integration_us >> 0) & 0xff;
|
|
ep->in_databuffer[2] =
|
|
(state->integration_us >> 8) & 0xff;
|
|
ep->in_databuffer[3] =
|
|
(state->integration_us >> 16) & 0xff;
|
|
ep->in_databuffer[4] =
|
|
(state->integration_us >> 24) & 0xff;
|
|
in_msgsize += 4;
|
|
}
|
|
break;
|
|
|
|
case USB_POWER_CMD_ADDINA:
|
|
result = usb_power_state_addina(config, cmd, count);
|
|
break;
|
|
|
|
case USB_POWER_CMD_SETTIME:
|
|
result = usb_power_state_settime(config, cmd, count);
|
|
break;
|
|
|
|
case USB_POWER_CMD_NEXT:
|
|
if (state->state == USB_POWER_STATE_CAPTURING) {
|
|
int ret;
|
|
|
|
ret = usb_power_write_line(config);
|
|
if (ret)
|
|
return EC_SUCCESS;
|
|
|
|
result = USB_POWER_ERROR_BUSY;
|
|
} else {
|
|
CPRINTS("[STOP] Error not capturing.");
|
|
result = USB_POWER_ERROR_NOT_CAPTURING;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
CPRINTS("[ERROR] Unknown command 0x%04x", (int)cmd->command);
|
|
result = USB_POWER_ERROR_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
/* Return result code if applicable. */
|
|
ep->in_databuffer[0] = result;
|
|
|
|
usb_write_ep(config->endpoint, in_msgsize, ep->in_databuffer);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
* INA231 interface.
|
|
* List the registers and fields here.
|
|
* TODO(nsanders): combine with the currently incompatible common INA drivers.
|
|
*/
|
|
|
|
#define INA231_REG_CONF 0
|
|
#define INA231_REG_RSHV 1
|
|
#define INA231_REG_BUSV 2
|
|
#define INA231_REG_PWR 3
|
|
#define INA231_REG_CURR 4
|
|
#define INA231_REG_CAL 5
|
|
#define INA231_REG_EN 6
|
|
|
|
|
|
#define INA231_CONF_AVG(val) (((int)(val & 0x7)) << 9)
|
|
#define INA231_CONF_BUS_TIME(val) (((int)(val & 0x7)) << 6)
|
|
#define INA231_CONF_SHUNT_TIME(val) (((int)(val & 0x7)) << 3)
|
|
#define INA231_CONF_MODE(val) (((int)(val & 0x7)) << 0)
|
|
#define INA231_MODE_OFF 0x0
|
|
#define INA231_MODE_SHUNT 0x5
|
|
#define INA231_MODE_BUS 0x6
|
|
#define INA231_MODE_BOTH 0x7
|
|
|
|
int reg_type_mapping(enum usb_power_ina_type ina_type)
|
|
{
|
|
switch (ina_type) {
|
|
case USBP_INA231_POWER:
|
|
return INA231_REG_PWR;
|
|
case USBP_INA231_BUSV:
|
|
return INA231_REG_BUSV;
|
|
case USBP_INA231_CURRENT:
|
|
return INA231_REG_CURR;
|
|
case USBP_INA231_SHUNTV:
|
|
return INA231_REG_RSHV;
|
|
|
|
default:
|
|
return INA231_REG_CONF;
|
|
}
|
|
}
|
|
|
|
uint16_t ina2xx_readagain(uint8_t port, uint16_t slave_addr_flags)
|
|
{
|
|
int res;
|
|
uint16_t val;
|
|
|
|
res = i2c_xfer(port, slave_addr_flags,
|
|
NULL, 0, (uint8_t *)&val, sizeof(uint16_t));
|
|
|
|
if (res) {
|
|
CPRINTS("INA2XX I2C readagain failed p:%d a:%02x",
|
|
(int)port, (int)I2C_GET_ADDR(slave_addr_flags));
|
|
return 0x0bad;
|
|
}
|
|
return (val >> 8) | ((val & 0xff) << 8);
|
|
}
|
|
|
|
|
|
uint16_t ina2xx_read(uint8_t port, uint16_t slave_addr_flags,
|
|
uint8_t reg)
|
|
{
|
|
int res;
|
|
int val;
|
|
|
|
res = i2c_read16(port, slave_addr_flags, reg, &val);
|
|
if (res) {
|
|
CPRINTS("INA2XX I2C read failed p:%d a:%02x, r:%02x",
|
|
(int)port, (int)I2C_GET_ADDR(slave_addr_flags),
|
|
(int)reg);
|
|
return 0x0bad;
|
|
}
|
|
return (val >> 8) | ((val & 0xff) << 8);
|
|
}
|
|
|
|
int ina2xx_write(uint8_t port, uint16_t slave_addr_flags,
|
|
uint8_t reg, uint16_t val)
|
|
{
|
|
int res;
|
|
uint16_t be_val = (val >> 8) | ((val & 0xff) << 8);
|
|
|
|
res = i2c_write16(port, slave_addr_flags, reg, be_val);
|
|
if (res)
|
|
CPRINTS("INA2XX I2C write failed");
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
* Background tasks
|
|
*
|
|
* Here we setup the INAs and read them at the specified interval.
|
|
* INA samples are stored in a ringbuffer that can be fetched using the
|
|
* USB commands.
|
|
*/
|
|
|
|
/* INA231 integration and averaging time presets, indexed by register value */
|
|
#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
|
|
static const int average_settings[] = {
|
|
1, 4, 16, 64, 128, 256, 512, 1024};
|
|
static const int conversion_time_us[] = {
|
|
140, 204, 332, 588, 1100, 2116, 4156, 8244};
|
|
|
|
static int usb_power_init_inas(struct usb_power_config const *config)
|
|
{
|
|
struct usb_power_state *state = config->state;
|
|
int i;
|
|
int shunt_time = 0;
|
|
int avg = 0;
|
|
int target_us = state->integration_us;
|
|
|
|
if (state->state != USB_POWER_STATE_SETUP) {
|
|
CPRINTS("[ERROR] usb_power_init_inas while not SETUP");
|
|
return -1;
|
|
}
|
|
|
|
/* Find an INA preset integration time less than specified */
|
|
while (shunt_time < (NELEMS(conversion_time_us) - 1)) {
|
|
if (conversion_time_us[shunt_time + 1] > target_us)
|
|
break;
|
|
shunt_time++;
|
|
}
|
|
|
|
/* Find an averaging setting from the INA presets that fits. */
|
|
while (avg < (NELEMS(average_settings) - 1)) {
|
|
if ((conversion_time_us[shunt_time] *
|
|
average_settings[avg + 1])
|
|
> target_us)
|
|
break;
|
|
avg++;
|
|
}
|
|
|
|
state->integration_us =
|
|
conversion_time_us[shunt_time] * average_settings[avg];
|
|
|
|
for (i = 0; i < state->ina_count; i++) {
|
|
int value;
|
|
int ret;
|
|
struct usb_power_ina_cfg *ina = state->ina_cfg + i;
|
|
|
|
#ifdef USB_POWER_VERBOSE
|
|
{
|
|
int conf, cal;
|
|
|
|
conf = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_CONF);
|
|
cal = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_CAL);
|
|
CPRINTS("[CAP] %d (%d,0x%02x): conf:%x, cal:%x",
|
|
i, ina->port, I2C_GET_ADDR(ina->addr_flags),
|
|
conf, cal);
|
|
}
|
|
#endif
|
|
/*
|
|
* Calculate INA231 Calibration register
|
|
* CurrentLSB = uA per div = 80mV / (Rsh * 2^15)
|
|
* CurrentLSB 100x uA = 100x 80000000nV / (Rsh mOhm * 0x8000)
|
|
*/
|
|
/* TODO: allow voltage readings if no sense resistor. */
|
|
if (ina->rs == 0)
|
|
return -1;
|
|
|
|
ina->scale = (100 * (80000000 / 0x8000)) / ina->rs;
|
|
|
|
/*
|
|
* CAL = .00512 / (CurrentLSB * Rsh)
|
|
* CAL = 5120000 / (uA * mOhm)
|
|
*/
|
|
if (ina->scale == 0)
|
|
return -1;
|
|
value = (5120000 * 100) / (ina->scale * ina->rs);
|
|
ret = ina2xx_write(ina->port, ina->addr_flags,
|
|
INA231_REG_CAL, value);
|
|
if (ret != EC_SUCCESS) {
|
|
CPRINTS("[CAP] usb_power_init_inas CAL FAIL: %d", ret);
|
|
return ret;
|
|
}
|
|
#ifdef USB_POWER_VERBOSE
|
|
{
|
|
int actual;
|
|
|
|
actual = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_CAL);
|
|
CPRINTS("[CAP] scale: %d uA/div, %d uW/div, cal:%x act:%x",
|
|
ina->scale / 100, ina->scale*25/100, value, actual);
|
|
}
|
|
#endif
|
|
/* Conversion time, shunt + bus, set average. */
|
|
value = INA231_CONF_MODE(INA231_MODE_BOTH) |
|
|
INA231_CONF_SHUNT_TIME(shunt_time) |
|
|
INA231_CONF_BUS_TIME(shunt_time) |
|
|
INA231_CONF_AVG(avg);
|
|
ret = ina2xx_write(ina->port, ina->addr_flags,
|
|
INA231_REG_CONF, value);
|
|
if (ret != EC_SUCCESS) {
|
|
CPRINTS("[CAP] usb_power_init_inas CONF FAIL: %d", ret);
|
|
return ret;
|
|
}
|
|
#ifdef USB_POWER_VERBOSE
|
|
{
|
|
int actual;
|
|
|
|
actual = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_CONF);
|
|
CPRINTS("[CAP] %d (%d,0x%02x): conf:%x, act:%x",
|
|
i, ina->port, I2C_GET_ADDR(ina->addr_flags),
|
|
value, actual);
|
|
}
|
|
#endif
|
|
#ifdef USB_POWER_VERBOSE
|
|
{
|
|
int busv_mv =
|
|
(ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_BUSV)
|
|
* 125) / 100;
|
|
|
|
CPRINTS("[CAP] %d (%d,0x%02x): busv:%dmv",
|
|
i, ina->port, I2C_GET_ADDR(ina->addr_flags),
|
|
busv_mv);
|
|
}
|
|
#endif
|
|
/* Initialize read from power register. This register address
|
|
* will be cached and all ina2xx_readagain() calls will read
|
|
* from the same address.
|
|
*/
|
|
ina2xx_read(ina->port, ina->addr_flags,
|
|
reg_type_mapping(ina->type));
|
|
#ifdef USB_POWER_VERBOSE
|
|
CPRINTS("[CAP] %d (%d,0x%02x): type:%d", (int)(ina->type));
|
|
#endif
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read each INA's power integration measurement.
|
|
*
|
|
* INAs recall the most recent address, so no register access write is
|
|
* necessary, simply read 16 bits from each INA and fill the result into
|
|
* the power record.
|
|
*
|
|
* If the power record ringbuffer is full, fail with USB_POWER_ERROR_OVERFLOW.
|
|
*/
|
|
static int usb_power_get_samples(struct usb_power_config const *config)
|
|
{
|
|
uint64_t time = get_time().val;
|
|
struct usb_power_state *state = config->state;
|
|
struct usb_power_report *r = (struct usb_power_report *)(
|
|
state->reports_data_area +
|
|
(USB_POWER_RECORD_SIZE(state->ina_count)
|
|
* state->reports_head));
|
|
struct usb_power_ina_cfg *inas = state->ina_cfg;
|
|
int i;
|
|
|
|
/* TODO(nsanders): Would we prefer to evict oldest? */
|
|
if (((state->reports_head + 1) % USB_POWER_MAX_CACHED(state->ina_count))
|
|
== state->reports_xmit_active) {
|
|
CPRINTS("Overflow! h:%d a:%d t:%d (%d)",
|
|
state->reports_head, state->reports_xmit_active,
|
|
state->reports_tail,
|
|
USB_POWER_MAX_CACHED(state->ina_count));
|
|
return USB_POWER_ERROR_OVERFLOW;
|
|
}
|
|
|
|
r->status = USB_POWER_SUCCESS;
|
|
r->size = state->ina_count;
|
|
if (config->state->wall_offset)
|
|
time = time + config->state->wall_offset;
|
|
else
|
|
time -= config->state->base_time;
|
|
r->timestamp = time;
|
|
|
|
for (i = 0; i < state->ina_count; i++) {
|
|
int regval;
|
|
struct usb_power_ina_cfg *ina = inas + i;
|
|
|
|
/* Read INA231.
|
|
* ina2xx_read(ina->port, ina->addr, INA231_REG_PWR);
|
|
* Readagain cached this address so we'll save an I2C
|
|
* transaction.
|
|
*/
|
|
if (ina->shared)
|
|
regval = ina2xx_read(ina->port, ina->addr_flags,
|
|
reg_type_mapping(ina->type));
|
|
else
|
|
regval = ina2xx_readagain(ina->port,
|
|
ina->addr_flags);
|
|
r->power[i] = regval;
|
|
#ifdef USB_POWER_VERBOSE
|
|
{
|
|
int current;
|
|
int power;
|
|
int voltage;
|
|
int bvoltage;
|
|
|
|
voltage = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_RSHV);
|
|
bvoltage = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_BUSV);
|
|
current = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_CURR);
|
|
power = ina2xx_read(ina->port, ina->addr_flags,
|
|
INA231_REG_PWR);
|
|
{
|
|
int uV = ((int)voltage * 25) / 10;
|
|
int mV = ((int)bvoltage * 125) / 100;
|
|
int uA = (uV * 1000) / ina->rs;
|
|
int CuA = (((int)current * ina->scale) / 100);
|
|
int uW = (((int)power * ina->scale*25)/100);
|
|
|
|
CPRINTS("[CAP] %d (%d,0x%02x): %dmV / %dmO = %dmA",
|
|
i, ina->port, I2C_GET_ADDR(ina->addr_flags),
|
|
uV/1000, ina->rs, uA/1000);
|
|
CPRINTS("[CAP] %duV %dmV %duA %dCuA "
|
|
"%duW v:%04x, b:%04x, p:%04x",
|
|
uV, mV, uA, CuA, uW, voltage, bvoltage, power);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Mark this slot as used. */
|
|
state->reports_head = (state->reports_head + 1) %
|
|
USB_POWER_MAX_CACHED(state->ina_count);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* This function is called every [interval] uS, and reads the accumulated
|
|
* values of the INAs, and reschedules itself for the next interval.
|
|
*
|
|
* It will stop collecting frames if a ringbuffer overflow is
|
|
* detected, or a stop request is seen..
|
|
*/
|
|
void usb_power_deferred_cap(struct usb_power_config const *config)
|
|
{
|
|
int ret;
|
|
uint64_t timeout = get_time().val + config->state->integration_us;
|
|
uint64_t timein;
|
|
|
|
/* Exit if we have stopped capturing in the meantime. */
|
|
if (config->state->state != USB_POWER_STATE_CAPTURING)
|
|
return;
|
|
|
|
/* Get samples for this timeslice */
|
|
ret = usb_power_get_samples(config);
|
|
if (ret == USB_POWER_ERROR_OVERFLOW) {
|
|
CPRINTS("[CAP] usb_power_deferred_cap: OVERFLOW");
|
|
return;
|
|
}
|
|
|
|
/* Calculate time remaining until next slice. */
|
|
timein = get_time().val;
|
|
if (timeout > timein)
|
|
timeout = timeout - timein;
|
|
else
|
|
timeout = 0;
|
|
|
|
/* Double check if we are still capturing. */
|
|
if (config->state->state == USB_POWER_STATE_CAPTURING)
|
|
hook_call_deferred(config->deferred_cap, timeout);
|
|
}
|
|
|