/* Copyright 2019 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <stdint.h>
#include <stddef.h>

#include "fpc_bio_algorithm.h"
#include "fpsensor.h"
#include "spi.h"
#include "system.h"
#include "util.h"

#include "driver/fingerprint/fpc/fpc_sensor.h"

/* Console output macros */
#define CPRINTF(format, args...) cprintf(CC_FP, format, ## args)
#define CPRINTS(format, args...) cprints(CC_FP, format, ## args)

static uint8_t enroll_ctx[FP_ALGORITHM_ENROLLMENT_SIZE] = {0};

/* Recorded error flags */
static uint16_t errors;

/* FPC specific initialization and de-initialization functions */
int fp_sensor_open(void);
int fp_sensor_close(void);

/* Get FPC library version code.*/
const char *fp_sensor_get_version(void);

/* Get FPC library build info.*/
const char *fp_sensor_get_build_info(void);

/* Sensor description */
static struct ec_response_fp_info ec_fp_sensor_info = {
	/* Sensor identification */
	.vendor_id = FOURCC('F', 'P', 'C', ' '),
	.product_id = 9,
	.model_id = 1,
	.version = 1,
	/* Image frame characteristics */
	.frame_size = FP_SENSOR_IMAGE_SIZE,
	.pixel_format = V4L2_PIX_FMT_GREY,
	.width = FP_SENSOR_RES_X,
	.height = FP_SENSOR_RES_Y,
	.bpp = FP_SENSOR_RES_BPP,
};

typedef struct fpc_bep_sensor fpc_bep_sensor_t;

typedef struct {
	const fpc_bep_sensor_t *sensor;
	uint32_t image_buffer_size;
} fpc_sensor_info_t;

#if defined(CONFIG_FP_SENSOR_FPC1025)

extern const fpc_bep_sensor_t fpc_bep_sensor_1025;
extern const fpc_bep_algorithm_t fpc_bep_algorithm_pfe_1025;

const fpc_sensor_info_t fpc_sensor_info = {
	.sensor = &fpc_bep_sensor_1025,
	.image_buffer_size = FP_SENSOR_IMAGE_SIZE,
};

const fpc_bio_info_t fpc_bio_info = {
	.algorithm = &fpc_bep_algorithm_pfe_1025,
	.template_size = FP_ALGORITHM_TEMPLATE_SIZE,
};

#elif defined(CONFIG_FP_SENSOR_FPC1035)

extern const fpc_bep_sensor_t fpc_bep_sensor_1035;
extern const fpc_bep_algorithm_t fpc_bep_algorithm_pfe_1035;

const fpc_sensor_info_t fpc_sensor_info = {
	.sensor = &fpc_bep_sensor_1035,
	.image_buffer_size = FP_SENSOR_IMAGE_SIZE,
};

const fpc_bio_info_t fpc_bio_info = {
	.algorithm = &fpc_bep_algorithm_pfe_1035,
	.template_size = FP_ALGORITHM_TEMPLATE_SIZE,
};
#else
#error "Sensor type not defined!"
#endif

/* Sensor IC commands */
enum fpc_cmd {
	FPC_CMD_DEEPSLEEP         = 0x2C,
	FPC_CMD_HW_ID             = 0xFC,
};

/* Maximum size of a sensor command SPI transfer */
#define MAX_CMD_SPI_TRANSFER_SIZE 3

/* Memory for the SPI transfer buffer */
static uint8_t spi_buf[MAX_CMD_SPI_TRANSFER_SIZE];

static int fpc_send_cmd(const uint8_t cmd)
{
	spi_buf[0] = cmd;

	return spi_transaction(SPI_FP_DEVICE, spi_buf, 1, spi_buf,
			       SPI_READBACK_ALL);
}

void fp_sensor_low_power(void)
{
	fpc_send_cmd(FPC_CMD_DEEPSLEEP);
}

static int fpc_check_hwid(void)
{
	uint16_t id;
	int rc;

	spi_buf[0] = FPC_CMD_HW_ID;

	rc = spi_transaction(SPI_FP_DEVICE, spi_buf, 3, spi_buf,
			     SPI_READBACK_ALL);
	if (rc) {
		CPRINTS("FPC HW ID read failed %d", rc);
		return FP_ERROR_SPI_COMM;
	}

	id = (spi_buf[1] << 8) | spi_buf[2];
	if ((id >> 4) != FP_SENSOR_HWID) {
		CPRINTS("FPC unknown silicon 0x%04x", id);
		return FP_ERROR_BAD_HWID;
	}
	CPRINTS(FP_SENSOR_NAME " id 0x%04x", id);

	return EC_SUCCESS;
}

/* Reset and initialize the sensor IC */
int fp_sensor_init(void)
{
	int rc;

	/* Print the binary libfpbep.a library version */
	CPRINTS("FPC libfpbep.a %s", fp_sensor_get_version());

	/* Print the BEP version and build time of the library */
	CPRINTS("Build information - %s", fp_sensor_get_build_info());

	rc = fp_sensor_open();
	if (rc) {
		errors |= FP_ERROR_INIT_FAIL;
		CPRINTS("Error: fp_sensor_open() failed, result=%d", rc);
	}

	errors |= fpc_check_hwid();

	rc = bio_algorithm_init();
	if (rc < 0) {
		errors |= FP_ERROR_INIT_FAIL;
		CPRINTS("Error: bio_algorithm_init() failed, result=%d", rc);
	}

	/* Go back to low power */
	fp_sensor_low_power();

	return EC_SUCCESS;
}

/* Deinitialize the sensor IC */
int fp_sensor_deinit(void)
{
	int rc;

	rc = bio_algorithm_exit();
	if (rc < 0)
		CPRINTS("Error: bio_algorithm_exit() failed, result=%d", rc);

	rc = fp_sensor_close();
	if (rc < 0)
		CPRINTS("Error: fp_sensor_close() failed, result=%d", rc);

	return rc;
}

int fp_sensor_get_info(struct ec_response_fp_info *resp)
{
	int rc;

	spi_buf[0] = FPC_CMD_HW_ID;

	memcpy(resp, &ec_fp_sensor_info, sizeof(struct ec_response_fp_info));

	rc = spi_transaction(SPI_FP_DEVICE, spi_buf, 3, spi_buf,
			     SPI_READBACK_ALL);
	if (rc)
		return EC_RES_ERROR;

	resp->model_id = (spi_buf[1] << 8) | spi_buf[2];
	resp->errors = errors;

	return EC_SUCCESS;
}

int fp_finger_match(void *templ, uint32_t templ_count, uint8_t *image,
		    int32_t *match_index, uint32_t *update_bitmap)
{
	int rc;

	rc = bio_template_image_match_list(templ, templ_count, image,
					   match_index, update_bitmap);
	if (rc < 0)
		CPRINTS("Error: bio_template_image_match_list() failed, result=%d",
			rc);

	return rc;
}

int fp_enrollment_begin(void)
{
	int rc;
	bio_enrollment_t bio_enroll = enroll_ctx;

	rc = bio_enrollment_begin(&bio_enroll);
	if (rc < 0)
		CPRINTS("Error: bio_enrollment_begin() failed, result=%d", rc);

	return rc;
}

int fp_enrollment_finish(void *templ)
{
	int rc;
	bio_enrollment_t bio_enroll = enroll_ctx;
	bio_template_t bio_templ = templ;

	rc = bio_enrollment_finish(bio_enroll, templ ? &bio_templ : NULL);
	if (rc < 0)
		CPRINTS("Error: bio_enrollment_finish() failed, result=%d", rc);

	return rc;
}

int fp_finger_enroll(uint8_t *image, int *completion)
{
	int rc;
	bio_enrollment_t bio_enroll = enroll_ctx;

	rc = bio_enrollment_add_image(bio_enroll, image);
	if (rc < 0) {
		CPRINTS("Error: bio_enrollment_add_image() failed, result=%d",
			rc);
		return rc;
	}

	*completion = bio_enrollment_get_percent_complete(bio_enroll);

	return rc;
}
