All of lore.kernel.org
 help / color / mirror / Atom feed
From: Hans de Goede <hdegoede@redhat.com>
To: Wolfram Sang <wsa@the-dreams.de>,
	Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
	Mika Westerberg <mika.westerberg@linux.intel.com>,
	Sebastian Reichel <sre@kernel.org>
Cc: Hans de Goede <hdegoede@redhat.com>,
	linux-acpi@vger.kernel.org, Takashi Iwai <tiwai@suse.de>,
	linux-pm@vger.kernel.org
Subject: [PATCH v3 4/4] power: supply: Add driver for Cherry Trail Whiskey Cove PMIC Fuel Gauge
Date: Sat, 25 Mar 2017 14:55:50 +0100	[thread overview]
Message-ID: <20170325135550.22509-5-hdegoede@redhat.com> (raw)
In-Reply-To: <20170325135550.22509-1-hdegoede@redhat.com>

Add a driver for the Cherry Trail Whiskey Cove PMIC Fuel Gauge.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v2:
-There was no v2, it is skipped to the version in sync with the rest of
 the patch-set
Changes in v3:
-Change into a stand-alone power_supply battery driver instead of being a
 provider of extra properties for another battery driver
-Rename Kconfig symbol from CHT_WC_FUEL_GAUGE to BATTERY_INTEL_CHT_WC
---
 drivers/power/supply/Kconfig             |   9 +
 drivers/power/supply/Makefile            |   1 +
 drivers/power/supply/cht_wc_fuel_gauge.c | 319 +++++++++++++++++++++++++++++++
 3 files changed, 329 insertions(+)
 create mode 100644 drivers/power/supply/cht_wc_fuel_gauge.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index fd93110..350d86a 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -538,4 +538,13 @@ config AXP20X_POWER
 	  This driver provides support for the power supply features of
 	  AXP20x PMIC.
 
+config BATTERY_INTEL_CHT_WC
+	tristate "Intel Cherry Trail Whiskey Cove PMIC Fuel Gauge"
+	depends on INTEL_SOC_PMIC_CHTWC
+	help
+	  This adds support for the battery fuel gauge found in the Intel
+	  Cherry Trail Whiskey Cove PMIC. This driver allows monitoring
+	  of the charge level of the battery on Intel Cherry Trail systems
+	  with a Whiskey Cove PMIC.
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 3789a2c..46dca5c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -74,3 +74,4 @@ obj-$(CONFIG_CHARGER_TPS65090)	+= tps65090-charger.o
 obj-$(CONFIG_CHARGER_TPS65217)	+= tps65217_charger.o
 obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
 obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
+obj-$(CONFIG_BATTERY_INTEL_CHT_WC) += cht_wc_fuel_gauge.o
diff --git a/drivers/power/supply/cht_wc_fuel_gauge.c b/drivers/power/supply/cht_wc_fuel_gauge.c
new file mode 100644
index 0000000..0db83ad
--- /dev/null
+++ b/drivers/power/supply/cht_wc_fuel_gauge.c
@@ -0,0 +1,319 @@
+/*
+ * Intel Cherry Trail Whiskey Cove Fuel Gauge driver
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define FG_CHARGE_NOW			0x05
+#define FG_VOLTAGE_NOW			0x09
+#define FG_CURRENT_NOW			0x0a
+#define FG_CURRENT_AVG			0x0b
+#define FG_CHARGE_FULL			0x10
+#define FG_CHARGE_DESIGN		0x18
+#define FG_VOLTAGE_AVG			0x19
+#define FG_VOLTAGE_OCV			0x1b /* Only updated during charging */
+
+#define PMIC_CHGRSTATUS			0x1a
+#define PMIC_CHGRSTATUS_NOT_CHARGING	BIT(0)
+
+#define CHT_WC_FG_PTYPE		4
+
+static const unsigned int charger_cable_ids[] = {
+	EXTCON_CHG_USB_SDP, EXTCON_CHG_USB_CDP,
+	EXTCON_CHG_USB_DCP, EXTCON_CHG_USB_ACA
+};
+
+struct cht_wc_fg_data {
+	struct device *dev;
+	/*
+	 * The ACPI _CRS table contains info for 4 clients, 1 for the charger-
+	 * manager part of the pmic and 3 for the actual fuel-gauge (which has
+	 * 3 i2c addresses) note we use only 1 fg address/client.
+	 */
+	struct i2c_client *pmic_client;
+	struct i2c_client *fg_client;
+	struct extcon_dev *extcon;
+	struct power_supply *battery;
+	struct work_struct changed_work;
+	struct notifier_block extcon_nb;
+};
+
+static int cht_wc_fg_read(struct cht_wc_fg_data *fg, u8 reg,
+			  union power_supply_propval *val, int scale,
+			  int sign_extend)
+{
+	int ret;
+
+	ret = i2c_smbus_read_word_data(fg->fg_client, reg);
+	if (ret < 0)
+		return ret;
+
+	if (sign_extend)
+		ret = sign_extend32(ret, 15);
+
+	val->intval = ret * scale;
+
+	return 0;
+}
+
+static int cht_wc_fg_get_status(struct cht_wc_fg_data *fg,
+				union power_supply_propval *val)
+{
+	int i, ret;
+	bool vbus_present = false;
+
+	for (i = 0; i < ARRAY_SIZE(charger_cable_ids); i++) {
+		if (extcon_get_state(fg->extcon, charger_cable_ids[i]) > 0) {
+			vbus_present = true;
+			break;
+		}
+	}
+
+	if (!vbus_present) {
+		val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		return 0;
+	}
+
+	ret = i2c_smbus_read_byte_data(fg->pmic_client, PMIC_CHGRSTATUS);
+	if (ret < 0)
+		return ret;
+
+	/* Not charging while we have Vbus means the battery is full */
+	if (ret & PMIC_CHGRSTATUS_NOT_CHARGING)
+		val->intval = POWER_SUPPLY_STATUS_FULL;
+	else
+		val->intval = POWER_SUPPLY_STATUS_CHARGING;
+
+	return 0;
+}
+
+static int cht_wc_fg_get_property(struct power_supply *psy,
+	enum power_supply_property prop, union power_supply_propval *val)
+{
+	struct cht_wc_fg_data *fg = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		ret = cht_wc_fg_get_status(fg, val);
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = cht_wc_fg_read(fg, FG_VOLTAGE_NOW, val, 75, 0);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+		ret = cht_wc_fg_read(fg, FG_VOLTAGE_AVG, val, 75, 0);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+		ret = cht_wc_fg_read(fg, FG_VOLTAGE_OCV, val, 75, 0);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = cht_wc_fg_read(fg, FG_CURRENT_NOW, val, 150, 1);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_AVG:
+		ret = cht_wc_fg_read(fg, FG_CURRENT_AVG, val, 150, 1);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		ret = cht_wc_fg_read(fg, FG_CHARGE_DESIGN, val, 500, 0);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		ret = cht_wc_fg_read(fg, FG_CHARGE_FULL, val, 500, 0);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		ret = cht_wc_fg_read(fg, FG_CHARGE_NOW, val, 500, 0);
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		break;
+	default:
+		ret = -ENODATA;
+	}
+
+	return ret;
+}
+
+static void cht_wc_fg_external_power_changed(struct power_supply *psy)
+{
+	struct cht_wc_fg_data *fg = power_supply_get_drvdata(psy);
+
+	schedule_work(&fg->changed_work);
+}
+
+static enum power_supply_property cht_wc_fg_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_AVG,
+	POWER_SUPPLY_PROP_VOLTAGE_OCV,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CURRENT_AVG,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_SCOPE,
+};
+
+static const struct power_supply_desc bat_desc = {
+	/* Matches charger.supplied_to for external_power_changed callback */
+	.name			= "main-battery",
+	.type			= POWER_SUPPLY_TYPE_BATTERY,
+	.properties		= cht_wc_fg_properties,
+	.num_properties		= ARRAY_SIZE(cht_wc_fg_properties),
+	.get_property		= cht_wc_fg_get_property,
+	.external_power_changed = cht_wc_fg_external_power_changed,
+};
+
+static void cht_wc_fg_changed_work(struct work_struct *work)
+{
+	struct cht_wc_fg_data *fg =
+		container_of(work, struct cht_wc_fg_data, changed_work);
+
+	/* Wait a bit to allow the fuel-gauge to also detect the new status */
+	msleep(200);
+
+	power_supply_changed(fg->battery);
+}
+
+static int cht_wc_fg_extcon_event(struct notifier_block *nb,
+				  unsigned long event, void *param)
+{
+	struct cht_wc_fg_data *fg =
+		container_of(nb, struct cht_wc_fg_data, extcon_nb);
+
+	schedule_work(&fg->changed_work);
+
+	return NOTIFY_OK;
+}
+
+static int cht_wc_fg_probe(struct i2c_client *client,
+			const struct i2c_device_id *i2c_id)
+{
+	struct device *dev = &client->dev;
+	struct power_supply_config bat_cfg = {};
+	struct cht_wc_fg_data *fg;
+	acpi_status status;
+	unsigned long long ptyp;
+	int ret;
+
+	status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Failed to get PTYPE\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * The same ACPI HID is used with different PMICs check PTYP to
+	 * ensure that we are dealing with a Whiskey Cove PMIC.
+	 */
+	if (ptyp != CHT_WC_FG_PTYPE)
+		return -ENODEV;
+
+	fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
+	if (!fg)
+		return -ENOMEM;
+
+	fg->dev = dev;
+	fg->pmic_client = client;
+	INIT_WORK(&fg->changed_work, cht_wc_fg_changed_work);
+
+	/*
+	 * We use the Whiskey Cove PMIC's pwrsrc detection block to see
+	 * if we are charging or not. We could access the pwrsrc regs
+	 * ourselves, but that requires re-implementing the extcon code,
+	 * so we just use the extcon interface.
+	 */
+	fg->extcon = extcon_get_extcon_dev("cht_wcove_pwrsrc");
+	if (!fg->extcon)
+		return -EPROBE_DEFER;
+
+	/*
+	 * The current resource settings table for the fuel gauge contains
+	 * multiple i2c devices on 2 different i2c-busses.
+	 */
+	fg->fg_client = i2c_acpi_new_device(dev, 1);
+	if (!fg->fg_client)
+		return -EPROBE_DEFER;
+
+	bat_cfg.drv_data = fg;
+	fg->battery = devm_power_supply_register(dev, &bat_desc, &bat_cfg);
+	if (IS_ERR(fg->battery)) {
+		i2c_unregister_device(fg->fg_client);
+		return PTR_ERR(fg->battery);
+	}
+
+	fg->extcon_nb.notifier_call = cht_wc_fg_extcon_event;
+	ret = devm_extcon_register_notifier(dev, fg->extcon, -1,
+					    &fg->extcon_nb);
+	if (ret) {
+		i2c_unregister_device(fg->fg_client);
+		return ret;
+	}
+
+	i2c_set_clientdata(client, fg);
+
+	return 0;
+}
+
+static int cht_wc_fg_remove(struct i2c_client *i2c)
+{
+	struct cht_wc_fg_data *fg = i2c_get_clientdata(i2c);
+
+	i2c_unregister_device(fg->fg_client);
+
+	return 0;
+}
+
+static const struct i2c_device_id cht_wc_fg_i2c_id[] = {
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, cht_wc_fg_i2c_id);
+
+static const struct acpi_device_id cht_wc_fg_acpi_ids[] = {
+	{ "INT33FE", },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, cht_wc_fg_acpi_ids);
+
+static struct i2c_driver cht_wc_fg_driver = {
+	.driver	= {
+		.name	= "CHT Whiskey Cove PMIC Fuel Gauge",
+		.acpi_match_table = ACPI_PTR(cht_wc_fg_acpi_ids),
+	},
+	.probe = cht_wc_fg_probe,
+	.remove = cht_wc_fg_remove,
+	.id_table = cht_wc_fg_i2c_id,
+	.irq_index = 1,
+};
+
+module_i2c_driver(cht_wc_fg_driver);
+
+MODULE_DESCRIPTION("Intel CHT Whiskey Cove PMIC Fuel Gauge driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");
-- 
2.9.3

  parent reply	other threads:[~2017-03-25 13:55 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-03-25 13:55 [PATCH v3 0/4]: i2c-core improvements for complex ACPI-devices + cht-wc-fuel-gauge driver Hans de Goede
2017-03-25 13:55 ` [PATCH v3 1/4] i2c: core: Allow getting ACPI info by index Hans de Goede
2017-03-26 12:16   ` Andy Shevchenko
2017-03-25 13:55 ` [PATCH v3 2/4] i2c: core: Add new i2c_acpi_new_device helper function Hans de Goede
2017-03-25 13:55 ` [PATCH v3 3/4] i2c: core: Allow drivers to specify index for irq to get from of / ACPI Hans de Goede
2017-03-26 12:15   ` Andy Shevchenko
2017-03-26 15:07     ` Hans de Goede
2017-03-30 17:39     ` Hans de Goede
2017-03-30 20:39       ` Wolfram Sang
2017-03-31 10:03         ` Hans de Goede
2017-03-31 16:23           ` Wolfram Sang
2017-03-31 18:22             ` Hans de Goede
2017-03-31 19:54               ` Wolfram Sang
2017-03-31 20:13                 ` Andy Shevchenko
2017-03-31 20:59                 ` Hans de Goede
2017-03-31 21:19                   ` Wolfram Sang
2017-04-01 16:33               ` Dmitry Torokhov
2017-04-02 12:17                 ` Hans de Goede
2017-04-03 18:29                   ` Dmitry Torokhov
2017-03-25 13:55 ` Hans de Goede [this message]
2017-03-25 18:42   ` [PATCH v3 4/4] power: supply: Add driver for Cherry Trail Whiskey Cove PMIC Fuel Gauge Sebastian Reichel
2017-03-26  8:56     ` Hans de Goede

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170325135550.22509-5-hdegoede@redhat.com \
    --to=hdegoede@redhat.com \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=mika.westerberg@linux.intel.com \
    --cc=sre@kernel.org \
    --cc=tiwai@suse.de \
    --cc=wsa@the-dreams.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.