/*
 * Copyright (c) 2021 ITE Technology Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ite_it8xxx2_tach

/**
 * @file
 * @brief ITE it8xxx2 tachometer sensor module driver
 *
 * This file contains a driver for the tachometer sensor module which contains
 * two independent counters (F1TL/MRR and F2TL/MRR). The content of the
 * Tachometer Reading Register is still update based on the sampling counter
 * that samples the tachometer input (T0A, T0B, T1A or T1B pins).
 * The following is block diagram of this module:
 *
 *                                    Sample Rate = TACH_FREQ / 128
 *                                                   |
 *                            |        Tachometer 0  |                T0A (GPD6)
 *                            |             |        | +-----------+   |
 *                            |       +-----+-----+  | |   _   _   |<--+
 *                            |------>| F1TL/MRR  |<-+-|  | |_| |_ |<--+
 *                            |       +-----------+    +-----------+   |
 *                            |       capture pulses                  T0B (GPJ2)
 *                            |       in sample rate
 *                            |       period
 *           +-----------+    |
 * Crystal-->| Prescaler |--->|        Tachometer 1                   T1A (GPD7)
 * 32.768k   +-----------+    |             |          +-----------+   |
 *                            |       +-----+-----+    |   _   _   |<--+
 *                            |------>| F2TL/MRR  |<-+-|  | |_| |_ |<--+
 *                            |       +-----------+    +-----------+   |
 *                            |       capture pulses                  T1B (GPJ3)
 *                            |       in one second
 *                            |       period
 *                            |
 *
 * Based on the counter value, we can compute the current RPM of external signal
 * from encoders.
 */

#include <zephyr/device.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/dt-bindings/sensor/it8xxx2_tach.h>
#include <errno.h>
#include <soc.h>
#include <soc_dt.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tach_ite_it8xxx2, CONFIG_SENSOR_LOG_LEVEL);

/*
 * NOTE: The PWM output maximum is 324Hz in EC LPM, so if we need fan to work
 *       then don't let EC enter LPM.
 */
#define TACH_FREQ		EC_FREQ

struct tach_it8xxx2_config {
	/* Fan x tachometer LSB reading register */
	uintptr_t reg_fxtlrr;
	/* Fan x tachometer MSB reading register */
	uintptr_t reg_fxtmrr;
	/* Tachometer switch control register */
	uintptr_t reg_tswctlr;
	/* Tachometer data valid bit of tswctlr register */
	int dvs_bit;
	/* Tachometer data valid status bit of tswctlr register */
	int chsel_bit;
	/* Tachometer alternate configuration */
	const struct pinctrl_dev_config *pcfg;
	/* Select channel of tachometer */
	int channel;
	/* Number of pulses per round of tachometer's input */
	int pulses_per_round;
};

/* Driver data */
struct tach_it8xxx2_data {
	/* Captured counts of tachometer */
	uint32_t capture;
};

static bool tach_ch_is_valid(const struct device *dev, int tach_ch)
{
	const struct tach_it8xxx2_config *const config = dev->config;
	volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr;
	int dvs_bit = config->dvs_bit;
	int chsel_bit = config->chsel_bit;
	int mask = (dvs_bit | chsel_bit);
	bool valid = false;

	switch (tach_ch) {
	case IT8XXX2_TACH_CHANNEL_A:
		if ((*reg_tswctlr & mask) == dvs_bit) {
			valid = true;
		}
		break;
	case IT8XXX2_TACH_CHANNEL_B:
		if ((*reg_tswctlr & mask) == mask) {
			valid = true;
		}
		break;
	default:
		break;
	}

	return valid;
}

static int tach_it8xxx2_sample_fetch(const struct device *dev,
				     enum sensor_channel chan)
{
	const struct tach_it8xxx2_config *const config = dev->config;
	volatile uint8_t *reg_fxtlrr = (uint8_t *)config->reg_fxtlrr;
	volatile uint8_t *reg_fxtmrr = (uint8_t *)config->reg_fxtmrr;
	volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr;
	int tach_ch = config->channel;
	struct tach_it8xxx2_data *const data = dev->data;

	if ((chan != SENSOR_CHAN_RPM) && (chan != SENSOR_CHAN_ALL)) {
		return -ENOTSUP;
	}

	if (tach_ch_is_valid(dev, tach_ch)) {
		/* If channel data of tachometer is valid, then save it */
		data->capture = ((*reg_fxtmrr) << 8) | (*reg_fxtlrr);
		/* Clear tachometer data valid status */
		*reg_tswctlr |= config->dvs_bit;
	} else {
		/* If channel data of tachometer isn't valid, then clear it */
		data->capture = 0;
	}

	return 0;
}

static int tach_it8xxx2_channel_get(const struct device *dev,
				    enum sensor_channel chan,
				    struct sensor_value *val)
{
	const struct tach_it8xxx2_config *const config = dev->config;
	int tachx = ((config->dvs_bit) == IT8XXX2_PWM_T0DVS) ? 0 : 1;
	int p = config->pulses_per_round;
	struct tach_it8xxx2_data *const data = dev->data;

	if (chan != SENSOR_CHAN_RPM) {
		LOG_ERR("Sensor chan %d, only support SENSOR_CHAN_RPM", chan);
		return -ENOTSUP;
	}

	/* Transform count unit to RPM */
	if (data->capture > 0) {
		if (tachx == 0) {
			/*
			 * Fan Speed (RPM) = 60 / (1/fs * {F1TMRR, F1TLRR} * P)
			 * - P denotes the numbers of pulses per round
			 * - {F1TMRR, F1TLRR} = 0000h denotes Fan Speed is zero
			 * - The sampling rate (fs) is TACH_FREQ / 128
			 */
			val->val1 = (60 * TACH_FREQ / 128 / p / (data->capture));
		} else {
			/*
			 * Fan Speed (RPM) = {F2TMRR, F2TLRR} * 60 / P
			 * - P denotes the numbers of pulses per round
			 * - {F2TMRR, F2TLRR} = 0000h denotes Fan Speed is zero
			 */
			val->val1 = ((data->capture) * 120 / (p * 2));
		}
	} else {
		val->val1 = 0U;
	}

	val->val2 = 0U;

	return 0;
}

static int tach_it8xxx2_init(const struct device *dev)
{
	const struct tach_it8xxx2_config *const config = dev->config;
	volatile uint8_t *reg_tswctlr = (uint8_t *)config->reg_tswctlr;
	int tach_ch = config->channel;
	int status;

	if (tach_ch > IT8XXX2_TACH_CHANNEL_B) {
		LOG_ERR("Tach channel %d, only support 0 or 1", tach_ch);
		return -EINVAL;
	}

	/* Select pin to alternate mode for tachometer */
	status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
	if (status < 0) {
		LOG_ERR("Failed to configure TACH pins");
		return status;
	}

	if (tach_ch == IT8XXX2_TACH_CHANNEL_A) {
		/* Select IT8XXX2_TACH_CHANNEL_A output to tachometer */
		*reg_tswctlr &= ~(config->chsel_bit);
		/* Clear tachometer data valid status */
		*reg_tswctlr |= config->dvs_bit;
	} else {
		/* Select IT8XXX2_TACH_CHANNEL_B output to tachometer */
		*reg_tswctlr |= config->chsel_bit;
		/* Clear tachometer data valid status */
		*reg_tswctlr |= config->dvs_bit;
	}

	/* Tachometer sensor already start */
	return 0;
}

static const struct sensor_driver_api tach_it8xxx2_driver_api = {
	.sample_fetch = tach_it8xxx2_sample_fetch,
	.channel_get = tach_it8xxx2_channel_get,
};

#define TACH_IT8XXX2_INIT(inst)						       \
	PINCTRL_DT_INST_DEFINE(inst);					       \
									       \
	static const struct tach_it8xxx2_config tach_it8xxx2_cfg_##inst = {    \
		.reg_fxtlrr = DT_INST_REG_ADDR_BY_IDX(inst, 0),		       \
		.reg_fxtmrr = DT_INST_REG_ADDR_BY_IDX(inst, 1),		       \
		.reg_tswctlr = DT_INST_REG_ADDR_BY_IDX(inst, 2),	       \
		.dvs_bit = DT_INST_PROP(inst, dvs_bit),			       \
		.chsel_bit = DT_INST_PROP(inst, chsel_bit),		       \
		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),		       \
		.channel = DT_INST_PROP(inst, channel),			       \
		.pulses_per_round = DT_INST_PROP(inst, pulses_per_round),      \
	};								       \
									       \
	static struct tach_it8xxx2_data tach_it8xxx2_data_##inst;	       \
									       \
	SENSOR_DEVICE_DT_INST_DEFINE(inst,				       \
			      tach_it8xxx2_init,			       \
			      NULL,					       \
			      &tach_it8xxx2_data_##inst,		       \
			      &tach_it8xxx2_cfg_##inst,			       \
			      POST_KERNEL,				       \
			      CONFIG_SENSOR_INIT_PRIORITY,		       \
			      &tach_it8xxx2_driver_api);

DT_INST_FOREACH_STATUS_OKAY(TACH_IT8XXX2_INIT)
