/* 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 "charger.h"
#include "charge_manager.h"
#include "console.h"
#include "crc8.h"
#include "mt6360.h"
#include "ec_commands.h"
#include "hooks.h"
#include "i2c.h"
#include "task.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd.h"
#include "util.h"

/* Console output macros */
#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args)
#define CPRINTS(format, args...) \
	cprints(CC_USBCHARGE, "%s " format, "MT6360", ## args)

static enum ec_error_list mt6360_read8(int reg, int *val)
{
	return i2c_read8(mt6360_config.i2c_port, mt6360_config.i2c_addr_flags,
			reg, val);
}

static enum ec_error_list mt6360_write8(int reg, int val)
{
	return i2c_write8(mt6360_config.i2c_port, mt6360_config.i2c_addr_flags,
			reg, val);
}

static int mt6360_update_bits(int reg, int mask, int val)
{
	int rv;
	int reg_val;

	rv = mt6360_read8(reg, &reg_val);
	if (rv)
		return rv;
	reg_val &= ~mask;
	reg_val |= (mask & val);
	rv = mt6360_write8(reg, reg_val);
	return rv;
}

static inline int mt6360_set_bit(int reg, int mask)
{
	return mt6360_update_bits(reg, mask, mask);
}

static inline int mt6360_clr_bit(int reg, int mask)
{
	return mt6360_update_bits(reg, mask, 0x00);
}

static int mt6360_get_bc12_device_type(void)
{
	int reg;

	if (mt6360_read8(MT6360_REG_USB_STATUS_1, &reg))
		return CHARGE_SUPPLIER_NONE;

	switch (reg & MT6360_MASK_USB_STATUS) {
	case MT6360_MASK_SDP:
		CPRINTS("BC12 SDP");
		return CHARGE_SUPPLIER_BC12_SDP;
	case MT6360_MASK_CDP:
		CPRINTS("BC12 CDP");
		return CHARGE_SUPPLIER_BC12_CDP;
	case MT6360_MASK_DCP:
		CPRINTS("BC12 DCP");
		return CHARGE_SUPPLIER_BC12_DCP;
	default:
		CPRINTS("BC12 NONE");
		return CHARGE_SUPPLIER_NONE;
	}
}

static int mt6360_get_bc12_ilim(int charge_supplier)
{
	switch (charge_supplier) {
	case CHARGE_SUPPLIER_BC12_DCP:
	case CHARGE_SUPPLIER_BC12_CDP:
		return USB_CHARGER_MAX_CURR_MA;
	case CHARGE_SUPPLIER_BC12_SDP:
	default:
		return USB_CHARGER_MIN_CURR_MA;
	}
}

static int mt6360_enable_bc12_detection(int en)
{
	int rv;

	if (en) {
#ifdef CONFIG_MT6360_BC12_GPIO
		gpio_set_level(GPIO_BC12_DET_EN, 1);
#endif
		return mt6360_set_bit(MT6360_REG_DEVICE_TYPE,
				      MT6360_MASK_USBCHGEN);
	}

	rv = mt6360_clr_bit(MT6360_REG_DEVICE_TYPE, MT6360_MASK_USBCHGEN);
#ifdef CONFIG_MT6360_BC12_GPIO
	gpio_set_level(GPIO_BC12_DET_EN, 0);
#endif
	return rv;
}

static void mt6360_update_charge_manager(int port,
					 enum charge_supplier new_bc12_type)
{
	static enum charge_supplier current_bc12_type = CHARGE_SUPPLIER_NONE;

	if (new_bc12_type != current_bc12_type) {
		if (current_bc12_type >= 0)
			charge_manager_update_charge(current_bc12_type, port,
							NULL);

		if (new_bc12_type != CHARGE_SUPPLIER_NONE) {
			struct charge_port_info chg = {
				.current = mt6360_get_bc12_ilim(new_bc12_type),
				.voltage = USB_CHARGER_VOLTAGE_MV,
			};

			charge_manager_update_charge(new_bc12_type, port, &chg);
		}

		current_bc12_type = new_bc12_type;
	}
}

static void mt6360_handle_bc12_irq(int port)
{
	int reg;

	mt6360_read8(MT6360_REG_DPDMIRQ, &reg);

	if (reg & MT6360_MASK_DPDMIRQ_ATTACH) {
		/* Check vbus again to avoid timing issue */
		if (pd_snk_is_vbus_provided(port))
			mt6360_update_charge_manager(
					port, mt6360_get_bc12_device_type());
		else
			mt6360_update_charge_manager(
					0, CHARGE_SUPPLIER_NONE);
	}

	/* write clear */
	mt6360_write8(MT6360_REG_DPDMIRQ, reg);
}

static void mt6360_usb_charger_task(const int port)
{
	mt6360_clr_bit(MT6360_REG_DPDM_MASK1,
		       MT6360_REG_DPDM_MASK1_CHGDET_DONEI_M);
	mt6360_enable_bc12_detection(0);

	while (1) {
		uint32_t evt = task_wait_event(-1);

		/* vbus change, start bc12 detection */
		if (evt & USB_CHG_EVENT_VBUS) {
			bool is_sink = pd_get_power_role(port) == PD_ROLE_SINK;
			bool is_non_pd_sink = !pd_capable(port) &&
				is_sink &&
				pd_snk_is_vbus_provided(port);

			if (is_sink)
				mt6360_clr_bit(MT6360_REG_CHG_CTRL1,
					       MT6360_MASK_HZ);
			else
				mt6360_set_bit(MT6360_REG_CHG_CTRL1,
					       MT6360_MASK_HZ);

			if (is_non_pd_sink)
				mt6360_enable_bc12_detection(1);
			else
				mt6360_update_charge_manager(
						0, CHARGE_SUPPLIER_NONE);
		}

		/* detection done, update charge_manager and stop detection */
		if (evt & USB_CHG_EVENT_BC12) {
			mt6360_handle_bc12_irq(port);
			mt6360_enable_bc12_detection(0);
		}
	}
}

/* Regulator: LDO & BUCK */
static int mt6360_regulator_write8(uint8_t addr, int reg, int val)
{
	/*
	 * Note: The checksum from I2C_FLAG_PEC happens to be correct because
	 * length == 1 -> the high 3 bits of the offset byte is 0.
	 */
	return i2c_write8(mt6360_config.i2c_port,
			  addr | I2C_FLAG_PEC, reg, val);
}

static int mt6360_regulator_read8(int addr, int reg, int *val)
{
	int rv;
	uint8_t crc = 0, real_crc;
	uint8_t out[3] = {(addr << 1) | 1, reg};

	rv = i2c_read16(mt6360_config.i2c_port, addr, reg, val);
	if (rv)
		return rv;

	real_crc = (*val >> 8) & 0xFF;
	*val &= 0xFF;
	out[2] = *val;
	crc = cros_crc8(out, ARRAY_SIZE(out));

	if (crc != real_crc)
		return EC_ERROR_CRC;

	return EC_SUCCESS;
}

static int mt6360_regulator_update_bits(int addr, int reg, int mask, int val)
{
	int rv;
	int reg_val = 0;

	rv = mt6360_regulator_read8(addr, reg, &reg_val);
	if (rv)
		return rv;
	reg_val &= ~mask;
	reg_val |= (mask & val);
	rv = mt6360_regulator_write8(addr, reg, reg_val);
	return rv;
}

struct mt6360_regulator_data {
	const char *name;
	const uint16_t *ldo_vosel_table;
	uint16_t ldo_vosel_table_len;
	uint8_t addr;
	uint8_t reg_en_ctrl2;
	uint8_t reg_ctrl3;
	uint8_t mask_vosel;
	uint8_t shift_vosel;
	uint8_t mask_vocal;
};

static const uint16_t MT6360_LDO3_VOSEL_TABLE[16] = {
	[0x4] = 1800,
	[0xA] = 2900,
	[0xB] = 3000,
	[0xD] = 3300,
};

static const uint16_t MT6360_LDO5_VOSEL_TABLE[8] = {
	[0x2] = 2900,
	[0x3] = 3000,
	[0x5] = 3300,
};

static const uint16_t MT6360_LDO6_VOSEL_TABLE[16] = {
	[0x0] = 500,
	[0x1] = 600,
	[0x2] = 700,
	[0x3] = 800,
	[0x4] = 900,
	[0x5] = 1000,
	[0x6] = 1100,
	[0x7] = 1200,
	[0x8] = 1300,
	[0x9] = 1400,
	[0xA] = 1500,
	[0xB] = 1600,
	[0xC] = 1700,
	[0xD] = 1800,
	[0xE] = 1900,
	[0xF] = 2000,
};

/* LDO7 VOSEL table is the same as LDO6's. */
static const uint16_t *const MT6360_LDO7_VOSEL_TABLE = MT6360_LDO6_VOSEL_TABLE;
static const uint16_t MT6360_LDO7_VOSEL_TABLE_SIZE =
	ARRAY_SIZE(MT6360_LDO6_VOSEL_TABLE);

static const
struct mt6360_regulator_data regulator_data[MT6360_REGULATOR_COUNT] = {
	[MT6360_LDO3] = {
		.name = "mt6360_ldo3",
		.ldo_vosel_table = MT6360_LDO3_VOSEL_TABLE,
		.ldo_vosel_table_len = ARRAY_SIZE(MT6360_LDO3_VOSEL_TABLE),
		.addr = MT6360_LDO_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_LDO3_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_LDO3_CTRL3,
		.mask_vosel = MT6360_MASK_LDO3_VOSEL,
		.shift_vosel = MT6360_MASK_LDO3_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_LDO3_VOCAL,
	},
	[MT6360_LDO5] = {
		.name = "mt6360_ldo5",
		.ldo_vosel_table = MT6360_LDO5_VOSEL_TABLE,
		.ldo_vosel_table_len = ARRAY_SIZE(MT6360_LDO5_VOSEL_TABLE),
		.addr = MT6360_LDO_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_LDO5_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_LDO5_CTRL3,
		.mask_vosel = MT6360_MASK_LDO5_VOSEL,
		.shift_vosel = MT6360_MASK_LDO5_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_LDO5_VOCAL,
	},
	[MT6360_LDO6] = {
		.name = "mt6360_ldo6",
		.ldo_vosel_table = MT6360_LDO6_VOSEL_TABLE,
		.ldo_vosel_table_len = ARRAY_SIZE(MT6360_LDO6_VOSEL_TABLE),
		.addr = MT6360_PMIC_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_LDO6_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_LDO6_CTRL3,
		.mask_vosel = MT6360_MASK_LDO6_VOSEL,
		.shift_vosel = MT6360_MASK_LDO6_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_LDO6_VOCAL,
	},
	[MT6360_LDO7] = {
		.name = "mt6360_ldo7",
		.ldo_vosel_table = MT6360_LDO7_VOSEL_TABLE,
		.ldo_vosel_table_len = MT6360_LDO7_VOSEL_TABLE_SIZE,
		.addr = MT6360_PMIC_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_LDO7_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_LDO7_CTRL3,
		.mask_vosel = MT6360_MASK_LDO7_VOSEL,
		.shift_vosel = MT6360_MASK_LDO7_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_LDO7_VOCAL,
	},
	[MT6360_BUCK1] = {
		.name = "mt6360_buck1",
		.addr = MT6360_PMIC_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_BUCK1_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_BUCK1_VOSEL,
		.mask_vosel = MT6360_MASK_BUCK1_VOSEL,
		.shift_vosel = MT6360_MASK_BUCK1_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_BUCK1_VOCAL,
	},
	[MT6360_BUCK2] = {
		.name = "mt6360_buck2",
		.addr = MT6360_PMIC_I2C_ADDR_FLAGS,
		.reg_en_ctrl2 = MT6360_REG_BUCK2_EN_CTRL2,
		.reg_ctrl3 = MT6360_REG_BUCK2_VOSEL,
		.mask_vosel = MT6360_MASK_BUCK2_VOSEL,
		.shift_vosel = MT6360_MASK_BUCK2_VOSEL_SHIFT,
		.mask_vocal = MT6360_MASK_BUCK2_VOCAL,
	},
};

static bool is_buck_regulator(const struct mt6360_regulator_data *data)
{
	/* There's no ldo_vosel_table, it's a buck. */
	return !(data->ldo_vosel_table);
}

int mt6360_regulator_get_info(enum mt6360_regulator_id id, char *name,
			      uint16_t *num_voltages, uint16_t *voltages_mv)
{
	int i;
	int cnt = 0;
	const struct mt6360_regulator_data *data;

	if (id >= MT6360_REGULATOR_COUNT)
		return EC_ERROR_INVAL;
	data = &regulator_data[id];

	strzcpy(name, data->name, EC_REGULATOR_NAME_MAX_LEN);

	if (is_buck_regulator(data)) {
		for (i = 0; i < MT6360_BUCK_VOSEL_MAX_STEP; ++i) {
			int mv = MT6360_BUCK_VOSEL_MIN +
				 i * MT6360_BUCK_VOSEL_STEP_MV;

			if (cnt < EC_REGULATOR_VOLTAGE_MAX_COUNT)
				voltages_mv[cnt++] = mv;
			else
				CPRINTS("%s voltage info overflow: %d-%d",
					data->name, mv, MT6360_BUCK_VOSEL_MAX);
		}
	} else {
		/* It's a LDO */
		for (i = 0; i < data->ldo_vosel_table_len; i++) {
			int mv = data->ldo_vosel_table[i];

			if (!mv)
				continue;
			if (cnt < EC_REGULATOR_VOLTAGE_MAX_COUNT)
				voltages_mv[cnt++] = mv;
			else
				CPRINTS("%s voltage info overflow: %d",
					data->name, mv);
		}
	}

	*num_voltages = cnt;
	return EC_SUCCESS;
}

int mt6360_regulator_enable(enum mt6360_regulator_id id, uint8_t enable)
{
	const struct mt6360_regulator_data *data;

	if (id >= MT6360_REGULATOR_COUNT)
		return EC_ERROR_INVAL;
	data = &regulator_data[id];

	if (enable)
		return mt6360_regulator_update_bits(
			data->addr,
			data->reg_en_ctrl2,
			MT6360_MASK_RGL_SW_OP_EN | MT6360_MASK_RGL_SW_EN,
			MT6360_MASK_RGL_SW_OP_EN | MT6360_MASK_RGL_SW_EN);
	else
		return mt6360_regulator_update_bits(
			data->addr,
			data->reg_en_ctrl2,
			MT6360_MASK_RGL_SW_OP_EN | MT6360_MASK_RGL_SW_EN,
			MT6360_MASK_RGL_SW_OP_EN);
}

int mt6360_regulator_is_enabled(enum mt6360_regulator_id id, uint8_t *enabled)
{
	int rv;
	int value;
	const struct mt6360_regulator_data *data;

	if (id >= MT6360_REGULATOR_COUNT)
		return EC_ERROR_INVAL;
	data = &regulator_data[id];

	rv = mt6360_regulator_read8(data->addr, data->reg_en_ctrl2, &value);
	if (rv) {
		CPRINTS("Error reading %s enabled: %d", data->name, rv);
		return rv;
	}
	*enabled = !!(value & MT6360_MASK_RGL_SW_EN);
	return EC_SUCCESS;
}

int mt6360_regulator_set_voltage(enum mt6360_regulator_id id, int min_mv,
				 int max_mv)
{
	int i;
	const struct mt6360_regulator_data *data;

	if (id >= MT6360_REGULATOR_COUNT)
		return EC_ERROR_INVAL;
	data = &regulator_data[id];

	if (is_buck_regulator(data)) {
		int mv;
		int step;

		if (max_mv < MT6360_BUCK_VOSEL_MIN)
			goto error;

		if (min_mv > MT6360_BUCK_VOSEL_MAX)
			goto error;

		mv = DIV_ROUND_UP((min_mv + max_mv) / 2,
				  MT6360_BUCK_VOSEL_STEP_MV) *
		     MT6360_BUCK_VOSEL_STEP_MV;
		mv = MIN(MAX(mv, MT6360_BUCK_VOSEL_MIN), MT6360_BUCK_VOSEL_MAX);

		step = (mv - MT6360_BUCK_VOSEL_MIN) / MT6360_BUCK_VOSEL_STEP_MV;

		return mt6360_regulator_update_bits(data->addr,
						    data->reg_ctrl3,
						    data->mask_vosel, step);
	}

	/* It's a LDO. */
	for (i = 0; i < data->ldo_vosel_table_len; i++) {
		int mv = data->ldo_vosel_table[i];
		int step = 0;

		if (!mv)
			continue;
		if (mv + MT6360_LDO_VOCAL_STEP_MV * MT6360_LDO_VOCAL_MAX_STEP <
		    min_mv)
			continue;
		if (mv < min_mv)
			step = DIV_ROUND_UP(min_mv - mv,
					    MT6360_LDO_VOCAL_STEP_MV);
		if (mv + step * MT6360_LDO_VOCAL_STEP_MV > max_mv)
			continue;
		return mt6360_regulator_update_bits(
			data->addr,
			data->reg_ctrl3,
			data->mask_vosel | data->mask_vocal,
			(i << data->shift_vosel) | step);
	}

error:
	CPRINTS("%s voltage %d - %d out of range", data->name, min_mv, max_mv);
	return EC_ERROR_INVAL;
}

int mt6360_regulator_get_voltage(enum mt6360_regulator_id id, int *voltage_mv)
{
	int value;
	int rv;
	const struct mt6360_regulator_data *data;

	if (id >= MT6360_REGULATOR_COUNT)
		return EC_ERROR_INVAL;
	data = &regulator_data[id];

	rv = mt6360_regulator_read8(data->addr, data->reg_ctrl3, &value);
	if (rv) {
		CPRINTS("Error reading %s ctrl3: %d", data->name, rv);
		return rv;
	}

	/* BUCK */
	if (is_buck_regulator(data)) {
		*voltage_mv = MT6360_BUCK_VOSEL_MIN +
			      value * MT6360_BUCK_VOSEL_STEP_MV;
		return EC_SUCCESS;
	}

	/* LDO */
	*voltage_mv = data->ldo_vosel_table[(value & data->mask_vosel) >>
					    data->shift_vosel];
	if (*voltage_mv == 0) {
		CPRINTS("Unknown %s voltage value: %d", data->name, value);
		return EC_ERROR_INVAL;
	}
	*voltage_mv +=
		MIN(MT6360_LDO_VOCAL_MAX_STEP, value & data->mask_vocal) *
		MT6360_LDO_VOCAL_STEP_MV;
	return EC_SUCCESS;
}

/* RGB LED */
void mt6360_led_init(void)
{
	/* Enable LED1 software mode */
	mt6360_set_bit(MT6360_REG_RGB_EN, MT6360_ISINK1_CHRIND_EN_SEL);
}
DECLARE_HOOK(HOOK_INIT, mt6360_led_init, HOOK_PRIO_DEFAULT);

int mt6360_led_enable(enum mt6360_led_id led_id, int enable)
{
	if (!IN_RANGE(led_id, 0, MT6360_LED_COUNT))
		return EC_ERROR_INVAL;

	if (enable)
		return mt6360_set_bit(MT6360_REG_RGB_EN,
				      MT6360_MASK_ISINK_EN(led_id));
	return mt6360_clr_bit(MT6360_REG_RGB_EN, MT6360_MASK_ISINK_EN(led_id));
}

int mt6360_led_set_brightness(enum mt6360_led_id led_id, int brightness)
{
	int val;

	if (!IN_RANGE(led_id, 0, MT6360_LED_COUNT))
		return EC_ERROR_INVAL;
	if (!IN_RANGE(brightness, 0, 16))
		return EC_ERROR_INVAL;

	RETURN_ERROR(mt6360_read8(MT6360_REG_RGB_ISINK(led_id), &val));
	val &= ~MT6360_MASK_CUR_SEL;
	val |= brightness;

	return mt6360_write8(MT6360_REG_RGB_ISINK(led_id), val);
}

const struct bc12_drv mt6360_drv = {
	.usb_charger_task = mt6360_usb_charger_task,
};

#ifdef CONFIG_BC12_SINGLE_DRIVER
/* provide a default bc12_ports[] for backward compatibility */
struct bc12_config bc12_ports[CHARGE_PORT_COUNT] = {
	[0 ... (CHARGE_PORT_COUNT - 1)] = {
		.drv = &mt6360_drv,
	},
};
#endif /* CONFIG_BC12_SINGLE_DRIVER */
