From: Li Jun Date: Fri, 9 Jan 2015 13:58:41 +0000 (+0800) Subject: MLK-10085-7 usb: chipidea: imx: add usb charger detection for imx6 X-Git-Tag: C0P2-H0.0--20200415~4725 X-Git-Url: https://git.somdevices.com/?a=commitdiff_plain;h=39dc874e2de875a9fbf8848babec3d4b1c077c5f;p=linux.git MLK-10085-7 usb: chipidea: imx: add usb charger detection for imx6 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 Signed-off-by: Li Jun (cherry picked from commit da98a621a79b6febf4e072ffb99e16e20b5bc36a) --- diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 099179457f60..e15e0933a40b 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -19,6 +19,10 @@ #include #include #include +#include +#include +#include +#include #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); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 409aa5ca8dda..1447d2c8839d 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -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 @@ -11,10 +11,38 @@ #ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H #define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +#include +#include + +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 */ diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 8e42022171f3..5e7d241973b7 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "ci_hdrc_imx.h" @@ -81,6 +82,24 @@ #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",