/* SPDX-License-Identifier: MIT */
/*
 * (c) 2014 Matthias Lange <matthias.lange@kernkonzept.com>
 */

#include <iostream>
#include <unistd.h>

#include <l4/re/env>
#include <l4/re/error_helper>
#include <l4/re/util/cap_alloc>
#include <l4/re/util/unique_cap>
#include <l4/re/util/object_registry>
#include <l4/re/util/br_manager>
#include <l4/vbus/vbus_gpio>
#include <l4/vbus/vbus>
#include <l4/sys/cxx/ipc_epiface>
#include <l4/cxx/exceptions>
#include <l4/cxx/unique_ptr>

using namespace std;

static L4Re::Util::Registry_server<> server;

class Gpio_input_server : public L4::Irqep_t<Gpio_input_server>
{
  L4::Cap<L4::Icu> _icu;
  int _irq_num;

public:
  Gpio_input_server(L4::Cap<L4::Icu> icu, int irq) : _icu(icu), _irq_num(irq) { }

  void handle_irq()
  {
    cout << "Received IRQ" << endl;

    // ready to receive next Irq
    _icu->unmask(_irq_num);
  }
};

int main()
{
  cout << "Hello, this is ex_gpio_input" << endl;

  try
    {
      L4::Cap<L4vbus::Vbus> vbus =
        L4Re::chkcap(L4Re::Env::env()->get_cap<L4vbus::Vbus>("vbus"),
                     "Could not find 'vbus' capability.\n"
                     "Check your lua config");

      L4vbus::Device root(vbus, L4VBUS_ROOT_BUS);
      L4vbus::Device dev;
      L4vbus::Icu icu_dev;

      unsigned pin = ~0;
      l4vbus_device_handle_t gpio_handle = ~0;

      // find ICU
      L4Re::chksys(root.device_by_hid(&dev, "L40009"));
      icu_dev = static_cast<L4vbus::Icu &>(dev);

      l4vbus_device_t dev_info;
      while (root.next_device(&dev, L4VBUS_MAX_DEPTH, &dev_info) == 0)
        {
          // match device against 'gpio-key' compatibility ID
          if (dev.is_compatible("gpio-key") == 1)
            {
              // get device resources, e.g. GPIO pin number
              // this is needed to create the GPIO pin device later
              for (unsigned i = 0; i < dev_info.num_resources; ++i)
                {
                  l4vbus_resource_t res;
                  if (dev.get_resource(i, &res))
                    break;

                  if (res.type == L4VBUS_RESOURCE_GPIO)
                    {
                      gpio_handle = res.provider;
                      pin = res.start;
                    }
                }
              // we found the device, exit
              break;
            }
        }

      if (gpio_handle == ~0)
        throw L4::Runtime_error(-L4_ENODEV, "No button found.");

      L4vbus::Device gpio_dev(vbus, gpio_handle);
      L4vbus::Gpio_pin irq_pin(gpio_dev, pin);

      // configure pin as Input
      L4Re::chksys(irq_pin.setup(L4VBUS_GPIO_SETUP_INPUT, 0),
                   "Failed to configure GPIO pin.");

      // get the IRQ number for this pin
      int _irq_num;
      L4Re::chksys(_irq_num = irq_pin.to_irq(), "Failed to aquire Irq.");

      // set pud mode
      L4Re::chksys(irq_pin.config_pull(L4VBUS_GPIO_PIN_PULL_DOWN),
                   "Failed to configure PULL mode.");

      // allocate capability slot for the Icu
      L4Re::Util::Unique_cap<L4::Icu> _icu =
        L4Re::chkcap(L4Re::Util::make_unique_cap<L4::Icu>(),
                     "Could not allocate icu cap.");

      // retrieve Icu capability
      L4Re::chksys(icu_dev.vicu(_icu.get()),
                   "Could not retrieve icu cap.");

      enum Irq_mode : unsigned { Mode = L4_IRQ_F_BOTH_EDGE, };

      // 1. set irq mode
      L4Re::chksys(_icu->set_mode(_irq_num, Mode),
                   "Could not set Irq mode.");

      // 2. register server object
      cxx::unique_ptr<Gpio_input_server> gpio_irq =
        cxx::make_unique<Gpio_input_server>(_icu.get(), _irq_num);
      L4::Cap<L4::Irq> _irq = server.registry()->register_irq_obj(gpio_irq.get());

      // 3. bind irq cap with irq number to Icu
      L4Re::chksys(_icu->bind(_irq_num, _irq),
                   "Could not bind Irq capability.");

      // 4. unmask irq
      // unmask() is a sender-only IPC, the return value is undefined
      // that's why we don't need to check it using chksys
      _icu->unmask(_irq_num);

      gpio_irq.release();

      server.loop();
    }
  catch (L4::Runtime_error &e)
    {
      cerr << "Runtime error: " << e.str() << ". Reason: " << e.extra_str()
           << endl;
    }

  return 0;
}
