// SPDX-License-Identifier: GPL-2.0 /* * Power Supply for UCSI * * Copyright (C) 2020, Intel Corporation * Author: K V, Abhilash * Author: Heikki Krogerus */ #include #include #include "ucsi.h" /* Power Supply access to expose source power information */ enum ucsi_psy_online_states { UCSI_PSY_OFFLINE = 0, UCSI_PSY_FIXED_ONLINE, UCSI_PSY_PROG_ONLINE, }; static enum power_supply_property ucsi_psy_props[] = { POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_MIN, POWER_SUPPLY_PROP_VOLTAGE_MAX, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, }; static int ucsi_psy_get_online(struct ucsi_connector *con, union power_supply_propval *val) { val->intval = UCSI_PSY_OFFLINE; if (con->status.flags & UCSI_CONSTAT_CONNECTED && (con->status.flags & UCSI_CONSTAT_PWR_DIR) == TYPEC_SINK) val->intval = UCSI_PSY_FIXED_ONLINE; return 0; } static int ucsi_psy_get_voltage_min(struct ucsi_connector *con, union power_supply_propval *val) { u32 pdo; switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { case UCSI_CONSTAT_PWR_OPMODE_PD: pdo = con->src_pdos[0]; val->intval = pdo_fixed_voltage(pdo) * 1000; break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: case UCSI_CONSTAT_PWR_OPMODE_BC: case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: val->intval = UCSI_TYPEC_VSAFE5V * 1000; break; default: val->intval = 0; break; } return 0; } static int ucsi_psy_get_voltage_max(struct ucsi_connector *con, union power_supply_propval *val) { u32 pdo; switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { case UCSI_CONSTAT_PWR_OPMODE_PD: if (con->num_pdos > 0) { pdo = con->src_pdos[con->num_pdos - 1]; val->intval = pdo_fixed_voltage(pdo) * 1000; } else { val->intval = 0; } break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: case UCSI_CONSTAT_PWR_OPMODE_BC: case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: val->intval = UCSI_TYPEC_VSAFE5V * 1000; break; default: val->intval = 0; break; } return 0; } static int ucsi_psy_get_voltage_now(struct ucsi_connector *con, union power_supply_propval *val) { int index; u32 pdo; switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { case UCSI_CONSTAT_PWR_OPMODE_PD: index = rdo_index(con->rdo); if (index > 0) { pdo = con->src_pdos[index - 1]; val->intval = pdo_fixed_voltage(pdo) * 1000; } else { val->intval = 0; } break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: case UCSI_CONSTAT_PWR_OPMODE_BC: case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: val->intval = UCSI_TYPEC_VSAFE5V * 1000; break; default: val->intval = 0; break; } return 0; } static int ucsi_psy_get_current_max(struct ucsi_connector *con, union power_supply_propval *val) { u32 pdo; switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { case UCSI_CONSTAT_PWR_OPMODE_PD: if (con->num_pdos > 0) { pdo = con->src_pdos[con->num_pdos - 1]; val->intval = pdo_max_current(pdo) * 1000; } else { val->intval = 0; } break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; break; case UCSI_CONSTAT_PWR_OPMODE_BC: case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ default: val->intval = 0; break; } return 0; } static int ucsi_psy_get_current_now(struct ucsi_connector *con, union power_supply_propval *val) { u16 flags = con->status.flags; if (UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) val->intval = rdo_op_current(con->rdo) * 1000; else val->intval = 0; return 0; } static int ucsi_psy_get_usb_type(struct ucsi_connector *con, union power_supply_propval *val) { u16 flags = con->status.flags; val->intval = POWER_SUPPLY_USB_TYPE_C; if (flags & UCSI_CONSTAT_CONNECTED && UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) val->intval = POWER_SUPPLY_USB_TYPE_PD; return 0; } static int ucsi_psy_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ucsi_connector *con = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_USB_TYPE: return ucsi_psy_get_usb_type(con, val); case POWER_SUPPLY_PROP_ONLINE: return ucsi_psy_get_online(con, val); case POWER_SUPPLY_PROP_VOLTAGE_MIN: return ucsi_psy_get_voltage_min(con, val); case POWER_SUPPLY_PROP_VOLTAGE_MAX: return ucsi_psy_get_voltage_max(con, val); case POWER_SUPPLY_PROP_VOLTAGE_NOW: return ucsi_psy_get_voltage_now(con, val); case POWER_SUPPLY_PROP_CURRENT_MAX: return ucsi_psy_get_current_max(con, val); case POWER_SUPPLY_PROP_CURRENT_NOW: return ucsi_psy_get_current_now(con, val); default: return -EINVAL; } } static enum power_supply_usb_type ucsi_psy_usb_types[] = { POWER_SUPPLY_USB_TYPE_C, POWER_SUPPLY_USB_TYPE_PD, POWER_SUPPLY_USB_TYPE_PD_PPS, }; int ucsi_register_port_psy(struct ucsi_connector *con) { struct power_supply_config psy_cfg = {}; struct device *dev = con->ucsi->dev; char *psy_name; psy_cfg.drv_data = con; psy_cfg.fwnode = dev_fwnode(dev); psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d", dev_name(dev), con->num); if (!psy_name) return -ENOMEM; con->psy_desc.name = psy_name; con->psy_desc.type = POWER_SUPPLY_TYPE_USB, con->psy_desc.usb_types = ucsi_psy_usb_types; con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types); con->psy_desc.properties = ucsi_psy_props, con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props), con->psy_desc.get_property = ucsi_psy_get_prop; con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); return PTR_ERR_OR_ZERO(con->psy); } void ucsi_unregister_port_psy(struct ucsi_connector *con) { if (IS_ERR_OR_NULL(con->psy)) return; power_supply_unregister(con->psy); }