]> Git Repo - linux.git/blob - drivers/usb/typec/ucsi/psy.c
x86/kaslr: Expose and use the end of the physical memory address space
[linux.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 <[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_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 };
33
34 static int ucsi_psy_get_scope(struct ucsi_connector *con,
35                               union power_supply_propval *val)
36 {
37         u8 scope = POWER_SUPPLY_SCOPE_UNKNOWN;
38         struct device *dev = con->ucsi->dev;
39
40         device_property_read_u8(dev, "scope", &scope);
41         if (scope == POWER_SUPPLY_SCOPE_UNKNOWN) {
42                 u32 mask = UCSI_CAP_ATTR_POWER_AC_SUPPLY |
43                            UCSI_CAP_ATTR_BATTERY_CHARGING;
44
45                 if (con->ucsi->cap.attributes & mask)
46                         scope = POWER_SUPPLY_SCOPE_SYSTEM;
47                 else
48                         scope = POWER_SUPPLY_SCOPE_DEVICE;
49         }
50         val->intval = scope;
51         return 0;
52 }
53
54 static int ucsi_psy_get_online(struct ucsi_connector *con,
55                                union power_supply_propval *val)
56 {
57         val->intval = UCSI_PSY_OFFLINE;
58         if (con->status.flags & UCSI_CONSTAT_CONNECTED &&
59             (con->status.flags & UCSI_CONSTAT_PWR_DIR) == TYPEC_SINK)
60                 val->intval = UCSI_PSY_FIXED_ONLINE;
61         return 0;
62 }
63
64 static int ucsi_psy_get_voltage_min(struct ucsi_connector *con,
65                                     union power_supply_propval *val)
66 {
67         u32 pdo;
68
69         switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
70         case UCSI_CONSTAT_PWR_OPMODE_PD:
71                 pdo = con->src_pdos[0];
72                 val->intval = pdo_fixed_voltage(pdo) * 1000;
73                 break;
74         case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
75         case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
76         case UCSI_CONSTAT_PWR_OPMODE_BC:
77         case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
78                 val->intval = UCSI_TYPEC_VSAFE5V * 1000;
79                 break;
80         default:
81                 val->intval = 0;
82                 break;
83         }
84         return 0;
85 }
86
87 static int ucsi_psy_get_voltage_max(struct ucsi_connector *con,
88                                     union power_supply_propval *val)
89 {
90         u32 pdo;
91
92         switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
93         case UCSI_CONSTAT_PWR_OPMODE_PD:
94                 if (con->num_pdos > 0) {
95                         pdo = con->src_pdos[con->num_pdos - 1];
96                         val->intval = pdo_fixed_voltage(pdo) * 1000;
97                 } else {
98                         val->intval = 0;
99                 }
100                 break;
101         case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
102         case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
103         case UCSI_CONSTAT_PWR_OPMODE_BC:
104         case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
105                 val->intval = UCSI_TYPEC_VSAFE5V * 1000;
106                 break;
107         default:
108                 val->intval = 0;
109                 break;
110         }
111         return 0;
112 }
113
114 static int ucsi_psy_get_voltage_now(struct ucsi_connector *con,
115                                     union power_supply_propval *val)
116 {
117         int index;
118         u32 pdo;
119
120         switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
121         case UCSI_CONSTAT_PWR_OPMODE_PD:
122                 index = rdo_index(con->rdo);
123                 if (index > 0) {
124                         pdo = con->src_pdos[index - 1];
125                         val->intval = pdo_fixed_voltage(pdo) * 1000;
126                 } else {
127                         val->intval = 0;
128                 }
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
143 static int ucsi_psy_get_current_max(struct ucsi_connector *con,
144                                     union power_supply_propval *val)
145 {
146         u32 pdo;
147
148         switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
149         case UCSI_CONSTAT_PWR_OPMODE_PD:
150                 if (con->num_pdos > 0) {
151                         pdo = con->src_pdos[con->num_pdos - 1];
152                         val->intval = pdo_max_current(pdo) * 1000;
153                 } else {
154                         val->intval = 0;
155                 }
156                 break;
157         case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
158                 val->intval = UCSI_TYPEC_1_5_CURRENT * 1000;
159                 break;
160         case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
161                 val->intval = UCSI_TYPEC_3_0_CURRENT * 1000;
162                 break;
163         case UCSI_CONSTAT_PWR_OPMODE_BC:
164         case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
165         /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */
166         default:
167                 val->intval = 0;
168                 break;
169         }
170         return 0;
171 }
172
173 static int ucsi_psy_get_current_now(struct ucsi_connector *con,
174                                     union power_supply_propval *val)
175 {
176         u16 flags = con->status.flags;
177
178         if (UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD)
179                 val->intval = rdo_op_current(con->rdo) * 1000;
180         else
181                 val->intval = 0;
182         return 0;
183 }
184
185 static int ucsi_psy_get_usb_type(struct ucsi_connector *con,
186                                  union power_supply_propval *val)
187 {
188         u16 flags = con->status.flags;
189
190         val->intval = POWER_SUPPLY_USB_TYPE_C;
191         if (flags & UCSI_CONSTAT_CONNECTED &&
192             UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD)
193                 val->intval = POWER_SUPPLY_USB_TYPE_PD;
194
195         return 0;
196 }
197
198 static int ucsi_psy_get_charge_type(struct ucsi_connector *con, union power_supply_propval *val)
199 {
200         if (!(con->status.flags & UCSI_CONSTAT_CONNECTED)) {
201                 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
202                 return 0;
203         }
204
205         /* The Battery Charging Cabability Status field is only valid in sink role. */
206         if ((con->status.flags & UCSI_CONSTAT_PWR_DIR) != TYPEC_SINK) {
207                 val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
208                 return 0;
209         }
210
211         switch (UCSI_CONSTAT_BC_STATUS(con->status.pwr_status)) {
212         case UCSI_CONSTAT_BC_NOMINAL_CHARGING:
213                 val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
214                 break;
215         case UCSI_CONSTAT_BC_SLOW_CHARGING:
216         case UCSI_CONSTAT_BC_TRICKLE_CHARGING:
217                 val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
218                 break;
219         default:
220                 val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
221                 break;
222         }
223
224         return 0;
225 }
226
227 static int ucsi_psy_get_prop(struct power_supply *psy,
228                              enum power_supply_property psp,
229                              union power_supply_propval *val)
230 {
231         struct ucsi_connector *con = power_supply_get_drvdata(psy);
232
233         switch (psp) {
234         case POWER_SUPPLY_PROP_CHARGE_TYPE:
235                 return ucsi_psy_get_charge_type(con, val);
236         case POWER_SUPPLY_PROP_USB_TYPE:
237                 return ucsi_psy_get_usb_type(con, val);
238         case POWER_SUPPLY_PROP_ONLINE:
239                 return ucsi_psy_get_online(con, val);
240         case POWER_SUPPLY_PROP_VOLTAGE_MIN:
241                 return ucsi_psy_get_voltage_min(con, val);
242         case POWER_SUPPLY_PROP_VOLTAGE_MAX:
243                 return ucsi_psy_get_voltage_max(con, val);
244         case POWER_SUPPLY_PROP_VOLTAGE_NOW:
245                 return ucsi_psy_get_voltage_now(con, val);
246         case POWER_SUPPLY_PROP_CURRENT_MAX:
247                 return ucsi_psy_get_current_max(con, val);
248         case POWER_SUPPLY_PROP_CURRENT_NOW:
249                 return ucsi_psy_get_current_now(con, val);
250         case POWER_SUPPLY_PROP_SCOPE:
251                 return ucsi_psy_get_scope(con, val);
252         default:
253                 return -EINVAL;
254         }
255 }
256
257 static enum power_supply_usb_type ucsi_psy_usb_types[] = {
258         POWER_SUPPLY_USB_TYPE_C,
259         POWER_SUPPLY_USB_TYPE_PD,
260         POWER_SUPPLY_USB_TYPE_PD_PPS,
261 };
262
263 int ucsi_register_port_psy(struct ucsi_connector *con)
264 {
265         struct power_supply_config psy_cfg = {};
266         struct device *dev = con->ucsi->dev;
267         char *psy_name;
268
269         psy_cfg.drv_data = con;
270         psy_cfg.fwnode = dev_fwnode(dev);
271
272         psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d",
273                                   dev_name(dev), con->num);
274         if (!psy_name)
275                 return -ENOMEM;
276
277         con->psy_desc.name = psy_name;
278         con->psy_desc.type = POWER_SUPPLY_TYPE_USB;
279         con->psy_desc.usb_types = ucsi_psy_usb_types;
280         con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types);
281         con->psy_desc.properties = ucsi_psy_props;
282         con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props);
283         con->psy_desc.get_property = ucsi_psy_get_prop;
284
285         con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg);
286
287         return PTR_ERR_OR_ZERO(con->psy);
288 }
289
290 void ucsi_unregister_port_psy(struct ucsi_connector *con)
291 {
292         if (IS_ERR_OR_NULL(con->psy))
293                 return;
294
295         power_supply_unregister(con->psy);
296         con->psy = NULL;
297 }
298
299 void ucsi_port_psy_changed(struct ucsi_connector *con)
300 {
301         if (IS_ERR_OR_NULL(con->psy))
302                 return;
303
304         power_supply_changed(con->psy);
305 }
This page took 0.050674 seconds and 4 git commands to generate.