// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 MediaTek Inc.
 */
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/thermal.h>
#include <linux/types.h>
#include "charger_cooling.h"

struct charger_cooler_info {
	int num_state;
	int cur_current;
	int cur_state;
};

static struct charger_cooler_info charger_cl_data;
/* < -1 is unlimit, unit is uA. */
static const int master_charger_state_to_current_limit[CHARGER_STATE_NUM] = {
	-1, 2600000, 2200000, 1800000, 1400000, 1000000, 700000, 500000, 0
};
static const int slave_charger_state_to_current_limit[CHARGER_STATE_NUM] = {
	-1, 1800000, 1600000, 1400000, 1200000, 1000000, 700000, 500000, 0
};

/*==================================================
 * cooler callback functions
 *==================================================
 */
static int charger_throttle(struct charger_cooling_device *charger_cdev, unsigned long state)
{
	struct device *dev = charger_cdev->dev;

	charger_cdev->target_state = state;
	charger_cdev->pdata->state_to_charger_limit(charger_cdev);
	charger_cl_data.cur_state = state;
	charger_cl_data.cur_current = master_charger_state_to_current_limit[state];
	dev_info(dev, "%s: set lv = %ld done\n", charger_cdev->name, state);
	return 0;
}
static int charger_cooling_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
	struct charger_cooling_device *charger_cdev = cdev->devdata;

	*state = charger_cdev->max_state;

	return 0;
}

static int charger_cooling_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
{
	struct charger_cooling_device *charger_cdev = cdev->devdata;

	*state = charger_cdev->target_state;

	return 0;
}

static int charger_cooling_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
	struct charger_cooling_device *charger_cdev = cdev->devdata;
	int ret;

	/* Request state should be less than max_state */
	if (WARN_ON(state > charger_cdev->max_state || !charger_cdev->throttle))
		return -EINVAL;

	if (charger_cdev->target_state == state)
		return 0;

	ret = charger_cdev->throttle(charger_cdev, state);

	return ret;
}

/*==================================================
 * platform data and platform driver callbacks
 *==================================================
 */

static int cooling_state_to_charger_limit_v1(struct charger_cooling_device *chg)
{
	union power_supply_propval prop_bat_chr;
	union power_supply_propval prop_input;
	union power_supply_propval prop_vbus;
	union power_supply_propval prop_s_bat_chr;
	int ret = -1;

	if (chg->chg_psy == NULL || IS_ERR(chg->chg_psy)) {
		pr_info("Couldn't get chg_psy\n");
		return ret;
	}
	prop_bat_chr.intval = master_charger_state_to_current_limit[chg->target_state];

	ret = power_supply_set_property(chg->chg_psy,
		POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
		&prop_bat_chr);
	if (ret != 0) {
		pr_notice("set bat curr fail\n");
		return ret;
	}
	if (prop_bat_chr.intval == 0)
		prop_input.intval = 0;
	else
		prop_input.intval = -1;
	ret = power_supply_set_property(chg->chg_psy,
		POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &prop_input);
	if (ret != 0) {
		pr_notice("set input curr fail\n");
		return ret;
	}

	/* High Voltage (Vbus) control*/
	/*Only master charger need to control Vbus*/
	/*prop.intval = 0, vbus 5V*/
	/*prop.intval = 1, vbus 9V*/
	prop_vbus.intval = 1;

	if (prop_bat_chr.intval == 0)
		prop_vbus.intval = 0;

	ret = power_supply_set_property(chg->chg_psy,
		POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop_vbus);
	if (ret != 0) {
		pr_notice("set vbus fail\n");
		return ret;
	}

	pr_notice("chr limit state %lu, chr %d, input %d, vbus %d\n",
		chg->target_state, prop_bat_chr.intval, prop_input.intval, prop_vbus.intval);

	power_supply_changed(chg->chg_psy);

	if (chg->type == DUAL_CHARGER) {
		if (chg->s_chg_psy == NULL || IS_ERR(chg->s_chg_psy)) {
			pr_info("Couldn't get s_chg_psy\n");
			return ret;
		}
		prop_s_bat_chr.intval = slave_charger_state_to_current_limit[chg->target_state];

		ret = power_supply_set_property(chg->s_chg_psy,
			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
			&prop_s_bat_chr);
		if (ret != 0) {
			pr_notice("set slave bat curr fail\n");
			return ret;
		}
		power_supply_changed(chg->s_chg_psy);
	}

	return ret;
}


static const struct charger_cooling_platform_data mt6360_pdata = {
	.state_to_charger_limit = cooling_state_to_charger_limit_v1,
};

static const struct charger_cooling_platform_data mt6375_pdata = {
	.state_to_charger_limit = cooling_state_to_charger_limit_v1,
};

static const struct of_device_id charger_cooling_of_match[] = {
	{
		.compatible = "mediatek,mt6360-charger-cooler",
		.data = (void *)&mt6360_pdata,
	},
	{
		.compatible = "mediatek,mt6375-charger-cooler",
		.data = (void *)&mt6375_pdata,
	},
	{},
};
MODULE_DEVICE_TABLE(of, charger_cooling_of_match);

static struct thermal_cooling_device_ops charger_cooling_ops = {
	.get_max_state		= charger_cooling_get_max_state,
	.get_cur_state		= charger_cooling_get_cur_state,
	.set_cur_state		= charger_cooling_set_cur_state,
};
/*return 0:single charger*/
/*return 1,2:dual charger*/
static int get_charger_type(void)
{
	struct device_node *node = NULL;
	u32 val = 0;

	node = of_find_compatible_node(NULL, NULL, "mediatek,charger");
	WARN_ON_ONCE(node == 0);

	if (of_property_read_u32(node, "charger_configuration", &val))
		return 0;
	else
		return val;
}
static ssize_t charger_curr_show(struct kobject *kobj,
	struct kobj_attribute *attr, char *buf)
{
	int len = 0;

	len += snprintf(buf + len, PAGE_SIZE - len, "%d\n",
				charger_cl_data.cur_current);
	return len;
}

static ssize_t charger_table_show(struct kobject *kobj,
	struct kobj_attribute *attr, char *buf)
{
	int i;
	int len = 0;

	for (i = 0; i < CHARGER_STATE_NUM; i++) {
		if (i == 0)
			len += snprintf(buf + len, PAGE_SIZE - len, "[%d,",
				-1);
		else if (i == CHARGER_STATE_NUM - 1)
			len += snprintf(buf + len, PAGE_SIZE - len, "%d]\n",
				master_charger_state_to_current_limit[i]/1000);
		else
			len += snprintf(buf + len, PAGE_SIZE - len, "%d,",
				master_charger_state_to_current_limit[i]/1000);
	}

	return len;
}

static struct kobj_attribute charger_curr_attr = __ATTR_RO(charger_curr);
static struct kobj_attribute charger_table_attr = __ATTR_RO(charger_table);

static struct attribute *charger_cooler_attrs[] = {
	&charger_curr_attr.attr,
	&charger_table_attr.attr,
	NULL
};
static struct attribute_group charger_cooler_attr_group = {
	.name	= "charger_cooler",
	.attrs	= charger_cooler_attrs,
};

static int charger_cooling_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = pdev->dev.of_node;
	struct thermal_cooling_device *cdev;
	struct charger_cooling_device *charger_cdev;
	int ret, len;

	charger_cdev = devm_kzalloc(dev, sizeof(*charger_cdev), GFP_KERNEL);
	if (!charger_cdev)
		return -ENOMEM;

	charger_cdev->pdata = of_device_get_match_data(dev);

	len = (strlen(np->name) > (MAX_CHARGER_COOLER_NAME_LEN - 1)) ?
		(MAX_CHARGER_COOLER_NAME_LEN - 1) : strlen(np->name);
	strncpy(charger_cdev->name, np->name, len);
	charger_cdev->target_state = CHARGER_COOLING_UNLIMITED_STATE;
	charger_cdev->dev = dev;
	charger_cdev->max_state = CHARGER_STATE_NUM - 1;
	charger_cdev->throttle = charger_throttle;
	charger_cdev->pdata = of_device_get_match_data(dev);
	charger_cdev->type = get_charger_type();
	charger_cdev->chg_psy = power_supply_get_by_name("mtk-master-charger");
	if (charger_cdev->chg_psy == NULL || IS_ERR(charger_cdev->chg_psy)) {
		pr_info("Couldn't get chg_psy\n");
		return -EINVAL;
	}
	if (charger_cdev->type == DUAL_CHARGER) {
		charger_cdev->s_chg_psy = power_supply_get_by_name("mtk-slave-charger");
		if (charger_cdev->s_chg_psy == NULL || IS_ERR(charger_cdev->s_chg_psy)) {
			pr_info("Couldn't get s_chg_psy\n");
			return -EINVAL;
		}
	}

	ret = sysfs_create_group(kernel_kobj, &charger_cooler_attr_group);
	if (ret) {
		dev_info(&pdev->dev, "failed to create charger cooler sysfs, ret=%d!\n", ret);
		return ret;
	}
	charger_cl_data.cur_state = 0;
	charger_cl_data.cur_current = -1;
	charger_cl_data.num_state = CHARGER_STATE_NUM;

	cdev = thermal_of_cooling_device_register(np, charger_cdev->name,
		charger_cdev, &charger_cooling_ops);
	if (IS_ERR(cdev))
		return -EINVAL;
	charger_cdev->cdev = cdev;

	platform_set_drvdata(pdev, charger_cdev);
	dev_info(dev, "register %s done\n", charger_cdev->name);

	return 0;
}

static int charger_cooling_remove(struct platform_device *pdev)
{
	struct charger_cooling_device *charger_cdev;

	charger_cdev = (struct charger_cooling_device *)platform_get_drvdata(pdev);

	thermal_cooling_device_unregister(charger_cdev->cdev);
	platform_set_drvdata(pdev, NULL);

	return 0;
}

static struct platform_driver charger_cooling_driver = {
	.probe = charger_cooling_probe,
	.remove = charger_cooling_remove,
	.driver = {
		.name = "mtk-charger-cooling",
		.of_match_table = charger_cooling_of_match,
	},
};
module_platform_driver(charger_cooling_driver);

MODULE_AUTHOR("Henry Huang <henry.huang@mediatek.com>");
MODULE_DESCRIPTION("Mediatek charger cooling driver");
MODULE_LICENSE("GPL v2");
