diff --git a/drivers/phy/phy-grx500-usb.c b/drivers/phy/phy-grx500-usb.c index 9404f439130490c74809b16c4be87c196c08b8f7..8f3a21e2639602547cc3596336e9d8df9db9ffd2 100644 --- a/drivers/phy/phy-grx500-usb.c +++ b/drivers/phy/phy-grx500-usb.c @@ -15,9 +15,22 @@ #include <linux/of_address.h> #include <linux/of_device.h> #include <linux/clk.h> +#include <linux/interrupt.h> #include <linux/regulator/consumer.h> +#include <linux/pinctrl/consumer.h> +#include <linux/workqueue.h> #include <linux/reset.h> +#define SECOND_TIME_DELAY 30 + +struct ltq_dwc3_phy; + +struct ltq_oc_ops { + int (*enable)(struct ltq_dwc3_phy *); + int (*disable)(struct ltq_dwc3_phy *); + int (*init)(struct ltq_dwc3_phy *); +}; + struct ltq_dwc3_phy_cfg { u8 tx_invert_ovrd; u8 rx_invert_ovrd; @@ -37,6 +50,13 @@ struct ltq_dwc3_phy { const struct ltq_dwc3_phy_cfg *cfg; bool is_tx_pol_inv; bool is_rx_pol_inv; + + /* OCP */ + bool is_enable_oc; + unsigned int irq; + struct workqueue_struct *workq; + struct delayed_work oc_recover_w; + const struct ltq_oc_ops *oc_ops; }; static const struct ltq_dwc3_phy_cfg grx500_phy_cfg = { @@ -46,6 +66,103 @@ static const struct ltq_dwc3_phy_cfg grx500_phy_cfg = { .rx_ovrd_offset = 0x4014, }; +static int ltq_grx500_oc_enable(struct ltq_dwc3_phy *dwc3_phy) +{ + struct pinctrl *p; + + p = devm_pinctrl_get_select( + dwc3_phy->dev, PINCTRL_STATE_IDLE); + if (IS_ERR(p)) + return PTR_ERR(p); + msleep(50); + + return 0; +} + +static int ltq_grx500_oc_disable(struct ltq_dwc3_phy *dwc3_phy) +{ + struct pinctrl *p; + + p = devm_pinctrl_get_select( + dwc3_phy->dev, PINCTRL_STATE_DEFAULT); + if (IS_ERR(p)) + return PTR_ERR(p); + msleep(50); + + return 0; +} + +void ltq_oc_recovery_worker(struct work_struct *work) +{ + struct ltq_dwc3_phy *dwc3_phy = container_of(work, + struct ltq_dwc3_phy, oc_recover_w.work); + + dev_info(dwc3_phy->dev, "Turn on the power again to recover\n"); + + /* Restart OCP to recover */ + dwc3_phy->oc_ops->disable(dwc3_phy); + dwc3_phy->oc_ops->enable(dwc3_phy); + + irq_set_irq_type(dwc3_phy->irq, IRQ_TYPE_LEVEL_LOW); +} + +static irqreturn_t ltq_oc_irq(int irq, void *_dwc3_phy) +{ + struct ltq_dwc3_phy *dwc3_phy = _dwc3_phy; + + dev_info(dwc3_phy->dev, "USB OC protection is triggered\n"); + + irq_set_irq_type(irq, IRQ_TYPE_EDGE_FALLING); + queue_delayed_work(dwc3_phy->workq, + &dwc3_phy->oc_recover_w, SECOND_TIME_DELAY*HZ); + + return IRQ_HANDLED; +} + +static int ltq_grx500_oc_init(struct ltq_dwc3_phy *dwc3_phy) +{ + int ret; + struct device *dev = dwc3_phy->dev; + struct platform_device *pdev = to_platform_device(dev); + + dwc3_phy->irq = platform_get_irq(pdev, 0); + if (dwc3_phy->irq < 0) { + dev_err(dev, "missing IRQ resource\n"); + return -EINVAL; + } + + dwc3_phy->workq = create_singlethread_workqueue("usb ocp wq"); + if (!dwc3_phy->workq) { + dev_err(dev, "create_singlethread_workqueue() failed\n"); + return -ENOMEM; + } + INIT_DELAYED_WORK(&dwc3_phy->oc_recover_w, ltq_oc_recovery_worker); + + if (dwc3_phy->vbus_reg) { + ret = regulator_enable(dwc3_phy->vbus_reg); + if (ret) { + dev_err(dev, "regulator enable failed\n"); + return ret; + } + } + + ret = devm_request_irq(dev, dwc3_phy->irq, <q_oc_irq, + IRQ_TYPE_LEVEL_LOW, "usb ocp", dwc3_phy); + if (ret) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + dwc3_phy->irq, ret); + return ret; + } + + return 0; +} + +static const struct ltq_oc_ops grx500_ltq_oc_ops = { + .enable = ltq_grx500_oc_enable, + .disable = ltq_grx500_oc_disable, + .init = ltq_grx500_oc_init, +}; + static inline u32 ltq_dwc3_phy_readl(void __iomem *base, u32 offset) { return readl(base + offset); @@ -108,6 +225,9 @@ static int ltq_dwc3_phy_power_on(struct phy *phy) } } + if (dwc3_phy->is_enable_oc) + dwc3_phy->oc_ops->enable(dwc3_phy); + return 0; } @@ -115,6 +235,9 @@ static int ltq_dwc3_phy_power_off(struct phy *phy) { struct ltq_dwc3_phy *dwc3_phy = phy_get_drvdata(phy); + if (dwc3_phy->is_enable_oc) + dwc3_phy->oc_ops->disable(dwc3_phy); + if (dwc3_phy->vbus_reg) regulator_disable(dwc3_phy->vbus_reg); @@ -151,6 +274,8 @@ static int ltq_usb_read_dt(struct ltq_dwc3_phy *dwc3_phy, "invert-rx-polarity"); dwc3_phy->is_tx_pol_inv = of_property_read_bool(dev->of_node, "invert-tx-polarity"); + dwc3_phy->is_enable_oc = of_property_read_bool(dev->of_node, + "enable-over-current"); return 0; } @@ -195,6 +320,8 @@ static int ltq_dwc3_phy_probe(struct platform_device *pdev) dwc3_phy->base = base; dwc3_phy->dev = dev; + if (dwc3_phy->is_enable_oc) + dwc3_phy->oc_ops = &grx500_ltq_oc_ops; platform_set_drvdata(pdev, dwc3_phy); @@ -206,6 +333,9 @@ static int ltq_dwc3_phy_probe(struct platform_device *pdev) phy_set_drvdata(dwc3_phy->phy, dwc3_phy); + if (dwc3_phy->is_enable_oc) + dwc3_phy->oc_ops->init(dwc3_phy); + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); return PTR_ERR_OR_ZERO(provider);