/* Copyright 2020 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 "accelgyro.h"
#include "atomic.h"
#include "hwtimer.h"
#include "online_calibration.h"
#include "common.h"
#include "mag_cal.h"
#include "util.h"
#include "vec3.h"
#include "task.h"
#include "ec_commands.h"
#include "accel_cal.h"
#include "mkbp_event.h"
#include "gyro_cal.h"

#define CPRINTS(format, args...) cprints(CC_MOTION_SENSE, format, ##args)

#ifndef CONFIG_MKBP_EVENT
#error "Must use CONFIG_MKBP_EVENT for online calibration"
#endif /* CONFIG_MKBP_EVENT */

/** Bitmap telling which online calibration values are valid. */
static uint32_t sensor_calib_cache_valid_map;
/** Bitmap telling which online calibration values are dirty. */
static uint32_t sensor_calib_cache_dirty_map;

struct mutex g_calib_cache_mutex;

static int get_temperature(struct motion_sensor_t *sensor, int *temp)
{
	struct online_calib_data *entry = sensor->online_calib_data;
	uint32_t now;

	if (sensor->drv->read_temp == NULL)
		return EC_ERROR_UNIMPLEMENTED;

	now = __hw_clock_source_read();
	if (entry->last_temperature < 0 ||
	    time_until(entry->last_temperature_timestamp, now) >
		    CONFIG_TEMP_CACHE_STALE_THRES) {
		int t;
		int rc = sensor->drv->read_temp(sensor, &t);

		if (rc == EC_SUCCESS) {
			entry->last_temperature = t;
			entry->last_temperature_timestamp = now;
		} else {
			return rc;
		}
	}

	*temp = entry->last_temperature;
	return EC_SUCCESS;
}

static void data_int16_to_fp(const struct motion_sensor_t *s,
			     const int16_t *data, fpv3_t out)
{
	int i;
	fp_t range = INT_TO_FP(s->current_range);

	for (i = 0; i < 3; ++i) {
		fp_t v = INT_TO_FP((int32_t)data[i]);

		out[i] = fp_div(v, INT_TO_FP((data[i] >= 0) ? 0x7fff : 0x8000));
		out[i] = fp_mul(out[i], range);
		/* Check for overflow */
		out[i] = CLAMP(out[i], -range, range);
	}
}

static void data_fp_to_int16(const struct motion_sensor_t *s, const fpv3_t data,
			     int16_t *out)
{
	int i;
	fp_t range = INT_TO_FP(s->current_range);

	for (i = 0; i < 3; ++i) {
		int32_t iv;
		fp_t v = fp_div(data[i], range);

		v = fp_mul(v, INT_TO_FP(0x7fff));
		iv = FP_TO_INT(v);
		/* Check for overflow */
		out[i] = ec_motion_sensor_clamp_i16(iv);
	}
}

/**
 * Check a gyroscope for new bias. This function checks a given sensor (must be
 * a gyroscope) for new bias values. If found, it will update the appropriate
 * caches and notify the AP.
 *
 * @param sensor Pointer to the gyroscope sensor to check.
 */
static bool check_gyro_cal_new_bias(struct motion_sensor_t *sensor,
				    fpv3_t bias_out)
{
	struct online_calib_data *calib_data =
		(struct online_calib_data *)sensor->online_calib_data;
	struct gyro_cal_data *data =
		(struct gyro_cal_data *)calib_data->type_specific_data;
	int temp_out;
	uint32_t timestamp_out;

	/* Check that we have a new bias. */
	if (data == NULL || calib_data == NULL ||
	    !gyro_cal_new_bias_available(&data->gyro_cal))
		return false;

	/* Read the calibration values. */
	gyro_cal_get_bias(&data->gyro_cal, bias_out, &temp_out, &timestamp_out);
	return true;
}

static void set_gyro_cal_cache_values(struct motion_sensor_t *sensor,
				      fpv3_t bias)
{
	size_t sensor_num = sensor - motion_sensors;
	struct online_calib_data *calib_data =
		(struct online_calib_data *)sensor->online_calib_data;

	mutex_lock(&g_calib_cache_mutex);
	/* Convert result to the right scale and save to cache. */
	data_fp_to_int16(sensor, bias, calib_data->cache);
	/* Set valid and dirty. */
	sensor_calib_cache_valid_map |= BIT(sensor_num);
	sensor_calib_cache_dirty_map |= BIT(sensor_num);
	mutex_unlock(&g_calib_cache_mutex);
	/* Notify the AP. */
	mkbp_send_event(EC_MKBP_EVENT_ONLINE_CALIBRATION);
}

/**
 * Update the data stream (accel/mag) for a given sensor and data in all
 * gyroscopes that are interested.
 *
 * @param sensor Pointer to the sensor that generated the data.
 * @param data 3 floats/fixed point data points generated by the sensor.
 * @param timestamp The timestamp at which the data was generated.
 */
static void update_gyro_cal(struct motion_sensor_t *sensor, fpv3_t data,
			    uint32_t timestamp)
{
	int i;
	fpv3_t gyro_cal_data_out;

	/*
	 * Find gyroscopes, while we don't currently have instance where more
	 * than one are present in a board, this loop will work with any number
	 * of them.
	 */
	for (i = 0; i < SENSOR_COUNT; ++i) {
		struct motion_sensor_t *s = motion_sensors + i;
		struct gyro_cal_data *gyro_cal_data =
			(struct gyro_cal_data *)
				s->online_calib_data->type_specific_data;
		bool has_new_gyro_cal_bias = false;

		/*
		 * If we're not looking at a gyroscope OR if the calibration
		 * data is NULL, skip this sensor.
		 */
		if (s->type != MOTIONSENSE_TYPE_GYRO || gyro_cal_data == NULL)
			continue;

		/*
		 * Update the appropriate data stream (accel/mag) depending on
		 * which sensors the gyroscope is tracking.
		 */
		if (sensor->type == MOTIONSENSE_TYPE_ACCEL &&
		    gyro_cal_data->accel_sensor_id == sensor - motion_sensors) {
			gyro_cal_update_accel(&gyro_cal_data->gyro_cal,
					      timestamp, data[X], data[Y],
					      data[Z]);
			has_new_gyro_cal_bias =
				check_gyro_cal_new_bias(s, gyro_cal_data_out);
		} else if (sensor->type == MOTIONSENSE_TYPE_MAG &&
			   gyro_cal_data->mag_sensor_id ==
				   sensor - motion_sensors) {
			gyro_cal_update_mag(&gyro_cal_data->gyro_cal, timestamp,
					    data[X], data[Y], data[Z]);
			has_new_gyro_cal_bias =
				check_gyro_cal_new_bias(s, gyro_cal_data_out);
		}

		if (has_new_gyro_cal_bias)
			set_gyro_cal_cache_values(s, gyro_cal_data_out);
	}
}

void online_calibration_init(void)
{
	size_t i;

	for (i = 0; i < SENSOR_COUNT; i++) {
		struct motion_sensor_t *s = motion_sensors + i;
		void *type_specific_data = NULL;

		if (s->online_calib_data) {
			s->online_calib_data->last_temperature = -1;
			type_specific_data =
				s->online_calib_data->type_specific_data;
		}

		if (!type_specific_data)
			continue;

		switch (s->type) {
		case MOTIONSENSE_TYPE_ACCEL: {
			accel_cal_reset((struct accel_cal *)type_specific_data);
			break;
		}
		case MOTIONSENSE_TYPE_MAG: {
			init_mag_cal((struct mag_cal_t *)type_specific_data);
			break;
		}
		case MOTIONSENSE_TYPE_GYRO: {
			init_gyro_cal(
				&((struct gyro_cal_data *)type_specific_data)
					 ->gyro_cal);
			break;
		}
		default:
			break;
		}
	}
}

bool online_calibration_has_new_values(void)
{
	bool has_dirty;

	mutex_lock(&g_calib_cache_mutex);
	has_dirty = sensor_calib_cache_dirty_map != 0;
	mutex_unlock(&g_calib_cache_mutex);

	return has_dirty;
}

bool online_calibration_read(struct motion_sensor_t *sensor,
			     struct ec_response_online_calibration_data *out)
{
	int sensor_num = sensor - motion_sensors;
	bool has_valid;

	mutex_lock(&g_calib_cache_mutex);
	has_valid = (sensor_calib_cache_valid_map & BIT(sensor_num)) != 0;
	if (has_valid) {
		/* Update data in out */
		memcpy(out->data, sensor->online_calib_data->cache,
		       sizeof(out->data));
		/* Clear dirty bit */
		sensor_calib_cache_dirty_map &= ~(1 << sensor_num);
	}
	mutex_unlock(&g_calib_cache_mutex);

	return has_valid;
}

int online_calibration_process_data(struct ec_response_motion_sensor_data *data,
				    struct motion_sensor_t *sensor,
				    uint32_t timestamp)
{
	int sensor_num = sensor - motion_sensors;
	int rc;
	int temperature;
	struct online_calib_data *calib_data;
	fpv3_t fdata;
	bool is_spoofed = IS_ENABLED(CONFIG_ONLINE_CALIB_SPOOF_MODE) &&
			  (sensor->flags & MOTIONSENSE_FLAG_IN_SPOOF_MODE);
	bool has_new_calibration_values = false;

	/* Convert data to fp. */
	data_int16_to_fp(sensor, data->data, fdata);

	calib_data = sensor->online_calib_data;
	switch (sensor->type) {
	case MOTIONSENSE_TYPE_ACCEL: {
		struct accel_cal *cal =
			(struct accel_cal *)(calib_data->type_specific_data);

		if (is_spoofed) {
			/* Copy the data to the calibration result. */
			cal->bias[X] = fdata[X];
			cal->bias[Y] = fdata[Y];
			cal->bias[Z] = fdata[Z];
			has_new_calibration_values = true;
		} else {
			/* Possibly update the gyroscope calibration. */
			update_gyro_cal(sensor, fdata, timestamp);

			/*
			 * Temperature is required for accelerometer
			 * calibration.
			 */
			rc = get_temperature(sensor, &temperature);
			if (rc != EC_SUCCESS)
				return rc;

			has_new_calibration_values = accel_cal_accumulate(
				cal, timestamp, fdata[X], fdata[Y], fdata[Z],
				temperature);
		}

		if (has_new_calibration_values) {
			mutex_lock(&g_calib_cache_mutex);
			/* Convert result to the right scale. */
			data_fp_to_int16(sensor, cal->bias, calib_data->cache);
			/* Set valid and dirty. */
			sensor_calib_cache_valid_map |= BIT(sensor_num);
			sensor_calib_cache_dirty_map |= BIT(sensor_num);
			mutex_unlock(&g_calib_cache_mutex);
			/* Notify the AP. */
			mkbp_send_event(EC_MKBP_EVENT_ONLINE_CALIBRATION);
		}
		break;
	}
	case MOTIONSENSE_TYPE_MAG: {
		struct mag_cal_t *cal =
			(struct mag_cal_t *)(calib_data->type_specific_data);
		int idata[] = {
			(int)data->data[X],
			(int)data->data[Y],
			(int)data->data[Z],
		};

		if (is_spoofed) {
			/* Copy the data to the calibration result. */
			cal->bias[X] = INT_TO_FP(idata[X]);
			cal->bias[Y] = INT_TO_FP(idata[Y]);
			cal->bias[Z] = INT_TO_FP(idata[Z]);
			has_new_calibration_values = true;
		} else {
			/* Possibly update the gyroscope calibration. */
			update_gyro_cal(sensor, fdata, timestamp);

			has_new_calibration_values = mag_cal_update(cal, idata);
		}

		if (has_new_calibration_values) {
			mutex_lock(&g_calib_cache_mutex);
			/* Copy the values */
			calib_data->cache[X] = cal->bias[X];
			calib_data->cache[Y] = cal->bias[Y];
			calib_data->cache[Z] = cal->bias[Z];
			/* Set valid and dirty. */
			sensor_calib_cache_valid_map |= BIT(sensor_num);
			sensor_calib_cache_dirty_map |= BIT(sensor_num);
			mutex_unlock(&g_calib_cache_mutex);
			/* Notify the AP. */
			mkbp_send_event(EC_MKBP_EVENT_ONLINE_CALIBRATION);
		}
		break;
	}
	case MOTIONSENSE_TYPE_GYRO: {
		if (is_spoofed) {
			/*
			 * Gyroscope uses fdata to store the calibration
			 * result, so there's no need to copy anything.
			 */
			has_new_calibration_values = true;
		} else {
			struct gyro_cal_data *gyro_cal_data =
				(struct gyro_cal_data *)
					calib_data->type_specific_data;
			struct gyro_cal *gyro_cal = &gyro_cal_data->gyro_cal;

			/* Temperature is required for gyro calibration. */
			rc = get_temperature(sensor, &temperature);
			if (rc != EC_SUCCESS)
				return rc;

			/* Update gyroscope calibration. */
			gyro_cal_update_gyro(gyro_cal, timestamp, fdata[X],
					     fdata[Y], fdata[Z], temperature);
			has_new_calibration_values =
				check_gyro_cal_new_bias(sensor, fdata);
		}

		if (has_new_calibration_values)
			set_gyro_cal_cache_values(sensor, fdata);
		break;
	}
	default:
		break;
	}

	return EC_SUCCESS;
}
