// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("Mediatek charger cooling driver"); MODULE_LICENSE("GPL v2");