psy.c (6304B)
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}