diff options
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.c | 156 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.h | 8 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.c | 43 | ||||
-rw-r--r-- | drivers/usb/chipidea/usbmisc_imx.c | 121 | ||||
-rw-r--r-- | include/linux/usb/chipidea.h | 3 |
5 files changed, 329 insertions, 2 deletions
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 52aab74a1b4e..549184b8acbf 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -24,6 +24,7 @@ #include <linux/mfd/syscon.h> #include <linux/power/imx6_usb_charger.h> #include <linux/busfreq-imx6.h> +#include <linux/regulator/consumer.h> #include "ci.h" #include "ci_hdrc_imx.h" @@ -72,8 +73,33 @@ struct ci_hdrc_imx_data { bool imx6_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; }; +static inline bool is_imx6q_con(struct ci_hdrc_imx_data *imx_data) +{ + return imx_data->data == &imx6q_usb_data; +} + +static inline bool is_imx6sl_con(struct ci_hdrc_imx_data *imx_data) +{ + return imx_data->data == &imx6sl_usb_data; +} + +static inline bool is_imx6sx_con(struct ci_hdrc_imx_data *imx_data) +{ + return imx_data->data == &imx6sx_usb_data; +} + +static inline bool imx_has_hsic_con(struct ci_hdrc_imx_data *imx_data) +{ + return is_imx6q_con(imx_data) || is_imx6sl_con(imx_data) + || is_imx6sx_con(imx_data); +} + /* Common functions shared by usbmisc drivers */ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) @@ -119,6 +145,26 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) 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; } @@ -148,6 +194,28 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned event) return ret; imx6_usb_charger_detect_post(&data->charger); 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; default: dev_dbg(dev, "unknown event\n"); } @@ -169,6 +237,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev); const struct ci_hdrc_imx_platform_flag *imx_platform_flag = of_id->data; struct device_node *np = pdev->dev.of_node; + struct pinctrl_state *pinctrl_hsic_idle; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -176,10 +245,40 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); + data->data = imx_platform_flag; data->usbmisc_data = usbmisc_get_init_data(&pdev->dev); if (IS_ERR(data->usbmisc_data)) return PTR_ERR(data->usbmisc_data); + data->pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(data->pinctrl)) { + dev_dbg(&pdev->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(&pdev->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(&pdev->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(&pdev->dev, + "pinctrl_hsic_active lookup failed, err=%ld\n", + PTR_ERR(data->pinctrl_hsic_active)); + } + data->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(data->clk)) { dev_err(&pdev->dev, @@ -214,6 +313,34 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (ret) goto err_clk; + if (data->usbmisc_data->index > 1 && (imx_has_hsic_con(data))) { + pdata.flags |= CI_HDRC_IMX_IS_HSIC; + data->hsic_pad_regulator = devm_regulator_get(&pdev->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(&pdev->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(&pdev->dev, + "Fail to enable hsic pad regulator\n"); + goto err_clk; + } + } + } + if (of_find_property(np, "imx6-usb-charger-detection", NULL)) data->imx6_usb_charger_detection = true; @@ -224,15 +351,17 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "failed to find regmap for anatop\n"); ret = PTR_ERR(data->anatop); - goto err_clk; + goto disable_hsic_regulator; } + if (data->usbmisc_data) + data->usbmisc_data->anatop = data->anatop; if (data->imx6_usb_charger_detection) { data->charger.anatop = data->anatop; data->charger.dev = &pdev->dev; ret = imx6_usb_create_charger(&data->charger, "imx6_usb_charger"); if (ret && ret != -ENODEV) - goto err_clk; + goto disable_hsic_regulator; if (!ret) dev_dbg(&pdev->dev, "USB Charger is created\n"); @@ -276,6 +405,9 @@ disable_device: remove_charger: if (data->imx6_usb_charger_detection) imx6_usb_remove_charger(&data->charger); +disable_hsic_regulator: + if (data->hsic_pad_regulator) + ret = regulator_disable(data->hsic_pad_regulator); err_clk: clk_disable_unprepare(data->clk); release_bus_freq(BUS_FREQ_HIGH); @@ -296,6 +428,8 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) release_bus_freq(BUS_FREQ_HIGH); if (data->imx6_usb_charger_detection) imx6_usb_remove_charger(&data->charger); + if (data->hsic_pad_regulator) + regulator_disable(data->hsic_pad_regulator); return 0; } @@ -304,9 +438,19 @@ static int ci_hdrc_imx_remove(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; + } + } + clk_disable_unprepare(data->clk); release_bus_freq(BUS_FREQ_HIGH); data->in_lpm = true; @@ -351,8 +495,16 @@ static int imx_controller_resume(struct device *dev) 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: clk_disable_unprepare(data->clk); release_bus_freq(BUS_FREQ_HIGH); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 620c538c9a1a..99765ef6a6d0 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -18,11 +18,19 @@ struct imx_usbmisc_data { unsigned int disable_oc:1; /* over current detect disabled */ unsigned int evdo:1; /* set external vbus divider option */ + /* + * Specifies the delay between powering up the xtal 24MHz clock + * and release the clock to the digital logic inside the analog block + */ + unsigned int osc_clkgate_delay; + struct regmap *anatop; }; 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_power_lost_check(struct imx_usbmisc_data *); +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *); +int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *, bool); #endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 20c720199cff..6e5a2b9f0088 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -43,6 +43,20 @@ struct ehci_ci_priv { struct regulator *reg_vbus; }; +/* This function is used to override WKCN, WKDN, and WKOC */ +static void ci_ehci_override_wakeup_flag(struct ehci_hcd *ehci, + u32 __iomem *reg, u32 flags, bool set) +{ + u32 val = ehci_readl(ehci, reg); + + if (set) + val |= flags; + else + val &= ~flags; + + ehci_writel(ehci, val, reg); +} + static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -122,6 +136,8 @@ static int ci_imx_ehci_hub_control( u32 temp; unsigned long flags; int retval = 0; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; @@ -146,6 +162,14 @@ static int ci_imx_ehci_hub_control( PORT_SUSPEND, 5000)) ehci_err(ehci, "timeout waiting for SUSPEND\n"); + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) { + if (ci->platdata->notify_event) + ci->platdata->notify_event + (ci, CI_HDRC_IMX_HSIC_SUSPEND_EVENT); + ci_ehci_override_wakeup_flag(ehci, status_reg, + PORT_WKDISC_E | PORT_WKCONN_E, false); + } + spin_unlock_irqrestore(&ehci->lock, flags); if (ehci_port_speed(ehci, temp) == USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) { @@ -248,6 +272,11 @@ static int host_start(struct ci_hdrc *ci) if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + if (ci->platdata->notify_event && + (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC)) + ci->platdata->notify_event + (ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT); + return ret; put_hcd: @@ -361,6 +390,8 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) struct ehci_hcd *ehci = hcd_to_ehci(hcd); int port; u32 tmp; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); int ret = orig_bus_suspend(hcd); @@ -391,6 +422,18 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) */ usleep_range(150, 200); + /* + * If a transaction is in progress, there may be a delay in + * suspending the port. Poll until the port is suspended. + */ + if (ehci_handshake(ehci, reg, PORT_SUSPEND, + PORT_SUSPEND, 5000)) + ehci_err(ehci, "timeout waiting for SUSPEND\n"); + + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) + ci_ehci_override_wakeup_flag(ehci, reg, + PORT_WKDISC_E | PORT_WKCONN_E, false); + if (hcd->usb_phy && test_bit(port, &ehci->bus_suspended) && (ehci_port_speed(ehci, portsc) == USB_PORT_STAT_HIGH_SPEED)) diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 4139ae11f6d9..3abe742b3684 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -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" @@ -56,10 +57,22 @@ #define MX6_BM_OVER_CUR_DIS BIT(7) #define MX6_BM_WAKEUP_ENABLE BIT(10) +#define MX6_BM_UTMI_ON_CLOCK BIT(13) #define MX6_BM_ID_WAKEUP BIT(16) #define MX6_BM_VBUS_WAKEUP BIT(17) #define MX6SX_BM_DPDM_WAKEUP_EN BIT(29) #define MX6_BM_WAKEUP_INTR BIT(31) + +#define MX6_USB_HSIC_CTRL_OFFSET 0x10 +/* Send resume signal without 480Mhz PHY clock */ +#define MX6SX_BM_HSIC_AUTO_RESUME BIT(23) +/* set before portsc.suspendM = 1 */ +#define MX6_BM_HSIC_DEV_CONN BIT(21) +/* HSIC enable */ +#define MX6_BM_HSIC_EN BIT(12) +/* Force HSIC module 480M clock on, even when in Host is in suspend mode */ +#define MX6_BM_HSIC_CLK_ON BIT(11) + #define MX6_USB_OTG1_PHY_CTRL 0x18 /* For imx6dql, it is host-only controller, for later imx6, it is otg's */ #define MX6_USB_OTG2_PHY_CTRL 0x1c @@ -71,6 +84,10 @@ #define VF610_OVER_CUR_DIS BIT(7) +#define ANADIG_ANA_MISC0 0x150 +#define ANADIG_ANA_MISC0_SET 0x154 +#define ANADIG_ANA_MISC0_CLK_DELAY(x) ((x >> 26) & 0x7) + struct usbmisc_ops { /* It's called once when probe a usb device */ int (*init)(struct imx_usbmisc_data *data); @@ -80,6 +97,10 @@ struct usbmisc_ops { int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled); /* It's called when system resume from usb power lost */ int (*power_lost_check)(struct imx_usbmisc_data *data); + /* It's called before setting portsc.suspendM */ + int (*hsic_set_connect)(struct imx_usbmisc_data *data); + /* It's called during suspend/resume */ + int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled); }; struct imx_usbmisc { @@ -219,6 +240,49 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data) return 0; } +static int usbmisc_imx6_hsic_set_connect(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 val; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->index == 2 || data->index == 3) { + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + if (!(val & MX6_BM_HSIC_DEV_CONN)) + writel(val | MX6_BM_HSIC_DEV_CONN, + usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static int usbmisc_imx6_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +{ + unsigned long flags; + u32 val; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->index == 2 || data->index == 3) { + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + if (on) + val |= MX6_BM_HSIC_CLK_ON; + else + val &= ~MX6_BM_HSIC_CLK_ON; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + static int usbmisc_imx6q_set_wakeup (struct imx_usbmisc_data *data, bool enabled) { @@ -296,6 +360,31 @@ static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data) spin_unlock_irqrestore(&usbmisc->lock, flags); } + /* For HSIC controller */ + if (data->index == 2) { + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + data->index * 4); + writel(val | MX6_BM_UTMI_ON_CLOCK, + usbmisc->base + data->index * 4); + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON | + MX6SX_BM_HSIC_AUTO_RESUME; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + /* + * Need to add delay to wait 24M OSC to be stable, + * it's board specific. + */ + regmap_read(data->anatop, ANADIG_ANA_MISC0, &val); + /* 0 <= data->osc_clkgate_delay <= 7 */ + if (data->osc_clkgate_delay > ANADIG_ANA_MISC0_CLK_DELAY(val)) + regmap_write(data->anatop, ANADIG_ANA_MISC0_SET, + (data->osc_clkgate_delay) << 26); + } + return ret; } @@ -354,6 +443,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, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, }; static const struct usbmisc_ops vf610_usbmisc_ops = { @@ -364,6 +455,8 @@ static const struct usbmisc_ops imx6sx_usbmisc_ops = { .set_wakeup = usbmisc_imx6q_set_wakeup, .init = usbmisc_imx6sx_init, .power_lost_check = usbmisc_imx6sx_power_lost_check, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, }; int imx_usbmisc_init(struct imx_usbmisc_data *data) @@ -422,6 +515,34 @@ int imx_usbmisc_power_lost_check(struct imx_usbmisc_data *data) } EXPORT_SYMBOL_GPL(imx_usbmisc_power_lost_check); +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->hsic_set_connect) + return 0; + return usbmisc->ops->hsic_set_connect(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect); + +int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->hsic_set_clk) + return 0; + return usbmisc->ops->hsic_set_clk(data, on); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk); + static const struct of_device_id usbmisc_imx_dt_ids[] = { { .compatible = "fsl,imx25-usbmisc", diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 7eefb513454d..d995f1e7d4ef 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -29,12 +29,15 @@ struct ci_hdrc_platform_data { #define CI_HDRC_IMX28_WRITE_FIX BIT(5) #define CI_HDRC_FORCE_FULLSPEED BIT(6) #define CI_HDRC_IMX_EHCI_QUIRK BIT(7) +#define CI_HDRC_IMX_IS_HSIC BIT(8) enum usb_dr_mode dr_mode; #define CI_HDRC_CONTROLLER_RESET_EVENT 0 #define CI_HDRC_CONTROLLER_STOPPED_EVENT 1 #define CI_HDRC_CONTROLLER_VBUS_EVENT 2 #define CI_HDRC_NOTIFY_RET_DEFER_EVENT 3 #define CI_HDRC_CONTROLLER_CHARGER_POST_EVENT 4 +#define CI_HDRC_IMX_HSIC_ACTIVE_EVENT 5 +#define CI_HDRC_IMX_HSIC_SUSPEND_EVENT 6 int (*notify_event)(struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; bool tpl_support; |