/******************************************************************************
 * file    lib/hardware/hwgpio.c 
 * author  YuLiang
 * version 1.0.0
 * date    24-Nov-2021
 * brief   This file provides all the gpio operation functions.
 *
 ******************************************************************************
 * Attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2021 LandPower</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of LandPower nor the names of its contributors may be used to 
 *      endorse or promote products derived from this software without specific
 *      prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* 标准C库头文件. */
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* 用户代码头文件. */
#include "hwgpio.h"
#include "cmd.h"

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private typedef -----------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static array_t *gpios = NULL;

int32_t gpio_run_idx;
int32_t gpio_wdg_idx;

/* Private function prototypes -----------------------------------------------*/

/* Internal functions --------------------------------------------------------*/
int32_t _gpio_name_get(uint8_t gpio, char *name)
{
    if (!name)
    {
        return E_NULL;
    }

    switch (gpio)
    {
    case GPIO_RUN:
        snprintf(name, DEV_NAME_STR_LEN, "run");
        break;
    
    default:
        snprintf(name, DEV_NAME_STR_LEN, "default");
        break;
    }

    return E_NONE;
}

CMD(gpio_show,
    gpio_show_cmd,
    "show gpio",
    SHOW_STR
    "Gpios\n")
{
    uint16_t i = 0;
    char gpio_name[DEV_NAME_STR_LEN] = {0};
    gpio_node_t *gpio_node = NULL;

    vty_out(vty, "GPIO NAME         DIR VALUE%s", VTY_NEWLINE);
    for(i = 0; i < array_active(gpios); i++)
    {
        gpio_node = array_lookup(gpios, i);
        if (!gpio_node)
        {
            continue;
        }

        if (gpio_node->curr_dir == GPIO_DIR_IN)
        {
            gpio_val_get(gpio_node->index, &gpio_node->curr_val);
        }

        _gpio_name_get(gpio_node->gpio, gpio_name);

        vty_out(vty, "%4d %-12s %-03s %d%s", gpio_node->gpio, gpio_name, gpio_node->curr_dir == GPIO_DIR_IN ? "in" : "out",
            gpio_node->curr_val, VTY_NEWLINE);
    }

    return CMD_SUCCESS;
}

/* Interface functions -------------------------------------------------------*/
/* 根据 gpio 找到对应的结构体, 如果不存在, 返回NULL. */
gpio_node_t *gpio_get_by_gpio(uint16_t gpio)
{
    uint16_t i = 0;
    gpio_node_t *gpio_node = NULL;

    for(i = 0; i < array_active(gpios); i++)
    {
        gpio_node = array_lookup(gpios, i);
        if (!gpio_node)
        {
            continue;
        }

        if (gpio_node->gpio == gpio)
        {
            return gpio_node;
        }
    }

    return NULL;
}

/* 根据数组 index 找到对应的结构体, 如果不存在, 返回 NULL. */
gpio_node_t *gpio_get_by_index(uint16_t index)
{
    return (gpio_node_t*)array_lookup(gpios, index);
}

/* 设置gpio输出值 */
int32_t gpio_val_set(uint16_t index, uint8_t value)
{
    int32_t fd = 0;
    char buf[40] = {0};
    gpio_node_t *gpio_node = gpio_get_by_index(index);

    /* 参数检查 */
    if (!gpio_node)
    {
        DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index);
        return E_NOT_FOUND;
    }

    /* 只有out方向可以设置值 */
    if (gpio_node->curr_dir != GPIO_DIR_OUT)
    {
        DBG(DBG_M_GPIO, "gpio %d direction is not out\r\n", gpio_node->gpio);
        return E_BAD_PARAM;
    }

    /* 如果值相等就不需要再操作了 */
    if (gpio_node->curr_val == value)
    {
        return E_NONE;
    }

    /* 打开文件 */
    snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio_node->gpio);
    fd = open(buf, O_WRONLY);
    if (fd <= 0) 
    {
        DBG(DBG_M_GPIO, "Open %s error", buf); 
        return E_SYS_CALL;
    }

    /* 设置值 */
    snprintf(buf, sizeof(buf), "%d", (value == 0) ? 0 : 1);
    if (write(fd, buf, 1) <= 0)
    {
        DBG(DBG_M_GPIO, "Write gpio %d value error", gpio_node->gpio); 
        return E_SYS_CALL;
    }
    close(fd);

    gpio_node->curr_val = value;
    return E_NONE;
}

/* 设置gpio输出值 */
int32_t gpio_val_get(uint16_t index, uint8_t *value)
{
    int32_t fd = 0;
    char buf[40] = {0};
    gpio_node_t *gpio_node = gpio_get_by_index(index);

    /* 参数检查 */
    if (!gpio_node)
    {
        DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index);
        return E_NOT_FOUND;
    }

    /* out方向直接读取 */
    if (GPIO_DIR_OUT == gpio_node->curr_dir)
    {
        *value = gpio_node->curr_val;
        return E_NONE;
    }

    /* 打开文件 */
    snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio_node->gpio);
    fd = open(buf, O_RDONLY);
    if (fd <= 0) 
    {
        DBG(DBG_M_GPIO, "Open %s error", buf); 
        return E_SYS_CALL;
    }

    /* 读取值 */
    memset(buf, 0, sizeof(buf));
    if (read(fd, buf, sizeof(buf) - 1) <= 0)
    {
        DBG(DBG_M_GPIO, "Read gpio %d value error", gpio_node->gpio); 
        return E_SYS_CALL;
    }
    close(fd);

    *value = strtol(buf, NULL, 10);
    return E_NONE;
}

/* 设置gpio方向 */
int32_t gpio_dir_set(uint16_t index, uint8_t dir)
{
    int32_t fd = 0;
    char buf[40] = {0};
    gpio_node_t *gpio_node = gpio_get_by_index(index);

    /* 参数检查 */
    if (!gpio_node)
    {
        DBG(DBG_M_GPIO, "gpio index %d is not found\r\n", index);
        return E_NOT_FOUND;
    }

    /* 如果方向相等,不用继续操作 */
    if (gpio_node->curr_dir == dir)
    {
        return E_NONE;
    }

    /* 打开文件 */
    snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio_node->gpio);
    fd = open(buf, O_WRONLY);
    if (fd <= 0) 
    {
        DBG(DBG_M_GPIO, "Open %s error", buf); 
        return E_SYS_CALL;
    }

    /* 设置方向 */
    snprintf(buf, sizeof(buf), "%s", (dir == GPIO_DIR_IN) ? "in" : "out");
    if (write(fd, buf, strlen(buf)) <= 0)
    {
        DBG(DBG_M_GPIO, "Write gpio %d direction error", gpio_node->gpio); 
        return E_SYS_CALL;
    }
    close(fd);

    gpio_node->curr_dir = dir;
    return E_NONE;
}

/* 导出 GPIO, 返回值大于等于 0, 表示加入数组的 index; 小于 0 表示失败. */
int32_t gpio_export(uint16_t gpio)
{
    int32_t fd = 0;
    char buf[40] = {0};
    gpio_node_t *gpio_node = gpio_get_by_gpio(gpio);

    /* 如果找到表示已经 export. */
    if (gpio_node)
    {
        return -1;
    }

    /* 文件不存在则导出, 存在不做操作. */
    snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d", gpio);
    if (access(buf, 0) < 0)
    {
        /* 打开文件描述符 */
        fd = open("/sys/class/gpio/export", O_WRONLY);
        if (fd <= 0) 
        {
            log_err(LOG_GPIO, "Open /sys/class/gpio/export error!"); 
            return -2;
        }

        /* 导出gpio */
        snprintf(buf, sizeof(buf), "%d", gpio);
        if (write(fd, buf, strlen(buf)) <= 0)
        {
            log_err(LOG_GPIO, "Write /sys/class/gpio/export(%d) error!", gpio); 
            return -3;
        }
        close(fd);
    }

    /* 添加结构体 */
    gpio_node = XMALLOC_Q(MTYPE_GPIO, sizeof(gpio_node_t));
    gpio_node->gpio = gpio;
    /* 默认值是不确定的 */
    gpio_node->curr_val = 0xff;
    gpio_node->is_export = TRUE;
    gpio_node->index = array_append(gpios, gpio_node, MTYPE_GPIO);
    
    return gpio_node->index;
}

/* 初始化函数 */
int32_t gpio_init(void)
{
    gpios = array_init(ARRAY_MIN_SIZE, MTYPE_GPIO);

    cmd_install_element(COMMON_NODE, &gpio_show_cmd);

    gpio_run_idx = gpio_export(GPIO_RUN);
    if (gpio_run_idx < 0)
    {
        DBG(DBG_M_GPIO, "ERROR return %d!\r\n", gpio_run_idx);
        return E_BAD_PARAM;
    }
    LD_E_RETURN(DBG_M_GPIO, gpio_dir_set(gpio_run_idx, GPIO_DIR_OUT));

    gpio_wdg_idx = gpio_export(GPIO_WDG);
    if (gpio_wdg_idx < 0)
    {
        DBG(DBG_M_GPIO, "ERROR return %d!\r\n", gpio_wdg_idx);
        return E_BAD_PARAM;
    }
    LD_E_RETURN(DBG_M_GPIO, gpio_dir_set(gpio_wdg_idx, GPIO_DIR_OUT));

    return E_NONE;
}
/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/
