MLK-10085-7 usb: chipidea: imx: add usb charger detection for imx6
authorLi Jun <b47624@freescale.com>
Fri, 9 Jan 2015 13:58:41 +0000 (21:58 +0800)
committerNitin Garg <nitin.garg@nxp.com>
Mon, 19 Mar 2018 19:46:58 +0000 (14:46 -0500)
The usb controller driver creates usb charger, and notify
the charger connect and disconnect using vbus connect and
disconnect event.

Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Li Jun <jun.li@freescale.com>
(cherry picked from commit da98a621a79b6febf4e072ffb99e16e20b5bc36a)

drivers/usb/chipidea/ci_hdrc_imx.c
drivers/usb/chipidea/ci_hdrc_imx.h
drivers/usb/chipidea/usbmisc_imx.c

index 0991794..e15e093 100644 (file)
 #include <linux/dma-mapping.h>
 #include <linux/usb/chipidea.h>
 #include <linux/clk.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regulator/consumer.h>
 
 #include "ci.h"
 #include "ci_hdrc_imx.h"
@@ -90,6 +94,9 @@ struct ci_hdrc_imx_data {
        struct imx_usbmisc_data *usbmisc_data;
        bool supports_runtime_pm;
        bool in_lpm;
+       bool imx_usb_charger_detection;
+       struct usb_charger charger;
+       struct regmap *anatop;
        /* SoC before i.mx6 (except imx23/imx28) needs three clks */
        bool need_three_clks;
        struct clk *clk_ipg;
@@ -98,6 +105,16 @@ struct ci_hdrc_imx_data {
        /* --------------------------------- */
 };
 
+static char *imx_usb_charger_supplied_to[] = {
+       "imx_usb_charger",
+};
+
+static enum power_supply_property imx_usb_charger_power_props[] = {
+       POWER_SUPPLY_PROP_PRESENT,      /* Charger detected */
+       POWER_SUPPLY_PROP_ONLINE,       /* VBUS online */
+       POWER_SUPPLY_PROP_CURRENT_MAX,  /* Maximum current in mA */
+};
+
 /* Common functions shared by usbmisc drivers */
 
 static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev)
@@ -247,16 +264,116 @@ static void imx_disable_unprepare_clks(struct device *dev)
        }
 }
 
+static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned event)
+{
+       struct device *dev = ci->dev->parent;
+       struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+       int ret = 0;
+
+       switch (event) {
+       case CI_HDRC_CONTROLLER_VBUS_EVENT:
+               if (data->usbmisc_data && ci->vbus_active) {
+                       if (data->imx_usb_charger_detection) {
+                               ret = imx_usbmisc_charger_detection(
+                                       data->usbmisc_data, true);
+                               if (!ret && data->charger.psy_desc.type !=
+                                                       POWER_SUPPLY_TYPE_USB)
+                                       ret = CI_HDRC_NOTIFY_RET_DEFER_EVENT;
+                       }
+               } else if (data->usbmisc_data && !ci->vbus_active) {
+                       if (data->imx_usb_charger_detection)
+                               ret = imx_usbmisc_charger_detection(
+                                       data->usbmisc_data, false);
+               }
+               break;
+       case CI_HDRC_CONTROLLER_CHARGER_POST_EVENT:
+               if (!data->imx_usb_charger_detection)
+                       return ret;
+               imx_usbmisc_charger_secondary_detection(data->usbmisc_data);
+               break;
+       default:
+               dev_dbg(dev, "unknown event\n");
+       }
+
+       return ret;
+}
+
+static int imx_usb_charger_get_property(struct power_supply *psy,
+                               enum power_supply_property psp,
+                               union power_supply_propval *val)
+{
+       struct usb_charger *charger =
+               container_of(psy->desc, struct usb_charger, psy_desc);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = charger->present;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = charger->online;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = charger->max_current;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+/*
+ * imx_usb_register_charger - register a USB charger
+ * @charger: the charger to be initialized
+ * @name: name for the power supply
+
+ * Registers a power supply for the charger. The USB Controller
+ * driver will call this after filling struct usb_charger.
+ */
+static int imx_usb_register_charger(struct usb_charger *charger,
+               const char *name)
+{
+       struct power_supply             *psy = charger->psy;
+       struct power_supply_desc        *desc = &charger->psy_desc;
+
+       if (!charger->dev)
+               return -EINVAL;
+
+       if (name)
+               desc->name = name;
+       else
+               desc->name = "imx_usb_charger";
+
+       charger->bc = BATTERY_CHARGING_SPEC_1_2;
+       mutex_init(&charger->lock);
+
+       desc->type              = POWER_SUPPLY_TYPE_MAINS;
+       desc->properties        = imx_usb_charger_power_props;
+       desc->num_properties    = ARRAY_SIZE(imx_usb_charger_power_props);
+       desc->get_property      = imx_usb_charger_get_property;
+       psy->supplied_to        = imx_usb_charger_supplied_to;
+       psy->num_supplicants    = sizeof(imx_usb_charger_supplied_to)
+                                       / sizeof(char *);
+
+       charger->psy = devm_power_supply_register(charger->dev->parent,
+                                               &charger->psy_desc, NULL);
+       if (IS_ERR(charger->psy))
+               return PTR_ERR(charger->psy);
+
+       return 0;
+}
+
 static int ci_hdrc_imx_probe(struct platform_device *pdev)
 {
        struct ci_hdrc_imx_data *data;
        struct ci_hdrc_platform_data pdata = {
                .name           = dev_name(&pdev->dev),
                .capoffset      = DEF_CAPOFFSET,
+               .notify_event   = ci_hdrc_imx_notify_event,
        };
        int ret;
        const struct of_device_id *of_id;
        const struct ci_hdrc_imx_platform_flag *imx_platform_flag;
+       struct device_node *np = pdev->dev.of_node;
 
        of_id = of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev);
        if (!of_id)
@@ -295,6 +412,32 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
        if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
                data->supports_runtime_pm = true;
 
+       if (of_find_property(np, "fsl,anatop", NULL)) {
+               data->anatop = syscon_regmap_lookup_by_phandle(np,
+                                                       "fsl,anatop");
+               if (IS_ERR(data->anatop)) {
+                       dev_dbg(&pdev->dev,
+                               "failed to find regmap for anatop\n");
+                       ret = PTR_ERR(data->anatop);
+                       goto err_clk;
+               }
+               if (data->usbmisc_data)
+                       data->usbmisc_data->anatop = data->anatop;
+       }
+
+       if (of_find_property(np, "imx-usb-charger-detection", NULL)) {
+               data->imx_usb_charger_detection = true;
+               data->charger.dev = &pdev->dev;
+               data->usbmisc_data->charger = &data->charger;
+               ret = imx_usb_register_charger(&data->charger,
+                                               "imx_usb_charger");
+               if (ret && ret != -ENODEV)
+                       goto err_clk;
+               if (!ret)
+                       dev_dbg(&pdev->dev,
+                                       "USB Charger is created\n");
+       }
+
        ret = imx_usbmisc_init(data->usbmisc_data);
        if (ret) {
                dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", ret);
index 409aa5c..1447d2c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2015 Freescale Semiconductor, Inc.
  *
  * The code contained herein is licensed under the GNU General Public
  * License. You may obtain a copy of the GNU General Public License
 
 #ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
 #define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H
+#include <linux/usb/otg.h>
+#include <linux/power_supply.h>
+
+enum battery_charging_spec {
+       BATTERY_CHARGING_SPEC_NONE = 0,
+       BATTERY_CHARGING_SPEC_UNKNOWN,
+       BATTERY_CHARGING_SPEC_1_0,
+       BATTERY_CHARGING_SPEC_1_1,
+       BATTERY_CHARGING_SPEC_1_2,
+};
+
+struct usb_charger {
+       /* USB controller */
+       struct device           *dev;
+       struct power_supply     *psy;
+       struct power_supply_desc        psy_desc;
+       struct mutex            lock;
+
+       /* Compliant with Battery Charging Specification version (if any) */
+       enum battery_charging_spec      bc;
+
+       /* properties */
+       unsigned                present:1;
+       unsigned                online:1;
+       unsigned                max_current;
+};
 
 struct imx_usbmisc_data {
        struct device *dev;
        int index;
+       struct regmap *anatop;
+       struct usb_charger *charger;
 
        unsigned int disable_oc:1; /* over current detect disabled */
        unsigned int oc_polarity:1; /* over current polarity if oc enabled */
@@ -24,5 +52,7 @@ struct imx_usbmisc_data {
 int imx_usbmisc_init(struct imx_usbmisc_data *);
 int imx_usbmisc_init_post(struct imx_usbmisc_data *);
 int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *, bool);
+int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect);
+int imx_usbmisc_charger_secondary_detection(struct imx_usbmisc_data *data);
 
 #endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
index 8e42022..5e7d241 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/err.h>
 #include <linux/io.h>
 #include <linux/delay.h>
+#include <linux/regmap.h>
 
 #include "ci_hdrc_imx.h"
 
 #define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID     MX7D_USB_VBUS_WAKEUP_SOURCE(2)
 #define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END   MX7D_USB_VBUS_WAKEUP_SOURCE(3)
 
+#define ANADIG_ANA_MISC0               0x150
+#define ANADIG_ANA_MISC0_SET           0x154
+#define ANADIG_ANA_MISC0_CLK_DELAY(x)  ((x >> 26) & 0x7)
+
+#define ANADIG_USB1_CHRG_DETECT_SET    0x1b4
+#define ANADIG_USB1_CHRG_DETECT_CLR    0x1b8
+#define ANADIG_USB1_CHRG_DETECT_EN_B           BIT(20)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B     BIT(19)
+#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT    BIT(18)
+
+#define ANADIG_USB1_VBUS_DET_STAT      0x1c0
+#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID   BIT(3)
+
+#define ANADIG_USB1_CHRG_DET_STAT      0x1d0
+#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE     BIT(2)
+#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED        BIT(1)
+#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0)
+
 struct usbmisc_ops {
        /* It's called once when probe a usb device */
        int (*init)(struct imx_usbmisc_data *data);
@@ -88,6 +107,10 @@ struct usbmisc_ops {
        int (*post)(struct imx_usbmisc_data *data);
        /* It's called when we need to enable/disable usb wakeup */
        int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled);
+       /* usb charger contact and primary detection */
+       int (*charger_primary_detection)(struct imx_usbmisc_data *data);
+       /* usb charger secondary detection */
+       int (*charger_secondary_detection)(struct imx_usbmisc_data *data);
 };
 
 struct imx_usbmisc {
@@ -389,6 +412,144 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data)
        return 0;
 }
 
+/***************************************************************************/
+/*                         imx usb charger detecton                        */
+/***************************************************************************/
+static void usb_charger_is_present(struct usb_charger *charger, bool present)
+{
+       if (present)
+               charger->present = 1;
+       else
+               charger->present = 0;
+
+       power_supply_changed(charger->psy);
+       sysfs_notify(&charger->psy->dev.kobj, NULL, "present");
+}
+
+static void imx6_disable_charger_detector(struct imx_usbmisc_data *data)
+{
+       struct regmap *regmap = data->anatop;
+
+       regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+                               ANADIG_USB1_CHRG_DETECT_EN_B |
+                               ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+}
+
+static int imx6_charger_data_contact_detect(struct imx_usbmisc_data *data)
+{
+       struct regmap *regmap = data->anatop;
+       struct usb_charger *charger = data->charger;
+       u32 val;
+       int i, data_pin_contact_count = 0;
+
+       /* check if vbus is valid */
+       regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val);
+       if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
+               dev_err(charger->dev, "vbus is error\n");
+               return -EINVAL;
+       }
+
+       /* Enable charger detector */
+       regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+                               ANADIG_USB1_CHRG_DETECT_EN_B);
+       /*
+        * - Do not check whether a charger is connected to the USB port
+        * - Check whether the USB plug has been in contact with each other
+        */
+       regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
+                       ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+                       ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+       /* Check if plug is connected */
+       for (i = 0; i < 100; i = i + 1) {
+               regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+               if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
+                       data_pin_contact_count++;
+                       if (data_pin_contact_count > 5)
+                               /* Data pin makes contact */
+                               break;
+                       else
+                               usleep_range(5000, 10000);
+               } else {
+                       data_pin_contact_count = 0;
+                       usleep_range(5000, 6000);
+               }
+       }
+
+       if (i == 100) {
+               dev_err(charger->dev,
+                       "VBUS is coming from a dedicated power supply.\n");
+               imx6_disable_charger_detector(data);
+               return -ENXIO;
+       }
+
+       return 0;
+}
+
+static int imx6_charger_primary_detection(struct imx_usbmisc_data *data)
+{
+       struct regmap *regmap = data->anatop;
+       struct usb_charger *charger = data->charger;
+       u32 val;
+       int ret;
+
+       ret = imx6_charger_data_contact_detect(data);
+       if (ret)
+               return ret;
+
+       /*
+        * - Do check whether a charger is connected to the USB port
+        * - Do not Check whether the USB plug has been in contact with
+        * each other
+        */
+       regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
+                       ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
+                       ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
+
+       msleep(100);
+
+       /* Check if it is a charger */
+       regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+       if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
+               dev_dbg(charger->dev, "It is a stardard downstream port\n");
+               charger->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+               charger->max_current = 500;
+       }
+
+       imx6_disable_charger_detector(data);
+       return 0;
+}
+
+/*
+ * It must be called after dp is pulled up (from USB controller driver),
+ * That is used to differentiate DCP and CDP
+ */
+int imx6_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+       struct regmap *regmap = data->anatop;
+       struct usb_charger *charger = data->charger;
+       int val;
+
+       msleep(80);
+
+       mutex_lock(&charger->lock);
+       regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val);
+       if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
+               dev_dbg(charger->dev, "It is a dedicate charging port\n");
+               charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
+               charger->max_current = 1500;
+       } else {
+               dev_dbg(charger->dev, "It is a charging downstream port\n");
+               charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
+               charger->max_current = 900;
+       }
+
+       usb_charger_is_present(charger, true);
+       mutex_unlock(&charger->lock);
+
+       return 0;
+}
+
 static const struct usbmisc_ops imx25_usbmisc_ops = {
        .init = usbmisc_imx25_init,
        .post = usbmisc_imx25_post,
@@ -405,6 +566,8 @@ static const struct usbmisc_ops imx53_usbmisc_ops = {
 static const struct usbmisc_ops imx6q_usbmisc_ops = {
        .set_wakeup = usbmisc_imx6q_set_wakeup,
        .init = usbmisc_imx6q_init,
+       .charger_primary_detection = imx6_charger_primary_detection,
+       .charger_secondary_detection = imx6_charger_secondary_detection,
 };
 
 static const struct usbmisc_ops vf610_usbmisc_ops = {
@@ -414,6 +577,8 @@ static const struct usbmisc_ops vf610_usbmisc_ops = {
 static const struct usbmisc_ops imx6sx_usbmisc_ops = {
        .set_wakeup = usbmisc_imx6q_set_wakeup,
        .init = usbmisc_imx6sx_init,
+       .charger_primary_detection = imx6_charger_primary_detection,
+       .charger_secondary_detection = imx6_charger_secondary_detection,
 };
 
 static const struct usbmisc_ops imx7d_usbmisc_ops = {
@@ -463,6 +628,57 @@ int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled)
 }
 EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup);
 
+int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect)
+{
+       struct imx_usbmisc *usbmisc;
+       struct usb_charger *charger = data->charger;
+       int ret = 0;
+
+       if (!data)
+               return -EINVAL;
+
+       usbmisc = dev_get_drvdata(data->dev);
+       if (!usbmisc->ops->charger_primary_detection)
+               return -ENOTSUPP;
+
+       mutex_lock(&charger->lock);
+       if (connect) {
+               charger->online = 1;
+               ret = usbmisc->ops->charger_primary_detection(data);
+               if (ret) {
+                       dev_err(charger->dev,
+                                       "Error occurs during detection: %d\n",
+                                       ret);
+               } else {
+                       if (charger->psy_desc.type == POWER_SUPPLY_TYPE_USB)
+                               usb_charger_is_present(charger, true);
+               }
+       } else {
+               charger->online = 0;
+               charger->max_current = 0;
+               charger->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
+
+               usb_charger_is_present(charger, false);
+       }
+       mutex_unlock(&charger->lock);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection);
+
+int imx_usbmisc_charger_secondary_detection(struct imx_usbmisc_data *data)
+{
+       struct imx_usbmisc *usbmisc;
+
+       if (!data)
+               return 0;
+
+       usbmisc = dev_get_drvdata(data->dev);
+       if (!usbmisc->ops->charger_secondary_detection)
+               return 0;
+       return usbmisc->ops->charger_secondary_detection(data);
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_charger_secondary_detection);
+
 static const struct of_device_id usbmisc_imx_dt_ids[] = {
        {
                .compatible = "fsl,imx25-usbmisc",