Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932883AbbBDE5s (ORCPT ); Tue, 3 Feb 2015 23:57:48 -0500 Received: from mailout2.samsung.com ([203.254.224.25]:50205 "EHLO mailout2.samsung.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753826AbbBDE4X (ORCPT ); Tue, 3 Feb 2015 23:56:23 -0500 X-AuditID: cbfee690-f79ab6d0000046f7-0f-54d1a671faf8 From: Jaewon Kim To: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-pm@vger.kernel.org Cc: Inki Dae , SangBae Lee , Rob Herring , Pawel Moll , Mark Rutland , Ian Campbell , Kumar Gala , Lee Jones , Chanwoo Choi , Sebastian Reichel , Mark Brown , Beomho Seo , Jaewon Kim Subject: [PATCH v3 3/6] power: max77843_charger: Add Max77843 charger device driver Date: Wed, 04 Feb 2015 13:56:08 +0900 Message-id: <1423025771-4139-4-git-send-email-jaewon02.kim@samsung.com> X-Mailer: git-send-email 1.7.9.5 In-reply-to: <1423025771-4139-1-git-send-email-jaewon02.kim@samsung.com> References: <1423025771-4139-1-git-send-email-jaewon02.kim@samsung.com> X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupnkeLIzCtJLcpLzFFi42JZI2JSolu47GKIwdL9khanP21jt5j68Amb xfUvz1kt5h85x2rR/2Yhq8W5VysZLSbdn8BisaPhCKvF/a9HGS0u75rDZvG59wijxdLrF5ks Jkxfy2LRuvcIu8XxTwdZLE7vLnEQ8Fgzbw2jx+W+XiaPlcu/sHlsWtXJ5nHn2h42j74tqxg9 Pm+SC2CP4rJJSc3JLEst0rdL4MrY2iZV0F5T8eP2PJYGxodpXYycHBICJhKvJ51kgrDFJC7c W8/WxcjFISSwlFHi1JZ2oAQHWNHbLfIQ8emMEre6TzFDOG1MEisPrGQH6WYT0Jb4vn4xK4gt IhAh8fzjSmYQm1ngDbPE45XRILawQKjEoQN3WUBsFgFVic5L18B6eQXcJdpfPGWGWKYgMWeS DUiYU8BDou3bObASIaCSf1fvsoPslRB4yS7xcO9Jdog5AhLfJh9igeiVldh0gBniGUmJgytu sExgFF7AyLCKUTS1ILmgOCm9yESvODG3uDQvXS85P3cTIzCeTv97NmEH470D1ocYBTgYlXh4 BfIvhgixJpYVV+YeYjQF2jCRWUo0OR8YtXkl8YbGZkYWpiamxkbmlmZK4ryvpX4GCwmkJ5ak ZqemFqQWxReV5qQWH2Jk4uCUamBc02u8Jvv7dZfbsktP6x+rZis1nnr71P8/rKWbTf5FT1Pb vfST4DvX5PCC6O/n7FY7le5fL3nSgW9ykVvRLCXemxvnPbeQOf8oKUvuW3PDjMvaHH3a2lWN S6s1me1CsibpBz8psZxw+OOHlvKq35l222fI7tX6P+PsnS/8u0SNbhS+vBhrxM6nxFKckWio xVxUnAgACgHcRaICAAA= X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFrrJIsWRmVeSWpSXmKPExsVy+t9jQd3CZRdDDDb08lqc/rSN3WLqwyds Fte/PGe1mH/kHKtF/5uFrBbnXq1ktJh0fwKLxY6GI6wW978eZbS4vGsOm8Xn3iOMFkuvX2Sy mDB9LYtF694j7BbHPx1ksTi9u8RBwGPNvDWMHpf7epk8Vi7/wuaxaVUnm8eda3vYPPq2rGL0 +LxJLoA9qoHRJiM1MSW1SCE1Lzk/JTMv3VbJOzjeOd7UzMBQ19DSwlxJIS8xN9VWycUnQNct MwfoeiWFssScUqBQQGJxsZK+HaYJoSFuuhYwjRG6viFBcD1GBmggYQ1jxtY2qYL2mooft+ex NDA+TOti5OCQEDCReLtFvouRE8gUk7hwbz1bFyMXh5DAdEaJW92nmCGcNiaJlQdWsoNUsQlo S3xfv5gVxBYRiJB4/nElM4jNLPCGWeLxymgQW1ggVOLQgbssIDaLgKpE56VrYL28Au4S7S+e MkMsVpCYM8kGJMwp4CHR9u0cWIkQUMm/q3fZJzDyLmBkWMUomlqQXFCclJ5rqFecmFtcmpeu l5yfu4kRHK3PpHYwrmywOMQowMGoxMMrkH8xRIg1say4MvcQowQHs5II7+J5QCHelMTKqtSi /Pii0pzU4kOMpkBHTWSWEk3OByaSvJJ4Q2MTMyNLI3NDCyNjcyVxXiX7thAhgfTEktTs1NSC 1CKYPiYOTqkGxulV/NdXWC/olAzY9f1ltNXGgHzz5Q0avPePzZmmp+R4TdpZ2MxtwuK8qqjY 32vfMG5l3WHOKX80mV8lZJ0Fs4aMY0X27gXH5FY9Usr44BO6spCheckkkfc1XFJ6Bw63vPop +X3+Nzbdxg87bqSsvhI89/UF0QUmyXs+VHxt1KtdlbtE+cCr90osxRmJhlrMRcWJAIwVAnXs AgAA DLP-Filter: Pass X-MTR: 20000000000000000@CPGS X-CFilter-Loop: Reflected Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 15561 Lines: 568 From: Beomho Seo This patch adds device driver of max77843 charger. This driver provide initialize each charging mode(e.g. fast charge, top-off mode and constant charging mode so on.). Additionally, control charging paramters to use i2c interface. Cc: Sebastian Reichel Signed-off-by: Beomho Seo --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/max77843_charger.c | 508 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 516 insertions(+) create mode 100644 drivers/power/max77843_charger.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0108c2a..a054a28 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -332,6 +332,13 @@ config CHARGER_MAX14577 Say Y to enable support for the battery charger control sysfs and platform data of MAX14577/77836 MUICs. +config CHARGER_MAX77843 + tristate "Maxim MAX77843 battery charger driver" + depends on MFD_MAX77843 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX77843 + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index dfa8942..212c6a2 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o +obj-$(CONFIG_CHARGER_MAX77843) += max77843_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o diff --git a/drivers/power/max77843_charger.c b/drivers/power/max77843_charger.c new file mode 100644 index 0000000..392eebc1a --- /dev/null +++ b/drivers/power/max77843_charger.c @@ -0,0 +1,508 @@ +/* + * Charger driver for Maxim MAX77843 + * + * Copyright (C) 2015 Samsung Electronics, Co., Ltd. + * Author: Beomho Seo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published bythe Free Software Foundation. + */ + +#include +#include +#include +#include + +struct max77843_charger_info { + u32 fast_charge_uamp; + u32 top_off_uamp; + u32 input_uamp_limit; +}; + +struct max77843_charger { + struct device *dev; + struct max77843 *max77843; + struct i2c_client *client; + struct regmap *regmap; + struct power_supply psy; + + struct max77843_charger_info *info; +}; + +static int max77843_charger_get_max_current(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = 0; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_CNFG_09, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read max current register: %d\n", ret); + return ret; + } + + if (reg_data <= 0x03) { + val = MAX77843_CHG_INPUT_CURRENT_LIMIT_MIN; + } else if (reg_data >= 0x78) { + val = MAX77843_CHG_INPUT_CURRENT_LIMIT_MAX; + } else { + val = reg_data / 3; + if (reg_data % 3 == 0) + val *= 100000; + else if (reg_data % 3 == 1) + val = val * 100000 + 33000; + else + val = val * 100000 + 67000; + } + + return val; +} + +static int max77843_charger_get_now_current(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = 0; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_CNFG_02, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read charge current register: %d\n", ret); + return ret; + } + + reg_data &= MAX77843_CHG_FAST_CHG_CURRENT_MASK; + + if (reg_data <= 0x02) + val = MAX77843_CHG_FAST_CHG_CURRENT_MIN; + else if (reg_data >= 0x3f) + val = MAX77843_CHG_FAST_CHG_CURRENT_MAX; + else + val = reg_data * MAX77843_CHG_FAST_CHG_CURRENT_STEP; + + return val; +} + +static int max77843_charger_get_online(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = 0; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_INT_OK, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read charger status: %d\n", ret); + return ret; + } + + if (reg_data & MAX77843_CHG_CHGIN_OK) + val = true; + else + val = false; + + return val; +} + +static int max77843_charger_get_present(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = 0; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_DTLS_00, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read battery present: %d\n", ret); + return ret; + } + + if (reg_data & MAX77843_CHG_BAT_DTLS) + val = false; + else + val = true; + + return val; +} + +static int max77843_charger_get_health(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = POWER_SUPPLY_HEALTH_UNKNOWN; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_DTLS_01, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read battery health: %d\n", ret); + return ret; + } + + reg_data &= MAX77843_CHG_BAT_DTLS_MASK; + + switch (reg_data) { + case MAX77843_CHG_NO_BAT: + val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case MAX77843_CHG_LOW_VOLT_BAT: + case MAX77843_CHG_OK_BAT: + case MAX77843_CHG_OK_LOW_VOLT_BAT: + val = POWER_SUPPLY_HEALTH_GOOD; + break; + case MAX77843_CHG_LONG_BAT_TIME: + val = POWER_SUPPLY_HEALTH_DEAD; + break; + case MAX77843_CHG_OVER_VOLT_BAT: + case MAX77843_CHG_OVER_CURRENT_BAT: + val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + default: + val = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + + return val; +} + +static int max77843_charger_get_status(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret, val = 0; + unsigned int reg_data; + + ret = regmap_read(regmap, MAX77843_CHG_REG_CHG_DTLS_01, ®_data); + if (ret) { + dev_err(charger->dev, + "Failed to read charger status: %d\n", ret); + return ret; + } + + reg_data &= MAX77843_CHG_DTLS_MASK; + + switch (reg_data) { + case MAX77843_CHG_PQ_MODE: + case MAX77843_CHG_CC_MODE: + case MAX77843_CHG_CV_MODE: + val = POWER_SUPPLY_STATUS_CHARGING; + break; + case MAX77843_CHG_TO_MODE: + case MAX77843_CHG_DO_MODE: + val = POWER_SUPPLY_STATUS_FULL; + break; + case MAX77843_CHG_HT_MODE: + case MAX77843_CHG_TF_MODE: + case MAX77843_CHG_TS_MODE: + val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case MAX77843_CHG_OFF_MODE: + val = POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + val = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + return val; +} + +static const char *model_name = "MAX77843"; +static const char *manufacturer = "Maxim Integrated"; + +static int max77843_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max77843_charger *charger = container_of(psy, + struct max77843_charger, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max77843_charger_get_status(charger); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max77843_charger_get_health(charger); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max77843_charger_get_present(charger); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max77843_charger_get_online(charger); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = max77843_charger_get_now_current(charger); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = max77843_charger_get_max_current(charger); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property max77843_charger_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static int max77843_charger_init_current_limit(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + struct max77843_charger_info *info = charger->info; + unsigned int input_uamp_limit = info->input_uamp_limit; + int ret; + unsigned int reg_data, val; + + ret = regmap_update_bits(regmap, MAX77843_CHG_REG_CHG_CNFG_02, + MAX77843_CHG_OTG_ILIMIT_MASK, + MAX77843_CHG_OTG_ILIMIT_900); + if (ret) { + dev_err(charger->dev, + "Failed to write OTG current limit: %d\n", ret); + return ret; + } + + if (input_uamp_limit == MAX77843_CHG_INPUT_CURRENT_LIMIT_MIN) { + reg_data = 0x03; + } else if (input_uamp_limit == MAX77843_CHG_INPUT_CURRENT_LIMIT_MAX) { + reg_data = 0x78; + } else { + if (input_uamp_limit < MAX77843_CHG_INPUT_CURRENT_LIMIT_REF) + val = 0x03; + else + val = 0x02; + + input_uamp_limit -= MAX77843_CHG_INPUT_CURRENT_LIMIT_MIN; + input_uamp_limit /= MAX77843_CHG_INPUT_CURRENT_LIMIT_STEP; + reg_data = val + input_uamp_limit; + } + + ret = regmap_write(regmap, MAX77843_CHG_REG_CHG_CNFG_09, reg_data); + if (ret) + dev_err(charger->dev, + "Failed to write charge current limit: %d\n", ret); + + return ret; +} + +static int max77843_charger_init_top_off(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + struct max77843_charger_info *info = charger->info; + unsigned int top_off_uamp = info->top_off_uamp; + int ret; + unsigned int reg_data; + + if (top_off_uamp == MAX77843_CHG_TOP_OFF_CURRENT_MIN) { + reg_data = 0x00; + } else if (top_off_uamp == MAX77843_CHG_TOP_OFF_CURRENT_MAX) { + reg_data = 0x07; + } else { + top_off_uamp -= MAX77843_CHG_TOP_OFF_CURRENT_MIN; + top_off_uamp /= MAX77843_CHG_TOP_OFF_CURRENT_STEP; + reg_data = top_off_uamp; + } + + ret = regmap_update_bits(regmap, MAX77843_CHG_REG_CHG_CNFG_03, + MAX77843_CHG_TOP_OFF_CURRENT_MASK, reg_data); + if (ret) + dev_err(charger->dev, + "Failed to write top off current: %d\n", ret); + + return ret; +} + +static int max77843_charger_init_fast_charge(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + struct max77843_charger_info *info = charger->info; + unsigned int fast_charge_uamp = info->fast_charge_uamp; + int ret; + unsigned int reg_data; + + if (fast_charge_uamp < info->input_uamp_limit) { + reg_data = 0x09; + } else if (fast_charge_uamp == MAX77843_CHG_FAST_CHG_CURRENT_MIN) { + reg_data = 0x02; + } else if (fast_charge_uamp == MAX77843_CHG_FAST_CHG_CURRENT_MAX) { + reg_data = 0x3f; + } else { + fast_charge_uamp -= MAX77843_CHG_FAST_CHG_CURRENT_MIN; + fast_charge_uamp /= MAX77843_CHG_FAST_CHG_CURRENT_STEP; + reg_data = 0x02 + fast_charge_uamp; + } + + ret = regmap_update_bits(regmap, MAX77843_CHG_REG_CHG_CNFG_02, + MAX77843_CHG_FAST_CHG_CURRENT_MASK, reg_data); + if (ret) + dev_err(charger->dev, + "Failed to write fast charge current: %d\n", ret); + + return ret; +} + +static int max77843_charger_init(struct max77843_charger *charger) +{ + struct regmap *regmap = charger->regmap; + int ret; + + ret = regmap_write(regmap, MAX77843_CHG_REG_CHG_CNFG_06, + MAX77843_CHG_WRITE_CAP_UNBLOCK); + if (ret) { + dev_err(charger->dev, + "Failed to unblock write capability: %d\n", ret); + return ret; + } + + ret = regmap_write(regmap, MAX77843_CHG_REG_CHG_CNFG_01, + MAX77843_CHG_RESTART_THRESHOLD_DISABLE); + if (ret) { + dev_err(charger->dev, + "Failed to write charger restart threshold: %d\n", ret); + return ret; + } + + ret = max77843_charger_init_fast_charge(charger); + if (ret) { + dev_err(charger->dev, + "Failed to set fast charge mode: %d\n", ret); + return ret; + } + + ret = max77843_charger_init_top_off(charger); + if (ret) { + dev_err(charger->dev, "Failed to set top off charge mode.\n"); + return ret; + } + + ret = max77843_charger_init_current_limit(charger); + if (ret) + dev_err(charger->dev, "Faied to set current limit.\n"); + + return 0; +} + +static struct max77843_charger_info *max77843_charger_dt_init( + struct platform_device *pdev) +{ + struct max77843_charger_info *info; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No charger OF node\n"); + return ERR_PTR(-EINVAL); + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "maxim,fast-charge-uamp", + &info->fast_charge_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse fast charge current.\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,top-off-uamp", + &info->top_off_uamp); + if (ret) { + dev_err(&pdev->dev, + "Cannot parse primary charger termination voltage.\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,input-uamp-limit", + &info->input_uamp_limit); + if (ret) { + dev_err(&pdev->dev, "Cannot parse input current limit value\n"); + return ERR_PTR(ret); + } + + return info; +} + +static int max77843_charger_probe(struct platform_device *pdev) +{ + struct max77843 *max77843 = dev_get_drvdata(pdev->dev.parent); + struct max77843_charger *charger; + int ret; + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + platform_set_drvdata(pdev, charger); + charger->dev = &pdev->dev; + charger->max77843 = max77843; + charger->client = max77843->i2c_chg; + charger->regmap = max77843->regmap_chg; + + charger->info = max77843_charger_dt_init(pdev); + if (IS_ERR_OR_NULL(charger->info)) + return PTR_ERR(charger->info); + + charger->psy.name = "max77843-charger"; + charger->psy.type = POWER_SUPPLY_TYPE_MAINS; + charger->psy.get_property = max77843_charger_get_property; + charger->psy.properties = max77843_charger_props; + charger->psy.num_properties = ARRAY_SIZE(max77843_charger_props); + + ret = max77843_charger_init(charger); + if (ret) + return ret; + + ret = power_supply_register(&pdev->dev, &charger->psy); + if (ret) { + dev_err(&pdev->dev, + "Failed to register power supply %d\n", ret); + return ret; + } + + return 0; +} + +static int max77843_charger_remove(struct platform_device *pdev) +{ + struct max77843_charger *charger = platform_get_drvdata(pdev); + + power_supply_unregister(&charger->psy); + + return 0; +} + +static const struct platform_device_id max77843_charger_id[] = { + { "max77843-charger", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77843_charger_id); + +static struct platform_driver max77843_charger_driver = { + .driver = { + .name = "max77843-charger", + }, + .probe = max77843_charger_probe, + .remove = max77843_charger_remove, + .id_table = max77843_charger_id, +}; +module_platform_driver(max77843_charger_driver); + +MODULE_DESCRIPTION("Maxim MAX77843 charger driver"); +MODULE_AUTHOR("Beomho Seo "); +MODULE_LICENSE("GPL"); -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/