// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright 2018 NXP
 * Copyright 2021 Purism
 */

#include <common.h>
#include <malloc.h>
#include <errno.h>
#include <asm/io.h>
#include <miiphy.h>
#include <asm/mach-imx/iomux-v3.h>
#include <asm-generic/gpio.h>
#include <asm/arch/sys_proto.h>
#include <fsl_esdhc.h>
#include <mmc.h>
#include <asm/arch/imx8mq_pins.h>
#include <asm/arch/sys_proto.h>
#include <asm/mach-imx/gpio.h>
#include <asm/mach-imx/mxc_i2c.h>
#include <asm/arch/clock.h>
#include <asm/mach-imx/video.h>
#include <fuse.h>
#include <i2c.h>
#include <spl.h>
#include <usb.h>
#include <dwc3-uboot.h>
#include <linux/delay.h>
#include <linux/bitfield.h>
#include <power/regulator.h>
#include <usb/xhci.h>
#include "librem5.h"

DECLARE_GLOBAL_DATA_PTR;

int board_early_init_f(void)
{
	return 0;
}

#if IS_ENABLED(CONFIG_LOAD_ENV_FROM_MMC_BOOT_PARTITION)
uint board_mmc_get_env_part(struct mmc *mmc)
{
	uint part = (mmc->part_config >> 3) & PART_ACCESS_MASK;

	if (part == 7)
		part = 0;
	return part;
}
#endif

int tps65982_wait_for_app(int timeout, int timeout_step)
{
	int ret;
	char response[6];
	struct udevice *udev, *bus;

	log_debug("%s: starting\n", __func__);

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
	if (ret) {
		log_err("%s: No bus %d\n", __func__, 0);
		return 1;
	}

	ret = i2c_get_chip(bus, 0x3f, 1, &udev);
	if (ret) {
		log_err("%s: setting chip offset failed %d\n", __func__, ret);
		return 1;
	}

	while (timeout > 0) {
		ret = dm_i2c_read(udev, 0x03, (u8 *)response, 5);
		log_debug("tps65982 mode %s\n", response);
		if (response[1] == 'A')
			return 0;
		mdelay(timeout_step);
		timeout -= timeout_step;
		log_debug("tps65982 waited %d ms %c\n", timeout_step, response[1]);
	}

	return 1;
}

int tps65982_clear_dead_battery(void)
{
	int ret;
	char cmd[5] = "\04DBfg";
	struct udevice *udev, *bus;

	log_debug("%s: starting\n", __func__);

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
	if (ret) {
		log_err("%s: No bus %d\n", __func__, 0);
		return 1;
	}

	ret = i2c_get_chip(bus, 0x3f, 1, &udev);
	if (ret) {
		log_err("%s: setting chip offset failed %d\n", __func__, ret);
		return 1;
	}

	/* clearing the dead battery flag when not in dead battery condition
	 * is a no-op, so there's no need to check if it's in effect
	 */
	ret = dm_i2c_write(udev, 0x08, cmd, 5);
	if (ret) {
		log_err("%s: writing 4CC command failed %d", __func__, ret);
		return 1;
	}

	return 0;
}

#define TPS_POWER_STATUS_PWROPMODE(x)	    FIELD_GET(GENMASK(3, 2), x)

#define TPS_PDO_CONTRACT_TYPE(x)	FIELD_GET(GENMASK(31, 30), x)
#define TPS_PDO_CONTRACT_FIXED	0
#define TPS_PDO_CONTRACT_BATTERY	1
#define TPS_PDO_CONTRACT_VARIABLE	2

#define TPS_TYPEC_PWR_MODE_USB	0
#define TPS_TYPEC_PWR_MODE_1_5A	1
#define TPS_TYPEC_PWR_MODE_3_0A	2
#define TPS_TYPEC_PWR_MODE_PD	3

#define TPS_PDO_FIXED_CONTRACT_MAX_CURRENT(x)	(FIELD_GET(GENMASK(9, 0), x) * 10)
#define TPS_PDO_VAR_CONTRACT_MAX_CURRENT(x)	(FIELD_GET(GENMASK(9, 0), x) * 10)
#define TPS_PDO_BAT_CONTRACT_MAX_VOLTAGE(x)	(FIELD_GET(GENMASK(29, 20), x) * 50)
#define TPS_PDO_BAT_CONTRACT_MAX_POWER(x)	(FIELD_GET(GENMASK(9, 0), x) * 250)

int tps65982_get_max_current(void)
{
	int ret;
	u8 buf[7];
	u8 pwr_status;
	u32 contract;
	int type, mode;
	struct udevice *udev, *bus;

	log_debug("%s: starting\n", __func__);

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
	if (ret) {
		log_debug("%s: No bus %d\n", __func__, 0);
		return -1;
	}

	ret = i2c_get_chip(bus, 0x3f, 1, &udev);
	if (ret) {
		log_debug("%s: setting chip offset failed %d\n", __func__, ret);
		return -1;
	}

	ret = dm_i2c_read(udev, 0x3f, buf, 3);
	if (ret) {
		log_debug("%s: reading pwr_status failed %d\n", __func__, ret);
		return -1;
	}

	pwr_status = buf[1];

	if (!(pwr_status & 1))
		return 0;

	mode = TPS_POWER_STATUS_PWROPMODE(pwr_status);
	switch (mode) {
	case TPS_TYPEC_PWR_MODE_1_5A:
		return 1500;
	case TPS_TYPEC_PWR_MODE_3_0A:
		return 3000;
	case TPS_TYPEC_PWR_MODE_PD:
		ret = dm_i2c_read(udev, 0x34, buf, 7);
		if (ret) {
			log_debug("%s: reading active contract failed %d\n", __func__, ret);
			return -1;
		}

		contract = buf[1] + (buf[2] << 8) + (buf[3] << 16) + (buf[4] << 24);

		type = TPS_PDO_CONTRACT_TYPE(contract);

		switch (type) {
		case TPS_PDO_CONTRACT_FIXED:
			return TPS_PDO_FIXED_CONTRACT_MAX_CURRENT(contract);
		case TPS_PDO_CONTRACT_BATTERY:
			return 1000 * TPS_PDO_BAT_CONTRACT_MAX_POWER(contract)
				/ TPS_PDO_BAT_CONTRACT_MAX_VOLTAGE(contract);
		case TPS_PDO_CONTRACT_VARIABLE:
			return TPS_PDO_VAR_CONTRACT_MAX_CURRENT(contract);
		default:
			log_debug("Unknown contract type: %d\n", type);
			return -1;
		}
	case TPS_TYPEC_PWR_MODE_USB:
		return 500;
	default:
		log_debug("Unknown power mode: %d\n", mode);
		return -1;
	}
}

int init_tps65982(void)
{
	log_debug("%s: starting\n", __func__);

	if (tps65982_wait_for_app(500, 100)) {
		log_err("tps65982 APP boot failed\n");
		return 1;
	}

	log_info("tps65982 boot successful\n");
	return 0;
}

int bq25895_set_iinlim(int current)
{
	u8 val, iinlim;
	int ret;
	struct udevice *udev, *bus;

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
	if (ret) {
		log_err("%s: No bus 3\n", __func__);
		return ret;
	}

	ret = i2c_get_chip(bus, 0x6a, 1, &udev);
	if (ret) {
		log_err("%s: setting chip offset failed %d\n", __func__, ret);
		return ret;
	}

	if (current > 3250)
		current = 3250;
	if (current < 100)
		current = 100;

	val = dm_i2c_reg_read(udev, 0x00);
	iinlim = ((current - 100) / 50) & 0x3f;
	val = (val & 0xc0) | iinlim;
	dm_i2c_reg_write(udev, 0x00, val);
	log_debug("REG00 0x%x\n", val);

	return 0;
}

bool bq25895_battery_present(void)
{
	u8 val;
	int ret;
	struct udevice *udev, *bus;

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
	if (ret) {
		log_err("%s: No bus 3\n", __func__);
		return ret;
	}

	ret = i2c_get_chip(bus, 0x6a, 1, &udev);
	if (ret) {
		log_err("%s: setting chip offset failed %d\n", __func__, ret);
		return ret;
	}

	/* note that this may return false negatives when there's
	 * no external power applied and the battery voltage is below
	 * Vsys. this isn't a problem when used for clearing the dead
	 * battery flag though, since it's certain that there's an external
	 * power applied in this case
	 */
	val = dm_i2c_reg_read(udev, 0x0e) & 0x7f;
	if (val == 0x00 || val == 0x7f)
		return false;

	return true;
}

/*
 * set some safe defaults for the battery charger
 */
int init_charger_bq25895(void)
{
	u8 val;
	int iinlim, ret;
	struct udevice *udev, *bus;

	/* Set the i2c bus */
	ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
	if (ret) {
		log_debug("%s: No bus 3\n", __func__);
		return ret;
	}

	ret = i2c_get_chip(bus, 0x6a, 1, &udev);
	if (ret) {
		log_debug("%s: setting chip offset failed %d\n", __func__, ret);
		return ret;
	}

	val = dm_i2c_reg_read(udev, 0x0b);
	log_debug("REG0B 0x%x\n", val);

	log_debug("VBUS_STAT 0x%x\n", val >> 5);
	switch (val >> 5) {
	case 0:
		log_debug("VBUS not detected\n");
		break;
	case 1:
		log_debug("USB SDP IINLIM 500mA\n");
		break;
	case 2:
		log_debug("USB CDP IINLIM 1500mA\n");
		break;
	case 3:
		log_debug("USB DCP IINLIM 3500mA\n");
		break;
	case 4:
		log_debug("MAXCHARGE IINLIM 1500mA\n");
		break;
	case 5:
		log_debug("Unknown IINLIM 500mA\n");
		break;
	case 6:
		log_debug("DIVIDER IINLIM > 1000mA\n");
		break;
	case 7:
		log_debug("OTG\n");
		break;
	};

	log_debug("CHRG_STAT 0x%x\n", (val >> 3) & 0x3);
	log_debug("PG_STAT 0x%x\n", (val >> 2) & 1);
	log_debug("SDP_STAT 0x%x\n", (val >> 1) & 1);
	log_debug("VSYS_STAT 0x%x\n", val & 1);

	val = dm_i2c_reg_read(udev, 0x00);
	log_debug("REG00 0x%x\n", val);
	iinlim = 100 + (val & 0x3f) * 50;
	log_debug("IINLIM %d mA\n", iinlim);
	log_debug("EN_HIZ 0x%x\n", (val >> 7) & 1);
	log_debug("EN_ILIM 0x%x\n", (val >> 6) & 1);

	/* set 1.6A charge limit */
	dm_i2c_reg_write(udev, 0x04, 0x19);

	/* re-enable charger */
	val = dm_i2c_reg_read(udev, 0x03);
	val = val | 0x10;
	dm_i2c_reg_write(udev, 0x03, val);

	return 0;
}

int board_init(void)
{
	struct udevice *dev;
	int tps_ret;

	if (IS_ENABLED(CONFIG_USB_DWC3) || IS_ENABLED(CONFIG_USB_XHCI_IMX8M)) {
		log_debug("%s: initializing USB clk\n", __func__);

		/* init_usb_clk won't enable the second clock if it's a USB boot */
		if (is_usb_boot()) {
			clock_enable(CCGR_USB_CTRL2, 1);
			clock_enable(CCGR_USB_PHY2, 1);
		}

		printf("Enabling regulator-hub\n");
		if (!regulator_get_by_devname("regulator-hub", &dev)) {
			if (regulator_set_enable(dev, true))
				pr_err("Failed to enable regulator-hub\n");
		}
	}

	tps_ret = init_tps65982();
	init_charger_bq25895();

	if (!tps_ret) {
		int current = tps65982_get_max_current();

		if (current > 500)
			bq25895_set_iinlim(current);

		if (bq25895_battery_present())
			tps65982_clear_dead_battery();
	}

	return 0;
}

int board_late_init(void)
{
	if (IS_ENABLED(CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG)) {
		u32 rev;
		char rev_str[3];

		env_set("board_name", "librem5");
		if (fuse_read(9, 0, &rev)) {
			env_set("board_rev", BOARD_REV_ERROR);
		} else if (rev == 0) {
			env_set("board_rev", BOARD_REV_UNKNOWN);
		} else if (rev > 0) {
			sprintf(rev_str, "%u", rev);
			env_set("board_rev", rev_str);
		}

		printf("Board name: %s\n", env_get("board_name"));
		printf("Board rev:  %s\n", env_get("board_rev"));
	}

	if (is_usb_boot()) {
		puts("USB Boot\n");
		env_set("bootcmd", "fastboot 0");
	}

	return 0;
}
