blob: 23119be9b11650f410ecc62d4acbf4e7c8f6e558 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* WC68 Direct Charger PPS Integration
* Based on PPS integration for PCA9468
*
* Copyright (C) 2022 Google, LLC
*
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/of_device.h>
#include "wc68_regs.h"
#include "wc68_charger.h"
/* Logging ----------------------------------------------------------------- */
int debug_printk_prlog = LOGLEVEL_INFO;
int debug_no_logbuffer;
/* DC PPS integration ------------------------------------------------------ */
static void wc68_chg_stats_set_apdo(struct wc68_chg_stats *chg_data, u32 apdo);
static struct device_node *wc68_find_config(struct device_node *node)
{
struct device_node *temp;
if (!node)
return node;
temp = of_parse_phandle(node, "wc68,google_cpm", 0);
if (temp)
node = temp;
return node;
}
int wc68_probe_pps(struct wc68_charger *wc68_chg)
{
const char *tmp_name = NULL;
bool pps_available = false;
struct device_node *node;
int ret;
node = wc68_find_config(wc68_chg->dev->of_node);
if (!node)
return -ENODEV;
ret = of_property_read_u32(node, "google,tcpm-power-supply",
&wc68_chg->tcpm_phandle);
if (ret < 0)
dev_warn(wc68_chg->dev, "wc68: pca,tcpm-power-supply not defined\n");
else
pps_available |= true;
ret = of_property_read_string(node, "google,wlc_dc-power-supply",
&tmp_name);
if (ret < 0)
dev_warn(wc68_chg->dev, "wc68: google,wlc_dc-power-supply not defined\n");
if (ret == 0) {
wc68_chg->wlc_psy_name =
devm_kstrdup(wc68_chg->dev, tmp_name, GFP_KERNEL);
if (!wc68_chg->wlc_psy_name)
return -ENOMEM;
pps_available |= true;
}
return pps_available ? 0 : -ENODEV;
}
/* ------------------------------------------------------------------------ */
/* switch PDO if needed */
int wc68_request_pdo(struct wc68_charger *wc68)
{
int ret = 0;
dev_dbg(wc68->dev, "%s: ta_objpos=%u, ta_vol=%u, ta_cur=%u\n", __func__,
wc68->ta_objpos, wc68->ta_vol, wc68->ta_cur);
/*
* the reference implementation call pps_request_pdo() twice with a
* 100 ms delay between the calls when the function returns -EBUSY:
*
* ret = pps_request_pdo(&wc68->pps_data, wc68->ta_objpos,
* wc68->ta_vol, wc68->ta_cur,
* wc68->pd);
*
* The wrapper in google_dc_pps route the calls to the tcpm engine
* via tcpm_update_sink_capabilities(). The sync capabilities are
* in pps_data, ->ta_objpos select the (A)PDO index, ->ta_vol and
* ->ta_cur are the desired TA voltage and current.
*
* this is now handled by pps_update_adapter()
*
* TODO: verify the timing and make sure that there are no races that
* cause the targets
*/
return ret;
}
int wc68_usbpd_setup(struct wc68_charger *wc68)
{
struct power_supply *tcpm_psy;
bool online;
int ret = 0;
if (wc68->pd != NULL)
goto check_online;
if (wc68->tcpm_psy_name) {
tcpm_psy = power_supply_get_by_name(wc68->tcpm_psy_name);
if (!tcpm_psy)
return -ENODEV;
wc68->pd = tcpm_psy;
} else if (wc68->tcpm_phandle) {
struct device_node *node;
node = wc68_find_config(wc68->dev->of_node);
if (!node)
return -ENODEV;
tcpm_psy = pps_get_tcpm_psy(node, 2);
if (IS_ERR(tcpm_psy))
return PTR_ERR(tcpm_psy);
if (!tcpm_psy) {
wc68->tcpm_phandle = 0;
return -ENODEV;
}
dev_err(wc68->dev, "%s: TCPM name is %s\n", __func__, pps_name(tcpm_psy));
wc68->tcpm_psy_name = tcpm_psy->desc->name;
wc68->pd = tcpm_psy;
} else {
dev_err(wc68->dev, "%s: TCPM DC not defined\n", __func__);
return -ENODEV;
}
/* not needed if tcpm-power-supply is not there */
ret = pps_init(&wc68->pps_data, wc68->dev, tcpm_psy);
if (ret == 0) {
pps_set_logbuffer(&wc68->pps_data, wc68->log);
pps_init_state(&wc68->pps_data);
}
check_online:
online = pps_prog_check_online(&wc68->pps_data, wc68->pd);
if (!online)
return -ENODEV;
return ret;
}
/* call holding mutex_unlock(&wc68->lock); */
int wc68_send_pd_message(struct wc68_charger *wc68,
unsigned int msg_type)
{
struct pd_pps_data *pps_data = &wc68->pps_data;
struct power_supply *tcpm_psy = wc68->pd;
bool online;
int pps_ui;
int ret;
if (!tcpm_psy || (wc68->charging_state == DC_STATE_NO_CHARGING &&
msg_type == PD_MSG_REQUEST_APDO) || !wc68->mains_online) {
dev_dbg(wc68->dev, "%s: failure tcpm_psy_ok=%d charging_state=%u online=%d",
__func__, tcpm_psy != 0, wc68->charging_state,
wc68->mains_online);
return -EINVAL;
}
/* false when offline (0) or not in prog (1) mode */
online = pps_prog_check_online(&wc68->pps_data, tcpm_psy);
if (!online) {
dev_dbg(wc68->dev, "%s: not online", __func__);
return -EINVAL;
}
/* turn off PPS/PROG, revert to PD */
if (msg_type == MSG_REQUEST_FIXED_PDO) {
ret = pps_prog_offline(&wc68->pps_data, tcpm_psy);
dev_dbg(wc68->dev, "%s: requesting offline ret=%d\n", __func__, ret);
/* TODO: reset state? */
return ret;
}
dev_dbg(wc68->dev, "%s: tcpm_psy_ok=%d pd_online=%d pps_stage=%d charging_state=%u",
__func__, tcpm_psy != 0, pps_data->pd_online,
pps_data->stage, wc68->charging_state);
if (wc68->pps_data.stage == PPS_ACTIVE) {
/* not sure I need to do this */
ret = wc68_request_pdo(wc68);
if (ret == 0) {
const int pre_out_uv = pps_data->out_uv;
const int pre_out_ua = pps_data->op_ua;
dev_dbg(wc68->dev, "%s: ta_vol=%u, ta_cur=%u, ta_objpos=%u\n",
__func__, wc68->ta_vol, wc68->ta_cur,
wc68->ta_objpos);
pps_ui = pps_update_adapter(&wc68->pps_data,
wc68->ta_vol,
wc68->ta_cur,
tcpm_psy);
dev_dbg(wc68->dev, "%s: out_uv=%d %d->%d, out_ua=%d %d->%d (%d)\n",
__func__,
pps_data->out_uv, pre_out_uv, wc68->ta_vol,
pps_data->op_ua, pre_out_ua, wc68->ta_cur,
pps_ui);
if (pps_ui == 0)
pps_ui = WC68_PDMSG_WAIT_T;
if (pps_ui < 0)
pps_ui = WC68_PDMSG_RETRY_T;
} else {
dev_dbg(wc68->dev, "%s: request_pdo failed ret=%d\n",
__func__, ret);
pps_ui = WC68_PDMSG_RETRY_T;
}
} else {
ret = pps_keep_alive(pps_data, tcpm_psy);
if (ret == 0)
pps_ui = PD_T_PPS_TIMEOUT;
dev_dbg(wc68->dev, "%s: keep alive ret=%d\n", __func__, ret);
}
if (((wc68->charging_state == DC_STATE_NO_CHARGING) &&
(msg_type == PD_MSG_REQUEST_APDO)) ||
(wc68->mains_online == false)) {
/*
* Vbus reset might occour even when PD comms is successful.
* Check again.
*/
pps_ui = -EINVAL;
}
/* PPS_Work: will reschedule */
dev_dbg(wc68->dev, "%s: pps_ui = %d\n", __func__, pps_ui);
if (pps_ui > 0)
mod_delayed_work(system_wq, &wc68->pps_work,
msecs_to_jiffies(pps_ui));
return pps_ui;
}
/*
* Get the max current/voltage/power of APDO from the CC/PD driver.
*
* Initialize &wc68->ta_max_vol, &wc68->ta_max_cur, &wc68->ta_max_pwr
* initialize wc68->pps_data and &wc68->ta_objpos also
*
* call holding mutex_unlock(&wc68->lock);
*/
int wc68_get_apdo_max_power(struct wc68_charger *wc68,
unsigned int ta_max_vol,
unsigned int ta_max_cur)
{
int ret;
/* limits */
wc68->ta_objpos = 0; /* if !=0 will return the ca */
wc68->ta_max_vol = ta_max_vol;
wc68->ta_max_cur = ta_max_cur;
/* check the phandle */
ret = wc68_usbpd_setup(wc68);
if (ret != 0) {
dev_err(wc68->dev, "cannot find TCPM %d\n", ret);
wc68->pd = NULL;
return ret;
}
/* technically already in pda_data since check online does it */
ret = pps_get_src_cap(&wc68->pps_data, wc68->pd);
if (ret < 0)
return ret;
ret = pps_get_apdo_max_power(&wc68->pps_data, &wc68->ta_objpos,
&wc68->ta_max_vol, &wc68->ta_max_cur,
&wc68->ta_max_pwr);
if (ret < 0) {
dev_err(wc68->dev, "cannot determine the apdo max power ret = %d\n", ret);
return ret;
}
dev_dbg(wc68->dev, "%s: APDO pos=%u max_v=%u max_c=%u max_pwr=%lu\n", __func__,
wc68->ta_objpos, wc68->ta_max_vol, wc68->ta_max_cur,
wc68->ta_max_pwr);
wc68_chg_stats_set_apdo(&wc68->chg_data,
wc68->pps_data.src_caps[wc68->ta_objpos - 1]);
return 0;
}
/* WLC_DC ---------------------------------------------------------------- */
/* call holding mutex_unlock(&wc68->lock); */
struct power_supply *wc68_get_rx_psy(struct wc68_charger *wc68)
{
if (!wc68->wlc_psy) {
const char *wlc_psy_name = wc68->wlc_psy_name ? : "wireless";
wc68->wlc_psy = power_supply_get_by_name(wlc_psy_name);
if (!wc68->wlc_psy) {
dev_err(wc68->dev, "%s Cannot find %s power supply\n",
__func__, wlc_psy_name);
}
}
return wc68->wlc_psy;
}
/* call holding mutex_unlock(&wc68->lock); */
int wc68_send_rx_voltage(struct wc68_charger *wc68,
unsigned int msg_type)
{
union power_supply_propval pro_val;
struct power_supply *wlc_psy;
int ret = -EINVAL;
/* Vbus reset happened in the previous PD communication */
if (wc68->mains_online == false)
goto out;
wlc_psy = wc68_get_rx_psy(wc68);
if (!wlc_psy) {
ret = -ENODEV;
goto out;
}
/* turn off PPS/PROG, revert to PD */
if (msg_type == MSG_REQUEST_FIXED_PDO) {
union power_supply_propval online;
int ret;
ret = power_supply_get_property(wlc_psy, POWER_SUPPLY_PROP_ONLINE, &online);
if (ret == 0 && online.intval == PPS_PSY_PROG_ONLINE) {
/*
* Make sure the pca is in stby mode before setting
* online back to PPS_PSY_FIXED_ONLINE.
*/
if (!wc68_check_standby(wc68)) {
dev_err(wc68->dev,
"Device not in stby ret=(%d)\n",
ret);
return -EINVAL;
}
pro_val.intval = PPS_PSY_FIXED_ONLINE;
ret = power_supply_set_property(wlc_psy, POWER_SUPPLY_PROP_ONLINE,
&pro_val);
dev_dbg(wc68->dev, "%s: online=%d->%d ret=%d\n", __func__,
online.intval, pro_val.intval, ret);
} else if (ret < 0) {
dev_err(wc68->dev, "%s: online=%d ret=%d\n", __func__,
online.intval, ret);
}
/* TODO: reset state? */
/* done if don't have alternate voltage */
if (!wc68->ta_vol)
return ret;
}
pro_val.intval = wc68->ta_vol;
ret = power_supply_set_property(wlc_psy, POWER_SUPPLY_PROP_VOLTAGE_NOW,
&pro_val);
if (ret < 0)
dev_err(wc68->dev, "Cannot set RX voltage to %d (%d)\n",
pro_val.intval, ret);
/* Vbus reset might happen, check the charging state again */
if (wc68->mains_online == false) {
dev_warn(wc68->dev, "%s: mains offline\n", __func__);
ret = -EINVAL;
}
logbuffer_prlog(wc68, LOGLEVEL_DEBUG, "WLCDC: online=%d ta_vol=%d (%d)",
wc68->mains_online, wc68->ta_vol, ret);
out:
return ret;
}
/*
* Get the max current/voltage/power of RXIC from the WCRX driver
* Initialize &wc68->ta_max_vol, &wc68->ta_max_cur, &wc68->ta_max_pwr
* call holding mutex_unlock(&wc68->lock);
*/
int wc68_get_rx_max_power(struct wc68_charger *wc68)
{
union power_supply_propval pro_val;
struct power_supply *wlc_psy;
int ret = 0;
wlc_psy = wc68_get_rx_psy(wc68);
if (!wlc_psy) {
dev_err(wc68->dev, "Cannot find wireless power supply\n");
return -ENODEV;
}
/* Get the maximum voltage */
ret = power_supply_get_property(wlc_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX,
&pro_val);
if (ret < 0) {
dev_err(wc68->dev, "%s Cannot get the maximum RX voltage (%d)\n",
__func__, ret);
return ret;
}
/* RX IC cannot support the request maximum voltage */
if (wc68->ta_max_vol > pro_val.intval) {
dev_err(wc68->dev, "%s max %d cannot support ta_max %d voltage\n",
__func__, pro_val.intval, wc68->ta_max_vol);
return -EINVAL;
}
wc68->ta_max_vol = pro_val.intval;
/* Get the maximum current */
ret = power_supply_get_property(wlc_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
&pro_val);
if (ret < 0) {
dev_err(wc68->dev, "%s Cannot get the maximum RX current (%d)\n",
__func__, ret);
return ret;
}
wc68->ta_max_cur = pro_val.intval;
wc68->ta_max_pwr = (wc68->ta_max_vol / 1000) *
(wc68->ta_max_cur / 1000);
logbuffer_prlog(wc68, LOGLEVEL_INFO, "WLCDC: max_cur=%d max_pwr=%ld",
wc68->ta_max_cur, wc68->ta_max_pwr);
return 0;
}
/* called from start_direct_charging(), negative will abort */
int wc68_set_ta_type(struct wc68_charger *wc68, int pps_index)
{
if (pps_index == PPS_INDEX_TCPM) {
int ret;
ret = wc68_usbpd_setup(wc68);
if (ret != 0) {
dev_err(wc68->dev, "Cannot find the TA %d\n", ret);
return ret;
}
wc68->ta_type = TA_TYPE_USBPD;
wc68->chg_mode = CHG_4TO1_DC_MODE;
} else if (pps_index == PPS_INDEX_WLC) {
struct power_supply *wlc_psy;
wlc_psy = wc68_get_rx_psy(wc68);
if (!wlc_psy) {
dev_err(wc68->dev, "Cannot find wireless power supply\n");
return -ENODEV;
}
wc68->ta_type = TA_TYPE_WIRELESS;
wc68->chg_mode = CHG_4TO1_DC_MODE;
} else {
wc68->ta_type = TA_TYPE_UNKNOWN;
wc68->chg_mode = 0;
return -EINVAL;
}
return 0;
}
/* GBMS integration ------------------------------------------------------ */
int wc68_get_charge_type(struct wc68_charger *wc68)
{
if (!wc68->mains_online)
return POWER_SUPPLY_CHARGE_TYPE_NONE;
/* Use SW state for now */
switch (wc68->charging_state) {
case DC_STATE_ADJUST_CC:
case DC_STATE_CC_MODE:
case DC_STATE_ADJUST_TAVOL:
case DC_STATE_ADJUST_TACUR:
return POWER_SUPPLY_CHARGE_TYPE_FAST;
case DC_STATE_START_CV:
case DC_STATE_CV_MODE:
return POWER_SUPPLY_CHARGE_TYPE_TAPER_EXT;
case DC_STATE_CHECK_ACTIVE: /* in preset */
case DC_STATE_CHARGING_DONE:
break;
}
return POWER_SUPPLY_CHARGE_TYPE_NONE;
}
#define WC68_NOT_CHARGING \
(WC68_BIT_SHUTDOWN_STATE_STS | WC68_BIT_STANDBY_STATE_STS)
#define WC68_ANY_CHARGING_LOOP \
(WC68_BIT_CHG_LOOP_STS | WC68_BIT_IIN_LOOP_STS | \
WC68_BIT_VFLT_LOOP_STS)
int wc68_get_status(struct wc68_charger *wc68)
{
if (wc68_check_standby(wc68)) {
const bool online = wc68->mains_online;
/* no disconnect during charger transition */
return online ? POWER_SUPPLY_STATUS_NOT_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
}
/* Use SW state (for now) */
switch (wc68->charging_state) {
case DC_STATE_NO_CHARGING:
case DC_STATE_CHECK_VBAT:
case DC_STATE_PRESET_DC:
case DC_STATE_CHECK_ACTIVE:
return POWER_SUPPLY_STATUS_NOT_CHARGING;
case DC_STATE_ADJUST_CC:
case DC_STATE_CC_MODE:
case DC_STATE_START_CV:
case DC_STATE_CV_MODE:
return POWER_SUPPLY_STATUS_CHARGING;
/* cpm will need to stop it */
case DC_STATE_CHARGING_DONE:
return POWER_SUPPLY_STATUS_CHARGING;
default:
break;
}
return POWER_SUPPLY_STATUS_UNKNOWN;
}
#define WC68_PRESENT_MASK \
(WC68_BIT_ACTIVE_STATE_STS | WC68_BIT_STANDBY_STATE_STS)
int wc68_is_present(struct wc68_charger *wc68)
{
return wc68_hw_ping(wc68);
}
int wc68_get_chg_chgr_state(struct wc68_charger *wc68,
union gbms_charger_state *chg_state)
{
int vchrg;
chg_state->v = 0;
chg_state->f.chg_status = wc68_get_status(wc68);
chg_state->f.chg_type = wc68_get_charge_type(wc68);
chg_state->f.flags = gbms_gen_chg_flags(chg_state->f.chg_status,
chg_state->f.chg_type);
chg_state->f.flags |= GBMS_CS_FLAG_DIRECT_CHG;
vchrg = wc68_read_adc(wc68, ADCCH_VBAT);
if (vchrg > 0)
chg_state->f.vchrg = vchrg / 1000;
if (chg_state->f.chg_status != POWER_SUPPLY_STATUS_DISCHARGING) {
int rc;
rc = wc68_input_current_limit(wc68);
if (rc > 0)
chg_state->f.icl = rc / 1000;
}
return 0;
}
/* ------------------------------------------------------------------------ */
/* call holding (&wc68->lock); */
void wc68_chg_stats_init(struct wc68_chg_stats *chg_data)
{
memset(chg_data, 0, sizeof(*chg_data));
chg_data->adapter_capabilities[0] |= WC68_CHGS_VER;
}
static void wc68_chg_stats_set_apdo(struct wc68_chg_stats *chg_data, u32 apdo)
{
chg_data->adapter_capabilities[1] = apdo;
}
/* call holding (&wc68->lock); */
int wc68_chg_stats_update(struct wc68_chg_stats *chg_data,
const struct wc68_charger *wc68)
{
switch (wc68->charging_state) {
case DC_STATE_NO_CHARGING:
chg_data->nc_count++;
break;
case DC_STATE_CHECK_VBAT:
case DC_STATE_PRESET_DC:
chg_data->pre_count++;
break;
case DC_STATE_CHECK_ACTIVE:
chg_data->ca_count++;
break;
case DC_STATE_ADJUST_CC:
case DC_STATE_CC_MODE:
chg_data->cc_count++;
break;
case DC_STATE_START_CV:
case DC_STATE_CV_MODE:
chg_data->cv_count++;
break;
case DC_STATE_ADJUST_TAVOL:
case DC_STATE_ADJUST_TACUR:
chg_data->adj_count++;
break;
case DC_STATE_CHARGING_DONE:
chg_data->receiver_state[0] |= WC68_CHGS_F_DONE;
break;
default:
break;
}
return 0;
}
void wc68_chg_stats_dump(const struct wc68_charger *wc68)
{
const struct wc68_chg_stats *chg_data = &wc68->chg_data;
logbuffer_prlog(wc68, LOGLEVEL_INFO,
"N: ovc=%d,ovc_ibatt=%d,ovc_delta=%d rcp=%d,stby=%d",
chg_data->ovc_count,
chg_data->ovc_max_ibatt, chg_data->ovc_max_delta,
chg_data->rcp_count, chg_data->stby_count);
logbuffer_prlog(wc68, LOGLEVEL_INFO,
"C: nc=%d,pre=%d,ca=%d,cc=%d,cv=%d,adj=%d\n",
chg_data->nc_count, chg_data->pre_count,
chg_data->ca_count, chg_data->cc_count,
chg_data->cv_count, chg_data->adj_count);
}
int wc68_chg_stats_done(struct wc68_chg_stats *chg_data,
const struct wc68_charger *wc68)
{
/* AC[0] version */
/* AC[1] is APDO */
/* RS[0][0:8] flags */
if (chg_data->stby_count)
wc68_chg_stats_update_flags(chg_data, WC68_CHGS_F_STBY);
chg_data->receiver_state[0] = (chg_data->pre_count & 0xff) <<
WC68_CHGS_PRE_SHIFT;
chg_data->receiver_state[0] |= (chg_data->rcp_count & 0xff) <<
WC68_CHGS_RCPC_SHIFT;
chg_data->receiver_state[0] |= (chg_data->nc_count & 0xff) <<
WC68_CHGS_NC_SHIFT;
/* RS[1] counters */
chg_data->receiver_state[1] = (chg_data->ovc_count & 0xffff) <<
WC68_CHGS_OVCC_SHIFT;
chg_data->receiver_state[1] |= (chg_data->adj_count & 0xffff) <<
WC68_CHGS_ADJ_SHIFT;
/* RS[2] counters */
chg_data->receiver_state[2] = (chg_data->adj_count & 0xffff) <<
WC68_CHGS_ADJ_SHIFT;
chg_data->receiver_state[2] |= (chg_data->adj_count & 0xffff) <<
WC68_CHGS_ADJ_SHIFT;
/* RS[3] counters */
chg_data->receiver_state[3] = (chg_data->cc_count & 0xffff) <<
WC68_CHGS_CC_SHIFT;
chg_data->receiver_state[3] |= (chg_data->cv_count & 0xffff) <<
WC68_CHGS_CV_SHIFT;
/* RS[4] counters */
chg_data->receiver_state[1] = (chg_data->ca_count & 0xff) <<
WC68_CHGS_CA_SHIFT;
chg_data->valid = true;
return 0;
}