Skip to content

Commit 992a60e

Browse files
kv-abhilashgregkh
authored andcommitted
usb: typec: ucsi: register with power_supply class
With this change the UCSI device will show up in /sys/class/power_supply/. The following values are exported: - online - usb_type - voltage_min - voltage_max - voltage_now - current_max - current_now Once a PD-capable type-C power source is connected to the system, GET_PDOS UCSI command is used to query all source capabilities. Request data object (RDO) is used to get current values. Signed-off-by: K V, Abhilash <[email protected]> Signed-off-by: Heikki Krogerus <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 4dbc6a4 commit 992a60e

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed

drivers/usb/typec/ucsi/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ typec_ucsi-y := ucsi.o
77

88
typec_ucsi-$(CONFIG_TRACING) += trace.o
99

10+
ifneq ($(CONFIG_POWER_SUPPLY),)
11+
typec_ucsi-y += psy.o
12+
endif
13+
1014
ifneq ($(CONFIG_TYPEC_DP_ALTMODE),)
1115
typec_ucsi-y += displayport.o
1216
endif

drivers/usb/typec/ucsi/psy.c

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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 <[email protected]>
7+
* Author: Heikki Krogerus <[email protected]>
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+
};
31+
32+
static 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+
42+
static 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+
65+
static 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+
92+
static 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+
121+
static 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+
151+
static 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+
163+
static 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+
176+
static 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+
202+
static 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+
208+
int 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+
235+
void 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+
}

drivers/usb/typec/ucsi/ucsi.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,10 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
936936
cap->driver_data = con;
937937
cap->ops = &ucsi_ops;
938938

939+
ret = ucsi_register_port_psy(con);
940+
if (ret)
941+
return ret;
942+
939943
/* Register the connector */
940944
con->port = typec_register_port(ucsi->dev, cap);
941945
if (IS_ERR(con->port))
@@ -1062,6 +1066,7 @@ int ucsi_init(struct ucsi *ucsi)
10621066
for (con = ucsi->connector; con->port; con++) {
10631067
ucsi_unregister_partner(con);
10641068
ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
1069+
ucsi_unregister_port_psy(con);
10651070
typec_unregister_port(con->port);
10661071
con->port = NULL;
10671072
}
@@ -1185,6 +1190,7 @@ void ucsi_unregister(struct ucsi *ucsi)
11851190
ucsi_unregister_partner(&ucsi->connector[i]);
11861191
ucsi_unregister_altmodes(&ucsi->connector[i],
11871192
UCSI_RECIPIENT_CON);
1193+
ucsi_unregister_port_psy(&ucsi->connector[i]);
11881194
typec_unregister_port(ucsi->connector[i].port);
11891195
}
11901196

drivers/usb/typec/ucsi/ucsi.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <linux/bitops.h>
77
#include <linux/device.h>
8+
#include <linux/power_supply.h>
89
#include <linux/types.h>
910
#include <linux/usb/typec.h>
1011

@@ -301,6 +302,10 @@ struct ucsi {
301302
#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
302303
#define UCSI_MAX_PDOS (4)
303304

305+
#define UCSI_TYPEC_VSAFE5V 5000
306+
#define UCSI_TYPEC_1_5_CURRENT 1500
307+
#define UCSI_TYPEC_3_0_CURRENT 3000
308+
304309
struct ucsi_connector {
305310
int num;
306311

@@ -319,6 +324,8 @@ struct ucsi_connector {
319324

320325
struct ucsi_connector_status status;
321326
struct ucsi_connector_capability cap;
327+
struct power_supply *psy;
328+
struct power_supply_desc psy_desc;
322329
u32 rdo;
323330
u32 src_pdos[UCSI_MAX_PDOS];
324331
int num_pdos;
@@ -330,6 +337,14 @@ int ucsi_send_command(struct ucsi *ucsi, u64 command,
330337
void ucsi_altmode_update_active(struct ucsi_connector *con);
331338
int ucsi_resume(struct ucsi *ucsi);
332339

340+
#if IS_ENABLED(CONFIG_POWER_SUPPLY)
341+
int ucsi_register_port_psy(struct ucsi_connector *con);
342+
void ucsi_unregister_port_psy(struct ucsi_connector *con);
343+
#else
344+
static inline int ucsi_register_port(struct ucsi_connector *con) { return 0; }
345+
static inline void ucsi_unregister_port_psy(struct ucsi_connector *con) { }
346+
#endif /* CONFIG_POWER_SUPPLY */
347+
333348
#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
334349
struct typec_altmode *
335350
ucsi_register_displayport(struct ucsi_connector *con,

0 commit comments

Comments
 (0)