From patchwork Thu Sep 27 20:08:51 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mathieu Poirier X-Patchwork-Id: 11807 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 88DFF24140 for ; Thu, 27 Sep 2012 20:10:05 +0000 (UTC) Received: from mail-ie0-f180.google.com (mail-ie0-f180.google.com [209.85.223.180]) by fiordland.canonical.com (Postfix) with ESMTP id 1546DA18968 for ; Thu, 27 Sep 2012 20:10:04 +0000 (UTC) Received: by mail-ie0-f180.google.com with SMTP id e10so5234801iej.11 for ; Thu, 27 Sep 2012 13:10:04 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf:from:to :subject:date:message-id:x-mailer:in-reply-to:references :x-gm-message-state; bh=QnI9ThqZiFQwNtqmJgLsN6fzbKHXEFo9WzzIrvXs4y4=; b=kd6uaxqBTJz0Ne6uhDmrGDdgL3NTG4XypaufPTw6aNJni1YvwCaQDGkUSnqjAC37bY X5k3HheutKOFwQqQ3k+TLcf5nSlcujInMpRehdV+Z+6KUneuDi5YESX4JtOZKo/PSw0m R+6sEzfgsIazi3auloqfGiLcYvsNoL6u7vaAou1BK5+GAbv+XfKbBkRy6wjwCb8DEtPa h286qI7P1swL2m5aU1KxJze+JiUcH9C4N/59oryNdzSwhUdRnc/bj6X+0HB/gIqgoy0h x5YMtTfi0u8WNammrkm/erGTp7rbrOT6n0zIaGVGL5M1HuInbU2bKW07Cip6WTxpWcs4 Kvzg== Received: by 10.42.110.130 with SMTP id q2mr3863394icp.53.1348776604836; Thu, 27 Sep 2012 13:10:04 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.50.184.232 with SMTP id ex8csp436346igc; Thu, 27 Sep 2012 13:10:04 -0700 (PDT) Received: by 10.68.138.229 with SMTP id qt5mr13225098pbb.122.1348776604145; Thu, 27 Sep 2012 13:10:04 -0700 (PDT) Received: from mail-da0-f50.google.com (mail-da0-f50.google.com [209.85.210.50]) by mx.google.com with ESMTPS id wn9si8349880pbc.234.2012.09.27.13.10.03 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 27 Sep 2012 13:10:04 -0700 (PDT) Received-SPF: neutral (google.com: 209.85.210.50 is neither permitted nor denied by best guess record for domain of mathieu.poirier@linaro.org) client-ip=209.85.210.50; Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.210.50 is neither permitted nor denied by best guess record for domain of mathieu.poirier@linaro.org) smtp.mail=mathieu.poirier@linaro.org Received: by mail-da0-f50.google.com with SMTP id z20so544914dae.37 for ; Thu, 27 Sep 2012 13:10:03 -0700 (PDT) Received: by 10.66.83.33 with SMTP id n1mr12100285pay.66.1348776603826; Thu, 27 Sep 2012 13:10:03 -0700 (PDT) Received: from localhost.localdomain (S0106002369de4dac.cg.shawcable.net. [70.73.24.112]) by mx.google.com with ESMTPS id sa2sm1587890pbc.4.2012.09.27.13.10.01 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 27 Sep 2012 13:10:02 -0700 (PDT) From: mathieu.poirier@linaro.org To: patches@linaro.org Subject: [PATCH 34/57] power: ab8500_fg: add power cut feature for ab8505 Date: Thu, 27 Sep 2012 14:08:51 -0600 Message-Id: <1348776554-10019-35-git-send-email-mathieu.poirier@linaro.org> X-Mailer: git-send-email 1.7.5.4 In-Reply-To: <1348776554-10019-1-git-send-email-mathieu.poirier@linaro.org> References: <1348776554-10019-1-git-send-email-mathieu.poirier@linaro.org> X-Gm-Message-State: ALoCoQn2mSmuTHx0M7KI1u2YFqbyMCAQRBp449D0NPOpNiZ+6G+d7Iea4MYc2HWGO2oVS6/DP2pw From: Rikard Olsson Add support for a power cut feature which allows user to configure when ab8505 should shut down system due to low battery. Signed-off-by: Rikard Olsson Signed-off-by: Mathieu Poirier Reviewed-by: Martin SJOBLOM Reviewed-by: Jonas ABERG --- drivers/power/ab8500_fg.c | 488 +++++++++++++++++++++++++++++++++- include/linux/mfd/abx500.h | 10 + include/linux/mfd/abx500/ab8500-bm.h | 8 + 3 files changed, 502 insertions(+), 4 deletions(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 5e4a46b..fde189a 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -2351,6 +2351,64 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) dev_err(di->dev, "BattOk init write failed.\n"); goto out; } + + if ((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) { + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, + di->bat->fg_params->pcut_max_time); + + if (ret) { + dev_err(di->dev, + "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", + __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, + di->bat->fg_params->pcut_flag_time); + + if (ret) { + dev_err(di->dev, + "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", + __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, + di->bat->fg_params->pcut_max_restart); + + if (ret) { + dev_err(di->dev, + "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", + __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, + di->bat->fg_params->pcut_debunce_time); + + if (ret) { + dev_err(di->dev, + "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", + __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, + di->bat->fg_params->pcut_enable); + + if (ret) { + dev_err(di->dev, + "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", + __func__); + goto out; + }; + } out: return ret; } @@ -2572,22 +2630,433 @@ static ssize_t ab8500_show_capacity(struct device *dev, return scnprintf(buf, PAGE_SIZE, "%d\n", capacity); } +static ssize_t ab8505_powercut_flagtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_flagtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long unsigned reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (kstrtoul(buf, 10, ®_value) != 0) + goto fail; + + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_maxtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; + +} + +static ssize_t ab8505_powercut_maxtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long unsigned int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (kstrtoul(buf, 10, ®_value) != 0) + goto fail; + + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_restart_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + unsigned long int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (kstrtoul(buf, 10, ®_value) != 0) + goto fail; + + if (reg_value > 0xF) { + dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n"); + +fail: + return count; + +} + +static ssize_t ab8505_powercut_timer_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) + goto fail; + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + unsigned long int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (kstrtoul(buf, 10, ®_value) != 0) + goto fail; + + if (reg_value > 0x1) { + dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_flag_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + unsigned long int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (kstrtoul(buf, 10, ®_value) != 0) + goto fail; + + if (reg_value > 0x7) { + dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_enable_status_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5)); + +fail: + return ret; +} + static struct device_attribute ab8500_fg_sysfs_psy_attrs[] = { __ATTR(capacity, S_IRUGO, ab8500_show_capacity, NULL), }; +static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { + __ATTR(powercut_flagtime, (S_IRUGO | S_IWUGO), + ab8505_powercut_flagtime_read, + ab8505_powercut_flagtime_write), + __ATTR(powercut_maxtime, (S_IRUGO | S_IWUGO), + ab8505_powercut_maxtime_read, + ab8505_powercut_maxtime_write), + __ATTR(powercut_restart_max, (S_IRUGO | S_IWUGO), + ab8505_powercut_restart_read, + ab8505_powercut_restart_write), + __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL), + __ATTR(powercut_restart_counter, S_IRUGO, + ab8505_powercut_restart_counter_read, NULL), + __ATTR(powercut_enable, (S_IRUGO | S_IWUGO), ab8505_powercut_read, + ab8505_powercut_write), + __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL), + __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUGO), + ab8505_powercut_debounce_read, + ab8505_powercut_debounce_write), + __ATTR(powercut_enable_status, S_IRUGO, + ab8505_powercut_enable_status_read, NULL), +}; + static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev) { - unsigned int i; + unsigned int i, j; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); for (i = 0; i < ARRAY_SIZE(ab8500_fg_sysfs_psy_attrs); i++) if (device_create_file(dev, &ab8500_fg_sysfs_psy_attrs[i])) - goto sysfs_psy_create_attrs_failed; + goto sysfs_psy_create_attrs_failed_ab8500; + + if ((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) { + for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++) + if (device_create_file(dev, + &ab8505_fg_sysfs_psy_attrs[j])) + goto sysfs_psy_create_attrs_failed_ab8505; + } return 0; -sysfs_psy_create_attrs_failed: - dev_err(dev, "Failed creating sysfs psy attrs.\n"); +sysfs_psy_create_attrs_failed_ab8505: + dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n"); + while (j--) + device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); + +sysfs_psy_create_attrs_failed_ab8500: + dev_err(dev, "Failed creating sysfs psy attrs for ab8500.\n"); while (i--) device_remove_file(dev, &ab8500_fg_sysfs_psy_attrs[i]); @@ -2597,9 +3066,20 @@ sysfs_psy_create_attrs_failed: static void ab8500_fg_sysfs_psy_remove_attrs(struct device *dev) { unsigned int i; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); for (i = 0; i < ARRAY_SIZE(ab8500_fg_sysfs_psy_attrs); i++) (void)device_remove_file(dev, &ab8500_fg_sysfs_psy_attrs[i]); + + if ((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) { + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + (void)device_remove_file(dev, + &ab8505_fg_sysfs_psy_attrs[i]); + } } /* Exposure to the sysfs interface <> */ diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index eef1ddc..97e918f 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -204,6 +204,11 @@ struct abx500_fg; * points. * @maint_thres This is the threshold where we stop reporting * battery full while in maintenance, in per cent + * @pcut_enable: Enable power cut feature in ab8505 + * @pcut_max_time: Max time threshold + * @pcut_flag_time: Flagtime threshold + * @pcut_max_restart: Max number of restarts + * @pcut_debunce_time: Sets battery debounce time */ struct abx500_fg_parameters { int recovery_sleep_timer; @@ -221,6 +226,11 @@ struct abx500_fg_parameters { int battok_raising_th_sel1; int user_cap_limit; int maint_thres; + bool pcut_enable; + u8 pcut_max_time; + u8 pcut_flag_time; + u8 pcut_max_restart; + u8 pcut_debunce_time; }; /** diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h index 175197c..cd15ea3 100644 --- a/include/linux/mfd/abx500/ab8500-bm.h +++ b/include/linux/mfd/abx500/ab8500-bm.h @@ -235,6 +235,14 @@ /* Battery type */ #define BATTERY_UNKNOWN 00 +/* Registers for pcut feature in ab8505 and ab9540 */ +#define AB8505_RTC_PCUT_CTL_STATUS_REG 0x12 +#define AB8505_RTC_PCUT_TIME_REG 0x13 +#define AB8505_RTC_PCUT_MAX_TIME_REG 0x14 +#define AB8505_RTC_PCUT_FLAG_TIME_REG 0x15 +#define AB8505_RTC_PCUT_RESTART_REG 0x16 +#define AB8505_RTC_PCUT_DEBOUNCE_REG 0x17 + /** * struct res_to_temp - defines one point in a temp to res curve. To * be used in battery packs that combines the identification resistor with a