diff options
Diffstat (limited to 'drivers/usb/phy')
-rw-r--r-- | drivers/usb/phy/phy-fsl-usb.c | 35 | ||||
-rw-r--r-- | drivers/usb/phy/phy-generic.c | 150 | ||||
-rw-r--r-- | drivers/usb/phy/phy-generic.h | 10 | ||||
-rw-r--r-- | drivers/usb/phy/phy-mxs-usb.c | 86 |
4 files changed, 199 insertions, 82 deletions
diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index ab38aa32a6c1..94eb2923afed 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -107,19 +107,6 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p); #define fsl_writel(val, addr) writel(val, addr) #endif /* CONFIG_PPC32 */ -/* Routines to access transceiver ULPI registers */ -u8 view_ulpi(u8 addr) -{ - u32 temp; - - temp = 0x40000000 | (addr << 16); - fsl_writel(temp, &usb_dr_regs->ulpiview); - udelay(1000); - while (temp & 0x40) - temp = fsl_readl(&usb_dr_regs->ulpiview); - return (le32_to_cpu(temp) & 0x0000ff00) >> 8; -} - int write_ulpi(u8 addr, u8 data) { u32 temp; @@ -460,28 +447,6 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) fsl_otg_del_timer(fsm, timer); } -/* - * Reduce timer count by 1, and find timeout conditions. - * Called by fsl_otg 1ms timer interrupt - */ -int fsl_otg_tick_timer(void) -{ - struct fsl_otg_timer *tmp_timer, *del_tmp; - int expired = 0; - - list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { - tmp_timer->count--; - /* check if timer expires */ - if (!tmp_timer->count) { - list_del(&tmp_timer->list); - tmp_timer->function(tmp_timer->data); - expired = 1; - } - } - - return expired; -} - /* Reset controller, not reset the bus */ void otg_reset_controller(void) { diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index f1b719b45a53..70be50b734b2 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -27,6 +27,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> +#include <linux/usb/gadget.h> #include <linux/usb/otg.h> #include <linux/usb/usb_phy_generic.h> #include <linux/slab.h> @@ -39,6 +40,10 @@ #include "phy-generic.h" +#define VBUS_IRQ_FLAGS \ + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \ + IRQF_ONESHOT) + struct platform_device *usb_phy_generic_register(void) { return platform_device_register_simple("usb_phy_generic", @@ -59,19 +64,79 @@ static int nop_set_suspend(struct usb_phy *x, int suspend) static void nop_reset_set(struct usb_phy_generic *nop, int asserted) { - int value; + if (!nop->gpiod_reset) + return; + + gpiod_direction_output(nop->gpiod_reset, !asserted); + usleep_range(10000, 20000); + gpiod_set_value(nop->gpiod_reset, asserted); +} + +/* interface to regulator framework */ +static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA) +{ + struct regulator *vbus_draw = nop->vbus_draw; + int enabled; + int ret; - if (!gpio_is_valid(nop->gpio_reset)) + if (!vbus_draw) return; - value = asserted; - if (nop->reset_active_low) - value = !value; + enabled = nop->vbus_draw_enabled; + if (mA) { + regulator_set_current_limit(vbus_draw, 0, 1000 * mA); + if (!enabled) { + ret = regulator_enable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 1; + } + } else { + if (enabled) { + ret = regulator_disable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 0; + } + } + nop->mA = mA; +} + + +static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) +{ + struct usb_phy_generic *nop = data; + struct usb_otg *otg = nop->phy.otg; + int vbus, status; + + vbus = gpiod_get_value(nop->gpiod_vbus); + if ((vbus ^ nop->vbus) == 0) + return IRQ_HANDLED; + nop->vbus = vbus; + + if (vbus) { + status = USB_EVENT_VBUS; + otg->state = OTG_STATE_B_PERIPHERAL; + nop->phy.last_event = status; + usb_gadget_vbus_connect(otg->gadget); + + /* drawing a "unit load" is *always* OK, except for OTG */ + nop_set_vbus_draw(nop, 100); + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } else { + nop_set_vbus_draw(nop, 0); - gpio_set_value_cansleep(nop->gpio_reset, value); + usb_gadget_vbus_disconnect(otg->gadget); + status = USB_EVENT_NONE; + otg->state = OTG_STATE_B_IDLE; + nop->phy.last_event = status; - if (!asserted) - usleep_range(10000, 20000); + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } + return IRQ_HANDLED; } int usb_gen_phy_init(struct usb_phy *phy) @@ -143,36 +208,47 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, struct usb_phy_generic_platform_data *pdata) { enum usb_phy_type type = USB_PHY_TYPE_USB2; - int err; + int err = 0; u32 clk_rate = 0; bool needs_vcc = false; - nop->reset_active_low = true; /* default behaviour */ - if (dev->of_node) { struct device_node *node = dev->of_node; - enum of_gpio_flags flags = 0; if (of_property_read_u32(node, "clock-frequency", &clk_rate)) clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpio_reset = of_get_named_gpio_flags(node, "reset-gpios", - 0, &flags); - if (nop->gpio_reset == -EPROBE_DEFER) - return -EPROBE_DEFER; - - nop->reset_active_low = flags & OF_GPIO_ACTIVE_LOW; - + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset"); + err = PTR_ERR_OR_ZERO(nop->gpiod_reset); + if (!err) { + nop->gpiod_vbus = devm_gpiod_get_optional(dev, + "vbus-detect"); + err = PTR_ERR_OR_ZERO(nop->gpiod_vbus); + } } else if (pdata) { type = pdata->type; clk_rate = pdata->clk_rate; needs_vcc = pdata->needs_vcc; - nop->gpio_reset = pdata->gpio_reset; - } else { - nop->gpio_reset = -1; + if (gpio_is_valid(pdata->gpio_reset)) { + err = devm_gpio_request_one(dev, pdata->gpio_reset, 0, + dev_name(dev)); + if (!err) + nop->gpiod_reset = + gpio_to_desc(pdata->gpio_reset); + } + nop->gpiod_vbus = pdata->gpiod_vbus; + } + + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (err) { + dev_err(dev, "Error requesting RESET or VBUS GPIO\n"); + return err; } + if (nop->gpiod_reset) + gpiod_direction_output(nop->gpiod_reset, 1); nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), GFP_KERNEL); @@ -201,24 +277,6 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, return -EPROBE_DEFER; } - if (gpio_is_valid(nop->gpio_reset)) { - unsigned long gpio_flags; - - /* Assert RESET */ - if (nop->reset_active_low) - gpio_flags = GPIOF_OUT_INIT_LOW; - else - gpio_flags = GPIOF_OUT_INIT_HIGH; - - err = devm_gpio_request_one(dev, nop->gpio_reset, - gpio_flags, dev_name(dev)); - if (err) { - dev_err(dev, "Error requesting RESET GPIO %d\n", - nop->gpio_reset); - return err; - } - } - nop->dev = dev; nop->phy.dev = nop->dev; nop->phy.label = "nop-xceiv"; @@ -247,6 +305,18 @@ static int usb_phy_generic_probe(struct platform_device *pdev) err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev)); if (err) return err; + if (nop->gpiod_vbus) { + err = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(nop->gpiod_vbus), + NULL, nop_gpio_vbus_thread, + VBUS_IRQ_FLAGS, "vbus_detect", + nop); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + gpiod_to_irq(nop->gpiod_vbus), err); + return err; + } + } nop->phy.init = usb_gen_phy_init; nop->phy.shutdown = usb_gen_phy_shutdown; diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h index d8feacc0b7fb..0d0eadd54ed9 100644 --- a/drivers/usb/phy/phy-generic.h +++ b/drivers/usb/phy/phy-generic.h @@ -2,14 +2,20 @@ #define _PHY_GENERIC_H_ #include <linux/usb/usb_phy_generic.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> struct usb_phy_generic { struct usb_phy phy; struct device *dev; struct clk *clk; struct regulator *vcc; - int gpio_reset; - bool reset_active_low; + struct gpio_desc *gpiod_reset; + struct gpio_desc *gpiod_vbus; + struct regulator *vbus_draw; + bool vbus_draw_enabled; + unsigned long mA; + unsigned int vbus; }; int usb_gen_phy_init(struct usb_phy *phy); diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index b9589f663683..8f7cb068d29b 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -40,6 +40,7 @@ #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27) #define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) #define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) #define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23) @@ -69,6 +70,9 @@ #define ANADIG_USB2_LOOPBACK_SET 0x244 #define ANADIG_USB2_LOOPBACK_CLR 0x248 +#define ANADIG_USB1_MISC 0x1f0 +#define ANADIG_USB2_MISC 0x250 + #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12) #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11) @@ -80,6 +84,11 @@ #define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2) #define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5) +#define BM_ANADIG_USB1_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB1_MISC_RX_VMIN_FS BIT(28) +#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -131,8 +140,7 @@ static const struct mxs_phy_data vf610_phy_data = { }; static const struct mxs_phy_data imx6sx_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, }; static const struct of_device_id mxs_phy_dt_ids[] = { @@ -256,6 +264,18 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) usleep_range(500, 1000); } +static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 phyctrl = readl(base + HW_USBPHY_CTRL); + + if (IS_ENABLED(CONFIG_USB_OTG) && + !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE)) + return true; + + return false; +} + static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; @@ -270,7 +290,7 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && !vbus_is_on) + if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); @@ -293,6 +313,17 @@ static int mxs_phy_init(struct usb_phy *phy) static void mxs_phy_shutdown(struct usb_phy *phy) { struct mxs_phy *mxs_phy = to_mxs_phy(phy); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP | + BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL; + + writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR); + writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD); writel(BM_USBPHY_CTRL_CLKGATE, phy->io_priv + HW_USBPHY_CTRL_SET); @@ -300,13 +331,56 @@ static void mxs_phy_shutdown(struct usb_phy *phy) clk_disable_unprepare(mxs_phy->clk); } +static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy) +{ + unsigned int line_state; + /* bit definition is the same for all controllers */ + unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS, + dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS; + unsigned int reg = ANADIG_USB1_MISC; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return false; + + if (mxs_phy->port_id == 0) + reg = ANADIG_USB1_MISC; + else if (mxs_phy->port_id == 1) + reg = ANADIG_USB2_MISC; + + regmap_read(mxs_phy->regmap_anatop, reg, &line_state); + + if ((line_state & (dp_bit | dm_bit)) == dm_bit) + return true; + else + return false; +} + static int mxs_phy_suspend(struct usb_phy *x, int suspend) { int ret; struct mxs_phy *mxs_phy = to_mxs_phy(x); + bool low_speed_connection, vbus_is_on; + + low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy); + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); if (suspend) { - writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + /* + * FIXME: Do not power down RXPWD1PT1 bit for low speed + * connect. The low speed connection will have problem at + * very rare cases during usb suspend and resume process. + */ + if (low_speed_connection & vbus_is_on) { + /* + * If value to be set as pwd value is not 0xffffffff, + * several 32Khz cycles are needed. + */ + mxs_phy_clock_switch_delay(); + writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD); + } else { + writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + } writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); @@ -359,7 +433,9 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy, dev_dbg(phy->dev, "%s device has disconnected\n", (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); - if (speed == USB_SPEED_HIGH) + /* Sometimes, the speed is not high speed when the error occurs */ + if (readl(phy->io_priv + HW_USBPHY_CTRL) & + BM_USBPHY_CTRL_ENHOSTDISCONDETECT) writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, phy->io_priv + HW_USBPHY_CTRL_CLR); |