summaryrefslogtreecommitdiff
path: root/board/genesis/pse.c
blob: bda7e1994d4946ccb8d107d8dd51c3448c25a3d4 (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
/* 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.
 */

/*
 * 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, const 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));