1 /**
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>
24 #include "names_values.hpp"
25 #include "ucd90160.hpp"
26 
27 namespace witherspoon
28 {
29 namespace power
30 {
31 
32 using namespace std::string_literals;
33 
34 const auto CLEAR_LOGGED_FAULTS = "clear_logged_faults"s;
35 const auto MFR_STATUS = "mfr_status"s;
36 
37 const auto DEVICE_NAME = "UCD90160"s;
38 const auto DRIVER_NAME = "ucd9000"s;
39 constexpr auto NUM_PAGES = 16;
40 
41 namespace fs = std::experimental::filesystem;
42 using namespace gpio;
43 using namespace pmbus;
44 using namespace phosphor::logging;
45 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
46 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
47 using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error;
48 
49 UCD90160::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 {
56     findGPIODevice();
57 }
58 
59 void 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 
84 void 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 
103 uint16_t UCD90160::readStatusWord()
104 {
105     return interface.read(STATUS_WORD, Type::Debug);
106 }
107 
108 uint32_t UCD90160::readMFRStatus()
109 {
110     return interface.read(MFR_STATUS, Type::DeviceDebug);
111 }
112 
113 void UCD90160::clearFaults()
114 {
115     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     }
128 }
129 
130 bool UCD90160::checkVOUTFaults()
131 {
132     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;
178 }
179 
180 bool UCD90160::checkPGOODFaults(bool polling)
181 {
182     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;
260 }
261 
262 void UCD90160::createPowerFaultLog()
263 {
264 
265 }
266 
267 void UCD90160::findGPIODevice()
268 {
269     auto& path = interface.path();
270 
271     //In the driver directory, look for a subdirectory
272     //named gpiochipX, where X is some number.  Then
273     //we'll access the GPIO at /dev/gpiochipX.
274     if (fs::is_directory(path))
275     {
276         for (auto& f : fs::directory_iterator(path))
277         {
278             if (f.path().filename().string().find("gpiochip") !=
279                     std::string::npos)
280             {
281                 gpioDevice = "/dev" / f.path().filename();
282                 break;
283             }
284         }
285     }
286 
287     if (gpioDevice.empty())
288     {
289         log<level::ERR>("Could not find UCD90160 GPIO device path",
290                 entry("BASE_PATH=%s", path.c_str()));
291     }
292 }
293 
294 }
295 }
296