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

#include <dt-bindings/pin/pin.h>

#include "pin_dm.h"

static const char * const gpio_suffixes[] =
{
    "gpios", "gpio"
};

rt_ssize_t rt_ofw_get_named_pin(struct rt_ofw_node *np, const char *propname, int index,
        rt_uint8_t *out_mode, rt_uint8_t *out_value)
{
    rt_ssize_t pin = -1;
    rt_uint8_t mode;
    rt_uint8_t value;
    rt_uint32_t flags;
    char gpios_name[64];
    struct rt_device_pin *pin_dev = 0;
    struct rt_ofw_node *pin_dev_np = 0;
    struct rt_ofw_cell_args pin_args = {0};

    if (!np && index < 0)
    {
        return -RT_EINVAL;
    }

    for (int i = 0; i < RT_ARRAY_SIZE(gpio_suffixes); ++i)
    {
        if (propname)
        {
            rt_snprintf(gpios_name, sizeof(gpios_name), "%s-%s", propname, gpio_suffixes[i]);
        }
        else
        {
            rt_snprintf(gpios_name, sizeof(gpios_name), "%s", gpio_suffixes[i]);
        }

        pin = rt_ofw_parse_phandle_cells(np, gpios_name, "#gpio-cells", index, &pin_args);

        if (pin >= 0)
        {
            break;
        }
    }

    if (pin < 0)
    {
        return pin;
    }

    pin_dev_np = pin_args.data;
    pin_dev = rt_ofw_data(pin_dev_np);

    if (!pin_dev)
    {
        pin = -RT_ERROR;

        goto _out_converts;
    }

    value = PIN_LOW;
    mode = PIN_MODE_OUTPUT;

    if (pin_dev->ops->pin_parse)
    {
        pin = pin_dev->ops->pin_parse(&pin_dev->parent, &pin_args, &flags);
    }
    else
    {
        /*
         * We always assume that the args[0] is the pin number if driver not
         * implemented `pin_parse`.
         */
        pin = pin_args.args[0];

        goto _out_converts;
    }

    if (out_mode)
    {
        if (flags & PIN_OPEN_DRAIN)
        {
            mode = PIN_MODE_OUTPUT_OD;
        }

        switch (flags & RT_GENMASK(6, 4))
        {
        case PIN_PULL_UP:
            mode = PIN_MODE_INPUT_PULLUP;
            break;

        case PIN_PULL_DOWN:
            mode = PIN_MODE_INPUT_PULLDOWN;
            break;

        case PIN_PULL_DISABLE:
            mode = PIN_MODE_INPUT;
            break;
        }
    }

    if (out_value)
    {
        if (flags == (PIN_ACTIVE_HIGH | PIN_PUSH_PULL))
        {
            value = PIN_HIGH;
        }
        else if (flags == (PIN_ACTIVE_LOW | PIN_PUSH_PULL))
        {
            value = PIN_LOW;
        }
    }

_out_converts:
    rt_ofw_node_put(pin_dev_np);

    if (out_mode)
    {
        *out_mode = mode;
    }

    if (out_value)
    {
        *out_value = value;
    }

    return pin;
}

rt_ssize_t rt_ofw_get_named_pin_count(struct rt_ofw_node *np, const char *propname)
{
    char gpios_name[64];
    rt_ssize_t count = 0;

    if (!np || !propname)
    {
        return -RT_EINVAL;
    }

    for (int i = 0; i < RT_ARRAY_SIZE(gpio_suffixes); ++i)
    {
        if (propname)
        {
            rt_snprintf(gpios_name, sizeof(gpios_name), "%s-%s", propname, gpio_suffixes[i]);
        }
        else
        {
            rt_snprintf(gpios_name, sizeof(gpios_name), "%s", gpio_suffixes[i]);
        }

        count = rt_ofw_count_phandle_cells(np, propname, "#gpio-cells");

        if (count > 0)
        {
            break;
        }
    }

    return count;
}
