summaryrefslogtreecommitdiff
path: root/driver/bc12/pi3usb9201.c
blob: d6e82c3c57ef8ebb69a7f610446a7d4039655cd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* PI3USB9201 USB BC 1.2 Charger Detector driver. */

#include "pi3usb9201.h"
#include "charge_manager.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "gpio.h"
#include "power.h"
#include "task.h"
#include "tcpm.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)

enum pi3usb9201_client_sts {
	CHG_OTHER = 0,
	CHG_2_4A,
	CHG_2_0A,
	CHG_1_0A,
	CHG_RESERVED,
	CHG_CDP,
	CHG_SDP,
	CHG_DCP,
};

struct bc12_status {
	enum charge_supplier supplier;
	int current_limit;
};

static const struct bc12_status bc12_chg_limits[] = {
	[CHG_OTHER] = {CHARGE_SUPPLIER_OTHER, 500},
	[CHG_2_4A] = {CHARGE_SUPPLIER_PROPRIETARY, 2400},
	[CHG_2_0A] = {CHARGE_SUPPLIER_PROPRIETARY, 2000},
	[CHG_1_0A] = {CHARGE_SUPPLIER_PROPRIETARY, 1000},
	[CHG_RESERVED] = {CHARGE_SUPPLIER_NONE, 0},
	[CHG_CDP] = {CHARGE_SUPPLIER_BC12_CDP, 1500},
	[CHG_SDP] = {CHARGE_SUPPLIER_BC12_SDP, 500},
#if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW)
	/*
	 * If ramping is supported, then for DCP set the current limit to be the
	 * max supported for the port by the board. This because for DCP the
	 * charger is allowed to set its own max up to 5A.
	 */
	[CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, PD_MAX_CURRENT_MA},
#else
	[CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, 500},
#endif
};

static inline int raw_read8(int port, int offset, int *value)
{
	return i2c_read8(pi3usb2901_bc12_chips[port].i2c_port,
			 pi3usb2901_bc12_chips[port].i2c_addr,
			 offset, value);
}

static inline int raw_write8(int port, int offset, int value)
{
	return i2c_write8(pi3usb2901_bc12_chips[port].i2c_port,
			  pi3usb2901_bc12_chips[port].i2c_addr,
			  offset, value);
}

static int pi3usb9201_raw(int port, int reg, int mask, int val)
{
	int rv;
	int reg_val;

	rv = raw_read8(port, reg, &reg_val);
	if (rv)
		return rv;

	reg_val &= ~mask;
	reg_val |= val;

	return raw_write8(port, reg, reg_val);
}

static int pi3usb9201_interrupt_mask(int port, int enable)
{
	return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_1,
			  PI3USB9201_REG_CTRL_1_INT_MASK,
			  enable);
}

static int pi3usb9201_bc12_detect_ctrl(int port, int enable)
{
	return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_2,
			      PI3USB9201_REG_CTRL_2_START_DET,
			      enable ? PI3USB9201_REG_CTRL_2_START_DET : 0);
}

static int pi3usb9201_set_mode(int port, int desired_mode)
{
	return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_1,
			      PI3USB9201_REG_CTRL_1_MODE_MASK,
			      desired_mode << PI3USB9201_REG_CTRL_1_MODE_SHIFT);
}

static int pi3usb9201_get_mode(int port, int *mode)
{
	int rv;

	rv = raw_read8(port, PI3USB9201_REG_CTRL_1, mode);
	if (rv)
		return rv;

	*mode &= PI3USB9201_REG_CTRL_1_MODE_MASK;
	*mode >>= PI3USB9201_REG_CTRL_1_MODE_SHIFT;

	return EC_SUCCESS;
}

static int pi3usb9201_get_status(int port, int *client, int *host)
{
	int rv;
	int status;

	rv = raw_read8(port, PI3USB9201_REG_CLIENT_STS, &status);
	if (client)
		*client = status;
	rv |= raw_read8(port, PI3USB9201_REG_HOST_STS, &status);
	if (host)
		*host = status;

	return rv;
}

static void bc12_update_charge_manager(int port, int client_status)
{
	struct charge_port_info new_chg;
	enum charge_supplier supplier;
	int bit_pos;

	/* Set charge voltage to 5V */
	new_chg.voltage = USB_CHARGER_VOLTAGE_MV;

	/*
	 * Find set bit position. Note that this funciton is only called if a
	 * bit was set in client_status, so bit_pos won't be negative.
	 */
	bit_pos = __builtin_ffs(client_status) - 1;

	new_chg.current = bc12_chg_limits[bit_pos].current_limit;
	supplier = bc12_chg_limits[bit_pos].supplier;

	CPRINTS("pi3usb9201[p%d]: sts = 0x%x, lim = %d mA, supplier = %d",
		port, client_status, new_chg.current, supplier);
	/* bc1.2 is complete and start bit does not auto clear */
	pi3usb9201_bc12_detect_ctrl(port, 0);
	/* Inform charge manager of new supplier type and current limit */
	charge_manager_update_charge(supplier, port, &new_chg);
}

static int bc12_detect_start(int port)
{
	int rv;

	/*
	 * Read both status registers to ensure that all interrupt indications
	 * are cleared prior to starting bc1.2 detection.
	 */
	pi3usb9201_get_status(port, NULL, NULL);

	/* Put pi3usb9201 into client mode */
	rv = pi3usb9201_set_mode(port, PI3USB9201_CLIENT_MODE);
	if (rv)
		return rv;
	/* Have pi3usb9201 start bc1.2 detection */
	rv = pi3usb9201_bc12_detect_ctrl(port, 1);
	if (rv)
		return rv;
	/* Unmask interrupt to wake task when detection completes */
	return pi3usb9201_interrupt_mask(port, 0);
}

static void bc12_power_down(int port)
{
	/* Put pi3usb9201 into its power down mode */
	pi3usb9201_set_mode(port, PI3USB9201_POWER_DOWN);
	/* The start bc1.2 bit does not auto clear */
	pi3usb9201_bc12_detect_ctrl(port, 0);
	/* Mask interrupts unitl next bc1.2 detection event */
	pi3usb9201_interrupt_mask(port, 1);
	/* Let charge manager know there's no more charge available. */
	charge_manager_update_charge(CHARGE_SUPPLIER_NONE, port, NULL);
#if defined(CONFIG_POWER_PP5000_CONTROL) && defined(HAS_TASK_CHIPSET)
	/* Indicate PP5000_A rail is not required by USB_CHG task. */
	power_5v_enable(task_get_current(), 0);
#endif
}

static void bc12_power_up(int port)
{
#if defined(CONFIG_POWER_PP5000_CONTROL) && defined(HAS_TASK_CHIPSET)
	/* Turn on the 5V rail to allow the chip to be powered. */
	power_5v_enable(task_get_current(), 1);
	/* Give the pi3usb9201 time so it's ready to receive i2c messages */
	msleep(1);
#endif
	pi3usb9201_interrupt_mask(port, 1);
}

void usb_charger_task(void *u)
{
	int port = (task_get_current() == TASK_ID_USB_CHG_P0 ? 0 : 1);
	uint32_t evt;

	/*
	 * The is no specific initialization required for the pi3usb9201 other
	 * than enabling the interrupt mask.
	 */
	pi3usb9201_interrupt_mask(port, 1);

	while (1) {
		/* Wait for interrupt */
		evt = task_wait_event(-1);

		/* Interrupt from the Pericom chip, determine charger type */
		if (evt & USB_CHG_EVENT_BC12) {
			int client;
			int rv;

			rv = pi3usb9201_get_status(port, &client, NULL);
			if (!rv && client)
				/*
				 * Any bit set in client status register
				 * indicates that BC1.2 detection has
				 * completed.
				 */
				bc12_update_charge_manager(port, client);
		}

#ifndef CONFIG_USB_PD_VBUS_DETECT_TCPC
		if (evt & USB_CHG_EVENT_VBUS)
			CPRINTS("VBUS p%d %d", port,
				pd_snk_is_vbus_provided(port));
#endif

		if (evt & USB_CHG_EVENT_DR_UFP) {
			bc12_power_up(port);
			if (bc12_detect_start(port)) {
				struct charge_port_info new_chg;

				/*
				 * VBUS is present, but starting bc1.2 detection
				 * failed for some reason. So limit charge
				 * current to default 500 mA for this case.
				 */

				new_chg.voltage = USB_CHARGER_VOLTAGE_MV;
				new_chg.current = USB_CHARGER_MIN_CURR_MA;
				charge_manager_update_charge(
					CHARGE_SUPPLIER_OTHER,
					port, &new_chg);
				CPRINTS("pi3usb9201[p%d]: bc1.2 failed use "
					"defaults", port);
			}
		}

		/*
		 * TODO(b/124061702): For host mode, currently only setting it
		 * to host CDP mode. However, there are 3 host status bits to
		 * know things such as an adapter connected, but no USB device
		 * present, or bc1.2 activity detected.
		 */
		if (evt & USB_CHG_EVENT_DR_DFP) {
			int mode;
			int rv;

			/*
			 * If the port is in DFP mode, then need to set mode to
			 * CDP_HOST which will auto close D+/D- switches.
			 */
			bc12_power_up(port);
			rv = pi3usb9201_get_mode(port, &mode);
			if (!rv && (mode != PI3USB9201_CDP_HOST_MODE))
				pi3usb9201_set_mode(port,
						    PI3USB9201_CDP_HOST_MODE);
		}

		if (evt & USB_CHG_EVENT_CC_OPEN)
			bc12_power_down(port);
	}
}

void usb_charger_set_switches(int port, enum usb_switch setting)
{
	/*
	 * Switches are controlled automatically based on whether the port is
	 * acting as a source or as sink and the result of BC1.2 detection.
	 */
}

#if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW)
int usb_charger_ramp_allowed(int supplier)
{
	/* Don't allow ramp if charge supplier is OTHER, SDP, or NONE */
	return !(supplier == CHARGE_SUPPLIER_OTHER ||
		 supplier == CHARGE_SUPPLIER_BC12_SDP ||
		 supplier == CHARGE_SUPPLIER_NONE);
}

int usb_charger_ramp_max(int supplier, int sup_curr)
{
	/*
	 * Use the level from the bc12_chg_limits table above except for
	 * proprietary of CDP and in those cases the charge current from the
	 * charge manager is already set at the max determined by bc1.2
	 * detection.
	 */
	switch (supplier) {
	case CHARGE_SUPPLIER_BC12_DCP:
		return PD_MAX_CURRENT_MA;
	case CHARGE_SUPPLIER_BC12_CDP:
	case CHARGE_SUPPLIER_PROPRIETARY:
		return sup_curr;
	case CHARGE_SUPPLIER_BC12_SDP:
	default:
		return 500;
	}
}
#endif /* CONFIG_CHARGE_RAMP_SW || CONFIG_CHARGE_RAMP_HW */