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_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
33static 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
53static 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
63static 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
86static 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
113static 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
142static 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
172static 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
184static 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
197static 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
225static 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
231int 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
258void 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
267void 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}
274