/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple LNXALL Hardware Abstraction Layer
 *
 * 2024/11/12
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>

#include "lnxall_hal.h"
#include "lnxhal_gpio.h"
#include "lnxall_hal_private.h"

static uint64_t lnxhal_uptimsec(uint64_t * out)
{
	uint64_t ret;
	struct timespec t;

	clock_gettime(CLOCK_MONOTONIC, &t);
	ret = (uint64_t) t.tv_sec;
	ret = ret * 1000 + (uint64_t) (t.tv_nsec / 1000000);
	if (out != NULL)
		*out = ret;
	return ret;
}

static char * lnxhal_readfile(const char * file, size_t * flen)
{
	int ret, fd, error;
	char * fdat;
	struct stat fst;
	size_t fsize;
	ssize_t readlen;

	error = 0;
	fsize = 0;
	fdat = NULL;
	fd = open(file, O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		return NULL;

	ret = fstat(fd, &fst);
	if (ret < 0) {
		error = errno;
		close(fd);
		errno = error;
		return NULL;
	}

	if (S_ISREG(fst.st_mode))
		fsize = (size_t) fst.st_size;
	if (fsize <= 8192)
		fsize = 8192;

	fdat = (char *) malloc(fsize + 1);
	if (fdat == NULL) {
		error = ENOMEM;
		close(fd);
		errno = error;
		return NULL;
	}

	readlen = read(fd, fdat, fsize);
	if (readlen < 0) {
		error = errno;
		free(fdat);
		close(fd);
		errno = error;
		return NULL;
	}

	close(fd);
	fdat[readlen] = '\0';
	if (flen != NULL)
		*flen = (size_t) readlen;
	return fdat;
}

const char * lnxhal_hw_getname(enum lnxhal_hwtype hwt)
{
	const char * ret;

	switch (hwt) {
	case LNXHAL_HWTYPE_RK3568:
		ret = "WLOSANY_RK3568";
		break;

	case LNXHAL_HWTYPE_RK3568_BAU:
		ret = "WLOSANY_BAU-B30-003";
		break;

	case LNXHAL_HWTYPE_RK3568_XDDL:
		ret = "WLOSANY_RK3568_XDDL";
		break;

	case LNXHAL_HWYTPE_IMX6UL_5F8280:
		ret = "WLOSANY_LC100";
		break;

	case LNXHAL_HWTYPE_EM1000:
		ret = "WLOSANY_EM1000";
		break;

	case LNXHAL_HWTYPE_RK3588:
		ret = "WLOSANY_RK3588";
		break;

	case LNXHAL_HWTYPE_UNKNOWN:
	default:
		ret = "unknown";
		break;
	}
	return ret;
}

enum lnxhal_hwtype lnxhal_hw_gettype(void)
{
	char * p;
	enum lnxhal_hwtype ret;

	ret = LNXHAL_HWTYPE_UNKNOWN;
	p = lnxhal_readfile("/proc/cpuinfo", NULL);
	if (p != NULL) {
		if (strstr(p, "Freescale i.MX6 Ultralite") != NULL) {
			ret = LNXHAL_HWYTPE_IMX6UL_5F8280;
			goto gottype;
		}

		free(p);
		p = NULL;
	}

	p = lnxhal_readfile("/proc/device-tree/model", NULL);
	if (p != NULL) {
		if (strstr(p, "Texas Instruments AM654") != NULL) {
			ret = LNXHAL_HWTYPE_EM1000;
		} else if (strstr(p, "XDDL3568") != NULL) {
			ret = LNXHAL_HWTYPE_RK3568_XDDL;
		} else if (strstr(p, "RK3568") != NULL) {
			ret = LNXHAL_HWTYPE_RK3568;
			if (access("/dev/i2c-6", F_OK) == -1 &&
				access("/dev/i2c-4", F_OK) == 0)
				ret = LNXHAL_HWTYPE_RK3568_BAU;
		} else if (strstr(p, "RK3588") != NULL) {
			ret = LNXHAL_HWTYPE_RK3588;
		}

		free(p);
		p = NULL;
	}

gottype:
	if (p != NULL)
		free(p);
	return ret;
}

static int lnxhal_hwhas_power_detect(enum lnxhal_hwtype hwtype)
{
	if (hwtype == LNXHAL_HWTYPE_RK3568) {
		if (access("/sys/bus/spi/drivers/mcp251xfd/spi0.0", F_OK) == 0 ||
			access("/sys/bus/spi/drivers/mcp251xfd/spi0.1", F_OK) == 0)
			return 1;
		return 0;
	}

	if (hwtype == LNXHAL_HWTYPE_RK3568_BAU)
		return 1;

	if (hwtype == LNXHAL_HWTYPE_RK3568_XDDL)
		return 0;

	if (hwtype == LNXHAL_HWYTPE_IMX6UL_5F8280)
		return 1;

	if (hwtype == LNXHAL_HWTYPE_EM1000)
		return 0; // Yet to be supported

	if (hwtype == LNXHAL_HWTYPE_RK3588)
		return 1;

	return 0;
}

int lnxhal_hw_has(int hwc)
{
	int ret;
	enum lnxhal_hwtype hwt;

	ret = 0;
	hwt = lnxhal_hw_gettype();
	switch (hwc) {
	case LNXHAL_HWCAP_POWER_DETECT:
		ret = lnxhal_hwhas_power_detect(hwt);
		break;

	default:
		break;
	}

	return ret;
}

lnxhal_power_detect_t lnxhal_powerd_get(enum lnxhal_hwtype hwt)
{
	int ret;
	struct lnxhal_power_det * pdet;

	pdet = (struct lnxhal_power_det *) calloc(0x1, sizeof(*pdet));
	if (pdet == NULL) {
		fprintf(stderr, "Error, system out of memory: %u\n",
			(unsigned int) sizeof(*pdet));
		fflush(stderr);
		return NULL;
	}
	pdet->gpio_num = -1;

	switch (hwt) {
	case LNXHAL_HWTYPE_RK3568:
		pdet->gpio_num = 73;
		pdet->power_down_level = 1;
		break;

	case LNXHAL_HWTYPE_RK3568_BAU:
		pdet->gpio_num = 28;
		pdet->power_down_level = 0;
		break;

	case LNXHAL_HWYTPE_IMX6UL_5F8280:
		pdet->gpio_num = 81;
		pdet->power_down_level = 1;
		break;

	case LNXHAL_HWTYPE_EM1000:
		break;

	case LNXHAL_HWTYPE_RK3588:
		pdet->gpio_num = 35;
		pdet->power_down_level = 1;
		break;

	default:
		break;
	}

	if (pdet->gpio_num < 0) {
		lnxhal_powerd_put(pdet);
		return NULL;
	}

	pdet->lgpio = lnxhal_gpios_init(1);
	if (pdet->lgpio == NULL) {
		lnxhal_powerd_put(pdet);
		return NULL;
	}

	pdet->lgpio->gpio_num = pdet->gpio_num;
	pdet->lgpio->isout = 0;
	ret = lnxhal_gpio_open(pdet->lgpio, 1);
	if (ret < 0) {
		lnxhal_powerd_put(pdet);
		fprintf(stderr, "Error, failed to initialize Powerdown GPIO: %d\n", ret);
		fflush(stderr);
		return NULL;
	}

	return (lnxhal_power_detect_t) pdet;
}

int lnxhal_powerd_poll(lnxhal_power_detect_t pdet_, int timeout, int * ispowerdown)
{
	int ret, pd;
	uint64_t t0, t1;
	struct lnxhal_power_det * pdet;

	ret = pd = 0;
	pdet = (struct lnxhal_power_det *) pdet_;
	if (pdet == NULL || pdet->lgpio == NULL)
		return -1;

	if (ispowerdown != NULL)
		*ispowerdown = 0;

	lnxhal_gpio_getval(pdet->lgpio, 1);
	if (pdet->lgpio->gpio_val == pdet->power_down_level) {
		pd = 1;
		goto next;
	}

retry:
	t0 = lnxhal_uptimsec(NULL);
	if (timeout != 0)
		ret = lnxhal_gpio_poll(pdet->lgpio, 1, timeout);
	if (ret < 0) {
		fprintf(stderr, "Error, Power-Down-Detect has failed: %d\n", ret);
		fflush(stderr);
		return -1;
	}

	lnxhal_gpio_getval(pdet->lgpio, 1);
	if (pdet->lgpio->gpio_val == pdet->power_down_level) {
		pd = 1;
		goto next;
	}

	if (timeout < 0)
		goto retry;
	t1 = lnxhal_uptimsec(NULL);
	if ((t1 - t0) < (uint64_t) timeout) {
		int delta;
		struct timespec t;

		t.tv_sec = 0;
		t.tv_nsec = 250 * 1000000;
		nanosleep(&t, NULL);

		t1 = lnxhal_uptimsec(NULL);
		delta = (int) (t1 - t0);
		timeout -= delta;
		if (timeout > 0)
			goto retry;
	}

next:
	if (ispowerdown != NULL)
		*ispowerdown = pd;
	return pd;
}

void lnxhal_powerd_put(lnxhal_power_detect_t pdet_)
{
	struct lnxhal_power_det * pdet;

	pdet = (struct lnxhal_power_det *) pdet_;
	if (pdet == NULL)
		return;

	pdet->gpio_num = -1;
	if (pdet->lgpio != NULL) {
		lnxhal_gpio_free(pdet->lgpio, 1);
		pdet->lgpio = NULL;
	}
	free(pdet);
}
