xref: /openbmc/linux/drivers/usb/typec/ucsi/psy.c (revision c9ca8de2)
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 
ucsi_psy_get_scope(struct ucsi_connector * con,union power_supply_propval * val)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 	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 
ucsi_psy_get_online(struct ucsi_connector * con,union power_supply_propval * val)53 static 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 
ucsi_psy_get_voltage_min(struct ucsi_connector * con,union power_supply_propval * val)63 static 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 
ucsi_psy_get_voltage_max(struct ucsi_connector * con,union power_supply_propval * val)86 static 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 
ucsi_psy_get_voltage_now(struct ucsi_connector * con,union power_supply_propval * val)113 static 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 
ucsi_psy_get_current_max(struct ucsi_connector * con,union power_supply_propval * val)142 static 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 
ucsi_psy_get_current_now(struct ucsi_connector * con,union power_supply_propval * val)172 static 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 
ucsi_psy_get_usb_type(struct ucsi_connector * con,union power_supply_propval * val)184 static 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 
ucsi_psy_get_prop(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)197 static 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 
225 static 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 
ucsi_register_port_psy(struct ucsi_connector * con)231 int 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 
ucsi_unregister_port_psy(struct ucsi_connector * con)258 void 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 
ucsi_port_psy_changed(struct ucsi_connector * con)267 void 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