FILE
psy.c
File preview with syntax highlighting.
linux-6.19.14/drivers/usb/typec/ucsi/psy.c
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_CHARGE_TYPE, 24 POWER_SUPPLY_PROP_USB_TYPE, 25 POWER_SUPPLY_PROP_ONLINE, 26 POWER_SUPPLY_PROP_VOLTAGE_MIN, 27 POWER_SUPPLY_PROP_VOLTAGE_MAX, 28 POWER_SUPPLY_PROP_VOLTAGE_NOW, 29 POWER_SUPPLY_PROP_CURRENT_MAX, 30 POWER_SUPPLY_PROP_CURRENT_NOW, 31 POWER_SUPPLY_PROP_SCOPE, 32 POWER_SUPPLY_PROP_STATUS, 33}; 34 35static int ucsi_psy_get_scope(struct ucsi_connector *con, 36 union power_supply_propval *val) 37{ 38 u8 scope = POWER_SUPPLY_SCOPE_UNKNOWN; 39 struct device *dev = con->ucsi->dev; 40 41 device_property_read_u8(dev, "scope", &scope); 42 if (scope == POWER_SUPPLY_SCOPE_UNKNOWN) { 43 u32 mask = UCSI_CAP_ATTR_POWER_AC_SUPPLY | 44 UCSI_CAP_ATTR_BATTERY_CHARGING; 45 46 if (con->ucsi->cap.attributes & mask) 47 scope = POWER_SUPPLY_SCOPE_SYSTEM; 48 else 49 scope = POWER_SUPPLY_SCOPE_DEVICE; 50 } 51 val->intval = scope; 52 return 0; 53} 54 55static int ucsi_psy_get_status(struct ucsi_connector *con, 56 union power_supply_propval *val) 57{ 58 bool is_sink = UCSI_CONSTAT(con, PWR_DIR) == TYPEC_SINK; 59 bool sink_path_enabled = true; 60 61 val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; 62 63 if (con->ucsi->version >= UCSI_VERSION_2_0) 64 sink_path_enabled = 65 UCSI_CONSTAT(con, SINK_PATH_STATUS_V2_0) == 66 UCSI_CONSTAT_SINK_PATH_ENABLED; 67 68 if (UCSI_CONSTAT(con, CONNECTED)) { 69 if (is_sink && sink_path_enabled) 70 val->intval = POWER_SUPPLY_STATUS_CHARGING; 71 else if (!is_sink) 72 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 73 } 74 75 return 0; 76} 77 78static int ucsi_psy_get_online(struct ucsi_connector *con, 79 union power_supply_propval *val) 80{ 81 val->intval = UCSI_PSY_OFFLINE; 82 if (UCSI_CONSTAT(con, CONNECTED) && 83 (UCSI_CONSTAT(con, PWR_DIR) == TYPEC_SINK)) 84 val->intval = UCSI_PSY_FIXED_ONLINE; 85 return 0; 86} 87 88static int ucsi_psy_get_voltage_min(struct ucsi_connector *con, 89 union power_supply_propval *val) 90{ 91 u32 pdo; 92 93 switch (UCSI_CONSTAT(con, PWR_OPMODE)) { 94 case UCSI_CONSTAT_PWR_OPMODE_PD: 95 pdo = con->src_pdos[0]; 96 val->intval = pdo_fixed_voltage(pdo) * 1000; 97 break; 98 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 99 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 100 case UCSI_CONSTAT_PWR_OPMODE_BC: 101 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 102 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 103 break; 104 default: 105 val->intval = 0; 106 break; 107 } 108 return 0; 109} 110 111static int ucsi_psy_get_voltage_max(struct ucsi_connector *con, 112 union power_supply_propval *val) 113{ 114 u32 pdo; 115 int max_voltage = 0; 116 117 switch (UCSI_CONSTAT(con, PWR_OPMODE)) { 118 case UCSI_CONSTAT_PWR_OPMODE_PD: 119 for (int i = 0; i < con->num_pdos; i++) { 120 int pdo_voltage = 0; 121 122 pdo = con->src_pdos[i]; 123 if (pdo_type(pdo) == PDO_TYPE_FIXED) 124 pdo_voltage = pdo_fixed_voltage(pdo) * 1000; 125 max_voltage = (pdo_voltage > max_voltage) ? pdo_voltage 126 : max_voltage; 127 } 128 val->intval = max_voltage; 129 break; 130 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 131 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 132 case UCSI_CONSTAT_PWR_OPMODE_BC: 133 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 134 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 135 break; 136 default: 137 val->intval = 0; 138 break; 139 } 140 return 0; 141} 142 143static int ucsi_psy_get_voltage_now(struct ucsi_connector *con, 144 union power_supply_propval *val) 145{ 146 int index; 147 u32 pdo; 148 149 switch (UCSI_CONSTAT(con, PWR_OPMODE)) { 150 case UCSI_CONSTAT_PWR_OPMODE_PD: 151 index = rdo_index(con->rdo); 152 if (index > 0) { 153 pdo = con->src_pdos[index - 1]; 154 val->intval = pdo_fixed_voltage(pdo) * 1000; 155 } else { 156 val->intval = 0; 157 } 158 break; 159 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 160 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 161 case UCSI_CONSTAT_PWR_OPMODE_BC: 162 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 163 val->intval = UCSI_TYPEC_VSAFE5V * 1000; 164 break; 165 default: 166 val->intval = 0; 167 break; 168 } 169 return 0; 170} 171 172static int ucsi_psy_get_current_max(struct ucsi_connector *con, 173 union power_supply_propval *val) 174{ 175 u32 pdo; 176 int max_current = 0; 177 178 if (!UCSI_CONSTAT(con, CONNECTED)) { 179 val->intval = 0; 180 return 0; 181 } 182 183 switch (UCSI_CONSTAT(con, PWR_OPMODE)) { 184 case UCSI_CONSTAT_PWR_OPMODE_PD: 185 for (int i = 0; i < con->num_pdos; i++) { 186 int pdo_current = 0; 187 188 pdo = con->src_pdos[i]; 189 if (pdo_type(pdo) == PDO_TYPE_FIXED) 190 pdo_current = pdo_max_current(pdo) * 1000; 191 max_current = (pdo_current > max_current) ? pdo_current 192 : max_current; 193 } 194 val->intval = max_current; 195 break; 196 case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: 197 val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; 198 break; 199 case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: 200 val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; 201 break; 202 case UCSI_CONSTAT_PWR_OPMODE_BC: 203 case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: 204 /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ 205 default: 206 val->intval = UCSI_TYPEC_DEFAULT_CURRENT * 1000; 207 break; 208 } 209 return 0; 210} 211 212static int ucsi_psy_get_current_now(struct ucsi_connector *con, 213 union power_supply_propval *val) 214{ 215 if (UCSI_CONSTAT(con, PWR_OPMODE) == UCSI_CONSTAT_PWR_OPMODE_PD) 216 val->intval = rdo_op_current(con->rdo) * 1000; 217 else 218 val->intval = 0; 219 return 0; 220} 221 222static int ucsi_psy_get_usb_type(struct ucsi_connector *con, 223 union power_supply_propval *val) 224{ 225 val->intval = POWER_SUPPLY_USB_TYPE_C; 226 if (UCSI_CONSTAT(con, CONNECTED) && 227 UCSI_CONSTAT(con, PWR_OPMODE) == UCSI_CONSTAT_PWR_OPMODE_PD) 228 val->intval = POWER_SUPPLY_USB_TYPE_PD; 229 230 return 0; 231} 232 233static int ucsi_psy_get_charge_type(struct ucsi_connector *con, union power_supply_propval *val) 234{ 235 if (!(UCSI_CONSTAT(con, CONNECTED))) { 236 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; 237 return 0; 238 } 239 240 /* The Battery Charging Cabability Status field is only valid in sink role. */ 241 if (UCSI_CONSTAT(con, PWR_DIR) != TYPEC_SINK) { 242 val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; 243 return 0; 244 } 245 246 switch (UCSI_CONSTAT(con, BC_STATUS)) { 247 case UCSI_CONSTAT_BC_NOMINAL_CHARGING: 248 val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; 249 break; 250 case UCSI_CONSTAT_BC_SLOW_CHARGING: 251 case UCSI_CONSTAT_BC_TRICKLE_CHARGING: 252 val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; 253 break; 254 default: 255 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; 256 break; 257 } 258 259 return 0; 260} 261 262static int ucsi_psy_get_prop(struct power_supply *psy, 263 enum power_supply_property psp, 264 union power_supply_propval *val) 265{ 266 struct ucsi_connector *con = power_supply_get_drvdata(psy); 267 268 switch (psp) { 269 case POWER_SUPPLY_PROP_CHARGE_TYPE: 270 return ucsi_psy_get_charge_type(con, val); 271 case POWER_SUPPLY_PROP_USB_TYPE: 272 return ucsi_psy_get_usb_type(con, val); 273 case POWER_SUPPLY_PROP_ONLINE: 274 return ucsi_psy_get_online(con, val); 275 case POWER_SUPPLY_PROP_VOLTAGE_MIN: 276 return ucsi_psy_get_voltage_min(con, val); 277 case POWER_SUPPLY_PROP_VOLTAGE_MAX: 278 return ucsi_psy_get_voltage_max(con, val); 279 case POWER_SUPPLY_PROP_VOLTAGE_NOW: 280 return ucsi_psy_get_voltage_now(con, val); 281 case POWER_SUPPLY_PROP_CURRENT_MAX: 282 return ucsi_psy_get_current_max(con, val); 283 case POWER_SUPPLY_PROP_CURRENT_NOW: 284 return ucsi_psy_get_current_now(con, val); 285 case POWER_SUPPLY_PROP_SCOPE: 286 return ucsi_psy_get_scope(con, val); 287 case POWER_SUPPLY_PROP_STATUS: 288 return ucsi_psy_get_status(con, val); 289 default: 290 return -EINVAL; 291 } 292} 293 294int ucsi_register_port_psy(struct ucsi_connector *con) 295{ 296 struct power_supply_config psy_cfg = {}; 297 struct device *dev = con->ucsi->dev; 298 char *psy_name; 299 300 psy_cfg.drv_data = con; 301 psy_cfg.fwnode = dev_fwnode(dev); 302 303 psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d", 304 dev_name(dev), con->num); 305 if (!psy_name) 306 return -ENOMEM; 307 308 con->psy_desc.name = psy_name; 309 con->psy_desc.type = POWER_SUPPLY_TYPE_USB; 310 con->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | 311 BIT(POWER_SUPPLY_USB_TYPE_PD) | 312 BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); 313 con->psy_desc.properties = ucsi_psy_props; 314 con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props); 315 con->psy_desc.get_property = ucsi_psy_get_prop; 316 317 con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); 318 319 return PTR_ERR_OR_ZERO(con->psy); 320} 321 322void ucsi_unregister_port_psy(struct ucsi_connector *con) 323{ 324 if (IS_ERR_OR_NULL(con->psy)) 325 return; 326 327 power_supply_unregister(con->psy); 328 con->psy = NULL; 329} 330 331void ucsi_port_psy_changed(struct ucsi_connector *con) 332{ 333 if (IS_ERR_OR_NULL(con->psy)) 334 return; 335 336 power_supply_changed(con->psy); 337}
~/
Read-only file preview.