summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea/ci_hdrc_imx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/ci_hdrc_imx.c')
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c407
1 files changed, 378 insertions, 29 deletions
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index 099179457f60..53f48cc2eeb5 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -1,5 +1,6 @@
/*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*
@@ -19,6 +20,13 @@
#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 <linux/busfreq-imx.h>
+#include <linux/pm_qos.h>
+#include <linux/usb/of.h>
#include "ci.h"
#include "ci_hdrc_imx.h"
@@ -40,25 +48,29 @@ static const struct ci_hdrc_imx_platform_flag imx27_usb_data = {
static const struct ci_hdrc_imx_platform_flag imx28_usb_data = {
.flags = CI_HDRC_IMX28_WRITE_FIX |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_STREAMING,
+ CI_HDRC_DISABLE_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_STREAMING,
+ CI_HDRC_DISABLE_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_HOST_STREAMING,
+ CI_HDRC_DISABLE_HOST_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON |
- CI_HDRC_DISABLE_HOST_STREAMING,
+ CI_HDRC_DISABLE_HOST_STREAMING |
+ CI_HDRC_IMX_EHCI_QUIRK,
};
static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = {
@@ -70,6 +82,16 @@ static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = {
.flags = CI_HDRC_SUPPORTS_RUNTIME_PM,
};
+static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
+ CI_HDRC_IMX_EHCI_QUIRK |
+ CI_HDRC_PMQOS,
+};
+
+static const struct ci_hdrc_imx_platform_flag imx8qm_usb_data = {
+ .flags = CI_HDRC_SUPPORTS_RUNTIME_PM,
+};
+
static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx23-usb", .data = &imx23_usb_data},
{ .compatible = "fsl,imx28-usb", .data = &imx28_usb_data},
@@ -79,6 +101,8 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = {
{ .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data},
{ .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data},
{ .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data},
+ { .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data},
+ { .compatible = "fsl,imx8qm-usb", .data = &imx8qm_usb_data},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids);
@@ -90,12 +114,30 @@ 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;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_hsic_active;
+ struct regulator *hsic_pad_regulator;
+ const struct ci_hdrc_imx_platform_flag *data;
/* SoC before i.mx6 (except imx23/imx28) needs three clks */
bool need_three_clks;
struct clk *clk_ipg;
struct clk *clk_ahb;
struct clk *clk_per;
/* --------------------------------- */
+ struct pm_qos_request pm_qos_req;
+};
+
+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 */
@@ -143,9 +185,32 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev)
if (of_find_property(np, "over-current-active-high", NULL))
data->oc_polarity = 1;
+ if (of_find_property(np, "power-polarity-active-high", NULL))
+ data->pwr_polarity = 1;
+
if (of_find_property(np, "external-vbus-divider", NULL))
data->evdo = 1;
+ if (of_find_property(np, "osc-clkgate-delay", NULL)) {
+ ret = of_property_read_u32(np, "osc-clkgate-delay",
+ &data->osc_clkgate_delay);
+ if (ret) {
+ dev_err(dev,
+ "failed to get osc-clkgate-delay value\n");
+ return ERR_PTR(ret);
+ }
+ /*
+ * 0 <= osc_clkgate_delay <=7
+ * - 0x0 (default) is 0.5ms,
+ * - 0x1-0x7: 1-7ms
+ */
+ if (data->osc_clkgate_delay > 7) {
+ dev_err(dev,
+ "value of osc-clkgate-delay is incorrect\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
return data;
}
@@ -247,41 +312,212 @@ 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;
+ case CI_HDRC_IMX_HSIC_ACTIVE_EVENT:
+ if (!IS_ERR(data->pinctrl) &&
+ !IS_ERR(data->pinctrl_hsic_active)) {
+ ret = pinctrl_select_state(data->pinctrl,
+ data->pinctrl_hsic_active);
+ if (ret)
+ dev_err(dev,
+ "hsic_active select failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ break;
+ case CI_HDRC_IMX_HSIC_SUSPEND_EVENT:
+ if (data->usbmisc_data) {
+ ret = imx_usbmisc_hsic_set_connect(data->usbmisc_data);
+ if (ret)
+ dev_err(dev,
+ "hsic_set_connect failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ break;
+ case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_FS:
+ if (data->usbmisc_data)
+ return imx_usbmisc_term_select_override(
+ data->usbmisc_data, true, 1);
+ break;
+ case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF:
+ if (data->usbmisc_data)
+ return imx_usbmisc_term_select_override(
+ data->usbmisc_data, false, 0);
+ 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_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;
+
+ charger->psy = devm_power_supply_register(charger->dev,
+ &charger->psy_desc, NULL);
+ if (IS_ERR(charger->psy))
+ return PTR_ERR(charger->psy);
+
+ charger->psy->supplied_to = imx_usb_charger_supplied_to;
+ charger->psy->num_supplicants = sizeof(imx_usb_charger_supplied_to)
+ / sizeof(char *);
+
+ 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 *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct pinctrl_state *pinctrl_hsic_idle;
- of_id = of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev);
+ of_id = of_match_device(ci_hdrc_imx_dt_ids, dev);
if (!of_id)
return -ENODEV;
imx_platform_flag = of_id->data;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
- data->usbmisc_data = usbmisc_get_init_data(&pdev->dev);
+
+ data->data = imx_platform_flag;
+ pdata.flags |= imx_platform_flag->flags;
+ data->usbmisc_data = usbmisc_get_init_data(dev);
if (IS_ERR(data->usbmisc_data))
return PTR_ERR(data->usbmisc_data);
- ret = imx_get_clks(&pdev->dev);
+ data->pinctrl = devm_pinctrl_get(dev);
+ if (IS_ERR(data->pinctrl)) {
+ dev_dbg(dev, "pinctrl get failed, err=%ld\n",
+ PTR_ERR(data->pinctrl));
+ } else {
+ pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle");
+ if (IS_ERR(pinctrl_hsic_idle)) {
+ dev_dbg(dev,
+ "pinctrl_hsic_idle lookup failed, err=%ld\n",
+ PTR_ERR(pinctrl_hsic_idle));
+ } else {
+ ret = pinctrl_select_state(data->pinctrl,
+ pinctrl_hsic_idle);
+ if (ret) {
+ dev_err(dev,
+ "hsic_idle select failed, err=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ data->pinctrl_hsic_active = pinctrl_lookup_state(data->pinctrl,
+ "active");
+ if (IS_ERR(data->pinctrl_hsic_active))
+ dev_dbg(dev,
+ "pinctrl_hsic_active lookup failed, err=%ld\n",
+ PTR_ERR(data->pinctrl_hsic_active));
+ }
+
+ ret = imx_get_clks(dev);
if (ret)
return ret;
- ret = imx_prepare_enable_clks(&pdev->dev);
+ request_bus_freq(BUS_FREQ_HIGH);
+ if (pdata.flags & CI_HDRC_PMQOS)
+ pm_qos_add_request(&data->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+
+ ret = imx_prepare_enable_clks(dev);
if (ret)
- return ret;
+ goto err_bus_freq;
- data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0);
+ data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
if (IS_ERR(data->phy)) {
ret = PTR_ERR(data->phy);
/* Return -EINVAL if no usbphy is available */
@@ -291,46 +527,119 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
}
pdata.usb_phy = data->phy;
- pdata.flags |= imx_platform_flag->flags;
if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
data->supports_runtime_pm = true;
+ if (of_find_property(np, "ci-disable-lpm", NULL)) {
+ data->supports_runtime_pm = false;
+ pdata.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM;
+ }
+
+ if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) {
+ pdata.flags |= CI_HDRC_IMX_IS_HSIC;
+ data->usbmisc_data->hsic = 1;
+ data->hsic_pad_regulator = devm_regulator_get(dev, "pad");
+ if (PTR_ERR(data->hsic_pad_regulator) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto err_clk;
+ } else if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) {
+ /* no pad regualator is needed */
+ data->hsic_pad_regulator = NULL;
+ } else if (IS_ERR(data->hsic_pad_regulator)) {
+ dev_err(dev, "Get hsic pad regulator error: %ld\n",
+ PTR_ERR(data->hsic_pad_regulator));
+ ret = PTR_ERR(data->hsic_pad_regulator);
+ goto err_clk;
+ }
+
+ if (data->hsic_pad_regulator) {
+ ret = regulator_enable(data->hsic_pad_regulator);
+ if (ret) {
+ dev_err(dev,
+ "Fail to enable hsic pad regulator\n");
+ goto err_clk;
+ }
+ }
+ }
+
+ if (of_find_property(np, "fsl,anatop", NULL) && data->usbmisc_data) {
+ data->anatop = syscon_regmap_lookup_by_phandle(np,
+ "fsl,anatop");
+ if (IS_ERR(data->anatop)) {
+ dev_dbg(dev, "failed to find regmap for anatop\n");
+ ret = PTR_ERR(data->anatop);
+ goto disable_hsic_regulator;
+ }
+ data->usbmisc_data->anatop = data->anatop;
+ }
+
+ if (of_find_property(np, "imx-usb-charger-detection", NULL) &&
+ data->usbmisc_data) {
+ data->imx_usb_charger_detection = true;
+ data->charger.dev = dev;
+ data->usbmisc_data->charger = &data->charger;
+ ret = imx_usb_register_charger(&data->charger,
+ "imx_usb_charger");
+ if (ret && ret != -ENODEV)
+ goto disable_hsic_regulator;
+ if (!ret)
+ dev_dbg(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);
- goto err_clk;
+ dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
+ goto disable_hsic_regulator;
}
- data->ci_pdev = ci_hdrc_add_device(&pdev->dev,
+ data->ci_pdev = ci_hdrc_add_device(dev,
pdev->resource, pdev->num_resources,
&pdata);
if (IS_ERR(data->ci_pdev)) {
ret = PTR_ERR(data->ci_pdev);
if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
+ dev_err(dev,
"ci_hdrc_add_device failed, err=%d\n", ret);
- goto err_clk;
+ goto disable_hsic_regulator;
}
ret = imx_usbmisc_init_post(data->usbmisc_data);
if (ret) {
- dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", ret);
+ dev_err(dev, "usbmisc post failed, ret=%d\n", ret);
goto disable_device;
}
+ ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
+ if (ret) {
+ dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
+ goto disable_device;
+ }
+
+ /* usbmisc needs to know dr mode to choose wakeup setting */
+ if (data->usbmisc_data)
+ data->usbmisc_data->available_role =
+ ci_hdrc_query_available_role(data->ci_pdev);
+
if (data->supports_runtime_pm) {
- pm_runtime_set_active(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
}
- device_set_wakeup_capable(&pdev->dev, true);
+ device_set_wakeup_capable(dev, true);
return 0;
disable_device:
ci_hdrc_remove_device(data->ci_pdev);
+disable_hsic_regulator:
+ if (data->hsic_pad_regulator)
+ ret = regulator_disable(data->hsic_pad_regulator);
err_clk:
imx_disable_unprepare_clks(&pdev->dev);
+err_bus_freq:
+ if (pdata.flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
return ret;
}
@@ -345,6 +654,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)
}
ci_hdrc_remove_device(data->ci_pdev);
imx_disable_unprepare_clks(&pdev->dev);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
+ if (data->hsic_pad_regulator)
+ regulator_disable(data->hsic_pad_regulator);
return 0;
}
@@ -358,10 +672,23 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev)
static int imx_controller_suspend(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
+ int ret;
dev_dbg(dev, "at %s\n", __func__);
+ if (data->usbmisc_data) {
+ ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, false);
+ if (ret) {
+ dev_err(dev,
+ "usbmisc hsic_set_clk failed, ret=%d\n", ret);
+ return ret;
+ }
+ }
+
imx_disable_unprepare_clks(dev);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
data->in_lpm = true;
return 0;
@@ -374,27 +701,51 @@ static int imx_controller_resume(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- if (!data->in_lpm) {
- WARN_ON(1);
+ if (!data->in_lpm)
return 0;
- }
+ request_bus_freq(BUS_FREQ_HIGH);
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_add_request(&data->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
ret = imx_prepare_enable_clks(dev);
if (ret)
- return ret;
+ goto err_bus_freq;
data->in_lpm = false;
+ ret = imx_usbmisc_power_lost_check(data->usbmisc_data);
+ /* re-init if resume from power lost */
+ if (ret > 0) {
+ ret = imx_usbmisc_init(data->usbmisc_data);
+ if (ret) {
+ dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
+ goto clk_disable;
+ }
+ }
+
ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
if (ret) {
dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
goto clk_disable;
}
+ ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, true);
+ if (ret) {
+ dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret);
+ goto hsic_set_clk_fail;
+ }
+
return 0;
+hsic_set_clk_fail:
+ imx_usbmisc_set_wakeup(data->usbmisc_data, true);
clk_disable:
imx_disable_unprepare_clks(dev);
+err_bus_freq:
+ if (data->data->flags & CI_HDRC_PMQOS)
+ pm_qos_remove_request(&data->pm_qos_req);
+ release_bus_freq(BUS_FREQ_HIGH);
return ret;
}
@@ -442,10 +793,8 @@ static int ci_hdrc_imx_runtime_suspend(struct device *dev)
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret;
- if (data->in_lpm) {
- WARN_ON(1);
+ if (data->in_lpm)
return 0;
- }
ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
if (ret) {