diff mbox series

[v2,2/2] Embedded USB Debugger (EUD) driver

Message ID 1536096853-30473-3-git-send-email-pheragu@codeaurora.org (mailing list archive)
State Not Applicable, archived
Delegated to: Andy Gross
Headers show
Series Add Embedded USB Debugger (EUD) driver | expand

Commit Message

Prakruthi Deepak Heragu Sept. 4, 2018, 9:34 p.m. UTC
Add support for control peripheral of EUD (Embedded USB Debugger) to
listen to events such as USB attach/detach, charger enable/disable, pet
EUD to indicate software is functional.

Signed-off-by: Satya Durga Srinivasu Prabhala <satyap@codeaurora.org>
Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
---
 drivers/soc/qcom/Kconfig  |  12 ++
 drivers/soc/qcom/Makefile |   1 +
 drivers/soc/qcom/eud.c    | 338 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 351 insertions(+)
 create mode 100644 drivers/soc/qcom/eud.c

Comments

Manu Gautam Sept. 5, 2018, 9:31 a.m. UTC | #1
Hi,


On 9/5/2018 3:04 AM, Prakruthi Deepak Heragu wrote:
> Add support for control peripheral of EUD (Embedded USB Debugger) to
> listen to events such as USB attach/detach, charger enable/disable, pet
> EUD to indicate software is functional.
>
> Signed-off-by: Satya Durga Srinivasu Prabhala <satyap@codeaurora.org>
> Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
> ---
>  drivers/soc/qcom/Kconfig  |  12 ++
>  drivers/soc/qcom/Makefile |   1 +
>  drivers/soc/qcom/eud.c    | 338 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 351 insertions(+)
>  create mode 100644 drivers/soc/qcom/eud.c
>
> diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
[snip]
> +
> +#define EUD_ENABLE_CMD 1
> +#define EUD_DISABLE_CMD 0

Why not use module param as boolean? I mean zero to disable and non-zero to enable?
> +
> +#define EUD_REG_INT1_EN_MASK	0x0024
> +#define EUD_REG_INT_STATUS_1	0x0044
> +#define EUD_REG_CTL_OUT_1	0x0074
> +#define EUD_REG_VBUS_INT_CLR	0x0080
> +#define EUD_REG_CHGR_INT_CLR	0x0084
> +#define EUD_REG_CSR_EUD_EN	0x1014
> +#define EUD_REG_SW_ATTACH_DET	0x1018
> +
> +#define EUD_INT_VBUS		BIT(2)
> +#define EUD_INT_CHGR		BIT(3)
> +#define EUD_INT_SAFE_MODE	BIT(4)
> +
> +struct eud_chip {
> +	struct device			*dev;
> +	int				eud_irq;
> +	unsigned int			extcon_id;
> +	unsigned int			int_status;
> +	bool				usb_attach;
> +	bool				chgr_enable;
> +	void __iomem			*eud_reg_base;
> +	struct extcon_dev		*extcon;
> +	struct work_struct		eud_work;
> +};
> +
> +static const unsigned int eud_extcon_cable[] = {
> +	EXTCON_USB,
> +	EXTCON_CHG_USB_SDP,
> +	EXTCON_NONE,
> +};
> +
> +/*
> + * On the kernel command line specify eud.enable=1 to enable EUD.
> + * EUD is disabled by default.
> + */
> +static int enable;
> +static bool eud_ready;
> +static struct eud_chip *eud_private;
> +
> +static void enable_eud(struct eud_chip *priv)
> +{
> +	struct power_supply *usb_psy = NULL;
> +	union power_supply_propval pval = {0};
> +	union power_supply_propval tval = {0};
> +	int ret;
> +
> +	usb_psy = power_supply_get_by_name("usb");
> +	if (!usb_psy)
> +		return;
> +
> +	ret = power_supply_get_property(usb_psy,
> +			POWER_SUPPLY_PROP_PRESENT, &pval);
> +	if (ret)
> +		return;
> +
> +	ret = power_supply_get_property(usb_psy,
> +			POWER_SUPPLY_PROP_REAL_TYPE, &tval);
> +	if (ret)
> +		return;
> +

	if(!pval.intval || (tval.intval != POWER_SUPPLY_TYPE_USB &&
	    tval.intval != POWER_SUPPLY_TYPE_USB_CDP))
 		return;

	/* following if-else can be removed */
 

> +	if (pval.intval && (tval.intval == POWER_SUPPLY_TYPE_USB ||
> +	    tval.intval == POWER_SUPPLY_TYPE_USB_CDP)) {
> +		/* write into CSR to enable EUD */
> +		writel_relaxed(BIT(0), priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
> +		/* Enable vbus, chgr & safe mode warning interrupts */
> +		writel_relaxed(EUD_INT_VBUS | EUD_INT_CHGR | EUD_INT_SAFE_MODE,
> +				priv->eud_reg_base + EUD_REG_INT1_EN_MASK);
> +
> +		/* Ensure Register Writes Complete */
> +		wmb();
> +
> +		/*
> +		 * Set the default cable state to usb connect and charger
> +		 * enable
> +		 */
> +		ret = extcon_set_state_sync(priv->extcon, EXTCON_USB, true);
> +		if (ret)
> +			return;
> +		ret = extcon_set_state_sync(priv->extcon,
> +				EXTCON_CHG_USB_SDP, true);
> +		if (ret)
> +			return;
> +	} else {
> +		return;
> +	}
> +
> +}
> +
> +static void disable_eud(struct eud_chip *priv)
> +{
> +	/* write into CSR to disable EUD */
> +	writel_relaxed(0, priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
> +}
> +
> +static int param_eud_set(const char *val, const struct kernel_param *kp)
> +{
> +	int enable = 0;
> +
> +	if (sscanf(val, "%du", &enable) != 1)
> +		return -EINVAL;
> +
> +	if (enable != EUD_ENABLE_CMD && enable != EUD_DISABLE_CMD)
> +		return -EINVAL;
Do we really need to worry about it? 'enable' could just be treated as bool

[snip]
> +
> +
> +static irqreturn_t handle_eud_irq(int irq, void *data)
> +{
> +	struct eud_chip *chip = data;
> +	u32 reg;
> +
> +	/* read status register and find out which interrupt triggered */
> +	reg = readl_relaxed(chip->eud_reg_base + EUD_REG_INT_STATUS_1);
> +	if (reg & EUD_INT_VBUS) {
> +		chip->int_status = EUD_INT_VBUS;
> +		usb_attach_detach(chip);
> +	} else if (reg & EUD_INT_CHGR) {
> +		chip->int_status = EUD_INT_CHGR;
> +		chgr_enable_disable(chip);
> +	} else if (reg & EUD_INT_SAFE_MODE) {
> +		pet_eud(chip);
> +	} else {
> +		return IRQ_NONE;
> +	}

switch-case instead?

> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int msm_eud_probe(struct platform_device *pdev)
> +{
> +	struct eud_chip *chip;
> +	struct resource *res;
> +	int ret;
> +
> +	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip) {
> +		ret = -ENOMEM;
> +		return ret;
return -ENOMEM;
> +	}
> +
> +	platform_set_drvdata(pdev, chip);
> +
> +	chip->extcon = devm_extcon_dev_allocate(&pdev->dev, eud_extcon_cable);
> +	if (IS_ERR(chip->extcon))
> +		return PTR_ERR(chip->extcon);
> +
> +	ret = devm_extcon_dev_register(&pdev->dev, chip->extcon);
> +	if (ret)
> +		return ret;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res) {
> +		ret = -ENOMEM;
> +		return ret;
same here
> +	}
> +
> +	chip->eud_reg_base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(chip->eud_reg_base))
> +		return PTR_ERR(chip->eud_reg_base);
> +
> +	chip->eud_irq = platform_get_irq(pdev, 0);
> +
> +	ret = devm_request_irq(&pdev->dev, chip->eud_irq, handle_eud_irq,
> +				IRQF_TRIGGER_HIGH, NULL, chip);
> +	if (ret)
> +		return ret;
> +
> +	device_init_wakeup(&pdev->dev, true);
> +	enable_irq_wake(chip->eud_irq);
> +
> +	INIT_WORK(&chip->eud_work, eud_event_notifier);
> +
> +	eud_private = chip;
> +	eud_ready = true;
> +
> +	/* Enable EUD */
> +	if (enable)
> +		enable_eud(eud_private);
> +
> +	return 0;
> +}
> +
> +static int msm_eud_remove(struct platform_device *pdev)
> +{
> +	struct eud_chip *chip = platform_get_drvdata(pdev);
> +
> +	if (enable)
> +		disable_eud(eud_private);
> +	device_init_wakeup(&pdev->dev, false);
> +	disable_irq_wake(chip->eud_irq);
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id msm_eud_dt_match[] = {
> +	{.compatible = "qcom,msm-eud"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, msm_eud_dt_match);
> +
> +static struct platform_driver msm_eud_driver = {
> +	.probe		= msm_eud_probe,
> +	.remove		= msm_eud_remove,
> +	.driver		= {
> +		.name		= "msm-eud",
> +		.of_match_table = msm_eud_dt_match,
> +	},
> +};
> +module_platform_driver(msm_eud_driver);
> +
> +MODULE_DESCRIPTION("QTI EUD driver");
> +MODULE_LICENSE("GPL v2");
Greg Kroah-Hartman Sept. 5, 2018, 11:18 a.m. UTC | #2
On Wed, Sep 05, 2018 at 03:01:26PM +0530, Manu Gautam wrote:
> Hi,
> 
> 
> On 9/5/2018 3:04 AM, Prakruthi Deepak Heragu wrote:
> > Add support for control peripheral of EUD (Embedded USB Debugger) to
> > listen to events such as USB attach/detach, charger enable/disable, pet
> > EUD to indicate software is functional.
> >
> > Signed-off-by: Satya Durga Srinivasu Prabhala <satyap@codeaurora.org>
> > Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
> > ---
> >  drivers/soc/qcom/Kconfig  |  12 ++
> >  drivers/soc/qcom/Makefile |   1 +
> >  drivers/soc/qcom/eud.c    | 338 ++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 351 insertions(+)
> >  create mode 100644 drivers/soc/qcom/eud.c
> >
> > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> [snip]
> > +
> > +#define EUD_ENABLE_CMD 1
> > +#define EUD_DISABLE_CMD 0
> 
> Why not use module param as boolean? I mean zero to disable and non-zero to enable?

Never use module parameters on new code, as it's really hard to ever
change them.  It also doesn't work for multiple devices of the same
type.

thanks,

greg k-h
Prakruthi Deepak Heragu Sept. 11, 2018, 8:40 p.m. UTC | #3
On 2018-09-05 04:18, Greg KH wrote:
> On Wed, Sep 05, 2018 at 03:01:26PM +0530, Manu Gautam wrote:
>> Hi,
>> 
>> 
>> On 9/5/2018 3:04 AM, Prakruthi Deepak Heragu wrote:
>> > Add support for control peripheral of EUD (Embedded USB Debugger) to
>> > listen to events such as USB attach/detach, charger enable/disable, pet
>> > EUD to indicate software is functional.
>> >
>> > Signed-off-by: Satya Durga Srinivasu Prabhala <satyap@codeaurora.org>
>> > Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
>> > ---
>> >  drivers/soc/qcom/Kconfig  |  12 ++
>> >  drivers/soc/qcom/Makefile |   1 +
>> >  drivers/soc/qcom/eud.c    | 338 ++++++++++++++++++++++++++++++++++++++++++++++
>> >  3 files changed, 351 insertions(+)
>> >  create mode 100644 drivers/soc/qcom/eud.c
>> >
>> > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
>> [snip]
>> > +
>> > +#define EUD_ENABLE_CMD 1
>> > +#define EUD_DISABLE_CMD 0
>> 
>> Why not use module param as boolean? I mean zero to disable and 
>> non-zero to enable?
> 
> Never use module parameters on new code, as it's really hard to ever
> change them.  It also doesn't work for multiple devices of the same
> type.
> 
If not for module_param, do you suggest we use sysfs? module_param also 
provides a
facility to control these parameters through command line too. If we 
can't use
module_param, do you expect us to use __setup(..) API?

> thanks,
> 
> greg k-h

Regards,
Prakruthi Deepak Heragu
Greg Kroah-Hartman Sept. 12, 2018, 6:29 a.m. UTC | #4
On Tue, Sep 11, 2018 at 01:40:19PM -0700, pheragu@codeaurora.org wrote:
> On 2018-09-05 04:18, Greg KH wrote:
> > On Wed, Sep 05, 2018 at 03:01:26PM +0530, Manu Gautam wrote:
> > > Hi,
> > > 
> > > 
> > > On 9/5/2018 3:04 AM, Prakruthi Deepak Heragu wrote:
> > > > Add support for control peripheral of EUD (Embedded USB Debugger) to
> > > > listen to events such as USB attach/detach, charger enable/disable, pet
> > > > EUD to indicate software is functional.
> > > >
> > > > Signed-off-by: Satya Durga Srinivasu Prabhala <satyap@codeaurora.org>
> > > > Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
> > > > ---
> > > >  drivers/soc/qcom/Kconfig  |  12 ++
> > > >  drivers/soc/qcom/Makefile |   1 +
> > > >  drivers/soc/qcom/eud.c    | 338 ++++++++++++++++++++++++++++++++++++++++++++++
> > > >  3 files changed, 351 insertions(+)
> > > >  create mode 100644 drivers/soc/qcom/eud.c
> > > >
> > > > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> > > [snip]
> > > > +
> > > > +#define EUD_ENABLE_CMD 1
> > > > +#define EUD_DISABLE_CMD 0
> > > 
> > > Why not use module param as boolean? I mean zero to disable and
> > > non-zero to enable?
> > 
> > Never use module parameters on new code, as it's really hard to ever
> > change them.  It also doesn't work for multiple devices of the same
> > type.
> > 
> If not for module_param, do you suggest we use sysfs?

It depends on what you want to control.

> module_param also provides a facility to control these parameters
> through command line too. If we can't use module_param, do you expect
> us to use __setup(..) API?

Again, it depends on what you are wanting to handle here.  module
parameters and setup variables are all "global" in that they affect all
devices of the same type.  sysfs can be "per device" which is almost
always what you want to have happen.

But don't use any of those for things that should be "automatic" in that
the driver should figure out what needs to be done here and not need any
external configuration.  That's the best way of all to handle this.

thanks,

greg k-h
diff mbox series

Patch

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 5856e79..12669ec 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -136,4 +136,16 @@  config QCOM_APR
           application processor and QDSP6. APR is
           used by audio driver to configure QDSP6
           ASM, ADM and AFE modules.
+
+config QCOM_EUD
+	tristate "QTI Embedded USB Debugger (EUD)"
+	depends on ARCH_QCOM
+	help
+	  The Embedded USB Debugger (EUD) driver is a driver for the
+	  control peripheral which waits on events like USB attach/detach
+	  and charger enable/disable. The control peripheral further helps
+	  support the USB-based debug and trace capabilities.
+	  This module enables support for Qualcomm Technologies, Inc.
+	  Embedded USB Debugger (EUD).
+	  If unsure, say N.
 endmenu
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 19dcf95..dd4701b 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -15,3 +15,4 @@  obj-$(CONFIG_QCOM_SMP2P)	+= smp2p.o
 obj-$(CONFIG_QCOM_SMSM)	+= smsm.o
 obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
 obj-$(CONFIG_QCOM_APR) += apr.o
+obj-$(CONFIG_QCOM_EUD) += eud.o
diff --git a/drivers/soc/qcom/eud.c b/drivers/soc/qcom/eud.c
new file mode 100644
index 0000000..16a0e7b
--- /dev/null
+++ b/drivers/soc/qcom/eud.c
@@ -0,0 +1,338 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/extcon.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/power_supply.h>
+
+#define EUD_ENABLE_CMD 1
+#define EUD_DISABLE_CMD 0
+
+#define EUD_REG_INT1_EN_MASK	0x0024
+#define EUD_REG_INT_STATUS_1	0x0044
+#define EUD_REG_CTL_OUT_1	0x0074
+#define EUD_REG_VBUS_INT_CLR	0x0080
+#define EUD_REG_CHGR_INT_CLR	0x0084
+#define EUD_REG_CSR_EUD_EN	0x1014
+#define EUD_REG_SW_ATTACH_DET	0x1018
+
+#define EUD_INT_VBUS		BIT(2)
+#define EUD_INT_CHGR		BIT(3)
+#define EUD_INT_SAFE_MODE	BIT(4)
+
+struct eud_chip {
+	struct device			*dev;
+	int				eud_irq;
+	unsigned int			extcon_id;
+	unsigned int			int_status;
+	bool				usb_attach;
+	bool				chgr_enable;
+	void __iomem			*eud_reg_base;
+	struct extcon_dev		*extcon;
+	struct work_struct		eud_work;
+};
+
+static const unsigned int eud_extcon_cable[] = {
+	EXTCON_USB,
+	EXTCON_CHG_USB_SDP,
+	EXTCON_NONE,
+};
+
+/*
+ * On the kernel command line specify eud.enable=1 to enable EUD.
+ * EUD is disabled by default.
+ */
+static int enable;
+static bool eud_ready;
+static struct eud_chip *eud_private;
+
+static void enable_eud(struct eud_chip *priv)
+{
+	struct power_supply *usb_psy = NULL;
+	union power_supply_propval pval = {0};
+	union power_supply_propval tval = {0};
+	int ret;
+
+	usb_psy = power_supply_get_by_name("usb");
+	if (!usb_psy)
+		return;
+
+	ret = power_supply_get_property(usb_psy,
+			POWER_SUPPLY_PROP_PRESENT, &pval);
+	if (ret)
+		return;
+
+	ret = power_supply_get_property(usb_psy,
+			POWER_SUPPLY_PROP_REAL_TYPE, &tval);
+	if (ret)
+		return;
+
+	if (pval.intval && (tval.intval == POWER_SUPPLY_TYPE_USB ||
+	    tval.intval == POWER_SUPPLY_TYPE_USB_CDP)) {
+		/* write into CSR to enable EUD */
+		writel_relaxed(BIT(0), priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
+		/* Enable vbus, chgr & safe mode warning interrupts */
+		writel_relaxed(EUD_INT_VBUS | EUD_INT_CHGR | EUD_INT_SAFE_MODE,
+				priv->eud_reg_base + EUD_REG_INT1_EN_MASK);
+
+		/* Ensure Register Writes Complete */
+		wmb();
+
+		/*
+		 * Set the default cable state to usb connect and charger
+		 * enable
+		 */
+		ret = extcon_set_state_sync(priv->extcon, EXTCON_USB, true);
+		if (ret)
+			return;
+		ret = extcon_set_state_sync(priv->extcon,
+				EXTCON_CHG_USB_SDP, true);
+		if (ret)
+			return;
+	} else {
+		return;
+	}
+
+}
+
+static void disable_eud(struct eud_chip *priv)
+{
+	/* write into CSR to disable EUD */
+	writel_relaxed(0, priv->eud_reg_base + EUD_REG_CSR_EUD_EN);
+}
+
+static int param_eud_set(const char *val, const struct kernel_param *kp)
+{
+	int enable = 0;
+
+	if (sscanf(val, "%du", &enable) != 1)
+		return -EINVAL;
+
+	if (enable != EUD_ENABLE_CMD && enable != EUD_DISABLE_CMD)
+		return -EINVAL;
+
+	*((uint *)kp->arg) = enable;
+	if (!eud_ready)
+		return 0;
+
+	if (enable == EUD_ENABLE_CMD)
+		enable_eud(eud_private);
+	else if (enable == EUD_DISABLE_CMD)
+		disable_eud(eud_private);
+
+	return 0;
+}
+
+static const struct kernel_param_ops eud_param_ops = {
+	.set = param_eud_set,
+	.get = param_get_int,
+};
+
+module_param_cb(enable, &eud_param_ops, &enable, 0644);
+
+static void eud_event_notifier(struct work_struct *eud_work)
+{
+	struct eud_chip *chip = container_of(eud_work, struct eud_chip,
+					eud_work);
+	int ret;
+
+	if (chip->int_status == EUD_INT_VBUS) {
+		ret = extcon_set_state_sync(chip->extcon, chip->extcon_id,
+					chip->usb_attach);
+		if (ret)
+			return;
+	} else if (chip->int_status == EUD_INT_CHGR) {
+		ret = extcon_set_state_sync(chip->extcon, chip->extcon_id,
+					chip->chgr_enable);
+		if (ret)
+			return;
+	}
+}
+
+static void usb_attach_detach(struct eud_chip *chip)
+{
+	u32 reg;
+
+	chip->extcon_id = EXTCON_USB;
+	/* read ctl_out_1[4] to find USB attach or detach event */
+	reg = readl_relaxed(chip->eud_reg_base + EUD_REG_CTL_OUT_1);
+	if (reg & BIT(4))
+		chip->usb_attach = true;
+	else
+		chip->usb_attach = false;
+
+	schedule_work(&chip->eud_work);
+
+	/* set and clear vbus_int_clr[0] to clear interrupt */
+	writel_relaxed(BIT(0), chip->eud_reg_base + EUD_REG_VBUS_INT_CLR);
+	/* Ensure Register Writes Complete */
+	wmb();
+	writel_relaxed(0, chip->eud_reg_base + EUD_REG_VBUS_INT_CLR);
+}
+
+static void chgr_enable_disable(struct eud_chip *chip)
+{
+	u32 reg;
+
+	chip->extcon_id = EXTCON_CHG_USB_SDP;
+	/* read ctl_out_1[6] to find charger enable or disable event */
+	reg = readl_relaxed(chip->eud_reg_base + EUD_REG_CTL_OUT_1);
+	if (reg & BIT(6))
+		chip->chgr_enable = true;
+	else
+		chip->chgr_enable = false;
+
+	schedule_work(&chip->eud_work);
+
+	/* set and clear chgr_int_clr[0] to clear interrupt */
+	writel_relaxed(BIT(0), chip->eud_reg_base + EUD_REG_CHGR_INT_CLR);
+	/* Ensure Register Writes Complete */
+	wmb();
+	writel_relaxed(0, chip->eud_reg_base + EUD_REG_CHGR_INT_CLR);
+}
+
+static void pet_eud(struct eud_chip *chip)
+{
+	u32 reg;
+
+	/* read sw_attach_det[0] to find attach/detach event */
+	reg = readl_relaxed(chip->eud_reg_base + EUD_REG_SW_ATTACH_DET);
+	if (reg & BIT(0)) {
+		/* Detach & Attach pet for EUD */
+		writel_relaxed(0, chip->eud_reg_base + EUD_REG_SW_ATTACH_DET);
+		/* Ensure Register Writes Complete */
+		wmb();
+		/* Delay to make sure detach pet is done before attach pet */
+		udelay(100);
+		writel_relaxed(BIT(0), chip->eud_reg_base +
+					EUD_REG_SW_ATTACH_DET);
+		/* Ensure Register Writes Complete */
+		wmb();
+	} else {
+		/* Attach pet for EUD */
+		writel_relaxed(BIT(0), chip->eud_reg_base +
+					EUD_REG_SW_ATTACH_DET);
+		/* Ensure Register Writes Complete */
+		wmb();
+	}
+}
+
+static irqreturn_t handle_eud_irq(int irq, void *data)
+{
+	struct eud_chip *chip = data;
+	u32 reg;
+
+	/* read status register and find out which interrupt triggered */
+	reg = readl_relaxed(chip->eud_reg_base + EUD_REG_INT_STATUS_1);
+	if (reg & EUD_INT_VBUS) {
+		chip->int_status = EUD_INT_VBUS;
+		usb_attach_detach(chip);
+	} else if (reg & EUD_INT_CHGR) {
+		chip->int_status = EUD_INT_CHGR;
+		chgr_enable_disable(chip);
+	} else if (reg & EUD_INT_SAFE_MODE) {
+		pet_eud(chip);
+	} else {
+		return IRQ_NONE;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int msm_eud_probe(struct platform_device *pdev)
+{
+	struct eud_chip *chip;
+	struct resource *res;
+	int ret;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	chip->extcon = devm_extcon_dev_allocate(&pdev->dev, eud_extcon_cable);
+	if (IS_ERR(chip->extcon))
+		return PTR_ERR(chip->extcon);
+
+	ret = devm_extcon_dev_register(&pdev->dev, chip->extcon);
+	if (ret)
+		return ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	chip->eud_reg_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(chip->eud_reg_base))
+		return PTR_ERR(chip->eud_reg_base);
+
+	chip->eud_irq = platform_get_irq(pdev, 0);
+
+	ret = devm_request_irq(&pdev->dev, chip->eud_irq, handle_eud_irq,
+				IRQF_TRIGGER_HIGH, NULL, chip);
+	if (ret)
+		return ret;
+
+	device_init_wakeup(&pdev->dev, true);
+	enable_irq_wake(chip->eud_irq);
+
+	INIT_WORK(&chip->eud_work, eud_event_notifier);
+
+	eud_private = chip;
+	eud_ready = true;
+
+	/* Enable EUD */
+	if (enable)
+		enable_eud(eud_private);
+
+	return 0;
+}
+
+static int msm_eud_remove(struct platform_device *pdev)
+{
+	struct eud_chip *chip = platform_get_drvdata(pdev);
+
+	if (enable)
+		disable_eud(eud_private);
+	device_init_wakeup(&pdev->dev, false);
+	disable_irq_wake(chip->eud_irq);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static const struct of_device_id msm_eud_dt_match[] = {
+	{.compatible = "qcom,msm-eud"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, msm_eud_dt_match);
+
+static struct platform_driver msm_eud_driver = {
+	.probe		= msm_eud_probe,
+	.remove		= msm_eud_remove,
+	.driver		= {
+		.name		= "msm-eud",
+		.of_match_table = msm_eud_dt_match,
+	},
+};
+module_platform_driver(msm_eud_driver);
+
+MODULE_DESCRIPTION("QTI EUD driver");
+MODULE_LICENSE("GPL v2");