/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * facini common for EM1000 device
 *
 * 2023/10/31
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "nvmem.h"
#include "lnxall_facini.h"

int facini_validate(const struct lnxall_facini * facini)
{
	unsigned int chksum;
	unsigned int cursize, iter;
	const unsigned int * p0;

	if (facini == NULL)
		return -1;

	if (facini->magic0 != LNXALL_MAGIC0)
		return -2;

	if (facini->magic1 != LNXALL_MAGIC1)
		return -3;

	chksum = FACINI_XOR_INIT;
	p0 = (const unsigned int *) facini;
	cursize = FACINI_SIZE - sizeof(unsigned int);

	for (iter = 0; iter < cursize;) {
		chksum ^= *p0++;
		iter += sizeof(*p0);
	}

	if (chksum != facini->chksum) {
		fprintf(stderr, "Error, invalid facini checksum, expected: %#x, read: %#x\n",
			chksum, facini->chksum);
		fflush(stderr);
		return -4;
	}

	return 0;
}

int facini_find(nvmem_t * pnv, struct lnxall_facini * facini)
{
	int ret;
	int offs, step;

	ret = -1;
	step = (int) FACINI_ALIGN;
	offs = FACINI_OFFSET_END - 1;

	if (facini == NULL)
		return ret;

	offs &= ~(step - 1);

	while (offs >= FACINI_OFFSET_BEGIN) {
		int rl1;

		memset(facini, 0, FACINI_SIZE);
		rl1 = nvmem_read(pnv, offs, facini, FACINI_SIZE);
		if (rl1 != (int) FACINI_SIZE) {
			int error = errno;
			fprintf(stderr, "Error, nvmem read at offset %d has failed: %d => %s\n",
				offs, rl1, strerror(error));
			fflush(stderr);
		} else if (facini_validate(facini) == 0) {
			ret = offs;
			break;
		}

		offs -= step;
	}

	return ret;
}

int facini_checksum(struct lnxall_facini * facini)
{
	const unsigned int * p0;
	unsigned int iter, cursize, chksum;

	if (facini == NULL)
		return -1;

	facini->magic0 = LNXALL_MAGIC0;
	facini->magic1 = LNXALL_MAGIC1;
	p0 = (const unsigned int *) facini;
	cursize = FACINI_SIZE - sizeof(unsigned int);

	chksum = FACINI_XOR_INIT;
	for (iter = 0; iter < cursize;) {
		chksum ^= *p0++;
		iter += sizeof(*p0);
	}

	facini->chksum = chksum;
	facini_validate(facini);
	return 0;
}

int facini_write(nvmem_t * pnv, int offs, const struct lnxall_facini * facini)
{
	int ret;
	void * what = (void *) facini;

	ret = nvmem_write(pnv, offs, what, FACINI_SIZE);
	if (ret == FACINI_SIZE)
		return offs;

	fprintf(stderr, "Error, failed to write factory information at offset %d: %d\n",
		offs, ret);
	fflush(stderr);

	offs += FACINI_ALIGN;
	while (offs < FACINI_OFFSET_END) {
		ret = nvmem_write(pnv, offs, what, FACINI_SIZE);
		if (ret == FACINI_SIZE)
			return offs;
		fprintf(stderr, "Error, failed to write factory information at offset %d: %d\n",
			offs, ret);
		fflush(stderr);

		offs += FACINI_ALIGN;
	}

	return -1;
}
