summaryrefslogtreecommitdiff
path: root/driver/usb_mux/amd_fp6.c
blob: 462d7c9297f6aeae83cddf6a8ef0f43b70eeb8a5 (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
/* Copyright 2021 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.
 *
 * AMD FP6 USB/DP Mux.
 */

#include "amd_fp6.h"
#include "chipset.h"
#include "common.h"
#include "hooks.h"
#include "i2c.h"
#include "queue.h"
#include "timer.h"
#include "usb_mux.h"

/*
 * This may be shorter than the internal MUX timeout.
 * Making it any longer could cause the PD task to miss messages.
 */
#define WRITE_CMD_TIMEOUT_MS 100

/*
 * Local data structure for saving mux state so it can be restored after
 * an AP reset.
 */
static struct {
	const struct usb_mux *mux;
	mux_state_t state;
} saved_mux_state[USBC_PORT_COUNT];

static int amd_fp6_mux_port0_read(const struct usb_mux *me, uint8_t *val)
{
	uint8_t payload[3] = { 0 };
	int rv;
	bool mux_ready;

	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return EC_ERROR_NOT_POWERED;

	rv = i2c_xfer(me->i2c_port, me->i2c_addr_flags, NULL, 0, payload, 3);
	if (rv)
		return rv;

	/*
	 * payload[0]: Status/ID
	 * payload[1]: Port 0 Control/Status
	 * payload[2]: Port 1 Control/Status (unused on FP6)
	 */
	mux_ready = !!((payload[0] >> AMD_FP6_MUX_PD_STATUS_OFFSET)
						& AMD_FP6_MUX_PD_STATUS_READY);
	if (!mux_ready)
		return EC_ERROR_BUSY;
	*val = payload[1];

	return EC_SUCCESS;
}

static int amd_fp6_mux_port0_write(const struct usb_mux *me, uint8_t write_val)
{
	int rv;
	uint8_t read_val;
	uint8_t port_status;
	timestamp_t start;

	/* Check if mux is ready */
	rv = amd_fp6_mux_port0_read(me, &read_val);
	if (rv)
		return rv;

	/* Write control register */
	rv = i2c_write8(me->i2c_port, me->i2c_addr_flags, 0, write_val);
	if (rv)
		return rv;

	/*
	 * Read status until write command finishes or times out.
	 * The mux has an internal opaque timeout, which we wrap with our own
	 * timeout to be safe.
	 */
	start = get_time();
	while (time_since32(start) < WRITE_CMD_TIMEOUT_MS * MSEC) {
		rv = amd_fp6_mux_port0_read(me, &read_val);
		if (rv)
			return rv;

		port_status = read_val >> AMD_FP6_MUX_PORT_STATUS_OFFSET;

		if (port_status == AMD_FP6_MUX_PORT_CMD_COMPLETE)
			return EC_SUCCESS;
		else if (port_status == AMD_FP6_MUX_PORT_CMD_TIMEOUT)
			return EC_ERROR_TIMEOUT;
		else if (port_status == AMD_FP6_MUX_PORT_CMD_BUSY)
			msleep(WRITE_CMD_TIMEOUT_MS / 5);
		else
			return EC_ERROR_UNKNOWN;
	}

	return EC_ERROR_TIMEOUT;
}

static int amd_fp6_init(const struct usb_mux *me)
{
	return EC_SUCCESS;
}

static int amd_fp6_set_mux(const struct usb_mux *me, mux_state_t mux_state)
{
	uint8_t val;
	int rv;

	saved_mux_state[me->usb_port].mux = me;
	saved_mux_state[me->usb_port].state = mux_state;

	if (mux_state == USB_PD_MUX_NONE)
		/*
		 * LOW_POWER must be set when connection mode is
		 * set to 00b (safe state)
		 */
		val = AMD_FP6_MUX_MODE_SAFE | AMD_FP6_MUX_LOW_POWER;
	else if ((mux_state & USB_PD_MUX_USB_ENABLED) &&
		 (mux_state & USB_PD_MUX_DP_ENABLED))
		val = AMD_FP6_MUX_MODE_DOCK;
	else if (mux_state & USB_PD_MUX_USB_ENABLED)
		val = AMD_FP6_MUX_MODE_USB;
	else if (mux_state & USB_PD_MUX_DP_ENABLED)
		val = AMD_FP6_MUX_MODE_DP;
	else {
		ccprintf("Unhandled mux_state %x\n", mux_state);
		return EC_ERROR_INVAL;
	}

	if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
		val |= AMD_FP6_MUX_ORIENTATION;

	rv = amd_fp6_mux_port0_write(me, val);
	/*
	 * This MUX is on the FP6 SoC.  If that device is not powered then
	 * we either have to complain that it is not powered or if we were
	 * setting the state to OFF, then go ahead and report that we did
	 * it because a powered down MUX is off.
	 */
	if (rv == EC_ERROR_NOT_POWERED && mux_state == USB_PD_MUX_NONE)
		rv = EC_SUCCESS;
	return rv;
}

static int amd_fp6_get_mux(const struct usb_mux *me, mux_state_t *mux_state)
{
	uint8_t val;
	bool inverted;
	uint8_t mode;
	int rv;

	rv = amd_fp6_mux_port0_read(me, &val);
	/*
	 * This MUX is on the FP6 SoC. If that device is not powered then claim
	 * thestate to be NONE, which is SAFE.
	 */
	if (rv == EC_ERROR_NOT_POWERED)
		val = 0;
	else if (rv)
		return rv;

	mode = (val & AMD_FP6_MUX_MODE_MASK);
	inverted = !!(val & AMD_FP6_MUX_ORIENTATION);

	if (mode == AMD_FP6_MUX_MODE_USB)
		*mux_state = USB_PD_MUX_USB_ENABLED;
	else if (mode == AMD_FP6_MUX_MODE_DP)
		*mux_state = USB_PD_MUX_DP_ENABLED;
	else if (mode == AMD_FP6_MUX_MODE_DOCK)
		*mux_state = USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED;
	else /* AMD_FP6_MUX_MODE_SAFE */
		*mux_state = USB_PD_MUX_NONE;

	if (inverted)
		*mux_state |= USB_PD_MUX_POLARITY_INVERTED;

	return EC_SUCCESS;
}

static void amd_fp6_chipset_reset_delay(void)
{
	int rv;
	int i;

	for (i = 0; i < ARRAY_SIZE(saved_mux_state); i++) {
		/* Check if saved_mux_state has been initialized */
		if (saved_mux_state[i].mux == NULL)
			continue;
		rv = amd_fp6_set_mux(saved_mux_state[i].mux,
				     saved_mux_state[i].state);
		if (rv)
			ccprints("C%d restore mux rv:%d", i, rv);
	}

}
DECLARE_DEFERRED(amd_fp6_chipset_reset_delay);

/*
 * The AP's internal USB-C mux is reset when AP resets, so wait for
 * it to be ready and then restore the previous setting.
 */
static int amd_fp6_chipset_reset(const struct usb_mux *mux)
{
	/* TODO: Tune 200ms delay for FP6 */
	hook_call_deferred(&amd_fp6_chipset_reset_delay_data, 200 * MSEC);
	return EC_SUCCESS;
}

const struct usb_mux_driver amd_fp6_usb_mux_driver = {
	.init = &amd_fp6_init,
	.set = &amd_fp6_set_mux,
	.get = &amd_fp6_get_mux,
	.chipset_reset = &amd_fp6_chipset_reset,
};