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_US 20000
#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)
{
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 < 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_CHIPSET_RESUME, 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 < 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");
|