/****************************************************************************
 * drivers/pci/pci.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <sys/pciio.h>
#include <sys/endian.h>

#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/pci/pci.h>
#include <nuttx/mutex.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define ALIGN(x, m) (((x) + ((m) - 1)) & ~((uintptr_t)(m) - 1))

/*  Wrappers for all PCI configuration access functions.  They just check
 *  alignment, do locking and call the low-level functions pointed to
 *  by ctrl->ops.
 */

#define PCI_byte_BAD  0
#define PCI_word_BAD  ((where) & 1)
#define PCI_dword_BAD ((where) & 3)

#define PCI_BUS_READ_CONFIG(len, type, size)                                   \
  int pci_bus_read_config_##len(FAR struct pci_bus_s *bus, unsigned int devfn, \
                                int where, FAR type *value)                    \
  {                                                                            \
    int ret = -EINVAL;                                                         \
    uint32_t data = 0;                                                         \
                                                                               \
    if (!PCI_##len##_BAD)                                                      \
      {                                                                        \
        ret = pci_bus_read_config(bus, devfn, where, size, &data);             \
      }                                                                        \
                                                                               \
    *value = (type)data;                                                       \
    return ret;                                                                \
  }

#define PCI_BUS_WRITE_CONFIG(len, type, size)                                   \
  int pci_bus_write_config_##len(FAR struct pci_bus_s *bus, unsigned int devfn, \
                                 int where, type value)                         \
  {                                                                             \
    int ret = -EINVAL;                                                          \
                                                                                \
    if (!PCI_##len##_BAD)                                                       \
      {                                                                         \
        ret = pci_bus_write_config(bus, devfn, where, size, value);             \
      }                                                                         \
                                                                                \
    return ret;                                                                 \
  }

#define PCI_BUS_READ_IO(len, type, size)                                        \
  int pci_bus_read_io_##len(FAR struct pci_bus_s *bus, uintptr_t where,         \
                            FAR type *value)                                    \
  {                                                                             \
    int ret = -EINVAL;                                                          \
    uint32_t data = 0;                                                          \
                                                                                \
    if (!PCI_##len##_BAD)                                                       \
      {                                                                         \
        ret = pci_bus_read_io(bus, where, size, &data);                         \
      }                                                                         \
                                                                                \
    *value = (type)data;                                                        \
    return ret;                                                                 \
  }

#define PCI_BUS_WRITE_IO(len, type, size)                                       \
  int pci_bus_write_io_##len(FAR struct pci_bus_s *bus, uintptr_t where,        \
                             type value)                                        \
  {                                                                             \
    int ret = -EINVAL;                                                          \
                                                                                \
    if (!PCI_##len##_BAD)                                                       \
      {                                                                         \
        ret = pci_bus_write_io(bus, where, size, value);                        \
      }                                                                         \
                                                                                \
    return ret;                                                                 \
  }

#define pci_match_one_device(id, dev)                               \
  (((id)->vendor == PCI_ANY_ID || (id)->vendor == (dev)->vendor) && \
   ((id)->device == PCI_ANY_ID || (id)->device == (dev)->device) && \
   ((id)->subvendor == PCI_ANY_ID ||                                \
    (id)->subvendor == (dev)->subsystem_vendor) &&                  \
   ((id)->subdevice == PCI_ANY_ID ||                                \
    (id)->subdevice == (dev)->subsystem_device) &&                  \
   ((((id)->class ^ (dev)->class) & (id)->class_mask) == 0))

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int pci_ioctl(FAR struct file *filep, int cmd, unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static DEFINE_PER_CPU_BMP(mutex_t, g_pci_lock) = NXMUTEX_INITIALIZER;
#define g_pci_lock this_cpu_var_bmp(g_pci_lock)

static DEFINE_PER_CPU_BSS_BMP(dq_queue_t, g_pci_device_queue);
#define g_pci_device_queue this_cpu_var_bmp(g_pci_device_queue)

static DEFINE_PER_CPU_BSS_BMP(dq_queue_t, g_pci_driver_queue);
#define g_pci_driver_queue this_cpu_var_bmp(g_pci_driver_queue)

static DEFINE_PER_CPU_BSS_BMP(dq_queue_t, g_pci_ctrl_queue);
#define g_pci_ctrl_queue this_cpu_var_bmp(g_pci_ctrl_queue)

static const struct file_operations g_pci_fops =
{
  NULL,                  /* open */
  NULL,                  /* close */
  NULL,                  /* read */
  NULL,                  /* write */
  NULL,                  /* seek */
  pci_ioctl,             /* ioctl */
  NULL,                  /* mmap */
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static FAR struct pci_device_s *
pci_do_find_device_from_bus(FAR struct pci_bus_s *bus, uint8_t busno,
                         unsigned int devfn)
{
  FAR struct pci_bus_s *bus_tmp;
  FAR struct pci_device_s *dev;

  dq_for_every_entry(&bus->devices, dev, struct pci_device_s, bus_node)
    {
      if (dev->bus->number == busno && devfn == dev->devfn)
        {
          return dev;
        }
    }

  dq_for_every_entry(&bus->children, bus_tmp,
                     struct pci_bus_s, node)
    {
      dev = pci_find_device_from_bus(bus_tmp, busno, devfn);
      if (dev != NULL)
        {
          return dev;
        }
    }

  return NULL;
}

static int pci_vpd_read(FAR struct pci_bus_s *bus, uint32_t devfn,
                        int offset, int count, FAR uint32_t *data)
{
  uint8_t pos;
  int i;

  if (offset + count >= PCI_VPD_ADDR_MASK || data != NULL)
    {
      return -EINVAL;
    }

  pos = pci_bus_find_capability(bus, devfn, PCI_CAP_ID_VPD);
  if (pos == 0)
    {
      return -ENOENT;
    }

  for (i = 0; i < count; i++, offset += 4)
    {
      int j = 0;

      pci_bus_write_config_word(bus, devfn, pos + PCI_VPD_ADDR, offset);

      /**
       * PCI 2.2 does not specify how long we should poll
       * for completion nor whether the operation can fail.
       */

      for (; ; )
        {
          uint16_t addr;

          pci_bus_read_config_word(bus, devfn, pos + PCI_VPD_ADDR, &addr);
          if (addr & PCI_VPD_ADDR_F)
            {
              break;
            }

          if (++j == 20)
            {
              return -EIO;
            }

          up_udelay(4);
        }

      pci_bus_read_config_dword(bus, devfn, pos + PCI_VPD_DATA, &data[i]);
      data[i] = le32toh(data[i]);
    }

  return 0;
}

/****************************************************************************
 * Name: pci_ioctl
 *
 * Description:
 *  for lspci read/write pci config space
 *
 * Input Parameters:
 *   filep - The open file description
 *   cmd - The cmd to read/write cmd
 *   arg - The arg to pass ioctl
 * Returned Value:
 *   The length of the param
 *
 ****************************************************************************/

static int pci_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct pci_controller_s *ctrl;
  FAR struct pcisel *sel;
  uint32_t devfn;
  int ret;

  sel = (FAR struct pcisel *)arg;
  devfn = PCI_DEVFN(sel->pc_dev, sel->pc_func);

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return ret;
    }

  dq_for_every_entry(&g_pci_ctrl_queue, ctrl, struct pci_controller_s, node)
    {
      if (ctrl->domain == sel->pc_domain)
        {
          break;
        }
    }

  nxmutex_unlock(&g_pci_lock);

  if (ctrl == NULL)
    {
      return -ENODEV;
    }

  switch (cmd)
    {
      case PCIOCREAD:
        {
          FAR struct pci_io *io = (FAR struct pci_io *)arg;
          ret = pci_bus_read_config(ctrl->bus, devfn, io->pi_reg,
                                    io->pi_width, &io->pi_data);
          break;
         }

      case PCIOCWRITE:
        {
          FAR struct pci_io *io = (FAR struct pci_io *)arg;
          ret = pci_bus_write_config(ctrl->bus, devfn, io->pi_reg,
                                     io->pi_width, io->pi_data);
          break;
        }

      case PCIOCGETROMLEN:
        {
          FAR struct pci_rom *rom = (FAR struct pci_rom *)arg;
          FAR struct pci_device_s *dev =
            pci_find_device_from_bus(ctrl->bus, sel->pc_bus, devfn);
          if (dev == NULL)
            {
              return -ENODEV;
            }

          rom->pr_romlen = pci_resource_len(dev, PCI_ROM_RESOURCE);
          ret = 0;
          break;
        }

      case PCIOCGETROM:
        {
          FAR void *p;
          uint32_t addr;
          uint32_t len;

          FAR struct pci_rom *rom = (FAR struct pci_rom *)arg;
          FAR struct pci_device_s *dev =
            pci_find_device_from_bus(ctrl->bus, sel->pc_bus, devfn);
          if (dev == NULL)
            {
              return -ENODEV;
            }

          addr = pci_resource_start(dev, PCI_ROM_RESOURCE);
          len = pci_resource_len(dev, PCI_ROM_RESOURCE);
          if (rom->pr_romlen < len)
            {
              rom->pr_romlen = len;
              ret = -E2BIG;
              break;
            }

          p = pci_map_bar(dev, PCI_ROM_RESOURCE);
          if (p == NULL)
            {
              ret = -ENOENT;
              break;
            }

          pci_bus_write_config_dword(ctrl->bus, devfn, PCI_ROM_ADDRESS,
                                     addr | PCI_ROM_ADDRESS_ENABLE);
          memcpy(rom->pr_rom, p, len);
          pci_bus_write_config_dword(ctrl->bus, devfn,
                                     PCI_ROM_ADDRESS, addr);
          break;
        }

      case PCIOCREADMASK:
        {
          uint32_t data;

          FAR struct pci_io *io = (FAR struct pci_io *)arg;
          if (io->pi_width != 4 || (io->pi_reg & 0x3) ||
              io->pi_reg <  PCI_BASE_ADDRESS_0 ||
              io->pi_reg >= PCI_BASE_ADDRESS_SPACE)
            {
              ret = -EINVAL;
              break;
            }

          pci_bus_read_config_dword(ctrl->bus, devfn, io->pi_reg, &data);
          pci_bus_write_config_dword(ctrl->bus, devfn, io->pi_reg,
                                     0xffffffff);
          pci_bus_read_config_dword(ctrl->bus, devfn,
                                    io->pi_reg, &io->pi_data);
          pci_bus_write_config_dword(ctrl->bus, devfn, io->pi_reg, data);
          break;
        }

      case PCIOCGETVPD:
        {
          FAR struct pci_vpd_req *pv = (FAR struct pci_vpd_req *)arg;
          ret = pci_vpd_read(ctrl->bus, devfn, pv->pv_offset,
                             pv->pv_count, pv->pv_data);
          break;
        }

      default:
        {
          ret = -ENOTTY;
          break;
        }
    }

  return ret;
}

/****************************************************************************
 * Name: pci_change_master
 *
 * Description:
 *   Enables/Disbale bus-mastering for device dev
 *
 * Input Parameters:
 *   dev    - The PCI device to cchange
 *   enable - True to enable, False to disable
 *
 ****************************************************************************/

static void pci_change_master(FAR struct pci_device_s *dev, bool enable)
{
  uint16_t old_cmd;
  uint16_t cmd;

  pci_read_config_word(dev, PCI_COMMAND, &old_cmd);
  if (enable)
    {
      cmd = old_cmd | PCI_COMMAND_MASTER;
    }
  else
    {
      cmd = old_cmd & ~PCI_COMMAND_MASTER;
    }

  if (cmd != old_cmd)
    {
      pci_write_config_word(dev, PCI_COMMAND, cmd);
    }
}

/****************************************************************************
 * Name: pci_bus_find_start_cap
 *
 * Description:
 *   Find the offset of first capability list entry.
 *
 * Input Parameters:
 *   bus      - The bus this dev exist on
 *   devfn    - BDF
 *   hdr_type - Cfg space head type ID
 *
 * Returned Value:
 *   Return the ID of first capability list entry
 *
 ****************************************************************************/

static uint8_t pci_bus_find_start_cap(FAR struct pci_bus_s *bus,
                                      unsigned int devfn,
                                      uint8_t hdr_type)
{
  uint16_t status;

  pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status);
  if (!(status & PCI_STATUS_CAP_LIST))
    {
      return 0;
    }

  /* Ignore MF bit */

  switch (hdr_type & 0x7f)
  {
    case PCI_HEADER_TYPE_NORMAL:
    case PCI_HEADER_TYPE_BRIDGE:
      return PCI_CAPABILITY_LIST;

    case PCI_HEADER_TYPE_CARDBUS:
      return PCI_CB_CAPABILITY_LIST;

    default:
      return 0;
  }
}

/****************************************************************************
 * Name: pci_find_next_cap_ttl
 *
 * Description:
 *
 * Input Parameters:
 *   dev   - The PCI device to find capbilities
 *   devfn - BDF
 *   pos   - List node
 *   cap   - Value of capabilities
 *   ttl   - The max depth to find
 *
 ****************************************************************************/

static uint8_t pci_find_next_cap_ttl(FAR struct pci_bus_s *bus,
                                     unsigned int devfn, uint8_t pos,
                                     int cap, FAR int *ttl)
{
  uint16_t ent;
  uint8_t id;

  pci_bus_read_config_byte(bus, devfn, pos, &pos);

  while ((*ttl)--)
    {
      if (pos < 0x40)
        {
          break;
        }

      pos &= ~3;
      pci_bus_read_config_word(bus, devfn, pos, &ent);

      id = ent & 0xff;
      if (id == 0xff)
        {
          break;
        }

      if (id == cap)
        {
          return pos;
        }

      pos = ent >> 8;
    }

  return 0;
}

/****************************************************************************
 * Name: pci_find_next_cap
 *
 * Description:
 *   To find the next capability.
 *
 * Input Parameters:
 *   dev   - The PCI device to find capbilities
 *   devfn - BDF
 *   pos   - List node
 *   cap   - Value of capabilities
 *
 * Returned Value:
 *   Return the capability data
 *
 ****************************************************************************/

static uint8_t pci_find_next_cap(FAR struct pci_bus_s *bus,
                                 unsigned int devfn, uint8_t pos, int cap)
{
  int ttl = 48;
  return pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl);
}

/****************************************************************************
 * Name: pci_alloc_bus
 *
 * Description:
 *   Alloc a memory for a bus and init list node.
 *
 * Returned Value:
 *   Return the memory address alloced
 *
 ****************************************************************************/

static FAR struct pci_bus_s *pci_alloc_bus(void)
{
  return kmm_zalloc(sizeof(struct pci_bus_s));
}

/****************************************************************************
 * Name: pci_alloc_device
 *
 * Description:
 *   Alloc a memory for device be scanned and init the list node.
 *
 * Returned Value:
 *   Return the device address alloced
 *
 ****************************************************************************/

static FAR struct pci_device_s *pci_alloc_device(void)
{
  return kmm_zalloc(sizeof(struct pci_device_s));
}

/****************************************************************************
 * Name: pci_register_bus_devices
 *
 * Description:
 *   Register all devices scanned and all buses scanned to responsing list.
 *
 * Input Parameters:
 *   bus - The boot bus
 *
 ****************************************************************************/

static void pci_register_bus_devices(FAR struct pci_bus_s *bus)
{
  FAR struct pci_device_s *dev;
  FAR struct pci_bus_s *child_bus;

  /* Activate all devices on this bus */

  dq_for_every_entry(&bus->devices, dev, struct pci_device_s, bus_node)
    {
      pci_register_device(dev);
    }

  /* Walk down the hierarchy */

  dq_for_every_entry(&bus->children, child_bus, struct pci_bus_s, node)
    {
      pci_register_bus_devices(child_bus);
    }
}

/****************************************************************************
 * Name: pci_size
 *
 * Description:
 *   To calculate the pci dev address size.
 *
 * Input Parameters:
 *   base    - PCI address base address
 *   maxbase - PCI max base address
 *   mask    - PCI addres mask
 *
 * Returned Value:
 *   Return the size result
 *
 ****************************************************************************/

static uint64_t pci_size(uint64_t base, uint64_t maxbase, uint64_t mask)
{
  uint64_t size = maxbase & mask;

  if (size == 0)
    {
      return 0;
    }

  size = (size & ~(size - 1)) - 1;

  if (base == maxbase && ((base | size) & mask) != mask)
    {
      return 0;
    }

  return size + 1;
}

/****************************************************************************
 * Name: pci_setup_device
 *
 * Description:
 *   Search every bar in the device be found, mapping memory if MEM or
 *   prefetchable MEM, and add this dev to the device list.
 *
 * Input Parameters:
 *   dev      - The PCI device be found
 *   max_bar  - Max bar number(6 or 2)
 *   rom_addr - The pci device rom addr
 *   io       - The pci bus io resource
 *   mem      - The pci bus mem resource
 *   mem_pref - The pci bus mem_pref resource
 *
 ****************************************************************************/

static void pci_setup_device(FAR struct pci_device_s *dev, int max_bar,
                             uint8_t rom_addr, FAR struct pci_resource_s *io,
                             FAR struct pci_resource_s *mem,
                             FAR struct pci_resource_s *mem_pref)
{
  int bar;
  uint32_t orig0;
  uint32_t orig1;
  uint32_t mask;
  uint64_t orig64;
  uint64_t size64;
  uintptr_t start;
  uint64_t maxbase;
  uint64_t mask64 = 0;
#ifdef CONFIG_PCI_ASSIGN_ALL_BUSES
  uint8_t cmd;

  pci_read_config_byte(dev, PCI_COMMAND, &cmd);
  pci_write_config_byte(dev, PCI_COMMAND,
                        cmd & ~PCI_COMMAND_IO & ~PCI_COMMAND_MEMORY);
#else
  uint32_t tmp;
#endif

  for (bar = 0; bar < max_bar; bar++)
    {
      int base_address_0 = PCI_BASE_ADDRESS_0 + bar * 4;
      int base_address_1 = PCI_BASE_ADDRESS_1 + bar * 4;
      FAR struct pci_resource_s *res;
      unsigned int flags;

      pci_read_config_dword(dev, base_address_0, &orig0);
      pci_write_config_dword(dev, base_address_0, 0xffffffff);
      pci_read_config_dword(dev, base_address_0, &mask);

      if (mask == 0 || mask == 0xffffffff)
        {
          pciinfo("pbar%d set bad mask\n", bar);
          continue;
        }

      if (mask & PCI_BASE_ADDRESS_SPACE_IO)
        {
          /* IO */

          mask64 = 0xfffffffe;
          flags  = PCI_RESOURCE_IO;
          res    = io;
        }
      else if ((mask & PCI_BASE_ADDRESS_MEM_PREFETCH) &&
               pci_resource_size(&dev->bus->ctrl->mem_pref))
        {
          if ((mask & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
               PCI_BASE_ADDRESS_MEM_TYPE_64)
            {
              /* Prefetchable MEM */

              mask64 = 0xfffffff0;
              flags  = PCI_RESOURCE_MEM_64 | PCI_RESOURCE_PREFETCH;
              res    = mem_pref;
            }
          else if (((mask & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
                    PCI_BASE_ADDRESS_MEM_TYPE_32) &&
                   (dev->bus->ctrl->mem_pref.flags & PCI_RESOURCE_MEM))
            {
              /* Prefetchable MEM */

              mask64 = 0xfffffff0;
              flags  = PCI_RESOURCE_MEM | PCI_RESOURCE_PREFETCH;
              res    = mem_pref;
            }
          else
            {
              /* Non-prefetch MEM */

              mask64 = 0xfffffff0;
              flags  = PCI_RESOURCE_MEM;
              res    = mem;
            }
        }
      else
        {
          /* Non-prefetch MEM */

          mask64 = 0xfffffff0;
          flags  = PCI_RESOURCE_MEM;
          res    = mem;
        }

      orig64 = orig0;
      maxbase = mask;
      if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64)
        {
          uint32_t masktmp;

          pci_read_config_dword(dev, base_address_1, &orig1);
          pci_write_config_dword(dev, base_address_1, 0xffffffff);
          pci_read_config_dword(dev, base_address_1, &masktmp);
          pci_write_config_dword(dev, base_address_1, orig1);
          mask64 |= (uint64_t)masktmp << 32;
          orig64 |= (uint64_t)orig1 << 32;
          maxbase |= (uint64_t)masktmp << 32;
        }

      pci_write_config_dword(dev, base_address_0, orig0);
      size64 = pci_size(orig64, maxbase, mask64);
      if (size64 == 0)
        {
          pcierr("pbar%d bad mask\n", bar);
          continue;
        }

      pciinfo("pbar%d: mask64=%08" PRIx64 " %" PRIu64 "bytes\n",
              bar, mask64, size64);

#ifdef CONFIG_PCI_ASSIGN_ALL_BUSES
      if (ALIGN(res->start, size64) + size64 > res->end)
        {
          pcierr("pbar%d: does not fit within bus res\n", bar);
          return;
        }

      res->start = ALIGN(res->start, size64);
      pci_write_config_dword(dev, base_address_0, res->start);
      if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64)
        {
          pci_write_config_dword(dev, base_address_1, res->start >> 32);
        }

      start = res->start;
      res->start += size64;
#else
      UNUSED(res);
      pci_read_config_dword(dev, base_address_0, &tmp);
      if (mask & PCI_BASE_ADDRESS_SPACE_IO)
        {
          start = tmp & PCI_BASE_ADDRESS_IO_MASK;
        }
      else
        {
          start = tmp & PCI_BASE_ADDRESS_MEM_MASK;
        }

      if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64)
        {
          pci_read_config_dword(dev, base_address_1, &tmp);
          start |= (uint64_t)tmp << 32;
        }
#endif

      dev->resource[bar].flags = flags;
      dev->resource[bar].start = start;
      dev->resource[bar].end   = start + size64 - 1;

      if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64)
        {
          dev->resource[bar++].flags |= PCI_RESOURCE_MEM_64;
        }
    }

  pci_read_config_dword(dev, rom_addr, &orig0);
  pci_write_config_dword(dev, rom_addr,
                         ~PCI_ROM_ADDRESS_ENABLE);
  pci_read_config_dword(dev, rom_addr, &mask);
  pci_write_config_dword(dev, rom_addr, orig0);
  start = PCI_ROM_ADDR(orig0);
  size64 = PCI_ROM_SIZE(mask);
  if (start != 0 && size64 != 0)
    {
      dev->resource[PCI_ROM_RESOURCE].flags |=
        PCI_RESOURCE_MEM | PCI_RESOURCE_PREFETCH;
      dev->resource[PCI_ROM_RESOURCE].start = start;
      dev->resource[PCI_ROM_RESOURCE].end = start + size64 - 1;
    }

#ifdef CONFIG_PCI_ASSIGN_ALL_BUSES
  pci_write_config_byte(dev, PCI_COMMAND, cmd);
#endif

  dq_addlast(&dev->bus_node, &dev->bus->devices);
}

/****************************************************************************
 * Name: pci_presetup_bridge
 *
 * Description:
 *    Setup data to the next bridge register.
 *
 * Input Parameters:
 *    dev - The next bridge dev
 *
 ****************************************************************************/

static void pci_presetup_bridge(FAR struct pci_device_s *dev)
{
#ifndef CONFIG_PCI_ASSIGN_ALL_BUSES
  pci_read_config_byte(dev, PCI_PRIMARY_BUS, &dev->bus->number);
  pci_read_config_byte(dev, PCI_SECONDARY_BUS, &dev->subordinate->number);
#else
  FAR struct pci_controller_s *ctrl = dev->bus->ctrl;
  uint16_t cmdstat;

  pci_read_config_word(dev, PCI_COMMAND, &cmdstat);

  /* Configure bus number registers */

  pci_write_config_byte(dev, PCI_PRIMARY_BUS, dev->bus->number);
  pci_write_config_byte(dev, PCI_SECONDARY_BUS, dev->subordinate->number);
  pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, 0xff);

  if (pci_resource_size(&ctrl->mem))
    {
      /* Set up memory and I/O filter limits, assume 32-bit I/O space */

      ctrl->mem.start = ALIGN(ctrl->mem.start, 1024 * 1024);
      pci_write_config_word(dev, PCI_MEMORY_BASE,
                            (ctrl->mem.start & 0xfff00000) >> 16);
      cmdstat |= PCI_COMMAND_MEMORY;
    }
  else
    {
      pci_write_config_word(dev, PCI_MEMORY_BASE, 0x1000);
      pci_write_config_word(dev, PCI_MEMORY_LIMIT, 0x0);
    }

  if (pci_resource_size(&ctrl->mem_pref))
    {
      uint8_t base;

      pci_read_config_byte(dev, PCI_PREF_MEMORY_BASE, &base);
      ctrl->mem_pref.start = ALIGN(ctrl->mem_pref.start, 1024 * 1024);
      pci_write_config_word(dev, PCI_PREF_MEMORY_BASE,
                            (ctrl->mem_pref.start & 0xfff00000) >> 16);
      if (base & PCI_PREF_RANGE_TYPE_64)
        {
          pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32,
                                 ctrl->mem_pref.start >> 32);
        }

      cmdstat |= PCI_COMMAND_MEMORY;
    }
  else
    {
      /* We don't support prefetchable memory for now, so disable */

      pci_write_config_word(dev, PCI_PREF_MEMORY_BASE, 0x1000);
      pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT, 0x0);
      pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0x0);
      pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32, 0x0);
    }

  if (pci_resource_size(&ctrl->io))
    {
      uint8_t base;

      pci_read_config_byte(dev, PCI_IO_BASE, &base);
      ctrl->io.start = ALIGN(ctrl->io.start, 1024 * 4);
      pci_write_config_byte(dev, PCI_IO_BASE,
                            (ctrl->io.start & 0x0000f000) >> 8);
      if (base & PCI_IO_RANGE_TYPE_32)
        {
          pci_write_config_word(dev, PCI_IO_BASE_UPPER16,
                                (ctrl->io.start & 0xffff0000) >> 16);
        }

      cmdstat |= PCI_COMMAND_IO;
    }

  /* Enable memory and I/O accesses, enable bus master */

  pci_write_config_word(dev, PCI_COMMAND, cmdstat | PCI_COMMAND_MASTER);
#endif
}

/****************************************************************************
 * Name: pci_postsetup_bridge
 *
 * Description:
 *   Setup data into Limit subordinate reg.
 *
 * Input Parameters:
 *   dev - The next bridge dev
 *
 ****************************************************************************/

static void pci_postsetup_bridge(FAR struct pci_device_s *dev)
{
#ifdef CONFIG_PCI_ASSIGN_ALL_BUSES
  FAR struct pci_controller_s *ctrl = dev->bus->ctrl;

  /* Limit subordinate to last used bus number */

  pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, ctrl->busno - 1);

  if (pci_resource_size(&ctrl->mem))
    {
      ctrl->mem.start = ALIGN(ctrl->mem.start, 1024 * 1024);
      pciinfo("bridge NP limit at %" PRIx64 "\n", ctrl->mem.start);
      pci_write_config_word(dev, PCI_MEMORY_LIMIT,
                            ((ctrl->mem.start - 1) & 0xfff00000) >> 16);
    }

  if (pci_resource_size(&ctrl->mem_pref))
    {
      ctrl->mem_pref.start = ALIGN(ctrl->mem_pref.start, 1024 * 1024);
      pciinfo("bridge P limit at %" PRIx64 "\n", ctrl->mem_pref.start);
      pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT,
                            ((ctrl->mem_pref.start - 1) & 0xfff00000) >> 16);
      pci_write_config_dword(dev, PCI_PREF_LIMIT_UPPER32,
                             (uint64_t)(ctrl->mem_pref.start - 1) >> 32);
    }

  if (pci_resource_size(&ctrl->io))
    {
      ctrl->io.start = ALIGN(ctrl->io.start, 1024 * 4);
      pciinfo("bridge IO limit at %" PRIx64 "\n", ctrl->io.start);
      pci_write_config_byte(dev, PCI_IO_LIMIT,
                            ((ctrl->io.start - 1) & 0x0000f000) >> 8);
      pci_write_config_word(dev, PCI_IO_LIMIT_UPPER16,
                            ((ctrl->io.start - 1) & 0xffff0000) >> 16);
    }
#endif
}

/****************************************************************************
 * Name: pci_scan_bus
 *
 * Description:
 *  Iterates over all slots on bus looking for devices and buses to
 *  enumerate.
 *
 * Input Parameters:
 *   bus - The root bus device that lets us address the whole tree
 *
 ****************************************************************************/

static void pci_scan_bus(FAR struct pci_bus_s *bus)
{
  FAR struct pci_device_s *dev;
  FAR struct pci_bus_s *child_bus;
  struct pci_resource_s mem_pref;
  struct pci_resource_s mem;
  struct pci_resource_s io;
  unsigned int devfn;
  uint32_t l;
  uint32_t class;
  uint8_t hdr_type;
  uint8_t is_multi = 0;

  pciinfo("pci_scan_bus for bus %d\n", bus->number);

  memcpy(&io, &bus->ctrl->io, sizeof(struct pci_resource_s));
  memcpy(&mem, &bus->ctrl->mem, sizeof(struct pci_resource_s));
  memcpy(&mem_pref, &bus->ctrl->mem_pref, sizeof(struct pci_resource_s));

  for (devfn = 0; devfn < 0xff; ++devfn)
    {
      if (PCI_FUNC(devfn) && !is_multi)
        {
          /* Not a multi-function device */

          continue;
        }

      if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
        {
          continue;
        }

      if (!PCI_FUNC(devfn))
        {
          is_multi = hdr_type & 0x80;
        }

      /* Some broken boards return 0 if a slot is empty: */

      if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l) ||
          l == 0xffffffff || l == 0x00000000 || l == 0x0000ffff ||
          l == 0xffff0000)
        {
          continue;
        }

      dev = pci_alloc_device();
      dev->bus = bus;
      dev->devfn = devfn;
      dev->vendor = l & 0xffff;
      dev->device = (l >> 16) & 0xffff;
      dev->hdr_type = hdr_type;

      pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
      dev->revision = class & 0xff;

      /* Upper 3 bytes */

      class >>= 8;
      dev->class = class;
      class >>= 8;

      pciinfo("class = %08" PRIx32 ", hdr_type = %08x\n", class, hdr_type);
      pciinfo("%02x:%02" PRIx32 " [%04x:%04x]\n", bus->number, dev->devfn,
              dev->vendor, dev->device);

      switch (hdr_type & 0x7f)
      {
        case PCI_HEADER_TYPE_NORMAL:
          if (class == PCI_CLASS_BRIDGE_PCI)
            {
              goto bad;
            }

          pci_setup_device(dev, 6, PCI_ROM_ADDRESS, &io, &mem, &mem_pref);

          pci_read_config_word(dev, PCI_SUBSYSTEM_ID,
                               &dev->subsystem_device);
          pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID,
                               &dev->subsystem_vendor);
          break;

        case PCI_HEADER_TYPE_BRIDGE:
          child_bus = pci_alloc_bus();

          /* Inherit parent properties */

          child_bus->ctrl = bus->ctrl;
          child_bus->parent_bus = bus;

#ifdef CONFIG_PCI_ASSIGN_ALL_BUSES
          child_bus->number = bus->ctrl->busno++;
#endif

          dq_addlast(&child_bus->node, &bus->children);
          dev->subordinate = child_bus;

          /* Scan pci hierarchy behind bridge */

          pci_presetup_bridge(dev);
          pci_scan_bus(child_bus);
          pci_postsetup_bridge(dev);

          pci_setup_device(dev, 2, PCI_ROM_ADDRESS1, &io, &mem, &mem_pref);
          break;

        default:
        bad:
          pcierr("PCI: %02x:%02" PRIx32 " [%04x/%04x/%06" PRIx32
                 "] has unknown header type %02x, ignoring.\n",
                 bus->number, dev->devfn, dev->vendor,
                 dev->device, class, hdr_type);
          continue;
      }
    }
}

/****************************************************************************
 * Name: pci_get_msi_base
 *
 * Description:
 *  Get MSI and MSI-X base
 *
 * Input Parameters:
 *   dev  - device
 *   msi  - returned MSI base
 *   msix - returned MSI-X base
 *
 * Return value:
 *   None
 *
 ****************************************************************************/

static void pci_get_msi_base(FAR struct pci_device_s *dev, FAR uint8_t *msi,
                             FAR uint8_t *msix)
{
  if (msi != NULL)
    {
      *msi = pci_find_capability(dev, PCI_CAP_ID_MSI);
    }

  if (msix != NULL)
    {
      *msix = pci_find_capability(dev, PCI_CAP_ID_MSIX);
    }
}

/****************************************************************************
 * Name: pci_enable_msi
 *
 * Description:
 *   Configure and enable MSI.
 *
 * Input Parameters:
 *   dev - device
 *   irq - allocated vectors
 *   num - number of vectors
 *   msi - MSI base address
 *
 * Return value:
 *   OK on success or a negative error code on failure
 *
 ****************************************************************************/

static int pci_enable_msi(FAR struct pci_device_s *dev, FAR int *irq,
                          int num, uint8_t msi)
{
  uint32_t  mdr   = 0;
  uint16_t  flags = 0;
  uintptr_t mar   = 0;
  uint16_t  mme   = 0;
  uint32_t  mmc   = 0;
  int       ret   = OK;

  /* Suppoted messages */

  for (mme = 0; (1 << mme) < num; mme++);

  /* Get Message Control Register */

  pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags);
  mmc = (flags & PCI_MSI_FLAGS_QMASK) >> PCI_MSI_FLAGS_QMASK_SHIFT;
  if (mme > mmc)
    {
      mme = mmc;
      num = 1 << mme;
      pciinfo("Limit MME to %"PRIx32", num to %d\n", mmc, num);
    }

  /* Configure MSI (arch-specific) */

  ret = dev->bus->ctrl->ops->connect_irq(dev->bus, irq, num, &mar, &mdr);
  if (ret < 0)
    {
      return ret;
    }

  /* Write Message Address Regsiter */

  pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_LO, mar);

  /* Write Message Data Register */

  if ((flags & PCI_MSI_FLAGS_64BIT) != 0)
    {
      pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_HI,
                             ((uint64_t)mar >> 32));
      pci_write_config_dword(dev, msi + PCI_MSI_DATA_64, mdr);
    }
  else
    {
      pci_write_config_word(dev, msi + PCI_MSI_DATA_32, mdr);
    }

  flags |= mme << PCI_MSI_FLAGS_QSIZE_SHIFT;

  /* Enable MSI */

  flags |= PCI_MSI_FLAGS_ENABLE;

  /* Write Message Control Register */

  pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags);
  return OK;
}

#ifdef CONFIG_PCI_MSIX
/****************************************************************************
 * Name: pci_disable_msi
 *
 * Description:
 *  Disable MSI.
 *
 * Input Parameters:
 *   dev  - device
 *   msi  - MSI base address
 *
 * Return value:
 *   None
 *
 ****************************************************************************/

static void pci_disable_msi(FAR struct pci_device_s *dev, uint8_t msi)
{
  uint16_t flags = 0;

  pci_read_config_word(dev, msi + PCI_MSI_FLAGS, &flags);

  /* Write Message Address Regsiter */

  pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_LO, 0);

  /* Write Message Data Register */

  if ((flags & PCI_MSI_FLAGS_64BIT) != 0)
    {
      pci_write_config_dword(dev, msi + PCI_MSI_ADDRESS_HI, 0);
      pci_write_config_dword(dev, msi + PCI_MSI_DATA_64, 0);
    }
  else
    {
      pci_write_config_dword(dev, msi + PCI_MSI_DATA_32, 0);
    }

  flags &= ~PCI_MSI_FLAGS_ENABLE;
  pci_write_config_word(dev, msi + PCI_MSI_FLAGS, flags);
}

/****************************************************************************
 * Name: pci_disable_msix
 *
 * Description:
 *  Disable MSI-X.
 *
 * Input Parameters:
 *   dev  - device
 *   msix - MSI-X base address
 *   num  - num of msix entry
 *
 * Return value:
 *   None
 *
 ****************************************************************************/

static void pci_disable_msix(FAR struct pci_device_s *dev, uint8_t msix,
                             int num)
{
  uint16_t  flags     = 0;
  uintptr_t tbladdr   = 0;
  uint32_t  tbloffset = 0;
  uint32_t  tblbar    = 0;
  uint32_t  tbl       = 0;
  int       i         = 0;

  pci_read_config_word(dev, msix + PCI_MSIX_FLAGS, &flags);
  pci_read_config_dword(dev, msix + PCI_MSIX_TABLE, &tbl);

  tblbar = tbl & PCI_MSIX_TABLE_BIR;
  tbladdr = pci_resource_start(dev, tblbar);
  tbloffset = tbl & PCI_MSIX_TABLE_OFFSET;
  tbladdr += tbloffset;

  for (i = 0; i < num; i++)
    {
      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_LOWER_ADDR, 0);
      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_UPPER_ADDR, 0);
      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_DATA, 0);
      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_VECTOR_CTRL, 0);

      /* Next vector */

      tbladdr += PCI_MSIX_ENTRY_SIZE;
    }

  flags &= ~PCI_MSIX_FLAGS_ENABLE;
  pci_write_config_word(dev, msix + PCI_MSIX_FLAGS, flags);
}

/****************************************************************************
 * Name: pci_enable_msix
 *
 * Description:
 *   Configure and enable MSI-X.
 *
 * Input Parameters:
 *   dev  - device
 *   irq  - allocated vectors
 *   num  - number of vectors
 *   msix - MSI-X base address
 *
 * Return value:
 *   OK on success or a negative error code on failure
 *
 ****************************************************************************/

static int pci_enable_msix(FAR struct pci_device_s *dev, FAR int *irq,
                           int num, uint8_t msix)
{
  uint32_t  mdr       = 0;
  uint16_t  flags     = 0;
  uintptr_t mar       = 0;
  uintptr_t tbladdr   = 0;
  uintptr_t tblend    = 0;
  uint32_t  tbloffset = 0;
  uint32_t  tblbar    = 0;
  uint32_t  tbl       = 0;
  uint16_t  tblsize   = 0;
  int       i         = 0;
  int       ret       = OK;

  /* Get Flags */

  pci_read_config_word(dev, msix + PCI_MSIX_FLAGS, &flags);

  /* Table Size is N - 1 encoded */

  tblsize = (flags & PCI_MSIX_FLAGS_QSIZE) + 1;

  /* Get MSI-X table */

  pci_read_config_dword(dev, msix + PCI_MSIX_TABLE, &tbl);

  /* Extract table address */

  tblbar = tbl & PCI_MSIX_TABLE_BIR;
  tbladdr = pci_resource_start(dev, tblbar);
  tbloffset = tbl & PCI_MSIX_TABLE_OFFSET;
  tbladdr += tbloffset;

  /* Map MSI-X table */

  tblend = tbladdr + tblsize * PCI_MSIX_ENTRY_SIZE;

  if (dev->bus->ctrl->ops->map)
    {
      tbladdr = dev->bus->ctrl->ops->map(dev->bus, tbladdr, tblend);
    }

  /* Limit tblsize */

  if (num > tblsize)
    {
      pciinfo("Limit tblszie to %xu\n", tblsize);
      num = tblsize;
    }

  for (i = 0; i < num; i++)
    {
      /* Connect MSI-X (arch-specific) */

      ret = dev->bus->ctrl->ops->connect_irq(dev->bus, &irq[i], 1,
                                             &mar, &mdr);
      if (ret < 0)
        {
          return ret;
        }

      /* Write Message Address Register */

      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_LOWER_ADDR, mar);

      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_UPPER_ADDR,
                           ((uint64_t)mar >> 32));

      /* Write Message Data Register */

      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_DATA, mdr);

      /* Write Vector Control register */

      pci_write_mmio_dword(dev, tbladdr + PCI_MSIX_ENTRY_VECTOR_CTRL, 0);

      /* Next vector */

      tbladdr += PCI_MSIX_ENTRY_SIZE;
    }

  /* Enable MSI-X */

  flags |= PCI_MSIX_FLAGS_ENABLE;
  pci_write_config_word(dev, msix + PCI_MSIX_FLAGS, flags);

  return OK;
}
#endif  /* CONFIG_PCI_MSIX */

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pci_find_device_from_bus
 *
 * Description:
 *   To find a PCI device from the bus
 *
 * Input Parameters:
 *   bus   - pci bus
 *   busno - bus number
 *   devfn - device number and function number
 *
 * Returned Value:
 *   Failed if return NULL, otherwise return pci devices
 *
 ****************************************************************************/

FAR struct pci_device_s *
pci_find_device_from_bus(FAR struct pci_bus_s *bus, uint8_t busno,
                         unsigned int devfn)
{
  FAR struct pci_device_s *dev;
  int ret;

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return NULL;
    }

  dev = pci_do_find_device_from_bus(bus, busno, devfn);
  nxmutex_unlock(&g_pci_lock);

  return dev;
}

/****************************************************************************
 * Name: pci_bus_read_config
 *
 * Description:
 *  Read pci device config space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   devfn - The PCI device dev number and function number
 *   where - The register address
 *   size  - The data length
 *   val   - The data buffer
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_bus_read_config(FAR struct pci_bus_s *bus,
                        unsigned int devfn, int where,
                        int size, FAR uint32_t *val)
{
  if (size != 1 && size != 2 && size != 4)
    {
      return -EINVAL;
    }

  return bus->ctrl->ops->read(bus, devfn, where, size, val);
}

/****************************************************************************
 * Name: pci_bus_write_config
 *
 * Description:
 *  Read pci device config space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   devfn - The PCI device dev number and function number
 *   where - The register address
 *   size  - The data length
 *   val   - The data
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_bus_write_config(FAR struct pci_bus_s *bus,
                         unsigned int devfn, int where,
                         int size, uint32_t val)
{
  if (size != 1 && size != 2 && size != 4)
    {
      return -EINVAL;
    }

  return bus->ctrl->ops->write(bus, devfn, where, size, val);
}

/****************************************************************************
 * Name: pci_bus_read_io
 *
 * Description:
 *  Read pci device io space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   addr  - The address to read
 *   size  - The data length
 *   val   - The data buffer
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_bus_read_io(FAR struct pci_bus_s *bus, uintptr_t addr,
                    int size, FAR uint32_t *val)
{
  if (size != 1 && size != 2 && size != 4)
    {
      return -EINVAL;
    }

  return bus->ctrl->ops->read_io(bus, addr, size, val);
}

/****************************************************************************
 * Name: pci_bus_write_io
 *
 * Description:
 *  Read pci device io space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   addr  - The address to write
 *   size  - The data length
 *   val   - The data
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_bus_write_io(FAR struct pci_bus_s *bus, uintptr_t addr,
                     int size, uint32_t val)
{
  if (size != 1 && size != 2 && size != 4)
    {
      return -EINVAL;
    }

  return bus->ctrl->ops->write_io(bus, addr, size, val);
}

/****************************************************************************
 * Name: pci_set_master
 *
 * Description:
 *   Enables bus-mastering for device
 *
 * Input Parameters:
 *   dev - The PCI device to enable
 *
 ****************************************************************************/

void pci_set_master(FAR struct pci_device_s *dev)
{
  pci_change_master(dev, true);
}

/****************************************************************************
 * Name: pci_clear_master
 *
 * Description:
 *   Disables bus-mastering for device
 *
 * Input Parameters:
 *   dev - The PCI device to disable
 *
 ****************************************************************************/

void pci_clear_master(FAR struct pci_device_s *dev)
{
  pci_change_master(dev, false);
}

/****************************************************************************
 * Name: pci_enable_device
 *
 * Description:
 *   Initialize device before it's used by a driver by setting command
 *   register.
 *
 * Input Parameters:
 *   dev - PCI device to be enabled
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_enable_device(FAR struct pci_device_s *dev)
{
  uint32_t cmd;

  pci_read_config_dword(dev, PCI_COMMAND, &cmd);
  return pci_write_config_dword(dev, PCI_COMMAND,
                                cmd | PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
}

/****************************************************************************
 * Name: pci_disable_device
 *
 * Description:
 *   Disable pci device before it's unused by a driver by setting command
 *   register.
 *
 * Input Parameters:
 *   dev - PCI device to be disable
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

int pci_disable_device(FAR struct pci_device_s *dev)
{
  uint32_t cmd;

  pci_read_config_dword(dev, PCI_COMMAND, &cmd);
  return pci_write_config_dword(dev, PCI_COMMAND,
                                cmd & ~PCI_COMMAND_IO & ~PCI_COMMAND_MEMORY);
}

/****************************************************************************
 * Name: pci_select_bars
 *
 * Description:
 *   Make BAR mask from the type of resource
 *   This helper routine makes bar mask from the type of resource.
 *
 * Input Parameters:
 *   dev   - The PCI device for which BAR mask is made
 *   flags - Resource type mask to be selected
 *
 ****************************************************************************/

int pci_select_bars(FAR struct pci_device_s *dev, unsigned int flags)
{
  int bars = 0;
  int i;

  for (i = 0; i < PCI_NUM_RESOURCES; i++)
    {
      if (pci_resource_flags(dev, i) & flags)
        {
          bars |= 1 << i;
        }
    }

  return bars;
}

/****************************************************************************
 * Name: pci_bus_map_region
 *
 * Description:
 *   Create a virtual mapping for a address.
 *
 *   Using this function you will get an virtual address.
 *   These functions hide the details if this is a MMIO or PIO address
 *   space and will just do what you expect from them in the correct way.
 *
 * Input Parameters:
 *   bus   - PCI bus
 *   start - The address base
 *   size  - The length of the address
 *
 * Returned Value:
 *  Virtual address or zero if failed
 ****************************************************************************/

FAR void *
pci_bus_map_region(FAR struct pci_bus_s *bus, uintptr_t start, size_t size)
{
  return bus->ctrl->ops->map ?
    (FAR void *)bus->ctrl->ops->map(bus, start, start + size)
    : (FAR void *)start;
}

/****************************************************************************
 * Name: pci_map_bar_region
 *
 * Description:
 *   Create a virtual mapping for a PCI BAR REGION.
 *
 *   Using this function you will get an address to your device BAR region.
 *   These functions hide the details if this is a MMIO or PIO address
 *   space and will just do what you expect from them in the correct way.
 *
 * Input Parameters:
 *   dev - PCI device that owns the BAR
 *   bar - BAR number
 *   offset - BAR region offset
 *   length - BAR region length
 *
 * Returned Value:
 *  IO address or zero if failed
 ****************************************************************************/

FAR void *pci_map_bar_region(FAR struct pci_device_s *dev, int bar,
                             uintptr_t offset, size_t length)
{
  uintptr_t start = pci_resource_start(dev, bar) + offset;

  if (pci_resource_len(dev, bar) == 0)
    {
      return NULL;
    }

  return pci_map_region(dev, start, length);
}

/****************************************************************************
 * Name: pci_map_bar
 *
 * Description:
 *   Create a virtual mapping cookie for a PCI BAR.
 *
 *   Using this function you will get an address to your device BAR.
 *   These functions hide the details if this is a MMIO or PIO address
 *   space and will just do what you expect from them in the correct way.
 *
 * Input Parameters:
 *   dev - PCI device that owns the BAR
 *   bar - BAR number
 *
 * Returned Value:
 *  IO address or zero if failed
 *
 ****************************************************************************/

FAR void *pci_map_bar(FAR struct pci_device_s *dev, int bar)
{
  return pci_map_bar_region(dev, bar, 0, pci_resource_len(dev, bar));
}

/****************************************************************************
 * Name: pci_find_capability
 *
 * Description:
 *   Query for devices' capabilities
 *
 *   Tell if a device supports a given PCI capability.
 *
 * Input Parameters:
 *   dev - PCI device to query
 *   cap - Capability code
 *
 * Returned Value:
 *   Returns the address of the requested capability structure within the
 *   device's PCI configuration space or 0 in case the device does not
 *   support it.
 *
 ****************************************************************************/

uint8_t pci_find_capability(FAR struct pci_device_s *dev, int cap)
{
  uint8_t pos;

  pos = pci_bus_find_start_cap(dev->bus, dev->devfn, dev->hdr_type);
  if (pos)
    {
      pos = pci_find_next_cap(dev->bus, dev->devfn, pos, cap);
    }

  return pos;
}

/****************************************************************************
 * Name: pci_find_next_capability
 *
 * Description:
 *   To find the next capability.
 *
 * Input Parameters:
 *   dev - The PCI device to find capbilities
 *   pos - List node
 *   cap - Value of capabilities
 *
 * Returned Value:
 *   Return the capability data
 *
 ****************************************************************************/

uint8_t pci_find_next_capability(FAR struct pci_device_s *dev, uint8_t pos,
                                 int cap)
{
  return pci_find_next_cap(dev->bus, dev->devfn,
                           pos + PCI_CAP_LIST_NEXT, cap);
}

/****************************************************************************
 * Name: pci_stat_line
 *
 * Description:
 *  Determine if the interrupt line is active for a given device
 *
 * Input Parameters:
 *   dev - device
 *
 * Return value:
 *   True if interrupt is active
 *
 ****************************************************************************/

bool pci_stat_line(FAR struct pci_device_s *dev)
{
  uint16_t tmp1;
  uint16_t tmp2;

  /* Interrupts enabled if Interrupt Disable is not set and Interrupt Status
   * is set.
   */

  pci_read_config_word(dev, PCI_COMMAND, &tmp1);
  pci_read_config_word(dev, PCI_STATUS, &tmp2);

  return (!(tmp1 & PCI_COMMAND_INTX_DISABLE) &&
          (tmp2 & PCI_STATUS_INTERRUPT));
}

/****************************************************************************
 * Name: pci_get_irq
 *
 * Description:
 *  Get interrupt number associated with a device PCI interrupt line
 *
 * Input Parameters:
 *   dev - PCI device
 *
 * Return value:
 *   Return interrupt number associated with a given INTx.
 *
 ****************************************************************************/

int pci_get_irq(FAR struct pci_device_s *dev)
{
  uint8_t line = 0;
  uint8_t pin = 0;

  pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &line);
  pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);

  if (dev->bus->ctrl->ops->get_irq)
    {
      return dev->bus->ctrl->ops->get_irq(dev->bus, dev->devfn, line, pin);
    }

  return -ENOTSUP;
}

/****************************************************************************
 * Name: pci_alloc_irq
 *
 * Description:
 *   Allocate MSI or MSI-X vectors
 *
 * Input Parameters:
 *   dev - PCI device
 *   irq - allocated vectors
 *   num - number of vectors
 *
 * Return value:
 *   Return the number of allocated vectors on succes or negative errno
 *   on failure.
 *
 ****************************************************************************/

int pci_alloc_irq(FAR struct pci_device_s *dev, FAR int *irq, int num)
{
  if (dev->bus->ctrl->ops->alloc_irq)
    {
      return dev->bus->ctrl->ops->alloc_irq(dev->bus, dev->devfn, irq, num);
    }

  return -ENOTSUP;
}

/****************************************************************************
 * Name: pci_release_irq
 *
 * Description:
 *   Release MSI or MSI-X vectors
 *
 * Input Parameters:
 *   dev - PCI device
 *   irq - allocated vectors
 *   num - number of vectors
 *
 * Return value:
 *   Failed if return a negative value, otherwise success
 *
 ****************************************************************************/

void pci_release_irq(FAR struct pci_device_s *dev, FAR int *irq, int num)
{
#ifdef CONFIG_PCI_MSIX
  uint8_t msi = 0;
  uint8_t msix = 0;

  pci_get_msi_base(dev, &msi, &msix);
  if (msi)
    {
       pci_disable_msi(dev, msi);
    }

  if (msix)
    {
       pci_disable_msix(dev, msix, num);
    }
#endif

  if (dev->bus->ctrl->ops->release_irq)
    {
      dev->bus->ctrl->ops->release_irq(dev->bus, irq, num);
    }
}

/****************************************************************************
 * Name: pci_connect_irq
 *
 * Description:
 *   Connect MSI or MSI-X if available.
 *
 * Input Parameters:
 *   dev - PCI device
 *   irq - allocated vectors
 *   num - number of vectors
 *
 * Return value:
 *   Return -ENOSETUP if MSI/MSI-X not available. Return OK on success.
 *
 ****************************************************************************/

int pci_connect_irq(FAR struct pci_device_s *dev, FAR int *irq, int num)
{
  uint8_t msi = 0;
  uint8_t msix = 0;

  if (dev->bus->ctrl->ops->connect_irq == NULL)
    {
      return -ENOTSUP;
    }

  /* Get MSI base */

  pci_get_msi_base(dev, &msi, &msix);
  if (msi == 0 && msix == 0)
    {
      /* MSI and MSI-X not supported */

      return -ENOTSUP;
    }

  /* Configure MSI or MSI-X */

#ifdef CONFIG_PCI_MSIX
  if (msix != 0)
    {
      /* Disalbe MSI */

      if (msi != 0)
        {
          pci_disable_msi(dev, msi);
        }

      /* Enable MSI-X */

      return pci_enable_msix(dev, irq, num, msix);
    }
  else
#endif
    {
      /* Enable MSI */

      return pci_enable_msi(dev, irq, num, msi);
    }
}

/****************************************************************************
 * Name: pci_enable_irq
 *
 * Description:
 *   Enable legacy irq if available.
 *
 * Input Parameters:
 *   dev - PCI device
 *   irq - allocated vectors
 *
 ****************************************************************************/

void pci_enable_irq(FAR struct pci_device_s *dev, int irq)
{
  uint16_t command;
  uint8_t line;

  pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &line);
  pci_read_config_word(dev, PCI_COMMAND, &command);
  command &= ~PCI_COMMAND_INTX_DISABLE;
  pci_write_config_word(dev, PCI_COMMAND, command);
  if (line == 0)
    {
      pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
    }
}

/****************************************************************************
 * Name: pci_disable_irq
 *
 * Description:
 *   Disable legacy irq.
 *
 * Input Parameters:
 *   dev - PCI device
 *
 ****************************************************************************/

void pci_disable_irq(FAR struct pci_device_s *dev)
{
  uint16_t command;
  pci_read_config_word(dev, PCI_COMMAND, &command);
  command |= PCI_COMMAND_INTX_DISABLE;
  pci_write_config_word(dev, PCI_COMMAND, command);
}

/****************************************************************************
 * Name: pci_register_driver
 *
 * Description:
 *   To register a PCI driver
 *
 * Input Parameters:
 *   drv - PCI driver
 *
 * Returned Value:
 *   Failed if return a negative value, otherwise success
 *
 ****************************************************************************/

int pci_register_driver(FAR struct pci_driver_s *drv)
{
  FAR const struct pci_device_id_s *id;
  FAR struct pci_device_s *dev;
  int ret;

  DEBUGASSERT(drv != NULL && drv->probe != NULL && drv->id_table != NULL);

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return ret;
    }

  /* Add the driver to the pci driver list */

  dq_addlast(&drv->node, &g_pci_driver_queue);

  dq_for_every_entry(&g_pci_device_queue, dev, struct pci_device_s, node)
    {
      if (dev->drv != NULL)
        {
          continue;
        }

      for (id = drv->id_table; id->vendor; id++)
        {
          if (pci_match_one_device(id, dev))
            {
              dev->id = id;
              if (drv->probe(dev) >= 0)
                {
                  dev->drv = drv;
                }
            }
        }
    }

  nxmutex_unlock(&g_pci_lock);
  return ret;
}

/****************************************************************************
 * Name: pci_unregister_driver
 *
 * Description:
 *   To unregister a PCI driver
 *
 * Input Parameters:
 *   drv - PCI driver
 *
 * Returned Value:
 *   Failed if return a negative value, otherwise success
 *
 ****************************************************************************/

int pci_unregister_driver(FAR struct pci_driver_s *drv)
{
  FAR struct pci_device_s *dev;
  int ret;

  DEBUGASSERT(drv != NULL && drv->remove != NULL);

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return ret;
    }

  dq_for_every_entry(&g_pci_device_queue, dev, struct pci_device_s, node)
    {
      if (dev->drv == drv)
        {
          drv->remove(dev);
          dev->drv = NULL;
        }
    }

  dq_rem(&drv->node, &g_pci_driver_queue);

  nxmutex_unlock(&g_pci_lock);
  return ret;
}

/****************************************************************************
 * Name: pci_register_device
 *
 * Description:
 *   To register a PCI device
 *
 * Input Parameters:
 *   dev - PCI device
 *
 * Returned Value:
 *   Failed if return a negative value, otherwise success
 *
 ****************************************************************************/

int pci_register_device(FAR struct pci_device_s *dev)
{
  FAR const struct pci_device_id_s *id;
  FAR struct pci_driver_s *drv;
  int ret;

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return ret;
    }

  dq_addlast(&dev->node, &g_pci_device_queue);

  dq_for_every_entry(&g_pci_driver_queue, drv, struct pci_driver_s, node)
    {
      for (id = drv->id_table; id->vendor; id++)
        {
          if (pci_match_one_device(id, dev))
            {
              dev->id = id;
              if (drv->probe(dev) >= 0)
                {
                  dev->drv = drv;
                  goto out;
                }
            }
        }
    }

out:
  nxmutex_unlock(&g_pci_lock);
  return ret;
}

/****************************************************************************
 * Name: pci_unregister_device
 *
 * Description:
 *   To unregister a PCI device
 *
 * Input Parameters:
 *   dev - PCI device
 *
 * Returned Value:
 *   Failed if return a negative value, otherwise success
 *
 ****************************************************************************/

int pci_unregister_device(FAR struct pci_device_s *dev)
{
  int ret;

  ret = nxmutex_lock(&g_pci_lock);
  if (ret < 0)
    {
      return ret;
    }

  if (dev->drv && dev->drv->remove)
    {
      dev->drv->remove(dev);
    }

  dev->drv = NULL;
  dq_rem(&dev->node, &g_pci_device_queue);

  nxmutex_unlock(&g_pci_lock);
  return ret;
}

/****************************************************************************
 * Name: pci_register_controller
 *
 * Description:
 *   Start pci bridge enumeration process, and register pci device.
 *
 * Input Parameters:
 *   ctrl - PCI controller to register
 *
 ****************************************************************************/

int pci_register_controller(FAR struct pci_controller_s *ctrl)
{
  FAR struct pci_bus_s *bus;

  if (ctrl == NULL)
    {
      return -EINVAL;
    }

  bus = pci_alloc_bus();
  if (bus == NULL)
    {
      return -ENOMEM;
    }

  bus->ctrl = ctrl;

  ctrl->bus = bus;
  ctrl->busno = 1;

  pci_scan_bus(bus);
  pci_register_bus_devices(bus);

  nxmutex_lock(&g_pci_lock);
  dq_addlast(&ctrl->node, &g_pci_ctrl_queue);
  nxmutex_unlock(&g_pci_lock);

  return 0;
}

/****************************************************************************
 * Name: pci_bus_find_capability
 *
 * Description:
 *   Query for devices' capabilities
 *
 *   Tell if a device supports a given PCI capability.
 *
 * Input Parameters:
 *   bus    - PCI device bus belong to
 *   devfn  - PCI device number and function number
 *   cap    - Capability code
 *
 * Returned Value:
 *   Returns the address of the requested capability structure within the
 *   device's PCI configuration space or 0 in case the device does not
 *   support it.
 *
 ****************************************************************************/

uint8_t pci_bus_find_capability(FAR struct pci_bus_s *bus,
                                unsigned int devfn, int cap)
{
  uint8_t type = 0;
  uint8_t pos;

  pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &type);
  pos = pci_bus_find_start_cap(bus, devfn, type);
  if (pos)
    {
      pos = pci_find_next_cap(bus, devfn, pos, cap);
    }

  return pos;
}

/****************************************************************************
 * Name: pci_dev_register
 *
 * Description:
 *   Create an pci dev driver.
 *
 * Returned Value:
 *   Zero (OK) on success; A negated errno value on failure.
 *
 ****************************************************************************/

int pci_dev_register(void)
{
  return register_driver("/dev/pci", &g_pci_fops, 0666, NULL);
}

/****************************************************************************
 * Name: pci_bus_read_config_xxx
 *
 * Description:
 *  Read pci device config space
 *
 * Input Parameters:
 *   bus   - The PCI device to belong to
 *   devfn - The PCI device number and function number
 *   where - The register address
 *   val   - The data buf
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

PCI_BUS_READ_CONFIG(byte, uint8_t, 1)
PCI_BUS_READ_CONFIG(word, uint16_t, 2)
PCI_BUS_READ_CONFIG(dword, uint32_t, 4)

/****************************************************************************
 * Name: pci_bus_write_config_xxx
 *
 * Description:
 *  Write pci device config space
 *
 * Input Parameters:
 *   bus   - The PCI device to belong to
 *   devfn - The PCI device number and function number
 *   where - The register address
 *   val   - The data
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

PCI_BUS_WRITE_CONFIG(byte, uint8_t, 1)
PCI_BUS_WRITE_CONFIG(word, uint16_t, 2)
PCI_BUS_WRITE_CONFIG(dword, uint32_t, 4)

/****************************************************************************
 * Name: pci_bus_read_io_xxx
 *
 * Description:
 *  Read pci device io space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   where - The address to read
 *   val   - The data buffer
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

PCI_BUS_READ_IO(byte, uint8_t, 1)
PCI_BUS_READ_IO(word, uint16_t, 2)
PCI_BUS_READ_IO(dword, uint32_t, 4)

/****************************************************************************
 * Name: pci_bus_write_io_xxx
 *
 * Description:
 *  Write pci device io space
 *
 * Input Parameters:
 *   bus   - The PCI device belong to
 *   where - The address to write
 *   val   - The data
 *
 * Returned Value:
 *   Zero if success, otherwise nagative
 *
 ****************************************************************************/

PCI_BUS_WRITE_IO(byte, uint8_t, 1)
PCI_BUS_WRITE_IO(word, uint16_t, 2)
PCI_BUS_WRITE_IO(dword, uint32_t, 4)