blob: 826369b50bc1b4b0ed0f0a4925d84abd7a2e83be [file] [log] [blame]
/* Copyright (c) 2014 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 "console.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "usb_pd.h"
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
/* TODO(crossbug.com/p/28869): update source and sink tables to spec. */
const uint32_t pd_src_pdo[] = {
PDO_FIXED(5000, 500, PDO_FIXED_EXTERNAL),
PDO_FIXED(5000, 900, 0),
};
const int pd_src_pdo_cnt = ARRAY_SIZE(pd_src_pdo);
/* TODO(crossbug.com/p/28869): update source and sink tables to spec. */
const uint32_t pd_snk_pdo[] = {
PDO_BATT(4500, 5500, 15000),
PDO_BATT(11500, 12500, 36000),
};
const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo);
/* Cap on the max voltage requested as a sink (in millivolts) */
static unsigned max_mv = -1; /* no cap */
/* PD MCU status for host response */
static struct ec_response_pd_status pd_status;
int pd_choose_voltage(int cnt, uint32_t *src_caps, uint32_t *rdo)
{
int i;
int sel_mv;
int max_uw = 0;
int max_ma;
int max_i = -1;
/* Get max power */
for (i = 0; i < cnt; i++) {
int uw;
int mv = ((src_caps[i] >> 10) & 0x3FF) * 50;
if ((src_caps[i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) {
uw = 250000 * (src_caps[i] & 0x3FF);
} else {
int ma = (src_caps[i] & 0x3FF) * 10;
uw = ma * mv;
}
if ((uw > max_uw) && (mv <= max_mv)) {
max_i = i;
max_uw = uw;
sel_mv = mv;
}
}
if (max_i < 0)
return -EC_ERROR_UNKNOWN;
/* request all the power ... */
if ((src_caps[max_i] & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) {
int uw = 250000 * (src_caps[max_i] & 0x3FF);
max_ma = uw / sel_mv;
*rdo = RDO_BATT(max_i + 1, uw/2, uw, 0);
ccprintf("Request [%d] %dV %dmW\n",
max_i, sel_mv/1000, uw/1000);
} else {
int ma = 10 * (src_caps[max_i] & 0x3FF);
max_ma = ma;
*rdo = RDO_FIXED(max_i + 1, ma / 2, ma, 0);
ccprintf("Request [%d] %dV %dmA\n",
max_i, sel_mv/1000, ma);
}
return max_ma;
}
void pd_set_max_voltage(unsigned mv)
{
max_mv = mv;
}
int pd_request_voltage(uint32_t rdo)
{
int op_ma = rdo & 0x3FF;
int max_ma = (rdo >> 10) & 0x3FF;
int idx = rdo >> 28;
uint32_t pdo;
uint32_t pdo_ma;
if (!idx || idx > pd_src_pdo_cnt)
return EC_ERROR_INVAL; /* Invalid index */
/* check current ... */
pdo = pd_src_pdo[idx - 1];
pdo_ma = (pdo & 0x3ff);
if (op_ma > pdo_ma)
return EC_ERROR_INVAL; /* too much op current */
if (max_ma > pdo_ma)
return EC_ERROR_INVAL; /* too much max current */
ccprintf("Switch to %d V %d mA (for %d/%d mA)\n",
((pdo >> 10) & 0x3ff) * 50, (pdo & 0x3ff) * 10,
((rdo >> 10) & 0x3ff) * 10, (rdo & 0x3ff) * 10);
return EC_SUCCESS;
}
int pd_set_power_supply_ready(int port)
{
/* provide VBUS */
gpio_set_level(port ? GPIO_USB_C1_5V_EN : GPIO_USB_C0_5V_EN, 1);
return EC_SUCCESS; /* we are ready */
}
void pd_power_supply_reset(int port)
{
/* Kill VBUS */
gpio_set_level(port ? GPIO_USB_C1_5V_EN : GPIO_USB_C0_5V_EN, 0);
}
static void pd_send_ec_int(void)
{
gpio_set_level(GPIO_EC_INT_L, 0);
/*
* Delay long enough to guarantee EC see's the change. Slowest
* EC clock speed is 250kHz in deep sleep -> 4us, and add 1us
* for buffer.
*/
usleep(5);
gpio_set_level(GPIO_EC_INT_L, 1);
}
void pd_set_input_current_limit(uint32_t max_ma)
{
pd_status.curr_lim_ma = max_ma;
pd_send_ec_int();
}
int pd_board_checks(void)
{
return EC_SUCCESS;
}
static void dual_role_on(void)
{
pd_set_dual_role(PD_DRP_TOGGLE_ON);
CPRINTS("PCH -> S0, enable dual-role toggling");
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, dual_role_on, HOOK_PRIO_DEFAULT);
static void dual_role_off(void)
{
pd_set_dual_role(PD_DRP_TOGGLE_OFF);
CPRINTS("PCH -> S3, disable dual-role toggling");
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, dual_role_off, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, dual_role_off, HOOK_PRIO_DEFAULT);
static void dual_role_force_sink(void)
{
pd_set_dual_role(PD_DRP_FORCE_SINK);
CPRINTS("PCH -> S5, force dual-role port to sink");
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, HOOK_PRIO_DEFAULT);
/* ----------------- Vendor Defined Messages ------------------ */
int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload)
{
int cmd = PD_VDO_CMD(payload[0]);
int i;
ccprintf("VDM/%d [%d] %08x\n", cnt, cmd, payload[0]);
/* make sure we have some payload */
if (cnt == 0)
return 0;
switch (cmd) {
case VDO_CMD_VERSION:
/* guarantee last byte of payload is null character */
*(payload + cnt - 1) = 0;
ccprintf("version: %s\n", (char *)(payload+1));
break;
case VDO_CMD_RW_HASH:
ccprintf("RW Hash: ");
payload++; /* skip cmd */
for (i = 0; i < cnt - 1; i++)
ccprintf("%08x ", *payload++);
ccprintf("\n");
break;
case VDO_CMD_CURRENT:
ccprintf("Current: %dmA\n", payload[1]);
break;
}
return 0;
}
/****************************************************************************/
/* Console commands */
static int command_ec_int(int argc, char **argv)
{
pd_send_ec_int();
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(ecint, command_ec_int,
"",
"Toggle EC interrupt line",
NULL);
/****************************************************************************/
/* Host commands */
static int ec_status_host_cmd(struct host_cmd_handler_args *args)
{
const struct ec_params_pd_status *p = args->params;
struct ec_response_pd_status *r = args->response;
board_update_battery_soc(p->batt_soc);
*r = pd_status;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PD_EXCHANGE_STATUS, ec_status_host_cmd,
EC_VER_MASK(0));