power: supply: Import xiaomi power supply drivers from yudi-t-oss

Converted to LF and ran clang-format

Change-Id: I04b75fbbaab9949b67dc7222c17af045bc21e98d
Signed-off-by: Jens Reidel <adrian@travitia.xyz>
This commit is contained in:
Jens Reidel 2024-04-03 06:44:39 +02:00
parent 05e7d49733
commit 680c1731f7
No known key found for this signature in database
GPG Key ID: 23C1E5F512C12303
141 changed files with 60604 additions and 0 deletions

View File

@ -895,6 +895,13 @@ config MI_CHARGER_M81
Say Y here to enable xiaomi properties similar to
qti_battery_charger_main_m81 module on stock
config XM_POWER_SUPPLY
tristate "Xiaomi power supply config"
help
Say Y or M here to enable xiaomi power supply
source "drivers/power/supply/qcom/Kconfig"
source "drivers/power/supply/xiaomi/Kconfig"
endif # POWER_SUPPLY

View File

@ -103,3 +103,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
obj-$(CONFIG_SCHGM_FLASH) += schgm-flash.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
obj-$(CONFIG_QCOM_POWER_SUPPLY) += qcom/
obj-$(CONFIG_XM_POWER_SUPPLY) += xiaomi/

View File

@ -0,0 +1,36 @@
# SPDX-License-Identifier: GPL-2.0-only
config PD_RT17XX
tristate "RT17XX PDPHY"
depends on I2C
default n
help
Say Y to enable support for rt17xx pdphy.
config CHARGER_SYV690D
tristate "SYV690D Charger"
depends on I2C
default n
help
Say Y to enable support for syv690d Charger.
config FUEL_GAUGE_BQ27Z561
tristate "BQ27z561 fuel gauge driver"
depends on I2C
default n
help
Say Y to enable support for bq27z561 fuel gauge driver
config CHARGER_PUMP_SC8551A
tristate "SC8551 Divider Charger Driver"
depends on I2C
default n
help
Say Y to enable support for sc8551a charger pump driver
config CHARGER_PUMP_LN8000
tristate "LN8000 Divider Charger Driver"
depends on I2C
default n
help
Say Y to enable support for ln8000 charger pump driver

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += battmngr/
obj-$(CONFIG_XM_POWER_SUPPLY) += common/
obj-$(CONFIG_XM_POWER_SUPPLY) += platform/

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += battery/
obj-$(CONFIG_XM_POWER_SUPPLY) += battmngr_iio/
obj-$(CONFIG_XM_POWER_SUPPLY) += charger/
obj-$(CONFIG_XM_POWER_SUPPLY) += init/

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += xm_battery.o
xm_battery-objs := xm_battery_core.o xm_battery_feature.o

View File

@ -0,0 +1,428 @@
#include <linux/battmngr/xm_battery_core.h>
struct xm_battery *g_xm_battery;
EXPORT_SYMBOL(g_xm_battery);
static void batt_check_charger_psy(struct xm_battery *battery)
{
if (!battery->usb_psy) {
battery->usb_psy = power_supply_get_by_name("usb");
if (!battery->usb_psy)
battery_err("%s usb psy not found!\n", __func__);
}
}
int battery_process_event_fg(struct battmngr_notify *noti_data)
{
int rc = 0;
battery_err("%s: msg_type %d\n", __func__, noti_data->fg_msg.msg_type);
switch (noti_data->fg_msg.msg_type) {
case BATTMNGR_MSG_FG:
cancel_delayed_work(
&(g_xm_battery->batt_feature->xm_prop_change_work));
g_xm_battery->batt_feature->update_cont = 1;
schedule_delayed_work(
&(g_xm_battery->batt_feature->xm_prop_change_work), 0);
break;
default:
break;
}
return rc;
}
EXPORT_SYMBOL(battery_process_event_fg);
static int get_prop_batt_health(struct xm_battery *battery,
union power_supply_propval *val)
{
int rc = 0;
if (!battery)
return -EINVAL;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TEMP,
&val->intval);
if (rc < 0)
return -EINVAL;
if (val->intval >= BATT_OVERHEAT_THRESHOLD)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (val->intval >= BATT_WARM_THRESHOLD &&
val->intval < BATT_OVERHEAT_THRESHOLD)
val->intval = POWER_SUPPLY_HEALTH_WARM;
else if (val->intval >= BATT_COOL_THRESHOLD &&
val->intval < BATT_WARM_THRESHOLD)
val->intval = POWER_SUPPLY_HEALTH_GOOD;
else if (val->intval >= BATT_COLD_THRESHOLD &&
val->intval < BATT_COOL_THRESHOLD)
val->intval = POWER_SUPPLY_HEALTH_COOL;
else if (val->intval < BATT_COLD_THRESHOLD)
val->intval = POWER_SUPPLY_HEALTH_COLD;
return 0;
}
static int get_prop_batt_capacity_level(struct xm_battery *battery,
union power_supply_propval *val)
{
int rc = 0, capacity;
if (!battery)
return -EINVAL;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CAPACITY, &capacity);
if (rc < 0)
return -EINVAL;
if (capacity == 0)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
else if (capacity > 0 && capacity <= 20)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
else if (capacity > 20 && capacity <= 80)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
else if (capacity > 80 && capacity <= 99)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
else if (capacity == 100)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
return rc;
}
static int get_prop_batt_capacity(struct xm_battery *battery,
union power_supply_propval *val)
{
int rc = 0;
if (!battery)
return -EINVAL;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CAPACITY, &val->intval);
if (rc < 0)
return -EINVAL;
if (val->intval == 0)
power_supply_changed(battery->batt_psy);
return rc;
}
static int set_prop_system_temp_level(struct xm_battery *battery,
enum power_supply_property psp,
const union power_supply_propval *val)
{
int rc = 0;
if (val->intval < 0)
return -EINVAL;
if (val->intval >= MAX_TEMP_LEVEL)
return -EINVAL;
rc = power_supply_set_property(battery->usb_psy, psp, val);
return rc;
}
static enum power_supply_property batt_psy_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
};
static int batt_psy_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *pval)
{
int rc = 0;
int chg_online = 0, chg_present = 0, fg_status = 0;
struct xm_battery *battery = power_supply_get_drvdata(psy);
batt_check_charger_psy(battery);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_PRESENT,
&chg_present);
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_ONLINE,
&chg_online);
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_STATUS,
&pval->intval);
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_STATUS, &fg_status);
if (chg_present && chg_online) {
if (fg_status == POWER_SUPPLY_STATUS_FULL ||
pval->intval == POWER_SUPPLY_STATUS_FULL)
pval->intval = POWER_SUPPLY_STATUS_FULL;
else if ((pval->intval != POWER_SUPPLY_STATUS_FULL) &&
(g_xm_charger->input_suspend == 0))
pval->intval = POWER_SUPPLY_STATUS_CHARGING;
}
break;
case POWER_SUPPLY_PROP_HEALTH:
rc = get_prop_batt_health(battery, pval);
if (rc != 0)
pval->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
case POWER_SUPPLY_PROP_PRESENT:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_PRESENT, &pval->intval);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
if (battery->usb_psy)
rc = power_supply_get_property(battery->usb_psy, psp,
pval);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_TYPE,
&pval->intval);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
rc = get_prop_batt_capacity_level(battery, pval);
break;
case POWER_SUPPLY_PROP_CAPACITY:
rc = get_prop_batt_capacity(battery, pval);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
if (battery->usb_psy)
rc = power_supply_get_property(battery->usb_psy, psp,
pval);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
if (battery->usb_psy)
rc = power_supply_get_property(battery->usb_psy, psp,
pval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_VOLTAGE_NOW,
&pval->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
if (battery->usb_psy)
rc = power_supply_get_property(battery->usb_psy, psp,
pval);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CURRENT_NOW,
&pval->intval);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
if (battery->usb_psy)
rc = power_supply_get_property(
battery->usb_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
pval);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
pval->intval = get_effective_result(g_xm_charger->fcc_votable);
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_TERM,
&pval->intval);
break;
case POWER_SUPPLY_PROP_TEMP:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_TEMP, &pval->intval);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
pval->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_RM, &pval->intval);
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CYCLE_COUNT,
&pval->intval);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CHARGE_FULL,
&pval->intval);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CHARGE_FULL_DESIGN,
&pval->intval);
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_TIME_TO_FULL_NOW,
&pval->intval);
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_TIME_TO_EMPTY_NOW,
&pval->intval);
break;
default:
battery_err("%s: batt power supply prop %d not supported\n",
__func__, psp);
return -EINVAL;
}
if (rc < 0) {
battery_err("%s: Couldn't get prop %d rc = %d\n", __func__, psp,
rc);
return -ENODATA;
}
return 0;
}
static int batt_psy_set_prop(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
int rc = 0;
struct xm_battery *battery = power_supply_get_drvdata(psy);
batt_check_charger_psy(battery);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
if (battery->usb_psy)
rc = set_prop_system_temp_level(battery, prop, val);
break;
case POWER_SUPPLY_PROP_CAPACITY:
rc = xm_battmngr_write_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CAPACITY, val->intval);
break;
case POWER_SUPPLY_PROP_TEMP:
rc = xm_battmngr_write_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_TEMP, val->intval);
break;
default:
battery_err("%s: batt power supply prop %d not supported\n",
__func__, prop);
return -EINVAL;
}
return rc;
}
static int batt_psy_prop_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_TEMP:
return 1;
default:
break;
}
return 0;
}
static const struct power_supply_desc batt_psy_desc = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = batt_psy_props,
.num_properties = ARRAY_SIZE(batt_psy_props),
.get_property = batt_psy_get_prop,
.set_property = batt_psy_set_prop,
.property_is_writeable = batt_psy_prop_is_writeable,
};
static int xm_init_batt_psy(struct xm_battery *battery)
{
struct power_supply_config batt_cfg = {};
int rc = 0;
batt_cfg.drv_data = battery;
batt_cfg.of_node = battery->dev->of_node;
battery->batt_psy = devm_power_supply_register(
battery->dev, &batt_psy_desc, &batt_cfg);
if (IS_ERR(battery->batt_psy)) {
battery_err("%s: Couldn't register battery power supply\n",
__func__);
return PTR_ERR(battery->batt_psy);
}
return rc;
}
static int xm_battery_parse_dt(struct xm_battery *battery)
{
struct device_node *node = battery->dev->of_node;
if (!node) {
battery_err("%s: device tree node missing\n", __func__);
return -EINVAL;
}
return 0;
};
int xm_battery_init(struct xm_battery *battery)
{
int rc = 0;
battery_err("%s: Start\n", __func__);
rc = xm_battery_parse_dt(battery);
if (rc < 0) {
battery_err("%s: Couldn't parse device tree rc=%d\n", __func__,
rc);
return rc;
}
rc = xm_init_batt_psy(battery);
if (rc < 0) {
battery_err("%s: Couldn't initialize batt psy rc=%d\n",
__func__, rc);
return -ENODATA;
}
rc = xm_batt_feature_init(battery);
if (rc < 0) {
battery_err("%s: Couldn't init xm_batt_feature rc=%d\n",
__func__, rc);
return rc;
}
battery->usb_psy = power_supply_get_by_name("usb");
if (!battery->usb_psy)
battery_err("%s get usb psy fail\n", __func__);
battery_err("%s: End\n", __func__);
return rc;
}
EXPORT_SYMBOL(xm_battery_init);
MODULE_DESCRIPTION("Xiaomi Battery core");
MODULE_AUTHOR("getian@xiaomi.com");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,94 @@
#include <linux/battmngr/xm_battery_core.h>
static int xm_get_shutdown_delay(void)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_CAPACITY,
&val);
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_SHUTDOWN_DELAY, &val);
return val;
}
#define MAX_UEVENT_LENGTH 50
static void generate_xm_charge_uvent(struct work_struct *work)
{
int count;
struct batt_feature_info *chip = container_of(
work, struct batt_feature_info, xm_prop_change_work.work);
static char uevent_string[][MAX_UEVENT_LENGTH + 1] = {
"POWER_SUPPLY_SHUTDOWN_DELAY=\n", //length=28+8
};
static char *envp[] = {
uevent_string[0],
NULL,
};
char *prop_buf = NULL;
count = chip->update_cont;
if (chip->update_cont < 0)
return;
prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
if (!prop_buf)
return;
scnprintf(prop_buf, PAGE_SIZE, "%u", xm_get_shutdown_delay());
strncpy(uevent_string[0] + 28, prop_buf, MAX_UEVENT_LENGTH - 28);
battery_err("uevent test : %s, count %d\n", envp[0], count);
/*add our prop end*/
kobject_uevent_env(&chip->dev->kobj, KOBJ_CHANGE, envp);
free_page((unsigned long)prop_buf);
chip->update_cont = count - 1;
schedule_delayed_work(&chip->xm_prop_change_work,
msecs_to_jiffies(2000));
return;
}
int xm_batt_feature_init(struct xm_battery *battery)
{
struct batt_feature_info *chip;
battery_err("%s: Start\n", __func__);
if (battery->batt_feature) {
battery_err("%s: Already initialized\n", __func__);
return -EINVAL;
}
chip = devm_kzalloc(battery->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = battery->dev;
chip->update_cont = 0;
INIT_DELAYED_WORK(&chip->xm_prop_change_work, generate_xm_charge_uvent);
schedule_delayed_work(&chip->xm_prop_change_work,
msecs_to_jiffies(30000));
battery->batt_feature = chip;
battery_err("%s: End\n", __func__);
return 0;
}
void xm_batt_feature_deinit(void)
{
struct batt_feature_info *chip = g_xm_battery->batt_feature;
if (!chip)
return;
cancel_delayed_work_sync(&chip->xm_prop_change_work);
chip = NULL;
return;
}

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += xm_battmngr_iio.o

View File

@ -0,0 +1,296 @@
#include <linux/battmngr/xm_battmngr_iio.h>
struct xm_battmngr_iio *g_battmngr_iio;
EXPORT_SYMBOL(g_battmngr_iio);
int xm_get_iio_channel(struct xm_battmngr_iio *battmngr_iio,
const char *propname, struct iio_channel **chan)
{
int rc = 0;
rc = of_property_match_string(battmngr_iio->dev->of_node,
"io-channel-names", propname);
if (rc < 0)
return 0;
*chan = devm_iio_channel_get(battmngr_iio->dev, propname);
if (IS_ERR(*chan)) {
rc = PTR_ERR(*chan);
if (rc != -EPROBE_DEFER)
pr_err("%s channel unavailable, %d\n", propname, rc);
*chan = NULL;
}
return rc;
}
EXPORT_SYMBOL(xm_get_iio_channel);
bool is_cp_master_chan_valid(struct xm_battmngr_iio *battmngr_iio,
enum cp_iio_channels chan)
{
int rc;
if (IS_ERR(battmngr_iio->iio_chan_list_cp[chan]))
return false;
if (!battmngr_iio->iio_chan_list_cp[chan]) {
battmngr_iio->iio_chan_list_cp[chan] = devm_iio_channel_get(
battmngr_iio->dev, cp_iio_chan[chan]);
if (IS_ERR(battmngr_iio->iio_chan_list_cp[chan])) {
rc = PTR_ERR(battmngr_iio->iio_chan_list_cp[chan]);
if (rc == -EPROBE_DEFER)
battmngr_iio->iio_chan_list_cp[chan] = NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
cp_iio_chan[chan], rc);
return false;
}
}
return true;
}
EXPORT_SYMBOL(is_cp_master_chan_valid);
bool is_cp_slave_chan_valid(struct xm_battmngr_iio *battmngr_iio,
enum cp_iio_channels chan)
{
int rc;
if (IS_ERR(battmngr_iio->iio_chan_list_cp_sec[chan]))
return false;
if (!battmngr_iio->iio_chan_list_cp_sec[chan]) {
battmngr_iio->iio_chan_list_cp_sec[chan] = devm_iio_channel_get(
battmngr_iio->dev, cp_sec_iio_chan[chan]);
if (IS_ERR(battmngr_iio->iio_chan_list_cp_sec[chan])) {
rc = PTR_ERR(battmngr_iio->iio_chan_list_cp_sec[chan]);
if (rc == -EPROBE_DEFER)
battmngr_iio->iio_chan_list_cp_sec[chan] = NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
cp_sec_iio_chan[chan], rc);
return false;
}
}
return true;
}
EXPORT_SYMBOL(is_cp_slave_chan_valid);
bool is_main_chg_chan_valid(struct xm_battmngr_iio *battmngr_iio,
enum main_chg_iio_channels chan)
{
int rc;
if (IS_ERR(battmngr_iio->iio_chan_list_main_chg[chan]))
return false;
if (!battmngr_iio->iio_chan_list_main_chg[chan]) {
battmngr_iio->iio_chan_list_main_chg[chan] =
devm_iio_channel_get(battmngr_iio->dev,
main_chg_iio_chan[chan]);
if (IS_ERR(battmngr_iio->iio_chan_list_main_chg[chan])) {
rc = PTR_ERR(
battmngr_iio->iio_chan_list_main_chg[chan]);
if (rc == -EPROBE_DEFER)
battmngr_iio->iio_chan_list_main_chg[chan] =
NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
main_chg_iio_chan[chan], rc);
return false;
}
}
return true;
}
EXPORT_SYMBOL(is_main_chg_chan_valid);
bool is_batt_fg_chan_valid(struct xm_battmngr_iio *battmngr_iio,
enum batt_fg_iio_channels chan)
{
int rc;
if (IS_ERR(battmngr_iio->iio_chan_list_batt_fg[chan]))
return false;
if (!battmngr_iio->iio_chan_list_batt_fg[chan]) {
battmngr_iio->iio_chan_list_batt_fg[chan] =
devm_iio_channel_get(battmngr_iio->dev,
batt_fg_iio_chan[chan]);
if (IS_ERR(battmngr_iio->iio_chan_list_batt_fg[chan])) {
rc = PTR_ERR(battmngr_iio->iio_chan_list_batt_fg[chan]);
if (rc == -EPROBE_DEFER)
battmngr_iio->iio_chan_list_batt_fg[chan] =
NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
batt_fg_iio_chan[chan], rc);
return false;
}
}
return true;
}
EXPORT_SYMBOL(is_batt_fg_chan_valid);
bool is_pd_chan_valid(struct xm_battmngr_iio *battmngr_iio,
enum batt_fg_iio_channels chan)
{
int rc;
if (IS_ERR(battmngr_iio->iio_chan_list_pd[chan]))
return false;
if (!battmngr_iio->iio_chan_list_pd[chan]) {
battmngr_iio->iio_chan_list_pd[chan] = devm_iio_channel_get(
battmngr_iio->dev, pd_iio_chan[chan]);
if (IS_ERR(battmngr_iio->iio_chan_list_pd[chan])) {
rc = PTR_ERR(battmngr_iio->iio_chan_list_pd[chan]);
if (rc == -EPROBE_DEFER)
battmngr_iio->iio_chan_list_pd[chan] = NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
pd_iio_chan[chan], rc);
return false;
}
}
return true;
}
EXPORT_SYMBOL(is_pd_chan_valid);
int xm_battmngr_read_iio_prop(struct xm_battmngr_iio *battmngr_iio,
enum iio_type type, int iio_chan, int *val)
{
struct iio_channel *iio_chan_list;
int rc;
if (!battmngr_iio)
return -ENODEV;
switch (type) {
case CP_MASTER:
if (!is_cp_master_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_cp[iio_chan];
break;
case CP_SLAVE:
if (!is_cp_slave_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_cp_sec[iio_chan];
break;
case MAIN_CHG:
if (!is_main_chg_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_main_chg[iio_chan];
break;
case BATT_FG:
if (!is_batt_fg_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_batt_fg[iio_chan];
break;
case PD_PHY:
if (!is_pd_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_pd[iio_chan];
break;
default:
pr_err_ratelimited("iio_type %d is not supported\n", type);
return -EINVAL;
}
rc = iio_read_channel_processed(iio_chan_list, val);
return rc < 0 ? rc : 0;
}
EXPORT_SYMBOL(xm_battmngr_read_iio_prop);
int xm_battmngr_write_iio_prop(struct xm_battmngr_iio *battmngr_iio,
enum iio_type type, int iio_chan, int val)
{
struct iio_channel *iio_chan_list;
int rc;
if (!battmngr_iio)
return -ENODEV;
switch (type) {
case CP_MASTER:
if (!is_cp_master_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_cp[iio_chan];
break;
case CP_SLAVE:
if (!is_cp_slave_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_cp_sec[iio_chan];
break;
case MAIN_CHG:
if (!is_main_chg_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_main_chg[iio_chan];
break;
case BATT_FG:
if (!is_batt_fg_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_batt_fg[iio_chan];
break;
case PD_PHY:
if (!is_pd_chan_valid(battmngr_iio, iio_chan))
return -ENODEV;
iio_chan_list = battmngr_iio->iio_chan_list_pd[iio_chan];
break;
default:
pr_err_ratelimited("iio_type %d is not supported\n", type);
return -EINVAL;
}
rc = iio_write_channel_raw(iio_chan_list, val);
return rc < 0 ? rc : 0;
}
EXPORT_SYMBOL(xm_battmngr_write_iio_prop);
int xm_battmngr_iio_init(struct xm_battmngr_iio *battmngr_iio)
{
int rc = 0;
pr_err("xm_battmngr_iio_init start\n");
battmngr_iio->iio_chan_list_cp =
devm_kcalloc(battmngr_iio->dev, ARRAY_SIZE(cp_iio_chan),
sizeof(*battmngr_iio->iio_chan_list_cp),
GFP_KERNEL);
if (!battmngr_iio->iio_chan_list_cp)
return -ENOMEM;
battmngr_iio->iio_chan_list_cp_sec =
devm_kcalloc(battmngr_iio->dev, ARRAY_SIZE(cp_sec_iio_chan),
sizeof(*battmngr_iio->iio_chan_list_cp_sec),
GFP_KERNEL);
if (!battmngr_iio->iio_chan_list_cp_sec)
return -ENOMEM;
battmngr_iio->iio_chan_list_main_chg =
devm_kcalloc(battmngr_iio->dev, ARRAY_SIZE(main_chg_iio_chan),
sizeof(*battmngr_iio->iio_chan_list_main_chg),
GFP_KERNEL);
if (!battmngr_iio->iio_chan_list_main_chg)
return -ENOMEM;
battmngr_iio->iio_chan_list_batt_fg =
devm_kcalloc(battmngr_iio->dev, ARRAY_SIZE(batt_fg_iio_chan),
sizeof(*battmngr_iio->iio_chan_list_batt_fg),
GFP_KERNEL);
if (!battmngr_iio->iio_chan_list_batt_fg)
return -ENOMEM;
battmngr_iio->iio_chan_list_pd =
devm_kcalloc(battmngr_iio->dev, ARRAY_SIZE(pd_iio_chan),
sizeof(*battmngr_iio->iio_chan_list_pd),
GFP_KERNEL);
if (!battmngr_iio->iio_chan_list_pd)
return -ENOMEM;
return rc;
}
EXPORT_SYMBOL(xm_battmngr_iio_init);
MODULE_DESCRIPTION("Xiaomi Battery Manager iio");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("getian@xiaomi.com");

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += main/
obj-$(CONFIG_XM_POWER_SUPPLY) += quick_chg/

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += xm_charger.o
xm_charger-objs := xm_charger_core.o xm_charger_votable.o xm_charger_ffc.o \
xm_charger_thermal.o step-chg-jeita.o xm_charger_feature.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,509 @@
#include <linux/battmngr/xm_charger_core.h>
struct xm_charger *g_xm_charger;
EXPORT_SYMBOL(g_xm_charger);
static void charger_check_battery_psy(struct xm_charger *charger)
{
if (!charger->batt_psy) {
charger->batt_psy = power_supply_get_by_name("battery");
if (!charger->batt_psy)
charger_err("%s batt psy not found!\n", __func__);
}
}
static void update_pd_bc12_active(struct xm_charger *charger,
struct battmngr_notify *noti_data)
{
static int active_flag;
charger->pd_active = noti_data->pd_msg.pd_active;
if (!charger->pd_active) {
charger->pd_verified = 0;
g_battmngr_noti->pd_msg.pd_verified = 0;
}
charger->bc12_active = noti_data->mainchg_msg.chg_plugin;
if (!active_flag && (charger->pd_active || charger->bc12_active)) {
vote(charger->awake_votable, CHG_AWAKE_VOTER, true, 0);
active_flag = 1;
charger_err("%s: chg pm_awake\n", __func__);
} else if (active_flag && !charger->pd_active &&
!charger->bc12_active) {
g_battmngr_noti->misc_msg.disable_thermal = 0;
g_battmngr_noti->misc_msg.vindpm_temp = 0;
vote(charger->awake_votable, CHG_AWAKE_VOTER, false, 0);
active_flag = 0;
charger_err("%s: chg pm_relax\n", __func__);
}
return;
}
static void update_usb_type(struct xm_charger *chg)
{
static int last_bc12_type;
static int last_pd_active;
if (chg->pd_active) {
usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
charger_err("%s: pd_active=%d\n", __func__, chg->pd_active);
} else {
chg->bc12_type = g_battmngr_noti->mainchg_msg.chg_type;
switch (chg->bc12_type) {
case POWER_SUPPLY_USB_TYPE_SDP:
usb_psy_desc.type = POWER_SUPPLY_TYPE_USB;
break;
case POWER_SUPPLY_TYPE_USB_FLOAT:
case POWER_SUPPLY_USB_TYPE_DCP:
case POWER_SUPPLY_TYPE_USB_HVDCP:
usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
break;
case POWER_SUPPLY_USB_TYPE_CDP:
usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
break;
default:
usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
}
charger_err("%s: usb_type=%d, usb_psy_desc.type=%d\n", __func__,
chg->bc12_type, usb_psy_desc.type);
}
if (last_bc12_type != chg->bc12_type ||
last_pd_active != chg->pd_active) {
cancel_delayed_work(&(chg->chg_feature->xm_prop_change_work));
chg->chg_feature->update_cont = 0;
schedule_delayed_work(&(chg->chg_feature->xm_prop_change_work),
msecs_to_jiffies(120));
}
last_bc12_type = chg->bc12_type;
last_pd_active = chg->pd_active;
chg->real_type = usb_psy_desc.type;
power_supply_changed(chg->usb_psy);
power_supply_changed(chg->batt_psy);
charger_err("%s: usb_type=%d, usb_psy_desc.type=%d\n", __func__,
chg->bc12_type, usb_psy_desc.type);
return;
}
int charger_process_event_cp(struct battmngr_notify *noti_data)
{
int rc = 0;
charger_err("%s: msg_type %d\n", __func__, noti_data->cp_msg.msg_type);
switch (noti_data->cp_msg.msg_type) {
case BATTMNGR_MSG_CP_MASTER:
break;
case BATTMNGR_MSG_CP_SLAVE:
break;
default:
break;
}
return rc;
}
EXPORT_SYMBOL(charger_process_event_cp);
int charger_process_event_mainchg(struct battmngr_notify *noti_data)
{
int rc = 0;
charger_err("%s: msg_type %d\n", __func__,
noti_data->mainchg_msg.msg_type);
switch (noti_data->mainchg_msg.msg_type) {
case BATTMNGR_MSG_MAINCHG_TYPE:
update_pd_bc12_active(g_xm_charger, noti_data);
update_usb_type(g_xm_charger);
typec_conn_therm_start_stop(g_xm_charger,
g_xm_charger->chg_feature);
stepchg_jeita_start_stop(g_xm_charger, g_xm_charger->step_chg);
night_charging_start_stop(g_xm_charger,
g_xm_charger->chg_feature);
xm_charger_set_fastcharge_mode(g_xm_charger,
g_xm_charger->pd_verified);
if (g_xm_charger->input_suspend == 1)
xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_ENABLED, 0);
break;
case BATTMNGR_MSG_MAINCHG_OTG_ENABLE:
g_xm_charger->otg_enable =
g_battmngr_noti->mainchg_msg.otg_enable;
typec_conn_therm_start_stop(g_xm_charger,
g_xm_charger->chg_feature);
break;
default:
break;
}
return rc;
}
EXPORT_SYMBOL(charger_process_event_mainchg);
int charger_process_event_pd(struct battmngr_notify *noti_data)
{
int rc = 0;
charger_err("%s: msg_type %d\n", __func__, noti_data->pd_msg.msg_type);
switch (noti_data->pd_msg.msg_type) {
case BATTMNGR_MSG_PD_ACTIVE:
update_pd_bc12_active(g_xm_charger, noti_data);
update_usb_type(g_xm_charger);
typec_conn_therm_start_stop(g_xm_charger,
g_xm_charger->chg_feature);
stepchg_jeita_start_stop(g_xm_charger, g_xm_charger->step_chg);
night_charging_start_stop(g_xm_charger,
g_xm_charger->chg_feature);
xm_charger_set_fastcharge_mode(g_xm_charger,
g_xm_charger->pd_verified);
break;
case BATTMNGR_MSG_PD_VERIFED:
xm_charger_set_fastcharge_mode(g_xm_charger,
g_xm_charger->pd_verified);
cancel_delayed_work(
&(g_xm_charger->chg_feature->xm_prop_change_work));
g_xm_charger->chg_feature->update_cont = 0;
schedule_delayed_work(
&(g_xm_charger->chg_feature->xm_prop_change_work),
msecs_to_jiffies(600));
break;
default:
break;
}
return rc;
}
EXPORT_SYMBOL(charger_process_event_pd);
static int chg_votable_init(struct xm_charger *chg)
{
chg->fcc_votable = find_votable("FCC");
chg->fv_votable = find_votable("FV");
chg->usb_icl_votable = find_votable("ICL");
if (!chg->fcc_votable || !chg->fv_votable || !chg->usb_icl_votable)
return -EINVAL;
vote(chg->fcc_votable, CHG_INIT_VOTER, true, chg->dt.batt_current_max);
vote(chg->fv_votable, CHG_INIT_VOTER, true, chg->dt.chg_voltage_max);
vote(chg->usb_icl_votable, CHG_INIT_VOTER, true,
chg->dt.input_batt_current_max);
vote(chg->input_suspend_votable, CHG_INIT_VOTER, false, 0);
return 0;
}
static enum power_supply_property usb_psy_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
};
static int usb_psy_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int rc = 0, mode;
struct xm_charger *charger = power_supply_get_drvdata(psy);
charger_check_battery_psy(charger);
//update_usb_type(charger);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_PRESENT,
&val->intval);
break;
case POWER_SUPPLY_PROP_ONLINE:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_ONLINE,
&val->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = charger->dt.chg_design_voltage_max;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_FASTCHARGE_MODE, &mode);
if (mode)
val->intval = charger->dt.chg_voltage_max;
else
val->intval = charger->dt.non_ffc_cv;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_BUS_VOLTAGE,
&val->intval);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_CURRENT,
&val->intval);
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
val->intval = charger->dt.batt_current_max;
break;
case POWER_SUPPLY_PROP_TYPE:
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = charger->dt.input_batt_current_max;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = charger->system_temp_level;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
val->intval = charger->thermal_levels;
break;
default:
charger_err("%s: get prop %d is not supported in usb\n",
__func__, psp);
rc = -EINVAL;
break;
}
if (rc < 0) {
charger_err("%s: Couldn't get prop %d rc = %d\n\n", __func__,
psp, rc);
return -ENODATA;
}
return 0;
}
static int usb_psy_set_prop(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
int rc = 0;
struct xm_charger *charger = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
charger->system_temp_level = val->intval;
rc = xm_charger_thermal(charger);
break;
default:
charger_err("%s: Set prop %d is not supported in usb psy\n",
__func__, psp);
rc = -EINVAL;
break;
}
return rc;
}
static int usb_psy_prop_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
return 1;
default:
break;
}
return 0;
}
static struct power_supply_desc usb_psy_desc = {
.name = "usb",
.type = POWER_SUPPLY_TYPE_UNKNOWN,
.properties = usb_psy_props,
.num_properties = ARRAY_SIZE(usb_psy_props),
.get_property = usb_psy_get_prop,
.set_property = usb_psy_set_prop,
.property_is_writeable = usb_psy_prop_is_writeable,
};
static int xm_init_usb_psy(struct xm_charger *charger)
{
struct power_supply_config usb_cfg = {};
int rc = 0;
usb_cfg.drv_data = charger;
usb_cfg.of_node = charger->dev->of_node;
charger->usb_psy = devm_power_supply_register(charger->dev,
&usb_psy_desc, &usb_cfg);
if (IS_ERR(charger->usb_psy)) {
charger_err("%s: Couldn't register USB power supply\n",
__func__);
return PTR_ERR(charger->usb_psy);
}
return rc;
}
static int xm_charger_parse_dt(struct xm_charger *charger)
{
struct device_node *node = charger->dev->of_node;
int rc = 0;
if (!node) {
charger_err("%s: device tree node missing\n", __func__);
return -EINVAL;
}
rc = of_property_read_u32(node, "xm,fv-max-uv",
&charger->dt.chg_voltage_max);
if (rc < 0)
charger->dt.chg_voltage_max = 4450000;
rc = of_property_read_u32(node, "xm,fcc-max-ua",
&charger->dt.batt_current_max);
if (rc < 0)
charger->dt.batt_current_max = 10000000;
rc = of_property_read_u32(node, "xm,fv-max-design-uv",
&charger->dt.chg_design_voltage_max);
if (rc < 0)
charger->dt.chg_design_voltage_max = 10000000;
rc = of_property_read_u32(node, "xm,icl-max-ua",
&charger->dt.input_batt_current_max);
if (rc < 0)
charger->dt.input_batt_current_max = 3000000;
rc = of_property_read_u32(node, "xm,step-chg-enable",
&charger->dt.step_chg_enable);
if (rc < 0)
charger->dt.step_chg_enable = false;
rc = of_property_read_u32(node, "xm,sw-jeita-enable",
&charger->dt.sw_jeita_enable);
if (rc < 0)
charger->dt.sw_jeita_enable = false;
rc = of_property_read_u32(node, "xm,ffc_ieoc_l",
&charger->dt.ffc_ieoc_l);
if (rc < 0)
charger->dt.ffc_ieoc_l = 850000;
rc = of_property_read_u32(node, "xm,ffc_ieoc_h",
&charger->dt.ffc_ieoc_h);
if (rc < 0)
charger->dt.ffc_ieoc_h = 890000;
rc = of_property_read_u32(node, "xm,non_ffc_ieoc",
&charger->dt.non_ffc_ieoc);
if (rc < 0)
charger->dt.non_ffc_ieoc = 200000;
rc = of_property_read_u32(node, "xm,non_ffc_cv",
&charger->dt.non_ffc_cv);
if (rc < 0)
charger->dt.non_ffc_cv = 4480000;
rc = of_property_read_u32(node, "xm,non_ffc_cc",
&charger->dt.non_ffc_cc);
if (rc < 0)
charger->dt.non_ffc_cc = 4000000;
rc = xm_charger_parse_dt_therm(charger);
if (rc < 0) {
charger_err("%s: Couldn't initialize parse_dt_therm rc = %d\n",
__func__, rc);
return rc;
}
/*
rc = xm_get_iio_channel(g_battmngr_iio, "chg_pump_therm",
&g_battmngr_iio->chg_pump_therm);
if (rc < 0)
charger_err("%s: Couldn't get IIO channel chg_pump_therm rc = %d\n",
__func__, rc);
*/
rc = xm_get_iio_channel(g_battmngr_iio, "typec_conn_therm",
&g_battmngr_iio->typec_conn_therm);
if (rc < 0)
charger_err(
"%s: Couldn't get IIO channel typec_conn_therm rc = %d\n",
__func__, rc);
return 0;
};
int xm_charger_init(struct xm_charger *charger)
{
int rc = 0;
charger_err("%s: Start\n", __func__);
rc = xm_charger_parse_dt(charger);
if (rc < 0) {
charger_err("%s: Couldn't parse device tree rc=%d\n", __func__,
rc);
return rc;
}
device_init_wakeup(charger->dev, true);
rc = xm_charger_create_votable(charger);
if (rc < 0) {
charger_err(
"%s: Couldn't init xm_charger_create_votable rc=%d\n",
__func__, rc);
return rc;
}
rc = xm_init_usb_psy(charger);
if (rc < 0) {
charger_err("%s: Couldn't initialize usb psy rc=%d\n", __func__,
rc);
return rc;
}
rc = xm_stepchg_jeita_init(charger, charger->dt.step_chg_enable,
charger->dt.sw_jeita_enable);
if (rc < 0) {
charger_err("%s: Couldn't init xm_stepchg_jeita_init rc=%d\n",
__func__, rc);
return rc;
}
rc = xm_chg_feature_init(charger);
if (rc < 0) {
charger_err("%s: Couldn't init xm_chg_feature_init rc=%d\n",
__func__, rc);
return rc;
}
charger->batt_psy = power_supply_get_by_name("battery");
if (!charger->usb_psy)
charger_err("%s get batt psy fail\n", __func__);
chg_votable_init(charger);
xm_charger_thermal(charger);
charger_err("%s: End\n", __func__);
return rc;
}
EXPORT_SYMBOL(xm_charger_init);
void xm_charger_deinit(void)
{
xm_step_chg_deinit();
xm_chg_feature_deinit();
return;
}
EXPORT_SYMBOL(xm_charger_deinit);
MODULE_DESCRIPTION("Xiaomi charger core");
MODULE_AUTHOR("getian@xiaomi.com");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,438 @@
#include <linux/battmngr/xm_charger_core.h>
#include <linux/battmngr/xm_battery_core.h>
typedef void (*usb_host_cb)(void);
usb_host_cb stop_usb_host_cb = NULL;
usb_host_cb start_usb_host_cb = NULL;
void stop_usb_host_cb_set(usb_host_cb cb)
{
stop_usb_host_cb = cb;
return;
}
EXPORT_SYMBOL(stop_usb_host_cb_set);
void start_usb_host_cb_set(usb_host_cb cb)
{
start_usb_host_cb = cb;
return;
}
EXPORT_SYMBOL(start_usb_host_cb_set);
static int xm_set_vbus_disable(struct chg_feature_info *chip, bool disable)
{
int ret = 0;
static bool conn_therm_flag = false;
charger_err("%s: set vbus disable:%d\n", __func__, disable);
if (disable && !conn_therm_flag) {
conn_therm_flag = true;
gpio_set_value(chip->vbus_ctrl_gpio, 1);
if (stop_usb_host_cb)
stop_usb_host_cb();
} else if (!disable && conn_therm_flag) {
conn_therm_flag = false;
gpio_set_value(chip->vbus_ctrl_gpio, 0);
if (start_usb_host_cb)
start_usb_host_cb();
}
chip->vbus_disable = disable;
return ret;
}
int typec_conn_therm_start_stop(struct xm_charger *charger,
struct chg_feature_info *info)
{
charger_err("%s\n", __func__);
if (g_xm_charger->bc12_active || g_xm_charger->pd_active ||
g_xm_charger->otg_enable) {
cancel_delayed_work_sync(&info->typec_conn_therm_work);
schedule_delayed_work(&info->typec_conn_therm_work,
msecs_to_jiffies(CONN_THERM_DELAY_5S));
} else {
if (!info->vbus_disable)
cancel_delayed_work_sync(&info->typec_conn_therm_work);
}
return 0;
}
static void typec_conn_therm_work(struct work_struct *work)
{
struct chg_feature_info *chip = container_of(
work, struct chg_feature_info, typec_conn_therm_work.work);
//union power_supply_propval pval = {0, };
int ret = 0;
if (!g_battmngr_iio->typec_conn_therm) {
xm_get_iio_channel(g_battmngr_iio, "typec_conn_therm",
&g_battmngr_iio->typec_conn_therm);
}
if (g_battmngr_iio->typec_conn_therm) {
ret = iio_read_channel_processed(
g_battmngr_iio->typec_conn_therm,
&chip->connector_temp);
if (ret < 0) {
charger_err("Couldn't read connector_temp, rc=%d\n",
ret);
return;
}
chip->connector_temp /= 100;
} else {
charger_err("Failed to get IIO channel typec_conn_therm\n");
return;
}
if (chip->fake_conn_temp != 0)
chip->connector_temp = chip->fake_conn_temp;
if (chip->connector_temp >= CONN_THERM_TOOHIG_70DEC)
xm_set_vbus_disable(chip, true);
else if (chip->connector_temp <
(CONN_THERM_TOOHIG_70DEC - CONN_THERM_HYS_2DEC))
xm_set_vbus_disable(chip, false);
/*
ret = power_supply_get_property(g_xm_charger->usb_psy,
POWER_SUPPLY_PROP_ONLINE, &pval);
if (ret < 0) {
charger_err("%s: Get usb online failed, rc=%d\n",
__func__, ret);
return;
}
*/
schedule_delayed_work(&chip->typec_conn_therm_work,
msecs_to_jiffies(CONN_THERM_DELAY_5S));
charger_err("%s: fake_conn_temp = %d, connector_temp = %d\n", __func__,
chip->fake_conn_temp, chip->connector_temp);
return;
}
int xm_get_adapter_power_max(void)
{
int val;
int apdo_max = 0;
int apdo_max_volt, apdo_max_curr;
if (g_xm_charger->input_suspend == 1)
return 0;
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_APDO_VOLT_MAX,
&apdo_max_volt);
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_APDO_CURR_MAX,
&apdo_max_curr);
apdo_max = (apdo_max_volt * apdo_max_curr) / 1000000;
charger_err("apdo_max:%d\n", apdo_max);
if (apdo_max == 65)
val = APDO_MAX_65W; /* only for J1 65W adapter */
else if (apdo_max >= 60)
val = APDO_MAX_67W;
else if (apdo_max >= 55 && apdo_max < 60)
val = APDO_MAX_55W;
else if (apdo_max >= 50 && apdo_max < 55)
val = APDO_MAX_50W;
else
val = apdo_max;
return val;
}
EXPORT_SYMBOL(xm_get_adapter_power_max);
int xm_get_soc_decimal(void)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_SOC_DECIMAL,
&val);
return val;
}
EXPORT_SYMBOL(xm_get_soc_decimal);
int xm_get_soc_decimal_rate(void)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_SOC_DECIMAL_RATE, &val);
if (val > 100 || val < 0)
val = 0;
return val;
}
EXPORT_SYMBOL(xm_get_soc_decimal_rate);
struct quick_charge adapter_cap[11] = {
{ POWER_SUPPLY_USB_TYPE_SDP, QUICK_CHARGE_NORMAL },
{ POWER_SUPPLY_USB_TYPE_DCP, QUICK_CHARGE_NORMAL },
{ POWER_SUPPLY_USB_TYPE_CDP, QUICK_CHARGE_NORMAL },
{ POWER_SUPPLY_USB_TYPE_ACA, QUICK_CHARGE_NORMAL },
{ POWER_SUPPLY_TYPE_USB_FLOAT, QUICK_CHARGE_NORMAL },
{ POWER_SUPPLY_USB_TYPE_PD, QUICK_CHARGE_FAST },
{ POWER_SUPPLY_TYPE_USB_HVDCP, QUICK_CHARGE_FAST },
{ POWER_SUPPLY_TYPE_USB_HVDCP_3, QUICK_CHARGE_FAST },
{ POWER_SUPPLY_TYPE_USB_HVDCP_3P5, QUICK_CHARGE_FAST },
{ POWER_SUPPLY_TYPE_WIRELESS, QUICK_CHARGE_FAST },
{ 0, 0 },
};
int xm_get_quick_charge_type(void)
{
int val, i = 0;
int power_max = 0;
int real_charger_type;
if (g_xm_charger->pd_active) {
real_charger_type = POWER_SUPPLY_USB_TYPE_PD;
} else {
real_charger_type = g_xm_charger->bc12_type;
}
charger_err("real charger type : %d\n ", real_charger_type);
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TEMP, &val);
if ((val >= BATT_WARM_THRESHOLD) || (val < LIGHTING_ICON_CHANGE)) {
charger_err("battery temp is under 5 or above 48\n");
return QUICK_CHARGE_NORMAL;
} else if ((real_charger_type == POWER_SUPPLY_USB_TYPE_PD) &&
g_xm_charger->pd_verified) {
power_max = xm_get_adapter_power_max();
charger_err("power_max : %d\n ", power_max);
if (power_max >= 50)
return QUICK_CHARGE_SUPER;
else
return QUICK_CHARGE_TURBE;
} else if (real_charger_type == POWER_SUPPLY_USB_TYPE_PD) {
return QUICK_CHARGE_FAST;
} else {
while (adapter_cap[i].adap_type != 0) {
if (real_charger_type == adapter_cap[i].adap_type) {
return adapter_cap[i].adap_cap;
}
i++;
}
}
return 0;
}
EXPORT_SYMBOL(xm_get_quick_charge_type);
#define MAX_UEVENT_LENGTH 50
static void generate_xm_charge_uvent(struct work_struct *work)
{
int count;
struct chg_feature_info *chip = container_of(
work, struct chg_feature_info, xm_prop_change_work.work);
static char uevent_string[][MAX_UEVENT_LENGTH + 1] = {
"POWER_SUPPLY_SOC_DECIMAL=\n", //length=31+8
"POWER_SUPPLY_SOC_DECIMAL_RATE=\n", //length=31+8
"POWER_SUPPLY_QUICK_CHARGE_TYPE=\n",
};
static char *envp[] = {
uevent_string[0],
uevent_string[1],
uevent_string[2],
NULL,
};
char *prop_buf = NULL;
count = chip->update_cont;
if (chip->update_cont < 0)
return;
prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
if (!prop_buf)
return;
scnprintf(prop_buf, PAGE_SIZE, "%u", xm_get_soc_decimal());
strncpy(uevent_string[0] + 25, prop_buf, MAX_UEVENT_LENGTH - 25);
scnprintf(prop_buf, PAGE_SIZE, "%u", xm_get_soc_decimal_rate());
strncpy(uevent_string[1] + 30, prop_buf, MAX_UEVENT_LENGTH - 30);
scnprintf(prop_buf, PAGE_SIZE, "%u", xm_get_quick_charge_type());
strncpy(uevent_string[2] + 31, prop_buf, MAX_UEVENT_LENGTH - 31);
charger_err("uevent test : %s, %s, %s, count %d\n", envp[0], envp[1],
envp[2], count);
/*add our prop end*/
kobject_uevent_env(&chip->dev->kobj, KOBJ_CHANGE, envp);
free_page((unsigned long)prop_buf);
chip->update_cont = count - 1;
schedule_delayed_work(&chip->xm_prop_change_work,
msecs_to_jiffies(CONN_THERM_DELAY_2S));
return;
}
static int xm_get_prop_battery_input_suspend(void)
{
int val;
val = (get_client_vote(g_xm_charger->fcc_votable, USER_VOTER) == 0);
return val;
}
static int xm_set_prop_battery_input_suspend(int val)
{
int rc;
/* vote 0mA when battery suspended */
rc = vote(g_xm_charger->fcc_votable, USER_VOTER, (bool)val, 0);
if (rc < 0) {
battery_err("%s: Couldn't vote to %s fcc rc=%d\n", __func__,
(bool)val ? "suspend" : "resume", rc);
return rc;
}
g_xm_charger->chg_feature->battery_input_suspend = val;
power_supply_changed(g_xm_charger->batt_psy);
return rc;
}
static void night_chargig_change_work(struct work_struct *work)
{
struct chg_feature_info *chip = container_of(
work, struct chg_feature_info, night_chargig_change_work.work);
static int pre_night_chg_flag = 0;
int capacity, battery_input_suspend;
int rc = 0, val = 0;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CAPACITY, &capacity);
battery_input_suspend = xm_get_prop_battery_input_suspend();
battery_err("%s: capacity:%d, battery_input_suspend:%d.\n", __func__,
capacity, battery_input_suspend);
battery_err("%s: pre_nchg:%d, nchg:%d.\n", __func__, pre_night_chg_flag,
g_xm_charger->chg_feature->night_chg_flag);
if (pre_night_chg_flag != g_xm_charger->chg_feature->night_chg_flag) {
if (g_xm_charger->chg_feature->night_chg_flag &&
capacity >= 80) {
val = 1;
rc = xm_set_prop_battery_input_suspend(val);
battery_err("%s: open night charging.\n", __func__);
pre_night_chg_flag =
g_xm_charger->chg_feature->night_chg_flag;
}
}
if (battery_input_suspend &&
(!g_xm_charger->chg_feature->night_chg_flag || capacity <= 75)) {
val = 0;
xm_set_prop_battery_input_suspend(val);
battery_err("%s: close night charging.\n", __func__);
pre_night_chg_flag = 0;
}
schedule_delayed_work(&chip->night_chargig_change_work,
msecs_to_jiffies(CONN_THERM_DELAY_10S));
return;
}
int night_charging_start_stop(struct xm_charger *charger,
struct chg_feature_info *chip)
{
charger_err("%s\n", __func__);
if (g_xm_charger->bc12_active || g_xm_charger->pd_active) {
cancel_delayed_work_sync(&chip->night_chargig_change_work);
schedule_delayed_work(&chip->night_chargig_change_work, 0);
} else {
cancel_delayed_work_sync(&chip->night_chargig_change_work);
}
return 0;
}
static int xm_chg_feature_parse_dt(struct device *dev,
struct chg_feature_info *chip)
{
int ret = 0;
struct device_node *np = dev->of_node;
chip->vbus_ctrl_gpio = of_get_named_gpio(np, "vbus_ctrl_gpio", 0);
if (chip->vbus_ctrl_gpio < 0) {
charger_err("%s no vbus_ctrl_gpio info\n", __func__);
return ret;
} else {
charger_err("%s vbus_ctrl_gpio info %d\n", __func__,
chip->vbus_ctrl_gpio);
}
return 0;
}
int xm_chg_feature_init(struct xm_charger *charger)
{
int ret;
struct chg_feature_info *chip;
charger_err("%s: Start\n", __func__);
if (charger->chg_feature) {
charger_err("%s: Already initialized\n", __func__);
return -EINVAL;
}
chip = devm_kzalloc(charger->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->dev = charger->dev;
chip->update_cont = 0;
ret = xm_chg_feature_parse_dt(chip->dev, chip);
if (ret) {
charger_err("%s parse dt fail(%d)\n", __func__, ret);
return ret;
}
ret = devm_gpio_request(chip->dev, chip->vbus_ctrl_gpio,
"vbus ctrl gpio");
if (ret) {
charger_err("%s: %d gpio request failed\n", __func__,
chip->vbus_ctrl_gpio);
return ret;
}
gpio_direction_output(chip->vbus_ctrl_gpio, false);
INIT_DELAYED_WORK(&chip->typec_conn_therm_work, typec_conn_therm_work);
INIT_DELAYED_WORK(&chip->xm_prop_change_work, generate_xm_charge_uvent);
INIT_DELAYED_WORK(&chip->night_chargig_change_work,
night_chargig_change_work);
schedule_delayed_work(&chip->typec_conn_therm_work,
msecs_to_jiffies(CONN_THERM_DELAY_5S));
schedule_delayed_work(&chip->xm_prop_change_work,
msecs_to_jiffies(30000));
charger->chg_feature = chip;
charger_err("%s: End\n", __func__);
return 0;
}
void xm_chg_feature_deinit(void)
{
struct chg_feature_info *chip = g_xm_charger->chg_feature;
if (!chip)
return;
cancel_delayed_work_sync(&chip->typec_conn_therm_work);
cancel_delayed_work_sync(&chip->xm_prop_change_work);
cancel_delayed_work_sync(&chip->night_chargig_change_work);
chip = NULL;
return;
}

View File

@ -0,0 +1,160 @@
#include <linux/battmngr/xm_charger_core.h>
static int xm_charger_config_iterm(struct xm_charger *charger, int mode)
{
int rc = 0, val = 0;
int batt_temp;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TEMP,
&val);
if (rc < 0) {
charger_err("%s: Couldn't get battery temp:%d\n", __func__, rc);
return rc;
}
batt_temp = val;
if (mode) {
if (batt_temp >= BATT_NORMAL_H_THRESHOLD)
val = charger->dt.ffc_ieoc_h;
else
val = charger->dt.ffc_ieoc_l;
} else {
val = charger->dt.non_ffc_ieoc;
}
val /= 1000;
charger_err("%s: charger_config_iterm:%d\n", __func__, val);
rc = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_TERM, val);
if (rc < 0) {
charger_err("%s: Couldn't get charge term:%d\n", __func__, rc);
return rc;
}
return 0;
}
int xm_charger_get_fastcharge_mode(struct xm_charger *charger, int *mode)
{
int rc = 0;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_FASTCHARGE_MODE, mode);
if (rc < 0) {
charger_err("%s: Couldn't write fastcharge mode:%d\n", __func__,
rc);
return rc;
}
return 0;
}
int xm_charger_set_fastcharge_mode(struct xm_charger *charger, int mode)
{
int rc = 0, val = 0;
int batt_temp;
int effective_fv = 0;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_BATTERY_AUTH, &val);
if (rc < 0) {
charger_err("%s: Couldn't get battery authentic:%d\n", __func__,
rc);
return rc;
}
charger_err("%s: get battery authentic:%d\n", __func__, val);
if (!val)
mode = false;
/*if soc > 95 do not set fastcharge flag*/
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_CAPACITY, &val);
charger_err("%s: get fg capacity:%d\n", __func__, val);
if (rc < 0) {
charger_err("%s: Couldn't get fg capacity:%d\n", __func__, rc);
return rc;
}
if (mode && val >= SOC_HIGH_THRESHOLD) {
charger_err(
"%s: soc:%d is more than 95, do not setfastcharge mode\n",
__func__, val);
mode = false;
}
/*if temp > 480 or temp < 150 do not set fastcharge flag*/
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TEMP,
&val);
charger_err("%s: get fg temp:%d\n", __func__, val);
if (rc < 0) {
charger_err("%s: Couldn't get fg temp:%d\n", __func__, rc);
return rc;
}
batt_temp = val;
if (mode &&
(val >= BATT_WARM_THRESHOLD || val <= BATT_COOL_THRESHOLD)) {
charger_err("%s: temp:%d is abort, do not setfastcharge mode\n",
__func__, val);
mode = false;
}
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_CHIP_OK,
&val);
charger_err("%s: get fg chip_ok:%d\n", __func__, val);
if (rc < 0) {
charger_err("%s: Couldn't get fg chip_ok:%d\n", __func__, rc);
return rc;
}
if (mode && !val) {
charger_err("%s: chip_ok is :%d, do not setfastcharge mode\n",
__func__, val);
mode = false;
}
rc = xm_battmngr_write_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_FASTCHARGE_MODE, mode);
if (rc < 0) {
charger_err("%s: Couldn't write fastcharge mode:%d\n", __func__,
rc);
return rc;
}
xm_charger_config_iterm(charger, mode);
xm_charger_thermal(charger);
vote(charger->fcc_votable, FFC_MODE_VOTER, !mode,
charger->dt.non_ffc_cc);
vote(charger->fv_votable, FFC_MODE_VOTER, !mode,
charger->dt.non_ffc_cv);
if (charger->smartBatVal) {
effective_fv = get_effective_result(charger->fv_votable);
charger_err("%s: Now fastcharge mode effective FV: %d\n",
__func__, effective_fv);
if (mode) {
vote(charger->fv_votable, SMART_BATTERY_FV, false, 0);
vote(charger->smart_batt_votable, SMART_BATTERY_FV,
true, charger->smartBatVal);
} else {
vote(charger->smart_batt_votable, SMART_BATTERY_FV,
true, 0);
vote(charger->fv_votable, SMART_BATTERY_FV, true,
charger->dt.non_ffc_cv -
charger->smartBatVal * 1000);
}
} else {
if (mode)
vote(charger->smart_batt_votable, SMART_BATTERY_FV,
true, 0);
else
vote(charger->fv_votable, SMART_BATTERY_FV, false, 0);
charger_err("%s: Cancel fastcharge mode effective FV: %d\n",
__func__,
get_effective_result(charger->fv_votable));
}
charger_err("%s: fastcharge mode:%d\n", __func__, mode);
return 0;
}

View File

@ -0,0 +1,198 @@
#include <linux/battmngr/xm_charger_core.h>
int xm_charger_parse_dt_therm(struct xm_charger *charger)
{
struct device_node *node = charger->dev->of_node;
int rc = 0, byte_len;
if (of_find_property(node, "xm,thermal-mitigation-icl", &byte_len)) {
charger->dt.thermal_mitigation_icl =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation_icl == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(
node, "xm,thermal-mitigation-icl",
charger->dt.thermal_mitigation_icl,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
if (of_find_property(node, "xm,thermal-mitigation", &byte_len)) {
charger->dt.thermal_mitigation =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(node, "xm,thermal-mitigation",
charger->dt.thermal_mitigation,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
if (of_find_property(node, "xm,thermal-mitigation-dcp", &byte_len)) {
charger->dt.thermal_mitigation_dcp =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation_dcp == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(
node, "xm,thermal-mitigation-dcp",
charger->dt.thermal_mitigation_dcp,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
if (of_find_property(node, "xm,thermal-mitigation-qc2", &byte_len)) {
charger->dt.thermal_mitigation_qc2 =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation_qc2 == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(
node, "xm,thermal-mitigation-qc2",
charger->dt.thermal_mitigation_qc2,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
if (of_find_property(node, "xm,thermal-mitigation-pd", &byte_len)) {
charger->dt.thermal_mitigation_pd =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation_pd == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(
node, "xm,thermal-mitigation-pd",
charger->dt.thermal_mitigation_pd,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
if (of_find_property(node, "xm,thermal-mitigation-pd-cp", &byte_len)) {
charger->dt.thermal_mitigation_pd_cp =
devm_kzalloc(charger->dev, byte_len, GFP_KERNEL);
if (charger->dt.thermal_mitigation_pd_cp == NULL)
return -ENOMEM;
charger->thermal_levels = byte_len / sizeof(u32);
rc = of_property_read_u32_array(
node, "xm,thermal-mitigation-pd-cp",
charger->dt.thermal_mitigation_pd_cp,
charger->thermal_levels);
if (rc < 0) {
charger_err("%s: Couldn't read threm limits rc = %d\n",
__func__, rc);
return rc;
}
}
return rc;
}
int xm_charger_thermal(struct xm_charger *charger)
{
int thermal_fcc_ua = 0;
int rc = 0;
if ((charger->system_temp_level >= MAX_TEMP_LEVEL) ||
(charger->system_temp_level < 0))
return -EINVAL;
switch (charger->real_type) {
case POWER_SUPPLY_TYPE_USB_PD:
if (charger->pd_active == QTI_POWER_SUPPLY_PD_PPS_ACTIVE) {
thermal_fcc_ua = charger->dt.thermal_mitigation_pd_cp
[charger->system_temp_level];
} else {
thermal_fcc_ua = charger->dt.thermal_mitigation_pd
[charger->system_temp_level];
g_battmngr_noti->misc_msg.thermal_icl =
charger->dt.thermal_mitigation_icl
[charger->system_temp_level];
}
break;
default:
if (charger->bc12_type == POWER_SUPPLY_TYPE_USB_HVDCP) {
g_battmngr_noti->misc_msg.thermal_icl =
charger->dt.thermal_mitigation_qc2
[charger->system_temp_level];
} else {
thermal_fcc_ua = charger->dt.thermal_mitigation
[charger->system_temp_level];
g_battmngr_noti->misc_msg.thermal_icl =
charger->dt.thermal_mitigation_dcp
[charger->system_temp_level];
}
break;
}
if (g_battmngr_noti->misc_msg.disable_thermal && charger->pd_active) {
if (charger->pd_active == QTI_POWER_SUPPLY_PD_PPS_ACTIVE) {
thermal_fcc_ua =
charger->dt.thermal_mitigation_pd_cp[0];
} else {
thermal_fcc_ua = charger->dt.thermal_mitigation_pd[0];
}
}
charger_err(
"%s: chg->system_temp_level: %d, thermal_icl:%d, real_type:%d, thermal_fcc_ua is %d, pd_active = %d, bc12_type = %d\n",
__func__, charger->system_temp_level,
g_battmngr_noti->misc_msg.thermal_icl, charger->real_type,
thermal_fcc_ua, charger->pd_active, charger->bc12_type);
if (charger->system_temp_level == 0) {
rc = vote(charger->fcc_votable, THERMAL_DAEMON_VOTER, false, 0);
if (rc < 0)
charger_err(
"%s: Couldn't disable USB thermal FCC vote rc = %d\n",
__func__, rc);
} else {
if (thermal_fcc_ua > 0) {
rc = vote(charger->fcc_votable, THERMAL_DAEMON_VOTER,
true, thermal_fcc_ua);
if (rc < 0)
charger_err(
"%s: Couldn't enable USB thermal FCC vote rc = %d\n",
__func__, rc);
}
}
rerun_election(charger->fcc_votable);
return rc;
}
EXPORT_SYMBOL(xm_charger_thermal);

View File

@ -0,0 +1,167 @@
#include <linux/battmngr/xm_charger_core.h>
static int xm_charger_fcc_vote_callback(struct votable *votable, void *data,
int value, const char *client)
{
int ret = 0;
charger_err("%s: vote FCC = %d, sw_disable = %d\n", __func__, value,
g_battmngr_noti->mainchg_msg.sw_disable);
if (g_battmngr_noti->mainchg_msg.sw_disable)
value = MAIN_MIN_FCC;
ret = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_CURRENT, value / 1000);
if (ret) {
charger_err("%s: failed to set FCC\n", __func__);
return ret;
}
return ret;
}
static int xm_charger_fv_vote_callback(struct votable *votable, void *data,
int value, const char *client)
{
int ret = 0;
charger_err("%s: vote FV = %d\n", __func__, value);
ret = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_VOLTAGE_TERM,
value / 1000);
if (ret) {
charger_err("%s: failed to set FV\n", __func__);
return ret;
}
return ret;
}
static int xm_charger_icl_vote_callback(struct votable *votable, void *data,
int value, const char *client)
{
int ret = 0;
charger_err("%s: vote ICL = %d\n", __func__, value);
ret = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_INPUT_CURRENT_SETTLED,
value / 1000);
if (ret) {
charger_err("%s: failed to set ICL\n", __func__);
return ret;
}
return ret;
}
static int xm_charger_awake_vote_callback(struct votable *votable, void *data,
int awake, const char *client)
{
struct xm_charger *charger = data;
if (awake)
pm_stay_awake(charger->dev);
else
pm_relax(charger->dev);
return 0;
}
static int xm_charger_input_suspend_vote_callback(struct votable *votable,
void *data, int value,
const char *client)
{
int ret = 0;
charger_err("%s: vote INPUT_SUSPEND = %d\n", __func__, value);
ret = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_ENABLED, !value);
if (ret) {
charger_err("%s: failed to set INPUT_SUSPEND\n", __func__);
return ret;
}
return ret;
}
static int xm_charger_smart_batt_vote_callback(struct votable *votable,
void *data, int value,
const char *client)
{
int ret = 0;
charger_err("%s: vote SMART_BATT = %d\n", __func__, value);
return ret;
}
int xm_charger_create_votable(struct xm_charger *charger)
{
int rc = 0;
charger_err("%s: Start\n", __func__);
charger->fcc_votable = create_votable(
"FCC", VOTE_MIN, xm_charger_fcc_vote_callback, charger);
if (IS_ERR(charger->fcc_votable)) {
rc = PTR_ERR(charger->fcc_votable);
charger->fcc_votable = NULL;
destroy_votable(charger->fcc_votable);
charger_err("%s: failed to create voter FCC\n", __func__);
}
charger->fv_votable = create_votable(
"FV", VOTE_MIN, xm_charger_fv_vote_callback, charger);
if (IS_ERR(charger->fv_votable)) {
rc = PTR_ERR(charger->fv_votable);
charger->fv_votable = NULL;
destroy_votable(charger->fv_votable);
charger_err("%s: failed to create voter FV\n", __func__);
}
charger->usb_icl_votable = create_votable(
"ICL", VOTE_MIN, xm_charger_icl_vote_callback, charger);
if (IS_ERR(charger->usb_icl_votable)) {
rc = PTR_ERR(charger->usb_icl_votable);
charger->usb_icl_votable = NULL;
destroy_votable(charger->usb_icl_votable);
charger_err("%s: failed to create voter ICL\n", __func__);
}
charger->awake_votable = create_votable(
"AWAKE", VOTE_SET_ANY, xm_charger_awake_vote_callback, charger);
if (IS_ERR(charger->awake_votable)) {
rc = PTR_ERR(charger->awake_votable);
charger->awake_votable = NULL;
destroy_votable(charger->awake_votable);
charger_err("%s: failed to create voter AWAKE\n", __func__);
}
charger->input_suspend_votable =
create_votable("INPUT_SUSPEND", VOTE_SET_ANY,
xm_charger_input_suspend_vote_callback, charger);
if (IS_ERR(charger->input_suspend_votable)) {
rc = PTR_ERR(charger->input_suspend_votable);
charger->input_suspend_votable = NULL;
destroy_votable(charger->input_suspend_votable);
charger_err("%s: failed to create voter INPUT_SUSPEND\n",
__func__);
}
charger->smart_batt_votable =
create_votable("SMART_BATT", VOTE_MIN,
xm_charger_smart_batt_vote_callback, charger);
if (IS_ERR(charger->smart_batt_votable)) {
rc = PTR_ERR(charger->smart_batt_votable);
charger->smart_batt_votable = NULL;
destroy_votable(charger->smart_batt_votable);
charger_err("%s: failed to create voter SMART_BATT\n",
__func__);
}
charger_err("%s: End\n", __func__);
return rc;
}

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += xm_pd_mngr.o

View File

@ -0,0 +1,349 @@
#ifndef XM_PD_MNGR_H_
#define XM_PD_MNGR_H_
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/usb/usbpd.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/usb/tcpc/tcpm.h>
#include <linux/battmngr/xm_battmngr_iio.h>
#include <linux/battmngr/battmngr_voter.h>
#include <linux/battmngr/battmngr_notifier.h>
enum pm_state {
PD_PM_STATE_ENTRY,
PD_PM_STATE_FC2_ENTRY,
PD_PM_STATE_FC2_ENTRY_1,
PD_PM_STATE_FC2_ENTRY_2,
PD_PM_STATE_FC2_ENTRY_3,
PD_PM_STATE_FC2_TUNE,
PD_PM_STATE_FC2_EXIT,
};
#define PROBE_CNT_MAX 50
#define PCA_PPS_CMD_RETRY_COUNT 2
#define PD_SRC_PDO_TYPE_FIXED 0
#define PD_SRC_PDO_TYPE_BATTERY 1
#define PD_SRC_PDO_TYPE_VARIABLE 2
#define PD_SRC_PDO_TYPE_AUGMENTED 3
#define BATT_MAX_CHG_VOLT 4480
#define BATT_FAST_CHG_CURR 6000
#define BUS_OVP_THRESHOLD 12000
#define BUS_OVP_ALARM_THRESHOLD 9500
#define BUS_VOLT_INIT_UP 800
#define MIN_ADATPER_VOLTAGE_11V 11000
#define CAPACITY_HIGH_THR 90
#define BAT_VOLT_LOOP_LMT BATT_MAX_CHG_VOLT
#define BAT_CURR_LOOP_LMT BATT_FAST_CHG_CURR
#define BUS_VOLT_LOOP_LMT BUS_OVP_THRESHOLD
#define PM_WORK_RUN_NORMAL_INTERVAL 500
#define PM_WORK_RUN_QUICK_INTERVAL 200
#define PM_WORK_RUN_CRITICAL_INTERVAL 100
enum {
PM_ALGO_RET_OK,
PM_ALGO_RET_THERM_FAULT,
PM_ALGO_RET_OTHER_FAULT,
PM_ALGO_RET_CHG_DISABLED,
PM_ALGO_RET_TAPER_DONE,
PM_ALGO_RET_UNSUPPORT_PPSTA,
PM_ALGO_RET_NIGHT_CHARGING,
};
enum adapter_cap_type {
XM_PD_FIXED,
XM_PD_APDO,
XM_PD_APDO_START,
XM_PD_APDO_END,
};
#define BAT_OVP_FAULT_SHIFT 0
#define BAT_OCP_FAULT_SHIFT 1
#define BUS_OVP_FAULT_SHIFT 2
#define BUS_OCP_FAULT_SHIFT 3
#define BAT_THERM_FAULT_SHIFT 4
#define BUS_THERM_FAULT_SHIFT 5
#define DIE_THERM_FAULT_SHIFT 6
#define BAT_OVP_FAULT_MASK (1 << BAT_OVP_FAULT_SHIFT)
#define BAT_OCP_FAULT_MASK (1 << BAT_OCP_FAULT_SHIFT)
#define BUS_OVP_FAULT_MASK (1 << BUS_OVP_FAULT_SHIFT)
#define BUS_OCP_FAULT_MASK (1 << BUS_OCP_FAULT_SHIFT)
#define BAT_THERM_FAULT_MASK (1 << BAT_THERM_FAULT_SHIFT)
#define BUS_THERM_FAULT_MASK (1 << BUS_THERM_FAULT_SHIFT)
#define DIE_THERM_FAULT_MASK (1 << DIE_THERM_FAULT_SHIFT)
#define BAT_OVP_ALARM_SHIFT 0
#define BAT_OCP_ALARM_SHIFT 1
#define BUS_OVP_ALARM_SHIFT 2
#define BUS_OCP_ALARM_SHIFT 3
#define BAT_THERM_ALARM_SHIFT 4
#define BUS_THERM_ALARM_SHIFT 5
#define DIE_THERM_ALARM_SHIFT 6
#define BAT_UCP_ALARM_SHIFT 7
#define BAT_OVP_ALARM_MASK (1 << BAT_OVP_ALARM_SHIFT)
#define BAT_OCP_ALARM_MASK (1 << BAT_OCP_ALARM_SHIFT)
#define BUS_OVP_ALARM_MASK (1 << BUS_OVP_ALARM_SHIFT)
#define BUS_OCP_ALARM_MASK (1 << BUS_OCP_ALARM_SHIFT)
#define BAT_THERM_ALARM_MASK (1 << BAT_THERM_ALARM_SHIFT)
#define BUS_THERM_ALARM_MASK (1 << BUS_THERM_ALARM_SHIFT)
#define DIE_THERM_ALARM_MASK (1 << DIE_THERM_ALARM_SHIFT)
#define BAT_UCP_ALARM_MASK (1 << BAT_UCP_ALARM_SHIFT)
#define VBAT_REG_STATUS_SHIFT 0
#define IBAT_REG_STATUS_SHIFT 1
#define VBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
#define IBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
/* voters for usbpd */
#define BQ_TAPER_FCC_VOTER "BQ_TAPER_FCC_VOTER"
#define BQ_TAPER_CELL_HGIH_FCC_VOTER "BQ_TAPER_CELL_HGIH_FCC_VOTER"
#define NON_PPS_PD_FCC_VOTER "NON_PPS_PD_FCC_VOTER"
#define MAIN_CHG_ICL_VOTER "MAIN_CHG_ICL_VOTER"
#define FFC_DISABLE_CP_FV_VOTER "FFC_DISABLE_CP_FV_VOTER"
#define USER_VOTER "USER_VOTER"
#define SMART_BATTERY_FV "SMART_BATTERY_FV"
#define MAIN_CHG_ICL (3000 * 1000)
#define FFC_DISABLE_CP_FV (4560 * 1000)
#define FFC_DISABLE_CP_FV_D (4576 * 1000)
/* defined min fcc threshold for start bq direct charging */
#define START_DRIECT_CHARGE_FCC_MIN_THR 2000
/* product related */
#define PPS_VOL_MAX 11000
#define PPS_VOL_HYS 1000
#define STEP_MV 20
#define TAPER_VOL_HYS 80
#define TAPER_WITH_IBUS_HYS 60
#define TAPER_IBUS_THR 450
#define MAX_THERMAL_LEVEL 13
#define MAX_THERMAL_LEVEL_FOR_DUAL_BQ 9
#define THERMAL_LEVEL_11 11
#define THERMAL_LEVEL_12 12
#define THERMAL_11_VBUS_UP 300
#define FCC_MAX_MA_FOR_MASTER_BQ 6000
#define IBUS_THRESHOLD_MA_FOR_DUAL_BQ 2100
#define IBUS_THRESHOLD_MA_FOR_DUAL_BQ_LN8000 2500
#define IBUS_THR_MA_HYS_FOR_DUAL_BQ 200
#define IBUS_THR_TO_CLOSE_SLAVE_COUNT_MAX 40
/* jeita related */
#define JEITA_WARM_THR 480
#define JEITA_COOL_NOT_ALLOW_CP_THR 100
/*
* add hysteresis for warm threshold to avoid flash
* charge and normal charge switch frequently at
* the warm threshold
*/
#define JEITA_HYSTERESIS 20
#define BQ_TAPER_HYS_MV 10
#define NON_FFC_BQ_TAPER_HYS_MV 50
#define BQ_TAPER_DECREASE_STEP_MA 200
#define CELL_VOLTAGE_HIGH_COUNT_MAX 2
#define CELL_VOLTAGE_MAX_COUNT_MAX 1
#define HIGH_VOL_THR_MV 4380
#define CRITICAL_HIGH_VOL_THR_MV 4480
#define TAPER_DONE_FFC_MA_LN8000 2500
#define TAPER_DONE_NORMAL_MA 2200
#define VBAT_HIGH_FOR_FC_HYS_MV 100
#define CAPACITY_TOO_HIGH_THR 95
#define CRITICAL_LOW_IBUS_THR 300
#define MAX_UNSUPPORT_PPS_CURRENT_MA 5500
#define NON_PPS_PD_FCC_LIMIT (3000 * 1000)
#define POWER_SUPPLY_PD_ACTIVE QTI_POWER_SUPPLY_PD_ACTIVE
#define POWER_SUPPLY_PD_PPS_ACTIVE QTI_POWER_SUPPLY_PD_PPS_ACTIVE
/* APDO */
#define APDO_MAX_WATT 68000000
#define APDO_MAX_MV 20000
enum {
POWER_SUPPLY_PPS_INACTIVE = 0,
POWER_SUPPLY_PPS_NON_VERIFIED,
POWER_SUPPLY_PPS_VERIFIED,
};
struct sw_device {
bool charge_enabled;
bool night_charging;
};
struct cp_device {
bool charge_enabled;
bool batt_connecter_present;
bool batt_pres;
bool vbus_pres;
/* alarm/fault status */
bool bat_ovp_fault;
bool bat_ocp_fault;
bool bus_ovp_fault;
bool bus_ocp_fault;
bool bat_ovp_alarm;
bool bat_ocp_alarm;
bool bus_ovp_alarm;
bool bus_ocp_alarm;
bool bat_ucp_alarm;
bool bat_therm_alarm;
bool bus_therm_alarm;
bool die_therm_alarm;
bool bat_therm_fault;
bool bus_therm_fault;
bool die_therm_fault;
bool vbat_reg;
bool ibat_reg;
int vbat_volt;
int fg_vbat_mv;
int vbus_volt;
int ibat_curr;
int ibus_curr;
int bat_temp;
int bus_temp;
int die_temp;
};
#define PM_STATE_LOG_MAX 32
struct usbpd_pm {
struct device *dev;
struct tcpc_device *tcpc;
enum pm_state state;
struct cp_device cp;
struct cp_device cp_sec;
struct sw_device sw;
int pd_active;
bool pps_supported;
bool fc2_exit_flag;
int request_voltage;
int request_current;
int apdo_max_volt;
int apdo_max_curr;
int apdo_maxwatt;
struct delayed_work pm_work;
struct delayed_work fc2_exit_work;
struct notifier_block nb;
struct notifier_block battmngr_nb;
struct notifier_block tcp_nb;
struct work_struct usb_psy_change_work;
spinlock_t psy_change_lock;
struct votable *fcc_votable;
struct votable *fv_votable;
struct votable *usb_icl_votable;
struct votable *input_suspend_votable;
struct votable *smart_batt_votable;
struct power_supply *batt_psy;
struct power_supply *usb_psy;
/* dtsi properties */
int bat_volt_max;
int bat_curr_max;
int bus_volt_max;
int bus_curr_max;
int non_ffc_bat_volt_max;
int bus_curr_compensate;
int therm_level_threshold;
int pd_power_max;
bool cp_sec_enable;
/* jeita or thermal related */
bool jeita_triggered;
bool is_temp_out_fc2_range;
int battery_warm_th;
/* bq taper related */
int over_cell_vol_high_count;
int over_cell_vol_max_count;
int step_charge_high_vol_curr_max;
int cell_vol_high_threshold_mv;
int cell_vol_max_threshold_mv;
/* dual bq contrl related */
bool no_need_en_slave_bq;
int slave_bq_disabled_check_count;
int master_ibus_below_critical_low_count;
int chip_ok_count;
int pd_auth_val;
/*unsupport pps ta check count*/
int unsupport_pps_ta_check_count;
/*pca_pps_tcp_notifier_call*/
bool is_pps_en_unlock;
int hrst_cnt;
};
struct pdpm_config {
int bat_volt_lp_lmt; /*bat volt loop limit*/
int bat_curr_lp_lmt;
int bus_volt_lp_lmt;
int bus_curr_lp_lmt;
int bus_curr_compensate;
int fc2_taper_current;
int fc2_steps;
int min_adapter_volt_required;
int min_adapter_curr_required;
int min_vbat_for_cp;
bool cp_sec_enable;
bool fc2_disable_sw; /* disable switching charger during flash charge*/
};
#endif /* XM_PD_MNGR_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += xm_battmngr.o
xm_battmngr-objs := xm_battmngr_init.o xm_battmngr_class.o

View File

@ -0,0 +1,42 @@
#ifndef __XM_BATTMNGR_INIT_H
#define __XM_BATTMNGR_INIT_H
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/battmngr/battmngr_voter.h>
#include <linux/battmngr/battmngr_notifier.h>
#include <linux/battmngr/xm_battmngr_iio.h>
#include <linux/battmngr/xm_charger_core.h>
#include <linux/battmngr/xm_battery_core.h>
struct xm_battmngr {
struct device *dev;
struct class battmngr_class;
struct xm_battmngr_iio battmngr_iio;
struct xm_battery battery;
struct xm_charger charger;
struct notifier_block battmngr_nb;
struct battmngr_notify battmngr_noti;
};
extern int get_verify_digest(char *buf);
extern int set_verify_digest(u8 *rand_num);
int battmngr_class_init(struct xm_battmngr *battmngr);
void battmngr_class_exit(struct xm_battmngr *battmngr);
#endif /* __XM_BATTMNGR_INIT_H */

View File

@ -0,0 +1,897 @@
#include "inc/xm_battmngr_init.h"
/* Standard usb_type definitions similar to power_supply_sysfs.c */
static const char *const power_supply_usb_type_text[] = {
"Unknown", "USB", "USB_DCP", "USB_CDP", "USB_ACA",
"USB_C", "USB_PD", "PD_DRP", "PD_PPS", "BrickID"
};
/* Custom usb_type definitions */
static const char *const qc_power_supply_usb_type_text[] = { "HVDCP", "HVDCP_3",
"HVDCP_3P5",
"USB_FLOAT" };
/* Custom typec mode definitions */
static const char *const typec_mode_type_text[] = {
"UNATTACHED", "ATTACHED_SNK", "ATTACHED_SRC",
"ATTACHED_AUDIO", "ATTACHED_DEBUG", "ATTACHED_DBGACC_SNK",
"ATTACHED_CUSTOM_SRC", "ATTACHED_NORP_SRC"
};
static const char *get_typec_mode_name(int typec_mode)
{
int i;
pr_err("%s: typec_mode=%d\n", __func__, typec_mode);
if (typec_mode < 0 || typec_mode > 7) {
return "Unkown";
}
for (i = 0; i < ARRAY_SIZE(typec_mode_type_text); i++) {
if (i == typec_mode)
return typec_mode_type_text[i];
}
return "Unknown";
}
static const char *get_usb_type_name(int usb_type)
{
int i;
pr_err("%s: usb_type=%d\n", __func__, usb_type);
if (usb_type >= POWER_SUPPLY_TYPE_USB_HVDCP &&
usb_type <= POWER_SUPPLY_TYPE_USB_FLOAT) {
for (i = 0; i < ARRAY_SIZE(qc_power_supply_usb_type_text);
i++) {
if (i == (usb_type - POWER_SUPPLY_TYPE_USB_HVDCP))
return qc_power_supply_usb_type_text[i];
}
return "Unknown";
}
for (i = 0; i < ARRAY_SIZE(power_supply_usb_type_text); i++) {
if (i == usb_type)
return power_supply_usb_type_text[i];
}
return "Unknown";
}
static ssize_t real_type_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int real_type = 0;
if (g_xm_charger->pd_active) {
if (g_xm_charger->pd_active == QTI_POWER_SUPPLY_PD_ACTIVE)
real_type = POWER_SUPPLY_USB_TYPE_PD;
else if (g_xm_charger->pd_active ==
QTI_POWER_SUPPLY_PD_PPS_ACTIVE)
real_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
} else {
real_type = g_xm_charger->bc12_type;
}
return scnprintf(buf, PAGE_SIZE, "%s\n", get_usb_type_name(real_type));
}
static CLASS_ATTR_RO(real_type);
static ssize_t pd_verifed_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
val = g_xm_charger->pd_verified;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t pd_verifed_store(struct class *c, struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
g_xm_charger->pd_verified = val;
g_battmngr_noti->pd_msg.msg_type = BATTMNGR_MSG_PD_VERIFED;
g_battmngr_noti->pd_msg.pd_verified = g_xm_charger->pd_verified;
battmngr_notifier_call_chain(BATTMNGR_EVENT_PD, g_battmngr_noti);
pr_err("pd_verified = %d\n", g_xm_charger->pd_verified);
return count;
}
static CLASS_ATTR_RW(pd_verifed);
static ssize_t input_current_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val, temp;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_BUS_CURRENT, &temp);
val = temp;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_SLAVE,
CHARGE_PUMP_LN_BUS_CURRENT, &temp);
val += temp;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(input_current);
static ssize_t cp_ibus_slave_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_SLAVE,
CHARGE_PUMP_LN_BUS_CURRENT, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_ibus_slave);
static ssize_t cp_ibus_master_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_BUS_CURRENT, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_ibus_master);
static ssize_t cp_ibus_delta_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val, val1, val2;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_BUS_CURRENT, &val1);
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_SLAVE,
CHARGE_PUMP_LN_BUS_CURRENT, &val2);
val = val1 - val2;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_ibus_delta);
static ssize_t cp_present_slave_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_SLAVE,
CHARGE_PUMP_LN_PRESENT, &val);
pr_err("cp_present_slave_show = %d\n", val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_present_slave);
static ssize_t cp_present_master_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_PRESENT, &val);
pr_err("cp_present_master_show = %d\n", val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_present_master);
static ssize_t cp_vbus_voltage_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_BUS_VOLTAGE, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_vbus_voltage);
static ssize_t cp_vbat_voltage_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, CP_MASTER,
CHARGE_PUMP_LN_BATTERY_VOLTAGE, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cp_vbat_voltage);
static ssize_t vbus_voltage_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_BUS_VOLTAGE, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(vbus_voltage);
static ssize_t vbat_voltage_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_VBAT_VOLTAGE, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(vbat_voltage);
static ssize_t cell_voltage_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_VOLTAGE_NOW,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(cell_voltage);
static ssize_t soc_decimal_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
val = xm_get_soc_decimal();
return scnprintf(buf, PAGE_SIZE, "%u", val);
}
static CLASS_ATTR_RO(soc_decimal);
static ssize_t soc_decimal_rate_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
val = xm_get_soc_decimal_rate();
return scnprintf(buf, PAGE_SIZE, "%u", val);
}
static CLASS_ATTR_RO(soc_decimal_rate);
static ssize_t verify_digest_show(struct class *c, struct class_attribute *attr,
char *buf)
{
get_verify_digest(buf);
return scnprintf(buf, PAGE_SIZE, "%s", buf);
}
static ssize_t verify_digest_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
char kbuf[70] = { 0 };
pr_err("verify_digest_store = %s\n", buf);
memset(kbuf, 0, sizeof(kbuf));
strncpy(kbuf, buf, count - 1);
set_verify_digest(kbuf);
return count;
}
static CLASS_ATTR_RW(verify_digest);
static ssize_t chip_ok_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_CHIP_OK,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(chip_ok);
static ssize_t apdo_max_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
int apdo_max_volt, apdo_max_curr;
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_APDO_VOLT_MAX,
&apdo_max_volt);
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_APDO_CURR_MAX,
&apdo_max_curr);
val = (apdo_max_volt * apdo_max_curr) / 1000000;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(apdo_max);
static ssize_t fastchg_mode_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_FASTCHARGE_MODE, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(fastchg_mode);
static ssize_t soh_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_SOH, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(soh);
static ssize_t connector_temp_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val = 0;
if (!g_battmngr_iio->typec_conn_therm) {
xm_get_iio_channel(g_battmngr_iio, "typec_conn_therm",
&g_battmngr_iio->typec_conn_therm);
}
if (g_battmngr_iio->typec_conn_therm) {
iio_read_channel_processed(g_battmngr_iio->typec_conn_therm,
&val);
val /= 100;
if (g_xm_charger->chg_feature->fake_conn_temp != 0)
val = g_xm_charger->chg_feature->fake_conn_temp;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
} else {
pr_err("Failed to get IIO channel typec_conn_therm\n");
return -ENODATA;
}
}
static ssize_t connector_temp_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
g_xm_charger->chg_feature->fake_conn_temp = val;
pr_err("fake_conn_temp = %d\n",
g_xm_charger->chg_feature->fake_conn_temp);
return count;
}
static CLASS_ATTR_RW(connector_temp);
static ssize_t authentic_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val = 0;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_BATTERY_AUTH,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t authentic_store(struct class *c, struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
xm_battmngr_write_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_BATTERY_AUTH, val);
return count;
}
static CLASS_ATTR_RW(authentic);
static ssize_t cc_orientation_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY,
PD_TYPEC_CC_ORIENTATION, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t cc_orientation_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
xm_battmngr_write_iio_prop(g_battmngr_iio, PD_PHY,
PD_TYPEC_CC_ORIENTATION, val);
return count;
}
static CLASS_ATTR_RW(cc_orientation);
static ssize_t typec_mode_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_TYPEC_MODE, &val);
return scnprintf(buf, PAGE_SIZE, "%s\n", get_typec_mode_name(val));
}
static CLASS_ATTR_RO(typec_mode);
static ssize_t resistance_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val = 0;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(resistance);
static ssize_t resistance_id_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_RESISTANCE_ID, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(resistance_id);
static ssize_t input_suspend_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
val = (get_client_vote(g_xm_charger->input_suspend_votable,
USER_VOTER) == 1);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t input_suspend_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
int rc;
static int last_input_suspend;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
pr_err("input_suspend = %d\n", val);
g_xm_charger->input_suspend = val;
if (g_xm_charger->input_suspend != last_input_suspend) {
cancel_delayed_work(
&(g_xm_charger->chg_feature->xm_prop_change_work));
g_xm_charger->chg_feature->update_cont = 0;
schedule_delayed_work(
&(g_xm_charger->chg_feature->xm_prop_change_work), 0);
last_input_suspend = g_xm_charger->input_suspend;
}
rc = vote(g_xm_charger->input_suspend_votable, USER_VOTER, (bool)val,
0);
return count;
}
static CLASS_ATTR_RW(input_suspend);
static ssize_t usb_real_type_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int real_type = 0;
if (g_xm_charger->pd_active) {
if (g_xm_charger->pd_active == QTI_POWER_SUPPLY_PD_ACTIVE)
real_type = POWER_SUPPLY_USB_TYPE_PD;
else if (g_xm_charger->pd_active ==
QTI_POWER_SUPPLY_PD_PPS_ACTIVE)
real_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
} else {
real_type = g_xm_charger->bc12_type;
}
return scnprintf(buf, PAGE_SIZE, "%s\n", get_usb_type_name(real_type));
}
static CLASS_ATTR_RO(usb_real_type);
static ssize_t adapter_id_show(struct class *c, struct class_attribute *attr,
char *buf)
{
uint32_t adapter_id;
xm_battmngr_read_iio_prop(g_battmngr_iio, PD_PHY, PD_TYPEC_ADAPTER_ID,
&adapter_id);
pr_err("%s: adapter_id is %08x\n", __func__, adapter_id);
return snprintf(buf, PAGE_SIZE, "%08x\n", adapter_id);
}
static CLASS_ATTR_RO(adapter_id);
static ssize_t quick_charge_type_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
val = xm_get_quick_charge_type();
return scnprintf(buf, PAGE_SIZE, "%u", val);
}
static CLASS_ATTR_RO(quick_charge_type);
static ssize_t power_max_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
val = xm_get_adapter_power_max();
return scnprintf(buf, PAGE_SIZE, "%u", val);
}
static CLASS_ATTR_RO(power_max);
static ssize_t shutdown_delay_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_SHUTDOWN_DELAY, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(shutdown_delay);
static ssize_t fg_temp_max_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TEMP_MAX,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(fg_temp_max);
static ssize_t fg_time_ot_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_TIME_OT,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(fg_time_ot);
static ssize_t fg_rsoc_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_RSOC, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(fg_rsoc);
static ssize_t fg_reg_rsoc_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG, BATT_FG_REG_ROC,
&val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static CLASS_ATTR_RO(fg_reg_rsoc);
static ssize_t smart_batt_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
val = g_xm_charger->smartBatVal;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t smart_batt_store(struct class *c, struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
int rc, mode;
bool ffc_enable = 0;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
rc = xm_battmngr_read_iio_prop(g_battmngr_iio, BATT_FG,
BATT_FG_FASTCHARGE_MODE, &mode);
ffc_enable = mode;
pr_err("Before process fv_profile:%d, non fv_profile:%d, ffc is: %d, Down value: %d.\n",
g_xm_charger->dt.chg_voltage_max, g_xm_charger->dt.non_ffc_cv,
ffc_enable, val);
if (g_xm_charger->dt.chg_voltage_max < 0 ||
g_xm_charger->dt.non_ffc_cv < 0)
return -EINVAL;
if (val != 0) {
if (!ffc_enable) {
rc = vote(g_xm_charger->smart_batt_votable,
SMART_BATTERY_FV, true, 0);
rc = vote(g_xm_charger->fv_votable, SMART_BATTERY_FV,
true,
g_xm_charger->dt.non_ffc_cv - val * 1000);
} else {
rc = vote(g_xm_charger->fv_votable, SMART_BATTERY_FV,
false, 0);
rc = vote(g_xm_charger->smart_batt_votable,
SMART_BATTERY_FV, true, val);
}
} else {
if (!ffc_enable)
rc = vote(g_xm_charger->fv_votable, SMART_BATTERY_FV,
false, 0);
else
rc = vote(g_xm_charger->smart_batt_votable,
SMART_BATTERY_FV, true, 0);
}
g_xm_charger->smartBatVal = val;
return count;
}
static CLASS_ATTR_RW(smart_batt);
static ssize_t night_charging_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
val = g_xm_charger->chg_feature->night_chg_flag;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t night_charging_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
g_xm_charger->chg_feature->night_chg_flag = val;
return count;
}
static CLASS_ATTR_RW(night_charging);
static ssize_t battery_input_suspend_show(struct class *c,
struct class_attribute *attr,
char *buf)
{
int val;
val = (get_client_vote(g_xm_charger->fcc_votable, USER_VOTER) == 0);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t battery_input_suspend_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val, rc;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
rc = vote(g_xm_charger->fcc_votable, USER_VOTER, (bool)val, 0);
if (rc < 0) {
pr_err("%s: Couldn't vote to %s fcc rc=%d\n", __func__,
(bool)val ? "suspend" : "resume", rc);
}
g_xm_charger->chg_feature->battery_input_suspend = val;
return count;
}
static CLASS_ATTR_RW(battery_input_suspend);
static ssize_t set_icl_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_INPUT_CURRENT_SETTLED, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t set_icl_store(struct class *c, struct class_attribute *attr,
const char *buf, size_t count)
{
int val, rc;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
rc = xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_INPUT_CURRENT_SETTLED,
val);
if (rc < 0) {
pr_err("%s: Couldn't set icl\n", __func__);
}
return count;
}
static CLASS_ATTR_RW(set_icl);
static ssize_t vindpm_volt_show(struct class *c, struct class_attribute *attr,
char *buf)
{
int val;
xm_battmngr_read_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_INPUT_VOLTAGE_SETTLED, &val);
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t vindpm_volt_store(struct class *c, struct class_attribute *attr,
const char *buf, size_t count)
{
int val;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
xm_battmngr_write_iio_prop(g_battmngr_iio, MAIN_CHG,
MAIN_CHARGER_INPUT_VOLTAGE_SETTLED, val);
g_battmngr_noti->misc_msg.vindpm_temp = val;
pr_err("%s: g_battmngr_noti->misc_msg.vindpm:%d\n", __func__,
g_battmngr_noti->misc_msg.vindpm_temp);
return count;
}
static CLASS_ATTR_RW(vindpm_volt);
static ssize_t disable_thermal_show(struct class *c,
struct class_attribute *attr, char *buf)
{
int val;
val = g_battmngr_noti->misc_msg.disable_thermal;
return scnprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t disable_thermal_store(struct class *c,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val, rc;
if (kstrtoint(buf, 10, &val))
return -EINVAL;
g_battmngr_noti->misc_msg.disable_thermal = val;
pr_err("%s: disable_thermal:%d\n", __func__,
g_battmngr_noti->misc_msg.disable_thermal);
rc = xm_charger_thermal(g_xm_charger);
if (rc < 0)
pr_err("%s: Couldn't xm_charger_thermal\n", __func__);
return count;
}
static CLASS_ATTR_RW(disable_thermal);
static struct attribute *battmngr_class_attrs[] = {
&class_attr_real_type.attr,
&class_attr_pd_verifed.attr,
&class_attr_input_current.attr,
&class_attr_cp_ibus_slave.attr,
&class_attr_cp_ibus_master.attr,
&class_attr_cp_present_slave.attr,
&class_attr_cp_present_master.attr,
&class_attr_cp_vbus_voltage.attr,
&class_attr_cp_vbat_voltage.attr,
&class_attr_vbus_voltage.attr,
&class_attr_vbat_voltage.attr,
&class_attr_cell_voltage.attr,
&class_attr_soc_decimal.attr,
&class_attr_soc_decimal_rate.attr,
&class_attr_verify_digest.attr,
&class_attr_chip_ok.attr,
&class_attr_apdo_max.attr,
&class_attr_fastchg_mode.attr,
&class_attr_soh.attr,
&class_attr_connector_temp.attr,
&class_attr_authentic.attr,
&class_attr_cc_orientation.attr,
&class_attr_typec_mode.attr,
&class_attr_resistance.attr,
&class_attr_resistance_id.attr,
&class_attr_input_suspend.attr,
&class_attr_usb_real_type.attr,
&class_attr_adapter_id.attr,
&class_attr_quick_charge_type.attr,
&class_attr_power_max.attr,
&class_attr_shutdown_delay.attr,
&class_attr_fg_temp_max.attr,
&class_attr_fg_time_ot.attr,
&class_attr_cp_ibus_delta.attr,
&class_attr_fg_rsoc.attr,
&class_attr_smart_batt.attr,
&class_attr_night_charging.attr,
&class_attr_battery_input_suspend.attr,
&class_attr_set_icl.attr,
&class_attr_vindpm_volt.attr,
&class_attr_disable_thermal.attr,
&class_attr_fg_reg_rsoc.attr,
NULL,
};
ATTRIBUTE_GROUPS(battmngr_class);
int battmngr_class_init(struct xm_battmngr *battmngr)
{
int rc = 0;
if (!battmngr)
return -EINVAL;
battmngr->battmngr_class.name = "qcom-battery";
battmngr->battmngr_class.class_groups = battmngr_class_groups;
rc = class_register(&battmngr->battmngr_class);
if (rc < 0) {
pr_err("Failed to create battmngr_class rc=%d\n", rc);
}
return rc;
}
void battmngr_class_exit(struct xm_battmngr *battmngr)
{
class_destroy(&battmngr->battmngr_class);
}

View File

@ -0,0 +1,140 @@
#include "inc/xm_battmngr_init.h"
struct xm_battmngr *g_battmngr;
EXPORT_SYMBOL(g_battmngr);
static int battmngr_notifier_call(struct notifier_block *nb,
unsigned long event, void *data)
{
struct battmngr_notify *noti_data = data;
pr_err("%s: event %d\n", __func__, event);
switch (event) {
case BATTMNGR_EVENT_FG:
battery_process_event_fg(noti_data);
break;
case BATTMNGR_EVENT_CP:
charger_process_event_cp(noti_data);
break;
case BATTMNGR_EVENT_MAINCHG:
charger_process_event_mainchg(noti_data);
break;
case BATTMNGR_EVENT_PD:
charger_process_event_pd(noti_data);
break;
default:
break;
}
return NOTIFY_OK;
}
static int xm_battmngr_probe(struct platform_device *pdev)
{
struct xm_battmngr *battmngr;
int rc;
pr_err("%s: Start\n", __func__);
battmngr = devm_kzalloc(&pdev->dev, sizeof(*battmngr), GFP_KERNEL);
if (!battmngr)
return -ENOMEM;
battmngr->dev = &pdev->dev;
battmngr->battmngr_iio.dev = &pdev->dev;
battmngr->battery.dev = &pdev->dev;
battmngr->charger.dev = &pdev->dev;
platform_set_drvdata(pdev, battmngr);
g_battmngr_iio = &battmngr->battmngr_iio;
g_xm_battery = &battmngr->battery;
g_xm_charger = &battmngr->charger;
g_battmngr_noti = &battmngr->battmngr_noti;
mutex_init(&g_battmngr_noti->notify_lock);
rc = xm_battmngr_iio_init(&battmngr->battmngr_iio);
if (rc < 0) {
pr_err("xm_battmngr_iio_init failed rc=%d\n", rc);
return rc;
}
rc = xm_battery_init(&battmngr->battery);
if (rc < 0) {
pr_err("xm_battery_init failed rc=%d\n", rc);
return rc;
}
rc = xm_charger_init(&battmngr->charger);
if (rc < 0) {
pr_err("xm_charger_init failed rc=%d\n", rc);
return rc;
}
rc = battmngr_class_init(battmngr);
if (rc < 0) {
pr_err("battmngr_class_init failed rc=%d\n", rc);
return rc;
}
battmngr->battmngr_nb.notifier_call = battmngr_notifier_call;
rc = battmngr_notifier_register(&battmngr->battmngr_nb);
if (rc < 0) {
pr_err("%s: register battmngr notifier fail\n", __func__);
return -EINVAL;
}
g_battmngr = battmngr;
pr_err("%s: End\n", __func__);
return 0;
}
static int xm_battmngr_remove(struct platform_device *pdev)
{
struct xm_battmngr *battmngr = platform_get_drvdata(pdev);
xm_charger_deinit();
battmngr_class_exit(battmngr);
devm_kfree(&pdev->dev, battmngr);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void xm_battmngr_shutdown(struct platform_device *pdev)
{
return;
}
static const struct of_device_id match_table[] = {
{ .compatible = "xiaomi,battmngr" },
{},
};
static struct platform_driver xm_battmngr_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "xm_battmngr",
.of_match_table = match_table,
},
.probe = xm_battmngr_probe,
.remove = xm_battmngr_remove,
.shutdown = xm_battmngr_shutdown,
};
static int __init xm_battmngr_init(void)
{
return platform_driver_register(&xm_battmngr_driver);
}
postcore_initcall(xm_battmngr_init);
static void __exit xm_battmngr_exit(void)
{
platform_driver_unregister(&xm_battmngr_driver);
}
module_exit(xm_battmngr_exit);
MODULE_DESCRIPTION("Xiaomi Battery Management");
MODULE_AUTHOR("getian@xiaomi.com");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += battmngr_common.o
battmngr_common-objs := battmngr_voter.o battmngr_notifier.o

View File

@ -0,0 +1,31 @@
#include <linux/battmngr/battmngr_notifier.h>
static BLOCKING_NOTIFIER_HEAD(battmngr_notifier);
struct battmngr_notify *g_battmngr_noti;
EXPORT_SYMBOL(g_battmngr_noti);
int battmngr_notifier_register(struct notifier_block *n)
{
return blocking_notifier_chain_register(&battmngr_notifier, n);
}
EXPORT_SYMBOL(battmngr_notifier_register);
int battmngr_notifier_unregister(struct notifier_block *n)
{
return blocking_notifier_chain_unregister(&battmngr_notifier, n);
}
EXPORT_SYMBOL(battmngr_notifier_unregister);
int battmngr_notifier_call_chain(unsigned long event,
struct battmngr_notify *data)
{
return blocking_notifier_call_chain(&battmngr_notifier, event,
(void *)data);
}
EXPORT_SYMBOL(battmngr_notifier_call_chain);
MODULE_DESCRIPTION("Battery Manager notifier");
MODULE_AUTHOR("getian@xiaomi.com");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,820 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2017, 2019-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/battmngr/battmngr_voter.h>
#define NUM_MAX_CLIENTS 32
#define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT"
static DEFINE_SPINLOCK(votable_list_slock);
static LIST_HEAD(votable_list);
static struct dentry *debug_root;
struct client_vote {
bool enabled;
int value;
};
struct votable {
const char *name;
const char *override_client;
struct list_head list;
struct client_vote votes[NUM_MAX_CLIENTS];
int num_clients;
int type;
int effective_client_id;
int effective_result;
int override_result;
struct mutex vote_lock;
void *data;
int (*callback)(struct votable *votable, void *data,
int effective_result, const char *effective_client);
char *client_strs[NUM_MAX_CLIENTS];
bool voted_on;
struct dentry *root;
struct dentry *status_ent;
u32 force_val;
bool force_active;
struct dentry *force_active_ent;
};
/**
* vote_set_any()
* @votable: votable object
* @client_id: client number of the latest voter
* @eff_res: sets 0 or 1 based on the voting
* @eff_id: Always returns the client_id argument
*
* Note that for SET_ANY voter, the value is always same as enabled. There is
* no idea of a voter abstaining from the election. Hence there is never a
* situation when the effective_id will be invalid, during election.
*
* Context:
* Must be called with the votable->lock held
*/
static void vote_set_any(struct votable *votable, int client_id, int *eff_res,
int *eff_id)
{
int i;
*eff_res = 0;
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
*eff_res |= votable->votes[i].enabled;
*eff_id = client_id;
}
/**
* vote_min() -
* @votable: votable object
* @client_id: client number of the latest voter
* @eff_res: sets this to the min. of all the values amongst enabled voters.
* If there is no enabled client, this is set to INT_MAX
* @eff_id: sets this to the client id that has the min value amongst all
* the enabled clients. If there is no enabled client, sets this
* to -EINVAL
*
* Context:
* Must be called with the votable->lock held
*/
static void vote_min(struct votable *votable, int client_id, int *eff_res,
int *eff_id)
{
int i;
*eff_res = INT_MAX;
*eff_id = -EINVAL;
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
if (votable->votes[i].enabled &&
*eff_res > votable->votes[i].value) {
*eff_res = votable->votes[i].value;
*eff_id = i;
}
}
if (*eff_id == -EINVAL)
*eff_res = -EINVAL;
}
/**
* vote_max() -
* @votable: votable object
* @client_id: client number of the latest voter
* @eff_res: sets this to the max. of all the values amongst enabled voters.
* If there is no enabled client, this is set to -EINVAL
* @eff_id: sets this to the client id that has the max value amongst all
* the enabled clients. If there is no enabled client, sets this to
* -EINVAL
*
* Context:
* Must be called with the votable->lock held
*/
static void vote_max(struct votable *votable, int client_id, int *eff_res,
int *eff_id)
{
int i;
*eff_res = INT_MIN;
*eff_id = -EINVAL;
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
if (votable->votes[i].enabled &&
*eff_res < votable->votes[i].value) {
*eff_res = votable->votes[i].value;
*eff_id = i;
}
}
if (*eff_id == -EINVAL)
*eff_res = -EINVAL;
}
static int get_client_id(struct votable *votable, const char *client_str)
{
int i;
for (i = 0; i < votable->num_clients; i++) {
if (votable->client_strs[i] &&
(strcmp(votable->client_strs[i], client_str) == 0))
return i;
}
/* new client */
for (i = 0; i < votable->num_clients; i++) {
if (!votable->client_strs[i]) {
votable->client_strs[i] =
kstrdup(client_str, GFP_KERNEL);
if (!votable->client_strs[i])
return -ENOMEM;
return i;
}
}
return -EINVAL;
}
static char *get_client_str(struct votable *votable, int client_id)
{
if (!votable || (client_id == -EINVAL))
return NULL;
return votable->client_strs[client_id];
}
void lock_votable(struct votable *votable)
{
mutex_lock(&votable->vote_lock);
}
EXPORT_SYMBOL(lock_votable);
void unlock_votable(struct votable *votable)
{
mutex_unlock(&votable->vote_lock);
}
EXPORT_SYMBOL(unlock_votable);
/**
* is_override_vote_enabled() -
* is_override_vote_enabled_locked() -
* The unlocked and locked variants of getting whether override
vote is enabled.
* @votable: the votable object
*
* Returns:
* True if the client's vote is enabled; false otherwise.
*/
bool is_override_vote_enabled_locked(struct votable *votable)
{
if (!votable)
return false;
return votable->override_result != -EINVAL;
}
EXPORT_SYMBOL(is_override_vote_enabled_locked);
bool is_override_vote_enabled(struct votable *votable)
{
bool enable;
if (!votable)
return false;
lock_votable(votable);
enable = is_override_vote_enabled_locked(votable);
unlock_votable(votable);
return enable;
}
EXPORT_SYMBOL(is_override_vote_enabled);
/**
* is_client_vote_enabled() -
* is_client_vote_enabled_locked() -
* The unlocked and locked variants of getting whether a client's
vote is enabled.
* @votable: the votable object
* @client_str: client of interest
*
* Returns:
* True if the client's vote is enabled; false otherwise.
*/
bool is_client_vote_enabled_locked(struct votable *votable,
const char *client_str)
{
int client_id;
if (!votable || !client_str)
return false;
client_id = get_client_id(votable, client_str);
if (client_id < 0)
return false;
return votable->votes[client_id].enabled;
}
EXPORT_SYMBOL(is_client_vote_enabled_locked);
bool is_client_vote_enabled(struct votable *votable, const char *client_str)
{
bool enabled;
if (!votable || !client_str)
return false;
lock_votable(votable);
enabled = is_client_vote_enabled_locked(votable, client_str);
unlock_votable(votable);
return enabled;
}
EXPORT_SYMBOL(is_client_vote_enabled);
/**
* get_client_vote() -
* get_client_vote_locked() -
* The unlocked and locked variants of getting a client's voted
* value.
* @votable: the votable object
* @client_str: client of interest
*
* Returns:
* The value the client voted for. -EINVAL is returned if the client
* is not enabled or the client is not found.
*/
int get_client_vote_locked(struct votable *votable, const char *client_str)
{
int client_id;
if (!votable || !client_str)
return -EINVAL;
client_id = get_client_id(votable, client_str);
if (client_id < 0)
return -EINVAL;
if ((votable->type != VOTE_SET_ANY) &&
!votable->votes[client_id].enabled)
return -EINVAL;
return votable->votes[client_id].value;
}
EXPORT_SYMBOL(get_client_vote_locked);
int get_client_vote(struct votable *votable, const char *client_str)
{
int value;
if (!votable || !client_str)
return -EINVAL;
lock_votable(votable);
value = get_client_vote_locked(votable, client_str);
unlock_votable(votable);
return value;
}
EXPORT_SYMBOL(get_client_vote);
/**
* get_effective_result() -
* get_effective_result_locked() -
* The unlocked and locked variants of getting the effective value
* amongst all the enabled voters.
*
* @votable: the votable object
*
* Returns:
* The effective result.
* For MIN and MAX votable, returns -EINVAL when the votable
* object has been created but no clients have casted their votes or
* the last enabled client disables its vote.
* For SET_ANY votable it returns 0 when no clients have casted their votes
* because for SET_ANY there is no concept of abstaining from election. The
* votes for all the clients of SET_ANY votable is defaulted to false.
*/
int get_effective_result_locked(struct votable *votable)
{
if (!votable)
return -EINVAL;
if (votable->force_active)
return votable->force_val;
if (votable->override_result != -EINVAL)
return votable->override_result;
return votable->effective_result;
}
EXPORT_SYMBOL(get_effective_result_locked);
int get_effective_result(struct votable *votable)
{
int value;
if (!votable)
return -EINVAL;
lock_votable(votable);
value = get_effective_result_locked(votable);
unlock_votable(votable);
return value;
}
EXPORT_SYMBOL(get_effective_result);
/**
* get_effective_client() -
* get_effective_client_locked() -
* The unlocked and locked variants of getting the effective client
* amongst all the enabled voters.
*
* @votable: the votable object
*
* Returns:
* The effective client.
* For MIN and MAX votable, returns NULL when the votable
* object has been created but no clients have casted their votes or
* the last enabled client disables its vote.
* For SET_ANY votable it returns NULL too when no clients have casted
* their votes. But for SET_ANY since there is no concept of abstaining
* from election, the only client that casts a vote or the client that
* caused the result to change is returned.
*/
const char *get_effective_client_locked(struct votable *votable)
{
if (!votable)
return NULL;
if (votable->force_active)
return DEBUG_FORCE_CLIENT;
if (votable->override_result != -EINVAL)
return votable->override_client;
return get_client_str(votable, votable->effective_client_id);
}
EXPORT_SYMBOL(get_effective_client_locked);
const char *get_effective_client(struct votable *votable)
{
const char *client_str;
if (!votable)
return NULL;
lock_votable(votable);
client_str = get_effective_client_locked(votable);
unlock_votable(votable);
return client_str;
}
EXPORT_SYMBOL(get_effective_client);
/**
* vote() -
*
* @votable: the votable object
* @client_str: the voting client
* @enabled: This provides a means for the client to exclude himself from
* election. This clients val (the next argument) will be
* considered only when he has enabled his participation.
* Note that this takes a differnt meaning for SET_ANY type, as
* there is no concept of abstaining from participation.
* Enabled is treated as the boolean value the client is voting.
* @val: The vote value. This is ignored for SET_ANY votable types.
* For MIN, MAX votable types this value is used as the
* clients vote value when the enabled is true, this value is
* ignored if enabled is false.
*
* The callback is called only when there is a change in the election results or
* if it is the first time someone is voting.
*
* Returns:
* The return from the callback when present and needs to be called
* or zero.
*/
int vote(struct votable *votable, const char *client_str, bool enabled, int val)
{
int effective_id = -EINVAL;
int effective_result;
int client_id;
int rc = 0;
bool similar_vote = false;
if (!votable || !client_str)
return -EINVAL;
lock_votable(votable);
client_id = get_client_id(votable, client_str);
if (client_id < 0) {
rc = client_id;
goto out;
}
/*
* for SET_ANY the val is to be ignored, set it
* to enabled so that the election still works based on
* value regardless of the type
*/
if (votable->type == VOTE_SET_ANY)
val = enabled;
if ((votable->votes[client_id].enabled == enabled) &&
(votable->votes[client_id].value == val)) {
pr_err("%s: %s,%d same vote %s of val=%d\n", votable->name,
client_str, client_id, enabled ? "on" : "off", val);
similar_vote = true;
}
votable->votes[client_id].enabled = enabled;
votable->votes[client_id].value = val;
if (similar_vote && votable->voted_on) {
pr_err("%s: %s,%d Ignoring similar vote %s of val=%d\n",
votable->name, client_str, client_id,
enabled ? "on" : "off", val);
goto out;
}
pr_err("%s: %s,%d voting %s of val=%d\n", votable->name, client_str,
client_id, enabled ? "on" : "off", val);
switch (votable->type) {
case VOTE_MIN:
vote_min(votable, client_id, &effective_result, &effective_id);
break;
case VOTE_MAX:
vote_max(votable, client_id, &effective_result, &effective_id);
break;
case VOTE_SET_ANY:
vote_set_any(votable, client_id, &effective_result,
&effective_id);
break;
default:
return -EINVAL;
}
/*
* Note that the callback is called with a NULL string and -EINVAL
* result when there are no enabled votes
*/
if (!votable->voted_on ||
(effective_result != votable->effective_result)) {
votable->effective_client_id = effective_id;
votable->effective_result = effective_result;
pr_err("%s: effective vote is now %d voted by %s,%d\n",
votable->name, effective_result,
get_client_str(votable, effective_id), effective_id);
if (votable->callback && !votable->force_active &&
(votable->override_result == -EINVAL))
rc = votable->callback(
votable, votable->data, effective_result,
get_client_str(votable, effective_id));
}
votable->voted_on = true;
out:
unlock_votable(votable);
return rc;
}
EXPORT_SYMBOL(vote);
/**
* vote_override() -
*
* @votable: The votable object
* @override_client: The voting client that will override other client's
* votes, that are already present. When force_active
* and override votes are set on a votable, force_active's
* client will have the higher priority and it's vote will
* be the effective one.
* @enabled: This provides a means for the override client to exclude
* itself from election. This client's vote
* (the next argument) will be considered only when
* it has enabled its participation. When this is
* set true, this will force a value on a MIN/MAX votable
* irrespective of its current value.
* @val: The vote value. This will be effective only if enabled
* is set true.
* Returns:
* The result of vote. 0 is returned if the vote
* is successfully set by the overriding client, when enabled is set.
*/
int vote_override(struct votable *votable, const char *override_client,
bool enabled, int val)
{
int rc = 0;
if (!votable || !override_client)
return -EINVAL;
lock_votable(votable);
if (votable->force_active) {
votable->override_result = enabled ? val : -EINVAL;
goto out;
}
if (enabled) {
rc = votable->callback(votable, votable->data, val,
override_client);
if (!rc) {
votable->override_client = override_client;
votable->override_result = val;
}
} else {
rc = votable->callback(
votable, votable->data, votable->effective_result,
get_client_str(votable, votable->effective_client_id));
votable->override_result = -EINVAL;
}
out:
unlock_votable(votable);
return rc;
}
EXPORT_SYMBOL(vote_override);
int rerun_election(struct votable *votable)
{
int rc = 0;
int effective_result;
if (!votable)
return -EINVAL;
lock_votable(votable);
effective_result = get_effective_result_locked(votable);
if (votable->callback)
rc = votable->callback(
votable, votable->data, effective_result,
get_client_str(votable, votable->effective_client_id));
unlock_votable(votable);
return rc;
}
EXPORT_SYMBOL(rerun_election);
struct votable *find_votable(const char *name)
{
unsigned long flags;
struct votable *v;
bool found = false;
if (!name)
return NULL;
spin_lock_irqsave(&votable_list_slock, flags);
if (list_empty(&votable_list))
goto out;
list_for_each_entry (v, &votable_list, list) {
if (strcmp(v->name, name) == 0) {
found = true;
break;
}
}
out:
spin_unlock_irqrestore(&votable_list_slock, flags);
if (found)
return v;
else
return NULL;
}
EXPORT_SYMBOL(find_votable);
static int force_active_get(void *data, u64 *val)
{
struct votable *votable = data;
*val = votable->force_active;
return 0;
}
static int force_active_set(void *data, u64 val)
{
struct votable *votable = data;
int rc = 0;
int effective_result;
const char *client;
lock_votable(votable);
votable->force_active = !!val;
if (!votable->callback)
goto out;
if (votable->force_active) {
rc = votable->callback(votable, votable->data,
votable->force_val, DEBUG_FORCE_CLIENT);
} else {
if (votable->override_result != -EINVAL) {
effective_result = votable->override_result;
client = votable->override_client;
} else {
effective_result = votable->effective_result;
client = get_client_str(votable,
votable->effective_client_id);
}
rc = votable->callback(votable, votable->data, effective_result,
client);
}
out:
unlock_votable(votable);
return rc;
}
DEFINE_DEBUGFS_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set,
"%lld\n");
static int show_votable_clients(struct seq_file *m, void *data)
{
struct votable *votable = m->private;
int i;
char *type_str = "Unkonwn";
const char *effective_client_str;
lock_votable(votable);
for (i = 0; i < votable->num_clients; i++) {
if (votable->client_strs[i]) {
seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n",
votable->name, votable->client_strs[i],
votable->votes[i].enabled,
votable->votes[i].value);
}
}
switch (votable->type) {
case VOTE_MIN:
type_str = "Min";
break;
case VOTE_MAX:
type_str = "Max";
break;
case VOTE_SET_ANY:
type_str = "Set_any";
break;
}
effective_client_str = get_effective_client_locked(votable);
seq_printf(m, "%s: effective=%s type=%s v=%d\n", votable->name,
effective_client_str ? effective_client_str : "none",
type_str, get_effective_result_locked(votable));
unlock_votable(votable);
return 0;
}
static int votable_status_open(struct inode *inode, struct file *file)
{
struct votable *votable = inode->i_private;
return single_open(file, show_votable_clients, votable);
}
static const struct file_operations votable_status_ops = {
.owner = THIS_MODULE,
.open = votable_status_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
struct votable *create_votable(const char *name, int votable_type,
int (*callback)(struct votable *votable,
void *data, int effective_result,
const char *effective_client),
void *data)
{
struct votable *votable;
unsigned long flags;
if (!name)
return ERR_PTR(-EINVAL);
votable = find_votable(name);
if (votable)
return ERR_PTR(-EEXIST);
if (debug_root == NULL) {
debug_root = debugfs_create_dir("battmngr-votable", NULL);
if (!debug_root) {
pr_err("Couldn't create debug dir\n");
return ERR_PTR(-ENOMEM);
}
}
if (votable_type >= NUM_VOTABLE_TYPES) {
pr_err("Invalid votable_type specified for voter\n");
return ERR_PTR(-EINVAL);
}
votable = kzalloc(sizeof(struct votable), GFP_KERNEL);
if (!votable)
return ERR_PTR(-ENOMEM);
votable->name = kstrdup(name, GFP_KERNEL);
if (!votable->name) {
kfree(votable);
return ERR_PTR(-ENOMEM);
}
votable->num_clients = NUM_MAX_CLIENTS;
votable->callback = callback;
votable->type = votable_type;
votable->data = data;
votable->override_result = -EINVAL;
mutex_init(&votable->vote_lock);
/*
* Because effective_result and client states are invalid
* before the first vote, initialize them to -EINVAL
*/
votable->effective_result = -EINVAL;
if (votable->type == VOTE_SET_ANY)
votable->effective_result = 0;
votable->effective_client_id = -EINVAL;
spin_lock_irqsave(&votable_list_slock, flags);
list_add(&votable->list, &votable_list);
spin_unlock_irqrestore(&votable_list_slock, flags);
votable->root = debugfs_create_dir(name, debug_root);
if (!votable->root) {
pr_err("Couldn't create debug dir %s\n", name);
kfree(votable->name);
kfree(votable);
return ERR_PTR(-ENOMEM);
}
votable->status_ent =
debugfs_create_file("status", S_IFREG | 0444, votable->root,
votable, &votable_status_ops);
if (!votable->status_ent) {
pr_err("Couldn't create status dbg file for %s\n", name);
debugfs_remove_recursive(votable->root);
kfree(votable->name);
kfree(votable);
return ERR_PTR(-EEXIST);
}
debugfs_create_u32("force_val", S_IFREG | 0644, votable->root,
&(votable->force_val));
votable->force_active_ent =
debugfs_create_file("force_active", S_IFREG | 0444,
votable->root, votable, &votable_force_ops);
if (!votable->force_active_ent) {
pr_err("Couldn't create force_active dbg file for %s\n", name);
debugfs_remove_recursive(votable->root);
kfree(votable->name);
kfree(votable);
return ERR_PTR(-EEXIST);
}
return votable;
}
EXPORT_SYMBOL(create_votable);
void destroy_votable(struct votable *votable)
{
unsigned long flags;
int i;
if (!votable)
return;
spin_lock_irqsave(&votable_list_slock, flags);
list_del(&votable->list);
spin_unlock_irqrestore(&votable_list_slock, flags);
debugfs_remove_recursive(votable->root);
for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
kfree(votable->client_strs[i]);
kfree(votable->name);
kfree(votable);
}
EXPORT_SYMBOL(destroy_votable);
MODULE_DESCRIPTION("Battery Manager Voter");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("getian@xiaomi.com");

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += external/
obj-$(CONFIG_MTK_POWER_SUPPLY) += mediatek/
obj-$(CONFIG_QCOM_POWER_SUPPLY) += qualcomm/

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_POWER_SUPPLY) += cp/
obj-$(CONFIG_XM_POWER_SUPPLY) += fg/
obj-$(CONFIG_XM_POWER_SUPPLY) += mainchg/
obj-$(CONFIG_XM_POWER_SUPPLY) += pd/

View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CHARGER_PUMP_SC8551A) += sc8551a_drv.o
sc8551a_drv-objs := sc8551a.o sc8551a_iio.o
obj-$(CONFIG_CHARGER_PUMP_LN8000) += ln8000_drv.o
ln8000_drv-objs := ln8000.o ln8000_iio.o

View File

@ -0,0 +1,262 @@
#ifndef __LN8000_H__
#define __LN8000_H__
/*
* ln8000-charger.c - Charger driver for LIONSEMI LN8000
*
* Copyright (C) 2021 Lion Semiconductor Inc.
*
* 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/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/err.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#include <linux/debugfs.h>
#include <linux/bitops.h>
#include <linux/math64.h>
#include <linux/version.h>
#include <linux/battmngr/battmngr_notifier.h>
static const char *ln8000_dev_name[] = {
"ln8000-standalone",
"ln8000-master",
"ln8000-slave",
};
#define LN8000_ROLE_STDALONE 0
#define LN8000_ROLE_SLAVE 1
#define LN8000_ROLE_MASTER 2
enum {
LN8000_STDALONE,
LN8000_SLAVE,
LN8000_MASTER,
};
#define PROBE_CNT_MAX 50
static int ln8000_mode_data[] = {
[LN8000_STDALONE] = LN8000_ROLE_STDALONE,
[LN8000_MASTER] = LN8000_ROLE_SLAVE,
[LN8000_SLAVE] = LN8000_ROLE_MASTER,
};
#define ln_err(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_ERR "ln8000-standalone: %s: " fmt, \
__func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_ERR "ln8000-master: %s: " fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_ERR "ln8000-slave: %s: " fmt, __func__, \
##__VA_ARGS__); \
} while (0);
#define ln_info(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_INFO "ln8000-standalone: %s: " fmt, \
__func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_INFO "ln8000-master: %s: " fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_INFO "ln8000-slave: %s: " fmt, __func__, \
##__VA_ARGS__); \
} while (0);
#define ln_dbg(fmt, ...) \
do { \
if (info->dev_role == LN_ROLE_STANDALONE) \
printk(KERN_DEBUG "ln8000-standalone: %s: " fmt, \
__func__, ##__VA_ARGS__); \
else if (info->dev_role == LN_ROLE_MASTER) \
printk(KERN_DEBUG "ln8000-master: %s: " fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_DEBUG "ln8000-slave: %s: " fmt, __func__, \
##__VA_ARGS__); \
} while (0);
#define LN8000_REG_PRINT(reg_addr, val) \
do { \
ln_info(" --> [%-20s] 0x%02X : 0x%02X\n", #reg_addr, \
LN8000_REG_##reg_addr, (val) & 0xFF); \
} while (0);
#define LN8000_PARSE_PROP(ret, pdata, field, prop, default_prop) \
do { \
if (ret) { \
ln_info("%s = %d (set to default)\n", #field, \
default_prop); \
pdata->field = default_prop; \
} else { \
ln_info("%s = %d\n", #field, prop); \
pdata->field = prop; \
} \
} while (0);
#define LN8000_BIT_CHECK(val, idx, desc) \
if (val & (1 << idx)) \
ln_info("-> %s\n", desc)
#define LN8000_USE_GPIO(pdata) \
((pdata != NULL) && (!IS_ERR_OR_NULL(pdata->irq_gpio)))
#define LN8000_STATUS(val, mask) ((val & mask) ? true : false)
enum {
VBUS_ERROR_NONE,
VBUS_ERROR_LOW,
VBUS_ERROR_HIGHT,
};
/**
* driver instance structure definition
*/
struct ln8000_platform_data {
struct gpio_desc
*irq_gpio; /* GPIO pin for (generic/power-on) interrupt */
/* feature configuration */
unsigned int bat_ovp_th; /* battery ovp threshold (mV) */
unsigned int bat_ovp_alarm_th; /* battery ovp alarm threshold (mV) */
unsigned int bus_ovp_th; /* IIN ovp threshold (mV) */
unsigned int bus_ovp_alarm_th; /* IIN ovp alarm threshold (mV) */
unsigned int bus_ocp_th; /* IIN ocp threshold (mA) */
unsigned int bus_ocp_alarm_th; /* IIN ocp alarm threshold */
unsigned int
ntc_alarm_cfg; /* input/battery NTC voltage threshold code: 0~1023 */
/* protection enable/disable */
bool vbat_ovp_disable; /* disable battery voltage OVP */
bool vbat_reg_disable; /* disable battery voltage (float) regulation */
bool iin_ocp_disable; /* disable input current OCP */
bool iin_reg_disable; /* disable input current regulation */
bool tbus_mon_disable; /* disable BUS temperature monitor (prot/alarm) */
bool tbat_mon_disable; /* disable BAT temperature monitor (prot/alarm) */
bool tdie_prot_disable; /* disable die temperature protection */
bool tdie_reg_disable; /* disable die temperature regulation */
bool revcurr_prot_disable; /* disable reverse current protection */
};
struct ln8000_info {
struct device *dev;
struct i2c_client *client;
struct ln8000_platform_data *pdata;
struct mutex irq_complete;
struct mutex data_lock;
struct mutex i2c_lock;
struct mutex irq_lock;
unsigned int op_mode; /* target operation mode */
unsigned int pwr_status; /* current device status */
unsigned int dev_role; /* device role */
/* system/device status */
bool vbat_regulated; /* vbat loop is active (+ OV alarm) */
bool iin_regulated; /* iin loop is active (+ OC alarm) */
bool tdie_fault; /* die temperature fault */
bool tbus_tbat_fault; /* BUS/BAT temperature fault */
bool tdie_alarm; /* die temperature alarm (regulated) */
bool tbus_tbat_alarm; /* BUS/BAT temperature alarm */
bool wdt_fault; /* watchdog timer expiration */
bool vbat_ov; /* vbat OV fault */
bool vac_ov; /* vac OV fault */
bool vbus_ov; /* vbus OV fault */
bool iin_oc; /* iin OC fault */
bool vac_unplug; /* vac unplugged */ //vbus_present
bool iin_rc; /* iin reverse current detected */
bool volt_qual; /* all voltages are qualified */
bool usb_present; /* usb plugged (present) */
bool batt_present;
bool chg_en; /* charging enavbled */
bool rcp_en; /* reverse current protection enabled */
int vbat_ovp_alarm_th; /* vbat ovp alarm threshold */
int vin_ovp_alarm_th; /* vin ovp alarm threshold */
int iin_ocp_alarm_th; /* iin ocp alarm threshold */
/* ADC readings */
int tbat_uV; /* BAT temperature (NTC, uV) */
int tbus_uV; /* BUS temperature (NTC, uV) */
int tdie_dC; /* die temperature (deci-Celsius) */
int vbat_uV; /* battery voltage (uV) */
int vbus_uV; /* input voltage (uV) */
int iin_uA; /* input current (uV) */
/* for restore reg_init_val */
u8 regulation_ctrl;
u8 adc_ctrl;
u8 v_float_ctrl;
u8 charge_ctrl;
/* debugfs */
struct dentry *debug_root;
u32 debug_address;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
struct delayed_work dump_regs_work;
};
int ln8000_set_sw_freq(struct ln8000_info *info, u8 fsw_cfg);
int ln8000_set_vac_ovp(struct ln8000_info *info, unsigned int ovp_th);
int ln8000_set_vbat_float(struct ln8000_info *info, unsigned int cfg);
int ln8000_set_iin_limit(struct ln8000_info *info, unsigned int cfg);
int ln8000_set_ntc_alarm(struct ln8000_info *info, unsigned int cfg);
int ln8000_enable_vbat_ovp(struct ln8000_info *info, bool enable);
int ln8000_enable_vbat_regulation(struct ln8000_info *info, bool enable);
int ln8000_enable_vbat_loop_int(struct ln8000_info *info, bool enable);
int ln8000_enable_iin_ocp(struct ln8000_info *info, bool enable);
int ln8000_enable_iin_regulation(struct ln8000_info *info, bool enable);
int ln8000_enable_iin_loop_int(struct ln8000_info *info, bool enable);
int ln8000_enable_tdie_prot(struct ln8000_info *info, bool enable);
int ln8000_enable_tdie_regulation(struct ln8000_info *info, bool enable);
int ln8000_enable_tbus_monitor(struct ln8000_info *info, bool enable);
int ln8000_enable_tbat_monitor(struct ln8000_info *info, bool enable);
int ln8000_enable_wdt(struct ln8000_info *info, bool enable);
int ln8000_enable_rcp(struct ln8000_info *info, bool enable);
int ln8000_enable_auto_recovery(struct ln8000_info *info, bool enable);
int ln8000_enable_rcp_auto_recovery(struct ln8000_info *info, bool enable);
int ln8000_set_adc_mode(struct ln8000_info *info, unsigned int cfg);
int ln8000_set_adc_hib_delay(struct ln8000_info *info, unsigned int cfg);
int ln8000_check_status(struct ln8000_info *info);
void ln8000_soft_reset(struct ln8000_info *info);
void ln8000_update_opmode(struct ln8000_info *info);
int ln8000_change_opmode(struct ln8000_info *info, unsigned int target_mode);
int ln8000_get_adc_data(struct ln8000_info *info, unsigned int ch, int *result);
int psy_chg_get_charging_enabled(struct ln8000_info *info);
int psy_chg_get_ti_alarm_status(struct ln8000_info *info);
int psy_chg_get_it_bus_error_status(struct ln8000_info *info);
int psy_chg_get_ti_fault_status(struct ln8000_info *info);
int psy_chg_set_charging_enable(struct ln8000_info *info, int val);
int psy_chg_set_present(struct ln8000_info *info, int val);
#endif /* __LN8000_H__ */

View File

@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#ifndef __LN8000_IIO_H
#define __LN8000_IIO_H
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/qti_power_supply.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
#define LN8000_RCP_PATCH 1
struct ln8000_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define LN8000_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define LN8000_CHAN_VOLT(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED))
#define LN8000_CHAN_CUR(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED))
#define LN8000_CHAN_TEMP(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED))
#define LN8000_CHAN_POW(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_POWER, BIT(IIO_CHAN_INFO_PROCESSED))
#define LN8000_CHAN_ENERGY(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_ENERGY, BIT(IIO_CHAN_INFO_PROCESSED))
#define LN8000_CHAN_COUNT(_name, _num) \
LN8000_IIO_CHAN(_name, _num, IIO_COUNT, BIT(IIO_CHAN_INFO_PROCESSED))
static const struct ln8000_iio_channels ln8000_iio_psy_channels[] = {
LN8000_CHAN_ENERGY("ln_present", PSY_IIO_SC_PRESENT) LN8000_CHAN_ENERGY(
"ln_charging_enabled",
PSY_IIO_SC_CHARGING_ENABLED) LN8000_CHAN_ENERGY("ln_status",
PSY_IIO_SC_STATUS)
LN8000_CHAN_ENERGY("ln_battery_present", PSY_IIO_SC_BATTERY_PRESENT) LN8000_CHAN_ENERGY(
"ln_vbus_present",
PSY_IIO_SC_VBUS_PRESENT) LN8000_CHAN_VOLT("ln_battery_voltage",
PSY_IIO_SC_BATTERY_VOLTAGE)
LN8000_CHAN_CUR("ln_battery_current", PSY_IIO_SC_BATTERY_CURRENT) LN8000_CHAN_TEMP(
"ln_battery_temperature",
PSY_IIO_SC_BATTERY_TEMPERATURE) LN8000_CHAN_VOLT("ln_bus_voltage",
PSY_IIO_SC_BUS_VOLTAGE)
LN8000_CHAN_CUR("ln_bus_current", PSY_IIO_SC_BUS_CURRENT) LN8000_CHAN_TEMP(
"ln_bus_temperature",
PSY_IIO_SC_BUS_TEMPERATURE)
LN8000_CHAN_TEMP(
"ln_die_temperature",
PSY_IIO_SC_DIE_TEMPERATURE)
LN8000_CHAN_ENERGY(
"ln_alarm_status",
PSY_IIO_SC_ALARM_STATUS)
LN8000_CHAN_ENERGY(
"ln_fault_status",
PSY_IIO_SC_FAULT_STATUS)
LN8000_CHAN_ENERGY(
"ln_vbus_error_status",
PSY_IIO_SC_VBUS_ERROR_STATUS)
LN8000_CHAN_ENERGY(
"ln_reg_status",
PSY_IIO_SC_REG_STATUS)
};
static const struct ln8000_iio_channels ln8000_slave_iio_psy_channels[] = {
LN8000_CHAN_ENERGY("ln_present_slave", PSY_IIO_SC_PRESENT) LN8000_CHAN_ENERGY(
"ln_charging_enabled_slave",
PSY_IIO_SC_CHARGING_ENABLED) LN8000_CHAN_ENERGY("ln_status_slave",
PSY_IIO_SC_STATUS)
LN8000_CHAN_ENERGY("ln_battery_present_slave", PSY_IIO_SC_BATTERY_PRESENT) LN8000_CHAN_ENERGY(
"ln_vbus_present_slave",
PSY_IIO_SC_VBUS_PRESENT) LN8000_CHAN_VOLT("ln_battery_voltage_slave",
PSY_IIO_SC_BATTERY_VOLTAGE)
LN8000_CHAN_CUR("ln_battery_current_slave", PSY_IIO_SC_BATTERY_CURRENT) LN8000_CHAN_TEMP(
"ln_battery_temperature_slave",
PSY_IIO_SC_BATTERY_TEMPERATURE) LN8000_CHAN_VOLT("ln_bus_voltage_slave",
PSY_IIO_SC_BUS_VOLTAGE)
LN8000_CHAN_CUR("ln_bus_current_slave", PSY_IIO_SC_BUS_CURRENT) LN8000_CHAN_TEMP(
"ln_bus_temperature_slave",
PSY_IIO_SC_BUS_TEMPERATURE)
LN8000_CHAN_TEMP(
"ln_die_temperature_slave",
PSY_IIO_SC_DIE_TEMPERATURE)
LN8000_CHAN_ENERGY(
"ln_alarm_status_slave",
PSY_IIO_SC_ALARM_STATUS)
LN8000_CHAN_ENERGY(
"ln_fault_status_slave",
PSY_IIO_SC_FAULT_STATUS)
LN8000_CHAN_ENERGY(
"ln_vbus_error_status_slave",
PSY_IIO_SC_VBUS_ERROR_STATUS)
LN8000_CHAN_ENERGY(
"ln_reg_status_slave",
PSY_IIO_SC_REG_STATUS)
};
int ln_init_iio_psy(struct ln8000_info *chip);
#endif /* __LN8000_IIO_H */

View File

@ -0,0 +1,292 @@
/*
* ln8000-charger.h - Charger driver for LIONSEMI LN8000
*
* Copyright (C) 2021 Lion Semiconductor Inc.
*
* 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.
*
*/
#ifndef __LN8000_CHARGER_H__
#define __LN8000_CHARGER_H__
/* For support TI extend propertis */
#define BAT_OVP_FAULT_SHIFT 0
#define BAT_OCP_FAULT_SHIFT 1
#define BUS_OVP_FAULT_SHIFT 2
#define BUS_OCP_FAULT_SHIFT 3
#define BAT_THERM_FAULT_SHIFT 4
#define BUS_THERM_FAULT_SHIFT 5
#define DIE_THERM_FAULT_SHIFT 6
#define BAT_OVP_ALARM_SHIFT 0
#define BAT_OCP_ALARM_SHIFT 1
#define BUS_OVP_ALARM_SHIFT 2
#define BUS_OCP_ALARM_SHIFT 3
#define BAT_THERM_ALARM_SHIFT 4
#define BUS_THERM_ALARM_SHIFT 5
#define DIE_THERM_ALARM_SHIFT 6
#define BAT_UCP_ALARM_SHIFT 7
#define VBAT_REG_STATUS_SHIFT 0
#define IBAT_REG_STATUS_SHIFT 1
#if 0
enum hvdcp3_type {
HVDCP3_NONE = 0,
HVDCP3_CLASSA_18W,
HVDCP3_CLASSB_27W,
HVDCP3P5_CLASSA_18W,
HVDCP3P5_CLASSB_27W,
};
#else
enum hvdcp3_type {
HVDCP3_NONE = 0,
HVDCP3_CLASSA_18W,
HVDCP3_CLASSB_27W,
HVDCP3_P_CLASSA_18W,
HVDCP3_P_CLASSB_27W,
};
#endif
/* bus protection values for QC */
#define BUS_OVP_FOR_QC \
13000000 /* ln8000 didn't used 10V, (support tot 6.5V, 11V, 12V, 13V) */
#define BUS_OVP_ALARM_FOR_QC 9500000
#define BUS_OCP_FOR_QC_CLASS_A 3250000
#define BUS_OCP_ALARM_FOR_QC_CLASS_A 2000000
#define BUS_OCP_FOR_QC_CLASS_B 4000000
#define BUS_OCP_ALARM_FOR_QC_CLASS_B 3000000
#define BUS_OVP_FOR_QC35 13000000
#define BUS_OVP_ALARM_FOR_QC35 9500000
#define BUS_OCP_FOR_QC35_CLASS_A_P 3000000
#define BUS_OCP_ALARM_FOR_QC35_CLASS_A_P 2550000
/**
* ln8000 device descripion definition
*/
#define ASSIGNED_BITS(_end, _start) ((BIT(_end) - BIT(_start)) + BIT(_end))
/* register map description */
enum ln8000_int1_desc {
LN8000_MASK_FAULT_INT = BIT(7),
LN8000_MASK_NTC_PROT_INT = BIT(6),
LN8000_MASK_CHARGE_PHASE_INT = BIT(5),
LN8000_MASK_MODE_INT = BIT(4),
LN8000_MASK_REV_CURR_INT = BIT(3),
LN8000_MASK_TEMP_INT = BIT(2),
LN8000_MASK_ADC_DONE_INT = BIT(1),
LN8000_MASK_TIMER_INT = BIT(0),
};
enum ln8000_sys_sts_desc {
LN8000_MASK_IIN_LOOP_STS = BIT(7),
LN8000_MASK_VFLOAT_LOOP_STS = BIT(6),
LN8000_MASK_BYPASS_ENABLED = BIT(3),
LN8000_MASK_SWITCHING_ENABLED = BIT(2),
LN8000_MASK_STANDBY_STS = BIT(1),
LN8000_MASK_SHUTDOWN_STS = BIT(0),
};
enum ln8000_safety_sts_desc {
LN8000_MASK_TEMP_MAX_STS = BIT(6),
LN8000_MASK_TEMP_REGULATION_STS = BIT(5),
LN8000_MASK_NTC_ALARM_STS = BIT(4),
LN8000_MASK_NTC_SHUTDOWN_STS = BIT(3),
LN8000_MASK_REV_IIN_STS = BIT(2),
};
enum ln8000_fault1_sts_desc {
LN8000_MASK_WATCHDOG_TIMER_STS = BIT(7),
LN8000_MASK_VBAT_OV_STS = BIT(6),
LN8000_MASK_VAC_UNPLUG_STS = BIT(4),
LN8000_MASK_VAC_OV_STS = BIT(3),
LN8000_MASK_VIN_OV_STS = BIT(1),
LN8000_MASK_VFAULTS = ASSIGNED_BITS(6, 0),
};
enum ln8000_fault2_sts_desc {
LN8000_MASK_IIN_OC_DETECTED = BIT(7),
};
enum ln8000_ldo_sts_desc {
LN8000_MASK_VBAT_MIN_OK_STS = BIT(7),
LN8000_MASK_CHARGE_TERM_STS = BIT(5),
LN8000_MASK_RECHARGE_STS = BIT(4),
};
enum ln8000_regulation_ctrl_desc {
LN8000_BIT_ENABLE_VFLOAT_LOOP_INT = 7,
LN8000_BIT_ENABLE_IIN_LOOP_INT = 6,
LN8000_BIT_DISABLE_VFLOAT_LOOP = 5,
LN8000_BIT_DISABLE_IIN_LOOP = 4,
LN8000_BIT_TEMP_MAX_EN = 2,
LN8000_BIT_TEMP_REG_EN = 1,
};
enum ln8000_sys_ctrl_desc {
LN8000_BIT_STANDBY_EN = 3,
LN8000_BIT_REV_IIN_DET = 2,
LN8000_BIT_SOFT_START_EN = 1,
LN8000_BIT_EN_1TO1 = 0,
};
enum ln8000_fault_ctrl_desc {
LN8000_BIT_DISABLE_IIN_OCP = 6,
LN8000_BIT_DISABLE_VBAT_OV = 5,
LN8000_BIT_DISABLE_VAC_OV = 4,
LN8000_BIT_DISABLE_VAC_UV = 3,
LN8000_BIT_DISABLE_VIN_OV = 2,
};
enum ln8000_bc_op1_desc {
LN8000_BIT_DUAL_FUNCTION_EN = 2,
LN8000_BIT_DUAL_CFG = 1,
LN8000_BIT_DUAL_LOCKOUT_EN = 0,
};
enum ln8000_reg_addr {
LN8000_REG_DEVICE_ID = 0x00,
LN8000_REG_INT1 = 0x01,
LN8000_REG_INT1_MSK = 0x02,
LN8000_REG_SYS_STS = 0x03,
LN8000_REG_SAFETY_STS = 0x04,
LN8000_REG_FAULT1_STS = 0x05,
LN8000_REG_FAULT2_STS = 0x06,
LN8000_REG_CURR1_STS = 0x07,
LN8000_REG_LDO_STS = 0x08,
LN8000_REG_ADC01_STS = 0x09,
LN8000_REG_ADC02_STS = 0x0A,
LN8000_REG_ADC03_STS = 0x0B,
LN8000_REG_ADC04_STS = 0x0C,
LN8000_REG_ADC05_STS = 0x0D,
LN8000_REG_ADC06_STS = 0x0E,
LN8000_REG_ADC07_STS = 0x0F,
LN8000_REG_ADC08_STS = 0x10,
LN8000_REG_ADC09_STS = 0x11,
LN8000_REG_ADC10_STS = 0x12,
LN8000_REG_IIN_CTRL = 0x1B,
LN8000_REG_REGULATION_CTRL = 0x1C,
LN8000_REG_PWR_CTRL = 0x1D,
LN8000_REG_SYS_CTRL = 0x1E,
LN8000_REG_LDO_CTRL = 0x1F,
LN8000_REG_GLITCH_CTRL = 0x20,
LN8000_REG_FAULT_CTRL = 0x21,
LN8000_REG_NTC_CTRL = 0x22,
LN8000_REG_ADC_CTRL = 0x23,
LN8000_REG_ADC_CFG = 0x24,
LN8000_REG_RECOVERY_CTRL = 0x25,
LN8000_REG_TIMER_CTRL = 0x26,
LN8000_REG_THRESHOLD_CTRL = 0x27,
LN8000_REG_V_FLOAT_CTRL = 0x28,
LN8000_REG_CHARGE_CTRL = 0x29,
LN8000_REG_LION_CTRL = 0x30,
LN8000_REG_BC_OP_1 = 0x41,
LN8000_REG_BC_OP_2 = 0x42,
LN8000_REG_BC_STS_A = 0x49,
LN8000_REG_BC_STS_B = 0x4A,
LN8000_REG_BC_STS_C = 0x4B,
LN8000_REG_BC_STS_D = 0x4C,
LN8000_REG_BC_STS_E = 0x4D,
LN8000_REG_MAX,
};
/* device feature configuration desc */
#define LN8000_DEVICE_ID 0x42
enum ln8000_role {
LN_ROLE_STANDALONE = 0x0,
LN_ROLE_MASTER = 0x1,
LN_ROLE_SLAVE = 0x2,
};
enum ln8000_opmode_ {
LN8000_OPMODE_UNKNOWN = 0x0,
LN8000_OPMODE_STANDBY = 0x1,
LN8000_OPMODE_BYPASS = 0x2,
LN8000_OPMODE_SWITCHING = 0x3,
};
enum ln8000_vac_ov_cfg_desc {
LN8000_VAC_OVP_6P5V = 0x0,
LN8000_VAC_OVP_11V = 0x1,
LN8000_VAC_OVP_12V = 0x2,
LN8000_VAC_OVP_13V = 0x3,
};
enum ln8000_watchdpg_cfg_desc {
LN8000_WATCHDOG_5SEC = 0x0,
LN8000_WATCHDOG_10SEC = 0x1,
LN8000_WATCHDOG_20SEC = 0x2,
LN8000_WATCHDOG_40SEC = 0x3,
LN8000_WATCHDOG_MAX
};
enum ln8000_adc_channel_index {
LN8000_ADC_CH_VOUT = 1,
LN8000_ADC_CH_VIN,
LN8000_ADC_CH_VBAT,
LN8000_ADC_CH_VAC,
LN8000_ADC_CH_IIN,
LN8000_ADC_CH_DIETEMP,
LN8000_ADC_CH_TSBAT,
LN8000_ADC_CH_TSBUS,
LN8000_ADC_CH_ALL
};
enum ln8000_adc_mode_desc { /* used FORCE_ADC_MODE + ADC_SHUTDOWN_CFG */
ADC_AUTO_HIB_MODE = 0x0,
ADC_AUTO_SHD_MODE = 0x1,
ADC_SHUTDOWN_MODE = 0x2,
ADC_HIBERNATE_MODE = 0x4,
ADC_NORMAL_MODE = 0x6,
};
enum ln8000_adc_hibernate_delay_desc {
ADC_HIBERNATE_500MS = 0x0,
ADC_HIBERNATE_1S = 0x1,
ADC_HIBERNATE_2S = 0x2,
ADC_HIBERNATE_4S = 0x3,
};
/* electrical numeric calculation unit description */
#define LN8000_VBAT_FLOAT_MIN 3725000 /* unit = uV */
#define LN8000_VBAT_FLOAT_MAX 5000000
#define LN8000_VBAT_FLOAT_LSB 5000
#define LN8000_ADC_VOUT_STEP 5000 /* 5mV= 5000uV LSB (0V ~ 5.115V) */
#define LN8000_ADC_VIN_STEP 16000 /* 16mV=16000uV LSB (0V ~ 16.386V) */
#define LN8000_ADC_VBAT_STEP 5000 /* 5mV= 5000uV LSB (0V ~ 5.115V) */
#define LN8000_ADC_VBAT_MIN 1000000 /* 1V */
#define LN8000_ADC_VAC_STEP 16000 /* 16mV=16000uV LSB (0V ~ 16.386V) */
#define LN8000_ADC_VAC_OS 5
#define LN8000_ADC_IIN_STEP 4890 /* 4.89mA=4890uA LSB (0A ~ 5A) */
#define LN8000_ADC_DIETEMP_STEP \
4350 /* 0.435C LSB = 4350dC/1000 (-25C ~ 160C) */
#define LN8000_ADC_DIETEMP_DENOM 1000 /* 1000 */
#define LN8000_ADC_DIETEMP_MIN (-250) /* -25C = -250dC */
#define LN8000_ADC_DIETEMP_MAX 1600 /* 160C = 1600dC */
#define LN8000_ADC_NTCV_STEP 2933 /* 2.933mV=2933uV LSB (0V ~ 3V) */
#define LN8000_IIN_CFG_MIN 500000 /* 500mA=500,000uA */
#define LN8000_IIN_CFG_LSB 50000 /* 50mA=50,000uA */
/* device default values */
#define LN8000_BAT_OVP_DEFAULT 4440000
#define LN8000_BUS_OVP_DEFAULT 12800000
#define LN8000_BUS_OCP_DEFAULT 2000000
#define LN8000_NTC_ALARM_CFG_DEFAULT 226 /* NTC alarm threshold (~40C) */
#define LN8000_NTC_SHUTDOWN_CFG 2 /* NTC shutdown config (-16LSB ~ 4.3C) */
#define LN8000_DEFAULT_FSW_CFG 8 /* 8=440kHz, switching freq */
#define LN8000_IIN_CFG_DEFAULT 2000000 /* 2A=2,000,000uA, input current limit */
#endif /* __LN8000_CHARGER_H__ */

View File

@ -0,0 +1,329 @@
#ifndef __SC8551A_H__
#define __SC8551A_H__
#define pr_fmt(fmt) "[sc8551] %s: " fmt, __func__
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/err.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#include <linux/debugfs.h>
#include <linux/bitops.h>
#include <linux/math64.h>
#include <linux/version.h>
#include <linux/battmngr/battmngr_notifier.h>
typedef enum {
ADC_IBUS,
ADC_VBUS,
ADC_VAC,
ADC_VOUT,
ADC_VBAT,
ADC_IBAT,
ADC_TBUS,
ADC_TBAT,
ADC_TDIE,
ADC_MAX_NUM,
} ADC_CH;
#define SC8551_ROLE_STDALONE 0
#define SC8551_ROLE_SLAVE 1
#define SC8551_ROLE_MASTER 2
enum {
SC8551_STDALONE,
SC8551_SLAVE,
SC8551_MASTER,
};
static int sc8551_mode_data[] = {
[SC8551_STDALONE] = SC8551_ROLE_STDALONE,
[SC8551_MASTER] = SC8551_ROLE_MASTER,
[SC8551_SLAVE] = SC8551_ROLE_SLAVE,
};
#define BAT_OVP_ALARM BIT(7)
#define BAT_OCP_ALARM BIT(6)
#define BUS_OVP_ALARM BIT(5)
#define BUS_OCP_ALARM BIT(4)
#define BAT_UCP_ALARM BIT(3)
#define VBUS_INSERT BIT(2)
#define VBAT_INSERT BIT(1)
#define ADC_DONE BIT(0)
#define BAT_OVP_FAULT BIT(7)
#define BAT_OCP_FAULT BIT(6)
#define BUS_OVP_FAULT BIT(5)
#define BUS_OCP_FAULT BIT(4)
#define TBUS_TBAT_ALARM BIT(3)
#define TS_BAT_FAULT BIT(2)
#define TS_BUS_FAULT BIT(1)
#define TS_DIE_FAULT BIT(0)
/*below used for comm with other module*/
#define BAT_OVP_FAULT_SHIFT 0
#define BAT_OCP_FAULT_SHIFT 1
#define BUS_OVP_FAULT_SHIFT 2
#define BUS_OCP_FAULT_SHIFT 3
#define BAT_THERM_FAULT_SHIFT 4
#define BUS_THERM_FAULT_SHIFT 5
#define DIE_THERM_FAULT_SHIFT 6
#define BAT_OVP_FAULT_MASK (1 << BAT_OVP_FAULT_SHIFT)
#define BAT_OCP_FAULT_MASK (1 << BAT_OCP_FAULT_SHIFT)
#define BUS_OVP_FAULT_MASK (1 << BUS_OVP_FAULT_SHIFT)
#define BUS_OCP_FAULT_MASK (1 << BUS_OCP_FAULT_SHIFT)
#define BAT_THERM_FAULT_MASK (1 << BAT_THERM_FAULT_SHIFT)
#define BUS_THERM_FAULT_MASK (1 << BUS_THERM_FAULT_SHIFT)
#define DIE_THERM_FAULT_MASK (1 << DIE_THERM_FAULT_SHIFT)
#define BAT_OVP_ALARM_SHIFT 0
#define BAT_OCP_ALARM_SHIFT 1
#define BUS_OVP_ALARM_SHIFT 2
#define BUS_OCP_ALARM_SHIFT 3
#define BAT_THERM_ALARM_SHIFT 4
#define BUS_THERM_ALARM_SHIFT 5
#define DIE_THERM_ALARM_SHIFT 6
#define BAT_UCP_ALARM_SHIFT 7
#define BAT_OVP_ALARM_MASK (1 << BAT_OVP_ALARM_SHIFT)
#define BAT_OCP_ALARM_MASK (1 << BAT_OCP_ALARM_SHIFT)
#define BUS_OVP_ALARM_MASK (1 << BUS_OVP_ALARM_SHIFT)
#define BUS_OCP_ALARM_MASK (1 << BUS_OCP_ALARM_SHIFT)
#define BAT_THERM_ALARM_MASK (1 << BAT_THERM_ALARM_SHIFT)
#define BUS_THERM_ALARM_MASK (1 << BUS_THERM_ALARM_SHIFT)
#define DIE_THERM_ALARM_MASK (1 << DIE_THERM_ALARM_SHIFT)
#define BAT_UCP_ALARM_MASK (1 << BAT_UCP_ALARM_SHIFT)
#define VBAT_REG_STATUS_SHIFT 0
#define IBAT_REG_STATUS_SHIFT 1
#define VBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
#define IBAT_REG_STATUS_MASK (1 << VBAT_REG_STATUS_SHIFT)
#define ADC_REG_BASE SC8551_REG_16
#define sc_err(fmt, ...) \
do { \
if (sc->mode == SC8551_ROLE_MASTER) \
printk(KERN_ERR "[sc8551-MASTER]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else if (sc->mode == SC8551_ROLE_SLAVE) \
printk(KERN_ERR "[sc8551-SLAVE]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_ERR "[sc8551-STANDALONE]:%s:" fmt, \
__func__, ##__VA_ARGS__); \
} while (0);
#define sc_info(fmt, ...) \
do { \
if (sc->mode == SC8551_ROLE_MASTER) \
printk(KERN_INFO "[sc8551-MASTER]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else if (sc->mode == SC8551_ROLE_SLAVE) \
printk(KERN_INFO "[sc8551-SLAVE]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_INFO "[sc8551-STANDALONE]:%s:" fmt, \
__func__, ##__VA_ARGS__); \
} while (0);
#define sc_dbg(fmt, ...) \
do { \
if (sc->mode == SC8551_ROLE_MASTER) \
printk(KERN_DEBUG "[sc8551-MASTER]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else if (sc->mode == SC8551_ROLE_SLAVE) \
printk(KERN_DEBUG "[sc8551-SLAVE]:%s:" fmt, __func__, \
##__VA_ARGS__); \
else \
printk(KERN_DEBUG "[sc8551-STANDALONE]:%s:" fmt, \
__func__, ##__VA_ARGS__); \
} while (0);
struct sc8551_cfg {
bool bat_ovp_disable;
bool bat_ocp_disable;
bool bat_ovp_alm_disable;
bool bat_ocp_alm_disable;
int bat_ovp_th;
int bat_ovp_alm_th;
int bat_ocp_th;
int bat_ocp_alm_th;
bool bus_ovp_alm_disable;
bool bus_ocp_disable;
bool bus_ocp_alm_disable;
int bus_ovp_th;
int bus_ovp_alm_th;
int bus_ocp_th;
int bus_ocp_alm_th;
bool bat_ucp_alm_disable;
int bat_ucp_alm_th;
int ac_ovp_th;
bool bat_therm_disable;
bool bus_therm_disable;
bool die_therm_disable;
int bat_therm_th; /*in %*/
int bus_therm_th; /*in %*/
int die_therm_th; /*in degC*/
int sense_r_mohm;
};
struct sc8551 {
struct device *dev;
struct i2c_client *client;
int part_no;
int revision;
int mode;
struct mutex data_lock;
struct mutex i2c_rw_lock;
struct mutex charging_disable_lock;
struct mutex irq_complete;
bool irq_waiting;
bool irq_disabled;
bool resume_completed;
bool batt_present;
bool vbus_present;
bool usb_present;
bool charge_enabled; /* Register bit status */
bool is_sc8551;
int vbus_error;
/* ADC reading */
int vbat_volt;
int vbus_volt;
int vout_volt;
int vac_volt;
int ibat_curr;
int ibus_curr;
int bat_temp;
int bus_temp;
int die_temp;
/* alarm/fault status */
bool bat_ovp_fault;
bool bat_ocp_fault;
bool bus_ovp_fault;
bool bus_ocp_fault;
bool bat_ovp_alarm;
bool bat_ocp_alarm;
bool bus_ovp_alarm;
bool bus_ocp_alarm;
bool bat_ucp_alarm;
bool bat_therm_alarm;
bool bus_therm_alarm;
bool die_therm_alarm;
bool bat_therm_fault;
bool bus_therm_fault;
bool die_therm_fault;
bool therm_shutdown_flag;
bool therm_shutdown_stat;
bool vbat_reg;
bool ibat_reg;
int prev_alarm;
int prev_fault;
int chg_ma;
int chg_mv;
int adc_status;
int charge_state;
struct sc8551_cfg *cfg;
int skip_writes;
int skip_reads;
struct delayed_work monitor_work;
struct dentry *debug_root;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
};
int sc8551_read_byte(struct sc8551 *sc, u8 reg, u8 *data);
int sc8551_enable_charge(struct sc8551 *sc, bool enable);
int sc8551_check_charge_enabled(struct sc8551 *sc, bool *enabled);
int sc8551_enable_wdt(struct sc8551 *sc, bool enable);
int sc8551_set_reg_reset(struct sc8551 *sc);
int sc8551_enable_batovp(struct sc8551 *sc, bool enable);
int sc8551_set_batovp_th(struct sc8551 *sc, int threshold);
int sc8551_enable_batovp_alarm(struct sc8551 *sc, bool enable);
int sc8551_set_batovp_alarm_th(struct sc8551 *sc, int threshold);
int sc8551_enable_batocp(struct sc8551 *sc, bool enable);
int sc8551_set_batocp_th(struct sc8551 *sc, int threshold);
int sc8551_enable_batocp_alarm(struct sc8551 *sc, bool enable);
int sc8551_set_batocp_alarm_th(struct sc8551 *sc, int threshold);
int sc8551_set_busovp_th(struct sc8551 *sc, int threshold);
int sc8551_enable_busovp_alarm(struct sc8551 *sc, bool enable);
int sc8551_set_busovp_alarm_th(struct sc8551 *sc, int threshold);
int sc8551_enable_busocp(struct sc8551 *sc, bool enable);
int sc8551_set_busocp_th(struct sc8551 *sc, int threshold);
int sc8551_enable_busocp_alarm(struct sc8551 *sc, bool enable);
int sc8551_set_busocp_alarm_th(struct sc8551 *sc, int threshold);
int sc8551_enable_batucp_alarm(struct sc8551 *sc, bool enable);
int sc8551_set_batucp_alarm_th(struct sc8551 *sc, int threshold);
int sc8551_set_acovp_th(struct sc8551 *sc, int threshold);
int sc8551_set_vdrop_th(struct sc8551 *sc, int threshold);
int sc8551_set_vdrop_deglitch(struct sc8551 *sc, int us);
int sc8551_enable_bat_therm(struct sc8551 *sc, bool enable);
int sc8551_set_bat_therm_th(struct sc8551 *sc, u8 threshold);
int sc8551_enable_bus_therm(struct sc8551 *sc, bool enable);
int sc8551_set_bus_therm_th(struct sc8551 *sc, u8 threshold);
int sc8551_set_die_therm_th(struct sc8551 *sc, u8 threshold);
int sc8551_enable_adc(struct sc8551 *sc, bool enable);
int sc8551_set_adc_scanrate(struct sc8551 *sc, bool oneshot);
int sc8551_get_adc_data(struct sc8551 *sc, int channel, int *result);
int sc8551_set_adc_scan(struct sc8551 *sc, int channel, bool enable);
int sc8551_set_alarm_int_mask(struct sc8551 *sc, u8 mask);
int sc8551_set_sense_resistor(struct sc8551 *sc, int r_mohm);
int sc8551_enable_regulation(struct sc8551 *sc, bool enable);
int sc8551_set_ss_timeout(struct sc8551 *sc, int timeout);
int sc8551_set_ibat_reg_th(struct sc8551 *sc, int th_ma);
int sc8551_set_vbat_reg_th(struct sc8551 *sc, int th_mv);
int sc8551_check_vbus_error_status(struct sc8551 *sc);
int sc8551_detect_device(struct sc8551 *sc);
void sc8551_check_alarm_status(struct sc8551 *sc);
void sc8551_check_fault_status(struct sc8551 *sc);
int sc8551_set_present(struct sc8551 *sc, bool present);
#endif /* __SC8551A_H__ */

View File

@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#ifndef __SC8551A_IIO_H
#define __SC8551A_IIO_H
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/qti_power_supply.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
struct sc8551_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define SC8551_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define SC8551_CHAN_VOLT(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED))
#define SC8551_CHAN_CUR(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED))
#define SC8551_CHAN_TEMP(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED))
#define SC8551_CHAN_POW(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_POWER, BIT(IIO_CHAN_INFO_PROCESSED))
#define SC8551_CHAN_ENERGY(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_ENERGY, BIT(IIO_CHAN_INFO_PROCESSED))
#define SC8551_CHAN_COUNT(_name, _num) \
SC8551_IIO_CHAN(_name, _num, IIO_COUNT, BIT(IIO_CHAN_INFO_PROCESSED))
static const struct sc8551_iio_channels sc8551_iio_psy_channels[] = {
SC8551_CHAN_ENERGY("sc_present", PSY_IIO_SC_PRESENT) SC8551_CHAN_ENERGY(
"sc_charging_enabled",
PSY_IIO_SC_CHARGING_ENABLED) SC8551_CHAN_ENERGY("sc_status",
PSY_IIO_SC_STATUS)
SC8551_CHAN_ENERGY("sc_battery_present", PSY_IIO_SC_BATTERY_PRESENT) SC8551_CHAN_ENERGY(
"sc_vbus_present",
PSY_IIO_SC_VBUS_PRESENT) SC8551_CHAN_VOLT("sc_battery_voltage",
PSY_IIO_SC_BATTERY_VOLTAGE)
SC8551_CHAN_CUR("sc_battery_current", PSY_IIO_SC_BATTERY_CURRENT) SC8551_CHAN_TEMP(
"sc_battery_temperature",
PSY_IIO_SC_BATTERY_TEMPERATURE) SC8551_CHAN_VOLT("sc_bus_voltage",
PSY_IIO_SC_BUS_VOLTAGE)
SC8551_CHAN_CUR("sc_bus_current", PSY_IIO_SC_BUS_CURRENT) SC8551_CHAN_TEMP(
"sc_bus_temperature",
PSY_IIO_SC_BUS_TEMPERATURE)
SC8551_CHAN_TEMP(
"sc_die_temperature",
PSY_IIO_SC_DIE_TEMPERATURE)
SC8551_CHAN_ENERGY(
"sc_alarm_status",
PSY_IIO_SC_ALARM_STATUS)
SC8551_CHAN_ENERGY(
"sc_fault_status",
PSY_IIO_SC_FAULT_STATUS)
SC8551_CHAN_ENERGY(
"sc_vbus_error_status",
PSY_IIO_SC_VBUS_ERROR_STATUS)
SC8551_CHAN_ENERGY(
"sc_enable_adc",
PSY_IIO_SC_ENABLE_ADC)
};
static const struct sc8551_iio_channels sc8551_slave_iio_psy_channels[] = {
SC8551_CHAN_ENERGY("sc_present_slave", PSY_IIO_SC_PRESENT) SC8551_CHAN_ENERGY(
"sc_charging_enabled_slave",
PSY_IIO_SC_CHARGING_ENABLED) SC8551_CHAN_ENERGY("sc_status_slave",
PSY_IIO_SC_STATUS)
SC8551_CHAN_ENERGY("sc_battery_present_slave", PSY_IIO_SC_BATTERY_PRESENT) SC8551_CHAN_ENERGY(
"sc_vbus_present_slave",
PSY_IIO_SC_VBUS_PRESENT) SC8551_CHAN_VOLT("sc_battery_voltage_slave",
PSY_IIO_SC_BATTERY_VOLTAGE)
SC8551_CHAN_CUR("sc_battery_current_slave", PSY_IIO_SC_BATTERY_CURRENT) SC8551_CHAN_TEMP(
"sc_battery_temperature_slave",
PSY_IIO_SC_BATTERY_TEMPERATURE) SC8551_CHAN_VOLT("sc_bus_voltage_slave",
PSY_IIO_SC_BUS_VOLTAGE)
SC8551_CHAN_CUR("sc_bus_current_slave", PSY_IIO_SC_BUS_CURRENT) SC8551_CHAN_TEMP(
"sc_bus_temperature_slave",
PSY_IIO_SC_BUS_TEMPERATURE)
SC8551_CHAN_TEMP(
"sc_die_temperature_slave",
PSY_IIO_SC_DIE_TEMPERATURE)
SC8551_CHAN_ENERGY(
"sc_alarm_status_slave",
PSY_IIO_SC_ALARM_STATUS)
SC8551_CHAN_ENERGY(
"sc_fault_status_slave",
PSY_IIO_SC_FAULT_STATUS)
SC8551_CHAN_ENERGY(
"sc_vbus_error_status_slave",
PSY_IIO_SC_VBUS_ERROR_STATUS)
SC8551_CHAN_ENERGY(
"sc_enable_adc_slave",
PSY_IIO_SC_ENABLE_ADC)
};
int sc_init_iio_psy(struct sc8551 *chip);
#endif /* __SC8551A_IIO_H */

View File

@ -0,0 +1,702 @@
#ifndef __SC8551_HEADER__
#define __SC8551_HEADER__
/* Register 00h */
#define SC8551_REG_00 0x00
#define SC8551_BAT_OVP_DIS_MASK 0x80
#define SC8551_BAT_OVP_DIS_SHIFT 7
#define SC8551_BAT_OVP_ENABLE 0
#define SC8551_BAT_OVP_DISABLE 1
#define SC8551_BAT_OVP_MASK 0x3F
#define SC8551_BAT_OVP_SHIFT 0
#define SC8551_BAT_OVP_BASE 3500
#define SC8551_BAT_OVP_LSB 25
/* Register 01h */
#define SC8551_REG_01 0x01
#define SC8551_BAT_OVP_ALM_DIS_MASK 0x80
#define SC8551_BAT_OVP_ALM_DIS_SHIFT 7
#define SC8551_BAT_OVP_ALM_ENABLE 0
#define SC8551_BAT_OVP_ALM_DISABLE 1
#define SC8551_BAT_OVP_ALM_MASK 0x3F
#define SC8551_BAT_OVP_ALM_SHIFT 0
#define SC8551_BAT_OVP_ALM_BASE 3500
#define SC8551_BAT_OVP_ALM_LSB 25
/* Register 02h */
#define SC8551_REG_02 0x02
#define SC8551_BAT_OCP_DIS_MASK 0x80
#define SC8551_BAT_OCP_DIS_SHIFT 7
#define SC8551_BAT_OCP_ENABLE 0
#define SC8551_BAT_OCP_DISABLE 1
#define SC8551_BAT_OCP_MASK 0x7F
#define SC8551_BAT_OCP_SHIFT 0
#define SC8551_BAT_OCP_BASE 2000
#define SC8551_BAT_OCP_LSB 100
/* Register 03h */
#define SC8551_REG_03 0x03
#define SC8551_BAT_OCP_ALM_DIS_MASK 0x80
#define SC8551_BAT_OCP_ALM_DIS_SHIFT 7
#define SC8551_BAT_OCP_ALM_ENABLE 0
#define SC8551_BAT_OCP_ALM_DISABLE 1
#define SC8551_BAT_OCP_ALM_MASK 0x7F
#define SC8551_BAT_OCP_ALM_SHIFT 0
#define SC8551_BAT_OCP_ALM_BASE 2000
#define SC8551_BAT_OCP_ALM_LSB 100
/* Register 04h */
#define SC8551_REG_04 0x04
#define SC8551_BAT_UCP_ALM_DIS_MASK 0x80
#define SC8551_BAT_UCP_ALM_DIS_SHIFT 7
#define SC8551_BAT_UCP_ALM_ENABLE 0
#define SC8551_BAT_UCP_ALM_DISABLE 1
#define SC8551_BAT_UCP_ALM_MASK 0x7F
#define SC8551_BAT_UCP_ALM_SHIFT 0
#define SC8551_BAT_UCP_ALM_BASE 0
#define SC8551_BAT_UCP_ALM_LSB 50
/* Register 05h */
#define SC8551_REG_05 0x05
#define SC8551_AC_OVP_STAT_MASK 0x80
#define SC8551_AC_OVP_STAT_SHIFT 7
#define SC8551_AC_OVP_FLAG_MASK 0x40
#define SC8551_AC_OVP_FLAG_SHIFT 6
#define SC8551_AC_OVP_MASK_MASK 0x20
#define SC8551_AC_OVP_MASK_SHIFT 5
#define SC8551_VDROP_THRESHOLD_SET_MASK 0x10
#define SC8551_VDROP_THRESHOLD_SET_SHIFT 4
#define SC8551_VDROP_THRESHOLD_300MV 0
#define SC8551_VDROP_THRESHOLD_400MV 1
#define SC8551_VDROP_DEGLITCH_SET_MASK 0x08
#define SC8551_VDROP_DEGLITCH_SET_SHIFT 3
#define SC8551_VDROP_DEGLITCH_8US 0
#define SC8551_VDROP_DEGLITCH_5MS 1
#define SC8551_AC_OVP_MASK 0x07
#define SC8551_AC_OVP_SHIFT 0
#define SC8551_AC_OVP_BASE 11
#define SC8551_AC_OVP_LSB 1
#define SC8551_AC_OVP_6P5V 65
/* Register 06h */
#define SC8551_REG_06 0x06
#define SC8551_VBUS_PD_EN_MASK 0x80
#define SC8551_VBUS_PD_EN_SHIFT 7
#define SC8551_VBUS_PD_ENABLE 1
#define SC8551_VBUS_PD_DISABLE 0
#define SC8551_BUS_OVP_MASK 0x7F
#define SC8551_BUS_OVP_SHIFT 0
#define SC8551_BUS_OVP_BASE 6000
#define SC8551_BUS_OVP_LSB 50
/* Register 07h */
#define SC8551_REG_07 0x07
#define SC8551_BUS_OVP_ALM_DIS_MASK 0x80
#define SC8551_BUS_OVP_ALM_DIS_SHIFT 7
#define SC8551_BUS_OVP_ALM_ENABLE 0
#define SC8551_BUS_OVP_ALM_DISABLE 1
#define SC8551_BUS_OVP_ALM_MASK 0x7F
#define SC8551_BUS_OVP_ALM_SHIFT 0
#define SC8551_BUS_OVP_ALM_BASE 6000
#define SC8551_BUS_OVP_ALM_LSB 50
/* Register 08h */
#define SC8551_REG_08 0x08
#define SC8551_BUS_OCP_DIS_MASK 0x80
#define SC8551_BUS_OCP_DIS_SHIFT 7
#define SC8551_BUS_OCP_ENABLE 0
#define SC8551_BUS_OCP_DISABLE 1
#define SC8551_IBUS_UCP_RISE_FLAG_MASK 0x40
#define SC8551_IBUS_UCP_RISE_FLAG_SHIFT 6
#define SC8551_IBUS_UCP_RISE_MASK_MASK 0x20
#define SC8551_IBUS_UCP_RISE_MASK_SHIFT 5
#define SC8551_IBUS_UCP_RISE_MASK_ENABLE 1
#define SC8551_IBUS_UCP_RISE_MASK_DISABLE 0
#define SC8551_IBUS_UCP_FALL_FLAG_MASK 0x10
#define SC8551_IBUS_UCP_FALL_FLAG_SHIFT 4
#define SC8551_BUS_OCP_MASK 0x0F
#define SC8551_BUS_OCP_SHIFT 0
#define SC8551_BUS_OCP_BASE 1000
#define SC8551_BUS_OCP_LSB 250
/* Register 09h */
#define SC8551_REG_09 0x09
#define SC8551_BUS_OCP_ALM_DIS_MASK 0x80
#define SC8551_BUS_OCP_ALM_DIS_SHIFT 7
#define SC8551_BUS_OCP_ALM_ENABLE 0
#define SC8551_BUS_OCP_ALM_DISABLE 1
#define SC8551_BUS_OCP_ALM_MASK 0x7F
#define SC8551_BUS_OCP_ALM_SHIFT 0
#define SC8551_BUS_OCP_ALM_BASE 0
#define SC8551_BUS_OCP_ALM_LSB 50
/* Register 0Ah */
#define SC8551_REG_0A 0x0A
#define SC8551_TSHUT_FLAG_MASK 0x80
#define SC8551_TSHUT_FLAG_SHIFT 7
#define SC8551_TSHUT_STAT_MASK 0x40
#define SC8551_TSHUT_STAT_SHIFT 6
#define SC8551_VBUS_ERRORLO_STAT_MASK 0x20
#define SC8551_VBUS_ERRORLO_STAT_SHIFT 5
#define SC8551_VBUS_ERRORHI_STAT_MASK 0x10
#define SC8551_VBUS_ERRORHI_STAT_SHIFT 4
#define SC8551_SS_TIMEOUT_FLAG_MASK 0x08
#define SC8551_SS_TIMEOUT_FLAG_SHIFT 3
#define SC8551_CONV_SWITCHING_STAT_MASK 0x04
#define SC8551_CONV_SWITCHING_STAT_SHIFT 2
#define SC8551_CONV_OCP_FLAG_MASK 0x02
#define SC8551_CONV_OCP_FLAG_SHIFT 1
#define SC8551_PIN_DIAG_FALL_FLAG_MASK 0x01
#define SC8551_PIN_DIAG_FALL_FLAG_SHIFT 0
/* Register 0Bh */
#define SC8551_REG_0B 0x0B
#define SC8551_REG_RST_MASK 0x80
#define SC8551_REG_RST_SHIFT 7
#define SC8551_REG_RST_ENABLE 1
#define SC8551_REG_RST_DISABLE 0
#define SC8551_FSW_SET_MASK 0x70
#define SC8551_FSW_SET_SHIFT 4
#define SC8551_FSW_SET_300KHZ 0
#define SC8551_FSW_SET_350KHZ 1
#define SC8551_FSW_SET_400KHZ 2
#define SC8551_FSW_SET_450KHZ 3
#define SC8551_FSW_SET_500KHZ 4
#define SC8551_FSW_SET_550KHZ 5
#define SC8551_FSW_SET_600KHZ 6
#define SC8551_FSW_SET_750KHZ 7
#define SC8551_WD_TIMEOUT_FLAG_MASK 0x08
#define SC8551_WD_TIMEOUT_SHIFT 3
#define SC8551_WATCHDOG_DIS_MASK 0x04
#define SC8551_WATCHDOG_DIS_SHIFT 2
#define SC8551_WATCHDOG_ENABLE 0
#define SC8551_WATCHDOG_DISABLE 1
#define SC8551_WATCHDOG_MASK 0x03
#define SC8551_WATCHDOG_SHIFT 0
#define SC8551_WATCHDOG_0P5S 0
#define SC8551_WATCHDOG_1S 1
#define SC8551_WATCHDOG_5S 2
#define SC8551_WATCHDOG_30S 3
/* Register 0Ch */
#define SC8551_REG_0C 0x0C
#define SC8551_CHG_EN_MASK 0x80
#define SC8551_CHG_EN_SHIFT 7
#define SC8551_CHG_ENABLE 1
#define SC8551_CHG_DISABLE 0
#define SC8551_MS_MASK 0x60
#define SC8551_MS_SHIFT 5
#define SC8551_MS_STANDALONE 0
#define SC8551_MS_SLAVE 1
#define SC8551_MS_MASTER 2
#define SC8551_FREQ_SHIFT_MASK 0x18
#define SC8551_FREQ_SHIFT_SHIFT 3
#define SC8551_FREQ_SHIFT_NORMINAL 0
#define SC8551_FREQ_SHIFT_POSITIVE10 1
#define SC8551_FREQ_SHIFT_NEGATIVE10 2
#define SC8551_FREQ_SHIFT_SPREAD_SPECTRUM 3
#define SC8551_TSBUS_DIS_MASK 0x04
#define SC8551_TSBUS_DIS_SHIFT 2
#define SC8551_TSBUS_ENABLE 0
#define SC8551_TSBUS_DISABLE 1
#define SC8551_TSBAT_DIS_MASK 0x02
#define SC8551_TSBAT_DIS_SHIFT 1
#define SC8551_TSBAT_ENABLE 0
#define SC8551_TSBAT_DISABLE 1
/* Register 0Dh */
#define SC8551_REG_0D 0x0D
#define SC8551_BAT_OVP_ALM_STAT_MASK 0x80
#define SC8551_BAT_OVP_ALM_STAT_SHIFT 7
#define SC8551_BAT_OCP_ALM_STAT_MASK 0x40
#define SC8551_BAT_OCP_ALM_STAT_SHIFT 6
#define SC8551_BUS_OVP_ALM_STAT_MASK 0x20
#define SC8551_BUS_OVP_ALM_STAT_SHIFT 5
#define SC8551_BUS_OCP_ALM_STAT_MASK 0x10
#define SC8551_BUS_OCP_ALM_STAT_SHIFT 4
#define SC8551_BAT_UCP_ALM_STAT_MASK 0x08
#define SC8551_BAT_UCP_ALM_STAT_SHIFT 3
#define SC8551_ADAPTER_INSERT_STAT_MASK 0x04
#define SC8551_ADAPTER_INSERT_STAT_SHIFT 2
#define SC8551_VBAT_INSERT_STAT_MASK 0x02
#define SC8551_VBAT_INSERT_STAT_SHIFT 1
#define SC8551_ADC_DONE_STAT_MASK 0x01
#define SC8551_ADC_DONE_STAT_SHIFT 0
#define SC8551_ADC_DONE_STAT_COMPLETE 1
#define SC8551_ADC_DONE_STAT_NOTCOMPLETE 0
/* Register 0Eh */
#define SC8551_REG_0E 0x0E
#define SC8551_BAT_OVP_ALM_FLAG_MASK 0x80
#define SC8551_BAT_OVP_ALM_FLAG_SHIFT 7
#define SC8551_BAT_OCP_ALM_FLAG_MASK 0x40
#define SC8551_BAT_OCP_ALM_FLAG_SHIFT 6
#define SC8551_BUS_OVP_ALM_FLAG_MASK 0x20
#define SC8551_BUS_OVP_ALM_FLAG_SHIFT 5
#define SC8551_BUS_OCP_ALM_FLAG_MASK 0x10
#define SC8551_BUS_OCP_ALM_FLAG_SHIFT 4
#define SC8551_BAT_UCP_ALM_FLAG_MASK 0x08
#define SC8551_BAT_UCP_ALM_FLAG_SHIFT 3
#define SC8551_ADAPTER_INSERT_FLAG_MASK 0x04
#define SC8551_ADAPTER_INSERT_FLAG_SHIFT 2
#define SC8551_VBAT_INSERT_FLAG_MASK 0x02
#define SC8551_VBAT_INSERT_FLAG_SHIFT 1
#define SC8551_ADC_DONE_FLAG_MASK 0x01
#define SC8551_ADC_DONE_FLAG_SHIFT 0
#define SC8551_ADC_DONE_FLAG_COMPLETE 1
#define SC8551_ADC_DONE_FLAG_NOTCOMPLETE 0
/* Register 0Fh */
#define SC8551_REG_0F 0x0F
#define SC8551_BAT_OVP_ALM_MASK_MASK 0x80
#define SC8551_BAT_OVP_ALM_MASK_SHIFT 7
#define SC8551_BAT_OVP_ALM_MASK_ENABLE 1
#define SC8551_BAT_OVP_ALM_MASK_DISABLE 0
#define SC8551_BAT_OCP_ALM_MASK_MASK 0x40
#define SC8551_BAT_OCP_ALM_MASK_SHIFT 6
#define SC8551_BAT_OCP_ALM_MASK_ENABLE 1
#define SC8551_BAT_OCP_ALM_MASK_DISABLE 0
#define SC8551_BUS_OVP_ALM_MASK_MASK 0x20
#define SC8551_BUS_OVP_ALM_MASK_SHIFT 5
#define SC8551_BUS_OVP_ALM_MASK_ENABLE 1
#define SC8551_BUS_OVP_ALM_MASK_DISABLE 0
#define SC8551_BUS_OCP_ALM_MASK_MASK 0x10
#define SC8551_BUS_OCP_ALM_MASK_SHIFT 4
#define SC8551_BUS_OCP_ALM_MASK_ENABLE 1
#define SC8551_BUS_OCP_ALM_MASK_DISABLE 0
#define SC8551_BAT_UCP_ALM_MASK_MASK 0x08
#define SC8551_BAT_UCP_ALM_MASK_SHIFT 3
#define SC8551_BAT_UCP_ALM_MASK_ENABLE 1
#define SC8551_BAT_UCP_ALM_MASK_DISABLE 0
#define SC8551_ADAPTER_INSERT_MASK_MASK 0x04
#define SC8551_ADAPTER_INSERT_MASK_SHIFT 2
#define SC8551_ADAPTER_INSERT_MASK_ENABLE 1
#define SC8551_ADAPTER_INSERT_MASK_DISABLE 0
#define SC8551_VBAT_INSERT_MASK_MASK 0x02
#define SC8551_VBAT_INSERT_MASK_SHIFT 1
#define SC8551_VBAT_INSERT_MASK_ENABLE 1
#define SC8551_VBAT_INSERT_MASK_DISABLE 0
#define SC8551_ADC_DONE_MASK_MASK 0x01
#define SC8551_ADC_DONE_MASK_SHIFT 0
#define SC8551_ADC_DONE_MASK_ENABLE 1
#define SC8551_ADC_DONE_MASK_DISABLE 0
/* Register 10h */
#define SC8551_REG_10 0x10
#define SC8551_BAT_OVP_FLT_STAT_MASK 0x80
#define SC8551_BAT_OVP_FLT_STAT_SHIFT 7
#define SC8551_BAT_OCP_FLT_STAT_MASK 0x40
#define SC8551_BAT_OCP_FLT_STAT_SHIFT 6
#define SC8551_BUS_OVP_FLT_STAT_MASK 0x20
#define SC8551_BUS_OVP_FLT_STAT_SHIFT 5
#define SC8551_BUS_OCP_FLT_STAT_MASK 0x10
#define SC8551_BUS_OCP_FLT_STAT_SHIFT 4
#define SC8551_TSBUS_TSBAT_ALM_STAT_MASK 0x08
#define SC8551_TSBUS_TSBAT_ALM_STAT_SHIFT 3
#define SC8551_TSBAT_FLT_STAT_MASK 0x04
#define SC8551_TSBAT_FLT_STAT_SHIFT 2
#define SC8551_TSBUS_FLT_STAT_MASK 0x02
#define SC8551_TSBUS_FLT_STAT_SHIFT 1
#define SC8551_TDIE_ALM_STAT_MASK 0x01
#define SC8551_TDIE_ALM_STAT_SHIFT 0
/* Register 11h */
#define SC8551_REG_11 0x11
#define SC8551_BAT_OVP_FLT_FLAG_MASK 0x80
#define SC8551_BAT_OVP_FLT_FLAG_SHIFT 7
#define SC8551_BAT_OCP_FLT_FLAG_MASK 0x40
#define SC8551_BAT_OCP_FLT_FLAG_SHIFT 6
#define SC8551_BUS_OVP_FLT_FLAG_MASK 0x20
#define SC8551_BUS_OVP_FLT_FLAG_SHIFT 5
#define SC8551_BUS_OCP_FLT_FLAG_MASK 0x10
#define SC8551_BUS_OCP_FLT_FLAG_SHIFT 4
#define SC8551_TSBUS_TSBAT_ALM_FLAG_MASK 0x08
#define SC8551_TSBUS_TSBAT_ALM_FLAG_SHIFT 3
#define SC8551_TSBAT_FLT_FLAG_MASK 0x04
#define SC8551_TSBAT_FLT_FLAG_SHIFT 2
#define SC8551_TSBUS_FLT_FLAG_MASK 0x02
#define SC8551_TSBUS_FLT_FLAG_SHIFT 1
#define SC8551_TDIE_ALM_FLAG_MASK 0x01
#define SC8551_TDIE_ALM_FLAG_SHIFT 0
/* Register 12h */
#define SC8551_REG_12 0x12
#define SC8551_BAT_OVP_FLT_MASK_MASK 0x80
#define SC8551_BAT_OVP_FLT_MASK_SHIFT 7
#define SC8551_BAT_OVP_FLT_MASK_ENABLE 1
#define SC8551_BAT_OVP_FLT_MASK_DISABLE 0
#define SC8551_BAT_OCP_FLT_MASK_MASK 0x40
#define SC8551_BAT_OCP_FLT_MASK_SHIFT 6
#define SC8551_BAT_OCP_FLT_MASK_ENABLE 1
#define SC8551_BAT_OCP_FLT_MASK_DISABLE 0
#define SC8551_BUS_OVP_FLT_MASK_MASK 0x20
#define SC8551_BUS_OVP_FLT_MASK_SHIFT 5
#define SC8551_BUS_OVP_FLT_MASK_ENABLE 1
#define SC8551_BUS_OVP_FLT_MASK_DISABLE 0
#define SC8551_BUS_OCP_FLT_MASK_MASK 0x10
#define SC8551_BUS_OCP_FLT_MASK_SHIFT 4
#define SC8551_BUS_OCP_FLT_MASK_ENABLE 1
#define SC8551_BUS_OCP_FLT_MASK_DISABLE 0
#define SC8551_TSBUS_TSBAT_ALM_MASK_MASK 0x08
#define SC8551_TSBUS_TSBAT_ALM_MASK_SHIFT 3
#define SC8551_TSBUS_TSBAT_ALM_MASK_ENABLE 1
#define SC8551_TSBUS_TSBAT_ALM_MASK_DISABLE 0
#define SC8551_TSBAT_FLT_MASK_MASK 0x04
#define SC8551_TSBAT_FLT_MASK_SHIFT 2
#define SC8551_TSBAT_FLT_MASK_ENABLE 1
#define SC8551_TSBAT_FLT_MASK_DISABLE 0
#define SC8551_TSBUS_FLT_MASK_MASK 0x02
#define SC8551_TSBUS_FLT_MASK_SHIFT 1
#define SC8551_TSBUS_FLT_MASK_ENABLE 1
#define SC8551_TSBUS_FLT_MASK_DISABLE 0
#define SC8551_TDIE_ALM_MASK_MASK 0x01
#define SC8551_TDIE_ALM_MASK_SHIFT 0
#define SC8551_TDIE_ALM_MASK_ENABLE 1
#define SC8551_TDIE_ALM_MASK_DISABLE 0
/* Register 13h */
#define SC8551_REG_13 0x13
#define SC8551_DEV_ID_MASK 0x0F
#define SC8551_DEV_ID_SHIFT 0
/* Register 14h */
#define SC8551_REG_14 0x14
#define SC8551_ADC_EN_MASK 0x80
#define SC8551_ADC_EN_SHIFT 7
#define SC8551_ADC_ENABLE 1
#define SC8551_ADC_DISABLE 0
#define SC8551_ADC_RATE_MASK 0x40
#define SC8551_ADC_RATE_SHIFT 6
#define SC8551_ADC_RATE_CONTINOUS 0
#define SC8551_ADC_RATE_ONESHOT 1
#define SC8551_IBUS_ADC_DIS_MASK 0x01
#define SC8551_IBUS_ADC_DIS_SHIFT 0
#define SC8551_IBUS_ADC_ENABLE 0
#define SC8551_IBUS_ADC_DISABLE 1
/* Register 15h */
#define SC8551_REG_15 0x15
#define SC8551_VBUS_ADC_DIS_MASK 0x80
#define SC8551_VBUS_ADC_DIS_SHIFT 7
#define SC8551_VBUS_ADC_ENABLE 0
#define SC8551_VBUS_ADC_DISABLE 1
#define SC8551_VAC_ADC_DIS_MASK 0x40
#define SC8551_VAC_ADC_DIS_SHIFT 6
#define SC8551_VAC_ADC_ENABLE 0
#define SC8551_VAC_ADC_DISABLE 1
#define SC8551_VOUT_ADC_DIS_MASK 0x20
#define SC8551_VOUT_ADC_DIS_SHIFT 5
#define SC8551_VOUT_ADC_ENABLE 0
#define SC8551_VOUT_ADC_DISABLE 1
#define SC8551_VBAT_ADC_DIS_MASK 0x10
#define SC8551_VBAT_ADC_DIS_SHIFT 4
#define SC8551_VBAT_ADC_ENABLE 0
#define SC8551_VBAT_ADC_DISABLE 1
#define SC8551_IBAT_ADC_DIS_MASK 0x08
#define SC8551_IBAT_ADC_DIS_SHIFT 3
#define SC8551_IBAT_ADC_ENABLE 0
#define SC8551_IBAT_ADC_DISABLE 1
#define SC8551_TSBUS_ADC_DIS_MASK 0x04
#define SC8551_TSBUS_ADC_DIS_SHIFT 2
#define SC8551_TSBUS_ADC_ENABLE 0
#define SC8551_TSBUS_ADC_DISABLE 1
#define SC8551_TSBAT_ADC_DIS_MASK 0x02
#define SC8551_TSBAT_ADC_DIS_SHIFT 1
#define SC8551_TSBAT_ADC_ENABLE 0
#define SC8551_TSBAT_ADC_DISABLE 1
#define SC8551_TDIE_ADC_DIS_MASK 0x01
#define SC8551_TDIE_ADC_DIS_SHIFT 0
#define SC8551_TDIE_ADC_ENABLE 0
#define SC8551_TDIE_ADC_DISABLE 1
/* Register 16h */
#define SC8551_REG_16 0x16
#define SC8551_IBUS_POL_H_MASK 0x0F
#define SC8551_IBUS_ADC_LSB 1.5625
/* Register 17h */
#define SC8551_REG_17 0x17
#define SC8551_IBUS_POL_L_MASK 0xFF
/* Register 18h */
#define SC8551_REG_18 0x18
#define SC8551_VBUS_POL_H_MASK 0x0F
#define SC8551_VBUS_ADC_LSB 3.75
/* Register 19h */
#define SC8551_REG_19 0x19
#define SC8551_VBUS_POL_L_MASK 0xFF
/* Register 1Ah */
#define SC8551_REG_1A 0x1A
#define SC8551_VAC_POL_H_MASK 0x0F
#define SC8551_VAC_ADC_LSB 5
/* Register 1Bh */
#define SC8551_REG_1B 0x1B
#define SC8551_VAC_POL_L_MASK 0xFF
/* Register 1Ch */
#define SC8551_REG_1C 0x1C
#define SC8551_VOUT_POL_H_MASK 0x0F
#define SC8551_VOUT_ADC_LSB 1.25
/* Register 1Dh */
#define SC8551_REG_1D 0x1D
#define SC8551_VOUT_POL_L_MASK 0x0F
/* Register 1Eh */
#define SC8551_REG_1E 0x1E
#define SC8551_VBAT_POL_H_MASK 0x0F
#define SC8551_VBAT_ADC_LSB 1.25
/* Register 1Fh */
#define SC8551_REG_1F 0x1F
#define SC8551_VBAT_POL_L_MASK 0xFF
/* Register 20h */
#define SC8551_REG_20 0x20
#define SC8551_IBAT_POL_H_MASK 0x0F
#define SC8551_IBAT_ADC_LSB 3.125
/* Register 21h */
#define SC8551_REG_21 0x21
#define SC8551_IBAT_POL_L_MASK 0xFF
/* Register 22h */
#define SC8551_REG_22 0x22
#define SC8551_TSBUS_POL_H_MASK 0x03
#define SC8551_TSBUS_ADC_LSB 0.09766
/* Register 23h */
#define SC8551_REG_23 0x23
#define SC8551_TSBUS_POL_L_MASK 0xFF
/* Register 24h */
#define SC8551_REG_24 0x24
#define SC8551_TSBAT_POL_H_MASK 0x03
#define SC8551_TSBAT_ADC_LSB 0.09766
/* Register 25h */
#define SC8551_REG_25 0x25
#define SC8551_TSBAT_POL_L_MASK 0xFF
/* Register 26h */
#define SC8551_REG_26 0x26
#define SC8551_TDIE_POL_H_MASK 0x01
#define SC8551_TDIE_ADC_LSB 0.5
/* Register 27h */
#define SC8551_REG_27 0x27
#define SC8551_TDIE_POL_L_MASK 0xFF
/* Register 28h */
#define SC8551_REG_28 0x28
#define SC8551_TSBUS_FLT1_MASK 0xFF
#define SC8551_TSBUS_FLT1_SHIFT 0
#define SC8551_TSBUS_FLT1_BASE 0
#define SC8551_TSBUS_FLT1_LSB 0.19531
/* Register 29h */
#define SC8551_REG_29 0x29
#define SC8551_TSBAT_FLT0_MASK 0xFF
#define SC8551_TSBAT_FLT0_SHIFT 0
#define SC8551_TSBAT_FLT0_BASE 0
#define SC8551_TSBAT_FLT0_LSB 0.19531
/* Register 2Ah */
#define SC8551_REG_2A 0x2A
#define SC8551_TDIE_ALM_MASK 0xFF
#define SC8551_TDIE_ALM_SHIFT 0
#define SC8551_TDIE_ALM_BASE 25
#define SC8551_TDIE_ALM_LSB 5 /*careful multiply is used for calc*/
/* Register 2Bh */
#define SC8551_REG_2B 0x2B
#define SC8551_SS_TIMEOUT_SET_MASK 0xE0
#define SC8551_SS_TIMEOUT_SET_SHIFT 5
#define SC8551_SS_TIMEOUT_DISABLE 0
#define SC8551_SS_TIMEOUT_12P5MS 1
#define SC8551_SS_TIMEOUT_25MS 2
#define SC8551_SS_TIMEOUT_50MS 3
#define SC8551_SS_TIMEOUT_100MS 4
#define SC8551_SS_TIMEOUT_400MS 5
#define SC8551_SS_TIMEOUT_1500MS 6
#define SC8551_SS_TIMEOUT_100000MS 7
#define SC8551_EN_REGULATION_MASK 0x10
#define SC8551_EN_REGULATION_SHIFT 4
#define SC8551_EN_REGULATION_ENABLE 1
#define SC8551_EN_REGULATION_DISABLE 0
#define SC8551_VOUT_OVP_DIS_MASK 0x08
#define SC8551_VOUT_OVP_DIS_SHIFT 3
#define SC8551_VOUT_OVP_ENABLE 1
#define SC8551_VOUT_OVP_DISABLE 0
#define SC8551_IBUS_UCP_RISE_TH_MASK 0x04
#define SC8551_IBUS_UCP_RISE_TH_SHIFT 2
#define SC8551_IBUS_UCP_RISE_150MA 0
#define SC8551_IBUS_UCP_RISE_250MA 1
#define SC8551_SET_IBAT_SNS_RES_MASK 0x02
#define SC8551_SET_IBAT_SNS_RES_SHIFT 1
#define SC8551_SET_IBAT_SNS_RES_2MHM 0
#define SC8551_SET_IBAT_SNS_RES_5MHM 1
#define SC8551_VAC_PD_EN_MASK 0x01
#define SC8551_VAC_PD_EN_SHIFT 0
#define SC8551_VAC_PD_ENABLE 1
#define SC8551_VAC_PD_DISABLE 0
/* Register 2Ch */
#define SC8551_REG_2C 0x2C
#define SC8551_IBAT_REG_MASK 0xC0
#define SC8551_IBAT_REG_SHIFT 6
#define SC8551_IBAT_REG_200MA 0
#define SC8551_IBAT_REG_300MA 1
#define SC8551_IBAT_REG_400MA 2
#define SC8551_IBAT_REG_500MA 3
#define SC8551_VBAT_REG_MASK 0x30
#define SC8551_VBAT_REG_SHIFT 4
#define SC8551_VBAT_REG_50MV 0
#define SC8551_VBAT_REG_100MV 1
#define SC8551_VBAT_REG_150MV 2
#define SC8551_VBAT_REG_200MV 3
#define SC8551_VBAT_REG_ACTIVE_STAT_MASK 0x08
#define SC8551_IBAT_REG_ACTIVE_STAT_MASK 0x04
#define SC8551_VDROP_OVP_ACTIVE_STAT_MASK 0x02
#define SC8551_VOUT_OVP_ACTIVE_STAT_MASK 0x01
#define SC8551_REG_2D 0x2D
#define SC8551_VBAT_REG_ACTIVE_FLAG_MASK 0x80
#define SC8551_IBAT_REG_ACTIVE_FLAG_MASK 0x40
#define SC8551_VDROP_OVP_FLAG_MASK 0x20
#define SC8551_VOUT_OVP_FLAG_MASK 0x10
#define SC8551_VBAT_REG_ACTIVE_MASK_MASK 0x08
#define SC8551_IBAT_REG_ACTIVE_MASK_MASK 0x04
#define SC8551_VDROP_OVP_MASK_MASK 0x02
#define SC8551_VOUT_OVP_MASK_MASK 0x01
#define SC8551_REG_2E 0x2E
#define SC8551_IBUS_LOW_DG_MASK 0x08
#define SC8551_IBUS_LOW_DG_SHIFT 3
#define SC8551_IBUS_LOW_DG_10US 0
#define SC8551_IBUS_LOW_DG_5MS 1
#define SC8551_REG_2F 0x2F
#define SC8551_PMID2OUT_UVP_FLAG_MASK 0x08
#define SC8551_PMID2OUT_OVP_FLAG_MASK 0x04
#define SC8551_PMID2OUT_UVP_STAT_MASK 0x02
#define SC8551_PMID2OUT_OVP_STAT_MASK 0x01
#define SC8551_REG_30 0x30
#define SC8551_IBUS_REG_EN_MASK 0x80
#define SC8551_IBUS_REG_EN_SHIFT 7
#define SC8551_IBUS_REG_ENABLE 1
#define SC8551_IBUS_REG_DISABLE 0
#define SC8551_IBUS_REG_ACTIVE_STAT_MASK 0x40
#define SC8551_IBUS_REG_ACTIVE_FLAG_MASK 0x20
#define SC8551_IBUS_REG_ACTIVE_MASK_MASK 0x10
#define SC8551_IBUS_REG_ACTIVE_MASK_SHIFT 4
#define SC8551_IBUS_REG_ACTIVE_NOT_MASK 0
#define SC8551_IBUS_REG_ACTIVE_MASK 1
#define SC8551_IBUS_REG_MASK 0x0F
#define SC8551_IBUS_REG_SHIFT 0
#define SC8551_IBUS_REG_BASE 1000
#define SC8551_IBUS_REG_LSB 250
#define SC8551_REG_31 0x31
#define SC8551_CHARGE_MODE_MASK 0x01
#define SC8551_CHARGE_MODE_SHIFT 0
#define SC8551_CHARGE_MODE_2_1 0
#define SC8551_CHARGE_MODE_1_1 1
#define SC8551_REG_34 0x34
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,246 @@
#include "inc/ln8000.h"
#include "inc/ln8000_reg.h"
#include "inc/ln8000_iio.h"
static int ln_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct ln8000_info *info = iio_priv(indio_dev);
int rc = 0;
switch (chan->channel) {
case PSY_IIO_SC_CHARGING_ENABLED:
ln_info("POWER_SUPPLY_PROP_CHARGING_ENABLED: %s\n",
val1 ? "enable" : "disable");
rc = psy_chg_set_charging_enable(info, val1);
break;
case PSY_IIO_SC_PRESENT:
//rc = psy_chg_set_present(info, !!val1);
break;
default:
pr_debug("Unsupported LN8000 IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int ln_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct ln8000_info *info = iio_priv(indio_dev);
int ret = 0;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_SC_CHARGING_ENABLED:
*val1 = psy_chg_get_charging_enabled(info);
break;
case PSY_IIO_SC_STATUS:
*val1 = 0;
break;
case PSY_IIO_SC_PRESENT:
*val1 = info->usb_present;
pr_err("val = %d, usb_present = %d\n", *val1,
info->usb_present);
break;
case PSY_IIO_SC_BATTERY_PRESENT:
ln8000_get_adc_data(info, LN8000_ADC_CH_VBAT, &info->vbat_uV);
if (info->vbat_uV > LN8000_ADC_VBAT_MIN) {
info->batt_present = 1; /* detected battery */
} else {
info->batt_present = 0; /* non-detected battery */
}
*val1 = info->batt_present;
break;
case PSY_IIO_SC_VBUS_PRESENT:
ret = ln8000_check_status(info);
*val1 = !(info->vac_unplug);
break;
case PSY_IIO_SC_BATTERY_VOLTAGE:
ln8000_get_adc_data(info, LN8000_ADC_CH_VBAT, &info->vbat_uV);
*val1 = info->vbat_uV / 1000;
break;
case PSY_IIO_SC_BATTERY_CURRENT:
ln8000_get_adc_data(info, LN8000_ADC_CH_IIN, &info->iin_uA);
*val1 = (info->iin_uA * 2) / 1000; /* return to IBUS_ADC x 2 */
break;
case PSY_IIO_SC_BATTERY_TEMPERATURE:
if (info->pdata->tbat_mon_disable) {
*val1 = 0;
} else {
ln8000_get_adc_data(info, LN8000_ADC_CH_TSBAT,
&info->tbat_uV);
*val1 = info->tbat_uV;
ln_info("ti_battery_temperature: adc_tbat=%d\n", *val1);
}
break;
case PSY_IIO_SC_BUS_VOLTAGE:
ln8000_get_adc_data(info, LN8000_ADC_CH_VIN, &info->vbus_uV);
*val1 = info->vbus_uV / 1000;
break;
case PSY_IIO_SC_BUS_CURRENT:
ln8000_get_adc_data(info, LN8000_ADC_CH_IIN, &info->iin_uA);
*val1 = info->iin_uA / 1000;
break;
case PSY_IIO_SC_BUS_TEMPERATURE:
if (info->pdata->tbus_mon_disable) {
*val1 = 0;
} else {
ln8000_get_adc_data(info, LN8000_ADC_CH_TSBUS,
&info->tbus_uV);
*val1 = info->tbus_uV;
ln_info("ti_bus_temperature: adc_tbus=%d\n", *val1);
}
break;
case PSY_IIO_SC_DIE_TEMPERATURE:
ln8000_get_adc_data(info, LN8000_ADC_CH_DIETEMP,
&info->tdie_dC);
*val1 = info->tdie_dC;
ln_info("ti_die_temperature: adc_tdie=%d\n", *val1);
break;
case PSY_IIO_SC_ALARM_STATUS:
*val1 = psy_chg_get_ti_alarm_status(info);
break;
case PSY_IIO_SC_FAULT_STATUS:
*val1 = psy_chg_get_ti_fault_status(info);
break;
case PSY_IIO_SC_VBUS_ERROR_STATUS:
*val1 = psy_chg_get_it_bus_error_status(info);
break;
case PSY_IIO_SC_REG_STATUS:
ln8000_check_status(info);
*val1 = ((info->vbat_regulated << VBAT_REG_STATUS_SHIFT) |
/* ln8000 not support ibat_reg, we are can be ibus_reg */
(info->iin_regulated << IBAT_REG_STATUS_SHIFT));
if (*val1) {
ln_info("ln_reg_status: val1=0x%x\n", *val1);
}
break;
default:
pr_err("Unsupported QG IIO chan %d\n", chan->channel);
ret = -EINVAL;
break;
}
if (ret < 0) {
pr_err("Couldn't read IIO channel %d, ret = %d\n",
chan->channel, ret);
return ret;
}
return IIO_VAL_INT;
}
static int ln_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct ln8000_info *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(ln8000_iio_psy_channels); i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info ln_iio_info = {
.read_raw = ln_iio_read_raw,
.write_raw = ln_iio_write_raw,
.of_xlate = ln_iio_of_xlate,
};
int ln_init_iio_psy(struct ln8000_info *chip)
{
struct iio_dev *indio_dev = chip->indio_dev;
struct iio_chan_spec *chan;
int num_iio_channels = ARRAY_SIZE(ln8000_iio_psy_channels);
int rc, i;
pr_err("LN8000 ln_init_iio_psy start\n");
chip->iio_chan = devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->iio_chan), GFP_KERNEL);
if (!chip->iio_chan)
return -ENOMEM;
chip->int_iio_chans =
devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->int_iio_chans), GFP_KERNEL);
if (!chip->int_iio_chans)
return -ENOMEM;
indio_dev->info = &ln_iio_info;
indio_dev->dev.parent = chip->dev;
indio_dev->dev.of_node = chip->dev->of_node;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = chip->iio_chan;
indio_dev->num_channels = num_iio_channels;
if (chip->dev_role == LN_ROLE_MASTER) {
indio_dev->name = "ln8000-master";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = ln8000_iio_psy_channels[i].channel_num;
chan->type = ln8000_iio_psy_channels[i].type;
chan->datasheet_name =
ln8000_iio_psy_channels[i].datasheet_name;
chan->extend_name =
ln8000_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
ln8000_iio_psy_channels[i].info_mask;
}
} else if (chip->dev_role == LN_ROLE_SLAVE) {
indio_dev->name = "ln8000-slave";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel =
ln8000_slave_iio_psy_channels[i].channel_num;
chan->type = ln8000_slave_iio_psy_channels[i].type;
chan->datasheet_name =
ln8000_slave_iio_psy_channels[i].datasheet_name;
chan->extend_name =
ln8000_slave_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
ln8000_slave_iio_psy_channels[i].info_mask;
}
} else {
indio_dev->name = "ln8000-standalone";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = ln8000_iio_psy_channels[i].channel_num;
chan->type = ln8000_iio_psy_channels[i].type;
chan->datasheet_name =
ln8000_iio_psy_channels[i].datasheet_name;
chan->extend_name =
ln8000_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
ln8000_iio_psy_channels[i].info_mask;
}
}
rc = devm_iio_device_register(chip->dev, indio_dev);
if (rc)
pr_err("Failed to register LN8000 IIO device, rc=%d\n", rc);
pr_err("LN8000 IIO device, rc=%d\n", rc);
return rc;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
#include "inc/sc8551a.h"
#include "inc/sc8551a_reg.h"
#include "inc/sc8551a_iio.h"
static int sc_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct sc8551 *sc = iio_priv(indio_dev);
int rc = 0;
switch (chan->channel) {
case PSY_IIO_SC_CHARGING_ENABLED:
sc8551_enable_charge(sc, val1);
sc8551_check_charge_enabled(sc, &sc->charge_enabled);
sc_info("POWER_SUPPLY_PROP_CHARGING_ENABLED: %s\n",
val1 ? "enable" : "disable");
break;
case PSY_IIO_SC_PRESENT:
sc8551_set_present(sc, !!val1);
break;
case PSY_IIO_SC_ENABLE_ADC:
sc8551_enable_adc(sc, !!val1);
sc->adc_status = !!val1;
break;
default:
pr_debug("Unsupported SC8551 IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int sc_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct sc8551 *sc = iio_priv(indio_dev);
int result = 0;
int ret = 0;
u8 reg_val;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_SC_CHARGING_ENABLED:
sc8551_check_charge_enabled(sc, &sc->charge_enabled);
*val1 = sc->charge_enabled;
break;
case PSY_IIO_SC_STATUS:
*val1 = 0;
break;
case PSY_IIO_SC_PRESENT:
*val1 = 1;
break;
case PSY_IIO_SC_BATTERY_PRESENT:
ret = sc8551_read_byte(sc, SC8551_REG_0D, &reg_val);
if (!ret)
sc->batt_present = !!(reg_val & VBAT_INSERT);
*val1 = sc->batt_present;
break;
case PSY_IIO_SC_VBUS_PRESENT:
ret = sc8551_read_byte(sc, SC8551_REG_0D, &reg_val);
if (!ret)
sc->vbus_present = !!(reg_val & VBUS_INSERT);
*val1 = sc->vbus_present;
break;
case PSY_IIO_SC_BATTERY_VOLTAGE:
ret = sc8551_get_adc_data(sc, ADC_VBAT, &result);
if (!ret)
sc->vbat_volt = result;
*val1 = sc->vbat_volt;
break;
case PSY_IIO_SC_BATTERY_CURRENT:
ret = sc8551_get_adc_data(sc, ADC_IBAT, &result);
if (!ret)
sc->ibat_curr = result;
*val1 = sc->ibat_curr;
break;
case PSY_IIO_SC_BATTERY_TEMPERATURE:
ret = sc8551_get_adc_data(sc, ADC_TBAT, &result);
if (!ret)
sc->bat_temp = result;
*val1 = sc->bat_temp;
break;
case PSY_IIO_SC_BUS_VOLTAGE:
ret = sc8551_get_adc_data(sc, ADC_VBUS, &result);
if (!ret)
sc->vbus_volt = result;
*val1 = sc->vbus_volt;
break;
case PSY_IIO_SC_BUS_CURRENT:
ret = sc8551_get_adc_data(sc, ADC_IBUS, &result);
if (!ret)
sc->ibus_curr = result;
*val1 = sc->ibus_curr;
break;
case PSY_IIO_SC_BUS_TEMPERATURE:
ret = sc8551_get_adc_data(sc, ADC_TBUS, &result);
if (!ret)
sc->bus_temp = result;
*val1 = sc->bus_temp;
break;
case PSY_IIO_SC_DIE_TEMPERATURE:
ret = sc8551_get_adc_data(sc, ADC_TDIE, &result);
if (!ret)
sc->die_temp = result;
*val1 = sc->die_temp;
break;
case PSY_IIO_SC_ALARM_STATUS:
sc8551_check_alarm_status(sc);
*val1 = ((sc->bat_ovp_alarm << BAT_OVP_ALARM_SHIFT) |
(sc->bat_ocp_alarm << BAT_OCP_ALARM_SHIFT) |
(sc->bat_ucp_alarm << BAT_UCP_ALARM_SHIFT) |
(sc->bus_ovp_alarm << BUS_OVP_ALARM_SHIFT) |
(sc->bus_ocp_alarm << BUS_OCP_ALARM_SHIFT) |
(sc->bat_therm_alarm << BAT_THERM_ALARM_SHIFT) |
(sc->bus_therm_alarm << BUS_THERM_ALARM_SHIFT) |
(sc->die_therm_alarm << DIE_THERM_ALARM_SHIFT));
break;
case PSY_IIO_SC_FAULT_STATUS:
sc8551_check_fault_status(sc);
*val1 = ((sc->bat_ovp_fault << BAT_OVP_FAULT_SHIFT) |
(sc->bat_ocp_fault << BAT_OCP_FAULT_SHIFT) |
(sc->bus_ovp_fault << BUS_OVP_FAULT_SHIFT) |
(sc->bus_ocp_fault << BUS_OCP_FAULT_SHIFT) |
(sc->bat_therm_fault << BAT_THERM_FAULT_SHIFT) |
(sc->bus_therm_fault << BUS_THERM_FAULT_SHIFT) |
(sc->die_therm_fault << DIE_THERM_FAULT_SHIFT));
break;
case PSY_IIO_SC_VBUS_ERROR_STATUS:
sc8551_check_vbus_error_status(sc);
*val1 = sc->vbus_error;
break;
case PSY_IIO_SC_ENABLE_ADC:
*val1 = sc->adc_status;
break;
default:
pr_debug("Unsupported QG IIO chan %d\n", chan->channel);
ret = -EINVAL;
break;
}
if (ret < 0) {
pr_err("Couldn't read IIO channel %d, ret = %d\n",
chan->channel, ret);
return ret;
}
return IIO_VAL_INT;
}
static int sc_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct sc8551 *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(sc8551_iio_psy_channels); i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info sc_iio_info = {
.read_raw = sc_iio_read_raw,
.write_raw = sc_iio_write_raw,
.of_xlate = sc_iio_of_xlate,
};
int sc_init_iio_psy(struct sc8551 *chip)
{
struct iio_dev *indio_dev = chip->indio_dev;
struct iio_chan_spec *chan;
int num_iio_channels = ARRAY_SIZE(sc8551_iio_psy_channels);
int rc, i;
pr_err("SC8551 sc_init_iio_psy start\n");
chip->iio_chan = devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->iio_chan), GFP_KERNEL);
if (!chip->iio_chan)
return -ENOMEM;
chip->int_iio_chans =
devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->int_iio_chans), GFP_KERNEL);
if (!chip->int_iio_chans)
return -ENOMEM;
indio_dev->info = &sc_iio_info;
indio_dev->dev.parent = chip->dev;
indio_dev->dev.of_node = chip->dev->of_node;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = chip->iio_chan;
indio_dev->num_channels = num_iio_channels;
if (chip->mode == SC8551_ROLE_MASTER) {
indio_dev->name = "sc8551-master";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = sc8551_iio_psy_channels[i].channel_num;
chan->type = sc8551_iio_psy_channels[i].type;
chan->datasheet_name =
sc8551_iio_psy_channels[i].datasheet_name;
chan->extend_name =
sc8551_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
sc8551_iio_psy_channels[i].info_mask;
}
} else if (chip->mode == SC8551_ROLE_SLAVE) {
indio_dev->name = "sc8551-slave";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel =
sc8551_slave_iio_psy_channels[i].channel_num;
chan->type = sc8551_slave_iio_psy_channels[i].type;
chan->datasheet_name =
sc8551_slave_iio_psy_channels[i].datasheet_name;
chan->extend_name =
sc8551_slave_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
sc8551_slave_iio_psy_channels[i].info_mask;
}
} else {
indio_dev->name = "sc8551-standalone";
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = sc8551_iio_psy_channels[i].channel_num;
chan->type = sc8551_iio_psy_channels[i].type;
chan->datasheet_name =
sc8551_iio_psy_channels[i].datasheet_name;
chan->extend_name =
sc8551_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
sc8551_iio_psy_channels[i].info_mask;
}
}
rc = devm_iio_device_register(chip->dev, indio_dev);
if (rc)
pr_err("Failed to register SC8551 IIO device, rc=%d\n", rc);
pr_err("SC8551 IIO device, rc=%d\n", rc);
return rc;
}

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_FUEL_GAUGE_BQ27Z561) += bq27z561_drv.o
bq27z561_drv-objs := bq27z561.o bq27z561_iio.o xm_battery_auth.o xm_soc_smooth.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,369 @@
#include "inc/bq27z561.h"
#include "inc/bq27z561_iio.h"
#include "inc/xm_battery_auth.h"
#include "inc/xm_soc_smooth.h"
static int fg_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct bq_fg_chip *bq = iio_priv(indio_dev);
int rc = 0;
switch (chan->channel) {
case PSY_IIO_BQFG_TEMP:
bq->fake_temp = val1;
break;
case PSY_IIO_BQFG_CAPACITY:
bq->fake_soc = val1;
break;
case PSY_IIO_BQFG_UPDATE_NOW:
fg_dump_registers(bq);
break;
case PSY_IIO_BQFG_THERM_CURR:
//bq->fcc_curr = val1;
break;
case PSY_IIO_BQFG_CHIP_OK:
bq->fake_chip_ok = !!val1;
break;
case PSY_IIO_BQFG_BATTERY_AUTH:
bq->verify_digest_success = !!val1;
break;
case PSY_IIO_BQFG_SHUTDOWN_DELAY:
bq->shutdown_delay = val1;
break;
case PSY_IIO_BQFG_FASTCHARGE_MODE:
fg_set_fastcharge_mode(bq, val1);
break;
default:
pr_debug("Unsupported FG IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err_ratelimited("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int fg_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct bq_fg_chip *bq = iio_priv(indio_dev);
static int ov_count;
int vbat_mv = 0;
static bool shutdown_delay_cancel;
static bool last_shutdown_delay;
int rc = 0;
int ret = 0, status;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_BQFG_PRESENT:
*val1 = 1;
break;
case PSY_IIO_BQFG_STATUS:
status = fg_get_batt_status(bq);
if (bq->charge_full)
status = POWER_SUPPLY_STATUS_FULL;
else if (status == POWER_SUPPLY_STATUS_FULL &&
!(bq->charge_full)) {
if (bq->batt_curr <= 0)
status = POWER_SUPPLY_STATUS_CHARGING;
else
status = POWER_SUPPLY_STATUS_DISCHARGING;
}
*val1 = status;
break;
case PSY_IIO_BQFG_VOLTAGE_NOW:
ret = fg_read_volt(bq);
mutex_lock(&bq->data_lock);
if (ret >= 0)
bq->batt_volt = ret;
*val1 = bq->batt_volt * 1000;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_VOLTAGE_MAX:
*val1 = 4450 * 1000;
*val1 = fg_read_charging_voltage(bq);
if (*val1 == BQ_MAXIUM_VOLTAGE_FOR_CELL) {
if (bq->batt_volt >
BQ_PACK_MAXIUM_VOLTAGE_FOR_PMIC_SAFETY) {
ov_count++;
if (ov_count > 2) {
ov_count = 0;
bq->cell_ov_check++;
}
} else {
ov_count = 0;
}
if (bq->cell_ov_check > 4)
bq->cell_ov_check = 4;
*val1 = BQ_PACK_MAXIUM_VOLTAGE_FOR_PMIC -
bq->cell_ov_check * 10;
if ((bq->batt_soc == 100) &&
(*val1 == BQ_PACK_MAXIUM_VOLTAGE_FOR_PMIC))
*val1 = BQ_MAXIUM_VOLTAGE_FOR_CELL;
}
*val1 *= 1000;
break;
case PSY_IIO_BQFG_CURRENT_NOW:
mutex_lock(&bq->data_lock);
fg_read_current(bq, &bq->batt_curr);
if (bq->batt_curr != BQ_I2C_FAILED_SOC) {
bq->old_batt_curr = bq->batt_curr;
}
if (bq->batt_soc >= 98 && bq->batt_curr == BQ_I2C_FAILED_SOC) {
bq->batt_curr = bq->old_batt_curr;
}
*val1 = bq->batt_curr * 1000;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_CAPACITY:
//add shutdown delay feature
if (bq->fake_soc >= 0) {
*val1 = bq->fake_soc;
if (bq->batt_volt < 3450) {
*val1 = 1;
bq->fake_soc = -1;
}
break;
}
if (bq->batt_soc < 0)
bq->batt_soc = bq->batt_soc_old;
else
bq->batt_soc_old = bq->batt_soc;
*val1 = bq->batt_soc;
if (bq->shutdown_delay_enable) {
if (*val1 == 0) {
vbat_mv = fg_read_volt(bq);
if (g_battmngr_noti->mainchg_msg.chg_plugin ||
g_battmngr_noti->pd_msg.pd_active)
status = 1;
else
status = 0;
if (vbat_mv > SHUTDOWN_DELAY_VOL && !status) {
bq->shutdown_delay = true;
*val1 = 1;
} else if (status && bq->shutdown_delay) {
bq->shutdown_delay = false;
shutdown_delay_cancel = true;
*val1 = 1;
} else {
bq->shutdown_delay = false;
if (vbat_mv > SHUTDOWN_DELAY_VOL + 20)
*val1 = 1;
}
} else {
bq->shutdown_delay = false;
shutdown_delay_cancel = false;
}
if (last_shutdown_delay != bq->shutdown_delay) {
last_shutdown_delay = bq->shutdown_delay;
if (bq->batt_psy)
power_supply_changed(bq->batt_psy);
}
}
break;
case PSY_IIO_BQFG_CAPACITY_LEVEL:
*val1 = fg_get_batt_capacity_level(bq);
break;
case PSY_IIO_BQFG_TEMP:
if (bq->fake_temp != -EINVAL) {
*val1 = bq->fake_temp;
break;
}
*val1 = bq->batt_temp;
break;
case PSY_IIO_BQFG_TIME_TO_EMPTY_NOW:
ret = fg_read_tte(bq);
mutex_lock(&bq->data_lock);
if (ret >= 0)
bq->batt_tte = ret;
*val1 = bq->batt_tte * 60;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_CHARGE_FULL:
ret = fg_read_fcc(bq);
mutex_lock(&bq->data_lock);
if (ret > 0)
bq->batt_fcc = ret;
*val1 = bq->batt_fcc * 1000;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_CHARGE_FULL_DESIGN:
ret = fg_read_dc(bq);
mutex_lock(&bq->data_lock);
if (ret > 0)
bq->batt_dc = ret;
*val1 = bq->batt_dc * 1000;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_CYCLE_COUNT:
ret = fg_read_cyclecount(bq);
mutex_lock(&bq->data_lock);
if (ret >= 0)
bq->batt_cyclecnt = ret;
*val1 = bq->batt_cyclecnt;
mutex_unlock(&bq->data_lock);
break;
case PSY_IIO_BQFG_TIME_TO_FULL_NOW:
*val1 = fg_read_ttf(bq) * 60;
break;
case PSY_IIO_BQFG_RESISTANCE_ID:
*val1 = 10000;
break;
case PSY_IIO_BQFG_UPDATE_NOW:
*val1 = 0;
break;
case PSY_IIO_BQFG_THERM_CURR:
//*val1 = bq->fcc_curr;
break;
case PSY_IIO_BQFG_CHIP_OK:
if (bq->fake_chip_ok != -EINVAL) {
*val1 = bq->fake_chip_ok;
break;
}
*val1 = fg_get_chip_ok(bq);
break;
case PSY_IIO_BQFG_BATTERY_AUTH:
*val1 = bq->verify_digest_success;
break;
case PSY_IIO_BQFG_SOC_DECIMAL:
*val1 = fg_get_soc_decimal(bq);
break;
case PSY_IIO_BQFG_SOC_DECIMAL_RATE:
*val1 = fg_get_soc_decimal_rate(bq);
break;
case PSY_IIO_BQFG_SOH:
*val1 = fg_read_soh(bq);
break;
case PSY_IIO_BQFG_BATTERY_ID:
*val1 = bq->batt_id;
break;
case PSY_IIO_BQFG_RSOC:
*val1 = (bq->batt_rm * 10000) / bq->batt_fcc;
break;
case PSY_IIO_BQFG_SHUTDOWN_DELAY:
*val1 = bq->shutdown_delay;
pr_err("vbat_mv %d bq->shutdown_delay = %d, shutdown_delay_enable = %d, batt_soc = %d\n",
bq->batt_volt, bq->shutdown_delay,
bq->shutdown_delay_enable, bq->batt_soc);
break;
case PSY_IIO_BQFG_FASTCHARGE_MODE:
*val1 = bq->fast_mode;
break;
case PSY_IIO_BQFG_TEMP_MAX:
*val1 = fg_get_temp_max_fac(bq);
break;
case PSY_IIO_BQFG_TIME_OT:
*val1 = fg_get_time_ot(bq);
break;
case PSY_IIO_BQFG_REG_RSOC:
*val1 = fg_read_rsoc(bq);
break;
case PSY_IIO_BQFG_RM:
*val1 = bq->batt_rm * 1000;
break;
default:
pr_debug("Unsupported FG IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0) {
pr_err_ratelimited("Couldn't read IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
return IIO_VAL_INT;
}
static int fg_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct bq_fg_chip *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(bq27z561_iio_psy_channels); i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info fg_iio_info = {
.read_raw = fg_iio_read_raw,
.write_raw = fg_iio_write_raw,
.of_xlate = fg_iio_of_xlate,
};
int bq27z561_init_iio_psy(struct bq_fg_chip *chip)
{
struct iio_dev *indio_dev = chip->indio_dev;
struct iio_chan_spec *chan;
int fg_num_iio_channels = ARRAY_SIZE(bq27z561_iio_psy_channels);
int rc, i;
chip->iio_chan = devm_kcalloc(chip->dev, fg_num_iio_channels,
sizeof(*chip->iio_chan), GFP_KERNEL);
if (!chip->iio_chan)
return -ENOMEM;
chip->int_iio_chans =
devm_kcalloc(chip->dev, fg_num_iio_channels,
sizeof(*chip->int_iio_chans), GFP_KERNEL);
if (!chip->int_iio_chans)
return -ENOMEM;
chip->ext_iio_chans =
devm_kcalloc(chip->dev, ARRAY_SIZE(bq27z561_iio_psy_channels),
sizeof(*chip->ext_iio_chans), GFP_KERNEL);
if (!chip->ext_iio_chans)
return -ENOMEM;
indio_dev->info = &fg_iio_info;
indio_dev->dev.parent = chip->dev;
indio_dev->dev.of_node = chip->dev->of_node;
indio_dev->name = "bq27z561,fg";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = chip->iio_chan;
indio_dev->num_channels = fg_num_iio_channels;
for (i = 0; i < fg_num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = bq27z561_iio_psy_channels[i].channel_num;
chan->type = bq27z561_iio_psy_channels[i].type;
chan->datasheet_name =
bq27z561_iio_psy_channels[i].datasheet_name;
chan->extend_name = bq27z561_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
bq27z561_iio_psy_channels[i].info_mask;
}
rc = devm_iio_device_register(chip->dev, indio_dev);
if (rc)
pr_err("Failed to register FG IIO device, rc=%d\n", rc);
pr_err("BQ27z561 IIO device, rc=%d\n", rc);
return rc;
}

View File

@ -0,0 +1,415 @@
#ifndef __BQ27Z561_H
#define __BQ27Z561_H
#define pr_fmt(fmt) "[bq27z561] %s: " fmt, __func__
#include <linux/module.h>
#include <linux/param.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
#include <linux/regmap.h>
#include <linux/random.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/battmngr/battmngr_notifier.h>
#include <linux/battmngr/battmngr_voter.h>
enum print_reason {
PR_INTERRUPT = BIT(0),
PR_REGISTER = BIT(1),
PR_OEM = BIT(2),
PR_DEBUG = BIT(3),
};
#define PROBE_CNT_MAX 50
#define INVALID_REG_ADDR 0xFF
#define FG_FLAGS_FD BIT(4)
#define FG_FLAGS_FC BIT(5)
#define FG_FLAGS_DSG BIT(6)
#define FG_FLAGS_RCA BIT(9)
#define FG_FLAGS_FASTCHAGE BIT(5)
#define BATTERY_DIGEST_LEN 32
#define DEFUALT_FULL_DESIGN 5000
#define BQ_REPORT_FULL_SOC 9800
#define BQ_CHARGE_FULL_SOC 9750
#define BQ_RECHARGE_SOC 9800
#define BQ_DEFUALT_FULL_SOC 100
#define BATT_NORMAL_H_THRESHOLD 350
#define TI_TERM_CURRENT_L 1100
#define TI_TERM_CURRENT_H 1232
#define NFG_TERM_CURRENT_L 1237
#define NFG_TERM_CURRENT_H 1458
#define BQ27Z561_DEFUALT_TERM -200
#define BQ27Z561_DEFUALT_FFC_TERM -680
#define BQ27Z561_DEFUALT_RECHARGE_VOL 4380
#define NO_FFC_OVER_FV (4464 * 1000)
#define STEP_FV (16 * 1000)
#define BATT_WARM_THRESHOLD 480
#define PD_CHG_UPDATE_DELAY_US 20 /*20 sec*/
#define BQ_I2C_FAILED_SOC -107
#define BQ_I2C_FAILED_TEMP -307
#define DEBUG_CAPATICY 15
#define BMS_FG_VERIFY "BMS_FG_VERIFY"
#define CC_CV_STEP "CC_CV_STEP"
#define FCCMAIN_FG_CHARGE_FULL_VOTER "FCCMAIN_FG_CHARGE_FULL_VOTER"
#define OVER_FV_VOTER "OVER_FV_VOTER"
#define SMART_BATTERY_FV "SMART_BATTERY_FV"
#define BQ_PACK_MAXIUM_VOLTAGE_FOR_PMIC 4490
#define BQ_MAXIUM_VOLTAGE_FOR_CELL 4480
#define BQ_PACK_MAXIUM_VOLTAGE_FOR_PMIC_SAFETY 4477
enum bq_fg_reg_idx {
BQ_FG_REG_CTRL = 0,
BQ_FG_REG_TEMP, /* Battery Temperature */
BQ_FG_REG_VOLT, /* Battery Voltage */
BQ_FG_REG_CN, /* Current Now */
BQ_FG_REG_AI, /* Average Current */
BQ_FG_REG_BATT_STATUS, /* BatteryStatus */
BQ_FG_REG_TTE, /* Time to Empty */
BQ_FG_REG_TTF, /* Time to Full */
BQ_FG_REG_FCC, /* Full Charge Capacity */
BQ_FG_REG_RM, /* Remaining Capacity */
BQ_FG_REG_CC, /* Cycle Count */
BQ_FG_REG_SOC, /* Relative State of Charge */
BQ_FG_REG_SOH, /* State of Health */
BQ_FG_REG_CHG_VOL, /* Charging Voltage*/
BQ_FG_REG_CHG_CUR, /* Charging Current*/
BQ_FG_REG_DC, /* Design Capacity */
BQ_FG_REG_ALT_MAC, /* AltManufactureAccess*/
BQ_FG_REG_MAC_DATA, /* MACData*/
BQ_FG_REG_MAC_CHKSUM, /* MACChecksum */
BQ_FG_REG_MAC_DATA_LEN, /* MACDataLen */
NUM_REGS,
};
static u8 bq27z561_regs[NUM_REGS] = {
0x00, /* CONTROL */
0x06, /* TEMP */
0x08, /* VOLT */
0x0C, /* CURRENT NOW */
0x14, /* AVG CURRENT */
0x0A, /* FLAGS */
0x16, /* Time to empty */
0x18, /* Time to full */
0x12, /* Full charge capacity */
0x10, /* Remaining Capacity */
0x2A, /* CycleCount */
0x2C, /* State of Charge */
0x2E, /* State of Health */
0x30, /* Charging Voltage*/
0x32, /* Charging Current*/
0x3C, /* Design Capacity */
0x3E, /* AltManufacturerAccess*/
0x40, /* MACData*/
0x60, /* MACChecksum */
0x61, /* MACDataLen */
};
enum bq_fg_mac_cmd {
FG_MAC_CMD_CTRL_STATUS = 0x0000,
FG_MAC_CMD_DEV_TYPE = 0x0001,
FG_MAC_CMD_FW_VER = 0x0002,
FG_MAC_CMD_HW_VER = 0x0003,
FG_MAC_CMD_IF_SIG = 0x0004,
FG_MAC_CMD_CHEM_ID = 0x0006,
FG_MAC_CMD_GAUGING = 0x0021,
FG_MAC_CMD_SEAL = 0x0030,
FG_MAC_CMD_FASTCHARGE_EN = 0x003E,
FG_MAC_CMD_FASTCHARGE_DIS = 0x003F,
FG_MAC_CMD_DEV_RESET = 0x0041,
FG_MAC_CMD_CHEM_NAME = 0x004B,
FG_MAC_CMD_MANU_NAME = 0x004C,
FG_MAC_CMD_CHARGING_STATUS = 0x0055,
FG_MAC_CMD_GAGUE_STATUS = 0x0056,
FG_MAC_CMD_LIFETIME1 = 0x0060,
FG_MAC_CMD_LIFETIME3 = 0x0062,
FG_MAC_CMD_ITSTATUS1 = 0x0073,
FG_MAC_CMD_QMAX = 0x0075,
FG_MAC_CMD_FCC_SOH = 0x0077,
FG_MAC_CMD_RA_TABLE = 0x40C0,
FG_MAC_CMD_TERM_CURRENTS = 0x456E,
};
enum {
SEAL_STATE_RSVED,
SEAL_STATE_UNSEALED,
SEAL_STATE_SEALED,
SEAL_STATE_FA,
};
enum bq_fg_device {
BQ27Z561 = 0,
BQ28Z610,
};
static const unsigned char *device2str[] = {
"bq27z561",
"bq28z610",
};
struct cold_thermal {
int index;
int temp_l;
int temp_h;
int curr_th;
};
#define STEP_TABLE_MAX 3
struct step_config {
int volt_lim;
int curr_lim;
};
/* for test
struct step_config cc_cv_step_config[STEP_TABLE_MAX] = {
{4150-2, 5500},
{4190-1, 4200},
{4225-1, 3000},
};*/
static struct step_config cc_cv_step_config[STEP_TABLE_MAX] = {
{ 4200 - 3, 8820 },
{ 4300 - 3, 7350 },
{ 4400 - 3, 5880 },
};
enum bq_fg_part_no {
TIBQ27Z561 = 0,
NFG1000B,
};
struct bq_fg_chip {
struct device *dev;
struct i2c_client *client;
struct regmap *regmap;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
struct iio_channel **ext_iio_chans;
struct platform_device *pdev;
struct votable *fv_votable;
struct votable *smart_batt_votable;
struct votable *usb_icl_votable;
struct votable *fcc_votable;
struct mutex i2c_rw_lock;
struct mutex data_lock;
enum bq_fg_part_no fg_device;
int fw_ver;
int df_ver;
u8 chip;
u8 regs[NUM_REGS];
char *model_name;
/* status tracking */
bool batt_fc;
bool batt_fd; /* full depleted */
bool batt_dsg;
bool batt_rca; /* remaining capacity alarm */
bool batt_fc_1;
bool batt_fd_1; /* full depleted */
bool batt_tc_1;
bool batt_td_1; /* full depleted */
int seal_state; /* 0 - Full Access, 1 - Unsealed, 2 - Sealed */
int batt_tte;
int batt_soc;
int batt_rsoc;
int batt_soc_old;
int batt_soc_flag;
int batt_fcc; /* Full charge capacity */
int batt_rm; /* Remaining capacity */
int batt_dc; /* Design Capacity */
int batt_volt;
int batt_temp;
int old_batt_temp;
int batt_curr;
int old_batt_curr;
int fcc_curr;
int batt_resistance;
int batt_cyclecnt; /* cycle count */
int batt_st;
int raw_soc;
int last_soc;
int batt_id;
int Qmax_old;
int rm_adjust;
int rm_adjust_max;
bool rm_flag;
/* debug */
int skip_reads;
int skip_writes;
int fake_soc;
int fake_temp;
int fake_volt;
int fake_chip_ok;
int retry_chip_ok;
int count;
int last_count;
struct delayed_work monitor_work;
//struct power_supply *fg_psy;
struct power_supply *usb_psy;
struct power_supply *batt_psy;
//struct power_supply_desc fg_psy_d;
//struct timeval *suspend_time;
ktime_t suspend_time;
u8 digest[BATTERY_DIGEST_LEN];
bool verify_digest_success;
int constant_charge_current_max;
bool charge_done;
bool charge_full;
int health;
int batt_recharge_vol;
bool recharge_done_flag;
/* workaround for debug or other purpose */
bool ignore_digest_for_debug;
bool old_hw;
int *dec_rate_seq;
int dec_rate_len;
struct cold_thermal *cold_thermal_seq;
int cold_thermal_len;
bool update_now;
bool resume_update;
bool fast_mode;
int optimiz_soc;
bool ffc_smooth;
bool shutdown_delay;
bool shutdown_delay_enable;
int cell1_max;
int max_charge_current;
int max_discharge_current;
int max_temp_cell;
int min_temp_cell;
int total_fw_runtime;
int time_spent_in_lt;
int time_spent_in_ht;
int time_spent_in_ot;
int cell_ov_check;
};
enum manu_macro {
TERMINATION = 0,
RECHARGE_VOL,
FFC_TERMINATION,
MANU_NAME,
MANU_DATA_LEN,
};
#define TERMINATION_BYTE 6
#define TERMINATION_BASE 30
#define TERMINATION_STEP 5
#define RECHARGE_VOL_BYTE 7
#define RECHARGE_VOL_BASE 4200
#define RECHARGE_VOL_STEP 5
#define FFC_TERMINATION_BYTE 8
#define FFC_TERMINATION_BASE 400
#define FFC_TERMINATION_STEP 20
#define MANU_NAME_BYTE 3
#define MANU_NAME_BASE 0x0C
#define MANU_NAME_STEP 1
struct manu_data {
int byte;
int base;
int step;
int data;
};
static struct manu_data manu_info[MANU_DATA_LEN] = {
{ TERMINATION_BYTE, TERMINATION_BASE, TERMINATION_STEP },
{ RECHARGE_VOL_BYTE, RECHARGE_VOL_BASE, RECHARGE_VOL_STEP },
{ FFC_TERMINATION_BYTE, FFC_TERMINATION_BASE, FFC_TERMINATION_STEP },
{ MANU_NAME, MANU_NAME_BASE, MANU_NAME_STEP },
};
#define SHUTDOWN_DELAY_VOL 3300
#define BQ_RESUME_UPDATE_TIME 600
static const u8 fg_dump_regs[] = {
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x16, 0x18, 0x1A,
0x1C, 0x1E, 0x20, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x66, 0x68, 0x6C, 0x6E,
};
extern struct bq_fg_chip *g_bq27z561;
int fg_write_byte(struct bq_fg_chip *bq, u8 reg, u8 val);
int fg_read_word(struct bq_fg_chip *bq, u8 reg, u16 *val);
int fg_read_block(struct bq_fg_chip *bq, u8 reg, u8 *buf, u8 len);
int fg_write_block(struct bq_fg_chip *bq, u8 reg, u8 *data, u8 len);
u8 checksum(u8 *data, u8 len);
int fg_mac_read_block(struct bq_fg_chip *bq, u16 cmd, u8 *buf, u8 len);
int fg_mac_write_block(struct bq_fg_chip *bq, u16 cmd, u8 *data, u8 len);
int fg_check_battery_psy(struct bq_fg_chip *bq);
int fg_get_gague_mode(struct bq_fg_chip *bq);
int fg_set_fastcharge_mode(struct bq_fg_chip *bq, bool enable);
int fg_read_status(struct bq_fg_chip *bq);
int fg_get_manufacture_data(struct bq_fg_chip *bq);
int fg_get_chem_data(struct bq_fg_chip *bq);
int fg_read_rsoc(struct bq_fg_chip *bq);
int fg_read_system_soc(struct bq_fg_chip *bq);
int fg_read_temperature(struct bq_fg_chip *bq);
int fg_read_volt(struct bq_fg_chip *bq);
int fg_read_avg_current(struct bq_fg_chip *bq, int *curr);
int fg_read_current(struct bq_fg_chip *bq, int *curr);
int fg_read_fcc(struct bq_fg_chip *bq);
int fg_read_dc(struct bq_fg_chip *bq);
int fg_read_rm(struct bq_fg_chip *bq);
int fg_read_soh(struct bq_fg_chip *bq);
int fg_read_cyclecount(struct bq_fg_chip *bq);
int fg_read_tte(struct bq_fg_chip *bq);
int fg_read_charging_voltage(struct bq_fg_chip *bq);
int fg_get_temp_max_fac(struct bq_fg_chip *bq);
int fg_get_time_ot(struct bq_fg_chip *bq);
int fg_read_ttf(struct bq_fg_chip *bq);
int fg_get_chip_ok(struct bq_fg_chip *bq);
int fg_get_batt_status(struct bq_fg_chip *bq);
int fg_get_batt_capacity_level(struct bq_fg_chip *bq);
int fg_get_soc_decimal_rate(struct bq_fg_chip *bq);
int fg_get_soc_decimal(struct bq_fg_chip *bq);
int fg_dump_registers(struct bq_fg_chip *bq);
int fg_get_lifetime_data(struct bq_fg_chip *bq);
void fg_update_status(struct bq_fg_chip *bq);
int fg_get_raw_soc(struct bq_fg_chip *bq);
int fg_update_charge_full(struct bq_fg_chip *bq);
int calc_delta_time(ktime_t time_last, int *delta_time);
#endif /* __BQ27Z561_H */

View File

@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#ifndef __BQ27Z561_IIO_H
#define __BQ27Z561_IIO_H
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/qti_power_supply.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
struct bq27z561_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define FG_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define FG_CHAN_VOLT(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_CUR(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_RES(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_RESISTANCE, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_TEMP(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_POW(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_POWER, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_ENERGY(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_ENERGY, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_INDEX(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_INDEX, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_ACT(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_ACTIVITY, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_TSTAMP(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_TIMESTAMP, BIT(IIO_CHAN_INFO_PROCESSED))
#define FG_CHAN_COUNT(_name, _num) \
FG_IIO_CHAN(_name, _num, IIO_COUNT, BIT(IIO_CHAN_INFO_PROCESSED))
static const struct bq27z561_iio_channels bq27z561_iio_psy_channels[] = {
FG_CHAN_ENERGY("bqfg_present", PSY_IIO_BQFG_PRESENT) FG_CHAN_ENERGY(
"bqfg_status",
PSY_IIO_BQFG_STATUS) FG_CHAN_VOLT("bqfg_voltage_now",
PSY_IIO_BQFG_VOLTAGE_NOW) FG_CHAN_VOLT("bqfg_voltage_max",
PSY_IIO_BQFG_VOLTAGE_MAX)
FG_CHAN_CUR("bqfg_current_now", PSY_IIO_BQFG_CURRENT_NOW) FG_CHAN_ENERGY(
"bqfg_capacity",
PSY_IIO_BQFG_CAPACITY) FG_CHAN_ENERGY("bqfg_capacity_level",
PSY_IIO_BQFG_CAPACITY_LEVEL)
FG_CHAN_TEMP("bqfg_temp", PSY_IIO_BQFG_TEMP) FG_CHAN_ENERGY(
"bqfg_charge_full",
PSY_IIO_BQFG_CHARGE_FULL) FG_CHAN_ENERGY("bqfg_charge_full_design",
PSY_IIO_BQFG_CHARGE_FULL_DESIGN)
FG_CHAN_COUNT("bqfg_cycle_count", PSY_IIO_BQFG_CYCLE_COUNT) FG_CHAN_ENERGY(
"bqfg_time_to_empty_now",
PSY_IIO_BQFG_TIME_TO_EMPTY_NOW) FG_CHAN_TSTAMP("bqfg_time_to_full_now",
PSY_IIO_BQFG_TIME_TO_FULL_NOW)
FG_CHAN_ENERGY("bqfg_update_now", PSY_IIO_BQFG_UPDATE_NOW) FG_CHAN_CUR(
"bqfg_therm_curr",
PSY_IIO_BQFG_THERM_CURR) FG_CHAN_CUR("bqfg_chip_ok",
PSY_IIO_BQFG_CHIP_OK)
FG_CHAN_CUR("bqfg_battery_auth", PSY_IIO_BQFG_BATTERY_AUTH) FG_CHAN_CUR(
"bqfg_soc_decimal",
PSY_IIO_BQFG_SOC_DECIMAL) FG_CHAN_CUR("bqfg_soc_decimal_rate",
PSY_IIO_BQFG_SOC_DECIMAL_RATE)
FG_CHAN_CUR("bqfg_soh", PSY_IIO_BQFG_SOH) FG_CHAN_CUR(
"bqfg_rsoc",
PSY_IIO_BQFG_RSOC) FG_CHAN_CUR("bqfg_battery_id",
PSY_IIO_BQFG_BATTERY_ID)
FG_CHAN_RES("bqfg_resistance_id", PSY_IIO_BQFG_RESISTANCE_ID) FG_CHAN_VOLT(
"bqfg_shutdown_delay",
PSY_IIO_BQFG_SHUTDOWN_DELAY)
FG_CHAN_VOLT(
"bqfg_fastcharge_mode",
PSY_IIO_BQFG_FASTCHARGE_MODE)
FG_CHAN_TEMP(
"bqfg_temp_max",
PSY_IIO_BQFG_TEMP_MAX)
FG_CHAN_TSTAMP(
"bqfg_time_ot",
PSY_IIO_BQFG_TIME_OT)
FG_CHAN_CUR(
"bqfg_reg_rsoc",
PSY_IIO_BQFG_REG_RSOC)
FG_CHAN_CUR(
"bqfg_rm",
PSY_IIO_BQFG_RM)
};
int bq27z561_init_iio_psy(struct bq_fg_chip *chip);
#endif /* __BQ27Z561_IIO_H */

View File

@ -0,0 +1,8 @@
#ifndef __XM_BATTERY_AUTH_H
#define __XM_BATTERY_AUTH_H
int StringToHex(char *str, unsigned char *out, unsigned int *outlen);
int fg_sha256_auth(struct bq_fg_chip *bq, u8 *rand_num, int length);
#endif /* __XM_BATTERY_AUTH_H */

View File

@ -0,0 +1,30 @@
#ifndef __XM_SOC_SMOOTH_H
#define __XM_SOC_SMOOTH_H
#define BATT_HIGH_AVG_CURRENT 1000000
#define NORMAL_TEMP_CHARGING_DELTA 10000
#define NORMAL_DISTEMP_CHARGING_DELTA 60000
#define LOW_TEMP_CHARGING_DELTA 20000
#define LOW_TEMP_DISCHARGING_DELTA 40000
#define FFC_SMOOTH_LEN 4
#define FG_RAW_SOC_FULL 10000
#define FG_REPORT_FULL_SOC 9400
#define FG_OPTIMIZ_FULL_TIME 64000
struct ffc_smooth {
int curr_lim;
int time;
};
static struct ffc_smooth ffc_dischg_smooth[FFC_SMOOTH_LEN] = {
{ 0, 300000 },
{ 300, 150000 },
{ 600, 72000 },
{ 1000, 50000 },
};
int bq_battery_soc_smooth_tracking(struct bq_fg_chip *bq, int raw_soc,
int batt_soc, int batt_temp, int batt_ma);
#endif /* __XM_SOC_SMOOTH_H */

View File

@ -0,0 +1,115 @@
#include "inc/bq27z561.h"
#include "inc/bq27z561_iio.h"
#include "inc/xm_battery_auth.h"
#include "inc/xm_soc_smooth.h"
int get_verify_digest(char *buf)
{
u8 digest_buf[4];
int i;
int len;
for (i = 0; i < BATTERY_DIGEST_LEN; i++) {
memset(digest_buf, 0, sizeof(digest_buf));
snprintf(digest_buf, sizeof(digest_buf) - 1, "%02x",
g_bq27z561->digest[i]);
strlcat(buf, digest_buf, BATTERY_DIGEST_LEN * 2 + 1);
}
len = strlen(buf);
buf[len] = '\0';
pr_err("buf = %s\n", buf);
return strlen(buf) + 1;
}
EXPORT_SYMBOL(get_verify_digest);
int set_verify_digest(u8 *rand_num)
{
int i;
u8 random[32] = { 0 };
char kbuf[70] = { 0 };
memset(kbuf, 0, sizeof(kbuf));
strncpy(kbuf, rand_num, 64);
StringToHex(kbuf, random, &i);
fg_sha256_auth(g_bq27z561, random, BATTERY_DIGEST_LEN);
return 0;
}
EXPORT_SYMBOL(set_verify_digest);
int StringToHex(char *str, unsigned char *out, unsigned int *outlen)
{
char *p = str;
char high = 0, low = 0;
int tmplen = strlen(p), cnt = 0;
tmplen = strlen(p);
while (cnt < (tmplen / 2)) {
high = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ?
*p - 48 - 7 :
*p - 48;
low = (*(++p) > '9' && ((*p <= 'F') || (*p <= 'f'))) ?
*(p)-48 - 7 :
*(p)-48;
out[cnt] = ((high & 0x0f) << 4 | (low & 0x0f));
p++;
cnt++;
}
if (tmplen % 2 != 0)
out[cnt] = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ?
*p - 48 - 7 :
*p - 48;
if (outlen != NULL)
*outlen = tmplen / 2 + tmplen % 2;
return tmplen / 2 + tmplen % 2;
}
int fg_sha256_auth(struct bq_fg_chip *bq, u8 *rand_num, int length)
{
int ret;
u8 cksum_calc;
u8 t_buf[2];
/*
1. The host writes 0x00 to 0x3E.
2. The host writes 0x00 to 0x3F
*/
t_buf[0] = 0x00;
t_buf[1] = 0x00;
ret = fg_write_block(bq, bq->regs[BQ_FG_REG_ALT_MAC], t_buf, 2);
if (ret < 0)
return ret;
/*
3. Write the random challenge should be written in a 32-byte block to address 0x40-0x5F
*/
msleep(2);
ret = fg_write_block(bq, bq->regs[BQ_FG_REG_MAC_DATA], rand_num,
length);
if (ret < 0)
return ret;
/*4. Write the checksum (2s complement sum of (1), (2), and (3)) to address 0x60.*/
cksum_calc = checksum(rand_num, length);
ret = fg_write_byte(bq, bq->regs[BQ_FG_REG_MAC_CHKSUM], cksum_calc);
if (ret < 0)
return ret;
/*5. Write the length 0x24 to address 0x61.*/
ret = fg_write_byte(bq, bq->regs[BQ_FG_REG_MAC_DATA_LEN], 0x24);
if (ret < 0)
return ret;
msleep(300);
ret = fg_read_block(bq, bq->regs[BQ_FG_REG_MAC_DATA], bq->digest,
length);
if (ret < 0)
return ret;
return 0;
}

View File

@ -0,0 +1,145 @@
#include "inc/bq27z561.h"
#include "inc/bq27z561_iio.h"
#include "inc/xm_battery_auth.h"
#include "inc/xm_soc_smooth.h"
static int log_level = 2;
#define smooth_err(fmt, ...) \
do { \
if (log_level >= 0) \
printk(KERN_ERR "[xm_soc_smooth] " fmt, \
##__VA_ARGS__); \
} while (0)
#define smooth_info(fmt, ...) \
do { \
if (log_level >= 1) \
printk(KERN_ERR "[xm_soc_smooth] " fmt, \
##__VA_ARGS__); \
} while (0)
#define smooth_dbg(fmt, ...) \
do { \
if (log_level >= 2) \
printk(KERN_ERR "[xm_soc_smooth] " fmt, \
##__VA_ARGS__); \
} while (0)
#ifndef abs
#define abs(x) ((x) > 0 ? (x) : -(x))
#endif
/* get MonotonicSoc*/
#define HW_REPORT_FULL_SOC 9700
#define SOC_PROPORTION 98
#define SOC_PROPORTION_C 97
int bq_battery_soc_smooth_tracking(struct bq_fg_chip *bq, int raw_soc, int soc,
int temp, int batt_ma)
{
int status;
static int system_soc, last_system_soc;
int change_delta = 0;
int soc_delta = 0, delta_time = 0, unit_time = 10000;
static ktime_t last_change_time = -1;
int soc_changed = 0;
ktime_t tmp_time = 0;
struct timespec64 time;
static int ibat_pos_count = 0;
tmp_time = ktime_get_boottime();
time = ktime_to_timespec64(tmp_time);
if ((batt_ma > 0) && (ibat_pos_count < 10))
ibat_pos_count++;
else if (batt_ma <= 0)
ibat_pos_count = 0;
status = fg_get_batt_status(bq);
// Map soc value according to raw_soc
if (raw_soc > HW_REPORT_FULL_SOC)
system_soc = 100;
else {
system_soc = ((raw_soc + SOC_PROPORTION_C) / SOC_PROPORTION);
if (system_soc > 99)
system_soc = 99;
}
// Get the initial value for the first time
if (last_change_time == -1) {
last_change_time = ktime_get();
if (system_soc != 0)
last_system_soc = system_soc;
else
last_system_soc = soc;
}
// If the soc jump, will smooth one cap every 10S
soc_delta = abs(system_soc - last_system_soc);
if (soc_delta > 1 || (bq->batt_volt < 3300 && system_soc > 0)) {
calc_delta_time(last_change_time, &change_delta);
delta_time = change_delta / unit_time;
if (delta_time < 0) {
last_change_time = ktime_get();
delta_time = 0;
}
smooth_err(
"%s: system soc=%d,last system soc: %d,delta time: %d\n",
__func__, system_soc, last_system_soc, delta_time);
soc_changed = min(1, delta_time);
if (soc_changed) {
if ((status == POWER_SUPPLY_STATUS_CHARGING) &&
(system_soc > last_system_soc))
system_soc = last_system_soc + soc_changed;
else if (status == POWER_SUPPLY_STATUS_DISCHARGING &&
system_soc < last_system_soc)
system_soc = last_system_soc - soc_changed;
} else
system_soc = last_system_soc;
smooth_err("%s: fg jump smooth soc_changed=%d\n", __func__,
soc_changed);
}
if (system_soc < last_system_soc)
system_soc = last_system_soc - 1;
// Avoid mismatches between charging status and soc changes
if (((status == POWER_SUPPLY_STATUS_DISCHARGING) &&
(system_soc > last_system_soc)) ||
((status == POWER_SUPPLY_STATUS_CHARGING) &&
(system_soc < last_system_soc) && (ibat_pos_count < 3) &&
((time.tv_sec > 10))))
system_soc = last_system_soc;
smooth_err(
"%s: smooth_new:sys_soc:%d last_sys_soc:%d soc_delta:%d charging_status:%d unit_time:%d batt_ma_now=%d\n",
__func__, system_soc, last_system_soc, soc_delta, status,
unit_time, batt_ma);
if (system_soc != last_system_soc) {
last_change_time = ktime_get();
last_system_soc = system_soc;
}
if (system_soc > 100)
system_soc = 100;
if (system_soc < 0)
system_soc = 0;
if ((system_soc == 0) &&
((bq->batt_volt >= 3400) || ((time.tv_sec <= 10)))) {
system_soc = 1;
smooth_err("%s: uisoc::hold 1 when volt > 3400mv. \n",
__func__);
}
if (bq->last_soc != system_soc)
bq->last_soc = system_soc;
return system_soc;
}

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CHARGER_SYV690D) += syv690d_drv.o
syv690d_drv-objs := syv690d.o syv690d_iio.o

View File

@ -0,0 +1,194 @@
#ifndef __SYV690D_H
#define __SYV690D_H
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/battmngr/battmngr_notifier.h>
#include <linux/battmngr/battmngr_voter.h>
#include <linux/regulator/driver.h>
#define PROBE_CNT_MAX 50
#define POWER_SUPPLY_TYPE_USB_FLOAT QTI_POWER_SUPPLY_TYPE_USB_FLOAT
#define POWER_SUPPLY_TYPE_USB_HVDCP QTI_POWER_SUPPLY_TYPE_USB_HVDCP
enum bq2589x_vbus_type {
BQ2589X_VBUS_NONE,
BQ2589X_VBUS_USB_SDP,
BQ2589X_VBUS_USB_CDP, /*CDP for bq25890, Adapter for bq25892*/
BQ2589X_VBUS_USB_DCP,
BQ2589X_VBUS_MAXC,
BQ2589X_VBUS_UNKNOWN,
BQ2589X_VBUS_NONSTAND,
BQ2589X_VBUS_OTG,
BQ2589X_VBUS_TYPE_NUM,
};
enum bq2589x_part_no {
SYV690 = 0x01,
BQ25890 = 0x03,
BQ25892 = 0x00,
BQ25895 = 0x07,
SC89890H = 0x04,
};
#define BQ2589X_STATUS_PLUGIN 0x0001
#define BQ2589X_STATUS_PG 0x0002
#define BQ2589X_STATUS_CHARGE_ENABLE 0x0004
#define BQ2589X_STATUS_FAULT 0x0008
#define BQ2589X_STATUS_EXIST 0x0100
/* voters for syv690d */
#define BQ_FCC_VOTER "BQ_FCC_VOTER"
#define BQ_ICL_VOTER "BQ_ICL_VOTER"
#define USER_VOTER "USER_VOTER"
#define MAIN_CHG_ICL_VOTER "MAIN_CHG_ICL_VOTER"
#define DETECT_FCC_VOTER "DETECT_FCC_VOTER"
#define BQ2589X_BAT_COMP 0
#define BQ2589X_VCLAMP 0
extern struct bq2589x *g_bq2589x;
struct bq2589x_config {
bool enable_auto_dpdm;
/* bool enable_12v;*/
int battery_voltage_term;
int charge_current;
int input_current_limit;
bool enable_term;
int term_current;
int charge_voltage;
bool enable_ico;
bool use_absolute_vindpm;
bool otg_status;
int otg_vol;
int otg_current;
};
struct bq2589x {
struct device *dev;
struct i2c_client *client;
enum bq2589x_part_no part_no;
int revision;
unsigned int status;
int vbus_type;
int charge_type;
bool enabled;
int vbus_volt;
int vbat_volt;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
int rsoc;
struct bq2589x_config cfg;
struct work_struct irq_work;
struct work_struct adapter_in_work;
struct work_struct adapter_out_work;
struct delayed_work monitor_work;
struct delayed_work ico_work;
struct delayed_work force_work;
struct delayed_work first_irq_work;
struct delayed_work dump_regs_work;
struct mutex bq2589x_i2c_lock;
struct wakeup_source *xm_ws;
int wakeup_flag;
struct power_supply *batt_psy;
struct power_supply *usb_psy;
struct votable *fcc_votable;
struct votable *usb_icl_votable;
struct votable *input_suspend_votable;
int old_type;
int otg_gpio;
int irq_gpio;
int hz_flag;
int curr_flag;
bool force_exit_flag;
int count;
int input_suspend;
struct regulator *dpdm_reg;
struct mutex dpdm_lock;
int dpdm_enabled;
u8 vbus_state;
u8 power_state;
};
int bq2589x_update_bits(struct bq2589x *bq, u8 reg, u8 mask, u8 data);
int sc89890h_set_hv(struct bq2589x *bq, u8 hv);
int bq2589x_get_chg_type(struct bq2589x *bq);
int bq2589x_get_chg_usb_type(struct bq2589x *bq);
int bq2589x_enable_otg(struct bq2589x *bq);
int bq2589x_disable_otg(struct bq2589x *bq);
int bq2589x_set_otg_volt(struct bq2589x *bq, int volt);
int bq2589x_set_otg_current(struct bq2589x *bq, int curr);
int bq2589x_enable_charger(struct bq2589x *bq);
int bq2589x_disable_charger(struct bq2589x *bq);
int bq2589x_get_charger_enable(struct bq2589x *bq);
int bq2589x_get_reserved_val(struct bq2589x *bq);
int bq2589x_reserved_disable(struct bq2589x *bq);
int bq2589x_adc_start(struct bq2589x *bq, bool oneshot);
int bq2589x_adc_stop(struct bq2589x *bq);
int bq2589x_adc_read_battery_volt(struct bq2589x *bq);
int bq2589x_adc_read_vbus_volt(struct bq2589x *bq);
int bq2589x_read_vindpm_volt(struct bq2589x *bq);
int bq2589x_adc_read_charge_current(struct bq2589x *bq);
int bq2589x_set_charge_current(struct bq2589x *bq, int curr);
int bq2589x_set_term_current(struct bq2589x *bq, int curr);
int bq2589x_set_prechg_current(struct bq2589x *bq, int curr);
int bq2589x_set_chargevoltage(struct bq2589x *bq, int volt);
int bq2589x_set_input_volt_limit(struct bq2589x *bq, int volt);
int bq2589x_set_input_current_limit(struct bq2589x *bq, int curr);
int bq2589x_set_vindpm_offset(struct bq2589x *bq, int offset);
int bq2589x_get_charging_status(struct bq2589x *bq);
int bq2589x_disable_watchdog_timer(struct bq2589x *bq);
int bq2589x_set_watchdog_timer(struct bq2589x *bq, u8 timeout);
int bq2589x_reset_watchdog_timer(struct bq2589x *bq);
int bq2589x_is_dpdm_done(struct bq2589x *bq, int *done);
int bq2589x_force_dpdm(struct bq2589x *bq);
void bq2589x_force_dpdm_done(struct bq2589x *bq);
int bq2589x_reset_chip(struct bq2589x *bq);
int bq2589x_enter_hiz_mode(struct bq2589x *bq);
int bq2589x_exit_hiz_mode(struct bq2589x *bq);
int bq2589x_get_hiz_mode(struct bq2589x *bq, u8 *state);
int bq2589x_force_ico(struct bq2589x *bq);
int bq2589x_check_force_ico_done(struct bq2589x *bq);
int bq2589x_enable_term(struct bq2589x *bq, bool enable);
int bq2589x_enable_auto_dpdm(struct bq2589x *bq, bool enable);
int bq2589x_use_absolute_vindpm(struct bq2589x *bq, bool enable);
int bq2589x_enable_ico(struct bq2589x *bq, bool enable);
bool bq2589x_is_charge_present(struct bq2589x *bq);
bool bq2589x_is_charge_online(struct bq2589x *bq);
bool bq2589x_is_charge_done(struct bq2589x *bq);
void bq2589x_dump_regs(struct bq2589x *bq);
int bq2589x_init_device(struct bq2589x *bq);
int bq2589x_charge_status(struct bq2589x *bq);
int bq2589x_detect_device(struct bq2589x *bq);
int charger_request_dpdm(struct bq2589x *charger, int enable);
int bq_init_iio_psy(struct bq2589x *chip);
#endif /* __SYV690D_H */

View File

@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#ifndef __SYV690D_IIO_H
#define __SYV690D_IIO_H
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/qti_power_supply.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
struct syv690d_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define BQ25890_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define BQ25890_CHAN_ENERGY(_name, _num) \
BQ25890_IIO_CHAN(_name, _num, IIO_ENERGY, BIT(IIO_CHAN_INFO_PROCESSED))
static const struct syv690d_iio_channels syv690d_iio_psy_channels[] = {
BQ25890_CHAN_ENERGY("syv_charge_present", PSY_IIO_SYV_CHARGE_PRESENT) BQ25890_CHAN_ENERGY(
"syv_charge_online",
PSY_IIO_SYV_CHARGE_ONLINE) BQ25890_CHAN_ENERGY("syv_charge_done",
PSY_IIO_SYV_CHARGE_DONE)
BQ25890_CHAN_ENERGY("syv_chager_hz", PSY_IIO_SYV_CHAGER_HZ) BQ25890_CHAN_ENERGY(
"syv_input_current_settled",
PSY_IIO_SYV_INPUT_CURRENT_SETTLED) BQ25890_CHAN_ENERGY("syv_input_voltage_settled",
PSY_IIO_SYV_INPUT_VOLTAGE_SETTLED)
BQ25890_CHAN_ENERGY("syv_charge_current", PSY_IIO_SYV_CHAGER_CURRENT) BQ25890_CHAN_ENERGY(
"syv_charger_enable",
PSY_IIO_SYV_CHARGING_ENABLED) BQ25890_CHAN_ENERGY("syv_otg_enable",
PSY_IIO_SYV_OTG_ENABLE)
BQ25890_CHAN_ENERGY("syv_charger_term", PSY_IIO_SYV_CHAGER_TERM) BQ25890_CHAN_ENERGY(
"syv_batt_voltage_term",
PSY_IIO_SYV_BATTERY_VOLTAGE_TERM)
BQ25890_CHAN_ENERGY("syv_charger_status", PSY_IIO_SYV_CHARGER_STATUS) BQ25890_CHAN_ENERGY(
"syv_charger_type",
PSY_IIO_SYV_CHARGE_TYPE)
BQ25890_CHAN_ENERGY(
"syv_charger_usb_type",
PSY_IIO_SYV_CHARGE_USB_TYPE)
BQ25890_CHAN_ENERGY(
"syv_vbus_voltage",
PSY_IIO_SYV_BUS_VOLTAGE)
BQ25890_CHAN_ENERGY(
"syv_vbat_voltage",
PSY_IIO_SYV_BATTERY_VOLTAGE)
BQ25890_CHAN_ENERGY(
"syv_enable_charger_term",
PSY_IIO_SYV_ENABLE_CHAGER_TERM)
};
int bq_init_iio_psy(struct bq2589x *chip);
#endif /* __SYV690D_IIO_H */

View File

@ -0,0 +1,377 @@
#ifndef __SYV690D_REG_H
#define __SYV690D_REG_H
/* Register 00h */
#define BQ2589X_REG_00 0x00
#define BQ2589X_ENHIZ_MASK 0x80
#define BQ2589X_ENHIZ_SHIFT 7
#define BQ2589X_HIZ_ENABLE 1
#define BQ2589X_HIZ_DISABLE 0
#define BQ2589X_ENILIM_MASK 0x40
#define BQ2589X_ENILIM_SHIFT 6
#define BQ2589X_ENILIM_ENABLE 1
#define BQ2589X_ENILIM_DISABLE 0
#define BQ2589X_IINLIM_MASK 0x3F
#define BQ2589X_IINLIM_SHIFT 0
#define BQ2589X_IINLIM_BASE 100
#define BQ2589X_IINLIM_LSB 50
/* Register 01h */
#define BQ2589X_REG_01 0x01
#define BQ2589X_BHOT_MASK 0xC0
#define BQ2589X_BHOT_SHIFT 6
#define BQ2589X_BCOLD_MASK 0x20
#define BQ2589X_BCOLD_SHIFT 5
#define BQ2589X_VINDPMOS_MASK 0x1F
#define BQ2589X_VINDPMOS_SHIFT 0
#define BQ2589X_VINDPMOS_BASE 0
#define BQ2589X_VINDPMOS_LSB 100
/* Register 0x02 */
#define BQ2589X_REG_02 0x02
#define BQ2589X_CONV_START_MASK 0x80
#define BQ2589X_CONV_START_SHIFT 7
#define BQ2589X_CONV_START 0
#define BQ2589X_CONV_STOP 1
#define BQ2589X_CONV_RATE_MASK 0x40
#define BQ2589X_CONV_RATE_SHIFT 6
#define BQ2589X_ADC_CONTINUE_ENABLE 1
#define BQ2589X_ADC_CONTINUE_DISABLE 0
#define BQ2589X_BOOST_FREQ_MASK 0x20
#define BQ2589X_BOOST_FREQ_SHIFT 5
#define BQ2589X_BOOST_FREQ_1500K 0
#define BQ2589X_BOOST_FREQ_500K 0
#define BQ2589X_ICOEN_MASK 0x10
#define BQ2589X_ICOEN_SHIFT 4
#define BQ2589X_ICO_ENABLE 1
#define BQ2589X_ICO_DISABLE 0
#define BQ2589X_HVDCPEN_MASK 0x08
#define BQ2589X_HVDCPEN_SHIFT 3
#define BQ2589X_HVDCP_ENABLE 1
#define BQ2589X_HVDCP_DISABLE 0
#define BQ2589X_MAXCEN_MASK 0x04
#define BQ2589X_MAXCEN_SHIFT 2
#define BQ2589X_MAXC_ENABLE 1
#define BQ2589X_MAXC_DISABLE 0
#define BQ2589X_FORCE_DPDM_MASK 0x02
#define BQ2589X_FORCE_DPDM_SHIFT 1
#define BQ2589X_FORCE_DPDM 1
#define BQ2589X_AUTO_DPDM_EN_MASK 0x01
#define BQ2589X_AUTO_DPDM_EN_SHIFT 0
#define BQ2589X_AUTO_DPDM_ENABLE 1
#define BQ2589X_AUTO_DPDM_DISABLE 0
/* Register 0x03 */
#define BQ2589X_REG_03 0x03
#define BQ2589X_BAT_LOADEN_MASK 0x80
#define BQ2589X_BAT_LOAEN_SHIFT 7
#define BQ2589X_WDT_RESET_MASK 0x40
#define BQ2589X_WDT_RESET_SHIFT 6
#define BQ2589X_WDT_RESET 1
#define BQ2589X_OTG_CONFIG_MASK 0x20
#define BQ2589X_OTG_CONFIG_SHIFT 5
#define BQ2589X_OTG_ENABLE 1
#define BQ2589X_OTG_DISABLE 0
#define BQ2589X_CHG_CONFIG_MASK 0x10
#define BQ2589X_CHG_CONFIG_SHIFT 4
#define BQ2589X_CHG_ENABLE 1
#define BQ2589X_CHG_DISABLE 0
#define BQ2589X_SYS_MINV_MASK 0x0E
#define BQ2589X_SYS_MINV_SHIFT 1
#define BQ2589X_SYS_MINV_BASE 3000
#define BQ2589X_SYS_MINV_LSB 100
#define BQ2589X_RESERVED_MASK 0x01
#define BQ2589X_RESERVED_SHIFT 0
#define BQ2589X_RESERVED_ENABLE 1
#define BQ2589X_RESERVED_DISABLE 0
/* Register 0x04*/
#define BQ2589X_REG_04 0x04
#define BQ2589X_EN_PUMPX_MASK 0x80
#define BQ2589X_EN_PUMPX_SHIFT 7
#define BQ2589X_PUMPX_ENABLE 1
#define BQ2589X_PUMPX_DISABLE 0
#define BQ2589X_ICHG_MASK 0x7F
#define BQ2589X_ICHG_SHIFT 0
#define BQ2589X_ICHG_BASE 0
#define BQ2589X_ICHG_LSB 64
/* Register 0x05*/
#define BQ2589X_REG_05 0x05
#define BQ2589X_IPRECHG_MASK 0xF0
#define BQ2589X_IPRECHG_SHIFT 4
#define BQ2589X_ITERM_MASK 0x0F
#define BQ2589X_ITERM_SHIFT 0
#define BQ2589X_IPRECHG_BASE 64
#define BQ2589X_IPRECHG_LSB 64
#define BQ2589X_ITERM_BASE 64
#define BQ2589X_ITERM_LSB 64
/* Register 0x06*/
#define BQ2589X_REG_06 0x06
#define BQ2589X_VREG_MASK 0xFC
#define BQ2589X_VREG_SHIFT 2
#define BQ2589X_BATLOWV_MASK 0x02
#define BQ2589X_BATLOWV_SHIFT 1
#define BQ2589X_BATLOWV_2800MV 0
#define BQ2589X_BATLOWV_3000MV 1
#define BQ2589X_VRECHG_MASK 0x01
#define BQ2589X_VRECHG_SHIFT 0
#define BQ2589X_VRECHG_100MV 0
#define BQ2589X_VRECHG_200MV 1
#define BQ2589X_VREG_BASE 3840
#define BQ2589X_VREG_LSB 16
/* Register 0x07*/
#define BQ2589X_REG_07 0x07
#define BQ2589X_EN_TERM_MASK 0x80
#define BQ2589X_EN_TERM_SHIFT 7
#define BQ2589X_TERM_ENABLE 1
#define BQ2589X_TERM_DISABLE 0
#define BQ2589X_WDT_MASK 0x30
#define BQ2589X_WDT_SHIFT 4
#define BQ2589X_WDT_DISABLE 0
#define BQ2589X_WDT_40S 1
#define BQ2589X_WDT_80S 2
#define BQ2589X_WDT_160S 3
#define BQ2589X_WDT_BASE 0
#define BQ2589X_WDT_LSB 40
#define BQ2589X_EN_TIMER_MASK 0x08
#define BQ2589X_EN_TIMER_SHIFT 3
#define BQ2589X_CHG_TIMER_ENABLE 1
#define BQ2589X_CHG_TIMER_DISABLE 0
#define BQ2589X_CHG_TIMER_MASK 0x06
#define BQ2589X_CHG_TIMER_SHIFT 1
#define BQ2589X_CHG_TIMER_5HOURS 0
#define BQ2589X_CHG_TIMER_8HOURS 1
#define BQ2589X_CHG_TIMER_12HOURS 2
#define BQ2589X_CHG_TIMER_20HOURS 3
#define BQ2589X_JEITA_ISET_MASK 0x01
#define BQ2589X_JEITA_ISET_SHIFT 0
#define BQ2589X_JEITA_ISET_50PCT 0
#define BQ2589X_JEITA_ISET_20PCT 1
/* Register 0x08*/
#define BQ2589X_REG_08 0x08
#define BQ2589X_BAT_COMP_MASK 0xE0
#define BQ2589X_BAT_COMP_SHIFT 5
#define BQ2589X_VCLAMP_MASK 0x1C
#define BQ2589X_VCLAMP_SHIFT 2
#define BQ2589X_TREG_MASK 0x03
#define BQ2589X_TREG_SHIFT 0
#define BQ2589X_TREG_60C 0
#define BQ2589X_TREG_80C 1
#define BQ2589X_TREG_100C 2
#define BQ2589X_TREG_120C 3
#define BQ2589X_BAT_COMP_BASE 0
#define BQ2589X_BAT_COMP_LSB 20
#define BQ2589X_VCLAMP_BASE 0
#define BQ2589X_VCLAMP_LSB 32
/* Register 0x09*/
#define BQ2589X_REG_09 0x09
#define BQ2589X_FORCE_ICO_MASK 0x80
#define BQ2589X_FORCE_ICO_SHIFT 7
#define BQ2589X_FORCE_ICO 1
#define BQ2589X_TMR2X_EN_MASK 0x40
#define BQ2589X_TMR2X_EN_SHIFT 6
#define BQ2589X_BATFET_DIS_MASK 0x20
#define BQ2589X_BATFET_DIS_SHIFT 5
#define BQ2589X_BATFET_OFF 1
#define BQ2589X_JEITA_VSET_MASK 0x10
#define BQ2589X_JEITA_VSET_SHIFT 4
#define BQ2589X_JEITA_VSET_N150MV 0
#define BQ2589X_JEITA_VSET_VREG 1
#define BQ2589X_BATFET_RST_EN_MASK 0x04
#define BQ2589X_BATFET_RST_EN_SHIFT 2
#define BQ2589X_PUMPX_UP_MASK 0x02
#define BQ2589X_PUMPX_UP_SHIFT 1
#define BQ2589X_PUMPX_UP 1
#define BQ2589X_PUMPX_DOWN_MASK 0x01
#define BQ2589X_PUMPX_DOWN_SHIFT 0
#define BQ2589X_PUMPX_DOWN 1
/* Register 0x0A*/
#define BQ2589X_REG_0A 0x0A
#define BQ2589X_BOOSTV_MASK 0xF0
#define BQ2589X_BOOSTV_SHIFT 4
#define BQ2589X_BOOSTV_BASE 4550
#define BQ2589X_BOOSTV_LSB 64
#define BQ2589X_BOOST_LIM_MASK 0x07
#define BQ2589X_BOOST_LIM_SHIFT 0
#define BQ2589X_BOOST_LIM_500MA 0x00
#define BQ2589X_BOOST_LIM_700MA 0x01
#define BQ2589X_BOOST_LIM_1100MA 0x02
#define BQ2589X_BOOST_LIM_1300MA 0x03
#define BQ2589X_BOOST_LIM_1600MA 0x04
#define BQ2589X_BOOST_LIM_1800MA 0x05
#define BQ2589X_BOOST_LIM_2100MA 0x06
#define BQ2589X_BOOST_LIM_2400MA 0x07
/* Register 0x0B*/
#define BQ2589X_REG_0B 0x0B
#define BQ2589X_VBUS_STAT_MASK 0xE0
#define BQ2589X_VBUS_STAT_SHIFT 5
#define BQ2589X_CHRG_STAT_MASK 0x18
#define BQ2589X_CHRG_STAT_SHIFT 3
#define BQ2589X_CHRG_STAT_IDLE 0
#define BQ2589X_CHRG_STAT_PRECHG 1
#define BQ2589X_CHRG_STAT_FASTCHG 2
#define BQ2589X_CHRG_STAT_CHGDONE 3
#define BQ2589X_PG_STAT_MASK 0x04
#define BQ2589X_PG_STAT_SHIFT 2
#define BQ2589X_SDP_STAT_MASK 0x02
#define BQ2589X_SDP_STAT_SHIFT 1
#define BQ2589X_VSYS_STAT_MASK 0x01
#define BQ2589X_VSYS_STAT_SHIFT 0
/* Register 0x0C*/
#define BQ2589X_REG_0C 0x0c
#define BQ2589X_FAULT_WDT_MASK 0x80
#define BQ2589X_FAULT_WDT_SHIFT 7
#define BQ2589X_FAULT_BOOST_MASK 0x40
#define BQ2589X_FAULT_BOOST_SHIFT 6
#define BQ2589X_FAULT_CHRG_MASK 0x30
#define BQ2589X_FAULT_CHRG_SHIFT 4
#define BQ2589X_FAULT_CHRG_NORMAL 0
#define BQ2589X_FAULT_CHRG_INPUT 1
#define BQ2589X_FAULT_CHRG_THERMAL 2
#define BQ2589X_FAULT_CHRG_TIMER 3
#define BQ2589X_FAULT_BAT_MASK 0x08
#define BQ2589X_FAULT_BAT_SHIFT 3
#define BQ2589X_FAULT_NTC_MASK 0x07
#define BQ2589X_FAULT_NTC_SHIFT 0
#define BQ2589X_FAULT_NTC_TSCOLD 1
#define BQ2589X_FAULT_NTC_TSHOT 2
#define BQ2589X_FAULT_NTC_WARM 2
#define BQ2589X_FAULT_NTC_COOL 3
#define BQ2589X_FAULT_NTC_COLD 5
#define BQ2589X_FAULT_NTC_HOT 6
/* Register 0x0D*/
#define BQ2589X_REG_0D 0x0D
#define BQ2589X_FORCE_VINDPM_MASK 0x80
#define BQ2589X_FORCE_VINDPM_SHIFT 7
#define BQ2589X_FORCE_VINDPM_ENABLE 1
#define BQ2589X_FORCE_VINDPM_DISABLE 0
#define BQ2589X_VINDPM_MASK 0x7F
#define BQ2589X_VINDPM_SHIFT 0
#define BQ2589X_VINDPM_BASE 2600
#define BQ2589X_VINDPM_LSB 100
/* Register 0x0E*/
#define BQ2589X_REG_0E 0x0E
#define BQ2589X_THERM_STAT_MASK 0x80
#define BQ2589X_THERM_STAT_SHIFT 7
#define BQ2589X_BATV_MASK 0x7F
#define BQ2589X_BATV_SHIFT 0
#define BQ2589X_BATV_BASE 2304
#define BQ2589X_BATV_LSB 20
/* Register 0x0F*/
#define BQ2589X_REG_0F 0x0F
#define BQ2589X_SYSV_MASK 0x7F
#define BQ2589X_SYSV_SHIFT 0
#define BQ2589X_SYSV_BASE 2304
#define BQ2589X_SYSV_LSB 20
/* Register 0x10*/
#define BQ2589X_REG_10 0x10
#define BQ2589X_TSPCT_MASK 0x7F
#define BQ2589X_TSPCT_SHIFT 0
#define BQ2589X_TSPCT_BASE 21
#define BQ2589X_TSPCT_LSB 465 //should be 0.465,kernel does not support float
/* Register 0x11*/
#define BQ2589X_REG_11 0x11
#define BQ2589X_VBUS_GD_MASK 0x80
#define BQ2589X_VBUS_GD_SHIFT 7
#define BQ2589X_VBUSV_MASK 0x7F
#define BQ2589X_VBUSV_SHIFT 0
#define BQ2589X_VBUSV_BASE 2600
#define BQ2589X_VBUSV_LSB 100
/* Register 0x12*/
#define BQ2589X_REG_12 0x12
#define BQ2589X_ICHGR_MASK 0x7F
#define BQ2589X_ICHGR_SHIFT 0
#define BQ2589X_ICHGR_BASE 0
#define BQ2589X_ICHGR_LSB 50
/* Register 0x13*/
#define BQ2589X_REG_13 0x13
#define BQ2589X_VDPM_STAT_MASK 0x80
#define BQ2589X_VDPM_STAT_SHIFT 7
#define BQ2589X_IDPM_STAT_MASK 0x40
#define BQ2589X_IDPM_STAT_SHIFT 6
#define BQ2589X_IDPM_LIM_MASK 0x3F
#define BQ2589X_IDPM_LIM_SHIFT 0
#define BQ2589X_IDPM_LIM_BASE 100
#define BQ2589X_IDPM_LIM_LSB 50
/* Register 0x14*/
#define BQ2589X_REG_14 0x14
#define BQ2589X_RESET_MASK 0x80
#define BQ2589X_RESET_SHIFT 7
#define BQ2589X_RESET 1
#define BQ2589X_ICO_OPTIMIZED_MASK 0x40
#define BQ2589X_ICO_OPTIMIZED_SHIFT 6
#define BQ2589X_PN_MASK 0x38
#define BQ2589X_PN_SHIFT 3
#define BQ2589X_TS_PROFILE_MASK 0x04
#define BQ2589X_TS_PROFILE_SHIFT 2
#define BQ2589X_DEV_REV_MASK 0x03
#define BQ2589X_DEV_REV_SHIFT 0
/* SC89890H */
/*0x01*/
#define SC89890H_HV_5V 0x45
#define SC89890H_HV_9V 0xC9
#define SC89890H_HV_12V 0x49
#define SC89890H_FORCE_DPDM1 0x45
#define SC89890H_FORCE_DPDM2 0x25
#define SC89890H_VINDPMOS_MASK 0x01
#define SC89890H_VINDPMOS_SHIFT 0
#define SC89890H_VINDPMOS_400MV 0
#define SC89890H_VINDPMOS_600MV 1
/*0x04*/
#define SC89890H_ICHG_LSB 60
/*0x05*/
#define SC89890H_IPRECHG_BASE 60
#define SC89890H_IPRECHG_LSB 60
#define SC89890H_ITERM_BASE 30
#define SC89890H_ITERM_LSB 60
#define SC89890H_ITERM_MAX 930
/*0x0A*/
#define SC89890H_BOOSTV_BASE 3900
#define SC89890H_BOOSTV_LSB 100
#endif /* __SYV690D_REG_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,268 @@
#include "inc/syv690d.h"
#include "inc/syv690d_reg.h"
#include "inc/syv690d_iio.h"
static int bq_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct bq2589x *bq = iio_priv(indio_dev);
int rc = 0;
u8 state;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_SYV_CHARGE_PRESENT:
*val1 = bq2589x_is_charge_present(bq);
break;
case PSY_IIO_SYV_CHARGE_ONLINE:
*val1 = bq2589x_is_charge_online(bq);
break;
case PSY_IIO_SYV_CHARGE_DONE:
*val1 = bq2589x_is_charge_done(bq);
break;
case PSY_IIO_SYV_CHAGER_HZ:
rc = bq2589x_get_hiz_mode(bq, &state);
if (!rc)
*val1 = (int)state;
break;
case PSY_IIO_SYV_INPUT_CURRENT_SETTLED:
*val1 = bq->cfg.input_current_limit;
break;
case PSY_IIO_SYV_INPUT_VOLTAGE_SETTLED:
*val1 = bq2589x_read_vindpm_volt(bq);
break;
case PSY_IIO_SYV_CHAGER_CURRENT:
*val1 = bq2589x_adc_read_charge_current(bq);
break;
case PSY_IIO_SYV_CHARGING_ENABLED:
*val1 = bq2589x_get_charger_enable(bq);
break;
case PSY_IIO_SYV_BUS_VOLTAGE:
*val1 = bq2589x_adc_read_vbus_volt(bq);
break;
case PSY_IIO_SYV_BATTERY_VOLTAGE:
*val1 = bq2589x_adc_read_battery_volt(bq);
break;
case PSY_IIO_SYV_OTG_ENABLE:
*val1 = bq->cfg.otg_status;
break;
case PSY_IIO_SYV_CHAGER_TERM:
*val1 = bq->cfg.term_current;
break;
case PSY_IIO_SYV_BATTERY_VOLTAGE_TERM:
*val1 = bq->cfg.battery_voltage_term;
break;
case PSY_IIO_SYV_CHARGER_STATUS:
*val1 = bq2589x_charge_status(bq);
break;
case PSY_IIO_SYV_CHARGE_TYPE:
*val1 = bq2589x_get_chg_type(bq);
;
break;
case PSY_IIO_SYV_CHARGE_USB_TYPE:
*val1 = bq2589x_get_chg_usb_type(bq);
;
break;
case PSY_IIO_SYV_ENABLE_CHAGER_TERM:
*val1 = bq->cfg.enable_term;
break;
default:
pr_debug("Unsupported QG IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0) {
pr_err_ratelimited("Couldn't read IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
return IIO_VAL_INT;
}
static int bq_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct bq2589x *bq = iio_priv(indio_dev);
int rc = 0;
union power_supply_propval val = {
0,
};
switch (chan->channel) {
case PSY_IIO_SYV_CHAGER_HZ:
if (val1) {
bq2589x_enter_hiz_mode(bq);
bq->hz_flag = true;
} else {
bq2589x_exit_hiz_mode(bq);
bq->hz_flag = false;
}
power_supply_changed(bq->batt_psy);
power_supply_changed(bq->usb_psy);
pr_err("iio_write: hz_flag %d\n", bq->hz_flag);
break;
case PSY_IIO_SYV_INPUT_CURRENT_SETTLED:
if (val1 >= 3100)
val1 = 3100;
bq2589x_update_bits(bq, BQ2589X_REG_00, BQ2589X_ENILIM_MASK,
BQ2589X_ENILIM_DISABLE
<< BQ2589X_ENILIM_SHIFT);
bq2589x_set_input_current_limit(bq, val1);
bq->cfg.input_current_limit = val1;
break;
case PSY_IIO_SYV_INPUT_VOLTAGE_SETTLED:
bq2589x_use_absolute_vindpm(bq, true);
bq2589x_set_input_volt_limit(bq, val1);
pr_err("bq2589x_set_input_volt_limit %d\n", val1);
break;
case PSY_IIO_SYV_CHAGER_CURRENT:
if (val1 >= 3100)
val1 = 3100;
bq->cfg.charge_current = val1;
bq2589x_set_charge_current(bq, val1);
break;
case PSY_IIO_SYV_CHARGING_ENABLED:
pr_err("%s: PSY_IIO_SYV_CHARGING_ENABLED, val = %d\n", __func__,
val1);
if (val1)
bq2589x_enable_charger(bq);
else
bq2589x_disable_charger(bq);
power_supply_changed(bq->batt_psy);
power_supply_changed(bq->usb_psy);
break;
case PSY_IIO_SYV_OTG_ENABLE:
if (!g_bq2589x) {
pr_err("%s: g_bq2589x is NULL\n", __func__);
return -EINVAL;
}
gpio_set_value(g_bq2589x->otg_gpio, val1);
pr_err("%s: PSY_IIO_SYV_OTG_ENABLE, val = %d\n", __func__,
val1);
if (val1) {
bq->cfg.otg_status = true;
bq2589x_exit_hiz_mode(bq);
bq2589x_disable_charger(bq);
bq2589x_enable_otg(bq);
bq2589x_set_otg_volt(bq, 5300);
if (bq->batt_psy)
power_supply_get_property(
bq->batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &val);
if (val.intval > 5)
bq2589x_set_otg_current(bq, 1800);
else
bq2589x_set_otg_current(bq, 1500);
if (!bq->wakeup_flag) {
__pm_stay_awake(bq->xm_ws);
bq->wakeup_flag = 1;
pr_err("otg workup\n");
}
} else {
bq->cfg.otg_status = false;
bq2589x_disable_otg(bq);
bq2589x_enable_charger(bq);
if (bq->wakeup_flag) {
__pm_relax(bq->xm_ws);
bq->wakeup_flag = 0;
pr_err("xm otg relax\n");
}
}
break;
case PSY_IIO_SYV_CHAGER_TERM:
bq->cfg.term_current = val1;
bq2589x_set_term_current(bq, val1);
break;
case PSY_IIO_SYV_BATTERY_VOLTAGE_TERM:
bq2589x_set_chargevoltage(bq, val1);
bq->cfg.battery_voltage_term = val1;
break;
case PSY_IIO_SYV_ENABLE_CHAGER_TERM:
bq->cfg.enable_term = val1;
bq2589x_enable_term(bq, bq->cfg.enable_term);
break;
default:
pr_debug("Unsupported BQ25890 IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err_ratelimited("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int bq_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct bq2589x *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(syv690d_iio_psy_channels); i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info syv690d_iio_info = {
.read_raw = bq_iio_read_raw,
.write_raw = bq_iio_write_raw,
.of_xlate = bq_iio_of_xlate,
};
int bq_init_iio_psy(struct bq2589x *chip)
{
struct iio_dev *indio_dev = chip->indio_dev;
struct iio_chan_spec *chan;
int num_iio_channels = ARRAY_SIZE(syv690d_iio_psy_channels);
int rc, i;
chip->iio_chan = devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->iio_chan), GFP_KERNEL);
if (!chip->iio_chan)
return -ENOMEM;
chip->int_iio_chans =
devm_kcalloc(chip->dev, num_iio_channels,
sizeof(*chip->int_iio_chans), GFP_KERNEL);
if (!chip->int_iio_chans)
return -ENOMEM;
indio_dev->info = &syv690d_iio_info;
indio_dev->dev.parent = chip->dev;
indio_dev->dev.of_node = chip->dev->of_node;
indio_dev->name = "syv690d,mainchg";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = chip->iio_chan;
indio_dev->num_channels = num_iio_channels;
for (i = 0; i < num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = syv690d_iio_psy_channels[i].channel_num;
chan->type = syv690d_iio_psy_channels[i].type;
chan->datasheet_name =
syv690d_iio_psy_channels[i].datasheet_name;
chan->extend_name = syv690d_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
syv690d_iio_psy_channels[i].info_mask;
}
rc = devm_iio_device_register(chip->dev, indio_dev);
if (rc)
pr_err("Failed to register QG IIO device, rc=%d\n", rc);
pr_err("BQ25890H IIO device, rc=%d\n", rc);
return rc;
}

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PD_RT17XX) += rt17xx_drv.o
rt17xx_drv-objs := rt17xx_iio.o xm_adapter_class.o xm_pd_adapter.o

View File

@ -0,0 +1,59 @@
#ifndef RT17XX_IIO_H
#define RT17XX_IIO_H
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/power_supply.h>
#include <linux/qti_power_supply.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
struct rt17xx_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define RT17XX_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define RT17XX_CHAN_ENERGY(_name, _num) \
RT17XX_IIO_CHAN(_name, _num, IIO_ENERGY, BIT(IIO_CHAN_INFO_PROCESSED))
static const struct rt17xx_iio_channels rt17xx_iio_psy_channels[] = {
RT17XX_CHAN_ENERGY("rt_pd_active", PSY_IIO_RT_PD_ACTIVE) RT17XX_CHAN_ENERGY(
"rt_pd_current_max",
PSY_IIO_RT_PD_CURRENT_MAX) RT17XX_CHAN_ENERGY("rt_pd_voltage_min",
PSY_IIO_RT_PD_VOLTAGE_MIN)
RT17XX_CHAN_ENERGY("rt_pd_voltage_max", PSY_IIO_RT_PD_VOLTAGE_MAX) RT17XX_CHAN_ENERGY(
"rt_pd_in_hard_reset",
PSY_IIO_RT_PD_IN_HARD_RESET) RT17XX_CHAN_ENERGY("rt_typec_cc_orientation",
PSY_IIO_RT_TYPEC_CC_ORIENTATION)
RT17XX_CHAN_ENERGY("rt_typec_mode", PSY_IIO_RT_TYPEC_MODE) RT17XX_CHAN_ENERGY(
"rt_pd_usb_suspend_supported",
PSY_IIO_RT_PD_USB_SUSPEND_SUPPORTED)
RT17XX_CHAN_ENERGY("rt_pd_apdo_volt_max",
PSY_IIO_RT_PD_APDO_VOLT_MAX)
RT17XX_CHAN_ENERGY(
"rt_pd_apdo_curr_max",
PSY_IIO_RT_PD_APDO_CURR_MAX)
RT17XX_CHAN_ENERGY(
"rt_pd_usb_real_type",
PSY_IIO_RT_PD_USB_REAL_TYPE)
RT17XX_CHAN_ENERGY(
"rt_typec_accessory_mode",
PSY_IIO_RT_TYPEC_ACCESSORY_MODE)
RT17XX_CHAN_ENERGY(
"rt_typec_adapter_id",
PSY_IIO_RT_TYPEC_ADAPTER_ID)
};
int rt17xx_init_iio_psy(struct xm_pd_adapter_info *info);
#endif /*RT17XX_IIO_H*/

View File

@ -0,0 +1,18 @@
#ifndef XM_ADAPTER_CLASS_H
#define XM_ADAPTER_CLASS_H
#define PD_ROLE_SINK_FOR_ADAPTER 0
#define PD_ROLE_SOURCE_FOR_ADAPTER 1
#define to_adapter_device(obj) container_of(obj, struct adapter_device, dev)
struct adapter_device *
adapter_device_register(const char *name, struct device *parent, void *devdata,
const struct adapter_ops *ops,
const struct adapter_properties *props);
void adapter_device_unregister(struct adapter_device *adapter_dev);
struct adapter_device *get_adapter_by_name(const char *name);
void adapter_class_exit(void);
int adapter_class_init(void);
#endif /*XM_ADAPTER_CLASS_H*/

View File

@ -0,0 +1,166 @@
#ifndef XM_PD_ADAPTER_H
#define XM_PD_ADAPTER_H
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/usb/tcpc/tcpm.h>
#include <linux/usb/tcpc/tcpci_config.h>
#include <linux/battmngr/battmngr_notifier.h>
#define ADAPTER_CAP_MAX_NR 10
extern struct rt1711_chip *g_tcpc_rt1711h;
struct adapter_power_cap {
uint8_t selected_cap_idx;
uint8_t nr;
uint8_t pdp;
uint8_t pwr_limit[ADAPTER_CAP_MAX_NR];
int max_mv[ADAPTER_CAP_MAX_NR];
int min_mv[ADAPTER_CAP_MAX_NR];
int ma[ADAPTER_CAP_MAX_NR];
int maxwatt[ADAPTER_CAP_MAX_NR];
int minwatt[ADAPTER_CAP_MAX_NR];
uint8_t type[ADAPTER_CAP_MAX_NR];
int info[ADAPTER_CAP_MAX_NR];
};
struct adapter_properties {
const char *alias_name;
};
enum uvdm_state {
USBPD_UVDM_DISCONNECT,
USBPD_UVDM_CHARGER_VERSION,
USBPD_UVDM_CHARGER_VOLTAGE,
USBPD_UVDM_CHARGER_TEMP,
USBPD_UVDM_SESSION_SEED,
USBPD_UVDM_AUTHENTICATION,
USBPD_UVDM_VERIFIED,
USBPD_UVDM_REMOVE_COMPENSATION,
USBPD_UVDM_REVERSE_AUTHEN,
USBPD_UVDM_CONNECT,
USBPD_UVDM_NAN_ACK,
};
enum adapter_cap_type {
XM_PD,
XM_PD_APDO,
XM_PD_APDO_REGAIN,
XM_CAP_TYPE_UNKNOWN,
};
#define USB_PD_MI_SVID 0x2717
#define USBPD_UVDM_SS_LEN 4
#define USBPD_UVDM_VERIFIED_LEN 1
#define USBPD_VDM_REQUEST 0x1
#define VDM_HDR(svid, cmd0, cmd1) \
(((svid) << 16) | (0 << 15) | ((cmd0) << 8) | (cmd1))
#define UVDM_HDR_CMD(hdr) ((hdr) & 0xFF)
struct usbpd_vdm_data {
int ta_version;
int ta_temp;
int ta_voltage;
bool reauth;
unsigned long s_secert[USBPD_UVDM_SS_LEN];
unsigned long digest[USBPD_UVDM_SS_LEN];
};
struct adapter_device {
struct adapter_properties props;
const struct adapter_ops *ops;
struct mutex ops_lock;
struct device dev;
struct srcu_notifier_head evt_nh;
void *driver_data;
uint32_t adapter_svid;
uint32_t adapter_id;
uint32_t adapter_fw_ver;
uint32_t adapter_hw_ver;
struct usbpd_vdm_data vdm_data;
int uvdm_state;
bool verify_process;
bool verifed;
uint8_t role;
uint8_t current_state;
uint32_t received_pdos[7];
};
struct adapter_ops {
int (*suspend)(struct adapter_device *dev, pm_message_t state);
int (*resume)(struct adapter_device *dev);
int (*get_cap)(struct adapter_device *dev, enum adapter_cap_type type,
struct adapter_power_cap *cap);
int (*get_svid)(struct adapter_device *dev);
int (*request_vdm_cmd)(struct adapter_device *dev, enum uvdm_state cmd,
unsigned char *data, unsigned int data_len);
int (*get_power_role)(struct adapter_device *dev);
int (*get_current_state)(struct adapter_device *dev);
int (*get_pdos)(struct adapter_device *dev);
int (*set_pd_verify_process)(struct adapter_device *dev,
int verify_in_process);
};
struct xm_pd_adapter_info {
struct device *dev;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
struct tcpc_device *tcpc;
struct notifier_block pd_nb;
struct adapter_device *adapter_dev;
struct task_struct *adapter_task;
const char *adapter_dev_name;
bool enable_kpoc_shdn;
struct tcpm_svid_list *adapter_svid_list;
struct power_supply *usb_psy;
struct power_supply *batt_psy;
int pd_active;
int pd_cur_max;
int pd_vol_min;
int pd_vol_max;
int pd_in_hard_reset;
int typec_cc_orientation;
int typec_mode;
int pd_usb_suspend_supported;
int pd_apdo_volt_max;
int pd_apdo_curr_max;
int pd_usb_real_type;
int typec_accessory_mode;
};
static inline void *
adapter_dev_get_drvdata(const struct adapter_device *adapter_dev)
{
return adapter_dev->driver_data;
}
static inline void adapter_dev_set_drvdata(struct adapter_device *adapter_dev,
void *data)
{
adapter_dev->driver_data = data;
}
int adapter_check_usb_psy(struct xm_pd_adapter_info *info);
int adapter_check_battery_psy(struct xm_pd_adapter_info *info);
#endif /*XM_PD_ADAPTER_H*/

View File

@ -0,0 +1,216 @@
#include "inc/xm_pd_adapter.h"
#include "inc/xm_adapter_class.h"
#include "inc/rt17xx_iio.h"
static int rt17xx_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct xm_pd_adapter_info *info = iio_priv(indio_dev);
int rc = 0;
switch (chan->channel) {
case PSY_IIO_RT_PD_ACTIVE:
if (info->pd_active == val1)
break;
info->pd_active = val1;
if (adapter_check_usb_psy(info) &&
adapter_check_battery_psy(info)) {
g_battmngr_noti->pd_msg.msg_type =
BATTMNGR_MSG_PD_ACTIVE;
g_battmngr_noti->pd_msg.pd_active = info->pd_active;
battmngr_notifier_call_chain(BATTMNGR_EVENT_PD,
g_battmngr_noti);
pr_err("%s pd_active: %d\n", __func__, info->pd_active);
}
break;
case PSY_IIO_RT_PD_CURRENT_MAX:
info->pd_cur_max = val1;
g_battmngr_noti->pd_msg.pd_curr_max = info->pd_cur_max;
pr_err("%s pd_curr_max: %d\n", __func__, info->pd_cur_max);
break;
case PSY_IIO_RT_PD_VOLTAGE_MIN:
info->pd_vol_min = val1;
break;
case PSY_IIO_RT_PD_VOLTAGE_MAX:
info->pd_vol_max = val1;
break;
case PSY_IIO_RT_PD_IN_HARD_RESET:
info->pd_in_hard_reset = val1;
break;
case PSY_IIO_RT_TYPEC_CC_ORIENTATION:
info->typec_cc_orientation = val1;
break;
case PSY_IIO_RT_TYPEC_MODE:
info->typec_mode = val1;
break;
case PSY_IIO_RT_PD_USB_SUSPEND_SUPPORTED:
info->pd_usb_suspend_supported = val1;
break;
case PSY_IIO_RT_PD_APDO_VOLT_MAX:
info->pd_apdo_volt_max = val1;
break;
case PSY_IIO_RT_PD_APDO_CURR_MAX:
info->pd_apdo_curr_max = val1;
break;
case PSY_IIO_RT_PD_USB_REAL_TYPE:
info->pd_usb_real_type = val1;
break;
case PSY_IIO_RT_TYPEC_ACCESSORY_MODE:
if (info->typec_accessory_mode == val1)
break;
info->typec_accessory_mode = val1;
pr_err("%s typec_accessory_mode: %d\n", __func__,
info->typec_accessory_mode);
g_battmngr_noti->pd_msg.msg_type = BATTMNGR_MSG_PD_AUDIO;
g_battmngr_noti->pd_msg.accessory_mode =
info->typec_accessory_mode;
battmngr_notifier_call_chain(BATTMNGR_EVENT_PD,
g_battmngr_noti);
break;
default:
pr_debug("Unsupported rt17xx IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err_ratelimited("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int rt17xx_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct xm_pd_adapter_info *info = iio_priv(indio_dev);
int rc = 0;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_RT_PD_ACTIVE:
*val1 = info->pd_active;
;
break;
case PSY_IIO_RT_PD_CURRENT_MAX:
*val1 = info->pd_cur_max;
break;
case PSY_IIO_RT_PD_VOLTAGE_MIN:
*val1 = info->pd_vol_min;
break;
case PSY_IIO_RT_PD_VOLTAGE_MAX:
*val1 = info->pd_vol_max;
;
break;
case PSY_IIO_RT_PD_IN_HARD_RESET:
*val1 = info->pd_in_hard_reset;
break;
case PSY_IIO_RT_TYPEC_CC_ORIENTATION:
*val1 = info->typec_cc_orientation;
break;
case PSY_IIO_RT_TYPEC_MODE:
*val1 = info->typec_mode;
break;
case PSY_IIO_RT_PD_USB_SUSPEND_SUPPORTED:
*val1 = info->pd_usb_suspend_supported;
break;
case PSY_IIO_RT_PD_APDO_VOLT_MAX:
*val1 = info->pd_apdo_volt_max;
break;
case PSY_IIO_RT_PD_APDO_CURR_MAX:
*val1 = info->pd_apdo_curr_max;
break;
case PSY_IIO_RT_PD_USB_REAL_TYPE:
*val1 = info->pd_usb_real_type;
break;
case PSY_IIO_RT_TYPEC_ACCESSORY_MODE:
*val1 = info->typec_accessory_mode;
break;
case PSY_IIO_RT_TYPEC_ADAPTER_ID:
*val1 = info->adapter_dev->adapter_id;
break;
default:
pr_debug("Unsupported rt17xx IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0) {
pr_err_ratelimited("Couldn't read IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
return IIO_VAL_INT;
}
static int rt17xx_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct xm_pd_adapter_info *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(rt17xx_iio_psy_channels); i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info rt17xx_iio_info = {
.read_raw = rt17xx_iio_read_raw,
.write_raw = rt17xx_iio_write_raw,
.of_xlate = rt17xx_iio_of_xlate,
};
int rt17xx_init_iio_psy(struct xm_pd_adapter_info *info)
{
struct iio_dev *indio_dev = info->indio_dev;
struct iio_chan_spec *chan;
int num_iio_channels = ARRAY_SIZE(rt17xx_iio_psy_channels);
int rc, i;
pr_err("rt17xx_init_iio_psy start\n");
info->iio_chan = devm_kcalloc(info->dev, num_iio_channels,
sizeof(*info->iio_chan), GFP_KERNEL);
if (!info->iio_chan)
return -ENOMEM;
info->int_iio_chans =
devm_kcalloc(info->dev, num_iio_channels,
sizeof(*info->int_iio_chans), GFP_KERNEL);
if (!info->int_iio_chans)
return -ENOMEM;
indio_dev->info = &rt17xx_iio_info;
indio_dev->dev.parent = info->dev;
indio_dev->dev.of_node = info->dev->of_node;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = info->iio_chan;
indio_dev->num_channels = num_iio_channels;
indio_dev->name = "rt17xx,pd";
for (i = 0; i < num_iio_channels; i++) {
info->int_iio_chans[i].indio_dev = indio_dev;
chan = &info->iio_chan[i];
info->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = rt17xx_iio_psy_channels[i].channel_num;
chan->type = rt17xx_iio_psy_channels[i].type;
chan->datasheet_name =
rt17xx_iio_psy_channels[i].datasheet_name;
chan->extend_name = rt17xx_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate = rt17xx_iio_psy_channels[i].info_mask;
}
rc = devm_iio_device_register(info->dev, indio_dev);
if (rc)
pr_err("Failed to register rt17xx IIO device, rc=%d\n", rc);
pr_err("rt17xx IIO device, rc=%d\n", rc);
return rc;
}

View File

@ -0,0 +1,727 @@
#include "inc/xm_pd_adapter.h"
#include "inc/xm_adapter_class.h"
#include "inc/rt17xx_iio.h"
static struct class *adapter_class;
static int log_level = 2;
#define class_err(fmt, ...) \
do { \
if (log_level >= 0) \
printk(KERN_ERR "[xm_adapter_class] " fmt, \
##__VA_ARGS__); \
} while (0)
#define class_info(fmt, ...) \
do { \
if (log_level >= 1) \
printk(KERN_ERR "[xm_adapter_class] " fmt, \
##__VA_ARGS__); \
} while (0)
#define class_dbg(fmt, ...) \
do { \
if (log_level >= 2) \
printk(KERN_ERR "[xm_adapter_class] " fmt, \
##__VA_ARGS__); \
} while (0)
static const char *const usbpd_state_strings[] = {
"UNKNOWN",
/******************* Source *******************/
#ifdef CONFIG_USB_PD_PE_SOURCE
"SRC_STARTUP",
"SRC_DISCOVERY",
"SRC_SEND_CAPABILITIES",
"SRC_NEGOTIATE_CAPABILITIES",
"SRC_TRANSITION_SUPPLY",
"SRC_TRANSITION_SUPPLY2",
"SRC_Ready",
"SRC_DISABLED",
"SRC_CAPABILITY_RESPONSE",
"SRC_HARD_RESET",
"SRC_HARD_RESET_RECEIVED",
"SRC_TRANSITION_TO_DEFAULT",
"SRC_GET_SINK_CAP",
"SRC_WAIT_NEW_CAPABILITIES",
"SRC_SEND_SOFT_RESET",
"SRC_SOFT_RESET",
/* Source Startup Discover Cable */
#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID
#ifdef CONFIG_PD_SRC_RESET_CABLE
"SRC_CBL_SEND_SOFT_RESET",
#endif /* CONFIG_PD_SRC_RESET_CABLE */
"SRC_VDM_IDENTITY_REQUEST",
"SRC_VDM_IDENTITY_ACKED",
"SRC_VDM_IDENTITY_NAKED",
#endif /* PD_CAP_PE_SRC_STARTUP_DISCOVER_ID */
/* Source for PD30 */
#ifdef CONFIG_USB_PD_REV30
"SRC_SEND_NOT_SUPPORTED",
"SRC_NOT_SUPPORTED_RECEIVED",
"SRC_CHUNK_RECEIVED",
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
"SRC_SEND_SOURCE_ALERT",
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
"SRC_SINK_ALERT_RECEIVED",
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
"SRC_GIVE_SOURCE_CAP_EXT",
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
"SRC_GIVE_SOURCE_STATUS",
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE
"SRC_GET_SINK_STATUS",
#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE
"SRC_GIVE_PPS_STATUS",
#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */
#endif /* CONFIG_USB_PD_REV30 */
#endif /* CONFIG_USB_PD_PE_SOURCE */
/******************* Sink *******************/
#ifdef CONFIG_USB_PD_PE_SINK
/* Sink Init */
"SNK_STARTUP",
"SNK_DISCOVERY",
"SNK_WAIT_FOR_CAPABILITIES",
"SNK_EVALUATE_CAPABILITY",
"SNK_SELECT_CAPABILITY",
"SNK_TRANSITION_SINK",
"SNK_Ready",
"SNK_HARD_RESET",
"SNK_TRANSITION_TO_DEFAULT",
"SNK_GIVE_SINK_CAP",
"SNK_GET_SOURCE_CAP",
"SNK_SEND_SOFT_RESET",
"SNK_SOFT_RESET",
/* Sink for PD30 */
#ifdef CONFIG_USB_PD_REV30
"SNK_SEND_NOT_SUPPORTED",
"SNK_NOT_SUPPORTED_RECEIVED",
"SNK_CHUNK_RECEIVED",
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
"SNK_SOURCE_ALERT_RECEIVED",
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
"SNK_SEND_SINK_ALERT",
#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
"SNK_GET_SOURCE_CAP_EXT",
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE
"SNK_GET_SOURCE_STATUS",
#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
"SNK_GIVE_SINK_STATUS",
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
"SNK_GET_PPS_STATUS",
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
#endif /* CONFIG_USB_PD_REV30 */
#endif /* CONFIG_USB_PD_PE_SINK */
/******************* DR_SWAP *******************/
#ifdef CONFIG_USB_PD_DR_SWAP
/* DR_SWAP_DFP */
"DRS_DFP_UFP_EVALUATE_DR_SWAP",
"DRS_DFP_UFP_ACCEPT_DR_SWAP",
"DRS_DFP_UFP_CHANGE_TO_UFP",
"DRS_DFP_UFP_SEND_DR_SWAP",
"DRS_DFP_UFP_REJECT_DR_SWAP",
/* DR_SWAP_UFP */
"DRS_UFP_DFP_EVALUATE_DR_SWAP",
"DRS_UFP_DFP_ACCEPT_DR_SWAP",
"DRS_UFP_DFP_CHANGE_TO_DFP",
"DRS_UFP_DFP_SEND_DR_SWAP",
"DRS_UFP_DFP_REJECT_DR_SWAP",
#endif /* CONFIG_USB_PD_DR_SWAP */
/******************* PR_SWAP *******************/
#ifdef CONFIG_USB_PD_PR_SWAP
/* PR_SWAP_SRC */
"PRS_SRC_SNK_EVALUATE_PR_SWAP",
"PRS_SRC_SNK_ACCEPT_PR_SWAP",
"PRS_SRC_SNK_TRANSITION_TO_OFF",
"PRS_SRC_SNK_ASSERT_RD",
"PRS_SRC_SNK_WAIT_SOURCE_ON",
"PRS_SRC_SNK_SEND_SWAP",
"PRS_SRC_SNK_REJECT_PR_SWAP",
/* PR_SWAP_SNK */
"PRS_SNK_SRC_EVALUATE_PR_SWAP",
"PRS_SNK_SRC_ACCEPT_PR_SWAP",
"PRS_SNK_SRC_TRANSITION_TO_OFF",
"PRS_SNK_SRC_ASSERT_RP",
"PRS_SNK_SRC_SOURCE_ON",
"PRS_SNK_SRC_SEND_SWAP",
"PRS_SNK_SRC_REJECT_SWAP",
/* get same role cap */
"DR_SRC_GET_SOURCE_CAP",
"DR_SRC_GIVE_SINK_CAP",
"DR_SNK_GET_SINK_CAP",
"DR_SNK_GIVE_SOURCE_CAP",
/* get same role cap for PD30 */
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
"DR_SNK_GIVE_SOURCE_CAP_EXT",
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
"DR_SRC_GET_SOURCE_CAP_EXT",
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#endif /* CONFIG_USB_PD_REV30 */
#endif /* CONFIG_USB_PD_PR_SWAP */
/******************* VCONN_SWAP *******************/
#ifdef CONFIG_USB_PD_VCONN_SWAP
"VCS_SEND_SWAP",
"VCS_EVALUATE_SWAP",
"VCS_ACCEPT_SWAP",
"VCS_REJECT_VCONN_SWAP",
"VCS_WAIT_FOR_VCONN",
"VCS_TURN_OFF_VCONN",
"VCS_TURN_ON_VCONN",
"VCS_SEND_PS_RDY",
#endif /* CONFIG_USB_PD_VCONN_SWAP */
/******************* UFP_VDM *******************/
"UFP_VDM_GET_IDENTITY",
"UFP_VDM_GET_SVIDS",
"UFP_VDM_GET_MODES",
"UFP_VDM_EVALUATE_MODE_ENTRY",
"UFP_VDM_MODE_EXIT",
"UFP_VDM_ATTENTION_REQUEST",
#ifdef CONFIG_USB_PD_ALT_MODE
"UFP_VDM_DP_STATUS_UPDATE",
"UFP_VDM_DP_CONFIGURE",
#endif /* CONFIG_USB_PD_ALT_MODE */
/******************* DFP_VDM *******************/
"DFP_UFP_VDM_IDENTITY_REQUEST",
"DFP_UFP_VDM_IDENTITY_ACKED",
"DFP_UFP_VDM_IDENTITY_NAKED",
"DFP_CBL_VDM_IDENTITY_REQUEST",
"DFP_CBL_VDM_IDENTITY_ACKED",
"DFP_CBL_VDM_IDENTITY_NAKED",
"DFP_VDM_SVIDS_REQUEST",
"DFP_VDM_SVIDS_ACKED",
"DFP_VDM_SVIDS_NAKED",
"DFP_VDM_MODES_REQUEST",
"DFP_VDM_MODES_ACKED",
"DFP_VDM_MODES_NAKED",
"DFP_VDM_MODE_ENTRY_REQUEST",
"DFP_VDM_MODE_ENTRY_ACKED",
"DFP_VDM_MODE_ENTRY_NAKED",
"DFP_VDM_MODE_EXIT_REQUEST",
"DFP_VDM_MODE_EXIT_ACKED",
"DFP_VDM_ATTENTION_REQUEST",
#ifdef CONFIG_PD_DFP_RESET_CABLE
"DFP_CBL_SEND_SOFT_RESET",
"DFP_CBL_SEND_CABLE_RESET",
#endif /* CONFIG_PD_DFP_RESET_CABLE */
#ifdef CONFIG_USB_PD_ALT_MODE_DFP
"DFP_VDM_DP_STATUS_UPDATE_REQUEST",
"DFP_VDM_DP_STATUS_UPDATE_ACKED",
"DFP_VDM_DP_STATUS_UPDATE_NAKED",
"DFP_VDM_DP_CONFIGURATION_REQUEST",
"DFP_VDM_DP_CONFIGURATION_ACKED",
"DFP_VDM_DP_CONFIGURATION_NAKED",
#endif /* CONFIG_USB_PD_ALT_MODE_DFP */
/******************* UVDM & SVDM *******************/
#ifdef CONFIG_USB_PD_CUSTOM_VDM
"UFP_UVDM_RECV",
"DFP_UVDM_SEND",
"DFP_UVDM_ACKED",
"DFP_UVDM_NAKED",
#endif /* CONFIG_USB_PD_CUSTOM_VDM */
/******************* PD30 Common *******************/
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE
"GET_BATTERY_CAP",
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL
"GIVE_BATTERY_CAP",
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE
"GET_BATTERY_STATUS",
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL
"GIVE_BATTERY_STATUS",
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE
"GET_MANUFACTURER_INFO",
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL
"GIVE_MANUFACTURER_INFO",
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE
"GET_COUNTRY_CODES",
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL
"GIVE_COUNTRY_CODES",
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE
"GET_COUNTRY_INFO",
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL
"GIVE_COUNTRY_INFO",
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */
"VDM_NOT_SUPPORTED",
#endif /* CONFIG_USB_PD_REV30 */
/******************* Others *******************/
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
"DBG_READY",
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */
#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER
"OVER_RECV_HRESET_LIMIT",
#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */
"REJECT",
"ERROR_RECOVERY",
#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE
"ERROR_RECOVERY_ONCE",
#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */
"BIST_TEST_DATA",
"BIST_CARRIER_MODE_2",
/* Wait tx finished */
"IDLE1",
"IDLE2",
"PD_NR_PE_STATES",
};
static ssize_t adapter_show_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
return snprintf(buf, 20, "%s\n",
adapter_dev->props.alias_name ?
adapter_dev->props.alias_name :
"anonymous");
}
static void adapter_device_release(struct device *dev)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
kfree(adapter_dev);
}
static ssize_t adapter_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_svid) {
adapter_dev->ops->get_svid(adapter_dev);
}
class_info("%s: adapter_id is %08x\n", __func__,
adapter_dev->adapter_id);
return snprintf(buf, PAGE_SIZE, "%08x\n", adapter_dev->adapter_id);
}
static DEVICE_ATTR_RO(adapter_id);
static ssize_t adapter_svid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_svid) {
adapter_dev->ops->get_svid(adapter_dev);
}
class_info("%s: adapter_svid is %04x\n", __func__,
adapter_dev->adapter_svid);
return snprintf(buf, PAGE_SIZE, "%04x\n", adapter_dev->adapter_svid);
}
static DEVICE_ATTR_RO(adapter_svid);
static int StringToHex(char *str, unsigned char *out, unsigned int *outlen)
{
char *p = str;
char high = 0, low = 0;
int tmplen = strlen(p), cnt = 0;
tmplen = strlen(p);
while (cnt < (tmplen / 2)) {
high = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ?
*p - 48 - 7 :
*p - 48;
low = (*(++p) > '9' && ((*p <= 'F') || (*p <= 'f'))) ?
*(p)-48 - 7 :
*(p)-48;
out[cnt] = ((high & 0x0f) << 4 | (low & 0x0f));
p++;
cnt++;
}
if (tmplen % 2 != 0)
out[cnt] = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ?
*p - 48 - 7 :
*p - 48;
if (outlen != NULL)
*outlen = tmplen / 2 + tmplen % 2;
return tmplen / 2 + tmplen % 2;
}
static ssize_t request_vdm_cmd_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
int cmd, ret;
unsigned char buffer[64];
unsigned char *data;
unsigned int count;
int i;
if (in_interrupt()) {
data = kmalloc(40, GFP_ATOMIC);
class_info("%s: kmalloc atomic ok.\n", __func__);
} else {
data = kmalloc(40, GFP_KERNEL);
class_info("%s: kmalloc kernel ok.\n", __func__);
}
memset(data, 0, 40);
ret = sscanf(buf, "%d,%s\n", &cmd, buffer);
class_info("%s:cmd:%d, buffer:%s\n", __func__, cmd, buffer);
StringToHex(buffer, data, &count);
class_info("%s:count = %d\n", __func__, count);
for (i = 0; i < count; i++)
class_info("%02x", data[i]);
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->request_vdm_cmd) {
adapter_dev->ops->request_vdm_cmd(adapter_dev, cmd, data,
count);
}
kfree(data);
return size;
}
static ssize_t request_vdm_cmd_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
int i;
char data[16], str_buf[128] = { 0 };
int cmd = adapter_dev->uvdm_state;
switch (cmd) {
case USBPD_UVDM_CHARGER_VERSION:
return snprintf(buf, PAGE_SIZE, "%d,%x\n", cmd,
adapter_dev->vdm_data.ta_version);
case USBPD_UVDM_CHARGER_TEMP:
return snprintf(buf, PAGE_SIZE, "%d,%d\n", cmd,
adapter_dev->vdm_data.ta_temp);
case USBPD_UVDM_CHARGER_VOLTAGE:
return snprintf(buf, PAGE_SIZE, "%d,%d\n", cmd,
adapter_dev->vdm_data.ta_voltage);
case USBPD_UVDM_SESSION_SEED:
case USBPD_UVDM_CONNECT:
case USBPD_UVDM_DISCONNECT:
case USBPD_UVDM_VERIFIED:
case USBPD_UVDM_REMOVE_COMPENSATION:
case USBPD_UVDM_NAN_ACK:
return snprintf(buf, PAGE_SIZE, "%d,Null\n", cmd);
case USBPD_UVDM_REVERSE_AUTHEN:
return snprintf(buf, PAGE_SIZE, "%d,%d", cmd,
adapter_dev->vdm_data.reauth);
case USBPD_UVDM_AUTHENTICATION:
for (i = 0; i < USBPD_UVDM_SS_LEN; i++) {
memset(data, 0, sizeof(data));
snprintf(data, sizeof(data), "%08lx",
adapter_dev->vdm_data.digest[i]);
strlcat(str_buf, data, sizeof(str_buf));
}
return snprintf(buf, PAGE_SIZE, "%d,%s\n", cmd, str_buf);
default:
class_err("feedbak cmd:%d is not support\n", cmd);
break;
}
return snprintf(buf, PAGE_SIZE, "%d,%s\n", cmd, str_buf);
}
static DEVICE_ATTR_RW(request_vdm_cmd);
static ssize_t verify_process_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
int val;
if (sscanf(buf, "%d\n", &val) != 1) {
adapter_dev->verify_process = 0;
return -EINVAL;
}
adapter_dev->verify_process = !!val;
class_info("%s: batterysecret verify process :%d\n", __func__,
adapter_dev->verify_process);
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->set_pd_verify_process) {
adapter_dev->ops->set_pd_verify_process(
adapter_dev, adapter_dev->verify_process);
}
return size;
}
static ssize_t verify_process_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", adapter_dev->verify_process);
}
static DEVICE_ATTR_RW(verify_process);
static ssize_t usbpd_verifed_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
int val = 0;
struct adapter_power_cap cap = { 0 };
if (sscanf(buf, "%d\n", &val) != 1) {
adapter_dev->verifed = 0;
return -EINVAL;
}
class_info("%s: batteryd set usbpd verifyed :%d\n", __func__, val);
adapter_dev->verifed = !!val;
if (adapter_dev->verifed) {
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_cap) {
adapter_dev->ops->get_cap(adapter_dev,
XM_PD_APDO_REGAIN, &cap);
}
}
return size;
}
static ssize_t usbpd_verifed_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
return snprintf(buf, PAGE_SIZE, "%d\n", adapter_dev->verifed);
}
static DEVICE_ATTR_RW(usbpd_verifed);
static ssize_t current_pr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
const char *pr = "none";
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_power_role) {
adapter_dev->ops->get_power_role(adapter_dev);
}
class_info("%s: current_pr is %d\n", __func__, adapter_dev->role);
if (adapter_dev->role == PD_ROLE_SINK_FOR_ADAPTER)
pr = "sink";
else if (adapter_dev->role == PD_ROLE_SOURCE_FOR_ADAPTER)
pr = "source";
return snprintf(buf, PAGE_SIZE, "%s\n", pr);
}
static DEVICE_ATTR_RO(current_pr);
static ssize_t current_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_current_state) {
adapter_dev->ops->get_current_state(adapter_dev);
}
class_err("%s: current_state is %d\n", __func__,
adapter_dev->current_state);
if (adapter_dev->current_state >=
(sizeof(usbpd_state_strings) / sizeof(usbpd_state_strings[0])))
adapter_dev->current_state = 0;
class_err("%s: %s\n", __func__,
usbpd_state_strings[adapter_dev->current_state]);
return snprintf(buf, PAGE_SIZE, "%s\n",
usbpd_state_strings[adapter_dev->current_state]);
}
static DEVICE_ATTR_RO(current_state);
static ssize_t pdo_n_show(struct device *dev, struct device_attribute *attr,
char *buf);
#define PDO_ATTR(n) \
{ \
.attr = { .name = __stringify(pdo##n), .mode = 0444 }, \
.show = pdo_n_show, \
}
static struct device_attribute dev_attr_pdos[] = {
PDO_ATTR(1), PDO_ATTR(2), PDO_ATTR(3), PDO_ATTR(4),
PDO_ATTR(5), PDO_ATTR(6), PDO_ATTR(7),
};
static ssize_t pdo_n_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct adapter_device *adapter_dev = to_adapter_device(dev);
int i;
if (adapter_dev != NULL && adapter_dev->ops != NULL &&
adapter_dev->ops->get_pdos) {
adapter_dev->ops->get_pdos(adapter_dev);
}
for (i = 0; i < ARRAY_SIZE(dev_attr_pdos); i++) {
if (attr == &dev_attr_pdos[i])
/* dump the PDO as a hex string */
return snprintf(buf, PAGE_SIZE, "%08x\n",
adapter_dev->received_pdos[i]);
}
class_err("%s: Invalid PDO index\n", __func__);
return -EINVAL;
}
static DEVICE_ATTR(name, 0444, adapter_show_name, NULL);
static struct attribute *adapter_class_attrs[] = {
&dev_attr_name.attr, &dev_attr_request_vdm_cmd.attr,
&dev_attr_current_state.attr, &dev_attr_adapter_id.attr,
&dev_attr_adapter_svid.attr, &dev_attr_verify_process.attr,
&dev_attr_usbpd_verifed.attr, &dev_attr_current_pr.attr,
&dev_attr_pdos[0].attr, &dev_attr_pdos[1].attr,
&dev_attr_pdos[2].attr, &dev_attr_pdos[3].attr,
&dev_attr_pdos[4].attr, &dev_attr_pdos[5].attr,
&dev_attr_pdos[6].attr, NULL,
};
static const struct attribute_group adapter_group = {
.attrs = adapter_class_attrs,
};
static const struct attribute_group *adapter_groups[] = {
&adapter_group,
NULL,
};
/**
* adapter_device_register - create and register a new object of
* adapter_device class.
* @name: the name of the new object
* @parent: a pointer to the parent device
* @devdata: an optional pointer to be stored for private driver use.
* The methods may retrieve it by using adapter_get_data(adapter_dev).
* @ops: the charger operations structure.
*
* Creates and registers new charger device. Returns either an
* ERR_PTR() or a pointer to the newly allocated device.
*/
struct adapter_device *
adapter_device_register(const char *name, struct device *parent, void *devdata,
const struct adapter_ops *ops,
const struct adapter_properties *props)
{
struct adapter_device *adapter_dev = NULL;
static struct lock_class_key key;
struct srcu_notifier_head *head = NULL;
int rc;
class_err("%s: name=%s\n", __func__, name);
adapter_dev = kzalloc(sizeof(*adapter_dev), GFP_KERNEL);
if (!adapter_dev)
return ERR_PTR(-ENOMEM);
mutex_init(&adapter_dev->ops_lock);
adapter_dev->dev.class = adapter_class;
adapter_dev->dev.parent = parent;
adapter_dev->dev.release = adapter_device_release;
dev_set_name(&adapter_dev->dev, "%s", name);
dev_set_drvdata(&adapter_dev->dev, devdata);
head = &adapter_dev->evt_nh;
srcu_init_notifier_head(head);
/* Rename srcu's lock to avoid LockProve warning */
lockdep_init_map(&(&head->srcu)->dep_map, name, &key, 0);
/* Copy properties */
if (props) {
memcpy(&adapter_dev->props, props,
sizeof(struct adapter_properties));
}
rc = device_register(&adapter_dev->dev);
if (rc) {
kfree(adapter_dev);
return ERR_PTR(rc);
}
adapter_dev->ops = ops;
return adapter_dev;
}
/**
* adapter_device_unregister - unregisters a switching charger device
* object.
* @adapter_dev: the switching charger device object to be unregistered
* and freed.
*
* Unregisters a previously registered via adapter_device_register object.
*/
void adapter_device_unregister(struct adapter_device *adapter_dev)
{
if (!adapter_dev)
return;
mutex_lock(&adapter_dev->ops_lock);
adapter_dev->ops = NULL;
mutex_unlock(&adapter_dev->ops_lock);
device_unregister(&adapter_dev->dev);
}
static int adapter_match_device_by_name(struct device *dev, const void *data)
{
const char *name = data;
return strcmp(dev_name(dev), name) == 0;
}
struct adapter_device *get_adapter_by_name(const char *name)
{
struct device *dev = NULL;
if (!name)
return (struct adapter_device *)NULL;
dev = class_find_device(adapter_class, NULL, name,
adapter_match_device_by_name);
return dev ? to_adapter_device(dev) : NULL;
}
void adapter_class_exit(void)
{
class_destroy(adapter_class);
}
int adapter_class_init(void)
{
adapter_class = class_create(THIS_MODULE, "Charging_Adapter");
if (IS_ERR(adapter_class)) {
class_err(
"Unable to create Charging Adapter class; errno = %ld\n",
PTR_ERR(adapter_class));
return PTR_ERR(adapter_class);
}
adapter_class->dev_groups = adapter_groups;
return 0;
}

View File

@ -0,0 +1,710 @@
#include "inc/xm_pd_adapter.h"
#include "inc/xm_adapter_class.h"
#include "inc/rt17xx_iio.h"
#define PROBE_CNT_MAX 50
int get_apdo_regain;
struct xm_pd_adapter_info *g_xm_pd_adapter;
EXPORT_SYMBOL(g_xm_pd_adapter);
static int log_level = 2;
#define adapter_err(fmt, ...) \
do { \
if (log_level >= 0) \
printk(KERN_ERR "[xm_pd_adapter] " fmt, \
##__VA_ARGS__); \
} while (0)
#define adapter_info(fmt, ...) \
do { \
if (log_level >= 1) \
printk(KERN_ERR "[xm_pd_adapter] " fmt, \
##__VA_ARGS__); \
} while (0)
#define adapter_dbg(fmt, ...) \
do { \
if (log_level >= 2) \
printk(KERN_ERR "[xm_pd_adapter] " fmt, \
##__VA_ARGS__); \
} while (0)
int adapter_check_usb_psy(struct xm_pd_adapter_info *info)
{
if (!info->usb_psy) {
info->usb_psy = power_supply_get_by_name("usb");
if (!info->usb_psy) {
pr_err("usb psy not found!\n");
return false;
}
}
return true;
}
int adapter_check_battery_psy(struct xm_pd_adapter_info *info)
{
if (!info->batt_psy) {
info->batt_psy = power_supply_get_by_name("battery");
if (!info->batt_psy) {
pr_err("batt psy not found!\n");
return false;
}
}
return true;
}
static void usbpd_mi_vdm_received(struct xm_pd_adapter_info *pinfo,
struct tcp_ny_uvdm uvdm)
{
int i, cmd;
if (uvdm.uvdm_svid != USB_PD_MI_SVID)
return;
cmd = UVDM_HDR_CMD(uvdm.uvdm_data[0]);
adapter_info("cmd = %d\n", cmd);
adapter_info(
"uvdm.ack: %d, uvdm.uvdm_cnt: %d, uvdm.uvdm_svid: 0x%04x\n",
uvdm.ack, uvdm.uvdm_cnt, uvdm.uvdm_svid);
switch (cmd) {
case USBPD_UVDM_CHARGER_VERSION:
pinfo->adapter_dev->vdm_data.ta_version = uvdm.uvdm_data[1];
adapter_info("ta_version:%x\n",
pinfo->adapter_dev->vdm_data.ta_version);
break;
case USBPD_UVDM_CHARGER_TEMP:
pinfo->adapter_dev->vdm_data.ta_temp =
(uvdm.uvdm_data[1] & 0xFFFF) * 10;
adapter_info("pinfo->adapter_dev->vdm_data.ta_temp:%d\n",
pinfo->adapter_dev->vdm_data.ta_temp);
break;
case USBPD_UVDM_CHARGER_VOLTAGE:
pinfo->adapter_dev->vdm_data.ta_voltage =
(uvdm.uvdm_data[1] & 0xFFFF) * 10;
pinfo->adapter_dev->vdm_data.ta_voltage *= 1000; /*V->mV*/
adapter_info("ta_voltage:%d\n",
pinfo->adapter_dev->vdm_data.ta_voltage);
break;
case USBPD_UVDM_SESSION_SEED:
for (i = 0; i < USBPD_UVDM_SS_LEN; i++) {
pinfo->adapter_dev->vdm_data.s_secert[i] =
uvdm.uvdm_data[i + 1];
adapter_info("usbpd s_secert uvdm.uvdm_data[%d]=0x%x",
i + 1, uvdm.uvdm_data[i + 1]);
}
break;
case USBPD_UVDM_AUTHENTICATION:
for (i = 0; i < USBPD_UVDM_SS_LEN; i++) {
pinfo->adapter_dev->vdm_data.digest[i] =
uvdm.uvdm_data[i + 1];
adapter_info("usbpd digest[%d]=0x%x", i + 1,
uvdm.uvdm_data[i + 1]);
}
break;
case USBPD_UVDM_REVERSE_AUTHEN:
pinfo->adapter_dev->vdm_data.reauth =
(uvdm.uvdm_data[1] & 0xFFFF);
break;
default:
break;
}
pinfo->adapter_dev->uvdm_state = cmd;
}
static int pd_tcp_notifier_call(struct notifier_block *pnb, unsigned long event,
void *data)
{
struct tcp_notify *noti = data;
struct xm_pd_adapter_info *pinfo;
pinfo = container_of(pnb, struct xm_pd_adapter_info, pd_nb);
adapter_err("PD charger event:%d %d\n", (int)event,
(int)noti->pd_state.connected);
switch (event) {
case TCP_NOTIFY_PD_STATE:
switch (noti->pd_state.connected) {
case PD_CONNECT_NONE:
pinfo->adapter_dev->adapter_id = 0;
pinfo->adapter_dev->adapter_svid = 0;
pinfo->adapter_dev->uvdm_state = USBPD_UVDM_DISCONNECT;
pinfo->adapter_dev->verifed = 0;
pinfo->adapter_dev->verify_process = 0;
break;
case PD_CONNECT_PE_READY_SNK_PD30:
pinfo->adapter_dev->uvdm_state = USBPD_UVDM_CONNECT;
break;
case PD_CONNECT_PE_READY_SNK_APDO:
get_apdo_regain = 1;
pinfo->adapter_dev->uvdm_state = USBPD_UVDM_CONNECT;
break;
};
break;
case TCP_NOTIFY_UVDM:
adapter_info("%s: tcpc received uvdm message.\n", __func__);
usbpd_mi_vdm_received(pinfo, noti->uvdm_msg);
break;
}
return NOTIFY_OK;
}
static int pd_get_svid(struct adapter_device *dev)
{
struct xm_pd_adapter_info *info;
struct pd_source_cap_ext cap_ext;
int ret;
int i = 0;
uint32_t pd_vdos[8];
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL)
return -EINVAL;
adapter_info("%s: enter\n", __func__);
if (info->adapter_dev->adapter_svid != 0)
return 0;
if (info->adapter_svid_list == NULL) {
if (in_interrupt()) {
info->adapter_svid_list = kmalloc(
sizeof(struct tcpm_svid_list), GFP_ATOMIC);
} else {
info->adapter_svid_list = kmalloc(
sizeof(struct tcpm_svid_list), GFP_KERNEL);
}
if (info->adapter_svid_list == NULL)
adapter_err("[%s] adapter_svid_list is still NULL!\n",
__func__);
}
ret = tcpm_inquire_pd_partner_inform(info->tcpc, pd_vdos);
if (ret == TCPM_SUCCESS) {
adapter_info("find adapter id success.\n");
for (i = 0; i < 8; i++)
adapter_info("VDO[%d] : %08x\n", i, pd_vdos[i]);
info->adapter_dev->adapter_svid = pd_vdos[0] & 0x0000FFFF;
info->adapter_dev->adapter_id = pd_vdos[2] & 0x0000FFFF;
adapter_info("adapter_svid = %04x\n",
info->adapter_dev->adapter_svid);
adapter_info("adapter_id = %08x\n",
info->adapter_dev->adapter_id);
ret = tcpm_inquire_pd_partner_svids(info->tcpc,
info->adapter_svid_list);
adapter_info("[%s] tcpm_inquire_pd_partner_svids, ret=%d!\n",
__func__, ret);
if (ret == TCPM_SUCCESS) {
adapter_info("discover svid number is %d\n",
info->adapter_svid_list->cnt);
for (i = 0; i < info->adapter_svid_list->cnt; i++) {
adapter_info("SVID[%d] : %04x\n", i,
info->adapter_svid_list->svids[i]);
if (info->adapter_svid_list->svids[i] ==
USB_PD_MI_SVID)
info->adapter_dev->adapter_svid =
USB_PD_MI_SVID;
}
}
} else {
ret = tcpm_dpm_pd_get_source_cap_ext(info->tcpc, NULL,
&cap_ext);
if (ret == TCPM_SUCCESS) {
info->adapter_dev->adapter_svid =
cap_ext.vid & 0x0000FFFF;
info->adapter_dev->adapter_id =
cap_ext.pid & 0x0000FFFF;
info->adapter_dev->adapter_fw_ver =
cap_ext.fw_ver & 0x0000FFFF;
info->adapter_dev->adapter_hw_ver =
cap_ext.hw_ver & 0x0000FFFF;
adapter_info("adapter_svid = %04x\n",
info->adapter_dev->adapter_svid);
adapter_info("adapter_id = %08x\n",
info->adapter_dev->adapter_id);
adapter_info("adapter_fw_ver = %08x\n",
info->adapter_dev->adapter_fw_ver);
adapter_info("adapter_hw_ver = %08x\n",
info->adapter_dev->adapter_hw_ver);
} else {
adapter_err("[%s] get adapter message failed!\n",
__func__);
return ret;
}
}
return 0;
}
#define BSWAP_32(x) \
(u32)((((u32)(x) & 0xff000000) >> 24) | \
(((u32)(x) & 0x00ff0000) >> 8) | \
(((u32)(x) & 0x0000ff00) << 8) | \
(((u32)(x) & 0x000000ff) << 24))
static void usbpd_sha256_bitswap32(unsigned int *array, int len)
{
int i;
for (i = 0; i < len; i++)
array[i] = BSWAP_32(array[i]);
}
void charToint(char *str, int input_len, unsigned int *out,
unsigned int *outlen)
{
int i;
if (outlen != NULL)
*outlen = 0;
for (i = 0; i < (input_len / 4 + 1); i++) {
out[i] = ((str[i * 4 + 3] * 0x1000000) |
(str[i * 4 + 2] * 0x10000) |
(str[i * 4 + 1] * 0x100) | str[i * 4]);
*outlen = *outlen + 1;
}
adapter_info("%s: outlen = %d\n", __func__, *outlen);
for (i = 0; i < *outlen; i++)
adapter_info("%s: out[%d] = %08x\n", __func__, i, out[i]);
adapter_info("%s: char to int done.\n", __func__);
}
static int tcp_dpm_event_cb_uvdm(struct tcpc_device *tcpc, int ret,
struct tcp_dpm_event *event)
{
int i;
struct tcp_dpm_custom_vdm_data vdm_data = event->tcp_dpm_data.vdm_data;
adapter_info("%s: vdm_data.cnt = %d\n", __func__, vdm_data.cnt);
for (i = 0; i < vdm_data.cnt; i++)
adapter_info("%s vdm_data.vdos[%d] = 0x%08x", __func__, i,
vdm_data.vdos[i]);
return 0;
}
const struct tcp_dpm_event_cb_data cb_data = {
.event_cb = tcp_dpm_event_cb_uvdm,
};
static int pd_request_vdm_cmd(struct adapter_device *dev, enum uvdm_state cmd,
unsigned char *data, unsigned int data_len)
{
u32 vdm_hdr = 0;
int rc = 0;
struct tcp_dpm_custom_vdm_data *vdm_data;
struct xm_pd_adapter_info *info;
unsigned int *int_data;
unsigned int outlen;
int i;
if (in_interrupt()) {
int_data = kmalloc(40, GFP_ATOMIC);
vdm_data = kmalloc(sizeof(*vdm_data), GFP_ATOMIC);
adapter_info("%s: kmalloc atomic ok.\n", __func__);
} else {
int_data = kmalloc(40, GFP_KERNEL);
vdm_data = kmalloc(sizeof(*vdm_data), GFP_KERNEL);
adapter_info("%s: kmalloc kernel ok.\n", __func__);
}
memset(int_data, 0, 40);
charToint(data, data_len, int_data, &outlen);
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL || info->tcpc == NULL) {
rc = -EINVAL;
goto release_req;
}
vdm_hdr = VDM_HDR(info->adapter_dev->adapter_svid, USBPD_VDM_REQUEST,
cmd);
vdm_data->wait_resp = true;
vdm_data->vdos[0] = vdm_hdr;
switch (cmd) {
case USBPD_UVDM_CHARGER_VERSION:
case USBPD_UVDM_CHARGER_TEMP:
case USBPD_UVDM_CHARGER_VOLTAGE:
vdm_data->cnt = 1;
rc = tcpm_dpm_send_custom_vdm(info->tcpc, vdm_data,
&cb_data); //&tcp_dpm_evt_cb_null
if (rc < 0) {
adapter_err("failed to send %d\n", cmd);
goto release_req;
}
break;
case USBPD_UVDM_VERIFIED:
case USBPD_UVDM_REMOVE_COMPENSATION:
vdm_data->cnt = 1 + USBPD_UVDM_VERIFIED_LEN;
for (i = 0; i < USBPD_UVDM_VERIFIED_LEN; i++)
vdm_data->vdos[i + 1] = int_data[i];
adapter_info("verify-0: %08x\n", vdm_data->vdos[1]);
rc = tcpm_dpm_send_custom_vdm(info->tcpc, vdm_data,
&cb_data); //&tcp_dpm_evt_cb_null
if (rc < 0) {
adapter_err("failed to send %d\n", cmd);
goto release_req;
}
break;
case USBPD_UVDM_SESSION_SEED:
case USBPD_UVDM_AUTHENTICATION:
case USBPD_UVDM_REVERSE_AUTHEN:
usbpd_sha256_bitswap32(int_data, USBPD_UVDM_SS_LEN);
vdm_data->cnt = 1 + USBPD_UVDM_SS_LEN;
for (i = 0; i < USBPD_UVDM_SS_LEN; i++)
vdm_data->vdos[i + 1] = int_data[i];
for (i = 0; i < USBPD_UVDM_SS_LEN; i++)
adapter_info("%08x\n", vdm_data->vdos[i + 1]);
rc = tcpm_dpm_send_custom_vdm(info->tcpc, vdm_data,
&cb_data); //&tcp_dpm_evt_cb_null
if (rc < 0) {
adapter_err("failed to send %d\n", cmd);
goto release_req;
}
break;
default:
adapter_err("cmd:%d is not support\n", cmd);
break;
}
release_req:
if (int_data != NULL)
kfree(int_data);
if (vdm_data != NULL)
kfree(vdm_data);
return rc;
}
static int pd_get_power_role(struct adapter_device *dev)
{
struct xm_pd_adapter_info *info;
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL || info->tcpc == NULL)
return -EINVAL;
info->adapter_dev->role = tcpm_inquire_pd_power_role(info->tcpc);
adapter_err("[%s] power role is %d\n", __func__,
info->adapter_dev->role);
return 0;
}
static int pd_get_current_state(struct adapter_device *dev)
{
struct xm_pd_adapter_info *info;
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL || info->tcpc == NULL)
return -EINVAL;
info->adapter_dev->current_state =
tcpm_inquire_pd_state_curr(info->tcpc);
adapter_err("[%s] current state is %d\n", __func__,
info->adapter_dev->current_state);
return 0;
}
static int pd_get_pdos(struct adapter_device *dev)
{
struct xm_pd_adapter_info *info;
struct tcpm_power_cap cap;
int ret, i;
int pd_wait_cnt = 0;
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL || info->tcpc == NULL)
return -EINVAL;
ret = tcpm_inquire_pd_source_cap(info->tcpc, &cap);
adapter_err("[%s] test01 tcpm_inquire_pd_source_cap is %d.\n", __func__,
ret);
if (ret < 0) {
for (pd_wait_cnt = 0; pd_wait_cnt < 25; pd_wait_cnt++) {
msleep(20);
adapter_err(
"retry [%s] tcpm_inquire_pd_source_cap is %d. cnt =%d\n",
__func__, ret, pd_wait_cnt);
ret = tcpm_inquire_pd_source_cap(info->tcpc, &cap);
if (ret == 0)
break;
}
}
for (i = 0; i < 7; i++) {
info->adapter_dev->received_pdos[i] = cap.pdos[i];
adapter_err(
"[%s]: pdo[%d] { received_pdos is %08x, cap.pdos is %08x}\n",
__func__, i, info->adapter_dev->received_pdos[i],
cap.pdos[i]);
}
return 0;
}
static int pd_set_pd_verify_process(struct adapter_device *dev,
int verify_in_process)
{
int ret = 0;
//union power_supply_propval val = {0,};
//struct power_supply *usb_psy = NULL;
adapter_err("[%s] pd verify in process:%d\n", __func__,
verify_in_process);
/*
usb_psy = power_supply_get_by_name("usb");
if (usb_psy) {
val.intval = verify_in_process;
ret = power_supply_set_property(usb_psy,
POWER_SUPPLY_PROP_PD_VERIFY_IN_PROCESS, &val);
} else {
adapter_err("[%s] usb psy not found!\n", __func__);
}
*/
return ret;
}
static int pd_get_cap(struct adapter_device *dev, enum adapter_cap_type type,
struct adapter_power_cap *tacap)
{
int ret;
int i;
int timeout = 0;
struct xm_pd_adapter_info *info;
struct tcpm_remote_power_cap pd_cap;
info = (struct xm_pd_adapter_info *)adapter_dev_get_drvdata(dev);
if (info == NULL || info->tcpc == NULL)
return -EINVAL;
if (info->adapter_dev->verify_process)
return -1;
if (type == XM_PD) {
APDO_REGAIN:
pd_cap.nr = 0;
pd_cap.selected_cap_idx = 0;
tcpm_get_remote_power_cap(info->tcpc, &pd_cap);
tacap->nr = pd_cap.nr;
tacap->selected_cap_idx = pd_cap.selected_cap_idx - 1;
adapter_err("[%s] nr:%d idx:%d\n", __func__, pd_cap.nr,
pd_cap.selected_cap_idx - 1);
for (i = 0; i < pd_cap.nr; i++) {
tacap->ma[i] = pd_cap.ma[i];
tacap->max_mv[i] = pd_cap.max_mv[i];
tacap->min_mv[i] = pd_cap.min_mv[i];
tacap->maxwatt[i] = tacap->max_mv[i] * tacap->ma[i];
tacap->type[i] = pd_cap.type[i];
adapter_err(
"[%s]:%d mv:[%d,%d] %d max:%d min:%d type:%d %d\n",
__func__, i, tacap->min_mv[i], tacap->max_mv[i],
tacap->ma[i], tacap->maxwatt[i],
tacap->minwatt[i], tacap->type[i],
pd_cap.type[i]);
}
} else if (type == XM_PD_APDO_REGAIN) {
get_apdo_regain = 0;
ret = tcpm_dpm_pd_get_source_cap(info->tcpc, NULL);
if (ret == TCPM_SUCCESS) {
while (timeout < 10) {
if (get_apdo_regain) {
adapter_err(
"[%s] ready to get pps info!\n",
__func__);
goto APDO_REGAIN;
} else {
msleep(100);
timeout++;
}
}
adapter_err("[%s] ready to get pps info - for test!\n",
__func__);
goto APDO_REGAIN;
} else {
adapter_err("[%s] tcpm_dpm_pd_get_source_cap failed!\n",
__func__);
return -EINVAL;
}
}
adapter_err("[%s] tacap->nr is %d\n", __func__, tacap->nr);
return 0;
}
static const struct adapter_ops adapter_ops = {
.get_cap = pd_get_cap,
.get_svid = pd_get_svid,
.request_vdm_cmd = pd_request_vdm_cmd,
.get_power_role = pd_get_power_role,
.get_current_state = pd_get_current_state,
.get_pdos = pd_get_pdos,
.set_pd_verify_process = pd_set_pd_verify_process,
};
static int adapter_parse_dt(struct xm_pd_adapter_info *info, struct device *dev)
{
struct device_node *np = dev->of_node;
adapter_err("%s\n", __func__);
if (!np) {
adapter_err("%s: no device node\n", __func__);
return -EINVAL;
}
if (of_property_read_string(np, "adapter_name",
&info->adapter_dev_name) < 0)
adapter_err("%s: no adapter name\n", __func__);
return 0;
}
static int xm_pd_adapter_probe(struct platform_device *pdev)
{
int ret = 0;
struct iio_dev *indio_dev;
struct xm_pd_adapter_info *info = NULL;
static int probe_cnt = 0;
adapter_err("%s probe_cnt = %d\n", __func__, ++probe_cnt);
indio_dev = devm_iio_device_alloc(&pdev->dev,
sizeof(struct xm_pd_adapter_info));
if (!indio_dev) {
adapter_err("Failed to allocate memory\n");
return -ENOMEM;
}
info = iio_priv(indio_dev);
info->indio_dev = indio_dev;
info->dev = &pdev->dev;
platform_set_drvdata(pdev, info);
if (!g_tcpc_rt1711h || !g_battmngr) {
adapter_err("%s: tcpc_rt1711h or g_battmngr not ready, defer\n",
__func__);
ret = -EPROBE_DEFER;
msleep(100);
if (probe_cnt >= PROBE_CNT_MAX)
goto out;
else
goto err_g_tcpc_rt1711h;
}
ret = rt17xx_init_iio_psy(info);
if (ret < 0)
pr_err("Failed to initialize RT17XX IIO PSY, rc=%d\n", ret);
ret = adapter_class_init();
if (ret < 0) {
pr_err("Failed to initialize adapter class, rc=%d\n", ret);
adapter_class_exit();
return -EINVAL;
}
adapter_check_usb_psy(info);
adapter_check_battery_psy(info);
ret = adapter_parse_dt(info, &pdev->dev);
if (ret < 0)
pr_err("Failed to initialize adapter parse dt, rc=%d\n", ret);
info->adapter_dev = adapter_device_register(
info->adapter_dev_name, &pdev->dev, info, &adapter_ops, NULL);
if (IS_ERR_OR_NULL(info->adapter_dev)) {
ret = PTR_ERR(info->adapter_dev);
goto err_register_adapter_dev;
}
adapter_dev_set_drvdata(info->adapter_dev, info);
info->tcpc = tcpc_dev_get_by_name("type_c_port0");
if (!info->tcpc) {
adapter_info("%s: tcpc device not ready, defer\n", __func__);
ret = -EPROBE_DEFER;
msleep(100);
if (probe_cnt >= PROBE_CNT_MAX)
goto out;
else
goto err_get_tcpc_dev;
}
info->pd_nb.notifier_call = pd_tcp_notifier_call;
ret = register_tcp_dev_notifier(info->tcpc, &info->pd_nb,
TCP_NOTIFY_TYPE_USB |
TCP_NOTIFY_TYPE_MISC |
TCP_NOTIFY_TYPE_MODE);
if (ret < 0) {
adapter_info("%s: register tcpc notifer fail\n", __func__);
return -EINVAL;
}
g_xm_pd_adapter = info;
pr_err("%s: End!\n", __func__);
out:
platform_set_drvdata(pdev, info);
adapter_err("%s %s!!\n", __func__,
ret == -EPROBE_DEFER ? "Over probe cnt max" : "OK");
return 0;
err_register_adapter_dev:
err_get_tcpc_dev:
adapter_device_unregister(info->adapter_dev);
adapter_class_exit();
err_g_tcpc_rt1711h:
return ret;
}
static int xm_pd_adapter_remove(struct platform_device *pdev)
{
adapter_device_unregister(g_xm_pd_adapter->adapter_dev);
adapter_class_exit();
devm_kfree(&pdev->dev, g_xm_pd_adapter);
return 0;
}
static const struct of_device_id xm_pd_adapter_of_match[] = {
{
.compatible = "xiaomi,pd_adapter",
},
{},
};
MODULE_DEVICE_TABLE(of, xm_pd_adapter_of_match);
static struct platform_driver xm_pd_adapter_driver = {
.probe = xm_pd_adapter_probe,
.remove = xm_pd_adapter_remove,
.driver = {
.name = "xm_pd_adapter",
.of_match_table = xm_pd_adapter_of_match,
},
};
static int __init xm_pd_adapter_init(void)
{
return platform_driver_register(&xm_pd_adapter_driver);
}
module_init(xm_pd_adapter_init);
static void __exit xm_pd_adapter_exit(void)
{
platform_driver_unregister(&xm_pd_adapter_driver);
}
module_exit(xm_pd_adapter_exit);
MODULE_DESCRIPTION("Xiaomi PD Adapter Driver");
MODULE_AUTHOR("getian@xiaomi.com");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1 @@
# SPDX-License-Identifier: GPL-2.0

View File

@ -0,0 +1 @@
# SPDX-License-Identifier: GPL-2.0

View File

@ -98,8 +98,15 @@ config TYPEC_QCOM_PMIC
It will also enable the VBUS output to connected devices when a
DFP connection is made.
config XM_USB_PDPHY
tristate "Xiaomi usb pdphy config"
help
Say Y or M here to enable xiaomi usb pdphy"
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
source "drivers/usb/typec/platform/external/tcpc/Kconfig"
endif # TYPEC

View File

@ -9,3 +9,4 @@ obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o
obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o
obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
obj-$(CONFIG_TYPEC) += mux/
obj-$(CONFIG_XM_USB_PDPHY) += platform/

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_USB_PDPHY) += external/
obj-$(CONFIG_MTK_USB_PDPHY) += mediatek/
obj-$(CONFIG_QCOM_USB_PDPHY) += qualcomm/

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XM_USB_PDPHY) += tcpc/

View File

@ -0,0 +1,62 @@
#
# TypeC Port Controller Device Configuration
#
config TCPC_CLASS
tristate "TypeC Port Controller Device Class"
depends on TYPEC
default n
help
Say Y to enable
Typec Port
Controller Device
Class
config USB_POWER_DELIVERY
tristate "Support USB power delivery Function"
depends on TCPC_CLASS
default n
help
Say Y to enable
USB
Power Delivery
support
config DUAL_ROLE_USB_INTF
tristate "Support DUAL ROLE USB INTF Function"
depends on TCPC_CLASS
default n
help
Say Y to enable
Dual Role USB Intf
support
config TCPC_RT1711H
tristate "Richtek RT1711H TypeC port Controller Driver"
depends on TCPC_CLASS
default n
help
Say Y to enable
Richtek RT1711H
TypeC port Controller
Driver
config USB_PD_VBUS_STABLE_TOUT
int "PD VBUS Stable Timeout"
depends on USB_POWER_DELIVERY
range 0 1000 # >= 0, <= 1000
default 125
help
Setup a timeout value (ms)
for
VBUS change
stable
config PD_DBG_INFO
bool "PD debug information"
depends on TCPC_CLASS
default y
help
Say Y to enable PD debug
information
Say N to disable

View File

@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
subdir-ccflags-y += -Wall -Werror -DCONFIG_RT_REGMAP
obj-$(CONFIG_DUAL_ROLE_USB_INTF) += class_dual_role.o
obj-$(CONFIG_TCPC_RT1711H) += tcpc_rt1711h.o
obj-$(CONFIG_XM_USB_PDPHY) += tcpci_drv.o
tcpci_drv-objs := tcpci_core.o tcpci_typec.o tcpci_timer.o tcpm.o tcpci.o \
pd_dbg_info.o tcpci_alert.o rt-regmap.o tcpci_dual_role.o
ifdef CONFIG_USB_POWER_DELIVERY
tcpci_drv-objs += tcpci_event.o \
pd_core.o pd_policy_engine.o pd_process_evt.o \
pd_dpm_core.o pd_dpm_uvdm.o pd_dpm_alt_mode_dp.o pd_dpm_pdo_select.o\
pd_dpm_reaction.o \
pd_process_evt_snk.o pd_process_evt_src.o pd_process_evt_vdm.o \
pd_process_evt_drs.o pd_process_evt_prs.o pd_process_evt_vcs.o \
pd_process_evt_dbg.o pd_process_evt_tcp.o pd_process_evt_com.o \
pd_policy_engine_src.o pd_policy_engine_snk.o pd_policy_engine_ufp.o pd_policy_engine_vcs.o \
pd_policy_engine_dfp.o pd_policy_engine_dr.o pd_policy_engine_drs.o pd_policy_engine_prs.o \
pd_policy_engine_dbg.o pd_policy_engine_com.o pd_dpm_alt_mode_dc.o
endif
obj-$(CONFIG_TCPC_CLASS) += rt_pd_manager.o

View File

@ -0,0 +1,515 @@
/*
* class-dual-role.c
*
* Copyright (C) 2015 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/ctype.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/types.h>
#include <linux/usb/tcpc/class-dual-role.h>
#define DUAL_ROLE_NOTIFICATION_TIMEOUT 2000
static ssize_t dual_role_store_property(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
static ssize_t dual_role_show_property(struct device *dev,
struct device_attribute *attr,
char *buf);
#define DUAL_ROLE_ATTR(_name) \
{ \
.attr = { .name = #_name }, .show = dual_role_show_property, \
.store = dual_role_store_property, \
}
static struct device_attribute dual_role_attrs[] = {
DUAL_ROLE_ATTR(supported_modes), DUAL_ROLE_ATTR(mode),
DUAL_ROLE_ATTR(power_role), DUAL_ROLE_ATTR(data_role),
DUAL_ROLE_ATTR(powers_vconn),
};
struct class *dual_role_class;
EXPORT_SYMBOL_GPL(dual_role_class);
static struct device_type dual_role_dev_type;
static char *kstrdupcase(const char *str, gfp_t gfp, bool to_upper)
{
char *ret, *ustr;
ustr = ret = kmalloc(strlen(str) + 1, gfp);
if (!ret)
return NULL;
while (*str)
*ustr++ = to_upper ? toupper(*str++) : tolower(*str++);
*ustr = 0;
return ret;
}
static void dual_role_changed_work(struct work_struct *work)
{
struct dual_role_phy_instance *dual_role =
container_of(work, struct dual_role_phy_instance, changed_work);
dev_dbg(&dual_role->dev, "%s\n", __func__);
kobject_uevent(&dual_role->dev.kobj, KOBJ_CHANGE);
}
void dual_role_instance_changed(struct dual_role_phy_instance *dual_role)
{
dev_dbg(&dual_role->dev, "%s\n", __func__);
pm_wakeup_event(&dual_role->dev, DUAL_ROLE_NOTIFICATION_TIMEOUT);
schedule_work(&dual_role->changed_work);
}
EXPORT_SYMBOL_GPL(dual_role_instance_changed);
int dual_role_get_property(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop, unsigned int *val)
{
return dual_role->desc->get_property(dual_role, prop, val);
}
EXPORT_SYMBOL_GPL(dual_role_get_property);
int dual_role_set_property(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop,
const unsigned int *val)
{
if (!dual_role->desc->set_property)
return -ENODEV;
return dual_role->desc->set_property(dual_role, prop, val);
}
EXPORT_SYMBOL_GPL(dual_role_set_property);
int dual_role_property_is_writeable(struct dual_role_phy_instance *dual_role,
enum dual_role_property prop)
{
if (!dual_role->desc->property_is_writeable)
return -ENODEV;
return dual_role->desc->property_is_writeable(dual_role, prop);
}
EXPORT_SYMBOL_GPL(dual_role_property_is_writeable);
static void dual_role_dev_release(struct device *dev)
{
struct dual_role_phy_instance *dual_role =
container_of(dev, struct dual_role_phy_instance, dev);
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
kfree(dual_role);
}
static struct dual_role_phy_instance *__must_check __dual_role_register(
struct device *parent, const struct dual_role_phy_desc *desc)
{
struct device *dev;
struct dual_role_phy_instance *dual_role;
int rc;
dual_role = kzalloc(sizeof(*dual_role), GFP_KERNEL);
if (!dual_role)
return ERR_PTR(-ENOMEM);
dev = &dual_role->dev;
device_initialize(dev);
dev->class = dual_role_class;
dev->type = &dual_role_dev_type;
dev->parent = parent;
dev->release = dual_role_dev_release;
dev_set_drvdata(dev, dual_role);
dual_role->desc = desc;
rc = dev_set_name(dev, "%s", desc->name);
if (rc)
goto dev_set_name_failed;
INIT_WORK(&dual_role->changed_work, dual_role_changed_work);
rc = device_init_wakeup(dev, true);
if (rc)
goto wakeup_init_failed;
rc = device_add(dev);
if (rc)
goto device_add_failed;
dual_role_instance_changed(dual_role);
return dual_role;
device_add_failed:
device_init_wakeup(dev, false);
wakeup_init_failed:
dev_set_name_failed:
put_device(dev);
kfree(dual_role);
return ERR_PTR(rc);
}
static void
dual_role_instance_unregister(struct dual_role_phy_instance *dual_role)
{
cancel_work_sync(&dual_role->changed_work);
device_init_wakeup(&dual_role->dev, false);
device_unregister(&dual_role->dev);
}
static void devm_dual_role_release(struct device *dev, void *res)
{
struct dual_role_phy_instance **dual_role = res;
dual_role_instance_unregister(*dual_role);
}
struct dual_role_phy_instance *__must_check devm_dual_role_instance_register(
struct device *parent, const struct dual_role_phy_desc *desc)
{
struct dual_role_phy_instance **ptr, *dual_role;
ptr = devres_alloc(devm_dual_role_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
dual_role = __dual_role_register(parent, desc);
if (IS_ERR(dual_role)) {
devres_free(ptr);
} else {
*ptr = dual_role;
devres_add(parent, ptr);
}
return dual_role;
}
EXPORT_SYMBOL_GPL(devm_dual_role_instance_register);
static int devm_dual_role_match(struct device *dev, void *res, void *data)
{
struct dual_role_phy_instance **r = res;
if (WARN_ON(!r || !*r))
return 0;
return *r == data;
}
void devm_dual_role_instance_unregister(
struct device *dev, struct dual_role_phy_instance *dual_role)
{
int rc;
rc = devres_release(dev, devm_dual_role_release, devm_dual_role_match,
dual_role);
WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_dual_role_instance_unregister);
void *dual_role_get_drvdata(struct dual_role_phy_instance *dual_role)
{
return dual_role->drv_data;
}
EXPORT_SYMBOL_GPL(dual_role_get_drvdata);
/***************** Device attribute functions **************************/
/* port type */
static char *supported_modes_text[] = { "ufp dfp", "dfp", "ufp" };
/* current mode */
static char *mode_text[] = { "ufp", "dfp", "none" };
/* Power role */
static char *pr_text[] = { "source", "sink", "none" };
/* Data role */
static char *dr_text[] = { "host", "device", "none" };
/* Vconn supply */
static char *vconn_supply_text[] = { "n", "y" };
static ssize_t dual_role_show_property(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret = 0;
struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
const ptrdiff_t off = attr - dual_role_attrs;
unsigned int value;
if (off == DUAL_ROLE_PROP_SUPPORTED_MODES) {
value = dual_role->desc->supported_modes;
} else {
ret = dual_role_get_property(dual_role, off, &value);
if (ret < 0) {
if (ret == -ENODATA)
dev_dbg(dev,
"driver has no data for `%s' property\n",
attr->attr.name);
else if (ret != -ENODEV)
dev_err(dev,
"driver failed to report `%s' property: %zd\n",
attr->attr.name, ret);
return ret;
}
}
if (off == DUAL_ROLE_PROP_SUPPORTED_MODES) {
BUILD_BUG_ON(DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL !=
ARRAY_SIZE(supported_modes_text));
if (value < DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL)
return snprintf(buf, PAGE_SIZE, "%s\n",
supported_modes_text[value]);
else
return -EIO;
} else if (off == DUAL_ROLE_PROP_MODE) {
BUILD_BUG_ON(DUAL_ROLE_PROP_MODE_TOTAL !=
ARRAY_SIZE(mode_text));
if (value < DUAL_ROLE_PROP_MODE_TOTAL)
return snprintf(buf, PAGE_SIZE, "%s\n",
mode_text[value]);
else
return -EIO;
} else if (off == DUAL_ROLE_PROP_PR) {
BUILD_BUG_ON(DUAL_ROLE_PROP_PR_TOTAL != ARRAY_SIZE(pr_text));
if (value < DUAL_ROLE_PROP_PR_TOTAL)
return snprintf(buf, PAGE_SIZE, "%s\n", pr_text[value]);
else
return -EIO;
} else if (off == DUAL_ROLE_PROP_DR) {
BUILD_BUG_ON(DUAL_ROLE_PROP_DR_TOTAL != ARRAY_SIZE(dr_text));
if (value < DUAL_ROLE_PROP_DR_TOTAL)
return snprintf(buf, PAGE_SIZE, "%s\n", dr_text[value]);
else
return -EIO;
} else if (off == DUAL_ROLE_PROP_VCONN_SUPPLY) {
BUILD_BUG_ON(DUAL_ROLE_PROP_VCONN_SUPPLY_TOTAL !=
ARRAY_SIZE(vconn_supply_text));
if (value < DUAL_ROLE_PROP_VCONN_SUPPLY_TOTAL)
return snprintf(buf, PAGE_SIZE, "%s\n",
vconn_supply_text[value]);
else
return -EIO;
} else
return -EIO;
}
static ssize_t dual_role_store_property(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
ssize_t ret;
struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
const ptrdiff_t off = attr - dual_role_attrs;
unsigned int value;
int total, i;
char *dup_buf, **text_array;
bool result = false;
dup_buf = kstrdupcase(buf, GFP_KERNEL, false);
switch (off) {
case DUAL_ROLE_PROP_MODE:
total = DUAL_ROLE_PROP_MODE_TOTAL;
text_array = mode_text;
break;
case DUAL_ROLE_PROP_PR:
total = DUAL_ROLE_PROP_PR_TOTAL;
text_array = pr_text;
break;
case DUAL_ROLE_PROP_DR:
total = DUAL_ROLE_PROP_DR_TOTAL;
text_array = dr_text;
break;
case DUAL_ROLE_PROP_VCONN_SUPPLY:
ret = strtobool(dup_buf, &result);
value = result;
if (!ret)
goto setprop;
default:
ret = -EINVAL;
goto error;
}
for (i = 0; i <= total; i++) {
if (i == total) {
ret = -ENOTSUPP;
goto error;
}
if (!strncmp(*(text_array + i), dup_buf,
strlen(*(text_array + i)))) {
value = i;
break;
}
}
setprop:
ret = dual_role->desc->set_property(dual_role, off, &value);
error:
kfree(dup_buf);
if (ret < 0)
return ret;
return count;
}
static umode_t dual_role_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int attrno)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
int i;
if (attrno == DUAL_ROLE_PROP_SUPPORTED_MODES)
return mode;
for (i = 0; i < dual_role->desc->num_properties; i++) {
int property = dual_role->desc->properties[i];
if (property == attrno) {
if (dual_role->desc->property_is_writeable &&
dual_role_property_is_writeable(dual_role,
property) > 0)
mode |= S_IWUSR;
return mode;
}
}
return 0;
}
static struct attribute *__dual_role_attrs[ARRAY_SIZE(dual_role_attrs) + 1];
static struct attribute_group dual_role_attr_group = {
.attrs = __dual_role_attrs,
.is_visible = dual_role_attr_is_visible,
};
static const struct attribute_group *dual_role_attr_groups[] = {
&dual_role_attr_group,
NULL,
};
void dual_role_init_attrs(struct device_type *dev_type)
{
int i;
dev_type->groups = dual_role_attr_groups;
for (i = 0; i < ARRAY_SIZE(dual_role_attrs); i++)
__dual_role_attrs[i] = &dual_role_attrs[i].attr;
}
int dual_role_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
int ret = 0, j;
char *prop_buf;
char *attrname;
dev_dbg(dev, "uevent\n");
if (!dual_role || !dual_role->desc) {
dev_dbg(dev, "No dual_role phy yet\n");
return ret;
}
dev_dbg(dev, "DUAL_ROLE_NAME=%s\n", dual_role->desc->name);
ret = add_uevent_var(env, "DUAL_ROLE_NAME=%s", dual_role->desc->name);
if (ret)
return ret;
prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
if (!prop_buf)
return -ENOMEM;
for (j = 0; j < dual_role->desc->num_properties; j++) {
struct device_attribute *attr;
char *line;
attr = &dual_role_attrs[dual_role->desc->properties[j]];
ret = dual_role_show_property(dev, attr, prop_buf);
if (ret == -ENODEV || ret == -ENODATA) {
ret = 0;
continue;
}
if (ret < 0)
goto out;
line = strnchr(prop_buf, PAGE_SIZE, '\n');
if (line)
*line = 0;
attrname = kstrdupcase(attr->attr.name, GFP_KERNEL, true);
if (!attrname)
ret = -ENOMEM;
dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);
ret = add_uevent_var(env, "DUAL_ROLE_%s=%s", attrname,
prop_buf);
kfree(attrname);
if (ret)
goto out;
}
out:
free_page((unsigned long)prop_buf);
return ret;
}
/******************* Module Init ***********************************/
static int __init dual_role_class_init(void)
{
dual_role_class = class_create(THIS_MODULE, "dual_role_usb");
if (IS_ERR(dual_role_class))
return PTR_ERR(dual_role_class);
dual_role_class->dev_uevent = dual_role_uevent;
dual_role_init_attrs(&dual_role_dev_type);
return 0;
}
static void __exit dual_role_class_exit(void)
{
class_destroy(dual_role_class);
}
subsys_initcall(dual_role_class_init);
module_exit(dual_role_class_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jeff Chang <jeff_chang@richtek.com>");
MODULE_DESCRIPTION("class-dual-role");
MODULE_VERSION("1.0.0");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,168 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/module.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/usb/tcpc/pd_dbg_info.h>
#ifdef CONFIG_PD_DBG_INFO
#define PD_INFO_BUF_SIZE (2048 * 256)
#define MSG_POLLING_MS 20
#define OUT_BUF_MAX (128)
static struct {
int used;
char buf[PD_INFO_BUF_SIZE + 1 + OUT_BUF_MAX];
} pd_dbg_buffer[2];
static struct mutex buff_lock;
static unsigned int using_buf;
static wait_queue_head_t print_out_wait_que;
static atomic_t pending_print_out;
static atomic_t busy = ATOMIC_INIT(0);
void pd_dbg_info_lock(void)
{
atomic_inc(&busy);
}
void pd_dbg_info_unlock(void)
{
atomic_dec_if_positive(&busy);
}
static inline bool pd_dbg_print_out(void)
{
char temp;
int used;
unsigned int index, i;
mutex_lock(&buff_lock);
index = using_buf;
using_buf ^= 0x01; /* exchange buffer */
mutex_unlock(&buff_lock);
used = pd_dbg_buffer[index].used;
if (used == 0)
return false;
pd_dbg_buffer[index].buf[used] = '\0';
pr_info("///PD dbg info %ud\n", used);
for (i = 0; i < used; i += OUT_BUF_MAX) {
temp = pd_dbg_buffer[index].buf[OUT_BUF_MAX + i];
pd_dbg_buffer[index].buf[OUT_BUF_MAX + i] = '\0';
while (atomic_read(&busy))
usleep_range(1000, 2000);
pr_notice("%s", pd_dbg_buffer[index].buf + i);
pd_dbg_buffer[index].buf[OUT_BUF_MAX + i] = temp;
}
/* pr_info("PD dbg info///\n"); */
pd_dbg_buffer[index].used = 0;
msleep(MSG_POLLING_MS);
return true;
}
static int print_out_thread_fn(void *data)
{
int ret = 0;
while (true) {
ret = wait_event_interruptible(
print_out_wait_que, atomic_read(&pending_print_out) ||
kthread_should_stop());
if (kthread_should_stop() || ret) {
pr_notice("%s exits(%d)\n", __func__, ret);
break;
}
do {
atomic_dec_if_positive(&pending_print_out);
} while (pd_dbg_print_out() && !kthread_should_stop());
}
return 0;
}
int pd_dbg_info(const char *fmt, ...)
{
unsigned int index;
va_list args;
int r;
int used;
u64 ts;
unsigned long rem_usec;
ts = local_clock();
rem_usec = do_div(ts, 1000000000) / 1000 / 1000;
va_start(args, fmt);
mutex_lock(&buff_lock);
index = using_buf;
used = pd_dbg_buffer[index].used;
r = snprintf(pd_dbg_buffer[index].buf + used, PD_INFO_BUF_SIZE - used,
"<%5lu.%03lu>", (unsigned long)ts, rem_usec);
if (r > 0)
used += r;
r = vsnprintf(pd_dbg_buffer[index].buf + used, PD_INFO_BUF_SIZE - used,
fmt, args);
if (r > 0)
used += r;
if (pd_dbg_buffer[index].used == 0) {
atomic_inc(&pending_print_out);
wake_up(&print_out_wait_que);
}
pd_dbg_buffer[index].used = used;
mutex_unlock(&buff_lock);
va_end(args);
return r;
}
EXPORT_SYMBOL(pd_dbg_info);
static struct task_struct *print_out_task;
int pd_dbg_info_init(void)
{
pr_info("%s\n", __func__);
mutex_init(&buff_lock);
init_waitqueue_head(&print_out_wait_que);
atomic_set(&pending_print_out, 0);
print_out_task = kthread_run(print_out_thread_fn, NULL, "pd_dbg_info");
return 0;
}
void pd_dbg_info_exit(void)
{
kthread_stop(print_out_task);
mutex_destroy(&buff_lock);
}
MODULE_DESCRIPTION("PD Debug Info Module");
MODULE_AUTHOR("Patrick Chang <patrick_chang@richtek.com>");
MODULE_LICENSE("GPL");
#endif /* CONFIG_PD_DBG_INFO */

View File

@ -0,0 +1,596 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* PD Device Policy Manager for Direct Charge
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/random.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/pd_dpm_prv.h>
#if IS_ENABLED(CONFIG_USB_POWER_DELIVERY)
#ifdef CONFIG_USB_PD_ALT_MODE_RTDC
#define RTDC_UVDM_EN_UNLOCK 0x2024
#define RTDC_UVDM_RECV_EN_UNLOCK 0x4024
#define RTDC_SVDM_PPS_AUTHORIZATION 0x10
#define RTDC_VALID_MODE 0x01
#define RTDC_UVDM_EN_UNLOCK_SUCCESS 0x01
void crcbits(uint32_t data, uint32_t *crc, uint32_t *ppolynomial)
{
uint32_t i, newbit, newword, rl_crc;
for (i = 0; i < 32; i++) {
newbit = ((*crc >> 31) ^ ((data >> i) & 1)) & 1;
if (newbit)
newword = *ppolynomial;
else
newword = 0;
rl_crc = (*crc << 1) | newbit;
*crc = rl_crc ^ newword;
}
}
uint32_t crcwrap(uint32_t V)
{
uint32_t ret = 0, i, j, bit;
V = ~V;
for (i = 0; i < 32; i++) {
j = 31 - i;
bit = (V >> i) & 1;
ret |= bit << j;
}
return ret;
}
static uint32_t dc_get_random_code(void)
{
uint32_t num;
get_random_bytes(&num, sizeof(num));
return num;
}
static uint32_t dc_get_authorization_code(uint32_t data)
{
uint32_t dwpolynomial = 0x04C11DB6, dwCrc = 0xFFFFFFFF;
crcbits(data, &dwCrc, &dwpolynomial);
dwCrc = crcwrap(dwCrc);
return dwCrc;
}
static inline bool dc_dfp_send_en_unlock(struct pd_port *pd_port, uint32_t cmd,
uint32_t data0, uint32_t data1)
{
pd_port->uvdm_cnt = 3;
pd_port->uvdm_wait_resp = true;
pd_port->uvdm_data[0] = PD_UVDM_HDR(USB_VID_DIRECTCHARGE, cmd);
pd_port->uvdm_data[1] = data0;
pd_port->uvdm_data[2] = data1;
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
if (pd_port->pe_data.dc_pps_mode) {
pd_port->uvdm_data[0] =
VDO_S(USB_VID_DIRECTCHARGE, SVDM_REV20, CMDT_INIT,
RTDC_SVDM_PPS_AUTHORIZATION, 0);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
return pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_UVDM);
}
enum pd_dc_dfp_state {
DC_DFP_NONE = 0,
DC_DFP_DISCOVER_ID,
DC_DFP_DISCOVER_SVIDS,
DC_DFP_DISCOVER_MODES,
DC_DFP_ENTER_MODE,
DC_DFP_EN_UNLOCK1,
DC_DFP_EN_UNLOCK2,
DC_DFP_OPERATION,
#ifdef RTDC_TA_EMULATE
DC_UFP_T0,
DC_UFP_T1,
DC_UFP_T2,
#endif
DC_DFP_STATE_NR,
DC_DFP_ERR = 0X10,
DC_DFP_ERR_DISCOVER_ID_TYPE,
DC_DFP_ERR_DISCOVER_ID_NAK_TIMEOUT,
DC_DFP_ERR_DISCOVER_SVID_DC_SID,
DC_DFP_ERR_DISCOVER_SVID_NAK_TIMEOUT,
DC_DFP_ERR_DISCOVER_CABLE,
DC_DFP_ERR_DISCOVER_MODE_DC_SID,
DC_DFP_ERR_DISCOVER_MODE_CAP,
DC_DFP_ERR_DISCOVER_MODE_NAK_TIMEROUT,
DC_DFP_ERR_ENTER_MODE_DC_SID,
DC_DFP_ERR_ENTER_MODE_NAK_TIMEOUT,
DC_DFP_ERR_EXIT_MODE_DC_SID,
DC_DFP_ERR_EXIT_MODE_NAK_TIMEOUT,
DC_DFP_ERR_EN_UNLOCK1_FAILED,
DC_DFP_ERR_EN_UNLOCK1_NAK_TIMEOUT,
DC_DFP_ERR_EN_UNLOCK2_FAILED,
DC_DFP_ERR_EN_UNLOCK2_NAK_TIMEOUT,
DC_DFP_ERR_PD_REV30,
};
#if DC_DBG_ENABLE
static const char *const dc_dfp_state_name[] = {
"dc_dfp_none",
"dc_dfp_discover_id",
"dc_dfp_discover_svids",
"dc_dfp_discover_modes",
"dc_dfp_enter_mode",
"dc_dfp_en_unlock1",
"dc_dfp_en_unlock2",
"dc_dfp_operation",
#ifdef RTDC_TA_EMULATE
"dc1",
"dc2",
"dc3",
#endif
};
#endif /* DC_DBG_ENABLE */
void dc_dfp_set_state(struct pd_port *pd_port, uint8_t state)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
pd_port->dc_dfp_state = state;
if (pd_port->dc_dfp_state < DC_DFP_STATE_NR)
DC_DBG("%s\n", dc_dfp_state_name[state]);
else
DC_DBG("dc_dfp_stop (%d)\n", state);
}
bool dc_dfp_start_en_unlock1(struct pd_port *pd_port)
{
uint32_t rn_code[2];
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
rn_code[0] = dc_get_random_code();
rn_code[1] = dc_get_random_code();
pd_port->dc_pass_code = dc_get_authorization_code(
(rn_code[0] & 0xffff) | (rn_code[1] & 0xffff0000));
DC_DBG("en_unlock1: 0x%x, 0x%x\n", rn_code[0], rn_code[1]);
dc_dfp_send_en_unlock(pd_port, RTDC_UVDM_EN_UNLOCK, rn_code[0],
rn_code[1]);
dc_dfp_set_state(pd_port, DC_DFP_EN_UNLOCK1);
return true;
}
#define SVDM_CMD_STATE_MASK(raw) (raw & (0x80df))
#define SVDM_CMD_STATE(cmd, cmd_type) \
((1 << 15) | (cmd & 0x1f) | ((cmd_type & 0x03) << 6))
bool dc_dfp_verify_en_unlock1(struct pd_port *pd_port)
{
uint32_t resp_cmd, expect_resp;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
expect_resp = RTDC_UVDM_RECV_EN_UNLOCK;
resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]);
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
if (pd_port->pe_data.dc_pps_mode) {
resp_cmd = SVDM_CMD_STATE_MASK(pd_port->uvdm_data[0]);
expect_resp = SVDM_CMD_STATE(RTDC_SVDM_PPS_AUTHORIZATION,
CMDT_RSP_ACK);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
if (resp_cmd != expect_resp) {
DC_INFO("en_unlock1: unexpect resp (0x%x)\n", resp_cmd);
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_FAILED);
return false;
}
if (pd_port->dc_pass_code != pd_port->uvdm_data[1]) {
DC_INFO("en_unlock1: pass wrong 0x%x 0x%x\n",
pd_port->dc_pass_code, pd_port->uvdm_data[1]);
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_FAILED);
return false;
}
return true;
}
bool dc_dfp_start_en_unlock2(struct pd_port *pd_port)
{
uint32_t rn_code = dc_get_random_code();
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
pd_port->dc_pass_code =
dc_get_authorization_code(pd_port->uvdm_data[2]);
DC_DBG("en_unlock2: 0x%x, 0x%x\n", pd_port->dc_pass_code, rn_code);
dc_dfp_send_en_unlock(pd_port, RTDC_UVDM_EN_UNLOCK,
pd_port->dc_pass_code, rn_code);
dc_dfp_set_state(pd_port, DC_DFP_EN_UNLOCK2);
return true;
}
bool dc_dfp_verify_en_unlock2(struct pd_port *pd_port)
{
uint32_t resp_cmd, expect_resp;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
expect_resp = RTDC_UVDM_RECV_EN_UNLOCK;
resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]);
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
if (pd_port->pe_data.dc_pps_mode) {
resp_cmd = SVDM_CMD_STATE_MASK(pd_port->uvdm_data[0]);
expect_resp = SVDM_CMD_STATE(RTDC_SVDM_PPS_AUTHORIZATION,
CMDT_RSP_ACK);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
if (resp_cmd != expect_resp) {
DC_INFO("en_unlock2: unexpect resp (0x%x)\n", resp_cmd);
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_FAILED);
return false;
}
if (pd_port->uvdm_data[1] != RTDC_UVDM_EN_UNLOCK_SUCCESS) {
DC_INFO("en_unlock2: failed\n");
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_FAILED);
return false;
}
return true;
}
bool dc_dfp_notify_pe_startup(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
if (!(pd_port->id_vdos[0] & PD_IDH_MODAL_SUPPORT))
return false;
if (pd_port->dpm_caps & DPM_CAP_ATTEMP_ENTER_DC_MODE)
dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_ID);
#ifdef RTDC_TA_EMULATE
dc_dfp_set_state(pd_port, DC_UFP_T0);
#endif
return true;
}
int dc_dfp_notify_pe_ready(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
#ifdef RTDC_TA_EMULATE
if (pd_port->data_role == PD_ROLE_DFP && svid_data->exist) {
pd_put_tcp_pd_event(pd_port, TCP_DPM_EVT_DR_SWAP_AS_UFP);
return 1;
} else {
return 0;
}
#endif
if (pd_port->data_role != PD_ROLE_DFP)
return 0;
if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_MODES)
return 0;
#ifdef CONFIG_USB_PD_RTDC_CHECK_CABLE
if (!pd_port->pe_data.power_cable_present) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_CABLE);
return 0;
}
if (pd_get_cable_curr_lvl(pd_port) != CABLE_CURR_5A) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_CABLE);
return 0;
}
#endif /* CONFIG_USB_PD_RTDC_CHECK_CABLE */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
/* If TA support pd revision30, using standard PPS flow */
if (pd_check_rev30(pd_port)) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_PD_REV30);
return 0;
}
if (pd_is_source_support_apdo(pd_port)) {
DC_INFO("pps_mode\n");
pd_port->pe_data.dc_pps_mode = true;
return dc_dfp_start_en_unlock1(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
pd_port->mode_svid = USB_VID_DIRECTCHARGE;
pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_DISCOVER_MODES);
return 1;
}
bool dc_dfp_notify_discover_id(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_ID)
return true;
if (!ack) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_ID_NAK_TIMEOUT);
return true;
}
/* TODO: Check device ID or Type */
dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_SVIDS);
return true;
}
bool dc_dfp_notify_discover_svid(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_SVIDS)
return false;
if (!ack) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_SVID_NAK_TIMEOUT);
return false;
}
if (!svid_data->exist) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_SVID_DC_SID);
return false;
}
dpm_reaction_set(pd_port, DPM_REACTION_DISCOVER_CABLE_FLOW);
dc_dfp_set_state(pd_port, DC_DFP_DISCOVER_MODES);
return true;
}
bool dc_dfp_notify_discover_modes(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_DISCOVER_MODES)
return false;
if (!ack) {
dc_dfp_set_state(pd_port,
DC_DFP_ERR_DISCOVER_MODE_NAK_TIMEROUT);
return false;
}
if (svid_data->remote_mode.mode_cnt == 0) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_MODE_DC_SID);
return false;
}
if (svid_data->remote_mode.mode_vdo[0] != RTDC_VALID_MODE) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_DISCOVER_MODE_CAP);
return false;
}
pd_port->mode_obj_pos = 1;
dc_dfp_set_state(pd_port, DC_DFP_ENTER_MODE);
pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_ENTER_MODE);
return true;
}
bool dc_dfp_notify_enter_mode(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, uint8_t ops,
bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_ENTER_MODE)
return true;
if (!ack) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_ENTER_MODE_NAK_TIMEOUT);
return true;
}
if (svid_data->active_mode == 0) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_ENTER_MODE_DC_SID);
return false;
}
return dc_dfp_start_en_unlock1(pd_port);
}
bool dc_dfp_notify_exit_mode(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, uint8_t ops)
{
if (pd_port->dc_dfp_state <= DC_DFP_ENTER_MODE)
return false;
if (svid_data->svid != USB_VID_DIRECTCHARGE)
return false;
dc_dfp_set_state(pd_port, DC_DFP_NONE);
return true;
}
static inline bool dc_dfp_notify_en_unlock1(struct pd_port *pd_port,
struct svdm_svid_data *svid_data,
bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_EN_UNLOCK1)
return false;
if (!ack) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK1_NAK_TIMEOUT);
return false;
}
if (!dc_dfp_verify_en_unlock1(pd_port))
return false;
return dc_dfp_start_en_unlock2(pd_port);
}
static inline bool dc_dfp_notify_en_unlock2(struct pd_port *pd_port,
struct svdm_svid_data *svid_data,
bool ack)
{
if (pd_port->dc_dfp_state != DC_DFP_EN_UNLOCK2)
return false;
if (!ack) {
dc_dfp_set_state(pd_port, DC_DFP_ERR_EN_UNLOCK2_NAK_TIMEOUT);
return false;
}
if (!dc_dfp_verify_en_unlock2(pd_port))
return false;
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
#ifdef CONFIG_USB_PD_REV30_SYNC_SPEC_REV
if (pd_port->pe_data.dc_pps_mode)
pd_port->pd_revision[0] = PD_REV30;
#endif /* CONFIG_USB_PD_REV30_SYNC_SPEC_REV */
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
dc_dfp_set_state(pd_port, DC_DFP_OPERATION);
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
/* PPS shoult not use en_unlock to notify system */
if (pd_port->pe_data.dc_pps_mode)
return true;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
tcpci_dc_notify_en_unlock(pd_port->tcpc);
return true;
}
bool dc_dfp_notify_uvdm(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, bool ack)
{
switch (pd_port->dc_dfp_state) {
case DC_DFP_EN_UNLOCK1:
dc_dfp_notify_en_unlock1(pd_port, svid_data, ack);
break;
case DC_DFP_EN_UNLOCK2:
dc_dfp_notify_en_unlock2(pd_port, svid_data, ack);
break;
}
return true;
}
bool dc_ufp_notify_uvdm(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
#ifdef RTDC_TA_EMULATE
uint32_t reply_cmd[3];
uint32_t recv_code[2], rn_code, pass_code;
reply_cmd[0] = PD_UVDM_HDR(0x29cf, RTDC_UVDM_EN_UNLOCK);
uint32_t cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]);
if (cmd != RTDC_UVDM_EN_UNLOCK) {
DC_INFO("What!?");
return true;
}
switch (pd_port->dc_dfp_state) {
case DC_UFP_T0: {
recv_code[0] = pd_port->uvdm_data[1];
recv_code[1] = pd_port->uvdm_data[2];
DC_INFO("T0: recv_code: 0x%x, 0x%x\n", recv_code[0],
recv_code[1]);
pass_code = dc_get_authorization_code(
(recv_code[0] & 0xffff) | (recv_code[1] & 0xffff0000));
rn_code = dc_get_random_code();
pd_port->dc_pass_code = dc_get_authorization_code(rn_code);
DC_INFO("T0: reply: 0x%x, 0x%x\n", rn_code,
pd_port->dc_pass_code);
reply_cmd[1] = pass_code;
reply_cmd[2] = rn_code;
pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, 3, reply_cmd);
dc_dfp_set_state(pd_port, DC_UFP_T1);
} break;
case DC_UFP_T1: {
recv_code[0] = pd_port->uvdm_data[1];
recv_code[1] = pd_port->uvdm_data[2];
DC_INFO("T1: recv_code: 0x%x, 0x%x\n", recv_code[0],
recv_code[1]);
if (recv_code[0] != pd_port->dc_pass_code) {
DC_INFO("T1: pass_code error\n");
reply_cmd[1] = 0;
} else {
DC_INFO("T1: pass_code success\n");
reply_cmd[1] = 1;
}
reply_cmd[2] = dc_get_random_code();
pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, 3, reply_cmd);
dc_dfp_set_state(pd_port, DC_UFP_T2);
} break;
}
#endif /* RTDC_TA_EMULATE */
return true;
}
bool dc_reset_state(struct pd_port *pd_port, struct svdm_svid_data *svid_data)
{
dc_dfp_set_state(pd_port, DC_DFP_NONE);
return true;
}
bool dc_parse_svid_data(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
svid_data->local_mode.mode_cnt = 1;
svid_data->local_mode.mode_vdo[0] = 0x00;
pd_port->dpm_caps |= DPM_CAP_ATTEMP_ENTER_DC_MODE;
return true;
}
#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */
#endif /* CONFIG_USB_POWER_DELIVERY */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,389 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Core Driver
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_dpm_pdo_select.h>
#if IS_ENABLED(CONFIG_USB_POWER_DELIVERY)
struct dpm_select_info_t {
uint8_t pos;
int max_uw;
int cur_mv;
uint8_t policy;
};
static inline void dpm_extract_apdo_info(uint32_t pdo,
struct dpm_pdo_info_t *info)
{
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
switch (APDO_TYPE(pdo)) {
case APDO_TYPE_PPS:
info->apdo_type = DPM_APDO_TYPE_PPS;
if (pdo & APDO_PPS_CURR_FOLDBACK)
info->apdo_type |= DPM_APDO_TYPE_PPS_CF;
info->pwr_limit = APDO_PPS_EXTRACT_PWR_LIMIT(pdo);
info->ma = APDO_PPS_EXTRACT_CURR(pdo);
info->vmin = APDO_PPS_EXTRACT_MIN_VOLT(pdo);
info->vmax = APDO_PPS_EXTRACT_MAX_VOLT(pdo);
info->uw = info->ma * info->vmax;
return;
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
info->type = TCPM_POWER_CAP_VAL_TYPE_UNKNOWN;
}
void dpm_extract_pdo_info(uint32_t pdo, struct dpm_pdo_info_t *info)
{
memset(info, 0, sizeof(struct dpm_pdo_info_t));
info->type = PDO_TYPE_VAL(pdo);
switch (PDO_TYPE(pdo)) {
case PDO_TYPE_FIXED:
info->ma = PDO_FIXED_EXTRACT_CURR(pdo);
info->vmax = info->vmin = PDO_FIXED_EXTRACT_VOLT(pdo);
info->uw = info->ma * info->vmax;
break;
case PDO_TYPE_VARIABLE:
info->ma = PDO_VAR_EXTRACT_CURR(pdo);
info->vmin = PDO_VAR_EXTRACT_MIN_VOLT(pdo);
info->vmax = PDO_VAR_EXTRACT_MAX_VOLT(pdo);
info->uw = info->ma * info->vmax;
break;
case PDO_TYPE_BATTERY:
info->uw = PDO_BATT_EXTRACT_OP_POWER(pdo) * 1000;
info->vmin = PDO_BATT_EXTRACT_MIN_VOLT(pdo);
info->vmax = PDO_BATT_EXTRACT_MAX_VOLT(pdo);
info->ma = info->uw / info->vmin;
break;
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
case PDO_TYPE_APDO:
dpm_extract_apdo_info(pdo, info);
break;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
}
}
#ifndef MIN
#define MIN(a, b) ((a < b) ? (a) : (b))
#endif
static inline int dpm_calc_src_cap_power_uw(struct dpm_pdo_info_t *source,
struct dpm_pdo_info_t *sink)
{
int uw, ma;
if (source->type == DPM_PDO_TYPE_BAT) {
uw = source->uw;
if (sink->type == DPM_PDO_TYPE_BAT)
uw = MIN(uw, sink->uw);
} else {
ma = source->ma;
if (sink->type != DPM_PDO_TYPE_BAT)
ma = MIN(ma, sink->ma);
uw = ma * source->vmax;
}
return uw;
}
/*
* Select PDO from VSafe5V
*/
static bool dpm_select_pdo_from_vsafe5v(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source)
{
int uw;
if ((sink->vmax != TCPC_VBUS_SINK_5V) ||
(sink->vmin != TCPC_VBUS_SINK_5V) ||
(source->vmax != TCPC_VBUS_SINK_5V) ||
(source->vmin != TCPC_VBUS_SINK_5V))
return false;
uw = dpm_calc_src_cap_power_uw(source, sink);
if (uw > select_info->max_uw) {
select_info->max_uw = uw;
select_info->cur_mv = source->vmax;
return true;
}
return false;
}
/*
* Select PDO from Direct Charge
*/
#ifdef CONFIG_USB_PD_ALT_MODE_RTDC
static bool
dpm_select_pdo_from_direct_charge(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source)
{
int uw;
if (sink->type != DPM_PDO_TYPE_VAR || source->type != DPM_PDO_TYPE_VAR)
return false;
if (source->vmin >= TCPC_VBUS_SINK_5V)
return false;
if (sink->vmax < source->vmax)
return false;
if (sink->vmin > source->vmin)
return false;
uw = dpm_calc_src_cap_power_uw(source, sink);
if (uw > select_info->max_uw) {
select_info->max_uw = uw;
select_info->cur_mv = source->vmax;
return true;
}
return false;
}
#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */
/*
* Select PDO from Custom
*/
static bool dpm_select_pdo_from_custom(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source)
{
/* TODO */
return dpm_select_pdo_from_vsafe5v(select_info, sink, source);
}
/*
* Select PDO from Max Power
*/
static inline bool dpm_is_valid_pdo_pair(struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source,
uint32_t policy)
{
if (sink->vmax < source->vmax)
return false;
if (sink->vmin > source->vmin)
return false;
if (policy & DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR)
return true;
return sink->ma <= source->ma;
}
static bool dpm_select_pdo_from_max_power(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source)
{
bool overload;
int uw;
#ifdef CONFIG_USB_PD_ALT_MODE_RTDC
/* Variable for direct charge only */
if ((sink->type == DPM_PDO_TYPE_VAR) && (sink->vmin < 5000))
return false;
#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */
#ifdef CONFIG_USB_PD_REV30
if (sink->type == DPM_PDO_TYPE_APDO)
return false;
#endif /* CONFIG_USB_PD_REV30 */
if (!dpm_is_valid_pdo_pair(sink, source, select_info->policy))
return false;
uw = dpm_calc_src_cap_power_uw(source, sink);
overload = uw > select_info->max_uw;
if ((!overload) && (uw == select_info->max_uw)) {
if (select_info->policy &
DPM_CHARGING_POLICY_PREFER_LOW_VOLTAGE)
overload = (source->vmax < select_info->cur_mv);
else if (select_info->policy &
DPM_CHARGING_POLICY_PREFER_HIGH_VOLTAGE)
overload = (source->vmax > select_info->cur_mv);
}
if (overload) {
select_info->max_uw = uw;
select_info->cur_mv = source->vmax;
return true;
}
return false;
}
/*
* Select PDO from PPS
*/
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
static bool dpm_select_pdo_from_pps(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source)
{
bool overload;
int uw, diff_mv;
const int tolerance = 300; /* 5900 * 5% */
if (sink->type != DPM_PDO_TYPE_APDO ||
source->type != DPM_PDO_TYPE_APDO)
return false;
if (!(source->apdo_type & DPM_APDO_TYPE_PPS))
return false;
if (sink->vmax > source->vmax)
return false;
if (sink->vmin < source->vmin)
return false;
if (!(select_info->policy & DPM_CHARGING_POLICY_IGNORE_MISMATCH_CURR)) {
if (source->ma < sink->ma)
return false;
}
uw = sink->vmax * source->ma;
diff_mv = source->vmax - sink->vmax;
if (uw > select_info->max_uw)
overload = true;
else if (uw < select_info->max_uw)
overload = false;
else if ((select_info->cur_mv < tolerance) && (diff_mv > tolerance))
overload = true;
else if (diff_mv < select_info->cur_mv)
overload = true;
else
overload = false;
if (overload) {
select_info->max_uw = uw;
select_info->cur_mv = diff_mv;
return true;
}
return false;
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
/*
* Select PDO from defined rule ...
*/
typedef bool (*dpm_select_pdo_fun)(struct dpm_select_info_t *select_info,
struct dpm_pdo_info_t *sink,
struct dpm_pdo_info_t *source);
bool dpm_find_match_req_info(struct dpm_rdo_info_t *req_info,
struct dpm_pdo_info_t *sink, int cnt,
uint32_t *src_pdos, int min_uw, uint32_t policy)
{
int i;
struct dpm_select_info_t select;
struct dpm_pdo_info_t source;
dpm_select_pdo_fun select_pdo_fun;
select.pos = 0;
select.cur_mv = 0;
select.max_uw = min_uw;
select.policy = policy;
switch (policy & DPM_CHARGING_POLICY_MASK) {
case DPM_CHARGING_POLICY_MAX_POWER:
select_pdo_fun = dpm_select_pdo_from_max_power;
break;
case DPM_CHARGING_POLICY_CUSTOM:
select_pdo_fun = dpm_select_pdo_from_custom;
break;
#ifdef CONFIG_USB_PD_ALT_MODE_RTDC
case DPM_CHARGING_POLICY_DIRECT_CHARGE:
select_pdo_fun = dpm_select_pdo_from_direct_charge;
break;
#endif /* CONFIG_USB_PD_ALT_MODE_RTDC */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
case DPM_CHARGING_POLICY_PPS:
select_pdo_fun = dpm_select_pdo_from_pps;
break;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
default: /* DPM_CHARGING_POLICY_VSAFE5V */
select_pdo_fun = dpm_select_pdo_from_vsafe5v;
break;
}
for (i = 0; i < cnt; i++) {
dpm_extract_pdo_info(src_pdos[i], &source);
if (select_pdo_fun(&select, sink, &source))
select.pos = i + 1;
}
if (select.pos > 0) {
dpm_extract_pdo_info(src_pdos[select.pos - 1], &source);
req_info->pos = select.pos;
req_info->type = source.type;
req_info->vmax = source.vmax;
req_info->vmin = source.vmin;
if (sink->type == DPM_PDO_TYPE_BAT)
req_info->mismatch = select.max_uw < sink->uw;
else
req_info->mismatch = source.ma < sink->ma;
if (source.type == DPM_PDO_TYPE_BAT) {
req_info->max_uw = sink->uw;
req_info->oper_uw = select.max_uw;
} else {
req_info->max_ma = sink->ma;
req_info->oper_ma = MIN(sink->ma, source.ma);
}
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
if (source.type == DPM_PDO_TYPE_APDO) {
req_info->vmax = sink->vmax;
req_info->vmin = sink->vmin;
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
return true;
}
return false;
}
#endif /* CONFIG_USB_POWER_DELIVERY */

View File

@ -0,0 +1,736 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* PD Device Policy Manager Ready State reactions
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/pd_dpm_pdo_select.h>
#include <linux/usb/tcpc/pd_dpm_prv.h>
#define DPM_REACTION_COND_ALWAYS (1 << 0)
#define DPM_REACTION_COND_UFP_ONLY (1 << 1)
#define DPM_REACTION_COND_DFP_ONLY (1 << 2)
#define DPM_REACTION_COND_PD30 (1 << 3)
#define DPM_REACCOND_DFP (DPM_REACTION_COND_ALWAYS | DPM_REACTION_COND_DFP_ONLY)
#define DPM_REACCOND_UFP (DPM_REACTION_COND_ALWAYS | DPM_REACTION_COND_UFP_ONLY)
#define DPM_REACTION_COND_CHECK_ONCE (1 << 5)
#define DPM_REACTION_COND_ONE_SHOT (1 << 6)
#define DPM_REACTION_COND_LIMITED_RETRIES (1 << 7)
/*
* DPM flow delay reactions
*/
#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY
static uint8_t dpm_reaction_ufp_flow_delay(struct pd_port *pd_port)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
DPM_INFO("UFP Delay\n");
pd_restart_timer(pd_port, PD_TIMER_UFP_FLOW_DELAY);
return DPM_READY_REACTION_BUSY;
}
#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */
#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY
static uint8_t dpm_reaction_dfp_flow_delay(struct pd_port *pd_port)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
DPM_INFO("DFP Delay\n");
pd_restart_timer(pd_port, PD_TIMER_DFP_FLOW_DELAY);
return DPM_READY_REACTION_BUSY;
}
#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */
#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY
static uint8_t dpm_reaction_vconn_stable_delay(struct pd_port *pd_port)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->vconn_role == PD_ROLE_VCONN_DYNAMIC_ON) {
DPM_INFO("VStable Delay\n");
return DPM_READY_REACTION_BUSY;
}
return 0;
}
#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */
/*
* DPM get cap reaction
*/
#ifdef CONFIG_USB_PD_REV30
static uint8_t dpm_reaction_get_source_cap_ext(struct pd_port *pd_port)
{
if (pd_port->power_role == PD_ROLE_SINK)
return TCP_DPM_EVT_GET_SOURCE_CAP_EXT;
return 0;
}
#endif /* CONFIG_USB_PD_REV30 */
static uint8_t dpm_reaction_get_sink_cap(struct pd_port *pd_port)
{
return TCP_DPM_EVT_GET_SINK_CAP;
}
static uint8_t dpm_reaction_get_source_cap(struct pd_port *pd_port)
{
return TCP_DPM_EVT_GET_SOURCE_CAP;
}
static uint8_t dpm_reaction_attemp_get_flag(struct pd_port *pd_port)
{
return TCP_DPM_EVT_GET_SINK_CAP;
}
/*
* DPM swap reaction
*/
#ifdef CONFIG_USB_PD_PR_SWAP
static uint8_t dpm_reaction_request_pr_swap(struct pd_port *pd_port)
{
uint32_t prefer_role = DPM_CAP_EXTRACT_PR_CHECK(pd_port->dpm_caps);
if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER))
return 0;
if (pd_port->power_role == PD_ROLE_SINK) {
if (prefer_role == DPM_CAP_PR_CHECK_PREFER_SRC)
return TCP_DPM_EVT_PR_SWAP_AS_SRC;
} else {
#ifdef CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW
if (dpm_check_good_power(pd_port) == GOOD_PW_PARTNER)
return TCP_DPM_EVT_PR_SWAP_AS_SNK;
#endif /* CONFIG_USB_PD_SRC_TRY_PR_SWAP_IF_BAD_PW */
if (prefer_role == DPM_CAP_PR_CHECK_PREFER_SNK)
return TCP_DPM_EVT_PR_SWAP_AS_SNK;
}
return 0;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_DR_SWAP
static uint8_t dpm_reaction_request_dr_swap(struct pd_port *pd_port)
{
uint32_t prefer_role = DPM_CAP_EXTRACT_DR_CHECK(pd_port->dpm_caps);
if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA))
return 0;
if (pd_port->data_role == PD_ROLE_DFP &&
prefer_role == DPM_CAP_DR_CHECK_PREFER_UFP)
return TCP_DPM_EVT_DR_SWAP_AS_UFP;
if (pd_port->data_role == PD_ROLE_UFP &&
prefer_role == DPM_CAP_DR_CHECK_PREFER_DFP)
return TCP_DPM_EVT_DR_SWAP_AS_DFP;
return 0;
}
#endif /* #ifdef CONFIG_USB_PD_DR_SWAP */
/*
* DPM DiscoverCable reaction
*/
#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE
static uint8_t dpm_reaction_dynamic_vconn(struct pd_port *pd_port)
{
pd_dpm_dynamic_enable_vconn(pd_port);
return 0;
}
#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */
#ifdef CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN
static uint8_t dpm_reaction_request_vconn_source(struct pd_port *pd_port)
{
bool return_vconn = true;
if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY))
return 0;
if (pd_port->vconn_role)
return 0;
#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE
if (pd_port->tcpc->tcpc_vconn_supply == TCPC_VCONN_SUPPLY_STARTUP)
return_vconn = false;
#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */
if (pd_check_rev30(pd_port))
return_vconn = false;
if (return_vconn)
dpm_reaction_set(pd_port, DPM_REACTION_RETURN_VCONN_SRC);
return TCP_DPM_EVT_VCONN_SWAP_ON;
}
#endif /* CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN */
#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID
static uint8_t pd_dpm_reaction_discover_cable(struct pd_port *pd_port)
{
#ifdef CONFIG_PD_DFP_RESET_CABLE
if (pd_is_reset_cable(pd_port))
return TCP_DPM_EVT_CABLE_SOFTRESET;
#endif /* CONFIG_PD_DFP_RESET_CABLE */
if (pd_is_discover_cable(pd_port)) {
pd_restart_timer(pd_port, PD_TIMER_DISCOVER_ID);
return DPM_READY_REACTION_BUSY;
}
return 0;
}
#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */
#ifdef CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN
static uint8_t dpm_reaction_return_vconn_source(struct pd_port *pd_port)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->vconn_role) {
DPM_DBG("VconnReturn\n");
return TCP_DPM_EVT_VCONN_SWAP_OFF;
}
return 0;
}
#endif /* CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN */
/*
* DPM EnterMode reaction
*/
#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_ID
static uint8_t dpm_reaction_discover_id(struct pd_port *pd_port)
{
return TCP_DPM_EVT_DISCOVER_ID;
}
#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_ID */
#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_SVID
static uint8_t dpm_reaction_discover_svid(struct pd_port *pd_port)
{
return TCP_DPM_EVT_DISCOVER_SVIDS;
}
#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_SVID */
#ifdef CONFIG_USB_PD_MODE_OPERATION
static uint8_t dpm_reaction_mode_operation(struct pd_port *pd_port)
{
if (svdm_notify_pe_ready(pd_port))
return DPM_READY_REACTION_BUSY;
return 0;
}
#endif /* CONFIG_USB_PD_MODE_OPERATION */
/*
* DPM Local/Remote Alert reaction
*/
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_DPM_AUTO_SEND_ALERT
static uint8_t dpm_reaction_send_alert(struct pd_port *pd_port)
{
uint32_t alert_urgent;
struct pe_data *pe_data = &pd_port->pe_data;
alert_urgent = pe_data->local_alert;
alert_urgent &= ~ADO_GET_STATUS_ONCE_MASK;
if (!pe_data->get_status_once)
pe_data->local_alert = alert_urgent;
if ((!pe_data->pe_ready) && (alert_urgent == 0))
return 0;
if (pe_data->local_alert == 0)
return 0;
return TCP_DPM_EVT_ALERT;
}
#endif /* CONFIG_USB_PD_DPM_AUTO_SEND_ALERT */
#ifdef CONFIG_USB_PD_DPM_AUTO_GET_STATUS
const uint32_t c_get_status_alert_type =
ADO_ALERT_OCP | ADO_ALERT_OTP | ADO_ALERT_OVP | ADO_ALERT_OPER_CHANGED |
ADO_ALERT_SRC_IN_CHANGED;
static inline uint8_t dpm_reaction_alert_status_changed(struct pd_port *pd_port)
{
pd_port->pe_data.remote_alert &=
~ADO_ALERT_TYPE_SET(c_get_status_alert_type);
return TCP_DPM_EVT_GET_STATUS;
}
static inline uint8_t dpm_reaction_alert_battry_changed(struct pd_port *pd_port)
{
uint8_t i;
uint8_t mask;
uint8_t bat_change_i = 255;
uint8_t bat_change_mask1, bat_change_mask2;
bat_change_mask1 = ADO_FIXED_BAT(pd_port->pe_data.remote_alert);
bat_change_mask2 = ADO_HOT_SWAP_BAT(pd_port->pe_data.remote_alert);
if (bat_change_mask1) {
for (i = 0; i < 4; i++) {
mask = 1 << i;
if (bat_change_mask1 & mask) {
bat_change_i = i;
bat_change_mask1 &= ~mask;
pd_port->pe_data.remote_alert &=
~ADO_FIXED_BAT_SET(bat_change_mask1);
break;
}
}
} else if (bat_change_mask2) {
for (i = 0; i < 4; i++) {
mask = 1 << i;
if (bat_change_mask2 & mask) {
bat_change_i = i + 4;
bat_change_mask2 &= ~mask;
pd_port->pe_data.remote_alert &=
~ADO_HOT_SWAP_BAT_SET(bat_change_mask2);
break;
}
}
}
if (bat_change_mask1 == 0 && bat_change_mask2 == 0) {
pd_port->pe_data.remote_alert &=
~ADO_ALERT_TYPE_SET(ADO_ALERT_BAT_CHANGED);
}
if (bat_change_i == 255)
return 0;
pd_port->tcp_event.tcp_dpm_data.data_object[0] = bat_change_i;
return TCP_DPM_EVT_GET_BAT_STATUS;
}
static uint8_t dpm_reaction_handle_alert(struct pd_port *pd_port)
{
uint32_t alert_type = ADO_ALERT_TYPE(pd_port->pe_data.remote_alert);
if (alert_type & c_get_status_alert_type)
return dpm_reaction_alert_status_changed(pd_port);
if (alert_type & ADO_ALERT_BAT_CHANGED)
return dpm_reaction_alert_battry_changed(pd_port);
return 0;
}
#endif /* CONFIG_USB_PD_DPM_AUTO_GET_STATUS */
#endif /* CONFIG_USB_PD_REV30 */
/*
* DPM Idle reaction
*/
static inline uint8_t dpm_get_pd_connect_state(struct pd_port *pd_port)
{
if (pd_port->power_role == PD_ROLE_SOURCE) {
if (pd_check_rev30(pd_port))
return PD_CONNECT_PE_READY_SRC_PD30;
return PD_CONNECT_PE_READY_SRC;
}
if (pd_check_rev30(pd_port)) {
if (pd_is_source_support_apdo(pd_port))
return PD_CONNECT_PE_READY_SNK_APDO;
return PD_CONNECT_PE_READY_SNK_PD30;
}
return PD_CONNECT_PE_READY_SNK;
}
static inline void dpm_check_vconn_highv_prot(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_VCONN_SAFE5V_ONLY
struct tcpc_device *tcpc = pd_port->tcpc;
struct pe_data *pe_data = &pd_port->pe_data;
bool vconn_highv_prot = pd_port->request_v_new > 5000;
if (pe_data->vconn_highv_prot && !vconn_highv_prot &&
tcpc->tcpc_flags & TCPC_FLAGS_VCONN_SAFE5V_ONLY) {
DPM_INFO("VC_HIGHV_PROT: %d\n", vconn_highv_prot);
pe_data->vconn_highv_prot = vconn_highv_prot;
pd_set_vconn(pd_port, pe_data->vconn_highv_prot_role);
}
#endif /* CONFIG_USB_PD_VCONN_SAFE5V_ONLY */
}
static uint8_t dpm_reaction_update_pe_ready(struct pd_port *pd_port)
{
uint8_t state;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (!pd_port->pe_data.pe_ready) {
DPM_INFO("PE_READY\n");
pd_port->pe_data.pe_ready = true;
#if IS_ENABLED(CONFIG_DUAL_ROLE_USB_INTF)
dual_role_instance_changed(pd_port->tcpc->dr_usb);
#endif /* CONFIG_DUAL_ROLE_USB_INTF */
}
state = dpm_get_pd_connect_state(pd_port);
pd_update_connect_state(pd_port, state);
dpm_check_vconn_highv_prot(pd_port);
pd_dpm_dynamic_disable_vconn(pd_port);
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
pd_port->pe_data.pd_traffic_idle = true;
if (pd_check_rev30(pd_port) && (pd_port->power_role == PD_ROLE_SOURCE))
pd_set_sink_tx(pd_port, PD30_SINK_TX_OK);
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
return 0;
}
/*
* DPM reaction declaration
*/
typedef uint8_t (*dpm_reaction_fun)(struct pd_port *pd_port);
struct dpm_ready_reaction {
uint32_t bit_mask;
uint8_t condition;
dpm_reaction_fun handler;
};
#define DECL_DPM_REACTION(xmask, xcond, xhandler) \
{ \
.bit_mask = xmask, .condition = xcond, .handler = xhandler, \
}
#define DECL_DPM_REACTION_ALWAYS(xhandler) \
DECL_DPM_REACTION(DPM_REACTION_CAP_ALWAYS, DPM_REACTION_COND_ALWAYS, \
xhandler)
#define DECL_DPM_REACTION_CHECK_ONCE(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_ALWAYS | \
DPM_REACTION_COND_CHECK_ONCE, \
xhandler)
#define DECL_DPM_REACTION_LIMITED_RETRIES(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_ALWAYS | \
DPM_REACTION_COND_LIMITED_RETRIES, \
xhandler)
#define DECL_DPM_REACTION_ONE_SHOT(xmask, xhandler) \
DECL_DPM_REACTION( \
xmask, DPM_REACTION_COND_ALWAYS | DPM_REACTION_COND_ONE_SHOT, \
xhandler)
#define DECL_DPM_REACTION_UFP(xmask, xhandler) \
DECL_DPM_REACTION(xmask, DPM_REACTION_COND_UFP_ONLY, xhandler)
#define DECL_DPM_REACTION_DFP(xmask, xhandler) \
DECL_DPM_REACTION(xmask, DPM_REACTION_COND_DFP_ONLY, xhandler)
#define DECL_DPM_REACTION_PD30(xmask, xhandler) \
DECL_DPM_REACTION(xmask, DPM_REACTION_COND_PD30, xhandler)
#define DECL_DPM_REACTION_PD30_LIMITED_RETRIES(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_PD30 | \
DPM_REACTION_COND_LIMITED_RETRIES, \
xhandler)
#define DECL_DPM_REACTION_PD30_ONE_SHOT(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_PD30 | DPM_REACTION_COND_ONE_SHOT, \
xhandler)
#define DECL_DPM_REACTION_DFP_PD30_ONE_SHOT(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_DFP_ONLY | \
DPM_REACTION_COND_PD30 | \
DPM_REACTION_COND_ONE_SHOT, \
xhandler)
#define DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_DFP_ONLY | \
DPM_REACTION_COND_PD30 | \
DPM_REACTION_COND_LIMITED_RETRIES, \
xhandler)
#define DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_DFP_ONLY | \
DPM_REACTION_COND_PD30 | \
DPM_REACTION_COND_CHECK_ONCE, \
xhandler)
#define DECL_DPM_REACTION_DFP_PD30_RUN_ONCE(xmask, xhandler) \
DECL_DPM_REACTION(xmask, \
DPM_REACTION_COND_DFP_ONLY | \
DPM_REACTION_COND_PD30 | \
DPM_REACTION_COND_CHECK_ONCE | \
DPM_REACTION_COND_ONE_SHOT, \
xhandler)
static const struct dpm_ready_reaction dpm_reactions[] = {
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_DPM_AUTO_SEND_ALERT
DECL_DPM_REACTION_PD30(DPM_REACTION_CAP_ALWAYS,
dpm_reaction_send_alert),
#endif /* CONFIG_USB_PD_DPM_AUTO_SEND_ALERT */
#ifdef CONFIG_USB_PD_DPM_AUTO_GET_STATUS
DECL_DPM_REACTION_PD30(DPM_REACTION_CAP_ALWAYS,
dpm_reaction_handle_alert),
#endif /* CONFIG_USB_PD_DPM_AUTO_GET_STATUS */
#endif /* CONFIG_USB_PD_REV30 */
#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY
DECL_DPM_REACTION_DFP(DPM_REACTION_DFP_FLOW_DELAY,
dpm_reaction_dfp_flow_delay),
#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */
#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY
DECL_DPM_REACTION_UFP(DPM_REACTION_UFP_FLOW_DELAY,
dpm_reaction_ufp_flow_delay),
#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */
DECL_DPM_REACTION_LIMITED_RETRIES(DPM_REACTION_GET_SINK_CAP,
dpm_reaction_get_sink_cap),
DECL_DPM_REACTION_LIMITED_RETRIES(DPM_REACTION_GET_SOURCE_CAP,
dpm_reaction_get_source_cap),
DECL_DPM_REACTION_LIMITED_RETRIES(DPM_REACTION_ATTEMPT_GET_FLAG,
dpm_reaction_attemp_get_flag),
#ifdef CONFIG_USB_PD_REV30
DECL_DPM_REACTION_PD30_ONE_SHOT(DPM_REACTION_GET_SOURCE_CAP_EXT,
dpm_reaction_get_source_cap_ext),
#endif /* CONFIG_USB_PD_REV30 */
#ifdef CONFIG_USB_PD_PR_SWAP
DECL_DPM_REACTION_CHECK_ONCE(DPM_REACTION_REQUEST_PR_SWAP,
dpm_reaction_request_pr_swap),
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_DR_SWAP
DECL_DPM_REACTION_CHECK_ONCE(DPM_REACTION_REQUEST_DR_SWAP,
dpm_reaction_request_dr_swap),
#endif /* CONFIG_USB_PD_DR_SWAP */
#ifdef CONFIG_TCPC_VCONN_SUPPLY_MODE
DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE(DPM_REACTION_DYNAMIC_VCONN,
dpm_reaction_dynamic_vconn),
#endif /* CONFIG_TCPC_VCONN_SUPPLY_MODE */
#ifdef CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN
DECL_DPM_REACTION_DFP_PD30_RUN_ONCE(DPM_REACTION_REQUEST_VCONN_SRC,
dpm_reaction_request_vconn_source),
#endif /* CONFIG_USB_PD_DISCOVER_CABLE_REQUEST_VCONN */
#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY
DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE(DPM_REACTION_VCONN_STABLE_DELAY,
dpm_reaction_vconn_stable_delay),
#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */
#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID
DECL_DPM_REACTION_DFP_PD30_CHECK_ONCE(DPM_REACTION_DISCOVER_CABLE,
pd_dpm_reaction_discover_cable),
#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */
#ifdef CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN
DECL_DPM_REACTION_DFP_PD30_RUN_ONCE(DPM_REACTION_RETURN_VCONN_SRC,
dpm_reaction_return_vconn_source),
#endif /* CONFIG_USB_PD_DISCOVER_CABLE_RETURN_VCONN */
#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_ID
DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES(DPM_REACTION_DISCOVER_ID,
dpm_reaction_discover_id),
#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_ID */
#ifdef CONFIG_USB_PD_ATTEMP_DISCOVER_SVID
DECL_DPM_REACTION_DFP_PD30_LIMITED_RETRIES(DPM_REACTION_DISCOVER_SVID,
dpm_reaction_discover_svid),
#endif /* CONFIG_USB_PD_ATTEMP_DISCOVER_SVID */
#ifdef CONFIG_USB_PD_MODE_OPERATION
DECL_DPM_REACTION_ALWAYS(dpm_reaction_mode_operation),
#endif /* CONFIG_USB_PD_MODE_OPERATION */
DECL_DPM_REACTION_ALWAYS(dpm_reaction_update_pe_ready),
};
/**
* dpm_get_reaction_env
*
* Get current reaction's environmental conditions.
*
* Returns environmental conditions.
*/
static inline uint8_t dpm_get_reaction_env(struct pd_port *pd_port)
{
uint8_t conditions;
if (pd_port->data_role == PD_ROLE_DFP)
conditions = DPM_REACCOND_DFP;
else
conditions = DPM_REACCOND_UFP;
if (pd_check_rev30(pd_port))
conditions |= DPM_REACTION_COND_PD30;
return conditions;
}
/**
* dpm_check_reaction_busy
*
* check this reaction is still keep busy
*
* @ reaction : which reaction is checked.
*
* Return Boolean to indicate busy or not.
*/
static inline bool
dpm_check_reaction_busy(struct pd_port *pd_port,
const struct dpm_ready_reaction *reaction)
{
if (reaction->bit_mask & DPM_REACTION_CAP_ALWAYS)
return false;
return !dpm_reaction_check(pd_port, DPM_REACTION_CAP_READY_ONCE);
}
/**
* dpm_check_reaction_available
*
* check this reaction is available.
*
* @ env : Environmental conditions of reaction table
* @ reaction : which reaction is checked.
*
* Returns TCP_DPM_EVT_ID if available;
* Returns Zero to indicate if not available.
* Returns DPM_READY_REACTION_BUSY to indicate
* this reaction is waiting for being finished.
*/
static inline uint8_t
dpm_check_reaction_available(struct pd_port *pd_port, uint8_t env,
const struct dpm_ready_reaction *reaction)
{
int ret;
if (!dpm_reaction_check(pd_port, reaction->bit_mask))
return 0;
if (!(reaction->condition & env))
return 0;
if (dpm_check_reaction_busy(pd_port, reaction))
return DPM_READY_REACTION_BUSY;
ret = reaction->handler(pd_port);
if (ret == 0 && reaction->condition & DPM_REACTION_COND_CHECK_ONCE)
dpm_reaction_clear(pd_port, reaction->bit_mask);
return ret;
}
/**
* dpm_check_reset_reaction
*
* Once trigger one reaction,
* check if automatically clear this reaction flag.
*
* For the following reactions type:
* DPM_REACTION_COND_ONE_SHOT,
* DPM_REACTION_COND_LIMITED_RETRIES
*
* @ reaction : which reaction is triggered.
*
* Return Boolean to indicate automatically clear or not.
*/
static inline bool
dpm_check_clear_reaction(struct pd_port *pd_port,
const struct dpm_ready_reaction *reaction)
{
if (pd_port->pe_data.dpm_reaction_id != reaction->bit_mask)
pd_port->pe_data.dpm_reaction_retry = 0;
pd_port->pe_data.dpm_reaction_retry++;
pd_port->pe_data.dpm_reaction_id = reaction->bit_mask;
if (reaction->condition & DPM_REACTION_COND_ONE_SHOT)
return true;
if (reaction->condition & DPM_REACTION_COND_LIMITED_RETRIES)
return pd_port->pe_data.dpm_reaction_retry > 3;
return false;
}
uint8_t pd_dpm_get_ready_reaction(struct pd_port *pd_port)
{
uint8_t evt;
uint8_t env;
uint32_t clear_reaction = DPM_REACTION_CAP_READY_ONCE;
const struct dpm_ready_reaction *reaction = dpm_reactions;
const struct dpm_ready_reaction *reaction_last =
dpm_reactions + ARRAY_SIZE(dpm_reactions);
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
env = dpm_get_reaction_env(pd_port);
do {
evt = dpm_check_reaction_available(pd_port, env, reaction);
} while ((evt == 0) && (++reaction < reaction_last));
if (evt > 0 && dpm_check_clear_reaction(pd_port, reaction)) {
clear_reaction |= reaction->bit_mask;
DPM_DBG("clear_reaction=%d\n", evt);
}
dpm_reaction_clear(pd_port, clear_reaction);
return evt;
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* PD Device Policy Manager for UVDM
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/pd_dpm_prv.h>
#ifdef CONFIG_USB_PD_RICHTEK_UVDM
bool richtek_dfp_notify_pe_startup(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
UVDM_INFO("%s\n", __func__);
pd_port->richtek_init_done = false;
return true;
}
int richtek_dfp_notify_pe_ready(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
if (pd_port->data_role != PD_ROLE_DFP)
return 0;
if (pd_port->richtek_init_done)
return 0;
pd_port->richtek_init_done = true;
UVDM_INFO("%s\n", __func__);
#if 0
pd_port->uvdm_cnt = 3;
pd_port->uvdm_wait_resp = true;
pd_port->uvdm_data[0] = PD_UVDM_HDR(USB_VID_RICHTEK, 0x4321);
pd_port->uvdm_data[1] = 0x11223344;
pd_port->uvdm_data[2] = 0x44332211;
pd_put_tcp_vdm_event(pd_port, TCP_DPM_EVT_UVDM);
#endif
return 1;
}
bool richtek_dfp_notify_uvdm(struct pd_port *pd_port,
struct svdm_svid_data *svid_data, bool ack)
{
uint32_t resp_cmd = 0;
if (ack) {
if (pd_port->uvdm_wait_resp)
resp_cmd = PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]);
UVDM_INFO("dfp_notify: ACK (0x%x)\n", resp_cmd);
} else
UVDM_INFO("dfp_notify: NAK\n");
return true;
}
bool richtek_ufp_notify_uvdm(struct pd_port *pd_port,
struct svdm_svid_data *svid_data)
{
uint8_t i;
uint32_t reply_cmd[VDO_MAX_NR];
uint16_t cmd = (uint16_t)PD_UVDM_HDR_CMD(pd_port->uvdm_data[0]);
UVDM_INFO("ufp_notify: 0x%x\n", cmd);
if (cmd >= 0x1000) {
UVDM_INFO("uvdm_no_reply\n");
VDM_STATE_DPM_INFORMED(pd_port);
return true;
}
reply_cmd[0] = PD_UVDM_HDR(USB_VID_RICHTEK, cmd + 1);
for (i = 1; i < pd_port->uvdm_cnt; i++)
reply_cmd[i] = ~pd_port->uvdm_data[i];
pd_reply_custom_vdm(pd_port, TCPC_TX_SOP, pd_port->uvdm_cnt, reply_cmd);
VDM_STATE_NORESP_CMD(pd_port);
return true;
}
#endif /* CONFIG_USB_PD_RICHTEK_UVDM */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,369 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for Common
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
static inline void *pd_get_tcp_event_data_object(struct pd_port *pd_port)
{
return pd_port->tcp_event.tcp_dpm_data.data_object;
}
/*
* Policy Engine General State Activity
*/
static void pe_idle_reset_data(struct pd_port *pd_port)
{
pd_reset_pe_timer(pd_port);
pd_reset_svid_data(pd_port);
pd_port->pe_data.pd_prev_connected = false;
pd_port->state_machine = PE_STATE_MACHINE_IDLE;
pd_enable_bist_test_mode(pd_port, false);
#ifndef CONFIG_USB_PD_TRANSMIT_BIST2
pd_disable_bist_mode2(pd_port);
#endif /* CONFIG_USB_PD_TRANSMIT_BIST2 */
pd_enable_timer(pd_port, PD_TIMER_PE_IDLE_TOUT);
pd_unlock_msg_output(pd_port);
}
void pe_idle1_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE
pd_port->error_recovery_once = 0;
#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */
pe_idle_reset_data(pd_port);
pd_try_put_pe_idle_event(pd_port);
}
void pe_idle2_entry(struct pd_port *pd_port)
{
memset(&pd_port->pe_data, 0, sizeof(struct pe_data));
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_IDLE);
pd_disable_timer(pd_port, PD_TIMER_PE_IDLE_TOUT);
pd_notify_pe_idle(pd_port);
}
void pe_reject_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_REJECT);
}
void pe_error_recovery_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE
pd_port->error_recovery_once++;
#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */
pe_idle_reset_data(pd_port);
pd_notify_pe_error_recovery(pd_port);
pd_try_put_pe_idle_event(pd_port);
}
#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE
void pe_error_recovery_once_entry(struct pd_port *pd_port)
{
uint8_t state = PD_CONNECT_TYPEC_ONLY_SNK_DFT;
pd_notify_pe_hard_reset_completed(pd_port);
if (pd_port->power_role == PD_ROLE_SOURCE) {
state = PD_CONNECT_TYPEC_ONLY_SRC;
pd_dpm_dynamic_disable_vconn(pd_port);
} else
pd_dpm_sink_vbus(pd_port, true);
pd_update_connect_state(pd_port, state);
}
#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */
#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER
void pe_over_recv_hreset_limit_entry(struct pd_port *pd_port)
{
PE_INFO("OverHResetLimit++\n");
pe_idle_reset_data(pd_port);
pd_notify_pe_over_recv_hreset(pd_port);
PE_INFO("OverHResetLimit--\n");
}
#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */
void pe_bist_test_data_entry(struct pd_port *pd_port)
{
PE_STATE_IGNORE_UNKNOWN_EVENT(pd_port);
pd_enable_bist_test_mode(pd_port, true);
}
void pe_bist_test_data_exit(struct pd_port *pd_port)
{
pd_enable_bist_test_mode(pd_port, false);
}
void pe_bist_carrier_mode_2_entry(struct pd_port *pd_port)
{
pd_send_bist_mode2(pd_port);
pd_enable_pe_state_timer(pd_port, PD_TIMER_BIST_CONT_MODE);
}
void pe_bist_carrier_mode_2_exit(struct pd_port *pd_port)
{
pd_disable_bist_mode2(pd_port);
}
/*
* Policy Engine Share State Activity
*/
static inline uint8_t pe30_power_ready_entry(struct pd_port *pd_port)
{
uint8_t rx_cap = PD_RX_CAP_PE_READY_UFP;
if (pd_port->vconn_role)
rx_cap = PD_RX_CAP_PE_READY_DFP;
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
dpm_reaction_clear(pd_port, DPM_REACTION_DFP_FLOW_DELAY |
DPM_REACTION_UFP_FLOW_DELAY);
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
return rx_cap;
}
static inline uint8_t pe20_power_ready_entry(struct pd_port *pd_port)
{
uint8_t rx_cap = PD_RX_CAP_PE_READY_UFP;
if (pd_port->data_role == PD_ROLE_DFP)
rx_cap = PD_RX_CAP_PE_READY_DFP;
return rx_cap;
}
void pe_power_ready_entry(struct pd_port *pd_port)
{
uint8_t rx_cap;
pd_port->state_machine = PE_STATE_MACHINE_NORMAL;
pd_port->pe_data.during_swap = false;
pd_port->pe_data.explicit_contract = true;
#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER
pd_port->pe_data.renegotiation_count = 0;
#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */
if (pd_check_rev30(pd_port))
rx_cap = pe30_power_ready_entry(pd_port);
else
rx_cap = pe20_power_ready_entry(pd_port);
pd_set_rx_enable(pd_port, rx_cap);
dpm_reaction_set(pd_port, DPM_REACTION_CAP_READY_ONCE);
pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_SUCCESS);
}
#ifdef CONFIG_USB_PD_REV30
/*
* [PD3.0] Figure 8-85 Get Battery Capabilities State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE
void pe_get_battery_cap_entry(struct pd_port *pd_port)
{
struct pd_get_battery_capabilities *gbcdb =
pd_get_tcp_event_data_object(pd_port);
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ext_msg(pd_port, PD_EXT_GET_BAT_CAP, PD_GBCDB_SIZE, gbcdb);
}
void pe_get_battery_cap_exit(struct pd_port *pd_port)
{
pd_dpm_inform_battery_cap(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */
/*
* [PD3.0] Figure 8-86 Give Battery Capabilities State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL
void pe_give_battery_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_battery_cap(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */
/*
* [PD3.0] Figure 8-87 Get Battery Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE
void pe_get_battery_status_entry(struct pd_port *pd_port)
{
struct pd_get_battery_status *gbsdb =
pd_get_tcp_event_data_object(pd_port);
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ext_msg(pd_port, PD_EXT_GET_BAT_STATUS, PD_GBSDB_SIZE,
gbsdb);
}
void pe_get_battery_status_exit(struct pd_port *pd_port)
{
pd_dpm_inform_battery_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */
/*
* [PD3.0] Figure 8-88 Give Battery Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL
void pe_give_battery_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_battery_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */
/*
* [PD3.0] Figure 8-89 Get Manufacturer Information State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE
void pe_get_manufacturer_info_entry(struct pd_port *pd_port)
{
struct pd_get_manufacturer_info *gmidb =
pd_get_tcp_event_data_object(pd_port);
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ext_msg(pd_port, PD_EXT_GET_MFR_INFO, PD_GMIDB_SIZE, gmidb);
}
void pe_get_manufacturer_info_exit(struct pd_port *pd_port)
{
pd_dpm_inform_mfrs_info(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */
/*
* [PD3.0] Figure 8-90 Give Manufacturer Information State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL
void pe_give_manufacturer_info_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_mfrs_info(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */
/*
* [PD3.0] Figure 8-91 Get Country Codes State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE
void pe_get_country_codes_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_COUNTRY_CODE);
}
void pe_get_country_codes_exit(struct pd_port *pd_port)
{
pd_dpm_inform_country_codes(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */
/*
* [PD3.0] Figure 8-92 Give Country Codes State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL
void pe_give_country_codes_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_country_codes(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */
/*
* [PD3.0] Figure 8-93 Get Country Information State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE
void pe_get_country_info_entry(struct pd_port *pd_port)
{
uint32_t *ccdo = pd_get_tcp_event_data_object(pd_port);
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_data_msg(pd_port, PD_DATA_GET_COUNTRY_INFO, PD_CCDO_SIZE,
ccdo);
}
void pe_get_country_info_exit(struct pd_port *pd_port)
{
pd_dpm_inform_country_info(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */
/*
* [PD3.0] Figure 8-94 Give Country Information State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL
void pe_give_country_info_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_country_info(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_LOCAL */
/*
* [PD3.0] Unsupported, Unrecognized UVDM and Unsupported SVDM.
*/
void pe_vdm_not_supported_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED);
}
#endif /* CONFIG_USB_PD_REV30 */

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for DBGACC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
void pe_dbg_ready_entry(struct pd_port *pd_port)
{
uint8_t state;
if (pd_port->pe_data.pe_ready)
return;
pd_port->pe_data.pe_ready = true;
pd_reset_protocol_layer(pd_port, false);
if (pd_port->data_role == PD_ROLE_UFP) {
PE_INFO("Custom_DBGACC : UFP\n");
state = PD_CONNECT_PE_READY_DBGACC_UFP;
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_READY_UFP);
} else {
PE_INFO("Custom_DBGACC : DFP\n");
state = PD_CONNECT_PE_READY_DBGACC_DFP;
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_READY_DFP);
}
pd_update_connect_state(pd_port, state);
}
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */

View File

@ -0,0 +1,221 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for DFP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-64 DFP to UFP VDM Discover Identity State Diagram
*/
void pe_dfp_ufp_vdm_identity_request_entry(struct pd_port *pd_port)
{
pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP);
}
void pe_dfp_ufp_vdm_identity_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_id(pd_port, true);
}
void pe_dfp_ufp_vdm_identity_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_id(pd_port, false);
}
/*
* [PD2.0] Figure 8-65 DFP VDM Discover Identity State Diagram
*/
void pe_dfp_cbl_vdm_identity_request_entry(struct pd_port *pd_port)
{
pd_port->pe_data.discover_id_counter++;
pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP_PRIME);
}
void pe_dfp_cbl_vdm_identity_acked_entry(struct pd_port *pd_port)
{
pd_dpm_inform_cable_id(pd_port, false);
}
void pe_dfp_cbl_vdm_identity_naked_entry(struct pd_port *pd_port)
{
pd_dpm_inform_cable_id(pd_port, false);
}
/*
* [PD2.0] Figure 8-66 DFP VDM Discover SVIDs State Diagram
*/
void pe_dfp_vdm_svids_request_entry(struct pd_port *pd_port)
{
pd_send_vdm_discover_svids(pd_port, TCPC_TX_SOP);
}
void pe_dfp_vdm_svids_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_svids(pd_port, true);
}
void pe_dfp_vdm_svids_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_svids(pd_port, false);
}
/*
* [PD2.0] Figure 8-67 DFP VDM Discover Modes State Diagram
*/
void pe_dfp_vdm_modes_request_entry(struct pd_port *pd_port)
{
pd_send_vdm_discover_modes(pd_port, TCPC_TX_SOP, pd_port->mode_svid);
}
void pe_dfp_vdm_modes_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_modes(pd_port, true);
}
void pe_dfp_vdm_modes_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_modes(pd_port, false);
}
/*
* [PD2.0] Figure 8-68 DFP VDM Mode Entry State Diagram
*/
void pe_dfp_vdm_mode_entry_request_entry(struct pd_port *pd_port)
{
pd_send_vdm_enter_mode(pd_port, TCPC_TX_SOP, pd_port->mode_svid,
pd_port->mode_obj_pos);
}
void pe_dfp_vdm_mode_entry_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_enter_mode(pd_port, true);
}
void pe_dfp_vdm_mode_entry_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_enter_mode(pd_port, false);
}
/*
* [PD2.0] Figure 8-69 DFP VDM Mode Exit State Diagram
*/
void pe_dfp_vdm_mode_exit_request_entry(struct pd_port *pd_port)
{
pd_send_vdm_exit_mode(pd_port, TCPC_TX_SOP, pd_port->mode_svid,
pd_port->mode_obj_pos);
}
void pe_dfp_vdm_mode_exit_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_exit_mode(pd_port);
}
/*
* [PD2.0] Figure 8-70 DFP VDM Attention State Diagram
*/
void pe_dfp_vdm_attention_request_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_attention(pd_port);
}
/*
* [PD2.0] Figure 8-83 DFP Cable Soft Reset or Cable Reset State Diagram
*/
#ifdef CONFIG_PD_DFP_RESET_CABLE
void pe_dfp_cbl_send_soft_reset_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG_OR_TX_FAILED(pd_port);
pd_send_cable_soft_reset(pd_port);
}
void pe_dfp_cbl_send_cable_reset_entry(struct pd_port *pd_port)
{
/* TODO : we don't do cable reset now */
}
#endif /* CONFIG_PD_DFP_RESET_CABLE */
/*
* [PD2.0] Display Port
*/
#ifdef CONFIG_USB_PD_ALT_MODE_DFP
void pe_dfp_vdm_dp_status_update_request_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_send_dp_status_update(pd_port);
}
void pe_dfp_vdm_dp_status_update_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_dp_status_update(pd_port, true);
}
void pe_dfp_vdm_dp_status_update_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_dp_status_update(pd_port, false);
}
void pe_dfp_vdm_dp_configuration_request_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_send_dp_configuration(pd_port);
}
void pe_dfp_vdm_dp_configuration_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_dp_configuration(pd_port, true);
}
void pe_dfp_vdm_dp_configuration_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_dp_configuration(pd_port, false);
}
#endif /* CONFIG_USB_PD_ALT_MODE_DFP */
/*
* UVDM
*/
#ifdef CONFIG_USB_PD_CUSTOM_VDM
void pe_dfp_uvdm_send_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_send_uvdm(pd_port);
}
void pe_dfp_uvdm_acked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_uvdm(pd_port, true);
}
void pe_dfp_uvdm_naked_entry(struct pd_port *pd_port)
{
pd_dpm_dfp_inform_uvdm(pd_port, false);
}
#endif /* CONFIG_USB_PD_CUSTOM_VDM */

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for DR
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0]
* Figure 8-53 Dual-Role (Source) Get Source Capabilities diagram
* Figure 8-54 Dual-Role (Source) Give Sink Capabilities diagram
* Figure 8-55 Dual-Role (Sink) Get Sink Capabilities State Diagram
* Figure 8-56 Dual-Role (Sink) Give Source Capabilities State Diagram
*/
void pe_dr_src_get_source_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG_OR_RJ(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP);
}
void pe_dr_src_get_source_cap_exit(struct pd_port *pd_port)
{
pd_dpm_dr_inform_source_cap(pd_port);
}
void pe_dr_src_give_sink_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_sink_caps(pd_port);
}
void pe_dr_snk_get_sink_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG_OR_RJ(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SINK_CAP);
}
void pe_dr_snk_get_sink_cap_exit(struct pd_port *pd_port)
{
pd_dpm_dr_inform_sink_cap(pd_port);
}
void pe_dr_snk_give_source_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_source_caps(pd_port);
}
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
void pe_dr_snk_give_source_cap_ext_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_source_cap_ext(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
void pe_dr_src_get_source_cap_ext_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP_EXT);
}
void pe_dr_src_get_source_cap_ext_exit(struct pd_port *pd_port)
{
pd_dpm_inform_source_cap_ext(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for DRS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-49: Type-C DFP to UFP Data Role Swap State Diagram
*/
void pe_drs_dfp_ufp_evaluate_dr_swap_entry(struct pd_port *pd_port)
{
pd_dpm_drs_evaluate_swap(pd_port, PD_ROLE_UFP);
}
void pe_drs_dfp_ufp_accept_dr_swap_entry(struct pd_port *pd_port)
{
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT);
}
void pe_drs_dfp_ufp_change_to_ufp_entry(struct pd_port *pd_port)
{
pd_dpm_drs_change_role(pd_port, PD_ROLE_UFP);
}
void pe_drs_dfp_ufp_send_dr_swap_entry(struct pd_port *pd_port)
{
pe_send_swap_request_entry(pd_port, PD_CTRL_DR_SWAP);
}
void pe_drs_dfp_ufp_reject_dr_swap_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg(pd_port);
}
/*
* [PD2.0] Figure 8-50: Type-C UFP to DFP Data Role Swap State Diagram
*/
void pe_drs_ufp_dfp_evaluate_dr_swap_entry(struct pd_port *pd_port)
{
pd_dpm_drs_evaluate_swap(pd_port, PD_ROLE_DFP);
}
void pe_drs_ufp_dfp_accept_dr_swap_entry(struct pd_port *pd_port)
{
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT);
}
void pe_drs_ufp_dfp_change_to_dfp_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_RESET_CABLE
dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE);
#endif /* CONFIG_USB_PD_RESET_CABLE */
pd_dpm_drs_change_role(pd_port, PD_ROLE_DFP);
}
void pe_drs_ufp_dfp_send_dr_swap_entry(struct pd_port *pd_port)
{
pe_send_swap_request_entry(pd_port, PD_CTRL_DR_SWAP);
}
void pe_drs_ufp_dfp_reject_dr_swap_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg(pd_port);
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for PRS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-51:
* Dual-Role Port in Source to Sink Power Role Swap State Diagram
*/
void pe_prs_src_snk_evaluate_pr_swap_entry(struct pd_port *pd_port)
{
pd_dpm_prs_evaluate_swap(pd_port, PD_ROLE_SINK);
}
void pe_prs_src_snk_accept_pr_swap_entry(struct pd_port *pd_port)
{
pd_notify_pe_execute_pr_swap(pd_port, true);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT);
}
void pe_prs_src_snk_transition_to_off_entry(struct pd_port *pd_port)
{
pd_lock_msg_output(pd_port); /* for tSRCTransition */
pd_notify_pe_execute_pr_swap(pd_port, true);
pd_enable_timer(pd_port, PD_TIMER_SOURCE_TRANSITION);
}
void pe_prs_src_snk_assert_rd_entry(struct pd_port *pd_port)
{
pd_dpm_prs_change_role(pd_port, PD_ROLE_SINK);
}
void pe_prs_src_snk_wait_source_on_entry(struct pd_port *pd_port)
{
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY);
}
void pe_prs_src_snk_send_swap_entry(struct pd_port *pd_port)
{
pe_send_swap_request_entry(pd_port, PD_CTRL_PR_SWAP);
}
void pe_prs_src_snk_reject_pr_swap_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg(pd_port);
}
/*
* [PD2.0] Figure 8-52:
* Dual-role Port in Sink to Source Power Role Swap State Diagram
*/
void pe_prs_snk_src_evaluate_pr_swap_entry(struct pd_port *pd_port)
{
pd_dpm_prs_evaluate_swap(pd_port, PD_ROLE_SOURCE);
}
void pe_prs_snk_src_accept_pr_swap_entry(struct pd_port *pd_port)
{
pd_notify_pe_execute_pr_swap(pd_port, true);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT);
}
void pe_prs_snk_src_transition_to_off_entry(struct pd_port *pd_port)
{
/*
* Sink should call pd_notify_pe_execute_pr_swap before this state,
* because source may turn off power & change CC before we got
* GoodCRC or Accept.
*/
pd_port->pe_data.during_swap = true;
pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_SOURCE_OFF);
pd_dpm_prs_turn_off_power_sink(pd_port);
}
void pe_prs_snk_src_assert_rp_entry(struct pd_port *pd_port)
{
pd_dpm_prs_change_role(pd_port, PD_ROLE_SOURCE);
}
void pe_prs_snk_src_source_on_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_RESET_CABLE
dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE);
#endif /* CONFIG_USB_PD_RESET_CABLE */
pd_dpm_dynamic_enable_vconn(pd_port);
pd_dpm_prs_enable_power_source(pd_port, true);
/* Send PS_Rdy in process_event after source_on */
}
void pe_prs_snk_src_send_swap_entry(struct pd_port *pd_port)
{
pd_notify_pe_execute_pr_swap(pd_port, false);
pe_send_swap_request_entry(pd_port, PD_CTRL_PR_SWAP);
}
void pe_prs_snk_src_reject_swap_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg(pd_port);
}

View File

@ -0,0 +1,324 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for SNK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-39 Sink Port state diagram
*/
void pe_snk_startup_entry(struct pd_port *pd_port)
{
uint8_t rx_cap = PD_RX_CAP_PE_STARTUP;
bool pr_swap = pd_port->state_machine == PE_STATE_MACHINE_PR_SWAP;
#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP
uint8_t msg_id_last = pd_port->pe_data.msg_id_rx[TCPC_TX_SOP];
#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */
pd_reset_protocol_layer(pd_port, false);
if (pr_swap) {
/*
* If PE reset rx_cap to startup in here,
* maybe can't meet tSwapSink for PR_SWAP case
*/
rx_cap = PD_RX_CAP_PE_SEND_WAIT_CAP;
#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP
pd_port->msg_id_pr_swap_last = msg_id_last;
#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */
}
pd_set_rx_enable(pd_port, rx_cap);
pd_put_pe_event(pd_port, PD_PE_RESET_PRL_COMPLETED);
}
void pe_snk_discovery_entry(struct pd_port *pd_port)
{
bool wait_valid = true;
if (pd_check_pe_during_hard_reset(pd_port)) {
wait_valid = false;
pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_TRANSITION);
}
pd_enable_vbus_valid_detection(pd_port, wait_valid);
}
void pe_snk_wait_for_capabilities_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW
/* Default current draw after HardReset */
if (pd_check_pe_during_hard_reset(pd_port))
pd_dpm_sink_vbus(pd_port, true);
#endif /* CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW */
pd_notify_pe_hard_reset_completed(pd_port);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_SEND_WAIT_CAP);
pd_enable_pe_state_timer(pd_port, PD_TIMER_SINK_WAIT_CAP);
}
void pe_snk_evaluate_capability_entry(struct pd_port *pd_port)
{
/* Disable UART output for Source SenderResponse */
pd_lock_msg_output(pd_port);
pd_handle_hard_reset_recovery(pd_port);
pd_handle_first_pd_command(pd_port);
pd_port->pe_data.explicit_contract = false;
pd_dpm_snk_evaluate_caps(pd_port);
}
void pe_snk_select_capability_entry(struct pd_port *pd_port)
{
struct pd_event *pd_event = pd_get_curr_pd_event(pd_port);
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
PE_STATE_WAIT_MSG_HRESET_IF_TOUT(pd_port);
if (pd_event->event_type == PD_EVT_DPM_MSG) {
PE_DBG("SelectCap%d, rdo:0x%08x\n", pd_event->msg_sec,
pd_port->last_rdo);
} else {
/* new request, for debug only */
/* pd_dpm_sink_vbus(pd_port, false); */
PE_DBG("NewReq, rdo:0x%08x\n", pd_port->last_rdo);
}
/* Disable UART output for Sink SenderResponse */
pd_lock_msg_output(pd_port);
pd_send_sop_data_msg(pd_port, PD_DATA_REQUEST, 1, &pd_port->last_rdo);
}
void pe_snk_select_capability_exit(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */
if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_ACCEPT)) {
pd_port->pe_data.remote_selected_cap =
RDO_POS(pd_port->last_rdo);
pd_port->cap_miss_match = 0;
} else if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_REJECT)) {
#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER
if (pd_port->cap_miss_match == 0x01) {
PE_INFO("reset renegotiation cnt by cap mismatch\n");
pd_port->pe_data.renegotiation_count = 0;
}
#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */
pd_port->cap_miss_match |= (1 << 1);
} else
pd_port->cap_miss_match = 0;
/* Waiting for Hard-Reset Done */
if (!pd_check_timer_msg_event(pd_port, PD_TIMER_SENDER_RESPONSE))
pd_unlock_msg_output(pd_port);
}
void pe_snk_transition_sink_entry(struct pd_port *pd_port)
{
pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_TRANSITION);
#ifdef CONFIG_USB_PD_SNK_GOTOMIN
if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_GOTO_MIN)) {
if (pd_port->dpm_caps & DPM_CAP_LOCAL_GIVE_BACK)
pd_port->request_i_new = pd_port->request_i_op;
}
#endif /* CONFIG_USB_PD_SNK_GOTOMIN */
pd_dpm_snk_standby_power(pd_port);
}
void pe_snk_ready_entry(struct pd_port *pd_port)
{
if (pd_check_ctrl_msg_event(pd_port, PD_CTRL_WAIT))
pd_enable_timer(pd_port, PD_TIMER_SINK_REQUEST);
pd_notify_pe_snk_explicit_contract(pd_port);
pe_power_ready_entry(pd_port);
}
void pe_snk_hard_reset_entry(struct pd_port *pd_port)
{
int rv = 0;
uint32_t chip_id = 0;
pd_send_hard_reset(pd_port);
rv = tcpci_get_chip_id(pd_port->tcpc, &chip_id);
if (!rv && SC2150A_DID == chip_id) {
pd_enable_timer(pd_port, PD_TIMER_HARD_RESET_COMPLETE);
}
}
void pe_snk_transition_to_default_entry(struct pd_port *pd_port)
{
pd_reset_local_hw(pd_port);
pd_dpm_snk_hard_reset(pd_port);
}
void pe_snk_give_sink_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_sink_caps(pd_port);
}
void pe_snk_get_source_cap_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_TCPM_CB_2ND
PE_STATE_WAIT_MSG(pd_port);
#else
PE_STATE_WAIT_TX_SUCCESS(pd_port);
#endif /* CONFIG_USB_PD_TCPM_CB_2ND */
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP);
}
void pe_snk_send_soft_reset_entry(struct pd_port *pd_port)
{
pd_send_soft_reset(pd_port);
}
void pe_snk_soft_reset_entry(struct pd_port *pd_port)
{
pd_handle_soft_reset(pd_port);
}
/* ---- Policy Engine (PD30) ---- */
#ifdef CONFIG_USB_PD_REV30
/*
* [PD3.0] Figure 8-71 Sink Port Not Supported Message State Diagram
*/
void pe_snk_send_not_supported_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED);
}
void pe_snk_not_supported_received_entry(struct pd_port *pd_port)
{
PE_STATE_DPM_INFORMED(pd_port);
pd_dpm_inform_not_support(pd_port);
}
void pe_snk_chunk_received_entry(struct pd_port *pd_port)
{
pd_enable_timer(pd_port, PD_TIMER_CK_NOT_SUPPORTED);
}
/*
* [PD3.0] Figure 8-74 Sink Port Source Alert State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
void pe_snk_source_alert_received_entry(struct pd_port *pd_port)
{
PE_STATE_DPM_INFORMED(pd_port);
pd_dpm_inform_alert(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
/*
* [PD3.0] Figure 8-75 Sink Port Sink Alert State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
void pe_snk_send_sink_alert_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_alert(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
/*
* [PD3.0] Figure 8-77 Sink Port Get Source Capabilities Extended State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
void pe_snk_get_source_cap_ext_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SOURCE_CAP_EXT);
}
void pe_snk_get_source_cap_ext_exit(struct pd_port *pd_port)
{
pd_dpm_inform_source_cap_ext(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
/*
* [PD3.0] Figure 8-79 Sink Port Get Source Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE
void pe_snk_get_source_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_STATUS);
}
void pe_snk_get_source_status_exit(struct pd_port *pd_port)
{
pd_dpm_inform_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */
/*
* [PD3.0] Figure 8-82 Sink Give Sink Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
void pe_snk_give_sink_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
/*
* [PD3.0] Figure 8-83 Sink Port Get Source PPS Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
void pe_snk_get_pps_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_PPS_STATUS);
}
void pe_snk_get_pps_status_exit(struct pd_port *pd_port)
{
pd_dpm_inform_pps_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
#endif /* CONFIG_USB_PD_REV30 */

View File

@ -0,0 +1,309 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for SRC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/delay.h>
#include <linux/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-38 Source Port Policy Engine state diagram
*/
void pe_src_startup_entry(struct pd_port *pd_port)
{
pd_reset_protocol_layer(pd_port, false);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_STARTUP);
/*
* When entering this state,
* VBUS must be valid even for cc_attached.
*/
pd_enable_timer(pd_port, PD_TIMER_SOURCE_START);
}
void pe_src_discovery_entry(struct pd_port *pd_port)
{
/* The SourceCapabilitiesTimer continues to run during the states
* defined in Source Startup Structured VDM Discover Identity State
* Diagram
*/
pd_port->pe_data.pd_connected = false;
pd_enable_timer(pd_port, PD_TIMER_SOURCE_CAPABILITY);
#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID
if (pd_is_discover_cable(pd_port))
pd_enable_timer(pd_port, PD_TIMER_DISCOVER_ID);
#endif
}
void pe_src_send_capabilities_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG_HRESET_IF_TOUT(pd_port);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_SEND_WAIT_CAP);
pd_dpm_send_source_caps(pd_port);
pd_port->pe_data.cap_counter++;
}
void pe_src_negotiate_capabilities_entry(struct pd_port *pd_port)
{
pd_handle_first_pd_command(pd_port);
pd_dpm_src_evaluate_request(pd_port);
}
void pe_src_transition_supply_entry(struct pd_port *pd_port)
{
uint8_t msg = PD_CTRL_ACCEPT;
struct pd_event *pd_event = pd_get_curr_pd_event(pd_port);
/* goto-min */
if (pd_event->event_type == PD_EVT_TCP_MSG) {
msg = PD_CTRL_GOTO_MIN;
pd_port->request_i_new = pd_port->request_i_op;
}
pd_send_sop_ctrl_msg(pd_port, msg);
}
void pe_src_transition_supply2_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY);
}
void pe_src_ready_entry(struct pd_port *pd_port)
{
pd_notify_pe_src_explicit_contract(pd_port);
pe_power_ready_entry(pd_port);
}
void pe_src_disabled_entry(struct pd_port *pd_port)
{
pd_notify_pe_hard_reset_completed(pd_port);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISABLE);
pd_update_connect_state(pd_port, PD_CONNECT_TYPEC_ONLY_SRC);
pd_dpm_dynamic_disable_vconn(pd_port);
}
void pe_src_capability_response_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg_no_resp(pd_port);
}
void pe_src_hard_reset_entry(struct pd_port *pd_port)
{
pd_send_hard_reset(pd_port);
pd_enable_timer(pd_port, PD_TIMER_PS_HARD_RESET);
pd_enable_timer(pd_port, PD_TIMER_NO_RESPONSE);
}
void pe_src_hard_reset_received_entry(struct pd_port *pd_port)
{
pd_enable_timer(pd_port, PD_TIMER_PS_HARD_RESET);
pd_enable_timer(pd_port, PD_TIMER_NO_RESPONSE);
}
void pe_src_transition_to_default_entry(struct pd_port *pd_port)
{
pd_reset_local_hw(pd_port);
pd_dpm_src_hard_reset(pd_port);
}
void pe_src_transition_to_default_exit(struct pd_port *pd_port)
{
pd_set_vconn(pd_port, PD_ROLE_VCONN_ON);
}
void pe_src_get_sink_cap_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_SINK_CAP);
}
void pe_src_get_sink_cap_exit(struct pd_port *pd_port)
{
pd_dpm_dr_inform_sink_cap(pd_port);
}
void pe_src_wait_new_capabilities_entry(struct pd_port *pd_port)
{
/* Wait for new Source Capabilities */
}
void pe_src_send_soft_reset_entry(struct pd_port *pd_port)
{
pd_send_soft_reset(pd_port);
}
void pe_src_soft_reset_entry(struct pd_port *pd_port)
{
pd_handle_soft_reset(pd_port);
}
/*
* [PD2.0] Figure 8-81
Source Startup Structured VDM Discover Identity State Diagram (TODO)
*/
#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID
#ifdef CONFIG_PD_SRC_RESET_CABLE
void pe_src_cbl_send_soft_reset_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_RESPONSE(pd_port);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISCOVER_CABLE);
pd_send_cable_soft_reset(pd_port);
}
#endif /* CONFIG_PD_SRC_RESET_CABLE */
void pe_src_vdm_identity_request_entry(struct pd_port *pd_port)
{
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_DISCOVER_CABLE);
pd_port->pe_data.discover_id_counter++;
pd_send_vdm_discover_id(pd_port, TCPC_TX_SOP_PRIME);
}
void pe_src_vdm_identity_acked_entry(struct pd_port *pd_port)
{
pd_dpm_inform_cable_id(pd_port, true);
}
void pe_src_vdm_identity_naked_entry(struct pd_port *pd_port)
{
pd_dpm_inform_cable_id(pd_port, true);
}
#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */
#ifdef CONFIG_USB_PD_REV30
/*
* [PD3.0] Source Port Not Supported Message State Diagram
*/
void pe_src_send_not_supported_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_NOT_SUPPORTED);
}
void pe_src_not_supported_received_entry(struct pd_port *pd_port)
{
PE_STATE_DPM_INFORMED(pd_port);
pd_dpm_inform_not_support(pd_port);
}
void pe_src_chunk_received_entry(struct pd_port *pd_port)
{
pd_enable_timer(pd_port, PD_TIMER_CK_NOT_SUPPORTED);
}
/*
* [PD3.0] Figure 8-73 Source Port Source Alert State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
void pe_src_send_source_alert_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_alert(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
/*
* [PD3.0] Figure 8-76 Source Port Sink Alert State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
void pe_src_sink_alert_received_entry(struct pd_port *pd_port)
{
PE_STATE_DPM_INFORMED(pd_port);
pd_dpm_inform_alert(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
/*
* [PD3.0] Figure 8-78 Source Give Source Capabilities Extended State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
void pe_src_give_source_cap_ext_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_source_cap_ext(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
/*
* [PD3.0] Figure 8-80 Source Give Source Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
void pe_src_give_source_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_dpm_send_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
/*
* [PD3.0] Figure 8-81 Source Port Get Sink Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE
void pe_src_get_sink_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_MSG(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_GET_STATUS);
}
void pe_src_get_sink_status_exit(struct pd_port *pd_port)
{
pd_dpm_inform_status(pd_port);
}
#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */
/*
* [PD3.0] Figure 8-84 Source Give Source PPS Status State Diagram
*/
#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE
void pe_src_give_pps_status_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
/* TODO */
PD_BUG_ON(1);
}
#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */
#endif /* CONFIG_USB_PD_REV30 */

View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for UFP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-58 UFP Structured VDM Discover Identity State Diagram
*/
void pe_ufp_vdm_get_identity_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_id_info(pd_port);
}
/*
* [PD2.0] Figure 8-59 UFP Structured VDM Discover SVIDs State Diagram
*/
void pe_ufp_vdm_get_svids_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_svid_info(pd_port);
}
/*
* [PD2.0] Figure 8-60 UFP Structured VDM Discover Modes State Diagram
*/
void pe_ufp_vdm_get_modes_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_mode_info(pd_port);
}
/*
* [PD2.0] Figure 8-61 UFP Structured VDM Enter Mode State Diagram
*/
void pe_ufp_vdm_evaluate_mode_entry_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_enter_mode(pd_port);
}
/*
* [PD2.0] Figure 8-62 UFP Structured VDM Exit Mode State Diagram
*/
void pe_ufp_vdm_mode_exit_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_exit_mode(pd_port);
}
/*
* [PD2.0] Figure 8-63 UFP VDM Attention State Diagram
*/
void pe_ufp_vdm_attention_request_entry(struct pd_port *pd_port)
{
VDM_STATE_NORESP_CMD(pd_port);
switch (pd_port->mode_svid) {
#ifdef CONFIG_USB_PD_ALT_MODE
case USB_SID_DISPLAYPORT:
pd_dpm_ufp_send_dp_attention(pd_port);
break;
#endif
default:
pd_send_vdm_attention(pd_port, TCPC_TX_SOP, pd_port->mode_svid,
pd_port->mode_obj_pos);
break;
}
}
/*
* ALT Mode
*/
#ifdef CONFIG_USB_PD_ALT_MODE
void pe_ufp_vdm_dp_status_update_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_dp_status(pd_port);
}
void pe_ufp_vdm_dp_configure_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_request_dp_config(pd_port);
}
#endif /* CONFIG_USB_PD_ALT_MODE */
/*
* SVMD/UVDM
*/
#ifdef CONFIG_USB_PD_CUSTOM_VDM
void pe_ufp_uvdm_recv_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_recv_uvdm(pd_port);
}
#endif /* CONFIG_USB_PD_CUSTOM_VDM */
void pe_ufp_vdm_send_nak_entry(struct pd_port *pd_port)
{
pd_dpm_ufp_send_svdm_nak(pd_port);
VDM_STATE_DPM_INFORMED(pd_port);
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for VCS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci.h>
#include <linux/usb/tcpc/pd_policy_engine.h>
/*
* [PD2.0] Figure 8-57 VCONN Swap State Diagram
*/
void pe_vcs_send_swap_entry(struct pd_port *pd_port)
{
pe_send_swap_request_entry(pd_port, PD_CTRL_VCONN_SWAP);
}
void pe_vcs_evaluate_swap_entry(struct pd_port *pd_port)
{
pd_dpm_vcs_evaluate_swap(pd_port);
}
void pe_vcs_accept_swap_entry(struct pd_port *pd_port)
{
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_ACCEPT);
}
void pe_vcs_reject_vconn_swap_entry(struct pd_port *pd_port)
{
pd_reply_wait_reject_msg(pd_port);
}
void pe_vcs_wait_for_vconn_entry(struct pd_port *pd_port)
{
pd_enable_pe_state_timer(pd_port, PD_TIMER_VCONN_ON);
}
void pe_vcs_turn_off_vconn_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_DPM_ACK(pd_port);
pd_dpm_vcs_enable_vconn(pd_port, PD_ROLE_VCONN_OFF);
}
void pe_vcs_turn_on_vconn_entry(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_RESET_CABLE
dpm_reaction_set(pd_port, DPM_REACTION_CAP_RESET_CABLE);
#endif /* CONFIG_USB_PD_RESET_CABLE */
#endif /* CONFIG_USB_PD_REV30 */
pd_dpm_vcs_enable_vconn(pd_port, PD_ROLE_VCONN_DYNAMIC_ON);
}
void pe_vcs_send_ps_rdy_entry(struct pd_port *pd_port)
{
PE_STATE_WAIT_TX_SUCCESS(pd_port);
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY);
}

View File

@ -0,0 +1,835 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
/*
* [BLOCK] print event
*/
#if PE_EVENT_DBG_ENABLE
static const char *const pd_ctrl_msg_name[] = {
"ctrl0", "good_crc", "goto_min", "accept",
"reject", "ping", "ps_rdy", "get_src_cap",
"get_snk_cap", "dr_swap", "pr_swap", "vs_swap",
"wait", "soft_reset", "ctrlE", "ctrlF",
#ifdef CONFIG_USB_PD_REV30
"no_support", "get_src_cap_ex", "get_status", "fr_swap",
"get_pps", "get_cc",
#endif /* CONFIG_USB_PD_REV30 */
};
static inline void print_ctrl_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_CTRL_MSG_NR)
PE_EVT_INFO("%s\n", pd_ctrl_msg_name[msg]);
}
static const char *const pd_data_msg_name[] = {
"data0", "src_cap", "request", "bist", "sink_cap",
#ifdef CONFIG_USB_PD_REV30
"bat_status", "alert", "get_ci",
#else
"data5", "data6", "data7",
#endif /* CONFIG_USB_PD_REV30 */
"data8", "data9", "dataA", "dataB", "dataC",
"dataD", "dataE", "vdm",
};
static inline void print_data_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_DATA_MSG_NR)
PE_EVT_INFO("%s\n", pd_data_msg_name[msg]);
}
#ifdef CONFIG_USB_PD_REV30
static const char *const pd_ext_msg_name[] = {
"ext0",
"src_cap_ex",
"status",
"get_bat_cap",
"get_bat_status",
"bat_cap",
"get_mfr_info",
"mfr_info",
"sec_req",
"sec_resp",
"fw_update_req",
"fw_update_res",
"pps_status",
"ci",
"cc",
};
static inline void print_ext_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_EXT_MSG_NR)
PE_EVT_INFO("%s\n", pd_ext_msg_name[msg]);
}
#endif /* CONFIG_USB_PD_REV30 */
static const char *const pd_hw_msg_name[] = {
"Detached", "Attached", "hard_reset", "vbus_high", "vbus_low",
"vbus_0v", "vbus_stable", "tx_err", "discard", "retry_vdm",
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
"sink_tx_change",
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
};
static inline void print_hw_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_HW_MSG_NR)
PE_EVT_INFO("%s\n", pd_hw_msg_name[msg]);
}
static const char *const pd_pe_msg_name[] = {
"reset_prl_done", "pr_at_dft", "hard_reset_done",
"pe_idle", "vdm_reset", "vdm_not_support",
};
static inline void print_pe_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_PE_MSG_NR)
PE_EVT_INFO("%s\n", pd_pe_msg_name[msg]);
}
static const char *const pd_dpm_msg_name[] = {
"ack",
"nak",
"cap_change",
"not_support",
};
static inline void print_dpm_msg_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < PD_DPM_MSG_NR)
PE_EVT_INFO("dpm_%s\n", pd_dpm_msg_name[msg]);
}
static const char *const tcp_dpm_evt_name[] = {
/* TCP_DPM_EVT_UNKONW */
"unknown",
/* TCP_DPM_EVT_PD_COMMAND */
"pr_swap_snk",
"pr_swap_src",
"dr_swap_ufp",
"dr_swap_dfp",
"vc_swap_off",
"vc_swap_on",
"goto_min",
"soft_reset",
"cable_soft_reset",
"get_src_cap",
"get_snk_cap",
"request",
"request_ex",
"request_again",
"bist_cm2",
"dummy",
#ifdef CONFIG_USB_PD_REV30
"get_src_cap_ext",
"get_status",
"fr_swap_snk",
"fr_swap_src",
"get_cc",
"get_pps",
"alert",
"get_ci",
"get_bat_cap",
"get_bat_status",
"get_mfrs_info",
#endif /* CONFIG_USB_PD_REV30 */
/* TCP_DPM_EVT_VDM_COMMAND */
"disc_cable",
"disc_id",
"disc_svid",
"disc_mode",
"enter_mode",
"exit_mode",
"attention",
#ifdef CONFIG_USB_PD_ALT_MODE
"dp_atten",
#ifdef CONFIG_USB_PD_ALT_MODE_DFP
"dp_status",
"dp_config",
#endif /* CONFIG_USB_PD_ALT_MODE_DFP */
#endif /* CONFIG_USB_PD_ALT_MODE */
#ifdef CONFIG_USB_PD_CUSTOM_VDM
"uvdm",
#endif /* CONFIG_USB_PD_CUSTOM_VDM */
/* TCP_DPM_EVT_IMMEDIATELY */
"hard_reset",
"error_recovery",
};
static inline void print_tcp_event(struct tcpc_device *tcpc, uint8_t msg)
{
if (msg < TCP_DPM_EVT_NR)
PE_EVT_INFO("tcp_event(%s), %d\n", tcp_dpm_evt_name[msg], msg);
}
#endif
static inline void print_event(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#if PE_EVENT_DBG_ENABLE
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
print_ctrl_msg_event(tcpc, pd_event->msg);
break;
case PD_EVT_DATA_MSG:
print_data_msg_event(tcpc, pd_event->msg);
break;
#ifdef CONFIG_USB_PD_REV30
case PD_EVT_EXT_MSG:
print_ext_msg_event(tcpc, pd_event->msg);
break;
#endif /* CONFIG_USB_PD_REV30 */
case PD_EVT_DPM_MSG:
print_dpm_msg_event(tcpc, pd_event->msg);
break;
case PD_EVT_HW_MSG:
print_hw_msg_event(tcpc, pd_event->msg);
break;
case PD_EVT_PE_MSG:
print_pe_msg_event(tcpc, pd_event->msg);
break;
case PD_EVT_TIMER_MSG:
PE_EVT_INFO("timer\n");
break;
case PD_EVT_TCP_MSG:
print_tcp_event(tcpc, pd_event->msg);
break;
}
#endif
}
/*---------------------------------------------------------------------------*/
bool pd_make_pe_state_transit(struct pd_port *pd_port, uint8_t curr_state,
const struct pe_state_reaction *state_reaction)
{
int i;
const struct pe_state_transition *state_transition =
state_reaction->state_transition;
for (i = 0; i < state_reaction->nr_transition; i++) {
if (state_transition[i].curr_state == curr_state) {
PE_TRANSIT_STATE(pd_port,
state_transition[i].next_state);
return true;
}
}
return false;
}
/*---------------------------------------------------------------------------*/
static inline bool pd_process_ready_protocol_error(struct pd_port *pd_port)
{
#ifdef CONFIG_USB_PD_REV30
bool multi_chunk;
#endif /* CONFIG_USB_PD_REV30 */
if (!pd_port->curr_unsupported_msg) {
pe_transit_soft_reset_state(pd_port);
return true;
}
if (!pd_check_rev30(pd_port)) {
PE_TRANSIT_STATE(pd_port, PE_REJECT);
return true;
}
#ifdef CONFIG_USB_PD_REV30
multi_chunk = pd_is_multi_chunk_msg(pd_port);
if (pd_port->power_role == PD_ROLE_SINK) {
PE_TRANSIT_STATE(pd_port, multi_chunk ?
PE_SNK_CHUNK_RECEIVED :
PE_SNK_SEND_NOT_SUPPORTED);
return true;
}
PE_TRANSIT_STATE(pd_port, multi_chunk ? PE_SRC_CHUNK_RECEIVED :
PE_SRC_SEND_NOT_SUPPORTED);
return true;
#else
return false;
#endif /* CONFIG_USB_PD_REV30 */
}
bool pd_process_protocol_error(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
bool power_change = false;
#if PE_INFO_ENABLE
uint8_t event_type = pd_event->event_type;
uint8_t msg_type = pd_event->msg;
uint8_t msg_id = pd_get_msg_hdr_id(pd_port);
#endif
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_IGNORE_UNKNOWN_EVENT) {
PE_INFO("Ignore Unknown Event\n");
goto out;
}
if (pd_check_pe_during_hard_reset(pd_port)) {
PE_INFO("Ignore Event during HReset\n");
goto out;
}
switch (pd_port->pe_state_curr) {
case PE_SNK_TRANSITION_SINK:
/* fall through */
case PE_SRC_TRANSITION_SUPPLY: /* never recv ping for Source =.=*/
/* fall through */
case PE_SRC_TRANSITION_SUPPLY2:
power_change = true;
if (pd_event_msg_match(pd_event, PD_EVT_CTRL_MSG,
PD_CTRL_PING)) {
PE_INFO("Ignore Ping\n");
goto out;
}
break;
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_PRS_SRC_SNK_WAIT_SOURCE_ON:
#endif /* CONFIG_USB_PD_PR_SWAP */
if (pd_event_msg_match(pd_event, PD_EVT_CTRL_MSG,
PD_CTRL_PING)) {
PE_INFO("Ignore Ping\n");
goto out;
}
break;
case PE_SNK_READY:
case PE_SRC_READY:
if (pd_process_ready_protocol_error(pd_port)) {
ret = true;
goto out;
}
break;
};
ret = true;
if (pd_port->pe_data.during_swap) {
#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
#else
pe_transit_hard_reset_state(pd_port);
#endif
} else if (power_change)
pe_transit_hard_reset_state(pd_port);
else
pe_transit_soft_reset_state(pd_port);
/*
* event_type: PD_EVT_CTRL_MSG (1), PD_EVT_DATA_MSG (2)
*/
out:
PE_INFO("PRL_ERR: %d-%d-%d\n", event_type, msg_type, msg_id);
return ret;
}
bool pd_process_tx_failed(struct pd_port *pd_port)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_check_pe_state_ready(pd_port) ||
pd_check_pe_during_hard_reset(pd_port)) {
PE_DBG("Ignore tx_failed\n");
return false;
}
pe_transit_soft_reset_state(pd_port);
return true;
}
/*---------------------------------------------------------------------------*/
#ifdef CONFIG_USB_PD_RESET_CABLE
static inline bool pd_process_cable_ctrl_msg_accept(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_port->pe_state_curr) {
#ifdef CONFIG_PD_SRC_RESET_CABLE
case PE_SRC_CBL_SEND_SOFT_RESET:
vdm_put_dpm_discover_cable_event(pd_port);
return false;
#endif /* CONFIG_PD_SRC_RESET_CABLE */
#ifdef CONFIG_PD_DFP_RESET_CABLE
case PE_DFP_CBL_SEND_SOFT_RESET:
pe_transit_ready_state(pd_port);
return true;
#endif /* CONFIG_PD_DFP_RESET_CABLE */
}
return false;
}
#endif /* CONFIG_USB_PD_RESET_CABLE */
static inline bool pd_process_event_cable(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
#ifdef CONFIG_USB_PD_RESET_CABLE
if (pd_event->msg == PD_CTRL_ACCEPT)
ret = pd_process_cable_ctrl_msg_accept(pd_port, pd_event);
#endif /* CONFIG_USB_PD_RESET_CABLE */
if (!ret)
PE_DBG("Ignore not SOP Ctrl Msg\n");
return ret;
}
/*---------------------------------------------------------------------------*/
static void pd_copy_msg_data(struct pd_port *pd_port, uint8_t *payload,
uint16_t count, uint8_t unit_sz)
{
pd_port->pd_msg_data_size = count * unit_sz;
pd_port->pd_msg_data_count = count;
pd_port->pd_msg_data_payload = payload;
}
#ifdef CONFIG_USB_PD_REV30
static inline void pd_copy_msg_data_from_ext_evt(struct pd_port *pd_port,
struct pd_msg *pd_msg)
{
uint32_t *payload = pd_msg->payload;
uint16_t *ext_hdr = (uint16_t *)payload;
uint8_t *ext_data = (uint8_t *)(ext_hdr + 1);
uint16_t size = PD_EXT_HEADER_DATA_SIZE(*ext_hdr);
pd_copy_msg_data(pd_port, ext_data, size, 1);
}
#endif /* CONFIG_USB_PD_REV30 */
static inline void pd_copy_msg_data_from_evt(struct pd_port *pd_port,
struct pd_event *pd_event)
{
struct pd_msg *pd_msg = pd_event->pd_msg;
switch (pd_event->event_type) {
case PD_EVT_DATA_MSG:
PD_BUG_ON(pd_msg == NULL);
pd_copy_msg_data(pd_port, (uint8_t *)pd_msg->payload,
pd_get_msg_hdr_cnt(pd_port), sizeof(uint32_t));
break;
#ifdef CONFIG_USB_PD_REV30
case PD_EVT_EXT_MSG:
PD_BUG_ON(pd_msg == NULL);
pd_copy_msg_data_from_ext_evt(pd_port, pd_msg);
return;
#endif /* CONFIG_USB_PD_REV30 */
default:
pd_copy_msg_data(pd_port, NULL, 0, 0);
}
}
/*---------------------------------------------------------------------------*/
/*
*
* @ true : valid message
* @ false : invalid message, pe should drop the message
*/
static inline bool pe_is_valid_pd_msg_id(struct pd_port *pd_port,
struct pd_event *pd_event,
struct pd_msg *pd_msg)
{
uint8_t sop_type = pd_msg->frame_type;
uint8_t msg_id = pd_get_msg_hdr_id(pd_port);
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->pe_state_curr == PE_BIST_TEST_DATA)
return false;
if (pd_event->event_type == PD_EVT_CTRL_MSG) {
switch (pd_event->msg) {
/* SofReset always has a MessageID value of zero */
case PD_CTRL_SOFT_RESET:
if (msg_id != 0) {
PE_INFO("Repeat soft_reset\n");
return false;
}
return true;
case PD_CTRL_GOOD_CRC:
PE_DBG("Discard_CRC\n");
return true;
#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP
case PD_CTRL_PS_RDY:
if (pd_port->msg_id_pr_swap_last == msg_id) {
PE_INFO("Repeat ps_rdy\n");
return false;
}
break;
#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */
}
}
if (pd_port->pe_data.msg_id_rx[sop_type] == msg_id) {
PE_INFO("Repeat msg: %c:%d:%d\n",
(pd_event->event_type == PD_EVT_CTRL_MSG) ? 'C' : 'D',
pd_event->msg, msg_id);
return false;
}
if (((pd_port->pe_data.msg_id_rx[sop_type] + 2) % PD_MSG_ID_MAX) ==
msg_id) {
PE_INFO("Miss Msg!!!\n");
pd_port->miss_msg = true;
}
pd_port->pe_data.msg_id_rx[sop_type] = msg_id;
return true;
}
static inline bool pe_is_valid_pd_msg_role(struct pd_port *pd_port,
struct pd_event *pd_event,
struct pd_msg *pd_msg)
{
bool ret = true;
uint8_t msg_pr, msg_dr;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_msg == NULL) /* Good-CRC */
return true;
if (pd_msg->frame_type != TCPC_TX_SOP)
return true;
msg_pr = PD_HEADER_PR(pd_msg->msg_hdr);
msg_dr = PD_HEADER_DR(pd_msg->msg_hdr);
/*
* The Port Power Role field of a received Message shall not be verified
* by the receiver and no error recovery action shall be
* taken if it is incorrect.
*/
if (msg_pr == pd_port->power_role)
PE_DBG("Wrong PR:%d\n", msg_pr);
/*
* Should a Type-C Port receive a Message with the Port Data Role field
* set to the same Data Role as its current Data Role,
* except for the GoodCRC Message,
* Type-C error recovery actions as defined
* in [USBType-C 1.0] shall be performed.
*/
if (msg_dr == pd_port->data_role) {
#ifdef CONFIG_USB_PD_CHECK_DATA_ROLE
ret = false;
#endif
PE_INFO("Wrong DR:%d\n", msg_dr);
}
return ret;
}
static inline void pe_translate_pd_msg_event(struct pd_port *pd_port,
struct pd_event *pd_event,
struct pd_msg *pd_msg)
{
uint16_t msg_hdr;
PD_BUG_ON(pd_msg == NULL);
msg_hdr = pd_msg->msg_hdr;
pd_port->curr_msg_hdr = msg_hdr;
pd_event->msg = PD_HEADER_TYPE(msg_hdr);
if (PD_HEADER_CNT(msg_hdr))
pd_event->event_type = PD_EVT_DATA_MSG;
else
pd_event->event_type = PD_EVT_CTRL_MSG;
#ifdef CONFIG_USB_PD_REV30
if (PD_HEADER_EXT(msg_hdr))
pd_event->event_type = PD_EVT_EXT_MSG;
if (pd_msg->frame_type == TCPC_TX_SOP_PRIME) {
pd_sync_sop_prime_spec_revision(pd_port,
PD_HEADER_REV(msg_hdr));
}
#endif /* CONFIG_USB_PD_REV30 */
}
static inline uint8_t pe_get_startup_state(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool act_as_sink = true;
uint8_t startup_state = 0xff;
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
pd_port->custom_dbgacc = false;
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */
switch (pd_event->msg_sec) {
case TYPEC_ATTACHED_DBGACC_SNK:
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
pd_port->custom_dbgacc = true;
startup_state = PE_DBG_READY;
break;
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */
case TYPEC_ATTACHED_SNK:
startup_state = PE_SNK_STARTUP;
break;
case TYPEC_ATTACHED_SRC:
act_as_sink = false;
startup_state = PE_SRC_STARTUP;
break;
}
/* At least > 4 for Ellisys VNDI PR_SWAP */
#ifdef CONFIG_USB_PD_ERROR_RECOVERY_ONCE
if (pd_port->error_recovery_once > 4)
startup_state = PE_ERROR_RECOVERY_ONCE;
#endif /* CONFIG_USB_PD_ERROR_RECOVERY_ONCE */
pd_init_message_hdr(pd_port, act_as_sink);
return startup_state;
}
static inline bool pe_transit_startup_state(struct pd_port *pd_port,
struct pd_event *pd_event)
{
uint8_t startup_state = pe_get_startup_state(pd_port, pd_event);
if (startup_state == 0xff)
return false;
pd_dpm_notify_pe_startup(pd_port);
PE_TRANSIT_STATE(pd_port, startup_state);
return true;
}
enum {
TII_TRAP_IN_IDLE = 0,
TII_TRANSIT_STATE = 1,
TII_PE_RUNNING = 2,
};
static inline uint8_t pe_check_trap_in_idle_state(struct pd_port *pd_port,
struct pd_event *pd_event)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
switch (pd_port->pe_pd_state) {
case PE_IDLE1:
case PE_ERROR_RECOVERY:
if (pd_event_pe_msg_match(pd_event, PD_PE_IDLE)) {
PE_TRANSIT_STATE(pd_port, PE_IDLE2);
return TII_TRANSIT_STATE;
}
pd_try_put_pe_idle_event(pd_port);
break;
case PE_IDLE2:
if (pd_event_hw_msg_match(pd_event, PD_HW_CC_ATTACHED)) {
if (pe_transit_startup_state(pd_port, pd_event))
return TII_TRANSIT_STATE;
}
/* The original IDLE2 may trigger by PE_IDLE_TOUT */
if (pd_event_hw_msg_match(pd_event, PD_HW_CC_DETACHED))
pd_notify_pe_idle(pd_port);
break;
default:
if (pd_event_hw_msg_match(pd_event, PD_HW_CC_DETACHED)) {
PE_TRANSIT_STATE(pd_port, PE_IDLE1);
return TII_TRANSIT_STATE;
}
return TII_PE_RUNNING;
}
PE_DBG("Trap in idle state, Ignore All MSG (%d:%d)\n",
pd_event->event_type, pd_event->msg);
return TII_TRAP_IN_IDLE;
}
static inline void pe_init_curr_state(struct pd_port *pd_port)
{
if (pd_port->power_role == PD_ROLE_SINK) {
pd_port->curr_ready_state = PE_SNK_READY;
pd_port->curr_hreset_state = PE_SNK_HARD_RESET;
pd_port->curr_sreset_state = PE_SNK_SEND_SOFT_RESET;
} else {
pd_port->curr_ready_state = PE_SRC_READY;
pd_port->curr_hreset_state = PE_SRC_HARD_RESET;
pd_port->curr_sreset_state = PE_SRC_SEND_SOFT_RESET;
}
pd_port->curr_unsupported_msg = false;
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
if (pd_port->custom_dbgacc)
pd_port->curr_ready_state = PE_DBG_READY;
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */
}
bool pd_process_event(struct pd_port *pd_port, struct pd_event *pd_event)
{
bool ret = false;
struct pd_msg *pd_msg = pd_event->pd_msg;
uint8_t tii = pe_check_trap_in_idle_state(pd_port, pd_event);
int rv = 0;
uint32_t chip_id = 0;
if (tii < TII_PE_RUNNING)
return tii;
pe_init_curr_state(pd_port);
if (pd_event->event_type == PD_EVT_PD_MSG)
pe_translate_pd_msg_event(pd_port, pd_event, pd_msg);
#if PE_EVT_INFO_VDM_DIS
if (!pd_curr_is_vdm_evt(pd_port))
#endif
print_event(pd_port, pd_event);
if ((pd_event->event_type < PD_EVT_PD_MSG_END) && (pd_msg != NULL)) {
if (!pe_is_valid_pd_msg_id(pd_port, pd_event, pd_msg))
return false;
if (!pe_is_valid_pd_msg_role(pd_port, pd_event, pd_msg)) {
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
return true;
}
rv = tcpci_get_chip_id(pd_port->tcpc, &chip_id);
if (!rv && (SC2150A_DID == chip_id) && pd_port->miss_msg) {
pd_port->miss_msg = false;
if (pd_port->pe_pd_state == PE_SNK_TRANSITION_SINK) {
if (pd_event->msg != PD_CTRL_PS_RDY) {
pd_add_miss_msg(pd_port, pd_event,
PD_CTRL_PS_RDY);
return false;
}
} else if (pd_port->pe_pd_state ==
PE_SNK_SELECT_CAPABILITY) {
switch (pd_event->msg) {
case PD_CTRL_PS_RDY:
pd_add_miss_msg(pd_port, pd_event,
PD_CTRL_ACCEPT);
break;
case PD_DATA_SOURCE_CAP:
pd_add_miss_msg(pd_port, pd_event,
PD_CTRL_REJECT);
break;
}
return false;
}
}
}
pd_copy_msg_data_from_evt(pd_port, pd_event);
if (pd_curr_is_vdm_evt(pd_port))
return pd_process_event_vdm(pd_port, pd_event);
if (pd_event->event_type == PD_EVT_TCP_MSG)
return pd_process_event_tcp(pd_port, pd_event);
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
if (pd_port->custom_dbgacc)
return pd_process_event_dbg(pd_port, pd_event);
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */
if ((pd_event->event_type == PD_EVT_CTRL_MSG) &&
(pd_event->msg != PD_CTRL_GOOD_CRC) && (pd_msg != NULL) &&
(pd_msg->frame_type != TCPC_TX_SOP))
return pd_process_event_cable(pd_port, pd_event);
if (pd_process_event_com(pd_port, pd_event))
return true;
switch (pd_port->state_machine) {
#ifdef CONFIG_USB_PD_DR_SWAP
case PE_STATE_MACHINE_DR_SWAP:
ret = pd_process_event_drs(pd_port, pd_event);
break;
#endif /* CONFIG_USB_PD_DR_SWAP */
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_STATE_MACHINE_PR_SWAP:
ret = pd_process_event_prs(pd_port, pd_event);
break;
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_VCONN_SWAP
case PE_STATE_MACHINE_VCONN_SWAP:
ret = pd_process_event_vcs(pd_port, pd_event);
break;
#endif /* CONFIG_USB_PD_VCONN_SWAP */
}
if (ret)
return true;
if (pd_port->power_role == PD_ROLE_SINK)
ret = pd_process_event_snk(pd_port, pd_event);
else
ret = pd_process_event_src(pd_port, pd_event);
return ret;
}

View File

@ -0,0 +1,657 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Policy Engine for Common
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
/*
* [BLOCK] DRP (dr_swap, pr_swap, vconn_swap)
*/
#ifdef CONFIG_USB_PD_DR_SWAP
static inline bool pd_evaluate_reject_dr_swap(struct pd_port *pd_port)
{
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA) {
if (pd_port->power_role == PD_ROLE_DFP)
return pd_port->dpm_caps &
DPM_CAP_DR_SWAP_REJECT_AS_UFP;
return pd_port->dpm_caps & DPM_CAP_DR_SWAP_REJECT_AS_DFP;
}
return true;
}
#endif /* CONFIG_USB_PD_DR_SWAP */
#ifdef CONFIG_USB_PD_PR_SWAP
static inline bool pd_evaluate_reject_pr_swap(struct pd_port *pd_port)
{
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
if (pd_port->power_role == PD_ROLE_SOURCE)
return pd_port->dpm_caps &
DPM_CAP_PR_SWAP_REJECT_AS_SNK;
return pd_port->dpm_caps & DPM_CAP_PR_SWAP_REJECT_AS_SRC;
}
return true;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_VCONN_SWAP
static inline bool pd_evaluate_accept_vconn_swap(struct pd_port *pd_port)
{
if (pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY)
return true;
return false;
}
#endif /* CONFIG_USB_PD_VCONN_SWAP */
static inline bool pd_process_ctrl_msg_dr_swap(struct pd_port *pd_port,
struct pd_event *pd_event)
{
if (pd_port->pe_data.modal_operation) {
pe_transit_hard_reset_state(pd_port);
return true;
}
#ifdef CONFIG_USB_PD_DR_SWAP
if (!pd_check_pe_state_ready(pd_port))
return false;
if (!pd_evaluate_reject_dr_swap(pd_port)) {
pd_port->pe_data.during_swap = false;
pd_port->state_machine = PE_STATE_MACHINE_DR_SWAP;
PE_TRANSIT_DATA_STATE(pd_port, PE_DRS_UFP_DFP_EVALUATE_DR_SWAP,
PE_DRS_DFP_UFP_EVALUATE_DR_SWAP);
return true;
}
#endif /* CONFIG_USB_PD_DR_SWAP */
PE_TRANSIT_STATE(pd_port, PE_REJECT);
return true;
}
static inline bool pd_process_ctrl_msg_pr_swap(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_PR_SWAP
if (!pd_evaluate_reject_pr_swap(pd_port)) {
pd_port->pe_data.during_swap = false;
pd_port->state_machine = PE_STATE_MACHINE_PR_SWAP;
pe_transit_evaluate_pr_swap_state(pd_port);
return true;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
PE_TRANSIT_STATE(pd_port, PE_REJECT);
return true;
}
static inline bool pd_process_ctrl_msg_vconn_swap(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_VCONN_SWAP
if (!pd_check_pe_state_ready(pd_port))
return false;
if (pd_evaluate_accept_vconn_swap(pd_port)) {
pd_port->state_machine = PE_STATE_MACHINE_VCONN_SWAP;
PE_TRANSIT_STATE(pd_port, PE_VCS_EVALUATE_SWAP);
return true;
}
#endif /* CONFIG_USB_PD_VCONN_SWAP */
if (!pd_check_rev30(pd_port)) {
PE_TRANSIT_STATE(pd_port, PE_REJECT);
return true;
}
return false;
}
/*
* [BLOCK] BIST
*/
static inline bool pd_process_data_msg_bist(struct pd_port *pd_port,
struct pd_event *pd_event)
{
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->request_v > 5000) {
PE_INFO("bist_not_vsafe5v\n");
return false;
}
switch (BDO_MODE(pd_event->pd_msg->payload[0])) {
case BDO_MODE_TEST_DATA:
PE_DBG("bist_test\n");
PE_TRANSIT_STATE(pd_port, PE_BIST_TEST_DATA);
pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_TEST_DATA);
return true;
case BDO_MODE_CARRIER2:
PE_DBG("bist_cm2\n");
PE_TRANSIT_STATE(pd_port, PE_BIST_CARRIER_MODE_2);
pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_DISABLE);
return true;
default:
#if 0
case BDO_MODE_RECV:
case BDO_MODE_TRANSMIT:
case BDO_MODE_COUNTERS:
case BDO_MODE_CARRIER0:
case BDO_MODE_CARRIER1:
case BDO_MODE_CARRIER3:
case BDO_MODE_EYE:
#endif
PE_DBG("Unsupport BIST\n");
pd_noitfy_pe_bist_mode(pd_port, PD_BIST_MODE_DISABLE);
return false;
}
return false;
}
/*
* [BLOCK] Porcess Ctrl MSG
*/
static void pd_cancel_dpm_reaction(struct pd_port *pd_port)
{
if (pd_port->pe_data.dpm_reaction_id < DPM_REACTION_REJECT_CANCEL)
dpm_reaction_clear(pd_port, pd_port->pe_data.dpm_reaction_id);
}
static bool pd_process_ctrl_msg_wait_reject(struct pd_port *pd_port)
{
pd_cancel_dpm_reaction(pd_port);
if (pd_port->state_machine == PE_STATE_MACHINE_PR_SWAP)
pd_notify_pe_cancel_pr_swap(pd_port);
pe_transit_ready_state(pd_port);
return true;
}
static inline bool pd_process_ctrl_msg_wait(struct pd_port *pd_port)
{
return pd_process_ctrl_msg_wait_reject(pd_port);
}
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
#ifdef CONFIG_USB_PD_REV30
if (!pd_check_rev30(pd_port) && pd_event->msg >= PD_CTRL_PD30_START) {
pd_event->msg = PD_CTRL_MSG_NR;
return false;
}
#endif /* CONFIG_USB_PD_REV30 */
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_ENABLE_SENDER_RESPONSE_TIMER)
pd_enable_timer(pd_port, PD_TIMER_SENDER_RESPONSE);
if (pd_port->pe_data.pe_state_flags2 &
PE_STATE_FLAG_BACK_READY_IF_RECV_GOOD_CRC) {
pe_transit_ready_state(pd_port);
return true;
}
break;
case PD_CTRL_REJECT:
pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_REJECT);
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_BACK_READY_IF_RECV_REJECT) {
return pd_process_ctrl_msg_wait_reject(pd_port);
}
break;
case PD_CTRL_WAIT:
pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_WAIT);
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_BACK_READY_IF_RECV_WAIT) {
return pd_process_ctrl_msg_wait(pd_port);
}
break;
case PD_CTRL_SOFT_RESET:
if (!pd_port->pe_data.during_swap &&
!pd_check_pe_during_hard_reset(pd_port)) {
pe_transit_soft_reset_recv_state(pd_port);
return true;
}
break;
/* Swap */
case PD_CTRL_DR_SWAP:
ret = pd_process_ctrl_msg_dr_swap(pd_port, pd_event);
break;
case PD_CTRL_PR_SWAP:
ret = pd_process_ctrl_msg_pr_swap(pd_port, pd_event);
break;
case PD_CTRL_VCONN_SWAP:
ret = pd_process_ctrl_msg_vconn_swap(pd_port, pd_event);
break;
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL
case PD_CTRL_GET_COUNTRY_CODE:
if (pd_port->country_nr) {
ret = PE_MAKE_STATE_TRANSIT_SINGLE(
pe_get_curr_ready_state(pd_port),
PE_GIVE_COUNTRY_CODES);
}
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */
case PD_CTRL_NOT_SUPPORTED:
pd_cancel_dpm_reaction(pd_port);
pd_notify_tcp_event_2nd_result(pd_port,
TCP_DPM_RET_NOT_SUPPORT);
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT) {
pe_transit_ready_state(pd_port);
return true;
} else if (pd_port->pe_data.vdm_state_timer) {
vdm_put_pe_event(pd_port->tcpc, PD_PE_VDM_NOT_SUPPORT);
}
break;
#endif /* CONFIG_USB_PD_REV30 */
}
return ret;
}
/*
* [BLOCK] Porcess Data MSG
*/
static inline bool pd_process_data_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
uint8_t ready_state = pe_get_curr_ready_state(pd_port);
#ifdef CONFIG_USB_PD_REV30
if (!pd_check_rev30(pd_port) && pd_event->msg >= PD_DATA_PD30_START) {
pd_event->msg = PD_DATA_MSG_NR;
return false;
}
#endif /* CONFIG_USB_PD_REV30 */
switch (pd_event->msg) {
case PD_DATA_BIST:
if (pd_port->pe_state_curr == ready_state)
ret = pd_process_data_msg_bist(pd_port, pd_event);
break;
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE
case PD_DATA_BAT_STATUS:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(PE_GET_BATTERY_STATUS,
ready_state);
break;
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL
case PD_DATA_GET_COUNTRY_INFO:
if (pd_port->country_nr) {
ret = PE_MAKE_STATE_TRANSIT_SINGLE(
ready_state, PE_GIVE_COUNTRY_INFO);
}
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_LOCAL */
#endif /* CONFIG_USB_PD_REV30 */
}
return ret;
}
/*
* [BLOCK] Porcess Extend MSG
*/
#ifdef CONFIG_USB_PD_REV30
static inline bool pd_process_ext_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
uint8_t ready_state = pe_get_curr_ready_state(pd_port);
if (!pd_check_rev30(pd_port)) {
pd_event->msg = PD_DATA_MSG_NR;
return false;
}
#ifndef CONFIG_USB_PD_REV30_CHUNKING_BY_PE
if (pd_port->pe_state_curr == ready_state &&
pd_is_multi_chunk_msg(pd_port)) {
pd_port->curr_unsupported_msg = true;
return pd_process_protocol_error(pd_port, pd_event);
}
#endif /* CONFIG_USB_PD_REV30_CHUNKING_BY_PE */
switch (pd_event->msg) {
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_LOCAL
case PD_EXT_GET_BAT_CAP:
if (pd_port->bat_nr) {
ret = PE_MAKE_STATE_TRANSIT_SINGLE(ready_state,
PE_GIVE_BATTERY_CAP);
}
break;
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_LOCAL */
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL
case PD_EXT_GET_BAT_STATUS:
if (pd_port->bat_nr) {
ret = PE_MAKE_STATE_TRANSIT_SINGLE(
ready_state, PE_GIVE_BATTERY_STATUS);
}
break;
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE
case PD_EXT_BAT_CAP:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(PE_GET_BATTERY_CAP,
ready_state);
break;
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL
case PD_EXT_GET_MFR_INFO:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(ready_state,
PE_GIVE_MANUFACTURER_INFO);
break;
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_LOCAL */
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE
case PD_EXT_MFR_INFO:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(PE_GET_MANUFACTURER_INFO,
ready_state);
break;
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE
case PD_EXT_COUNTRY_INFO:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(PE_GET_COUNTRY_INFO,
ready_state);
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE
case PD_EXT_COUNTRY_CODES:
ret = PE_MAKE_STATE_TRANSIT_SINGLE(PE_GET_COUNTRY_CODES,
ready_state);
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */
}
return ret;
}
#endif /* CONFIG_USB_PD_REV30 */
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
bool ret = false;
switch (pd_event->msg) {
case PD_DPM_ACK:
if (pd_port->pe_data.pe_state_flags2 &
PE_STATE_FLAG_BACK_READY_IF_DPM_ACK) {
pe_transit_ready_state(pd_port);
return true;
}
break;
#ifdef CONFIG_USB_PD_REV30
case PD_DPM_NOT_SUPPORT:
if (pd_check_rev30(pd_port)) {
PE_TRANSIT_STATE(pd_port, PE_VDM_NOT_SUPPORTED);
return true;
}
break;
#endif /* CONFIG_USB_PD_REV30 */
}
return ret;
}
/*
* [BLOCK] Porcess HW MSG
*/
static inline bool pd_process_recv_hard_reset(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_RECV_HRESET_COUNTER
if (pd_port->pe_data.recv_hard_reset_count > PD_HARD_RESET_COUNT) {
PE_TRANSIT_STATE(pd_port, PE_OVER_RECV_HRESET_LIMIT);
return true;
}
pd_port->pe_data.recv_hard_reset_count++;
#endif /* CONFIG_USB_PD_RECV_HRESET_COUNTER */
#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER
if (pd_check_pe_during_hard_reset(pd_port))
pd_port->pe_data.renegotiation_count++;
#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */
pe_transit_hard_reset_recv_state(pd_port);
return true;
}
static inline bool pd_process_hw_msg_tx_failed(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_RENEGOTIATION_COUNTER
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->pe_data.renegotiation_count > PD_HARD_RESET_COUNT) {
PE_INFO("renegotiation failed\n");
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
return true;
}
#endif /* CONFIG_USB_PD_RENEGOTIATION_COUNTER */
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_BACK_READY_IF_TX_FAILED) {
pd_notify_tcp_event_2nd_result(pd_port,
TCP_DPM_RET_NO_RESPONSE);
pe_transit_ready_state(pd_port);
return true;
} else if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_HRESET_IF_TX_FAILED) {
pe_transit_hard_reset_state(pd_port);
return true;
}
return false;
}
static inline bool pd_process_hw_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_HW_RECV_HARD_RESET:
return pd_process_recv_hard_reset(pd_port, pd_event);
case PD_HW_TX_FAILED:
return pd_process_hw_msg_tx_failed(pd_port, pd_event);
default:
return false;
};
}
/*
* [BLOCK] Porcess Timer MSG
*/
#ifdef CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT
static inline bool pd_check_rx_pending(struct pd_port *pd_port)
{
uint32_t alert;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (tcpci_get_alert_status(tcpc, &alert))
return false;
if (alert & TCPC_REG_ALERT_RX_STATUS) {
PE_INFO("rx_pending\n");
#ifndef CONFIG_USB_PD_ONLY_PRINT_SYSTEM_BUSY
pd_enable_timer(pd_port, PD_TIMER_SENDER_RESPONSE);
#endif
return true;
}
return false;
}
#endif /* CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT */
static inline bool pd_process_timer_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
uint8_t ready_state = pe_get_curr_ready_state(pd_port);
switch (pd_event->msg) {
#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT
case PD_TIMER_SENDER_RESPONSE:
#ifdef CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT
#ifndef CONFIG_USB_PD_ONLY_PRINT_SYSTEM_BUSY
if (pd_check_rx_pending(pd_port))
return false;
#else
pd_check_rx_pending(pd_port);
#endif /* CONFIG_USB_PD_PRINT_SYSTEM_BUSY */
#endif /* CONFIG_USB_PD_CHECK_RX_PENDING_IF_SRTOUT */
pd_cancel_dpm_reaction(pd_port);
pd_notify_pe_cancel_pr_swap(pd_port);
pd_notify_tcp_event_2nd_result(pd_port, TCP_DPM_RET_TIMEOUT);
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_BACK_READY_IF_SR_TIMER_TOUT) {
PE_TRANSIT_STATE(pd_port, ready_state);
return true;
}
if (pd_port->pe_data.pe_state_flags &
PE_STATE_FLAG_HRESET_IF_SR_TIMEOUT) {
pe_transit_hard_reset_state(pd_port);
return true;
}
break;
#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */
case PD_TIMER_BIST_CONT_MODE:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_BIST_CARRIER_MODE_2,
ready_state))
return true;
break;
#ifdef CONFIG_USB_PD_DFP_FLOW_DELAY
case PD_TIMER_DFP_FLOW_DELAY:
if (pd_port->pe_state_curr == ready_state &&
pd_port->data_role == PD_ROLE_DFP) {
dpm_reaction_set_clear(pd_port,
DPM_REACTION_CAP_READY_ONCE,
DPM_REACTION_DFP_FLOW_DELAY);
}
break;
#endif /* CONFIG_USB_PD_DFP_FLOW_DELAY */
#ifdef CONFIG_USB_PD_UFP_FLOW_DELAY
case PD_TIMER_UFP_FLOW_DELAY:
if (pd_port->pe_state_curr == ready_state &&
pd_port->data_role == PD_ROLE_UFP) {
dpm_reaction_set_clear(pd_port,
DPM_REACTION_CAP_READY_ONCE,
DPM_REACTION_UFP_FLOW_DELAY);
}
break;
#endif /* CONFIG_USB_PD_UFP_FLOW_DELAY */
#ifdef CONFIG_USB_PD_VCONN_STABLE_DELAY
case PD_TIMER_VCONN_STABLE:
if (pd_port->vconn_role == PD_ROLE_VCONN_DYNAMIC_ON) {
pd_set_vconn(pd_port, PD_ROLE_VCONN_ON);
dpm_reaction_set_clear(pd_port,
DPM_REACTION_CAP_READY_ONCE,
DPM_REACTION_VCONN_STABLE_DELAY);
}
break;
#endif /* CONFIG_USB_PD_VCONN_STABLE_DELAY */
#if defined(CONFIG_USB_PD_REV30) && defined(CONFIG_USB_PD_REV30_COLLISION_AVOID)
case PD_TIMER_DEFERRED_EVT:
pd_notify_tcp_event_buf_reset(pd_port,
TCP_DPM_RET_DROP_PE_BUSY);
break;
#endif
default:
break;
}
return false;
}
bool pd_process_event_com(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DATA_MSG:
return pd_process_data_msg(pd_port, pd_event);
#ifdef CONFIG_USB_PD_REV30
case PD_EVT_EXT_MSG:
return pd_process_ext_msg(pd_port, pd_event);
#endif /* CONFIG_USB_PD_REV30 */
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
case PD_EVT_HW_MSG:
return pd_process_hw_msg(pd_port, pd_event);
case PD_EVT_TIMER_MSG:
return pd_process_timer_msg(pd_port, pd_event);
default:
return false;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For DBGACC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#ifdef CONFIG_USB_PD_CUSTOM_DBGACC
bool pd_process_event_dbg(struct pd_port *pd_port, struct pd_event *pd_event)
{
/* Don't need to handle any PD message, Keep VBUS 5V, and using VDM */
return false;
}
#endif /* CONFIG_USB_PD_CUSTOM_DBGACC */

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For DRS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
/* PD Control MSG reactions */
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_GOOD_CRC) = {
{ PE_DRS_DFP_UFP_ACCEPT_DR_SWAP, PE_DRS_DFP_UFP_CHANGE_TO_UFP },
{ PE_DRS_UFP_DFP_ACCEPT_DR_SWAP, PE_DRS_UFP_DFP_CHANGE_TO_DFP },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_GOOD_CRC);
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = {
{ PE_DRS_DFP_UFP_SEND_DR_SWAP, PE_DRS_DFP_UFP_CHANGE_TO_UFP },
{ PE_DRS_UFP_DFP_SEND_DR_SWAP, PE_DRS_UFP_DFP_CHANGE_TO_DFP },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT);
/* DPM Event reactions */
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = {
{ PE_DRS_DFP_UFP_EVALUATE_DR_SWAP, PE_DRS_DFP_UFP_ACCEPT_DR_SWAP },
{ PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, PE_DRS_UFP_DFP_ACCEPT_DR_SWAP },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK);
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_NAK) = {
{ PE_DRS_DFP_UFP_EVALUATE_DR_SWAP, PE_DRS_DFP_UFP_REJECT_DR_SWAP },
{ PE_DRS_UFP_DFP_EVALUATE_DR_SWAP, PE_DRS_UFP_DFP_REJECT_DR_SWAP },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_NAK);
/*
* [BLOCK] Porcess PD Ctrl MSG
*/
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_GOOD_CRC);
case PD_CTRL_ACCEPT:
return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT);
default:
return false;
}
}
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DPM_ACK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK);
case PD_DPM_NAK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_NAK);
}
return false;
}
/*
* [BLOCK] Process Policy Engine's DRS Message
*/
bool pd_process_event_drs(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
default:
return false;
}
}

View File

@ -0,0 +1,238 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For PRS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#ifdef CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY
#define PE_PRS_SNK_HARD_RESET PE_ERROR_RECOVERY
#define PE_PRS_SRC_HARD_RESET PE_ERROR_RECOVERY
#else
#define PE_PRS_SNK_HARD_RESET PE_SNK_HARD_RESET
#define PE_PRS_SRC_HARD_RESET PE_SRC_HARD_RESET
#endif /* CONFIG_USB_PD_PR_SWAP_ERROR_RECOVERY */
/* PD Control MSG reactions */
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_GOOD_CRC) = {
{ PE_PRS_SRC_SNK_ACCEPT_PR_SWAP, PE_PRS_SRC_SNK_TRANSITION_TO_OFF },
{ PE_PRS_SNK_SRC_ACCEPT_PR_SWAP, PE_PRS_SNK_SRC_TRANSITION_TO_OFF },
/* VBUS-ON & PS_RDY SENT */
{ PE_PRS_SNK_SRC_SOURCE_ON, PE_SRC_STARTUP },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_GOOD_CRC);
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = {
{ PE_PRS_SRC_SNK_SEND_SWAP, PE_PRS_SRC_SNK_TRANSITION_TO_OFF },
{ PE_PRS_SNK_SRC_SEND_SWAP, PE_PRS_SNK_SRC_TRANSITION_TO_OFF },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT);
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_PS_RDY) = {
{ PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_SNK_STARTUP },
{ PE_PRS_SNK_SRC_TRANSITION_TO_OFF, PE_PRS_SNK_SRC_ASSERT_RP },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_PS_RDY);
/* DPM Event reactions */
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = {
{ PE_PRS_SRC_SNK_EVALUATE_PR_SWAP, PE_PRS_SRC_SNK_ACCEPT_PR_SWAP },
{ PE_PRS_SNK_SRC_EVALUATE_PR_SWAP, PE_PRS_SNK_SRC_ACCEPT_PR_SWAP },
{ PE_PRS_SRC_SNK_ASSERT_RD, PE_PRS_SRC_SNK_WAIT_SOURCE_ON },
{ PE_PRS_SNK_SRC_ASSERT_RP, PE_PRS_SNK_SRC_SOURCE_ON },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK);
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_NAK) = {
{ PE_PRS_SRC_SNK_EVALUATE_PR_SWAP, PE_PRS_SRC_SNK_REJECT_PR_SWAP },
{ PE_PRS_SNK_SRC_EVALUATE_PR_SWAP, PE_PRS_SNK_SRC_REJECT_SWAP },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_NAK);
/* HW Event reactions */
DECL_PE_STATE_TRANSITION(PD_HW_VBUS_PRESENT) = {
#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP
{ PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_SNK_STARTUP },
#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */
};
DECL_PE_STATE_REACTION(PD_HW_VBUS_PRESENT);
DECL_PE_STATE_TRANSITION(PD_HW_TX_FAILED) = {
{ PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_PRS_SNK_HARD_RESET },
{ PE_PRS_SNK_SRC_SOURCE_ON, PE_PRS_SRC_HARD_RESET },
};
DECL_PE_STATE_REACTION(PD_HW_TX_FAILED);
DECL_PE_STATE_TRANSITION(PD_HW_VBUS_SAFE0V) = {
{ PE_PRS_SRC_SNK_TRANSITION_TO_OFF, PE_PRS_SRC_SNK_ASSERT_RD },
#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP
{ PE_PRS_SNK_SRC_TRANSITION_TO_OFF, PE_PRS_SNK_SRC_ASSERT_RP },
#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */
};
DECL_PE_STATE_REACTION(PD_HW_VBUS_SAFE0V);
/*
* [BLOCK] Porcess PD Ctrl MSG
*/
static inline bool pd_process_ctrl_msg_good_crc(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_port->pe_state_curr) {
case PE_PRS_SRC_SNK_WAIT_SOURCE_ON:
pd_enable_pe_state_timer(pd_port, PD_TIMER_PS_SOURCE_ON);
pd_unlock_msg_output(pd_port); /* for tSRCTransition */
return false;
default:
return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_GOOD_CRC);
}
}
static inline bool pd_process_ctrl_msg_ps_rdy(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_port->pe_state_curr) {
#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP
case PE_PRS_SRC_SNK_WAIT_SOURCE_ON:
pd_enable_vbus_valid_detection(pd_port, true);
return false;
case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
pd_enable_vbus_safe0v_detection(pd_port);
return false;
#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */
default:
return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_PS_RDY);
}
}
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
return pd_process_ctrl_msg_good_crc(pd_port, pd_event);
case PD_CTRL_ACCEPT:
return PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT);
case PD_CTRL_PS_RDY:
return pd_process_ctrl_msg_ps_rdy(pd_port, pd_event);
default:
return false;
}
}
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DPM_ACK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK);
case PD_DPM_NAK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_NAK);
}
return false;
}
/*
* [BLOCK] Porcess HW MSG
*/
static inline bool pd_process_hw_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_HW_VBUS_PRESENT:
if (pd_port->pe_state_curr == PE_PRS_SNK_SRC_SOURCE_ON)
pd_send_sop_ctrl_msg(pd_port, PD_CTRL_PS_RDY);
return PE_MAKE_STATE_TRANSIT(PD_HW_VBUS_PRESENT);
case PD_HW_TX_FAILED:
return PE_MAKE_STATE_TRANSIT(PD_HW_TX_FAILED);
case PD_HW_VBUS_SAFE0V:
return PE_MAKE_STATE_TRANSIT(PD_HW_VBUS_SAFE0V);
default:
return false;
}
}
/*
* [BLOCK] Porcess Timer MSG
*/
static inline bool pd_process_timer_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_TIMER_PS_SOURCE_ON:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_PRS_SRC_SNK_WAIT_SOURCE_ON, PE_PRS_SNK_HARD_RESET);
case PD_TIMER_PS_SOURCE_OFF:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_PRS_SNK_SRC_TRANSITION_TO_OFF,
PE_PRS_SNK_HARD_RESET);
case PD_TIMER_SOURCE_TRANSITION:
pd_dpm_prs_enable_power_source(pd_port, false);
return false;
default:
return false;
}
}
/*
* [BLOCK] Process Policy Engine's PRS Message
*/
bool pd_process_event_prs(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
case PD_EVT_HW_MSG:
return pd_process_hw_msg(pd_port, pd_event);
case PD_EVT_TIMER_MSG:
return pd_process_timer_msg(pd_port, pd_event);
default:
return false;
}
}

View File

@ -0,0 +1,499 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For SNK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/tcpci_typec.h>
/* PD Control MSG reactions */
DECL_PE_STATE_TRANSITION(PD_CTRL_MSG_ACCEPT) = {
{ PE_SNK_SELECT_CAPABILITY, PE_SNK_TRANSITION_SINK },
{ PE_SNK_SEND_SOFT_RESET, PE_SNK_WAIT_FOR_CAPABILITIES },
};
DECL_PE_STATE_REACTION(PD_CTRL_MSG_ACCEPT);
/* PD Data MSG reactions */
DECL_PE_STATE_TRANSITION(PD_DATA_MSG_SOURCE_CAP) = {
{ PE_SNK_WAIT_FOR_CAPABILITIES, PE_SNK_EVALUATE_CAPABILITY },
{ PE_SNK_READY, PE_SNK_EVALUATE_CAPABILITY },
/* PR-Swap issue (Check it later) */
{ PE_SNK_STARTUP, PE_SNK_EVALUATE_CAPABILITY },
{ PE_SNK_DISCOVERY, PE_SNK_EVALUATE_CAPABILITY },
#ifdef CONFIG_USB_PD_TCPM_CB_2ND
{ PE_SNK_GET_SOURCE_CAP, PE_SNK_EVALUATE_CAPABILITY },
#endif /* CONFIG_USB_PD_TCPM_CB_2ND */
};
DECL_PE_STATE_REACTION(PD_DATA_MSG_SOURCE_CAP);
/*
* [BLOCK] Porcess Ctrl MSG
*/
static bool pd_process_ctrl_msg_get_source_cap(struct pd_port *pd_port,
uint8_t next)
{
if (pd_port->pe_state_curr != PE_SNK_READY)
return false;
#ifdef CONFIG_USB_PD_PR_SWAP
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
PE_TRANSIT_STATE(pd_port, next);
return true;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
pd_port->curr_unsupported_msg = true;
return false;
}
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
switch (pd_port->pe_state_curr) {
case PE_SNK_GET_SOURCE_CAP:
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_DR_SNK_GET_SINK_CAP:
#endif /* CONFIG_USB_PD_PR_SWAP */
if (pd_event->msg >= PD_CTRL_GET_SOURCE_CAP &&
pd_event->msg <= PD_CTRL_VCONN_SWAP) {
PE_DBG("Port Partner Request First\n");
pd_port->pe_state_curr = PE_SNK_READY;
pd_disable_timer(pd_port, PD_TIMER_SENDER_RESPONSE);
}
break;
}
#endif /* CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST */
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SNK_SOFT_RESET, PE_SNK_WAIT_FOR_CAPABILITIES);
case PD_CTRL_GOTO_MIN:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_TRANSITION_SINK))
return true;
break;
case PD_CTRL_ACCEPT:
if (PE_MAKE_STATE_TRANSIT(PD_CTRL_MSG_ACCEPT))
return true;
break;
case PD_CTRL_PS_RDY:
switch (pd_port->pe_state_curr) {
case PE_SNK_TRANSITION_SINK:
pd_dpm_snk_transition_power(pd_port);
PE_TRANSIT_STATE(pd_port, PE_SNK_READY);
return true;
#ifdef CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP
case PE_PRS_SRC_SNK_WAIT_SOURCE_ON:
case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
return false;
#endif /* CONFIG_USB_PD_VBUS_DETECTION_DURING_PR_SWAP */
default:
break;
}
break;
case PD_CTRL_GET_SOURCE_CAP:
if (pd_process_ctrl_msg_get_source_cap(
pd_port, PE_DR_SNK_GIVE_SOURCE_CAP))
return true;
break;
case PD_CTRL_GET_SINK_CAP:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_GIVE_SINK_CAP))
return true;
break;
case PD_CTRL_REJECT:
case PD_CTRL_WAIT:
if (pd_port->pe_state_curr == PE_SNK_SELECT_CAPABILITY) {
if (pd_port->pe_data.explicit_contract)
PE_TRANSIT_STATE(pd_port, PE_SNK_READY);
else {
PE_TRANSIT_STATE(pd_port,
PE_SNK_WAIT_FOR_CAPABILITIES);
}
return true;
}
break;
#ifdef CONFIG_USB_PD_REV30
case PD_CTRL_NOT_SUPPORTED:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_NOT_SUPPORTED_RECEIVED))
return true;
break;
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
case PD_CTRL_GET_SOURCE_CAP_EXT:
if (pd_process_ctrl_msg_get_source_cap(
pd_port, PE_DR_SNK_GIVE_SOURCE_CAP_EXT))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
case PD_CTRL_GET_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_GIVE_SINK_STATUS))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
#endif /* CONFIG_USB_PD_REV30 */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
/*
* [BLOCK] Porcess Data MSG
*/
static inline bool pd_process_data_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DATA_SOURCE_CAP:
#ifdef CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP
pd_port->msg_id_pr_swap_last = 0xff;
#endif /* CONFIG_USB_PD_IGNORE_PS_RDY_AFTER_PR_SWAP */
if (PE_MAKE_STATE_TRANSIT(PD_DATA_MSG_SOURCE_CAP))
return true;
break;
#ifdef CONFIG_USB_PD_PR_SWAP
case PD_DATA_SINK_CAP:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_DR_SNK_GET_SINK_CAP,
PE_SNK_READY))
return true;
break;
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
case PD_DATA_ALERT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_SOURCE_ALERT_RECEIVED))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
#endif /* CONFIG_USB_PD_REV30 */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
/*
* [BLOCK] Porcess Extend MSG
*/
#ifdef CONFIG_USB_PD_REV30
static inline bool pd_process_ext_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
case PD_EXT_SOURCE_CAP_EXT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_GET_SOURCE_CAP_EXT,
PE_SNK_READY))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
case PD_EXT_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_GET_SOURCE_STATUS,
PE_SNK_READY))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
case PD_EXT_PPS_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_GET_PPS_STATUS,
PE_SNK_READY))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
#endif /* CONFIG_USB_PD_REV30 */
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DPM_ACK:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_EVALUATE_CAPABILITY,
PE_SNK_SELECT_CAPABILITY);
default:
return false;
}
}
/*
* [BLOCK] Porcess HW MSG
*/
static inline bool pd_process_hw_msg_sink_tx_change(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
struct pe_data *pe_data = &pd_port->pe_data;
uint8_t pd_traffic;
#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP
if (pe_data->pd_traffic_control == PD_SINK_TX_START)
return false;
#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */
if (!pd_check_rev30(pd_port))
return false;
pd_traffic = pd_event->msg_sec ? PD_SINK_TX_OK : PD_SINK_TX_NG;
if (pe_data->pd_traffic_control == pd_traffic)
return false;
pe_data->pd_traffic_control = pd_traffic;
dpm_reaction_set_ready_once(pd_port);
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
return false;
}
static inline bool pd_process_vbus_absent(struct pd_port *pd_port)
{
if (pd_port->pe_state_curr != PE_SNK_DISCOVERY)
return false;
#ifdef CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW
/* iSafe0mA: Maximum current a Sink
* is allowed to draw when VBUS is driven to vSafe0V
*/
pd_dpm_sink_vbus(pd_port, false);
#endif /* CONFIG_USB_PD_SNK_HRESET_KEEP_DRAW */
pd_disable_pe_state_timer(pd_port);
pd_enable_vbus_valid_detection(pd_port, true);
return false;
}
static inline bool pd_process_hw_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_HW_VBUS_PRESENT:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SNK_DISCOVERY, PE_SNK_WAIT_FOR_CAPABILITIES);
case PD_HW_VBUS_ABSENT:
return pd_process_vbus_absent(pd_port);
case PD_HW_TX_FAILED:
return pd_process_tx_failed(pd_port);
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
case PD_HW_SINK_TX_CHANGE:
return pd_process_hw_msg_sink_tx_change(pd_port, pd_event);
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
};
return false;
}
/*
* [BLOCK] Porcess PE MSG
*/
static inline bool pd_process_pe_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_PE_RESET_PRL_COMPLETED:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_STARTUP,
PE_SNK_DISCOVERY);
case PD_PE_HARD_RESET_COMPLETED:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SNK_HARD_RESET, PE_SNK_TRANSITION_TO_DEFAULT);
case PD_PE_POWER_ROLE_AT_DEFAULT:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SNK_TRANSITION_TO_DEFAULT, PE_SNK_STARTUP);
default:
return false;
}
}
/*
* [BLOCK] Porcess Timer MSG
*/
static inline void pd_report_typec_only_charger(struct pd_port *pd_port)
{
uint8_t state;
struct tcpc_device *tcpc = pd_port->tcpc;
if (tcpc->typec_remote_rp_level == TYPEC_CC_VOLT_SNK_DFT)
state = PD_CONNECT_TYPEC_ONLY_SNK_DFT;
else
state = PD_CONNECT_TYPEC_ONLY_SNK;
PE_INFO("TYPE-C Only Charger!\n");
pd_dpm_sink_vbus(pd_port, true);
pd_set_rx_enable(pd_port, PD_RX_CAP_PE_IDLE);
pd_notify_pe_hard_reset_completed(pd_port);
pd_update_connect_state(pd_port, state);
}
static inline bool pd_process_timer_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */
struct pe_data __maybe_unused *pe_data = &pd_port->pe_data;
switch (pd_event->msg) {
case PD_TIMER_SINK_REQUEST:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_READY,
PE_SNK_SELECT_CAPABILITY);
#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT
case PD_TIMER_SINK_WAIT_CAP:
case PD_TIMER_PS_TRANSITION:
if ((pd_port->pe_state_curr != PE_SNK_DISCOVERY) &&
(pe_data->hard_reset_counter <= PD_HARD_RESET_COUNT)) {
PE_TRANSIT_STATE(pd_port, PE_SNK_HARD_RESET);
return true;
}
#ifdef CONFIG_SUPPORT_PISEN_ADAPTER
if ((pd_port->pe_state_curr == PE_SNK_DISCOVERY) &&
(pe_data->retry_cnt < PD_HARD_RESET_RETRY_COUNT)) {
pe_data->retry_cnt++;
PE_TRANSIT_STATE(pd_port, PE_SNK_HARD_RESET);
return true;
}
#endif /* CONFIG_SUPPORT_PISEN_ADAPTER */
PE_INFO("SRC NoResp\n");
if (pd_port->request_v == TCPC_VBUS_SINK_5V) {
pd_report_typec_only_charger(pd_port);
} else {
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
return true;
}
break;
#endif /* CONFIG_USB_PD_DBG_IGRONE_TIMEOUT */
#ifdef CONFIG_USB_PD_DFP_READY_DISCOVER_ID
case PD_TIMER_DISCOVER_ID:
vdm_put_dpm_discover_cable_event(pd_port);
break;
#endif /* CONFIG_USB_PD_DFP_READY_DISCOVER_ID */
/* fall through */
#ifdef CONFIG_USB_PD_REV30
case PD_TIMER_CK_NOT_SUPPORTED:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SNK_CHUNK_RECEIVED,
PE_SNK_SEND_NOT_SUPPORTED))
return true;
/* fall through */
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
#ifdef CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP
case PD_TIMER_SNK_FLOW_DELAY:
if (pe_data->pd_traffic_control == PD_SINK_TX_START) {
if (typec_get_cc_res() == TYPEC_CC_VOLT_SNK_3_0)
pe_data->pd_traffic_control = PD_SINK_TX_OK;
else
pe_data->pd_traffic_control = PD_SINK_TX_NG;
if (pd_check_rev30(pd_port))
dpm_reaction_set_ready_once(pd_port);
}
break;
#endif /* CONFIG_USB_PD_REV30_SNK_FLOW_DELAY_STARTUP */
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
#endif /* CONFIG_USB_PD_REV30 */
}
return false;
}
/*
* [BLOCK] Process Policy Engine's SNK Message
*/
bool pd_process_event_snk(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DATA_MSG:
return pd_process_data_msg(pd_port, pd_event);
#ifdef CONFIG_USB_PD_REV30
case PD_EVT_EXT_MSG:
return pd_process_ext_msg(pd_port, pd_event);
#endif /* CONFIG_USB_PD_REV30 */
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
case PD_EVT_HW_MSG:
return pd_process_hw_msg(pd_port, pd_event);
case PD_EVT_PE_MSG:
return pd_process_pe_msg(pd_port, pd_event);
case PD_EVT_TIMER_MSG:
return pd_process_timer_msg(pd_port, pd_event);
default:
return false;
}
}

View File

@ -0,0 +1,538 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For SRC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
/* PD Data MSG reactions */
DECL_PE_STATE_TRANSITION(PD_DATA_MSG_REQUEST) = {
{ PE_SRC_SEND_CAPABILITIES, PE_SRC_NEGOTIATE_CAPABILITIES },
{ PE_SRC_READY, PE_SRC_NEGOTIATE_CAPABILITIES },
};
DECL_PE_STATE_REACTION(PD_DATA_MSG_REQUEST);
/* DPM Event reactions */
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = {
{ PE_SRC_NEGOTIATE_CAPABILITIES, PE_SRC_TRANSITION_SUPPLY },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK);
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_CAP_CHANGED) = {
{ PE_SRC_READY, PE_SRC_SEND_CAPABILITIES },
{ PE_SRC_WAIT_NEW_CAPABILITIES, PE_SRC_SEND_CAPABILITIES },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_CAP_CHANGED);
/* Timer Event reactions */
DECL_PE_STATE_TRANSITION(PD_TIMER_PS_HARD_RESET) = {
{ PE_SRC_HARD_RESET, PE_SRC_TRANSITION_TO_DEFAULT },
{ PE_SRC_HARD_RESET_RECEIVED, PE_SRC_TRANSITION_TO_DEFAULT },
};
DECL_PE_STATE_REACTION(PD_TIMER_PS_HARD_RESET);
/*
* [BLOCK] Porcess Ctrl MSG
*/
static inline bool pd_process_ctrl_msg_good_crc(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_port->pe_state_curr) {
case PE_SRC_SEND_CAPABILITIES:
pd_port->pe_data.cap_counter = 0;
pd_handle_hard_reset_recovery(pd_port);
return false;
case PE_SRC_TRANSITION_SUPPLY:
pd_enable_pe_state_timer(pd_port, PD_TIMER_SOURCE_TRANSITION);
return false;
case PE_SRC_CAPABILITY_RESPONSE:
if (!pd_port->pe_data.explicit_contract)
PE_TRANSIT_STATE(pd_port, PE_SRC_WAIT_NEW_CAPABILITIES);
else if (pd_port->pe_data.invalid_contract)
PE_TRANSIT_STATE(pd_port, PE_SRC_HARD_RESET);
else
PE_TRANSIT_STATE(pd_port, PE_SRC_READY);
return true;
case PE_SRC_SOFT_RESET:
PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES);
return true;
default:
return false;
}
}
static inline bool pd_process_ctrl_msg_get_sink_cap(struct pd_port *pd_port,
struct pd_event *pd_event)
{
if (pd_port->pe_state_curr != PE_SRC_READY)
return false;
#ifdef CONFIG_USB_PD_PR_SWAP
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
PE_TRANSIT_STATE(pd_port, PE_DR_SRC_GIVE_SINK_CAP);
return true;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
pd_port->curr_unsupported_msg = true;
return false;
}
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
switch (pd_port->pe_state_curr) {
case PE_SRC_GET_SINK_CAP:
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_DR_SRC_GET_SOURCE_CAP:
#endif /* CONFIG_USB_PD_PR_SWAP */
if (pd_event->msg >= PD_CTRL_GET_SOURCE_CAP &&
pd_event->msg <= PD_CTRL_VCONN_SWAP) {
PE_DBG("Port Partner Request First\n");
pd_port->pe_state_curr = PE_SRC_READY;
pd_disable_timer(pd_port, PD_TIMER_SENDER_RESPONSE);
}
break;
}
#endif /* CONFIG_USB_PD_PARTNER_CTRL_MSG_FIRST */
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
return pd_process_ctrl_msg_good_crc(pd_port, pd_event);
case PD_CTRL_ACCEPT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_SEND_SOFT_RESET,
PE_SRC_SEND_CAPABILITIES))
return true;
break;
case PD_CTRL_GET_SOURCE_CAP:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_SEND_CAPABILITIES))
return true;
break;
case PD_CTRL_GET_SINK_CAP:
if (pd_process_ctrl_msg_get_sink_cap(pd_port, pd_event))
return true;
break;
#ifdef CONFIG_USB_PD_REV30
case PD_CTRL_NOT_SUPPORTED:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_NOT_SUPPORTED_RECEIVED))
return true;
break;
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL
case PD_CTRL_GET_SOURCE_CAP_EXT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_GIVE_SOURCE_CAP_EXT))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
case PD_CTRL_GET_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_GIVE_SOURCE_STATUS))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
#ifdef CONFIG_USB_PD_REV30_PPS_SOURCE
case PD_CTRL_GET_PPS_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_GIVE_PPS_STATUS))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_PPS_SOURCE */
#endif /* CONFIG_USB_PD_REV30 */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
/*
* [BLOCK] Porcess Data MSG
*/
static inline bool pd_process_data_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
#ifdef CONFIG_USB_PD_PR_SWAP
case PD_DATA_SOURCE_CAP:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_DR_SRC_GET_SOURCE_CAP,
PE_SRC_READY))
return true;
break;
#endif /* CONFIG_USB_PD_PR_SWAP */
case PD_DATA_SINK_CAP:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_GET_SINK_CAP,
PE_SRC_READY))
return true;
break;
case PD_DATA_REQUEST:
if (PE_MAKE_STATE_TRANSIT(PD_DATA_MSG_REQUEST))
return true;
break;
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_ALERT_REMOTE
case PD_DATA_ALERT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_READY,
PE_SRC_SINK_ALERT_RECEIVED))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_ALERT_REMOTE */
#endif /* CONFIG_USB_PD_REV30 */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
/*
* [BLOCK] Porcess Extend MSG
*/
#ifdef CONFIG_USB_PD_REV30
static inline bool pd_process_ext_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
case PD_EXT_SOURCE_CAP_EXT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_DR_SRC_GET_SOURCE_CAP_EXT,
PE_SRC_READY))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_STATUS_LOCAL
case PD_EXT_STATUS:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_GET_SINK_STATUS,
PE_SRC_READY))
return true;
break;
#endif /* CONFIG_USB_PD_REV30_STATUS_LOCAL */
default:
pd_port->curr_unsupported_msg = true;
break;
}
return pd_process_protocol_error(pd_port, pd_event);
}
#endif /* CONFIG_USB_PD_REV30 */
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DPM_ACK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK);
case PD_DPM_NAK:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SRC_NEGOTIATE_CAPABILITIES,
PE_SRC_CAPABILITY_RESPONSE);
case PD_DPM_CAP_CHANGED:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_CAP_CHANGED);
default:
return false;
}
}
/*
* [BLOCK] Porcess HW MSG
*/
static inline bool pd_process_hw_msg_vbus_present(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_port->pe_state_curr) {
case PE_SRC_STARTUP:
pd_enable_timer(pd_port, PD_TIMER_SOURCE_START);
break;
case PE_SRC_TRANSITION_TO_DEFAULT:
pd_put_pe_event(pd_port, PD_PE_POWER_ROLE_AT_DEFAULT);
break;
}
return false;
}
static inline bool pd_process_hw_msg_tx_failed(struct pd_port *pd_port,
struct pd_event *pd_event)
{
struct pe_data *pe_data = &pd_port->pe_data;
struct tcpc_device __maybe_unused *tcpc = pd_port->tcpc;
if (pd_port->pe_state_curr == PE_SRC_SEND_CAPABILITIES) {
if (!pe_data->pd_connected || !pe_data->explicit_contract) {
PE_TRANSIT_STATE(pd_port, PE_SRC_DISCOVERY);
return true;
}
}
#ifdef CONFIG_PD_SRC_RESET_CABLE
if (pd_port->pe_state_curr == PE_SRC_CBL_SEND_SOFT_RESET) {
PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES);
return true;
}
#endif /* CONFIG_PD_SRC_RESET_CABLE */
return pd_process_tx_failed(pd_port);
}
static inline bool pd_process_hw_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_HW_VBUS_PRESENT:
return pd_process_hw_msg_vbus_present(pd_port, pd_event);
case PD_HW_VBUS_SAFE0V:
pd_enable_timer(pd_port, PD_TIMER_SRC_RECOVER);
return false;
case PD_HW_VBUS_STABLE:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_TRANSITION_SUPPLY,
PE_SRC_TRANSITION_SUPPLY2);
case PD_HW_TX_FAILED:
return pd_process_hw_msg_tx_failed(pd_port, pd_event);
default:
return false;
};
}
/*
* [BLOCK] Porcess PE MSG
*/
static inline bool pd_process_pe_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_PE_RESET_PRL_COMPLETED:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_STARTUP,
PE_SRC_SEND_CAPABILITIES);
case PD_PE_POWER_ROLE_AT_DEFAULT:
return PE_MAKE_STATE_TRANSIT_SINGLE(
PE_SRC_TRANSITION_TO_DEFAULT, PE_SRC_STARTUP);
default:
return false;
}
}
/*
* [BLOCK] Porcess Timer MSG
*/
static inline bool pd_process_timer_msg_source_start(struct pd_port *pd_port,
struct pd_event *pd_event)
{
#ifdef CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID
if (pd_is_discover_cable(pd_port) &&
pd_port->pe_data.msg_id_tx[TCPC_TX_SOP_PRIME] == 0) {
#ifdef CONFIG_PD_SRC_RESET_CABLE
if (pd_is_reset_cable(pd_port)) {
PE_TRANSIT_STATE(pd_port, PE_SRC_CBL_SEND_SOFT_RESET);
return true;
}
#endif /* CONFIG_PD_SRC_RESET_CABLE */
if (vdm_put_dpm_discover_cable_event(pd_port))
return false;
}
#endif /* CONFIG_USB_PD_SRC_STARTUP_DISCOVER_ID */
switch (pd_port->pe_state_curr) {
case PE_SRC_STARTUP:
#ifdef CONFIG_PD_SRC_RESET_CABLE
case PE_SRC_CBL_SEND_SOFT_RESET:
#endif /* CONFIG_PD_SRC_RESET_CABLE */
PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES);
return true;
}
return false;
};
static inline bool pd_process_timer_msg_source_cap(struct pd_port *pd_port,
struct pd_event *pd_event)
{
if (pd_port->pe_state_curr != PE_SRC_DISCOVERY)
return false;
if (pd_port->pe_data.cap_counter <= PD_CAPS_COUNT)
PE_TRANSIT_STATE(pd_port, PE_SRC_SEND_CAPABILITIES);
else /* in this state, PD always not connected */
PE_TRANSIT_STATE(pd_port, PE_SRC_DISABLED);
return true;
}
static inline bool pd_process_timer_msg_no_response(struct pd_port *pd_port,
struct pd_event *pd_event)
{
if (pd_port->pe_data.hard_reset_counter <= PD_HARD_RESET_COUNT)
PE_TRANSIT_STATE(pd_port, PE_SRC_HARD_RESET);
else if (pd_port->pe_data.pd_prev_connected)
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
else
PE_TRANSIT_STATE(pd_port, PE_SRC_DISABLED);
return true;
}
static inline bool pd_process_timer_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_TIMER_SOURCE_CAPABILITY:
return pd_process_timer_msg_source_cap(pd_port, pd_event);
#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT
#ifdef CONFIG_PD_SRC_RESET_CABLE
case PD_TIMER_SENDER_RESPONSE:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_CBL_SEND_SOFT_RESET,
PE_SRC_SEND_CAPABILITIES);
#endif /* CONFIG_PD_SRC_RESET_CABLE */
#endif
case PD_TIMER_PS_HARD_RESET:
return PE_MAKE_STATE_TRANSIT(PD_TIMER_PS_HARD_RESET);
case PD_TIMER_SOURCE_START:
return pd_process_timer_msg_source_start(pd_port, pd_event);
#ifndef CONFIG_USB_PD_DBG_IGRONE_TIMEOUT
case PD_TIMER_NO_RESPONSE:
return pd_process_timer_msg_no_response(pd_port, pd_event);
#endif
case PD_TIMER_SOURCE_TRANSITION:
if (pd_port->state_machine != PE_STATE_MACHINE_PR_SWAP)
pd_dpm_src_transition_power(pd_port);
break;
#ifdef CONFIG_PD_DISCOVER_CABLE_ID
case PD_TIMER_DISCOVER_ID:
vdm_put_dpm_discover_cable_event(pd_port);
break;
#endif /* CONFIG_PD_DISCOVER_CABLE_ID */
case PD_TIMER_SRC_RECOVER:
pd_dpm_source_vbus(pd_port, true);
pd_enable_vbus_valid_detection(pd_port, true);
break;
#ifdef CONFIG_USB_PD_REV30_COLLISION_AVOID
case PD_TIMER_SINK_TX:
if (pd_port->pe_data.pd_traffic_control == PD_SINK_TX_NG)
pd_port->pe_data.pd_traffic_control = PD_SOURCE_TX_OK;
#ifdef CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP
if (pd_port->pe_data.pd_traffic_control == PD_SOURCE_TX_START)
pd_port->pe_data.pd_traffic_control = PD_SINK_TX_OK;
#endif /* CONFIG_USB_PD_REV30_SRC_FLOW_DELAY_STARTUP */
break;
#endif /* CONFIG_USB_PD_REV30_COLLISION_AVOID */
/* fall through */
#ifdef CONFIG_USB_PD_REV30
case PD_TIMER_CK_NOT_SUPPORTED:
return PE_MAKE_STATE_TRANSIT_SINGLE(PE_SRC_CHUNK_RECEIVED,
PE_SRC_SEND_NOT_SUPPORTED);
#endif /* CONFIG_USB_PD_REV30 */
}
return false;
}
/*
* [BLOCK] Process Policy Engine's SRC Message
*/
bool pd_process_event_src(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DATA_MSG:
return pd_process_data_msg(pd_port, pd_event);
#ifdef CONFIG_USB_PD_REV30
case PD_EVT_EXT_MSG:
return pd_process_ext_msg(pd_port, pd_event);
#endif /* CONFIG_USB_PD_REV30 */
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
case PD_EVT_HW_MSG:
return pd_process_hw_msg(pd_port, pd_event);
case PD_EVT_PE_MSG:
return pd_process_pe_msg(pd_port, pd_event);
case PD_EVT_TIMER_MSG:
return pd_process_timer_msg(pd_port, pd_event);
default:
return false;
}
}

View File

@ -0,0 +1,469 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event for TCP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#include <linux/usb/tcpc/pd_dpm_core.h>
#ifdef CONFIG_USB_PD_PR_SWAP
static inline int pd_handle_tcp_event_pr_swap(struct pd_port *pd_port,
uint8_t new_role)
{
if (pd_port->power_role == new_role)
return TCP_DPM_RET_DENIED_SAME_ROLE;
if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER))
return TCP_DPM_RET_DENIED_LOCAL_CAP;
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
pd_port->pe_data.during_swap = false;
pd_port->state_machine = PE_STATE_MACHINE_PR_SWAP;
pe_transit_send_pr_swap_state(pd_port);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
#ifdef CONFIG_USB_PD_DR_SWAP
static inline int pd_handle_tcp_event_dr_swap(struct pd_port *pd_port,
uint8_t new_role)
{
if (pd_port->data_role == new_role)
return TCP_DPM_RET_DENIED_SAME_ROLE;
if (!(pd_port->dpm_caps & DPM_CAP_LOCAL_DR_DATA))
return TCP_DPM_RET_DENIED_LOCAL_CAP;
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
pd_port->pe_data.during_swap = false;
pd_port->state_machine = PE_STATE_MACHINE_DR_SWAP;
PE_TRANSIT_DATA_STATE(pd_port, PE_DRS_UFP_DFP_SEND_DR_SWAP,
PE_DRS_DFP_UFP_SEND_DR_SWAP);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_DR_SWAP */
#ifdef CONFIG_USB_PD_VCONN_SWAP
static inline int pd_handle_tcp_event_vconn_swap(struct pd_port *pd_port,
uint8_t new_role)
{
uint8_t old_role = pd_port->vconn_role ? 1 : 0;
if (old_role == new_role)
return TCP_DPM_RET_DENIED_SAME_ROLE;
if ((!pd_port->vconn_role) &&
(!(pd_port->dpm_caps & DPM_CAP_LOCAL_VCONN_SUPPLY)))
return TCP_DPM_RET_DENIED_LOCAL_CAP;
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
pd_port->state_machine = PE_STATE_MACHINE_VCONN_SWAP;
PE_TRANSIT_STATE(pd_port, PE_VCS_SEND_SWAP);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_VCONN_SWAP */
#ifdef CONFIG_USB_PD_PE_SOURCE
static inline int pd_handle_tcp_event_gotomin(struct pd_port *pd_port)
{
if (pd_port->pe_state_curr != PE_SRC_READY)
return TCP_DPM_RET_DENIED_NOT_READY;
if (!(pd_port->pe_data.dpm_flags & DPM_FLAGS_PARTNER_GIVE_BACK))
return TCP_DPM_RET_DENIED_PARTNER_CAP;
PE_TRANSIT_STATE(pd_port, PE_SRC_TRANSITION_SUPPLY);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PE_SOURCE */
static inline int pd_handle_tcp_event_softreset(struct pd_port *pd_port)
{
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
pe_transit_soft_reset_state(pd_port);
return TCP_DPM_RET_SENT;
}
#ifdef CONFIG_PD_DFP_RESET_CABLE
static inline int pd_handle_tcp_event_cable_softreset(struct pd_port *pd_port)
{
bool role_check;
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
role_check = pd_port->data_role == PD_ROLE_DFP;
if (pd_check_rev30(pd_port))
role_check = pd_port->vconn_role;
if (!role_check)
return TCP_DPM_RET_DENIED_WRONG_DATA_ROLE;
PE_TRANSIT_STATE(pd_port, PE_DFP_CBL_SEND_SOFT_RESET);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_PD_DFP_RESET_CABLE */
static inline int pd_handle_tcp_event_get_source_cap(struct pd_port *pd_port)
{
switch (pd_port->pe_state_curr) {
case PE_SNK_READY:
PE_TRANSIT_STATE(pd_port, PE_SNK_GET_SOURCE_CAP);
return TCP_DPM_RET_SENT;
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_SRC_READY:
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
PE_TRANSIT_STATE(pd_port, PE_DR_SRC_GET_SOURCE_CAP);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
return TCP_DPM_RET_DENIED_LOCAL_CAP;
}
return TCP_DPM_RET_DENIED_NOT_READY;
}
static inline int pd_handle_tcp_event_get_sink_cap(struct pd_port *pd_port)
{
switch (pd_port->pe_state_curr) {
case PE_SRC_READY:
PE_TRANSIT_STATE(pd_port, PE_SRC_GET_SINK_CAP);
return TCP_DPM_RET_SENT;
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_SNK_READY:
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
PE_TRANSIT_STATE(pd_port, PE_DR_SNK_GET_SINK_CAP);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
return TCP_DPM_RET_DENIED_LOCAL_CAP;
}
return TCP_DPM_RET_DENIED_NOT_READY;
}
#ifdef CONFIG_USB_PD_PE_SINK
static inline int pd_handle_tcp_event_request(struct pd_port *pd_port)
{
int ret = 0;
struct tcp_dpm_event *tcp_event = &pd_port->tcp_event;
if (pd_port->pe_state_curr != PE_SNK_READY)
return TCP_DPM_RET_DENIED_NOT_READY;
switch (pd_get_curr_pd_event(pd_port)->msg) {
case TCP_DPM_EVT_REQUEST:
ret = pd_dpm_update_tcp_request(
pd_port, &tcp_event->tcp_dpm_data.pd_req);
break;
case TCP_DPM_EVT_REQUEST_EX:
ret = pd_dpm_update_tcp_request_ex(
pd_port, &tcp_event->tcp_dpm_data.pd_req_ex);
break;
case TCP_DPM_EVT_REQUEST_AGAIN:
ret = pd_dpm_update_tcp_request_again(pd_port);
break;
}
if (ret != TCP_DPM_RET_SUCCESS)
return ret;
PE_TRANSIT_STATE(pd_port, PE_SNK_SELECT_CAPABILITY);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PE_SINK */
static inline int pd_handle_tcp_event_bist_cm2(struct pd_port *pd_port)
{
uint32_t bist = BDO_MODE_CARRIER2;
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
pd_send_sop_data_msg(pd_port, PD_DATA_BIST, 1, &bist);
return TCP_DPM_RET_SENT;
}
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
static inline int
pd_handle_tcp_event_get_source_cap_ext(struct pd_port *pd_port)
{
switch (pd_port->pe_state_curr) {
case PE_SNK_READY:
PE_TRANSIT_STATE(pd_port, PE_SNK_GET_SOURCE_CAP_EXT);
return TCP_DPM_RET_SENT;
#ifdef CONFIG_USB_PD_PR_SWAP
case PE_SRC_READY:
if (pd_port->dpm_caps & DPM_CAP_LOCAL_DR_POWER) {
PE_TRANSIT_STATE(pd_port, PE_DR_SRC_GET_SOURCE_CAP_EXT);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_PR_SWAP */
return TCP_DPM_RET_DENIED_LOCAL_CAP;
}
return TCP_DPM_RET_DENIED_NOT_READY;
}
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
static inline int pd_handle_tcp_event_get_pps_status(struct pd_port *pd_port)
{
if (pd_port->pe_state_curr != PE_SNK_READY)
return TCP_DPM_RET_DENIED_NOT_READY;
PE_TRANSIT_STATE(pd_port, PE_SNK_GET_PPS_STATUS);
return TCP_DPM_RET_SENT;
}
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
static inline int pd_make_tcp_event_transit_ready(struct pd_port *pd_port,
uint8_t state)
{
if (!pd_check_pe_state_ready(pd_port))
return TCP_DPM_RET_DENIED_NOT_READY;
PE_TRANSIT_STATE(pd_port, state);
return TCP_DPM_RET_SENT;
}
static inline int pd_make_tcp_event_transit_ready2(struct pd_port *pd_port,
uint8_t snk_state,
uint8_t src_state)
{
switch (pd_port->pe_state_curr) {
#ifdef CONFIG_USB_PD_PE_SINK
case PE_SNK_READY:
PE_TRANSIT_STATE(pd_port, snk_state);
return TCP_DPM_RET_SENT;
#endif /* CONFIG_USB_PD_PE_SINK */
#ifdef CONFIG_USB_PD_PE_SOURCE
case PE_SRC_READY:
PE_TRANSIT_STATE(pd_port, src_state);
return TCP_DPM_RET_SENT;
#endif /* CONFIG_USB_PD_PE_SOURCE */
}
return TCP_DPM_RET_DENIED_NOT_READY;
}
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
static inline int pd_handle_tcp_event_alert(struct pd_port *pd_port)
{
struct tcp_dpm_event *tcp_event = &pd_port->tcp_event;
if (pd_get_curr_pd_event(pd_port)->msg_sec == PD_TCP_FROM_TCPM)
pd_port->pe_data.local_alert |= tcp_event->tcp_dpm_data.index;
return pd_make_tcp_event_transit_ready2(pd_port, PE_SNK_SEND_SINK_ALERT,
PE_SRC_SEND_SOURCE_ALERT);
}
#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */
#endif /* CONFIG_USB_PD_REV30 */
static inline int pd_handle_tcp_event_hardreset(struct pd_port *pd_port)
{
pe_transit_hard_reset_state(pd_port);
return TCP_DPM_RET_SENT;
}
static inline int pd_handle_tcp_event_error_recovery(struct pd_port *pd_port)
{
PE_TRANSIT_STATE(pd_port, PE_ERROR_RECOVERY);
return TCP_DPM_RET_SENT;
}
static inline int pd_handle_tcp_dpm_event(struct pd_port *pd_port,
struct pd_event *pd_event)
{
int ret = TCP_DPM_RET_DENIED_UNKNOWN;
#ifdef CONFIG_USB_PD_REV30
if (pd_event->msg >= TCP_DPM_EVT_PD30_COMMAND &&
pd_event->msg < TCP_DPM_EVT_VDM_COMMAND) {
if (!pd_check_rev30(pd_port))
return TCP_DPM_RET_DENIED_PD_REV;
}
#endif /* CONFIG_USB_PD_REV30 */
switch (pd_event->msg) {
default:
break;
case TCP_DPM_EVT_PR_SWAP_AS_SNK:
case TCP_DPM_EVT_PR_SWAP_AS_SRC:
#ifdef CONFIG_USB_PD_PR_SWAP
ret = pd_handle_tcp_event_pr_swap(
pd_port, pd_event->msg - TCP_DPM_EVT_PR_SWAP_AS_SNK);
#endif /* CONFIG_USB_PD_PR_SWAP */
break;
case TCP_DPM_EVT_DR_SWAP_AS_UFP:
case TCP_DPM_EVT_DR_SWAP_AS_DFP:
#ifdef CONFIG_USB_PD_DR_SWAP
ret = pd_handle_tcp_event_dr_swap(
pd_port, pd_event->msg - TCP_DPM_EVT_DR_SWAP_AS_UFP);
#endif /* CONFIG_USB_PD_DR_SWAP */
break;
case TCP_DPM_EVT_VCONN_SWAP_OFF:
case TCP_DPM_EVT_VCONN_SWAP_ON:
#ifdef CONFIG_USB_PD_VCONN_SWAP
ret = pd_handle_tcp_event_vconn_swap(
pd_port, pd_event->msg - TCP_DPM_EVT_VCONN_SWAP_OFF);
#endif /* CONFIG_USB_PD_VCONN_SWAP */
break;
case TCP_DPM_EVT_GOTOMIN:
#ifdef CONFIG_USB_PD_PE_SOURCE
ret = pd_handle_tcp_event_gotomin(pd_port);
#endif /* CONFIG_USB_PD_PE_SOURCE */
break;
case TCP_DPM_EVT_SOFTRESET:
ret = pd_handle_tcp_event_softreset(pd_port);
break;
case TCP_DPM_EVT_CABLE_SOFTRESET:
#ifdef CONFIG_PD_DFP_RESET_CABLE
ret = pd_handle_tcp_event_cable_softreset(pd_port);
#endif /* CONFIG_PD_DFP_RESET_CABLE */
break;
case TCP_DPM_EVT_GET_SOURCE_CAP:
ret = pd_handle_tcp_event_get_source_cap(pd_port);
break;
case TCP_DPM_EVT_GET_SINK_CAP:
ret = pd_handle_tcp_event_get_sink_cap(pd_port);
break;
#ifdef CONFIG_USB_PD_PE_SINK
case TCP_DPM_EVT_REQUEST:
ret = pd_handle_tcp_event_request(pd_port);
break;
case TCP_DPM_EVT_REQUEST_EX:
ret = pd_handle_tcp_event_request(pd_port);
break;
case TCP_DPM_EVT_REQUEST_AGAIN:
ret = pd_handle_tcp_event_request(pd_port);
break;
#endif /* CONFIG_USB_PD_PE_SINK */
case TCP_DPM_EVT_BIST_CM2:
ret = pd_handle_tcp_event_bist_cm2(pd_port);
break;
#ifdef CONFIG_USB_PD_REV30
#ifdef CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE
case TCP_DPM_EVT_GET_SOURCE_CAP_EXT:
ret = pd_handle_tcp_event_get_source_cap_ext(pd_port);
break;
#endif /* CONFIG_USB_PD_REV30_SRC_CAP_EXT_REMOTE */
#ifdef CONFIG_USB_PD_REV30_STATUS_REMOTE
case TCP_DPM_EVT_GET_STATUS:
ret = pd_make_tcp_event_transit_ready2(pd_port,
PE_SNK_GET_SOURCE_STATUS,
PE_SRC_GET_SINK_STATUS);
break;
#endif /* CONFIG_USB_PD_REV30_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE
case TCP_DPM_EVT_GET_COUNTRY_CODE:
ret = pd_make_tcp_event_transit_ready(pd_port,
PE_GET_COUNTRY_CODES);
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_CODE_REMOTE */
#ifdef CONFIG_USB_PD_REV30_PPS_SINK
case TCP_DPM_EVT_GET_PPS_STATUS:
ret = pd_handle_tcp_event_get_pps_status(pd_port);
break;
#endif /* CONFIG_USB_PD_REV30_PPS_SINK */
#ifdef CONFIG_USB_PD_REV30_ALERT_LOCAL
case TCP_DPM_EVT_ALERT:
ret = pd_handle_tcp_event_alert(pd_port);
break;
#endif /* CONFIG_USB_PD_REV30_ALERT_LOCAL */
#ifdef CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE
case TCP_DPM_EVT_GET_COUNTRY_INFO:
ret = pd_make_tcp_event_transit_ready(pd_port,
PE_GET_COUNTRY_INFO);
break;
#endif /* CONFIG_USB_PD_REV30_COUNTRY_INFO_REMOTE */
#ifdef CONFIG_USB_PD_REV30_BAT_CAP_REMOTE
case TCP_DPM_EVT_GET_BAT_CAP:
ret = pd_make_tcp_event_transit_ready(pd_port,
PE_GET_BATTERY_CAP);
break;
#endif /* CONFIG_USB_PD_REV30_BAT_CAP_REMOTE */
#ifdef CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE
case TCP_DPM_EVT_GET_BAT_STATUS:
ret = pd_make_tcp_event_transit_ready(pd_port,
PE_GET_BATTERY_STATUS);
break;
#endif /* CONFIG_USB_PD_REV30_BAT_STATUS_REMOTE */
#ifdef CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE
case TCP_DPM_EVT_GET_MFRS_INFO:
ret = pd_make_tcp_event_transit_ready(pd_port,
PE_GET_MANUFACTURER_INFO);
break;
#endif /* CONFIG_USB_PD_REV30_MFRS_INFO_REMOTE */
#endif /* CONFIG_USB_PD_REV30 */
case TCP_DPM_EVT_HARD_RESET:
ret = pd_handle_tcp_event_hardreset(pd_port);
break;
case TCP_DPM_EVT_ERROR_RECOVERY:
ret = pd_handle_tcp_event_error_recovery(pd_port);
break;
}
return ret;
}
bool pd_process_event_tcp(struct pd_port *pd_port, struct pd_event *pd_event)
{
int ret = pd_handle_tcp_dpm_event(pd_port, pd_event);
pd_notify_tcp_event_1st_result(pd_port, ret);
return ret == TCP_DPM_RET_SENT;
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2020 Richtek Inc.
*
* Power Delivery Process Event For VCS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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/usb/tcpc/pd_core.h>
#include <linux/usb/tcpc/tcpci_event.h>
#include <linux/usb/tcpc/pd_process_evt.h>
#ifdef CONFIG_USB_PD_VCONN_SWAP
/* DPM Event reactions */
DECL_PE_STATE_TRANSITION(PD_DPM_MSG_ACK) = {
{ PE_VCS_EVALUATE_SWAP, PE_VCS_ACCEPT_SWAP },
{ PE_VCS_TURN_ON_VCONN, PE_VCS_SEND_PS_RDY },
};
DECL_PE_STATE_REACTION(PD_DPM_MSG_ACK);
/*
* [BLOCK] Porcess PD Ctrl MSG
*/
static inline bool pd_process_ctrl_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
uint8_t vconn_state = pd_port->vconn_role ? PE_VCS_WAIT_FOR_VCONN :
PE_VCS_TURN_ON_VCONN;
switch (pd_event->msg) {
case PD_CTRL_GOOD_CRC:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_VCS_ACCEPT_SWAP,
vconn_state))
return true;
break;
case PD_CTRL_ACCEPT:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_VCS_SEND_SWAP, vconn_state))
return true;
break;
case PD_CTRL_PS_RDY:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_VCS_WAIT_FOR_VCONN,
PE_VCS_TURN_OFF_VCONN))
return true;
break;
}
return false;
}
/*
* [BLOCK] Porcess DPM MSG
*/
static inline bool pd_process_dpm_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_DPM_ACK:
return PE_MAKE_STATE_TRANSIT(PD_DPM_MSG_ACK);
case PD_DPM_NAK:
if (PE_MAKE_STATE_TRANSIT_SINGLE(PE_VCS_EVALUATE_SWAP,
PE_VCS_REJECT_VCONN_SWAP))
return true;
break;
}
return false;
}
/*
* [BLOCK] Porcess Timer MSG
*/
static inline bool pd_process_timer_msg(struct pd_port *pd_port,
struct pd_event *pd_event)
{
switch (pd_event->msg) {
case PD_TIMER_VCONN_ON:
if (PE_MAKE_STATE_TRANSIT_TO_HRESET(PE_VCS_WAIT_FOR_VCONN))
return true;
break;
#if CONFIG_USB_PD_VCONN_READY_TOUT != 0
case PD_TIMER_VCONN_READY:
PE_STATE_DPM_ACK_IMMEDIATELY(pd_port);
break;
#endif
}
return false;
}
/*
* [BLOCK] Process Policy Engine's VCS Message
*/
bool pd_process_event_vcs(struct pd_port *pd_port, struct pd_event *pd_event)
{
switch (pd_event->event_type) {
case PD_EVT_CTRL_MSG:
return pd_process_ctrl_msg(pd_port, pd_event);
case PD_EVT_DPM_MSG:
return pd_process_dpm_msg(pd_port, pd_event);
case PD_EVT_TIMER_MSG:
return pd_process_timer_msg(pd_port, pd_event);
default:
return false;
}
}
#endif /* CONFIG_USB_PD_VCONN_SWAP */

Some files were not shown because too many files have changed in this diff Show More