blob: 3343f5dec228751e609fbef695815932f8d491bb [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 "google_bms.h"
#include "google_psy.h"
#include "google_dc_pps.h"
#include "../../drivers/usb/typec/tcpm/google/max77759_export.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#endif
#define PPS_UPDATE_DELAY_MS 2000
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);
/*
* State is initialized to PPS_DISABLED and SW will enable detect setting
* ->stage to PPS_NONE and calling pps_work() until the state becomes
* PPS_ACTIVE or PPS_NOTSUPP.
*/
void pps_init_state(struct pd_pps_data *pps_data)
{
/* pps_data->src_caps can be NULL */
if (pps_data->src_caps) {
if (!pps_data->nr_src_cap)
pr_err("%s: %s non zero src_caps, zero nr_src_cap\n",
__func__, pps_name(pps_data->pps_psy));
tcpm_put_partner_src_caps(&pps_data->src_caps);
}
pps_data->pd_online = PPS_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);
/*
* pps_psy can be tcpm, wireless or gcpm_pps.
* NOTE: gcpm_pps route the props to the underlying psy
*/
static int pps_check_type(struct pd_pps_data *pps_data,
struct power_supply *pps_psy)
{
union power_supply_propval pval;
int ret;
if (!pps_psy->desc)
return -EINVAL;
/* TODO: add POWER_SUPPLY_TYPE_PPS_PORT? */
pr_debug("%s: name=%s type=%d\n", __func__, pps_name(pps_psy),
pps_psy->desc->type);
if (pps_psy->desc->type == POWER_SUPPLY_TYPE_WIRELESS)
return true;
/* NOTE: this keep trying with "slow" adapters */
ret = power_supply_get_property(pps_psy, POWER_SUPPLY_PROP_USB_TYPE, &pval);
pr_debug("%s: name=%s type=%d ret=%d\n", __func__, pps_name(pps_psy),
pval.intval, ret);
if (ret == 0 && pval.intval == POWER_SUPPLY_USB_TYPE_PD_PPS)
return true;
if (ret == 0 && pval.intval == POWER_SUPPLY_USB_TYPE_PD)
return true;
if (ret == 0 && pval.intval == POWER_SUPPLY_USB_TYPE_C)
return true;
return false;
}
/* always call with a tcpm_psy */
struct tcpm_port *chg_get_tcpm_port(struct power_supply *tcpm_psy)
{
union power_supply_propval pval;
void *port = NULL;
int ret;
/* port is valid for a tcpm power supply but not for gcpm */
ret = power_supply_get_property(tcpm_psy, POWER_SUPPLY_PROP_USB_TYPE, &pval);
if (ret == 0 && (pval.intval == POWER_SUPPLY_USB_TYPE_PD_PPS ||
pval.intval == POWER_SUPPLY_USB_TYPE_PD ||
pval.intval == POWER_SUPPLY_USB_TYPE_C))
port = power_supply_get_drvdata(tcpm_psy);
/* TODO: make sure that tcpm_psy is really a tcpm source */
return (struct tcpm_port *)port;
}
/* false when not present or error (either way don't run) */
static enum pd_pps_stage pps_is_avail(struct pd_pps_data *pps,
struct power_supply *tcpm_psy)
{
/* TODO: make silent, check return value and value */
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;
/* NOTE: the adapter should already be in PROG_ONLINE */
rc = GPSY_SET_PROP(tcpm_psy, POWER_SUPPLY_PROP_ONLINE,
PPS_PSY_PROG_ONLINE);
if (rc == 0)
pps->pd_online = PPS_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;
if (!pps || !tcpm_psy)
return -EINVAL;
if (pps->nr_src_cap && pps->src_caps) {
pr_debug("%s: %s using cached nr_src_cap=%d\n", __func__,
pps_name(tcpm_psy), pps->nr_src_cap);
return pps->nr_src_cap;
}
port = chg_get_tcpm_port(tcpm_psy);
if (port) {
int nr_src_cap;
if (pps->src_caps)
pr_debug("%s: %s warning src_caps!=0, nr_src_cap=%d\n",
__func__, pps_name(tcpm_psy), pps->nr_src_cap);
nr_src_cap = tcpm_get_partner_src_caps(port, &pps->src_caps);
if (nr_src_cap < 0)
return nr_src_cap;
pr_debug("%s: %s found nr_src_cap=%d\n", __func__,
pps_name(tcpm_psy), nr_src_cap);
pps->nr_src_cap = nr_src_cap;
}
return pps->nr_src_cap;
}
// EXPORT_SYMBOL_GPL(pps_get_src_cap);
bool pps_check_prog_online(struct pd_pps_data *pps_data)
{
if (!pps_data || !pps_data->pps_psy)
return false;
return GPSY_GET_PROP(pps_data->pps_psy, POWER_SUPPLY_PROP_ONLINE) ==
PPS_PSY_PROG_ONLINE;
}
/*
* bail if not online and PROG, query source caps and advance to ACTIVE
* if not there.
*/
bool pps_prog_check_online(struct pd_pps_data *pps_data,
struct power_supply *tcpm_psy)
{
int pd_online = 0;
if (!pps_data || !tcpm_psy)
return -ENODEV;
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 != PPS_PSY_PROG_ONLINE)
goto not_supp;
/* make the transition to active */
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;
}
pr_debug("%s: online & active nr_src_cap=%d\n",
__func__, pps_data->nr_src_cap);
pps_data->pd_online = PPS_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.
* <0 when not supported, 0 if supported (and update state)
*/
static int pps_prog_online(struct pd_pps_data *pps,
struct power_supply *tcpm_psy)
{
union power_supply_propval pval = { .intval = PPS_PSY_PROG_ONLINE, };
int ret;
ret = power_supply_set_property(tcpm_psy, POWER_SUPPLY_PROP_ONLINE, &pval);
pr_debug("%s: name=%s ret=%d\n", __func__, pps_name(tcpm_psy), ret);
if (ret == -EOPNOTSUPP) {
pps->stage = PPS_NOTSUPP;
} else if (ret == 0) {
pps->pd_online = PPS_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)
{
union power_supply_propval pval;
int ret;
if (!pps || !tcpm_psy)
return -ENODEV;
ret = power_supply_get_property(tcpm_psy, POWER_SUPPLY_PROP_ONLINE, &pval);
if (ret < 0 || pval.intval != PPS_PSY_PROG_ONLINE)
goto exit_done;
pval.intval = PPS_PSY_FIXED_ONLINE;
ret = power_supply_set_property(tcpm_psy, POWER_SUPPLY_PROP_ONLINE, &pval);
exit_done:
pps_init_state(pps);
return ret;
}
// EXPORT_SYMBOL_GPL(pps_prog_offline);
void pps_adjust_volt(struct pd_pps_data *pps, int mod)
{
if (!pps)
return;
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;
}
}
/* ------------------------------------------------------------------------ */
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 || !pps_data)
return -EINVAL;
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;
}
void pps_set_logbuffer(struct pd_pps_data *pps_data, struct logbuffer *log)
{
pps_data->log = IS_ERR(log) ? NULL : log;
}
/* ------------------------------------------------------------------------ */
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)
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_snk(struct pd_pps_data *pps_data,
struct device_node *node)
{
int ret, nr_snk_pdo = 0;
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 find connector property (%d)\n", nr_snk_pdo);
nr_snk_pdo = 0;
} else {
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;
}
}
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,
struct power_supply *pps_psy, const char *pps_ws_name)
{
int ret;
if (!pps_data || !pps_psy || !dev)
return -EINVAL;
ret = pps_init_snk(pps_data, dev->of_node);
if (ret < 0)
return ret;
/* TODO: look in the power supply device node ? maybe? */
if (!pps_data->nr_snk_pdo)
dev_warn(dev, "%s has nr_sink_pdo=0\n", pps_name(pps_psy));
/*
* 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(dev->of_node,
"google,pps-awake");
if (pps_data->stay_awake) {
pps_data->pps_ws = wakeup_source_register(NULL, pps_ws_name);
if (!pps_data->pps_ws) {
dev_err(dev, "Failed to register wakeup source\n");
return -ENODEV;
}
}
pps_data->pps_psy = pps_psy;
return 0;
}
void pps_free(struct pd_pps_data *pps_data)
{
if (!pps_data || !pps_data->pps_psy)
return;
if (pps_data->pps_ws)
wakeup_source_unregister(pps_data->pps_ws);
pps_data->pps_psy = NULL;
}
/* ------------------------------------------------------------------------- */
/*
* This is the first part of the DC/PPS charging state machine.
* Detect and configure the PPS adapter for the PPS profile.
* pps->stage is updated to PPS_NONE, PPS_AVAILABLE, PPS_ACTIVE or
* PPS_NOTSUPP.
*
* Returns:
* . 0 to disable the PPS update interval voter
* . <0 for error
* . the max update interval pps should request
*/
int pps_work(struct pd_pps_data *pps, struct power_supply *pps_psy)
{
union power_supply_propval pval;
int ret, type_ok, pd_online;
enum pd_pps_stage stage;
if (!pps)
return -EINVAL;
if (!pps_psy) {
pps->stage = PPS_NOTSUPP;
return -EINVAL;
}
/*
* POWER_SUPPLY_PROP_PRESENT must reports cable (or field)
* NOTE: TCPM doesn't implement this, WLC and GCPM pps do.
*/
ret = power_supply_get_property(pps_psy, POWER_SUPPLY_PROP_PRESENT,
&pval);
if (ret == 0 && pval.intval == 0) {
pr_debug("%s: %s pval.intval=%d ret=%d\n", __func__,
pps_name(pps_psy), pval.intval, ret);
pps->stage = PPS_NOTSUPP;
}
/* detection is done for this cycle */
if (pps->stage == PPS_NOTSUPP)
return 0;
/*
* 2) pps->pd_online == PPS_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->stage == PPS_NONE && pps->pd_online == PPS_PSY_PROG_ONLINE) {
int rc;
pps->stage = pps_is_avail(pps, pps_psy);
if (pps->stage == PPS_AVAILABLE) {
rc = pps_ping(pps, pps_psy);
if (rc < 0) {
pps->pd_online = PPS_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, pps_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 PPS_PSY_PROG_ONLINE (from PPS_PSY_FIXED_ONLINE)
* when usbc_type is POWER_SUPPLY_USB_TYPE_PD_PPS.
*/
pd_online = GPSY_GET_PROP(pps_psy, POWER_SUPPLY_PROP_ONLINE);
if (pd_online < 0)
return 0;
/*
* 3) pd_online == PPS_PSY_PROG_ONLINE == pps->pd_online
* pps is active now, we are done here. pd_online will change to
* if pd_online is !PPS_PSY_PROG_ONLINE go back to 1) OR exit.
*/
stage = (pd_online == pps->pd_online) &&
(pd_online == PPS_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",
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!=PPS_PSY_PROG_ONLINE
* If usbc_type is ok and pd_online is PPS_PSY_FIXED_ONLINE,
* set POWER_SUPPLY_PROP_ONLINE to PPS_PSY_PROG_ONLINE (enable PPS)
* and reschedule in PD_T_PPS_TIMEOUT.
*/
type_ok = pps_check_type(pps, pps_psy);
if (!type_ok)
pr_debug("%s: %s type not ok\n", __func__, pps_name(pps_psy));
if (type_ok && pd_online == PPS_PSY_FIXED_ONLINE) {
int rc;
/* 0 = when in PROG */
rc = pps_prog_online(pps, pps_psy);
switch (rc) {
case 0:
return PD_T_PPS_TIMEOUT;
case -EAGAIN:
pps_log(pps, "work: not in SNK_READY, rerun");
pps->pd_online = pd_online;
return rc;
case -EOPNOTSUPP:
pps->stage = PPS_NOTSUPP;
break;
default:
pps_log(pps, "work: PROP_ONLINE (%d)", rc);
break;
}
}
/* reset PPS_NOTSUPP with pps_init_state() */
if (pps->stage == PPS_NOTSUPP) {
if (pps->stay_awake)
__pm_relax(pps->pps_ws);
pps_log(pps, "work: PPS not supported");
}
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 (!pps || !tcpm_psy)
return -EINVAL;
ret = pps_ping(pps, tcpm_psy);
if (ret < 0) {
pps->pd_online = PPS_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;
if (!pps || !tcpm_psy)
return -EINVAL;
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 = PPS_PSY_FIXED_ONLINE;
pps->keep_alive_cnt = 0;
if (pps->stay_awake)
__pm_relax(pps->pps_ws);
}
return ret;
}
static void pps_log_keepalive(struct pd_pps_data *pps)
{
pps_log(pps, "%d KEEP ALIVE", pps->keep_alive_cnt);
pps->keep_alive_cnt = 0;
}
/*
* 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;
if (!tcpm_psy)
return -EINVAL;
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) {
pr_debug("%s: %s error out_uv=%d op_ua=%d\n", __func__,
pps_name(tcpm_psy), pps->out_uv, pps->op_ua);
return -EIO;
}
if (pending_uv < 0)
pending_uv = pps->out_uv;
if (pending_ua < 0)
pending_ua = pps->op_ua;
/*
* 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 (pps->keep_alive_cnt)
pps_log_keepalive(pps);
ret = pps_set_prop(pps, POWER_SUPPLY_PROP_CURRENT_NOW,
pending_ua, tcpm_psy);
pr_debug("%s: %s SET_UA out_ua %d->%d, ret=%d", __func__,
pps_name(tcpm_psy), 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;
/* voltage is pending too */
if (pps->out_uv != pending_uv)
return 0;
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) {
ret = pps_set_prop(pps, POWER_SUPPLY_PROP_VOLTAGE_NOW,
pending_uv, tcpm_psy);
if (pps->keep_alive_cnt)
pps_log_keepalive(pps);
pr_debug("%s: %s SET_UV out_v %d->%d, ret=%d\n", __func__,
pps_name(tcpm_psy), 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 VOLTAGE_NOW, ret = %d",
ret);
} else if (interval < PD_T_PPS_DEADLINE_S) {
pr_debug("%s: %s mv=%d->%d ua=%d->%d interval=%d\n", __func__,
pps_name(tcpm_psy), pps->out_uv, pending_uv,
pps->op_ua, pending_ua, interval);
/* 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);
if (ret < 0) {
if (pps->keep_alive_cnt)
pps_log_keepalive(pps);
pps_log(pps, "KEEP ALIVE out_v %d, op_c %d (%d)",
pps->out_uv, pps->op_ua, ret);
} else {
pps->keep_alive_cnt += 1;
}
pr_debug("%s: %s KEEP ALIVE out_v %d, op_c %d (%d)", __func__,
pps_name(tcpm_psy), 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);
/* just a wrapper for power_supply_get_by_phandle_array() */
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;
if (!node)
return ERR_PTR(-EINVAL);
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)
{
const unsigned int max_mw = ta_max_vol * ta_max_cur;
struct tcpm_port *port;
int ret = -ENODEV;
if (!pps_data || !pps_data->pps_psy || ta_idx > PDO_MAX_OBJECTS)
return -EINVAL;
/* tcpm pport */
port = chg_get_tcpm_port(pps_data->pps_psy);
if (port)
ret = tcpm_update_sink_capabilities(port, pps_data->snk_pdo,
ta_idx, max_mw);
return ret;
}
// EXPORT_SYMBOL_GPL(pps_request_pdo);
/* ------------------------------------------------------------------------- */
/*
* return the first APDO from the TCPM source which voltage greater or equal
* to *ta_max_vol and current greater or equal to *ta_max_cur.
* NOTE: 0 in ta_max_vol and ta_max_cur will select the first APDO.
*/
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;
const int ta_max_cur_mv = *ta_max_cur / 1000;
int i;
if (!pps_data)
return -EINVAL;
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 first APDO that that exceeds the limits */
for (i = 0; i < pps_data->nr_src_cap; i++) {
const u32 pdo = pps_data->src_caps[i];
bool voltage_ok, current_ok;
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 match */
voltage_ok = max_voltage >= ta_max_vol_mv;
current_ok = max_current >= ta_max_cur_mv;
if (voltage_ok && current_ok) {
*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);