summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/Kconfig2
-rw-r--r--drivers/usb/cdns3/Makefile4
-rw-r--r--drivers/usb/cdns3/core.c6
-rw-r--r--drivers/usb/cdns3/gadget.c21
-rw-r--r--drivers/usb/common/Makefile2
-rw-r--r--drivers/usb/dwc3/Makefile4
-rw-r--r--drivers/usb/dwc3/core.h2
-rw-r--r--drivers/usb/dwc3/dwc3-generic.c6
-rw-r--r--drivers/usb/dwc3/ep0.c6
-rw-r--r--drivers/usb/dwc3/gadget.c10
-rw-r--r--drivers/usb/dwc3/io.h13
-rw-r--r--drivers/usb/gadget/Kconfig14
-rw-r--r--drivers/usb/gadget/Makefile11
-rw-r--r--drivers/usb/gadget/f_sdp.c10
-rw-r--r--drivers/usb/gadget/rcar/Makefile8
-rw-r--r--drivers/usb/gadget/rcar/common.c478
-rw-r--r--drivers/usb/gadget/rcar/common.h328
-rw-r--r--drivers/usb/gadget/rcar/fifo.c1067
-rw-r--r--drivers/usb/gadget/rcar/fifo.h114
-rw-r--r--drivers/usb/gadget/rcar/mod.c345
-rw-r--r--drivers/usb/gadget/rcar/mod.h161
-rw-r--r--drivers/usb/gadget/rcar/mod_gadget.c1136
-rw-r--r--drivers/usb/gadget/rcar/pipe.c849
-rw-r--r--drivers/usb/gadget/rcar/pipe.h114
-rw-r--r--drivers/usb/gadget/rcar/renesas_usb.h125
-rw-r--r--drivers/usb/gadget/udc/Makefile4
-rw-r--r--drivers/usb/host/Kconfig8
-rw-r--r--drivers/usb/host/Makefile7
-rw-r--r--drivers/usb/host/ehci-tegra.c136
-rw-r--r--drivers/usb/host/xhci-generic.c75
-rw-r--r--drivers/usb/host/xhci-ring.c3
-rw-r--r--drivers/usb/mtu3/mtu3_plat.c4
-rw-r--r--drivers/usb/tcpm/Kconfig16
-rw-r--r--drivers/usb/tcpm/Makefile4
-rw-r--r--drivers/usb/tcpm/fusb302.c1323
-rw-r--r--drivers/usb/tcpm/fusb302_reg.h177
-rw-r--r--drivers/usb/tcpm/tcpm-internal.h173
-rw-r--r--drivers/usb/tcpm/tcpm-uclass.c149
-rw-r--r--drivers/usb/tcpm/tcpm.c2288
39 files changed, 9144 insertions, 59 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 311aaa7e67f..960b6a906ac 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -85,6 +85,8 @@ source "drivers/usb/emul/Kconfig"
source "drivers/usb/phy/Kconfig"
+source "drivers/usb/tcpm/Kconfig"
+
source "drivers/usb/ulpi/Kconfig"
if USB_HOST
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index 18d7190755d..d6047856091 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -4,8 +4,8 @@ cdns3-y := core.o drd.o
obj-$(CONFIG_USB_CDNS3) += cdns3.o
-cdns3-$(CONFIG_$(SPL_)USB_CDNS3_GADGET) += gadget.o ep0.o
+cdns3-$(CONFIG_$(XPL_)USB_CDNS3_GADGET) += gadget.o ep0.o
-cdns3-$(CONFIG_$(SPL_)USB_CDNS3_HOST) += host.o
+cdns3-$(CONFIG_$(XPL_)USB_CDNS3_HOST) += host.o
obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index cbe06a9e7b6..4cfd38ec245 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -149,7 +149,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
dr_mode = best_dr_mode;
-#if defined(CONFIG_SPL_USB_HOST) || !defined(CONFIG_SPL_BUILD)
+#if defined(CONFIG_SPL_USB_HOST) || !defined(CONFIG_XPL_BUILD)
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
ret = cdns3_host_init(cdns);
if (ret) {
@@ -412,7 +412,7 @@ int cdns3_bind(struct udevice *parent)
switch (dr_mode) {
#if defined(CONFIG_SPL_USB_HOST) || \
- (!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
+ (!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
case USB_DR_MODE_HOST:
debug("%s: dr_mode: HOST\n", __func__);
driver = "cdns-usb3-host";
@@ -498,7 +498,7 @@ int dm_usb_gadget_handle_interrupts(struct udevice *dev)
#endif
#if defined(CONFIG_SPL_USB_HOST) || \
- (!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
+ (!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
static int cdns3_host_probe(struct udevice *dev)
{
struct cdns3_host_priv *priv = dev_get_priv(dev);
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 32b2c412068..a30c40ef80e 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -965,6 +965,12 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
if (priv_dev->dev_ver <= DEV_VER_V2)
cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep);
+ /* Flush TRBs */
+ flush_dcache_range((unsigned long)priv_ep->trb_pool,
+ (unsigned long)priv_ep->trb_pool +
+ ROUND(sizeof(struct cdns3_trb) * priv_ep->num_trbs,
+ CONFIG_SYS_CACHELINE_SIZE));
+
trace_cdns3_prepare_trb(priv_ep, priv_req->trb);
/*
@@ -1153,6 +1159,13 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
priv_ep->endpoint.desc->bEndpointAddress);
#endif
+ /* Invalidate TRBs */
+ invalidate_dcache_range((unsigned long)priv_ep->trb_pool,
+ (unsigned long)priv_ep->trb_pool +
+ ROUND(sizeof(struct cdns3_trb) *
+ priv_ep->num_trbs,
+ CONFIG_SYS_CACHELINE_SIZE));
+
if (!cdns3_request_handled(priv_ep, priv_req))
goto prepare_next_td;
@@ -1624,6 +1637,14 @@ void cdns3_ep_config(struct cdns3_endpoint *priv_ep)
else
priv_ep->trb_burst_size = 16;
+ /*
+ * The Endpoint is configured to handle a maximum packet size of
+ * max_packet_size. Hence, set priv_ep->endpoint.maxpacket to
+ * max_packet_size. This is necessary to ensure that the TD_SIZE
+ * is calculated correctly in cdns3_ep_run_transfer().
+ */
+ priv_ep->endpoint.maxpacket = max_packet_size;
+
ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1,
!!priv_ep->dir);
if (ret) {
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index 2e9353b76a6..11cc4657a0f 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -3,7 +3,7 @@
# (C) Copyright 2016 Freescale Semiconductor, Inc.
#
-obj-$(CONFIG_$(SPL_)DM_USB) += common.o
+obj-$(CONFIG_$(XPL_)DM_USB) += common.o
obj-$(CONFIG_USB_ISP1760) += usb_urb.o
obj-$(CONFIG_USB_MUSB_HOST) += usb_urb.o
obj-$(CONFIG_USB_MUSB_GADGET) += usb_urb.o
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index a46b6824ab7..a085c9d4628 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -6,11 +6,11 @@ dwc3-y := core.o
obj-$(CONFIG_USB_DWC3_GADGET) += gadget.o ep0.o
-obj-$(CONFIG_$(SPL_)USB_DWC3_AM62) += dwc3-am62.o
+obj-$(CONFIG_$(XPL_)USB_DWC3_AM62) += dwc3-am62.o
obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o
obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o
obj-$(CONFIG_USB_DWC3_MESON_GXL) += dwc3-meson-gxl.o
-obj-$(CONFIG_$(SPL_)USB_DWC3_GENERIC) += dwc3-generic.o
+obj-$(CONFIG_$(XPL_)USB_DWC3_GENERIC) += dwc3-generic.o
obj-$(CONFIG_USB_DWC3_UNIPHIER) += dwc3-uniphier.o
obj-$(CONFIG_USB_DWC3_LAYERSCAPE) += dwc3-layerscape.o
obj-$(CONFIG_USB_DWC3_PHY_OMAP) += ti_usb_phy.o
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 7374ce950da..b572ea340c8 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -670,6 +670,7 @@ struct dwc3_scratchpad_array {
* @ep0_trb: dma address of ep0_trb
* @ep0_usb_req: dummy req used while handling STD USB requests
* @ep0_bounce_addr: dma address of ep0_bounce
+ * @setup_buf_addr: dma address of setup_buf
* @scratch_addr: dma address of scratchbuf
* @lock: for synchronizing
* @dev: pointer to our struct device
@@ -757,6 +758,7 @@ struct dwc3 {
dma_addr_t ep0_trb_addr;
dma_addr_t ep0_bounce_addr;
dma_addr_t scratch_addr;
+ dma_addr_t setup_buf_addr;
struct dwc3_request ep0_usb_req;
/* device lock */
diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c
index a9ba315463c..2ab41cbae45 100644
--- a/drivers/usb/dwc3/dwc3-generic.c
+++ b/drivers/usb/dwc3/dwc3-generic.c
@@ -246,12 +246,12 @@ static int dwc3_generic_host_probe(struct udevice *dev)
return rc;
rc = device_get_supply_regulator(dev, "vbus-supply", &priv->vbus_supply);
- if (rc)
+ if (rc && rc != -ENOSYS)
debug("%s: No vbus regulator found: %d\n", dev->name, rc);
- /* Only returns an error if regulator is valid and failed to enable due to a driver issue */
+ /* Does not return an error if regulator is invalid - but does so when DM_REGULATOR is disabled */
rc = regulator_set_enable_if_allowed(priv->vbus_supply, true);
- if (rc)
+ if (rc && rc != -ENOSYS)
return rc;
hccr = (struct xhci_hccr *)priv->gen_priv.base;
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index 24f516a131b..531f0b522af 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -380,7 +380,7 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc,
dep = dwc->eps[0];
dwc->ep0_usb_req.dep = dep;
dwc->ep0_usb_req.request.length = sizeof(*response_pkt);
- dwc->ep0_usb_req.request.buf = dwc->setup_buf;
+ dwc->ep0_usb_req.request.buf = (void *)dwc->setup_buf_addr;
dwc->ep0_usb_req.request.complete = dwc3_ep0_status_cmpl;
return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req);
@@ -662,7 +662,7 @@ static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
dep = dwc->eps[0];
dwc->ep0_usb_req.dep = dep;
dwc->ep0_usb_req.request.length = dep->endpoint.maxpacket;
- dwc->ep0_usb_req.request.buf = dwc->setup_buf;
+ dwc->ep0_usb_req.request.buf = (void *)dwc->setup_buf_addr;
dwc->ep0_usb_req.request.complete = dwc3_ep0_set_sel_cmpl;
return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req);
@@ -742,6 +742,8 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc,
if (!dwc->gadget_driver)
goto out;
+ dwc3_invalidate_cache((uintptr_t)ctrl, sizeof(*ctrl));
+
len = le16_to_cpu(ctrl->wLength);
if (!len) {
dwc->three_stage_setup = false;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index fe33e307d3e..e5a383407a2 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -2534,6 +2534,8 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf)
while (left > 0) {
union dwc3_event event;
+ dwc3_invalidate_cache((uintptr_t)evt->buf, evt->length);
+
event.raw = *(u32 *) (evt->buf + evt->lpos);
dwc3_process_event_entry(dwc, &event);
@@ -2653,8 +2655,8 @@ int dwc3_gadget_init(struct dwc3 *dwc)
goto err1;
}
- dwc->setup_buf = memalign(CONFIG_SYS_CACHELINE_SIZE,
- DWC3_EP0_BOUNCE_SIZE);
+ dwc->setup_buf = dma_alloc_coherent(DWC3_EP0_BOUNCE_SIZE,
+ (unsigned long *)&dwc->setup_buf_addr);
if (!dwc->setup_buf) {
ret = -ENOMEM;
goto err2;
@@ -2701,7 +2703,7 @@ err4:
dma_free_coherent(dwc->ep0_bounce);
err3:
- kfree(dwc->setup_buf);
+ dma_free_coherent(dwc->setup_buf);
err2:
dma_free_coherent(dwc->ep0_trb);
@@ -2723,7 +2725,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
dma_free_coherent(dwc->ep0_bounce);
- kfree(dwc->setup_buf);
+ dma_free_coherent(dwc->setup_buf);
dma_free_coherent(dwc->ep0_trb);
diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h
index 04791d4c9be..c1ab0288142 100644
--- a/drivers/usb/dwc3/io.h
+++ b/drivers/usb/dwc3/io.h
@@ -50,6 +50,17 @@ static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value)
static inline void dwc3_flush_cache(uintptr_t addr, int length)
{
- flush_dcache_range(addr, addr + ROUND(length, CACHELINE_SIZE));
+ uintptr_t start_addr = (uintptr_t)addr & ~(CACHELINE_SIZE - 1);
+ uintptr_t end_addr = ALIGN((uintptr_t)addr + length, CACHELINE_SIZE);
+
+ flush_dcache_range((unsigned long)start_addr, (unsigned long)end_addr);
+}
+
+static inline void dwc3_invalidate_cache(uintptr_t addr, int length)
+{
+ uintptr_t start_addr = (uintptr_t)addr & ~(CACHELINE_SIZE - 1);
+ uintptr_t end_addr = ALIGN((uintptr_t)addr + length, CACHELINE_SIZE);
+
+ invalidate_dcache_range((unsigned long)start_addr, (unsigned long)end_addr);
}
#endif /* __DRIVERS_USB_DWC3_IO_H */
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 4621a6fd5e6..010084ef7f3 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -114,6 +114,15 @@ config USB_GADGET_DWC2_OTG
driver to operate in Peripheral mode. This option requires
USB_GADGET to be enabled.
+config USB_RENESAS_USBHS
+ bool "Renesas RCar USB2.0 HS controller (gadget mode)"
+ select USB_GADGET_DUALSPEED
+ help
+ The Renesas Rcar USB 2.0 high-speed gadget controller
+ integrated into Salvator and Kingfisher boards. Select this
+ option if you want the driver to operate in Peripheral mode.
+ This option requires USB_GADGET to be enabled.
+
if USB_GADGET_DWC2_OTG
config USB_GADGET_DWC2_OTG_PHY
@@ -224,7 +233,7 @@ endif # USB_GADGET_DOWNLOAD
config USB_ETHER
bool "USB Ethernet Gadget"
- depends on NET
+ depends on NET || NET_LWIP
default y if ARCH_SUNXI && USB_MUSB_GADGET
help
Creates an Ethernet network device through a USB peripheral
@@ -323,7 +332,8 @@ config SPL_DFU
bool "Support DFU (Device Firmware Upgrade) in SPL"
select SPL_HASH
select SPL_DFU_NO_RESET
- depends on SPL_RAM_SUPPORT
+ select SPL_RAM_SUPPORT
+ depends on DFU_OVER_USB
help
This feature enables the DFU (Device Firmware Upgrade) in SPL with
RAM memory device support. The ROM code will load and execute
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 6abcce0d9c7..4bda224ff1a 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -3,11 +3,11 @@
# (C) Copyright 2000-2007
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
-obj-$(CONFIG_$(SPL_TPL_)USB_GADGET) += epautoconf.o config.o usbstring.o
-obj-$(CONFIG_$(SPL_TPL_)USB_ETHER) += epautoconf.o config.o usbstring.o ether.o
-obj-$(CONFIG_$(SPL_TPL_)USB_ETH_RNDIS) += rndis.o
+obj-$(CONFIG_$(PHASE_)USB_GADGET) += epautoconf.o config.o usbstring.o
+obj-$(CONFIG_$(PHASE_)USB_ETHER) += epautoconf.o config.o usbstring.o ether.o
+obj-$(CONFIG_$(PHASE_)USB_ETH_RNDIS) += rndis.o
-ifdef CONFIG_SPL_BUILD
+ifdef CONFIG_XPL_BUILD
obj-$(CONFIG_SPL_USB_GADGET) += g_dnl.o
obj-$(CONFIG_SPL_DFU) += f_dfu.o
obj-$(CONFIG_SPL_USB_SDP_SUPPORT) += f_sdp.o
@@ -21,7 +21,8 @@ obj-$(CONFIG_USB_GADGET_BCM_UDC_OTG_PHY) += bcm_udc_otg_phy.o
obj-$(CONFIG_USB_GADGET_DWC2_OTG) += dwc2_udc_otg.o
obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) += dwc2_udc_otg_phy.o
obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o
-ifndef CONFIG_SPL_BUILD
+obj-$(CONFIG_USB_RENESAS_USBHS) += rcar/
+ifndef CONFIG_XPL_BUILD
obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
obj-$(CONFIG_USB_FUNCTION_THOR) += f_thor.o
obj-$(CONFIG_DFU_OVER_USB) += f_dfu.o
diff --git a/drivers/usb/gadget/f_sdp.c b/drivers/usb/gadget/f_sdp.c
index 5d62eb475d0..36934b1bcf7 100644
--- a/drivers/usb/gadget/f_sdp.c
+++ b/drivers/usb/gadget/f_sdp.c
@@ -411,7 +411,7 @@ static void sdp_rx_data_complete(struct usb_ep *ep, struct usb_request *req)
return;
}
-#ifndef CONFIG_SPL_BUILD
+#ifndef CONFIG_XPL_BUILD
env_set_hex("filesize", sdp->dnl_bytes);
#endif
printf("done\n");
@@ -736,7 +736,7 @@ static u32 sdp_jump_imxheader(void *address)
return 0;
}
-#ifdef CONFIG_SPL_BUILD
+#ifdef CONFIG_XPL_BUILD
static ulong sdp_load_read(struct spl_load_info *load, ulong sector,
ulong count, void *buf)
{
@@ -825,7 +825,7 @@ static int sdp_handle_in_ep(struct spl_image_info *spl_image,
/* If imx header fails, try some U-Boot specific headers */
if (status) {
-#ifdef CONFIG_SPL_BUILD
+#ifdef CONFIG_XPL_BUILD
if (IS_ENABLED(CONFIG_SPL_LOAD_IMX_CONTAINER))
sdp_func->jmp_address = (u32)search_container_header((ulong)sdp_func->jmp_address, sdp_func->dnl_bytes);
else if (IS_ENABLED(CONFIG_SPL_LOAD_FIT))
@@ -907,7 +907,7 @@ static void sdp_handle_out_ep(void)
}
}
-#ifndef CONFIG_SPL_BUILD
+#ifndef CONFIG_XPL_BUILD
int sdp_handle(struct udevice *udc)
#else
int spl_sdp_handle(struct udevice *udc, struct spl_image_info *spl_image,
@@ -928,7 +928,7 @@ int spl_sdp_handle(struct udevice *udc, struct spl_image_info *spl_image,
schedule();
dm_usb_gadget_handle_interrupts(udc);
-#ifdef CONFIG_SPL_BUILD
+#ifdef CONFIG_XPL_BUILD
flag = sdp_handle_in_ep(spl_image, bootdev);
#else
flag = sdp_handle_in_ep(NULL, NULL);
diff --git a/drivers/usb/gadget/rcar/Makefile b/drivers/usb/gadget/rcar/Makefile
new file mode 100644
index 00000000000..676f39c8e24
--- /dev/null
+++ b/drivers/usb/gadget/rcar/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_USB_RENESAS_USBHS) += \
+ common.o \
+ fifo.o \
+ mod.o \
+ mod_gadget.o \
+ pipe.o
diff --git a/drivers/usb/gadget/rcar/common.c b/drivers/usb/gadget/rcar/common.c
new file mode 100644
index 00000000000..2ba022a3f2c
--- /dev/null
+++ b/drivers/usb/gadget/rcar/common.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+
+#include <asm/io.h>
+#include <clk.h>
+#include <dm.h>
+#include <errno.h>
+#include <generic-phy.h>
+#include <linux/err.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <usb.h>
+
+#include "common.h"
+
+/*
+ * image of renesas_usbhs
+ *
+ * ex) gadget case
+
+ * mod.c
+ * mod_gadget.c
+ * mod_host.c pipe.c fifo.c
+ *
+ * +-------+ +-----------+
+ * | pipe0 |------>| fifo pio |
+ * +------------+ +-------+ +-----------+
+ * | mod_gadget |=====> | pipe1 |--+
+ * +------------+ +-------+ | +-----------+
+ * | pipe2 | | +-| fifo dma0 |
+ * +------------+ +-------+ | | +-----------+
+ * | mod_host | | pipe3 |<-|--+
+ * +------------+ +-------+ | +-----------+
+ * | .... | +--->| fifo dma1 |
+ * | .... | +-----------+
+ */
+
+/*
+ * common functions
+ */
+u16 usbhs_read(struct usbhs_priv *priv, u32 reg)
+{
+ return ioread16(priv->base + reg);
+}
+
+void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data)
+{
+ iowrite16(data, priv->base + reg);
+}
+
+void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data)
+{
+ u16 val = usbhs_read(priv, reg);
+
+ val &= ~mask;
+ val |= data & mask;
+
+ usbhs_write(priv, reg, val);
+}
+
+/*
+ * syscfg functions
+ */
+static void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable)
+{
+ usbhs_bset(priv, SYSCFG, SCKE, enable ? SCKE : 0);
+}
+
+void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable)
+{
+ u16 mask = DCFM | DRPD | DPRPU | HSE | USBE;
+ u16 val = DCFM | DRPD | HSE | USBE;
+
+ /*
+ * if enable
+ *
+ * - select Host mode
+ * - D+ Line/D- Line Pull-down
+ */
+ usbhs_bset(priv, SYSCFG, mask, enable ? val : 0);
+}
+
+void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable)
+{
+ u16 mask = DCFM | DRPD | DPRPU | HSE | USBE;
+ u16 val = HSE | USBE;
+
+ /*
+ * if enable
+ *
+ * - select Function mode
+ * - D+ Line Pull-up is disabled
+ * When D+ Line Pull-up is enabled,
+ * calling usbhs_sys_function_pullup(,1)
+ */
+ usbhs_bset(priv, SYSCFG, mask, enable ? val : 0);
+}
+
+void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable)
+{
+ usbhs_bset(priv, SYSCFG, DPRPU, enable ? DPRPU : 0);
+}
+
+void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode)
+{
+ usbhs_write(priv, TESTMODE, mode);
+}
+
+/*
+ * frame functions
+ */
+int usbhs_frame_get_num(struct usbhs_priv *priv)
+{
+ return usbhs_read(priv, FRMNUM) & FRNM_MASK;
+}
+
+/*
+ * usb request functions
+ */
+void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req)
+{
+ u16 val;
+
+ val = usbhs_read(priv, USBREQ);
+ req->bRequest = (val >> 8) & 0xFF;
+ req->bRequestType = (val >> 0) & 0xFF;
+
+ req->wValue = cpu_to_le16(usbhs_read(priv, USBVAL));
+ req->wIndex = cpu_to_le16(usbhs_read(priv, USBINDX));
+ req->wLength = cpu_to_le16(usbhs_read(priv, USBLENG));
+}
+
+void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req)
+{
+ usbhs_write(priv, USBREQ, (req->bRequest << 8) | req->bRequestType);
+ usbhs_write(priv, USBVAL, le16_to_cpu(req->wValue));
+ usbhs_write(priv, USBINDX, le16_to_cpu(req->wIndex));
+ usbhs_write(priv, USBLENG, le16_to_cpu(req->wLength));
+
+ usbhs_bset(priv, DCPCTR, SUREQ, SUREQ);
+}
+
+/*
+ * bus/vbus functions
+ */
+void usbhs_bus_send_sof_enable(struct usbhs_priv *priv)
+{
+ u16 status = usbhs_read(priv, DVSTCTR) & (USBRST | UACT);
+
+ if (status != USBRST) {
+ struct device *dev = usbhs_priv_to_dev(priv);
+ dev_err(dev, "usbhs should be reset\n");
+ }
+
+ usbhs_bset(priv, DVSTCTR, (USBRST | UACT), UACT);
+}
+
+void usbhs_bus_send_reset(struct usbhs_priv *priv)
+{
+ usbhs_bset(priv, DVSTCTR, (USBRST | UACT), USBRST);
+}
+
+int usbhs_bus_get_speed(struct usbhs_priv *priv)
+{
+ u16 dvstctr = usbhs_read(priv, DVSTCTR);
+
+ switch (RHST & dvstctr) {
+ case RHST_LOW_SPEED:
+ return USB_SPEED_LOW;
+ case RHST_FULL_SPEED:
+ return USB_SPEED_FULL;
+ case RHST_HIGH_SPEED:
+ return USB_SPEED_HIGH;
+ }
+
+ return USB_SPEED_UNKNOWN;
+}
+
+static void usbhsc_bus_init(struct usbhs_priv *priv)
+{
+ usbhs_write(priv, DVSTCTR, 0);
+}
+
+/*
+ * device configuration
+ */
+int usbhs_set_device_config(struct usbhs_priv *priv, int devnum,
+ u16 upphub, u16 hubport, u16 speed)
+{
+ struct device *dev = usbhs_priv_to_dev(priv);
+ u16 usbspd = 0;
+ u32 reg = DEVADD0 + (2 * devnum);
+
+ if (devnum > 10) {
+ dev_err(dev, "cannot set speed to unknown device %d\n", devnum);
+ return -EIO;
+ }
+
+ if (upphub > 0xA) {
+ dev_err(dev, "unsupported hub number %d\n", upphub);
+ return -EIO;
+ }
+
+ switch (speed) {
+ case USB_SPEED_LOW:
+ usbspd = USBSPD_SPEED_LOW;
+ break;
+ case USB_SPEED_FULL:
+ usbspd = USBSPD_SPEED_FULL;
+ break;
+ case USB_SPEED_HIGH:
+ usbspd = USBSPD_SPEED_HIGH;
+ break;
+ default:
+ dev_err(dev, "unsupported speed %d\n", speed);
+ return -EIO;
+ }
+
+ usbhs_write(priv, reg, UPPHUB(upphub) |
+ HUBPORT(hubport)|
+ USBSPD(usbspd));
+
+ return 0;
+}
+
+/*
+ * interrupt functions
+ */
+void usbhs_xxxsts_clear(struct usbhs_priv *priv, u16 sts_reg, u16 bit)
+{
+ u16 pipe_mask = (u16)GENMASK(usbhs_get_dparam(priv, pipe_size), 0);
+
+ usbhs_write(priv, sts_reg, ~(1 << bit) & pipe_mask);
+}
+
+/*
+ * local functions
+ */
+static void usbhsc_set_buswait(struct usbhs_priv *priv)
+{
+ int wait = usbhs_get_dparam(priv, buswait_bwait);
+
+ /* set bus wait if platform have */
+ if (wait)
+ usbhs_bset(priv, BUSWAIT, 0x000F, wait);
+}
+
+/*
+ * platform default param
+ */
+
+/* commonly used on newer SH-Mobile and R-Car SoCs */
+static struct renesas_usbhs_driver_pipe_config usbhsc_new_pipe[] = {
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_CONTROL, 64, 0x00, false),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x08, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_ISOC, 1024, 0x28, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x48, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x58, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x68, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x04, false),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x05, false),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_INT, 64, 0x06, false),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x78, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x88, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0x98, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xa8, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xb8, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xc8, true),
+ RENESAS_USBHS_PIPE(USB_ENDPOINT_XFER_BULK, 512, 0xd8, true),
+};
+
+#define LPSTS 0x102
+#define LPSTS_SUSPM BIT(14)
+
+#define UGCTRL2 0x184
+#define UGCTRL2_RESERVED_3 BIT(0)
+#define UGCTRL2_USB0SEL_EHCI 0x10
+#define UGCTRL2_USB0SEL_HSUSB 0x20
+#define UGCTRL2_USB0SEL_OTG 0x30
+#define UGCTRL2_USB0SEL_MASK 0x30
+#define UGCTRL2_VBUSSEL BIT(10)
+
+struct usbhs_priv_otg_data {
+ void __iomem *base;
+ void __iomem *phybase;
+
+ struct platform_device usbhs_dev;
+ struct usbhs_priv usbhs_priv;
+
+ struct phy phy;
+};
+
+static int usbhs_rcar3_power_ctrl(struct usbhs_priv *priv, bool enable)
+{
+ if (enable) {
+ writel(UGCTRL2_USB0SEL_OTG | UGCTRL2_VBUSSEL | UGCTRL2_RESERVED_3,
+ priv->base + UGCTRL2);
+
+ usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM);
+ /* The controller on R-Car Gen3 needs to wait up to 90 usec */
+ udelay(90);
+
+ usbhs_sys_clock_ctrl(priv, enable);
+ } else {
+ usbhs_sys_clock_ctrl(priv, enable);
+
+ usbhs_bset(priv, LPSTS, LPSTS_SUSPM, 0);
+ }
+
+ return 0;
+}
+
+void usbhsc_hotplug(struct usbhs_priv *priv)
+{
+ int ret;
+
+ ret = usbhs_mod_change(priv, USBHS_GADGET);
+ if (ret < 0)
+ return;
+
+ usbhs_rcar3_power_ctrl(priv, true);
+
+ /* bus init */
+ usbhsc_set_buswait(priv);
+ usbhsc_bus_init(priv);
+
+ /* module start */
+ usbhs_mod_call(priv, start, priv);
+}
+
+#define USB2_OBINTSTA 0x604
+#define USB2_OBINT_SESSVLDCHG BIT(12)
+#define USB2_OBINT_IDDIGCHG BIT(11)
+
+static int usbhs_udc_otg_gadget_handle_interrupts(struct udevice *dev)
+{
+ struct usbhs_priv_otg_data *priv = dev_get_priv(dev);
+ const u32 status = readl(priv->phybase + USB2_OBINTSTA);
+
+ /* We don't have a good way to forward IRQ to PHY yet */
+ if (status & (USB2_OBINT_SESSVLDCHG | USB2_OBINT_IDDIGCHG)) {
+ writel(USB2_OBINT_SESSVLDCHG | USB2_OBINT_IDDIGCHG,
+ priv->phybase + USB2_OBINTSTA);
+ generic_phy_set_mode(&priv->phy, PHY_MODE_USB_OTG, 0);
+ }
+
+ usbhs_interrupt(0, &priv->usbhs_priv);
+
+ return 0;
+}
+
+static int usbhs_probe(struct usbhs_priv *priv)
+{
+ int ret;
+
+ priv->dparam.type = USBHS_TYPE_RCAR_GEN3;
+ priv->dparam.pio_dma_border = 64;
+ priv->dparam.pipe_configs = usbhsc_new_pipe;
+ priv->dparam.pipe_size = ARRAY_SIZE(usbhsc_new_pipe);
+
+ /* call pipe and module init */
+ ret = usbhs_pipe_probe(priv);
+ if (ret < 0)
+ return ret;
+
+ ret = usbhs_fifo_probe(priv);
+ if (ret < 0)
+ goto probe_end_pipe_exit;
+
+ ret = usbhs_mod_probe(priv);
+ if (ret < 0)
+ goto probe_end_fifo_exit;
+
+ usbhs_sys_clock_ctrl(priv, 0);
+
+ usbhs_rcar3_power_ctrl(priv, true);
+ usbhs_mod_autonomy_mode(priv);
+ usbhsc_hotplug(priv);
+
+ return ret;
+
+probe_end_fifo_exit:
+ usbhs_fifo_remove(priv);
+probe_end_pipe_exit:
+ usbhs_pipe_remove(priv);
+ return ret;
+}
+
+static int usbhs_udc_otg_probe(struct udevice *dev)
+{
+ struct usbhs_priv_otg_data *priv = dev_get_priv(dev);
+ struct usb_gadget *gadget;
+ struct clk_bulk clk_bulk;
+ int ret = -EINVAL;
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ ret = clk_get_bulk(dev, &clk_bulk);
+ if (ret)
+ return ret;
+
+ ret = clk_enable_bulk(&clk_bulk);
+ if (ret)
+ return ret;
+
+ clrsetbits_le32(priv->base + UGCTRL2, UGCTRL2_USB0SEL_MASK, UGCTRL2_USB0SEL_EHCI);
+ clrsetbits_le16(priv->base + LPSTS, LPSTS_SUSPM, LPSTS_SUSPM);
+
+ ret = generic_setup_phy(dev, &priv->phy, 0, PHY_MODE_USB_OTG, 1);
+ if (ret)
+ goto err_clk;
+
+ priv->phybase = dev_read_addr_ptr(priv->phy.dev);
+
+ priv->usbhs_priv.pdev = &priv->usbhs_dev;
+ priv->usbhs_priv.base = priv->base;
+ priv->usbhs_dev.dev.driver_data = &priv->usbhs_priv;
+ ret = usbhs_probe(&priv->usbhs_priv);
+ if (ret < 0)
+ goto err_phy;
+
+ gadget = usbhsg_get_gadget(&priv->usbhs_priv);
+ gadget->is_dualspeed = 1;
+ gadget->is_otg = 0;
+ gadget->is_a_peripheral = 0;
+ gadget->b_hnp_enable = 0;
+ gadget->a_hnp_support = 0;
+ gadget->a_alt_hnp_support = 0;
+
+ return usb_add_gadget_udc((struct device *)dev, gadget);
+
+err_phy:
+ generic_shutdown_phy(&priv->phy);
+err_clk:
+ clk_disable_bulk(&clk_bulk);
+ return ret;
+}
+
+static int usbhs_udc_otg_remove(struct udevice *dev)
+{
+ struct usbhs_priv_otg_data *priv = dev_get_priv(dev);
+
+ usbhs_rcar3_power_ctrl(&priv->usbhs_priv, false);
+ usbhs_mod_remove(&priv->usbhs_priv);
+ usbhs_fifo_remove(&priv->usbhs_priv);
+ usbhs_pipe_remove(&priv->usbhs_priv);
+
+ generic_shutdown_phy(&priv->phy);
+
+ return dm_scan_fdt_dev(dev);
+}
+
+static const struct udevice_id usbhs_udc_otg_ids[] = {
+ { .compatible = "renesas,rcar-gen3-usbhs" },
+ {},
+};
+
+static const struct usb_gadget_generic_ops usbhs_udc_otg_ops = {
+ .handle_interrupts = usbhs_udc_otg_gadget_handle_interrupts,
+};
+
+U_BOOT_DRIVER(usbhs_udc_otg) = {
+ .name = "usbhs-udc-otg",
+ .id = UCLASS_USB_GADGET_GENERIC,
+ .ops = &usbhs_udc_otg_ops,
+ .of_match = usbhs_udc_otg_ids,
+ .probe = usbhs_udc_otg_probe,
+ .remove = usbhs_udc_otg_remove,
+ .priv_auto = sizeof(struct usbhs_priv_otg_data),
+};
diff --git a/drivers/usb/gadget/rcar/common.h b/drivers/usb/gadget/rcar/common.h
new file mode 100644
index 00000000000..544cfd77cdf
--- /dev/null
+++ b/drivers/usb/gadget/rcar/common.h
@@ -0,0 +1,328 @@
+/* SPDX-License-Identifier: GPL-1.0+ */
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#ifndef RENESAS_USB_DRIVER_H
+#define RENESAS_USB_DRIVER_H
+
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include "renesas_usb.h"
+
+struct usbhs_priv;
+
+#include "mod.h"
+#include "pipe.h"
+
+/*
+ *
+ * register define
+ *
+ */
+#define SYSCFG 0x0000
+#define BUSWAIT 0x0002
+#define DVSTCTR 0x0008
+#define TESTMODE 0x000C
+#define CFIFO 0x0014
+#define CFIFOSEL 0x0020
+#define CFIFOCTR 0x0022
+#define D0FIFO 0x0100
+#define D0FIFOSEL 0x0028
+#define D0FIFOCTR 0x002A
+#define D1FIFO 0x0120
+#define D1FIFOSEL 0x002C
+#define D1FIFOCTR 0x002E
+#define INTENB0 0x0030
+#define INTENB1 0x0032
+#define BRDYENB 0x0036
+#define NRDYENB 0x0038
+#define BEMPENB 0x003A
+#define INTSTS0 0x0040
+#define INTSTS1 0x0042
+#define BRDYSTS 0x0046
+#define NRDYSTS 0x0048
+#define BEMPSTS 0x004A
+#define FRMNUM 0x004C
+#define USBREQ 0x0054 /* USB request type register */
+#define USBVAL 0x0056 /* USB request value register */
+#define USBINDX 0x0058 /* USB request index register */
+#define USBLENG 0x005A /* USB request length register */
+#define DCPCFG 0x005C
+#define DCPMAXP 0x005E
+#define DCPCTR 0x0060
+#define PIPESEL 0x0064
+#define PIPECFG 0x0068
+#define PIPEBUF 0x006A
+#define PIPEMAXP 0x006C
+#define PIPEPERI 0x006E
+#define PIPEnCTR 0x0070
+#define PIPE1TRE 0x0090
+#define PIPE1TRN 0x0092
+#define PIPE2TRE 0x0094
+#define PIPE2TRN 0x0096
+#define PIPE3TRE 0x0098
+#define PIPE3TRN 0x009A
+#define PIPE4TRE 0x009C
+#define PIPE4TRN 0x009E
+#define PIPE5TRE 0x00A0
+#define PIPE5TRN 0x00A2
+#define PIPEBTRE 0x00A4
+#define PIPEBTRN 0x00A6
+#define PIPECTRE 0x00A8
+#define PIPECTRN 0x00AA
+#define PIPEDTRE 0x00AC
+#define PIPEDTRN 0x00AE
+#define PIPEETRE 0x00B0
+#define PIPEETRN 0x00B2
+#define PIPEFTRE 0x00B4
+#define PIPEFTRN 0x00B6
+#define PIPE9TRE 0x00B8
+#define PIPE9TRN 0x00BA
+#define PIPEATRE 0x00BC
+#define PIPEATRN 0x00BE
+#define DEVADD0 0x00D0 /* Device address n configuration */
+#define DEVADD1 0x00D2
+#define DEVADD2 0x00D4
+#define DEVADD3 0x00D6
+#define DEVADD4 0x00D8
+#define DEVADD5 0x00DA
+#define DEVADD6 0x00DC
+#define DEVADD7 0x00DE
+#define DEVADD8 0x00E0
+#define DEVADD9 0x00E2
+#define DEVADDA 0x00E4
+#define D2FIFOSEL 0x00F0 /* for R-Car Gen2 */
+#define D2FIFOCTR 0x00F2 /* for R-Car Gen2 */
+#define D3FIFOSEL 0x00F4 /* for R-Car Gen2 */
+#define D3FIFOCTR 0x00F6 /* for R-Car Gen2 */
+#define SUSPMODE 0x0102 /* for RZ/A */
+
+/* SYSCFG */
+#define SCKE (1 << 10) /* USB Module Clock Enable */
+#define CNEN (1 << 8) /* Single-ended receiver operation Enable */
+#define HSE (1 << 7) /* High-Speed Operation Enable */
+#define DCFM (1 << 6) /* Controller Function Select */
+#define DRPD (1 << 5) /* D+ Line/D- Line Resistance Control */
+#define DPRPU (1 << 4) /* D+ Line Resistance Control */
+#define USBE (1 << 0) /* USB Module Operation Enable */
+#define UCKSEL (1 << 2) /* Clock Select for RZ/A1 */
+#define UPLLE (1 << 1) /* USB PLL Enable for RZ/A1 */
+
+/* DVSTCTR */
+#define EXTLP (1 << 10) /* Controls the EXTLP pin output state */
+#define PWEN (1 << 9) /* Controls the PWEN pin output state */
+#define USBRST (1 << 6) /* Bus Reset Output */
+#define UACT (1 << 4) /* USB Bus Enable */
+#define RHST (0x7) /* Reset Handshake */
+#define RHST_LOW_SPEED 1 /* Low-speed connection */
+#define RHST_FULL_SPEED 2 /* Full-speed connection */
+#define RHST_HIGH_SPEED 3 /* High-speed connection */
+
+/* CFIFOSEL */
+#define DREQE (1 << 12) /* DMA Transfer Request Enable */
+#define MBW_32 (0x2 << 10) /* CFIFO Port Access Bit Width */
+
+/* CFIFOCTR */
+#define BVAL (1 << 15) /* Buffer Memory Enable Flag */
+#define BCLR (1 << 14) /* CPU buffer clear */
+#define FRDY (1 << 13) /* FIFO Port Ready */
+#define DTLN_MASK (0x0FFF) /* Receive Data Length */
+
+/* INTENB0 */
+#define VBSE (1 << 15) /* Enable IRQ VBUS_0 and VBUSIN_0 */
+#define RSME (1 << 14) /* Enable IRQ Resume */
+#define SOFE (1 << 13) /* Enable IRQ Frame Number Update */
+#define DVSE (1 << 12) /* Enable IRQ Device State Transition */
+#define CTRE (1 << 11) /* Enable IRQ Control Stage Transition */
+#define BEMPE (1 << 10) /* Enable IRQ Buffer Empty */
+#define NRDYE (1 << 9) /* Enable IRQ Buffer Not Ready Response */
+#define BRDYE (1 << 8) /* Enable IRQ Buffer Ready */
+
+/* INTENB1 */
+#define BCHGE (1 << 14) /* USB Bus Change Interrupt Enable */
+#define DTCHE (1 << 12) /* Disconnection Detect Interrupt Enable */
+#define ATTCHE (1 << 11) /* Connection Detect Interrupt Enable */
+#define EOFERRE (1 << 6) /* EOF Error Detect Interrupt Enable */
+#define SIGNE (1 << 5) /* Setup Transaction Error Interrupt Enable */
+#define SACKE (1 << 4) /* Setup Transaction ACK Interrupt Enable */
+
+/* INTSTS0 */
+#define VBINT (1 << 15) /* VBUS0_0 and VBUS1_0 Interrupt Status */
+#define DVST (1 << 12) /* Device State Transition Interrupt Status */
+#define CTRT (1 << 11) /* Control Stage Interrupt Status */
+#define BEMP (1 << 10) /* Buffer Empty Interrupt Status */
+#define BRDY (1 << 8) /* Buffer Ready Interrupt Status */
+#define VBSTS (1 << 7) /* VBUS_0 and VBUSIN_0 Input Status */
+#define VALID (1 << 3) /* USB Request Receive */
+
+#define DVSQ_MASK (0x7 << 4) /* Device State */
+#define POWER_STATE (0 << 4)
+#define DEFAULT_STATE (1 << 4)
+#define ADDRESS_STATE (2 << 4)
+#define CONFIGURATION_STATE (3 << 4)
+#define SUSPENDED_STATE (4 << 4)
+
+#define CTSQ_MASK (0x7) /* Control Transfer Stage */
+#define IDLE_SETUP_STAGE 0 /* Idle stage or setup stage */
+#define READ_DATA_STAGE 1 /* Control read data stage */
+#define READ_STATUS_STAGE 2 /* Control read status stage */
+#define WRITE_DATA_STAGE 3 /* Control write data stage */
+#define WRITE_STATUS_STAGE 4 /* Control write status stage */
+#define NODATA_STATUS_STAGE 5 /* Control write NoData status stage */
+#define SEQUENCE_ERROR 6 /* Control transfer sequence error */
+
+/* INTSTS1 */
+#define OVRCR (1 << 15) /* OVRCR Interrupt Status */
+#define BCHG (1 << 14) /* USB Bus Change Interrupt Status */
+#define DTCH (1 << 12) /* USB Disconnection Detect Interrupt Status */
+#define ATTCH (1 << 11) /* ATTCH Interrupt Status */
+#define EOFERR (1 << 6) /* EOF Error Detect Interrupt Status */
+#define SIGN (1 << 5) /* Setup Transaction Error Interrupt Status */
+#define SACK (1 << 4) /* Setup Transaction ACK Response Interrupt Status */
+
+/* PIPECFG */
+/* DCPCFG */
+#define TYPE_NONE (0 << 14) /* Transfer Type */
+#define TYPE_BULK (1 << 14)
+#define TYPE_INT (2 << 14)
+#define TYPE_ISO (3 << 14)
+#define BFRE (1 << 10) /* BRDY Interrupt Operation Spec. */
+#define DBLB (1 << 9) /* Double Buffer Mode */
+#define SHTNAK (1 << 7) /* Pipe Disable in Transfer End */
+#define DIR_OUT (1 << 4) /* Transfer Direction */
+
+/* PIPEMAXP */
+/* DCPMAXP */
+#define DEVSEL_MASK (0xF << 12) /* Device Select */
+#define DCP_MAXP_MASK (0x7F)
+#define PIPE_MAXP_MASK (0x7FF)
+
+/* PIPEBUF */
+#define BUFSIZE_SHIFT 10
+#define BUFSIZE_MASK (0x1F << BUFSIZE_SHIFT)
+#define BUFNMB_MASK (0xFF)
+
+/* PIPEnCTR */
+/* DCPCTR */
+#define BSTS (1 << 15) /* Buffer Status */
+#define SUREQ (1 << 14) /* Sending SETUP Token */
+#define INBUFM (1 << 14) /* (PIPEnCTR) Transfer Buffer Monitor */
+#define CSSTS (1 << 12) /* CSSTS Status */
+#define ACLRM (1 << 9) /* Buffer Auto-Clear Mode */
+#define SQCLR (1 << 8) /* Toggle Bit Clear */
+#define SQSET (1 << 7) /* Toggle Bit Set */
+#define SQMON (1 << 6) /* Toggle Bit Check */
+#define PBUSY (1 << 5) /* Pipe Busy */
+#define PID_MASK (0x3) /* Response PID */
+#define PID_NAK 0
+#define PID_BUF 1
+#define PID_STALL10 2
+#define PID_STALL11 3
+
+#define CCPL (1 << 2) /* Control Transfer End Enable */
+
+/* PIPEnTRE */
+#define TRENB (1 << 9) /* Transaction Counter Enable */
+#define TRCLR (1 << 8) /* Transaction Counter Clear */
+
+/* FRMNUM */
+#define FRNM_MASK (0x7FF)
+
+/* DEVADDn */
+#define UPPHUB(x) (((x) & 0xF) << 11) /* HUB Register */
+#define HUBPORT(x) (((x) & 0x7) << 8) /* HUB Port for Target Device */
+#define USBSPD(x) (((x) & 0x3) << 6) /* Device Transfer Rate */
+#define USBSPD_SPEED_LOW 0x1
+#define USBSPD_SPEED_FULL 0x2
+#define USBSPD_SPEED_HIGH 0x3
+
+/* SUSPMODE */
+#define SUSPM (1 << 14) /* SuspendM Control */
+
+/*
+ * struct
+ */
+struct usbhs_priv {
+ void __iomem *base;
+ struct renesas_usbhs_driver_param dparam;
+ struct platform_device *pdev;
+
+ /*
+ * module control
+ */
+ struct usbhs_mod_info mod_info;
+
+ /*
+ * pipe control
+ */
+ struct usbhs_pipe_info pipe_info;
+
+ /*
+ * fifo control
+ */
+ struct usbhs_fifo_info fifo_info;
+};
+
+/*
+ * common
+ */
+u16 usbhs_read(struct usbhs_priv *priv, u32 reg);
+void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data);
+void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data);
+
+#define usbhs_lock(p, f) spin_lock_irqsave(usbhs_priv_to_lock(p), f)
+#define usbhs_unlock(p, f) spin_unlock_irqrestore(usbhs_priv_to_lock(p), f)
+
+/*
+ * sysconfig
+ */
+void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable);
+void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable);
+void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable);
+void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode);
+
+/*
+ * usb request
+ */
+void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req);
+void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req);
+
+/*
+ * bus
+ */
+void usbhs_bus_send_sof_enable(struct usbhs_priv *priv);
+void usbhs_bus_send_reset(struct usbhs_priv *priv);
+int usbhs_bus_get_speed(struct usbhs_priv *priv);
+int usbhs_vbus_ctrl(struct usbhs_priv *priv, int enable);
+void usbhsc_hotplug(struct usbhs_priv *priv);
+
+/*
+ * frame
+ */
+int usbhs_frame_get_num(struct usbhs_priv *priv);
+
+/*
+ * device config
+ */
+int usbhs_set_device_config(struct usbhs_priv *priv, int devnum, u16 upphub,
+ u16 hubport, u16 speed);
+
+/*
+ * interrupt functions
+ */
+void usbhs_xxxsts_clear(struct usbhs_priv *priv, u16 sts_reg, u16 bit);
+
+/*
+ * data
+ */
+#define usbhs_get_dparam(priv, param) (priv->dparam.param)
+#define usbhs_priv_to_dev(priv) (&priv->pdev->dev)
+
+#endif /* RENESAS_USB_DRIVER_H */
diff --git a/drivers/usb/gadget/rcar/fifo.c b/drivers/usb/gadget/rcar/fifo.c
new file mode 100644
index 00000000000..6016b2987d5
--- /dev/null
+++ b/drivers/usb/gadget/rcar/fifo.c
@@ -0,0 +1,1067 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include "common.h"
+#include "pipe.h"
+
+#define usbhsf_get_cfifo(p) (&((p)->fifo_info.cfifo))
+
+#define usbhsf_fifo_is_busy(f) ((f)->pipe) /* see usbhs_pipe_select_fifo */
+
+/*
+ * packet initialize
+ */
+void usbhs_pkt_init(struct usbhs_pkt *pkt)
+{
+ INIT_LIST_HEAD(&pkt->node);
+}
+
+/*
+ * packet control function
+ */
+static int usbhsf_null_handle(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+
+ dev_err(dev, "null handler\n");
+
+ return -EINVAL;
+}
+
+static const struct usbhs_pkt_handle usbhsf_null_handler = {
+ .prepare = usbhsf_null_handle,
+ .try_run = usbhsf_null_handle,
+};
+
+void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt,
+ void (*done)(struct usbhs_priv *priv,
+ struct usbhs_pkt *pkt),
+ void *buf, int len, int zero, int sequence)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ unsigned long flags;
+
+ if (!done) {
+ dev_err(dev, "no done function\n");
+ return;
+ }
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ if (!pipe->handler) {
+ dev_err(dev, "no handler function\n");
+ pipe->handler = &usbhsf_null_handler;
+ }
+
+ list_move_tail(&pkt->node, &pipe->list);
+
+ /*
+ * each pkt must hold own handler.
+ * because handler might be changed by its situation.
+ * dma handler -> pio handler.
+ */
+ pkt->pipe = pipe;
+ pkt->buf = buf;
+ pkt->handler = pipe->handler;
+ pkt->length = len;
+ pkt->zero = zero;
+ pkt->actual = 0;
+ pkt->done = done;
+ pkt->sequence = sequence;
+
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ******************/
+}
+
+static void __usbhsf_pkt_del(struct usbhs_pkt *pkt)
+{
+ list_del_init(&pkt->node);
+}
+
+struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe)
+{
+ return list_first_entry_or_null(&pipe->list, struct usbhs_pkt, node);
+}
+
+static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo);
+static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo,
+ struct usbhs_pkt *pkt);
+#define usbhsf_dma_map(p) __usbhsf_dma_map_ctrl(p, 1)
+#define usbhsf_dma_unmap(p) __usbhsf_dma_map_ctrl(p, 0)
+static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map);
+static void usbhsf_tx_irq_ctrl(struct usbhs_pipe *pipe, int enable);
+static void usbhsf_rx_irq_ctrl(struct usbhs_pipe *pipe, int enable);
+struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt)
+{
+ struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe);
+ unsigned long flags;
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ usbhs_pipe_disable(pipe);
+
+ if (!pkt)
+ pkt = __usbhsf_pkt_get(pipe);
+
+ if (pkt) {
+ struct dma_chan *chan = NULL;
+
+ if (fifo)
+ chan = usbhsf_dma_chan_get(fifo, pkt);
+ if (chan)
+ usbhsf_dma_unmap(pkt);
+
+ usbhs_pipe_clear_without_sequence(pipe, 0, 0);
+ usbhs_pipe_running(pipe, 0);
+
+ __usbhsf_pkt_del(pkt);
+ }
+
+ if (fifo)
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ******************/
+
+ return pkt;
+}
+
+enum {
+ USBHSF_PKT_PREPARE,
+ USBHSF_PKT_TRY_RUN,
+ USBHSF_PKT_DMA_DONE,
+};
+
+static int usbhsf_pkt_handler(struct usbhs_pipe *pipe, int type)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_pkt *pkt;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int (*func)(struct usbhs_pkt *pkt, int *is_done);
+ unsigned long flags;
+ int ret = 0;
+ int is_done = 0;
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ pkt = __usbhsf_pkt_get(pipe);
+ if (!pkt) {
+ ret = -EINVAL;
+ goto __usbhs_pkt_handler_end;
+ }
+
+ switch (type) {
+ case USBHSF_PKT_PREPARE:
+ func = pkt->handler->prepare;
+ break;
+ case USBHSF_PKT_TRY_RUN:
+ func = pkt->handler->try_run;
+ break;
+ case USBHSF_PKT_DMA_DONE:
+ func = pkt->handler->dma_done;
+ break;
+ default:
+ dev_err(dev, "unknown pkt handler\n");
+ goto __usbhs_pkt_handler_end;
+ }
+
+ if (likely(func))
+ ret = func(pkt, &is_done);
+
+ if (is_done)
+ __usbhsf_pkt_del(pkt);
+
+__usbhs_pkt_handler_end:
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ******************/
+
+ if (is_done) {
+ pkt->done(priv, pkt);
+ usbhs_pkt_start(pipe);
+ }
+
+ return ret;
+}
+
+void usbhs_pkt_start(struct usbhs_pipe *pipe)
+{
+ usbhsf_pkt_handler(pipe, USBHSF_PKT_PREPARE);
+}
+
+/*
+ * irq enable/disable function
+ */
+#define usbhsf_irq_empty_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_bempsts, e)
+#define usbhsf_irq_ready_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_brdysts, e)
+#define usbhsf_irq_callback_ctrl(pipe, status, enable) \
+ ({ \
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); \
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv); \
+ u16 status = (1 << usbhs_pipe_number(pipe)); \
+ if (!mod) \
+ return; \
+ if (enable) \
+ mod->status |= status; \
+ else \
+ mod->status &= ~status; \
+ usbhs_irq_callback_update(priv, mod); \
+ })
+
+static void usbhsf_tx_irq_ctrl(struct usbhs_pipe *pipe, int enable)
+{
+ /*
+ * And DCP pipe can NOT use "ready interrupt" for "send"
+ * it should use "empty" interrupt.
+ * see
+ * "Operation" - "Interrupt Function" - "BRDY Interrupt"
+ *
+ * on the other hand, normal pipe can use "ready interrupt" for "send"
+ * even though it is single/double buffer
+ */
+ if (usbhs_pipe_is_dcp(pipe))
+ usbhsf_irq_empty_ctrl(pipe, enable);
+ else
+ usbhsf_irq_ready_ctrl(pipe, enable);
+}
+
+static void usbhsf_rx_irq_ctrl(struct usbhs_pipe *pipe, int enable)
+{
+ usbhsf_irq_ready_ctrl(pipe, enable);
+}
+
+/*
+ * FIFO ctrl
+ */
+static void usbhsf_send_terminator(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ usbhs_bset(priv, fifo->ctr, BVAL, BVAL);
+}
+
+static int usbhsf_fifo_barrier(struct usbhs_priv *priv,
+ struct usbhs_fifo *fifo)
+{
+ /* The FIFO port is accessible */
+ if (usbhs_read(priv, fifo->ctr) & FRDY)
+ return 0;
+
+ return -EBUSY;
+}
+
+static void usbhsf_fifo_clear(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ int ret = 0;
+
+ if (!usbhs_pipe_is_dcp(pipe)) {
+ /*
+ * This driver checks the pipe condition first to avoid -EBUSY
+ * from usbhsf_fifo_barrier() if the pipe is RX direction and
+ * empty.
+ */
+ if (usbhs_pipe_is_dir_in(pipe))
+ ret = usbhs_pipe_is_accessible(pipe);
+ if (!ret)
+ ret = usbhsf_fifo_barrier(priv, fifo);
+ }
+
+ /*
+ * if non-DCP pipe, this driver should set BCLR when
+ * usbhsf_fifo_barrier() returns 0.
+ */
+ if (!ret)
+ usbhs_write(priv, fifo->ctr, BCLR);
+}
+
+static int usbhsf_fifo_rcv_len(struct usbhs_priv *priv,
+ struct usbhs_fifo *fifo)
+{
+ return usbhs_read(priv, fifo->ctr) & DTLN_MASK;
+}
+
+static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ usbhs_pipe_select_fifo(pipe, NULL);
+ usbhs_write(priv, fifo->sel, 0);
+}
+
+static int usbhsf_fifo_select(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo,
+ int write)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int timeout = 1024;
+ u16 mask = ((1 << 5) | 0xF); /* mask of ISEL | CURPIPE */
+ u16 base = usbhs_pipe_number(pipe); /* CURPIPE */
+
+ if (usbhs_pipe_is_busy(pipe) ||
+ usbhsf_fifo_is_busy(fifo))
+ return -EBUSY;
+
+ if (usbhs_pipe_is_dcp(pipe)) {
+ base |= (1 == write) << 5; /* ISEL */
+
+ if (usbhs_mod_is_host(priv))
+ usbhs_dcp_dir_for_host(pipe, write);
+ }
+
+ /* "base" will be used below */
+ usbhs_write(priv, fifo->sel, base | MBW_32);
+
+ /* check ISEL and CURPIPE value */
+ while (timeout--) {
+ if (base == (mask & usbhs_read(priv, fifo->sel))) {
+ usbhs_pipe_select_fifo(pipe, fifo);
+ return 0;
+ }
+ udelay(10);
+ }
+
+ dev_err(dev, "fifo select error\n");
+
+ return -EIO;
+}
+
+/*
+ * DCP status stage
+ */
+static int usbhs_dcp_dir_switch_to_write(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int ret;
+
+ usbhs_pipe_disable(pipe);
+
+ ret = usbhsf_fifo_select(pipe, fifo, 1);
+ if (ret < 0) {
+ dev_err(dev, "%s() failed\n", __func__);
+ return ret;
+ }
+
+ usbhs_pipe_sequence_data1(pipe); /* DATA1 */
+
+ usbhsf_fifo_clear(pipe, fifo);
+ usbhsf_send_terminator(pipe, fifo);
+
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ usbhsf_tx_irq_ctrl(pipe, 1);
+ usbhs_pipe_enable(pipe);
+
+ return ret;
+}
+
+static int usbhs_dcp_dir_switch_to_read(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int ret;
+
+ usbhs_pipe_disable(pipe);
+
+ ret = usbhsf_fifo_select(pipe, fifo, 0);
+ if (ret < 0) {
+ dev_err(dev, "%s() fail\n", __func__);
+ return ret;
+ }
+
+ usbhs_pipe_sequence_data1(pipe); /* DATA1 */
+ usbhsf_fifo_clear(pipe, fifo);
+
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ usbhsf_rx_irq_ctrl(pipe, 1);
+ usbhs_pipe_enable(pipe);
+
+ return ret;
+
+}
+
+static int usbhs_dcp_dir_switch_done(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+
+ if (pkt->handler == &usbhs_dcp_status_stage_in_handler)
+ usbhsf_tx_irq_ctrl(pipe, 0);
+ else
+ usbhsf_rx_irq_ctrl(pipe, 0);
+
+ pkt->actual = pkt->length;
+ *is_done = 1;
+
+ return 0;
+}
+
+const struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler = {
+ .prepare = usbhs_dcp_dir_switch_to_write,
+ .try_run = usbhs_dcp_dir_switch_done,
+};
+
+const struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler = {
+ .prepare = usbhs_dcp_dir_switch_to_read,
+ .try_run = usbhs_dcp_dir_switch_done,
+};
+
+/*
+ * DCP data stage (push)
+ */
+static int usbhsf_dcp_data_stage_try_push(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+
+ usbhs_pipe_sequence_data1(pipe); /* DATA1 */
+
+ /*
+ * change handler to PIO push
+ */
+ pkt->handler = &usbhs_fifo_pio_push_handler;
+
+ return pkt->handler->prepare(pkt, is_done);
+}
+
+const struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler = {
+ .prepare = usbhsf_dcp_data_stage_try_push,
+};
+
+/*
+ * DCP data stage (pop)
+ */
+static int usbhsf_dcp_data_stage_prepare_pop(struct usbhs_pkt *pkt,
+ int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv);
+
+ if (usbhs_pipe_is_busy(pipe))
+ return 0;
+
+ /*
+ * prepare pop for DCP should
+ * - change DCP direction,
+ * - clear fifo
+ * - DATA1
+ */
+ usbhs_pipe_disable(pipe);
+
+ usbhs_pipe_sequence_data1(pipe); /* DATA1 */
+
+ usbhsf_fifo_select(pipe, fifo, 0);
+ usbhsf_fifo_clear(pipe, fifo);
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ /*
+ * change handler to PIO pop
+ */
+ pkt->handler = &usbhs_fifo_pio_pop_handler;
+
+ return pkt->handler->prepare(pkt, is_done);
+}
+
+const struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler = {
+ .prepare = usbhsf_dcp_data_stage_prepare_pop,
+};
+
+/*
+ * PIO push handler
+ */
+static int usbhsf_pio_try_push(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */
+ void __iomem *addr = priv->base + fifo->port;
+ u8 *buf;
+ int maxp = usbhs_pipe_get_maxpacket(pipe);
+ int total_len;
+ int i, ret, len;
+ int is_short;
+
+ usbhs_pipe_data_sequence(pipe, pkt->sequence);
+ pkt->sequence = -1; /* -1 sequence will be ignored */
+
+ usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length);
+
+ ret = usbhsf_fifo_select(pipe, fifo, 1);
+ if (ret < 0)
+ return 0;
+
+ ret = usbhs_pipe_is_accessible(pipe);
+ if (ret < 0) {
+ /* inaccessible pipe is not an error */
+ ret = 0;
+ goto usbhs_fifo_write_busy;
+ }
+
+ ret = usbhsf_fifo_barrier(priv, fifo);
+ if (ret < 0)
+ goto usbhs_fifo_write_busy;
+
+ buf = pkt->buf + pkt->actual;
+ len = pkt->length - pkt->actual;
+ len = min(len, maxp);
+ total_len = len;
+ is_short = total_len < maxp;
+
+ /*
+ * FIXME
+ *
+ * 32-bit access only
+ */
+ if (len >= 4 && !((unsigned long)buf & 0x03)) {
+ iowrite32_rep(addr, buf, len / 4);
+ len %= 4;
+ buf += total_len - len;
+ }
+
+ /* the rest operation */
+ if (usbhs_get_dparam(priv, cfifo_byte_addr)) {
+ for (i = 0; i < len; i++)
+ iowrite8(buf[i], addr + (i & 0x03));
+ } else {
+ for (i = 0; i < len; i++)
+ iowrite8(buf[i], addr + (0x03 - (i & 0x03)));
+ }
+
+ /*
+ * variable update
+ */
+ pkt->actual += total_len;
+
+ if (pkt->actual < pkt->length)
+ *is_done = 0; /* there are remainder data */
+ else if (is_short)
+ *is_done = 1; /* short packet */
+ else
+ *is_done = !pkt->zero; /* send zero packet ? */
+
+ /*
+ * pipe/irq handling
+ */
+ if (is_short)
+ usbhsf_send_terminator(pipe, fifo);
+
+ usbhsf_tx_irq_ctrl(pipe, !*is_done);
+ usbhs_pipe_running(pipe, !*is_done);
+ usbhs_pipe_enable(pipe);
+
+ dev_dbg(dev, " send %d (%d/ %d/ %d/ %d)\n",
+ usbhs_pipe_number(pipe),
+ pkt->length, pkt->actual, *is_done, pkt->zero);
+
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ return 0;
+
+usbhs_fifo_write_busy:
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ /*
+ * pipe is busy.
+ * retry in interrupt
+ */
+ usbhsf_tx_irq_ctrl(pipe, 1);
+ usbhs_pipe_running(pipe, 1);
+
+ return ret;
+}
+
+static int usbhsf_pio_prepare_push(struct usbhs_pkt *pkt, int *is_done)
+{
+ if (usbhs_pipe_is_running(pkt->pipe))
+ return 0;
+
+ return usbhsf_pio_try_push(pkt, is_done);
+}
+
+const struct usbhs_pkt_handle usbhs_fifo_pio_push_handler = {
+ .prepare = usbhsf_pio_prepare_push,
+ .try_run = usbhsf_pio_try_push,
+};
+
+/*
+ * PIO pop handler
+ */
+static int usbhsf_prepare_pop(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv);
+
+ if (usbhs_pipe_is_busy(pipe))
+ return 0;
+
+ if (usbhs_pipe_is_running(pipe))
+ return 0;
+
+ /*
+ * pipe enable to prepare packet receive
+ */
+ usbhs_pipe_data_sequence(pipe, pkt->sequence);
+ pkt->sequence = -1; /* -1 sequence will be ignored */
+
+ if (usbhs_pipe_is_dcp(pipe))
+ usbhsf_fifo_clear(pipe, fifo);
+
+ usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length);
+ usbhs_pipe_enable(pipe);
+ usbhs_pipe_running(pipe, 1);
+ usbhsf_rx_irq_ctrl(pipe, 1);
+
+ return 0;
+}
+
+static int usbhsf_pio_try_pop(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */
+ void __iomem *addr = priv->base + fifo->port;
+ u8 *buf;
+ u32 data = 0;
+ int maxp = usbhs_pipe_get_maxpacket(pipe);
+ int rcv_len, len;
+ int i, ret;
+ int total_len = 0;
+
+ ret = usbhsf_fifo_select(pipe, fifo, 0);
+ if (ret < 0)
+ return 0;
+
+ ret = usbhsf_fifo_barrier(priv, fifo);
+ if (ret < 0)
+ goto usbhs_fifo_read_busy;
+
+ rcv_len = usbhsf_fifo_rcv_len(priv, fifo);
+
+ buf = pkt->buf + pkt->actual;
+ len = pkt->length - pkt->actual;
+ len = min(len, rcv_len);
+ total_len = len;
+
+ /*
+ * update actual length first here to decide disable pipe.
+ * if this pipe keeps BUF status and all data were popped,
+ * then, next interrupt/token will be issued again
+ */
+ pkt->actual += total_len;
+
+ if ((pkt->actual == pkt->length) || /* receive all data */
+ (total_len < maxp)) { /* short packet */
+ *is_done = 1;
+ usbhsf_rx_irq_ctrl(pipe, 0);
+ usbhs_pipe_running(pipe, 0);
+ /*
+ * If function mode, since this controller is possible to enter
+ * Control Write status stage at this timing, this driver
+ * should not disable the pipe. If such a case happens, this
+ * controller is not able to complete the status stage.
+ */
+ if (!usbhs_mod_is_host(priv) && !usbhs_pipe_is_dcp(pipe))
+ usbhs_pipe_disable(pipe); /* disable pipe first */
+ }
+
+ /*
+ * Buffer clear if Zero-Length packet
+ *
+ * see
+ * "Operation" - "FIFO Buffer Memory" - "FIFO Port Function"
+ */
+ if (0 == rcv_len) {
+ pkt->zero = 1;
+ usbhsf_fifo_clear(pipe, fifo);
+ goto usbhs_fifo_read_end;
+ }
+
+ /*
+ * FIXME
+ *
+ * 32-bit access only
+ */
+ if (len >= 4 && !((unsigned long)buf & 0x03)) {
+ ioread32_rep(addr, buf, len / 4);
+ len %= 4;
+ buf += total_len - len;
+ }
+
+ /* the rest operation */
+ for (i = 0; i < len; i++) {
+ if (!(i & 0x03))
+ data = ioread32(addr);
+
+ buf[i] = (data >> ((i & 0x03) * 8)) & 0xff;
+ }
+
+usbhs_fifo_read_end:
+ dev_dbg(dev, " recv %d (%d/ %d/ %d/ %d)\n",
+ usbhs_pipe_number(pipe),
+ pkt->length, pkt->actual, *is_done, pkt->zero);
+
+usbhs_fifo_read_busy:
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ return ret;
+}
+
+const struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler = {
+ .prepare = usbhsf_prepare_pop,
+ .try_run = usbhsf_pio_try_pop,
+};
+
+/*
+ * DCP ctrol statge handler
+ */
+static int usbhsf_ctrl_stage_end(struct usbhs_pkt *pkt, int *is_done)
+{
+ usbhs_dcp_control_transfer_done(pkt->pipe);
+
+ *is_done = 1;
+
+ return 0;
+}
+
+const struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler = {
+ .prepare = usbhsf_ctrl_stage_end,
+ .try_run = usbhsf_ctrl_stage_end,
+};
+
+/*
+ * DMA fifo functions
+ */
+static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo,
+ struct usbhs_pkt *pkt)
+{
+ if (&usbhs_fifo_dma_push_handler == pkt->handler)
+ return fifo->tx_chan;
+
+ if (&usbhs_fifo_dma_pop_handler == pkt->handler)
+ return fifo->rx_chan;
+
+ return NULL;
+}
+
+#define usbhsf_dma_start(p, f) __usbhsf_dma_ctrl(p, f, DREQE)
+#define usbhsf_dma_stop(p, f) __usbhsf_dma_ctrl(p, f, 0)
+static void __usbhsf_dma_ctrl(struct usbhs_pipe *pipe,
+ struct usbhs_fifo *fifo,
+ u16 dreqe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ usbhs_bset(priv, fifo->sel, DREQE, dreqe);
+}
+
+static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv);
+
+ return info->dma_map_ctrl(pkt, map);
+}
+
+/*
+ * DMA push handler
+ */
+static int usbhsf_dma_prepare_push(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+
+ if (usbhs_pipe_is_busy(pipe))
+ return 0;
+
+ /*
+ * change handler to PIO
+ */
+ pkt->handler = &usbhs_fifo_pio_push_handler;
+
+ return pkt->handler->prepare(pkt, is_done);
+}
+
+static int usbhsf_dma_push_done(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ int is_short = pkt->trans % usbhs_pipe_get_maxpacket(pipe);
+
+ pkt->actual += pkt->trans;
+
+ if (pkt->actual < pkt->length)
+ *is_done = 0; /* there are remainder data */
+ else if (is_short)
+ *is_done = 1; /* short packet */
+ else
+ *is_done = !pkt->zero; /* send zero packet? */
+
+ usbhs_pipe_running(pipe, !*is_done);
+
+ usbhsf_dma_stop(pipe, pipe->fifo);
+ usbhsf_dma_unmap(pkt);
+ usbhsf_fifo_unselect(pipe, pipe->fifo);
+
+ if (!*is_done) {
+ /* change handler to PIO */
+ pkt->handler = &usbhs_fifo_pio_push_handler;
+ return pkt->handler->try_run(pkt, is_done);
+ }
+
+ return 0;
+}
+
+const struct usbhs_pkt_handle usbhs_fifo_dma_push_handler = {
+ .prepare = usbhsf_dma_prepare_push,
+ .dma_done = usbhsf_dma_push_done,
+};
+
+/*
+ * DMA pop handler
+ */
+
+static int usbhsf_dma_prepare_pop_with_rx_irq(struct usbhs_pkt *pkt,
+ int *is_done)
+{
+ return usbhsf_prepare_pop(pkt, is_done);
+}
+
+static int usbhsf_dma_prepare_pop(struct usbhs_pkt *pkt, int *is_done)
+{
+ return usbhsf_dma_prepare_pop_with_rx_irq(pkt, is_done);
+}
+
+static int usbhsf_dma_try_pop_with_rx_irq(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+
+ if (usbhs_pipe_is_busy(pipe))
+ return 0;
+
+ /*
+ * change handler to PIO
+ */
+ pkt->handler = &usbhs_fifo_pio_pop_handler;
+
+ return pkt->handler->try_run(pkt, is_done);
+}
+
+static int usbhsf_dma_try_pop(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe);
+
+ BUG_ON(usbhs_get_dparam(priv, has_usb_dmac));
+
+ return usbhsf_dma_try_pop_with_rx_irq(pkt, is_done);
+}
+
+static int usbhsf_dma_pop_done_with_rx_irq(struct usbhs_pkt *pkt, int *is_done)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ int maxp = usbhs_pipe_get_maxpacket(pipe);
+
+ usbhsf_dma_stop(pipe, pipe->fifo);
+ usbhsf_dma_unmap(pkt);
+ usbhsf_fifo_unselect(pipe, pipe->fifo);
+
+ pkt->actual += pkt->trans;
+
+ if ((pkt->actual == pkt->length) || /* receive all data */
+ (pkt->trans < maxp)) { /* short packet */
+ *is_done = 1;
+ usbhs_pipe_running(pipe, 0);
+ } else {
+ /* re-enable */
+ usbhs_pipe_running(pipe, 0);
+ usbhsf_prepare_pop(pkt, is_done);
+ }
+
+ return 0;
+}
+
+static int usbhsf_dma_pop_done(struct usbhs_pkt *pkt, int *is_done)
+{
+ return usbhsf_dma_pop_done_with_rx_irq(pkt, is_done);
+}
+
+const struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler = {
+ .prepare = usbhsf_dma_prepare_pop,
+ .try_run = usbhsf_dma_try_pop,
+ .dma_done = usbhsf_dma_pop_done
+};
+
+/*
+ * irq functions
+ */
+static int usbhsf_irq_empty(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state)
+{
+ struct usbhs_pipe *pipe;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int i, ret;
+
+ if (!irq_state->bempsts) {
+ dev_err(dev, "debug %s !!\n", __func__);
+ return -EIO;
+ }
+
+ dev_dbg(dev, "irq empty [0x%04x]\n", irq_state->bempsts);
+
+ /*
+ * search interrupted "pipe"
+ * not "uep".
+ */
+ usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
+ if (!(irq_state->bempsts & (1 << i)))
+ continue;
+
+ ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN);
+ if (ret < 0)
+ dev_err(dev, "irq_empty run_error %d : %d\n", i, ret);
+ }
+
+ return 0;
+}
+
+static int usbhsf_irq_ready(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state)
+{
+ struct usbhs_pipe *pipe;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int i, ret;
+
+ if (!irq_state->brdysts) {
+ dev_err(dev, "debug %s !!\n", __func__);
+ return -EIO;
+ }
+
+ dev_dbg(dev, "irq ready [0x%04x]\n", irq_state->brdysts);
+
+ /*
+ * search interrupted "pipe"
+ * not "uep".
+ */
+ usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
+ if (!(irq_state->brdysts & (1 << i)))
+ continue;
+
+ ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN);
+ if (ret < 0)
+ dev_err(dev, "irq_ready run_error %d : %d\n", i, ret);
+ }
+
+ return 0;
+}
+
+void usbhs_fifo_clear_dcp(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */
+
+ /* clear DCP FIFO of transmission */
+ if (usbhsf_fifo_select(pipe, fifo, 1) < 0)
+ return;
+ usbhsf_fifo_clear(pipe, fifo);
+ usbhsf_fifo_unselect(pipe, fifo);
+
+ /* clear DCP FIFO of reception */
+ if (usbhsf_fifo_select(pipe, fifo, 0) < 0)
+ return;
+ usbhsf_fifo_clear(pipe, fifo);
+ usbhsf_fifo_unselect(pipe, fifo);
+}
+
+/*
+ * fifo init
+ */
+void usbhs_fifo_init(struct usbhs_priv *priv)
+{
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ struct usbhs_fifo *cfifo = usbhsf_get_cfifo(priv);
+ struct usbhs_fifo *dfifo;
+ int i;
+
+ mod->irq_empty = usbhsf_irq_empty;
+ mod->irq_ready = usbhsf_irq_ready;
+ mod->irq_bempsts = 0;
+ mod->irq_brdysts = 0;
+
+ cfifo->pipe = NULL;
+ usbhs_for_each_dfifo(priv, dfifo, i)
+ dfifo->pipe = NULL;
+}
+
+void usbhs_fifo_quit(struct usbhs_priv *priv)
+{
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+
+ mod->irq_empty = NULL;
+ mod->irq_ready = NULL;
+ mod->irq_bempsts = 0;
+ mod->irq_brdysts = 0;
+}
+
+#define __USBHS_DFIFO_INIT(priv, fifo, channel, fifo_port) \
+do { \
+ fifo = usbhsf_get_dnfifo(priv, channel); \
+ fifo->name = "D"#channel"FIFO"; \
+ fifo->port = fifo_port; \
+ fifo->sel = D##channel##FIFOSEL; \
+ fifo->ctr = D##channel##FIFOCTR; \
+ fifo->tx_slave.shdma_slave.slave_id = \
+ usbhs_get_dparam(priv, d##channel##_tx_id); \
+ fifo->rx_slave.shdma_slave.slave_id = \
+ usbhs_get_dparam(priv, d##channel##_rx_id); \
+} while (0)
+
+#define USBHS_DFIFO_INIT(priv, fifo, channel) \
+ __USBHS_DFIFO_INIT(priv, fifo, channel, D##channel##FIFO)
+#define USBHS_DFIFO_INIT_NO_PORT(priv, fifo, channel) \
+ __USBHS_DFIFO_INIT(priv, fifo, channel, 0)
+
+int usbhs_fifo_probe(struct usbhs_priv *priv)
+{
+ struct usbhs_fifo *fifo;
+
+ /* CFIFO */
+ fifo = usbhsf_get_cfifo(priv);
+ fifo->name = "CFIFO";
+ fifo->port = CFIFO;
+ fifo->sel = CFIFOSEL;
+ fifo->ctr = CFIFOCTR;
+
+ /* DFIFO */
+ USBHS_DFIFO_INIT(priv, fifo, 0);
+ USBHS_DFIFO_INIT(priv, fifo, 1);
+ USBHS_DFIFO_INIT_NO_PORT(priv, fifo, 2);
+ USBHS_DFIFO_INIT_NO_PORT(priv, fifo, 3);
+
+ return 0;
+}
+
+void usbhs_fifo_remove(struct usbhs_priv *priv)
+{
+}
diff --git a/drivers/usb/gadget/rcar/fifo.h b/drivers/usb/gadget/rcar/fifo.h
new file mode 100644
index 00000000000..86746ca9bdd
--- /dev/null
+++ b/drivers/usb/gadget/rcar/fifo.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-1.0+ */
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#ifndef RENESAS_USB_FIFO_H
+#define RENESAS_USB_FIFO_H
+
+#include <dma.h>
+#include "pipe.h"
+
+/*
+ * Drivers, using this library are expected to embed struct shdma_dev,
+ * struct shdma_chan, struct shdma_desc, and struct shdma_slave
+ * in their respective device, channel, descriptor and slave objects.
+ */
+
+struct shdma_slave {
+ int slave_id;
+};
+
+/* Used by slave DMA clients to request DMA to/from a specific peripheral */
+struct sh_dmae_slave {
+ struct shdma_slave shdma_slave; /* Set by the platform */
+};
+
+struct usbhs_fifo {
+ char *name;
+ u32 port; /* xFIFO */
+ u32 sel; /* xFIFOSEL */
+ u32 ctr; /* xFIFOCTR */
+
+ struct usbhs_pipe *pipe;
+
+ struct dma_chan *tx_chan;
+ struct dma_chan *rx_chan;
+
+ struct sh_dmae_slave tx_slave;
+ struct sh_dmae_slave rx_slave;
+};
+
+#define USBHS_MAX_NUM_DFIFO 4
+struct usbhs_fifo_info {
+ struct usbhs_fifo cfifo;
+ struct usbhs_fifo dfifo[USBHS_MAX_NUM_DFIFO];
+};
+#define usbhsf_get_dnfifo(p, n) (&((p)->fifo_info.dfifo[n]))
+#define usbhs_for_each_dfifo(priv, dfifo, i) \
+ for ((i) = 0; \
+ ((i) < USBHS_MAX_NUM_DFIFO) && \
+ ((dfifo) = usbhsf_get_dnfifo(priv, (i))); \
+ (i)++)
+
+struct usbhs_pkt_handle;
+struct usbhs_pkt {
+ struct list_head node;
+ struct usbhs_pipe *pipe;
+ const struct usbhs_pkt_handle *handler;
+ void (*done)(struct usbhs_priv *priv,
+ struct usbhs_pkt *pkt);
+ struct work_struct work;
+ dma_addr_t dma;
+ const struct dmaengine_result *dma_result;
+ void *buf;
+ int length;
+ int trans;
+ int actual;
+ int zero;
+ int sequence;
+};
+
+struct usbhs_pkt_handle {
+ int (*prepare)(struct usbhs_pkt *pkt, int *is_done);
+ int (*try_run)(struct usbhs_pkt *pkt, int *is_done);
+ int (*dma_done)(struct usbhs_pkt *pkt, int *is_done);
+};
+
+/*
+ * fifo
+ */
+int usbhs_fifo_probe(struct usbhs_priv *priv);
+void usbhs_fifo_remove(struct usbhs_priv *priv);
+void usbhs_fifo_init(struct usbhs_priv *priv);
+void usbhs_fifo_quit(struct usbhs_priv *priv);
+void usbhs_fifo_clear_dcp(struct usbhs_pipe *pipe);
+
+/*
+ * packet info
+ */
+extern const struct usbhs_pkt_handle usbhs_fifo_pio_push_handler;
+extern const struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler;
+extern const struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler;
+
+extern const struct usbhs_pkt_handle usbhs_fifo_dma_push_handler;
+extern const struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler;
+
+extern const struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler;
+extern const struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler;
+
+extern const struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler;
+extern const struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler;
+
+void usbhs_pkt_init(struct usbhs_pkt *pkt);
+void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt,
+ void (*done)(struct usbhs_priv *priv,
+ struct usbhs_pkt *pkt),
+ void *buf, int len, int zero, int sequence);
+struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt);
+void usbhs_pkt_start(struct usbhs_pipe *pipe);
+struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe);
+
+#endif /* RENESAS_USB_FIFO_H */
diff --git a/drivers/usb/gadget/rcar/mod.c b/drivers/usb/gadget/rcar/mod.c
new file mode 100644
index 00000000000..f5f8d169e17
--- /dev/null
+++ b/drivers/usb/gadget/rcar/mod.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#include "common.h"
+#include "mod.h"
+
+/*
+ * autonomy
+ *
+ * these functions are used if platform doesn't have external phy.
+ * -> there is no "notify_hotplug" callback from platform
+ * -> call "notify_hotplug" by itself
+ * -> use own interrupt to connect/disconnect
+ * -> it mean module clock is always ON
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+static int usbhsm_autonomy_irq_vbus(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state)
+{
+ usbhsc_hotplug(priv);
+
+ return 0;
+}
+
+void usbhs_mod_autonomy_mode(struct usbhs_priv *priv)
+{
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+
+ info->irq_vbus = usbhsm_autonomy_irq_vbus;
+
+ usbhs_irq_callback_update(priv, NULL);
+}
+
+/*
+ * host / gadget functions
+ *
+ * renesas_usbhs host/gadget can register itself by below functions.
+ * these functions are called when probe
+ *
+ */
+void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *mod, int id)
+{
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+
+ info->mod[id] = mod;
+ mod->priv = priv;
+}
+
+struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id)
+{
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+ struct usbhs_mod *ret = NULL;
+
+ switch (id) {
+ case USBHS_HOST:
+ case USBHS_GADGET:
+ ret = info->mod[id];
+ break;
+ }
+
+ return ret;
+}
+
+int usbhs_mod_is_host(struct usbhs_priv *priv)
+{
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+
+ if (!mod)
+ return -EINVAL;
+
+ return info->mod[USBHS_HOST] == mod;
+}
+
+struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv)
+{
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+
+ return info->curt;
+}
+
+int usbhs_mod_change(struct usbhs_priv *priv, int id)
+{
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+ struct usbhs_mod *mod = NULL;
+ int ret = 0;
+
+ /* id < 0 mean no current */
+ switch (id) {
+ case USBHS_HOST:
+ case USBHS_GADGET:
+ mod = info->mod[id];
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ info->curt = mod;
+
+ return ret;
+}
+
+irqreturn_t usbhs_interrupt(int irq, void *data);
+int usbhs_mod_probe(struct usbhs_priv *priv)
+{
+ int ret;
+
+ /*
+ * install host/gadget driver
+ */
+ ret = usbhs_mod_host_probe(priv);
+ if (ret < 0)
+ return ret;
+
+ ret = usbhs_mod_gadget_probe(priv);
+ if (ret < 0)
+ goto mod_init_host_err;
+
+ return ret;
+
+mod_init_host_err:
+ usbhs_mod_host_remove(priv);
+
+ return ret;
+}
+
+void usbhs_mod_remove(struct usbhs_priv *priv)
+{
+ usbhs_mod_host_remove(priv);
+ usbhs_mod_gadget_remove(priv);
+}
+
+/*
+ * status functions
+ */
+int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state)
+{
+ return (int)irq_state->intsts0 & DVSQ_MASK;
+}
+
+int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state)
+{
+ /*
+ * return value
+ *
+ * IDLE_SETUP_STAGE
+ * READ_DATA_STAGE
+ * READ_STATUS_STAGE
+ * WRITE_DATA_STAGE
+ * WRITE_STATUS_STAGE
+ * NODATA_STATUS_STAGE
+ * SEQUENCE_ERROR
+ */
+ return (int)irq_state->intsts0 & CTSQ_MASK;
+}
+
+static int usbhs_status_get_each_irq(struct usbhs_priv *priv,
+ struct usbhs_irq_state *state)
+{
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ u16 intenb0, intenb1;
+ unsigned long flags;
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+ state->intsts0 = usbhs_read(priv, INTSTS0);
+ intenb0 = usbhs_read(priv, INTENB0);
+
+ if (usbhs_mod_is_host(priv)) {
+ state->intsts1 = usbhs_read(priv, INTSTS1);
+ intenb1 = usbhs_read(priv, INTENB1);
+ } else {
+ state->intsts1 = intenb1 = 0;
+ }
+
+ /* mask */
+ if (mod) {
+ state->brdysts = usbhs_read(priv, BRDYSTS);
+ state->nrdysts = usbhs_read(priv, NRDYSTS);
+ state->bempsts = usbhs_read(priv, BEMPSTS);
+
+ state->bempsts &= mod->irq_bempsts;
+ state->brdysts &= mod->irq_brdysts;
+ }
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ******************/
+
+ return 0;
+}
+
+/*
+ * interrupt
+ */
+#define INTSTS0_MAGIC 0xF800 /* acknowledge magical interrupt sources */
+#define INTSTS1_MAGIC 0xA870 /* acknowledge magical interrupt sources */
+irqreturn_t usbhs_interrupt(int irq, void *data)
+{
+ struct usbhs_priv *priv = data;
+ struct usbhs_irq_state irq_state;
+
+ if (usbhs_status_get_each_irq(priv, &irq_state) < 0)
+ return IRQ_NONE;
+
+ /*
+ * clear interrupt
+ *
+ * The hardware is _very_ picky to clear interrupt bit.
+ * Especially INTSTS0_MAGIC, INTSTS1_MAGIC value.
+ *
+ * see
+ * "Operation"
+ * - "Control Transfer (DCP)"
+ * - Function :: VALID bit should 0
+ */
+ usbhs_write(priv, INTSTS0, ~irq_state.intsts0 & INTSTS0_MAGIC);
+ if (usbhs_mod_is_host(priv))
+ usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC);
+
+ /*
+ * The driver should not clear the xxxSTS after the line of
+ * "call irq callback functions" because each "if" statement is
+ * possible to call the callback function for avoiding any side effects.
+ */
+ if (irq_state.intsts0 & BRDY)
+ usbhs_write(priv, BRDYSTS, ~irq_state.brdysts);
+ usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts);
+ if (irq_state.intsts0 & BEMP)
+ usbhs_write(priv, BEMPSTS, ~irq_state.bempsts);
+
+ /*
+ * call irq callback functions
+ * see also
+ * usbhs_irq_setting_update
+ */
+
+ /* INTSTS0 */
+ if (irq_state.intsts0 & VBINT)
+ usbhs_mod_info_call(priv, irq_vbus, priv, &irq_state);
+
+ if (irq_state.intsts0 & DVST)
+ usbhs_mod_call(priv, irq_dev_state, priv, &irq_state);
+
+ if (irq_state.intsts0 & CTRT)
+ usbhs_mod_call(priv, irq_ctrl_stage, priv, &irq_state);
+
+ if (irq_state.intsts0 & BEMP)
+ usbhs_mod_call(priv, irq_empty, priv, &irq_state);
+
+ if (irq_state.intsts0 & BRDY)
+ usbhs_mod_call(priv, irq_ready, priv, &irq_state);
+
+ if (usbhs_mod_is_host(priv)) {
+ /* INTSTS1 */
+ if (irq_state.intsts1 & ATTCH)
+ usbhs_mod_call(priv, irq_attch, priv, &irq_state);
+
+ if (irq_state.intsts1 & DTCH)
+ usbhs_mod_call(priv, irq_dtch, priv, &irq_state);
+
+ if (irq_state.intsts1 & SIGN)
+ usbhs_mod_call(priv, irq_sign, priv, &irq_state);
+
+ if (irq_state.intsts1 & SACK)
+ usbhs_mod_call(priv, irq_sack, priv, &irq_state);
+ }
+ return IRQ_HANDLED;
+}
+
+void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod)
+{
+ u16 intenb0 = 0;
+ u16 intenb1 = 0;
+ struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv);
+
+ /*
+ * BEMPENB/BRDYENB are picky.
+ * below method is required
+ *
+ * - clear INTSTS0
+ * - update BEMPENB/BRDYENB
+ * - update INTSTS0
+ */
+ usbhs_write(priv, INTENB0, 0);
+ if (usbhs_mod_is_host(priv))
+ usbhs_write(priv, INTENB1, 0);
+
+ usbhs_write(priv, BEMPENB, 0);
+ usbhs_write(priv, BRDYENB, 0);
+
+ /*
+ * see also
+ * usbhs_interrupt
+ */
+
+ if (info->irq_vbus)
+ intenb0 |= VBSE;
+
+ if (mod) {
+ /*
+ * INTSTS0
+ */
+ if (mod->irq_ctrl_stage)
+ intenb0 |= CTRE;
+
+ if (mod->irq_dev_state)
+ intenb0 |= DVSE;
+
+ if (mod->irq_empty && mod->irq_bempsts) {
+ usbhs_write(priv, BEMPENB, mod->irq_bempsts);
+ intenb0 |= BEMPE;
+ }
+
+ if (mod->irq_ready && mod->irq_brdysts) {
+ usbhs_write(priv, BRDYENB, mod->irq_brdysts);
+ intenb0 |= BRDYE;
+ }
+
+ if (usbhs_mod_is_host(priv)) {
+ /*
+ * INTSTS1
+ */
+ if (mod->irq_attch)
+ intenb1 |= ATTCHE;
+
+ if (mod->irq_dtch)
+ intenb1 |= DTCHE;
+
+ if (mod->irq_sign)
+ intenb1 |= SIGNE;
+
+ if (mod->irq_sack)
+ intenb1 |= SACKE;
+ }
+ }
+
+ if (intenb0)
+ usbhs_write(priv, INTENB0, intenb0);
+
+ if (usbhs_mod_is_host(priv) && intenb1)
+ usbhs_write(priv, INTENB1, intenb1);
+}
diff --git a/drivers/usb/gadget/rcar/mod.h b/drivers/usb/gadget/rcar/mod.h
new file mode 100644
index 00000000000..b670e950a28
--- /dev/null
+++ b/drivers/usb/gadget/rcar/mod.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-1.0+ */
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#ifndef RENESAS_USB_MOD_H
+#define RENESAS_USB_MOD_H
+
+#include "common.h"
+
+/*
+ * struct
+ */
+struct usbhs_irq_state {
+ u16 intsts0;
+ u16 intsts1;
+ u16 brdysts;
+ u16 nrdysts;
+ u16 bempsts;
+};
+
+struct usbhs_mod {
+ char *name;
+
+ /*
+ * entry point from common.c
+ */
+ int (*start)(struct usbhs_priv *priv);
+ int (*stop)(struct usbhs_priv *priv);
+
+ /*
+ * INTSTS0
+ */
+
+ /* DVST (DVSQ) */
+ int (*irq_dev_state)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ /* CTRT (CTSQ) */
+ int (*irq_ctrl_stage)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ /* BEMP / BEMPSTS */
+ int (*irq_empty)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+ u16 irq_bempsts;
+
+ /* BRDY / BRDYSTS */
+ int (*irq_ready)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+ u16 irq_brdysts;
+
+ /*
+ * INTSTS1
+ */
+
+ /* ATTCHE */
+ int (*irq_attch)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ /* DTCHE */
+ int (*irq_dtch)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ /* SIGN */
+ int (*irq_sign)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ /* SACK */
+ int (*irq_sack)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+
+ struct usbhs_priv *priv;
+};
+
+struct usbhs_mod_info {
+ struct usbhs_mod *mod[USBHS_MAX];
+ struct usbhs_mod *curt; /* current mod */
+
+ /*
+ * INTSTS0 :: VBINT
+ *
+ * This function will be used as autonomy mode (runtime_pwctrl == 0)
+ * when the platform doesn't have own get_vbus function.
+ *
+ * This callback cannot be member of "struct usbhs_mod" because it
+ * will be used even though host/gadget has not been selected.
+ */
+ int (*irq_vbus)(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state);
+};
+
+/*
+ * for host/gadget module
+ */
+struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id);
+struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv);
+void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *usb, int id);
+int usbhs_mod_is_host(struct usbhs_priv *priv);
+int usbhs_mod_change(struct usbhs_priv *priv, int id);
+int usbhs_mod_probe(struct usbhs_priv *priv);
+void usbhs_mod_remove(struct usbhs_priv *priv);
+
+void usbhs_mod_autonomy_mode(struct usbhs_priv *priv);
+void usbhs_mod_non_autonomy_mode(struct usbhs_priv *priv);
+
+/*
+ * status functions
+ */
+int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state);
+int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state);
+
+/*
+ * callback functions
+ */
+void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod);
+
+irqreturn_t usbhs_interrupt(int irq, void *data);
+
+#define usbhs_mod_call(priv, func, param...) \
+ ({ \
+ struct usbhs_mod *mod; \
+ mod = usbhs_mod_get_current(priv); \
+ !mod ? -ENODEV : \
+ !mod->func ? 0 : \
+ mod->func(param); \
+ })
+
+#define usbhs_priv_to_modinfo(priv) (&priv->mod_info)
+#define usbhs_mod_info_call(priv, func, param...) \
+({ \
+ struct usbhs_mod_info *info; \
+ info = usbhs_priv_to_modinfo(priv); \
+ !info->func ? 0 : \
+ info->func(param); \
+})
+
+/*
+ * host / gadget control
+ */
+#if defined(CONFIG_USB_RENESAS_USBHS_HCD) || \
+ defined(CONFIG_USB_RENESAS_USBHS_HCD_MODULE)
+extern int usbhs_mod_host_probe(struct usbhs_priv *priv);
+extern int usbhs_mod_host_remove(struct usbhs_priv *priv);
+#else
+static inline int usbhs_mod_host_probe(struct usbhs_priv *priv)
+{
+ return 0;
+}
+static inline void usbhs_mod_host_remove(struct usbhs_priv *priv)
+{
+}
+#endif
+
+extern int usbhs_mod_gadget_probe(struct usbhs_priv *priv);
+extern void usbhs_mod_gadget_remove(struct usbhs_priv *priv);
+
+#endif /* RENESAS_USB_MOD_H */
diff --git a/drivers/usb/gadget/rcar/mod_gadget.c b/drivers/usb/gadget/rcar/mod_gadget.c
new file mode 100644
index 00000000000..bd9855eb4fa
--- /dev/null
+++ b/drivers/usb/gadget/rcar/mod_gadget.c
@@ -0,0 +1,1136 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2019 Renesas Electronics Corporation
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include "common.h"
+
+/*
+ * struct
+ */
+struct usbhsg_request {
+ struct usb_request req;
+ struct usbhs_pkt pkt;
+};
+
+#define EP_NAME_SIZE 8
+struct usbhsg_gpriv;
+struct usbhsg_uep {
+ struct usb_ep ep;
+ struct usbhs_pipe *pipe;
+ spinlock_t lock; /* protect the pipe */
+
+ char ep_name[EP_NAME_SIZE];
+
+ struct usbhsg_gpriv *gpriv;
+};
+
+struct usbhsg_gpriv {
+ struct usb_gadget gadget;
+ struct usbhs_mod mod;
+
+ struct usbhsg_uep *uep;
+ int uep_size;
+
+ struct usb_gadget_driver *driver;
+ bool vbus_active;
+
+ u32 status;
+#define USBHSG_STATUS_STARTED (1 << 0)
+#define USBHSG_STATUS_REGISTERD (1 << 1)
+#define USBHSG_STATUS_WEDGE (1 << 2)
+#define USBHSG_STATUS_SELF_POWERED (1 << 3)
+#define USBHSG_STATUS_SOFT_CONNECT (1 << 4)
+};
+
+struct usbhsg_recip_handle {
+ char *name;
+ int (*device)(struct usbhs_priv *priv, struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl);
+ int (*interface)(struct usbhs_priv *priv, struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl);
+ int (*endpoint)(struct usbhs_priv *priv, struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl);
+};
+
+/*
+ * macro
+ */
+#define usbhsg_priv_to_gpriv(priv) \
+ container_of( \
+ usbhs_mod_get(priv, USBHS_GADGET), \
+ struct usbhsg_gpriv, mod)
+
+#define __usbhsg_for_each_uep(start, pos, g, i) \
+ for ((i) = start; \
+ ((i) < (g)->uep_size) && ((pos) = (g)->uep + (i)); \
+ (i)++)
+
+#define usbhsg_for_each_uep(pos, gpriv, i) \
+ __usbhsg_for_each_uep(1, pos, gpriv, i)
+
+#define usbhsg_for_each_uep_with_dcp(pos, gpriv, i) \
+ __usbhsg_for_each_uep(0, pos, gpriv, i)
+
+#define usbhsg_gadget_to_gpriv(g)\
+ container_of(g, struct usbhsg_gpriv, gadget)
+
+#define usbhsg_req_to_ureq(r)\
+ container_of(r, struct usbhsg_request, req)
+
+#define usbhsg_ep_to_uep(e) container_of(e, struct usbhsg_uep, ep)
+#define usbhsg_gpriv_to_dev(gp) usbhs_priv_to_dev((gp)->mod.priv)
+#define usbhsg_gpriv_to_priv(gp) ((gp)->mod.priv)
+#define usbhsg_gpriv_to_dcp(gp) ((gp)->uep)
+#define usbhsg_gpriv_to_nth_uep(gp, i) ((gp)->uep + i)
+#define usbhsg_uep_to_gpriv(u) ((u)->gpriv)
+#define usbhsg_uep_to_pipe(u) ((u)->pipe)
+#define usbhsg_pipe_to_uep(p) ((p)->mod_private)
+#define usbhsg_is_dcp(u) ((u) == usbhsg_gpriv_to_dcp((u)->gpriv))
+
+#define usbhsg_ureq_to_pkt(u) (&(u)->pkt)
+#define usbhsg_pkt_to_ureq(i) \
+ container_of(i, struct usbhsg_request, pkt)
+
+#define usbhsg_is_not_connected(gp) ((gp)->gadget.speed == USB_SPEED_UNKNOWN)
+
+/* status */
+#define usbhsg_status_init(gp) do {(gp)->status = 0; } while (0)
+#define usbhsg_status_set(gp, b) (gp->status |= b)
+#define usbhsg_status_clr(gp, b) (gp->status &= ~b)
+#define usbhsg_status_has(gp, b) (gp->status & b)
+
+/*
+ * queue push/pop
+ */
+static void __usbhsg_queue_pop(struct usbhsg_uep *uep,
+ struct usbhsg_request *ureq,
+ int status)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+
+ if (pipe)
+ dev_dbg(dev, "pipe %d : queue pop\n", usbhs_pipe_number(pipe));
+
+ ureq->req.status = status;
+ spin_unlock(usbhs_priv_to_lock(priv));
+ usb_gadget_giveback_request(&uep->ep, &ureq->req);
+ spin_lock(usbhs_priv_to_lock(priv));
+}
+
+static void usbhsg_queue_pop(struct usbhsg_uep *uep,
+ struct usbhsg_request *ureq,
+ int status)
+{
+ unsigned long flags;
+
+ usbhs_lock(priv, flags);
+ __usbhsg_queue_pop(uep, ureq, status);
+ usbhs_unlock(priv, flags);
+}
+
+static void usbhsg_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt)
+{
+ struct usbhs_pipe *pipe = pkt->pipe;
+ struct usbhsg_uep *uep = usbhsg_pipe_to_uep(pipe);
+ struct usbhsg_request *ureq = usbhsg_pkt_to_ureq(pkt);
+ unsigned long flags;
+
+ ureq->req.actual = pkt->actual;
+
+ usbhs_lock(priv, flags);
+ if (uep)
+ __usbhsg_queue_pop(uep, ureq, 0);
+ usbhs_unlock(priv, flags);
+}
+
+static void usbhsg_queue_push(struct usbhsg_uep *uep,
+ struct usbhsg_request *ureq)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+ struct usbhs_pkt *pkt = usbhsg_ureq_to_pkt(ureq);
+ struct usb_request *req = &ureq->req;
+
+ req->actual = 0;
+ req->status = -EINPROGRESS;
+ usbhs_pkt_push(pipe, pkt, usbhsg_queue_done,
+ req->buf, req->length, req->zero, -1);
+ usbhs_pkt_start(pipe);
+
+ dev_dbg(dev, "pipe %d : queue push (%d)\n",
+ usbhs_pipe_number(pipe),
+ req->length);
+}
+
+/*
+ * dma map/unmap
+ */
+static int usbhsg_dma_map_ctrl(struct usbhs_pkt *pkt, int map)
+{
+ return -1;
+}
+
+/*
+ * USB_TYPE_STANDARD / clear feature functions
+ */
+static int usbhsg_recip_handler_std_control_done(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp);
+
+ usbhs_dcp_control_transfer_done(pipe);
+
+ return 0;
+}
+
+static int usbhsg_recip_handler_std_clear_endpoint(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+
+ if (!usbhsg_status_has(gpriv, USBHSG_STATUS_WEDGE)) {
+ usbhs_pipe_disable(pipe);
+ usbhs_pipe_sequence_data0(pipe);
+ usbhs_pipe_enable(pipe);
+ }
+
+ usbhsg_recip_handler_std_control_done(priv, uep, ctrl);
+
+ usbhs_pkt_start(pipe);
+
+ return 0;
+}
+
+static struct usbhsg_recip_handle req_clear_feature = {
+ .name = "clear feature",
+ .device = usbhsg_recip_handler_std_control_done,
+ .interface = usbhsg_recip_handler_std_control_done,
+ .endpoint = usbhsg_recip_handler_std_clear_endpoint,
+};
+
+/*
+ * USB_TYPE_STANDARD / set feature functions
+ */
+static int usbhsg_recip_handler_std_set_device(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ switch (le16_to_cpu(ctrl->wValue)) {
+ case USB_DEVICE_TEST_MODE:
+ usbhsg_recip_handler_std_control_done(priv, uep, ctrl);
+ udelay(100);
+ usbhs_sys_set_test_mode(priv, le16_to_cpu(ctrl->wIndex) >> 8);
+ break;
+ default:
+ usbhsg_recip_handler_std_control_done(priv, uep, ctrl);
+ break;
+ }
+
+ return 0;
+}
+
+static int usbhsg_recip_handler_std_set_endpoint(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+
+ usbhs_pipe_stall(pipe);
+
+ usbhsg_recip_handler_std_control_done(priv, uep, ctrl);
+
+ return 0;
+}
+
+static struct usbhsg_recip_handle req_set_feature = {
+ .name = "set feature",
+ .device = usbhsg_recip_handler_std_set_device,
+ .interface = usbhsg_recip_handler_std_control_done,
+ .endpoint = usbhsg_recip_handler_std_set_endpoint,
+};
+
+/*
+ * USB_TYPE_STANDARD / get status functions
+ */
+static void __usbhsg_recip_send_complete(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct usbhsg_request *ureq = usbhsg_req_to_ureq(req);
+
+ /* free allocated recip-buffer/usb_request */
+ kfree(ureq->pkt.buf);
+ usb_ep_free_request(ep, req);
+}
+
+static void __usbhsg_recip_send_status(struct usbhsg_gpriv *gpriv,
+ unsigned short status)
+{
+ struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ struct usb_request *req;
+ __le16 *buf;
+
+ /* alloc new usb_request for recip */
+ req = usb_ep_alloc_request(&dcp->ep, GFP_ATOMIC);
+ if (!req) {
+ dev_err(dev, "recip request allocation fail\n");
+ return;
+ }
+
+ /* alloc recip data buffer */
+ buf = kmalloc(sizeof(*buf), GFP_ATOMIC);
+ if (!buf) {
+ usb_ep_free_request(&dcp->ep, req);
+ return;
+ }
+
+ /* recip data is status */
+ *buf = cpu_to_le16(status);
+
+ /* allocated usb_request/buffer will be freed */
+ req->complete = __usbhsg_recip_send_complete;
+ req->buf = buf;
+ req->length = sizeof(*buf);
+ req->zero = 0;
+
+ /* push packet */
+ pipe->handler = &usbhs_fifo_pio_push_handler;
+ usbhsg_queue_push(dcp, usbhsg_req_to_ureq(req));
+}
+
+static int usbhsg_recip_handler_std_get_device(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ unsigned short status = 0;
+
+ if (usbhsg_status_has(gpriv, USBHSG_STATUS_SELF_POWERED))
+ status = 1 << USB_DEVICE_SELF_POWERED;
+
+ __usbhsg_recip_send_status(gpriv, status);
+
+ return 0;
+}
+
+static int usbhsg_recip_handler_std_get_interface(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ unsigned short status = 0;
+
+ __usbhsg_recip_send_status(gpriv, status);
+
+ return 0;
+}
+
+static int usbhsg_recip_handler_std_get_endpoint(struct usbhs_priv *priv,
+ struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+ unsigned short status = 0;
+
+ if (usbhs_pipe_is_stall(pipe))
+ status = 1 << USB_ENDPOINT_HALT;
+
+ __usbhsg_recip_send_status(gpriv, status);
+
+ return 0;
+}
+
+static struct usbhsg_recip_handle req_get_status = {
+ .name = "get status",
+ .device = usbhsg_recip_handler_std_get_device,
+ .interface = usbhsg_recip_handler_std_get_interface,
+ .endpoint = usbhsg_recip_handler_std_get_endpoint,
+};
+
+/*
+ * USB_TYPE handler
+ */
+static int usbhsg_recip_run_handle(struct usbhs_priv *priv,
+ struct usbhsg_recip_handle *handler,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ struct usbhsg_uep *uep;
+ struct usbhs_pipe *pipe;
+ int recip = ctrl->bRequestType & USB_RECIP_MASK;
+ int nth = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK;
+ int ret = 0;
+ int (*func)(struct usbhs_priv *priv, struct usbhsg_uep *uep,
+ struct usb_ctrlrequest *ctrl);
+ char *msg;
+
+ uep = usbhsg_gpriv_to_nth_uep(gpriv, nth);
+ pipe = usbhsg_uep_to_pipe(uep);
+ if (!pipe) {
+ dev_err(dev, "wrong recip request\n");
+ return -EINVAL;
+ }
+
+ switch (recip) {
+ case USB_RECIP_DEVICE:
+ msg = "DEVICE";
+ func = handler->device;
+ break;
+ case USB_RECIP_INTERFACE:
+ msg = "INTERFACE";
+ func = handler->interface;
+ break;
+ case USB_RECIP_ENDPOINT:
+ msg = "ENDPOINT";
+ func = handler->endpoint;
+ break;
+ default:
+ dev_warn(dev, "unsupported RECIP(%d)\n", recip);
+ func = NULL;
+ ret = -EINVAL;
+ }
+
+ if (func) {
+ dev_dbg(dev, "%s (pipe %d :%s)\n", handler->name, nth, msg);
+ ret = func(priv, uep, ctrl);
+ }
+
+ return ret;
+}
+
+/*
+ * irq functions
+ *
+ * it will be called from usbhs_interrupt
+ */
+static int usbhsg_irq_dev_state(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ int state = usbhs_status_get_device_state(irq_state);
+
+ gpriv->gadget.speed = usbhs_bus_get_speed(priv);
+
+ dev_dbg(dev, "state = %x : speed : %d\n", state, gpriv->gadget.speed);
+
+ if (gpriv->gadget.speed != USB_SPEED_UNKNOWN &&
+ (state & SUSPENDED_STATE)) {
+ if (gpriv->driver && gpriv->driver->suspend)
+ gpriv->driver->suspend(&gpriv->gadget);
+ usb_gadget_set_state(&gpriv->gadget, USB_STATE_SUSPENDED);
+ }
+
+ return 0;
+}
+
+static int usbhsg_irq_ctrl_stage(struct usbhs_priv *priv,
+ struct usbhs_irq_state *irq_state)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ struct usb_ctrlrequest ctrl;
+ struct usbhsg_recip_handle *recip_handler = NULL;
+ int stage = usbhs_status_get_ctrl_stage(irq_state);
+ int ret = 0;
+
+ dev_dbg(dev, "stage = %d\n", stage);
+
+ /*
+ * see Manual
+ *
+ * "Operation"
+ * - "Interrupt Function"
+ * - "Control Transfer Stage Transition Interrupt"
+ * - Fig. "Control Transfer Stage Transitions"
+ */
+
+ switch (stage) {
+ case READ_DATA_STAGE:
+ pipe->handler = &usbhs_fifo_pio_push_handler;
+ break;
+ case WRITE_DATA_STAGE:
+ pipe->handler = &usbhs_fifo_pio_pop_handler;
+ break;
+ case NODATA_STATUS_STAGE:
+ pipe->handler = &usbhs_ctrl_stage_end_handler;
+ break;
+ case READ_STATUS_STAGE:
+ case WRITE_STATUS_STAGE:
+ usbhs_dcp_control_transfer_done(pipe);
+ fallthrough;
+ default:
+ return ret;
+ }
+
+ /*
+ * get usb request
+ */
+ usbhs_usbreq_get_val(priv, &ctrl);
+
+ switch (ctrl.bRequestType & USB_TYPE_MASK) {
+ case USB_TYPE_STANDARD:
+ switch (ctrl.bRequest) {
+ case USB_REQ_CLEAR_FEATURE:
+ recip_handler = &req_clear_feature;
+ break;
+ case USB_REQ_SET_FEATURE:
+ recip_handler = &req_set_feature;
+ break;
+ case USB_REQ_GET_STATUS:
+ recip_handler = &req_get_status;
+ break;
+ }
+ }
+
+ /*
+ * setup stage / run recip
+ */
+ if (recip_handler)
+ ret = usbhsg_recip_run_handle(priv, recip_handler, &ctrl);
+ else
+ ret = gpriv->driver->setup(&gpriv->gadget, &ctrl);
+
+ if (ret < 0)
+ usbhs_pipe_stall(pipe);
+
+ return ret;
+}
+
+/*
+ *
+ * usb_dcp_ops
+ *
+ */
+static int usbhsg_pipe_disable(struct usbhsg_uep *uep)
+{
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+ struct usbhs_pkt *pkt;
+
+ while (1) {
+ pkt = usbhs_pkt_pop(pipe, NULL);
+ if (!pkt)
+ break;
+
+ usbhsg_queue_pop(uep, usbhsg_pkt_to_ureq(pkt), -ESHUTDOWN);
+ }
+
+ usbhs_pipe_disable(pipe);
+
+ return 0;
+}
+
+/*
+ *
+ * usb_ep_ops
+ *
+ */
+static int usbhsg_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep);
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+ struct usbhs_pipe *pipe;
+ int ret = -EIO;
+ unsigned long flags;
+
+ usbhs_lock(priv, flags);
+
+ /*
+ * if it already have pipe,
+ * nothing to do
+ */
+ if (uep->pipe) {
+ usbhs_pipe_clear(uep->pipe);
+ usbhs_pipe_sequence_data0(uep->pipe);
+ ret = 0;
+ goto usbhsg_ep_enable_end;
+ }
+
+ pipe = usbhs_pipe_malloc(priv,
+ usb_endpoint_type(desc),
+ usb_endpoint_dir_in(desc));
+ if (pipe) {
+ uep->pipe = pipe;
+ pipe->mod_private = uep;
+
+ /* set epnum / maxp */
+ usbhs_pipe_config_update(pipe, 0,
+ usb_endpoint_num(desc),
+ usb_endpoint_maxp(desc));
+
+ /*
+ * usbhs_fifo_dma_push/pop_handler try to
+ * use dmaengine if possible.
+ * It will use pio handler if impossible.
+ */
+ if (usb_endpoint_dir_in(desc)) {
+ pipe->handler = &usbhs_fifo_dma_push_handler;
+ } else {
+ pipe->handler = &usbhs_fifo_dma_pop_handler;
+ usbhs_xxxsts_clear(priv, BRDYSTS,
+ usbhs_pipe_number(pipe));
+ }
+
+ ret = 0;
+ }
+
+usbhsg_ep_enable_end:
+ usbhs_unlock(priv, flags);
+
+ return ret;
+}
+
+static int usbhsg_ep_disable(struct usb_ep *ep)
+{
+ struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep);
+ struct usbhs_pipe *pipe;
+ unsigned long flags;
+
+ spin_lock_irqsave(&uep->lock, flags);
+ pipe = usbhsg_uep_to_pipe(uep);
+ if (!pipe)
+ goto out;
+
+ usbhsg_pipe_disable(uep);
+ usbhs_pipe_free(pipe);
+
+ uep->pipe->mod_private = NULL;
+ uep->pipe = NULL;
+
+out:
+ spin_unlock_irqrestore(&uep->lock, flags);
+
+ return 0;
+}
+
+static struct usb_request *usbhsg_ep_alloc_request(struct usb_ep *ep,
+ gfp_t gfp_flags)
+{
+ struct usbhsg_request *ureq;
+
+ ureq = kzalloc(sizeof *ureq, gfp_flags);
+ if (!ureq)
+ return NULL;
+
+ usbhs_pkt_init(usbhsg_ureq_to_pkt(ureq));
+
+ return &ureq->req;
+}
+
+static void usbhsg_ep_free_request(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct usbhsg_request *ureq = usbhsg_req_to_ureq(req);
+
+ WARN_ON(!list_empty(&ureq->pkt.node));
+ kfree(ureq);
+}
+
+static int usbhsg_ep_queue(struct usb_ep *ep, struct usb_request *req,
+ gfp_t gfp_flags)
+{
+ struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep);
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct usbhsg_request *ureq = usbhsg_req_to_ureq(req);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+
+ /* param check */
+ if (usbhsg_is_not_connected(gpriv) ||
+ unlikely(!gpriv->driver) ||
+ unlikely(!pipe))
+ return -ESHUTDOWN;
+
+ usbhsg_queue_push(uep, ureq);
+
+ return 0;
+}
+
+static int usbhsg_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
+{
+ struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep);
+ struct usbhsg_request *ureq = usbhsg_req_to_ureq(req);
+ struct usbhs_pipe *pipe;
+ unsigned long flags;
+
+ spin_lock_irqsave(&uep->lock, flags);
+ pipe = usbhsg_uep_to_pipe(uep);
+ if (pipe)
+ usbhs_pkt_pop(pipe, usbhsg_ureq_to_pkt(ureq));
+
+ /*
+ * To dequeue a request, this driver should call the usbhsg_queue_pop()
+ * even if the pipe is NULL.
+ */
+ usbhsg_queue_pop(uep, ureq, -ECONNRESET);
+ spin_unlock_irqrestore(&uep->lock, flags);
+
+ return 0;
+}
+
+bool usbhs_pipe_contains_transmittable_data(struct usbhs_pipe *pipe);
+static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge)
+{
+ struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep);
+ struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep);
+ struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep);
+ struct device *dev = usbhsg_gpriv_to_dev(gpriv);
+ unsigned long flags;
+ int ret = 0;
+
+ dev_dbg(dev, "set halt %d (pipe %d)\n",
+ halt, usbhs_pipe_number(pipe));
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ /*
+ * According to usb_ep_set_halt()'s description, this function should
+ * return -EAGAIN if the IN endpoint has any queue or data. Note
+ * that the usbhs_pipe_is_dir_in() returns false if the pipe is an
+ * IN endpoint in the gadget mode.
+ */
+ if (!usbhs_pipe_is_dir_in(pipe) && (__usbhsf_pkt_get(pipe) ||
+ usbhs_pipe_contains_transmittable_data(pipe))) {
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ if (halt)
+ usbhs_pipe_stall(pipe);
+ else
+ usbhs_pipe_disable(pipe);
+
+ if (halt && wedge)
+ usbhsg_status_set(gpriv, USBHSG_STATUS_WEDGE);
+ else
+ usbhsg_status_clr(gpriv, USBHSG_STATUS_WEDGE);
+
+out:
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ******************/
+
+ return ret;
+}
+
+static int usbhsg_ep_set_halt(struct usb_ep *ep, int value)
+{
+ return __usbhsg_ep_set_halt_wedge(ep, value, 0);
+}
+
+static int usbhsg_ep_set_wedge(struct usb_ep *ep)
+{
+ return __usbhsg_ep_set_halt_wedge(ep, 1, 1);
+}
+
+static const struct usb_ep_ops usbhsg_ep_ops = {
+ .enable = usbhsg_ep_enable,
+ .disable = usbhsg_ep_disable,
+
+ .alloc_request = usbhsg_ep_alloc_request,
+ .free_request = usbhsg_ep_free_request,
+
+ .queue = usbhsg_ep_queue,
+ .dequeue = usbhsg_ep_dequeue,
+
+ .set_halt = usbhsg_ep_set_halt,
+ .set_wedge = usbhsg_ep_set_wedge,
+};
+
+/*
+ * pullup control
+ */
+static int usbhsg_can_pullup(struct usbhs_priv *priv)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+
+ return gpriv->driver &&
+ usbhsg_status_has(gpriv, USBHSG_STATUS_SOFT_CONNECT);
+}
+
+static void usbhsg_update_pullup(struct usbhs_priv *priv)
+{
+ if (usbhsg_can_pullup(priv))
+ usbhs_sys_function_pullup(priv, 1);
+ else
+ usbhs_sys_function_pullup(priv, 0);
+}
+
+/*
+ * usb module start/end
+ */
+static int usbhsg_try_start(struct usbhs_priv *priv, u32 status)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv);
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ unsigned long flags;
+ int ret = 0;
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ usbhsg_status_set(gpriv, status);
+ if (!(usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) &&
+ usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD)))
+ ret = -1; /* not ready */
+
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ********************/
+
+ if (ret < 0)
+ return 0; /* not ready is not error */
+
+ /*
+ * enable interrupt and systems if ready
+ */
+ dev_dbg(dev, "start gadget\n");
+
+ /*
+ * pipe initialize and enable DCP
+ */
+ usbhs_fifo_init(priv);
+ usbhs_pipe_init(priv,
+ usbhsg_dma_map_ctrl);
+
+ /* dcp init instead of usbhsg_ep_enable() */
+ dcp->pipe = usbhs_dcp_malloc(priv);
+ dcp->pipe->mod_private = dcp;
+ usbhs_pipe_config_update(dcp->pipe, 0, 0, 64);
+
+ /*
+ * system config enble
+ * - HI speed
+ * - function
+ * - usb module
+ */
+ usbhs_sys_function_ctrl(priv, 1);
+ usbhsg_update_pullup(priv);
+
+ /*
+ * enable irq callback
+ */
+ mod->irq_dev_state = usbhsg_irq_dev_state;
+ mod->irq_ctrl_stage = usbhsg_irq_ctrl_stage;
+ usbhs_irq_callback_update(priv, mod);
+
+ return 0;
+}
+
+static int usbhsg_try_stop(struct usbhs_priv *priv, u32 status)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ struct usbhs_mod *mod = usbhs_mod_get_current(priv);
+ struct usbhsg_uep *uep;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ unsigned long flags;
+ int ret = 0, i;
+
+ /******************** spin lock ********************/
+ usbhs_lock(priv, flags);
+
+ usbhsg_status_clr(gpriv, status);
+ if (!usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) &&
+ !usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD))
+ ret = -1; /* already done */
+
+ usbhs_unlock(priv, flags);
+ /******************** spin unlock ********************/
+
+ if (ret < 0)
+ return 0; /* already done is not error */
+
+ /*
+ * disable interrupt and systems if 1st try
+ */
+ usbhs_fifo_quit(priv);
+
+ /* disable all irq */
+ mod->irq_dev_state = NULL;
+ mod->irq_ctrl_stage = NULL;
+ usbhs_irq_callback_update(priv, mod);
+
+ gpriv->gadget.speed = USB_SPEED_UNKNOWN;
+
+ /* disable sys */
+ usbhs_sys_set_test_mode(priv, 0);
+ usbhs_sys_function_ctrl(priv, 0);
+
+ /* disable all eps */
+ usbhsg_for_each_uep_with_dcp(uep, gpriv, i)
+ usbhsg_ep_disable(&uep->ep);
+
+ dev_dbg(dev, "stop gadget\n");
+
+ return 0;
+}
+
+/*
+ * VBUS provided by the PHY
+ */
+static void usbhs_mod_phy_mode(struct usbhs_priv *priv)
+{
+ struct usbhs_mod_info *info = &priv->mod_info;
+
+ info->irq_vbus = NULL;
+
+ usbhs_irq_callback_update(priv, NULL);
+}
+
+/*
+ *
+ * linux usb function
+ *
+ */
+static int usbhsg_gadget_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+
+ if (!driver || !driver->setup)
+ return -EINVAL;
+
+ /* get vbus using phy versions */
+ usbhs_mod_phy_mode(priv);
+
+ /* first hook up the driver ... */
+ gpriv->driver = driver;
+
+ return usbhsg_try_start(priv, USBHSG_STATUS_REGISTERD);
+}
+
+static int usbhsg_gadget_stop(struct usb_gadget *gadget)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+
+ usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD);
+
+ gpriv->driver = NULL;
+
+ return 0;
+}
+
+/*
+ * usb gadget ops
+ */
+static int usbhsg_get_frame(struct usb_gadget *gadget)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+
+ return usbhs_frame_get_num(priv);
+}
+
+static int usbhsg_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+ unsigned long flags;
+
+ usbhs_lock(priv, flags);
+ if (is_on)
+ usbhsg_status_set(gpriv, USBHSG_STATUS_SOFT_CONNECT);
+ else
+ usbhsg_status_clr(gpriv, USBHSG_STATUS_SOFT_CONNECT);
+ usbhsg_update_pullup(priv);
+ usbhs_unlock(priv, flags);
+
+ return 0;
+}
+
+static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+
+ if (is_self)
+ usbhsg_status_set(gpriv, USBHSG_STATUS_SELF_POWERED);
+ else
+ usbhsg_status_clr(gpriv, USBHSG_STATUS_SELF_POWERED);
+
+ return 0;
+}
+
+static int usbhsg_vbus_session(struct usb_gadget *gadget, int is_active)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget);
+ struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv);
+
+ gpriv->vbus_active = !!is_active;
+
+ usbhsc_hotplug(priv);
+
+ return 0;
+}
+
+static const struct usb_gadget_ops usbhsg_gadget_ops = {
+ .get_frame = usbhsg_get_frame,
+ .set_selfpowered = usbhsg_set_selfpowered,
+ .udc_start = usbhsg_gadget_start,
+ .udc_stop = usbhsg_gadget_stop,
+ .pullup = usbhsg_pullup,
+ .vbus_session = usbhsg_vbus_session,
+};
+
+static int usbhsg_start(struct usbhs_priv *priv)
+{
+ return usbhsg_try_start(priv, USBHSG_STATUS_STARTED);
+}
+
+static int usbhsg_stop(struct usbhs_priv *priv)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+
+ /* cable disconnect */
+ if (gpriv->driver &&
+ gpriv->driver->disconnect)
+ gpriv->driver->disconnect(&gpriv->gadget);
+
+ return usbhsg_try_stop(priv, USBHSG_STATUS_STARTED);
+}
+
+int usbhs_mod_gadget_probe(struct usbhs_priv *priv)
+{
+ struct usbhsg_gpriv *gpriv;
+ struct usbhsg_uep *uep;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ struct renesas_usbhs_driver_pipe_config *pipe_configs =
+ usbhs_get_dparam(priv, pipe_configs);
+ int pipe_size = usbhs_get_dparam(priv, pipe_size);
+ int i;
+ int ret;
+
+ gpriv = kzalloc(sizeof(struct usbhsg_gpriv), GFP_KERNEL);
+ if (!gpriv)
+ return -ENOMEM;
+
+ uep = kcalloc(pipe_size, sizeof(struct usbhsg_uep), GFP_KERNEL);
+ if (!uep) {
+ ret = -ENOMEM;
+ goto usbhs_mod_gadget_probe_err_gpriv;
+ }
+
+ /*
+ * CAUTION
+ *
+ * There is no guarantee that it is possible to access usb module here.
+ * Don't accesses to it.
+ * The accesse will be enable after "usbhsg_start"
+ */
+
+ /*
+ * register itself
+ */
+ usbhs_mod_register(priv, &gpriv->mod, USBHS_GADGET);
+
+ /* init gpriv */
+ gpriv->mod.name = "gadget";
+ gpriv->mod.start = usbhsg_start;
+ gpriv->mod.stop = usbhsg_stop;
+ gpriv->uep = uep;
+ gpriv->uep_size = pipe_size;
+ usbhsg_status_init(gpriv);
+
+ /*
+ * init gadget
+ */
+ gpriv->gadget.dev.parent = dev;
+ gpriv->gadget.name = "renesas_usbhs_udc";
+ gpriv->gadget.ops = &usbhsg_gadget_ops;
+ gpriv->gadget.max_speed = USB_SPEED_HIGH;
+
+ INIT_LIST_HEAD(&gpriv->gadget.ep_list);
+
+ /*
+ * init usb_ep
+ */
+ usbhsg_for_each_uep_with_dcp(uep, gpriv, i) {
+ uep->gpriv = gpriv;
+ uep->pipe = NULL;
+ snprintf(uep->ep_name, EP_NAME_SIZE, "ep%d", i);
+
+ uep->ep.name = uep->ep_name;
+ uep->ep.ops = &usbhsg_ep_ops;
+ INIT_LIST_HEAD(&uep->ep.ep_list);
+ spin_lock_init(&uep->lock);
+
+ /* init DCP */
+ if (usbhsg_is_dcp(uep)) {
+ gpriv->gadget.ep0 = &uep->ep;
+ usb_ep_set_maxpacket_limit(&uep->ep, 64);
+ uep->ep.caps.type_control = true;
+ } else {
+ /* init normal pipe */
+ if (pipe_configs[i].type == USB_ENDPOINT_XFER_ISOC)
+ uep->ep.caps.type_iso = true;
+ if (pipe_configs[i].type == USB_ENDPOINT_XFER_BULK)
+ uep->ep.caps.type_bulk = true;
+ if (pipe_configs[i].type == USB_ENDPOINT_XFER_INT)
+ uep->ep.caps.type_int = true;
+ usb_ep_set_maxpacket_limit(&uep->ep,
+ pipe_configs[i].bufsize);
+ list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list);
+ }
+ uep->ep.caps.dir_in = true;
+ uep->ep.caps.dir_out = true;
+ }
+
+ ret = usb_add_gadget_udc(dev, &gpriv->gadget);
+ if (ret)
+ goto err_add_udc;
+
+
+ dev_info(dev, "gadget probed\n");
+
+ return 0;
+
+err_add_udc:
+ kfree(gpriv->uep);
+
+usbhs_mod_gadget_probe_err_gpriv:
+ kfree(gpriv);
+
+ return ret;
+}
+
+void usbhs_mod_gadget_remove(struct usbhs_priv *priv)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+
+ usb_del_gadget_udc(&gpriv->gadget);
+
+ kfree(gpriv->uep);
+ kfree(gpriv);
+}
+
+struct usb_gadget *usbhsg_get_gadget(struct usbhs_priv *priv)
+{
+ struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv);
+ return &gpriv->gadget;
+}
diff --git a/drivers/usb/gadget/rcar/pipe.c b/drivers/usb/gadget/rcar/pipe.c
new file mode 100644
index 00000000000..a2b24f38144
--- /dev/null
+++ b/drivers/usb/gadget/rcar/pipe.c
@@ -0,0 +1,849 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#include <linux/delay.h>
+#include "common.h"
+#include "pipe.h"
+
+/*
+ * macros
+ */
+#define usbhsp_addr_offset(p) ((usbhs_pipe_number(p) - 1) * 2)
+
+#define usbhsp_flags_set(p, f) ((p)->flags |= USBHS_PIPE_FLAGS_##f)
+#define usbhsp_flags_clr(p, f) ((p)->flags &= ~USBHS_PIPE_FLAGS_##f)
+#define usbhsp_flags_has(p, f) ((p)->flags & USBHS_PIPE_FLAGS_##f)
+#define usbhsp_flags_init(p) do {(p)->flags = 0; } while (0)
+
+/*
+ * for debug
+ */
+static char *usbhsp_pipe_name[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "DCP",
+ [USB_ENDPOINT_XFER_BULK] = "BULK",
+ [USB_ENDPOINT_XFER_INT] = "INT",
+ [USB_ENDPOINT_XFER_ISOC] = "ISO",
+};
+
+char *usbhs_pipe_name(struct usbhs_pipe *pipe)
+{
+ return usbhsp_pipe_name[usbhs_pipe_type(pipe)];
+}
+
+static struct renesas_usbhs_driver_pipe_config
+*usbhsp_get_pipe_config(struct usbhs_priv *priv, int pipe_num)
+{
+ struct renesas_usbhs_driver_pipe_config *pipe_configs =
+ usbhs_get_dparam(priv, pipe_configs);
+
+ return &pipe_configs[pipe_num];
+}
+
+/*
+ * DCPCTR/PIPEnCTR functions
+ */
+static void usbhsp_pipectrl_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ int offset = usbhsp_addr_offset(pipe);
+
+ if (usbhs_pipe_is_dcp(pipe))
+ usbhs_bset(priv, DCPCTR, mask, val);
+ else
+ usbhs_bset(priv, PIPEnCTR + offset, mask, val);
+}
+
+static u16 usbhsp_pipectrl_get(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ int offset = usbhsp_addr_offset(pipe);
+
+ if (usbhs_pipe_is_dcp(pipe))
+ return usbhs_read(priv, DCPCTR);
+ else
+ return usbhs_read(priv, PIPEnCTR + offset);
+}
+
+/*
+ * DCP/PIPE functions
+ */
+static void __usbhsp_pipe_xxx_set(struct usbhs_pipe *pipe,
+ u16 dcp_reg, u16 pipe_reg,
+ u16 mask, u16 val)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ if (usbhs_pipe_is_dcp(pipe))
+ usbhs_bset(priv, dcp_reg, mask, val);
+ else
+ usbhs_bset(priv, pipe_reg, mask, val);
+}
+
+static u16 __usbhsp_pipe_xxx_get(struct usbhs_pipe *pipe,
+ u16 dcp_reg, u16 pipe_reg)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ if (usbhs_pipe_is_dcp(pipe))
+ return usbhs_read(priv, dcp_reg);
+ else
+ return usbhs_read(priv, pipe_reg);
+}
+
+/*
+ * DCPCFG/PIPECFG functions
+ */
+static void usbhsp_pipe_cfg_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ __usbhsp_pipe_xxx_set(pipe, DCPCFG, PIPECFG, mask, val);
+}
+
+static u16 usbhsp_pipe_cfg_get(struct usbhs_pipe *pipe)
+{
+ return __usbhsp_pipe_xxx_get(pipe, DCPCFG, PIPECFG);
+}
+
+/*
+ * PIPEnTRN/PIPEnTRE functions
+ */
+static void usbhsp_pipe_trn_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int num = usbhs_pipe_number(pipe);
+ u16 reg;
+
+ /*
+ * It is impossible to calculate address,
+ * since PIPEnTRN addresses were mapped randomly.
+ */
+#define CASE_PIPExTRN(a) \
+ case 0x ## a: \
+ reg = PIPE ## a ## TRN; \
+ break;
+
+ switch (num) {
+ CASE_PIPExTRN(1);
+ CASE_PIPExTRN(2);
+ CASE_PIPExTRN(3);
+ CASE_PIPExTRN(4);
+ CASE_PIPExTRN(5);
+ CASE_PIPExTRN(B);
+ CASE_PIPExTRN(C);
+ CASE_PIPExTRN(D);
+ CASE_PIPExTRN(E);
+ CASE_PIPExTRN(F);
+ CASE_PIPExTRN(9);
+ CASE_PIPExTRN(A);
+ default:
+ dev_err(dev, "unknown pipe (%d)\n", num);
+ return;
+ }
+ __usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val);
+}
+
+static void usbhsp_pipe_tre_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int num = usbhs_pipe_number(pipe);
+ u16 reg;
+
+ /*
+ * It is impossible to calculate address,
+ * since PIPEnTRE addresses were mapped randomly.
+ */
+#define CASE_PIPExTRE(a) \
+ case 0x ## a: \
+ reg = PIPE ## a ## TRE; \
+ break;
+
+ switch (num) {
+ CASE_PIPExTRE(1);
+ CASE_PIPExTRE(2);
+ CASE_PIPExTRE(3);
+ CASE_PIPExTRE(4);
+ CASE_PIPExTRE(5);
+ CASE_PIPExTRE(B);
+ CASE_PIPExTRE(C);
+ CASE_PIPExTRE(D);
+ CASE_PIPExTRE(E);
+ CASE_PIPExTRE(F);
+ CASE_PIPExTRE(9);
+ CASE_PIPExTRE(A);
+ default:
+ dev_err(dev, "unknown pipe (%d)\n", num);
+ return;
+ }
+
+ __usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val);
+}
+
+/*
+ * PIPEBUF
+ */
+static void usbhsp_pipe_buf_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ if (usbhs_pipe_is_dcp(pipe))
+ return;
+
+ __usbhsp_pipe_xxx_set(pipe, 0, PIPEBUF, mask, val);
+}
+
+/*
+ * DCPMAXP/PIPEMAXP
+ */
+static void usbhsp_pipe_maxp_set(struct usbhs_pipe *pipe, u16 mask, u16 val)
+{
+ __usbhsp_pipe_xxx_set(pipe, DCPMAXP, PIPEMAXP, mask, val);
+}
+
+/*
+ * pipe control functions
+ */
+static void usbhsp_pipe_select(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ /*
+ * On pipe, this is necessary before
+ * accesses to below registers.
+ *
+ * PIPESEL : usbhsp_pipe_select
+ * PIPECFG : usbhsp_pipe_cfg_xxx
+ * PIPEBUF : usbhsp_pipe_buf_xxx
+ * PIPEMAXP : usbhsp_pipe_maxp_xxx
+ * PIPEPERI
+ */
+
+ /*
+ * if pipe is dcp, no pipe is selected.
+ * it is no problem, because dcp have its register
+ */
+ usbhs_write(priv, PIPESEL, 0xF & usbhs_pipe_number(pipe));
+}
+
+static int usbhsp_pipe_barrier(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ int timeout = 1024;
+ u16 mask = usbhs_mod_is_host(priv) ? (CSSTS | PID_MASK) : PID_MASK;
+
+ /*
+ * make sure....
+ *
+ * Modify these bits when CSSTS = 0, PID = NAK, and no pipe number is
+ * specified by the CURPIPE bits.
+ * When changing the setting of this bit after changing
+ * the PID bits for the selected pipe from BUF to NAK,
+ * check that CSSTS = 0 and PBUSY = 0.
+ */
+
+ /*
+ * CURPIPE bit = 0
+ *
+ * see also
+ * "Operation"
+ * - "Pipe Control"
+ * - "Pipe Control Registers Switching Procedure"
+ */
+ usbhs_write(priv, CFIFOSEL, 0);
+ usbhs_pipe_disable(pipe);
+
+ do {
+ if (!(usbhsp_pipectrl_get(pipe) & mask))
+ return 0;
+
+ udelay(10);
+
+ } while (timeout--);
+
+ return -EBUSY;
+}
+
+int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe)
+{
+ u16 val;
+
+ val = usbhsp_pipectrl_get(pipe);
+ if (val & BSTS)
+ return 0;
+
+ return -EBUSY;
+}
+
+bool usbhs_pipe_contains_transmittable_data(struct usbhs_pipe *pipe)
+{
+ u16 val;
+
+ /* Do not support for DCP pipe */
+ if (usbhs_pipe_is_dcp(pipe))
+ return false;
+
+ val = usbhsp_pipectrl_get(pipe);
+ if (val & INBUFM)
+ return true;
+
+ return false;
+}
+
+/*
+ * PID ctrl
+ */
+static void __usbhsp_pid_try_nak_if_stall(struct usbhs_pipe *pipe)
+{
+ u16 pid = usbhsp_pipectrl_get(pipe);
+
+ pid &= PID_MASK;
+
+ /*
+ * see
+ * "Pipe n Control Register" - "PID"
+ */
+ switch (pid) {
+ case PID_STALL11:
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10);
+ fallthrough;
+ case PID_STALL10:
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK);
+ }
+}
+
+void usbhs_pipe_disable(struct usbhs_pipe *pipe)
+{
+ int timeout = 1024;
+ u16 val;
+
+ /* see "Pipe n Control Register" - "PID" */
+ __usbhsp_pid_try_nak_if_stall(pipe);
+
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK);
+
+ do {
+ val = usbhsp_pipectrl_get(pipe);
+ val &= PBUSY;
+ if (!val)
+ break;
+
+ udelay(10);
+ } while (timeout--);
+}
+
+void usbhs_pipe_enable(struct usbhs_pipe *pipe)
+{
+ /* see "Pipe n Control Register" - "PID" */
+ __usbhsp_pid_try_nak_if_stall(pipe);
+
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_BUF);
+}
+
+void usbhs_pipe_stall(struct usbhs_pipe *pipe)
+{
+ u16 pid = usbhsp_pipectrl_get(pipe);
+
+ pid &= PID_MASK;
+
+ /*
+ * see
+ * "Pipe n Control Register" - "PID"
+ */
+ switch (pid) {
+ case PID_NAK:
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10);
+ break;
+ case PID_BUF:
+ usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL11);
+ break;
+ }
+}
+
+int usbhs_pipe_is_stall(struct usbhs_pipe *pipe)
+{
+ u16 pid = usbhsp_pipectrl_get(pipe) & PID_MASK;
+
+ return (int)(pid == PID_STALL10 || pid == PID_STALL11);
+}
+
+void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len)
+{
+ if (!usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK))
+ return;
+
+ /*
+ * clear and disable transfer counter for IN/OUT pipe
+ */
+ usbhsp_pipe_tre_set(pipe, TRCLR | TRENB, TRCLR);
+
+ /*
+ * Only IN direction bulk pipe can use transfer count.
+ * Without using this function,
+ * received data will break if it was large data size.
+ * see PIPEnTRN/PIPEnTRE for detail
+ */
+ if (usbhs_pipe_is_dir_in(pipe)) {
+ int maxp = usbhs_pipe_get_maxpacket(pipe);
+
+ usbhsp_pipe_trn_set(pipe, 0xffff, DIV_ROUND_UP(len, maxp));
+ usbhsp_pipe_tre_set(pipe, TRENB, TRENB); /* enable */
+ }
+}
+
+
+/*
+ * pipe setup
+ */
+static int usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, int is_host,
+ int dir_in, u16 *pipecfg)
+{
+ u16 type = 0;
+ u16 bfre = 0;
+ u16 dblb = 0;
+ u16 cntmd = 0;
+ u16 dir = 0;
+ u16 epnum = 0;
+ u16 shtnak = 0;
+ static const u16 type_array[] = {
+ [USB_ENDPOINT_XFER_BULK] = TYPE_BULK,
+ [USB_ENDPOINT_XFER_INT] = TYPE_INT,
+ [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO,
+ };
+
+ if (usbhs_pipe_is_dcp(pipe))
+ return -EINVAL;
+
+ /*
+ * PIPECFG
+ *
+ * see
+ * - "Register Descriptions" - "PIPECFG" register
+ * - "Features" - "Pipe configuration"
+ * - "Operation" - "Pipe Control"
+ */
+
+ /* TYPE */
+ type = type_array[usbhs_pipe_type(pipe)];
+
+ /* BFRE */
+ if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) ||
+ usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK))
+ bfre = 0; /* FIXME */
+
+ /* DBLB: see usbhs_pipe_config_update() */
+
+ /* CNTMD */
+ if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK))
+ cntmd = 0; /* FIXME */
+
+ /* DIR */
+ if (dir_in)
+ usbhsp_flags_set(pipe, IS_DIR_HOST);
+
+ if (!!is_host ^ !!dir_in)
+ dir |= DIR_OUT;
+
+ if (!dir)
+ usbhsp_flags_set(pipe, IS_DIR_IN);
+
+ /* SHTNAK */
+ if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) &&
+ !dir)
+ shtnak = SHTNAK;
+
+ /* EPNUM */
+ epnum = 0; /* see usbhs_pipe_config_update() */
+ *pipecfg = type |
+ bfre |
+ dblb |
+ cntmd |
+ dir |
+ shtnak |
+ epnum;
+ return 0;
+}
+
+static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ struct device *dev = usbhs_priv_to_dev(priv);
+ int pipe_num = usbhs_pipe_number(pipe);
+ u16 buff_size;
+ u16 bufnmb;
+ u16 bufnmb_cnt;
+ struct renesas_usbhs_driver_pipe_config *pipe_config =
+ usbhsp_get_pipe_config(priv, pipe_num);
+
+ /*
+ * PIPEBUF
+ *
+ * see
+ * - "Register Descriptions" - "PIPEBUF" register
+ * - "Features" - "Pipe configuration"
+ * - "Operation" - "FIFO Buffer Memory"
+ * - "Operation" - "Pipe Control"
+ */
+ buff_size = pipe_config->bufsize;
+ bufnmb = pipe_config->bufnum;
+
+ /* change buff_size to register value */
+ bufnmb_cnt = (buff_size / 64) - 1;
+
+ dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n",
+ pipe_num, buff_size, bufnmb);
+
+ return (0x1f & bufnmb_cnt) << 10 |
+ (0xff & bufnmb) << 0;
+}
+
+void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel,
+ u16 epnum, u16 maxp)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+ int pipe_num = usbhs_pipe_number(pipe);
+ struct renesas_usbhs_driver_pipe_config *pipe_config =
+ usbhsp_get_pipe_config(priv, pipe_num);
+ u16 dblb = pipe_config->double_buf ? DBLB : 0;
+
+ if (devsel > 0xA) {
+ struct device *dev = usbhs_priv_to_dev(priv);
+
+ dev_err(dev, "devsel error %d\n", devsel);
+
+ devsel = 0;
+ }
+
+ usbhsp_pipe_barrier(pipe);
+
+ pipe->maxp = maxp;
+
+ usbhsp_pipe_select(pipe);
+ usbhsp_pipe_maxp_set(pipe, 0xFFFF,
+ (devsel << 12) |
+ maxp);
+
+ if (!usbhs_pipe_is_dcp(pipe))
+ usbhsp_pipe_cfg_set(pipe, 0x000F | DBLB, epnum | dblb);
+}
+
+/*
+ * pipe control
+ */
+int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe)
+{
+ /*
+ * see
+ * usbhs_pipe_config_update()
+ * usbhs_dcp_malloc()
+ */
+ return pipe->maxp;
+}
+
+int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe)
+{
+ return usbhsp_flags_has(pipe, IS_DIR_IN);
+}
+
+int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe)
+{
+ return usbhsp_flags_has(pipe, IS_DIR_HOST);
+}
+
+int usbhs_pipe_is_running(struct usbhs_pipe *pipe)
+{
+ return usbhsp_flags_has(pipe, IS_RUNNING);
+}
+
+void usbhs_pipe_running(struct usbhs_pipe *pipe, int running)
+{
+ if (running)
+ usbhsp_flags_set(pipe, IS_RUNNING);
+ else
+ usbhsp_flags_clr(pipe, IS_RUNNING);
+}
+
+void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int sequence)
+{
+ u16 mask = (SQCLR | SQSET);
+ u16 val;
+
+ /*
+ * sequence
+ * 0 : data0
+ * 1 : data1
+ * -1 : no change
+ */
+ switch (sequence) {
+ case 0:
+ val = SQCLR;
+ break;
+ case 1:
+ val = SQSET;
+ break;
+ default:
+ return;
+ }
+
+ usbhsp_pipectrl_set(pipe, mask, val);
+}
+
+static int usbhs_pipe_get_data_sequence(struct usbhs_pipe *pipe)
+{
+ return !!(usbhsp_pipectrl_get(pipe) & SQMON);
+}
+
+void usbhs_pipe_clear(struct usbhs_pipe *pipe)
+{
+ if (usbhs_pipe_is_dcp(pipe)) {
+ usbhs_fifo_clear_dcp(pipe);
+ } else {
+ usbhsp_pipectrl_set(pipe, ACLRM, ACLRM);
+ usbhsp_pipectrl_set(pipe, ACLRM, 0);
+ }
+}
+
+/* Should call usbhsp_pipe_select() before */
+void usbhs_pipe_clear_without_sequence(struct usbhs_pipe *pipe,
+ int needs_bfre, int bfre_enable)
+{
+ int sequence;
+
+ usbhsp_pipe_select(pipe);
+ sequence = usbhs_pipe_get_data_sequence(pipe);
+ if (needs_bfre)
+ usbhsp_pipe_cfg_set(pipe, BFRE, bfre_enable ? BFRE : 0);
+ usbhs_pipe_clear(pipe);
+ usbhs_pipe_data_sequence(pipe, sequence);
+}
+
+void usbhs_pipe_config_change_bfre(struct usbhs_pipe *pipe, int enable)
+{
+ if (usbhs_pipe_is_dcp(pipe))
+ return;
+
+ usbhsp_pipe_select(pipe);
+ /* check if the driver needs to change the BFRE value */
+ if (!(enable ^ !!(usbhsp_pipe_cfg_get(pipe) & BFRE)))
+ return;
+
+ usbhs_pipe_clear_without_sequence(pipe, 1, enable);
+}
+
+static struct usbhs_pipe *usbhsp_get_pipe(struct usbhs_priv *priv, u32 type)
+{
+ struct usbhs_pipe *pos, *pipe;
+ int i;
+
+ /*
+ * find target pipe
+ */
+ pipe = NULL;
+ usbhs_for_each_pipe_with_dcp(pos, priv, i) {
+ if (!usbhs_pipe_type_is(pos, type))
+ continue;
+ if (usbhsp_flags_has(pos, IS_USED))
+ continue;
+
+ pipe = pos;
+ break;
+ }
+
+ if (!pipe)
+ return NULL;
+
+ /*
+ * initialize pipe flags
+ */
+ usbhsp_flags_init(pipe);
+ usbhsp_flags_set(pipe, IS_USED);
+
+ return pipe;
+}
+
+static void usbhsp_put_pipe(struct usbhs_pipe *pipe)
+{
+ usbhsp_flags_init(pipe);
+}
+
+void usbhs_pipe_init(struct usbhs_priv *priv,
+ int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map))
+{
+ struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv);
+ struct usbhs_pipe *pipe;
+ int i;
+
+ usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
+ usbhsp_flags_init(pipe);
+ pipe->fifo = NULL;
+ pipe->mod_private = NULL;
+ INIT_LIST_HEAD(&pipe->list);
+
+ /* pipe force init */
+ usbhs_pipe_clear(pipe);
+ }
+
+ info->dma_map_ctrl = dma_map_ctrl;
+}
+
+struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv,
+ int endpoint_type,
+ int dir_in)
+{
+ struct device *dev = usbhs_priv_to_dev(priv);
+ struct usbhs_pipe *pipe;
+ int is_host = usbhs_mod_is_host(priv);
+ int ret;
+ u16 pipecfg, pipebuf;
+
+ pipe = usbhsp_get_pipe(priv, endpoint_type);
+ if (!pipe) {
+ dev_err(dev, "can't get pipe (%s)\n",
+ usbhsp_pipe_name[endpoint_type]);
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&pipe->list);
+
+ usbhs_pipe_disable(pipe);
+
+ /* make sure pipe is not busy */
+ ret = usbhsp_pipe_barrier(pipe);
+ if (ret < 0) {
+ dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe));
+ return NULL;
+ }
+
+ if (usbhsp_setup_pipecfg(pipe, is_host, dir_in, &pipecfg)) {
+ dev_err(dev, "can't setup pipe\n");
+ return NULL;
+ }
+
+ pipebuf = usbhsp_setup_pipebuff(pipe);
+
+ usbhsp_pipe_select(pipe);
+ usbhsp_pipe_cfg_set(pipe, 0xFFFF, pipecfg);
+ usbhsp_pipe_buf_set(pipe, 0xFFFF, pipebuf);
+ usbhs_pipe_clear(pipe);
+
+ usbhs_pipe_sequence_data0(pipe);
+
+ dev_dbg(dev, "enable pipe %d : %s (%s)\n",
+ usbhs_pipe_number(pipe),
+ usbhs_pipe_name(pipe),
+ usbhs_pipe_is_dir_in(pipe) ? "in" : "out");
+
+ /*
+ * epnum / maxp are still not set to this pipe.
+ * call usbhs_pipe_config_update() after this function !!
+ */
+
+ return pipe;
+}
+
+void usbhs_pipe_free(struct usbhs_pipe *pipe)
+{
+ usbhsp_pipe_select(pipe);
+ usbhsp_pipe_cfg_set(pipe, 0xFFFF, 0);
+ usbhsp_put_pipe(pipe);
+}
+
+void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo)
+{
+ if (pipe->fifo)
+ pipe->fifo->pipe = NULL;
+
+ pipe->fifo = fifo;
+
+ if (fifo)
+ fifo->pipe = pipe;
+}
+
+
+/*
+ * dcp control
+ */
+struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv)
+{
+ struct usbhs_pipe *pipe;
+
+ pipe = usbhsp_get_pipe(priv, USB_ENDPOINT_XFER_CONTROL);
+ if (!pipe)
+ return NULL;
+
+ INIT_LIST_HEAD(&pipe->list);
+
+ /*
+ * call usbhs_pipe_config_update() after this function !!
+ */
+
+ return pipe;
+}
+
+void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe)
+{
+ struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);
+
+ WARN_ON(!usbhs_pipe_is_dcp(pipe));
+
+ usbhs_pipe_enable(pipe);
+
+ if (!usbhs_mod_is_host(priv)) /* funconly */
+ usbhsp_pipectrl_set(pipe, CCPL, CCPL);
+}
+
+void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out)
+{
+ usbhsp_pipe_cfg_set(pipe, DIR_OUT,
+ dir_out ? DIR_OUT : 0);
+}
+
+/*
+ * pipe module function
+ */
+int usbhs_pipe_probe(struct usbhs_priv *priv)
+{
+ struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv);
+ struct usbhs_pipe *pipe;
+ struct device *dev = usbhs_priv_to_dev(priv);
+ struct renesas_usbhs_driver_pipe_config *pipe_configs =
+ usbhs_get_dparam(priv, pipe_configs);
+ int pipe_size = usbhs_get_dparam(priv, pipe_size);
+ int i;
+
+ /* This driver expects 1st pipe is DCP */
+ if (pipe_configs[0].type != USB_ENDPOINT_XFER_CONTROL) {
+ dev_err(dev, "1st PIPE is not DCP\n");
+ return -EINVAL;
+ }
+
+ info->pipe = kcalloc(pipe_size, sizeof(struct usbhs_pipe),
+ GFP_KERNEL);
+ if (!info->pipe)
+ return -ENOMEM;
+
+ info->size = pipe_size;
+
+ /*
+ * init pipe
+ */
+ usbhs_for_each_pipe_with_dcp(pipe, priv, i) {
+ pipe->priv = priv;
+
+ usbhs_pipe_type(pipe) =
+ pipe_configs[i].type & USB_ENDPOINT_XFERTYPE_MASK;
+
+ dev_dbg(dev, "pipe %x\t: %s\n",
+ i, usbhsp_pipe_name[pipe_configs[i].type]);
+ }
+
+ return 0;
+}
+
+void usbhs_pipe_remove(struct usbhs_priv *priv)
+{
+ struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv);
+
+ kfree(info->pipe);
+}
diff --git a/drivers/usb/gadget/rcar/pipe.h b/drivers/usb/gadget/rcar/pipe.h
new file mode 100644
index 00000000000..01c15178a28
--- /dev/null
+++ b/drivers/usb/gadget/rcar/pipe.h
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ */
+#ifndef RENESAS_USB_PIPE_H
+#define RENESAS_USB_PIPE_H
+
+#include "common.h"
+#include "fifo.h"
+
+/*
+ * struct
+ */
+struct usbhs_pipe {
+ u32 pipe_type; /* USB_ENDPOINT_XFER_xxx */
+
+ struct usbhs_priv *priv;
+ struct usbhs_fifo *fifo;
+ struct list_head list;
+
+ int maxp;
+
+ u32 flags;
+#define USBHS_PIPE_FLAGS_IS_USED (1 << 0)
+#define USBHS_PIPE_FLAGS_IS_DIR_IN (1 << 1)
+#define USBHS_PIPE_FLAGS_IS_DIR_HOST (1 << 2)
+#define USBHS_PIPE_FLAGS_IS_RUNNING (1 << 3)
+
+ const struct usbhs_pkt_handle *handler;
+
+ void *mod_private;
+};
+
+struct usbhs_pipe_info {
+ struct usbhs_pipe *pipe;
+ int size; /* array size of "pipe" */
+
+ int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map);
+};
+
+/*
+ * pipe list
+ */
+#define __usbhs_for_each_pipe(start, pos, info, i) \
+ for ((i) = start; \
+ ((i) < (info)->size) && ((pos) = (info)->pipe + (i)); \
+ (i)++)
+
+#define usbhs_for_each_pipe(pos, priv, i) \
+ __usbhs_for_each_pipe(1, pos, &((priv)->pipe_info), i)
+
+#define usbhs_for_each_pipe_with_dcp(pos, priv, i) \
+ __usbhs_for_each_pipe(0, pos, &((priv)->pipe_info), i)
+
+/*
+ * data
+ */
+#define usbhs_priv_to_pipeinfo(pr) (&(pr)->pipe_info)
+
+/*
+ * pipe control
+ */
+char *usbhs_pipe_name(struct usbhs_pipe *pipe);
+struct usbhs_pipe
+*usbhs_pipe_malloc(struct usbhs_priv *priv, int endpoint_type, int dir_in);
+void usbhs_pipe_free(struct usbhs_pipe *pipe);
+int usbhs_pipe_probe(struct usbhs_priv *priv);
+void usbhs_pipe_remove(struct usbhs_priv *priv);
+int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe);
+int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe);
+int usbhs_pipe_is_running(struct usbhs_pipe *pipe);
+void usbhs_pipe_running(struct usbhs_pipe *pipe, int running);
+
+void usbhs_pipe_init(struct usbhs_priv *priv,
+ int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map));
+int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe);
+void usbhs_pipe_clear(struct usbhs_pipe *pipe);
+void usbhs_pipe_clear_without_sequence(struct usbhs_pipe *pipe,
+ int needs_bfre, int bfre_enable);
+int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe);
+void usbhs_pipe_enable(struct usbhs_pipe *pipe);
+void usbhs_pipe_disable(struct usbhs_pipe *pipe);
+void usbhs_pipe_stall(struct usbhs_pipe *pipe);
+int usbhs_pipe_is_stall(struct usbhs_pipe *pipe);
+void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len);
+void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo);
+void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel,
+ u16 epnum, u16 maxp);
+void usbhs_pipe_config_change_bfre(struct usbhs_pipe *pipe, int enable);
+
+#define usbhs_pipe_sequence_data0(pipe) usbhs_pipe_data_sequence(pipe, 0)
+#define usbhs_pipe_sequence_data1(pipe) usbhs_pipe_data_sequence(pipe, 1)
+void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int data);
+
+#define usbhs_pipe_to_priv(p) ((p)->priv)
+#define usbhs_pipe_number(p) (int)((p) - (p)->priv->pipe_info.pipe)
+#define usbhs_pipe_is_dcp(p) ((p)->priv->pipe_info.pipe == (p))
+#define usbhs_pipe_to_fifo(p) ((p)->fifo)
+#define usbhs_pipe_is_busy(p) usbhs_pipe_to_fifo(p)
+
+#define usbhs_pipe_type(p) ((p)->pipe_type)
+#define usbhs_pipe_type_is(p, t) ((p)->pipe_type == t)
+
+/*
+ * dcp control
+ */
+struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv);
+void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe);
+void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out);
+
+#endif /* RENESAS_USB_PIPE_H */
diff --git a/drivers/usb/gadget/rcar/renesas_usb.h b/drivers/usb/gadget/rcar/renesas_usb.h
new file mode 100644
index 00000000000..8155e3dcaf6
--- /dev/null
+++ b/drivers/usb/gadget/rcar/renesas_usb.h
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * Renesas USB
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+ *
+ * Ported to u-boot
+ * Copyright (C) 2016 GlobalLogic
+ */
+#ifndef RENESAS_USB_H
+#define RENESAS_USB_H
+
+#include <linux/usb/ch9.h>
+#include <linux/compat.h>
+
+struct platform_device {
+ const char *name;
+ struct device dev;
+};
+
+/*
+ * module type
+ *
+ * it will be return value from get_id
+ */
+enum {
+ USBHS_HOST = 0,
+ USBHS_GADGET,
+ USBHS_MAX,
+};
+
+/*
+ * parameters for renesas usbhs
+ *
+ * some register needs USB chip specific parameters.
+ * This struct show it to driver
+ */
+
+struct renesas_usbhs_driver_pipe_config {
+ u8 type; /* USB_ENDPOINT_XFER_xxx */
+ u16 bufsize;
+ u8 bufnum;
+ bool double_buf;
+};
+#define RENESAS_USBHS_PIPE(_type, _size, _num, _double_buf) { \
+ .type = (_type), \
+ .bufsize = (_size), \
+ .bufnum = (_num), \
+ .double_buf = (_double_buf), \
+ }
+
+struct renesas_usbhs_driver_param {
+ /*
+ * pipe settings
+ */
+ struct renesas_usbhs_driver_pipe_config *pipe_configs;
+ int pipe_size; /* pipe_configs array size */
+
+ /*
+ * option:
+ *
+ * for BUSWAIT :: BWAIT
+ * see
+ * renesas_usbhs/common.c :: usbhsc_set_buswait()
+ * */
+ int buswait_bwait;
+
+ /*
+ * option:
+ *
+ * delay time from notify_hotplug callback
+ */
+ int detection_delay; /* msec */
+
+ /*
+ * option:
+ *
+ * dma id for dmaengine
+ * The data transfer direction on D0FIFO/D1FIFO should be
+ * fixed for keeping consistency.
+ * So, the platform id settings will be..
+ * .d0_tx_id = xx_TX,
+ * .d1_rx_id = xx_RX,
+ * or
+ * .d1_tx_id = xx_TX,
+ * .d0_rx_id = xx_RX,
+ */
+ int d0_tx_id;
+ int d0_rx_id;
+ int d1_tx_id;
+ int d1_rx_id;
+ int d2_tx_id;
+ int d2_rx_id;
+ int d3_tx_id;
+ int d3_rx_id;
+
+ /*
+ * option:
+ *
+ * pio <--> dma border.
+ */
+ int pio_dma_border; /* default is 64byte */
+
+ uintptr_t type;
+ u32 enable_gpio;
+
+ /*
+ * option:
+ */
+ u32 has_otg:1; /* for controlling PWEN/EXTLP */
+ u32 has_sudmac:1; /* for SUDMAC */
+ u32 has_usb_dmac:1; /* for USB-DMAC */
+ u32 cfifo_byte_addr:1; /* CFIFO is byte addressable */
+#define USBHS_USB_DMAC_XFER_SIZE 32 /* hardcode the xfer size */
+ u32 multi_clks:1;
+ u32 has_new_pipe_configs:1;
+};
+
+#define USBHS_TYPE_RCAR_GEN3 2
+
+struct usbhs_priv;
+struct usb_gadget *usbhsg_get_gadget(struct usbhs_priv *priv);
+
+#endif /* RENESAS_USB_H */
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index 467c566f6d3..4b6a8fdfeee 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -2,9 +2,9 @@
#
# USB peripheral controller drivers
-ifndef CONFIG_$(SPL_)DM_USB_GADGET
+ifndef CONFIG_$(XPL_)DM_USB_GADGET
obj-$(CONFIG_USB_DWC3_GADGET) += udc-core.o
endif
-obj-$(CONFIG_$(SPL_)DM_USB_GADGET) += udc-core.o
+obj-$(CONFIG_$(XPL_)DM_USB_GADGET) += udc-core.o
obj-y += udc-uclass.o
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 6e10b629a3c..bb5893d56db 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -68,6 +68,14 @@ config USB_XHCI_MVEBU
SoCs, which includes Armada8K, Armada3700 and other Armada
family SoCs.
+config USB_XHCI_GENERIC
+ bool "Generic SoC USB 3.0 support"
+ depends on OF_CONTROL
+ default n
+ help
+ Choose this option to add support for USB 3.0 driver for SoCs
+ that do not need platform specific code, like on emulated targets.
+
config USB_XHCI_OCTEON
bool "Support for Marvell Octeon family on-chip xHCI USB controller"
depends on ARCH_OCTEON
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 8dad36f9369..301bb9fdee1 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -3,13 +3,13 @@
# (C) Copyright 2000-2007
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
-ifdef CONFIG_$(SPL_)DM_USB
+ifdef CONFIG_$(XPL_)DM_USB
obj-y += usb-uclass.o
obj-$(CONFIG_SANDBOX) += usb-sandbox.o
endif
-ifdef CONFIG_$(SPL_TPL_)USB_STORAGE
-obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += usb_bootdev.o
+ifdef CONFIG_$(PHASE_)USB_STORAGE
+obj-$(CONFIG_$(PHASE_)BOOTSTD) += usb_bootdev.o
endif
# ohci
@@ -50,6 +50,7 @@ obj-$(CONFIG_USB_XHCI_EXYNOS) += xhci-exynos5.o
obj-$(CONFIG_USB_XHCI_FSL) += xhci-fsl.o
obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o
obj-$(CONFIG_USB_XHCI_MVEBU) += xhci-mvebu.o
+obj-$(CONFIG_USB_XHCI_GENERIC) += xhci-generic.o
obj-$(CONFIG_USB_XHCI_OMAP) += xhci-omap.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar.o
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 343893b9f19..7c73eb66b60 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -66,9 +66,24 @@ enum usb_ctlr_type {
USB_CTRL_COUNT,
};
+struct tegra_utmip_config {
+ u32 hssync_start_delay;
+ u32 elastic_limit;
+ u32 idle_wait_delay;
+ u32 term_range_adj;
+ bool xcvr_setup_use_fuses;
+ u32 xcvr_setup;
+ u32 xcvr_lsfslew;
+ u32 xcvr_lsrslew;
+ u32 xcvr_hsslew;
+ u32 hssquelch_level;
+ u32 hsdiscon_level;
+};
+
/* Information about a USB port */
struct fdt_usb {
struct ehci_ctrl ehci;
+ struct tegra_utmip_config utmip_config;
struct usb_ctlr *reg; /* address of registers in physical memory */
unsigned utmi:1; /* 1 if port has external tranceiver, else 0 */
unsigned ulpi:1; /* 1 if port has external ULPI transceiver */
@@ -192,15 +207,6 @@ static const unsigned T210_usb_pll[CLOCK_OSC_FREQ_COUNT][PARAM_COUNT] = {
{ 0x028, 0x01, 0x01, 0x0, 0, 0x02, 0x2F, 0x08, 0x76, 65000, 5 }
};
-/* UTMIP Idle Wait Delay */
-static const u8 utmip_idle_wait_delay = 17;
-
-/* UTMIP Elastic limit */
-static const u8 utmip_elastic_limit = 16;
-
-/* UTMIP High Speed Sync Start Delay */
-static const u8 utmip_hs_sync_start_delay = 9;
-
struct fdt_usb_controller {
/* flag to determine whether controller supports hostpc register */
u32 has_hostpc:1;
@@ -377,6 +383,7 @@ static int init_utmi_usb_controller(struct fdt_usb *config,
u32 b_sess_valid_mask, val;
int loop_count;
const unsigned *timing;
+ struct tegra_utmip_config *utmip_config = &config->utmip_config;
struct usb_ctlr *usbctlr = config->reg;
struct clk_rst_ctlr *clkrst;
struct usb_ctlr *usb1ctlr;
@@ -463,16 +470,29 @@ static int init_utmi_usb_controller(struct fdt_usb *config,
/* Recommended PHY settings for EYE diagram */
val = readl(&usbctlr->utmip_xcvr_cfg0);
- clrsetbits_le32(&val, UTMIP_XCVR_SETUP_MASK,
- 0x4 << UTMIP_XCVR_SETUP_SHIFT);
- clrsetbits_le32(&val, UTMIP_XCVR_SETUP_MSB_MASK,
- 0x3 << UTMIP_XCVR_SETUP_MSB_SHIFT);
- clrsetbits_le32(&val, UTMIP_XCVR_HSSLEW_MSB_MASK,
- 0x8 << UTMIP_XCVR_HSSLEW_MSB_SHIFT);
+
+ if (!utmip_config->xcvr_setup_use_fuses) {
+ clrsetbits_le32(&val, UTMIP_XCVR_SETUP(~0),
+ UTMIP_XCVR_SETUP(utmip_config->xcvr_setup));
+ clrsetbits_le32(&val, UTMIP_XCVR_SETUP_MSB(~0),
+ UTMIP_XCVR_SETUP_MSB(utmip_config->xcvr_setup));
+ }
+
+ clrsetbits_le32(&val, UTMIP_XCVR_LSFSLEW(~0),
+ UTMIP_XCVR_LSFSLEW(utmip_config->xcvr_lsfslew));
+ clrsetbits_le32(&val, UTMIP_XCVR_LSRSLEW(~0),
+ UTMIP_XCVR_LSRSLEW(utmip_config->xcvr_lsrslew));
+
+ clrsetbits_le32(&val, UTMIP_XCVR_HSSLEW(~0),
+ UTMIP_XCVR_HSSLEW(utmip_config->xcvr_hsslew));
+ clrsetbits_le32(&val, UTMIP_XCVR_HSSLEW_MSB(~0),
+ UTMIP_XCVR_HSSLEW_MSB(utmip_config->xcvr_hsslew));
writel(val, &usbctlr->utmip_xcvr_cfg0);
+
clrsetbits_le32(&usbctlr->utmip_xcvr_cfg1,
UTMIP_XCVR_TERM_RANGE_ADJ_MASK,
- 0x7 << UTMIP_XCVR_TERM_RANGE_ADJ_SHIFT);
+ utmip_config->term_range_adj <<
+ UTMIP_XCVR_TERM_RANGE_ADJ_SHIFT);
/* Some registers can be controlled from USB1 only. */
if (config->periph_id != PERIPH_ID_USBD) {
@@ -485,9 +505,11 @@ static int init_utmi_usb_controller(struct fdt_usb *config,
val = readl(&usb1ctlr->utmip_bias_cfg0);
setbits_le32(&val, UTMIP_HSDISCON_LEVEL_MSB);
clrsetbits_le32(&val, UTMIP_HSDISCON_LEVEL_MASK,
- 0x1 << UTMIP_HSDISCON_LEVEL_SHIFT);
+ utmip_config->hsdiscon_level <<
+ UTMIP_HSDISCON_LEVEL_SHIFT);
clrsetbits_le32(&val, UTMIP_HSSQUELCH_LEVEL_MASK,
- 0x2 << UTMIP_HSSQUELCH_LEVEL_SHIFT);
+ utmip_config->hssquelch_level <<
+ UTMIP_HSSQUELCH_LEVEL_SHIFT);
writel(val, &usb1ctlr->utmip_bias_cfg0);
/* Miscellaneous setting mentioned in Programming Guide */
@@ -521,7 +543,11 @@ static int init_utmi_usb_controller(struct fdt_usb *config,
setbits_le32(&usbctlr->utmip_bat_chrg_cfg0, UTMIP_PD_CHRG);
clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_XCVR_LSBIAS_SE);
- setbits_le32(&usbctlr->utmip_spare_cfg0, FUSE_SETUP_SEL);
+
+ if (utmip_config->xcvr_setup_use_fuses)
+ setbits_le32(&usbctlr->utmip_spare_cfg0, FUSE_SETUP_SEL);
+ else
+ clrbits_le32(&usbctlr->utmip_spare_cfg0, FUSE_SETUP_SEL);
/*
* Configure the UTMIP_IDLE_WAIT and UTMIP_ELASTIC_LIMIT
@@ -535,15 +561,16 @@ static int init_utmi_usb_controller(struct fdt_usb *config,
/* Set PLL enable delay count and Crystal frequency count */
val = readl(&usbctlr->utmip_hsrx_cfg0);
clrsetbits_le32(&val, UTMIP_IDLE_WAIT_MASK,
- utmip_idle_wait_delay << UTMIP_IDLE_WAIT_SHIFT);
+ utmip_config->idle_wait_delay << UTMIP_IDLE_WAIT_SHIFT);
clrsetbits_le32(&val, UTMIP_ELASTIC_LIMIT_MASK,
- utmip_elastic_limit << UTMIP_ELASTIC_LIMIT_SHIFT);
+ utmip_config->elastic_limit << UTMIP_ELASTIC_LIMIT_SHIFT);
writel(val, &usbctlr->utmip_hsrx_cfg0);
/* Configure the UTMIP_HS_SYNC_START_DLY */
clrsetbits_le32(&usbctlr->utmip_hsrx_cfg1,
UTMIP_HS_SYNC_START_DLY_MASK,
- utmip_hs_sync_start_delay << UTMIP_HS_SYNC_START_DLY_SHIFT);
+ utmip_config->hssync_start_delay <<
+ UTMIP_HS_SYNC_START_DLY_SHIFT);
/* Preceed the crystal clock disable by >100ns delay. */
udelay(1);
@@ -763,6 +790,69 @@ static int fdt_decode_usb(struct udevice *dev, struct fdt_usb *config)
return 0;
}
+static void fdt_decode_usb_phy(struct udevice *dev)
+{
+ struct fdt_usb *priv = dev_get_priv(dev);
+ struct tegra_utmip_config *utmip_config = &priv->utmip_config;
+ u32 usb_phy_phandle;
+ ofnode usb_phy_node;
+ int ret;
+
+ ret = ofnode_read_u32(dev_ofnode(dev), "nvidia,phy", &usb_phy_phandle);
+ if (ret)
+ log_debug("%s: required usb phy node isn't provided\n", __func__);
+
+ usb_phy_node = ofnode_get_by_phandle(usb_phy_phandle);
+ if (!ofnode_valid(usb_phy_node) || !ofnode_is_enabled(usb_phy_node)) {
+ log_debug("%s: failed to find usb phy node or it is disabled\n", __func__);
+ utmip_config->xcvr_setup_use_fuses = true;
+ } else {
+ utmip_config->xcvr_setup_use_fuses =
+ ofnode_read_bool(usb_phy_node, "nvidia,xcvr-setup-use-fuses");
+ }
+
+ utmip_config->hssync_start_delay =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,hssync-start-delay", 0x9);
+
+ utmip_config->elastic_limit =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,elastic-limit", 0x10);
+
+ utmip_config->idle_wait_delay =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,idle-wait-delay", 0x11);
+
+ utmip_config->term_range_adj =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,term-range-adj", 0x7);
+
+ utmip_config->xcvr_lsfslew =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,xcvr-lsfslew", 0x0);
+
+ utmip_config->xcvr_lsrslew =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,xcvr-lsrslew", 0x3);
+
+ utmip_config->xcvr_hsslew =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,xcvr-hsslew", 0x8);
+
+ utmip_config->hssquelch_level =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,hssquelch-level", 0x2);
+
+ utmip_config->hsdiscon_level =
+ ofnode_read_u32_default(usb_phy_node,
+ "nvidia,hsdiscon-level", 0x1);
+
+ if (!utmip_config->xcvr_setup_use_fuses) {
+ ofnode_read_u32(usb_phy_node, "nvidia,xcvr-setup",
+ &utmip_config->xcvr_setup);
+ }
+}
+
int usb_common_init(struct fdt_usb *config, enum usb_init_type init)
{
int ret = 0;
@@ -850,6 +940,8 @@ static int ehci_usb_of_to_plat(struct udevice *dev)
priv->type = dev_get_driver_data(dev);
+ fdt_decode_usb_phy(dev);
+
return 0;
}
diff --git a/drivers/usb/host/xhci-generic.c b/drivers/usb/host/xhci-generic.c
new file mode 100644
index 00000000000..355d4883176
--- /dev/null
+++ b/drivers/usb/host/xhci-generic.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2024 9elements GmbH
+ *
+ * GENERIC USB HOST xHCI Controller
+ */
+#include <dm.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <usb.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <usb/xhci.h>
+
+struct generic_xhci_plat {
+ fdt_addr_t hcd_base;
+};
+
+/**
+ * Contains pointers to register base addresses
+ * for the usb controller.
+ */
+struct generic_xhci {
+ struct xhci_ctrl ctrl; /* Needs to come first in this struct! */
+ struct usb_plat usb_plat;
+ struct xhci_hccr *hcd;
+};
+
+static int xhci_usb_probe(struct udevice *dev)
+{
+ struct generic_xhci_plat *plat = dev_get_plat(dev);
+ struct generic_xhci *ctx = dev_get_priv(dev);
+ struct xhci_hcor *hcor;
+ int len;
+
+ ctx->hcd = (struct xhci_hccr *)phys_to_virt(plat->hcd_base);
+ len = HC_LENGTH(xhci_readl(&ctx->hcd->cr_capbase));
+ hcor = (struct xhci_hcor *)((uintptr_t)ctx->hcd + len);
+
+ return xhci_register(dev, ctx->hcd, hcor);
+}
+
+static int xhci_usb_of_to_plat(struct udevice *dev)
+{
+ struct generic_xhci_plat *plat = dev_get_plat(dev);
+
+ /*
+ * Get the base address for XHCI controller from the device node
+ */
+ plat->hcd_base = dev_read_addr(dev);
+ if (plat->hcd_base == FDT_ADDR_T_NONE) {
+ dev_dbg(dev, "Can't get the XHCI register base address\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static const struct udevice_id xhci_usb_ids[] = {
+ { .compatible = "generic-xhci" },
+ { }
+};
+
+U_BOOT_DRIVER(usb_xhci) = {
+ .name = "xhci_generic",
+ .id = UCLASS_USB,
+ .of_match = xhci_usb_ids,
+ .of_to_plat = xhci_usb_of_to_plat,
+ .probe = xhci_usb_probe,
+ .remove = xhci_deregister,
+ .ops = &xhci_usb_ops,
+ .plat_auto = sizeof(struct generic_xhci_plat),
+ .priv_auto = sizeof(struct generic_xhci),
+ .flags = DM_FLAG_ALLOC_PRIV_DMA,
+};
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 68cf08e0b6b..34eb4536f0e 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -17,6 +17,7 @@
#include <log.h>
#include <asm/byteorder.h>
#include <usb.h>
+#include <watchdog.h>
#include <asm/unaligned.h>
#include <linux/bug.h>
#include <linux/errno.h>
@@ -796,6 +797,8 @@ int xhci_bulk_tx(struct usb_device *udev, unsigned long pipe,
/* Calculate length for next transfer */
addr += trb_buff_len;
trb_buff_len = min((length - running_total), TRB_MAX_BUFF_SIZE);
+
+ schedule();
} while (running_total < length);
giveback_first_trb(udev, ep_index, start_cycle, start_trb);
diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c
index f8e14eabfb2..26fee141f6e 100644
--- a/drivers/usb/mtu3/mtu3_plat.c
+++ b/drivers/usb/mtu3/mtu3_plat.c
@@ -266,7 +266,7 @@ U_BOOT_DRIVER(mtu3_peripheral) = {
#endif
#if defined(CONFIG_SPL_USB_HOST) || \
- (!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
+ (!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
static int mtu3_host_probe(struct udevice *dev)
{
struct ssusb_mtk *ssusb = dev_to_ssusb(dev->parent);
@@ -334,7 +334,7 @@ static int mtu3_glue_bind(struct udevice *parent)
#endif
#if defined(CONFIG_SPL_USB_HOST) || \
- (!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
+ (!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
case USB_DR_MODE_HOST:
dev_dbg(parent, "%s: dr_mode: host\n", __func__);
driver = "mtu3-host";
diff --git a/drivers/usb/tcpm/Kconfig b/drivers/usb/tcpm/Kconfig
new file mode 100644
index 00000000000..9be4b496e82
--- /dev/null
+++ b/drivers/usb/tcpm/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config TYPEC_TCPM
+ tristate "USB Type-C Port Controller Manager"
+ depends on DM
+ help
+ The Type-C Port Controller Manager provides a USB PD and USB Type-C
+ state machine for use with Type-C Port Controllers.
+
+config TYPEC_FUSB302
+ tristate "Fairchild FUSB302 Type-C chip driver"
+ depends on DM && DM_I2C && TYPEC_TCPM
+ help
+ The Fairchild FUSB302 Type-C chip driver that works with
+ Type-C Port Controller Manager to provide USB PD and USB
+ Type-C functionalities.
diff --git a/drivers/usb/tcpm/Makefile b/drivers/usb/tcpm/Makefile
new file mode 100644
index 00000000000..668d33155bf
--- /dev/null
+++ b/drivers/usb/tcpm/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_TYPEC_TCPM) += tcpm.o tcpm-uclass.o
+obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o
diff --git a/drivers/usb/tcpm/fusb302.c b/drivers/usb/tcpm/fusb302.c
new file mode 100644
index 00000000000..fe93ff3d339
--- /dev/null
+++ b/drivers/usb/tcpm/fusb302.c
@@ -0,0 +1,1323 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2016-2017 Google, Inc
+ *
+ * Fairchild FUSB302 Type-C Chip Driver
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <asm/gpio.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <dm/device_compat.h>
+#include <usb/tcpm.h>
+#include "fusb302_reg.h"
+
+#define FUSB302_MAX_MSG_LEN 0x1F
+
+enum toggling_mode {
+ TOGGLING_MODE_OFF,
+ TOGGLING_MODE_DRP,
+ TOGGLING_MODE_SNK,
+ TOGGLING_MODE_SRC,
+};
+
+enum src_current_status {
+ SRC_CURRENT_DEFAULT,
+ SRC_CURRENT_MEDIUM,
+ SRC_CURRENT_HIGH,
+};
+
+static const u8 ra_mda_value[] = {
+ [SRC_CURRENT_DEFAULT] = 4, /* 210mV */
+ [SRC_CURRENT_MEDIUM] = 9, /* 420mV */
+ [SRC_CURRENT_HIGH] = 18, /* 798mV */
+};
+
+static const u8 rd_mda_value[] = {
+ [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */
+ [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */
+ [SRC_CURRENT_HIGH] = 61, /* 2604mV */
+};
+
+struct fusb302_chip {
+ enum toggling_mode toggling_mode;
+ enum src_current_status src_current_status;
+ bool intr_togdone;
+ bool intr_bc_lvl;
+ bool intr_comp_chng;
+
+ /* port status */
+ bool vconn_on;
+ bool vbus_present;
+ enum typec_cc_polarity cc_polarity;
+ enum typec_cc_status cc1;
+ enum typec_cc_status cc2;
+};
+
+static int fusb302_i2c_write(struct udevice *dev, u8 address, u8 data)
+{
+ int ret;
+
+ ret = dm_i2c_write(dev, address, &data, 1);
+ if (ret)
+ dev_err(dev, "cannot write 0x%02x to 0x%02x, ret=%d\n",
+ data, address, ret);
+
+ return ret;
+}
+
+static int fusb302_i2c_block_write(struct udevice *dev, u8 address,
+ u8 length, const u8 *data)
+{
+ int ret;
+
+ if (!length)
+ return 0;
+
+ ret = dm_i2c_write(dev, address, data, length);
+ if (ret)
+ dev_err(dev, "cannot block write 0x%02x, len=%d, ret=%d\n",
+ address, length, ret);
+
+ return ret;
+}
+
+static int fusb302_i2c_read(struct udevice *dev, u8 address, u8 *data)
+{
+ int ret, retries;
+
+ for (retries = 0; retries < 3; retries++) {
+ ret = dm_i2c_read(dev, address, data, 1);
+ if (ret == 0)
+ return ret;
+ dev_err(dev, "cannot read %02x, ret=%d\n", address, ret);
+ }
+
+ return ret;
+}
+
+static int fusb302_i2c_block_read(struct udevice *dev, u8 address,
+ u8 length, u8 *data)
+{
+ int ret;
+
+ if (!length)
+ return 0;
+
+ ret = dm_i2c_read(dev, address, data, length);
+ if (ret)
+ dev_err(dev, "cannot block read 0x%02x, len=%d, ret=%d\n",
+ address, length, ret);
+ return ret;
+}
+
+static int fusb302_i2c_mask_write(struct udevice *dev, u8 address,
+ u8 mask, u8 value)
+{
+ int ret;
+ u8 data;
+
+ ret = fusb302_i2c_read(dev, address, &data);
+ if (ret)
+ return ret;
+ data &= ~mask;
+ data |= value;
+ ret = fusb302_i2c_write(dev, address, data);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int fusb302_i2c_set_bits(struct udevice *dev, u8 address, u8 set_bits)
+{
+ return fusb302_i2c_mask_write(dev, address, 0x00, set_bits);
+}
+
+static int fusb302_i2c_clear_bits(struct udevice *dev, u8 address, u8 clear_bits)
+{
+ return fusb302_i2c_mask_write(dev, address, clear_bits, 0x00);
+}
+
+static int fusb302_sw_reset(struct udevice *dev)
+{
+ int ret = fusb302_i2c_write(dev, FUSB_REG_RESET, FUSB_REG_RESET_SW_RESET);
+
+ if (ret)
+ dev_err(dev, "cannot sw reset the fusb302: %d\n", ret);
+
+ return ret;
+}
+
+static int fusb302_enable_tx_auto_retries(struct udevice *dev, u8 retry_count)
+{
+ int ret;
+
+ ret = fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL3, retry_count |
+ FUSB_REG_CONTROL3_AUTO_RETRY);
+
+ return ret;
+}
+
+/*
+ * mask all interrupt on the chip
+ */
+static int fusb302_mask_interrupt(struct udevice *dev)
+{
+ int ret;
+
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASK, 0xFF);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASKA, 0xFF);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASKB, 0xFF);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_INT_MASK);
+ return ret;
+}
+
+/*
+ * initialize interrupt on the chip
+ * - unmasked interrupt: VBUS_OK
+ */
+static int fusb302_init_interrupt(struct udevice *dev)
+{
+ int ret;
+
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASK,
+ 0xFF & ~FUSB_REG_MASK_VBUSOK);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASKA, 0xFF);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_write(dev, FUSB_REG_MASKB, 0xFF);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_clear_bits(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_INT_MASK);
+ return ret;
+}
+
+static int fusb302_set_power_mode(struct udevice *dev, u8 power_mode)
+{
+ int ret;
+
+ ret = fusb302_i2c_write(dev, FUSB_REG_POWER, power_mode);
+
+ return ret;
+}
+
+static int fusb302_init(struct udevice *dev)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+ u8 data;
+
+ ret = fusb302_sw_reset(dev);
+ if (ret)
+ return ret;
+ ret = fusb302_enable_tx_auto_retries(dev, FUSB_REG_CONTROL3_N_RETRIES_3);
+ if (ret)
+ return ret;
+ ret = fusb302_init_interrupt(dev);
+ if (ret)
+ return ret;
+ ret = fusb302_set_power_mode(dev, FUSB_REG_POWER_PWR_ALL);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &data);
+ if (ret)
+ return ret;
+ chip->vbus_present = !!(data & FUSB_REG_STATUS0_VBUSOK);
+ ret = fusb302_i2c_read(dev, FUSB_REG_DEVICE_ID, &data);
+ if (ret)
+ return ret;
+ dev_info(dev, "fusb302 device ID: 0x%02x\n", data);
+
+ return ret;
+}
+
+static int fusb302_get_vbus(struct udevice *dev)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+
+ return chip->vbus_present ? 1 : 0;
+}
+
+static int fusb302_set_src_current(struct udevice *dev,
+ enum src_current_status status)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+
+ chip->src_current_status = status;
+ switch (status) {
+ case SRC_CURRENT_DEFAULT:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_HOST_CUR_MASK,
+ FUSB_REG_CONTROL0_HOST_CUR_DEF);
+ break;
+ case SRC_CURRENT_MEDIUM:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_HOST_CUR_MASK,
+ FUSB_REG_CONTROL0_HOST_CUR_MED);
+ break;
+ case SRC_CURRENT_HIGH:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_HOST_CUR_MASK,
+ FUSB_REG_CONTROL0_HOST_CUR_HIGH);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int fusb302_set_toggling(struct udevice *dev,
+ enum toggling_mode mode)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+
+ /* first disable toggling */
+ ret = fusb302_i2c_clear_bits(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_TOGGLE);
+ if (ret)
+ return ret;
+ /* mask interrupts for SRC or SNK */
+ ret = fusb302_i2c_set_bits(dev, FUSB_REG_MASK,
+ FUSB_REG_MASK_BC_LVL |
+ FUSB_REG_MASK_COMP_CHNG);
+ if (ret)
+ return ret;
+ chip->intr_bc_lvl = false;
+ chip->intr_comp_chng = false;
+ /* configure toggling mode: none/snk/src/drp */
+ switch (mode) {
+ case TOGGLING_MODE_OFF:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_MODE_MASK,
+ FUSB_REG_CONTROL2_MODE_NONE);
+ break;
+ case TOGGLING_MODE_SNK:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_MODE_MASK,
+ FUSB_REG_CONTROL2_MODE_UFP);
+ break;
+ case TOGGLING_MODE_SRC:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_MODE_MASK,
+ FUSB_REG_CONTROL2_MODE_DFP);
+ break;
+ case TOGGLING_MODE_DRP:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_MODE_MASK,
+ FUSB_REG_CONTROL2_MODE_DRP);
+ break;
+ default:
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ if (mode == TOGGLING_MODE_OFF) {
+ /* mask TOGDONE interrupt */
+ ret = fusb302_i2c_set_bits(dev, FUSB_REG_MASKA,
+ FUSB_REG_MASKA_TOGDONE);
+ if (ret)
+ return ret;
+ chip->intr_togdone = false;
+ } else {
+ /* Datasheet says vconn MUST be off when toggling */
+ if (chip->vconn_on)
+ dev_warn(dev, "Vconn is on during toggle start\n");
+ /* unmask TOGDONE interrupt */
+ ret = fusb302_i2c_clear_bits(dev, FUSB_REG_MASKA,
+ FUSB_REG_MASKA_TOGDONE);
+ if (ret)
+ return ret;
+ chip->intr_togdone = true;
+ /* start toggling */
+ ret = fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL2,
+ FUSB_REG_CONTROL2_TOGGLE);
+ if (ret)
+ return ret;
+ /* during toggling, consider cc as Open */
+ chip->cc1 = TYPEC_CC_OPEN;
+ chip->cc2 = TYPEC_CC_OPEN;
+ }
+ chip->toggling_mode = mode;
+
+ return ret;
+}
+
+static const enum src_current_status cc_src_current[] = {
+ [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT,
+ [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT,
+ [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT,
+ [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT,
+ [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM,
+ [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH,
+};
+
+static int fusb302_set_cc(struct udevice *dev, enum typec_cc_status cc)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ const u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN |
+ FUSB_REG_SWITCHES0_CC2_PU_EN |
+ FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
+ u8 rd_mda, switches0_data = 0x00;
+ int ret;
+
+ switch (cc) {
+ case TYPEC_CC_OPEN:
+ break;
+ case TYPEC_CC_RD:
+ switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
+ break;
+ case TYPEC_CC_RP_DEF:
+ case TYPEC_CC_RP_1_5:
+ case TYPEC_CC_RP_3_0:
+ switches0_data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
+ FUSB_REG_SWITCHES0_CC1_PU_EN :
+ FUSB_REG_SWITCHES0_CC2_PU_EN;
+ break;
+ default:
+ dev_err(dev, "unsupported CC value: %s\n",
+ typec_cc_status_name[cc]);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = fusb302_set_toggling(dev, TOGGLING_MODE_OFF);
+ if (ret) {
+ dev_err(dev, "cannot set toggling mode: %d\n", ret);
+ goto done;
+ }
+
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_SWITCHES0,
+ switches0_mask, switches0_data);
+ if (ret) {
+ dev_err(dev, "cannot set pull-up/-down: %d\n", ret);
+ goto done;
+ }
+ /* reset the cc status */
+ chip->cc1 = TYPEC_CC_OPEN;
+ chip->cc2 = TYPEC_CC_OPEN;
+
+ /* adjust current for SRC */
+ ret = fusb302_set_src_current(dev, cc_src_current[cc]);
+ if (ret) {
+ dev_err(dev, "cannot set src current %s: %d\n",
+ typec_cc_status_name[cc], ret);
+ goto done;
+ }
+
+ /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */
+ switch (cc) {
+ case TYPEC_CC_RP_DEF:
+ case TYPEC_CC_RP_1_5:
+ case TYPEC_CC_RP_3_0:
+ rd_mda = rd_mda_value[cc_src_current[cc]];
+ ret = fusb302_i2c_write(dev, FUSB_REG_MEASURE, rd_mda);
+ if (ret) {
+ dev_err(dev, "cannot set SRC measure value: %d\n", ret);
+ goto done;
+ }
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_MASK,
+ FUSB_REG_MASK_BC_LVL |
+ FUSB_REG_MASK_COMP_CHNG,
+ FUSB_REG_MASK_BC_LVL);
+ if (ret) {
+ dev_err(dev, "cannot set SRC irq: %d\n", ret);
+ goto done;
+ }
+ chip->intr_comp_chng = true;
+ break;
+ case TYPEC_CC_RD:
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_MASK,
+ FUSB_REG_MASK_BC_LVL |
+ FUSB_REG_MASK_COMP_CHNG,
+ FUSB_REG_MASK_COMP_CHNG);
+ if (ret) {
+ dev_err(dev, "cannot set SRC irq: %d\n", ret);
+ goto done;
+ }
+ chip->intr_bc_lvl = true;
+ break;
+ default:
+ break;
+ }
+done:
+ return ret;
+}
+
+static int fusb302_get_cc(struct udevice *dev, enum typec_cc_status *cc1,
+ enum typec_cc_status *cc2)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+
+ *cc1 = chip->cc1;
+ *cc2 = chip->cc2;
+ dev_dbg(dev, "get cc1 = %s, cc2 = %s\n", typec_cc_status_name[*cc1],
+ typec_cc_status_name[*cc2]);
+
+ return 0;
+}
+
+static int fusb302_set_vconn(struct udevice *dev, bool on)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+ u8 switches0_data = 0x00;
+ u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 |
+ FUSB_REG_SWITCHES0_VCONN_CC2;
+
+ if (chip->vconn_on == on) {
+ ret = 0;
+ dev_dbg(dev, "vconn is already %s\n", on ? "on" : "off");
+ goto done;
+ }
+ if (on) {
+ switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ?
+ FUSB_REG_SWITCHES0_VCONN_CC2 :
+ FUSB_REG_SWITCHES0_VCONN_CC1;
+ }
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_SWITCHES0,
+ switches0_mask, switches0_data);
+ if (ret)
+ goto done;
+ dev_dbg(dev, "set vconn = %s\n", on ? "on" : "off");
+done:
+ return ret;
+}
+
+static int fusb302_set_vbus(struct udevice *dev, bool on, bool charge)
+{
+ return 0;
+}
+
+static int fusb302_pd_tx_flush(struct udevice *dev)
+{
+ return fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL0,
+ FUSB_REG_CONTROL0_TX_FLUSH);
+}
+
+static int fusb302_pd_rx_flush(struct udevice *dev)
+{
+ return fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL1,
+ FUSB_REG_CONTROL1_RX_FLUSH);
+}
+
+static int fusb302_pd_set_auto_goodcrc(struct udevice *dev, bool on)
+{
+ if (on)
+ return fusb302_i2c_set_bits(dev, FUSB_REG_SWITCHES1,
+ FUSB_REG_SWITCHES1_AUTO_GCRC);
+ return fusb302_i2c_clear_bits(dev, FUSB_REG_SWITCHES1,
+ FUSB_REG_SWITCHES1_AUTO_GCRC);
+}
+
+static int fusb302_pd_set_interrupts(struct udevice *dev, bool on)
+{
+ int ret;
+ u8 mask_interrupts = FUSB_REG_MASK_COLLISION;
+ u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL |
+ FUSB_REG_MASKA_HARDSENT |
+ FUSB_REG_MASKA_TX_SUCCESS |
+ FUSB_REG_MASKA_HARDRESET;
+ u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT;
+
+ ret = on ?
+ fusb302_i2c_clear_bits(dev, FUSB_REG_MASK, mask_interrupts) :
+ fusb302_i2c_set_bits(dev, FUSB_REG_MASK, mask_interrupts);
+ if (ret)
+ return ret;
+ ret = on ?
+ fusb302_i2c_clear_bits(dev, FUSB_REG_MASKA, maska_interrupts) :
+ fusb302_i2c_set_bits(dev, FUSB_REG_MASKA, maska_interrupts);
+ if (ret)
+ return ret;
+ ret = on ?
+ fusb302_i2c_clear_bits(dev, FUSB_REG_MASKB, maskb_interrupts) :
+ fusb302_i2c_set_bits(dev, FUSB_REG_MASKB, maskb_interrupts);
+ return ret;
+}
+
+static int fusb302_set_pd_rx(struct udevice *dev, bool on)
+{
+ int ret;
+
+ ret = fusb302_pd_rx_flush(dev);
+ if (ret) {
+ dev_err(dev, "cannot flush pd rx buffer: %d\n", ret);
+ goto done;
+ }
+ ret = fusb302_pd_tx_flush(dev);
+ if (ret) {
+ dev_err(dev, "cannot flush pd tx buffer: %d\n", ret);
+ goto done;
+ }
+ ret = fusb302_pd_set_auto_goodcrc(dev, on);
+ if (ret) {
+ dev_err(dev, "cannot turn %s auto GoodCRC: %d\n",
+ on ? "on" : "off", ret);
+ goto done;
+ }
+ ret = fusb302_pd_set_interrupts(dev, on);
+ if (ret) {
+ dev_err(dev, "cannot turn %s pd interrupts: %d\n",
+ on ? "on" : "off", ret);
+ goto done;
+ }
+ dev_dbg(dev, "set pd RX %s\n", on ? "on" : "off");
+done:
+ return ret;
+}
+
+static int fusb302_set_roles(struct udevice *dev, bool attached,
+ enum typec_role pwr, enum typec_data_role data)
+{
+ int ret;
+ u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE |
+ FUSB_REG_SWITCHES1_DATAROLE;
+ u8 switches1_data = 0x00;
+
+ if (pwr == TYPEC_SOURCE)
+ switches1_data |= FUSB_REG_SWITCHES1_POWERROLE;
+ if (data == TYPEC_HOST)
+ switches1_data |= FUSB_REG_SWITCHES1_DATAROLE;
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_SWITCHES1,
+ switches1_mask, switches1_data);
+ if (ret) {
+ dev_err(dev, "unable to set pd header %s, %s, ret=%d\n",
+ typec_role_name[pwr], typec_data_role_name[data], ret);
+ goto done;
+ }
+ dev_dbg(dev, "pd header : %s, %s\n", typec_role_name[pwr],
+ typec_data_role_name[data]);
+done:
+
+ return ret;
+}
+
+static int fusb302_start_toggling(struct udevice *dev,
+ enum typec_port_type port_type,
+ enum typec_cc_status cc)
+{
+ enum toggling_mode mode = TOGGLING_MODE_OFF;
+ int ret;
+
+ switch (port_type) {
+ case TYPEC_PORT_SRC:
+ mode = TOGGLING_MODE_SRC;
+ break;
+ case TYPEC_PORT_SNK:
+ mode = TOGGLING_MODE_SNK;
+ break;
+ case TYPEC_PORT_DRP:
+ mode = TOGGLING_MODE_DRP;
+ break;
+ }
+
+ ret = fusb302_set_src_current(dev, cc_src_current[cc]);
+ if (ret) {
+ dev_err(dev, "unable to set src current %s, ret=%d",
+ typec_cc_status_name[cc], ret);
+ goto done;
+ }
+ ret = fusb302_set_toggling(dev, mode);
+ if (ret) {
+ dev_err(dev, "unable to start drp toggling: %d\n", ret);
+ goto done;
+ }
+ dev_info(dev, "fusb302 start drp toggling\n");
+done:
+
+ return ret;
+}
+
+static int fusb302_pd_send_message(struct udevice *dev,
+ const struct pd_message *msg)
+{
+ int ret;
+ /* SOP tokens */
+ u8 buf[40] = {FUSB302_TKN_SYNC1, FUSB302_TKN_SYNC1, FUSB302_TKN_SYNC1,
+ FUSB302_TKN_SYNC2};
+ u8 pos = 4;
+ int len;
+
+ len = pd_header_cnt_le(msg->header) * 4;
+ /* plug 2 for header */
+ len += 2;
+ if (len > FUSB302_MAX_MSG_LEN) {
+ dev_err(dev, "PD message too long %d (incl. header)", len);
+ return -EINVAL;
+ }
+ /* packsym tells the FUSB302 chip that the next X bytes are payload */
+ buf[pos++] = FUSB302_TKN_PACKSYM | (len & FUSB302_MAX_MSG_LEN);
+ memcpy(&buf[pos], &msg->header, sizeof(msg->header));
+ pos += sizeof(msg->header);
+
+ len -= 2;
+ memcpy(&buf[pos], msg->payload, len);
+ pos += len;
+
+ /* CRC */
+ buf[pos++] = FUSB302_TKN_JAMCRC;
+ /* EOP */
+ buf[pos++] = FUSB302_TKN_EOP;
+ /* turn tx off after sending message */
+ buf[pos++] = FUSB302_TKN_TXOFF;
+ /* start transmission */
+ buf[pos++] = FUSB302_TKN_TXON;
+
+ ret = fusb302_i2c_block_write(dev, FUSB_REG_FIFOS, pos, buf);
+ if (ret)
+ return ret;
+ dev_dbg(dev, "Send PD message (header=0x%x len=%d)\n", msg->header, len);
+
+ return ret;
+}
+
+static int fusb302_pd_send_hardreset(struct udevice *dev)
+{
+ return fusb302_i2c_set_bits(dev, FUSB_REG_CONTROL3,
+ FUSB_REG_CONTROL3_SEND_HARDRESET);
+}
+
+static const char * const transmit_type_name[] = {
+ [TCPC_TX_SOP] = "SOP",
+ [TCPC_TX_SOP_PRIME] = "SOP'",
+ [TCPC_TX_SOP_PRIME_PRIME] = "SOP''",
+ [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'",
+ [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''",
+ [TCPC_TX_HARD_RESET] = "HARD_RESET",
+ [TCPC_TX_CABLE_RESET] = "CABLE_RESET",
+ [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2",
+};
+
+static int fusb302_pd_transmit(struct udevice *dev, enum tcpm_transmit_type type,
+ const struct pd_message *msg, unsigned int negotiated_rev)
+{
+ int ret;
+
+ switch (type) {
+ case TCPC_TX_SOP:
+ /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */
+ ret = fusb302_enable_tx_auto_retries(dev, negotiated_rev > PD_REV20 ?
+ FUSB_REG_CONTROL3_N_RETRIES_2 :
+ FUSB_REG_CONTROL3_N_RETRIES_3);
+ if (ret)
+ dev_err(dev, "cannot update retry count: %d\n", ret);
+
+ ret = fusb302_pd_send_message(dev, msg);
+ if (ret)
+ dev_err(dev, "cannot send PD message: %d\n", ret);
+ break;
+ case TCPC_TX_HARD_RESET:
+ ret = fusb302_pd_send_hardreset(dev);
+ if (ret)
+ dev_err(dev, "cannot send hardreset: %d\n", ret);
+ break;
+ default:
+ dev_err(dev, "type %s not supported", transmit_type_name[type]);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl)
+{
+ if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX)
+ return TYPEC_CC_RP_3_0;
+ if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230)
+ return TYPEC_CC_RP_1_5;
+ if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600)
+ return TYPEC_CC_RP_DEF;
+ return TYPEC_CC_OPEN;
+}
+
+static void fusb302_bc_lvl_handler(struct udevice *dev)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ enum typec_cc_status cc_status;
+ u8 status0, bc_lvl;
+ int ret;
+
+ if (!chip->intr_bc_lvl) {
+ dev_err(dev, "BC_LVL interrupt is turned off, abort\n");
+ goto done;
+ }
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &status0);
+ if (ret)
+ goto done;
+
+ dev_dbg(dev, "BC_LVL handler, status0 = 0x%02x\n", status0);
+ if (status0 & FUSB_REG_STATUS0_ACTIVITY)
+ dev_info(dev, "CC activities detected, delay handling\n");
+ bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK;
+ cc_status = fusb302_bc_lvl_to_cc(bc_lvl);
+ if (chip->cc_polarity == TYPEC_POLARITY_CC1) {
+ if (chip->cc1 != cc_status) {
+ dev_dbg(dev, "cc1: %s -> %s\n",
+ typec_cc_status_name[chip->cc1],
+ typec_cc_status_name[cc_status]);
+ chip->cc1 = cc_status;
+ tcpm_cc_change(dev);
+ }
+ } else {
+ if (chip->cc2 != cc_status) {
+ dev_dbg(dev, "cc2: %s -> %s\n",
+ typec_cc_status_name[chip->cc2],
+ typec_cc_status_name[cc_status]);
+ chip->cc2 = cc_status;
+ tcpm_cc_change(dev);
+ }
+ }
+
+done:
+ return;
+}
+
+static int fusb302_enter_low_power_mode(struct udevice *dev,
+ bool attached, bool pd_capable)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = fusb302_mask_interrupt(dev);
+ if (ret)
+ return ret;
+ if (attached && pd_capable)
+ reg = FUSB_REG_POWER_PWR_MEDIUM;
+ else if (attached)
+ reg = FUSB_REG_POWER_PWR_LOW;
+ else
+ reg = 0;
+
+ return fusb302_set_power_mode(dev, reg);
+}
+
+static const char * const cc_polarity_name[] = {
+ [TYPEC_POLARITY_CC1] = "Polarity_CC1",
+ [TYPEC_POLARITY_CC2] = "Polarity_CC2",
+};
+
+static int fusb302_set_cc_polarity_and_pull(struct udevice *dev,
+ enum typec_cc_polarity cc_polarity,
+ bool pull_up, bool pull_down)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+ u8 switches0_data = 0x00;
+ u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN |
+ FUSB_REG_SWITCHES1_TXCC2_EN;
+ u8 switches1_data = 0x00;
+
+ if (pull_down)
+ switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN |
+ FUSB_REG_SWITCHES0_CC2_PD_EN;
+
+ if (cc_polarity == TYPEC_POLARITY_CC1) {
+ switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC1;
+ if (chip->vconn_on)
+ switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2;
+ if (pull_up)
+ switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN;
+ switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN;
+ } else {
+ switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC2;
+ if (chip->vconn_on)
+ switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1;
+ if (pull_up)
+ switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN;
+ switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN;
+ }
+ ret = fusb302_i2c_write(dev, FUSB_REG_SWITCHES0, switches0_data);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_mask_write(dev, FUSB_REG_SWITCHES1,
+ switches1_mask, switches1_data);
+ if (ret)
+ return ret;
+ chip->cc_polarity = cc_polarity;
+
+ return ret;
+}
+
+static int fusb302_handle_togdone_snk(struct udevice *dev,
+ u8 togdone_result)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ int ret;
+ u8 status0;
+ u8 bc_lvl;
+ enum typec_cc_polarity cc_polarity;
+ enum typec_cc_status cc_status_active, cc1, cc2;
+
+ /* set polarity and pull_up, pull_down */
+ cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ?
+ TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2;
+ ret = fusb302_set_cc_polarity_and_pull(dev, cc_polarity, false, true);
+ if (ret) {
+ dev_err(dev, "cannot set cc polarity %s, ret = %d\n",
+ cc_polarity_name[cc_polarity], ret);
+ return ret;
+ }
+ /* fusb302_set_cc_polarity() has set the correct measure block */
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &status0);
+ if (ret < 0)
+ return ret;
+ bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK;
+ cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl);
+ /* restart toggling if the cc status on the active line is OPEN */
+ if (cc_status_active == TYPEC_CC_OPEN) {
+ dev_info(dev, "restart toggling as CC_OPEN detected\n");
+ ret = fusb302_set_toggling(dev, chip->toggling_mode);
+ return ret;
+ }
+ /* update tcpm with the new cc value */
+ cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ?
+ cc_status_active : TYPEC_CC_OPEN;
+ cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ?
+ cc_status_active : TYPEC_CC_OPEN;
+ if (chip->cc1 != cc1 || chip->cc2 != cc2) {
+ chip->cc1 = cc1;
+ chip->cc2 = cc2;
+ tcpm_cc_change(dev);
+ }
+ /* turn off toggling */
+ ret = fusb302_set_toggling(dev, TOGGLING_MODE_OFF);
+ if (ret) {
+ dev_err(dev, "cannot set toggling mode off, ret=%d\n", ret);
+ return ret;
+ }
+ /* unmask bc_lvl interrupt */
+ ret = fusb302_i2c_clear_bits(dev, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL);
+ if (ret) {
+ dev_err(dev, "cannot unmask bc_lcl irq, ret=%d\n", ret);
+ return ret;
+ }
+ chip->intr_bc_lvl = true;
+ dev_dbg(dev, "detected cc1=%s, cc2=%s\n",
+ typec_cc_status_name[cc1],
+ typec_cc_status_name[cc2]);
+
+ return ret;
+}
+
+/* On error returns < 0, otherwise a typec_cc_status value */
+static int fusb302_get_src_cc_status(struct udevice *dev,
+ enum typec_cc_polarity cc_polarity,
+ enum typec_cc_status *cc)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ u8 ra_mda = ra_mda_value[chip->src_current_status];
+ u8 rd_mda = rd_mda_value[chip->src_current_status];
+ u8 switches0_data, status0;
+ int ret;
+
+ /* Step 1: Set switches so that we measure the right CC pin */
+ switches0_data = (cc_polarity == TYPEC_POLARITY_CC1) ?
+ FUSB_REG_SWITCHES0_CC1_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC1 :
+ FUSB_REG_SWITCHES0_CC2_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC2;
+ ret = fusb302_i2c_write(dev, FUSB_REG_SWITCHES0, switches0_data);
+ if (ret < 0)
+ return ret;
+
+ fusb302_i2c_read(dev, FUSB_REG_SWITCHES0, &status0);
+ dev_dbg(dev, "get_src_cc_status switches: 0x%0x", status0);
+
+ /* Step 2: Set compararator volt to differentiate between Open and Rd */
+ ret = fusb302_i2c_write(dev, FUSB_REG_MEASURE, rd_mda);
+ if (ret)
+ return ret;
+
+ udelay(100);
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &status0);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "get_src_cc_status rd_mda status0: 0x%0x", status0);
+ if (status0 & FUSB_REG_STATUS0_COMP) {
+ *cc = TYPEC_CC_OPEN;
+ return 0;
+ }
+
+ /* Step 3: Set compararator input to differentiate between Rd and Ra. */
+ ret = fusb302_i2c_write(dev, FUSB_REG_MEASURE, ra_mda);
+ if (ret)
+ return ret;
+
+ udelay(100);
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &status0);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "get_src_cc_status ra_mda status0: 0x%0x", status0);
+ if (status0 & FUSB_REG_STATUS0_COMP)
+ *cc = TYPEC_CC_RD;
+ else
+ *cc = TYPEC_CC_RA;
+
+ return 0;
+}
+
+static int fusb302_handle_togdone_src(struct udevice *dev,
+ u8 togdone_result)
+{
+ /*
+ * - set polarity (measure cc, vconn, tx)
+ * - set pull_up, pull_down
+ * - set cc1, cc2, and update to tcpm state machine
+ * - set I_COMP interrupt on
+ */
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ u8 rd_mda = rd_mda_value[chip->src_current_status];
+ enum toggling_mode toggling_mode = chip->toggling_mode;
+ enum typec_cc_polarity cc_polarity;
+ enum typec_cc_status cc1, cc2;
+ int ret;
+
+ /*
+ * The toggle-engine will stop in a src state if it sees either Ra or
+ * Rd. Determine the status for both CC pins, starting with the one
+ * where toggling stopped, as that is where the switches point now.
+ */
+ if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1)
+ ret = fusb302_get_src_cc_status(dev, TYPEC_POLARITY_CC1, &cc1);
+ else
+ ret = fusb302_get_src_cc_status(dev, TYPEC_POLARITY_CC2, &cc2);
+ if (ret)
+ return ret;
+ /* we must turn off toggling before we can measure the other pin */
+ ret = fusb302_set_toggling(dev, TOGGLING_MODE_OFF);
+ if (ret) {
+ dev_err(dev, "cannot set toggling mode off, ret=%d\n", ret);
+ return ret;
+ }
+ /* get the status of the other pin */
+ if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1)
+ ret = fusb302_get_src_cc_status(dev, TYPEC_POLARITY_CC2, &cc2);
+ else
+ ret = fusb302_get_src_cc_status(dev, TYPEC_POLARITY_CC1, &cc1);
+ if (ret)
+ return ret;
+
+ /* determine polarity based on the status of both pins */
+ if (cc1 == TYPEC_CC_RD && (cc2 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_RA)) {
+ cc_polarity = TYPEC_POLARITY_CC1;
+ } else if (cc2 == TYPEC_CC_RD &&
+ (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA)) {
+ cc_polarity = TYPEC_POLARITY_CC2;
+ } else {
+ dev_err(dev, "unexpected CC status cc1=%s, cc2=%s, restarting toggling\n",
+ typec_cc_status_name[cc1],
+ typec_cc_status_name[cc2]);
+ return fusb302_set_toggling(dev, toggling_mode);
+ }
+ /* set polarity and pull_up, pull_down */
+ ret = fusb302_set_cc_polarity_and_pull(dev, cc_polarity, true, false);
+ if (ret < 0) {
+ dev_err(dev, "cannot set cc polarity %s, ret=%d\n",
+ cc_polarity_name[cc_polarity], ret);
+ return ret;
+ }
+ /* update tcpm with the new cc value */
+ if (chip->cc1 != cc1 || chip->cc2 != cc2) {
+ chip->cc1 = cc1;
+ chip->cc2 = cc2;
+ tcpm_cc_change(dev);
+ }
+ /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */
+ ret = fusb302_i2c_write(dev, FUSB_REG_MEASURE, rd_mda);
+ if (ret)
+ return ret;
+ /* unmask comp_chng interrupt */
+ ret = fusb302_i2c_clear_bits(dev, FUSB_REG_MASK,
+ FUSB_REG_MASK_COMP_CHNG);
+ if (ret) {
+ dev_err(dev, "cannot unmask comp_chng irq, ret=%d\n", ret);
+ return ret;
+ }
+ chip->intr_comp_chng = true;
+ dev_dbg(dev, "detected cc1=%s, cc2=%s\n",
+ typec_cc_status_name[cc1],
+ typec_cc_status_name[cc2]);
+
+ return ret;
+}
+
+static int fusb302_handle_togdone(struct udevice *dev)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ u8 togdone_result, status1a;
+ int ret;
+
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS1A, &status1a);
+ if (ret < 0)
+ return ret;
+ togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) &
+ FUSB_REG_STATUS1A_TOGSS_MASK;
+ switch (togdone_result) {
+ case FUSB_REG_STATUS1A_TOGSS_SNK1:
+ case FUSB_REG_STATUS1A_TOGSS_SNK2:
+ return fusb302_handle_togdone_snk(dev, togdone_result);
+ case FUSB_REG_STATUS1A_TOGSS_SRC1:
+ case FUSB_REG_STATUS1A_TOGSS_SRC2:
+ return fusb302_handle_togdone_src(dev, togdone_result);
+ case FUSB_REG_STATUS1A_TOGSS_AA:
+ /* doesn't support */
+ dev_err(dev, "AudioAccessory not supported\n");
+ fusb302_set_toggling(dev, chip->toggling_mode);
+ break;
+ default:
+ dev_err(dev, "TOGDONE with an invalid state: %d\n",
+ togdone_result);
+ fusb302_set_toggling(dev, chip->toggling_mode);
+ break;
+ }
+ return ret;
+}
+
+static int fusb302_pd_reset(struct udevice *dev)
+{
+ return fusb302_i2c_set_bits(dev, FUSB_REG_RESET,
+ FUSB_REG_RESET_PD_RESET);
+}
+
+static int fusb302_pd_read_message(struct udevice *dev,
+ struct pd_message *msg)
+{
+ int len, ret;
+ u8 crc[4];
+ u8 token;
+
+ /* first SOP token */
+ ret = fusb302_i2c_read(dev, FUSB_REG_FIFOS, &token);
+ if (ret)
+ return ret;
+ ret = fusb302_i2c_block_read(dev, FUSB_REG_FIFOS, 2,
+ (u8 *)&msg->header);
+ if (ret)
+ return ret;
+ len = pd_header_cnt_le(msg->header) * 4;
+ /* add 4 to length to include the CRC */
+ if (len > PD_MAX_PAYLOAD * 4) {
+ dev_err(dev, "PD message too long %d\n", len);
+ return -EINVAL;
+ }
+ if (len > 0) {
+ ret = fusb302_i2c_block_read(dev, FUSB_REG_FIFOS, len,
+ (u8 *)msg->payload);
+ if (ret)
+ return ret;
+ }
+ /* another 4 bytes to read CRC out */
+ ret = fusb302_i2c_block_read(dev, FUSB_REG_FIFOS, 4, crc);
+ if (ret)
+ return ret;
+ dev_dbg(dev, "Received PD message (header=0x%x len=%d)\n", msg->header, len);
+
+ /*
+ * Check if we've read off a GoodCRC message. If so then indicate to
+ * TCPM that the previous transmission has completed. Otherwise we pass
+ * the received message over to TCPM for processing.
+ *
+ * We make this check here instead of basing the reporting decision on
+ * the IRQ event type, as it's possible for the chip to report the
+ * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need
+ * to check the message type to ensure correct reporting to TCPM.
+ */
+ if (!len && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC))
+ tcpm_pd_transmit_complete(dev, TCPC_TX_SUCCESS);
+ else
+ tcpm_pd_receive(dev, msg);
+
+ return ret;
+}
+
+static void fusb302_interrupt_handle(struct udevice *dev)
+{
+ struct fusb302_chip *chip = dev_get_priv(dev);
+ u8 interrupt;
+ u8 interrupta;
+ u8 interruptb;
+ u8 status0;
+ bool vbus_present;
+ bool comp_result;
+ bool intr_togdone;
+ bool intr_bc_lvl;
+ bool intr_comp_chng;
+ struct pd_message pd_msg;
+ int ret;
+
+ /* grab a snapshot of intr flags */
+ intr_togdone = chip->intr_togdone;
+ intr_bc_lvl = chip->intr_bc_lvl;
+ intr_comp_chng = chip->intr_comp_chng;
+
+ ret = fusb302_i2c_read(dev, FUSB_REG_INTERRUPT, &interrupt);
+ if (ret)
+ return;
+ ret = fusb302_i2c_read(dev, FUSB_REG_INTERRUPTA, &interrupta);
+ if (ret)
+ return;
+ ret = fusb302_i2c_read(dev, FUSB_REG_INTERRUPTB, &interruptb);
+ if (ret)
+ return;
+ ret = fusb302_i2c_read(dev, FUSB_REG_STATUS0, &status0);
+ if (ret)
+ return;
+
+ /*
+ * Since we are polling the IRQs, avoid printing messages when there
+ * no interrupts at all to avoid spamming the log.
+ */
+ if (interrupt != 0 || interrupta != 0 || interruptb != 0)
+ dev_dbg(dev, "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x\n",
+ interrupt, interrupta, interruptb, status0);
+
+ if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) {
+ vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK);
+ dev_dbg(dev, "IRQ: VBUS_OK, vbus=%s\n",
+ vbus_present ? "On" : "Off");
+ if (vbus_present != chip->vbus_present) {
+ chip->vbus_present = vbus_present;
+ tcpm_vbus_change(dev);
+ }
+ }
+
+ if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) {
+ dev_dbg(dev, "IRQ: TOGDONE\n");
+ ret = fusb302_handle_togdone(dev);
+ if (ret) {
+ dev_err(dev, "handle togdone error: %d\n", ret);
+ return;
+ }
+ }
+
+ if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) {
+ dev_dbg(dev, "IRQ: BC_LVL, handler pending\n");
+ fusb302_bc_lvl_handler(dev);
+ }
+
+ if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) {
+ comp_result = !!(status0 & FUSB_REG_STATUS0_COMP);
+ dev_dbg(dev, "IRQ: COMP_CHNG, comp=%s\n",
+ comp_result ? "true" : "false");
+ if (comp_result) {
+ /* cc level > Rd_threshold, detach */
+ chip->cc1 = TYPEC_CC_OPEN;
+ chip->cc2 = TYPEC_CC_OPEN;
+ tcpm_cc_change(dev);
+ }
+ }
+
+ if (interrupt & FUSB_REG_INTERRUPT_COLLISION) {
+ dev_dbg(dev, "IRQ: PD collision\n");
+ tcpm_pd_transmit_complete(dev, TCPC_TX_FAILED);
+ }
+
+ if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) {
+ dev_dbg(dev, "IRQ: PD retry failed\n");
+ tcpm_pd_transmit_complete(dev, TCPC_TX_FAILED);
+ }
+
+ if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) {
+ dev_dbg(dev, "IRQ: PD hardreset sent\n");
+ ret = fusb302_pd_reset(dev);
+ if (ret) {
+ dev_err(dev, "cannot PD reset, ret=%d\n", ret);
+ return;
+ }
+ tcpm_pd_transmit_complete(dev, TCPC_TX_SUCCESS);
+ }
+
+ if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) {
+ dev_dbg(dev, "IRQ: PD tx success\n");
+ ret = fusb302_pd_read_message(dev, &pd_msg);
+ if (ret) {
+ dev_err(dev, "cannot read in PD message, ret=%d\n", ret);
+ return;
+ }
+ }
+
+ if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) {
+ dev_dbg(dev, "IRQ: PD received hardreset\n");
+ ret = fusb302_pd_reset(dev);
+ if (ret) {
+ dev_err(dev, "cannot PD reset, ret=%d\n", ret);
+ return;
+ }
+ tcpm_pd_hard_reset(dev);
+ }
+
+ if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) {
+ dev_dbg(dev, "IRQ: PD sent good CRC\n");
+ ret = fusb302_pd_read_message(dev, &pd_msg);
+ if (ret) {
+ dev_err(dev, "cannot read in PD message, ret=%d\n", ret);
+ return;
+ }
+ }
+}
+
+static void fusb302_poll_event(struct udevice *dev)
+{
+ fusb302_interrupt_handle(dev);
+}
+
+static int fusb302_get_connector_node(struct udevice *dev, ofnode *connector_node)
+{
+ *connector_node = dev_read_subnode(dev, "connector");
+ if (!ofnode_valid(*connector_node)) {
+ dev_err(dev, "'connector' node is not found\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static struct dm_tcpm_ops fusb302_ops = {
+ .get_connector_node = fusb302_get_connector_node,
+ .init = fusb302_init,
+ .get_vbus = fusb302_get_vbus,
+ .set_cc = fusb302_set_cc,
+ .get_cc = fusb302_get_cc,
+ .set_vconn = fusb302_set_vconn,
+ .set_vbus = fusb302_set_vbus,
+ .set_pd_rx = fusb302_set_pd_rx,
+ .set_roles = fusb302_set_roles,
+ .start_toggling = fusb302_start_toggling,
+ .pd_transmit = fusb302_pd_transmit,
+ .poll_event = fusb302_poll_event,
+ .enter_low_power_mode = fusb302_enter_low_power_mode,
+};
+
+static const struct udevice_id fusb302_ids[] = {
+ { .compatible = "fcs,fusb302" },
+ { }
+};
+
+U_BOOT_DRIVER(fusb302) = {
+ .name = "fusb302",
+ .id = UCLASS_TCPM,
+ .of_match = fusb302_ids,
+ .ops = &fusb302_ops,
+ .priv_auto = sizeof(struct fusb302_chip),
+};
diff --git a/drivers/usb/tcpm/fusb302_reg.h b/drivers/usb/tcpm/fusb302_reg.h
new file mode 100644
index 00000000000..edc0e4b0f1e
--- /dev/null
+++ b/drivers/usb/tcpm/fusb302_reg.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2016-2017 Google, Inc
+ *
+ * Fairchild FUSB302 Type-C Chip Driver
+ */
+
+#ifndef FUSB302_REG_H
+#define FUSB302_REG_H
+
+#define FUSB_REG_DEVICE_ID 0x01
+#define FUSB_REG_SWITCHES0 0x02
+#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7)
+#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6)
+#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5)
+#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4)
+#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3)
+#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2)
+#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1)
+#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0)
+#define FUSB_REG_SWITCHES1 0x03
+#define FUSB_REG_SWITCHES1_POWERROLE BIT(7)
+#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6)
+#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5)
+#define FUSB_REG_SWITCHES1_DATAROLE BIT(4)
+#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2)
+#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1)
+#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0)
+#define FUSB_REG_MEASURE 0x04
+#define FUSB_REG_MEASURE_MDAC5 BIT(7)
+#define FUSB_REG_MEASURE_MDAC4 BIT(6)
+#define FUSB_REG_MEASURE_MDAC3 BIT(5)
+#define FUSB_REG_MEASURE_MDAC2 BIT(4)
+#define FUSB_REG_MEASURE_MDAC1 BIT(3)
+#define FUSB_REG_MEASURE_MDAC0 BIT(2)
+#define FUSB_REG_MEASURE_VBUS BIT(1)
+#define FUSB_REG_MEASURE_XXXX5 BIT(0)
+#define FUSB_REG_CONTROL0 0x06
+#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6)
+#define FUSB_REG_CONTROL0_INT_MASK BIT(5)
+#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC)
+#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC)
+#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8)
+#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4)
+#define FUSB_REG_CONTROL0_TX_START BIT(0)
+#define FUSB_REG_CONTROL1 0x07
+#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6)
+#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5)
+#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4)
+#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2)
+#define FUSB_REG_CONTROL1_ENSOP2 BIT(1)
+#define FUSB_REG_CONTROL1_ENSOP1 BIT(0)
+#define FUSB_REG_CONTROL2 0x08
+#define FUSB_REG_CONTROL2_MODE BIT(1)
+#define FUSB_REG_CONTROL2_MODE_MASK (0x6)
+#define FUSB_REG_CONTROL2_MODE_DFP (0x6)
+#define FUSB_REG_CONTROL2_MODE_UFP (0x4)
+#define FUSB_REG_CONTROL2_MODE_DRP (0x2)
+#define FUSB_REG_CONTROL2_MODE_NONE (0x0)
+#define FUSB_REG_CONTROL2_TOGGLE BIT(0)
+#define FUSB_REG_CONTROL3 0x09
+#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6)
+#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */
+#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4)
+#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3)
+#define FUSB_REG_CONTROL3_N_RETRIES BIT(1)
+#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6)
+#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6)
+#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4)
+#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2)
+#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0)
+#define FUSB_REG_MASK 0x0A
+#define FUSB_REG_MASK_VBUSOK BIT(7)
+#define FUSB_REG_MASK_ACTIVITY BIT(6)
+#define FUSB_REG_MASK_COMP_CHNG BIT(5)
+#define FUSB_REG_MASK_CRC_CHK BIT(4)
+#define FUSB_REG_MASK_ALERT BIT(3)
+#define FUSB_REG_MASK_WAKE BIT(2)
+#define FUSB_REG_MASK_COLLISION BIT(1)
+#define FUSB_REG_MASK_BC_LVL BIT(0)
+#define FUSB_REG_POWER 0x0B
+#define FUSB_REG_POWER_PWR BIT(0)
+#define FUSB_REG_POWER_PWR_LOW 0x1
+#define FUSB_REG_POWER_PWR_MEDIUM 0x3
+#define FUSB_REG_POWER_PWR_HIGH 0x7
+#define FUSB_REG_POWER_PWR_ALL 0xF
+#define FUSB_REG_RESET 0x0C
+#define FUSB_REG_RESET_PD_RESET BIT(1)
+#define FUSB_REG_RESET_SW_RESET BIT(0)
+#define FUSB_REG_MASKA 0x0E
+#define FUSB_REG_MASKA_OCP_TEMP BIT(7)
+#define FUSB_REG_MASKA_TOGDONE BIT(6)
+#define FUSB_REG_MASKA_SOFTFAIL BIT(5)
+#define FUSB_REG_MASKA_RETRYFAIL BIT(4)
+#define FUSB_REG_MASKA_HARDSENT BIT(3)
+#define FUSB_REG_MASKA_TX_SUCCESS BIT(2)
+#define FUSB_REG_MASKA_SOFTRESET BIT(1)
+#define FUSB_REG_MASKA_HARDRESET BIT(0)
+#define FUSB_REG_MASKB 0x0F
+#define FUSB_REG_MASKB_GCRCSENT BIT(0)
+#define FUSB_REG_STATUS0A 0x3C
+#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5)
+#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4)
+#define FUSB_REG_STATUS0A_POWER BIT(2)
+#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1)
+#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0)
+#define FUSB_REG_STATUS1A 0x3D
+#define FUSB_REG_STATUS1A_TOGSS BIT(3)
+#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0
+#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1
+#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2
+#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5
+#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6
+#define FUSB_REG_STATUS1A_TOGSS_AA 0x7
+#define FUSB_REG_STATUS1A_TOGSS_POS (3)
+#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7)
+#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2)
+#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1)
+#define FUSB_REG_STATUS1A_RXSOP BIT(0)
+#define FUSB_REG_INTERRUPTA 0x3E
+#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7)
+#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6)
+#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5)
+#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4)
+#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3)
+#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2)
+#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1)
+#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0)
+#define FUSB_REG_INTERRUPTB 0x3F
+#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0)
+#define FUSB_REG_STATUS0 0x40
+#define FUSB_REG_STATUS0_VBUSOK BIT(7)
+#define FUSB_REG_STATUS0_ACTIVITY BIT(6)
+#define FUSB_REG_STATUS0_COMP BIT(5)
+#define FUSB_REG_STATUS0_CRC_CHK BIT(4)
+#define FUSB_REG_STATUS0_ALERT BIT(3)
+#define FUSB_REG_STATUS0_WAKE BIT(2)
+#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03
+#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0
+#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1
+#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2
+#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3
+#define FUSB_REG_STATUS0_BC_LVL1 BIT(1)
+#define FUSB_REG_STATUS0_BC_LVL0 BIT(0)
+#define FUSB_REG_STATUS1 0x41
+#define FUSB_REG_STATUS1_RXSOP2 BIT(7)
+#define FUSB_REG_STATUS1_RXSOP1 BIT(6)
+#define FUSB_REG_STATUS1_RX_EMPTY BIT(5)
+#define FUSB_REG_STATUS1_RX_FULL BIT(4)
+#define FUSB_REG_STATUS1_TX_EMPTY BIT(3)
+#define FUSB_REG_STATUS1_TX_FULL BIT(2)
+#define FUSB_REG_INTERRUPT 0x42
+#define FUSB_REG_INTERRUPT_VBUSOK BIT(7)
+#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6)
+#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5)
+#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4)
+#define FUSB_REG_INTERRUPT_ALERT BIT(3)
+#define FUSB_REG_INTERRUPT_WAKE BIT(2)
+#define FUSB_REG_INTERRUPT_COLLISION BIT(1)
+#define FUSB_REG_INTERRUPT_BC_LVL BIT(0)
+#define FUSB_REG_FIFOS 0x43
+
+/* Tokens defined for the FUSB302 TX FIFO */
+enum fusb302_txfifo_tokens {
+ FUSB302_TKN_TXON = 0xA1,
+ FUSB302_TKN_SYNC1 = 0x12,
+ FUSB302_TKN_SYNC2 = 0x13,
+ FUSB302_TKN_SYNC3 = 0x1B,
+ FUSB302_TKN_RST1 = 0x15,
+ FUSB302_TKN_RST2 = 0x16,
+ FUSB302_TKN_PACKSYM = 0x80,
+ FUSB302_TKN_JAMCRC = 0xFF,
+ FUSB302_TKN_EOP = 0x14,
+ FUSB302_TKN_TXOFF = 0xFE,
+};
+
+#endif
diff --git a/drivers/usb/tcpm/tcpm-internal.h b/drivers/usb/tcpm/tcpm-internal.h
new file mode 100644
index 00000000000..56144209002
--- /dev/null
+++ b/drivers/usb/tcpm/tcpm-internal.h
@@ -0,0 +1,173 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2015-2017 Google, Inc
+ * Copyright 2024 Collabora
+ */
+
+#ifndef __LINUX_USB_TCPM_INTERNAL_H
+#define __LINUX_USB_TCPM_INTERNAL_H
+
+#define FOREACH_TCPM_STATE(S) \
+ S(INVALID_STATE), \
+ S(TOGGLING), \
+ S(SRC_UNATTACHED), \
+ S(SRC_ATTACH_WAIT), \
+ S(SRC_ATTACHED), \
+ S(SRC_STARTUP), \
+ S(SRC_SEND_CAPABILITIES), \
+ S(SRC_SEND_CAPABILITIES_TIMEOUT), \
+ S(SRC_NEGOTIATE_CAPABILITIES), \
+ S(SRC_TRANSITION_SUPPLY), \
+ S(SRC_READY), \
+ S(SRC_WAIT_NEW_CAPABILITIES), \
+ \
+ S(SNK_UNATTACHED), \
+ S(SNK_ATTACH_WAIT), \
+ S(SNK_DEBOUNCED), \
+ S(SNK_ATTACHED), \
+ S(SNK_STARTUP), \
+ S(SNK_DISCOVERY), \
+ S(SNK_DISCOVERY_DEBOUNCE), \
+ S(SNK_DISCOVERY_DEBOUNCE_DONE), \
+ S(SNK_WAIT_CAPABILITIES), \
+ S(SNK_NEGOTIATE_CAPABILITIES), \
+ S(SNK_TRANSITION_SINK), \
+ S(SNK_TRANSITION_SINK_VBUS), \
+ S(SNK_READY), \
+ \
+ S(HARD_RESET_SEND), \
+ S(HARD_RESET_START), \
+ S(SRC_HARD_RESET_VBUS_OFF), \
+ S(SRC_HARD_RESET_VBUS_ON), \
+ S(SNK_HARD_RESET_SINK_OFF), \
+ S(SNK_HARD_RESET_WAIT_VBUS), \
+ S(SNK_HARD_RESET_SINK_ON), \
+ \
+ S(SOFT_RESET), \
+ S(SOFT_RESET_SEND), \
+ \
+ S(DR_SWAP_ACCEPT), \
+ S(DR_SWAP_CHANGE_DR), \
+ \
+ S(ERROR_RECOVERY), \
+ S(PORT_RESET), \
+ S(PORT_RESET_WAIT_OFF)
+
+#define GENERATE_TCPM_ENUM(e) e
+#define GENERATE_TCPM_STRING(s) #s
+#define TCPM_POLL_EVENT_TIME_OUT 2000
+
+enum tcpm_state {
+ FOREACH_TCPM_STATE(GENERATE_TCPM_ENUM)
+};
+
+enum pd_msg_request {
+ PD_MSG_NONE = 0,
+ PD_MSG_CTRL_REJECT,
+ PD_MSG_CTRL_WAIT,
+ PD_MSG_CTRL_NOT_SUPP,
+ PD_MSG_DATA_SINK_CAP,
+ PD_MSG_DATA_SOURCE_CAP,
+};
+
+struct tcpm_port {
+ enum typec_port_type typec_type;
+ int typec_prefer_role;
+
+ enum typec_role vconn_role;
+ enum typec_role pwr_role;
+ enum typec_data_role data_role;
+
+ struct typec_partner *partner;
+
+ enum typec_cc_status cc_req;
+ enum typec_cc_status cc1;
+ enum typec_cc_status cc2;
+ enum typec_cc_polarity polarity;
+
+ bool attached;
+ bool connected;
+ int poll_event_cnt;
+ enum typec_port_type port_type;
+
+ /*
+ * Set to true when vbus is greater than VSAFE5V min.
+ * Set to false when vbus falls below vSinkDisconnect max threshold.
+ */
+ bool vbus_present;
+
+ /*
+ * Set to true when vbus is less than VSAFE0V max.
+ * Set to false when vbus is greater than VSAFE0V max.
+ */
+ bool vbus_vsafe0v;
+
+ bool vbus_never_low;
+ bool vbus_source;
+ bool vbus_charge;
+
+ int try_role;
+
+ enum pd_msg_request queued_message;
+
+ enum tcpm_state enter_state;
+ enum tcpm_state prev_state;
+ enum tcpm_state state;
+ enum tcpm_state delayed_state;
+ unsigned long delay_ms;
+
+ bool state_machine_running;
+
+ bool tx_complete;
+ enum tcpm_transmit_status tx_status;
+
+ unsigned int negotiated_rev;
+ unsigned int message_id;
+ unsigned int caps_count;
+ unsigned int hard_reset_count;
+ bool pd_capable;
+ bool explicit_contract;
+ unsigned int rx_msgid;
+
+ /* Partner capabilities/requests */
+ u32 sink_request;
+ u32 source_caps[PDO_MAX_OBJECTS];
+ unsigned int nr_source_caps;
+ u32 sink_caps[PDO_MAX_OBJECTS];
+ unsigned int nr_sink_caps;
+
+ /*
+ * whether to wait for the Type-C device to send the DR_SWAP Message flag
+ * For Type-C device with Dual-Role Power and Dual-Role Data, the port side
+ * is used as sink + ufp, then the tcpm framework needs to wait for Type-C
+ * device to initiate DR_swap Message.
+ */
+ bool wait_dr_swap_message;
+
+ /* Local capabilities */
+ u32 src_pdo[PDO_MAX_OBJECTS];
+ unsigned int nr_src_pdo;
+ u32 snk_pdo[PDO_MAX_OBJECTS];
+ unsigned int nr_snk_pdo;
+
+ unsigned int operating_snk_mw;
+ bool update_sink_caps;
+
+ /* Requested current / voltage to the port partner */
+ u32 req_current_limit;
+ u32 req_supply_voltage;
+ /* Actual current / voltage limit of the local port */
+ u32 current_limit;
+ u32 supply_voltage;
+
+ /* port belongs to a self powered device */
+ bool self_powered;
+
+ unsigned long delay_target;
+};
+
+extern const char * const tcpm_states[];
+
+int tcpm_post_probe(struct udevice *dev);
+
+#endif
diff --git a/drivers/usb/tcpm/tcpm-uclass.c b/drivers/usb/tcpm/tcpm-uclass.c
new file mode 100644
index 00000000000..d4fe260e0db
--- /dev/null
+++ b/drivers/usb/tcpm/tcpm-uclass.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 Collabora Ltd.
+ *
+ * USB Power Delivery protocol stack.
+ */
+
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <dm/uclass.h>
+#include <linux/err.h>
+#include <usb/tcpm.h>
+#include "tcpm-internal.h"
+
+int tcpm_get_voltage(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->supply_voltage;
+}
+
+int tcpm_get_current(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->current_limit;
+}
+
+enum typec_orientation tcpm_get_orientation(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ switch (port->polarity) {
+ case TYPEC_POLARITY_CC1:
+ return TYPEC_ORIENTATION_NORMAL;
+ case TYPEC_POLARITY_CC2:
+ return TYPEC_ORIENTATION_REVERSE;
+ default:
+ return TYPEC_ORIENTATION_NONE;
+ }
+}
+
+const char *tcpm_get_state(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return tcpm_states[port->state];
+}
+
+int tcpm_get_pd_rev(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->negotiated_rev;
+}
+
+enum typec_role tcpm_get_pwr_role(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->pwr_role;
+}
+
+enum typec_data_role tcpm_get_data_role(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->data_role;
+}
+
+bool tcpm_is_connected(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return port->connected;
+}
+
+int tcpm_get(int index, struct udevice **devp)
+{
+ return uclass_get_device(UCLASS_TCPM, index, devp);
+}
+
+static int tcpm_post_bind(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ const char *cap_str;
+ ofnode node;
+ int ret;
+
+ /*
+ * USB Power Delivery (USB PD) specification requires, that communication
+ * with a sink happens within roughly 5 seconds. Otherwise the source
+ * might assume that the sink does not support USB PD. Starting to do
+ * USB PD communication after that results in a hard reset, which briefly
+ * removes any power from the USB-C port.
+ *
+ * On systems with alternative power supplies this is not an issue, but
+ * systems, which get soleley powered through their USB-C port will end
+ * up losing their power supply and doing a board level reset. The hard
+ * reset will also restart the 5 second timeout. That means a operating
+ * system initializing USB PD will put the system into a boot loop when
+ * it takes more than 5 seconds from cold boot to the operating system
+ * starting to transmit USB PD messages.
+ *
+ * The issue can be avoided by doing the initial USB PD communication
+ * in U-Boot. The operating system can then re-negotiate by doing a
+ * soft reset, which does not trigger removal of the supply voltage.
+ *
+ * Since the TCPM state machine is quite complex and depending on the
+ * remote side can take quite some time to finish, this tries to limit
+ * the automatic probing to systems probably relying on power being
+ * provided by the USB-C port(s):
+ *
+ * 1. self-powered devices won't reset when the USB-C port looses power
+ * 2. if the power is allowed to go into anything else than sink mode
+ * it is not the only power source
+ */
+ ret = drvops->get_connector_node(dev, &node);
+ if (ret)
+ return ret;
+
+ if (ofnode_read_bool(node, "self-powered"))
+ return 0;
+
+ cap_str = ofnode_read_string(node, "power-role");
+ if (!cap_str)
+ return -EINVAL;
+
+ if (strcmp("sink", cap_str))
+ return 0;
+
+ /* Do not auto-probe PD controller when PD is disabled */
+ if (ofnode_read_bool(node, "pd-disable"))
+ return 0;
+
+ dev_info(dev, "probing Type-C port manager...");
+
+ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+
+ return 0;
+}
+
+UCLASS_DRIVER(tcpm) = {
+ .id = UCLASS_TCPM,
+ .name = "tcpm",
+ .per_device_plat_auto = sizeof(struct tcpm_port),
+ .post_bind = tcpm_post_bind,
+ .post_probe = tcpm_post_probe,
+};
diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c
new file mode 100644
index 00000000000..0aee57cb2f4
--- /dev/null
+++ b/drivers/usb/tcpm/tcpm.c
@@ -0,0 +1,2288 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2015-2017 Google, Inc
+ *
+ * USB Power Delivery protocol stack.
+ */
+
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/devres.h>
+#include <linux/compat.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <time.h>
+#include <usb/tcpm.h>
+#include "tcpm-internal.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+const char * const tcpm_states[] = {
+ FOREACH_TCPM_STATE(GENERATE_TCPM_STRING)
+};
+
+const char * const typec_pd_rev_name[] = {
+ [PD_REV10] = "rev1",
+ [PD_REV20] = "rev2",
+ [PD_REV30] = "rev3",
+};
+
+const char * const typec_role_name[] = {
+ [TYPEC_SINK] = "sink",
+ [TYPEC_SOURCE] = "source",
+};
+
+const char * const typec_data_role_name[] = {
+ [TYPEC_DEVICE] = "device",
+ [TYPEC_HOST] = "host",
+};
+
+const char * const typec_orientation_name[] = {
+ [TYPEC_ORIENTATION_NONE] = "none",
+ [TYPEC_ORIENTATION_NORMAL] = "normal",
+ [TYPEC_ORIENTATION_REVERSE] = "reverse",
+};
+
+const char * const typec_cc_status_name[] = {
+ [TYPEC_CC_OPEN] = "open",
+ [TYPEC_CC_RA] = "ra",
+ [TYPEC_CC_RD] = "rd",
+ [TYPEC_CC_RP_DEF] = "rp-def",
+ [TYPEC_CC_RP_1_5] = "rp-1.5",
+ [TYPEC_CC_RP_3_0] = "rp-3.0",
+};
+
+static inline bool tcpm_cc_is_sink(enum typec_cc_status cc)
+{
+ return cc == TYPEC_CC_RP_DEF ||
+ cc == TYPEC_CC_RP_1_5 ||
+ cc == TYPEC_CC_RP_3_0;
+}
+
+static inline bool tcpm_port_is_sink(struct tcpm_port *port)
+{
+ bool cc1_is_snk = tcpm_cc_is_sink(port->cc1);
+ bool cc2_is_snk = tcpm_cc_is_sink(port->cc2);
+
+ return (cc1_is_snk && !cc2_is_snk) ||
+ (cc2_is_snk && !cc1_is_snk);
+}
+
+static inline bool tcpm_cc_is_source(enum typec_cc_status cc)
+{
+ return cc == TYPEC_CC_RD;
+}
+
+static inline bool tcpm_port_is_source(struct tcpm_port *port)
+{
+ bool cc1_is_src = tcpm_cc_is_source(port->cc1);
+ bool cc2_is_src = tcpm_cc_is_source(port->cc2);
+
+ return (cc1_is_src && !cc2_is_src) ||
+ (cc2_is_src && !cc1_is_src);
+}
+
+static inline bool tcpm_try_src(struct tcpm_port *port)
+{
+ return port->try_role == TYPEC_SOURCE &&
+ port->port_type == TYPEC_PORT_DRP;
+}
+
+static inline void tcpm_reset_event_cnt(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ port->poll_event_cnt = 0;
+}
+
+static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
+{
+ if (port->port_type == TYPEC_PORT_DRP) {
+ if (port->try_role == TYPEC_SINK)
+ return SNK_UNATTACHED;
+ else if (port->try_role == TYPEC_SOURCE)
+ return SRC_UNATTACHED;
+ } else if (port->port_type == TYPEC_PORT_SNK) {
+ return SNK_UNATTACHED;
+ }
+ return SRC_UNATTACHED;
+}
+
+static bool tcpm_port_is_disconnected(struct tcpm_port *port)
+{
+ return (!port->attached && port->cc1 == TYPEC_CC_OPEN &&
+ port->cc2 == TYPEC_CC_OPEN) ||
+ (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 &&
+ port->cc1 == TYPEC_CC_OPEN) ||
+ (port->polarity == TYPEC_POLARITY_CC2 &&
+ port->cc2 == TYPEC_CC_OPEN)));
+}
+
+static void tcpm_set_cc(struct udevice *dev, enum typec_cc_status cc)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ dev_dbg(dev, "TCPM: set cc = %d\n", cc);
+ port->cc_req = cc;
+ drvops->set_cc(dev, cc);
+}
+
+/*
+ * Determine RP value to set based on maximum current supported
+ * by a port if configured as source.
+ * Returns CC value to report to link partner.
+ */
+static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port)
+{
+ const u32 *src_pdo = port->src_pdo;
+ int nr_pdo = port->nr_src_pdo;
+ int i;
+
+ /*
+ * Search for first entry with matching voltage.
+ * It should report the maximum supported current.
+ */
+ for (i = 0; i < nr_pdo; i++) {
+ const u32 pdo = src_pdo[i];
+
+ if (pdo_type(pdo) == PDO_TYPE_FIXED &&
+ pdo_fixed_voltage(pdo) == 5000) {
+ unsigned int curr = pdo_max_current(pdo);
+
+ if (curr >= 3000)
+ return TYPEC_CC_RP_3_0;
+ else if (curr >= 1500)
+ return TYPEC_CC_RP_1_5;
+ return TYPEC_CC_RP_DEF;
+ }
+ }
+
+ return TYPEC_CC_RP_DEF;
+}
+
+static void tcpm_check_and_run_delayed_work(struct udevice *dev);
+
+static bool tcpm_transmit_helper(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ drvops->poll_event(dev);
+ udelay(500);
+ tcpm_check_and_run_delayed_work(dev);
+ return port->tx_complete;
+}
+
+static int tcpm_pd_transmit(struct udevice *dev,
+ enum tcpm_transmit_type type,
+ const struct pd_message *msg)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ u32 timeout_us = PD_T_TCPC_TX_TIMEOUT * 1000;
+ bool tx_complete;
+ int ret;
+
+ if (msg)
+ dev_dbg(dev, "TCPM: PD TX, header: %#x\n",
+ le16_to_cpu(msg->header));
+ else
+ dev_dbg(dev, "TCPM: PD TX, type: %#x\n", type);
+
+ port->tx_complete = false;
+ ret = drvops->pd_transmit(dev, type, msg, port->negotiated_rev);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * At this point we basically need to block until the TCPM controller
+ * returns successful transmission. Since this is usually done using
+ * the generic interrupt status bits, we poll for any events. That
+ * will clear the interrupt status, so we also need to process any
+ * of the incoming events. This means we will do more processing and
+ * thus let's give everything a bit more time.
+ */
+ timeout_us *= 5;
+ ret = read_poll_timeout(tcpm_transmit_helper, tx_complete,
+ !tx_complete, false, timeout_us, dev);
+ if (ret < 0) {
+ dev_err(dev, "TCPM: PD transmit data failed: %d\n", ret);
+ return ret;
+ }
+
+ switch (port->tx_status) {
+ case TCPC_TX_SUCCESS:
+ port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK;
+ break;
+ case TCPC_TX_DISCARDED:
+ ret = -EAGAIN;
+ break;
+ case TCPC_TX_FAILED:
+ default:
+ ret = -EIO;
+ break;
+ }
+
+ return ret;
+}
+
+void tcpm_pd_transmit_complete(struct udevice *dev,
+ enum tcpm_transmit_status status)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ dev_dbg(dev, "TCPM: PD TX complete, status: %u\n", status);
+ tcpm_reset_event_cnt(dev);
+ port->tx_status = status;
+ port->tx_complete = true;
+}
+
+static int tcpm_set_polarity(struct udevice *dev,
+ enum typec_cc_polarity polarity)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ int ret;
+
+ dev_dbg(dev, "TCPM: set polarity = %d\n", polarity);
+
+ if (drvops->set_polarity) {
+ ret = drvops->set_polarity(dev, polarity);
+ if (ret < 0)
+ return ret;
+ }
+
+ port->polarity = polarity;
+
+ return 0;
+}
+
+static int tcpm_set_vconn(struct udevice *dev, bool enable)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ dev_dbg(dev, "TCPM: set vconn = %d\n", enable);
+
+ ret = drvops->set_vconn(dev, enable);
+ if (!ret)
+ port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK;
+
+ return ret;
+}
+
+static inline u32 tcpm_get_current_limit(struct tcpm_port *port)
+{
+ switch (port->polarity ? port->cc2 : port->cc1) {
+ case TYPEC_CC_RP_1_5:
+ return 1500;
+ case TYPEC_CC_RP_3_0:
+ return 3000;
+ case TYPEC_CC_RP_DEF:
+ default:
+ return 0;
+ }
+}
+
+static int tcpm_set_current_limit(struct udevice *dev, u32 max_ma, u32 mv)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret = -EOPNOTSUPP;
+
+ dev_info(dev, "TCPM: set voltage limit = %u mV\n", mv);
+ dev_info(dev, "TCPM: set current limit = %u mA\n", max_ma);
+
+ port->supply_voltage = mv;
+ port->current_limit = max_ma;
+
+ return ret;
+}
+
+static int tcpm_set_attached_state(struct udevice *dev, bool attached)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ return drvops->set_roles(dev, attached, port->pwr_role,
+ port->data_role);
+}
+
+static int tcpm_set_roles(struct udevice *dev, bool attached,
+ enum typec_role role, enum typec_data_role data)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ ret = drvops->set_roles(dev, attached, role, data);
+ if (ret < 0)
+ return ret;
+
+ port->pwr_role = role;
+ port->data_role = data;
+
+ return 0;
+}
+
+static int tcpm_pd_send_source_caps(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ struct pd_message msg;
+ int i;
+
+ memset(&msg, 0, sizeof(msg));
+
+ if (!port->nr_src_pdo) {
+ /* No source capabilities defined, sink only */
+ msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id, 0);
+ } else {
+ msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id,
+ port->nr_src_pdo);
+ }
+
+ for (i = 0; i < port->nr_src_pdo; i++)
+ msg.payload[i] = cpu_to_le32(port->src_pdo[i]);
+
+ return tcpm_pd_transmit(dev, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_send_sink_caps(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ struct pd_message msg;
+ unsigned int i;
+
+ memset(&msg, 0, sizeof(msg));
+
+ if (!port->nr_snk_pdo) {
+ /* No sink capabilities defined, source only */
+ msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id, 0);
+ } else {
+ msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id,
+ port->nr_snk_pdo);
+ }
+
+ for (i = 0; i < port->nr_snk_pdo; i++)
+ msg.payload[i] = cpu_to_le32(port->snk_pdo[i]);
+
+ return tcpm_pd_transmit(dev, TCPC_TX_SOP, &msg);
+}
+
+static void tcpm_state_machine(struct udevice *dev);
+
+static inline void tcpm_timer_uninit(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ port->delay_target = 0;
+}
+
+static void tcpm_timer_init(struct udevice *dev, uint32_t ms)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ unsigned long time_us = ms * 1000;
+
+ port->delay_target = timer_get_us() + time_us;
+}
+
+static void tcpm_check_and_run_delayed_work(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ /* no delayed state changes scheduled */
+ if (port->delay_target == 0)
+ return;
+
+ /* it's not yet time */
+ if (timer_get_us() < port->delay_target)
+ return;
+
+ tcpm_timer_uninit(dev);
+ tcpm_state_machine(dev);
+}
+
+static void mod_tcpm_delayed_work(struct udevice *dev, unsigned int delay_ms)
+{
+ if (delay_ms) {
+ tcpm_timer_init(dev, delay_ms);
+ } else {
+ tcpm_timer_uninit(dev);
+ tcpm_state_machine(dev);
+ }
+}
+
+static void tcpm_set_state(struct udevice *dev, enum tcpm_state state,
+ unsigned int delay_ms)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ if (delay_ms) {
+ dev_dbg(dev, "TCPM: pending state change %s -> %s @ %u ms [%s]\n",
+ tcpm_states[port->state], tcpm_states[state], delay_ms,
+ typec_pd_rev_name[port->negotiated_rev]);
+ port->delayed_state = state;
+ mod_tcpm_delayed_work(dev, delay_ms);
+ port->delay_ms = delay_ms;
+ } else {
+ dev_dbg(dev, "TCPM: state change %s -> %s\n",
+ tcpm_states[port->state], tcpm_states[state]);
+ port->delayed_state = INVALID_STATE;
+ port->prev_state = port->state;
+ port->state = state;
+ /*
+ * Don't re-queue the state machine work item if we're currently
+ * in the state machine and we're immediately changing states.
+ * tcpm_state_machine_work() will continue running the state
+ * machine.
+ */
+ if (!port->state_machine_running)
+ mod_tcpm_delayed_work(dev, 0);
+ }
+}
+
+static void tcpm_set_state_cond(struct udevice *dev, enum tcpm_state state,
+ unsigned int delay_ms)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ if (port->enter_state == port->state)
+ tcpm_set_state(dev, state, delay_ms);
+ else
+ dev_dbg(dev, "TCPM: skipped %sstate change %s -> %s [%u ms], context state %s [%s]\n",
+ delay_ms ? "delayed " : "",
+ tcpm_states[port->state], tcpm_states[state],
+ delay_ms, tcpm_states[port->enter_state],
+ typec_pd_rev_name[port->negotiated_rev]);
+}
+
+static void tcpm_queue_message(struct udevice *dev,
+ enum pd_msg_request message)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ port->queued_message = message;
+ mod_tcpm_delayed_work(dev, 0);
+}
+
+enum pdo_err {
+ PDO_NO_ERR,
+ PDO_ERR_NO_VSAFE5V,
+ PDO_ERR_VSAFE5V_NOT_FIRST,
+ PDO_ERR_PDO_TYPE_NOT_IN_ORDER,
+ PDO_ERR_FIXED_NOT_SORTED,
+ PDO_ERR_VARIABLE_BATT_NOT_SORTED,
+ PDO_ERR_DUPE_PDO,
+ PDO_ERR_PPS_APDO_NOT_SORTED,
+ PDO_ERR_DUPE_PPS_APDO,
+};
+
+static const char * const pdo_err_msg[] = {
+ [PDO_ERR_NO_VSAFE5V] =
+ " err: source/sink caps should at least have vSafe5V",
+ [PDO_ERR_VSAFE5V_NOT_FIRST] =
+ " err: vSafe5V Fixed Supply Object Shall always be the first object",
+ [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] =
+ " err: PDOs should be in the following order: Fixed; Battery; Variable",
+ [PDO_ERR_FIXED_NOT_SORTED] =
+ " err: Fixed supply pdos should be in increasing order of their fixed voltage",
+ [PDO_ERR_VARIABLE_BATT_NOT_SORTED] =
+ " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
+ [PDO_ERR_DUPE_PDO] =
+ " err: Variable/Batt supply pdos cannot have same min/max voltage",
+ [PDO_ERR_PPS_APDO_NOT_SORTED] =
+ " err: Programmable power supply apdos should be in increasing order of their maximum voltage",
+ [PDO_ERR_DUPE_PPS_APDO] =
+ " err: Programmable power supply apdos cannot have same min/max voltage and max current",
+};
+
+static enum pdo_err tcpm_caps_err(struct udevice *dev, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ unsigned int i;
+
+ /* Should at least contain vSafe5v */
+ if (nr_pdo < 1)
+ return PDO_ERR_NO_VSAFE5V;
+
+ /* The vSafe5V Fixed Supply Object Shall always be the first object */
+ if (pdo_type(pdo[0]) != PDO_TYPE_FIXED ||
+ pdo_fixed_voltage(pdo[0]) != VSAFE5V)
+ return PDO_ERR_VSAFE5V_NOT_FIRST;
+
+ for (i = 1; i < nr_pdo; i++) {
+ if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) {
+ return PDO_ERR_PDO_TYPE_NOT_IN_ORDER;
+ } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) {
+ enum pd_pdo_type type = pdo_type(pdo[i]);
+
+ switch (type) {
+ /*
+ * The remaining Fixed Supply Objects, if
+ * present, shall be sent in voltage order;
+ * lowest to highest.
+ */
+ case PDO_TYPE_FIXED:
+ if (pdo_fixed_voltage(pdo[i]) <=
+ pdo_fixed_voltage(pdo[i - 1]))
+ return PDO_ERR_FIXED_NOT_SORTED;
+ break;
+ /*
+ * The Battery Supply Objects and Variable
+ * supply, if present shall be sent in Minimum
+ * Voltage order; lowest to highest.
+ */
+ case PDO_TYPE_VAR:
+ case PDO_TYPE_BATT:
+ if (pdo_min_voltage(pdo[i]) <
+ pdo_min_voltage(pdo[i - 1]))
+ return PDO_ERR_VARIABLE_BATT_NOT_SORTED;
+ else if ((pdo_min_voltage(pdo[i]) ==
+ pdo_min_voltage(pdo[i - 1])) &&
+ (pdo_max_voltage(pdo[i]) ==
+ pdo_max_voltage(pdo[i - 1])))
+ return PDO_ERR_DUPE_PDO;
+ break;
+ /*
+ * The Programmable Power Supply APDOs, if present,
+ * shall be sent in Maximum Voltage order;
+ * lowest to highest.
+ */
+ case PDO_TYPE_APDO:
+ if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
+ break;
+
+ if (pdo_pps_apdo_max_voltage(pdo[i]) <
+ pdo_pps_apdo_max_voltage(pdo[i - 1]))
+ return PDO_ERR_PPS_APDO_NOT_SORTED;
+ else if (pdo_pps_apdo_min_voltage(pdo[i]) ==
+ pdo_pps_apdo_min_voltage(pdo[i - 1]) &&
+ pdo_pps_apdo_max_voltage(pdo[i]) ==
+ pdo_pps_apdo_max_voltage(pdo[i - 1]) &&
+ pdo_pps_apdo_max_current(pdo[i]) ==
+ pdo_pps_apdo_max_current(pdo[i - 1]))
+ return PDO_ERR_DUPE_PPS_APDO;
+ break;
+ default:
+ dev_err(dev, "TCPM: Unknown pdo type\n");
+ }
+ }
+ }
+
+ return PDO_NO_ERR;
+}
+
+static int tcpm_validate_caps(struct udevice *dev, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ enum pdo_err err_index = tcpm_caps_err(dev, pdo, nr_pdo);
+
+ if (err_index != PDO_NO_ERR) {
+ dev_err(dev, "TCPM:%s\n", pdo_err_msg[err_index]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * PD (data, control) command handling functions
+ */
+static inline enum tcpm_state ready_state(struct tcpm_port *port)
+{
+ if (port->pwr_role == TYPEC_SOURCE)
+ return SRC_READY;
+ else
+ return SNK_READY;
+}
+
+static void tcpm_pd_data_request(struct udevice *dev,
+ const struct pd_message *msg)
+{
+ enum pd_data_msg_type type = pd_header_type_le(msg->header);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ unsigned int cnt = pd_header_cnt_le(msg->header);
+ unsigned int rev = pd_header_rev_le(msg->header);
+ unsigned int i;
+
+ switch (type) {
+ case PD_DATA_SOURCE_CAP:
+ for (i = 0; i < cnt; i++)
+ port->source_caps[i] = le32_to_cpu(msg->payload[i]);
+
+ port->nr_source_caps = cnt;
+
+ tcpm_validate_caps(dev, port->source_caps,
+ port->nr_source_caps);
+
+ /*
+ * Adjust revision in subsequent message headers, as required,
+ * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+ * support Rev 1.0 so just do nothing in that scenario.
+ */
+ if (rev == PD_REV10)
+ break;
+
+ if (rev < PD_MAX_REV)
+ port->negotiated_rev = rev;
+
+ if ((pdo_type(port->source_caps[0]) == PDO_TYPE_FIXED) &&
+ (port->source_caps[0] & PDO_FIXED_DUAL_ROLE) &&
+ (port->source_caps[0] & PDO_FIXED_DATA_SWAP)) {
+ /* Dual role power and data, eg: self-powered Type-C */
+ port->wait_dr_swap_message = true;
+ } else {
+ /* Non-Dual role power, eg: adapter */
+ port->wait_dr_swap_message = false;
+ }
+
+ /*
+ * This message may be received even if VBUS is not
+ * present. This is quite unexpected; see USB PD
+ * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2.
+ * However, at the same time, we must be ready to
+ * receive this message and respond to it 15ms after
+ * receiving PS_RDY during power swap operations, no matter
+ * if VBUS is available or not (USB PD specification,
+ * section 6.5.9.2).
+ * So we need to accept the message either way,
+ * but be prepared to keep waiting for VBUS after it was
+ * handled.
+ */
+ tcpm_set_state(dev, SNK_NEGOTIATE_CAPABILITIES, 0);
+ break;
+ case PD_DATA_REQUEST:
+ /*
+ * Adjust revision in subsequent message headers, as required,
+ * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+ * support Rev 1.0 so just reject in that scenario.
+ */
+ if (rev == PD_REV10) {
+ tcpm_queue_message(dev, PD_MSG_CTRL_REJECT);
+ break;
+ }
+
+ if (rev < PD_MAX_REV)
+ port->negotiated_rev = rev;
+
+ port->sink_request = le32_to_cpu(msg->payload[0]);
+
+ tcpm_set_state(dev, SRC_NEGOTIATE_CAPABILITIES, 0);
+ break;
+ case PD_DATA_SINK_CAP:
+ /* We don't do anything with this at the moment... */
+ for (i = 0; i < cnt; i++)
+ port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
+
+ port->nr_sink_caps = cnt;
+ break;
+ default:
+ break;
+ }
+}
+
+static void tcpm_pd_ctrl_request(struct udevice *dev,
+ const struct pd_message *msg)
+{
+ enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum tcpm_state next_state;
+
+ switch (type) {
+ case PD_CTRL_GOOD_CRC:
+ case PD_CTRL_PING:
+ break;
+ case PD_CTRL_GET_SOURCE_CAP:
+ switch (port->state) {
+ case SRC_READY:
+ case SNK_READY:
+ tcpm_queue_message(dev, PD_MSG_DATA_SOURCE_CAP);
+ break;
+ default:
+ tcpm_queue_message(dev, PD_MSG_CTRL_REJECT);
+ break;
+ }
+ break;
+ case PD_CTRL_GET_SINK_CAP:
+ switch (port->state) {
+ case SRC_READY:
+ case SNK_READY:
+ tcpm_queue_message(dev, PD_MSG_DATA_SINK_CAP);
+ break;
+ default:
+ tcpm_queue_message(dev, PD_MSG_CTRL_REJECT);
+ break;
+ }
+ break;
+ case PD_CTRL_GOTO_MIN:
+ break;
+ case PD_CTRL_PS_RDY:
+ switch (port->state) {
+ case SNK_TRANSITION_SINK:
+ if (port->vbus_present) {
+ tcpm_set_current_limit(dev,
+ port->req_current_limit,
+ port->req_supply_voltage);
+ port->explicit_contract = true;
+ tcpm_set_state(dev, SNK_READY, 0);
+ } else {
+ /*
+ * Seen after power swap. Keep waiting for VBUS
+ * in a transitional state.
+ */
+ tcpm_set_state(dev,
+ SNK_TRANSITION_SINK_VBUS, 0);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case PD_CTRL_REJECT:
+ case PD_CTRL_WAIT:
+ case PD_CTRL_NOT_SUPP:
+ switch (port->state) {
+ case SNK_NEGOTIATE_CAPABILITIES:
+ /* USB PD specification, Figure 8-43 */
+ if (port->explicit_contract)
+ next_state = SNK_READY;
+ else
+ next_state = SNK_WAIT_CAPABILITIES;
+
+ tcpm_set_state(dev, next_state, 0);
+ break;
+ default:
+ break;
+ }
+ break;
+ case PD_CTRL_ACCEPT:
+ switch (port->state) {
+ case SNK_NEGOTIATE_CAPABILITIES:
+ tcpm_set_state(dev, SNK_TRANSITION_SINK, 0);
+ break;
+ case SOFT_RESET_SEND:
+ port->message_id = 0;
+ port->rx_msgid = -1;
+ if (port->pwr_role == TYPEC_SOURCE)
+ next_state = SRC_SEND_CAPABILITIES;
+ else
+ next_state = SNK_WAIT_CAPABILITIES;
+ tcpm_set_state(dev, next_state, 0);
+ break;
+ default:
+ break;
+ }
+ break;
+ case PD_CTRL_SOFT_RESET:
+ tcpm_set_state(dev, SOFT_RESET, 0);
+ break;
+ case PD_CTRL_DR_SWAP:
+ if (port->port_type != TYPEC_PORT_DRP) {
+ tcpm_queue_message(dev, PD_MSG_CTRL_REJECT);
+ break;
+ }
+ /*
+ * 6.3.9: If an alternate mode is active, a request to swap
+ * alternate modes shall trigger a port reset.
+ */
+ switch (port->state) {
+ case SRC_READY:
+ case SNK_READY:
+ tcpm_set_state(dev, DR_SWAP_ACCEPT, 0);
+ break;
+ default:
+ tcpm_queue_message(dev, PD_MSG_CTRL_WAIT);
+ break;
+ }
+ break;
+ case PD_CTRL_PR_SWAP:
+ case PD_CTRL_VCONN_SWAP:
+ case PD_CTRL_GET_SOURCE_CAP_EXT:
+ case PD_CTRL_GET_STATUS:
+ case PD_CTRL_FR_SWAP:
+ case PD_CTRL_GET_PPS_STATUS:
+ case PD_CTRL_GET_COUNTRY_CODES:
+ /* Currently not supported */
+ dev_err(dev, "TCPM: Currently not supported type %#x\n", type);
+ tcpm_queue_message(dev, PD_MSG_CTRL_NOT_SUPP);
+ break;
+ default:
+ dev_err(dev, "TCPM: Unrecognized ctrl message type %#x\n", type);
+ break;
+ }
+}
+
+static void tcpm_pd_rx_handler(struct udevice *dev,
+ const struct pd_message *msg)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ unsigned int cnt = pd_header_cnt_le(msg->header);
+ bool remote_is_host, local_is_host;
+
+ dev_dbg(dev, "TCPM: PD RX, header: %#x [%d]\n",
+ le16_to_cpu(msg->header), port->attached);
+
+ if (port->attached) {
+ enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
+ unsigned int msgid = pd_header_msgid_le(msg->header);
+
+ /*
+ * USB PD standard, 6.6.1.2:
+ * "... if MessageID value in a received Message is the
+ * same as the stored value, the receiver shall return a
+ * GoodCRC Message with that MessageID value and drop
+ * the Message (this is a retry of an already received
+ * Message). Note: this shall not apply to the Soft_Reset
+ * Message which always has a MessageID value of zero."
+ */
+ if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET)
+ return;
+ port->rx_msgid = msgid;
+
+ /*
+ * If both ends believe to be DFP/host, we have a data role
+ * mismatch.
+ */
+ remote_is_host = !!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE);
+ local_is_host = port->data_role == TYPEC_HOST;
+ if (remote_is_host == local_is_host) {
+ dev_err(dev, "TCPM: data role mismatch, initiating error recovery\n");
+ tcpm_set_state(dev, ERROR_RECOVERY, 0);
+ } else {
+ if (cnt)
+ tcpm_pd_data_request(dev, msg);
+ else
+ tcpm_pd_ctrl_request(dev, msg);
+ }
+ }
+}
+
+void tcpm_pd_receive(struct udevice *dev, const struct pd_message *msg)
+{
+ tcpm_reset_event_cnt(dev);
+ tcpm_pd_rx_handler(dev, msg);
+}
+
+static int tcpm_pd_send_control(struct udevice *dev,
+ enum pd_ctrl_msg_type type)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ struct pd_message msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.header = PD_HEADER_LE(type, port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id, 0);
+
+ return tcpm_pd_transmit(dev, TCPC_TX_SOP, &msg);
+}
+
+/*
+ * Send queued message without affecting state.
+ * Return true if state machine should go back to sleep,
+ * false otherwise.
+ */
+static bool tcpm_send_queued_message(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum pd_msg_request queued_message;
+ int max_messages = 100;
+
+ do {
+ queued_message = port->queued_message;
+ port->queued_message = PD_MSG_NONE;
+ max_messages--;
+
+ switch (queued_message) {
+ case PD_MSG_CTRL_WAIT:
+ tcpm_pd_send_control(dev, PD_CTRL_WAIT);
+ break;
+ case PD_MSG_CTRL_REJECT:
+ tcpm_pd_send_control(dev, PD_CTRL_REJECT);
+ break;
+ case PD_MSG_CTRL_NOT_SUPP:
+ tcpm_pd_send_control(dev, PD_CTRL_NOT_SUPP);
+ break;
+ case PD_MSG_DATA_SINK_CAP:
+ tcpm_pd_send_sink_caps(dev);
+ break;
+ case PD_MSG_DATA_SOURCE_CAP:
+ tcpm_pd_send_source_caps(dev);
+ break;
+ default:
+ break;
+ }
+ } while (max_messages > 0 && port->queued_message != PD_MSG_NONE);
+
+ if (!max_messages)
+ dev_err(dev, "Aborted sending of too many queued messages\n");
+
+ return false;
+}
+
+static int tcpm_pd_check_request(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ u32 pdo, rdo = port->sink_request;
+ unsigned int max, op, pdo_max, index;
+ enum pd_pdo_type type;
+
+ index = rdo_index(rdo);
+ if (!index || index > port->nr_src_pdo)
+ return -EINVAL;
+
+ pdo = port->src_pdo[index - 1];
+ type = pdo_type(pdo);
+ switch (type) {
+ case PDO_TYPE_FIXED:
+ case PDO_TYPE_VAR:
+ max = rdo_max_current(rdo);
+ op = rdo_op_current(rdo);
+ pdo_max = pdo_max_current(pdo);
+
+ if (op > pdo_max)
+ return -EINVAL;
+ if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+ return -EINVAL;
+
+ if (type == PDO_TYPE_FIXED)
+ dev_dbg(dev, "TCPM: Requested %u mV, %u mA for %u / %u mA\n",
+ pdo_fixed_voltage(pdo), pdo_max, op, max);
+ else
+ dev_dbg(dev, "TCPM: Requested %u -> %u mV, %u mA for %u / %u mA\n",
+ pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+ pdo_max, op, max);
+ break;
+ case PDO_TYPE_BATT:
+ max = rdo_max_power(rdo);
+ op = rdo_op_power(rdo);
+ pdo_max = pdo_max_power(pdo);
+
+ if (op > pdo_max)
+ return -EINVAL;
+ if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+ return -EINVAL;
+ dev_info(dev, "TCPM: Requested %u -> %u mV, %u mW for %u / %u mW\n",
+ pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+ pdo_max, op, max);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define min_power(x, y) min(pdo_max_power(x), pdo_max_power(y))
+#define min_current(x, y) min(pdo_max_current(x), pdo_max_current(y))
+
+static int tcpm_pd_select_pdo(struct udevice *dev, int *sink_pdo,
+ int *src_pdo)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ unsigned int i, j, max_src_mv = 0, min_src_mv = 0, max_mw = 0,
+ max_mv = 0, src_mw = 0, src_ma = 0, max_snk_mv = 0,
+ min_snk_mv = 0;
+ int ret = -EINVAL;
+
+ /*
+ * Select the source PDO providing the most power which has a
+ * matchig sink cap.
+ */
+ for (i = 0; i < port->nr_source_caps; i++) {
+ u32 pdo = port->source_caps[i];
+ enum pd_pdo_type type = pdo_type(pdo);
+
+ switch (type) {
+ case PDO_TYPE_FIXED:
+ max_src_mv = pdo_fixed_voltage(pdo);
+ min_src_mv = max_src_mv;
+ break;
+ case PDO_TYPE_BATT:
+ case PDO_TYPE_VAR:
+ max_src_mv = pdo_max_voltage(pdo);
+ min_src_mv = pdo_min_voltage(pdo);
+ break;
+ case PDO_TYPE_APDO:
+ continue;
+ default:
+ dev_err(dev, "TCPM: Invalid source PDO type, ignoring\n");
+ continue;
+ }
+
+ switch (type) {
+ case PDO_TYPE_FIXED:
+ case PDO_TYPE_VAR:
+ src_ma = pdo_max_current(pdo);
+ src_mw = src_ma * min_src_mv / 1000;
+ break;
+ case PDO_TYPE_BATT:
+ src_mw = pdo_max_power(pdo);
+ break;
+ case PDO_TYPE_APDO:
+ continue;
+ default:
+ dev_err(dev, "TCPM: Invalid source PDO type, ignoring\n");
+ continue;
+ }
+
+ for (j = 0; j < port->nr_snk_pdo; j++) {
+ pdo = port->snk_pdo[j];
+
+ switch (pdo_type(pdo)) {
+ case PDO_TYPE_FIXED:
+ max_snk_mv = pdo_fixed_voltage(pdo);
+ min_snk_mv = max_snk_mv;
+ break;
+ case PDO_TYPE_BATT:
+ case PDO_TYPE_VAR:
+ max_snk_mv = pdo_max_voltage(pdo);
+ min_snk_mv = pdo_min_voltage(pdo);
+ break;
+ case PDO_TYPE_APDO:
+ continue;
+ default:
+ dev_err(dev, "TCPM: Invalid sink PDO type, ignoring\n");
+ continue;
+ }
+
+ if (max_src_mv <= max_snk_mv && min_src_mv >= min_snk_mv) {
+ /* Prefer higher voltages if available */
+ if ((src_mw == max_mw && min_src_mv > max_mv) ||
+ src_mw > max_mw) {
+ *src_pdo = i;
+ *sink_pdo = j;
+ max_mw = src_mw;
+ max_mv = min_src_mv;
+ ret = 0;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int tcpm_pd_build_request(struct udevice *dev, u32 *rdo)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ unsigned int mv, ma, mw, flags;
+ unsigned int max_ma, max_mw;
+ enum pd_pdo_type type;
+ u32 pdo, matching_snk_pdo;
+ int src_pdo_index = 0;
+ int snk_pdo_index = 0;
+ int ret;
+
+ ret = tcpm_pd_select_pdo(dev, &snk_pdo_index, &src_pdo_index);
+ if (ret < 0)
+ return ret;
+
+ pdo = port->source_caps[src_pdo_index];
+ matching_snk_pdo = port->snk_pdo[snk_pdo_index];
+ type = pdo_type(pdo);
+
+ switch (type) {
+ case PDO_TYPE_FIXED:
+ mv = pdo_fixed_voltage(pdo);
+ break;
+ case PDO_TYPE_BATT:
+ case PDO_TYPE_VAR:
+ mv = pdo_min_voltage(pdo);
+ break;
+ default:
+ dev_err(dev, "TCPM: Invalid PDO selected!\n");
+ return -EINVAL;
+ }
+
+ /* Select maximum available current within the sink pdo's limit */
+ if (type == PDO_TYPE_BATT) {
+ mw = min_power(pdo, matching_snk_pdo);
+ ma = 1000 * mw / mv;
+ } else {
+ ma = min_current(pdo, matching_snk_pdo);
+ mw = ma * mv / 1000;
+ }
+
+ flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+ /* Set mismatch bit if offered power is less than operating power */
+ max_ma = ma;
+ max_mw = mw;
+ if (mw < port->operating_snk_mw) {
+ flags |= RDO_CAP_MISMATCH;
+ if (type == PDO_TYPE_BATT &&
+ (pdo_max_power(matching_snk_pdo) > pdo_max_power(pdo)))
+ max_mw = pdo_max_power(matching_snk_pdo);
+ else if (pdo_max_current(matching_snk_pdo) >
+ pdo_max_current(pdo))
+ max_ma = pdo_max_current(matching_snk_pdo);
+ }
+
+ dev_dbg(dev, "TCPM: cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d\n",
+ port->cc_req, port->cc1, port->cc2, port->vbus_source,
+ port->vconn_role == TYPEC_SOURCE ? "source" : "sink",
+ port->polarity);
+
+ if (type == PDO_TYPE_BATT) {
+ *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags);
+
+ dev_info(dev, "TCPM: requesting PDO %d: %u mV, %u mW%s\n",
+ src_pdo_index, mv, mw,
+ flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+ } else {
+ *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags);
+
+ dev_info(dev, "TCPM: requesting PDO %d: %u mV, %u mA%s\n",
+ src_pdo_index, mv, ma,
+ flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+ }
+
+ port->req_current_limit = ma;
+ port->req_supply_voltage = mv;
+
+ return 0;
+}
+
+static int tcpm_pd_send_request(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ struct pd_message msg;
+ int ret;
+ u32 rdo;
+
+ ret = tcpm_pd_build_request(dev, &rdo);
+ if (ret < 0)
+ return ret;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
+ port->pwr_role,
+ port->data_role,
+ port->negotiated_rev,
+ port->message_id, 1);
+ msg.payload[0] = cpu_to_le32(rdo);
+
+ return tcpm_pd_transmit(dev, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_set_vbus(struct udevice *dev, bool enable)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ int ret;
+
+ if (enable && port->vbus_charge)
+ return -EINVAL;
+
+ dev_dbg(dev, "TCPM: set vbus = %d charge = %d\n",
+ enable, port->vbus_charge);
+
+ ret = drvops->set_vbus(dev, enable, port->vbus_charge);
+ if (ret < 0)
+ return ret;
+
+ port->vbus_source = enable;
+ return 0;
+}
+
+static int tcpm_set_charge(struct udevice *dev, bool charge)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ if (charge && port->vbus_source)
+ return -EINVAL;
+
+ if (charge != port->vbus_charge) {
+ dev_dbg(dev, "TCPM: set vbus = %d charge = %d\n",
+ port->vbus_source, charge);
+ ret = drvops->set_vbus(dev, port->vbus_source,
+ charge);
+ if (ret < 0)
+ return ret;
+ }
+ port->vbus_charge = charge;
+ return 0;
+}
+
+static bool tcpm_start_toggling(struct udevice *dev, enum typec_cc_status cc)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ if (!drvops->start_toggling)
+ return false;
+
+ dev_dbg(dev, "TCPM: Start toggling\n");
+ ret = drvops->start_toggling(dev, port->port_type, cc);
+ return ret == 0;
+}
+
+static int tcpm_init_vbus(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ ret = drvops->set_vbus(dev, false, false);
+ port->vbus_source = false;
+ port->vbus_charge = false;
+ return ret;
+}
+
+static int tcpm_init_vconn(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ ret = drvops->set_vconn(dev, false);
+ port->vconn_role = TYPEC_SINK;
+ return ret;
+}
+
+static inline void tcpm_typec_connect(struct tcpm_port *port)
+{
+ if (!port->connected)
+ port->connected = true;
+}
+
+static int tcpm_src_attach(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum typec_cc_polarity polarity =
+ port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2
+ : TYPEC_POLARITY_CC1;
+ int ret;
+
+ if (port->attached)
+ return 0;
+
+ ret = tcpm_set_polarity(dev, polarity);
+ if (ret < 0)
+ return ret;
+
+ ret = tcpm_set_roles(dev, true, TYPEC_SOURCE, TYPEC_HOST);
+ if (ret < 0)
+ return ret;
+
+ ret = drvops->set_pd_rx(dev, true);
+ if (ret < 0)
+ goto out_disable_mux;
+
+ /*
+ * USB Type-C specification, version 1.2,
+ * chapter 4.5.2.2.8.1 (Attached.SRC Requirements)
+ * Enable VCONN only if the non-RD port is set to RA.
+ */
+ if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) ||
+ (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) {
+ ret = tcpm_set_vconn(dev, true);
+ if (ret < 0)
+ goto out_disable_pd;
+ }
+
+ ret = tcpm_set_vbus(dev, true);
+ if (ret < 0)
+ goto out_disable_vconn;
+
+ port->pd_capable = false;
+
+ port->partner = NULL;
+
+ port->attached = true;
+
+ return 0;
+
+out_disable_vconn:
+ tcpm_set_vconn(dev, false);
+out_disable_pd:
+ drvops->set_pd_rx(dev, false);
+out_disable_mux:
+ dev_err(dev, "TCPM: CC connected in %s as DFP\n",
+ polarity ? "CC2" : "CC1");
+ return 0;
+}
+
+static inline void tcpm_typec_disconnect(struct tcpm_port *port)
+{
+ if (port->connected) {
+ port->partner = NULL;
+ port->connected = false;
+ }
+}
+
+static void tcpm_reset_port(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ tcpm_timer_uninit(dev);
+ tcpm_typec_disconnect(port);
+ tcpm_reset_event_cnt(dev);
+ port->wait_dr_swap_message = false;
+ port->attached = false;
+ port->pd_capable = false;
+
+ /*
+ * First Rx ID should be 0; set this to a sentinel of -1 so that
+ * we can check tcpm_pd_rx_handler() if we had seen it before.
+ */
+ port->rx_msgid = -1;
+
+ drvops->set_pd_rx(dev, false);
+ tcpm_init_vbus(dev); /* also disables charging */
+ tcpm_init_vconn(dev);
+ tcpm_set_current_limit(dev, 0, 0);
+ tcpm_set_polarity(dev, TYPEC_POLARITY_CC1);
+ tcpm_set_attached_state(dev, false);
+ port->nr_sink_caps = 0;
+}
+
+static void tcpm_detach(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ if (tcpm_port_is_disconnected(port))
+ port->hard_reset_count = 0;
+
+ if (!port->attached)
+ return;
+
+ tcpm_reset_port(dev);
+}
+
+static void tcpm_src_detach(struct udevice *dev)
+{
+ tcpm_detach(dev);
+}
+
+static int tcpm_snk_attach(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ if (port->attached)
+ return 0;
+
+ ret = tcpm_set_polarity(dev, port->cc2 != TYPEC_CC_OPEN ?
+ TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
+ if (ret < 0)
+ return ret;
+
+ ret = tcpm_set_roles(dev, true, TYPEC_SINK, TYPEC_DEVICE);
+ if (ret < 0)
+ return ret;
+
+ port->pd_capable = false;
+
+ port->partner = NULL;
+
+ port->attached = true;
+ dev_info(dev, "TCPM: CC connected in %s as UFP\n",
+ port->cc1 != TYPEC_CC_OPEN ? "CC1" : "CC2");
+
+ return 0;
+}
+
+static void tcpm_snk_detach(struct udevice *dev)
+{
+ tcpm_detach(dev);
+}
+
+static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
+{
+ if (port->hard_reset_count < PD_N_HARD_RESET_COUNT)
+ return HARD_RESET_SEND;
+ if (port->pd_capable)
+ return ERROR_RECOVERY;
+ if (port->pwr_role == TYPEC_SOURCE)
+ return SRC_UNATTACHED;
+ if (port->state == SNK_WAIT_CAPABILITIES)
+ return SNK_READY;
+ return SNK_UNATTACHED;
+}
+
+static inline enum tcpm_state unattached_state(struct tcpm_port *port)
+{
+ if (port->port_type == TYPEC_PORT_DRP) {
+ if (port->pwr_role == TYPEC_SOURCE)
+ return SRC_UNATTACHED;
+ else
+ return SNK_UNATTACHED;
+ } else if (port->port_type == TYPEC_PORT_SRC) {
+ return SRC_UNATTACHED;
+ }
+
+ return SNK_UNATTACHED;
+}
+
+static void run_state_machine(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int ret;
+
+ port->enter_state = port->state;
+ switch (port->state) {
+ case TOGGLING:
+ break;
+ /* SRC states */
+ case SRC_UNATTACHED:
+ tcpm_src_detach(dev);
+ if (tcpm_start_toggling(dev, tcpm_rp_cc(port))) {
+ tcpm_set_state(dev, TOGGLING, 0);
+ break;
+ }
+ tcpm_set_cc(dev, tcpm_rp_cc(port));
+ if (port->port_type == TYPEC_PORT_DRP)
+ tcpm_set_state(dev, SNK_UNATTACHED, PD_T_DRP_SNK);
+ break;
+ case SRC_ATTACH_WAIT:
+ if (tcpm_port_is_source(port))
+ tcpm_set_state(dev, SRC_ATTACHED, PD_T_CC_DEBOUNCE);
+ break;
+
+ case SRC_ATTACHED:
+ ret = tcpm_src_attach(dev);
+ /*
+ * Currently, vbus control is not implemented,
+ * and the SRC detection process cannot be fully implemented.
+ */
+ tcpm_set_state(dev, SRC_READY, 0);
+ break;
+ case SRC_STARTUP:
+ port->caps_count = 0;
+ port->negotiated_rev = PD_MAX_REV;
+ port->message_id = 0;
+ port->rx_msgid = -1;
+ port->explicit_contract = false;
+ tcpm_set_state(dev, SRC_SEND_CAPABILITIES, 0);
+ break;
+ case SRC_SEND_CAPABILITIES:
+ port->caps_count++;
+ if (port->caps_count > PD_N_CAPS_COUNT) {
+ tcpm_set_state(dev, SRC_READY, 0);
+ break;
+ }
+ ret = tcpm_pd_send_source_caps(dev);
+ if (ret < 0) {
+ tcpm_set_state(dev, SRC_SEND_CAPABILITIES,
+ PD_T_SEND_SOURCE_CAP);
+ } else {
+ /*
+ * Per standard, we should clear the reset counter here.
+ * However, that can result in state machine hang-ups.
+ * Reset it only in READY state to improve stability.
+ */
+ /* port->hard_reset_count = 0; */
+ port->caps_count = 0;
+ port->pd_capable = true;
+ tcpm_set_state_cond(dev, SRC_SEND_CAPABILITIES_TIMEOUT,
+ PD_T_SEND_SOURCE_CAP);
+ }
+ break;
+ case SRC_SEND_CAPABILITIES_TIMEOUT:
+ /*
+ * Error recovery for a PD_DATA_SOURCE_CAP reply timeout.
+ *
+ * PD 2.0 sinks are supposed to accept src-capabilities with a
+ * 3.0 header and simply ignore any src PDOs which the sink does
+ * not understand such as PPS but some 2.0 sinks instead ignore
+ * the entire PD_DATA_SOURCE_CAP message, causing contract
+ * negotiation to fail.
+ *
+ * After PD_N_HARD_RESET_COUNT hard-reset attempts, we try
+ * sending src-capabilities with a lower PD revision to
+ * make these broken sinks work.
+ */
+ if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) {
+ tcpm_set_state(dev, HARD_RESET_SEND, 0);
+ } else if (port->negotiated_rev > PD_REV20) {
+ port->negotiated_rev--;
+ port->hard_reset_count = 0;
+ tcpm_set_state(dev, SRC_SEND_CAPABILITIES, 0);
+ } else {
+ tcpm_set_state(dev, hard_reset_state(port), 0);
+ }
+ break;
+ case SRC_NEGOTIATE_CAPABILITIES:
+ ret = tcpm_pd_check_request(dev);
+ if (ret < 0) {
+ tcpm_pd_send_control(dev, PD_CTRL_REJECT);
+ if (!port->explicit_contract) {
+ tcpm_set_state(dev,
+ SRC_WAIT_NEW_CAPABILITIES, 0);
+ } else {
+ tcpm_set_state(dev, SRC_READY, 0);
+ }
+ } else {
+ tcpm_pd_send_control(dev, PD_CTRL_ACCEPT);
+ tcpm_set_state(dev, SRC_TRANSITION_SUPPLY,
+ PD_T_SRC_TRANSITION);
+ }
+ break;
+ case SRC_TRANSITION_SUPPLY:
+ /* XXX: regulator_set_voltage(vbus, ...) */
+ tcpm_pd_send_control(dev, PD_CTRL_PS_RDY);
+ port->explicit_contract = true;
+ tcpm_set_state_cond(dev, SRC_READY, 0);
+ break;
+ case SRC_READY:
+ port->hard_reset_count = 0;
+
+ tcpm_typec_connect(port);
+ break;
+ case SRC_WAIT_NEW_CAPABILITIES:
+ /* Nothing to do... */
+ break;
+
+ /* SNK states */
+ case SNK_UNATTACHED:
+ tcpm_snk_detach(dev);
+ if (tcpm_start_toggling(dev, TYPEC_CC_RD)) {
+ tcpm_set_state(dev, TOGGLING, 0);
+ break;
+ }
+ tcpm_set_cc(dev, TYPEC_CC_RD);
+ if (port->port_type == TYPEC_PORT_DRP)
+ tcpm_set_state(dev, SRC_UNATTACHED, PD_T_DRP_SRC);
+ break;
+ case SNK_ATTACH_WAIT:
+ if ((port->cc1 == TYPEC_CC_OPEN &&
+ port->cc2 != TYPEC_CC_OPEN) ||
+ (port->cc1 != TYPEC_CC_OPEN &&
+ port->cc2 == TYPEC_CC_OPEN))
+ tcpm_set_state(dev, SNK_DEBOUNCED,
+ PD_T_CC_DEBOUNCE);
+ else if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, SNK_UNATTACHED,
+ PD_T_CC_DEBOUNCE);
+ break;
+ case SNK_DEBOUNCED:
+ if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, SNK_UNATTACHED, PD_T_PD_DEBOUNCE);
+ else if (port->vbus_present)
+ tcpm_set_state(dev, SNK_ATTACHED, 0);
+ else
+ /* Wait for VBUS, but not forever */
+ tcpm_set_state(dev, PORT_RESET, PD_T_PS_SOURCE_ON);
+ break;
+ case SNK_ATTACHED:
+ ret = tcpm_snk_attach(dev);
+ if (ret < 0)
+ tcpm_set_state(dev, SNK_UNATTACHED, 0);
+ else
+ tcpm_set_state(dev, SNK_STARTUP, 0);
+ break;
+ case SNK_STARTUP:
+ port->negotiated_rev = PD_MAX_REV;
+ port->message_id = 0;
+ port->rx_msgid = -1;
+ port->explicit_contract = false;
+ tcpm_set_state(dev, SNK_DISCOVERY, 0);
+ break;
+ case SNK_DISCOVERY:
+ if (port->vbus_present) {
+ tcpm_set_current_limit(dev,
+ tcpm_get_current_limit(port),
+ 5000);
+ tcpm_set_charge(dev, true);
+ tcpm_set_state(dev, SNK_WAIT_CAPABILITIES, 0);
+ break;
+ }
+ /*
+ * For DRP, timeouts differ. Also, handling is supposed to be
+ * different and much more complex (dead battery detection;
+ * see USB power delivery specification, section 8.3.3.6.1.5.1).
+ */
+ tcpm_set_state(dev, hard_reset_state(port),
+ port->port_type == TYPEC_PORT_DRP ?
+ PD_T_DB_DETECT : PD_T_NO_RESPONSE);
+ break;
+ case SNK_DISCOVERY_DEBOUNCE:
+ tcpm_set_state(dev, SNK_DISCOVERY_DEBOUNCE_DONE,
+ PD_T_CC_DEBOUNCE);
+ break;
+ case SNK_DISCOVERY_DEBOUNCE_DONE:
+ tcpm_set_state(dev, unattached_state(port), 0);
+ break;
+ case SNK_WAIT_CAPABILITIES:
+ ret = drvops->set_pd_rx(dev, true);
+ if (ret < 0) {
+ tcpm_set_state(dev, SNK_READY, 0);
+ break;
+ }
+ /*
+ * If VBUS has never been low, and we time out waiting
+ * for source cap, try a soft reset first, in case we
+ * were already in a stable contract before this boot.
+ * Do this only once.
+ */
+ if (port->vbus_never_low) {
+ port->vbus_never_low = false;
+ tcpm_set_state(dev, SOFT_RESET_SEND,
+ PD_T_SINK_WAIT_CAP);
+ } else {
+ tcpm_set_state(dev, hard_reset_state(port),
+ PD_T_SINK_WAIT_CAP);
+ }
+ break;
+ case SNK_NEGOTIATE_CAPABILITIES:
+ port->pd_capable = true;
+ port->hard_reset_count = 0;
+ ret = tcpm_pd_send_request(dev);
+ if (ret < 0) {
+ /* Let the Source send capabilities again. */
+ tcpm_set_state(dev, SNK_WAIT_CAPABILITIES, 0);
+ } else {
+ tcpm_set_state_cond(dev, hard_reset_state(port),
+ PD_T_SENDER_RESPONSE);
+ }
+ break;
+ case SNK_TRANSITION_SINK:
+ case SNK_TRANSITION_SINK_VBUS:
+ tcpm_set_state(dev, hard_reset_state(port),
+ PD_T_PS_TRANSITION);
+ break;
+ case SNK_READY:
+ port->update_sink_caps = false;
+ tcpm_typec_connect(port);
+ /*
+ * Here poll_event_cnt is cleared, waiting for self-powered Type-C devices
+ * to send DR_swap Messge until 1s (TCPM_POLL_EVENT_TIME_OUT * 500us)timeout
+ */
+ if (port->wait_dr_swap_message)
+ tcpm_reset_event_cnt(dev);
+
+ break;
+
+ /* Hard_Reset states */
+ case HARD_RESET_SEND:
+ tcpm_pd_transmit(dev, TCPC_TX_HARD_RESET, NULL);
+ tcpm_set_state(dev, HARD_RESET_START, 0);
+ port->wait_dr_swap_message = false;
+ break;
+ case HARD_RESET_START:
+ port->hard_reset_count++;
+ drvops->set_pd_rx(dev, false);
+ port->nr_sink_caps = 0;
+ if (port->pwr_role == TYPEC_SOURCE)
+ tcpm_set_state(dev, SRC_HARD_RESET_VBUS_OFF,
+ PD_T_PS_HARD_RESET);
+ else
+ tcpm_set_state(dev, SNK_HARD_RESET_SINK_OFF, 0);
+ break;
+ case SRC_HARD_RESET_VBUS_OFF:
+ tcpm_set_vconn(dev, true);
+ tcpm_set_vbus(dev, false);
+ tcpm_set_roles(dev, port->self_powered, TYPEC_SOURCE,
+ TYPEC_HOST);
+ tcpm_set_state(dev, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+ break;
+ case SRC_HARD_RESET_VBUS_ON:
+ tcpm_set_vconn(dev, true);
+ tcpm_set_vbus(dev, true);
+ drvops->set_pd_rx(dev, true);
+ tcpm_set_attached_state(dev, true);
+ tcpm_set_state(dev, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
+ break;
+ case SNK_HARD_RESET_SINK_OFF:
+ tcpm_set_vconn(dev, false);
+ if (port->pd_capable)
+ tcpm_set_charge(dev, false);
+ tcpm_set_roles(dev, port->self_powered, TYPEC_SINK,
+ TYPEC_DEVICE);
+ /*
+ * VBUS may or may not toggle, depending on the adapter.
+ * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON
+ * directly after timeout.
+ */
+ tcpm_set_state(dev, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V);
+ break;
+ case SNK_HARD_RESET_WAIT_VBUS:
+ /* Assume we're disconnected if VBUS doesn't come back. */
+ tcpm_set_state(dev, SNK_UNATTACHED,
+ PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON);
+ break;
+ case SNK_HARD_RESET_SINK_ON:
+ /* Note: There is no guarantee that VBUS is on in this state */
+ /*
+ * XXX:
+ * The specification suggests that dual mode ports in sink
+ * mode should transition to state PE_SRC_Transition_to_default.
+ * See USB power delivery specification chapter 8.3.3.6.1.3.
+ * This would mean to
+ * - turn off VCONN, reset power supply
+ * - request hardware reset
+ * - turn on VCONN
+ * - Transition to state PE_Src_Startup
+ * SNK only ports shall transition to state Snk_Startup
+ * (see chapter 8.3.3.3.8).
+ * Similar, dual-mode ports in source mode should transition
+ * to PE_SNK_Transition_to_default.
+ */
+ if (port->pd_capable) {
+ tcpm_set_current_limit(dev,
+ tcpm_get_current_limit(port),
+ 5000);
+ tcpm_set_charge(dev, true);
+ }
+ tcpm_set_attached_state(dev, true);
+ tcpm_set_state(dev, SNK_STARTUP, 0);
+ break;
+
+ /* Soft_Reset states */
+ case SOFT_RESET:
+ port->message_id = 0;
+ port->rx_msgid = -1;
+ tcpm_pd_send_control(dev, PD_CTRL_ACCEPT);
+ if (port->pwr_role == TYPEC_SOURCE)
+ tcpm_set_state(dev, SRC_SEND_CAPABILITIES, 0);
+ else
+ tcpm_set_state(dev, SNK_WAIT_CAPABILITIES, 0);
+ break;
+ case SOFT_RESET_SEND:
+ port->message_id = 0;
+ port->rx_msgid = -1;
+ if (tcpm_pd_send_control(dev, PD_CTRL_SOFT_RESET))
+ tcpm_set_state_cond(dev, hard_reset_state(port), 0);
+ else
+ tcpm_set_state_cond(dev, hard_reset_state(port),
+ PD_T_SENDER_RESPONSE);
+ break;
+
+ /* DR_Swap states */
+ case DR_SWAP_ACCEPT:
+ tcpm_pd_send_control(dev, PD_CTRL_ACCEPT);
+ tcpm_set_state_cond(dev, DR_SWAP_CHANGE_DR, 0);
+ break;
+ case DR_SWAP_CHANGE_DR:
+ if (port->data_role == TYPEC_HOST) {
+ tcpm_set_roles(dev, true, port->pwr_role,
+ TYPEC_DEVICE);
+ } else {
+ tcpm_set_roles(dev, true, port->pwr_role,
+ TYPEC_HOST);
+ }
+ /* DR_swap process complete, wait_dr_swap_message is cleared */
+ port->wait_dr_swap_message = false;
+ tcpm_set_state(dev, ready_state(port), 0);
+ break;
+ case ERROR_RECOVERY:
+ tcpm_set_state(dev, PORT_RESET, 0);
+ break;
+ case PORT_RESET:
+ tcpm_reset_port(dev);
+ if (port->self_powered)
+ tcpm_set_cc(dev, TYPEC_CC_OPEN);
+ else
+ tcpm_set_cc(dev, tcpm_default_state(port) == SNK_UNATTACHED ?
+ TYPEC_CC_RD : tcpm_rp_cc(port));
+ tcpm_set_state(dev, PORT_RESET_WAIT_OFF,
+ PD_T_ERROR_RECOVERY);
+ break;
+ case PORT_RESET_WAIT_OFF:
+ tcpm_set_state(dev,
+ tcpm_default_state(port),
+ port->vbus_present ? PD_T_PS_SOURCE_OFF : 0);
+ break;
+ default:
+ dev_err(dev, "TCPM: Unexpected port state %d\n", port->state);
+ break;
+ }
+}
+
+static void tcpm_state_machine(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum tcpm_state prev_state;
+
+ mutex_lock(&port->lock);
+ port->state_machine_running = true;
+
+ if (port->queued_message && tcpm_send_queued_message(dev))
+ goto done;
+
+ /* If we were queued due to a delayed state change, update it now */
+ if (port->delayed_state) {
+ dev_dbg(dev, "TCPM: state change %s -> %s [delayed %ld ms]\n",
+ tcpm_states[port->state],
+ tcpm_states[port->delayed_state], port->delay_ms);
+ port->prev_state = port->state;
+ port->state = port->delayed_state;
+ port->delayed_state = INVALID_STATE;
+ }
+
+ /*
+ * Continue running as long as we have (non-delayed) state changes
+ * to make.
+ */
+ do {
+ prev_state = port->state;
+ run_state_machine(dev);
+ if (port->queued_message)
+ tcpm_send_queued_message(dev);
+ } while (port->state != prev_state && !port->delayed_state);
+
+done:
+ port->state_machine_running = false;
+ mutex_unlock(&port->lock);
+}
+
+static void _tcpm_cc_change(struct udevice *dev, enum typec_cc_status cc1,
+ enum typec_cc_status cc2)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum typec_cc_status old_cc1, old_cc2;
+ enum tcpm_state new_state;
+
+ old_cc1 = port->cc1;
+ old_cc2 = port->cc2;
+ port->cc1 = cc1;
+ port->cc2 = cc2;
+
+ dev_dbg(dev, "TCPM: CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]\n",
+ old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state],
+ port->polarity,
+ tcpm_port_is_disconnected(port) ? "disconnected" : "connected");
+
+ switch (port->state) {
+ case TOGGLING:
+ if (tcpm_port_is_source(port))
+ tcpm_set_state(dev, SRC_ATTACH_WAIT, 0);
+ else if (tcpm_port_is_sink(port))
+ tcpm_set_state(dev, SNK_ATTACH_WAIT, 0);
+ break;
+ case SRC_UNATTACHED:
+ case SRC_ATTACH_WAIT:
+ if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, SRC_UNATTACHED, 0);
+ else if (cc1 != old_cc1 || cc2 != old_cc2)
+ tcpm_set_state(dev, SRC_ATTACH_WAIT, 0);
+ break;
+ case SRC_ATTACHED:
+ case SRC_SEND_CAPABILITIES:
+ case SRC_READY:
+ if (tcpm_port_is_disconnected(port) ||
+ !tcpm_port_is_source(port))
+ tcpm_set_state(dev, SRC_UNATTACHED, 0);
+ break;
+ case SNK_UNATTACHED:
+ if (tcpm_port_is_sink(port))
+ tcpm_set_state(dev, SNK_ATTACH_WAIT, 0);
+ break;
+ case SNK_ATTACH_WAIT:
+ if ((port->cc1 == TYPEC_CC_OPEN &&
+ port->cc2 != TYPEC_CC_OPEN) ||
+ (port->cc1 != TYPEC_CC_OPEN &&
+ port->cc2 == TYPEC_CC_OPEN))
+ new_state = SNK_DEBOUNCED;
+ else if (tcpm_port_is_disconnected(port))
+ new_state = SNK_UNATTACHED;
+ else
+ break;
+ if (new_state != port->delayed_state)
+ tcpm_set_state(dev, SNK_ATTACH_WAIT, 0);
+ break;
+ case SNK_DEBOUNCED:
+ if (tcpm_port_is_disconnected(port))
+ new_state = SNK_UNATTACHED;
+ else if (port->vbus_present)
+ new_state = tcpm_try_src(port) ? INVALID_STATE : SNK_ATTACHED;
+ else
+ new_state = SNK_UNATTACHED;
+ if (new_state != port->delayed_state)
+ tcpm_set_state(dev, SNK_DEBOUNCED, 0);
+ break;
+ case SNK_READY:
+ if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, unattached_state(port), 0);
+ else if (!port->pd_capable &&
+ (cc1 != old_cc1 || cc2 != old_cc2))
+ tcpm_set_current_limit(dev,
+ tcpm_get_current_limit(port),
+ 5000);
+ break;
+
+ case SNK_DISCOVERY:
+ /* CC line is unstable, wait for debounce */
+ if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, SNK_DISCOVERY_DEBOUNCE, 0);
+ break;
+ case SNK_DISCOVERY_DEBOUNCE:
+ break;
+
+ case PORT_RESET:
+ case PORT_RESET_WAIT_OFF:
+ /*
+ * State set back to default mode once the timer completes.
+ * Ignore CC changes here.
+ */
+ break;
+ default:
+ /*
+ * While acting as sink and auto vbus discharge is enabled, Allow disconnect
+ * to be driven by vbus disconnect.
+ */
+ if (tcpm_port_is_disconnected(port))
+ tcpm_set_state(dev, unattached_state(port), 0);
+ break;
+ }
+}
+
+static void _tcpm_pd_vbus_on(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ dev_dbg(dev, "TCPM: VBUS on event\n");
+ port->vbus_present = true;
+ /*
+ * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly
+ * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here.
+ */
+ port->vbus_vsafe0v = false;
+
+ switch (port->state) {
+ case SNK_TRANSITION_SINK_VBUS:
+ port->explicit_contract = true;
+ tcpm_set_state(dev, SNK_READY, 0);
+ break;
+ case SNK_DISCOVERY:
+ tcpm_set_state(dev, SNK_DISCOVERY, 0);
+ break;
+ case SNK_DEBOUNCED:
+ tcpm_set_state(dev, SNK_ATTACHED, 0);
+ break;
+ case SNK_HARD_RESET_WAIT_VBUS:
+ tcpm_set_state(dev, SNK_HARD_RESET_SINK_ON, 0);
+ break;
+ case SRC_ATTACHED:
+ tcpm_set_state(dev, SRC_STARTUP, 0);
+ break;
+ case SRC_HARD_RESET_VBUS_ON:
+ tcpm_set_state(dev, SRC_STARTUP, 0);
+ break;
+
+ case PORT_RESET:
+ case PORT_RESET_WAIT_OFF:
+ /*
+ * State set back to default mode once the timer completes.
+ * Ignore vbus changes here.
+ */
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void _tcpm_pd_vbus_off(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ dev_dbg(dev, "TCPM: VBUS off event\n");
+ port->vbus_present = false;
+ port->vbus_never_low = false;
+ switch (port->state) {
+ case SNK_HARD_RESET_SINK_OFF:
+ tcpm_set_state(dev, SNK_HARD_RESET_WAIT_VBUS, 0);
+ break;
+ case HARD_RESET_SEND:
+ break;
+ case SNK_ATTACH_WAIT:
+ tcpm_set_state(dev, SNK_UNATTACHED, 0);
+ break;
+
+ case SNK_NEGOTIATE_CAPABILITIES:
+ break;
+
+ case PORT_RESET_WAIT_OFF:
+ tcpm_set_state(dev, tcpm_default_state(port), 0);
+ break;
+
+ case PORT_RESET:
+ /*
+ * State set back to default mode once the timer completes.
+ * Ignore vbus changes here.
+ */
+ break;
+
+ default:
+ if (port->pwr_role == TYPEC_SINK && port->attached)
+ tcpm_set_state(dev, SNK_UNATTACHED, 0);
+ break;
+ }
+}
+
+void tcpm_cc_change(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ enum typec_cc_status cc1, cc2;
+
+ tcpm_reset_event_cnt(dev);
+ if (drvops->get_cc(dev, &cc1, &cc2) == 0)
+ _tcpm_cc_change(dev, cc1, cc2);
+}
+
+void tcpm_vbus_change(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ bool vbus;
+
+ tcpm_reset_event_cnt(dev);
+ vbus = drvops->get_vbus(dev);
+ if (vbus)
+ _tcpm_pd_vbus_on(dev);
+ else
+ _tcpm_pd_vbus_off(dev);
+}
+
+void tcpm_pd_hard_reset(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ tcpm_reset_event_cnt(dev);
+ dev_dbg(dev, "TCPM: Received hard reset\n");
+
+ /* If a hard reset message is received during the port reset process,
+ * we should ignore it, that is, do not set port->state to HARD_RESET_START.
+ */
+ if (port->state == PORT_RESET || port->state == PORT_RESET_WAIT_OFF)
+ return;
+
+ /*
+ * If we keep receiving hard reset requests, executing the hard reset
+ * must have failed. Revert to error recovery if that happens.
+ */
+ tcpm_set_state(dev,
+ port->hard_reset_count < PD_N_HARD_RESET_COUNT ?
+ HARD_RESET_START : ERROR_RECOVERY,
+ 0);
+}
+
+static void tcpm_init(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ enum typec_cc_status cc1, cc2;
+
+ drvops->init(dev);
+
+ tcpm_reset_port(dev);
+
+ /*
+ * XXX
+ * Should possibly wait for VBUS to settle if it was enabled locally
+ * since tcpm_reset_port() will disable VBUS.
+ */
+ port->vbus_present = drvops->get_vbus(dev);
+ if (port->vbus_present)
+ port->vbus_never_low = true;
+
+ /*
+ * 1. When vbus_present is true, voltage on VBUS is already at VSAFE5V.
+ * So implicitly vbus_vsafe0v = false.
+ *
+ * 2. When vbus_present is false and TCPC does NOT support querying
+ * vsafe0v status, then, it's best to assume vbus is at VSAFE0V i.e.
+ * vbus_vsafe0v is true.
+ *
+ * 3. When vbus_present is false and TCPC does support querying vsafe0v,
+ * then, query tcpc for vsafe0v status.
+ */
+ if (port->vbus_present)
+ port->vbus_vsafe0v = false;
+ else
+ port->vbus_vsafe0v = true;
+
+ tcpm_set_state(dev, tcpm_default_state(port), 0);
+
+ if (drvops->get_cc(dev, &cc1, &cc2) == 0)
+ _tcpm_cc_change(dev, cc1, cc2);
+}
+
+static int tcpm_fw_get_caps(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ ofnode node;
+ const char *cap_str;
+ int ret;
+ u32 mw;
+
+ ret = drvops->get_connector_node(dev, &node);
+ if (ret)
+ return ret;
+
+ cap_str = ofnode_read_string(node, "power-role");
+ if (!cap_str)
+ return -EINVAL;
+
+ if (!strcmp("dual", cap_str))
+ port->typec_type = TYPEC_PORT_DRP;
+ else if (!strcmp("source", cap_str))
+ port->typec_type = TYPEC_PORT_SRC;
+ else if (!strcmp("sink", cap_str))
+ port->typec_type = TYPEC_PORT_SNK;
+ else
+ return -EINVAL;
+
+ port->port_type = port->typec_type;
+
+ if (port->port_type == TYPEC_PORT_SNK)
+ goto sink;
+
+ /* Get source pdos */
+ ret = ofnode_read_size(node, "source-pdos") / sizeof(u32);
+ if (ret <= 0)
+ return -EINVAL;
+
+ port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
+ ret = ofnode_read_u32_array(node, "source-pdos",
+ port->src_pdo, port->nr_src_pdo);
+ if (ret || tcpm_validate_caps(dev, port->src_pdo, port->nr_src_pdo))
+ return -EINVAL;
+
+ if (port->port_type == TYPEC_PORT_SRC)
+ return 0;
+
+ /* Get the preferred power role for DRP */
+ cap_str = ofnode_read_string(node, "try-power-role");
+ if (!cap_str)
+ return -EINVAL;
+
+ if (!strcmp("sink", cap_str))
+ port->typec_prefer_role = TYPEC_SINK;
+ else if (!strcmp("source", cap_str))
+ port->typec_prefer_role = TYPEC_SOURCE;
+ else
+ return -EINVAL;
+
+ if (port->typec_prefer_role < 0)
+ return -EINVAL;
+sink:
+ /* Get sink pdos */
+ ret = ofnode_read_size(node, "sink-pdos") / sizeof(u32);
+ if (ret <= 0)
+ return -EINVAL;
+
+ port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
+ ret = ofnode_read_u32_array(node, "sink-pdos",
+ port->snk_pdo, port->nr_snk_pdo);
+ if (ret || tcpm_validate_caps(dev, port->snk_pdo, port->nr_snk_pdo))
+ return -EINVAL;
+
+ if (ofnode_read_u32_array(node, "op-sink-microwatt", &mw, 1))
+ return -EINVAL;
+ port->operating_snk_mw = mw / 1000;
+
+ port->self_powered = ofnode_read_bool(node, "self-powered");
+
+ return 0;
+}
+
+static int tcpm_port_init(struct udevice *dev)
+{
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+ int err;
+
+ err = tcpm_fw_get_caps(dev);
+ if (err < 0) {
+ dev_err(dev, "TCPM: please check the dts config: %d\n", err);
+ return err;
+ }
+
+ port->try_role = port->typec_prefer_role;
+ port->port_type = port->typec_type;
+
+ tcpm_init(dev);
+
+ dev_info(dev, "TCPM: init finished\n");
+
+ return 0;
+}
+
+static void tcpm_poll_event(struct udevice *dev)
+{
+ const struct dm_tcpm_ops *drvops = dev_get_driver_ops(dev);
+ struct tcpm_port *port = dev_get_uclass_plat(dev);
+
+ if (!drvops->get_vbus(dev))
+ return;
+
+ while (port->poll_event_cnt < TCPM_POLL_EVENT_TIME_OUT) {
+ if (!port->wait_dr_swap_message &&
+ (port->state == SNK_READY || port->state == SRC_READY))
+ break;
+
+ drvops->poll_event(dev);
+ port->poll_event_cnt++;
+ udelay(500);
+ tcpm_check_and_run_delayed_work(dev);
+ }
+
+ if (port->state != SNK_READY && port->state != SRC_READY)
+ dev_warn(dev, "TCPM: exit in state %s\n",
+ tcpm_states[port->state]);
+
+ /*
+ * At this time, call the callback function of the respective pd chip
+ * to enter the low-power mode. In order to reduce the time spent on
+ * the PD chip driver as much as possible, the tcpm framework does not
+ * fully process the communication initiated by the device,so it should
+ * be noted that we can disable the internal oscillator, etc., but do
+ * not turn off the power of the transceiver module, otherwise the
+ * self-powered Type-C device will initiate a Message(eg: self-powered
+ * Type-C hub initiates a SINK capability request(PD_CTRL_GET_SINK_CAP))
+ * and the pd chip cannot reply to GoodCRC, causing the self-powered Type-C
+ * device to switch vbus to vSafe5v, or even turn off vbus.
+ */
+ if (!drvops->enter_low_power_mode)
+ return;
+
+ if (drvops->enter_low_power_mode(dev, port->attached, port->pd_capable))
+ dev_err(dev, "TCPM: failed to enter low power\n");
+ else
+ dev_info(dev, "TCPM: PD chip enter low power mode\n");
+}
+
+int tcpm_post_probe(struct udevice *dev)
+{
+ int ret = tcpm_port_init(dev);
+
+ if (ret < 0) {
+ dev_err(dev, "failed to tcpm port init\n");
+ return ret;
+ }
+
+ tcpm_poll_event(dev);
+
+ return 0;
+}