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 MFR_STATUS = "mfr_status"s;
35 
36 const auto DEVICE_NAME = "UCD90160"s;
37 const auto DRIVER_NAME = "ucd9000"s;
38 constexpr auto NUM_PAGES = 16;
39 
40 namespace fs = std::experimental::filesystem;
41 using namespace gpio;
42 using namespace pmbus;
43 using namespace phosphor::logging;
44 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
45 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
46 using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error;
47 
48 UCD90160::UCD90160(size_t instance) :
49     Device(DEVICE_NAME, instance),
50     interface(std::get<ucd90160::pathField>(
51                       deviceMap.find(instance)->second),
52               DRIVER_NAME,
53               instance)
54 {
55     findGPIODevice();
56 }
57 
58 void UCD90160::onFailure()
59 {
60     try
61     {
62         auto voutError = checkVOUTFaults();
63 
64         auto pgoodError = checkPGOODFaults(false);
65 
66         //Not a voltage or PGOOD fault, but we know something
67         //failed so still create an error log.
68         if (!voutError && !pgoodError)
69         {
70             createPowerFaultLog();
71         }
72     }
73     catch (ReadFailure& e)
74     {
75         if (!accessError)
76         {
77             commit<ReadFailure>();
78             accessError = true;
79         }
80     }
81 }
82 
83 void UCD90160::analyze()
84 {
85     try
86     {
87         //Note: Voltage faults are always fatal, so they just
88         //need to be analyzed in onFailure().
89 
90         checkPGOODFaults(true);
91     }
92     catch (ReadFailure& e)
93     {
94         if (!accessError)
95         {
96             commit<ReadFailure>();
97             accessError = true;
98         }
99     }
100 }
101 
102 uint16_t UCD90160::readStatusWord()
103 {
104     return interface.read(STATUS_WORD, Type::Debug);
105 }
106 
107 uint32_t UCD90160::readMFRStatus()
108 {
109     return interface.read(MFR_STATUS, Type::DeviceDebug);
110 }
111 
112 bool UCD90160::checkVOUTFaults()
113 {
114     bool errorCreated = false;
115     auto statusWord = readStatusWord();
116 
117     //The status_word register has a summary bit to tell us
118     //if each page even needs to be checked
119     if (!(statusWord & status_word::VOUT_FAULT))
120     {
121         return errorCreated;
122     }
123 
124     for (size_t page = 0; page < NUM_PAGES; page++)
125     {
126         if (isVoutFaultLogged(page))
127         {
128             continue;
129         }
130 
131         auto statusVout = interface.insertPageNum(STATUS_VOUT, page);
132         uint8_t vout = interface.read(statusVout, Type::Debug);
133 
134         //Any bit on is an error
135         if (vout)
136         {
137             auto& railNames = std::get<ucd90160::railNamesField>(
138                     deviceMap.find(getInstance())->second);
139             auto railName = railNames.at(page);
140 
141             util::NamesValues nv;
142             nv.add("STATUS_WORD", statusWord);
143             nv.add("STATUS_VOUT", vout);
144             nv.add("MFR_STATUS", readMFRStatus());
145 
146             using metadata = xyz::openbmc_project::Power::Fault::
147                     PowerSequencerVoltageFault;
148 
149             report<PowerSequencerVoltageFault>(
150                     metadata::RAIL(page),
151                     metadata::RAIL_NAME(railName.c_str()),
152                     metadata::RAW_STATUS(nv.get().c_str()));
153 
154             setVoutFaultLogged(page);
155             errorCreated = true;
156         }
157     }
158 
159     return errorCreated;
160 }
161 
162 bool UCD90160::checkPGOODFaults(bool polling)
163 {
164     bool errorCreated = false;
165 
166     //While PGOOD faults could show up in MFR_STATUS (and we could then
167     //check the summary bit in STATUS_WORD first), they are edge triggered,
168     //and as the device driver sends a clear faults command every time we
169     //do a read, we will never see them.  So, we'll have to just read the
170     //real time GPI status GPIO.
171 
172     //Check only the GPIs configured on this system.
173     auto& gpiConfigs = std::get<ucd90160::gpiConfigField>(
174             deviceMap.find(getInstance())->second);
175 
176     for (const auto& gpiConfig : gpiConfigs)
177     {
178         auto gpiNum = std::get<ucd90160::gpiNumField>(gpiConfig);
179         auto doPoll = std::get<ucd90160::pollField>(gpiConfig);
180 
181         //Can skip this one if there is already an error on this input,
182         //or we are polling and these inputs don't need to be polled
183         //(because errors on them are fatal).
184         if (isPGOODFaultLogged(gpiNum) || (polling && !doPoll))
185         {
186             continue;
187         }
188 
189         //The real time status is read via the pin ID
190         auto pinID = std::get<ucd90160::pinIDField>(gpiConfig);
191         auto gpio = gpios.find(pinID);
192         Value gpiStatus;
193 
194         try
195         {
196             //The first time through, create the GPIO objects
197             if (gpio == gpios.end())
198             {
199                 gpios.emplace(
200                         pinID,
201                         std::make_unique<GPIO>(
202                                 gpioDevice, pinID, Direction::input));
203                 gpio = gpios.find(pinID);
204             }
205 
206             gpiStatus = gpio->second->read();
207         }
208         catch (std::exception& e)
209         {
210             if (!accessError)
211             {
212                 log<level::ERR>(e.what());
213                 accessError = true;
214             }
215             continue;
216         }
217 
218         if (gpiStatus == Value::low)
219         {
220             auto& gpiName = std::get<ucd90160::gpiNameField>(gpiConfig);
221             auto status = (gpiStatus == Value::low) ? 0 : 1;
222 
223             util::NamesValues nv;
224             nv.add("STATUS_WORD", readStatusWord());
225             nv.add("MFR_STATUS", readMFRStatus());
226             nv.add("INPUT_STATUS", status);
227 
228             using metadata =  xyz::openbmc_project::Power::Fault::
229                     PowerSequencerPGOODFault;
230 
231             report<PowerSequencerPGOODFault>(
232                     metadata::INPUT_NUM(gpiNum),
233                     metadata::INPUT_NAME(gpiName.c_str()),
234                     metadata::RAW_STATUS(nv.get().c_str()));
235 
236             setPGOODFaultLogged(gpiNum);
237             errorCreated = true;
238         }
239     }
240 
241     return errorCreated;
242 }
243 
244 void UCD90160::createPowerFaultLog()
245 {
246     util::NamesValues nv;
247     nv.add("STATUS_WORD", readStatusWord());
248     nv.add("MFR_STATUS", readMFRStatus());
249 
250     using metadata = xyz::openbmc_project::Power::Fault::
251         PowerSequencerFault;
252 
253     report<PowerSequencerFault>(
254             metadata::RAW_STATUS(nv.get().c_str()));
255 }
256 
257 void UCD90160::findGPIODevice()
258 {
259     auto& path = interface.path();
260 
261     //In the driver directory, look for a subdirectory
262     //named gpiochipX, where X is some number.  Then
263     //we'll access the GPIO at /dev/gpiochipX.
264     if (fs::is_directory(path))
265     {
266         for (auto& f : fs::directory_iterator(path))
267         {
268             if (f.path().filename().string().find("gpiochip") !=
269                     std::string::npos)
270             {
271                 gpioDevice = "/dev" / f.path().filename();
272                 break;
273             }
274         }
275     }
276 
277     if (gpioDevice.empty())
278     {
279         log<level::ERR>("Could not find UCD90160 GPIO device path",
280                 entry("BASE_PATH=%s", path.c_str()));
281     }
282 }
283 
284 }
285 }
286