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 POWER_SUPPLY_PROP_SCOPE, 31}; 32 33static int ucsi_psy_get_scope(struct ucsi_connector *con, 34 union power_supply_propval *val) 35{ 36 u8 scope = POWER_SUPPLY_SCOPE_UNKNOWN; 37 struct device *dev = con->ucsi->dev; 38 39 device_property_read_u8(dev, "scope", &scope); 40 if (scope == POWER_SUPPLY_SCOPE_UNKNOWN) { 41 u32 mask = UCSI_CAP_ATTR_POWER_AC_SUPPLY | 42 UCSI_CAP_ATTR_BATTERY_CHARGING; 43 44 if (con->ucsi->cap.attributes & mask) 45 scope = POWER_SUPPLY_SCOPE_SYSTEM; 46 else 47 scope = POWER_SUPPLY_SCOPE_DEVICE; 48 } 49 val->intval = scope; 50 return 0; 51} 52 53static int ucsi_psy_get_online(struct ucsi_connector *con, 54 union power_supply_propval *val) 55{ 56 val->intval = UCSI_PSY_OFFLINE; 57 if (con->status.flags & UCSI_CONSTAT_CONNECTED && 58 (con->status.flags & UCSI_CONSTAT_PWR_DIR) == TYPEC_SINK) 59 val->intval = UCSI_PSY_FIXED_ONLINE; 60 return 0; 61} 62 63static int ucsi_psy_get_voltage_min(struct ucsi_connector *con, 64 union power_supply_propval *val) 65{ 66 u32 pdo; 67 68 switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { 69 case UCSI_CONSTAT_PWR_OPMODE_PD: 70 pdo = con->src_pdos[0]; 71 val->intval = pdo_fixed_voltage(pdo) * 1000; 72 break; 73 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 74 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 75 case UCSI_CONSTAT_PWR_OPMODE_BC: 76 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 77 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 78 break; 79 default: 80 val->intval = 0; 81 break; 82 } 83 return 0; 84} 85 86static int ucsi_psy_get_voltage_max(struct ucsi_connector *con, 87 union power_supply_propval *val) 88{ 89 u32 pdo; 90 91 switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { 92 case UCSI_CONSTAT_PWR_OPMODE_PD: 93 if (con->num_pdos > 0) { 94 pdo = con->src_pdos[con->num_pdos - 1]; 95 val->intval = pdo_fixed_voltage(pdo) * 1000; 96 } else { 97 val->intval = 0; 98 } 99 break; 100 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 101 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 102 case UCSI_CONSTAT_PWR_OPMODE_BC: 103 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 104 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 105 break; 106 default: 107 val->intval = 0; 108 break; 109 } 110 return 0; 111} 112 113static int ucsi_psy_get_voltage_now(struct ucsi_connector *con, 114 union power_supply_propval *val) 115{ 116 int index; 117 u32 pdo; 118 119 switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { 120 case UCSI_CONSTAT_PWR_OPMODE_PD: 121 index = rdo_index(con->rdo); 122 if (index > 0) { 123 pdo = con->src_pdos[index - 1]; 124 val->intval = pdo_fixed_voltage(pdo) * 1000; 125 } else { 126 val->intval = 0; 127 } 128 break; 129 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 130 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 131 case UCSI_CONSTAT_PWR_OPMODE_BC: 132 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 133 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 134 break; 135 default: 136 val->intval = 0; 137 break; 138 } 139 return 0; 140} 141 142static int ucsi_psy_get_current_max(struct ucsi_connector *con, 143 union power_supply_propval *val) 144{ 145 u32 pdo; 146 147 switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { 148 case UCSI_CONSTAT_PWR_OPMODE_PD: 149 if (con->num_pdos > 0) { 150 pdo = con->src_pdos[con->num_pdos - 1]; 151 val->intval = pdo_max_current(pdo) * 1000; 152 } else { 153 val->intval = 0; 154 } 155 break; 156 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 157 val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; 158 break; 159 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 160 val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; 161 break; 162 case UCSI_CONSTAT_PWR_OPMODE_BC: 163 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 164 /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ 165 default: 166 val->intval = 0; 167 break; 168 } 169 return 0; 170} 171 172static int ucsi_psy_get_current_now(struct ucsi_connector *con, 173 union power_supply_propval *val) 174{ 175 u16 flags = con->status.flags; 176 177 if (UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) 178 val->intval = rdo_op_current(con->rdo) * 1000; 179 else 180 val->intval = 0; 181 return 0; 182} 183 184static int ucsi_psy_get_usb_type(struct ucsi_connector *con, 185 union power_supply_propval *val) 186{ 187 u16 flags = con->status.flags; 188 189 val->intval = POWER_SUPPLY_USB_TYPE_C; 190 if (flags & UCSI_CONSTAT_CONNECTED && 191 UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) 192 val->intval = POWER_SUPPLY_USB_TYPE_PD; 193 194 return 0; 195} 196 197static int ucsi_psy_get_prop(struct power_supply *psy, 198 enum power_supply_property psp, 199 union power_supply_propval *val) 200{ 201 struct ucsi_connector *con = power_supply_get_drvdata(psy); 202 203 switch (psp) { 204 case POWER_SUPPLY_PROP_USB_TYPE: 205 return ucsi_psy_get_usb_type(con, val); 206 case POWER_SUPPLY_PROP_ONLINE: 207 return ucsi_psy_get_online(con, val); 208 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 209 return ucsi_psy_get_voltage_min(con, val); 210 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 211 return ucsi_psy_get_voltage_max(con, val); 212 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 213 return ucsi_psy_get_voltage_now(con, val); 214 case POWER_SUPPLY_PROP_CURRENT_MAX: 215 return ucsi_psy_get_current_max(con, val); 216 case POWER_SUPPLY_PROP_CURRENT_NOW: 217 return ucsi_psy_get_current_now(con, val); 218 case POWER_SUPPLY_PROP_SCOPE: 219 return ucsi_psy_get_scope(con, val); 220 default: 221 return -EINVAL; 222 } 223} 224 225static enum power_supply_usb_type ucsi_psy_usb_types[] = { 226 POWER_SUPPLY_USB_TYPE_C, 227 POWER_SUPPLY_USB_TYPE_PD, 228 POWER_SUPPLY_USB_TYPE_PD_PPS, 229}; 230 231int ucsi_register_port_psy(struct ucsi_connector *con) 232{ 233 struct power_supply_config psy_cfg = {}; 234 struct device *dev = con->ucsi->dev; 235 char *psy_name; 236 237 psy_cfg.drv_data = con; 238 psy_cfg.fwnode = dev_fwnode(dev); 239 240 psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d", 241 dev_name(dev), con->num); 242 if (!psy_name) 243 return -ENOMEM; 244 245 con->psy_desc.name = psy_name; 246 con->psy_desc.type = POWER_SUPPLY_TYPE_USB; 247 con->psy_desc.usb_types = ucsi_psy_usb_types; 248 con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types); 249 con->psy_desc.properties = ucsi_psy_props; 250 con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props); 251 con->psy_desc.get_property = ucsi_psy_get_prop; 252 253 con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); 254 255 return PTR_ERR_OR_ZERO(con->psy); 256} 257 258void ucsi_unregister_port_psy(struct ucsi_connector *con) 259{ 260 if (IS_ERR_OR_NULL(con->psy)) 261 return; 262 263 power_supply_unregister(con->psy); 264 con->psy = NULL; 265} 266 267void ucsi_port_psy_changed(struct ucsi_connector *con) 268{ 269 if (IS_ERR_OR_NULL(con->psy)) 270 return; 271 272 power_supply_changed(con->psy); 273} 274