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, propname: "scope" , val: &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(rdo: 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(rdo: 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, fmt: "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(parent: dev, desc: &con->psy_desc, cfg: &psy_cfg); |
254 | |
255 | return PTR_ERR_OR_ZERO(ptr: con->psy); |
256 | } |
257 | |
258 | void ucsi_unregister_port_psy(struct ucsi_connector *con) |
259 | { |
260 | if (IS_ERR_OR_NULL(ptr: con->psy)) |
261 | return; |
262 | |
263 | power_supply_unregister(psy: 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(ptr: con->psy)) |
270 | return; |
271 | |
272 | power_supply_changed(psy: con->psy); |
273 | } |
274 | |