summaryrefslogtreecommitdiff
path: root/driver/retimer/nb7v904m.c
blob: 18fdf3b2ba6ef9ecfc854ffa2fdd5fde05d303f0 (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
/* Copyright 2020 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * ON Semiconductor NB7V904M USB Type-C DisplayPort Alt Mode Redriver
 */
#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "i2c.h"
#include "nb7v904m.h"
#include "usb_mux.h"

#include <stdbool.h>

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

#ifdef CONFIG_NB7V904M_LPM_OVERRIDE
int nb7v904m_lpm_disable = 0;
#endif

static int nb7v904m_write(const struct usb_mux *me, int offset, int data)
{
	return i2c_write8(me->i2c_port, me->i2c_addr_flags, offset, data);
}

static int nb7v904m_read(const struct usb_mux *me, int offset, int *regval)
{
	return i2c_read8(me->i2c_port, me->i2c_addr_flags, offset, regval);
}

static int set_low_power_mode(const struct usb_mux *me, bool enable)
{
	int regval;
	int rv;

	rv = nb7v904m_read(me, NB7V904M_REG_GEN_DEV_SETTINGS, &regval);
	if (rv)
		return rv;
#ifdef CONFIG_NB7V904M_LPM_OVERRIDE
	if (nb7v904m_lpm_disable)
		enable = 0;
#endif

	if (enable)
		regval &= ~NB7V904M_CHIP_EN;
	else
		regval |= NB7V904M_CHIP_EN;

	return nb7v904m_write(me, NB7V904M_REG_GEN_DEV_SETTINGS, regval);
}

static int nb7v904m_enter_low_power_mode(const struct usb_mux *me)
{
	int rv = set_low_power_mode(me, 1);

	if (rv)
		CPRINTS("C%d: NB7V904M: Failed to enter low power mode!",
			me->usb_port);
	return rv;
}

/* Tune USB Eq All: This must be called on board_init context */
int nb7v904m_tune_usb_set_eq(const struct usb_mux *me, uint8_t eq_a,
			     uint8_t eq_b, uint8_t eq_c, uint8_t eq_d)
{
	int rv = EC_SUCCESS;

	if (eq_a != NB7V904M_CH_ALL_SKIP_EQ)
		rv = nb7v904m_write(me, NB7V904M_REG_CH_A_EQ_SETTINGS, eq_a);

	if (eq_b != NB7V904M_CH_ALL_SKIP_EQ)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_B_EQ_SETTINGS, eq_b);

	if (eq_c != NB7V904M_CH_ALL_SKIP_EQ)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_C_EQ_SETTINGS, eq_c);

	if (eq_d != NB7V904M_CH_ALL_SKIP_EQ)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_D_EQ_SETTINGS, eq_d);

	return rv;
}

/* Tune USB Flat Gain: This must be called on board_init context */
int nb7v904m_tune_usb_flat_gain(const struct usb_mux *me, uint8_t gain_a,
				uint8_t gain_b, uint8_t gain_c, uint8_t gain_d)
{
	int rv = EC_SUCCESS;

	if (gain_a != NB7V904M_CH_ALL_SKIP_GAIN)
		rv = nb7v904m_write(me, NB7V904M_REG_CH_A_FLAT_GAIN, gain_a);

	if (gain_b != NB7V904M_CH_ALL_SKIP_GAIN)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_B_FLAT_GAIN, gain_b);

	if (gain_c != NB7V904M_CH_ALL_SKIP_GAIN)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_C_FLAT_GAIN, gain_c);

	if (gain_d != NB7V904M_CH_ALL_SKIP_GAIN)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_D_FLAT_GAIN, gain_d);

	return rv;
}

/* Set Loss Profile Matching : This must be called on board_init context */
int nb7v904m_set_loss_profile_match(const struct usb_mux *me, uint8_t loss_a,
				    uint8_t loss_b, uint8_t loss_c,
				    uint8_t loss_d)
{
	int rv = EC_SUCCESS;

	if (loss_a != NB7V904M_CH_ALL_SKIP_LOSS)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_A_LOSS_CTRL, loss_a);

	if (loss_b != NB7V904M_CH_ALL_SKIP_LOSS)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_B_LOSS_CTRL, loss_b);

	if (loss_c != NB7V904M_CH_ALL_SKIP_LOSS)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_C_LOSS_CTRL, loss_c);

	if (loss_d != NB7V904M_CH_ALL_SKIP_LOSS)
		rv |= nb7v904m_write(me, NB7V904M_REG_CH_D_LOSS_CTRL, loss_d);

	return rv;
}

/* Set AUX control switch */
int nb7v904m_set_aux_ch_switch(const struct usb_mux *me, uint8_t aux_ch)
{
	int rv = EC_SUCCESS;

	rv = nb7v904m_write(me, NB7V904M_REG_AUX_CH_CTRL, aux_ch);
	return rv;
}

static int nb7v904m_init(const struct usb_mux *me)
{
	int rv = set_low_power_mode(me, 0);

	if (rv)
		CPRINTS("C%d: NB7V904M: init failed!", me->usb_port);
	return rv;
}

static int nb7v904m_set_mux(const struct usb_mux *me, mux_state_t mux_state,
			    bool *ack_required)
{
	int rv = EC_SUCCESS;
	int regval;
	int flipped = !!(mux_state & USB_PD_MUX_POLARITY_INVERTED);

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

	/* This driver treats safe mode as none */
	if (mux_state & USB_PD_MUX_SAFE_MODE)
		mux_state = USB_PD_MUX_NONE;

	/* Turn off redriver if it's not needed at all. */
	if (mux_state == USB_PD_MUX_NONE)
		return nb7v904m_enter_low_power_mode(me);

	rv = nb7v904m_init(me);
	if (rv)
		return rv;

	/* Clear operation mode field */
	rv = nb7v904m_read(me, NB7V904M_REG_GEN_DEV_SETTINGS, &regval);
	if (rv) {
		CPRINTS("C%d %s: Failed to obtain dev settings!", me->usb_port,
			__func__);
		return rv;
	}
	regval &= ~NB7V904M_OP_MODE_MASK;

	if (mux_state & USB_PD_MUX_USB_ENABLED) {
		/* USB with DP */
		if (mux_state & USB_PD_MUX_DP_ENABLED) {
			if (flipped)
				regval |= NB7V904M_USB_DP_FLIPPED;
			else
				regval |= NB7V904M_USB_DP_NORMAL;
		} else {
			/* USB only */
			regval |= NB7V904M_USB_ONLY;
		}

	} else if (mux_state & USB_PD_MUX_DP_ENABLED) {
		/* 4 lanes DP */
		regval |= NB7V904M_DP_ONLY;
	}

	if (mux_state & USB_PD_MUX_DP_ENABLED) {
		/* Connect AUX */
		rv = nb7v904m_write(me, NB7V904M_REG_AUX_CH_CTRL,
				    flipped ? NB7V904M_AUX_CH_FLIPPED :
					      NB7V904M_AUX_CH_NORMAL);
		/* Enable all channels for DP */
		regval |= NB7V904M_CH_EN_MASK;
	} else {
		/* Disconnect AUX since it's not being used. */
		rv = nb7v904m_write(me, NB7V904M_REG_AUX_CH_CTRL,
				    NB7V904M_AUX_CH_HI_Z);

		/* Disable the unused channels to save power */
		regval &= ~NB7V904M_CH_EN_MASK;
		if (flipped) {
			/* Only enable channels A & B */
			regval |= NB7V904M_CH_A_EN | NB7V904M_CH_B_EN;
		} else {
			/* Only enable channels C & D */
			regval |= NB7V904M_CH_C_EN | NB7V904M_CH_D_EN;
		}
	}

	rv |= nb7v904m_write(me, NB7V904M_REG_GEN_DEV_SETTINGS, regval);
	if (rv)
		CPRINTS("C%d: %s failed!", me->usb_port, __func__);

	return rv;
}

const struct usb_mux_driver nb7v904m_usb_redriver_drv = {
	.enter_low_power_mode = &nb7v904m_enter_low_power_mode,
	.init = &nb7v904m_init,
	.set = &nb7v904m_set_mux,
};