Commit d0ed062a authored by Chunfeng Yun's avatar Chunfeng Yun Committed by Greg Kroah-Hartman

usb: mtu3: dual-role mode support

support dual-role mode; there are two ways to switch between
host and device modes, one is by idpin, another is by debugfs
which depends on user input.
Signed-off-by: default avatarChunfeng Yun <chunfeng.yun@mediatek.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b3f4e727
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
config USB_MTU3 config USB_MTU3
tristate "MediaTek USB3 Dual Role controller" tristate "MediaTek USB3 Dual Role controller"
depends on (USB || USB_GADGET) && HAS_DMA depends on EXTCON && (USB || USB_GADGET) && HAS_DMA
depends on ARCH_MEDIATEK || COMPILE_TEST depends on ARCH_MEDIATEK || COMPILE_TEST
select USB_XHCI_MTK if USB_SUPPORT && USB_XHCI_HCD select USB_XHCI_MTK if USB_SUPPORT && USB_XHCI_HCD
help help
...@@ -19,6 +19,7 @@ config USB_MTU3 ...@@ -19,6 +19,7 @@ config USB_MTU3
if USB_MTU3 if USB_MTU3
choice choice
bool "MTU3 Mode Selection" bool "MTU3 Mode Selection"
default USB_MTU3_DUAL_ROLE if (USB && USB_GADGET)
default USB_MTU3_HOST if (USB && !USB_GADGET) default USB_MTU3_HOST if (USB && !USB_GADGET)
default USB_MTU3_GADGET if (!USB && USB_GADGET) default USB_MTU3_GADGET if (!USB && USB_GADGET)
...@@ -36,6 +37,18 @@ config USB_MTU3_GADGET ...@@ -36,6 +37,18 @@ config USB_MTU3_GADGET
Select this when you want to use MTU3 in gadget mode only, Select this when you want to use MTU3 in gadget mode only,
thereby the host feature will be regressed. thereby the host feature will be regressed.
config USB_MTU3_DUAL_ROLE
bool "Dual Role mode"
depends on ((USB=y || USB=USB_MTU3) && (USB_GADGET=y || USB_GADGET=USB_MTU3))
help
This is the default mode of working of MTU3 controller where
both host and gadget features are enabled.
endchoice endchoice
config USB_MTU3_DEBUG
bool "Enable Debugging Messages"
help
Say Y here to enable debugging messages in the MTU3 Driver.
endif endif
ccflags-$(CONFIG_USB_MTU3_DEBUG) += -DDEBUG
obj-$(CONFIG_USB_MTU3) += mtu3.o obj-$(CONFIG_USB_MTU3) += mtu3.o
mtu3-y := mtu3_plat.o mtu3-y := mtu3_plat.o
ifneq ($(filter y,$(CONFIG_USB_MTU3_HOST)),) ifneq ($(filter y,$(CONFIG_USB_MTU3_HOST) $(CONFIG_USB_MTU3_DUAL_ROLE)),)
mtu3-y += mtu3_host.o mtu3-y += mtu3_host.o
endif endif
ifneq ($(filter y,$(CONFIG_USB_MTU3_GADGET)),) ifneq ($(filter y,$(CONFIG_USB_MTU3_GADGET) $(CONFIG_USB_MTU3_DUAL_ROLE)),)
mtu3-y += mtu3_core.o mtu3_gadget_ep0.o mtu3_gadget.o mtu3_qmu.o mtu3-y += mtu3_core.o mtu3_gadget_ep0.o mtu3_gadget.o mtu3_qmu.o
endif endif
ifneq ($(CONFIG_USB_MTU3_DUAL_ROLE),)
mtu3-y += mtu3_dr.o
endif
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/dmapool.h> #include <linux/dmapool.h>
#include <linux/extcon.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/phy/phy.h> #include <linux/phy/phy.h>
...@@ -172,15 +173,44 @@ struct mtu3_gpd_ring { ...@@ -172,15 +173,44 @@ struct mtu3_gpd_ring {
struct qmu_gpd *enqueue; struct qmu_gpd *enqueue;
struct qmu_gpd *dequeue; struct qmu_gpd *dequeue;
}; };
/**
* @vbus: vbus 5V used by host mode
* @edev: external connector used to detect vbus and iddig changes
* @vbus_nb: notifier for vbus detection
* @vbus_nb: notifier for iddig(idpin) detection
* @extcon_reg_dwork: delay work for extcon notifier register, waiting for
* xHCI driver initialization, it's necessary for system bootup
* as device.
* @is_u3_drd: whether port0 supports usb3.0 dual-role device or not
* @id_*: used to maually switch between host and device modes by idpin
* @manual_drd_enabled: it's true when supports dual-role device by debugfs
* to switch host/device modes depending on user input.
*/
struct otg_switch_mtk {
struct regulator *vbus;
struct extcon_dev *edev;
struct notifier_block vbus_nb;
struct notifier_block id_nb;
struct delayed_work extcon_reg_dwork;
bool is_u3_drd;
/* dual-role switch by debugfs */
struct pinctrl *id_pinctrl;
struct pinctrl_state *id_float;
struct pinctrl_state *id_ground;
bool manual_drd_enabled;
};
/** /**
* @mac_base: register base address of device MAC, exclude xHCI's * @mac_base: register base address of device MAC, exclude xHCI's
* @ippc_base: register base address of ip port controller interface (IPPC) * @ippc_base: register base address of IP Power and Clock interface (IPPC)
* @vusb33: usb3.3V shared by device/host IP * @vusb33: usb3.3V shared by device/host IP
* @sys_clk: system clock of mtu3, shared by device/host IP * @sys_clk: system clock of mtu3, shared by device/host IP
* @dr_mode: works in which mode: * @dr_mode: works in which mode:
* host only, device only or dual-role mode * host only, device only or dual-role mode
* @u2_ports: number of usb2.0 host ports * @u2_ports: number of usb2.0 host ports
* @u3_ports: number of usb3.0 host ports * @u3_ports: number of usb3.0 host ports
* @dbgfs_root: only used when supports manual dual-role switch via debugfs
* @wakeup_en: it's true when supports remote wakeup in host mode * @wakeup_en: it's true when supports remote wakeup in host mode
* @wk_deb_p0: port0's wakeup debounce clock * @wk_deb_p0: port0's wakeup debounce clock
* @wk_deb_p1: it's optional, and depends on port1 is supported or not * @wk_deb_p1: it's optional, and depends on port1 is supported or not
...@@ -196,10 +226,12 @@ struct ssusb_mtk { ...@@ -196,10 +226,12 @@ struct ssusb_mtk {
struct regulator *vusb33; struct regulator *vusb33;
struct clk *sys_clk; struct clk *sys_clk;
/* otg */ /* otg */
struct otg_switch_mtk otg_switch;
enum usb_dr_mode dr_mode; enum usb_dr_mode dr_mode;
bool is_host; bool is_host;
int u2_ports; int u2_ports;
int u3_ports; int u3_ports;
struct dentry *dbgfs_root;
/* usb wakeup for host mode */ /* usb wakeup for host mode */
bool wakeup_en; bool wakeup_en;
struct clk *wk_deb_p0; struct clk *wk_deb_p0;
......
...@@ -150,7 +150,6 @@ static void mtu3_intr_disable(struct mtu3 *mtu) ...@@ -150,7 +150,6 @@ static void mtu3_intr_disable(struct mtu3 *mtu)
/* Disable level 1 interrupts */ /* Disable level 1 interrupts */
mtu3_writel(mbase, U3D_LV1IECR, ~0x0); mtu3_writel(mbase, U3D_LV1IECR, ~0x0);
/* Disable endpoint interrupts */ /* Disable endpoint interrupts */
mtu3_writel(mbase, U3D_EPIECR, ~0x0); mtu3_writel(mbase, U3D_EPIECR, ~0x0);
} }
...@@ -161,13 +160,10 @@ static void mtu3_intr_status_clear(struct mtu3 *mtu) ...@@ -161,13 +160,10 @@ static void mtu3_intr_status_clear(struct mtu3 *mtu)
/* Clear EP0 and Tx/Rx EPn interrupts status */ /* Clear EP0 and Tx/Rx EPn interrupts status */
mtu3_writel(mbase, U3D_EPISR, ~0x0); mtu3_writel(mbase, U3D_EPISR, ~0x0);
/* Clear U2 USB common interrupts status */ /* Clear U2 USB common interrupts status */
mtu3_writel(mbase, U3D_COMMON_USB_INTR, ~0x0); mtu3_writel(mbase, U3D_COMMON_USB_INTR, ~0x0);
/* Clear U3 LTSSM interrupts status */ /* Clear U3 LTSSM interrupts status */
mtu3_writel(mbase, U3D_LTSSM_INTR, ~0x0); mtu3_writel(mbase, U3D_LTSSM_INTR, ~0x0);
/* Clear speed change interrupt status */ /* Clear speed change interrupt status */
mtu3_writel(mbase, U3D_DEV_LINK_INTR, ~0x0); mtu3_writel(mbase, U3D_DEV_LINK_INTR, ~0x0);
} }
...@@ -268,7 +264,6 @@ void mtu3_start(struct mtu3 *mtu) ...@@ -268,7 +264,6 @@ void mtu3_start(struct mtu3 *mtu)
/* Initialize the default interrupts */ /* Initialize the default interrupts */
mtu3_intr_enable(mtu); mtu3_intr_enable(mtu);
mtu->is_active = 1; mtu->is_active = 1;
if (mtu->softconnect) if (mtu->softconnect)
...@@ -516,7 +511,6 @@ static int mtu3_mem_alloc(struct mtu3 *mtu) ...@@ -516,7 +511,6 @@ static int mtu3_mem_alloc(struct mtu3 *mtu)
mtu->out_eps = &ep_array[mtu->num_eps]; mtu->out_eps = &ep_array[mtu->num_eps];
/* ep0 uses in_eps[0], out_eps[0] is reserved */ /* ep0 uses in_eps[0], out_eps[0] is reserved */
mtu->ep0 = mtu->in_eps; mtu->ep0 = mtu->in_eps;
mtu->ep0->mtu = mtu; mtu->ep0->mtu = mtu;
mtu->ep0->epnum = 0; mtu->ep0->epnum = 0;
...@@ -560,6 +554,7 @@ static void mtu3_set_speed(struct mtu3 *mtu) ...@@ -560,6 +554,7 @@ static void mtu3_set_speed(struct mtu3 *mtu)
/* HS/FS detected by HW */ /* HS/FS detected by HW */
mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE);
} }
dev_info(mtu->dev, "max_speed: %s\n", dev_info(mtu->dev, "max_speed: %s\n",
usb_speed_string(mtu->max_speed)); usb_speed_string(mtu->max_speed));
} }
...@@ -586,13 +581,10 @@ static void mtu3_regs_init(struct mtu3 *mtu) ...@@ -586,13 +581,10 @@ static void mtu3_regs_init(struct mtu3 *mtu)
/* delay about 0.1us from detecting reset to send chirp-K */ /* delay about 0.1us from detecting reset to send chirp-K */
mtu3_clrbits(mbase, U3D_LINK_RESET_INFO, WTCHRP_MSK); mtu3_clrbits(mbase, U3D_LINK_RESET_INFO, WTCHRP_MSK);
/* U2/U3 detected by HW */ /* U2/U3 detected by HW */
mtu3_writel(mbase, U3D_DEVICE_CONF, 0); mtu3_writel(mbase, U3D_DEVICE_CONF, 0);
/* enable QMU 16B checksum */ /* enable QMU 16B checksum */
mtu3_setbits(mbase, U3D_QCR0, QMU_CS16B_EN); mtu3_setbits(mbase, U3D_QCR0, QMU_CS16B_EN);
/* vbus detected by HW */ /* vbus detected by HW */
mtu3_clrbits(mbase, U3D_MISC_CTRL, VBUS_FRC_EN | VBUS_ON); mtu3_clrbits(mbase, U3D_MISC_CTRL, VBUS_FRC_EN | VBUS_ON);
} }
...@@ -838,6 +830,10 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb) ...@@ -838,6 +830,10 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb)
goto gadget_err; goto gadget_err;
} }
/* init as host mode, power down device IP for power saving */
if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG)
mtu3_stop(mtu);
dev_dbg(dev, " %s() done...\n", __func__); dev_dbg(dev, " %s() done...\n", __func__);
return 0; return 0;
......
This diff is collapsed.
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#ifndef _MTU3_DR_H_ #ifndef _MTU3_DR_H_
#define _MTU3_DR_H_ #define _MTU3_DR_H_
#if IS_ENABLED(CONFIG_USB_MTU3_HOST) #if IS_ENABLED(CONFIG_USB_MTU3_HOST) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn); int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn);
void ssusb_host_exit(struct ssusb_mtk *ssusb); void ssusb_host_exit(struct ssusb_mtk *ssusb);
...@@ -69,7 +69,7 @@ static inline void ssusb_wakeup_disable(struct ssusb_mtk *ssusb) ...@@ -69,7 +69,7 @@ static inline void ssusb_wakeup_disable(struct ssusb_mtk *ssusb)
#endif #endif
#if IS_ENABLED(CONFIG_USB_MTU3_GADGET) #if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
int ssusb_gadget_init(struct ssusb_mtk *ssusb); int ssusb_gadget_init(struct ssusb_mtk *ssusb);
void ssusb_gadget_exit(struct ssusb_mtk *ssusb); void ssusb_gadget_exit(struct ssusb_mtk *ssusb);
#else #else
...@@ -82,4 +82,27 @@ static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb) ...@@ -82,4 +82,27 @@ static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb)
{} {}
#endif #endif
#if IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE)
int ssusb_otg_switch_init(struct ssusb_mtk *ssusb);
void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb);
int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on);
#else
static inline int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
{
return 0;
}
static inline void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
{}
static inline int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on)
{
return 0;
}
#endif
#endif /* _MTU3_DR_H_ */ #endif /* _MTU3_DR_H_ */
...@@ -522,7 +522,8 @@ static int mtu3_gadget_start(struct usb_gadget *gadget, ...@@ -522,7 +522,8 @@ static int mtu3_gadget_start(struct usb_gadget *gadget,
mtu->softconnect = 0; mtu->softconnect = 0;
mtu->gadget_driver = driver; mtu->gadget_driver = driver;
mtu3_start(mtu); if (mtu->ssusb->dr_mode == USB_DR_MODE_PERIPHERAL)
mtu3_start(mtu);
spin_unlock_irqrestore(&mtu->lock, flags); spin_unlock_irqrestore(&mtu->lock, flags);
...@@ -575,7 +576,8 @@ static int mtu3_gadget_stop(struct usb_gadget *g) ...@@ -575,7 +576,8 @@ static int mtu3_gadget_stop(struct usb_gadget *g)
stop_activity(mtu); stop_activity(mtu);
mtu->gadget_driver = NULL; mtu->gadget_driver = NULL;
mtu3_stop(mtu); if (mtu->ssusb->dr_mode == USB_DR_MODE_PERIPHERAL)
mtu3_stop(mtu);
spin_unlock_irqrestore(&mtu->lock, flags); spin_unlock_irqrestore(&mtu->lock, flags);
......
...@@ -230,10 +230,16 @@ static void ssusb_host_setup(struct ssusb_mtk *ssusb) ...@@ -230,10 +230,16 @@ static void ssusb_host_setup(struct ssusb_mtk *ssusb)
* if support OTG, gadget driver will switch port0 to device mode * if support OTG, gadget driver will switch port0 to device mode
*/ */
ssusb_host_enable(ssusb); ssusb_host_enable(ssusb);
/* if port0 supports dual-role, works as host mode by default */
ssusb_set_vbus(&ssusb->otg_switch, 1);
} }
static void ssusb_host_cleanup(struct ssusb_mtk *ssusb) 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, false);
} }
......
...@@ -142,13 +142,10 @@ static int ssusb_rscs_init(struct ssusb_mtk *ssusb) ...@@ -142,13 +142,10 @@ static int ssusb_rscs_init(struct ssusb_mtk *ssusb)
phy_err: phy_err:
ssusb_phy_exit(ssusb); ssusb_phy_exit(ssusb);
phy_init_err: phy_init_err:
clk_disable_unprepare(ssusb->sys_clk); clk_disable_unprepare(ssusb->sys_clk);
clk_err: clk_err:
regulator_disable(ssusb->vusb33); regulator_disable(ssusb->vusb33);
vusb33_err: vusb33_err:
return ret; return ret;
...@@ -170,10 +167,39 @@ static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb) ...@@ -170,10 +167,39 @@ static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb)
mtu3_clrbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST); mtu3_clrbits(ssusb->ippc_base, U3D_SSUSB_IP_PW_CTRL0, SSUSB_IP_SW_RST);
} }
static int get_iddig_pinctrl(struct ssusb_mtk *ssusb)
{
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
otg_sx->id_pinctrl = devm_pinctrl_get(ssusb->dev);
if (IS_ERR(otg_sx->id_pinctrl)) {
dev_err(ssusb->dev, "Cannot find id pinctrl!\n");
return PTR_ERR(otg_sx->id_pinctrl);
}
otg_sx->id_float =
pinctrl_lookup_state(otg_sx->id_pinctrl, "id_float");
if (IS_ERR(otg_sx->id_float)) {
dev_err(ssusb->dev, "Cannot find pinctrl id_float!\n");
return PTR_ERR(otg_sx->id_float);
}
otg_sx->id_ground =
pinctrl_lookup_state(otg_sx->id_pinctrl, "id_ground");
if (IS_ERR(otg_sx->id_ground)) {
dev_err(ssusb->dev, "Cannot find pinctrl id_ground!\n");
return PTR_ERR(otg_sx->id_ground);
}
return 0;
}
static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
{ {
struct device_node *node = pdev->dev.of_node; struct device_node *node = pdev->dev.of_node;
struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct regulator *vbus;
struct resource *res; struct resource *res;
int i; int i;
int ret; int ret;
...@@ -230,6 +256,37 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) ...@@ -230,6 +256,37 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb)
if (ret) if (ret)
return ret; return ret;
if (ssusb->dr_mode != USB_DR_MODE_OTG)
return 0;
/* if dual-role mode is supported */
vbus = devm_regulator_get(&pdev->dev, "vbus");
if (IS_ERR(vbus)) {
dev_err(dev, "failed to get vbus\n");
return PTR_ERR(vbus);
}
otg_sx->vbus = vbus;
otg_sx->is_u3_drd = of_property_read_bool(node, "mediatek,usb3-drd");
otg_sx->manual_drd_enabled =
of_property_read_bool(node, "enable-manual-drd");
if (of_property_read_bool(node, "extcon")) {
otg_sx->edev = extcon_get_edev_by_phandle(ssusb->dev, 0);
if (IS_ERR(otg_sx->edev)) {
dev_err(ssusb->dev, "couldn't get extcon device\n");
return -EPROBE_DEFER;
}
if (otg_sx->manual_drd_enabled) {
ret = get_iddig_pinctrl(ssusb);
if (ret)
return ret;
}
}
dev_info(dev, "dr_mode: %d, is_u3_dr: %d\n",
ssusb->dr_mode, otg_sx->is_u3_drd);
return 0; return 0;
} }
...@@ -292,6 +349,21 @@ static int mtu3_probe(struct platform_device *pdev) ...@@ -292,6 +349,21 @@ static int mtu3_probe(struct platform_device *pdev)
goto comm_exit; goto comm_exit;
} }
break; break;
case USB_DR_MODE_OTG:
ret = ssusb_gadget_init(ssusb);
if (ret) {
dev_err(dev, "failed to initialize gadget\n");
goto comm_exit;
}
ret = ssusb_host_init(ssusb, node);
if (ret) {
dev_err(dev, "failed to initialize host\n");
goto gadget_exit;
}
ssusb_otg_switch_init(ssusb);
break;
default: default:
dev_err(dev, "unsupported mode: %d\n", ssusb->dr_mode); dev_err(dev, "unsupported mode: %d\n", ssusb->dr_mode);
ret = -EINVAL; ret = -EINVAL;
...@@ -300,9 +372,10 @@ static int mtu3_probe(struct platform_device *pdev) ...@@ -300,9 +372,10 @@ static int mtu3_probe(struct platform_device *pdev)
return 0; return 0;
gadget_exit:
ssusb_gadget_exit(ssusb);
comm_exit: comm_exit:
ssusb_rscs_exit(ssusb); ssusb_rscs_exit(ssusb);
comm_init_err: comm_init_err:
pm_runtime_put_sync(dev); pm_runtime_put_sync(dev);
pm_runtime_disable(dev); pm_runtime_disable(dev);
...@@ -321,6 +394,11 @@ static int mtu3_remove(struct platform_device *pdev) ...@@ -321,6 +394,11 @@ static int mtu3_remove(struct platform_device *pdev)
case USB_DR_MODE_HOST: case USB_DR_MODE_HOST:
ssusb_host_exit(ssusb); ssusb_host_exit(ssusb);
break; break;
case USB_DR_MODE_OTG:
ssusb_otg_switch_exit(ssusb);
ssusb_gadget_exit(ssusb);
ssusb_host_exit(ssusb);
break;
default: default:
return -EINVAL; return -EINVAL;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment