summaryrefslogtreecommitdiffstats
path: root/drivers/usb/chipidea/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/core.c')
-rw-r--r--drivers/usb/chipidea/core.c197
1 files changed, 134 insertions, 63 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index a5df24c578f..94626409559 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -65,12 +65,14 @@
#include <linux/usb/chipidea.h>
#include <linux/usb/of.h>
#include <linux/phy.h>
+#include <linux/regulator/consumer.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "host.h"
#include "debug.h"
+#include "otg.h"
/* Controller register map */
static uintptr_t ci_regs_nolpm[] = {
@@ -197,6 +199,12 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
if (ci->hw_ep_max > ENDPT_MAX)
return -ENODEV;
+ /* Disable all interrupts bits */
+ hw_write(ci, OP_USBINTR, 0xffffffff, 0);
+
+ /* Clear all interrupts status bits*/
+ hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff);
+
dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n",
ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op);
@@ -264,8 +272,6 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode)
while (hw_read(ci, OP_USBCMD, USBCMD_RST))
udelay(10); /* not RTOS friendly */
- hw_phymode_configure(ci);
-
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_RESET_EVENT);
@@ -289,37 +295,35 @@ int hw_device_reset(struct ci_hdrc *ci, u32 mode)
}
/**
- * ci_otg_role - pick role based on ID pin state
+ * hw_wait_reg: wait the register value
+ *
+ * Sometimes, it needs to wait register value before going on.
+ * Eg, when switch to device mode, the vbus value should be lower
+ * than OTGSC_BSV before connects to host.
+ *
* @ci: the controller
+ * @reg: register index
+ * @mask: mast bit
+ * @value: the bit value to wait
+ * @timeout_ms: timeout in millisecond
+ *
+ * This function returns an error code if timeout
*/
-static enum ci_role ci_otg_role(struct ci_hdrc *ci)
-{
- u32 sts = hw_read(ci, OP_OTGSC, ~0);
- enum ci_role role = sts & OTGSC_ID
- ? CI_ROLE_GADGET
- : CI_ROLE_HOST;
-
- return role;
-}
-
-/**
- * ci_role_work - perform role changing based on ID pin
- * @work: work struct
- */
-static void ci_role_work(struct work_struct *work)
+int hw_wait_reg(struct ci_hdrc *ci, enum ci_hw_regs reg, u32 mask,
+ u32 value, unsigned int timeout_ms)
{
- struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
- enum ci_role role = ci_otg_role(ci);
-
- if (role != ci->role) {
- dev_dbg(ci->dev, "switching from %s to %s\n",
- ci_role(ci)->name, ci->roles[role]->name);
-
- ci_role_stop(ci);
- ci_role_start(ci, role);
+ unsigned long elapse = jiffies + msecs_to_jiffies(timeout_ms);
+
+ while (hw_read(ci, reg, mask) != value) {
+ if (time_after(jiffies, elapse)) {
+ dev_err(ci->dev, "timeout waiting for %08x in %d\n",
+ mask, reg);
+ return -ETIMEDOUT;
+ }
+ msleep(20);
}
- enable_irq(ci->irq);
+ return 0;
}
static irqreturn_t ci_irq(int irq, void *data)
@@ -331,19 +335,55 @@ static irqreturn_t ci_irq(int irq, void *data)
if (ci->is_otg)
otgsc = hw_read(ci, OP_OTGSC, ~0);
- if (ci->role != CI_ROLE_END)
- ret = ci_role(ci)->irq(ci);
+ /*
+ * Handle id change interrupt, it indicates device/host function
+ * switch.
+ */
+ if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {
+ ci->id_event = true;
+ ci_clear_otg_interrupt(ci, OTGSC_IDIS);
+ disable_irq_nosync(ci->irq);
+ queue_work(ci->wq, &ci->work);
+ return IRQ_HANDLED;
+ }
- if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
- hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
+ /*
+ * Handle vbus change interrupt, it indicates device connection
+ * and disconnection events.
+ */
+ if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
+ ci->b_sess_valid_event = true;
+ ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
disable_irq_nosync(ci->irq);
queue_work(ci->wq, &ci->work);
- ret = IRQ_HANDLED;
+ return IRQ_HANDLED;
}
+ /* Handle device/host interrupt */
+ if (ci->role != CI_ROLE_END)
+ ret = ci_role(ci)->irq(ci);
+
return ret;
}
+static int ci_get_platdata(struct device *dev,
+ struct ci_hdrc_platform_data *platdata)
+{
+ /* Get the vbus regulator */
+ platdata->reg_vbus = devm_regulator_get(dev, "vbus");
+ if (PTR_ERR(platdata->reg_vbus) == -EPROBE_DEFER) {
+ return -EPROBE_DEFER;
+ } else if (PTR_ERR(platdata->reg_vbus) == -ENODEV) {
+ platdata->reg_vbus = NULL; /* no vbus regualator is needed */
+ } else if (IS_ERR(platdata->reg_vbus)) {
+ dev_err(dev, "Getting regulator error: %ld\n",
+ PTR_ERR(platdata->reg_vbus));
+ return PTR_ERR(platdata->reg_vbus);
+ }
+
+ return 0;
+}
+
static DEFINE_IDA(ci_ida);
struct platform_device *ci_hdrc_add_device(struct device *dev,
@@ -353,6 +393,10 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
struct platform_device *pdev;
int id, ret;
+ ret = ci_get_platdata(dev, platdata);
+ if (ret)
+ return ERR_PTR(ret);
+
id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL);
if (id < 0)
return ERR_PTR(id);
@@ -398,6 +442,29 @@ void ci_hdrc_remove_device(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(ci_hdrc_remove_device);
+static inline void ci_role_destroy(struct ci_hdrc *ci)
+{
+ ci_hdrc_gadget_destroy(ci);
+ ci_hdrc_host_destroy(ci);
+ if (ci->is_otg)
+ ci_hdrc_otg_destroy(ci);
+}
+
+static void ci_get_otg_capable(struct ci_hdrc *ci)
+{
+ if (ci->platdata->flags & CI_HDRC_DUAL_ROLE_NOT_OTG)
+ ci->is_otg = false;
+ else
+ ci->is_otg = (hw_read(ci, CAP_DCCPARAMS,
+ DCCPARAMS_DC | DCCPARAMS_HC)
+ == (DCCPARAMS_DC | DCCPARAMS_HC));
+ if (ci->is_otg) {
+ dev_dbg(ci->dev, "It is OTG capable controller\n");
+ ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS);
+ ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS);
+ }
+}
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -406,15 +473,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
void __iomem *base;
int ret;
enum usb_dr_mode dr_mode;
+ struct device_node *of_node = dev->of_node ?: dev->parent->of_node;
if (!dev->platform_data) {
dev_err(dev, "platform data missing\n");
return -ENODEV;
}
- if (!dev->of_node && dev->parent)
- dev->of_node = dev->parent->of_node;
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
@@ -447,18 +512,15 @@ static int ci_hdrc_probe(struct platform_device *pdev)
return -ENODEV;
}
- INIT_WORK(&ci->work, ci_role_work);
- ci->wq = create_singlethread_workqueue("ci_otg");
- if (!ci->wq) {
- dev_err(dev, "can't create workqueue\n");
- return -ENODEV;
- }
+ ci_get_otg_capable(ci);
if (!ci->platdata->phy_mode)
- ci->platdata->phy_mode = of_usb_get_phy_mode(dev->of_node);
+ ci->platdata->phy_mode = of_usb_get_phy_mode(of_node);
+
+ hw_phymode_configure(ci);
if (!ci->platdata->dr_mode)
- ci->platdata->dr_mode = of_usb_get_dr_mode(dev->of_node);
+ ci->platdata->dr_mode = of_usb_get_dr_mode(of_node);
if (ci->platdata->dr_mode == USB_DR_MODE_UNKNOWN)
ci->platdata->dr_mode = USB_DR_MODE_OTG;
@@ -479,15 +541,34 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) {
dev_err(dev, "no supported roles\n");
- ret = -ENODEV;
- goto rm_wq;
+ return -ENODEV;
+ }
+
+ if (ci->is_otg) {
+ ret = ci_hdrc_otg_init(ci);
+ if (ret) {
+ dev_err(dev, "init otg fails, ret = %d\n", ret);
+ goto stop;
+ }
}
if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
- ci->is_otg = true;
- /* ID pin needs 1ms debouce time, we delay 2ms for safe */
- mdelay(2);
- ci->role = ci_otg_role(ci);
+ if (ci->is_otg) {
+ /*
+ * ID pin needs 1ms debouce time,
+ * we delay 2ms for safe.
+ */
+ mdelay(2);
+ ci->role = ci_otg_role(ci);
+ ci_enable_otg_interrupt(ci, OTGSC_IDIE);
+ } else {
+ /*
+ * If the controller is not OTG capable, but support
+ * role switch, the defalt role is gadget, and the
+ * user can switch it through debugfs.
+ */
+ ci->role = CI_ROLE_GADGET;
+ }
} else {
ci->role = ci->roles[CI_ROLE_HOST]
? CI_ROLE_HOST
@@ -497,8 +578,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
- ret = -ENODEV;
- goto rm_wq;
+ goto stop;
}
platform_set_drvdata(pdev, ci);
@@ -507,19 +587,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret)
goto stop;
- if (ci->is_otg)
- hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE);
-
ret = dbg_create_files(ci);
if (!ret)
return 0;
free_irq(ci->irq, ci);
stop:
- ci_role_stop(ci);
-rm_wq:
- flush_workqueue(ci->wq);
- destroy_workqueue(ci->wq);
+ ci_role_destroy(ci);
return ret;
}
@@ -529,10 +603,8 @@ static int ci_hdrc_remove(struct platform_device *pdev)
struct ci_hdrc *ci = platform_get_drvdata(pdev);
dbg_remove_files(ci);
- flush_workqueue(ci->wq);
- destroy_workqueue(ci->wq);
free_irq(ci->irq, ci);
- ci_role_stop(ci);
+ ci_role_destroy(ci);
return 0;
}
@@ -548,7 +620,6 @@ static struct platform_driver ci_hdrc_driver = {
module_platform_driver(ci_hdrc_driver);
MODULE_ALIAS("platform:ci_hdrc");
-MODULE_ALIAS("platform:ci13xxx");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Lopo <dlopo@chipidea.mips.com>");
MODULE_DESCRIPTION("ChipIdea HDRC Driver");