LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH v0 2/2] extcon-axp288: Add axp288 extcon driver support
@ 2015-03-31 16:04 Ramakrishna Pallala
  0 siblings, 0 replies; only message in thread
From: Ramakrishna Pallala @ 2015-03-31 16:04 UTC (permalink / raw)
  To: linux-kernel, MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones
  Cc: Carlo Caione, Jacob Pan, Pallala Ramakrishna

This patch adds the extcon support for AXP288 PMIC which
has the BC1.2 charger detection capability. Additionally
it also adds the USB mux switching support b/w SOC and PMIC
based on GPIO control.

Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
---
 drivers/extcon/Kconfig         |    7 +
 drivers/extcon/Makefile        |    1 +
 drivers/extcon/extcon-axp288.c |  479 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 487 insertions(+)
 create mode 100644 drivers/extcon/extcon-axp288.c

diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 6a1f7de..b8627f7 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -93,4 +93,11 @@ config EXTCON_SM5502
 	  Silicon Mitus SM5502. The SM5502 is a USB port accessory
 	  detector and switch.
 
+config EXTCON_AXP288
+	tristate "AXP288 EXTCON support"
+	depends on MFD_AXP20X && USB_PHY
+	help
+	  Say Y here to enable support for USB peripheral detection
+	  and USB MUX switching by AXP288 PMIC.
+
 endif # MULTISTATE_SWITCH
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 0370b42..832ad79 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_EXTCON_MAX8997)	+= extcon-max8997.o
 obj-$(CONFIG_EXTCON_PALMAS)	+= extcon-palmas.o
 obj-$(CONFIG_EXTCON_RT8973A)	+= extcon-rt8973a.o
 obj-$(CONFIG_EXTCON_SM5502)	+= extcon-sm5502.o
+obj-$(CONFIG_EXTCON_AXP288)	+= extcon-axp288.o
diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
new file mode 100644
index 0000000..7228ae4
--- /dev/null
+++ b/drivers/extcon/extcon-axp288.c
@@ -0,0 +1,479 @@
+/*
+ * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ * Ramakrishna Pallala <ramakrishna.pallala@intel.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/module.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/usb/phy.h>
+#include <linux/notifier.h>
+#include <linux/extcon.h>
+#include <linux/regmap.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/axp20x.h>
+
+#define AXP288_PS_STAT_REG		0x00
+#define PS_STAT_VBUS_TRIGGER		(1 << 0)
+#define PS_STAT_BAT_CHRG_DIR		(1 << 2)
+#define PS_STAT_VBUS_ABOVE_VHOLD	(1 << 3)
+#define PS_STAT_VBUS_VALID		(1 << 4)
+#define PS_STAT_VBUS_PRESENT		(1 << 5)
+
+#define AXP288_BC_GLOBAL_REG		0x2c
+#define BC_GLOBAL_RUN			(1 << 0)
+#define BC_GLOBAL_DET_STAT		(1 << 2)
+#define BC_GLOBAL_DBP_TOUT		(1 << 3)
+#define BC_GLOBAL_VLGC_COM_SEL		(1 << 4)
+#define BC_GLOBAL_DCD_TOUT_MASK		0x60
+#define BC_GLOBAL_DCD_TOUT_300MS	0x0
+#define BC_GLOBAL_DCD_TOUT_100MS	0x1
+#define BC_GLOBAL_DCD_TOUT_500MS	0x2
+#define BC_GLOBAL_DCD_TOUT_900MS	0x3
+#define BC_GLOBAL_DCD_DET_SEL		(1 << 7)
+
+#define AXP288_BC_VBUS_CNTL_REG		0x2d
+#define VBUS_CNTL_DPDM_PD_EN		(1 << 4)
+#define VBUS_CNTL_DPDM_FD_EN		(1 << 5)
+#define VBUS_CNTL_FIRST_PO_STAT		(1 << 6)
+
+#define AXP288_BC_USB_STAT_REG		0x2e
+#define USB_STAT_BUS_STAT_MASK		0x0f
+#define USB_STAT_BUS_STAT_OFFSET	0
+#define USB_STAT_BUS_STAT_ATHD		0x0
+#define USB_STAT_BUS_STAT_CONN		0x1
+#define USB_STAT_BUS_STAT_SUSP		0x2
+#define USB_STAT_BUS_STAT_CONF		0x3
+#define USB_STAT_USB_SS_MODE		(1 << 4)
+#define USB_STAT_DEAD_BAT_DET		(1 << 6)
+#define USB_STAT_DBP_UNCFG		(1 << 7)
+
+#define AXP288_BC_DET_STAT_REG		0x2f
+#define DET_STAT_MASK			0xe0
+#define DET_STAT_OFFSET			5
+#define DET_STAT_SDP			0x1
+#define DET_STAT_CDP			0x2
+#define DET_STAT_DCP			0x3
+
+#define AXP288_PS_BOOT_REASON_REG	0x2
+
+#define AXP288_PWRSRC_IRQ_CFG_REG	0x40
+#define PWRSRC_IRQ_CFG_MASK		0x1c
+
+#define AXP288_BC12_IRQ_CFG_REG		0x45
+#define BC12_IRQ_CFG_MASK		0x2
+
+#define AXP288_PWRSRC_INTR_NUM		4
+
+#define AXP288_DRV_NAME			"extcon-axp288"
+
+#define AXP288_EXTCON_CABLE_SDP		"Slow-charger"
+#define AXP288_EXTCON_CABLE_CDP		"Charge-downstream"
+#define AXP288_EXTCON_CABLE_DCP		"Fast-charger"
+
+#define EXTCON_GPIO_MUX_SEL_PMIC	0
+#define EXTCON_GPIO_MUX_SEL_SOC		1
+
+enum {
+	VBUS_FALLING_IRQ = 0,
+	VBUS_RISING_IRQ,
+	MV_CHNG_IRQ,
+	BC_USB_CHNG_IRQ,
+};
+
+static const char *axp288_extcon_cables[] = {
+	AXP288_EXTCON_CABLE_SDP,
+	AXP288_EXTCON_CABLE_CDP,
+	AXP288_EXTCON_CABLE_DCP,
+	NULL,
+};
+
+struct axp288_extcon_info {
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	struct regmap_irq_chip_data *regmap_irqc;
+	struct axp288_extcon_pdata *pdata;
+	int irq[AXP288_PWRSRC_INTR_NUM];
+	struct extcon_dev *edev;
+	struct usb_phy *otg;
+	struct notifier_block extcon_nb;
+	struct extcon_specific_cable_nb cable;
+	bool is_sdp;
+	bool usb_id_short;
+};
+
+static char *pwr_up_down_info[] = {
+	/* bit 0 */ "Last wake caused by user pressing the power button",
+	/* bit 2 */ "Last wake caused by a charger insertion",
+	/* bit 1 */ "Last wake caused by a battery insertion",
+	/* bit 3 */ "Last wake caused by SOC initiated global reset",
+	/* bit 4 */ "Last wake caused by cold reset",
+	/* bit 5 */ "Last shutdown caused by PMIC UVLO threshold",
+	/* bit 6 */ "Last shutdown caused by SOC initiated cold off",
+	/* bit 7 */ "Last shutdown caused by user pressing the power button",
+	NULL,
+};
+
+/*
+ * Decode and log the given "reset source indicator"
+ * register and then clear it.
+ */
+static void axp288_extcon_log_rsi(struct axp288_extcon_info *info,
+				char **pwrsrc_rsi_info, int reg)
+{
+	char **rsi;
+	unsigned int val, i, clear_mask = 0;
+	int ret;
+
+	ret = regmap_read(info->regmap, reg, &val);
+	for (i = 0, rsi = pwrsrc_rsi_info; *rsi; rsi++, i++) {
+		if (val & BIT(i)) {
+			dev_dbg(&info->pdev->dev, "%s\n", *rsi);
+			clear_mask |= BIT(i);
+		}
+	}
+
+	/* Clear the register value for next reboot (write 1 to clear bit) */
+	regmap_write(info->regmap, reg, clear_mask);
+}
+
+static int handle_chrg_det_event(struct axp288_extcon_info *info)
+{
+	static bool notify_otg, notify_charger;
+	static char *cable;
+	int ret, stat, cfg, pwr_stat;
+	u8 chrg_type;
+	bool vbus_attach = false;
+
+	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
+	if (ret < 0) {
+		dev_err(&info->pdev->dev, "vbus status read error\n");
+		return ret;
+	}
+
+	vbus_attach = (pwr_stat & PS_STAT_VBUS_PRESENT) && !info->usb_id_short;
+	if (vbus_attach) {
+		dev_dbg(&info->pdev->dev, "vbus present\n");
+	} else {
+		dev_dbg(&info->pdev->dev, "vbus not present\n");
+		goto notify_otg;
+	}
+
+	/* Check charger detection completion status */
+	ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
+	if (ret < 0)
+		goto dev_det_ret;
+	if (cfg & BC_GLOBAL_DET_STAT) {
+		dev_dbg(&info->pdev->dev, "charger detection not complete!!\n");
+		goto dev_det_ret;
+	}
+
+	ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
+	if (ret < 0)
+		goto dev_det_ret;
+	dev_dbg(&info->pdev->dev, "stat:%x, cfg:%x\n", stat, cfg);
+
+	chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_OFFSET;
+	info->is_sdp = false;
+
+	if (chrg_type == DET_STAT_SDP) {
+		dev_dbg(&info->pdev->dev, "sdp cable connecetd\n");
+		notify_otg = true;
+		notify_charger = true;
+		info->is_sdp = true;
+		cable = AXP288_EXTCON_CABLE_SDP;
+	} else if (chrg_type == DET_STAT_CDP) {
+		dev_dbg(&info->pdev->dev, "cdp cable connecetd\n");
+		notify_otg = true;
+		notify_charger = true;
+		cable = AXP288_EXTCON_CABLE_CDP;
+	} else if (chrg_type == DET_STAT_DCP) {
+		dev_dbg(&info->pdev->dev, "dcp cable connecetd\n");
+		notify_charger = true;
+		cable = AXP288_EXTCON_CABLE_DCP;
+	} else {
+		dev_warn(&info->pdev->dev,
+			"disconnect or unknown or ID event\n");
+	}
+
+notify_otg:
+	if (notify_otg) {
+		/*
+		 * If VBUS is absent Connect D+/D- lines to PMIC for BC
+		 * detection. Else connect them to SOC for USB communication.
+		 */
+		if (info->pdata->gpio_mux_cntl != NULL)
+			gpiod_set_value(info->pdata->gpio_mux_cntl,
+				vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
+						: EXTCON_GPIO_MUX_SEL_PMIC);
+
+		atomic_notifier_call_chain(&info->otg->notifier,
+			vbus_attach ? USB_EVENT_VBUS : USB_EVENT_NONE, NULL);
+	}
+
+	if (notify_charger)
+		extcon_set_cable_state(info->edev, cable, vbus_attach);
+
+	/* Clear the flags on disconnect event */
+	if (!vbus_attach) {
+		notify_otg = false;
+		notify_charger = false;
+	}
+
+	return 0;
+
+dev_det_ret:
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "BC Mod detection error\n");
+
+	return ret;
+}
+
+static irqreturn_t axp288_extcon_isr(int irq, void *data)
+{
+	struct axp288_extcon_info *info = data;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < AXP288_PWRSRC_INTR_NUM; i++) {
+		if (info->irq[i] == irq)
+			break;
+	}
+
+	if (i == AXP288_PWRSRC_INTR_NUM) {
+		dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
+		return IRQ_NONE;
+	}
+
+	ret = handle_chrg_det_event(info);
+	if (ret < 0)
+		dev_warn(&info->pdev->dev, "error in PWRSRC INT handling\n");
+
+	return IRQ_HANDLED;
+}
+
+static int axp288_extcon_registration(struct axp288_extcon_info *info)
+{
+	int ret;
+
+	/* Register with extcon */
+	info->edev = devm_kzalloc(&info->pdev->dev,
+				sizeof(struct extcon_dev), GFP_KERNEL);
+	if (!info->edev)
+		return -ENOMEM;
+
+	info->edev->name = "extcon-axp288";
+	info->edev->supported_cable = axp288_extcon_cables;
+	ret = extcon_dev_register(info->edev);
+	if (ret)
+		dev_err(&info->pdev->dev, "extcon registration failed!!\n");
+
+	return ret;
+}
+
+static inline bool is_usb_host_mode(struct extcon_dev *evdev)
+{
+	return !!evdev->state;
+}
+
+static int axp288_handle_extcon_event(struct notifier_block *nb,
+				   unsigned long event, void *param)
+{
+	struct axp288_extcon_info *info =
+	    container_of(nb, struct axp288_extcon_info, extcon_nb);
+	struct extcon_dev *edev = param;
+	int usb_host = is_usb_host_mode(edev);
+
+	dev_info(&info->pdev->dev,
+		"[extcon notification] evt:USB-Host val:%s\n",
+		usb_host ? "Connected" : "Disconnected");
+
+	/*
+	 * Set usb_id_short flag to avoid running charger detection logic
+	 * in case usb host.
+	 */
+	info->usb_id_short = usb_host;
+
+	/*
+	 * Connect the USB mux to SOC in case of usb host else connect
+	 * it to PMIC.
+	 */
+	if (info->pdata->gpio_mux_cntl != NULL) {
+		dev_dbg(&info->pdev->dev,
+			"usb_id_short=%d\n", info->usb_id_short);
+		if (info->usb_id_short)
+			gpiod_set_value(info->pdata->gpio_mux_cntl,
+					EXTCON_GPIO_MUX_SEL_SOC);
+		else
+			gpiod_set_value(info->pdata->gpio_mux_cntl,
+					EXTCON_GPIO_MUX_SEL_PMIC);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int axp288_init_gpio_mux_cntl(struct axp288_extcon_info *info)
+{
+	int ret;
+
+	ret = gpio_request(desc_to_gpio(info->pdata->gpio_mux_cntl), "USB_MUX");
+	if (ret < 0) {
+		dev_err(&info->pdev->dev,
+			"usb mux gpio request failed:gpio=%d\n",
+			desc_to_gpio(info->pdata->gpio_mux_cntl));
+		return ret;
+	}
+	gpiod_direction_output(info->pdata->gpio_mux_cntl,
+					EXTCON_GPIO_MUX_SEL_PMIC);
+
+	info->extcon_nb.notifier_call = axp288_handle_extcon_event;
+	ret = extcon_register_interest(&info->cable, NULL,
+				"USB-Host", &info->extcon_nb);
+	if (ret) {
+		dev_err(&info->pdev->dev, "failed to register extcon notifier\n");
+		return ret;
+	}
+
+	if (info->cable.edev)
+		info->usb_id_short =
+				is_usb_host_mode(info->cable.edev);
+	if (info->usb_id_short)
+		gpiod_set_value(info->pdata->gpio_mux_cntl,
+					EXTCON_GPIO_MUX_SEL_SOC);
+
+	return 0;
+}
+
+static int axp288_extcon_probe(struct platform_device *pdev)
+{
+	struct axp288_extcon_info *info;
+	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+	int ret, i, pirq;
+
+	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->pdev = pdev;
+	info->regmap = axp20x->regmap;
+	info->regmap_irqc = axp20x->regmap_irqc;
+	info->pdata = pdev->dev.platform_data;
+
+	if (!info->pdata) {
+		/* TODO: Try ACPI provided pdata via device properties */
+		if (!device_property_present(&pdev->dev,
+					"axp288_extcon_data\n"))
+			dev_err(&pdev->dev, "no platform data\n");
+		return -ENODEV;
+	}
+	platform_set_drvdata(pdev, info);
+
+	axp288_extcon_log_rsi(info, pwr_up_down_info,
+				AXP288_PS_BOOT_REASON_REG);
+
+	/* Register extcon device */
+	ret = axp288_extcon_registration(info);
+	if (ret < 0)
+		goto extcon_reg_failed;
+
+	/* Get otg transceiver phy */
+	info->otg = usb_get_phy(USB_PHY_TYPE_USB2);
+	if (IS_ERR(info->otg)) {
+		dev_warn(&info->pdev->dev, "Failed to get otg transceiver!!\n");
+		ret = PTR_ERR(info->otg);
+		goto otg_reg_failed;
+	}
+
+	for (i = 0; i < AXP288_PWRSRC_INTR_NUM; i++) {
+		pirq = platform_get_irq(pdev, i);
+		info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
+		if (info->irq[i] < 0) {
+			dev_warn(&info->pdev->dev,
+				"regmap_irq get virq failed for IRQ %d: %d\n",
+				pirq, info->irq[i]);
+			info->irq[i] = -1;
+			goto intr_reg_failed;
+		}
+		ret = request_threaded_irq(info->irq[i],
+				NULL, axp288_extcon_isr,
+				IRQF_ONESHOT, AXP288_DRV_NAME, info);
+		if (ret) {
+			dev_err(&pdev->dev, "request_irq fail :%d err:%d\n",
+							info->irq[i], ret);
+			goto intr_reg_failed;
+		}
+	}
+
+	/* Set up gpio control for USB Mux */
+	if (info->pdata->gpio_mux_cntl != NULL) {
+		ret = axp288_init_gpio_mux_cntl(info);
+		if (ret < 0)
+			goto intr_reg_failed;
+	}
+
+	/* Unmask VBUS interrupt */
+	regmap_write(info->regmap, AXP288_PWRSRC_IRQ_CFG_REG,
+						PWRSRC_IRQ_CFG_MASK);
+	regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
+						BC_GLOBAL_RUN, 0);
+	/* Unmask the BC1.2 complte interrupts */
+	regmap_write(info->regmap, AXP288_BC12_IRQ_CFG_REG, BC12_IRQ_CFG_MASK);
+	/* Enable the charger detection logic */
+	regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
+					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
+
+	return 0;
+
+intr_reg_failed:
+	for (; i > 0; i--) {
+		free_irq(info->irq[i - 1], info);
+		info->irq[i - 1] = -1;
+	}
+	usb_put_phy(info->otg);
+otg_reg_failed:
+	extcon_dev_unregister(info->edev);
+extcon_reg_failed:
+	return ret;
+}
+
+static int axp288_extcon_remove(struct platform_device *pdev)
+{
+	struct axp288_extcon_info *info = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < AXP288_PWRSRC_INTR_NUM; i++)
+		free_irq(info->irq[i], info);
+	usb_put_phy(info->otg);
+	extcon_dev_unregister(info->edev);
+	return 0;
+}
+
+static struct platform_driver axp288_extcon_driver = {
+	.probe = axp288_extcon_probe,
+	.remove = axp288_extcon_remove,
+	.driver = {
+		.name = AXP288_DRV_NAME,
+	},
+};
+module_platform_driver(axp288_extcon_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.5


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2015-03-31  7:55 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-31 16:04 [PATCH v0 2/2] extcon-axp288: Add axp288 extcon driver support Ramakrishna Pallala

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).