blob: 0b5359a746fcbc1ffc62245085020c04acd520da [file] [log] [blame] [edit]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2021 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "google_psy.h"
#define PD_VOLTAGE_MAX_MV 9000 /* 9V */
#define PD_CURRENT_MAX_UA 3000000 /* 3A */
#define GCCD_MAIN_CHARGE_CURRENT_MAX 4000000 /* 4A */
#define GCCD_BUCK_CHARGE_CURRENT_MAX 900000 /* 0.9A */
#define GCCD_BUCK_CHARGE_PWR_THRESHOLD 27000000 /* 27W */
#define GCCD_MAIN_CHGIN_ILIM 2200000 /* 2.2A */
struct gccd_drv {
struct device *device;
struct power_supply *psy;
const char *main_chg_psy_name;
const char *buck_chg_psy_name;
struct power_supply *main_chg_psy;
struct power_supply *buck_chg_psy;
struct mutex gccd_lock;
struct delayed_work init_work;
struct gvotable_election *fcc_votable;
struct gvotable_election *fv_votable;
bool init_complete;
int voltage_max;
int current_max;
int buck_chg_en;
bool ftm_mode; /* factory test, force enable buck charger */
};
/* ------------------------------------------------------------------------- */
static int gccd_get_charge_current_max(struct gccd_drv *gccd);
static int gccd_set_charge_current_max(struct gccd_drv *gccd, int chg_current, bool pwr_changed);
/* this function is only for debug */
static int gccd_set_buck_active(struct gccd_drv *gccd, int enabled)
{
int cc_max, ret;
/* convert type to boolean */
gccd->ftm_mode = !!enabled;
cc_max = gccd_get_charge_current_max(gccd);
pr_info("%s: charge_current=%d (0)\n", __func__, cc_max);
ret = gccd_set_charge_current_max(gccd, cc_max, false);
return ret;
}
static int debug_buck_active_get(void *data, u64 *val)
{
struct gccd_drv *gccd = (struct gccd_drv *)data;
*val = gccd->ftm_mode;
return 0;
}
static int debug_buck_active_set(void *data, u64 val)
{
struct gccd_drv *gccd = (struct gccd_drv *)data;
int ret;
if (val < 0 || val > 1)
return -EINVAL;
ret = gccd_set_buck_active(gccd, val);
if (ret)
pr_info("%s: Failed to set buck active: %d\n", __func__, ret);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(debug_buck_active_fops, debug_buck_active_get,
debug_buck_active_set, "%llu\n");
static int gccd_init_fs(struct gccd_drv *gccd)
{
/* TODO: ... */
return 0;
}
static int gccd_init_debugfs(struct gccd_drv *gccd)
{
struct dentry *de = NULL;
de = debugfs_create_dir("google_ccd", 0);
if (IS_ERR_OR_NULL(de))
return 0;
/* buck_active */
debugfs_create_file("buck_active", 0600, de, gccd,
&debug_buck_active_fops);
return 0;
}
/* ------------------------------------------------------------------------- */
static bool gccd_get_chg_psy(struct gccd_drv *gccd)
{
if (!gccd->main_chg_psy && gccd->main_chg_psy_name) {
gccd->main_chg_psy = power_supply_get_by_name(gccd->main_chg_psy_name);
if (!gccd->main_chg_psy)
return false;
}
if (!gccd->buck_chg_psy && gccd->buck_chg_psy_name) {
gccd->buck_chg_psy = power_supply_get_by_name(gccd->buck_chg_psy_name);
if (!gccd->buck_chg_psy)
return false;
}
return true;
}
static int gccd_has_chg_in(struct gccd_drv *gccd)
{
int ret;
if (!gccd_get_chg_psy(gccd))
return -EINVAL;
ret = PSY_GET_PROP(gccd->main_chg_psy, POWER_SUPPLY_PROP_PRESENT);
if (ret < 0) {
dev_err(gccd->device, "Error getting charging status: %d\n", ret);
return -EINVAL;
}
return ret != 0;
}
static bool gccd_find_votable(struct gccd_drv *gccd)
{
if (!gccd->fcc_votable) {
gccd->fcc_votable = gvotable_election_get_handle("MSC_FCC");
if (!gccd->fcc_votable) {
dev_err(gccd->device, "Could not get votable: MSC_FCC\n");
return false;
}
}
if (!gccd->fv_votable) {
gccd->fv_votable = gvotable_election_get_handle("MSC_FV");
if (!gccd->fv_votable) {
dev_err(gccd->device, "Could not get votable: MSC_FV\n");
return false;
}
}
return true;
}
static int gccd_get_charge_current_max(struct gccd_drv *gccd)
{
int cc_max = -1;
if (!gccd_find_votable(gccd))
return cc_max;
cc_max = gvotable_get_current_int_vote(gccd->fcc_votable);
return cc_max;
}
static int gccd_get_charge_voltage_max(struct gccd_drv *gccd)
{
int fv_uv = -1;
if (!gccd_find_votable(gccd))
return fv_uv;
fv_uv = gvotable_get_current_int_vote(gccd->fv_votable);
return fv_uv;
}
static int gccd_set_charge_current_max(struct gccd_drv *gccd,
int chg_current, bool pwr_changed)
{
int main_chg_current = chg_current, buck_chg_current = 0;
int watt = gccd->voltage_max * gccd->current_max;
int fv_uv = gccd_get_charge_voltage_max(gccd);
struct power_supply *main_psy = gccd->main_chg_psy;
bool pwr_ok = false;
int ret;
if (gccd->ftm_mode) {
/* set CHGIN_ILIM (CHG_CNFG_09) to 2200mA in ftm_mode */
ret = PSY_SET_PROP(main_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
GCCD_MAIN_CHGIN_ILIM);
/* force enable buck_chg*/
if (ret == 0)
pwr_ok = true;
goto set_current_max;
}
pwr_ok = watt >= GCCD_BUCK_CHARGE_PWR_THRESHOLD &&
main_chg_current > GCCD_MAIN_CHARGE_CURRENT_MAX;
if (pwr_changed)
pr_info("%s: pwr_ok=%d (%d, %d, %d)\n",
__func__, pwr_ok, watt, main_chg_current, fv_uv);
/* Don't enable buck charging */
if (pwr_changed && !pwr_ok)
return 0;
set_current_max:
if (pwr_ok) {
/*
* Sequoia has a solution for mechanical heat dissipation,
* set SQ: 4A, buck: (fcc - 4A)
*/
main_chg_current = GCCD_MAIN_CHARGE_CURRENT_MAX;
buck_chg_current = (chg_current - GCCD_MAIN_CHARGE_CURRENT_MAX);
if (buck_chg_current > GCCD_BUCK_CHARGE_CURRENT_MAX)
buck_chg_current = GCCD_BUCK_CHARGE_CURRENT_MAX;
}
pr_info("%s: charge_current=%d, main=%d, buck=%d, v_max=%d, c_max=%d\n",
__func__, chg_current, main_chg_current, buck_chg_current,
gccd->voltage_max, gccd->current_max);
ret = PSY_SET_PROP(main_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
main_chg_current);
/*
* enable buck charging by pull charging gpio high active when
* buck_chg_current is non-zero
*/
if (ret == 0 && gccd->buck_chg_en >= 0) {
struct power_supply *buck_psy = gccd->buck_chg_psy;
int en = (buck_chg_current > 0);
pr_info("%s: buck_charger enable=%d\n", __func__, en);
ret = PSY_SET_PROP(buck_psy, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
buck_chg_current);
if (ret == 0)
gpio_direction_output(gccd->buck_chg_en, en);
}
return ret;
}
/* ------------------------------------------------------------------------ */
static int gccd_gpio_init(struct device *dev, struct gccd_drv *gccd)
{
int ret = 0;
struct device_node *node = dev->of_node;
/* BUCK_CHG_EN */
ret = of_get_named_gpio(node, "google,buck_chg_en", 0);
gccd->buck_chg_en = ret;
if (ret < 0)
dev_warn(dev, "unable to read google,buck_chg_en from dt: %d\n",
ret);
else
dev_info(dev, "BUCK_CHG_EN gpio:%d", gccd->buck_chg_en);
return (ret < 0) ? ret : 0;
}
/* use the charger one when avalaible or fallback to the generated one */
static uint64_t gccd_get_charger_state(const struct gccd_drv *gccd,
struct power_supply *chg_psy)
{
union gbms_charger_state chg_state;
int rc;
rc = gbms_read_charger_state(&chg_state, chg_psy);
if (rc < 0)
return 0;
return chg_state.v;
}
static enum power_supply_property gccd_psy_properties[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX, /* compat */
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_STATUS,
};
static int gccd_psy_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *pval)
{
struct gccd_drv *gccd = (struct gccd_drv *)power_supply_get_drvdata(psy);
int ret = 0;
if (!gccd->init_complete)
return -EAGAIN;
if (!gccd_get_chg_psy(gccd))
return -EAGAIN;
mutex_lock(&gccd->gccd_lock);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
pval->intval = gccd_has_chg_in(gccd);
if (pval->intval < 0)
pval->intval = 0;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_CURRENT_NOW:
pval->intval = PSY_GET_INT_PROP(gccd->main_chg_psy, psp, &ret);
break;
default:
pr_debug("%s: property (%d) unsupported.\n", __func__, psp);
ret = -EINVAL;
break;
}
mutex_unlock(&gccd->gccd_lock);
return ret;
}
static int gccd_psy_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *pval)
{
struct gccd_drv *gccd = (struct gccd_drv *)power_supply_get_drvdata(psy);
int ret = 0;
int current_max, voltage_max;
bool changed = false;
if (!gccd->init_complete)
return -EAGAIN;
if (!gccd_get_chg_psy(gccd))
return -EAGAIN;
mutex_lock(&gccd->gccd_lock);
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX: {
bool is_adapter_9v3a = gccd->voltage_max == PD_VOLTAGE_MAX_MV &&
pval->intval == PD_CURRENT_MAX_UA;
/* set CHGIN_ILIM (CHG_CNFG_09) to 2200mA for 9V/3A adapter */
if (is_adapter_9v3a || gccd->ftm_mode)
ret = PSY_SET_PROP(gccd->main_chg_psy, psp, GCCD_MAIN_CHGIN_ILIM);
else
ret = PSY_SET_PROP(gccd->main_chg_psy, psp, pval->intval);
if (ret)
break;
current_max = pval->intval / 1000;
if (gccd->current_max != current_max) {
dev_dbg(gccd->device, "current_max: %d->%d\n", gccd->current_max, current_max);
changed = true;
gccd->current_max = current_max;
}
break;
}
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = PSY_SET_PROP(gccd->main_chg_psy, psp, pval->intval);
if (ret)
break;
voltage_max = pval->intval / 1000;
if (gccd->voltage_max != voltage_max) {
dev_dbg(gccd->device, "voltage_max: %d->%d\n", gccd->voltage_max, voltage_max);
changed = true;
gccd->voltage_max = voltage_max;
}
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
pr_debug("%s: charge_current=%d (0)\n", __func__, pval->intval);
ret = gccd_set_charge_current_max(gccd, pval->intval, false);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
pr_debug("%s: charge_voltage=%d \n", __func__, pval->intval);
ret = PSY_SET_PROP(gccd->main_chg_psy, psp, pval->intval);
if (!ret)
ret = PSY_SET_PROP(gccd->buck_chg_psy, psp, pval->intval);
break;
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = PSY_SET_PROP(gccd->main_chg_psy, psp, pval->intval);
break;
default:
ret = -EINVAL;
break;
}
if (changed) {
int cc_max;
cc_max = gccd_get_charge_current_max(gccd);
pr_info("%s: charge_current=%d (1)\n", __func__, cc_max);
ret = gccd_set_charge_current_max(gccd, cc_max, true);
}
mutex_unlock(&gccd->gccd_lock);
return ret;
}
static int gccd_psy_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_VOLTAGE_MAX: /* compat, same the next */
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return 1;
default:
break;
}
return 0;
}
static int gccd_gbms_psy_get_property(struct power_supply *psy,
enum gbms_property psp,
union gbms_propval *pval)
{
struct gccd_drv *gccd = (struct gccd_drv *)power_supply_get_drvdata(psy);
union gbms_charger_state chg_state;
int ret = 0;
if (!gccd->init_complete)
return -EAGAIN;
if (!gccd_get_chg_psy(gccd))
return -EAGAIN;
mutex_lock(&gccd->gccd_lock);
switch (psp) {
case GBMS_PROP_CHARGE_CHARGER_STATE:
chg_state.v = gccd_get_charger_state(gccd, gccd->main_chg_psy);
pval->int64val = chg_state.v;
break;
case GBMS_PROP_CHARGE_DISABLE:
case GBMS_PROP_CHARGING_ENABLED:
case GBMS_PROP_INPUT_CURRENT_LIMITED:
case GBMS_PROP_TAPER_CONTROL:
pval->prop.intval = GPSY_GET_INT_PROP(gccd->main_chg_psy, psp, &ret);
break;
default:
pr_debug("%s: route to gccd_psy_get_property, psp:%d\n", __func__, psp);
ret = -ENODATA;
break;
}
mutex_unlock(&gccd->gccd_lock);
return ret;
}
static int gccd_gbms_psy_set_property(struct power_supply *psy,
enum gbms_property psp,
const union gbms_propval *pval)
{
struct gccd_drv *gccd = (struct gccd_drv *)power_supply_get_drvdata(psy);
int ret = 0;
if (!gccd->init_complete)
return -EAGAIN;
if (!gccd_get_chg_psy(gccd))
return -EAGAIN;
mutex_lock(&gccd->gccd_lock);
switch (psp) {
case GBMS_PROP_CHARGING_ENABLED:
case GBMS_PROP_CHARGE_DISABLE:
ret = GPSY_SET_PROP(gccd->main_chg_psy, psp, pval->prop.intval);
break;
case GBMS_PROP_TAPER_CONTROL:
if (pval->prop.intval == GBMS_TAPER_CONTROL_ON)
gpio_direction_output(gccd->buck_chg_en, 0);
break;
default:
pr_debug("%s: route to gccd_psy_set_property, psp:%d\n", __func__, psp);
ret = -ENODATA;
break;
}
mutex_unlock(&gccd->gccd_lock);
return ret;
}
static int gccd_gbms_psy_is_writeable(struct power_supply *psy,
enum gbms_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_VOLTAGE_MAX: /* compat, same the next */
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CURRENT_MAX:
case GBMS_PROP_CHARGING_ENABLED:
case GBMS_PROP_CHARGE_DISABLE:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
case GBMS_PROP_TAPER_CONTROL:
return 1;
default:
break;
}
return 0;
}
static struct gbms_desc gccd_psy_desc = {
.psy_dsc.name = "gccd",
.psy_dsc.type = POWER_SUPPLY_TYPE_UNKNOWN,
.psy_dsc.get_property = gccd_psy_get_property,
.psy_dsc.set_property = gccd_psy_set_property,
.psy_dsc.property_is_writeable = gccd_psy_is_writeable,
.psy_dsc.properties = gccd_psy_properties,
.psy_dsc.num_properties = ARRAY_SIZE(gccd_psy_properties),
.get_property = gccd_gbms_psy_get_property,
.set_property = gccd_gbms_psy_set_property,
.property_is_writeable = gccd_gbms_psy_is_writeable,
.forward = true,
};
/* ------------------------------------------------------------------------ */
#define GCCD_DELAY_INIT_MS 500
static void gccd_init_work(struct work_struct *work)
{
struct gccd_drv *gccd = container_of(work, struct gccd_drv,
init_work.work);
if (!gccd_get_chg_psy(gccd))
goto retry_init_work;
if (gccd_gpio_init(gccd->device, gccd) < 0)
goto retry_init_work;
(void)gccd_init_fs(gccd);
(void)gccd_init_debugfs(gccd);
gccd->init_complete = true;
dev_info(gccd->device, "gccd_init_work done\n");
return;
retry_init_work:
schedule_delayed_work(&gccd->init_work,
msecs_to_jiffies(GCCD_DELAY_INIT_MS));
}
static int google_ccd_probe(struct platform_device *pdev)
{
const char *main_chg_psy_name;
const char *buck_chg_psy_name;
struct gccd_drv *gccd;
int ret;
struct power_supply_config psy_cfg = { };
gccd = devm_kzalloc(&pdev->dev, sizeof(*gccd), GFP_KERNEL);
if (!gccd)
return -ENOMEM;
gccd->device = &pdev->dev;
/* main charger */
ret = of_property_read_string(pdev->dev.of_node, "google,main-chg-psy-name",
&main_chg_psy_name);
if (ret < 0)
return -ENODEV;
/* buck charger */
ret = of_property_read_string(pdev->dev.of_node, "google,buck-chg-psy-name",
&buck_chg_psy_name);
if (ret < 0)
return -ENODEV;
dev_info(gccd->device, "google,main-chg-psy-name=%s\n", main_chg_psy_name);
gccd->main_chg_psy_name = devm_kstrdup(&pdev->dev,
main_chg_psy_name, GFP_KERNEL);
if (!gccd->main_chg_psy_name) {
devm_kfree(&pdev->dev, gccd);
return -ENOMEM;
}
dev_info(gccd->device, "google,buck-chg-psy-name=%s\n", buck_chg_psy_name);
gccd->buck_chg_psy_name = devm_kstrdup(&pdev->dev,
buck_chg_psy_name, GFP_KERNEL);
if (!gccd->buck_chg_psy_name) {
devm_kfree(&pdev->dev, gccd);
return -ENOMEM;
}
mutex_init(&gccd->gccd_lock);
INIT_DELAYED_WORK(&gccd->init_work, gccd_init_work);
platform_set_drvdata(pdev, gccd);
psy_cfg.drv_data = gccd;
psy_cfg.of_node = pdev->dev.of_node;
gccd->psy = devm_power_supply_register(gccd->device,
&gccd_psy_desc.psy_dsc, &psy_cfg);
if (IS_ERR(gccd->psy)) {
ret = PTR_ERR(gccd->psy);
dev_err(gccd->device, "Couldn't register as power supply, ret=%d\n", ret);
devm_kfree(&pdev->dev, gccd);
return ret;
}
schedule_delayed_work(&gccd->init_work, 0);
dev_info(gccd->device, "google_ccd_probe done\n");
return 0;
}
static int google_ccd_remove(struct platform_device *pdev)
{
struct gccd_drv *gccd = platform_get_drvdata(pdev);
cancel_delayed_work(&gccd->init_work);
if (gccd->main_chg_psy)
power_supply_put(gccd->main_chg_psy);
if (gccd->buck_chg_psy)
power_supply_put(gccd->buck_chg_psy);
return 0;
}
static const struct of_device_id google_ccd_of_match[] = {
{.compatible = "google,ccd"},
{},
};
MODULE_DEVICE_TABLE(of, google_ccd_of_match);
static struct platform_driver google_ccd_driver = {
.driver = {
.name = "google_ccd",
.owner = THIS_MODULE,
.of_match_table = google_ccd_of_match,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.probe = google_ccd_probe,
.remove = google_ccd_remove,
};
module_platform_driver(google_ccd_driver);
MODULE_DESCRIPTION("Google Charger Combine Driver");
MODULE_AUTHOR("Jack Wu <[email protected]>");
MODULE_LICENSE("GPL");