summaryrefslogtreecommitdiff
path: root/board/endeavour/pse.c
blob: c4484d257c451ceccc49b8a91f8e34d328b810b7 (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
/* 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 "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_STATPIN	0x11
#define LTC4291_REG_OPMD	0x12
#define LTC4291_REG_DISENA	0x13
#define LTC4291_REG_DETENA	0x14
#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_FLD_STATPIN_AUTO	BIT(0)
#define LTC4291_FLD_RSTPB_RSTALL	BIT(4)

#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_RESET_DELAY_MS	10

#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_init_worker(void)
{
	int err, id, devid, statpin, port;

	err = I2C_PSE_WRITE(RSTPB, LTC4291_FLD_RSTPB_RSTALL);
	if (err != 0)
		return err;

	msleep(LTC4291_RESET_DELAY_MS);

	err = I2C_PSE_READ(ID, &id);
	if (err != 0)
		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 < 4; 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_INIT, pse_init, HOOK_PRIO_INIT_I2C);

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 < 1 || port > 4)
		return EC_ERROR_PARAM1;

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