/******************************************************************************* Copyright (C), 2014, TP-LINK TECHNOLOGIES CO., LTD. File name : mp2617-charger.c Description : Driver for charge ic mp2617. Author : linyunfeng History: ------------------------------ V0.2, 2014-04-29, linyunfeng complete the driver of mp2617. V0.1, 2014-03-29, linyunfeng create file. *******************************************************************************/ #include #include #include #include #include #include #include #include #ifdef MP2617_DEBUG #undef dev_dbg #define dev_dbg(dev, format, arg...) dev_printk(KERN_INFO, dev, format, ##arg) #endif /* USB, set 500mA limit */ #define USB_INPUT_CURRENT_LIMIT_UA 500000 /* Charger, set 1000mA limit */ #define CHARGER_INPUT_CURRENT_LIMIT_UA 1000000 static u32 supported_input_current[] = { USB_INPUT_CURRENT_LIMIT_UA, CHARGER_INPUT_CURRENT_LIMIT_UA, }; #define DEFAULT_INPUT_CURRENT_LIMIT_UA CHARGER_INPUT_CURRENT_LIMIT_UA /* Default resistor used for detection */ #define DEFAULT_CHARGE_DET_RESISTOR 100 /* Default battery resistor */ #define DEFAULT_BATTERY_RESISTOR 120 /* Correspondence table of voltage and temperature */ static const int adc_map_temp[][2] = { {1800, -100}, {1577, -20}, {1524, -15}, {1463, -10}, {1396, -5}, {1322, 0}, {1243, 5}, {1159, 10}, {1073, 15}, {986, 20}, {900, 25}, {816, 30}, {736, 35}, {660, 40}, {590, 45}, {526, 50}, {468, 55}, {416, 60}, {369, 65}, {327, 70}, {0, 1000} }; struct mp2617_chip { struct platform_device *client; struct power_supply psy; struct power_supply *usb_psy; struct mutex lock; int input_current_limit_ua; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT int charging_det_resistor; int battery_resistor; u32 boost_en_gpio; #endif int battery_health; int charging_vbat_div; bool battery_present; bool charging_enabled; bool charging_allowed; u32 charging_en_gpio; u32 charging_ok_gpio; u32 charging_m0_gpio; u32 charging_m1_gpio; }; static enum power_supply_property mp2617_power_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CHARGING_ENABLED, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_CHG_OK, #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT POWER_SUPPLY_PROP_VOLTAGE_DROP, #endif }; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT /******************************************************************************* Function : mp2617_enable_boost Description : Enable or disable the boost chip Input : chip: chip data enable: true: enable, false: disable Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int mp2617_enable_boost(struct mp2617_chip *chip, bool enable) { int rc = 0; if (true == enable) { rc = gpio_direction_output(chip->boost_en_gpio, 1); } else { rc = gpio_direction_output(chip->boost_en_gpio, 0); } if (rc < 0) { dev_err(&chip->client->dev, "%s: enable or disable boost error\n", __func__); } dev_dbg(&chip->client->dev, "%s, enable=%d, rc = %d\n", __func__, enable, rc); return rc; } /******************************************************************************* Function : mp2617_get_property_voltage_drop Description : Get the voltage drop by power supply Input : chip: chip data Output : None Return : voltage or error code Others : None *******************************************************************************/ static int mp2617_get_property_voltage_drop(struct mp2617_chip *chip) { int rc = 0; int vol_drop = 0; struct qpnp_vadc_result results; rc = qpnp_vadc_read(P_MUX1_1_1, &results); if (rc) { pr_err("Unable to read vbat rc=%d\n", rc); return rc; } vol_drop = ((int)(results.physical)/chip->charging_det_resistor) * chip->battery_resistor / 1000; return vol_drop; } #endif /******************************************************************************* Function : mp2617_enable_charging Description : Enable charging Input : chip: chip data Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int mp2617_enable_charging(struct mp2617_chip *chip) { int rc = 0; if ((!chip->charging_enabled) && (true == chip->charging_allowed) && (true == chip->battery_present)) { rc = gpio_direction_output(chip->charging_en_gpio, 1); if (!rc) { chip->charging_enabled = true; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT mp2617_enable_boost(chip, false); #endif } } dev_dbg(&chip->client->dev, "%s, rc = %d\n", __func__, rc); return rc; } /******************************************************************************* Function : mp2617_disable_charging Description : Disable charging Input : chip: chip data Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int mp2617_disable_charging(struct mp2617_chip *chip) { int rc = 0; if (chip->charging_enabled) { rc = gpio_direction_output(chip->charging_en_gpio, 0); if (!rc) { chip->charging_enabled = false; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT mp2617_enable_boost(chip, true); #endif } } dev_dbg(&chip->client->dev, "%s, rc = %d\n", __func__, rc); return rc; } /******************************************************************************* Function : mp2617_get_property_chg_ok_level Description : Get the level of chg_ok gpio Input : chip: chip data Output : None Return : Gpio level Others : None *******************************************************************************/ static int mp2617_get_property_chg_ok_level(struct mp2617_chip *chip) { int rc = 0; int result = 0; rc = gpio_direction_input(chip->charging_ok_gpio); if (!rc) { result = gpio_get_value_cansleep(chip->charging_ok_gpio); } return result; } /******************************************************************************* Function : mp2617_set_input_current_limit Description : Set the limit of input current Input : chip: chip data current_limit_ua: the limit of input current Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int mp2617_set_input_current_limit(struct mp2617_chip *chip, int current_limit_ua) { int rc = 0; int i = 0; for (i = ARRAY_SIZE(supported_input_current) - 1; i >= 0; i--) { if (current_limit_ua >= supported_input_current[i]) { current_limit_ua = supported_input_current[i]; break; } } switch (current_limit_ua) { case USB_INPUT_CURRENT_LIMIT_UA: gpio_direction_output(chip->charging_m0_gpio, 0); gpio_direction_output(chip->charging_m1_gpio, 0); break; case CHARGER_INPUT_CURRENT_LIMIT_UA: gpio_direction_output(chip->charging_m0_gpio, 1); gpio_direction_output(chip->charging_m1_gpio, 0); break; default: dev_err(&chip->client->dev, "%s: Unsupported current_limit_ua=%d uA\n", __func__, current_limit_ua); rc = -EINVAL; break; } if (!rc) { chip->input_current_limit_ua = current_limit_ua; dev_dbg(&chip->client->dev, "%s: current_limit_ua=%d uA\n", __func__, current_limit_ua); } return rc; } /******************************************************************************* Function : mp2617_set_property_battery_present Description : Set the present state of battery Input : chip: chip data batt_present_state: the present state of battery Output : None Return : 0: OK Others : None *******************************************************************************/ static int mp2617_set_property_battery_present(struct mp2617_chip *chip, bool batt_present_state) { chip->battery_present = batt_present_state; return 0; } /******************************************************************************* Function : mp2617_get_property_battery_present Description : Get the present state of battery Input : chip: chip data Output : None Return : The present state of battery Others : None *******************************************************************************/ static int mp2617_get_property_battery_present(struct mp2617_chip *chip) { return chip->battery_present; } /******************************************************************************* Function : mp2617_set_property_battery_health Description : Set the health state of battery Input : chip: chip data health_state: the health state of battery Output : None Return : 0: OK Others : None *******************************************************************************/ static int mp2617_set_property_battery_health(struct mp2617_chip *chip, int health_state) { if ((health_state > POWER_SUPPLY_HEALTH_UNKNOWN) && (health_state <= POWER_SUPPLY_HEALTH_DEAD_OVERCOLD)) { chip->battery_health = health_state; if (POWER_SUPPLY_HEALTH_GOOD == health_state) { chip->charging_allowed = true; } else { chip->charging_allowed = false; } } else { chip->battery_health = POWER_SUPPLY_HEALTH_UNKNOWN; } return 0; } /******************************************************************************* Function : mp2617_get_property_battery_health Description : Get the health state of battery Input : chip: chip data Output : None Return : The health state of battery Others : None *******************************************************************************/ static int mp2617_get_property_battery_health(struct mp2617_chip *chip) { return chip->battery_health; } /******************************************************************************* Function : mp2617_get_property_battery_voltage Description : Get the voltage of battery Input : chip: chip data Output : None Return : The voltage of battery Others : None *******************************************************************************/ static int mp2617_get_property_battery_voltage(struct mp2617_chip *chip) { int rc = 0; struct qpnp_vadc_result results; rc = qpnp_vadc_read(P_MUX2_1_1, &results); if (rc) { pr_err("Unable to read vbat rc=%d\n", rc); return rc; } return ((int)(results.physical * chip->charging_vbat_div)/1000); } /******************************************************************************* Function : em_batt_map_linear Description : Get the result form table by linear map Input : table: linear map table table_size: table size in_num: number input Output : out_num: result with linear map Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int em_batt_map_linear(const int (*table)[2], const u32 table_size, int in_num, int *out_num) { u32 i = 0; if ((NULL == table) || (NULL == out_num)) { pr_err("Invalid input or output parameter\n"); return -EINVAL; } while (i < table_size) { if (table[i][0] < in_num) { break; } else { i++; } } if (i == 0) { *out_num = table[0][1]; } else if (i == table_size) { *out_num = table[table_size-1][1]; } else { /* result is between search_index and search_index-1, interpolate linearly */ *out_num = (((table[i][1] - table[i-1][1]) * (in_num - table[i-1][0])) / (table[i][0] - table[i-1][0])) + table[i-1][1]; } return 0; } /******************************************************************************* Function : mp2617_get_property_battery_id_therm Description : Get the temperature of battery Input : chip: chip data Output : None Return : The temperature of battery or error code Others : None *******************************************************************************/ static int mp2617_get_property_battery_id_therm(struct mp2617_chip *chip) { int rc = 0; int temp_value = 0; struct qpnp_vadc_result results; rc = qpnp_vadc_read(LR_MUX2_BAT_ID, &results); if (rc) { pr_err("Unable to read battery temperature, rc=%d\n", rc); return rc; } temp_value = ((int)results.physical) / 1000; /* convert to mV */ rc = em_batt_map_linear(adc_map_temp, sizeof(adc_map_temp)/sizeof(adc_map_temp[0]), temp_value, &temp_value); if(rc) { pr_err("Unable to get battery temperature, rc=%d\n", rc); return rc; } return temp_value; } static int mp2617_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_HEALTH: case POWER_SUPPLY_PROP_PRESENT: return 1; default: break; } return 0; } static int mp2617_power_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct mp2617_chip *chip = container_of(psy, struct mp2617_chip, psy); mutex_lock(&chip->lock); switch (psp) { case POWER_SUPPLY_PROP_CHARGING_ENABLED: if (val->intval) { mp2617_enable_charging(chip); } else { mp2617_disable_charging(chip); } break; case POWER_SUPPLY_PROP_CURRENT_MAX: mp2617_set_input_current_limit(chip, val->intval); break; case POWER_SUPPLY_PROP_HEALTH: mp2617_set_property_battery_health(chip, val->intval); break; case POWER_SUPPLY_PROP_PRESENT: mp2617_set_property_battery_present(chip, (bool)val->intval); break; default: mutex_unlock(&chip->lock); return -EINVAL; } mutex_unlock(&chip->lock); power_supply_changed(&chip->psy); return 0; } static int mp2617_power_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct mp2617_chip *chip = container_of(psy, struct mp2617_chip, psy); mutex_lock(&chip->lock); switch (psp) { case POWER_SUPPLY_PROP_HEALTH: val->intval = mp2617_get_property_battery_health(chip); break; case POWER_SUPPLY_PROP_PRESENT: val->intval = mp2617_get_property_battery_present(chip); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = mp2617_get_property_battery_voltage(chip); break; case POWER_SUPPLY_PROP_TEMP: val->intval = mp2617_get_property_battery_id_therm(chip); break; case POWER_SUPPLY_PROP_CHG_OK: val->intval = mp2617_get_property_chg_ok_level(chip); break; case POWER_SUPPLY_PROP_CHARGING_ENABLED: val->intval = chip->charging_enabled; break; case POWER_SUPPLY_PROP_CURRENT_MAX: val->intval = chip->input_current_limit_ua; break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = "MP2617"; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = "MPS"; break; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT case POWER_SUPPLY_PROP_VOLTAGE_DROP: val->intval = mp2617_get_property_voltage_drop(chip); break; #endif default: mutex_unlock(&chip->lock); return -EINVAL; } mutex_unlock(&chip->lock); return 0; } /******************************************************************************* Function : mp2617_external_power_changed Description : Set charging parameters because power supply device changes Input : psy: power supply psy Output : None Return : None Others : None *******************************************************************************/ static void mp2617_external_power_changed(struct power_supply *psy) { struct mp2617_chip *chip = container_of(psy, struct mp2617_chip, psy); union power_supply_propval prop = {0,}; int scope = POWER_SUPPLY_SCOPE_DEVICE; int current_limit = 0; int online = 0; int rc; mutex_lock(&chip->lock); dev_dbg(&chip->client->dev, "%s: start\n", __func__); rc = chip->usb_psy->get_property(chip->usb_psy, POWER_SUPPLY_PROP_ONLINE, &prop); if (rc) { dev_err(&chip->client->dev, "%s: could not read USB online property, rc=%d\n", __func__, rc); } else { online = prop.intval; } rc = chip->usb_psy->get_property(chip->usb_psy, POWER_SUPPLY_PROP_SCOPE, &prop); if (rc) { dev_err(&chip->client->dev, "%s: could not read USB scope property, rc=%d\n", __func__, rc); } else { scope = prop.intval; } rc = chip->usb_psy->get_property(chip->usb_psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop); if (rc) { dev_err(&chip->client->dev, "%s: could not read USB current max property, rc=%d\n", __func__, rc); } else { current_limit = prop.intval; } dev_dbg(&chip->client->dev, "%s: online=%d, scope=%d, current_limit=%d, present=%d\n", __func__, online, scope, current_limit, chip->battery_present); if (scope == POWER_SUPPLY_SCOPE_DEVICE) { if (online && chip->battery_present) { mp2617_set_input_current_limit(chip, current_limit); if (current_limit != 0 && POWER_SUPPLY_HEALTH_GOOD == chip->battery_health) { mp2617_enable_charging(chip); } } else { mp2617_disable_charging(chip); } } dev_dbg(&chip->client->dev, "%s: end\n", __func__); mutex_unlock(&chip->lock); power_supply_changed(&chip->psy); } /******************************************************************************* Function : mp2617_apply_dt_configs Description : Set default config Input : chip: chip data Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int __devinit mp2617_apply_dt_configs(struct mp2617_chip *chip) { struct device *dev = &chip->client->dev; struct device_node *node = chip->client->dev.of_node; int current_ma = 0; int value = 0; int rc = 0; chip->input_current_limit_ua = DEFAULT_INPUT_CURRENT_LIMIT_UA; #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT chip->charging_det_resistor = DEFAULT_CHARGE_DET_RESISTOR; chip->battery_resistor = DEFAULT_BATTERY_RESISTOR; mp2617_enable_boost(chip, true); #endif chip->battery_health = POWER_SUPPLY_HEALTH_GOOD; chip->battery_present = true; chip->charging_allowed = true; mp2617_disable_charging(chip); /* * All device tree parameters are optional so it is ok if read calls * fail. */ rc = of_property_read_u32(node, "mps,chg-current-ma", ¤t_ma); if (rc == 0) { chip->input_current_limit_ua = current_ma * 1000; } else { dev_err(dev, "%s: Failed to get charge current node, rc=%d\n", __func__, rc); } rc = of_property_read_u32(node, "mps,chg-vbat-div", &value); if (rc == 0) { chip->charging_vbat_div = value; } else { chip->charging_vbat_div = 1; dev_err(dev, "%s: Failed to get battery voltage division, rc=%d\n", __func__, rc); } rc = mp2617_set_input_current_limit(chip, chip->input_current_limit_ua); if (rc) { dev_err(dev, "%s: Failed to set charge current, rc=%d\n", __func__, rc); return rc; } #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT rc = of_property_read_u32(node, "mps,chg-det-resistor", &value); if (rc == 0) { chip->charging_det_resistor = value; } else { dev_err(dev, "%s: Failed to get detection resistor, rc=%d\n", __func__, rc); } rc = of_property_read_u32(node, "mps,chg-batt-resistor", &value); if (rc == 0) { chip->battery_resistor = value; } else { dev_err(dev, "%s: Failed to get battery resistor, rc=%d\n", __func__, rc); } #endif return rc; } /******************************************************************************* Function : mp2617_gpio_configs Description : Set gpio default config Input : chip: chip data Output : None Return : 0: OK, Others: Error Others : None *******************************************************************************/ static int __devinit mp2617_gpio_configs(struct mp2617_chip *chip) { struct device_node *node = chip->client->dev.of_node; int rc = 0; chip->charging_en_gpio = of_get_named_gpio(node, "mps,chg-en-gpio", 0); rc = gpio_request(chip->charging_en_gpio, "mps,chg-en-gpio"); if (rc) { pr_err("request charge en gpio failed, rc=%d\n", rc); gpio_free(chip->charging_en_gpio); return rc; } chip->charging_ok_gpio = of_get_named_gpio(node, "mps,chg-ok-gpio", 0); rc = gpio_request(chip->charging_ok_gpio, "mps,chg-ok-gpio"); if (rc) { pr_err("request charge ok gpio failed, rc=%d\n",rc); gpio_free(chip->charging_ok_gpio); return rc; } chip->charging_m0_gpio = of_get_named_gpio(node, "mps,chg-m0-gpio", 0); rc = gpio_request(chip->charging_m0_gpio, "mps,chg-m0-gpio"); if (rc) { pr_err("request charge m0 gpio failed, rc=%d\n", rc); gpio_free(chip->charging_m0_gpio); return rc; } chip->charging_m1_gpio = of_get_named_gpio(node, "mps,chg-m1-gpio", 0); rc = gpio_request(chip->charging_m1_gpio, "mps,chg-m1-gpio"); if (rc) { pr_err("request charge m1 gpio failed, rc=%d\n", rc); gpio_free(chip->charging_m1_gpio); return rc; } #ifdef CONFIG_POWER_BANK_DETECT_SUPPORT chip->boost_en_gpio = of_get_named_gpio(node, "mps,chg-boost-en-gpio", 0); rc = gpio_request(chip->boost_en_gpio, "mps,chg-boost-en-gpio"); if (rc) { pr_err("request charge m1 gpio failed, rc=%d\n", rc); gpio_free(chip->boost_en_gpio); return rc; } #endif return rc; } static int __devinit mp2617_probe(struct platform_device *pdev) { struct mp2617_chip *chip; struct device *dev = &pdev->dev; struct device_node *node = pdev->dev.of_node; int rc = 0; if (!node) { dev_err(dev, "%s: device tree information missing\n", __func__); return -ENODEV; } chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) { dev_err(dev, "%s: devm_kzalloc failed\n", __func__); return -ENOMEM; } mutex_init(&chip->lock); chip->client = pdev; chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) { dev_dbg(dev, "%s: USB supply not found; deferring charger probe\n", __func__); return -EPROBE_DEFER; } rc = mp2617_gpio_configs(chip); if (rc) { return rc; } rc = mp2617_apply_dt_configs(chip); if (rc) { return rc; } chip->psy.name = "battery"; chip->psy.type = POWER_SUPPLY_TYPE_BATTERY; chip->psy.properties = mp2617_power_properties; chip->psy.num_properties = ARRAY_SIZE(mp2617_power_properties); chip->psy.get_property = mp2617_power_get_property; chip->psy.set_property = mp2617_power_set_property; chip->psy.property_is_writeable = mp2617_property_is_writeable; chip->psy.external_power_changed = mp2617_external_power_changed; rc = power_supply_register(dev, &chip->psy); if (rc < 0) { dev_err(dev, "%s: power_supply_register failed, rc=%d\n", __func__, rc); return rc; } mp2617_external_power_changed(&chip->psy); dev_info(dev, "%s: MP2617 charger probed successfully\n", __func__); return rc; } static int __devexit mp2617_remove(struct platform_device *pdev) { return 0; } static const struct of_device_id mp2617_match[] = { { .compatible = "mps,mp2617", }, { }, }; static struct platform_driver mp2617_driver = { .driver = { .name = "mp2617", .owner = THIS_MODULE, .of_match_table = mp2617_match, }, .probe = mp2617_probe, .remove = __devexit_p(mp2617_remove), }; static int __init mp2617_init(void) { return platform_driver_register(&mp2617_driver); } module_init(mp2617_init); static void __exit mp2617_exit(void) { return platform_driver_unregister(&mp2617_driver); } module_exit(mp2617_exit); MODULE_DESCRIPTION("MP2617 Charger"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("mp2617");