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

#include "charger_class.h"
#include "mtk_charger.h"
#include "mtk_pe5p.h"

enum mtk_chg_type {
	MTK_CHGTYP_SWCHG = 0,
	MTK_CHGTYP_DVCHG,
	MTK_CHGTYP_DVCHG_SLAVE,
	MTK_CHGTYP_BUCKBOOSTCHG,
	MTK_CHGTYP_HV_DVCHG,
	MTK_CHGTYP_HV_DVCHG_SLAVE,
	MTK_CHGTYP_MAX,
};

struct mtk_chgdev_desc {
	enum mtk_chg_type type;
	const char *name;
	bool must_exist;
};

static struct mtk_chgdev_desc mtk_chgdev_desc_tbl[MTK_CHGTYP_MAX] = {
	{
		.type = MTK_CHGTYP_SWCHG,
		.name = "primary_chg",
		.must_exist = true,
	},
	{
		.type = MTK_CHGTYP_DVCHG,
		.name = "primary_dvchg",
		.must_exist = false,
	},
	{
		.type = MTK_CHGTYP_DVCHG_SLAVE,
		.name = "secondary_dvchg",
		.must_exist = false,
	},
	{
		.type = MTK_CHGTYP_BUCKBOOSTCHG,
		.name = "buck_boost_chg",
		.must_exist = false,
	},
	{
		.type = MTK_CHGTYP_HV_DVCHG,
		.name = "hvdiv2_chg1",
		.must_exist = true,
	},
	{
		.type = MTK_CHGTYP_HV_DVCHG_SLAVE,
		.name = "hvdiv2_chg2",
		.must_exist = false,
	},
};

struct pe5p_hal {
	struct device *dev;
	struct charger_device *chgdevs[MTK_CHGTYP_MAX];
	struct adapter_device **adapters;
	struct adapter_device *adapter;
	const char **support_ta;
	int support_ta_cnt;
};

static inline int to_chgtyp(enum chg_idx idx)
{
	switch (idx) {
	case CHG1:
		return MTK_CHGTYP_SWCHG;
	case DVCHG1:
		return MTK_CHGTYP_DVCHG;
	case DVCHG2:
		return MTK_CHGTYP_DVCHG_SLAVE;
	case HVDVCHG1:
		return MTK_CHGTYP_HV_DVCHG;
	case HVDVCHG2:
		return MTK_CHGTYP_HV_DVCHG_SLAVE;
	case BUCKBSTCHG:
		return MTK_CHGTYP_BUCKBOOSTCHG;
	default:
		return -EOPNOTSUPP;
	}
}

static inline int to_chgclass_adc(enum pe5p_adc_channel chan)
{
	switch (chan) {
	case PE5P_ADCCHAN_VBUS:
		return ADC_CHANNEL_VBUS;
	case PE5P_ADCCHAN_IBUS:
		return ADC_CHANNEL_IBUS;
	case PE5P_ADCCHAN_VBAT:
		return ADC_CHANNEL_VBAT;
	case PE5P_ADCCHAN_IBAT:
		return ADC_CHANNEL_IBAT;
	case PE5P_ADCCHAN_TBAT:
		return ADC_CHANNEL_TBAT;
	case PE5P_ADCCHAN_TCHG:
		return ADC_CHANNEL_TEMP_JC;
	case PE5P_ADCCHAN_VOUT:
		return ADC_CHANNEL_VOUT;
	case PE5P_ADCCHAN_VSYS:
		return ADC_CHANNEL_VSYS;
	default:
		break;
	}
	return -EOPNOTSUPP;
}

int pe5p_hal_get_ta_output(struct chg_alg_device *alg, int *mV, int *mA)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_get_output(hal->adapter, mV, mA);
	if (ret < 0)
		return ret;
	return (ret == MTK_ADAPTER_OK) ? 0 : -ret;
}

int pe5p_hal_get_ta_status(struct chg_alg_device *alg,
			    struct pe5p_ta_status *status)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	struct adapter_status _status;

	_status.temperature = 0;
	_status.ocp = 0;
	_status.ovp = 0;
	_status.otp = 0;

	ret = adapter_dev_get_status(hal->adapter, &_status);
	if (ret < 0)
		return ret;
	if (ret != MTK_ADAPTER_OK)
		return -ret;
	/* 0 means NOT SUPPORT */
	status->temperature = (_status.temperature == 0) ? 25 :
							   _status.temperature;
	status->ocp = _status.ocp;
	status->ovp = _status.ovp;
	status->otp = _status.otp;
	return 0;
}

int pe5p_hal_set_ta_cap(struct chg_alg_device *alg, int mV, int mA)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_set_cap(hal->adapter, MTK_PD_APDO, mV, mA);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_is_ta_cc(struct chg_alg_device *alg, bool *is_cc)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_is_cc(hal->adapter, is_cc);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_set_ta_wdt(struct chg_alg_device *alg, u32 ms)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_set_wdt(hal->adapter, ms);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_enable_ta_wdt(struct chg_alg_device *alg, bool en)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_enable_wdt(hal->adapter, en);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_enable_ta_charging(struct chg_alg_device *alg, bool en,
				 int mV, int mA)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	enum adapter_cap_type type = en ? MTK_PD_APDO_START : MTK_PD_APDO_END;

	ret = adapter_dev_set_cap(hal->adapter, type, mV, mA);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_sync_ta_volt(struct chg_alg_device *alg, u32 mV)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_sync_volt(hal->adapter, mV);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_authenticate_ta(struct chg_alg_device *alg,
			      struct pe5p_ta_auth_data *data)
{
	int ret, i;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	struct adapter_auth_data _data = {
		.vcap_min = data->vcap_min,
		.vcap_max = data->vcap_max,
		.icap_min = data->icap_min,
	};

	for (i = 0; i < hal->support_ta_cnt; i++) {
		if (!hal->adapters[i])
			continue;
		ret = adapter_dev_authentication(hal->adapters[i], &_data);
		if (ret < 0 || ret != MTK_ADAPTER_OK) {
			PE5P_DBG("authenticate fail(%d)\n", ret);
			continue;
		}
		hal->adapter = hal->adapters[i];
		data->vta_min = _data.vta_min;
		data->vta_max = _data.vta_max;
		data->ita_min = _data.ita_min;
		data->ita_max = _data.ita_max;
		data->pwr_lmt = _data.pwr_lmt;
		data->pdp = _data.pdp;
		data->support_meas_cap = _data.support_meas_cap;
		data->support_status = _data.support_status;
		data->support_cc = _data.support_cc;
		data->vta_step = _data.vta_step;
		data->ita_step = _data.ita_step;
		data->ita_gap_per_vstep = _data.ita_gap_per_vstep;
		PE5P_INFO("lmt(%d,%dW),step(%d,%d),cc=%d,cap=%d,status=%d\n",
			  data->pwr_lmt, data->pdp, data->vta_step,
			  data->ita_step, data->support_cc,
			  data->support_meas_cap,
			  data->support_status);
		return 0;
	}
	return -EINVAL;
}

int pe5p_hal_send_ta_hardreset(struct chg_alg_device *alg)
{
	int ret;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	ret = adapter_dev_send_hardreset(hal->adapter);
	return (ret <= MTK_ADAPTER_OK) ? ret : -ret;
}

int pe5p_hal_init_hardware(struct chg_alg_device *alg, const char **support_ta,
			    int support_ta_cnt)
{
	struct pe5p_algo_info *info = chg_alg_dev_get_drvdata(alg);
	struct pe5p_algo_data *data = info->data;
	struct pe5p_hal *hal;
	int i, ret;
	bool has_ta = false;

	if (support_ta_cnt <= 0)
		return -EINVAL;

	hal = devm_kzalloc(info->dev, sizeof(*hal), GFP_KERNEL);
	if (!hal)
		return -ENOMEM;
	hal->adapters = devm_kzalloc(info->dev,
				     sizeof(struct adapter_device *) *
				     support_ta_cnt, GFP_KERNEL);
	if (!hal->adapters)
		return -ENOMEM;

	hal->support_ta = support_ta;
	hal->support_ta_cnt = support_ta_cnt;
	/* get TA */
	for (i = 0; i < hal->support_ta_cnt; i++) {
		hal->adapters[i] = get_adapter_by_name(support_ta[i]);
		if (hal->adapters[i]) {
			has_ta = true;
			continue;
		}
		PE5P_ERR("no %s\n", hal->support_ta[i]);
	}
	if (!has_ta) {
		ret = -ENODEV;
		goto err;
	}

	/* get charger device */
	for (i = 0; i < MTK_CHGTYP_MAX; i++) {
		hal->chgdevs[i] =
			get_charger_by_name(mtk_chgdev_desc_tbl[i].name);
		if (!hal->chgdevs[i]) {
			PE5P_ERR("get %s fail\n", mtk_chgdev_desc_tbl[i].name);
			if (mtk_chgdev_desc_tbl[i].must_exist) {
				ret = -ENODEV;
				goto err;
			}
		}
	}
	data->is_dvchg_exist[PE5P_BUCK_BSTCHG] = true;
	data->is_dvchg_exist[PE5P_HVDVCHG_MASTER] = true;
	if (hal->chgdevs[MTK_CHGTYP_HV_DVCHG_SLAVE])
		data->is_dvchg_exist[PE5P_HVDVCHG_SLAVE] = true;
	chg_alg_dev_set_drv_hal_data(alg, hal);
	hal->dev = info->dev;
	PE5P_INFO("successfully\n");
	return 0;
err:
	return ret;
}

int pe5p_hal_enable_sw_vbusovp(struct chg_alg_device *alg, bool en)
{
	mtk_chg_enable_vbus_ovp(en);
	return 0;
}

int pe5p_set_operating_mode(struct chg_alg_device *alg, enum chg_idx chgidx,
			    bool en)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_operation_mode(hal->chgdevs[chgtyp], en);
}

int pe5p_hal_enable_charging(struct chg_alg_device *alg,
			      enum chg_idx chgidx, bool en)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_enable(hal->chgdevs[chgtyp], en);
}

int pe5p_hal_dump_registers(struct chg_alg_device *alg, enum chg_idx chgidx)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_dump_registers(hal->chgdevs[chgtyp]);
}

int pe5p_hal_enable_hz(struct chg_alg_device *alg,
			enum chg_idx chgidx, bool en)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	u32 mV = en ? 24000 : 4500;

	if (chgtyp < 0)
		return chgtyp;

	return charger_dev_set_mivr(hal->chgdevs[chgtyp], milli_to_micro(mV));
}

int pe5p_hal_set_vacovp(struct chg_alg_device *alg,
			  enum chg_idx chgidx, u32 mV)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_vac_ovp(hal->chgdevs[chgtyp],
				       milli_to_micro(mV));
}

int pe5p_hal_set_vbusovp(struct chg_alg_device *alg,
			  enum chg_idx chgidx, u32 mV)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_vbusovp(hal->chgdevs[chgtyp],
				       milli_to_micro(mV));
}

int pe5p_hal_set_ibusocp(struct chg_alg_device *alg,
			  enum chg_idx chgidx, u32 mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_ibusocp(hal->chgdevs[chgtyp],
				       milli_to_micro(mA));
}

int pe5p_hal_set_vbatovp(struct chg_alg_device *alg,
			  enum chg_idx chgidx, u32 mV)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_vbatovp(hal->chgdevs[chgtyp],
				       milli_to_micro(mV));
}

int pe5p_hal_set_ibatocp(struct chg_alg_device *alg,
			  enum chg_idx chgidx, u32 mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_ibatocp(hal->chgdevs[chgtyp],
				       milli_to_micro(mA));
}

int pe5p_hal_set_vbatovp_alarm(struct chg_alg_device *alg,
				enum chg_idx chgidx, u32 mV)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_vbatovp_alarm(hal->chgdevs[chgtyp],
					     milli_to_micro(mV));
}

int pe5p_hal_reset_vbatovp_alarm(struct chg_alg_device *alg,
				  enum chg_idx chgidx)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_reset_vbatovp_alarm(hal->chgdevs[chgtyp]);
}

int pe5p_hal_set_vbusovp_alarm(struct chg_alg_device *alg,
				enum chg_idx chgidx, u32 mV)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_vbusovp_alarm(hal->chgdevs[chgtyp],
					     milli_to_micro(mV));
}

int pe5p_hal_reset_vbusovp_alarm(struct chg_alg_device *alg,
				  enum chg_idx chgidx)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_reset_vbusovp_alarm(hal->chgdevs[chgtyp]);
}

static int pe5p_get_tbat(struct pe5p_hal *hal)
{
	int ret = 0;
	int tmp_ret;
	union power_supply_propval prop;
	struct power_supply *bat_psy;

	bat_psy = devm_power_supply_get_by_phandle(hal->dev, "gauge");
	if (IS_ERR_OR_NULL(bat_psy)) {
		PE5P_ERR("%s Couldn't get bat_psy\n", __func__);
		ret = 27;
	} else {
		tmp_ret = power_supply_get_property(bat_psy,
						 POWER_SUPPLY_PROP_TEMP, &prop);
		ret = prop.intval / 10;
	}

	ret = 25;
	PE5P_DBG("%d\n", ret);
	return ret;
}

int pe5p_hal_get_adc(struct chg_alg_device *alg, enum chg_idx chgidx,
		      enum pe5p_adc_channel chan, int *val)
{
	int ret;
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	int _chan = to_chgclass_adc(chan);

	if (chgtyp < 0)
		return chgtyp;
	if (_chan < 0)
		return _chan;

	if (_chan == ADC_CHANNEL_TBAT) {
		*val = pe5p_get_tbat(hal);
		return 0;
	}
	ret = charger_dev_get_adc(hal->chgdevs[chgtyp], _chan, val, val);
	if (ret < 0)
		return ret;
	if (_chan == ADC_CHANNEL_VBAT || _chan == ADC_CHANNEL_IBAT ||
	    _chan == ADC_CHANNEL_VBUS || _chan == ADC_CHANNEL_IBUS ||
	    _chan == ADC_CHANNEL_VOUT || _chan == ADC_CHANNEL_VSYS)
		*val = micro_to_milli(*val);
	return 0;
}

int pe5p_hal_get_soc(struct chg_alg_device *alg, u32 *soc)
{
	int ret;
	int ret_tmp;
	struct power_supply *bat_psy;
	union power_supply_propval prop;
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	bat_psy = devm_power_supply_get_by_phandle(hal->dev, "gauge");
	if (IS_ERR_OR_NULL(bat_psy)) {
		PE5P_ERR("%s Couldn't get bat_psy\n", __func__);
		ret = 50;
	} else {
		ret_tmp = power_supply_get_property(bat_psy,
					     POWER_SUPPLY_PROP_CAPACITY, &prop);
		ret = prop.intval;
	}
	if (ret < 0)
		return ret;
	*soc = ret;
	PE5P_DBG("%d\n", *soc);
	return 0;
}

int pe5p_hal_is_pd_adapter_ready(struct chg_alg_device *alg)
{
	struct pe5p_hal *hal;
	int type = 0;
	int i;

	if (alg == NULL) {
		pr_notice("%s: alg is null\n", __func__);
		return -EINVAL;
	}

	hal = chg_alg_dev_get_drv_hal_data(alg);
	for (i = 0; i < hal->support_ta_cnt; i++) {
		if (!hal->adapters[i])
			continue;
		type = adapter_dev_get_property(hal->adapters[i], PD_TYPE);
		if (type < 0)
			continue;
	}

	pr_notice("%s type:%d\n", __func__, type);

	if (type == MTK_PD_CONNECT_PE_READY_SNK_APDO)
		return ALG_READY;
	else if (type == MTK_PD_CONNECT_TYPEC_ONLY_SNK ||
			 type == MTK_PD_CONNECT_PE_READY_SNK ||
			 type == MTK_PD_CONNECT_PE_READY_SNK_PD30)
		return ALG_TA_NOT_SUPPORT;
	return ALG_TA_CHECKING;
}

int pe5p_hal_set_ichg(struct chg_alg_device *alg, enum chg_idx chgidx, u32 mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_charging_current(hal->chgdevs[chgtyp],
						milli_to_micro(mA));
}

int pe5p_hal_set_aicr(struct chg_alg_device *alg, enum chg_idx chgidx, u32 mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_set_input_current(hal->chgdevs[chgtyp],
					     milli_to_micro(mA));
}

int pe5p_hal_get_ichg(struct chg_alg_device *alg, enum chg_idx chgidx, u32 *mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	int ret, uA = 0;

	if (chgtyp < 0)
		return chgtyp;

	ret = charger_dev_get_charging_current(hal->chgdevs[chgtyp], &uA);
	*mA = micro_to_milli(uA);

	return ret;
}

int pe5p_hal_get_aicr(struct chg_alg_device *alg, enum chg_idx chgidx, u32 *mA)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	int ret, uA = 0;

	if (chgtyp < 0)
		return chgtyp;

	ret = charger_dev_get_input_current(hal->chgdevs[chgtyp], &uA);
	*mA = micro_to_milli(uA);

	return ret;
}

int pe5p_hal_is_vbuslowerr(struct chg_alg_device *alg,
			    enum chg_idx chgidx, bool *err)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_is_vbuslowerr(hal->chgdevs[chgtyp], err);
}

int pe5p_hal_get_adc_accuracy(struct chg_alg_device *alg,
			       enum chg_idx chgidx,
			       enum pe5p_adc_channel chan, int *val)
{
	int ret;
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);
	int _chan = to_chgclass_adc(chan);

	if (chgtyp < 0)
		return chgtyp;
	if (_chan < 0)
		return _chan;

	ret = charger_dev_get_adc_accuracy(hal->chgdevs[chgtyp], _chan, val,
					   val);
	if (ret < 0)
		return ret;
	if (_chan == ADC_CHANNEL_VBAT || _chan == ADC_CHANNEL_IBAT ||
	    _chan == ADC_CHANNEL_VBUS || _chan == ADC_CHANNEL_IBUS ||
	    _chan == ADC_CHANNEL_VOUT)
		*val = micro_to_milli(*val);
	return 0;
}

int pe5p_hal_init_chip(struct chg_alg_device *alg, enum chg_idx chgidx)
{
	int chgtyp = to_chgtyp(chgidx);
	struct pe5p_hal *hal = chg_alg_dev_get_drv_hal_data(alg);

	if (chgtyp < 0)
		return chgtyp;
	return charger_dev_init_chip(hal->chgdevs[chgtyp]);
}
