linux/drivers/usb/typec/ucsi/psy.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Power Supply for UCSI
   4 *
   5 * Copyright (C) 2020, Intel Corporation
   6 * Author: K V, Abhilash <abhilash.k.v@intel.com>
   7 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
   8 */
   9
  10#include <linux/property.h>
  11#include <linux/usb/pd.h>
  12
  13#include "ucsi.h"
  14
  15/* Power Supply access to expose source power information */
  16enum ucsi_psy_online_states {
  17        UCSI_PSY_OFFLINE = 0,
  18        UCSI_PSY_FIXED_ONLINE,
  19        UCSI_PSY_PROG_ONLINE,
  20};
  21
  22static enum power_supply_property ucsi_psy_props[] = {
  23        POWER_SUPPLY_PROP_USB_TYPE,
  24        POWER_SUPPLY_PROP_ONLINE,
  25        POWER_SUPPLY_PROP_VOLTAGE_MIN,
  26        POWER_SUPPLY_PROP_VOLTAGE_MAX,
  27        POWER_SUPPLY_PROP_VOLTAGE_NOW,
  28        POWER_SUPPLY_PROP_CURRENT_MAX,
  29        POWER_SUPPLY_PROP_CURRENT_NOW,
  30};
  31
  32static int ucsi_psy_get_online(struct ucsi_connector *con,
  33                               union power_supply_propval *val)
  34{
  35        val->intval = UCSI_PSY_OFFLINE;
  36        if (con->status.flags & UCSI_CONSTAT_CONNECTED &&
  37            (con->status.flags & UCSI_CONSTAT_PWR_DIR) == TYPEC_SINK)
  38                val->intval = UCSI_PSY_FIXED_ONLINE;
  39        return 0;
  40}
  41
  42static int ucsi_psy_get_voltage_min(struct ucsi_connector *con,
  43                                    union power_supply_propval *val)
  44{
  45        u32 pdo;
  46
  47        switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
  48        case UCSI_CONSTAT_PWR_OPMODE_PD:
  49                pdo = con->src_pdos[0];
  50                val->intval = pdo_fixed_voltage(pdo) * 1000;
  51                break;
  52        case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
  53        case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
  54        case UCSI_CONSTAT_PWR_OPMODE_BC:
  55        case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
  56                val->intval = UCSI_TYPEC_VSAFE5V * 1000;
  57                break;
  58        default:
  59                val->intval = 0;
  60                break;
  61        }
  62        return 0;
  63}
  64
  65static int ucsi_psy_get_voltage_max(struct ucsi_connector *con,
  66                                    union power_supply_propval *val)
  67{
  68        u32 pdo;
  69
  70        switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
  71        case UCSI_CONSTAT_PWR_OPMODE_PD:
  72                if (con->num_pdos > 0) {
  73                        pdo = con->src_pdos[con->num_pdos - 1];
  74                        val->intval = pdo_fixed_voltage(pdo) * 1000;
  75                } else {
  76                        val->intval = 0;
  77                }
  78                break;
  79        case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
  80        case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
  81        case UCSI_CONSTAT_PWR_OPMODE_BC:
  82        case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
  83                val->intval = UCSI_TYPEC_VSAFE5V * 1000;
  84                break;
  85        default:
  86                val->intval = 0;
  87                break;
  88        }
  89        return 0;
  90}
  91
  92static int ucsi_psy_get_voltage_now(struct ucsi_connector *con,
  93                                    union power_supply_propval *val)
  94{
  95        int index;
  96        u32 pdo;
  97
  98        switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
  99        case UCSI_CONSTAT_PWR_OPMODE_PD:
 100                index = rdo_index(con->rdo);
 101                if (index > 0) {
 102                        pdo = con->src_pdos[index - 1];
 103                        val->intval = pdo_fixed_voltage(pdo) * 1000;
 104                } else {
 105                        val->intval = 0;
 106                }
 107                break;
 108        case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
 109        case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
 110        case UCSI_CONSTAT_PWR_OPMODE_BC:
 111        case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
 112                val->intval = UCSI_TYPEC_VSAFE5V * 1000;
 113                break;
 114        default:
 115                val->intval = 0;
 116                break;
 117        }
 118        return 0;
 119}
 120
 121static int ucsi_psy_get_current_max(struct ucsi_connector *con,
 122                                    union power_supply_propval *val)
 123{
 124        u32 pdo;
 125
 126        switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
 127        case UCSI_CONSTAT_PWR_OPMODE_PD:
 128                if (con->num_pdos > 0) {
 129                        pdo = con->src_pdos[con->num_pdos - 1];
 130                        val->intval = pdo_max_current(pdo) * 1000;
 131                } else {
 132                        val->intval = 0;
 133                }
 134                break;
 135        case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
 136                val->intval = UCSI_TYPEC_1_5_CURRENT * 1000;
 137                break;
 138        case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
 139                val->intval = UCSI_TYPEC_3_0_CURRENT * 1000;
 140                break;
 141        case UCSI_CONSTAT_PWR_OPMODE_BC:
 142        case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
 143        /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */
 144        default:
 145                val->intval = 0;
 146                break;
 147        }
 148        return 0;
 149}
 150
 151static int ucsi_psy_get_current_now(struct ucsi_connector *con,
 152                                    union power_supply_propval *val)
 153{
 154        u16 flags = con->status.flags;
 155
 156        if (UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD)
 157                val->intval = rdo_op_current(con->rdo) * 1000;
 158        else
 159                val->intval = 0;
 160        return 0;
 161}
 162
 163static int ucsi_psy_get_usb_type(struct ucsi_connector *con,
 164                                 union power_supply_propval *val)
 165{
 166        u16 flags = con->status.flags;
 167
 168        val->intval = POWER_SUPPLY_USB_TYPE_C;
 169        if (flags & UCSI_CONSTAT_CONNECTED &&
 170            UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD)
 171                val->intval = POWER_SUPPLY_USB_TYPE_PD;
 172
 173        return 0;
 174}
 175
 176static int ucsi_psy_get_prop(struct power_supply *psy,
 177                             enum power_supply_property psp,
 178                             union power_supply_propval *val)
 179{
 180        struct ucsi_connector *con = power_supply_get_drvdata(psy);
 181
 182        switch (psp) {
 183        case POWER_SUPPLY_PROP_USB_TYPE:
 184                return ucsi_psy_get_usb_type(con, val);
 185        case POWER_SUPPLY_PROP_ONLINE:
 186                return ucsi_psy_get_online(con, val);
 187        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
 188                return ucsi_psy_get_voltage_min(con, val);
 189        case POWER_SUPPLY_PROP_VOLTAGE_MAX:
 190                return ucsi_psy_get_voltage_max(con, val);
 191        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 192                return ucsi_psy_get_voltage_now(con, val);
 193        case POWER_SUPPLY_PROP_CURRENT_MAX:
 194                return ucsi_psy_get_current_max(con, val);
 195        case POWER_SUPPLY_PROP_CURRENT_NOW:
 196                return ucsi_psy_get_current_now(con, val);
 197        default:
 198                return -EINVAL;
 199        }
 200}
 201
 202static enum power_supply_usb_type ucsi_psy_usb_types[] = {
 203        POWER_SUPPLY_USB_TYPE_C,
 204        POWER_SUPPLY_USB_TYPE_PD,
 205        POWER_SUPPLY_USB_TYPE_PD_PPS,
 206};
 207
 208int ucsi_register_port_psy(struct ucsi_connector *con)
 209{
 210        struct power_supply_config psy_cfg = {};
 211        struct device *dev = con->ucsi->dev;
 212        char *psy_name;
 213
 214        psy_cfg.drv_data = con;
 215        psy_cfg.fwnode = dev_fwnode(dev);
 216
 217        psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d",
 218                                  dev_name(dev), con->num);
 219        if (!psy_name)
 220                return -ENOMEM;
 221
 222        con->psy_desc.name = psy_name;
 223        con->psy_desc.type = POWER_SUPPLY_TYPE_USB,
 224        con->psy_desc.usb_types = ucsi_psy_usb_types;
 225        con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types);
 226        con->psy_desc.properties = ucsi_psy_props,
 227        con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props),
 228        con->psy_desc.get_property = ucsi_psy_get_prop;
 229
 230        con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg);
 231
 232        return PTR_ERR_OR_ZERO(con->psy);
 233}
 234
 235void ucsi_unregister_port_psy(struct ucsi_connector *con)
 236{
 237        if (IS_ERR_OR_NULL(con->psy))
 238                return;
 239
 240        power_supply_unregister(con->psy);
 241        con->psy = NULL;
 242}
 243
 244void ucsi_port_psy_changed(struct ucsi_connector *con)
 245{
 246        if (IS_ERR_OR_NULL(con->psy))
 247                return;
 248
 249        power_supply_changed(con->psy);
 250}
 251