summaryrefslogtreecommitdiffstats
path: root/drivers/usb/mtu3
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/mtu3')
-rw-r--r--drivers/usb/mtu3/mtu3.h8
-rw-r--r--drivers/usb/mtu3/mtu3_core.c119
-rw-r--r--drivers/usb/mtu3/mtu3_dr.c26
-rw-r--r--drivers/usb/mtu3/mtu3_dr.h30
-rw-r--r--drivers/usb/mtu3/mtu3_gadget.c11
-rw-r--r--drivers/usb/mtu3/mtu3_host.c108
-rw-r--r--drivers/usb/mtu3/mtu3_plat.c170
7 files changed, 402 insertions, 70 deletions
diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h
index 5546b868b08b..022bbdc54e68 100644
--- a/drivers/usb/mtu3/mtu3.h
+++ b/drivers/usb/mtu3/mtu3.h
@@ -199,6 +199,7 @@ struct mtu3_gpd_ring {
* @id_nb : notifier for iddig(idpin) detection
* @dr_work : work for drd mode switch, used to avoid sleep in atomic context
* @desired_role : role desired to switch
+* @default_role : default mode while usb role is USB_ROLE_NONE
* @role_sw : use USB Role Switch to support dual-role switch, can't use
* extcon at the same time, and extcon is deprecated.
* @role_sw_used : true when the USB Role Switch is used.
@@ -212,6 +213,7 @@ struct otg_switch_mtk {
struct notifier_block id_nb;
struct work_struct dr_work;
enum usb_role desired_role;
+ enum usb_role default_role;
struct usb_role_switch *role_sw;
bool role_sw_used;
bool is_u3_drd;
@@ -226,6 +228,9 @@ struct otg_switch_mtk {
* host only, device only or dual-role mode
* @u2_ports: number of usb2.0 host ports
* @u3_ports: number of usb3.0 host ports
+ * @u2p_dis_msk: mask of disabling usb2 ports, e.g. bit0==1 to
+ * disable u2port0, bit1==1 to disable u2port1,... etc,
+ * but when use dual-role mode, can't disable u2port0
* @u3p_dis_msk: mask of disabling usb3 ports, for example, bit0==1 to
* disable u3port0, bit1==1 to disable u3port1,... etc
* @dbgfs_root: only used when supports manual dual-role switch via debugfs
@@ -241,6 +246,7 @@ struct ssusb_mtk {
void __iomem *ippc_base;
struct phy **phys;
int num_phys;
+ int wakeup_irq;
/* common power & clock */
struct regulator *vusb33;
struct clk_bulk_data clks[BULK_CLKS_CNT];
@@ -250,6 +256,7 @@ struct ssusb_mtk {
bool is_host;
int u2_ports;
int u3_ports;
+ int u2p_dis_msk;
int u3p_dis_msk;
struct dentry *dbgfs_root;
/* usb wakeup for host mode */
@@ -349,6 +356,7 @@ struct mtu3 {
unsigned is_u3_ip:1;
unsigned delayed_status:1;
unsigned gen2cp:1;
+ unsigned connected:1;
u8 address;
u8 test_mode_nr;
diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
index 562f4357831e..c4a2c37abf62 100644
--- a/drivers/usb/mtu3/mtu3_core.c
+++ b/drivers/usb/mtu3/mtu3_core.c
@@ -141,6 +141,28 @@ static void mtu3_device_disable(struct mtu3 *mtu)
mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
}
+static void mtu3_dev_power_on(struct mtu3 *mtu)
+{
+ void __iomem *ibase = mtu->ippc_base;
+
+ mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+ if (mtu->is_u3_ip)
+ mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+
+ mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
+}
+
+static void mtu3_dev_power_down(struct mtu3 *mtu)
+{
+ void __iomem *ibase = mtu->ippc_base;
+
+ if (mtu->is_u3_ip)
+ mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+
+ mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
+ mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+}
+
/* reset U3D's device module. */
static void mtu3_device_reset(struct mtu3 *mtu)
{
@@ -227,11 +249,13 @@ static void mtu3_set_speed(struct mtu3 *mtu, enum usb_device_speed speed)
mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
break;
case USB_SPEED_SUPER:
+ mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
SSUSB_U3_PORT_SSP_SPEED);
break;
case USB_SPEED_SUPER_PLUS:
- mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
+ mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
+ mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0),
SSUSB_U3_PORT_SSP_SPEED);
break;
default:
@@ -333,12 +357,7 @@ void mtu3_start(struct mtu3 *mtu)
dev_dbg(mtu->dev, "%s devctl 0x%x\n", __func__,
mtu3_readl(mbase, U3D_DEVICE_CONTROL));
- mtu3_clrbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
- if (mtu->is_u3_ip)
- mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
-
- mtu3_clrbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
-
+ mtu3_dev_power_on(mtu);
mtu3_csr_init(mtu);
mtu3_set_speed(mtu, mtu->speed);
@@ -360,12 +379,25 @@ void mtu3_stop(struct mtu3 *mtu)
mtu3_dev_on_off(mtu, 0);
mtu->is_active = 0;
+ mtu3_dev_power_down(mtu);
+}
- if (mtu->is_u3_ip)
- mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN);
+static void mtu3_dev_suspend(struct mtu3 *mtu)
+{
+ if (!mtu->is_active)
+ return;
- mtu3_setbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN);
- mtu3_setbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
+ mtu3_intr_disable(mtu);
+ mtu3_dev_power_down(mtu);
+}
+
+static void mtu3_dev_resume(struct mtu3 *mtu)
+{
+ if (!mtu->is_active)
+ return;
+
+ mtu3_dev_power_on(mtu);
+ mtu3_intr_enable(mtu);
}
/* for non-ep0 */
@@ -688,11 +720,15 @@ static irqreturn_t mtu3_link_isr(struct mtu3 *mtu)
mtu->g.speed = udev_speed;
mtu->g.ep0->maxpacket = maxpkt;
mtu->ep0_state = MU3D_EP0_STATE_SETUP;
+ mtu->connected = !!(udev_speed != USB_SPEED_UNKNOWN);
- if (udev_speed == USB_SPEED_UNKNOWN)
+ if (udev_speed == USB_SPEED_UNKNOWN) {
mtu3_gadget_disconnect(mtu);
- else
+ pm_runtime_put(mtu->dev);
+ } else {
+ pm_runtime_get(mtu->dev);
mtu3_ep0_setup(mtu);
+ }
return IRQ_HANDLED;
}
@@ -888,9 +924,16 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb)
if (mtu == NULL)
return -ENOMEM;
- mtu->irq = platform_get_irq(pdev, 0);
- if (mtu->irq < 0)
- return mtu->irq;
+ mtu->irq = platform_get_irq_byname_optional(pdev, "device");
+ if (mtu->irq < 0) {
+ if (mtu->irq == -EPROBE_DEFER)
+ return mtu->irq;
+
+ /* for backward compatibility */
+ mtu->irq = platform_get_irq(pdev, 0);
+ if (mtu->irq < 0)
+ return mtu->irq;
+ }
dev_info(dev, "irq %d\n", mtu->irq);
mtu->mac_base = devm_platform_ioremap_resource_byname(pdev, "mac");
@@ -965,3 +1008,47 @@ void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
device_init_wakeup(ssusb->dev, false);
mtu3_hw_exit(mtu);
}
+
+bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb)
+{
+ struct mtu3 *mtu = ssusb->u3d;
+
+ /* host only, should wait for ip sleep */
+ if (!mtu)
+ return true;
+
+ /* device is started and pullup D+, ip can sleep */
+ if (mtu->is_active && mtu->softconnect)
+ return true;
+
+ /* ip can't sleep if not pullup D+ when support device mode */
+ return false;
+}
+
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+ struct mtu3 *mtu = ssusb->u3d;
+
+ if (!mtu->gadget_driver)
+ return 0;
+
+ if (mtu->connected)
+ return -EBUSY;
+
+ mtu3_dev_suspend(mtu);
+ synchronize_irq(mtu->irq);
+
+ return 0;
+}
+
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+ struct mtu3 *mtu = ssusb->u3d;
+
+ if (!mtu->gadget_driver)
+ return 0;
+
+ mtu3_dev_resume(mtu);
+
+ return 0;
+}
diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c
index 318fbc618137..a6b04831b20b 100644
--- a/drivers/usb/mtu3/mtu3_dr.c
+++ b/drivers/usb/mtu3/mtu3_dr.c
@@ -137,14 +137,19 @@ static void ssusb_mode_sw_work(struct work_struct *work)
current_role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE;
- if (desired_role == USB_ROLE_NONE)
+ if (desired_role == USB_ROLE_NONE) {
+ /* the default mode is host as probe does */
desired_role = USB_ROLE_HOST;
+ if (otg_sx->default_role == USB_ROLE_DEVICE)
+ desired_role = USB_ROLE_DEVICE;
+ }
if (current_role == desired_role)
return;
dev_dbg(ssusb->dev, "set role : %s\n", usb_role_string(desired_role));
mtu3_dbg_trace(ssusb->dev, "set role : %s", usb_role_string(desired_role));
+ pm_runtime_get_sync(ssusb->dev);
switch (desired_role) {
case USB_ROLE_HOST:
@@ -165,6 +170,7 @@ static void ssusb_mode_sw_work(struct work_struct *work)
default:
dev_err(ssusb->dev, "invalid role\n");
}
+ pm_runtime_put(ssusb->dev);
}
static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role)
@@ -274,17 +280,29 @@ static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx)
{
struct usb_role_switch_desc role_sx_desc = { 0 };
struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx);
+ struct device *dev = ssusb->dev;
+ enum usb_dr_mode mode;
if (!otg_sx->role_sw_used)
return 0;
+ mode = usb_get_role_switch_default_mode(dev);
+ if (mode == USB_DR_MODE_PERIPHERAL)
+ otg_sx->default_role = USB_ROLE_DEVICE;
+ else
+ otg_sx->default_role = USB_ROLE_HOST;
+
role_sx_desc.set = ssusb_role_sw_set;
role_sx_desc.get = ssusb_role_sw_get;
- role_sx_desc.fwnode = dev_fwnode(ssusb->dev);
+ role_sx_desc.fwnode = dev_fwnode(dev);
role_sx_desc.driver_data = ssusb;
- otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc);
+ otg_sx->role_sw = usb_role_switch_register(dev, &role_sx_desc);
+ if (IS_ERR(otg_sx->role_sw))
+ return PTR_ERR(otg_sx->role_sw);
- return PTR_ERR_OR_ZERO(otg_sx->role_sw);
+ ssusb_set_mode(otg_sx, otg_sx->default_role);
+
+ return 0;
}
int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
diff --git a/drivers/usb/mtu3/mtu3_dr.h b/drivers/usb/mtu3/mtu3_dr.h
index 760fe7d69c6b..e325508bddf4 100644
--- a/drivers/usb/mtu3/mtu3_dr.h
+++ b/drivers/usb/mtu3/mtu3_dr.h
@@ -16,8 +16,8 @@ int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn);
void ssusb_host_exit(struct ssusb_mtk *ssusb);
int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb,
struct device_node *dn);
-int ssusb_host_enable(struct ssusb_mtk *ssusb);
-int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend);
+int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped);
+int ssusb_host_suspend(struct ssusb_mtk *ssusb);
void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable);
#else
@@ -38,12 +38,12 @@ static inline int ssusb_wakeup_of_property_parse(
return 0;
}
-static inline int ssusb_host_enable(struct ssusb_mtk *ssusb)
+static inline int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped)
{
return 0;
}
-static inline int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
+static inline int ssusb_host_suspend(struct ssusb_mtk *ssusb)
{
return 0;
}
@@ -57,6 +57,10 @@ static inline void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable)
#if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
int ssusb_gadget_init(struct ssusb_mtk *ssusb);
void ssusb_gadget_exit(struct ssusb_mtk *ssusb);
+int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg);
+int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg);
+bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb);
+
#else
static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
{
@@ -65,6 +69,24 @@ static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb)
static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
{}
+
+static inline int
+ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+ return 0;
+}
+
+static inline int
+ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+ return 0;
+}
+
+static inline bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb)
+{
+ return true;
+}
+
#endif
diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c
index 5e21ba05ebf0..a9a65b4bbfed 100644
--- a/drivers/usb/mtu3/mtu3_gadget.c
+++ b/drivers/usb/mtu3/mtu3_gadget.c
@@ -64,14 +64,12 @@ static int mtu3_ep_enable(struct mtu3_ep *mep)
u32 interval = 0;
u32 mult = 0;
u32 burst = 0;
- int max_packet;
int ret;
desc = mep->desc;
comp_desc = mep->comp_desc;
mep->type = usb_endpoint_type(desc);
- max_packet = usb_endpoint_maxp(desc);
- mep->maxp = max_packet & GENMASK(10, 0);
+ mep->maxp = usb_endpoint_maxp(desc);
switch (mtu->g.speed) {
case USB_SPEED_SUPER:
@@ -92,7 +90,7 @@ static int mtu3_ep_enable(struct mtu3_ep *mep)
usb_endpoint_xfer_int(desc)) {
interval = desc->bInterval;
interval = clamp_val(interval, 1, 16) - 1;
- burst = (max_packet & GENMASK(12, 11)) >> 11;
+ mult = usb_endpoint_maxp_mult(desc) - 1;
}
break;
default:
@@ -469,6 +467,8 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__,
is_on ? "on" : "off", mtu->is_active ? "" : "in");
+ pm_runtime_get_sync(mtu->dev);
+
/* we'd rather not pullup unless the device is active. */
spin_lock_irqsave(&mtu->lock, flags);
@@ -482,6 +482,7 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on)
}
spin_unlock_irqrestore(&mtu->lock, flags);
+ pm_runtime_put(mtu->dev);
return 0;
}
@@ -499,6 +500,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
}
dev_dbg(mtu->dev, "bind driver %s\n", driver->function);
+ pm_runtime_get_sync(mtu->dev);
spin_lock_irqsave(&mtu->lock, flags);
@@ -509,6 +511,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
mtu3_start(mtu);
spin_unlock_irqrestore(&mtu->lock, flags);
+ pm_runtime_put(mtu->dev);
return 0;
}
diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c
index 93a1a4c11e1e..f3903367a6a0 100644
--- a/drivers/usb/mtu3/mtu3_host.c
+++ b/drivers/usb/mtu3/mtu3_host.c
@@ -8,7 +8,6 @@
*/
#include <linux/clk.h>
-#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
@@ -63,7 +62,7 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable)
case SSUSB_UWK_V1_1:
reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P;
- val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0;
+ val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0;
break;
case SSUSB_UWK_V1_2:
reg = ssusb->uwk_reg_base + PERI_WK_CTRL0;
@@ -126,7 +125,7 @@ static void host_ports_num_get(struct ssusb_mtk *ssusb)
}
/* only configure ports will be used later */
-int ssusb_host_enable(struct ssusb_mtk *ssusb)
+static int ssusb_host_enable(struct ssusb_mtk *ssusb)
{
void __iomem *ibase = ssusb->ippc_base;
int num_u3p = ssusb->u3_ports;
@@ -155,6 +154,9 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb)
/* power on and enable all u2 ports */
for (i = 0; i < num_u2p; i++) {
+ if ((0x1 << i) & ssusb->u2p_dis_msk)
+ continue;
+
value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS);
value |= SSUSB_U2_PORT_HOST_SEL;
@@ -168,13 +170,12 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb)
return ssusb_check_clocks(ssusb, check_clk);
}
-int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
+static int ssusb_host_disable(struct ssusb_mtk *ssusb)
{
void __iomem *ibase = ssusb->ippc_base;
int num_u3p = ssusb->u3_ports;
int num_u2p = ssusb->u2_ports;
u32 value;
- int ret;
int i;
/* power down and disable u3 ports except skipped ones */
@@ -183,32 +184,101 @@ int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend)
continue;
value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
- value |= SSUSB_U3_PORT_PDN;
- value |= suspend ? 0 : SSUSB_U3_PORT_DIS;
+ value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS;
mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
}
- /* power down and disable all u2 ports */
+ /* power down and disable u2 ports except skipped ones */
for (i = 0; i < num_u2p; i++) {
+ if ((0x1 << i) & ssusb->u2p_dis_msk)
+ continue;
+
value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
- value |= SSUSB_U2_PORT_PDN;
- value |= suspend ? 0 : SSUSB_U2_PORT_DIS;
+ value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS;
mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
}
/* power down host ip */
mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
- if (!suspend)
- return 0;
+ return 0;
+}
- /* wait for host ip to sleep */
- ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value,
- (value & SSUSB_IP_SLEEP_STS), 100, 100000);
- if (ret)
- dev_err(ssusb->dev, "ip sleep failed!!!\n");
+int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped)
+{
+ void __iomem *ibase = ssusb->ippc_base;
+ int u3p_skip_msk = ssusb->u3p_dis_msk;
+ int u2p_skip_msk = ssusb->u2p_dis_msk;
+ int num_u3p = ssusb->u3_ports;
+ int num_u2p = ssusb->u2_ports;
+ u32 value;
+ int i;
- return ret;
+ if (p0_skipped) {
+ u2p_skip_msk |= 0x1;
+ if (ssusb->otg_switch.is_u3_drd)
+ u3p_skip_msk |= 0x1;
+ }
+
+ /* power on host ip */
+ mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
+
+ /* power on u3 ports except skipped ones */
+ for (i = 0; i < num_u3p; i++) {
+ if ((0x1 << i) & u3p_skip_msk)
+ continue;
+
+ value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
+ value &= ~SSUSB_U3_PORT_PDN;
+ mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
+ }
+
+ /* power on all u2 ports except skipped ones */
+ for (i = 0; i < num_u2p; i++) {
+ if ((0x1 << i) & u2p_skip_msk)
+ continue;
+
+ value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
+ value &= ~SSUSB_U2_PORT_PDN;
+ mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
+ }
+
+ return 0;
+}
+
+/* here not skip port0 due to PDN can be set repeatedly */
+int ssusb_host_suspend(struct ssusb_mtk *ssusb)
+{
+ void __iomem *ibase = ssusb->ippc_base;
+ int num_u3p = ssusb->u3_ports;
+ int num_u2p = ssusb->u2_ports;
+ u32 value;
+ int i;
+
+ /* power down u3 ports except skipped ones */
+ for (i = 0; i < num_u3p; i++) {
+ if ((0x1 << i) & ssusb->u3p_dis_msk)
+ continue;
+
+ value = mtu3_readl(ibase, SSUSB_U3_CTRL(i));
+ value |= SSUSB_U3_PORT_PDN;
+ mtu3_writel(ibase, SSUSB_U3_CTRL(i), value);
+ }
+
+ /* power down u2 ports except skipped ones */
+ for (i = 0; i < num_u2p; i++) {
+ if ((0x1 << i) & ssusb->u2p_dis_msk)
+ continue;
+
+ value = mtu3_readl(ibase, SSUSB_U2_CTRL(i));
+ value |= SSUSB_U2_PORT_PDN;
+ mtu3_writel(ibase, SSUSB_U2_CTRL(i), value);
+ }
+
+ /* power down host ip */
+ mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN);
+
+ return 0;
}
static void ssusb_host_setup(struct ssusb_mtk *ssusb)
@@ -231,7 +301,7 @@ static void ssusb_host_cleanup(struct ssusb_mtk *ssusb)
if (ssusb->is_host)
ssusb_set_vbus(&ssusb->otg_switch, 0);
- ssusb_host_disable(ssusb, false);
+ ssusb_host_disable(ssusb);
}
/*
diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c
index c0615f6e5cce..f13531022f4a 100644
--- a/drivers/usb/mtu3/mtu3_plat.c
+++ b/drivers/usb/mtu3/mtu3_plat.c
@@ -12,6 +12,7 @@
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
#include "mtu3.h"
#include "mtu3_dr.h"
@@ -44,6 +45,32 @@ int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks)
return 0;
}
+static int wait_for_ip_sleep(struct ssusb_mtk *ssusb)
+{
+ bool sleep_check = true;
+ u32 value;
+ int ret;
+
+ if (!ssusb->is_host)
+ sleep_check = ssusb_gadget_ip_sleep_check(ssusb);
+
+ if (!sleep_check)
+ return 0;
+
+ /* wait for ip enter sleep mode */
+ ret = readl_poll_timeout(ssusb->ippc_base + U3D_SSUSB_IP_PW_STS1, value,
+ (value & SSUSB_IP_SLEEP_STS), 100, 100000);
+ if (ret) {
+ dev_err(ssusb->dev, "ip sleep failed!!!\n");
+ ret = -EBUSY;
+ } else {
+ /* workaround: avoid wrong wakeup signal latch for some soc */
+ usleep_range(100, 200);
+ }
+
+ return ret;
+}
+
static int ssusb_phy_init(struct ssusb_mtk *ssusb)
{
int i;
@@ -208,6 +235,10 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
if (IS_ERR(ssusb->ippc_base))
return PTR_ERR(ssusb->ippc_base);
+ ssusb->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
+ if (ssusb->wakeup_irq == -EPROBE_DEFER)
+ return ssusb->wakeup_irq;
+
ssusb->dr_mode = usb_get_dr_mode(dev);
if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN)
ssusb->dr_mode = USB_DR_MODE_OTG;
@@ -225,6 +256,8 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
/* optional property, ignore the error if it does not exist */
of_property_read_u32(node, "mediatek,u3p-dis-msk",
&ssusb->u3p_dis_msk);
+ of_property_read_u32(node, "mediatek,u2p-dis-msk",
+ &ssusb->u2p_dis_msk);
otg_sx->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(otg_sx->vbus)) {
@@ -241,6 +274,9 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
of_property_read_bool(node, "enable-manual-drd");
otg_sx->role_sw_used = of_property_read_bool(node, "usb-role-switch");
+ /* can't disable port0 when use dual-role mode */
+ ssusb->u2p_dis_msk &= ~0x1;
+
if (otg_sx->role_sw_used || otg_sx->manual_drd_enabled)
goto out;
@@ -253,9 +289,11 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
}
out:
- dev_info(dev, "dr_mode: %d, is_u3_dr: %d, u3p_dis_msk: %x, drd: %s\n",
- ssusb->dr_mode, otg_sx->is_u3_drd, ssusb->u3p_dis_msk,
+ dev_info(dev, "dr_mode: %d, is_u3_dr: %d, drd: %s\n",
+ ssusb->dr_mode, otg_sx->is_u3_drd,
otg_sx->manual_drd_enabled ? "manual" : "auto");
+ dev_info(dev, "u2p_dis_msk: %x, u3p_dis_msk: %x\n",
+ ssusb->u2p_dis_msk, ssusb->u3p_dis_msk);
return 0;
}
@@ -288,14 +326,25 @@ static int mtu3_probe(struct platform_device *pdev)
ssusb_debugfs_create_root(ssusb);
/* enable power domain */
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, 4000);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
- device_enable_async_suspend(dev);
ret = ssusb_rscs_init(ssusb);
if (ret)
goto comm_init_err;
+ if (ssusb->wakeup_irq > 0) {
+ ret = dev_pm_set_dedicated_wake_irq(dev, ssusb->wakeup_irq);
+ if (ret) {
+ dev_err(dev, "failed to set wakeup irq %d\n", ssusb->wakeup_irq);
+ goto comm_exit;
+ }
+ dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq);
+ }
+
ssusb_ip_sw_reset(ssusb);
if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
@@ -346,6 +395,11 @@ static int mtu3_probe(struct platform_device *pdev)
goto comm_exit;
}
+ device_enable_async_suspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ pm_runtime_forbid(dev);
+
return 0;
host_exit:
@@ -355,7 +409,7 @@ gadget_exit:
comm_exit:
ssusb_rscs_exit(ssusb);
comm_init_err:
- pm_runtime_put_sync(dev);
+ pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
ssusb_debugfs_remove_root(ssusb);
@@ -366,6 +420,8 @@ static int mtu3_remove(struct platform_device *pdev)
{
struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);
+ pm_runtime_get_sync(&pdev->dev);
+
switch (ssusb->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
ssusb_gadget_exit(ssusb);
@@ -383,45 +439,87 @@ static int mtu3_remove(struct platform_device *pdev)
}
ssusb_rscs_exit(ssusb);
- pm_runtime_put_sync(&pdev->dev);
- pm_runtime_disable(&pdev->dev);
ssusb_debugfs_remove_root(ssusb);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
return 0;
}
-/*
- * when support dual-role mode, we reject suspend when
- * it works as device mode;
- */
-static int __maybe_unused mtu3_suspend(struct device *dev)
+static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg)
+{
+ switch (ssusb->dr_mode) {
+ case USB_DR_MODE_PERIPHERAL:
+ ssusb_gadget_resume(ssusb, msg);
+ break;
+ case USB_DR_MODE_HOST:
+ ssusb_host_resume(ssusb, false);
+ break;
+ case USB_DR_MODE_OTG:
+ ssusb_host_resume(ssusb, !ssusb->is_host);
+ if (!ssusb->is_host)
+ ssusb_gadget_resume(ssusb, msg);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtu3_suspend_common(struct device *dev, pm_message_t msg)
{
struct ssusb_mtk *ssusb = dev_get_drvdata(dev);
+ int ret = 0;
dev_dbg(dev, "%s\n", __func__);
- /* REVISIT: disconnect it for only device mode? */
- if (!ssusb->is_host)
- return 0;
+ switch (ssusb->dr_mode) {
+ case USB_DR_MODE_PERIPHERAL:
+ ret = ssusb_gadget_suspend(ssusb, msg);
+ if (ret)
+ goto err;
+
+ break;
+ case USB_DR_MODE_HOST:
+ ssusb_host_suspend(ssusb);
+ break;
+ case USB_DR_MODE_OTG:
+ if (!ssusb->is_host) {
+ ret = ssusb_gadget_suspend(ssusb, msg);
+ if (ret)
+ goto err;
+ }
+ ssusb_host_suspend(ssusb);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = wait_for_ip_sleep(ssusb);
+ if (ret)
+ goto sleep_err;
- ssusb_host_disable(ssusb, true);
ssusb_phy_power_off(ssusb);
clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
ssusb_wakeup_set(ssusb, true);
-
return 0;
+
+sleep_err:
+ resume_ip_and_ports(ssusb, msg);
+err:
+ return ret;
}
-static int __maybe_unused mtu3_resume(struct device *dev)
+static int mtu3_resume_common(struct device *dev, pm_message_t msg)
{
struct ssusb_mtk *ssusb = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "%s\n", __func__);
- if (!ssusb->is_host)
- return 0;
-
ssusb_wakeup_set(ssusb, false);
ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks);
if (ret)
@@ -431,9 +529,7 @@ static int __maybe_unused mtu3_resume(struct device *dev)
if (ret)
goto phy_err;
- ssusb_host_enable(ssusb);
-
- return 0;
+ return resume_ip_and_ports(ssusb, msg);
phy_err:
clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks);
@@ -441,8 +537,36 @@ clks_err:
return ret;
}
+static int __maybe_unused mtu3_suspend(struct device *dev)
+{
+ return mtu3_suspend_common(dev, PMSG_SUSPEND);
+}
+
+static int __maybe_unused mtu3_resume(struct device *dev)
+{
+ return mtu3_resume_common(dev, PMSG_SUSPEND);
+}
+
+static int __maybe_unused mtu3_runtime_suspend(struct device *dev)
+{
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ return mtu3_suspend_common(dev, PMSG_AUTO_SUSPEND);
+}
+
+static int __maybe_unused mtu3_runtime_resume(struct device *dev)
+{
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ return mtu3_resume_common(dev, PMSG_AUTO_SUSPEND);
+}
+
static const struct dev_pm_ops mtu3_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mtu3_suspend, mtu3_resume)
+ SET_RUNTIME_PM_OPS(mtu3_runtime_suspend,
+ mtu3_runtime_resume, NULL)
};
#define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL)