File Index
Files and directories
Sign in
FILE

psy.c

File preview with syntax highlighting.

linux-6.19.14/drivers/usb/typec/ucsi/psy.c 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.