// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */

/*
 *
 * Filename:
 * ---------
 *    mtk_pulse_charger.c
 *
 * Project:
 * --------
 *   Android_Software
 *
 * Description:
 * ------------
 *   This Module defines functions of Battery charging
 *
 * Author:
 * -------
 * Wy Chuang
 *
 */
#include <linux/init.h>		/* For init/exit macros */
#include <linux/module.h>	/* For MODULE_ marcros  */
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/power_supply.h>
#include <linux/pm_wakeup.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/proc_fs.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/scatterlist.h>
#include <linux/suspend.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/reboot.h>

#include "mtk_charger.h"

#define MAX_TOPOFF_CHARGING_TIME (3 * 60 * 60) /* 3 hours */

#define RECHARGE_OFFSET 150000 /* uV */
#define TOPOFF_VOLTAGE 4200000 /* uV */
#define CHG_FULL_CURRENT 150000 /* uA */

struct pcharger_data {
	int state;
	bool disable_charging;

	unsigned int total_charging_time;
	unsigned int cc_charging_time;
	unsigned int topoff_charging_time;
	unsigned int full_charging_time;
	struct timespec64 topoff_begin_time;
	struct timespec64 charging_begin_time;

	int recharge_offset; /* uv */
	int topoff_voltage; /* uv */
	int chg_full_current; /* uA */

};

enum pcharger_state_enum {
	CHR_CC,
	CHR_TOPOFF,
	CHR_BATFULL,
	CHR_ERROR
};

static int _uA_to_mA(int uA)
{
	if (uA == -1)
		return -1;
	else
		return uA / 1000;
}

static void pchr_select_cv(struct mtk_charger *info)
{
	u32 constant_voltage;

	if (info->enable_sw_jeita)
		if (info->sw_jeita.cv != 0) {
			info->setting.cv = info->sw_jeita.cv;
			return;
		}

	constant_voltage = info->data.battery_cv;
	info->setting.cv = constant_voltage;
	charger_dev_set_constant_voltage(info->chg1_dev,
		info->setting.cv);
}

static bool pchr_select_charging_current_limit(struct mtk_charger *info,
	struct chg_limit_setting *setting)
{
	struct charger_data *pdata;
	bool is_basic = false;
	u32 ichg1_min = 0, aicr1_min = 0;
	int ret;

	pchr_select_cv(info);

	pdata = &info->chg_data[CHG1_SETTING];
	if (info->usb_unlimited) {
		pdata->input_current_limit =
					info->data.ac_charger_input_current;
		pdata->charging_current_limit =
					info->data.ac_charger_current;
		is_basic = true;
		goto done;
	}

	if ((info->bootmode == 1) ||
	    (info->bootmode == 5)) {
		pdata->input_current_limit = 200000; /* 200mA */
		is_basic = true;
		goto done;
	}

	if (info->atm_enabled == true
		&& (info->chr_type == POWER_SUPPLY_TYPE_USB ||
		info->chr_type == POWER_SUPPLY_TYPE_USB_CDP)
		) {
		pdata->input_current_limit = 100000; /* 100mA */
		is_basic = true;
		goto done;
	}

	if (info->chr_type == POWER_SUPPLY_TYPE_USB) {
		pdata->input_current_limit =
				info->data.usb_charger_current;
		/* it can be larger */
		pdata->charging_current_limit =
				info->data.usb_charger_current;
		is_basic = true;

	} else if (info->chr_type == POWER_SUPPLY_TYPE_USB_DCP) {
		pdata->input_current_limit =
			info->data.ac_charger_input_current;
		pdata->charging_current_limit =
			info->data.ac_charger_current;
	} else if (info->chr_type == POWER_SUPPLY_TYPE_USB_CDP) {
		pdata->input_current_limit =
			info->data.charging_host_charger_current;
		pdata->charging_current_limit =
			info->data.charging_host_charger_current;
		is_basic = true;
	}

	if (info->enable_sw_jeita) {
		if (IS_ENABLED(CONFIG_USBIF_COMPLIANCE)
			&& info->chr_type == POWER_SUPPLY_TYPE_USB)
			chr_debug("USBIF & STAND_HOST skip current check\n");
		else {
			if (info->sw_jeita.sm == TEMP_T0_TO_T1) {
				pdata->input_current_limit = 500000;
				pdata->charging_current_limit = 350000;
			}
		}
	}

	if (pdata->thermal_charging_current_limit != -1) {
		if (pdata->thermal_charging_current_limit <
			pdata->charging_current_limit) {
			pdata->charging_current_limit =
				pdata->thermal_charging_current_limit;
			info->setting.charging_current_limit1 =
				pdata->thermal_charging_current_limit;
		}
	} else
		info->setting.charging_current_limit1 = -1;

	if (pdata->thermal_input_current_limit != -1) {
		if (pdata->thermal_input_current_limit <
			pdata->input_current_limit) {
			pdata->input_current_limit =
				pdata->thermal_input_current_limit;
			info->setting.input_current_limit1 =
				pdata->thermal_input_current_limit;
		}
	} else
		info->setting.input_current_limit1 = -1;

	if (info->pd_type == MTK_PD_CONNECT_PE_READY_SNK ||
		info->pd_type == MTK_PD_CONNECT_PE_READY_SNK_PD30 ||
		info->pd_type == MTK_PD_CONNECT_PE_READY_SNK_APDO)
		is_basic = false;

done:

	ret = charger_dev_get_min_charging_current(info->chg1_dev, &ichg1_min);
	if (ret != -ENOTSUPP && pdata->charging_current_limit < ichg1_min)
		pdata->charging_current_limit = 0;

	ret = charger_dev_get_min_input_current(info->chg1_dev, &aicr1_min);
	if (ret != -ENOTSUPP && pdata->input_current_limit < aicr1_min)
		pdata->input_current_limit = 0;

	chr_err("thermal:%d %d setting:%d %d type:%d:%d usb_unlimited:%d usbif:%d usbsm:%d aicl:%d atm:%d bm:%d b:%d\n",
		_uA_to_mA(pdata->thermal_input_current_limit),
		_uA_to_mA(pdata->thermal_charging_current_limit),
		_uA_to_mA(pdata->input_current_limit),
		_uA_to_mA(pdata->charging_current_limit),
		info->chr_type, info->pd_type,
		info->usb_unlimited,
		IS_ENABLED(CONFIG_USBIF_COMPLIANCE), info->usb_state,
		pdata->input_current_limit_by_aicl, info->atm_enabled,
		info->bootmode, is_basic);

	return is_basic;
}

static void linear_chg_turn_on_charging(struct mtk_charger *info)
{
	struct pcharger_data *algo_data = info->algo.algo_data;
	bool charging_enable = true;
	struct charger_data *pdata;

	pdata = &info->chg_data[CHG1_SETTING];

	if (algo_data->state == CHR_ERROR) {
		charging_enable = false;
		chr_err("[charger]Charger Error, turn OFF charging !\n");
#ifdef SUPPORT_BOOTMODE
	} else if ((get_boot_mode() == META_BOOT) ||
			((get_boot_mode() == ADVMETA_BOOT))) {
		charging_enable = false;
		chr_err("[charger]In meta or advanced meta mode, disable charging\n");
#endif
	} else {
		pchr_select_charging_current_limit(info, &info->setting);
		if (info->chg_data[CHG1_SETTING].charging_current_limit == 0) {
			charging_enable = false;
			chr_err("[charger]charging current is set 0mA, turn off charging\n");
		}
		charger_dev_set_input_current(info->chg1_dev,
			pdata->input_current_limit);
		charger_dev_set_charging_current(info->chg1_dev,
			pdata->charging_current_limit);
		charger_dev_set_constant_voltage(info->chg1_dev,
			info->setting.cv);

	}
	charger_dev_enable(info->chg1_dev, charging_enable);
}


static int mtk_linear_chr_cc(struct mtk_charger *info)
{
	ktime_t ktime_now, ktime_diff;
	struct timespec64 charging_time;
	u32 vbat;
	struct pcharger_data *algo_data;

	algo_data = info->algo.algo_data;
	pr_notice("%s time:%d %d %d %d\n", __func__,
		algo_data->total_charging_time,
		algo_data->cc_charging_time,
		algo_data->topoff_charging_time,
		algo_data->full_charging_time);

	ktime_now = ktime_get_boottime();
	ktime_diff = ktime_sub(ktime_now, timespec64_to_ktime(algo_data->charging_begin_time));
	charging_time = ktime_to_timespec64(ktime_diff);


	algo_data->cc_charging_time = charging_time.tv_sec;
	algo_data->topoff_charging_time = 0;
	algo_data->total_charging_time = charging_time.tv_sec;

	/* discharge for 1 second and charge for 9 seconds */
	charger_dev_enable(info->chg1_dev, false);
	msleep(1000);

	vbat = get_battery_voltage(info) * 1000; /* uV */
	if (vbat > algo_data->topoff_voltage) {
		algo_data->state = CHR_TOPOFF;
		ktime_now = ktime_get_boottime();
		algo_data->topoff_begin_time = ktime_to_timespec64(ktime_now);
		pr_notice("%s: enter TOPOFF mode on vbat = %d uV\n",
			__func__, vbat);
	}

	linear_chg_turn_on_charging(info);

	return 0;
}

static bool charging_full_check(struct mtk_charger *info)
{
	struct pcharger_data *algo_data = info->algo.algo_data;
	static u32 full_check_count;
	bool chg_full_status = false;
	int chg_current = get_battery_current(info) * 1000; /* uA */

	if (chg_current > algo_data->chg_full_current)
		full_check_count = 0;
	else {
		full_check_count++;
		if (full_check_count >= 6) {
			full_check_count = 0;
			chg_full_status = true;
		}
	}

	return chg_full_status;
}

static int mtk_linear_chr_topoff(struct mtk_charger *info)
{
	ktime_t ktime_now, ktime_diff;
	struct pcharger_data *algo_data = info->algo.algo_data;
	struct timespec64 charging_time, topoff_time;


	pr_notice("%s time:%d %d %d %d\n", __func__,
		algo_data->total_charging_time,
		algo_data->cc_charging_time,
		algo_data->topoff_charging_time,
		algo_data->full_charging_time);
	ktime_now = ktime_get_boottime();
	ktime_diff = ktime_sub(ktime_now, timespec64_to_ktime(algo_data->charging_begin_time));
	charging_time = ktime_to_timespec64(ktime_diff);

	ktime_diff = ktime_sub(ktime_now, timespec64_to_ktime(algo_data->topoff_begin_time));
	charging_time = ktime_to_timespec64(ktime_diff);

	algo_data->cc_charging_time = 0;
	algo_data->topoff_charging_time = topoff_time.tv_sec;
	algo_data->total_charging_time = charging_time.tv_sec;

	linear_chg_turn_on_charging(info);

	if (algo_data->topoff_charging_time >= MAX_TOPOFF_CHARGING_TIME
	    || charging_full_check(info) == true) {
		algo_data->state = CHR_BATFULL;

		/* Disable charging */
		charger_dev_enable(info->chg1_dev, false);
		pr_notice("%s: disable charging\n", __func__);
	}

	return 0;
}

static int mtk_linear_chr_full(struct mtk_charger *info)
{
	struct pcharger_data *algo_data = info->algo.algo_data;
	u32 vbat;
	bool is_recharging = false;
	ktime_t ktime_now;

	algo_data->total_charging_time = 0;
	algo_data->cc_charging_time = 0;
	algo_data->topoff_charging_time = 0;

	pr_notice("%s time:%d %d %d %d\n", __func__,
		algo_data->total_charging_time,
		algo_data->cc_charging_time,
		algo_data->topoff_charging_time,
		algo_data->full_charging_time);

	/*
	 * If CV is set to lower value by JEITA,
	 * Reset CV to normal value if temperture is in normal zone
	 */
	pchr_select_cv(info);
	info->polling_interval = CHARGING_FULL_INTERVAL;

	vbat = get_battery_voltage(info) * 1000; /* uV */
	if (info->enable_sw_jeita && info->sw_jeita.cv != 0) {
		if (vbat < (info->sw_jeita.cv - algo_data->recharge_offset))
			is_recharging = true;
	} else {
		if (vbat < (info->data.battery_cv - algo_data->recharge_offset))
			is_recharging = true;
	}

	if (is_recharging) {
		algo_data->state = CHR_CC;
		ktime_now = ktime_get_boottime();
		algo_data->charging_begin_time = ktime_to_timespec64(ktime_now);
		pr_notice("battery recharging on vbat = %d uV\n", vbat);
		info->polling_interval = CHARGING_INTERVAL;
	}

	return 0;
}

static void pchr_disable_all_charging(struct mtk_charger *info)
{
	charger_dev_enable(info->chg1_dev, false);
}


static int mtk_linear_chr_err(struct mtk_charger *info)
{
	struct pcharger_data *algo_data = info->algo.algo_data;
	ktime_t ktime_now;

	pr_notice("%s time:%d %d %d %d\n", __func__,
		algo_data->total_charging_time,
		algo_data->cc_charging_time,
		algo_data->topoff_charging_time,
		algo_data->full_charging_time);


	if (info->enable_sw_jeita) {
		if ((info->sw_jeita.sm == TEMP_BELOW_T0) ||
			(info->sw_jeita.sm == TEMP_ABOVE_T4))
			info->sw_jeita.error_recovery_flag = false;

		if ((info->sw_jeita.error_recovery_flag == false) &&
			(info->sw_jeita.sm != TEMP_BELOW_T0) &&
			(info->sw_jeita.sm != TEMP_ABOVE_T4)) {
			info->sw_jeita.error_recovery_flag = true;
			algo_data->state = CHR_CC;
			ktime_now = ktime_get_boottime();
			algo_data->charging_begin_time = ktime_to_timespec64(ktime_now);
		}
	}

	algo_data->total_charging_time = 0;
	algo_data->cc_charging_time = 0;
	algo_data->topoff_charging_time = 0;

	pchr_disable_all_charging(info);
	return 0;
}


static int pchr_do_algorithm(struct mtk_charger *info)
{
	struct charger_data *pdata;
	bool is_basic = true;
	int ret;
	struct pcharger_data *algo_data = info->algo.algo_data;

	charger_dev_kick_wdt(info->chg1_dev);
	pdata = &info->chg_data[CHG1_SETTING];
	chr_err("%s: %d\n", __func__, algo_data->state);

	switch (algo_data->state) {
	case CHR_CC:
		ret = mtk_linear_chr_cc(info);
		break;

	case CHR_TOPOFF:
		ret = mtk_linear_chr_topoff(info);
		break;

	case CHR_BATFULL:
		ret = mtk_linear_chr_full(info);
		break;

	case CHR_ERROR:
		ret = mtk_linear_chr_err(info);
		break;
	}

	if (is_basic == true) {
		charger_dev_set_input_current(info->chg1_dev,
			pdata->input_current_limit);
		charger_dev_set_charging_current(info->chg1_dev,
			pdata->charging_current_limit);
		charger_dev_set_constant_voltage(info->chg1_dev,
			info->setting.cv);
	}

	charger_dev_dump_registers(info->chg1_dev);
	return 0;
}

static void mtk_pulse_charger_parse_dt(struct mtk_charger *info,
				struct device *dev)
{
	struct device_node *np = dev->of_node;
	u32 val;
	struct pcharger_data *algo_data;

	algo_data = info->algo.algo_data;

	if (of_property_read_u32(np, "recharge_offset", &val) >= 0) {
		algo_data->recharge_offset = val;
	} else {
		chr_err("use default RECHARGE_OFFSET: %d\n", RECHARGE_OFFSET);
		algo_data->recharge_offset = RECHARGE_OFFSET;
	}

	if (of_property_read_u32(np, "topoff_voltage", &val) >= 0) {
		algo_data->topoff_voltage = val;
	} else {
		chr_err("use default TOPOFF_VOLTAGE: %d\n", TOPOFF_VOLTAGE);
		algo_data->topoff_voltage = TOPOFF_VOLTAGE;
	}

	if (of_property_read_u32(np, "chg_full_current", &val) >= 0) {
		algo_data->chg_full_current = val;
	} else {
		chr_err("use default CHG_FULL_CURRENT: %d\n", CHG_FULL_CURRENT);
		algo_data->chg_full_current = CHG_FULL_CURRENT;
	}

}

static int mtk_linear_charging_do_charging(struct mtk_charger *info,
						bool en)
{
	struct pcharger_data *algo_data = info->algo.algo_data;
	ktime_t ktime_now;

	pr_info("%s en:%d %s\n", __func__, en, info->algorithm_name);
	if (en) {
		algo_data->disable_charging = false;
		algo_data->state = CHR_CC;
		ktime_now = ktime_get_boottime();
		algo_data->charging_begin_time = ktime_to_timespec64(ktime_now);

	} else {
		algo_data->disable_charging = true;
		algo_data->state = CHR_ERROR;

		pchr_disable_all_charging(info);
	}

	return 0;
}

int mtk_pulse_charger_init(struct mtk_charger *info)
{
	static struct pcharger_data pdata;

	pr_notice("%s\n", __func__);
	info->algo.do_algorithm = pchr_do_algorithm;
	info->algo.enable_charging = mtk_linear_charging_do_charging;
	//info->algo.do_event = charger_dev_event;
	info->algo.algo_data = &pdata;
	mtk_pulse_charger_parse_dt(info, &info->pdev->dev);
	pr_notice("%s end\n", __func__);

	return 0;
}
