summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r--drivers/usb/chipidea/Makefile1
-rw-r--r--drivers/usb/chipidea/bits.h10
-rw-r--r--drivers/usb/chipidea/ci.h23
-rw-r--r--drivers/usb/chipidea/ci_hdrc_msm.c24
-rw-r--r--drivers/usb/chipidea/core.c92
-rw-r--r--drivers/usb/chipidea/debug.c135
-rw-r--r--drivers/usb/chipidea/host.c21
-rw-r--r--drivers/usb/chipidea/otg.c48
-rw-r--r--drivers/usb/chipidea/otg.h23
-rw-r--r--drivers/usb/chipidea/otg_fsm.c842
-rw-r--r--drivers/usb/chipidea/otg_fsm.h129
-rw-r--r--drivers/usb/chipidea/udc.c80
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c68
13 files changed, 1398 insertions, 98 deletions
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index 480bd4d5710a..2f099c7df7b5 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -6,6 +6,7 @@ ci_hdrc-y := core.o otg.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o
ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o
+ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o
# Glue/Bridge layers go here
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index 83d06c1455b7..ca57e3dcd3d5 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -44,9 +44,14 @@
#define DEVICEADDR_USBADR (0x7FUL << 25)
/* PORTSC */
+#define PORTSC_CCS BIT(0)
+#define PORTSC_CSC BIT(1)
+#define PORTSC_PEC BIT(3)
+#define PORTSC_OCC BIT(5)
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
+#define PORTSC_PP BIT(12)
#define PORTSC_PTC (0x0FUL << 16)
#define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23))
/* PTS and PTW for non lpm version only */
@@ -56,6 +61,9 @@
#define PORTSC_PTW BIT(28)
#define PORTSC_STS BIT(29)
+#define PORTSC_W1C_BITS \
+ (PORTSC_CSC | PORTSC_PEC | PORTSC_OCC)
+
/* DEVLC */
#define DEVLC_PFSC BIT(23)
#define DEVLC_PSPD (0x03UL << 25)
@@ -72,6 +80,8 @@
/* OTGSC */
#define OTGSC_IDPU BIT(5)
+#define OTGSC_HADP BIT(6)
+#define OTGSC_HABA BIT(7)
#define OTGSC_ID BIT(8)
#define OTGSC_AVV BIT(9)
#define OTGSC_ASV BIT(10)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index e206406ae1d9..9563cb56d564 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -17,6 +17,7 @@
#include <linux/irqreturn.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/otg-fsm.h>
/******************************************************************************
* DEFINE
@@ -139,6 +140,8 @@ struct hw_bank {
* @roles: array of supported roles for this controller
* @role: current role
* @is_otg: if the device is otg-capable
+ * @fsm: otg finite state machine
+ * @fsm_timer: pointer to timer list of otg fsm
* @work: work for role changing
* @wq: workqueue thread
* @qh_pool: allocation pool for queue heads
@@ -174,6 +177,8 @@ struct ci_hdrc {
struct ci_role_driver *roles[CI_ROLE_END];
enum ci_role role;
bool is_otg;
+ struct otg_fsm fsm;
+ struct ci_otg_fsm_timer_list *fsm_timer;
struct work_struct work;
struct workqueue_struct *wq;
@@ -319,6 +324,24 @@ static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg,
return (val & mask) >> __ffs(mask);
}
+/**
+ * ci_otg_is_fsm_mode: runtime check if otg controller
+ * is in otg fsm mode.
+ */
+static inline bool ci_otg_is_fsm_mode(struct ci_hdrc *ci)
+{
+#ifdef CONFIG_USB_OTG_FSM
+ return ci->is_otg && ci->roles[CI_ROLE_HOST] &&
+ ci->roles[CI_ROLE_GADGET];
+#else
+ return false;
+#endif
+}
+
+u32 hw_read_intr_enable(struct ci_hdrc *ci);
+
+u32 hw_read_intr_status(struct ci_hdrc *ci);
+
int hw_device_reset(struct ci_hdrc *ci, u32 mode);
int hw_port_test_set(struct ci_hdrc *ci, u8 mode);
diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c
index 2d51d852b474..d72b9d2de2c5 100644
--- a/drivers/usb/chipidea/ci_hdrc_msm.c
+++ b/drivers/usb/chipidea/ci_hdrc_msm.c
@@ -47,6 +47,7 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
.name = "ci_hdrc_msm",
+ .capoffset = DEF_CAPOFFSET,
.flags = CI_HDRC_REGS_SHARED |
CI_HDRC_REQUIRE_TRANSCEIVER |
CI_HDRC_DISABLE_STREAMING,
@@ -57,9 +58,21 @@ static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
static int ci_hdrc_msm_probe(struct platform_device *pdev)
{
struct platform_device *plat_ci;
+ struct usb_phy *phy;
dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n");
+ /*
+ * OTG(PHY) driver takes care of PHY initialization, clock management,
+ * powering up VBUS, mapping of registers address space and power
+ * management.
+ */
+ phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ ci_hdrc_msm_platdata.phy = phy;
+
plat_ci = ci_hdrc_add_device(&pdev->dev,
pdev->resource, pdev->num_resources,
&ci_hdrc_msm_platdata);
@@ -86,10 +99,19 @@ static int ci_hdrc_msm_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id msm_ci_dt_match[] = {
+ { .compatible = "qcom,ci-hdrc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, msm_ci_dt_match);
+
static struct platform_driver ci_hdrc_msm_driver = {
.probe = ci_hdrc_msm_probe,
.remove = ci_hdrc_msm_remove,
- .driver = { .name = "msm_hsusb", },
+ .driver = {
+ .name = "msm_hsusb",
+ .of_match_table = msm_ci_dt_match,
+ },
};
module_platform_driver(ci_hdrc_msm_driver);
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 1cd5d0ba587c..619d13e29995 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -23,7 +23,7 @@
* - BUS: bus glue code, bus abstraction layer
*
* Compile Options
- * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities
+ * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities
* - STALL_IN: non-empty bulk-in pipes cannot be halted
* if defined mass storage compliance succeeds but with warnings
* => case 4: Hi > Dn
@@ -42,10 +42,6 @@
* - Not Supported: 15 & 16 (ISO)
*
* TODO List
- * - OTG
- * - Interrupt Traffic
- * - GET_STATUS(device) - always reports 0
- * - Gadget API (majority of optional features)
* - Suspend & Remote Wakeup
*/
#include <linux/delay.h>
@@ -74,6 +70,7 @@
#include "host.h"
#include "debug.h"
#include "otg.h"
+#include "otg_fsm.h"
/* Controller register map */
static const u8 ci_regs_nolpm[] = {
@@ -140,6 +137,26 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
}
/**
+ * hw_read_intr_enable: returns interrupt enable register
+ *
+ * This function returns register data
+ */
+u32 hw_read_intr_enable(struct ci_hdrc *ci)
+{
+ return hw_read(ci, OP_USBINTR, ~0);
+}
+
+/**
+ * hw_read_intr_status: returns interrupt status register
+ *
+ * This function returns register data
+ */
+u32 hw_read_intr_status(struct ci_hdrc *ci)
+{
+ return hw_read(ci, OP_USBSTS, ~0);
+}
+
+/**
* hw_port_test_set: writes port test mode (execute without interruption)
* @mode: new value
*
@@ -179,11 +196,10 @@ static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
0);
/*
- * The controller needs at least 1ms to reflect
- * PHY's status, the PHY also needs some time (less
+ * the PHY needs some time (less
* than 1ms) to leave low power mode.
*/
- usleep_range(1500, 2000);
+ usleep_range(1000, 1100);
}
}
@@ -392,8 +408,14 @@ static irqreturn_t ci_irq(int irq, void *data)
irqreturn_t ret = IRQ_NONE;
u32 otgsc = 0;
- if (ci->is_otg)
- otgsc = hw_read(ci, OP_OTGSC, ~0);
+ if (ci->is_otg) {
+ otgsc = hw_read_otgsc(ci, ~0);
+ if (ci_otg_is_fsm_mode(ci)) {
+ ret = ci_otg_fsm_irq(ci);
+ if (ret == IRQ_HANDLED)
+ return ret;
+ }
+ }
/*
* Handle id change interrupt, it indicates device/host function
@@ -401,9 +423,9 @@ static irqreturn_t ci_irq(int irq, void *data)
*/
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);
+ /* Clear ID change irq status */
+ hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
+ ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
@@ -413,9 +435,9 @@ static irqreturn_t ci_irq(int irq, void *data)
*/
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);
+ /* Clear BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
+ ci_otg_queue_work(ci);
return IRQ_HANDLED;
}
@@ -533,11 +555,8 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
ci->is_otg = (hw_read(ci, CAP_DCCPARAMS,
DCCPARAMS_DC | DCCPARAMS_HC)
== (DCCPARAMS_DC | DCCPARAMS_HC));
- if (ci->is_otg) {
+ 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)
@@ -599,6 +618,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret) {
dev_err(dev, "unable to init phy: %d\n", ret);
return ret;
+ } else {
+ /*
+ * The delay to sync PHY's status, the maximum delay is
+ * 2ms since the otgsc uses 1ms timer to debounce the
+ * PHY's input
+ */
+ usleep_range(2000, 2500);
}
ci->hw_bank.phys = res->start;
@@ -633,6 +659,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
if (ci->is_otg) {
+ /* Disable and clear all OTG irq */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
ret = ci_hdrc_otg_init(ci);
if (ret) {
dev_err(dev, "init otg fails, ret = %d\n", ret);
@@ -642,13 +671,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
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);
+ /* Enable ID change irq */
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
} else {
/*
* If the controller is not OTG capable, but support
@@ -667,10 +692,13 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ci->role == CI_ROLE_GADGET)
ci_handle_vbus_change(ci);
- ret = ci_role_start(ci, ci->role);
- if (ret) {
- dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
- goto stop;
+ if (!ci_otg_is_fsm_mode(ci)) {
+ ret = ci_role_start(ci, ci->role);
+ if (ret) {
+ dev_err(dev, "can't start %s role\n",
+ ci_role(ci)->name);
+ goto stop;
+ }
}
platform_set_drvdata(pdev, ci);
@@ -679,6 +707,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret)
goto stop;
+ if (ci_otg_is_fsm_mode(ci))
+ ci_hdrc_otg_fsm_start(ci);
+
ret = dbg_create_files(ci);
if (!ret)
return 0;
@@ -711,6 +742,7 @@ static struct platform_driver ci_hdrc_driver = {
.remove = ci_hdrc_remove,
.driver = {
.name = "ci_hdrc",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c
index 96d899aee473..7cccab6ff308 100644
--- a/drivers/usb/chipidea/debug.c
+++ b/drivers/usb/chipidea/debug.c
@@ -7,11 +7,15 @@
#include <linux/uaccess.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/phy.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/otg-fsm.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "debug.h"
+#include "otg.h"
/**
* ci_device_show: prints information about device capabilities and status
@@ -204,6 +208,80 @@ static const struct file_operations ci_requests_fops = {
.release = single_release,
};
+int ci_otg_show(struct seq_file *s, void *unused)
+{
+ struct ci_hdrc *ci = s->private;
+ struct otg_fsm *fsm;
+
+ if (!ci || !ci_otg_is_fsm_mode(ci))
+ return 0;
+
+ fsm = &ci->fsm;
+
+ /* ------ State ----- */
+ seq_printf(s, "OTG state: %s\n\n",
+ usb_otg_state_string(ci->transceiver->state));
+
+ /* ------ State Machine Variables ----- */
+ seq_printf(s, "a_bus_drop: %d\n", fsm->a_bus_drop);
+
+ seq_printf(s, "a_bus_req: %d\n", fsm->a_bus_req);
+
+ seq_printf(s, "a_srp_det: %d\n", fsm->a_srp_det);
+
+ seq_printf(s, "a_vbus_vld: %d\n", fsm->a_vbus_vld);
+
+ seq_printf(s, "b_conn: %d\n", fsm->b_conn);
+
+ seq_printf(s, "adp_change: %d\n", fsm->adp_change);
+
+ seq_printf(s, "power_up: %d\n", fsm->power_up);
+
+ seq_printf(s, "a_bus_resume: %d\n", fsm->a_bus_resume);
+
+ seq_printf(s, "a_bus_suspend: %d\n", fsm->a_bus_suspend);
+
+ seq_printf(s, "a_conn: %d\n", fsm->a_conn);
+
+ seq_printf(s, "b_bus_req: %d\n", fsm->b_bus_req);
+
+ seq_printf(s, "b_bus_suspend: %d\n", fsm->b_bus_suspend);
+
+ seq_printf(s, "b_se0_srp: %d\n", fsm->b_se0_srp);
+
+ seq_printf(s, "b_ssend_srp: %d\n", fsm->b_ssend_srp);
+
+ seq_printf(s, "b_sess_vld: %d\n", fsm->b_sess_vld);
+
+ seq_printf(s, "b_srp_done: %d\n", fsm->b_srp_done);
+
+ seq_printf(s, "drv_vbus: %d\n", fsm->drv_vbus);
+
+ seq_printf(s, "loc_conn: %d\n", fsm->loc_conn);
+
+ seq_printf(s, "loc_sof: %d\n", fsm->loc_sof);
+
+ seq_printf(s, "adp_prb: %d\n", fsm->adp_prb);
+
+ seq_printf(s, "id: %d\n", fsm->id);
+
+ seq_printf(s, "protocol: %d\n", fsm->protocol);
+
+ return 0;
+}
+
+static int ci_otg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ci_otg_show, inode->i_private);
+}
+
+static const struct file_operations ci_otg_fops = {
+ .open = ci_otg_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
static int ci_role_show(struct seq_file *s, void *data)
{
struct ci_hdrc *ci = s->private;
@@ -253,6 +331,50 @@ static const struct file_operations ci_role_fops = {
.release = single_release,
};
+int ci_registers_show(struct seq_file *s, void *unused)
+{
+ struct ci_hdrc *ci = s->private;
+ u32 tmp_reg;
+
+ if (!ci)
+ return 0;
+
+ /* ------ Registers ----- */
+ tmp_reg = hw_read_intr_enable(ci);
+ seq_printf(s, "USBINTR reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read_intr_status(ci);
+ seq_printf(s, "USBSTS reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_USBMODE, ~0);
+ seq_printf(s, "USBMODE reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_USBCMD, ~0);
+ seq_printf(s, "USBCMD reg: %08x\n", tmp_reg);
+
+ tmp_reg = hw_read(ci, OP_PORTSC, ~0);
+ seq_printf(s, "PORTSC reg: %08x\n", tmp_reg);
+
+ if (ci->is_otg) {
+ tmp_reg = hw_read_otgsc(ci, ~0);
+ seq_printf(s, "OTGSC reg: %08x\n", tmp_reg);
+ }
+
+ return 0;
+}
+
+static int ci_registers_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ci_registers_show, inode->i_private);
+}
+
+static const struct file_operations ci_registers_fops = {
+ .open = ci_registers_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
/**
* dbg_create_files: initializes the attribute interface
* @ci: device
@@ -287,8 +409,21 @@ int dbg_create_files(struct ci_hdrc *ci)
if (!dent)
goto err;
+ if (ci_otg_is_fsm_mode(ci)) {
+ dent = debugfs_create_file("otg", S_IRUGO, ci->debugfs, ci,
+ &ci_otg_fops);
+ if (!dent)
+ goto err;
+ }
+
dent = debugfs_create_file("role", S_IRUGO | S_IWUSR, ci->debugfs, ci,
&ci_role_fops);
+ if (!dent)
+ goto err;
+
+ dent = debugfs_create_file("registers", S_IRUGO, ci->debugfs, ci,
+ &ci_registers_fops);
+
if (dent)
return 0;
err:
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index a8ac6c16dac9..a93d950e9468 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -67,7 +67,11 @@ static int host_start(struct ci_hdrc *ci)
ehci->has_tdi_phy_lpm = ci->hw_bank.lpm;
ehci->imx28_write_fix = ci->imx28_write_fix;
- if (ci->platdata->reg_vbus) {
+ /*
+ * vbus is always on if host is not in OTG FSM mode,
+ * otherwise should be controlled by OTG FSM
+ */
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
ret = regulator_enable(ci->platdata->reg_vbus);
if (ret) {
dev_err(ci->dev,
@@ -78,10 +82,17 @@ static int host_start(struct ci_hdrc *ci)
}
ret = usb_add_hcd(hcd, 0, 0);
- if (ret)
+ if (ret) {
goto disable_reg;
- else
+ } else {
+ struct usb_otg *otg = ci->transceiver->otg;
+
ci->hcd = hcd;
+ if (otg) {
+ otg->host = &hcd->self;
+ hcd->self.otg_port = 1;
+ }
+ }
if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)
hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS);
@@ -89,7 +100,7 @@ static int host_start(struct ci_hdrc *ci)
return ret;
disable_reg:
- if (ci->platdata->reg_vbus)
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci))
regulator_disable(ci->platdata->reg_vbus);
put_hcd:
@@ -105,7 +116,7 @@ static void host_stop(struct ci_hdrc *ci)
if (hcd) {
usb_remove_hcd(hcd);
usb_put_hcd(hcd);
- if (ci->platdata->reg_vbus)
+ if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci))
regulator_disable(ci->platdata->reg_vbus);
}
}
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 39bd7ec8bf75..a048b08b9d4d 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -11,8 +11,8 @@
*/
/*
- * This file mainly handles otgsc register, it may include OTG operation
- * in the future.
+ * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
+ * are also included.
*/
#include <linux/usb/otg.h>
@@ -22,6 +22,26 @@
#include "ci.h"
#include "bits.h"
#include "otg.h"
+#include "otg_fsm.h"
+
+/**
+ * hw_read_otgsc returns otgsc register bits value.
+ * @mask: bitfield mask
+ */
+u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
+{
+ return hw_read(ci, OP_OTGSC, mask);
+}
+
+/**
+ * hw_write_otgsc updates target bits of OTGSC register.
+ * @mask: bitfield mask
+ * @data: to be written
+ */
+void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
+{
+ hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
+}
/**
* ci_otg_role - pick role based on ID pin state
@@ -29,8 +49,7 @@
*/
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
+ enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
? CI_ROLE_GADGET
: CI_ROLE_HOST;
@@ -39,14 +58,10 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
- u32 otgsc;
-
if (!ci->is_otg)
return;
- otgsc = hw_read(ci, OP_OTGSC, ~0);
-
- if (otgsc & OTGSC_BSV)
+ if (hw_read_otgsc(ci, OTGSC_BSV))
usb_gadget_vbus_connect(&ci->gadget);
else
usb_gadget_vbus_disconnect(&ci->gadget);
@@ -76,6 +91,11 @@ static void ci_otg_work(struct work_struct *work)
{
struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
+ if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
+ enable_irq(ci->irq);
+ return;
+ }
+
if (ci->id_event) {
ci->id_event = false;
ci_handle_id_switch(ci);
@@ -102,6 +122,9 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci)
return -ENODEV;
}
+ if (ci_otg_is_fsm_mode(ci))
+ return ci_hdrc_otg_fsm_init(ci);
+
return 0;
}
@@ -115,6 +138,9 @@ void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
flush_workqueue(ci->wq);
destroy_workqueue(ci->wq);
}
- ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS);
- ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS);
+ /* Disable all OTG irq and clear status */
+ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
+ OTGSC_INT_STATUS_BITS);
+ if (ci_otg_is_fsm_mode(ci))
+ ci_hdrc_otg_fsm_remove(ci);
}
diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
index 449bee07f4fe..9ecb598e48f0 100644
--- a/drivers/usb/chipidea/otg.h
+++ b/drivers/usb/chipidea/otg.h
@@ -11,25 +11,16 @@
#ifndef __DRIVERS_USB_CHIPIDEA_OTG_H
#define __DRIVERS_USB_CHIPIDEA_OTG_H
-static inline void ci_clear_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- /* Only clear request bits */
- hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, bits);
-}
-
-static inline void ci_enable_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- hw_write(ci, OP_OTGSC, bits | OTGSC_INT_STATUS_BITS, bits);
-}
-
-static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits)
-{
- hw_write(ci, OP_OTGSC, bits | OTGSC_INT_STATUS_BITS, 0);
-}
-
+u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask);
+void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data);
int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
void ci_handle_vbus_change(struct ci_hdrc *ci);
+static inline void ci_otg_queue_work(struct ci_hdrc *ci)
+{
+ disable_irq_nosync(ci->irq);
+ queue_work(ci->wq, &ci->work);
+}
#endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
new file mode 100644
index 000000000000..caaabc58021e
--- /dev/null
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -0,0 +1,842 @@
+/*
+ * otg_fsm.c - ChipIdea USB IP core OTG FSM driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Jun Li
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * This file mainly handles OTG fsm, it includes OTG fsm operations
+ * for HNP and SRP.
+ *
+ * TODO List
+ * - ADP
+ * - OTG test device
+ */
+
+#include <linux/usb/otg.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/chipidea.h>
+#include <linux/regulator/consumer.h>
+
+#include "ci.h"
+#include "bits.h"
+#include "otg.h"
+#include "otg_fsm.h"
+
+static struct ci_otg_fsm_timer *otg_timer_initializer
+(struct ci_hdrc *ci, void (*function)(void *, unsigned long),
+ unsigned long expires, unsigned long data)
+{
+ struct ci_otg_fsm_timer *timer;
+
+ timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer),
+ GFP_KERNEL);
+ if (!timer)
+ return NULL;
+ timer->function = function;
+ timer->expires = expires;
+ timer->data = data;
+ return timer;
+}
+
+/* Add for otg: interact with user space app */
+static ssize_t
+get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_req);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_req(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0') {
+ ci->fsm.a_bus_req = 0;
+ } else if (buf[0] == '1') {
+ /* If a_bus_drop is TRUE, a_bus_req can't be set */
+ if (ci->fsm.a_bus_drop) {
+ mutex_unlock(&ci->fsm.lock);
+ return count;
+ }
+ ci->fsm.a_bus_req = 1;
+ }
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req, set_a_bus_req);
+
+static ssize_t
+get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.a_bus_drop);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_drop(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0') {
+ ci->fsm.a_bus_drop = 0;
+ } else if (buf[0] == '1') {
+ ci->fsm.a_bus_drop = 1;
+ ci->fsm.a_bus_req = 0;
+ }
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR, get_a_bus_drop,
+ set_a_bus_drop);
+
+static ssize_t
+get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ char *next;
+ unsigned size, t;
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ next = buf;
+ size = PAGE_SIZE;
+ t = scnprintf(next, size, "%d\n", ci->fsm.b_bus_req);
+ size -= t;
+ next += t;
+
+ return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_b_bus_req(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '0')
+ ci->fsm.b_bus_req = 0;
+ else if (buf[0] == '1')
+ ci->fsm.b_bus_req = 1;
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUSR, get_b_bus_req, set_b_bus_req);
+
+static ssize_t
+set_a_clr_err(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ if (count > 2)
+ return -1;
+
+ mutex_lock(&ci->fsm.lock);
+ if (buf[0] == '1')
+ ci->fsm.a_clr_err = 1;
+
+ ci_otg_queue_work(ci);
+ mutex_unlock(&ci->fsm.lock);
+
+ return count;
+}
+static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
+
+static struct attribute *inputs_attrs[] = {
+ &dev_attr_a_bus_req.attr,
+ &dev_attr_a_bus_drop.attr,
+ &dev_attr_b_bus_req.attr,
+ &dev_attr_a_clr_err.attr,
+ NULL,
+};
+
+static struct attribute_group inputs_attr_group = {
+ .name = "inputs",
+ .attrs = inputs_attrs,
+};
+
+/*
+ * Add timer to active timer list
+ */
+static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t)
+{
+ struct ci_otg_fsm_timer *tmp_timer;
+ struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t];
+ struct list_head *active_timers = &ci->fsm_timer->active_timers;
+
+ if (t >= NUM_CI_OTG_FSM_TIMERS)
+ return;
+
+ /*
+ * Check if the timer is already in the active list,
+ * if so update timer count
+ */
+ list_for_each_entry(tmp_timer, active_timers, list)
+ if (tmp_timer == timer) {
+ timer->count = timer->expires;
+ return;
+ }
+
+ timer->count = timer->expires;
+ list_add_tail(&timer->list, active_timers);
+
+ /* Enable 1ms irq */
+ if (!(hw_read_otgsc(ci, OTGSC_1MSIE)))
+ hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE);
+}
+
+/*
+ * Remove timer from active timer list
+ */
+static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t)
+{
+ struct ci_otg_fsm_timer *tmp_timer, *del_tmp;
+ struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t];
+ struct list_head *active_timers = &ci->fsm_timer->active_timers;
+
+ if (t >= NUM_CI_OTG_FSM_TIMERS)
+ return;
+
+ list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list)
+ if (tmp_timer == timer)
+ list_del(&timer->list);
+
+ /* Disable 1ms irq if there is no any active timer */
+ if (list_empty(active_timers))
+ hw_write_otgsc(ci, OTGSC_1MSIE, 0);
+}
+
+/*
+ * Reduce timer count by 1, and find timeout conditions.
+ * Called by otg 1ms timer interrupt
+ */
+static inline int ci_otg_tick_timer(struct ci_hdrc *ci)
+{
+ struct ci_otg_fsm_timer *tmp_timer, *del_tmp;
+ struct list_head *active_timers = &ci->fsm_timer->active_timers;
+ 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(ci, tmp_timer->data);
+ expired = 1;
+ }
+ }
+
+ /* disable 1ms irq if there is no any timer active */
+ if ((expired == 1) && list_empty(active_timers))
+ hw_write_otgsc(ci, OTGSC_1MSIE, 0);
+
+ return expired;
+}
+
+/* The timeout callback function to set time out bit */
+static void set_tmout(void *ptr, unsigned long indicator)
+{
+ *(int *)indicator = 1;
+}
+
+static void set_tmout_and_fsm(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ set_tmout(ci, indicator);
+
+ ci_otg_queue_work(ci);
+}
+
+static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ set_tmout(ci, indicator);
+ /* Disable port power */
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0);
+ /* Clear exsiting DP irq */
+ hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
+ /* Enable data pulse irq */
+ hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE);
+ ci_otg_queue_work(ci);
+}
+
+static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ set_tmout(ci, indicator);
+ if (!hw_read_otgsc(ci, OTGSC_BSV))
+ ci->fsm.b_sess_vld = 0;
+
+ ci_otg_queue_work(ci);
+}
+
+static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ set_tmout(ci, indicator);
+
+ /* only vbus fall below B_sess_vld in b_idle state */
+ if (ci->transceiver->state == OTG_STATE_B_IDLE)
+ ci_otg_queue_work(ci);
+}
+
+static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ /* Check if A detached */
+ if (!(hw_read_otgsc(ci, OTGSC_BSV))) {
+ ci->fsm.b_sess_vld = 0;
+ ci_otg_add_timer(ci, B_SSEND_SRP);
+ ci_otg_queue_work(ci);
+ }
+}
+
+static void b_data_pulse_end(void *ptr, unsigned long indicator)
+{
+ struct ci_hdrc *ci = (struct ci_hdrc *)ptr;
+
+ ci->fsm.b_srp_done = 1;
+ ci->fsm.b_bus_req = 0;
+ if (ci->fsm.power_up)
+ ci->fsm.power_up = 0;
+
+ hw_write_otgsc(ci, OTGSC_HABA, 0);
+
+ ci_otg_queue_work(ci);
+}
+
+/* Initialize timers */
+static int ci_otg_init_timers(struct ci_hdrc *ci)
+{
+ struct otg_fsm *fsm = &ci->fsm;
+
+ /* FSM used timers */
+ ci->fsm_timer->timer_list[A_WAIT_VRISE] =
+ otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE,
+ (unsigned long)&fsm->a_wait_vrise_tmout);
+ if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[A_WAIT_VFALL] =
+ otg_timer_initializer(ci, &a_wait_vfall_tmout_func,
+ TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout);
+ if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[A_WAIT_BCON] =
+ otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON,
+ (unsigned long)&fsm->a_wait_bcon_tmout);
+ if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[A_AIDL_BDIS] =
+ otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS,
+ (unsigned long)&fsm->a_aidl_bdis_tmout);
+ if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[A_BIDL_ADIS] =
+ otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS,
+ (unsigned long)&fsm->a_bidl_adis_tmout);
+ if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_ASE0_BRST] =
+ otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST,
+ (unsigned long)&fsm->b_ase0_brst_tmout);
+ if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_SE0_SRP] =
+ otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP,
+ (unsigned long)&fsm->b_se0_srp);
+ if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_SSEND_SRP] =
+ otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP,
+ (unsigned long)&fsm->b_ssend_srp);
+ if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_SRP_FAIL] =
+ otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL,
+ (unsigned long)&fsm->b_srp_done);
+ if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_DATA_PLS] =
+ otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0);
+ if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL)
+ return -ENOMEM;
+
+ ci->fsm_timer->timer_list[B_SESS_VLD] = otg_timer_initializer(ci,
+ &b_sess_vld_tmout_func, TB_SESS_VLD, 0);
+ if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/* -------------------------------------------------------------*/
+/* Operations that will be called from OTG Finite State Machine */
+/* -------------------------------------------------------------*/
+static void ci_otg_fsm_add_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (t < NUM_OTG_FSM_TIMERS)
+ ci_otg_add_timer(ci, t);
+ return;
+}
+
+static void ci_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (t < NUM_OTG_FSM_TIMERS)
+ ci_otg_del_timer(ci, t);
+ return;
+}
+
+/*
+ * A-device drive vbus: turn on vbus regulator and enable port power
+ * Data pulse irq should be disabled while vbus is on.
+ */
+static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
+{
+ int ret;
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on) {
+ /* Enable power power */
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP,
+ PORTSC_PP);
+ if (ci->platdata->reg_vbus) {
+ ret = regulator_enable(ci->platdata->reg_vbus);
+ if (ret) {
+ dev_err(ci->dev,
+ "Failed to enable vbus regulator, ret=%d\n",
+ ret);
+ return;
+ }
+ }
+ /* Disable data pulse irq */
+ hw_write_otgsc(ci, OTGSC_DPIE, 0);
+
+ fsm->a_srp_det = 0;
+ fsm->power_up = 0;
+ } else {
+ if (ci->platdata->reg_vbus)
+ regulator_disable(ci->platdata->reg_vbus);
+
+ fsm->a_bus_drop = 1;
+ fsm->a_bus_req = 0;
+ }
+}
+
+/*
+ * Control data line by Run Stop bit.
+ */
+static void ci_otg_loc_conn(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on)
+ hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS);
+ else
+ hw_write(ci, OP_USBCMD, USBCMD_RS, 0);
+}
+
+/*
+ * Generate SOF by host.
+ * This is controlled through suspend/resume the port.
+ * In host mode, controller will automatically send SOF.
+ * Suspend will block the data on the port.
+ */
+static void ci_otg_loc_sof(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ if (on)
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_FPR,
+ PORTSC_FPR);
+ else
+ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_SUSP,
+ PORTSC_SUSP);
+}
+
+/*
+ * Start SRP pulsing by data-line pulsing,
+ * no v-bus pulsing followed
+ */
+static void ci_otg_start_pulse(struct otg_fsm *fsm)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ /* Hardware Assistant Data pulse */
+ hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP);
+
+ ci_otg_add_timer(ci, B_DATA_PLS);
+}
+
+static int ci_otg_start_host(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ mutex_unlock(&fsm->lock);
+ if (on) {
+ ci_role_stop(ci);
+ ci_role_start(ci, CI_ROLE_HOST);
+ } else {
+ ci_role_stop(ci);
+ hw_device_reset(ci, USBMODE_CM_DC);
+ ci_role_start(ci, CI_ROLE_GADGET);
+ }
+ mutex_lock(&fsm->lock);
+ return 0;
+}
+
+static int ci_otg_start_gadget(struct otg_fsm *fsm, int on)
+{
+ struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm);
+
+ mutex_unlock(&fsm->lock);
+ if (on)
+ usb_gadget_vbus_connect(&ci->gadget);
+ else
+ usb_gadget_vbus_disconnect(&ci->gadget);
+ mutex_lock(&fsm->lock);
+
+ return 0;
+}
+
+static struct otg_fsm_ops ci_otg_ops = {
+ .drv_vbus = ci_otg_drv_vbus,
+ .loc_conn = ci_otg_loc_conn,
+ .loc_sof = ci_otg_loc_sof,
+ .start_pulse = ci_otg_start_pulse,
+ .add_timer = ci_otg_fsm_add_timer,
+ .del_timer = ci_otg_fsm_del_timer,
+ .start_host = ci_otg_start_host,
+ .start_gadget = ci_otg_start_gadget,
+};
+
+int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+ /*
+ * Don't do fsm transition for B device
+ * when there is no gadget class driver
+ */
+ if (ci->fsm.id && !(ci->driver) &&
+ ci->transceiver->state < OTG_STATE_A_IDLE)
+ return 0;
+
+ if (otg_statemachine(&ci->fsm)) {
+ if (ci->transceiver->state == OTG_STATE_A_IDLE) {
+ /*
+ * Further state change for cases:
+ * a_idle to b_idle; or
+ * a_idle to a_wait_vrise due to ID change(1->0), so
+ * B-dev becomes A-dev can try to start new session
+ * consequently; or
+ * a_idle to a_wait_vrise when power up
+ */
+ if ((ci->fsm.id) || (ci->id_event) ||
+ (ci->fsm.power_up))
+ ci_otg_queue_work(ci);
+ if (ci->id_event)
+ ci->id_event = false;
+ } else if (ci->transceiver->state == OTG_STATE_B_IDLE) {
+ if (ci->fsm.b_sess_vld) {
+ ci->fsm.power_up = 0;
+ /*
+ * Further transite to b_periphearl state
+ * when register gadget driver with vbus on
+ */
+ ci_otg_queue_work(ci);
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Update fsm variables in each state if catching expected interrupts,
+ * called by otg fsm isr.
+ */
+static void ci_otg_fsm_event(struct ci_hdrc *ci)
+{
+ u32 intr_sts, otg_bsess_vld, port_conn;
+ struct otg_fsm *fsm = &ci->fsm;
+
+ intr_sts = hw_read_intr_status(ci);
+ otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV);
+ port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS);
+
+ switch (ci->transceiver->state) {
+ case OTG_STATE_A_WAIT_BCON:
+ if (port_conn) {
+ fsm->b_conn = 1;
+ fsm->a_bus_req = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_IDLE:
+ if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) {
+ fsm->b_sess_vld = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) {
+ fsm->a_bus_suspend = 1;
+ ci_otg_queue_work(ci);
+ } else if (intr_sts & USBi_PCI) {
+ if (fsm->a_bus_suspend == 1)
+ fsm->a_bus_suspend = 0;
+ }
+ break;
+ case OTG_STATE_B_HOST:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ fsm->a_conn = 0;
+ fsm->b_bus_req = 0;
+ ci_otg_queue_work(ci);
+ ci_otg_add_timer(ci, B_SESS_VLD);
+ }
+ break;
+ case OTG_STATE_A_PERIPHERAL:
+ if (intr_sts & USBi_SLI) {
+ fsm->b_bus_suspend = 1;
+ /*
+ * Init a timer to know how long this suspend
+ * will contine, if time out, indicates B no longer
+ * wants to be host role
+ */
+ ci_otg_add_timer(ci, A_BIDL_ADIS);
+ }
+
+ if (intr_sts & USBi_URI)
+ ci_otg_del_timer(ci, A_BIDL_ADIS);
+
+ if (intr_sts & USBi_PCI) {
+ if (fsm->b_bus_suspend == 1) {
+ ci_otg_del_timer(ci, A_BIDL_ADIS);
+ fsm->b_bus_suspend = 0;
+ }
+ }
+ break;
+ case OTG_STATE_A_SUSPEND:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ fsm->b_conn = 0;
+
+ /* if gadget driver is binded */
+ if (ci->driver) {
+ /* A device to be peripheral mode */
+ ci->gadget.is_a_peripheral = 1;
+ }
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_A_HOST:
+ if ((intr_sts & USBi_PCI) && !port_conn) {
+ fsm->b_conn = 0;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ case OTG_STATE_B_WAIT_ACON:
+ if ((intr_sts & USBi_PCI) && port_conn) {
+ fsm->a_conn = 1;
+ ci_otg_queue_work(ci);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * ci_otg_irq - otg fsm related irq handling
+ * and also update otg fsm variable by monitoring usb host and udc
+ * state change interrupts.
+ * @ci: ci_hdrc
+ */
+irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
+{
+ irqreturn_t retval = IRQ_NONE;
+ u32 otgsc, otg_int_src = 0;
+ struct otg_fsm *fsm = &ci->fsm;
+
+ otgsc = hw_read_otgsc(ci, ~0);
+ otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8);
+ fsm->id = (otgsc & OTGSC_ID) ? 1 : 0;
+
+ if (otg_int_src) {
+ if (otg_int_src & OTGSC_1MSIS) {
+ hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS);
+ retval = ci_otg_tick_timer(ci);
+ return IRQ_HANDLED;
+ } else if (otg_int_src & OTGSC_DPIS) {
+ hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS);
+ fsm->a_srp_det = 1;
+ fsm->a_bus_drop = 0;
+ } else if (otg_int_src & OTGSC_IDIS) {
+ hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS);
+ if (fsm->id == 0) {
+ fsm->a_bus_drop = 0;
+ fsm->a_bus_req = 1;
+ ci->id_event = true;
+ }
+ } else if (otg_int_src & OTGSC_BSVIS) {
+ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
+ if (otgsc & OTGSC_BSV) {
+ fsm->b_sess_vld = 1;
+ ci_otg_del_timer(ci, B_SSEND_SRP);
+ ci_otg_del_timer(ci, B_SRP_FAIL);
+ fsm->b_ssend_srp = 0;
+ } else {
+ fsm->b_sess_vld = 0;
+ if (fsm->id)
+ ci_otg_add_timer(ci, B_SSEND_SRP);
+ }
+ } else if (otg_int_src & OTGSC_AVVIS) {
+ hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS);
+ if (otgsc & OTGSC_AVV) {
+ fsm->a_vbus_vld = 1;
+ } else {
+ fsm->a_vbus_vld = 0;
+ fsm->b_conn = 0;
+ }
+ }
+ ci_otg_queue_work(ci);
+ return IRQ_HANDLED;
+ }
+
+ ci_otg_fsm_event(ci);
+
+ return retval;
+}
+
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+ ci_otg_queue_work(ci);
+}
+
+int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
+{
+ int retval = 0;
+ struct usb_otg *otg;
+
+ otg = devm_kzalloc(ci->dev,
+ sizeof(struct usb_otg), GFP_KERNEL);
+ if (!otg) {
+ dev_err(ci->dev,
+ "Failed to allocate usb_otg structure for ci hdrc otg!\n");
+ return -ENOMEM;
+ }
+
+ otg->phy = ci->transceiver;
+ otg->gadget = &ci->gadget;
+ ci->fsm.otg = otg;
+ ci->transceiver->otg = ci->fsm.otg;
+ ci->fsm.power_up = 1;
+ ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0;
+ ci->transceiver->state = OTG_STATE_UNDEFINED;
+ ci->fsm.ops = &ci_otg_ops;
+
+ mutex_init(&ci->fsm.lock);
+
+ ci->fsm_timer = devm_kzalloc(ci->dev,
+ sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL);
+ if (!ci->fsm_timer) {
+ dev_err(ci->dev,
+ "Failed to allocate timer structure for ci hdrc otg!\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&ci->fsm_timer->active_timers);
+ retval = ci_otg_init_timers(ci);
+ if (retval) {
+ dev_err(ci->dev, "Couldn't init OTG timers\n");
+ return retval;
+ }
+
+ retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group);
+ if (retval < 0) {
+ dev_dbg(ci->dev,
+ "Can't register sysfs attr group: %d\n", retval);
+ return retval;
+ }
+
+ /* Enable A vbus valid irq */
+ hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE);
+
+ if (ci->fsm.id) {
+ ci->fsm.b_ssend_srp =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1;
+ ci->fsm.b_sess_vld =
+ hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0;
+ /* Enable BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIE, OTGSC_BSVIE);
+ }
+
+ return 0;
+}
+
+void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
+{
+ sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group);
+}
diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h
new file mode 100644
index 000000000000..94c085f456a9
--- /dev/null
+++ b/drivers/usb/chipidea/otg_fsm.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Jun Li
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
+#define __DRIVERS_USB_CHIPIDEA_OTG_FSM_H
+
+#include <linux/usb/otg-fsm.h>
+
+/*
+ * A-DEVICE timing constants
+ */
+
+/* Wait for VBUS Rise */
+#define TA_WAIT_VRISE (100) /* a_wait_vrise: section 7.1.2
+ * a_wait_vrise_tmr: section 7.4.5.1
+ * TA_VBUS_RISE <= 100ms, section 4.4
+ * Table 4-1: Electrical Characteristics
+ * ->DC Electrical Timing
+ */
+/* Wait for VBUS Fall */
+#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7
+ * a_wait_vfall_tmr: section: 7.4.5.2
+ */
+/* Wait for B-Connect */
+#define TA_WAIT_BCON (10000) /* a_wait_bcon: section 7.1.3
+ * TA_WAIT_BCON: should be between 1100
+ * and 30000 ms, section 5.5, Table 5-1
+ */
+/* A-Idle to B-Disconnect */
+#define TA_AIDL_BDIS (5000) /* a_suspend min 200 ms, section 5.2.1
+ * TA_AIDL_BDIS: section 5.5, Table 5-1
+ */
+/* B-Idle to A-Disconnect */
+#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1
+ * 500ms is used for B switch to host
+ * for safe
+ */
+
+/*
+ * B-device timing constants
+ */
+
+/* Data-Line Pulse Time*/
+#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms
+ * section:5.1.3
+ */
+/* SRP Fail Time */
+#define TB_SRP_FAIL (6000) /* b_srp_init,fail time 5~6s
+ * section:5.1.6
+ */
+/* A-SE0 to B-Reset */
+#define TB_ASE0_BRST (155) /* minimum 155 ms, section:5.3.1 */
+/* SE0 Time Before SRP */
+#define TB_SE0_SRP (1000) /* b_idle,minimum 1s, section:5.1.2 */
+/* SSEND time before SRP */
+#define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */
+
+#define TB_SESS_VLD (1000)
+
+enum ci_otg_fsm_timer_index {
+ /*
+ * CI specific timers, start from the end
+ * of standard and auxiliary OTG timers
+ */
+ B_DATA_PLS = NUM_OTG_FSM_TIMERS,
+ B_SSEND_SRP,
+ B_SESS_VLD,
+
+ NUM_CI_OTG_FSM_TIMERS,
+};
+
+struct ci_otg_fsm_timer {
+ unsigned long expires; /* Number of count increase to timeout */
+ unsigned long count; /* Tick counter */
+ void (*function)(void *, unsigned long); /* Timeout function */
+ unsigned long data; /* Data passed to function */
+ struct list_head list;
+};
+
+struct ci_otg_fsm_timer_list {
+ struct ci_otg_fsm_timer *timer_list[NUM_CI_OTG_FSM_TIMERS];
+ struct list_head active_timers;
+};
+
+#ifdef CONFIG_USB_OTG_FSM
+
+int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci);
+int ci_otg_fsm_work(struct ci_hdrc *ci);
+irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci);
+void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci);
+
+#else
+
+static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci)
+{
+ return 0;
+}
+
+static inline int ci_otg_fsm_work(struct ci_hdrc *ci)
+{
+ return -ENXIO;
+}
+
+static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci)
+{
+ return IRQ_NONE;
+}
+
+static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci)
+{
+
+}
+
+static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci)
+{
+
+}
+
+#endif
+
+#endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 7739c64ef259..b8125aa64ad8 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -20,6 +20,7 @@
#include <linux/pm_runtime.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/otg-fsm.h>
#include <linux/usb/chipidea.h>
#include "ci.h"
@@ -27,6 +28,7 @@
#include "bits.h"
#include "debug.h"
#include "otg.h"
+#include "otg_fsm.h"
/* control endpoint description */
static const struct usb_endpoint_descriptor
@@ -242,26 +244,6 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci)
}
/**
- * hw_read_intr_enable: returns interrupt enable register
- *
- * This function returns register data
- */
-static u32 hw_read_intr_enable(struct ci_hdrc *ci)
-{
- return hw_read(ci, OP_USBINTR, ~0);
-}
-
-/**
- * hw_read_intr_status: returns interrupt status register
- *
- * This function returns register data
- */
-static u32 hw_read_intr_status(struct ci_hdrc *ci)
-{
- return hw_read(ci, OP_USBSTS, ~0);
-}
-
-/**
* hw_test_and_clear_complete: test & clear complete status (execute without
* interruption)
* @n: endpoint number
@@ -727,6 +709,8 @@ __acquires(ci->lock)
if (ci->status == NULL)
retval = -ENOMEM;
+ usb_gadget_set_state(&ci->gadget, USB_STATE_DEFAULT);
+
done:
spin_lock(&ci->lock);
@@ -841,7 +825,6 @@ __acquires(hwep->lock)
if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
/* Assume that device is bus powered for now. */
*(u16 *)req->buf = ci->remote_wakeup << 1;
- retval = 0;
} else if ((setup->bRequestType & USB_RECIP_MASK) \
== USB_RECIP_ENDPOINT) {
dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ?
@@ -883,6 +866,8 @@ isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req)
if (ci->setaddr) {
hw_usb_set_address(ci, ci->address);
ci->setaddr = false;
+ if (ci->address)
+ usb_gadget_set_state(&ci->gadget, USB_STATE_ADDRESS);
}
spin_lock_irqsave(&ci->lock, flags);
@@ -1072,6 +1057,14 @@ __acquires(ci->lock)
default:
break;
}
+ break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ if (ci_otg_is_fsm_mode(ci)) {
+ ci->gadget.b_hnp_enable = 1;
+ err = isr_setup_status_phase(
+ ci);
+ }
+ break;
default:
goto delegate;
}
@@ -1176,8 +1169,8 @@ static int ep_enable(struct usb_ep *ep,
if (hwep->type == USB_ENDPOINT_XFER_CONTROL)
cap |= QH_IOS;
- if (hwep->num)
- cap |= QH_ZLT;
+
+ cap |= QH_ZLT;
cap |= (hwep->ep.maxpacket << __ffs(QH_MAX_PKT)) & QH_MAX_PKT;
/*
* For ISO-TX, we set mult at QH as the largest value, and use
@@ -1328,6 +1321,7 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
struct ci_hw_ep *hwep = container_of(ep, struct ci_hw_ep, ep);
struct ci_hw_req *hwreq = container_of(req, struct ci_hw_req, req);
unsigned long flags;
+ struct td_node *node, *tmpnode;
if (ep == NULL || req == NULL || hwreq->req.status != -EALREADY ||
hwep->ep.desc == NULL || list_empty(&hwreq->queue) ||
@@ -1338,6 +1332,12 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
+ list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
+ dma_pool_free(hwep->td_pool, node->ptr, node->dma);
+ list_del(&node->td);
+ kfree(node);
+ }
+
/* pop request */
list_del_init(&hwreq->queue);
@@ -1477,7 +1477,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
pm_runtime_get_sync(&_gadget->dev);
hw_device_reset(ci, USBMODE_CM_DC);
hw_device_state(ci, ci->ep0out->qh.dma);
- dev_dbg(ci->dev, "Connected to host\n");
+ usb_gadget_set_state(_gadget, USB_STATE_POWERED);
} else {
if (ci->driver)
ci->driver->disconnect(&ci->gadget);
@@ -1487,7 +1487,7 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
CI_HDRC_CONTROLLER_STOPPED_EVENT);
_gadget_stop_activity(&ci->gadget);
pm_runtime_put_sync(&_gadget->dev);
- dev_dbg(ci->dev, "Disconnected from host\n");
+ usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED);
}
}
@@ -1655,6 +1655,13 @@ static int ci_udc_start(struct usb_gadget *gadget,
return retval;
ci->driver = driver;
+
+ /* Start otg fsm for B-device */
+ if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) {
+ ci_hdrc_otg_fsm_start(ci);
+ return retval;
+ }
+
pm_runtime_get_sync(&ci->gadget.dev);
if (ci->vbus_active) {
spin_lock_irqsave(&ci->lock, flags);
@@ -1753,6 +1760,8 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
ci->suspended = 1;
spin_unlock(&ci->lock);
ci->driver->suspend(&ci->gadget);
+ usb_gadget_set_state(&ci->gadget,
+ USB_STATE_SUSPENDED);
spin_lock(&ci->lock);
}
}
@@ -1779,7 +1788,7 @@ static int udc_start(struct ci_hdrc *ci)
ci->gadget.ops = &usb_gadget_ops;
ci->gadget.speed = USB_SPEED_UNKNOWN;
ci->gadget.max_speed = USB_SPEED_HIGH;
- ci->gadget.is_otg = 0;
+ ci->gadget.is_otg = ci->is_otg ? 1 : 0;
ci->gadget.name = ci->platdata->name;
INIT_LIST_HEAD(&ci->gadget.ep_list);
@@ -1843,21 +1852,22 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci)
static int udc_id_switch_for_device(struct ci_hdrc *ci)
{
- if (ci->is_otg) {
- ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
- ci_enable_otg_interrupt(ci, OTGSC_BSVIE);
- }
+ if (ci->is_otg)
+ /* Clear and enable BSV irq */
+ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
+ OTGSC_BSVIS | OTGSC_BSVIE);
return 0;
}
static void udc_id_switch_for_host(struct ci_hdrc *ci)
{
- if (ci->is_otg) {
- /* host doesn't care B_SESSION_VALID event */
- ci_clear_otg_interrupt(ci, OTGSC_BSVIS);
- ci_disable_otg_interrupt(ci, OTGSC_BSVIE);
- }
+ /*
+ * host doesn't care B_SESSION_VALID event
+ * so clear and disbale BSV irq
+ */
+ if (ci->is_otg)
+ hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
}
/**
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index cd061abe3507..85293b8b1df9 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -21,16 +21,39 @@
#define MX25_USB_PHY_CTRL_OFFSET 0x08
#define MX25_BM_EXTERNAL_VBUS_DIVIDER BIT(23)
+#define MX25_EHCI_INTERFACE_SINGLE_UNI (2 << 0)
+#define MX25_EHCI_INTERFACE_DIFF_UNI (0 << 0)
+#define MX25_EHCI_INTERFACE_MASK (0xf)
+
+#define MX25_OTG_SIC_SHIFT 29
+#define MX25_OTG_SIC_MASK (0x3 << MX25_OTG_SIC_SHIFT)
+#define MX25_OTG_PM_BIT BIT(24)
+#define MX25_OTG_PP_BIT BIT(11)
+#define MX25_OTG_OCPOL_BIT BIT(3)
+
+#define MX25_H1_SIC_SHIFT 21
+#define MX25_H1_SIC_MASK (0x3 << MX25_H1_SIC_SHIFT)
+#define MX25_H1_PP_BIT BIT(18)
+#define MX25_H1_PM_BIT BIT(16)
+#define MX25_H1_IPPUE_UP_BIT BIT(7)
+#define MX25_H1_IPPUE_DOWN_BIT BIT(6)
+#define MX25_H1_TLL_BIT BIT(5)
+#define MX25_H1_USBTE_BIT BIT(4)
+#define MX25_H1_OCPOL_BIT BIT(2)
+
#define MX27_H1_PM_BIT BIT(8)
#define MX27_H2_PM_BIT BIT(16)
#define MX27_OTG_PM_BIT BIT(24)
#define MX53_USB_OTG_PHY_CTRL_0_OFFSET 0x08
+#define MX53_USB_OTG_PHY_CTRL_1_OFFSET 0x0c
#define MX53_USB_UH2_CTRL_OFFSET 0x14
#define MX53_USB_UH3_CTRL_OFFSET 0x18
#define MX53_BM_OVER_CUR_DIS_H1 BIT(5)
#define MX53_BM_OVER_CUR_DIS_OTG BIT(8)
#define MX53_BM_OVER_CUR_DIS_UHx BIT(30)
+#define MX53_USB_PHYCTRL1_PLLDIV_MASK 0x3
+#define MX53_USB_PLL_DIV_24_MHZ 0x01
#define MX6_BM_OVER_CUR_DIS BIT(7)
@@ -50,6 +73,39 @@ struct imx_usbmisc {
static struct imx_usbmisc *usbmisc;
+static int usbmisc_imx25_init(struct imx_usbmisc_data *data)
+{
+ unsigned long flags;
+ u32 val = 0;
+
+ if (data->index > 1)
+ return -EINVAL;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ switch (data->index) {
+ case 0:
+ val = readl(usbmisc->base);
+ val &= ~(MX25_OTG_SIC_MASK | MX25_OTG_PP_BIT);
+ val |= (MX25_EHCI_INTERFACE_DIFF_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_OTG_SIC_SHIFT;
+ val |= (MX25_OTG_PM_BIT | MX25_OTG_OCPOL_BIT);
+ writel(val, usbmisc->base);
+ break;
+ case 1:
+ val = readl(usbmisc->base);
+ val &= ~(MX25_H1_SIC_MASK | MX25_H1_PP_BIT | MX25_H1_IPPUE_UP_BIT);
+ val |= (MX25_EHCI_INTERFACE_SINGLE_UNI & MX25_EHCI_INTERFACE_MASK) << MX25_H1_SIC_SHIFT;
+ val |= (MX25_H1_PM_BIT | MX25_H1_OCPOL_BIT | MX25_H1_TLL_BIT |
+ MX25_H1_USBTE_BIT | MX25_H1_IPPUE_DOWN_BIT);
+
+ writel(val, usbmisc->base);
+
+ break;
+ }
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+
+ return 0;
+}
+
static int usbmisc_imx25_post(struct imx_usbmisc_data *data)
{
void __iomem *reg;
@@ -111,6 +167,13 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data)
if (data->index > 3)
return -EINVAL;
+ /* Select a 24 MHz reference clock for the PHY */
+ reg = usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET;
+ val = readl(reg);
+ val &= ~MX53_USB_PHYCTRL1_PLLDIV_MASK;
+ val |= MX53_USB_PLL_DIV_24_MHZ;
+ writel(val, usbmisc->base + MX53_USB_OTG_PHY_CTRL_1_OFFSET);
+
if (data->disable_oc) {
spin_lock_irqsave(&usbmisc->lock, flags);
switch (data->index) {
@@ -159,6 +222,7 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data)
}
static const struct usbmisc_ops imx25_usbmisc_ops = {
+ .init = usbmisc_imx25_init,
.post = usbmisc_imx25_post,
};
@@ -200,6 +264,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = {
.data = &imx25_usbmisc_ops,
},
{
+ .compatible = "fsl,imx35-usbmisc",
+ .data = &imx25_usbmisc_ops,
+ },
+ {
.compatible = "fsl,imx27-usbmisc",
.data = &imx27_usbmisc_ops,
},