blob: a5a6ede931501005db99ef98815321602de8b70c [file] [log] [blame]
Matt Spinlerb54357f2017-08-21 14:38:54 -05001/**
2 * Copyright © 2017 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include <map>
17#include <memory>
18#include <phosphor-logging/elog.hpp>
19#include <phosphor-logging/log.hpp>
20#include <elog-errors.hpp>
21#include <xyz/openbmc_project/Sensor/Device/error.hpp>
22#include <xyz/openbmc_project/Control/Device/error.hpp>
23#include <xyz/openbmc_project/Power/Fault/error.hpp>
Matt Spinlere7e432b2017-08-21 15:01:40 -050024#include "names_values.hpp"
Matt Spinlerb54357f2017-08-21 14:38:54 -050025#include "ucd90160.hpp"
26
27namespace witherspoon
28{
29namespace power
30{
31
32using namespace std::string_literals;
33
Matt Spinler1e365692017-08-21 14:43:55 -050034const auto CLEAR_LOGGED_FAULTS = "clear_logged_faults"s;
Matt Spinlere7e432b2017-08-21 15:01:40 -050035const auto MFR_STATUS = "mfr_status"s;
Matt Spinler1e365692017-08-21 14:43:55 -050036
Matt Spinlerb54357f2017-08-21 14:38:54 -050037const auto DEVICE_NAME = "UCD90160"s;
38const auto DRIVER_NAME = "ucd9000"s;
Matt Spinlere7e432b2017-08-21 15:01:40 -050039constexpr auto NUM_PAGES = 16;
Matt Spinlerb54357f2017-08-21 14:38:54 -050040
Matt Spinler110b2842017-08-21 15:23:27 -050041namespace fs = std::experimental::filesystem;
Matt Spinlerd998b732017-08-21 15:35:54 -050042using namespace gpio;
Matt Spinlerb54357f2017-08-21 14:38:54 -050043using namespace pmbus;
44using namespace phosphor::logging;
45using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
46using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
47using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error;
48
49UCD90160::UCD90160(size_t instance) :
50 Device(DEVICE_NAME, instance),
51 interface(std::get<ucd90160::pathField>(
52 deviceMap.find(instance)->second),
53 DRIVER_NAME,
54 instance)
55{
Matt Spinler110b2842017-08-21 15:23:27 -050056 findGPIODevice();
Matt Spinlerb54357f2017-08-21 14:38:54 -050057}
58
59void UCD90160::onFailure()
60{
61 try
62 {
63 auto voutError = checkVOUTFaults();
64
65 auto pgoodError = checkPGOODFaults(false);
66
67 //Not a voltage or PGOOD fault, but we know something
68 //failed so still create an error log.
69 if (!voutError && !pgoodError)
70 {
71 createPowerFaultLog();
72 }
73 }
74 catch (ReadFailure& e)
75 {
76 if (!accessError)
77 {
78 commit<ReadFailure>();
79 accessError = true;
80 }
81 }
82}
83
84void UCD90160::analyze()
85{
86 try
87 {
88 //Note: Voltage faults are always fatal, so they just
89 //need to be analyzed in onFailure().
90
91 checkPGOODFaults(true);
92 }
93 catch (ReadFailure& e)
94 {
95 if (!accessError)
96 {
97 commit<ReadFailure>();
98 accessError = true;
99 }
100 }
101}
102
Matt Spinlere7e432b2017-08-21 15:01:40 -0500103uint16_t UCD90160::readStatusWord()
104{
105 return interface.read(STATUS_WORD, Type::Debug);
106}
107
108uint32_t UCD90160::readMFRStatus()
109{
110 return interface.read(MFR_STATUS, Type::DeviceDebug);
111}
112
Matt Spinlerb54357f2017-08-21 14:38:54 -0500113void UCD90160::clearFaults()
114{
Matt Spinler1e365692017-08-21 14:43:55 -0500115 try
116 {
117 interface.write(CLEAR_LOGGED_FAULTS, 1, Type::Base);
118 }
119 catch (WriteFailure& e)
120 {
121 if (!accessError)
122 {
123 log<level::ERR>("UCD90160 clear logged faults command failed");
124 commit<WriteFailure>();
125 accessError = true;
126 }
127 }
Matt Spinlerb54357f2017-08-21 14:38:54 -0500128}
129
130bool UCD90160::checkVOUTFaults()
131{
Matt Spinlere7e432b2017-08-21 15:01:40 -0500132 bool errorCreated = false;
133 auto statusWord = readStatusWord();
134
135 //The status_word register has a summary bit to tell us
136 //if each page even needs to be checked
137 if (!(statusWord & status_word::VOUT_FAULT))
138 {
139 return errorCreated;
140 }
141
142 for (size_t page = 0; page < NUM_PAGES; page++)
143 {
144 if (isVoutFaultLogged(page))
145 {
146 continue;
147 }
148
149 auto statusVout = interface.insertPageNum(STATUS_VOUT, page);
150 uint8_t vout = interface.read(statusVout, Type::Debug);
151
152 //Any bit on is an error
153 if (vout)
154 {
155 auto& railNames = std::get<ucd90160::railNamesField>(
156 deviceMap.find(getInstance())->second);
157 auto railName = railNames.at(page);
158
159 util::NamesValues nv;
160 nv.add("STATUS_WORD", statusWord);
161 nv.add("STATUS_VOUT", vout);
162 nv.add("MFR_STATUS", readMFRStatus());
163
164 using metadata = xyz::openbmc_project::Power::Fault::
165 PowerSequencerVoltageFault;
166
167 report<PowerSequencerVoltageFault>(
168 metadata::RAIL(page),
169 metadata::RAIL_NAME(railName.c_str()),
170 metadata::RAW_STATUS(nv.get().c_str()));
171
172 setVoutFaultLogged(page);
173 errorCreated = true;
174 }
175 }
176
177 return errorCreated;
Matt Spinlerb54357f2017-08-21 14:38:54 -0500178}
179
180bool UCD90160::checkPGOODFaults(bool polling)
181{
Matt Spinlerd998b732017-08-21 15:35:54 -0500182 bool errorCreated = false;
183
184 //While PGOOD faults could show up in MFR_STATUS (and we could then
185 //check the summary bit in STATUS_WORD first), they are edge triggered,
186 //and as the device driver sends a clear faults command every time we
187 //do a read, we will never see them. So, we'll have to just read the
188 //real time GPI status GPIO.
189
190 //Check only the GPIs configured on this system.
191 auto& gpiConfigs = std::get<ucd90160::gpiConfigField>(
192 deviceMap.find(getInstance())->second);
193
194 for (const auto& gpiConfig : gpiConfigs)
195 {
196 auto gpiNum = std::get<ucd90160::gpiNumField>(gpiConfig);
197 auto doPoll = std::get<ucd90160::pollField>(gpiConfig);
198
199 //Can skip this one if there is already an error on this input,
200 //or we are polling and these inputs don't need to be polled
201 //(because errors on them are fatal).
202 if (isPGOODFaultLogged(gpiNum) || (polling && !doPoll))
203 {
204 continue;
205 }
206
207 //The real time status is read via the pin ID
208 auto pinID = std::get<ucd90160::pinIDField>(gpiConfig);
209 auto gpio = gpios.find(pinID);
210 Value gpiStatus;
211
212 try
213 {
214 //The first time through, create the GPIO objects
215 if (gpio == gpios.end())
216 {
217 gpios.emplace(
218 pinID,
219 std::make_unique<GPIO>(
220 gpioDevice, pinID, Direction::input));
221 gpio = gpios.find(pinID);
222 }
223
224 gpiStatus = gpio->second->read();
225 }
226 catch (std::exception& e)
227 {
228 if (!accessError)
229 {
230 log<level::ERR>(e.what());
231 accessError = true;
232 }
233 continue;
234 }
235
236 if (gpiStatus == Value::low)
237 {
238 auto& gpiName = std::get<ucd90160::gpiNameField>(gpiConfig);
239 auto status = (gpiStatus == Value::low) ? 0 : 1;
240
241 util::NamesValues nv;
242 nv.add("STATUS_WORD", readStatusWord());
243 nv.add("MFR_STATUS", readMFRStatus());
244 nv.add("INPUT_STATUS", status);
245
246 using metadata = xyz::openbmc_project::Power::Fault::
247 PowerSequencerPGOODFault;
248
249 report<PowerSequencerPGOODFault>(
250 metadata::INPUT_NUM(gpiNum),
251 metadata::INPUT_NAME(gpiName.c_str()),
252 metadata::RAW_STATUS(nv.get().c_str()));
253
254 setPGOODFaultLogged(gpiNum);
255 errorCreated = true;
256 }
257 }
258
259 return errorCreated;
Matt Spinlerb54357f2017-08-21 14:38:54 -0500260}
261
262void UCD90160::createPowerFaultLog()
263{
Matt Spinler9efb3082017-08-21 15:43:43 -0500264 util::NamesValues nv;
265 nv.add("STATUS_WORD", readStatusWord());
266 nv.add("MFR_STATUS", readMFRStatus());
Matt Spinlerb54357f2017-08-21 14:38:54 -0500267
Matt Spinler9efb3082017-08-21 15:43:43 -0500268 using metadata = xyz::openbmc_project::Power::Fault::
269 PowerSequencerFault;
270
271 report<PowerSequencerFault>(
272 metadata::RAW_STATUS(nv.get().c_str()));
Matt Spinlerb54357f2017-08-21 14:38:54 -0500273}
274
Matt Spinler110b2842017-08-21 15:23:27 -0500275void UCD90160::findGPIODevice()
276{
277 auto& path = interface.path();
278
279 //In the driver directory, look for a subdirectory
280 //named gpiochipX, where X is some number. Then
281 //we'll access the GPIO at /dev/gpiochipX.
282 if (fs::is_directory(path))
283 {
284 for (auto& f : fs::directory_iterator(path))
285 {
286 if (f.path().filename().string().find("gpiochip") !=
287 std::string::npos)
288 {
289 gpioDevice = "/dev" / f.path().filename();
290 break;
291 }
292 }
293 }
294
295 if (gpioDevice.empty())
296 {
297 log<level::ERR>("Could not find UCD90160 GPIO device path",
298 entry("BASE_PATH=%s", path.c_str()));
299 }
300}
301
Matt Spinlerb54357f2017-08-21 14:38:54 -0500302}
303}