xref: /openbmc/linux/drivers/usb/typec/ucsi/psy.c (revision 5ad1ab30)
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