/**************************************************************************
 * Copyright (C) 2020-2020  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : user_gpio.c
 * Author      : yuanshifeng@unisound.com
 * Date        : 2021.03.01
 *
 **************************************************************************/
#include "user_gpio.h"
#include "printf.h"
#include "fakelibc.h"
#include "board_misc_config.h"

#define LOG_TAG "[user_gpio]"

static pwm_info_t g_pwm_info[GPIO_PWM_MAX] = {0};

void user_pin_set_func(UserPinNum user_pin, UserPinFunc user_func) {
#ifdef CONFIG_HW_EXT_FLASH
  if (user_pin == 7 || user_pin == 8 || user_pin == 9 || user_pin == 10) {
    printf(LOG_TAG "warning: %d pin is used as ext flash spi!\n", user_pin);
    return;
  }
#endif
  switch (user_func) {
    case USER_PIN_FUNC_GPIO_PWM:
      if (user_pin != 2) {
        padmux_set(user_pin, 1);
      } else {
        padmux_set(user_pin, 0);
      }
      break;
    case USER_PIN_FUNC_UART:
      if (user_pin == 5 || user_pin == 6 || user_pin == 11 || user_pin == 12) {
        padmux_set(user_pin, 0);
      } else {
        printf(LOG_TAG "error: %d pin not support uart func\n", user_pin);
      }
      break;
    case USER_PIN_FUNC_DAC:
      if (user_pin == 5 || user_pin == 6) {
        padmux_set(user_pin, 4);
      } else if (user_pin == 7 || user_pin == 8 || user_pin == 11 || user_pin == 12) {
        padmux_set(user_pin, 7);
      } else {
        printf(LOG_TAG "error: %d pin not support DAC func\n", user_pin);
      }
      break;
    default:
      printf(LOG_TAG "error: unknown func %d\n", user_func);
      break;
  }
}

int user_gpio_set_direction(UserPinNum user_pin, UNI_GPIO_DIRECTION direction) {
  return uni_gpio_set_direction(user_pin, direction);
}

int user_gpio_set_level(UserPinNum user_pin, UNI_GPIO_LEVEL level) {
  return uni_gpio_set_level(user_pin, level);
}

int user_pwm_init(UserPinNum user_pin, uint32_t freq, uint8_t inverse,
                  uint8_t duty) {
  int i;
  int pwm_index = -1;
  for (i = 0; i < GPIO_PWM_MAX; ++i) {
    if (g_pwm_info[i].used == 0) {
      pwm_index = i;
      break;
    }
  }
  if (pwm_index == -1) {
    printf(LOG_TAG "error:no more pwm channel can init\n");
    return -1;
  }
  g_pwm_info[pwm_index].used = 1;
  g_pwm_info[pwm_index].mapped_gpio_port = user_pin;
  g_pwm_info[pwm_index].freq = freq;
  g_pwm_info[pwm_index].inverse = inverse;
  g_pwm_info[pwm_index].duty = duty;
  return 0;
}

int _find_pwm_info(uint32_t pwm_port) {
  int i;
  for (i = 0; i < GPIO_PWM_MAX; ++i) {
    if (g_pwm_info[i].used == 1 && g_pwm_info[i].mapped_gpio_port == pwm_port) {
      return i;
    }
  }
  return -1;
}

int user_pwm_duty_inc(uint32_t pwm_port, uint8_t det_duty) {
  int pwm_index = _find_pwm_info(pwm_port);
  int new_duty = 0;

  if (pwm_index == -1) {
    printf(LOG_TAG "error:pwm_port %d is not init\n", pwm_port);
    return -1;
  }

  if (g_pwm_info[pwm_index].duty + det_duty > 100) {
    new_duty = 100;
  } else {
    new_duty = g_pwm_info[pwm_index].duty + det_duty;
  }

  return user_pwm_enable(pwm_port, new_duty);
}

int user_pwm_duty_dec(uint32_t pwm_port, uint8_t det_duty) {
  int pwm_index = _find_pwm_info(pwm_port);
  int new_duty = 0;

  if (pwm_index == -1) {
    printf(LOG_TAG "error:pwm_port %d is not init\n", pwm_port);
    return -1;
  }

  if (g_pwm_info[pwm_index].duty - det_duty < 0 ||
      g_pwm_info[pwm_index].duty - det_duty > 100) {
    new_duty = 0;
  } else {
    new_duty = g_pwm_info[pwm_index].duty - det_duty;
  }

  return user_pwm_enable(pwm_port, new_duty);
}

int user_pwm_disable(uint32_t pwm_port) {
  return uni_gpio_disable_pwm(pwm_port);
}

int user_pwm_enable(uint32_t pwm_port, uint8_t duty) {
  int ret = 0;
  unsigned long int period_ns = 0;
  unsigned long int duty_ns = 0;
  int pwm_index = _find_pwm_info(pwm_port);

  if (pwm_index == -1) {
    printf(LOG_TAG "error:pwm_port %d is not init\n", pwm_port);
    return -1;
  }

  if (g_pwm_info[pwm_index].freq > PWM_HZ_MAX ||
      g_pwm_info[pwm_index].freq <= 0) {
    printf(LOG_TAG "error:pwm freq %d is invalid\n",
           g_pwm_info[pwm_index].freq);
    return -1;
  }

  if (duty > 100) {
    printf(LOG_TAG "error:pwm duty %d is invalid\n", duty);
    return -1;
  }

  period_ns = NSEC_PER_SEC / g_pwm_info[pwm_index].freq;
  if (0 == g_pwm_info[pwm_index].inverse) {
    duty_ns = period_ns * duty / 100;
  } else {
    duty_ns = period_ns * (100 - duty) / 100;
  }
  ret = uni_gpio_enable_pwm(g_pwm_info[pwm_index].mapped_gpio_port, period_ns,
                            duty_ns);
  if (ret < 0) {
    printf(LOG_TAG "error:pin %d pwm enable failed\n",
           g_pwm_info[pwm_index].mapped_gpio_port);
  } else {
    g_pwm_info[pwm_index].duty = duty;
  }
  return ret;
}

static int _get_uart_port_num(UserPinNum user_pin) {
  if (user_pin == 5 || user_pin == 6) {
    return 0;
  }

  if (user_pin == 11 || user_pin == 12) {
    return 1;
  }

  return -1;
}

int user_uart_init(UserPinNum user_pin, uint32_t baudrate, int data_bits,
                   int stop_bits, int parity) {
  int ret = 0;
  int port = _get_uart_port_num(user_pin);

#ifdef CONFIG_MCU_ENABLE_PRINTF
  if (DEBUG_UART == port) {
    printf(LOG_TAG
           "WARNING: uart port %s is used by printf debug, please undefine "
           "CONFIG_MCU_ENABLE_PRINTF if you want to use it\n",
           port);
  }
#endif

  if (port < 0 || port > 1) {
    printf(LOG_TAG "error:uart port %d invalid\n", port);
    return -1;
  }

  if (data_bits != 8) {
    printf(LOG_TAG "WARN:uart data_bits support 8 only\n");
  }

  if (stop_bits != 1) {
    printf(LOG_TAG "WARN:uart stop_bits support 1 only\n");
  }

  if (parity != 0) {
    printf(LOG_TAG "WARN:uart parity support none only\n");
  }
  ret = uni_uart_init(port, baudrate);
  return ret;
};

static int _usr_gpio_pulse_callback(void *priv) {
  gpio_pulse_t *gpio_pulse = (gpio_pulse_t *)priv;
  if (gpio_pulse->is_top) {
    user_gpio_set_level(gpio_pulse->port, !gpio_pulse->def_val);
    gpio_pulse->is_top = 0;
  } else {
    user_gpio_set_level(gpio_pulse->port, gpio_pulse->def_val);
    gpio_pulse->is_top = 1;
    gpio_pulse->times--;
    if (gpio_pulse->times <= 0) {
      uni_timer_unregister(gpio_pulse->timer_id);
      uni_free(gpio_pulse);
    }
  }
  return 0;
}

int user_sw_timer_gpio_pulse(int port, int period_ms, int times,
                             uint8_t dev_val) {
  gpio_pulse_t *gpio_pulse = uni_malloc(sizeof(gpio_pulse_t));
  memset(gpio_pulse, 0, sizeof(gpio_pulse_t));
  gpio_pulse->port = port;
  gpio_pulse->times = times;
  gpio_pulse->def_val = dev_val;
  gpio_pulse->period_ms = (period_ms + 1) / 2;
  gpio_pulse->timer_id = uni_timer_register(
    (int (*)(void *))_usr_gpio_pulse_callback, gpio_pulse->period_ms,
    gpio_pulse, UNI_TIMER_MODE_CONTINUE);
  if (gpio_pulse->timer_id < 0) {
    printf(LOG_TAG "error:%s timer register failed\n", __func__);
    return -1;
  }
  user_gpio_set_level(gpio_pulse->port, gpio_pulse->def_val);
  gpio_pulse->is_top = 1;
  return 0;
}
