blob: 9162cf221048c6644b83e297fcb38eb7bb332c5f [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020 Google, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/of.h>
#include <linux/time.h>
#include <linux/usb/pd.h>
#include <linux/usb/tcpm.h>
#include <linux/alarmtimer.h>
#include <misc/logbuffer.h>
#include "google_bms.h"
#include "google_psy.h"
#include "google_dc_pps.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#endif
#define PPS_UPDATE_DELAY_MS 2000
#define OP_SNK_MW 7600 /* see b/159863291 */
#define PD_SNK_MAX_MV 9000
#define PD_SNK_MIN_MV 5000
#define PD_SNK_MAX_MA 3000
#define PD_SNK_MAX_MA_9V 2200
#define PDO_FIXED_FLAGS \
(PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM)
/*
* There is a similar one in tcpm.c
* NOTE: we don't really need to replicate the values in tcpm.c in the
* internal state, we just need to know to set 2 to the TCPM power supply.
*/
enum tcpm_psy_online_states {
TCPM_PSY_OFFLINE = 0,
TCPM_PSY_FIXED_ONLINE,
TCPM_PSY_PROG_ONLINE,
};
#define get_boot_sec() div_u64(ktime_to_ns(ktime_get_boottime()), NSEC_PER_SEC)
void pps_log(struct pd_pps_data *pps, const char *fmt, ...)
{
va_list args;
if (!pps || !pps->log)
return;
va_start(args, fmt);
logbuffer_vlog(pps->log, fmt, args);
va_end(args);
}
// EXPORT_SYMBOL_GPL(pps_log);
/* SW enable detect setting ->stage = PPS_NONE and calling pps_work() */
void pps_init_state(struct pd_pps_data *pps_data)
{
/* pps_data->src_caps can be NULL */
#if 0 //TODO
tcpm_put_partner_src_caps(&pps_data->src_caps);
#endif
pps_data->pd_online = TCPM_PSY_OFFLINE;
pps_data->stage = PPS_DISABLED;
pps_data->keep_alive_cnt = 0;
pps_data->nr_src_cap = 0;
pps_data->src_caps = NULL;
/* not reference counted */
if (pps_data->stay_awake)
__pm_relax(pps_data->pps_ws);
}
// EXPORT_SYMBOL_GPL(pps_init_state);
static struct tcpm_port *chg_get_tcpm_port(struct power_supply *tcpm_psy)
{
return tcpm_psy ? power_supply_get_drvdata(tcpm_psy) : NULL;
}
/* PUBLIC TODO: pass the actual PDO, deprecate */
int chg_update_capability(struct power_supply *tcpm_psy,
unsigned int nr_pdo,
u32 pps_cap)
{
struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
const u32 pdo[] = {PDO_FIXED(5000, PD_SNK_MAX_MA, PDO_FIXED_FLAGS),
PDO_FIXED(PD_SNK_MAX_MV, PD_SNK_MAX_MA_9V, 0),
pps_cap};
if (!port)
return -ENODEV;
if (!nr_pdo || nr_pdo > PDO_MAX_SUPP)
return -EINVAL;
return tcpm_update_sink_capabilities(port, pdo, nr_pdo, OP_SNK_MW);
}
/* false when not present or error (either way don't run) */
static unsigned int pps_is_avail(struct pd_pps_data *pps,
struct power_supply *tcpm_psy)
{
pps->max_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX);
pps->min_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_MIN);
pps->max_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_MAX);
pps->out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
pps->op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
if (pps->max_uv < 0 || pps->min_uv < 0 || pps->max_ua < 0 ||
pps->out_uv < 0 || pps->op_ua < 0)
return PPS_NONE;
/* TODO: lower the loglevel after the development stage */
pps_log(pps, "max_v %d, min_v %d, max_c %d, out_v %d, op_c %d",
pps->max_uv, pps->min_uv, pps->max_ua, pps->out_uv,
pps->op_ua);
/* FIXME: set interval to PD_T_PPS_TIMEOUT here may cause timeout */
return PPS_AVAILABLE;
}
/* make sure that the adapter doesn't revert back to FIXED PDO */
int pps_ping(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
{
int rc;
if (!tcpm_psy)
return -ENODEV;
rc = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
TCPM_PSY_PROG_ONLINE);
if (rc == 0)
pps->pd_online = TCPM_PSY_PROG_ONLINE;
else if (rc != -EAGAIN && rc != -EOPNOTSUPP)
pps_log(pps, "failed to ping, ret = %d", rc);
return rc;
}
// EXPORT_SYMBOL_GPL(pps_ping);
int pps_get_src_cap(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
{
struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
if (!pps || !port)
return -EINVAL;
pps->nr_src_cap = tcpm_get_partner_src_caps(port, &pps->src_caps);
return pps->nr_src_cap;
}
// EXPORT_SYMBOL_GPL(pps_get_src_cap);
/* assume that we are already online and in PPS stage */
bool pps_prog_check_online(struct pd_pps_data *pps_data,
struct power_supply *tcpm_psy)
{
int pd_online = 0;
pd_online = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE);
if (pd_online == 0) {
pps_init_state(pps_data);
return false;
}
if (pd_online != TCPM_PSY_PROG_ONLINE)
goto not_supp;
if (pps_data->stage != PPS_ACTIVE) {
int rc;
pps_data->stage = pps_is_avail(pps_data, tcpm_psy);
if (pps_data->stage != PPS_AVAILABLE) {
pr_debug("%s: not available\n", __func__);
goto not_supp;
}
rc = pps_ping(pps_data, tcpm_psy);
if (rc < 0) {
pr_debug("%s: ping failed %d\n", __func__, rc);
goto not_supp;
}
pps_data->last_update = get_boot_sec();
rc = pps_get_src_cap(pps_data, tcpm_psy);
if (rc < 0) {
pr_debug("%s: no source caps %d\n", __func__, rc);
goto not_supp;
}
pps_data->pd_online = TCPM_PSY_PROG_ONLINE;
pps_data->stage = PPS_ACTIVE;
}
return true;
not_supp:
pps_init_state(pps_data);
pps_data->stage = PPS_NOTSUPP;
return false;
}
// EXPORT_SYMBOL_GPL(pps_prog_check_online);
/* enable PPS prog mode (Internal), also start the negotiation */
static int pps_prog_online(struct pd_pps_data *pps,
struct power_supply *tcpm_psy)
{
int ret;
ret = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
TCPM_PSY_PROG_ONLINE);
if (ret == 0) {
pps->pd_online = TCPM_PSY_PROG_ONLINE;
pps->stage = PPS_NONE;
}
pps->last_update = get_boot_sec();
return ret;
}
/* Disable PPS prog mode, will end up in PPS_NOTSUP */
int pps_prog_offline(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
{
int ret;
if (!tcpm_psy)
return -ENODEV;
/* pd_online = TCPM_PSY_OFFLINE, stage = PPS_NONE; */
ret = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
TCPM_PSY_FIXED_ONLINE);
if (ret == -EOPNOTSUPP)
ret = 0;
if (ret == 0)
pps_init_state(pps);
return ret;
}
// EXPORT_SYMBOL_GPL(pps_prog_offline);
void pps_adjust_volt(struct pd_pps_data *pps, int mod)
{
if (mod > 0) {
pps->out_uv = (pps->out_uv + mod) < pps->max_uv ?
(pps->out_uv + mod) : pps->max_uv;
} else if (mod < 0) {
pps->out_uv = (pps->out_uv + mod) > pps->min_uv ?
(pps->out_uv + mod) : pps->min_uv;
}
}
/*
* Note: Some adapters have several PPS profiles providing different voltage
* ranges and different maximal currents. If the device demands more power from
* the adapter but reached the maximum it can get in the current profile,
* search if there exists another profile providing more power. If it demands
* less power, search if there exists another profile providing enough power
* with higher current.
*
* return negative on errors or no suitable profile
* return 0 on successful profile switch
*/
int pps_switch_profile(struct pd_pps_data *pps, struct power_supply *tcpm_psy,
bool more_pwr)
{
int i, ret = -ENODATA;
u32 pdo;
u32 max_mv, max_ma, max_mw;
u32 current_mw, current_ma;
if (pps->nr_src_cap < 2)
return -EINVAL;
current_ma = pps->op_ua / 1000;
current_mw = (pps->out_uv / 1000) * current_ma / 1000;
for (i = 1; i < pps->nr_src_cap; i++) {
pdo = pps->src_caps[i];
if (pdo_type(pdo) != PDO_TYPE_APDO)
continue;
/* TODO: use pps_data sink capabilities */
max_mv = min_t(u32, PD_SNK_MAX_MV,
pdo_pps_apdo_max_voltage(pdo));
/* TODO: use pps_data sink capabilities */
max_ma = min_t(u32, PD_SNK_MAX_MA,
pdo_pps_apdo_max_current(pdo));
max_mw = max_mv * max_ma / 1000;
if (more_pwr && max_mw > current_mw) {
/* export maximal capability, TODO: use sink cap */
pdo = PDO_PPS_APDO(PD_SNK_MIN_MV,
PD_SNK_MAX_MV,
PD_SNK_MAX_MA);
ret = chg_update_capability(tcpm_psy, PDO_PPS, pdo);
if (ret < 0)
pps_log(pps, "Failed to update sink caps, ret %d",
ret);
break;
} else if (!more_pwr && max_mw >= current_mw &&
max_ma > current_ma) {
/* TODO: tune the max_mv, fix this */
pdo = PDO_PPS_APDO(PD_SNK_MIN_MV, 6000, PD_SNK_MAX_MA);
ret = chg_update_capability(tcpm_psy, PDO_PPS, pdo);
if (ret < 0)
pps_log(pps, "Failed to update sink caps, ret %d",
ret);
break;
}
}
if (ret == 0) {
pps->keep_alive_cnt = 0;
pps->stage = PPS_NONE;
}
return ret;
}
/* ------------------------------------------------------------------------ */
static int debug_get_pps_out_uv(void *data, u64 *val)
{
struct pd_pps_data *pps_data = data;
*val = pps_data->out_uv;
return 0;
}
static int debug_set_pps_out_uv(void *data, u64 val)
{
struct pd_pps_data *pps_data = data;
/* TODO: use votable */
pps_data->out_uv = val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_pps_out_uv_fops,
debug_get_pps_out_uv,
debug_set_pps_out_uv, "%llu\n");
static int debug_get_pps_op_ua(void *data, u64 *val)
{
struct pd_pps_data *pps_data = data;
*val = pps_data->op_ua;
return 0;
}
static int debug_set_pps_op_ua(void *data, u64 val)
{
struct pd_pps_data *pps_data = data;
/* TODO: use votable */
pps_data->op_ua = val;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_pps_op_ua_fops,
debug_get_pps_op_ua,
debug_set_pps_op_ua, "%llu\n");
int pps_init_fs(struct pd_pps_data *pps_data, struct dentry *de)
{
if (!de)
return -ENODEV;
debugfs_create_file("pps_out_uv", 0600, de, pps_data,
&debug_pps_out_uv_fops);
debugfs_create_file("pps_out_ua", 0600, de, pps_data,
&debug_pps_op_ua_fops);
debugfs_create_file("pps_op_ua", 0600, de, pps_data,
&debug_pps_op_ua_fops);
return 0;
}
// EXPORT_SYMBOL_GPL(pps_init_fs);
/* ------------------------------------------------------------------------ */
static int pps_get_sink_pdo(u32 *snk_pdo, int max, struct device_node *node)
{
struct device_node *dn, *conn;
unsigned int nr_snk_pdo;
const __be32 *prop;
int length, ret;
prop = of_get_property(node, "google,usbc-connector", NULL);
if (!prop)
prop = of_get_property(node, "google,usb-c-connector", NULL);
if (!prop)
prop = of_get_property(node, "google,tcpm-power-supply", NULL);
if (!prop) {
pr_err("Couldn't find property \n");
return -ENOENT;
}
dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!dn) {
pr_err("Couldn't find usb_con node\n");
return -ENOENT;
}
conn = of_get_child_by_name(dn, "connector");
if (conn) {
of_node_put(dn);
dn = conn;
}
prop = of_get_property(dn, "sink-pdos", &length);
if (!prop) {
pr_err("Couldn't find sink-pdos property\n");
of_node_put(dn);
return -ENOENT;
}
nr_snk_pdo = length / sizeof(u32);
if (nr_snk_pdo == 0 || nr_snk_pdo > max) {
pr_err("Invalid length of sink-pdos\n");
of_node_put(dn);
return -EINVAL;
}
ret = of_property_read_u32_array(dn, "sink-pdos", snk_pdo, nr_snk_pdo);
of_node_put(dn);
if (ret < 0)
return ret;
return nr_snk_pdo;
}
static int pps_find_apdo(struct pd_pps_data *pps_data, int nr_snk_pdo)
{
int i;
for (i = 0; i < nr_snk_pdo; i++) {
u32 pdo = pps_data->snk_pdo[i];
if (pdo_type(pdo) != PDO_TYPE_FIXED)
pr_debug("%s %d type=%d", __func__, i, pdo_type(pdo));
else
pr_debug("%s %d FIXED v=%d c=%d", __func__, i,
pdo_fixed_voltage(pdo),
pdo_max_current(pdo));
if (pdo_type(pdo) == PDO_TYPE_APDO) {
/* TODO: check APDO_TYPE_PPS */
pps_data->default_pps_pdo = pdo;
return 0;
}
}
return -ENODATA;
}
static int pps_init_node(struct pd_pps_data *pps_data,
struct device_node *node)
{
int ret, nr_snk_pdo;
memset(pps_data, 0, sizeof(*pps_data));
nr_snk_pdo = pps_get_sink_pdo(pps_data->snk_pdo, PDO_MAX_OBJECTS, node);
if (nr_snk_pdo < 0) {
pr_err("Couldn't read sink-pdos, ret %d\n", nr_snk_pdo);
return -ENOENT;
}
ret = pps_find_apdo(pps_data, nr_snk_pdo);
if (ret < 0) {
pr_err("nr_sink_pdo=%d sink APDO not found ret=%d\n",
nr_snk_pdo, ret);
return -ENOENT;
}
/*
* The port needs to ping or update the PPS adapter every 10 seconds
* (maximum). However, Qualcomm PD phy returns error when system is
* waking up. To prevent the timeout when system is resumed from
* suspend, hold a wakelock while PPS is active.
*
* Remove this wakeup source once we fix the Qualcomm PD phy issue.
*/
pps_data->stay_awake = of_property_read_bool(node, "google,pps-awake");
if (pps_data->stay_awake) {
pps_data->pps_ws = wakeup_source_register(NULL, "google-pps");
if (!pps_data->pps_ws) {
pr_err("Failed to register wakeup source\n");
return -ENODEV;
}
}
pps_data->nr_snk_pdo = nr_snk_pdo;
return 0;
}
/* Look for the connector and retrieve source capabilities.
* pps_data->nr_snk_pdo == 0 means no PPS
*/
int pps_init(struct pd_pps_data *pps_data, struct device *dev)
{
int ret;
ret = pps_init_node(pps_data, dev->of_node);
if (ret < 0)
return ret;
/* TODO(168287929): name should be const*/
pps_data->log = logbuffer_register((char*)dev_name(dev));
if (IS_ERR(pps_data->log))
pps_data->log = NULL;
return 0;
}
// EXPORT_SYMBOL_GPL(pps_init);
/* ------------------------------------------------------------------------- */
/*
* This is the first part of the DC/PPS charging state machine.
* Detect and configure the PPS adapter for the profile.
*
* returns:
* . the max update interval pps should vote for
* . 0 to disable the PPS update interval voter
* . <0 for error
*/
int pps_work(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
{
int pd_online, usbc_type;
unsigned int stage;
if (!tcpm_psy)
return -ENODEV;
/*
* 2) pps->pd_online == TCPM_PSY_PROG_ONLINE && stage == PPS_NONE
* If the source really support PPS (set in 1): set stage to
* PPS_AVAILABLE and reschedule after PD_T_PPS_TIMEOUT
*/
if (pps->pd_online == TCPM_PSY_PROG_ONLINE && pps->stage == PPS_NONE) {
int rc;
pps->stage = pps_is_avail(pps, tcpm_psy);
if (pps->stage == PPS_AVAILABLE) {
rc = pps_ping(pps, tcpm_psy);
if (rc < 0) {
pps->pd_online = TCPM_PSY_FIXED_ONLINE;
return 0;
}
if (pps->stay_awake)
__pm_stay_awake(pps->pps_ws);
pps->last_update = get_boot_sec();
rc = pps_get_src_cap(pps, tcpm_psy);
if (rc < 0)
pps_log(pps, "Cannot get partner src caps");
}
return PD_T_PPS_TIMEOUT;
}
/*
* no need to retry (error) when I cannot read POWER_SUPPLY_PROP_ONLINE.
* The prop is set to TCPM_PSY_PROG_ONLINE (from TCPM_PSY_FIXED_ONLINE)
* when usbc_type is POWER_SUPPLY_USB_TYPE_PD_PPS.
*/
pd_online = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE);
if (pd_online < 0)
return 0;
/*
* 3) pd_online == TCPM_PSY_PROG_ONLINE == pps->pd_online
* pps is active now, we are done here. pd_online will change to
* if pd_online is !TCPM_PSY_PROG_ONLINE go back to 1) OR exit.
*/
stage = (pd_online == pps->pd_online) &&
(pd_online == TCPM_PSY_PROG_ONLINE) &&
(pps->stage == PPS_AVAILABLE || pps->stage == PPS_ACTIVE) ?
PPS_ACTIVE : PPS_NONE;
if (stage != pps->stage) {
pps_log(pps, "work: pd_online %d->%d stage %d->%d", __func__,
pps->pd_online, pd_online,
pps->stage, stage);
pps->stage = stage;
}
if (pps->stage == PPS_ACTIVE)
return 0;
/*
* 1) stage == PPS_NONE && pps->pd_online!=TCPM_PSY_PROG_ONLINE
* If usbc_type is POWER_SUPPLY_USB_TYPE_PD_PPS and pd_online is
* TCPM_PSY_FIXED_ONLINE, enable PSPS (set POWER_SUPPLY_PROP_ONLINE to
* TCPM_PSY_PROG_ONLINE and reschedule in PD_T_PPS_TIMEOUT.
*/
usbc_type = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_USB_TYPE);
if (pd_online == TCPM_PSY_FIXED_ONLINE &&
usbc_type == POWER_SUPPLY_USB_TYPE_PD_PPS) {
int rc;
rc = pps_prog_online(pps, tcpm_psy);
switch (rc) {
case 0:
return PD_T_PPS_TIMEOUT;
case -EAGAIN:
pps_log(pps, "work: not in SNK_READY, rerun");
return rc;
case -EOPNOTSUPP:
pps->stage = PPS_NOTSUPP;
if (pps->stay_awake)
__pm_relax(pps->pps_ws);
pps_log(pps, "work: PPS not supported for this TA");
break;
default:
pps_log(pps, "work: PROP_ONLINE (%d)", rc);
break;
}
return 0;
}
pps->pd_online = pd_online;
return 0;
}
// EXPORT_SYMBOL_GPL(pps_work);
int pps_keep_alive(struct pd_pps_data *pps, struct power_supply *tcpm_psy)
{
int ret;
if (!tcpm_psy)
return -ENODEV;
ret = pps_ping(pps, tcpm_psy);
if (ret < 0) {
pps->pd_online = TCPM_PSY_FIXED_ONLINE;
pps->keep_alive_cnt = 0;
return ret;
}
pps->keep_alive_cnt += (pps->keep_alive_cnt < UINT_MAX);
pps->last_update = get_boot_sec();
return 0;
}
// EXPORT_SYMBOL_GPL(pps_keep_alive);
int pps_check_adapter(struct pd_pps_data *pps,
int pending_uv, int pending_ua,
struct power_supply *tcpm_psy)
{
const int scale = 50000; /* HACK */
int out_uv, op_ua;
out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
pr_debug("%s: mv=%d->%d ua=%d,%d\n", __func__,
out_uv, pending_uv, op_ua, pending_ua);
if (out_uv < 0 || op_ua < 0)
return -EIO;
return (out_uv / scale) >= (pending_uv /scale) &&
(op_ua / scale) >= (pending_ua / scale);
}
static int pps_set_prop(struct pd_pps_data *pps,
enum power_supply_property prop, int val,
struct power_supply *tcpm_psy)
{
int ret;
ret = GPSY_SET_PROP(tcpm_psy, prop, val);
if (ret == 0) {
pps->keep_alive_cnt = 0;
} else if (ret == -EOPNOTSUPP) {
pps->pd_online = TCPM_PSY_FIXED_ONLINE;
pps->keep_alive_cnt = 0;
if (pps->stay_awake)
__pm_relax(pps->pps_ws);
}
return ret;
}
/*
* return negative values on errors
* return PD_T_PPS_TIMEOUT after successful updates or pings
* return PPS_UPDATE_DELAY_MS when the update interval is less than
* PPS_UPDATE_DELAY_MS
* return the delta to the nex ping deadline otherwise
*/
int pps_update_adapter(struct pd_pps_data *pps,
int pending_uv, int pending_ua,
struct power_supply *tcpm_psy)
{
int interval = get_boot_sec() - pps->last_update;
int ret;
pps->out_uv = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW);
pps->op_ua = GPSY_GET_PROP(tcpm_psy, POWER_SUPPLY_PROP_CURRENT_NOW);
if (pps->out_uv < 0 || pps->op_ua < 0)
return -EIO;
if (pending_uv < 0)
pending_uv = pps->out_uv;
if (pending_ua < 0)
pending_ua = pps->op_ua;
pr_debug("%s: mv=%d->%d ua=%d->%d interval=%d\n", __func__,
pps->out_uv, pending_uv, pps->op_ua, pending_ua, interval);
/*
* TCPM accepts one change per power negotiation cycle.
* TODO: change voltage, current or current, voltage depending on
* the values.
*/
if (pps->op_ua != pending_ua) {
if (interval * 1000 < PPS_UPDATE_DELAY_MS)
return PPS_UPDATE_DELAY_MS;
ret = pps_set_prop(pps, POWER_SUPPLY_PROP_CURRENT_NOW,
pending_ua, tcpm_psy);
pr_debug("%s: SET_UA out_ua %d->%d, ret=%d", __func__,
pps->op_ua, pending_ua, ret);
pps_log(pps, "SET_UA out_ua %d->%d, ret=%d",
pps->op_ua, pending_ua, ret);
if (ret == 0) {
pps->last_update = get_boot_sec();
pps->op_ua = pending_ua;
return PD_T_PPS_TIMEOUT;
}
if (ret != -EAGAIN && ret != -EOPNOTSUPP)
pps_log(pps, "failed to set CURRENT_NOW, ret = %d",
ret);
} else if (pps->out_uv != pending_uv) {
if (interval * 1000 < PPS_UPDATE_DELAY_MS)
return PPS_UPDATE_DELAY_MS;
ret = pps_set_prop(pps, POWER_SUPPLY_PROP_VOLTAGE_NOW,
pending_uv, tcpm_psy);
pr_debug("%s: SET_UV out_v %d->%d, ret=%d\n", __func__,
pps->out_uv, pending_uv, ret);
pps_log(pps, "SET_UV out_v %d->%d, ret=%d",
pps->out_uv, pending_uv, ret);
if (ret == 0) {
pps->last_update = get_boot_sec();
pps->out_uv = pending_uv;
return PD_T_PPS_TIMEOUT;
}
if (ret != -EAGAIN && ret != -EOPNOTSUPP)
pps_log(pps, "failed to set CURRENT_NOW, ret = %d",
ret);
} else if (interval < PD_T_PPS_DEADLINE_S) {
/* TODO: tune this, now assume that PD_T_PPS_TIMEOUT >= 7s */
return PD_T_PPS_TIMEOUT - (interval * MSEC_PER_SEC);
} else {
ret = pps_keep_alive(pps, tcpm_psy);
pr_debug("%s: KEEP ALIVE out_v %d, op_c %d (%d)", __func__,
pps->out_uv, pps->op_ua, ret);
pps_log(pps, "KEEP ALIVE out_v %d, op_c %d (%d)",
pps->out_uv, pps->op_ua, ret);
if (ret == 0)
return PD_T_PPS_TIMEOUT;
}
if (ret == -EOPNOTSUPP)
pps_log(pps, "PPS deactivated while updating");
return ret;
}
// EXPORT_SYMBOL_GPL(pps_update_adapter);
struct power_supply *pps_get_tcpm_psy(struct device_node *node, size_t size)
{
const char *propname = "google,tcpm-power-supply";
struct power_supply *tcpm_psy = NULL;
struct power_supply *psy[2];
int i, ret;
ret = power_supply_get_by_phandle_array(node, propname, psy,
ARRAY_SIZE(psy));
if (ret < 0)
return ERR_PTR(-EAGAIN);
for (i = 0; i < ret; i++) {
const char *name = psy[i]->desc ? psy[i]->desc->name : NULL;
if (!tcpm_psy && name && !strncmp(name, "tcpm", 4))
tcpm_psy = psy[i];
else
power_supply_put(psy[i]);
}
return tcpm_psy;
}
// EXPORT_SYMBOL_GPL(pps_get_tcpm_psy);
/* TODO: */
int pps_request_pdo(struct pd_pps_data *pps_data, unsigned int ta_idx,
unsigned int ta_max_vol, unsigned int ta_max_cur,
struct power_supply *tcpm_psy)
{
struct tcpm_port *port = chg_get_tcpm_port(tcpm_psy);
const unsigned int max_mw = ta_max_vol * ta_max_cur;
if (!port)
return -ENODEV;
if (ta_idx > PDO_MAX_OBJECTS)
return -EINVAL;
/* max_mw was, now using the max OP_SNK_MW */
return tcpm_update_sink_capabilities(port, pps_data->snk_pdo,
ta_idx, max_mw);
}
// EXPORT_SYMBOL_GPL(pps_request_pdo);
/* ------------------------------------------------------------------------- */
/* max APDO power from the TCPM source */
int pps_get_apdo_max_power(struct pd_pps_data *pps_data, unsigned int *ta_idx,
unsigned int *ta_max_vol, unsigned int *ta_max_cur,
unsigned long *ta_max_pwr)
{
int max_current, max_voltage, max_power;
const int ta_max_vol_mv = *ta_max_vol / 1000;
int i;
if (pps_data->nr_src_cap <= 0)
return -ENOENT;
/* already done that */
if (*ta_idx != 0)
return -ENOTSUPP;
/* find the new max_uv, max_ua, and max_pwr */
for (i = 0; i < pps_data->nr_src_cap; i++) {
const u32 pdo = pps_data->src_caps[i];
if (pdo_type(pdo) != PDO_TYPE_FIXED)
continue;
max_voltage = pdo_max_voltage(pdo); /* mV */
max_current = pdo_max_current(pdo); /* mA */
max_power = pdo_max_power(pdo); /* mW */
*ta_max_pwr = max_power * 1000; /* uW */
}
/* Get the TA maximum current and voltage for APDOs */
for (i = 0; i < pps_data->nr_src_cap; i++) {
const u32 pdo = pps_data->src_caps[i];
if (pdo_type(pdo) != PDO_TYPE_APDO)
continue;
max_current = pdo_pps_apdo_max_current(pdo); /* mA */
max_voltage = pdo_pps_apdo_max_voltage(pdo); /* mV */
/* stop on first */
if (max_voltage > ta_max_vol_mv) {
*ta_max_vol = max_voltage * 1000; /* uV */
*ta_max_cur = max_current * 1000; /* uA */
*ta_idx = i + 1;
return 0;
}
}
pr_debug("%s: max_uv (%u) and max_ua (%u) out of APDO src caps\n",
__func__, *ta_max_vol, *ta_max_cur);
return -EINVAL;
}
// EXPORT_SYMBOL_GPL(pps_get_apdo_max_power);