summaryrefslogtreecommitdiff
path: root/driver/usb_mux/anx3443.c
blob: 2e57f4d30c18bb3d59a383f9258d6ba828b27314 (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 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.
 *
 * ANX3443: 10G Active Mux (6x4) with
 * Integrated Re-timers for USB3.2/DisplayPort
 */

#include "anx3443.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "i2c.h"
#include "time.h"
#include "usb_mux.h"
#include "util.h"

/*
 * Empirical testing found it takes ~12ms to wake mux.
 * Setting timeout to 20ms for some buffer.
 */
#define ANX3443_I2C_WAKE_TIMEOUT_MS 20
#define ANX3443_I2C_WAKE_RETRY_DELAY_US 500

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

static inline int anx3443_read(const struct usb_mux *me,
			       uint8_t reg, int *val)
{
	return i2c_read8(me->i2c_port, me->i2c_addr_flags, reg, val);
}

static inline int anx3443_write(const struct usb_mux *me,
				uint8_t reg, uint8_t val)
{
	return i2c_write8(me->i2c_port, me->i2c_addr_flags, reg, val);
}

static int anx3443_power_off(const struct usb_mux *me)
{
	/*
	 * The mux will not send an acknowledgment when powered off, so ignore
	 * response and always return success.
	 */
	anx3443_write(me, ANX3443_REG_POWER_CNTRL, ANX3443_POWER_CNTRL_OFF);
	return EC_SUCCESS;
}

static int anx3443_wake_up(const struct usb_mux *me)
{
	timestamp_t start;
	int rv;
	int val;

	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return EC_ERROR_NOT_POWERED;

	/* Keep reading top register until mux wakes up or timesout */
	start = get_time();
	do {
		rv = anx3443_read(me, 0x0, &val);
		if (!rv)
			break;
		usleep(ANX3443_I2C_WAKE_RETRY_DELAY_US);
	} while (time_since32(start) < ANX3443_I2C_WAKE_TIMEOUT_MS * MSEC);
	if (rv) {
		CPRINTS("ANX3443: Failed to wake mux rv:%d", rv);
		return EC_ERROR_TIMEOUT;
	}

	/* ULTRA_LOW_POWER must always be disabled (Fig 2-2) */
	RETURN_ERROR(anx3443_write(me, ANX3443_REG_ULTRA_LOW_POWER,
				   ANX3443_ULTRA_LOW_POWER_DIS));

	return EC_SUCCESS;
}

static int anx3443_set_mux(const struct usb_mux *me, mux_state_t mux_state,
			   bool *ack_required)
{
	int reg;

	/* This driver does not use host command ACKs */
	*ack_required = false;

	/* Mux is not powered in Z1 */
	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return (mux_state == USB_PD_MUX_NONE) ? EC_SUCCESS
						      : EC_ERROR_NOT_POWERED;

	/* To disable both DP and USB the mux must be powered off. */
	if (!(mux_state & (USB_PD_MUX_USB_ENABLED | USB_PD_MUX_DP_ENABLED)))
		return anx3443_power_off(me);

	RETURN_ERROR(anx3443_wake_up(me));

	/* ULP_CFG_MODE_EN overrides pin control. Always set it */
	reg = ANX3443_ULP_CFG_MODE_EN;
	if (mux_state & USB_PD_MUX_USB_ENABLED)
		reg |= ANX3443_ULP_CFG_MODE_USB_EN;
	if (mux_state & USB_PD_MUX_DP_ENABLED)
		reg |= ANX3443_ULP_CFG_MODE_DP_EN;
	if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
		reg |= ANX3443_ULP_CFG_MODE_FLIP;

	return anx3443_write(me, ANX3443_REG_ULP_CFG_MODE, reg);
}

static int anx3443_get_mux(const struct usb_mux *me, mux_state_t *mux_state)
{
	int reg;

	/* Mux is not powered in Z1 */
	if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
		return USB_PD_MUX_NONE;

	RETURN_ERROR(anx3443_wake_up(me));

	*mux_state = 0;
	RETURN_ERROR(anx3443_read(me, ANX3443_REG_ULP_CFG_MODE, &reg));

	if (reg & ANX3443_ULP_CFG_MODE_USB_EN)
		*mux_state |= USB_PD_MUX_USB_ENABLED;
	if (reg & ANX3443_ULP_CFG_MODE_DP_EN)
		*mux_state |= USB_PD_MUX_DP_ENABLED;
	if (reg & ANX3443_ULP_CFG_MODE_FLIP)
		*mux_state |= USB_PD_MUX_POLARITY_INVERTED;

	return EC_SUCCESS;
}

static int anx3443_init(const struct usb_mux *me)
{
	uint64_t now;
	bool unused;

	/*
	 * ANX3443 requires 30ms to power on. EC and ANX3443 are on the same
	 * power rail, so just wait 30ms since EC boot.
	 */
	now = get_time().val;
	if (now < ANX3443_I2C_READY_DELAY)
		usleep(ANX3443_I2C_READY_DELAY - now);

	RETURN_ERROR(anx3443_wake_up(me));

	/*
	 * Note that bypassing the usb_mux API is okay for internal driver calls
	 * since the task calling init already holds this port's mux lock.
	 */
	/* Default to USB mode */
	RETURN_ERROR(anx3443_set_mux(me, USB_PD_MUX_USB_ENABLED, &unused));

	return EC_SUCCESS;
}

const struct usb_mux_driver anx3443_usb_mux_driver = {
	.init = anx3443_init,
	.set = anx3443_set_mux,
	.get = anx3443_get_mux,
};