Implement power control for x86 based platforms

This power control module provides the capability to
power on/off the host via gpio.
And provides some interfaces for system to contorl the system
power like:
    setPowerState
    getPowerState

Change-Id: Icd6530c42f2bc7c4d84062be786d25710b53f434
Signed-off-by: Kuiying Wang <kuiying.wang@intel.com>
diff --git a/power-control/CMakeLists.txt b/power-control/CMakeLists.txt
new file mode 100644
index 0000000..282620d
--- /dev/null
+++ b/power-control/CMakeLists.txt
@@ -0,0 +1,48 @@
+cmake_minimum_required(VERSION 2.8.10 FATAL_ERROR)
+project(power-control CXX)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}
+    ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+include(GNUInstallDirs)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+set(DBUS_OBJECT_NAME "xyz/openbmc_project/Chassis/Control/Power")
+set(DBUS_INTF_NAME "xyz.openbmc_project.Chassis.Control.Power")
+
+add_definitions(-DDBUS_OBJECT_NAME="/${DBUS_OBJECT_NAME}0")
+add_definitions(-DDBUS_INTF_NAME="${DBUS_INTF_NAME}")
+set(SRC_FILES
+    src/power_control.cpp
+    src/main.cpp
+)
+
+# import sdbusplus
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(SDBUSPLUSPLUS sdbusplus REQUIRED)
+include_directories(${SDBUSPLUSPLUS_INCLUDE_DIRS})
+link_directories(${SDBUSPLUSPLUS_LIBRARY_DIRS})
+find_program(SDBUSPLUSPLUS sdbus++)
+
+# import phosphor-logging
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(LOGGING phosphor-logging REQUIRED)
+include_directories(${LOGGING_INCLUDE_DIRS})
+link_directories(${LOGGING_LIBRARY_DIRS})
+
+# phosphor-dbus-interfaces
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(DBUSINTERFACE phosphor-dbus-interfaces REQUIRED)
+include_directories(${DBUSINTERFACE_INCLUDE_DIRS})
+link_directories(${DBUSINTERFACE_LIBRARY_DIRS})
+
+add_executable(${PROJECT_NAME} ${SRC_FILES})
+target_link_libraries(${PROJECT_NAME} ${DBUSINTERFACE_LIBRARIES}
+                      chassisgpio )
+target_link_libraries(${PROJECT_NAME} "${SDBUSPLUSPLUS_LIBRARIES} -lstdc++fs -lphosphor_dbus")
+
+install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/power-control/inc/power_control.hpp b/power-control/inc/power_control.hpp
new file mode 100644
index 0000000..4983fb7
--- /dev/null
+++ b/power-control/inc/power_control.hpp
@@ -0,0 +1,169 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include <fcntl.h>
+#include <linux/aspeed-lpc-sio.h>
+#include <unistd.h>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Chassis/Common/error.hpp>
+#include <xyz/openbmc_project/Chassis/Control/Power/server.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include "gpio.hpp"
+
+static constexpr size_t POLLING_INTERVAL_MS = 500;
+
+const static constexpr char* LPC_SIO_DEVPATH = "/dev/lpc-sio";
+const static constexpr char* PGOOD_PIN = "PGOOD";
+const static constexpr char* POWER_UP_PIN = "POWER_UP_PIN";
+
+const static constexpr size_t PCH_DEVICE_BUS_ADDRESS = 3;
+const static constexpr size_t PCH_DEVICE_SLAVE_ADDRESS = 0x44;
+const static constexpr size_t PCH_CMD_REGISTER = 0;
+const static constexpr size_t PCH_POWER_DOWN_CMD = 0x02;
+
+const static constexpr size_t POWER_UP_PIN_PULSE_TIME_MS = 200;
+
+using pwr_control =
+    sdbusplus::xyz::openbmc_project::Chassis::Control::server::Power;
+
+struct PowerControl : sdbusplus::server::object_t<pwr_control>
+{
+    PowerControl(sdbusplus::bus::bus& bus, const char* path,
+                 phosphor::watchdog::EventPtr event,
+                 sd_event_io_handler_t handler = PowerControl::EventHandler) :
+        sdbusplus::server::object_t<pwr_control>(bus, path),
+        bus(bus), callbackHandler(handler)
+    {
+        int ret = -1;
+        char buf = '0';
+
+        // config gpio
+        ret = configGpio(PGOOD_PIN, &pgood_fd, bus);
+        if (ret < 0)
+        {
+            throw std::runtime_error("failed to config PGOOD_PIN");
+        }
+
+        ret = configGpio(POWER_UP_PIN, &power_up_fd, bus);
+        if (ret < 0)
+        {
+            closeGpio(pgood_fd);
+            throw std::runtime_error("failed to config POWER_UP_PIN");
+        }
+
+        ret = sd_event_add_io(event.get(), nullptr, pgood_fd, EPOLLPRI,
+                              callbackHandler, this);
+        if (ret < 0)
+        {
+            closeGpio(pgood_fd);
+            closeGpio(power_up_fd);
+            throw std::runtime_error("failed to add to event loop");
+        }
+
+        timer.start(std::chrono::duration_cast<std::chrono::microseconds>(
+            std::chrono::milliseconds(POLLING_INTERVAL_MS)));
+        timer.setEnabled<std::true_type>();
+        phosphor::logging::log<phosphor::logging::level::DEBUG>("Enable timer");
+    }
+
+    ~PowerControl()
+    {
+        closeGpio(pgood_fd);
+        closeGpio(power_up_fd);
+    }
+
+    static int EventHandler(sd_event_source* es, int fd, uint32_t revents,
+                            void* userdata)
+    {
+        // For the first event, only set the initial status,  do not emit signal
+        // since is it not triggered by the real gpio change
+        static bool first_event = true;
+        int n = -1;
+        char buf = '0';
+
+        if (!userdata)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "userdata null!");
+            return -1;
+        }
+
+        PowerControl* powercontrol = static_cast<PowerControl*>(userdata);
+
+        if (!powercontrol)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "null pointer!");
+            return -1;
+        }
+
+        n = ::lseek(fd, 0, SEEK_SET);
+        if (n < 0)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "lseek error!");
+            return n;
+        }
+
+        n = ::read(fd, &buf, sizeof(buf));
+        if (n < 0)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "read error!");
+            return n;
+        }
+
+        if (buf == '0')
+        {
+            powercontrol->state(0);
+            powercontrol->pgood(0);
+
+            if (first_event)
+            {
+                first_event = false;
+            }
+            else
+            {
+                powercontrol->powerLost();
+            }
+        }
+        else
+        {
+            powercontrol->state(1);
+            powercontrol->pgood(1);
+            if (first_event)
+            {
+                first_event = false;
+            }
+            else
+            {
+                powercontrol->powerGood();
+            }
+        }
+
+        return 0;
+    }
+
+    int32_t setPowerState(int32_t newState) override;
+    int32_t getPowerState() override;
+
+  private:
+    int power_up_fd;
+    int pgood_fd;
+    sdbusplus::bus::bus& bus;
+    sd_event_io_handler_t callbackHandler;
+};
diff --git a/power-control/src/main.cpp b/power-control/src/main.cpp
new file mode 100644
index 0000000..695666e
--- /dev/null
+++ b/power-control/src/main.cpp
@@ -0,0 +1,64 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "power_control.hpp"
+
+int main(int argc, char* argv[])
+{
+    int ret = 0;
+
+    phosphor::logging::log<phosphor::logging::level::INFO>(
+        "Start Chassis power control service...");
+
+    sd_event* event = nullptr;
+    ret = sd_event_default(&event);
+    if (ret < 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Error creating a default sd_event handler");
+        return ret;
+    }
+    phosphor::watchdog::EventPtr eventP{event,
+                                        phosphor::watchdog::EventDeleter()};
+    event = nullptr;
+
+    sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+    sdbusplus::server::manager_t m{bus, DBUS_OBJECT_NAME};
+
+    bus.request_name(DBUS_INTF_NAME);
+
+    PowerControl powerControl{bus, DBUS_OBJECT_NAME, eventP};
+
+    auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(
+        std::chrono::system_clock::now());
+
+    try
+    {
+        bus.attach_event(eventP.get(), SD_EVENT_PRIORITY_NORMAL);
+        ret = sd_event_loop(eventP.get());
+        if (ret < 0)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Error occurred during the sd_event_loop",
+                phosphor::logging::entry("RET=%d", ret));
+        }
+    }
+    catch (std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+        return -1;
+    }
+    return 0;
+}
diff --git a/power-control/src/power_control.cpp b/power-control/src/power_control.cpp
new file mode 100644
index 0000000..74b64b8
--- /dev/null
+++ b/power-control/src/power_control.cpp
@@ -0,0 +1,82 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "power_control.hpp"
+
+int32_t PowerControl::setPowerState(int32_t newState)
+{
+    int ret = 0;
+    int count = 0;
+    char buf = '0';
+
+    phosphor::logging::log<phosphor::logging::level::DEBUG>(
+        "setPowerState", phosphor::logging::entry("NEWSTATE=%d", newState));
+
+    if (state() == newState)
+    {
+        phosphor::logging::log<phosphor::logging::level::WARNING>(
+            "Same powerstate",
+            phosphor::logging::entry("NEWSTATE=%d", newState));
+        return 0;
+    }
+    
+    ret = ::lseek(power_up_fd, 0, SEEK_SET);
+    if (ret < 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+                                                     "lseek error!");
+        throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+            IOError();
+    }
+
+    /*
+    This power control just handle out pin "POWER_UP_PIN", change it
+    to low "0" form high "1" and set it back to high after over 200ms,
+    which will notify host (PCH) to switch power. Host to determine it
+    is power on or power off operation based on current power status.
+    For BMC (power control), just need to notify host (PCH) to switch
+    power, don't need to judge it should power on or off.
+    */
+    buf = '0';
+    ret = ::write(power_up_fd, &buf, sizeof(buf));
+    if (ret < 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+                                       "write error for setting 0 !");
+        throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+            IOError();
+    }
+
+    std::this_thread::sleep_for(
+            std::chrono::milliseconds(POWER_UP_PIN_PULSE_TIME_MS));
+
+    buf = '1';
+    ret = ::write(power_up_fd, &buf, sizeof(buf));
+    if (ret < 0)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+                                       "write error for setting 1 !");
+        throw sdbusplus::xyz::openbmc_project::Chassis::Common::Error::
+            IOError();
+    }
+
+    state(newState);
+    return 0;
+}
+
+int32_t PowerControl::getPowerState()
+{
+    return state();
+}