// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. * Author Wy Chuang */ #include #include #include #include /* PMIC MFD core header */ #include #include #include #include #include #include "charger_class.h" #include "mt6357-pulse-charger.h" struct mt6357_charger_desc { u32 ichg; /* uA */ u32 cv; /* uV */ u32 vcdt_hv_thres; /* uV */ u32 vbat_ov_thres; /* uV */ }; static struct mt6357_charger_desc mt6357_chg_default_desc = { .ichg = 500000, .cv = 4350000, .vcdt_hv_thres = 7000000, .vbat_ov_thres = 4450000, }; struct mt6357_charger { const char *charger_dev_name; struct charger_properties charger_prop; struct charger_device *charger_dev; struct mt6357_charger_desc *desc; struct platform_device *pdev; struct mt6397_chip *chip; struct regmap *regmap; }; static const u32 CS_VTH[] = { 2000000, 1600000, 1500000, 1350000, 1200000, 1100000, 1000000, 900000, 800000, 700000, 650000, 550000, 450000, 300000, 200000, 70000, }; static const u32 VBAT_CV_VTH[] = { 3900000, 4000000, 4050000, 4100000, 4150000, 4200000, 4212500, 4225000, 4237500, 4250000, 4262500, 4275000, 4287500, 4300000, 4312500, 4325000, 4337500, 4350000, 4362500, 4375000, 4387500, 4400000, 4412500, 4425000, 4437500, 4450000, 4462500, 4475000, 4487500, 4500000, 4600000, }; static const u32 VCDT_HV_VTH[] = { 3000000, 3900000, 4000000, 4100000, 4150000, 4200000, 4250000, 4300000, 4350000, 4400000, 4450000, 6000000, 6500000, 7000000, 7500000, 8500000, 9500000, 10500000, 11500000, 12500000, 14000000, }; static const u32 VBAT_OV_VTH[] = { 4200000, 4200000, 4300000, 4400000, 4450000, 4500000, 4600000, 4700000, }; static void chr_set_register_value(struct regmap *map, unsigned int addr, unsigned int mask, unsigned int shift, unsigned int val) { regmap_update_bits(map, addr, mask << shift, val << shift); } static unsigned int chr_get_register_value(struct regmap *map, unsigned int addr, unsigned int mask, unsigned int shift) { unsigned int value; regmap_read(map, addr, &value); value = (value & (mask << shift)) >> shift; return value; } static unsigned int chg_get_register(struct regmap *map, unsigned int addr) { return chr_get_register_value(map, addr, 0xffff, 0); } static u32 charging_value_to_parameter(const u32 *parameter, const u32 array_size, const u32 val) { if (val < array_size) return parameter[val]; pr_notice("Can't find the parameter\n"); return parameter[0]; } static u32 charging_parameter_to_value(const u32 *parameter, const u32 array_size, const u32 val) { u32 i; for (i = 0; i < array_size; i++) { if (val == *(parameter + i)) return i; } pr_notice("no register value matched\n"); return 0; } static u32 bmt_find_closest_level(const u32 *pList, u32 number, u32 level) { int i; u32 max_value_in_last_element; if (pList[0] < pList[1]) max_value_in_last_element = true; else max_value_in_last_element = false; if (max_value_in_last_element == true) { /* max value in the last element */ for (i = (number - 1); i >= 0; i--) { if (pList[i] <= level) { /* pr_notice("zzf_%d<=%d i=%d\n", pList[i], level, i); */ return pList[i]; } } pr_notice("Can't find closest level\n"); return pList[0]; } /* max value in the first element */ for (i = 0; i < number; i++) { if (pList[i] <= level) return pList[i]; } pr_notice("Can't find closest level\n"); return pList[number - 1]; } static int mt6357_get_min_ichg(struct charger_device *chg_dev, u32 *uA) { u32 array_size; array_size = ARRAY_SIZE(CS_VTH); *uA = charging_value_to_parameter(CS_VTH, array_size, array_size - 1); return 0; } static int mt6357_set_cv(struct charger_device *chg_dev, u32 cv) { int ret = 0; u32 array_size; u32 set_cv, register_value; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); array_size = ARRAY_SIZE(VBAT_CV_VTH); set_cv = bmt_find_closest_level(VBAT_CV_VTH, array_size, cv); register_value = charging_parameter_to_value(VBAT_CV_VTH, array_size, set_cv); chr_set_register_value(info->regmap, PMIC_RG_VBAT_CV_VTH_ADDR, PMIC_RG_VBAT_CV_VTH_MASK, PMIC_RG_VBAT_CV_VTH_SHIFT, register_value); pr_notice("%s: cv = %d mV (0x%x)\n", __func__, set_cv, register_value); return ret; } static int mt6357_kick_wdt(struct charger_device *chg_dev) { int ret = 0; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); pr_notice("%s\n", __func__); chr_set_register_value(info->regmap, PMIC_RG_CHRWDT_WR_ADDR, PMIC_RG_CHRWDT_WR_MASK, PMIC_RG_CHRWDT_WR_SHIFT, 1); return ret; } static int mt6357_get_ichg(struct charger_device *chg_dev, u32 *ichg) { int ret = 0; u32 array_size; u32 val; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); array_size = ARRAY_SIZE(CS_VTH); val = chr_get_register_value(info->regmap, PMIC_RG_CS_VTH_ADDR, PMIC_RG_CS_VTH_MASK, PMIC_RG_CS_VTH_SHIFT); *ichg = charging_value_to_parameter(CS_VTH, array_size, val); return ret; } static int mt6357_set_ichg(struct charger_device *chg_dev, u32 ichg) { u32 array_size; u32 set_ichg, register_value; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); array_size = ARRAY_SIZE(CS_VTH); set_ichg = bmt_find_closest_level(CS_VTH, array_size, ichg); register_value = charging_parameter_to_value(CS_VTH, array_size, set_ichg); chr_set_register_value(info->regmap, PMIC_RG_CS_VTH_ADDR, PMIC_RG_CS_VTH_MASK, PMIC_RG_CS_VTH_SHIFT, register_value); pr_notice("%s: 0x%x %d %d\n", __func__, register_value, ichg, set_ichg); return 0; } static int mt6357_is_charging_enabled(struct charger_device *chg_dev, bool *en) { unsigned short val; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); val = chr_get_register_value(info->regmap, PMIC_RG_CHR_EN_ADDR, PMIC_RG_CHR_EN_MASK, PMIC_RG_CHR_EN_SHIFT); *en = (val == 1) ? true : false; return 0; } static int mt6357_plug_in(struct charger_device *chg_dev) { int ret = 0; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); chr_set_register_value(info->regmap, PMIC_RG_ULC_DET_EN_ADDR, PMIC_RG_ULC_DET_EN_MASK, PMIC_RG_ULC_DET_EN_SHIFT, 1); return ret; } static int mt6357_enable_charging(struct charger_device *chg_dev, bool en) { int ret = 0; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); pr_notice("[%s] en: %d\n", __func__, en); if (en) { chr_set_register_value(info->regmap, PMIC_RG_CSDAC_MODE_ADDR, PMIC_RG_CSDAC_MODE_MASK, PMIC_RG_CSDAC_MODE_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_CS_EN_ADDR, PMIC_RG_CS_EN_MASK, PMIC_RG_CS_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_ULC_DET_EN_ADDR, PMIC_RG_ULC_DET_EN_MASK, PMIC_RG_ULC_DET_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_HWCV_EN_ADDR, PMIC_RG_HWCV_EN_MASK, PMIC_RG_HWCV_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_VBAT_CV_EN_ADDR, PMIC_RG_VBAT_CV_EN_MASK, PMIC_RG_VBAT_CV_EN_SHIFT, 1); /* enable debug flag output */ chr_set_register_value(info->regmap, PMIC_RG_PCHR_FLAG_EN_ADDR, PMIC_RG_PCHR_FLAG_EN_MASK, PMIC_RG_PCHR_FLAG_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_CSDAC_EN_ADDR, PMIC_RG_CSDAC_EN_MASK, PMIC_RG_CSDAC_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_CHR_EN_ADDR, PMIC_RG_CHR_EN_MASK, PMIC_RG_CHR_EN_SHIFT, 1); /* pmic_enable_interrupt(INT_WATCHDOG, 1, "PMIC"); */ } else { /* chr_set_register_value(info->regmap,PMIC_RG_INT_EN_WATCHDOG, 0); * TODO: remove it */ /* pmic_enable_interrupt(INT_WATCHDOG, 0, "PMIC"); */ chr_set_register_value(info->regmap, PMIC_RG_CHRWDT_EN_ADDR, PMIC_RG_CHRWDT_EN_MASK, PMIC_RG_CHRWDT_EN_SHIFT, 0); chr_set_register_value(info->regmap, PMIC_RG_CHR_EN_ADDR, PMIC_RG_CHR_EN_MASK, PMIC_RG_CHR_EN_SHIFT, 0); } return ret; } static int mt6357_get_cv(struct charger_device *chg_dev, u32 *cv) { int ret = 0; u32 array_size; u32 val; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); array_size = ARRAY_SIZE(VBAT_CV_VTH); val = chr_get_register_value(info->regmap, PMIC_RG_VBAT_CV_VTH_ADDR, PMIC_RG_VBAT_CV_VTH_MASK, PMIC_RG_VBAT_CV_VTH_SHIFT); *cv = charging_value_to_parameter(VBAT_CV_VTH, array_size, val); return ret; } static int mt6357_dump_register(struct charger_device *chg_dev) { int ret = 0; u32 i = 0; u32 ichg = 0, cv = 0; bool chg_en = false; struct mt6357_charger *info = dev_get_drvdata(&chg_dev->dev); ret = mt6357_get_ichg(chg_dev, &ichg); ret = mt6357_get_cv(chg_dev, &cv); ret = mt6357_is_charging_enabled(chg_dev, &chg_en); for (i = MT6357_CHR_TOP_CON0; i <= MT6357_PCHR_ELR1; i += 2) pr_notice("[0x%x]=0x%x\t", i, chg_get_register(info->regmap, i)); pr_notice("\n"); for (i = MT6357_CHR_CON0; i <= MT6357_CHR_CON9; i += 2) pr_notice("[0x%x]=0x%x\t", i, chg_get_register(info->regmap, i)); pr_notice("\n"); pr_notice("ICHG = %dmA, CV = %dmV, CHG_EN = %d\n", ichg / 1000, cv / 1000, chg_en); return ret; } static struct charger_ops mt6357_charger_ops = { /* normal charging */ .plug_in = mt6357_plug_in, /* .plug_out = mt6357_plug_out, */ .enable = mt6357_enable_charging, .is_enabled = mt6357_is_charging_enabled, .get_charging_current = mt6357_get_ichg, .set_charging_current = mt6357_set_ichg, .get_min_charging_current = mt6357_get_min_ichg, .get_constant_voltage = mt6357_get_cv, .set_constant_voltage = mt6357_set_cv, .kick_wdt = mt6357_kick_wdt, .dump_registers = mt6357_dump_register, /* Event */ //.event = mt6357_do_event, }; static int mt6357_charger_parse_dt(struct mt6357_charger *info, struct device *dev) { struct device_node *np = dev->of_node; struct mt6357_charger_desc *desc = NULL; pr_notice("%s: starts\n", __func__); if (!np) { pr_notice("%s: no device node\n", __func__); return -EINVAL; } info->desc = &mt6357_chg_default_desc; desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); if (!desc) return -ENOMEM; memcpy(desc, &mt6357_chg_default_desc, sizeof(struct mt6357_charger_desc)); if (of_property_read_string(np, "charger_name", &info->charger_dev_name) < 0) { pr_notice("%s: no charger name\n", __func__); info->charger_dev_name = "primary_chg"; } if (of_property_read_string(np, "alias_name", &info->charger_prop.alias_name) < 0) { pr_notice("%s: no alias name\n", __func__); info->charger_prop.alias_name = "mt6357"; } if (of_property_read_u32(np, "ichg", &desc->ichg) < 0) pr_notice("%s: no ichg\n", __func__); if (of_property_read_u32(np, "cv", &desc->cv) < 0) pr_notice("%s: no cv\n", __func__); if (of_property_read_u32(np, "vcdt_hv_thres", &desc->vcdt_hv_thres) < 0) pr_notice("%s: no vcdt_hv_thres\n", __func__); if (of_property_read_u32(np, "vbat_ov_thres", &desc->vbat_ov_thres) < 0) pr_notice("%s: no vbat_ov_thres\n", __func__); info->desc = desc; pr_notice("chr name:%s alias:%s\n", info->charger_dev_name, info->charger_prop.alias_name); return 0; } static int mt6357_charger_init_setting(struct mt6357_charger *info) { int ret = 0; unsigned short val; val = chr_get_register_value(info->regmap, PMIC_RG_VBAT_CV_VTH_ADDR, PMIC_RG_VBAT_CV_VTH_MASK, PMIC_RG_VBAT_CV_VTH_SHIFT); pr_notice("[%s] VBAT_CV_VTH: 0x%x\n", __func__, val); chr_set_register_value(info->regmap, PMIC_RG_CHRWDT_TD_ADDR, PMIC_RG_CHRWDT_TD_MASK, PMIC_RG_CHRWDT_TD_SHIFT, 3); /* 32s */ chr_set_register_value(info->regmap, PMIC_RG_CHRWDT_EN_ADDR, PMIC_RG_CHRWDT_EN_MASK, PMIC_RG_CHRWDT_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_CHRWDT_WR_ADDR, PMIC_RG_CHRWDT_WR_MASK, PMIC_RG_CHRWDT_WR_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_VCDT_MODE_ADDR, PMIC_RG_VCDT_MODE_MASK, PMIC_RG_VCDT_MODE_SHIFT, 0); chr_set_register_value(info->regmap, PMIC_RG_VCDT_HV_EN_ADDR, PMIC_RG_VCDT_HV_EN_MASK, PMIC_RG_VCDT_HV_EN_SHIFT, 1); /* force leave USBDL mode */ chr_set_register_value(info->regmap, PMIC_RG_USBDL_SET_ADDR, PMIC_RG_USBDL_SET_MASK, PMIC_RG_USBDL_SET_SHIFT, 0); chr_set_register_value(info->regmap, PMIC_RG_USBDL_RST_ADDR, PMIC_RG_USBDL_RST_MASK, PMIC_RG_USBDL_RST_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_BC11_BB_CTRL_ADDR, PMIC_RG_BC11_BB_CTRL_MASK, PMIC_RG_BC11_BB_CTRL_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_BC11_RST_ADDR, PMIC_RG_BC11_RST_MASK, PMIC_RG_BC11_RST_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_VBAT_OV_EN_ADDR, PMIC_RG_VBAT_OV_EN_MASK, PMIC_RG_VBAT_OV_EN_SHIFT, 1); chr_set_register_value(info->regmap, PMIC_RG_CSDAC_MODE_ADDR, PMIC_RG_CSDAC_MODE_MASK, PMIC_RG_CSDAC_MODE_SHIFT, 1); /* CC mode */ /* TODO */ chr_set_register_value(info->regmap, PMIC_RG_VBAT_OV_VTH_ADDR, PMIC_RG_VBAT_OV_VTH_MASK, PMIC_RG_VBAT_OV_VTH_SHIFT, 4); /* 4450mV */ chr_set_register_value(info->regmap, PMIC_RG_VCDT_HV_VTH_ADDR, PMIC_RG_VCDT_HV_VTH_MASK, PMIC_RG_VCDT_HV_VTH_SHIFT, 0xd); /* 7000mV */ chr_set_register_value(info->regmap, PMIC_RG_ULC_DET_EN_ADDR, PMIC_RG_ULC_DET_EN_MASK, PMIC_RG_ULC_DET_EN_SHIFT, 1); /* chr_set_register_value(info->regmap, PMIC_RG_LOW_ICH_DB, 1); 16ms */ return ret; } static int mt6357_charger_probe(struct platform_device *pdev) { int ret = 0; struct mt6357_charger *info = NULL; pr_notice("%s: starts\n", __func__); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; info->chip = (struct mt6397_chip *)dev_get_drvdata( pdev->dev.parent); info->regmap = info->chip->regmap; mt6357_charger_parse_dt(info, &pdev->dev); platform_set_drvdata(pdev, info); info->pdev = pdev; /* Register charger device */ info->charger_dev = charger_device_register(info->charger_dev_name, &pdev->dev, info, &mt6357_charger_ops, &info->charger_prop); if (IS_ERR_OR_NULL(info->charger_dev)) { ret = PTR_ERR(info->charger_dev); goto err_register_charger_dev; } info->charger_dev->is_polling_mode = true; dev_set_drvdata(&info->charger_dev->dev, info); mt6357_charger_init_setting(info); pr_notice("%s: done\n", __func__); return 0; err_register_charger_dev: devm_kfree(&pdev->dev, info); return ret; } static const struct of_device_id mt6357_charger_of_match[] = { {.compatible = "mediatek,mt6357-pulse-charger",}, {}, }; static int mt6357_charger_remove(struct platform_device *pdev) { struct mtk_charger_type *info = platform_get_drvdata(pdev); if (info) devm_kfree(&pdev->dev, info); return 0; } MODULE_DEVICE_TABLE(of, mt6357_charger_of_match); static struct platform_driver mt6357_charger_driver = { .probe = mt6357_charger_probe, .remove = mt6357_charger_remove, //.shutdown = mt6357_charger_shutdown, .driver = { .name = "mt6357-charger", .of_match_table = mt6357_charger_of_match, }, }; static int __init mt6357_charger_init(void) { return platform_driver_register(&mt6357_charger_driver); } module_init(mt6357_charger_init); static void __exit mt6357_charger_exit(void) { platform_driver_unregister(&mt6357_charger_driver); } module_exit(mt6357_charger_exit); MODULE_AUTHOR("wy.chuang "); MODULE_DESCRIPTION("MTK 6357 Charger Driver"); MODULE_LICENSE("GPL");