#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"
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;
/* --------------------------------- */
};
+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)
}
}
+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)
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);
/*
- * 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 */
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 */
#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);
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 {
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,
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 = {
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 = {
}
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",