]> git.ipfire.org Git - thirdparty/kernel/stable.git/blob - drivers/usb/typec/ucsi/psy.c
Merge tag 'kvm-x86-docs-6.7' of https://github.com/kvm-x86/linux into HEAD
[thirdparty/kernel/stable.git] / drivers / usb / typec / ucsi / psy.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 */
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 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
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
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
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
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
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
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
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
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
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
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
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 }