summaryrefslogtreecommitdiff
path: root/board/endeavour/pse.c
blob: 671288ccf535bb4986f0689a60196fdb2ff54ad7 (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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/* Copyright 2020 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.
 */

/*
 * The LTC4291 is a power over ethernet (PoE) power sourcing equipment (PSE)
 * controller.
 */

#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "hooks.h"
#include "host_command.h"
#include "i2c.h"
#include "string.h"
#include "timer.h"
#include "util.h"

#define LTC4291_I2C_ADDR	0x2C

#define LTC4291_REG_SUPEVN_COR	0x0B
#define LTC4291_REG_STATPWR	0x10
#define LTC4291_REG_STATPIN	0x11
#define LTC4291_REG_OPMD	0x12
#define LTC4291_REG_DISENA	0x13
#define LTC4291_REG_DETENA	0x14
#define LTC4291_REG_DETPB	0x18
#define LTC4291_REG_PWRPB	0x19
#define LTC4291_REG_RSTPB	0x1A
#define LTC4291_REG_ID		0x1B
#define LTC4291_REG_DEVID	0x43
#define LTC4291_REG_HPMD1	0x46
#define LTC4291_REG_HPMD2	0x4B
#define LTC4291_REG_HPMD3	0x50
#define LTC4291_REG_HPMD4	0x55
#define LTC4291_REG_LPWRPB	0x6E

#define LTC4291_FLD_STATPIN_AUTO	BIT(0)
#define LTC4291_FLD_RSTPB_RSTALL	BIT(4)

#define LTC4291_STATPWR_ON_PORT(port)	(0x01 << (port))
#define LTC4291_DETENA_EN_PORT(port)	(0x11 << (port))
#define LTC4291_DETPB_EN_PORT(port)	(0x11 << (port))
#define LTC4291_PWRPB_OFF_PORT(port)	(0x10 << (port))

#define LTC4291_OPMD_AUTO	0xFF
#define LTC4291_DISENA_ALL	0x0F
#define LTC4291_DETENA_ALL	0xFF
#define LTC4291_ID		0x64
#define LTC4291_DEVID		0x38
#define LTC4291_HPMD_MIN	0x00
#define LTC4291_HPMD_MAX	0xA8

#define LTC4291_PORT_MAX	4

#define LTC4291_RESET_DELAY_US	(20 * MSEC)

#define I2C_PSE_READ(reg, data) \
	i2c_read8(I2C_PORT_PSE, LTC4291_I2C_ADDR, LTC4291_REG_##reg, (data))

#define I2C_PSE_WRITE(reg, data) \
	i2c_write8(I2C_PORT_PSE, LTC4291_I2C_ADDR, LTC4291_REG_##reg, (data))

#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)

static int pse_write_hpmd(int port, int val)
{
	switch (port) {
	case 0:
		return I2C_PSE_WRITE(HPMD1, val);
	case 1:
		return I2C_PSE_WRITE(HPMD2, val);
	case 2:
		return I2C_PSE_WRITE(HPMD3, val);
	case 3:
		return I2C_PSE_WRITE(HPMD4, val);
	default:
		return EC_ERROR_INVAL;
	}
}

/*
 * Port 1: 100W
 * Port 2-4: 15W
 */
static int pse_port_hpmd[4] = {
	LTC4291_HPMD_MAX,
	LTC4291_HPMD_MIN,
	LTC4291_HPMD_MIN,
	LTC4291_HPMD_MIN,
};

static int pse_port_enable(int port)
{
	/* Enable detection and classification */
	return I2C_PSE_WRITE(DETPB, LTC4291_DETPB_EN_PORT(port));
}

static int pse_port_disable(int port)
{
	/* Request power off (this also disables detection/classification) */
	return I2C_PSE_WRITE(PWRPB, LTC4291_PWRPB_OFF_PORT(port));
}

static int pse_init_worker(void)
{
	timestamp_t deadline;
	int err, id, devid, statpin, port;

	/* Ignore errors -- may already be resetting */
	I2C_PSE_WRITE(RSTPB, LTC4291_FLD_RSTPB_RSTALL);

	deadline.val = get_time().val + LTC4291_RESET_DELAY_US;
	while ((err = I2C_PSE_READ(ID, &id)) != 0) {
		if (timestamp_expired(deadline, NULL))
			return err;
	}

	err = I2C_PSE_READ(DEVID, &devid);
	if (err != 0)
		return err;

	if (id != LTC4291_ID || devid != LTC4291_DEVID)
		return EC_ERROR_INVAL;

	err = I2C_PSE_READ(STATPIN, &statpin);
	if (err != 0)
		return err;

	/*
	 * We don't want to supply power until we've had a chance to set the
	 * limits.
	 */
	if (statpin & LTC4291_FLD_STATPIN_AUTO)
		CPRINTS("WARN: PSE reset in AUTO mode");

	err = I2C_PSE_WRITE(OPMD, LTC4291_OPMD_AUTO);
	if (err != 0)
		return err;

	/* Set maximum power each port is allowed to allocate. */
	for (port = 0; port < LTC4291_PORT_MAX; port++) {
		err = pse_write_hpmd(port, pse_port_hpmd[port]);
		if (err != 0)
			return err;
	}

	err = I2C_PSE_WRITE(DISENA, LTC4291_DISENA_ALL);
	if (err != 0)
		return err;

	err = I2C_PSE_WRITE(DETENA, LTC4291_DETENA_ALL);
	if (err != 0)
		return err;

	return 0;
}

static void pse_init(void)
{
	int err;

	err = pse_init_worker();
	if (err != 0)
		CPRINTS("PSE init failed: %d", err);
	else
		CPRINTS("PSE init done");
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, pse_init, HOOK_PRIO_DEFAULT);

/* Also reset the PSE on a reboot to toggle the power. */
DECLARE_HOOK(HOOK_CHIPSET_RESET, pse_init, HOOK_PRIO_DEFAULT);

static int command_pse(int argc, char **argv)
{
	int port;

	/*
	 *  TODO(b/156399232): endeavour: PSE controller reset by PLTRST
	 *
	 *  Initialization does not reliably work after reset because the device
	 *  is held in reset by the AP. Running this command after boot finishes
	 *  always succeeds. Remove once the reset signal changes.
	 */
	if (!strncmp(argv[1], "init", 4))
		return pse_init_worker();

	if (argc != 3)
		return EC_ERROR_PARAM_COUNT;

	port = atoi(argv[1]);
	if (port < 0 || port >= LTC4291_PORT_MAX)
		return EC_ERROR_PARAM1;

	if (!strncmp(argv[2], "off", 3))
		return pse_port_disable(port);
	else if (!strncmp(argv[2], "on", 2))
		return pse_port_enable(port);
	else if (!strncmp(argv[2], "min", 3))
		return pse_write_hpmd(port, LTC4291_HPMD_MIN);
	else if (!strncmp(argv[2], "max", 3))
		return pse_write_hpmd(port, LTC4291_HPMD_MAX);
	else
		return EC_ERROR_PARAM2;
}
DECLARE_CONSOLE_COMMAND(pse, command_pse,
			"<port# 0-3> <off | on | min | max>",
			"Set PSE port power");

static int ec_command_pse_status(int port, uint8_t *status)
{
	int detena, statpwr;
	int err;

	err = I2C_PSE_READ(DETENA, &detena);
	if (err != 0)
		return err;

	err = I2C_PSE_READ(STATPWR, &statpwr);
	if (err != 0)
		return err;

	if ((detena & LTC4291_DETENA_EN_PORT(port)) == 0)
		*status = EC_PSE_STATUS_DISABLED;
	else if ((statpwr & LTC4291_STATPWR_ON_PORT(port)) == 0)
		*status = EC_PSE_STATUS_ENABLED;
	else
		*status = EC_PSE_STATUS_POWERED;

	return 0;
}

static enum ec_status ec_command_pse(struct host_cmd_handler_args *args)
{
	const struct ec_params_pse *p = args->params;
	int err = 0;

	if (p->port >= LTC4291_PORT_MAX)
		return EC_RES_INVALID_PARAM;

	switch (p->cmd) {
	case EC_PSE_STATUS: {
		struct ec_response_pse_status *r = args->response;

		args->response_size = sizeof(*r);
		err = ec_command_pse_status(p->port, &r->status);
		break;
	}
	case EC_PSE_ENABLE:
		err = pse_port_enable(p->port);
		break;
	case EC_PSE_DISABLE:
		err = pse_port_disable(p->port);
		break;
	default:
		return EC_RES_INVALID_PARAM;
	}

	if (err)
		return EC_RES_ERROR;

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_PSE, ec_command_pse, EC_VER_MASK(0));