Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1605141 > unrolled thread

[PATCH v2 0/3] Add Intel Cherry Trail Whiskey Cove PMIC support

Started byHans de Goede <hdegoede@redhat.com>
First post2017-03-20 22:40 +0100
Last post2017-03-26 13:10 +0200
Articles 6 — 3 participants

Back to article view | Back to linux.kernel


Contents

  [PATCH v2 0/3] Add Intel Cherry Trail Whiskey Cove PMIC support Hans de Goede <hdegoede@redhat.com> - 2017-03-20 22:40 +0100
    [PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver Hans de Goede <hdegoede@redhat.com> - 2017-03-20 22:40 +0100
      Re: [PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove  PMIC extcon driver Chanwoo Choi <cw00.choi@samsung.com> - 2017-03-22 02:00 +0100
    [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver Hans de Goede <hdegoede@redhat.com> - 2017-03-20 22:40 +0100
      Re: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver Lee Jones <lee.jones@linaro.org> - 2017-03-23 17:50 +0100
        Re: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver Hans de Goede <hdegoede@redhat.com> - 2017-03-26 13:10 +0200

#1605141 — [PATCH v2 0/3] Add Intel Cherry Trail Whiskey Cove PMIC support

FromHans de Goede <hdegoede@redhat.com>
Date2017-03-20 22:40 +0100
Subject[PATCH v2 0/3] Add Intel Cherry Trail Whiskey Cove PMIC support
Message-ID<tndbX-2bk-3@gated-at.bofh.it>
Hi all,

Here is a v2 (v4 for the first 2 patches) of patches 1-3 from my
Intel Cherry Trail Whiskey Cove PMIC support series.

I believe these 3 patches are ready for merging into the mfd / acpi /
extcon subsystems now. They can be merged indepdently, so I would
like to ask the maintainers of these subsystems to merge these now.

Next up is a rework of some of the other patches I will send a v2
of those when ready, but these do not need to wait for those (and
at least the mfd + acpi patch together fix suspend/resume issues on
some devices so are useful without the rest).

Regards,

Hans

[toc] | [next] | [standalone]


#1605142 — [PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver

FromHans de Goede <hdegoede@redhat.com>
Date2017-03-20 22:40 +0100
Subject[PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver
Message-ID<tndbY-2bk-29@gated-at.bofh.it>
In reply to#1605141
Add a driver for charger detection / control on the Intel Cherrytrail
Whiskey Cove PMIC.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v2:
-Improve wait for charger detection loop, use jiffies to get an accurate timeout
-Sort registers by address, remove duplicate definition
-Return IRQ_NONE on interrupt handling errors
-Various small style cleanups
-Fix reporting no charger cable being present when detecting the same
 charger type 2 times in a row
-Reset to autonomous / hw-mode on probe-error and on remove
---
 drivers/extcon/Kconfig         |   7 +
 drivers/extcon/Makefile        |   1 +
 drivers/extcon/extcon-cht-wc.c | 390 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+)
 create mode 100644 drivers/extcon/extcon-cht-wc.c

diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 96bbae5..4cace6b 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496
 	  This ACPI device is typically found on Intel Baytrail or Cherrytrail
 	  based tablets, or other Baytrail / Cherrytrail devices.
 
+config EXTCON_CHT_WC
+	tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
+	depends on INTEL_SOC_PMIC_CHTWC
+	help
+	  Say Y here to enable extcon support for charger detection / control
+	  on the Intel Cherrytrail Whiskey Cove PMIC.
+
 config EXTCON_MAX14577
 	tristate "Maxim MAX14577/77836 EXTCON Support"
 	depends on MFD_MAX14577
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 237ac3f..160f88b 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -7,6 +7,7 @@ extcon-core-objs		+= extcon.o devres.o
 obj-$(CONFIG_EXTCON_ADC_JACK)	+= extcon-adc-jack.o
 obj-$(CONFIG_EXTCON_ARIZONA)	+= extcon-arizona.o
 obj-$(CONFIG_EXTCON_AXP288)	+= extcon-axp288.o
+obj-$(CONFIG_EXTCON_CHT_WC)	+= extcon-cht-wc.o
 obj-$(CONFIG_EXTCON_GPIO)	+= extcon-gpio.o
 obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
 obj-$(CONFIG_EXTCON_MAX14577)	+= extcon-max14577.o
diff --git a/drivers/extcon/extcon-cht-wc.c b/drivers/extcon/extcon-cht-wc.c
new file mode 100644
index 0000000..9e14972
--- /dev/null
+++ b/drivers/extcon/extcon-cht-wc.c
@@ -0,0 +1,390 @@
+/*
+ * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
+ * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/extcon.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define CHT_WC_PHYCTRL			0x5e07
+
+#define CHT_WC_CHGRCTRL0		0x5e16
+#define CHT_WC_CHGRCTRL0_CHGRRESET	BIT(0)
+#define CHT_WC_CHGRCTRL0_EMRGCHREN	BIT(1)
+#define CHT_WC_CHGRCTRL0_EXTCHRDIS	BIT(2)
+#define CHT_WC_CHGRCTRL0_SWCONTROL	BIT(3)
+#define CHT_WC_CHGRCTRL0_TTLCK_MASK	BIT(4)
+#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK	BIT(5)
+#define CHT_WC_CHGRCTRL0_DBPOFF_MASK	BIT(6)
+#define CHT_WC_CHGRCTRL0_WDT_NOKICK	BIT(7)
+
+#define CHT_WC_CHGRCTRL1		0x5e17
+
+#define CHT_WC_USBSRC			0x5e29
+#define CHT_WC_USBSRC_STS_MASK		GENMASK(1, 0)
+#define CHT_WC_USBSRC_STS_SUCCESS	2
+#define CHT_WC_USBSRC_STS_FAIL		3
+#define CHT_WC_USBSRC_TYPE_SHIFT	2
+#define CHT_WC_USBSRC_TYPE_MASK		GENMASK(5, 2)
+#define CHT_WC_USBSRC_TYPE_NONE		0
+#define CHT_WC_USBSRC_TYPE_SDP		1
+#define CHT_WC_USBSRC_TYPE_DCP		2
+#define CHT_WC_USBSRC_TYPE_CDP		3
+#define CHT_WC_USBSRC_TYPE_ACA		4
+#define CHT_WC_USBSRC_TYPE_SE1		5
+#define CHT_WC_USBSRC_TYPE_MHL		6
+#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN	7
+#define CHT_WC_USBSRC_TYPE_OTHER	8
+#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY	9
+
+#define CHT_WC_PWRSRC_IRQ		0x6e03
+#define CHT_WC_PWRSRC_IRQ_MASK		0x6e0f
+#define CHT_WC_PWRSRC_STS		0x6e1e
+#define CHT_WC_PWRSRC_VBUS		BIT(0)
+#define CHT_WC_PWRSRC_DC		BIT(1)
+#define CHT_WC_PWRSRC_BAT		BIT(2)
+#define CHT_WC_PWRSRC_ID_GND		BIT(3)
+#define CHT_WC_PWRSRC_ID_FLOAT		BIT(4)
+
+enum cht_wc_usb_id {
+	USB_ID_OTG,
+	USB_ID_GND,
+	USB_ID_FLOAT,
+	USB_RID_A,
+	USB_RID_B,
+	USB_RID_C,
+};
+
+/* Strings matching the cht_wc_usb_id enum labels */
+static const char * const usb_id_str[] = {
+	"otg", "gnd", "float", "rid_a", "rid_b", "rid_c" };
+
+enum cht_wc_mux_select {
+	MUX_SEL_PMIC = 0,
+	MUX_SEL_SOC,
+};
+
+static const unsigned int cht_wc_extcon_cables[] = {
+	EXTCON_USB,
+	EXTCON_USB_HOST,
+	EXTCON_CHG_USB_SDP,
+	EXTCON_CHG_USB_CDP,
+	EXTCON_CHG_USB_DCP,
+	EXTCON_CHG_USB_ACA,
+	EXTCON_NONE,
+};
+
+struct cht_wc_extcon_data {
+	struct device *dev;
+	struct regmap *regmap;
+	struct extcon_dev *edev;
+	unsigned int previous_cable;
+	int usb_id;
+};
+
+static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
+{
+	if (ext->usb_id)
+		return ext->usb_id;
+
+	if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
+		return USB_ID_GND;
+	if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
+		return USB_ID_FLOAT;
+
+	/*
+	 * Once we have iio support for the gpadc we should read the USBID
+	 * gpadc channel here and determine ACA role based on that.
+	 */
+	return USB_ID_FLOAT;
+}
+
+static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext)
+{
+	int ret, usbsrc, status;
+	unsigned long timeout;
+
+	/* Charger detection can take upto 600ms, wait 800ms max. */
+	timeout = jiffies + msecs_to_jiffies(800);
+	do {
+		ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
+		if (ret) {
+			dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
+			return ret;
+		}
+
+		status = usbsrc & CHT_WC_USBSRC_STS_MASK;
+		if (status == CHT_WC_USBSRC_STS_SUCCESS ||
+		    status == CHT_WC_USBSRC_STS_FAIL)
+			break;
+
+		msleep(50); /* Wait a bit before retrying */
+	} while (time_before(jiffies, timeout));
+
+	if (status != CHT_WC_USBSRC_STS_SUCCESS) {
+		if (status == CHT_WC_USBSRC_STS_FAIL)
+			dev_warn(ext->dev, "Could not detect charger type\n");
+		else
+			dev_warn(ext->dev, "Timeout detecting charger type\n");
+		return EXTCON_CHG_USB_SDP; /* Save fallback */
+	}
+
+	usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
+	switch (usbsrc) {
+	default:
+		dev_warn(ext->dev, "Unhandled charger type %d\n", ret);
+		/* Fall through treat as SDP */
+	case CHT_WC_USBSRC_TYPE_SDP:
+	case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
+	case CHT_WC_USBSRC_TYPE_OTHER:
+		return EXTCON_CHG_USB_SDP;
+	case CHT_WC_USBSRC_TYPE_CDP:
+		return EXTCON_CHG_USB_CDP;
+	case CHT_WC_USBSRC_TYPE_DCP:
+	case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
+	case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
+		return EXTCON_CHG_USB_DCP;
+	case CHT_WC_USBSRC_TYPE_ACA:
+		return EXTCON_CHG_USB_ACA;
+	}
+}
+
+static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
+{
+	int ret;
+
+	ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
+	if (ret)
+		dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
+}
+
+/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
+static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
+				    unsigned int cable, bool state)
+{
+	extcon_set_state_sync(ext->edev, cable, state);
+	if (cable == EXTCON_CHG_USB_SDP)
+		extcon_set_state_sync(ext->edev, EXTCON_USB, state);
+}
+
+static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
+{
+	int ret, pwrsrc_sts, id;
+	unsigned int cable = EXTCON_NONE;
+
+	ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
+	if (ret) {
+		dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
+		return;
+	}
+
+	id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
+	if (id == USB_ID_GND) {
+		/* The 5v boost causes a false VBUS / SDP detect, skip */
+		goto charger_det_done;
+	}
+
+	/* Plugged into a host/charger or not connected? */
+	if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
+		/* Route D+ and D- to PMIC for future charger detection */
+		cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
+		goto set_state;
+	}
+
+	ret = cht_wc_extcon_get_charger(ext);
+	if (ret >= 0)
+		cable = ret;
+
+charger_det_done:
+	/* Route D+ and D- to SoC for the host / gadget controller */
+	cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
+
+set_state:
+	if (cable != ext->previous_cable) {
+		cht_wc_extcon_set_state(ext, cable, true);
+		cht_wc_extcon_set_state(ext, ext->previous_cable, false);
+		ext->previous_cable = cable;
+	}
+
+	extcon_set_state_sync(ext->edev, EXTCON_USB_HOST,
+			      id == USB_ID_GND || id == USB_RID_A);
+}
+
+static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
+{
+	struct cht_wc_extcon_data *ext = data;
+	int ret, irqs;
+
+	ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
+	if (ret) {
+		dev_err(ext->dev, "Error reading irqs: %d\n", ret);
+		return IRQ_NONE;
+	}
+
+	cht_wc_extcon_pwrsrc_event(ext);
+
+	ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
+	if (ret) {
+		dev_err(ext->dev, "Error writing irqs: %d\n", ret);
+		return IRQ_NONE;
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* usb_id sysfs attribute for debug / testing purposes */
+static ssize_t usb_id_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	struct cht_wc_extcon_data *ext = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", usb_id_str[ext->usb_id]);
+}
+
+static ssize_t usb_id_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t n)
+{
+	struct cht_wc_extcon_data *ext = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(usb_id_str); i++) {
+		if (sysfs_streq(buf, usb_id_str[i])) {
+			dev_info(ext->dev, "New usb_id %s\n", usb_id_str[i]);
+			ext->usb_id = i;
+			cht_wc_extcon_pwrsrc_event(ext);
+			return n;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static DEVICE_ATTR(usb_id, 0644, usb_id_show, usb_id_store);
+
+static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
+{
+	int ret, mask, val;
+
+	mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
+	val = enable ? mask : 0;
+	ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
+	if (ret)
+		dev_err(ext->dev, "Failed setting sw control: %d\n", ret);
+
+	return ret;
+}
+
+static int cht_wc_extcon_probe(struct platform_device *pdev)
+{
+	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+	struct cht_wc_extcon_data *ext;
+	int irq, ret;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
+	if (!ext)
+		return -ENOMEM;
+
+	ext->dev = &pdev->dev;
+	ext->regmap = pmic->regmap;
+	ext->previous_cable = EXTCON_NONE;
+
+	/* Initialize extcon device */
+	ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
+	if (IS_ERR(ext->edev))
+		return PTR_ERR(ext->edev);
+
+	/* Enable sw control */
+	ret = cht_wc_extcon_sw_control(ext, true);
+	if (ret)
+		return ret;
+
+	/* Register extcon device */
+	ret = devm_extcon_dev_register(ext->dev, ext->edev);
+	if (ret) {
+		dev_err(ext->dev, "Failed to register extcon device\n");
+		goto disable_sw_control;
+	}
+
+	/* Route D+ and D- to PMIC for initial charger detection */
+	cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
+
+	/* Get initial state */
+	cht_wc_extcon_pwrsrc_event(ext);
+
+	ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
+					IRQF_ONESHOT, pdev->name, ext);
+	if (ret) {
+		dev_err(ext->dev, "Failed to request interrupt\n");
+		goto disable_sw_control;
+	}
+
+	/* Unmask irqs */
+	ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
+			   (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
+				  CHT_WC_PWRSRC_ID_FLOAT));
+	if (ret) {
+		dev_err(ext->dev, "Failed to write the irq-mask: %d\n", ret);
+		goto disable_sw_control;
+	}
+
+	platform_set_drvdata(pdev, ext);
+	device_create_file(ext->dev, &dev_attr_usb_id);
+
+	return 0;
+
+disable_sw_control:
+	cht_wc_extcon_sw_control(ext, false);
+	return ret;
+}
+
+static int cht_wc_extcon_remove(struct platform_device *pdev)
+{
+	struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
+
+	device_remove_file(ext->dev, &dev_attr_usb_id);
+	cht_wc_extcon_sw_control(ext, false);
+
+	return 0;
+}
+
+static const struct platform_device_id cht_wc_extcon_table[] = {
+	{ .name = "cht_wcove_pwrsrc" },
+	{},
+};
+MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
+
+static struct platform_driver cht_wc_extcon_driver = {
+	.probe = cht_wc_extcon_probe,
+	.remove = cht_wc_extcon_remove,
+	.id_table = cht_wc_extcon_table,
+	.driver = {
+		.name = "cht_wcove_pwrsrc",
+	},
+};
+module_platform_driver(cht_wc_extcon_driver);
+
+MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.9.3

[toc] | [prev] | [next] | [standalone]


#1606122 — Re: [PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver

FromChanwoo Choi <cw00.choi@samsung.com>
Date2017-03-22 02:00 +0100
SubjectRe: [PATCH v2 3/3] extcon: cht-wc: Add Intel Cherry Trail Whiskey Cove PMIC extcon driver
Message-ID<tnCN4-2HJ-13@gated-at.bofh.it>
In reply to#1605142
Hi,

I already replied the some comment from v2 patch[1]
[1] https://patchwork.kernel.org/patch/9630101/

-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

On 2017년 03월 21일 06:30, Hans de Goede wrote:
> Add a driver for charger detection / control on the Intel Cherrytrail
> Whiskey Cove PMIC.
> 
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
> Changes in v2:
> -Improve wait for charger detection loop, use jiffies to get an accurate timeout
> -Sort registers by address, remove duplicate definition
> -Return IRQ_NONE on interrupt handling errors
> -Various small style cleanups
> -Fix reporting no charger cable being present when detecting the same
>  charger type 2 times in a row
> -Reset to autonomous / hw-mode on probe-error and on remove
> ---
>  drivers/extcon/Kconfig         |   7 +
>  drivers/extcon/Makefile        |   1 +
>  drivers/extcon/extcon-cht-wc.c | 390 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 398 insertions(+)
>  create mode 100644 drivers/extcon/extcon-cht-wc.c
> 
> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
> index 96bbae5..4cace6b 100644
> --- a/drivers/extcon/Kconfig
> +++ b/drivers/extcon/Kconfig
> @@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496
>  	  This ACPI device is typically found on Intel Baytrail or Cherrytrail
>  	  based tablets, or other Baytrail / Cherrytrail devices.
>  
> +config EXTCON_CHT_WC
> +	tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
> +	depends on INTEL_SOC_PMIC_CHTWC
> +	help
> +	  Say Y here to enable extcon support for charger detection / control
> +	  on the Intel Cherrytrail Whiskey Cove PMIC.
> +
>  config EXTCON_MAX14577
>  	tristate "Maxim MAX14577/77836 EXTCON Support"
>  	depends on MFD_MAX14577
> diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
> index 237ac3f..160f88b 100644
> --- a/drivers/extcon/Makefile
> +++ b/drivers/extcon/Makefile
> @@ -7,6 +7,7 @@ extcon-core-objs		+= extcon.o devres.o
>  obj-$(CONFIG_EXTCON_ADC_JACK)	+= extcon-adc-jack.o
>  obj-$(CONFIG_EXTCON_ARIZONA)	+= extcon-arizona.o
>  obj-$(CONFIG_EXTCON_AXP288)	+= extcon-axp288.o
> +obj-$(CONFIG_EXTCON_CHT_WC)	+= extcon-cht-wc.o
>  obj-$(CONFIG_EXTCON_GPIO)	+= extcon-gpio.o
>  obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
>  obj-$(CONFIG_EXTCON_MAX14577)	+= extcon-max14577.o
> diff --git a/drivers/extcon/extcon-cht-wc.c b/drivers/extcon/extcon-cht-wc.c
> new file mode 100644
> index 0000000..9e14972
> --- /dev/null
> +++ b/drivers/extcon/extcon-cht-wc.c
> @@ -0,0 +1,390 @@
> +/*
> + * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
> + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
> + *
> + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
> + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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/extcon.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/intel_soc_pmic.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#define CHT_WC_PHYCTRL			0x5e07
> +
> +#define CHT_WC_CHGRCTRL0		0x5e16
> +#define CHT_WC_CHGRCTRL0_CHGRRESET	BIT(0)
> +#define CHT_WC_CHGRCTRL0_EMRGCHREN	BIT(1)
> +#define CHT_WC_CHGRCTRL0_EXTCHRDIS	BIT(2)
> +#define CHT_WC_CHGRCTRL0_SWCONTROL	BIT(3)
> +#define CHT_WC_CHGRCTRL0_TTLCK_MASK	BIT(4)
> +#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK	BIT(5)
> +#define CHT_WC_CHGRCTRL0_DBPOFF_MASK	BIT(6)
> +#define CHT_WC_CHGRCTRL0_WDT_NOKICK	BIT(7)
> +
> +#define CHT_WC_CHGRCTRL1		0x5e17
> +
> +#define CHT_WC_USBSRC			0x5e29
> +#define CHT_WC_USBSRC_STS_MASK		GENMASK(1, 0)
> +#define CHT_WC_USBSRC_STS_SUCCESS	2
> +#define CHT_WC_USBSRC_STS_FAIL		3
> +#define CHT_WC_USBSRC_TYPE_SHIFT	2
> +#define CHT_WC_USBSRC_TYPE_MASK		GENMASK(5, 2)
> +#define CHT_WC_USBSRC_TYPE_NONE		0
> +#define CHT_WC_USBSRC_TYPE_SDP		1
> +#define CHT_WC_USBSRC_TYPE_DCP		2
> +#define CHT_WC_USBSRC_TYPE_CDP		3
> +#define CHT_WC_USBSRC_TYPE_ACA		4
> +#define CHT_WC_USBSRC_TYPE_SE1		5
> +#define CHT_WC_USBSRC_TYPE_MHL		6
> +#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN	7
> +#define CHT_WC_USBSRC_TYPE_OTHER	8
> +#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY	9
> +
> +#define CHT_WC_PWRSRC_IRQ		0x6e03
> +#define CHT_WC_PWRSRC_IRQ_MASK		0x6e0f
> +#define CHT_WC_PWRSRC_STS		0x6e1e
> +#define CHT_WC_PWRSRC_VBUS		BIT(0)
> +#define CHT_WC_PWRSRC_DC		BIT(1)
> +#define CHT_WC_PWRSRC_BAT		BIT(2)
> +#define CHT_WC_PWRSRC_ID_GND		BIT(3)
> +#define CHT_WC_PWRSRC_ID_FLOAT		BIT(4)
> +
> +enum cht_wc_usb_id {
> +	USB_ID_OTG,
> +	USB_ID_GND,
> +	USB_ID_FLOAT,
> +	USB_RID_A,
> +	USB_RID_B,
> +	USB_RID_C,
> +};
> +
> +/* Strings matching the cht_wc_usb_id enum labels */
> +static const char * const usb_id_str[] = {
> +	"otg", "gnd", "float", "rid_a", "rid_b", "rid_c" };
> +
> +enum cht_wc_mux_select {
> +	MUX_SEL_PMIC = 0,
> +	MUX_SEL_SOC,
> +};
> +
> +static const unsigned int cht_wc_extcon_cables[] = {
> +	EXTCON_USB,
> +	EXTCON_USB_HOST,
> +	EXTCON_CHG_USB_SDP,
> +	EXTCON_CHG_USB_CDP,
> +	EXTCON_CHG_USB_DCP,
> +	EXTCON_CHG_USB_ACA,
> +	EXTCON_NONE,
> +};
> +
> +struct cht_wc_extcon_data {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct extcon_dev *edev;
> +	unsigned int previous_cable;
> +	int usb_id;
> +};
> +
> +static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
> +{
> +	if (ext->usb_id)
> +		return ext->usb_id;
> +
> +	if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
> +		return USB_ID_GND;
> +	if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
> +		return USB_ID_FLOAT;
> +
> +	/*
> +	 * Once we have iio support for the gpadc we should read the USBID
> +	 * gpadc channel here and determine ACA role based on that.
> +	 */
> +	return USB_ID_FLOAT;
> +}
> +
> +static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext)
> +{
> +	int ret, usbsrc, status;
> +	unsigned long timeout;
> +
> +	/* Charger detection can take upto 600ms, wait 800ms max. */
> +	timeout = jiffies + msecs_to_jiffies(800);
> +	do {
> +		ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
> +		if (ret) {
> +			dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
> +			return ret;
> +		}
> +
> +		status = usbsrc & CHT_WC_USBSRC_STS_MASK;
> +		if (status == CHT_WC_USBSRC_STS_SUCCESS ||
> +		    status == CHT_WC_USBSRC_STS_FAIL)
> +			break;
> +
> +		msleep(50); /* Wait a bit before retrying */
> +	} while (time_before(jiffies, timeout));
> +
> +	if (status != CHT_WC_USBSRC_STS_SUCCESS) {
> +		if (status == CHT_WC_USBSRC_STS_FAIL)
> +			dev_warn(ext->dev, "Could not detect charger type\n");
> +		else
> +			dev_warn(ext->dev, "Timeout detecting charger type\n");
> +		return EXTCON_CHG_USB_SDP; /* Save fallback */
> +	}
> +
> +	usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
> +	switch (usbsrc) {
> +	default:
> +		dev_warn(ext->dev, "Unhandled charger type %d\n", ret);
> +		/* Fall through treat as SDP */
> +	case CHT_WC_USBSRC_TYPE_SDP:
> +	case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
> +	case CHT_WC_USBSRC_TYPE_OTHER:
> +		return EXTCON_CHG_USB_SDP;
> +	case CHT_WC_USBSRC_TYPE_CDP:
> +		return EXTCON_CHG_USB_CDP;
> +	case CHT_WC_USBSRC_TYPE_DCP:
> +	case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
> +	case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
> +		return EXTCON_CHG_USB_DCP;
> +	case CHT_WC_USBSRC_TYPE_ACA:
> +		return EXTCON_CHG_USB_ACA;
> +	}
> +}
> +
> +static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
> +{
> +	int ret;
> +
> +	ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
> +	if (ret)
> +		dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
> +}
> +
> +/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
> +static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
> +				    unsigned int cable, bool state)
> +{
> +	extcon_set_state_sync(ext->edev, cable, state);
> +	if (cable == EXTCON_CHG_USB_SDP)
> +		extcon_set_state_sync(ext->edev, EXTCON_USB, state);
> +}
> +
> +static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
> +{
> +	int ret, pwrsrc_sts, id;
> +	unsigned int cable = EXTCON_NONE;
> +
> +	ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
> +	if (ret) {
> +		dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
> +		return;
> +	}
> +
> +	id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
> +	if (id == USB_ID_GND) {
> +		/* The 5v boost causes a false VBUS / SDP detect, skip */
> +		goto charger_det_done;
> +	}
> +
> +	/* Plugged into a host/charger or not connected? */
> +	if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
> +		/* Route D+ and D- to PMIC for future charger detection */
> +		cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
> +		goto set_state;
> +	}
> +
> +	ret = cht_wc_extcon_get_charger(ext);
> +	if (ret >= 0)
> +		cable = ret;
> +
> +charger_det_done:
> +	/* Route D+ and D- to SoC for the host / gadget controller */
> +	cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
> +
> +set_state:
> +	if (cable != ext->previous_cable) {
> +		cht_wc_extcon_set_state(ext, cable, true);
> +		cht_wc_extcon_set_state(ext, ext->previous_cable, false);
> +		ext->previous_cable = cable;
> +	}
> +
> +	extcon_set_state_sync(ext->edev, EXTCON_USB_HOST,
> +			      id == USB_ID_GND || id == USB_RID_A);
> +}
> +
> +static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
> +{
> +	struct cht_wc_extcon_data *ext = data;
> +	int ret, irqs;
> +
> +	ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
> +	if (ret) {
> +		dev_err(ext->dev, "Error reading irqs: %d\n", ret);
> +		return IRQ_NONE;
> +	}
> +
> +	cht_wc_extcon_pwrsrc_event(ext);
> +
> +	ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
> +	if (ret) {
> +		dev_err(ext->dev, "Error writing irqs: %d\n", ret);
> +		return IRQ_NONE;
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/* usb_id sysfs attribute for debug / testing purposes */
> +static ssize_t usb_id_show(struct device *dev, struct device_attribute *attr,
> +			   char *buf)
> +{
> +	struct cht_wc_extcon_data *ext = dev_get_drvdata(dev);
> +
> +	return sprintf(buf, "%s\n", usb_id_str[ext->usb_id]);
> +}
> +
> +static ssize_t usb_id_store(struct device *dev, struct device_attribute *attr,
> +			    const char *buf, size_t n)
> +{
> +	struct cht_wc_extcon_data *ext = dev_get_drvdata(dev);
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(usb_id_str); i++) {
> +		if (sysfs_streq(buf, usb_id_str[i])) {
> +			dev_info(ext->dev, "New usb_id %s\n", usb_id_str[i]);
> +			ext->usb_id = i;
> +			cht_wc_extcon_pwrsrc_event(ext);
> +			return n;
> +		}
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static DEVICE_ATTR(usb_id, 0644, usb_id_show, usb_id_store);
> +
> +static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
> +{
> +	int ret, mask, val;
> +
> +	mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
> +	val = enable ? mask : 0;
> +	ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
> +	if (ret)
> +		dev_err(ext->dev, "Failed setting sw control: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int cht_wc_extcon_probe(struct platform_device *pdev)
> +{
> +	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
> +	struct cht_wc_extcon_data *ext;
> +	int irq, ret;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
> +	if (!ext)
> +		return -ENOMEM;
> +
> +	ext->dev = &pdev->dev;
> +	ext->regmap = pmic->regmap;
> +	ext->previous_cable = EXTCON_NONE;
> +
> +	/* Initialize extcon device */
> +	ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
> +	if (IS_ERR(ext->edev))
> +		return PTR_ERR(ext->edev);
> +
> +	/* Enable sw control */
> +	ret = cht_wc_extcon_sw_control(ext, true);
> +	if (ret)
> +		return ret;
> +
> +	/* Register extcon device */
> +	ret = devm_extcon_dev_register(ext->dev, ext->edev);
> +	if (ret) {
> +		dev_err(ext->dev, "Failed to register extcon device\n");
> +		goto disable_sw_control;
> +	}
> +
> +	/* Route D+ and D- to PMIC for initial charger detection */
> +	cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
> +
> +	/* Get initial state */
> +	cht_wc_extcon_pwrsrc_event(ext);
> +
> +	ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
> +					IRQF_ONESHOT, pdev->name, ext);
> +	if (ret) {
> +		dev_err(ext->dev, "Failed to request interrupt\n");
> +		goto disable_sw_control;
> +	}
> +
> +	/* Unmask irqs */
> +	ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
> +			   (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
> +				  CHT_WC_PWRSRC_ID_FLOAT));
> +	if (ret) {
> +		dev_err(ext->dev, "Failed to write the irq-mask: %d\n", ret);
> +		goto disable_sw_control;
> +	}
> +
> +	platform_set_drvdata(pdev, ext);
> +	device_create_file(ext->dev, &dev_attr_usb_id);
> +
> +	return 0;
> +
> +disable_sw_control:
> +	cht_wc_extcon_sw_control(ext, false);
> +	return ret;
> +}
> +
> +static int cht_wc_extcon_remove(struct platform_device *pdev)
> +{
> +	struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
> +
> +	device_remove_file(ext->dev, &dev_attr_usb_id);
> +	cht_wc_extcon_sw_control(ext, false);
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id cht_wc_extcon_table[] = {
> +	{ .name = "cht_wcove_pwrsrc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
> +
> +static struct platform_driver cht_wc_extcon_driver = {
> +	.probe = cht_wc_extcon_probe,
> +	.remove = cht_wc_extcon_remove,
> +	.id_table = cht_wc_extcon_table,
> +	.driver = {
> +		.name = "cht_wcove_pwrsrc",
> +	},
> +};
> +module_platform_driver(cht_wc_extcon_driver);
> +
> +MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> +MODULE_LICENSE("GPL v2");
> 

[toc] | [prev] | [next] | [standalone]


#1605143 — [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver

FromHans de Goede <hdegoede@redhat.com>
Date2017-03-20 22:40 +0100
Subject[PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver
Message-ID<tndbZ-2bk-41@gated-at.bofh.it>
In reply to#1605141
Add mfd driver for Intel CHT Whiskey Cove PMIC, based on various non
upstreamed CHT Whiskey Cove PMIC patches.

This is a somewhat minimal version which adds irqchip support and cells
for: ACPI PMIC opregion support, the i2c-controller driving the external
charger irc and the pwrsrc/extcon block.

Further cells can be added in the future if/when drivers are upstreamed
for them.

Cc: Bin Gao <bin.gao@intel.com>
Cc: Felipe Balbi <felipe.balbi@linux.intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---
Changes in v2:
-Since this uses plain mfd and not the intel_soc_pmic stuff give it
 its own Kconfig and allow this to be built as a module
-Add missing #include <acpi/acpi_bus.h>
Changes in v3:
-Drop #include <acpi/acpi_bus.h> again, not the right fix for the build errors
-Error out when the upper byte of the register-address passed to the regmap
 functions is 0 rather then hardcoding an address in that case
-Various minor style tweaks / cleanups
-Move defines of regulator register addresses to intel_pmic_chtwc.c,
 it is the only place where they are used
-Drop now empty include/linux/mfd/intel_chtwc.h
-Rename intel_soc_pmic_chtwc.c to intel_cht_wc.c to match Kconfig option name
-Add irqchip support
-Add external charger cell
-Add pwrsrc cell
Changes in v4:
-Use PLATFORM_DEVID_NONE
---
 drivers/mfd/Kconfig                |  11 ++
 drivers/mfd/Makefile               |   1 +
 drivers/mfd/intel_soc_pmic_chtwc.c | 244 +++++++++++++++++++++++++++++++++++++
 3 files changed, 256 insertions(+)
 create mode 100644 drivers/mfd/intel_soc_pmic_chtwc.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 55ecdfb..d427a10 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -437,6 +437,17 @@ config INTEL_SOC_PMIC
 	  thermal, charger and related power management functions
 	  on these systems.
 
+config INTEL_SOC_PMIC_CHTWC
+	tristate "Support for Intel Cherry Trail Whiskey Cove PMIC"
+	depends on ACPI
+	depends on I2C
+	select MFD_CORE
+	select REGMAP_I2C
+	select REGMAP_IRQ
+	help
+	  Select this option to enable support for the Intel Cherry Trail
+	  Whiskey Cove PMIC found on some Intel Cherry Trail systems.
+
 config MFD_INTEL_LPSS
 	tristate
 	select COMMON_CLK
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 31ce076..d2ca514 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -209,6 +209,7 @@ obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
 intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
 intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
+obj-$(CONFIG_INTEL_SOC_PMIC_CHTWC) += intel_soc_pmic_chtwc.o
 obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
 
 obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o
diff --git a/drivers/mfd/intel_soc_pmic_chtwc.c b/drivers/mfd/intel_soc_pmic_chtwc.c
new file mode 100644
index 0000000..50aec44
--- /dev/null
+++ b/drivers/mfd/intel_soc_pmic_chtwc.c
@@ -0,0 +1,244 @@
+/*
+ * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
+ * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+/* PMIC device registers */
+#define REG_OFFSET_MASK		GENMASK(7, 0)
+#define REG_ADDR_MASK		GENMASK(15, 8)
+#define REG_ADDR_SHIFT		8
+
+#define CHT_WC_IRQLVL1		0x6e02
+#define CHT_WC_IRQLVL1_MASK	0x6e0e
+
+/* Whiskey Cove PMIC share same ACPI ID between different platforms */
+#define CHT_WC_HRV		3
+
+/* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */
+enum {
+	CHT_WC_PWRSRC_IRQ = 0,
+	CHT_WC_THRM_IRQ,
+	CHT_WC_BCU_IRQ,
+	CHT_WC_ADC_IRQ,
+	CHT_WC_EXT_CHGR_IRQ,
+	CHT_WC_GPIO_IRQ,
+	/* There is no irq 6 */
+	CHT_WC_CRIT_IRQ = 7,
+};
+
+static struct resource cht_wc_pwrsrc_resources[] = {
+	DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ),
+};
+
+static struct resource cht_wc_ext_charger_resources[] = {
+	DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ),
+};
+
+static struct mfd_cell cht_wc_dev[] = {
+	{
+		.name = "cht_wcove_pwrsrc",
+		.num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources),
+		.resources = cht_wc_pwrsrc_resources,
+	},
+	{
+		.name = "cht_wcove_ext_chgr",
+		.num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources),
+		.resources = cht_wc_ext_charger_resources,
+	},
+	{
+		.name = "cht_wcove_region",
+	},
+};
+
+/*
+ * The CHT Whiskey Cove covers multiple i2c addresses, with a 1 byte
+ * register address space per i2c address, so we use 16 bit register
+ * addresses where the high 8 bits contain the i2c client address.
+ */
+static int cht_wc_byte_reg_read(void *context, unsigned int reg,
+				unsigned int *val)
+{
+	struct i2c_client *client = context;
+	int ret, orig_addr = client->addr;
+
+	if (!(reg & REG_ADDR_MASK)) {
+		dev_err(&client->dev, "Error i2c address not specified\n");
+		return -EINVAL;
+	}
+
+	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
+	ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK);
+	client->addr = orig_addr;
+
+	if (ret < 0)
+		return ret;
+
+	*val = ret;
+	return 0;
+}
+
+static int cht_wc_byte_reg_write(void *context, unsigned int reg,
+				 unsigned int val)
+{
+	struct i2c_client *client = context;
+	int ret, orig_addr = client->addr;
+
+	if (!(reg & REG_ADDR_MASK)) {
+		dev_err(&client->dev, "Error i2c address not specified\n");
+		return -EINVAL;
+	}
+
+	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
+	ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val);
+	client->addr = orig_addr;
+
+	return ret;
+}
+
+static const struct regmap_config cht_wc_regmap_cfg = {
+	.reg_bits = 16,
+	.val_bits = 8,
+	.reg_write = cht_wc_byte_reg_write,
+	.reg_read = cht_wc_byte_reg_read,
+};
+
+static const struct regmap_irq cht_wc_regmap_irqs[] = {
+	REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)),
+	REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)),
+};
+
+static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
+	.name = "cht_wc_irq_chip",
+	.status_base = CHT_WC_IRQLVL1,
+	.mask_base = CHT_WC_IRQLVL1_MASK,
+	.irqs = cht_wc_regmap_irqs,
+	.num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs),
+	.num_regs = 1,
+};
+
+static int cht_wc_probe(struct i2c_client *client,
+			const struct i2c_device_id *i2c_id)
+{
+	struct device *dev = &client->dev;
+	struct intel_soc_pmic *pmic;
+	acpi_status status;
+	unsigned long long hrv;
+	int ret;
+
+	status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Failed to get PMIC hardware revision\n");
+		return -ENODEV;
+	}
+	if (hrv != CHT_WC_HRV) {
+		dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv);
+		return -ENODEV;
+	}
+	if (client->irq < 0) {
+		dev_err(dev, "Invalid IRQ\n");
+		return -ENODEV;
+	}
+
+	pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
+	if (!pmic)
+		return -ENOMEM;
+
+	pmic->irq = client->irq;
+	pmic->dev = dev;
+	i2c_set_clientdata(client, pmic);
+
+	pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg);
+	if (IS_ERR(pmic->regmap))
+		return PTR_ERR(pmic->regmap);
+
+	ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
+				       IRQF_ONESHOT | IRQF_SHARED, 0,
+				       &cht_wc_regmap_irq_chip,
+				       &pmic->irq_chip_data);
+	if (ret)
+		return ret;
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+				cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0,
+				regmap_irq_get_domain(pmic->irq_chip_data));
+}
+
+static void cht_wc_shutdown(struct i2c_client *client)
+{
+	struct intel_soc_pmic *pmic = i2c_get_clientdata(client);
+
+	disable_irq(pmic->irq);
+}
+
+static int __maybe_unused cht_wc_suspend(struct device *dev)
+{
+	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
+
+	disable_irq(pmic->irq);
+	return 0;
+}
+
+static int __maybe_unused cht_wc_resume(struct device *dev)
+{
+	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
+
+	enable_irq(pmic->irq);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume);
+
+static const struct i2c_device_id cht_wc_i2c_id[] = {
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, cht_wc_i2c_id);
+
+static const struct acpi_device_id cht_wc_acpi_ids[] = {
+	{ "INT34D3", },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, cht_wc_acpi_ids);
+
+static struct i2c_driver cht_wc_driver = {
+	.driver	= {
+		.name	= "CHT Whiskey Cove PMIC",
+		.pm     = &cht_wc_pm_ops,
+		.acpi_match_table = ACPI_PTR(cht_wc_acpi_ids),
+	},
+	.probe = cht_wc_probe,
+	.shutdown = cht_wc_shutdown,
+	.id_table = cht_wc_i2c_id,
+};
+
+module_i2c_driver(cht_wc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
-- 
2.9.3

[toc] | [prev] | [next] | [standalone]


#1607700 — Re: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver

FromLee Jones <lee.jones@linaro.org>
Date2017-03-23 17:50 +0100
SubjectRe: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver
Message-ID<toe5Y-4Wc-21@gated-at.bofh.it>
In reply to#1605143
On Mon, 20 Mar 2017, Hans de Goede wrote:

> Add mfd driver for Intel CHT Whiskey Cove PMIC, based on various non
> upstreamed CHT Whiskey Cove PMIC patches.
> 
> This is a somewhat minimal version which adds irqchip support and cells
> for: ACPI PMIC opregion support, the i2c-controller driving the external
> charger irc and the pwrsrc/extcon block.
> 
> Further cells can be added in the future if/when drivers are upstreamed
> for them.
> 
> Cc: Bin Gao <bin.gao@intel.com>
> Cc: Felipe Balbi <felipe.balbi@linux.intel.com>
> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> ---
> Changes in v2:
> -Since this uses plain mfd and not the intel_soc_pmic stuff give it
>  its own Kconfig and allow this to be built as a module
> -Add missing #include <acpi/acpi_bus.h>
> Changes in v3:
> -Drop #include <acpi/acpi_bus.h> again, not the right fix for the build errors
> -Error out when the upper byte of the register-address passed to the regmap
>  functions is 0 rather then hardcoding an address in that case
> -Various minor style tweaks / cleanups
> -Move defines of regulator register addresses to intel_pmic_chtwc.c,
>  it is the only place where they are used
> -Drop now empty include/linux/mfd/intel_chtwc.h
> -Rename intel_soc_pmic_chtwc.c to intel_cht_wc.c to match Kconfig option name
> -Add irqchip support
> -Add external charger cell
> -Add pwrsrc cell
> Changes in v4:
> -Use PLATFORM_DEVID_NONE
> ---
>  drivers/mfd/Kconfig                |  11 ++
>  drivers/mfd/Makefile               |   1 +
>  drivers/mfd/intel_soc_pmic_chtwc.c | 244 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 256 insertions(+)
>  create mode 100644 drivers/mfd/intel_soc_pmic_chtwc.c
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 55ecdfb..d427a10 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -437,6 +437,17 @@ config INTEL_SOC_PMIC
>  	  thermal, charger and related power management functions
>  	  on these systems.
>  
> +config INTEL_SOC_PMIC_CHTWC
> +	tristate "Support for Intel Cherry Trail Whiskey Cove PMIC"
> +	depends on ACPI
> +	depends on I2C

Nit: Any reason why these can't be on one line?

> +	select MFD_CORE
> +	select REGMAP_I2C
> +	select REGMAP_IRQ
> +	help
> +	  Select this option to enable support for the Intel Cherry Trail
> +	  Whiskey Cove PMIC found on some Intel Cherry Trail systems.
> +
>  config MFD_INTEL_LPSS
>  	tristate
>  	select COMMON_CLK
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 31ce076..d2ca514 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -209,6 +209,7 @@ obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
>  intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>  intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
> +obj-$(CONFIG_INTEL_SOC_PMIC_CHTWC) += intel_soc_pmic_chtwc.o
>  obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
>  
>  obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o
> diff --git a/drivers/mfd/intel_soc_pmic_chtwc.c b/drivers/mfd/intel_soc_pmic_chtwc.c
> new file mode 100644
> index 0000000..50aec44
> --- /dev/null
> +++ b/drivers/mfd/intel_soc_pmic_chtwc.c
> @@ -0,0 +1,244 @@
> +/*
> + * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC
> + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
> + *
> + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
> + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + */

We have a shortened version of the licence now -- any reason why it
can't be used here?

> +#include <linux/acpi.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/intel_soc_pmic.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +
> +/* PMIC device registers */
> +#define REG_OFFSET_MASK		GENMASK(7, 0)
> +#define REG_ADDR_MASK		GENMASK(15, 8)
> +#define REG_ADDR_SHIFT		8
> +
> +#define CHT_WC_IRQLVL1		0x6e02
> +#define CHT_WC_IRQLVL1_MASK	0x6e0e
> +
> +/* Whiskey Cove PMIC share same ACPI ID between different platforms */
> +#define CHT_WC_HRV		3
> +
> +/* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */
> +enum {
> +	CHT_WC_PWRSRC_IRQ = 0,
> +	CHT_WC_THRM_IRQ,
> +	CHT_WC_BCU_IRQ,
> +	CHT_WC_ADC_IRQ,
> +	CHT_WC_EXT_CHGR_IRQ,
> +	CHT_WC_GPIO_IRQ,
> +	/* There is no irq 6 */
> +	CHT_WC_CRIT_IRQ = 7,
> +};
> +
> +static struct resource cht_wc_pwrsrc_resources[] = {
> +	DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ),
> +};
> +
> +static struct resource cht_wc_ext_charger_resources[] = {
> +	DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ),
> +};
> +
> +static struct mfd_cell cht_wc_dev[] = {
> +	{
> +		.name = "cht_wcove_pwrsrc",
> +		.num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources),
> +		.resources = cht_wc_pwrsrc_resources,
> +	},
> +	{

}, {

> +		.name = "cht_wcove_ext_chgr",
> +		.num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources),
> +		.resources = cht_wc_ext_charger_resources,
> +	},
> +	{

}, {

> +		.name = "cht_wcove_region",
> +	},

One line please.

> +};
> +
> +/*
> + * The CHT Whiskey Cove covers multiple i2c addresses, with a 1 byte

"I2C"

"Byte"

> + * register address space per i2c address, so we use 16 bit register

"I2C"

> + * addresses where the high 8 bits contain the i2c client address.

"I2C", etc.

> + */
> +static int cht_wc_byte_reg_read(void *context, unsigned int reg,
> +				unsigned int *val)
> +{
> +	struct i2c_client *client = context;
> +	int ret, orig_addr = client->addr;
> +
> +	if (!(reg & REG_ADDR_MASK)) {
> +		dev_err(&client->dev, "Error i2c address not specified\n");
> +		return -EINVAL;
> +	}
> +
> +	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
> +	ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK);
> +	client->addr = orig_addr;
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = ret;
> +	return 0;
> +}
> +
> +static int cht_wc_byte_reg_write(void *context, unsigned int reg,
> +				 unsigned int val)
> +{
> +	struct i2c_client *client = context;
> +	int ret, orig_addr = client->addr;
> +
> +	if (!(reg & REG_ADDR_MASK)) {
> +		dev_err(&client->dev, "Error i2c address not specified\n");
> +		return -EINVAL;
> +	}
> +
> +	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
> +	ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val);
> +	client->addr = orig_addr;
> +
> +	return ret;
> +}
> +
> +static const struct regmap_config cht_wc_regmap_cfg = {
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +	.reg_write = cht_wc_byte_reg_write,
> +	.reg_read = cht_wc_byte_reg_read,
> +};
> +
> +static const struct regmap_irq cht_wc_regmap_irqs[] = {
> +	REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)),
> +	REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)),
> +};
> +
> +static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
> +	.name = "cht_wc_irq_chip",
> +	.status_base = CHT_WC_IRQLVL1,
> +	.mask_base = CHT_WC_IRQLVL1_MASK,
> +	.irqs = cht_wc_regmap_irqs,
> +	.num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs),
> +	.num_regs = 1,
> +};
> +
> +static int cht_wc_probe(struct i2c_client *client,
> +			const struct i2c_device_id *i2c_id)
> +{
> +	struct device *dev = &client->dev;
> +	struct intel_soc_pmic *pmic;
> +	acpi_status status;
> +	unsigned long long hrv;
> +	int ret;
> +
> +	status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv);
> +	if (ACPI_FAILURE(status)) {
> +		dev_err(dev, "Failed to get PMIC hardware revision\n");
> +		return -ENODEV;
> +	}
> +	if (hrv != CHT_WC_HRV) {
> +		dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv);
> +		return -ENODEV;
> +	}
> +	if (client->irq < 0) {
> +		dev_err(dev, "Invalid IRQ\n");
> +		return -ENODEV;
> +	}
> +
> +	pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
> +	if (!pmic)
> +		return -ENOMEM;
> +
> +	pmic->irq = client->irq;
> +	pmic->dev = dev;
> +	i2c_set_clientdata(client, pmic);
> +
> +	pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg);
> +	if (IS_ERR(pmic->regmap))
> +		return PTR_ERR(pmic->regmap);
> +
> +	ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
> +				       IRQF_ONESHOT | IRQF_SHARED, 0,
> +				       &cht_wc_regmap_irq_chip,
> +				       &pmic->irq_chip_data);
> +	if (ret)
> +		return ret;
> +
> +	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
> +				cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0,
> +				regmap_irq_get_domain(pmic->irq_chip_data));
> +}
> +
> +static void cht_wc_shutdown(struct i2c_client *client)
> +{
> +	struct intel_soc_pmic *pmic = i2c_get_clientdata(client);
> +
> +	disable_irq(pmic->irq);
> +}
> +
> +static int __maybe_unused cht_wc_suspend(struct device *dev)
> +{
> +	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
> +
> +	disable_irq(pmic->irq);
> +	return 0;
> +}
> +
> +static int __maybe_unused cht_wc_resume(struct device *dev)
> +{
> +	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
> +
> +	enable_irq(pmic->irq);
> +	return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume);
> +
> +static const struct i2c_device_id cht_wc_i2c_id[] = {
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, cht_wc_i2c_id);
> +
> +static const struct acpi_device_id cht_wc_acpi_ids[] = {
> +	{ "INT34D3", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(acpi, cht_wc_acpi_ids);
> +
> +static struct i2c_driver cht_wc_driver = {
> +	.driver	= {
> +		.name	= "CHT Whiskey Cove PMIC",
> +		.pm     = &cht_wc_pm_ops,
> +		.acpi_match_table = ACPI_PTR(cht_wc_acpi_ids),
> +	},
> +	.probe = cht_wc_probe,
> +	.shutdown = cht_wc_shutdown,
> +	.id_table = cht_wc_i2c_id,
> +};
> +
> +module_i2c_driver(cht_wc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

[toc] | [prev] | [next] | [standalone]


#1609315 — Re: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver

FromHans de Goede <hdegoede@redhat.com>
Date2017-03-26 13:10 +0200
SubjectRe: [PATCH v2 1/3] mfd: Add Cherry Trail Whiskey Cove PMIC driver
Message-ID<tpedz-7pp-3@gated-at.bofh.it>
In reply to#1607700
Hi,

On 23-03-17 17:44, Lee Jones wrote:
> On Mon, 20 Mar 2017, Hans de Goede wrote:
>
>> Add mfd driver for Intel CHT Whiskey Cove PMIC, based on various non
>> upstreamed CHT Whiskey Cove PMIC patches.
>>
>> This is a somewhat minimal version which adds irqchip support and cells
>> for: ACPI PMIC opregion support, the i2c-controller driving the external
>> charger irc and the pwrsrc/extcon block.
>>
>> Further cells can be added in the future if/when drivers are upstreamed
>> for them.
>>
>> Cc: Bin Gao <bin.gao@intel.com>
>> Cc: Felipe Balbi <felipe.balbi@linux.intel.com>
>> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
>> ---
>> Changes in v2:
>> -Since this uses plain mfd and not the intel_soc_pmic stuff give it
>>  its own Kconfig and allow this to be built as a module
>> -Add missing #include <acpi/acpi_bus.h>
>> Changes in v3:
>> -Drop #include <acpi/acpi_bus.h> again, not the right fix for the build errors
>> -Error out when the upper byte of the register-address passed to the regmap
>>  functions is 0 rather then hardcoding an address in that case
>> -Various minor style tweaks / cleanups
>> -Move defines of regulator register addresses to intel_pmic_chtwc.c,
>>  it is the only place where they are used
>> -Drop now empty include/linux/mfd/intel_chtwc.h
>> -Rename intel_soc_pmic_chtwc.c to intel_cht_wc.c to match Kconfig option name
>> -Add irqchip support
>> -Add external charger cell
>> -Add pwrsrc cell
>> Changes in v4:
>> -Use PLATFORM_DEVID_NONE
>> ---
>>  drivers/mfd/Kconfig                |  11 ++
>>  drivers/mfd/Makefile               |   1 +
>>  drivers/mfd/intel_soc_pmic_chtwc.c | 244 +++++++++++++++++++++++++++++++++++++
>>  3 files changed, 256 insertions(+)
>>  create mode 100644 drivers/mfd/intel_soc_pmic_chtwc.c
>>
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>> index 55ecdfb..d427a10 100644
>> --- a/drivers/mfd/Kconfig
>> +++ b/drivers/mfd/Kconfig
>> @@ -437,6 +437,17 @@ config INTEL_SOC_PMIC
>>  	  thermal, charger and related power management functions
>>  	  on these systems.
>>
>> +config INTEL_SOC_PMIC_CHTWC
>> +	tristate "Support for Intel Cherry Trail Whiskey Cove PMIC"
>> +	depends on ACPI
>> +	depends on I2C
>
> Nit: Any reason why these can't be on one line?

No, fixed for v5.

>
>> +	select MFD_CORE
>> +	select REGMAP_I2C
>> +	select REGMAP_IRQ
>> +	help
>> +	  Select this option to enable support for the Intel Cherry Trail
>> +	  Whiskey Cove PMIC found on some Intel Cherry Trail systems.
>> +
>>  config MFD_INTEL_LPSS
>>  	tristate
>>  	select COMMON_CLK
>> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
>> index 31ce076..d2ca514 100644
>> --- a/drivers/mfd/Makefile
>> +++ b/drivers/mfd/Makefile
>> @@ -209,6 +209,7 @@ obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
>>  intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>>  intel-soc-pmic-$(CONFIG_INTEL_PMC_IPC)	+= intel_soc_pmic_bxtwc.o
>>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
>> +obj-$(CONFIG_INTEL_SOC_PMIC_CHTWC) += intel_soc_pmic_chtwc.o
>>  obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o
>>
>>  obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o
>> diff --git a/drivers/mfd/intel_soc_pmic_chtwc.c b/drivers/mfd/intel_soc_pmic_chtwc.c
>> new file mode 100644
>> index 0000000..50aec44
>> --- /dev/null
>> +++ b/drivers/mfd/intel_soc_pmic_chtwc.c
>> @@ -0,0 +1,244 @@
>> +/*
>> + * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC
>> + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
>> + *
>> + * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
>> + * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope 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.
>> + */
>
> We have a shortened version of the licence now -- any reason why it
> can't be used here?

No reason, fixed for v5.

>
>> +#include <linux/acpi.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/i2c.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/kernel.h>
>> +#include <linux/mfd/core.h>
>> +#include <linux/mfd/intel_soc_pmic.h>
>> +#include <linux/module.h>
>> +#include <linux/regmap.h>
>> +
>> +/* PMIC device registers */
>> +#define REG_OFFSET_MASK		GENMASK(7, 0)
>> +#define REG_ADDR_MASK		GENMASK(15, 8)
>> +#define REG_ADDR_SHIFT		8
>> +
>> +#define CHT_WC_IRQLVL1		0x6e02
>> +#define CHT_WC_IRQLVL1_MASK	0x6e0e
>> +
>> +/* Whiskey Cove PMIC share same ACPI ID between different platforms */
>> +#define CHT_WC_HRV		3
>> +
>> +/* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */
>> +enum {
>> +	CHT_WC_PWRSRC_IRQ = 0,
>> +	CHT_WC_THRM_IRQ,
>> +	CHT_WC_BCU_IRQ,
>> +	CHT_WC_ADC_IRQ,
>> +	CHT_WC_EXT_CHGR_IRQ,
>> +	CHT_WC_GPIO_IRQ,
>> +	/* There is no irq 6 */
>> +	CHT_WC_CRIT_IRQ = 7,
>> +};
>> +
>> +static struct resource cht_wc_pwrsrc_resources[] = {
>> +	DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ),
>> +};
>> +
>> +static struct resource cht_wc_ext_charger_resources[] = {
>> +	DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ),
>> +};
>> +
>> +static struct mfd_cell cht_wc_dev[] = {
>> +	{
>> +		.name = "cht_wcove_pwrsrc",
>> +		.num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources),
>> +		.resources = cht_wc_pwrsrc_resources,
>> +	},
>> +	{
>
> }, {
>
>> +		.name = "cht_wcove_ext_chgr",
>> +		.num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources),
>> +		.resources = cht_wc_ext_charger_resources,
>> +	},
>> +	{
>
> }, {
>
>> +		.name = "cht_wcove_region",
>> +	},
>
> One line please.
>
>> +};
>> +
>> +/*
>> + * The CHT Whiskey Cove covers multiple i2c addresses, with a 1 byte
>
> "I2C"
>
> "Byte"
>
>> + * register address space per i2c address, so we use 16 bit register
>
> "I2C"
>
>> + * addresses where the high 8 bits contain the i2c client address.
>
> "I2C", etc.
>
>> + */
>> +static int cht_wc_byte_reg_read(void *context, unsigned int reg,
>> +				unsigned int *val)
>> +{
>> +	struct i2c_client *client = context;
>> +	int ret, orig_addr = client->addr;
>> +
>> +	if (!(reg & REG_ADDR_MASK)) {
>> +		dev_err(&client->dev, "Error i2c address not specified\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
>> +	ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK);
>> +	client->addr = orig_addr;
>> +
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	*val = ret;
>> +	return 0;
>> +}
>> +
>> +static int cht_wc_byte_reg_write(void *context, unsigned int reg,
>> +				 unsigned int val)
>> +{
>> +	struct i2c_client *client = context;
>> +	int ret, orig_addr = client->addr;
>> +
>> +	if (!(reg & REG_ADDR_MASK)) {
>> +		dev_err(&client->dev, "Error i2c address not specified\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT;
>> +	ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val);
>> +	client->addr = orig_addr;
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct regmap_config cht_wc_regmap_cfg = {
>> +	.reg_bits = 16,
>> +	.val_bits = 8,
>> +	.reg_write = cht_wc_byte_reg_write,
>> +	.reg_read = cht_wc_byte_reg_read,
>> +};
>> +
>> +static const struct regmap_irq cht_wc_regmap_irqs[] = {
>> +	REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)),
>> +	REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)),
>> +};
>> +
>> +static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
>> +	.name = "cht_wc_irq_chip",
>> +	.status_base = CHT_WC_IRQLVL1,
>> +	.mask_base = CHT_WC_IRQLVL1_MASK,
>> +	.irqs = cht_wc_regmap_irqs,
>> +	.num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs),
>> +	.num_regs = 1,
>> +};
>> +
>> +static int cht_wc_probe(struct i2c_client *client,
>> +			const struct i2c_device_id *i2c_id)
>> +{
>> +	struct device *dev = &client->dev;
>> +	struct intel_soc_pmic *pmic;
>> +	acpi_status status;
>> +	unsigned long long hrv;
>> +	int ret;
>> +
>> +	status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv);
>> +	if (ACPI_FAILURE(status)) {
>> +		dev_err(dev, "Failed to get PMIC hardware revision\n");
>> +		return -ENODEV;
>> +	}
>> +	if (hrv != CHT_WC_HRV) {
>> +		dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv);
>> +		return -ENODEV;
>> +	}
>> +	if (client->irq < 0) {
>> +		dev_err(dev, "Invalid IRQ\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
>> +	if (!pmic)
>> +		return -ENOMEM;
>> +
>> +	pmic->irq = client->irq;
>> +	pmic->dev = dev;
>> +	i2c_set_clientdata(client, pmic);
>> +
>> +	pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg);
>> +	if (IS_ERR(pmic->regmap))
>> +		return PTR_ERR(pmic->regmap);
>> +
>> +	ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
>> +				       IRQF_ONESHOT | IRQF_SHARED, 0,
>> +				       &cht_wc_regmap_irq_chip,
>> +				       &pmic->irq_chip_data);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
>> +				cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0,
>> +				regmap_irq_get_domain(pmic->irq_chip_data));
>> +}
>> +
>> +static void cht_wc_shutdown(struct i2c_client *client)
>> +{
>> +	struct intel_soc_pmic *pmic = i2c_get_clientdata(client);
>> +
>> +	disable_irq(pmic->irq);
>> +}
>> +
>> +static int __maybe_unused cht_wc_suspend(struct device *dev)
>> +{
>> +	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
>> +
>> +	disable_irq(pmic->irq);
>> +	return 0;
>> +}
>> +
>> +static int __maybe_unused cht_wc_resume(struct device *dev)
>> +{
>> +	struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
>> +
>> +	enable_irq(pmic->irq);
>> +	return 0;
>> +}
>> +
>> +static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume);
>> +
>> +static const struct i2c_device_id cht_wc_i2c_id[] = {
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, cht_wc_i2c_id);
>> +
>> +static const struct acpi_device_id cht_wc_acpi_ids[] = {
>> +	{ "INT34D3", },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(acpi, cht_wc_acpi_ids);
>> +
>> +static struct i2c_driver cht_wc_driver = {
>> +	.driver	= {
>> +		.name	= "CHT Whiskey Cove PMIC",
>> +		.pm     = &cht_wc_pm_ops,
>> +		.acpi_match_table = ACPI_PTR(cht_wc_acpi_ids),
>> +	},
>> +	.probe = cht_wc_probe,
>> +	.shutdown = cht_wc_shutdown,
>> +	.id_table = cht_wc_i2c_id,
>> +};
>> +
>> +module_i2c_driver(cht_wc_driver);
>> +
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>


Regards,

Hans

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web