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