summaryrefslogtreecommitdiff
path: root/driver/ppc/aoz1380.c
blob: 4e2188c60d25f0355d4c7a658fb7c92720b35328 (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
/* 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.
 */

/*
 * AOZ1380 USB-C Power Path Controller
 *
 * This is a basic TCPM controlled PPC driver.  It could easily be
 * renamed and repurposed to be generic, if there are other TCPM
 * controlled PPC chips that are similar to the AOZ1380
 */

#include "atomic.h"
#include "common.h"
#include "console.h"
#include "aoz1380.h"
#include "hooks.h"
#include "system.h"
#include "tcpm/tcpm.h"
#include "usb_pd.h"
#include "usb_pd_tcpc.h"
#include "usbc_ppc.h"

#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)

static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */

#define AOZ1380_FLAGS_SOURCE_ENABLED    BIT(0)
#define AOZ1380_FLAGS_SINK_ENABLED      BIT(1)
#define AOZ1380_FLAGS_INT_ON_DISCONNECT BIT(2)
static uint32_t flags[CONFIG_USB_PD_PORT_MAX_COUNT];

#define AOZ1380_SET_FLAG(port, flag) atomic_or(&flags[port], (flag))
#define AOZ1380_CLR_FLAG(port, flag) atomic_clear_bits(&flags[port], (flag))

static int aoz1380_init(int port)
{
	flags[port] = 0;

	if (tcpm_get_snk_ctrl(port))
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SINK_ENABLED);

	if (tcpm_get_src_ctrl(port))
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SOURCE_ENABLED);

	return EC_SUCCESS;
}

static int aoz1380_vbus_sink_enable(int port, int enable)
{
	int rv;

	rv = tcpm_set_snk_ctrl(port, enable);
	if (rv)
		return rv;

	/*
	 * On enable, we want to indicate connection as a SINK.
	 * On disable, clear SINK and that we have interrupted.
	 */
	if (enable)
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SINK_ENABLED);
	else
		AOZ1380_CLR_FLAG(port, (AOZ1380_FLAGS_SINK_ENABLED |
					AOZ1380_FLAGS_INT_ON_DISCONNECT));

	return EC_SUCCESS;
}

static int aoz1380_vbus_source_enable(int port, int enable)
{
	int rv;

	rv = tcpm_set_src_ctrl(port, enable);
	if (rv)
		return rv;

	/*
	 * On enable, we want to indicate connection as a SOURCE.
	 * On disable, clear SOURCE and that we have interrupted.
	 */
	if (enable)
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_SOURCE_ENABLED);
	else
		AOZ1380_CLR_FLAG(port, (AOZ1380_FLAGS_SOURCE_ENABLED |
					AOZ1380_FLAGS_INT_ON_DISCONNECT));

	return EC_SUCCESS;
}

static int aoz1380_is_sourcing_vbus(int port)
{
	return flags[port] & AOZ1380_FLAGS_SOURCE_ENABLED;
}

static int aoz1380_set_vbus_source_current_limit(int port,
						 enum tcpc_rp_value rp)
{
	return board_aoz1380_set_vbus_source_current_limit(port, rp);
}

/*
 * AOZ1380 Interrupt Handler
 *
 * This device only has a single over current/temperature interrupt.
 * TODO(b/141939343) Determine how to clear the interrupt
 * TODO(b/142076004) Test this to verify we shut off vbus current
 * TODO(b/147359722) Verify correct fault functionality
 */
static void aoz1380_handle_interrupt(int port)
{
	/*
	 * We can get a false positive on disconnect that we
	 * had an over current/temperature event when we are no
	 * longer connected as sink or source.  Ignore it if
	 * that is the case.
	 */
	if (flags[port] != 0) {
		/*
		 * This is a over current/temperature condition
		 */
		ppc_prints("Vbus overcurrent/temperature", port);
		pd_handle_overcurrent(port);
	} else {
		/*
		 * Just in case there is a condition that we will
		 * continue an interrupt storm, track that we have
		 * already been here once and will take the other
		 * path if we do this again before setting the
		 * sink/source as enabled or disabled again.
		 */
		AOZ1380_SET_FLAG(port, AOZ1380_FLAGS_INT_ON_DISCONNECT);
	}
}

static void aoz1380_irq_deferred(void)
{
	int i;
	uint32_t pending = atomic_clear(&irq_pending);

	for (i = 0; i < board_get_usb_pd_port_count(); i++)
		if (BIT(i) & pending)
			aoz1380_handle_interrupt(i);
}
DECLARE_DEFERRED(aoz1380_irq_deferred);

void aoz1380_interrupt(int port)
{
	atomic_or(&irq_pending, BIT(port));
	hook_call_deferred(&aoz1380_irq_deferred_data, 0);
}

const struct ppc_drv aoz1380_drv = {
	.init = &aoz1380_init,
	.is_sourcing_vbus = &aoz1380_is_sourcing_vbus,
	.vbus_sink_enable = &aoz1380_vbus_sink_enable,
	.vbus_source_enable = &aoz1380_vbus_source_enable,
	.set_vbus_source_current_limit =
		&aoz1380_set_vbus_source_current_limit,
};