328 lines
9.1 KiB
C
328 lines
9.1 KiB
C
// 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, 1600000, 1200000, 800000, 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, id=%d\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");
|