/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-10-24     GuEe-GUI     first version
 */

#include <rthw.h>
#include <rtthread.h>

#define DBG_TAG "pci.ofw"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>

#include <drivers/pci.h>
#include <drivers/ofw.h>
#include <drivers/ofw_io.h>
#include <drivers/ofw_irq.h>
#include <drivers/ofw_fdt.h>

static rt_err_t pci_ofw_irq_parse(struct rt_pci_device *pdev, struct rt_ofw_cell_args *out_irq)
{
    rt_err_t err = RT_EOK;
    rt_uint8_t pin;
    fdt32_t map_addr[4];
    struct rt_pci_device *p2pdev;
    struct rt_ofw_node *dev_np, *p2pnode = RT_NULL;

    /* Parse device tree if dev have a device node */
    dev_np = pdev->parent.ofw_node;

    if (dev_np)
    {
        err = rt_ofw_parse_irq_cells(dev_np, 0, out_irq);

        if (err)
        {
            return err;
        }
    }

    /* Assume #interrupt-cells is 1 */
    if ((err = rt_pci_read_config_u8(pdev, PCIR_INTPIN, &pin)))
    {
        goto _err;
    }

    /* No pin, exit with no error message. */
    if (pin == 0)
    {
        return -RT_ENOSYS;
    }

    /* Try local interrupt-map in the device node */
    if (rt_ofw_prop_read_raw(dev_np, "interrupt-map", RT_NULL))
    {
        pin = rt_pci_irq_intx(pdev, pin);
        p2pnode = dev_np;
    }

    /* Walk up the PCI tree */
    while (!p2pnode)
    {
        p2pdev = pdev->bus->self;

        /* Is the root bus -> host bridge */
        if (rt_pci_is_root_bus(pdev->bus))
        {
            struct rt_pci_host_bridge *host_bridge = pdev->bus->host_bridge;

            p2pnode = host_bridge->parent.ofw_node;

            if (!p2pnode)
            {
                err = -RT_EINVAL;

                goto _err;
            }
        }
        else
        {
            /* Is P2P bridge */
            p2pnode = p2pdev->parent.ofw_node;
        }

        if (p2pnode)
        {
            break;
        }

        /* Try get INTx in P2P */
        pin = rt_pci_irq_intx(pdev, pin);
        pdev = p2pdev;
    }

    /* For more format detail, please read `components/drivers/ofw/irq.c:ofw_parse_irq_map` */
    out_irq->data = map_addr;
    out_irq->args_count = 2;
    out_irq->args[0] = 3;
    out_irq->args[1] = 1;

    /* In addr cells */
    map_addr[0] = cpu_to_fdt32((pdev->bus->number << 16) | (pdev->devfn << 8));
    map_addr[1] = cpu_to_fdt32(0);
    map_addr[2] = cpu_to_fdt32(0);
    /* In pin cells */
    map_addr[3] = cpu_to_fdt32(pin);

    err = rt_ofw_parse_irq_map(p2pnode, out_irq);

_err:
    if (err == -RT_EEMPTY)
    {
        LOG_W("PCI-Device<%s> no interrupt-map found, INTx interrupts not available",
                rt_dm_dev_get_name(&pdev->parent));
        LOG_W("PCI-Device<%s> possibly some PCI slots don't have level triggered interrupts capability",
                rt_dm_dev_get_name(&pdev->parent));
    }
    else if (err && err != -RT_ENOSYS)
    {
        LOG_E("PCI-Device<%s> irq parse failed with err = %s",
                rt_dm_dev_get_name(&pdev->parent), rt_strerror(err));
    }

    return err;
}

int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev,
        rt_uint8_t slot, rt_uint8_t pin)
{
    int irq = -1;
    rt_err_t status;
    struct rt_ofw_cell_args irq_args;

    if (!pdev)
    {
        goto _end;
    }

    status = pci_ofw_irq_parse(pdev, &irq_args);

    if (status)
    {
        goto _end;
    }

    irq = rt_ofw_map_irq(&irq_args);

    if (irq >= 0)
    {
        pdev->intx_pic = rt_pic_dynamic_cast(rt_ofw_data(irq_args.data));
    }

_end:
    return irq;
}

static rt_err_t pci_ofw_parse_ranges(struct rt_ofw_node *dev_np, const char *propname,
        int phy_addr_cells, int phy_size_cells, int cpu_addr_cells,
        struct rt_pci_bus_region **out_regions, rt_size_t *out_regions_nr)
{
    const fdt32_t *cell;
    rt_ssize_t total_cells;
    int groups, space_code;
    rt_uint32_t phy_addr[3];
    rt_uint64_t cpu_addr, phy_addr_size;

    *out_regions = RT_NULL;
    *out_regions_nr = 0;
    cell = rt_ofw_prop_read_raw(dev_np, propname, &total_cells);

    if (!cell)
    {
        return -RT_EEMPTY;
    }

    groups = total_cells / sizeof(*cell) / (phy_addr_cells + phy_size_cells + cpu_addr_cells);
    *out_regions = rt_malloc(groups * sizeof(struct rt_pci_bus_region));

    if (!*out_regions)
    {
        return -RT_ENOMEM;
    }

    for (int i = 0; i < groups; ++i)
    {
        /*
         * ranges:
         *  phys.hi  cell: npt000ss bbbbbbbb dddddfff rrrrrrrr
         *  phys.low cell: llllllll llllllll llllllll llllllll
         *  phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh
         *
         *  n: relocatable region flag (doesn't play a role here)
         *  p: prefetchable (cacheable) region flag
         *  t: aliased address flag (doesn't play a role here)
         *  ss: space code
         *      00: configuration space
         *      01: I/O space
         *      10: 32 bit memory space
         *      11: 64 bit memory space
         *  bbbbbbbb: The PCI bus number
         *  ddddd: The device number
         *  fff: The function number. Used for multifunction PCI devices.
         *  rrrrrrrr: Register number; used for configuration cycles.
         */

        for (int j = 0; j < phy_addr_cells; ++j)
        {
            phy_addr[j] = rt_fdt_read_number(cell++, 1);
        }

        space_code = (phy_addr[0] >> 24) & 0x3;

        cpu_addr = rt_fdt_read_number(cell, cpu_addr_cells);
        cell += cpu_addr_cells;
        phy_addr_size = rt_fdt_read_number(cell, phy_size_cells);
        cell += phy_size_cells;

        (*out_regions)[i].phy_addr = ((rt_uint64_t)phy_addr[1] << 32) | phy_addr[2];
        (*out_regions)[i].cpu_addr = cpu_addr;
        (*out_regions)[i].size = phy_addr_size;

        (*out_regions)[i].bus_start = (*out_regions)[i].phy_addr;

        if (space_code & 2)
        {
            (*out_regions)[i].flags = phy_addr[0] & (1U << 30) ?
                    PCI_BUS_REGION_F_PREFETCH : PCI_BUS_REGION_F_MEM;
        }
        else if (space_code & 1)
        {
            (*out_regions)[i].flags = PCI_BUS_REGION_F_IO;
        }
        else
        {
            (*out_regions)[i].flags = PCI_BUS_REGION_F_NONE;
        }

        ++*out_regions_nr;
    }

    return RT_EOK;
}

rt_err_t rt_pci_ofw_parse_ranges(struct rt_ofw_node *dev_np,
        struct rt_pci_host_bridge *host_bridge)
{
    rt_err_t err;
    int phy_addr_cells = -1, phy_size_cells = -1, cpu_addr_cells;

    if (!dev_np || !host_bridge)
    {
        return -RT_EINVAL;
    }

    cpu_addr_cells = rt_ofw_io_addr_cells(dev_np);
    rt_ofw_prop_read_s32(dev_np, "#address-cells", &phy_addr_cells);
    rt_ofw_prop_read_s32(dev_np, "#size-cells", &phy_size_cells);

    if (phy_addr_cells != 3 || phy_size_cells < 1 || cpu_addr_cells < 1)
    {
        return -RT_EINVAL;
    }

    if (pci_ofw_parse_ranges(dev_np, "ranges",
        phy_addr_cells, phy_size_cells, cpu_addr_cells,
        &host_bridge->bus_regions, &host_bridge->bus_regions_nr))
    {
        return -RT_EINVAL;
    }

    if ((err = rt_pci_region_setup(host_bridge)))
    {
        rt_free(host_bridge->bus_regions);
        host_bridge->bus_regions_nr = 0;

        return err;
    }

    err = pci_ofw_parse_ranges(dev_np, "dma-ranges",
            phy_addr_cells, phy_size_cells, cpu_addr_cells,
            &host_bridge->dma_regions, &host_bridge->dma_regions_nr);

    if (err != -RT_EEMPTY)
    {
        rt_free(host_bridge->bus_regions);
        host_bridge->bus_regions_nr = 0;

        LOG_E("%s: Read dma-ranges error = %s", rt_ofw_node_full_name(dev_np),
                rt_strerror(err));

        return err;
    }

    return RT_EOK;
}

rt_err_t rt_pci_ofw_host_bridge_init(struct rt_ofw_node *dev_np,
        struct rt_pci_host_bridge *host_bridge)
{
    rt_err_t err;
    const char *propname;

    if (!dev_np || !host_bridge)
    {
        return -RT_EINVAL;
    }

    host_bridge->irq_slot = rt_pci_irq_slot;
    host_bridge->irq_map = rt_pci_ofw_irq_parse_and_map;

    if (rt_ofw_prop_read_u32_array_index(dev_np, "bus-range", 0, 2, host_bridge->bus_range) < 0)
    {
        return -RT_EIO;
    }

    propname = rt_ofw_get_prop_fuzzy_name(dev_np, ",pci-domain$");
    rt_ofw_prop_read_u32(dev_np, propname, &host_bridge->domain);

    err = rt_pci_ofw_parse_ranges(dev_np, host_bridge);

    return err;
}

rt_err_t rt_pci_ofw_bus_init(struct rt_pci_bus *bus)
{
    rt_err_t err = RT_EOK;

    return err;
}

rt_err_t rt_pci_ofw_bus_free(struct rt_pci_bus *bus)
{
    rt_err_t err = RT_EOK;

    return err;
}

/*
 * RID (Requester ID) is formatted such that:
 *  Bits [15:8] are the Bus number.
 *  Bits [7:3] are the Device number.
 *  Bits [2:0] are the Function number.
 *
 * msi-map: Maps a Requester ID to an MSI controller and associated
 *  msi-specifier data. The property is an arbitrary number of tuples of
 *  (rid-base,msi-controller,msi-base,length), where:
 *
 *    - rid-base is a single cell describing the first RID matched by the entry.
 *
 *    - msi-controller is a single phandle to an MSI controller
 *
 *    - msi-base is an msi-specifier describing the msi-specifier produced for
 *      the first RID matched by the entry.
 *
 *    - length is a single cell describing how many consecutive RIDs are matched
 *      following the rid-base.
 *
 *  Any RID r in the interval [rid-base, rid-base + length) is associated with
 *  the listed msi-controller, with the msi-specifier (r - rid-base + msi-base).
 *
 * msi-map-mask: A mask to be applied to each Requester ID prior to being mapped
 *  to an msi-specifier per the msi-map property.
 *
 * msi-parent: Describes the MSI parent of the root complex itself. Where
 *  the root complex and MSI controller do not pass sideband data with MSI
 *  writes, this property may be used to describe the MSI controller(s)
 *  used by PCI devices under the root complex, if defined as such in the
 *  binding for the root complex.
 *
 *  / {
 *      #address-cells = <1>;
 *      #size-cells = <1>;
 *
 *      msi_a: msi-controller@a {
 *          reg = <0xa 0x1>;
 *          msi-controller;
 *          #msi-cells = <1>;
 *      };
 *
 *      msi_b: msi-controller@b {
 *          reg = <0xb 0x1>;
 *          msi-controller;
 *          #msi-cells = <1>;
 *      };
 *
 *      msi_c: msi-controller@c {
 *          reg = <0xc 0x1>;
 *          msi-controller;
 *          #msi-cells = <1>;
 *      };
 *
 *  Example (1)
 *  ===========
 *      pci: pci@f {
 *          reg = <0xf 0x1>;
 *          device_type = "pci";
 *
 *          // The sideband data provided to the MSI controller is
 *          //  the RID, identity-mapped.
 *          msi-map = <0x0 &msi_a 0x0 0x10000>;
 *      };
 *
 *  Example (2)
 *  ===========
 *      pci: pci@ff {
 *          reg = <0xff 0x1>;
 *          device_type = "pci";
 *
 *          // The sideband data provided to the MSI controller is
 *          //  the RID, masked to only the device and function bits.
 *          msi-map = <0x0 &msi_a 0x0 0x100>;
 *          msi-map-mask = <0xff>
 *      };
 *
 *  Example (3)
 *  ===========
 *      pci: pci@fff {
 *          reg = <0xfff 0x1>;
 *          device_type = "pci";
 *
 *          // The sideband data provided to the MSI controller is
 *          //  the RID, but the high bit of the bus number is ignored.
 *          msi-map = <0x0000 &msi_a 0x0000 0x8000>,
 *                    <0x8000 &msi_a 0x0000 0x8000>;
 *      };
 *
 *  Example (4)
 *  ===========
 *      pci: pci@f {
 *          reg = <0xf 0x1>;
 *          device_type = "pci";
 *
 *          // The sideband data provided to the MSI controller is
 *          //  the RID, but the high bit of the bus number is negated.
 *          msi-map = <0x0000 &msi 0x8000 0x8000>,
 *                    <0x8000 &msi 0x0000 0x8000>;
 *      };
 *
 *  Example (5)
 *  ===========
 *      pci: pci@f {
 *          reg = <0xf 0x1>;
 *          device_type = "pci";
 *
 *          // The sideband data provided to MSI controller a is the
 *          //  RID, but the high bit of the bus number is negated.
 *          // The sideband data provided to MSI controller b is the
 *          //  RID, identity-mapped.
 *          // MSI controller c is not addressable.
 *          msi-map = <0x0000 &msi_a 0x8000 0x08000>,
 *                    <0x8000 &msi_a 0x0000 0x08000>,
 *                    <0x0000 &msi_b 0x0000 0x10000>;
 *      };
 *  };
 */
static void ofw_msi_pic_init(struct rt_pci_device *pdev)
{
#ifdef RT_PCI_MSI
    rt_uint32_t rid;
    struct rt_pci_host_bridge *bridge;
    struct rt_ofw_node *np, *msi_ic_np = RT_NULL;

    /*
     * NOTE: Typically, a device's RID is equal to the PCI device's ID.
     * However, in complex bus management scenarios such as servers and PCs,
     * the RID needs to be associated with DMA. In these cases,
     * the RID should be equal to the DMA alias assigned to the
     * PCI device by the system bus.
     */
    rid = rt_pci_dev_id(pdev);

    bridge = rt_pci_find_host_bridge(pdev->bus);
    RT_ASSERT(bridge != RT_NULL);

    np = bridge->parent.ofw_node;

    if (!(msi_ic_np = rt_ofw_parse_phandle(np, "msi-parent", 0)))
    {
        rt_ofw_map_id(np, rid, "msi-map", "msi-map-mask", &msi_ic_np, RT_NULL);
    }

    if (!msi_ic_np)
    {
        LOG_W("%s: MSI PIC not found", rt_dm_dev_get_name(&pdev->parent));

        return;
    }

    pdev->msi_pic = rt_pic_dynamic_cast(rt_ofw_data(msi_ic_np));

    if (!pdev->msi_pic)
    {
        LOG_W("%s: '%s' not supported", rt_dm_dev_get_name(&pdev->parent), "msi-parent");

        goto _out_put_msi_parent_node;
    }

    if (!pdev->msi_pic->ops->irq_compose_msi_msg)
    {
        LOG_E("%s: MSI pic MUST implemented %s",
                rt_ofw_node_full_name(msi_ic_np), "irq_compose_msi_msg");
        RT_ASSERT(0);
    }

    if (!pdev->msi_pic->ops->irq_alloc_msi)
    {
        LOG_E("%s: MSI pic MUST implemented %s",
                rt_ofw_node_full_name(msi_ic_np), "irq_alloc_msi");
        RT_ASSERT(0);
    }

    if (!pdev->msi_pic->ops->irq_free_msi)
    {
        LOG_E("%s: MSI pic MUST implemented %s",
                rt_ofw_node_full_name(msi_ic_np), "irq_free_msi");
        RT_ASSERT(0);
    }

_out_put_msi_parent_node:
    rt_ofw_node_put(msi_ic_np);
#endif
}

static rt_int32_t ofw_pci_devfn(struct rt_ofw_node *np)
{
    rt_int32_t res;
    rt_uint32_t reg[5];

    res = rt_ofw_prop_read_u32_array_index(np, "reg", 0, RT_ARRAY_SIZE(reg), reg);

    return res > 0 ? ((reg[0] >> 8) & 0xff) : res;
}

static struct rt_ofw_node *ofw_find_device(struct rt_ofw_node *np, rt_uint32_t devfn)
{
    struct rt_ofw_node *dev_np, *mfd_np;

    rt_ofw_foreach_child_node(np, dev_np)
    {
        if (ofw_pci_devfn(dev_np) == devfn)
        {
            return dev_np;
        }

        if (rt_ofw_node_tag_equ(dev_np, "multifunc-device"))
        {
            rt_ofw_foreach_child_node(dev_np, mfd_np)
            {
                if (ofw_pci_devfn(mfd_np) == devfn)
                {
                    rt_ofw_node_put(dev_np);

                    return mfd_np;
                }
            }
        }
    }

    return RT_NULL;
}

rt_err_t rt_pci_ofw_device_init(struct rt_pci_device *pdev)
{
    struct rt_ofw_node *np = RT_NULL;

    if (!pdev)
    {
        return -RT_EINVAL;
    }

    ofw_msi_pic_init(pdev);

    if (rt_pci_is_root_bus(pdev->bus) || !pdev->bus->self)
    {
        struct rt_pci_host_bridge *host_bridge;

        host_bridge = rt_pci_find_host_bridge(pdev->bus);
        RT_ASSERT(host_bridge != RT_NULL);

        np = host_bridge->parent.ofw_node;
    }
    else
    {
        np = pdev->bus->self->parent.ofw_node;
    }

    if (np)
    {
        pdev->parent.ofw_node = ofw_find_device(np, pdev->devfn);
    }

    return RT_EOK;
}

rt_err_t rt_pci_ofw_device_free(struct rt_pci_device *pdev)
{
    if (!pdev)
    {
        return -RT_EINVAL;
    }

    rt_ofw_node_put(pdev->parent.ofw_node);

    return RT_EOK;
}