summaryrefslogtreecommitdiff
path: root/driver/retimer/pi3dpx1207.c
blob: e40f269e3b5d9062eac331848664bbc1633db1d4 (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
/* Copyright 2019 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * PI3DPX1207 retimer.
 */

#include "common.h"
#include "console.h"
#include "gpio.h"
#include "i2c.h"
#include "ioexpander.h"
#include "pi3dpx1207.h"
#include "usb_mux.h"

#define I2C_MAX_RETRIES 2

/* Stack space is limited, so put the buffer somewhere else */
static uint8_t buf[PI3DPX1207_NUM_REGISTERS];

/**
 * Local utility functions
 */
static int pi3dpx1207_i2c_write(const struct usb_mux *me, uint8_t offset,
				uint8_t val)
{
	int rv = EC_SUCCESS;
	int attempt;

	if (offset >= PI3DPX1207_NUM_REGISTERS)
		return EC_ERROR_INVAL;

	/*
	 * PI3DPX1207 does not support device register offset in
	 * the typical I2C sense. Have to read the values starting
	 * from 0, modify the byte and then write the block.
	 *
	 * NOTE: The device may not respond correctly if it was
	 * just powered or has gone to sleep.  Allow for retries
	 * in case this happens.
	 */
	if (offset > 0) {
		attempt = 0;
		do {
			attempt++;
			rv = i2c_xfer(me->i2c_port, me->i2c_addr_flags, NULL, 0,
				      buf, offset);
		} while ((rv != EC_SUCCESS) && (attempt < I2C_MAX_RETRIES));
	}

	if (rv == EC_SUCCESS) {
		buf[offset] = val;

		attempt = 0;
		do {
			attempt++;
			rv = i2c_xfer(me->i2c_port, me->i2c_addr_flags, buf,
				      offset + 1, NULL, 0);
		} while ((rv != EC_SUCCESS) && (attempt < I2C_MAX_RETRIES));
	}
	return rv;
}

static void pi3dpx1207_shutoff_power(const struct usb_mux *me)
{
	const int port = me->usb_port;
	const int gpio_enable = pi3dpx1207_controls[port].enable_gpio;
	const int gpio_dp_enable = pi3dpx1207_controls[port].dp_enable_gpio;

	gpio_or_ioex_set_level(gpio_enable, 0);
	gpio_or_ioex_set_level(gpio_dp_enable, 0);
}

/**
 * Driver interface code
 */
static int pi3dpx1207_init(const struct usb_mux *me)
{
	const int port = me->usb_port;
	const int gpio_enable = pi3dpx1207_controls[port].enable_gpio;

	gpio_or_ioex_set_level(gpio_enable, 1);
	return EC_SUCCESS;
}

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

static int pi3dpx1207_set_mux(const struct usb_mux *me, mux_state_t mux_state,
			      bool *ack_required)
{
	int rv = EC_SUCCESS;
	uint8_t mode_val = PI3DPX1207_MODE_WATCHDOG_EN;
	const int port = me->usb_port;
	const int gpio_enable = pi3dpx1207_controls[port].enable_gpio;
	const int gpio_dp_enable = pi3dpx1207_controls[port].dp_enable_gpio;

	/* 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;

	/* USB */
	if (mux_state & USB_PD_MUX_USB_ENABLED) {
		gpio_or_ioex_set_level(gpio_enable, 1);
		/* USB with DP */
		if (mux_state & USB_PD_MUX_DP_ENABLED) {
			gpio_or_ioex_set_level(gpio_dp_enable, 1);
			mode_val |= (mux_state & USB_PD_MUX_POLARITY_INVERTED) ?
					    PI3DPX1207_MODE_CONF_USB_DP_FLIP :
					    PI3DPX1207_MODE_CONF_USB_DP;
		}
		/* USB without DP */
		else {
			gpio_or_ioex_set_level(gpio_dp_enable, 0);
			mode_val |= (mux_state & USB_PD_MUX_POLARITY_INVERTED) ?
					    PI3DPX1207_MODE_CONF_USB_FLIP :
					    PI3DPX1207_MODE_CONF_USB;
		}
	}
	/* DP without USB */
	else if (mux_state & USB_PD_MUX_DP_ENABLED) {
		gpio_or_ioex_set_level(gpio_enable, 1);
		gpio_or_ioex_set_level(gpio_dp_enable, 1);
		mode_val |= (mux_state & USB_PD_MUX_POLARITY_INVERTED) ?
				    PI3DPX1207_MODE_CONF_DP_FLIP :
				    PI3DPX1207_MODE_CONF_DP;
	}
	/* Nothing enabled, power down the retimer */
	else {
		pi3dpx1207_shutoff_power(me);
		return EC_SUCCESS;
	}

	/* Write the retimer config byte */
	rv = pi3dpx1207_i2c_write(me, PI3DPX1207_MODE_OFFSET, mode_val);
	return rv;
}

const struct usb_mux_driver pi3dpx1207_usb_retimer = {
	.init = pi3dpx1207_init,
	.set = pi3dpx1207_set_mux,
	.enter_low_power_mode = pi3dpx1207_enter_low_power_mode,
};