summaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 17:39:57 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 17:39:57 +0000
commitdc50eab76b709d68175a358d6e23a5a3890764d3 (patch)
treec754d0390db060af0213ff994f0ac310e4cfd6e9 /drivers/usb
parentAdding debian version 6.6.15-2. (diff)
downloadlinux-dc50eab76b709d68175a358d6e23a5a3890764d3.tar.xz
linux-dc50eab76b709d68175a358d6e23a5a3890764d3.zip
Merging upstream version 6.7.7.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/Kconfig10
-rw-r--r--drivers/usb/atm/usbatm.c3
-rw-r--r--drivers/usb/c67x00/c67x00-hcd.h1
-rw-r--r--drivers/usb/cdns3/cdns3-gadget.c8
-rw-r--r--drivers/usb/cdns3/cdnsp-debug.h3
-rw-r--r--drivers/usb/cdns3/cdnsp-gadget.c6
-rw-r--r--drivers/usb/cdns3/cdnsp-gadget.h5
-rw-r--r--drivers/usb/cdns3/cdnsp-ring.c24
-rw-r--r--drivers/usb/cdns3/core.c1
-rw-r--r--drivers/usb/cdns3/drd.c13
-rw-r--r--drivers/usb/cdns3/drd.h6
-rw-r--r--drivers/usb/cdns3/host.c16
-rw-r--r--drivers/usb/chipidea/Kconfig4
-rw-r--r--drivers/usb/chipidea/Makefile1
-rw-r--r--drivers/usb/chipidea/ci.h2
-rw-r--r--drivers/usb/chipidea/ci_hdrc_npcm.c114
-rw-r--r--drivers/usb/chipidea/ci_hdrc_tegra.c16
-rw-r--r--drivers/usb/chipidea/ci_hdrc_usb2.c13
-rw-r--r--drivers/usb/chipidea/core.c44
-rw-r--r--drivers/usb/chipidea/otg.c5
-rw-r--r--drivers/usb/common/ulpi.c2
-rw-r--r--drivers/usb/core/devio.c26
-rw-r--r--drivers/usb/core/hcd-pci.c3
-rw-r--r--drivers/usb/core/hub.c83
-rw-r--r--drivers/usb/core/hub.h3
-rw-r--r--drivers/usb/core/port.c22
-rw-r--r--drivers/usb/dwc2/params.c21
-rw-r--r--drivers/usb/dwc3/Kconfig11
-rw-r--r--drivers/usb/dwc3/Makefile1
-rw-r--r--drivers/usb/dwc3/core.c28
-rw-r--r--drivers/usb/dwc3/core.h6
-rw-r--r--drivers/usb/dwc3/dwc3-pci.c4
-rw-r--r--drivers/usb/dwc3/dwc3-qcom.c12
-rw-r--r--drivers/usb/dwc3/dwc3-rtk.c481
-rw-r--r--drivers/usb/dwc3/dwc3-xilinx.c14
-rw-r--r--drivers/usb/dwc3/gadget.c11
-rw-r--r--drivers/usb/dwc3/host.c4
-rw-r--r--drivers/usb/gadget/function/f_fs.c6
-rw-r--r--drivers/usb/gadget/function/f_mass_storage.c20
-rw-r--r--drivers/usb/gadget/function/f_midi.c4
-rw-r--r--drivers/usb/gadget/function/f_ncm.c10
-rw-r--r--drivers/usb/gadget/function/f_tcm.c3
-rw-r--r--drivers/usb/gadget/function/f_uac2.c16
-rw-r--r--drivers/usb/gadget/function/f_uvc.c14
-rw-r--r--drivers/usb/gadget/function/u_ether.c4
-rw-r--r--drivers/usb/gadget/function/u_uac2.h8
-rw-r--r--drivers/usb/gadget/legacy/inode.c19
-rw-r--r--drivers/usb/gadget/legacy/raw_gadget.c65
-rw-r--r--drivers/usb/gadget/udc/aspeed_udc.c19
-rw-r--r--drivers/usb/gadget/udc/at91_udc.c3
-rw-r--r--drivers/usb/gadget/udc/core.c19
-rw-r--r--drivers/usb/gadget/udc/fsl_qe_udc.c10
-rw-r--r--drivers/usb/gadget/udc/fsl_udc_core.c3
-rw-r--r--drivers/usb/gadget/udc/fusb300_udc.c7
-rw-r--r--drivers/usb/gadget/udc/lpc32xx_udc.c3
-rw-r--r--drivers/usb/gadget/udc/m66592-udc.c3
-rw-r--r--drivers/usb/gadget/udc/omap_udc.c3
-rw-r--r--drivers/usb/gadget/udc/r8a66597-udc.c3
-rw-r--r--drivers/usb/host/ohci.h2
-rw-r--r--drivers/usb/host/pci-quirks.c144
-rw-r--r--drivers/usb/host/pci-quirks.h34
-rw-r--r--drivers/usb/host/xhci-debugfs.c2
-rw-r--r--drivers/usb/host/xhci-ext-caps.h27
-rw-r--r--drivers/usb/host/xhci-hub.c4
-rw-r--r--drivers/usb/host/xhci-mem.c96
-rw-r--r--drivers/usb/host/xhci-mtk-sch.c411
-rw-r--r--drivers/usb/host/xhci-mtk.h19
-rw-r--r--drivers/usb/host/xhci-plat.c26
-rw-r--r--drivers/usb/host/xhci-ring.c118
-rw-r--r--drivers/usb/host/xhci-trace.h23
-rw-r--r--drivers/usb/host/xhci.c28
-rw-r--r--drivers/usb/host/xhci.h48
-rw-r--r--drivers/usb/misc/Kconfig13
-rw-r--r--drivers/usb/misc/Makefile1
-rw-r--r--drivers/usb/misc/onboard_usb_hub.c7
-rw-r--r--drivers/usb/misc/onboard_usb_hub.h1
-rw-r--r--drivers/usb/misc/usb-ljca.c893
-rw-r--r--drivers/usb/misc/usbtest.c2
-rw-r--r--drivers/usb/mon/mon_main.c23
-rw-r--r--drivers/usb/mtu3/mtu3_plat.c18
-rw-r--r--drivers/usb/musb/da8xx.c1
-rw-r--r--drivers/usb/musb/musb_dsps.c2
-rw-r--r--drivers/usb/roles/class.c29
-rw-r--r--drivers/usb/serial/cp210x.c1
-rw-r--r--drivers/usb/serial/option.c1
-rw-r--r--drivers/usb/serial/qcserial.c2
-rw-r--r--drivers/usb/storage/scsiglue.c7
-rw-r--r--drivers/usb/storage/uas-detect.h4
-rw-r--r--drivers/usb/storage/uas.c11
-rw-r--r--drivers/usb/storage/usb.c20
-rw-r--r--drivers/usb/storage/usb.h4
-rw-r--r--drivers/usb/storage/usual-tables.c17
-rw-r--r--drivers/usb/typec/altmodes/displayport.c25
-rw-r--r--drivers/usb/typec/anx7411.c3
-rw-r--r--drivers/usb/typec/class.c108
-rw-r--r--drivers/usb/typec/class.h16
-rw-r--r--drivers/usb/typec/mux/Kconfig10
-rw-r--r--drivers/usb/typec/mux/Makefile1
-rw-r--r--drivers/usb/typec/mux/fsa4480.c71
-rw-r--r--drivers/usb/typec/mux/intel_pmc_mux.c25
-rw-r--r--drivers/usb/typec/mux/ptn36502.c444
-rw-r--r--drivers/usb/typec/pd.c10
-rw-r--r--drivers/usb/typec/port-mapper.c9
-rw-r--r--drivers/usb/typec/tcpm/tcpci_rt1711h.c46
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c7
-rw-r--r--drivers/usb/typec/tipd/core.c632
-rw-r--r--drivers/usb/typec/tipd/tps6598x.h36
-rw-r--r--drivers/usb/typec/tipd/trace.h92
-rw-r--r--drivers/usb/typec/ucsi/displayport.c2
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c2
-rw-r--r--drivers/usb/typec/ucsi/ucsi_acpi.c88
-rw-r--r--drivers/usb/typec/ucsi/ucsi_ccg.c4
-rw-r--r--drivers/usb/usbip/usbip_common.h6
-rw-r--r--drivers/usb/usbip/vhci_hcd.c44
114 files changed, 4259 insertions, 730 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 7f33bcc315..abf8c6cdea 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -91,6 +91,16 @@ config USB_PCI
If you have such a device you may say N here and PCI related code
will not be built in the USB driver.
+config USB_PCI_AMD
+ bool "AMD PCI USB host support"
+ depends on USB_PCI && HAS_IOPORT
+ default X86 || MACH_LOONGSON64 || PPC_PASEMI
+ help
+ Enable workarounds for USB implementation quirks in SB600/SB700/SB800
+ and later south bridge implementations. These are common on x86 PCs
+ with AMD CPUs but rarely used elsewhere, with the exception of a few
+ powerpc and mips desktop machines.
+
if USB
source "drivers/usb/core/Kconfig"
diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c
index 1cdb8758ae..2da6615fbb 100644
--- a/drivers/usb/atm/usbatm.c
+++ b/drivers/usb/atm/usbatm.c
@@ -1018,7 +1018,8 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
size_t size;
/* instance init */
- size = struct_size(instance, urbs, num_rcv_urbs + num_snd_urbs);
+ size = struct_size(instance, urbs,
+ size_add(num_rcv_urbs, num_snd_urbs));
instance = kzalloc(size, GFP_KERNEL);
if (!instance)
return -ENOMEM;
diff --git a/drivers/usb/c67x00/c67x00-hcd.h b/drivers/usb/c67x00/c67x00-hcd.h
index 6332a6b5dc..6ba5adced6 100644
--- a/drivers/usb/c67x00/c67x00-hcd.h
+++ b/drivers/usb/c67x00/c67x00-hcd.h
@@ -109,7 +109,6 @@ int c67x00_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
void c67x00_endpoint_disable(struct usb_hcd *hcd,
struct usb_host_endpoint *ep);
-void c67x00_hcd_msg_received(struct c67x00_sie *sie, u16 msg);
void c67x00_sched_kick(struct c67x00_hcd *c67x00);
int c67x00_sched_start_scheduler(struct c67x00_hcd *c67x00);
void c67x00_sched_stop_scheduler(struct c67x00_hcd *c67x00);
diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c
index d140010257..b1b46c7c63 100644
--- a/drivers/usb/cdns3/cdns3-gadget.c
+++ b/drivers/usb/cdns3/cdns3-gadget.c
@@ -828,7 +828,11 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
return;
}
- if (request->complete) {
+ /*
+ * zlp request is appended by driver, needn't call usb_gadget_giveback_request() to notify
+ * gadget composite driver.
+ */
+ if (request->complete && request->buf != priv_dev->zlp_buf) {
spin_unlock(&priv_dev->lock);
usb_gadget_giveback_request(&priv_ep->endpoint,
request);
@@ -2539,11 +2543,11 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep)
while (!list_empty(&priv_ep->wa2_descmiss_req_list)) {
priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list);
+ list_del_init(&priv_req->list);
kfree(priv_req->request.buf);
cdns3_gadget_ep_free_request(&priv_ep->endpoint,
&priv_req->request);
- list_del_init(&priv_req->list);
--priv_ep->wa2_counter;
}
diff --git a/drivers/usb/cdns3/cdnsp-debug.h b/drivers/usb/cdns3/cdnsp-debug.h
index f0ca865cce..ad617b7455 100644
--- a/drivers/usb/cdns3/cdnsp-debug.h
+++ b/drivers/usb/cdns3/cdnsp-debug.h
@@ -131,8 +131,6 @@ static inline const char *cdnsp_trb_type_string(u8 type)
return "Endpoint Not ready";
case TRB_HALT_ENDPOINT:
return "Halt Endpoint";
- case TRB_FLUSH_ENDPOINT:
- return "FLush Endpoint";
default:
return "UNKNOWN";
}
@@ -328,7 +326,6 @@ static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0,
break;
case TRB_RESET_EP:
case TRB_HALT_ENDPOINT:
- case TRB_FLUSH_ENDPOINT:
ret = snprintf(str, size,
"%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
cdnsp_trb_type_string(type),
diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c
index 4b67749edb..4a3f0f9582 100644
--- a/drivers/usb/cdns3/cdnsp-gadget.c
+++ b/drivers/usb/cdns3/cdnsp-gadget.c
@@ -1024,10 +1024,8 @@ static int cdnsp_gadget_ep_disable(struct usb_ep *ep)
pep->ep_state |= EP_DIS_IN_RROGRESS;
/* Endpoint was unconfigured by Reset Device command. */
- if (!(pep->ep_state & EP_UNCONFIGURED)) {
+ if (!(pep->ep_state & EP_UNCONFIGURED))
cdnsp_cmd_stop_ep(pdev, pep);
- cdnsp_cmd_flush_ep(pdev, pep);
- }
/* Remove all queued USB requests. */
while (!list_empty(&pep->pending_list)) {
@@ -1424,8 +1422,6 @@ static void cdnsp_stop(struct cdnsp_device *pdev)
{
u32 temp;
- cdnsp_cmd_flush_ep(pdev, &pdev->eps[0]);
-
/* Remove internally queued request for ep0. */
if (!list_empty(&pdev->eps[0].pending_list)) {
struct cdnsp_request *req;
diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h
index e1b5801fdd..dbee6f0852 100644
--- a/drivers/usb/cdns3/cdnsp-gadget.h
+++ b/drivers/usb/cdns3/cdnsp-gadget.h
@@ -1128,8 +1128,6 @@ union cdnsp_trb {
#define TRB_HALT_ENDPOINT 54
/* Doorbell Overflow Event. */
#define TRB_DRB_OVERFLOW 57
-/* Flush Endpoint Command. */
-#define TRB_FLUSH_ENDPOINT 58
#define TRB_TYPE_LINK(x) (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK))
#define TRB_TYPE_LINK_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \
@@ -1539,8 +1537,6 @@ void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev,
void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index);
void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev,
unsigned int ep_index);
-void cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev,
- unsigned int ep_index);
void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num);
void cdnsp_queue_reset_device(struct cdnsp_device *pdev);
void cdnsp_queue_new_dequeue_state(struct cdnsp_device *pdev,
@@ -1574,7 +1570,6 @@ void cdnsp_irq_reset(struct cdnsp_device *pdev);
int cdnsp_halt_endpoint(struct cdnsp_device *pdev,
struct cdnsp_ep *pep, int value);
int cdnsp_cmd_stop_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep);
-int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep);
void cdnsp_setup_analyze(struct cdnsp_device *pdev);
int cdnsp_status_stage(struct cdnsp_device *pdev);
int cdnsp_reset_device(struct cdnsp_device *pdev);
diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c
index 275a6a2fa6..02f297f563 100644
--- a/drivers/usb/cdns3/cdnsp-ring.c
+++ b/drivers/usb/cdns3/cdnsp-ring.c
@@ -2126,19 +2126,6 @@ ep_stopped:
return ret;
}
-int cdnsp_cmd_flush_ep(struct cdnsp_device *pdev, struct cdnsp_ep *pep)
-{
- int ret;
-
- cdnsp_queue_flush_endpoint(pdev, pep->idx);
- cdnsp_ring_cmd_db(pdev);
- ret = cdnsp_wait_for_cmd_compl(pdev);
-
- trace_cdnsp_handle_cmd_flush_ep(pep->out_ctx);
-
- return ret;
-}
-
/*
* The transfer burst count field of the isochronous TRB defines the number of
* bursts that are required to move all packets in this TD. Only SuperSpeed
@@ -2468,17 +2455,6 @@ void cdnsp_queue_halt_endpoint(struct cdnsp_device *pdev, unsigned int ep_index)
EP_ID_FOR_TRB(ep_index));
}
-/*
- * Queue a flush endpoint request on the command ring.
- */
-void cdnsp_queue_flush_endpoint(struct cdnsp_device *pdev,
- unsigned int ep_index)
-{
- cdnsp_queue_command(pdev, 0, 0, 0, TRB_TYPE(TRB_FLUSH_ENDPOINT) |
- SLOT_ID_FOR_TRB(pdev->slot_id) |
- EP_ID_FOR_TRB(ep_index));
-}
-
void cdnsp_force_header_wakeup(struct cdnsp_device *pdev, int intf_num)
{
u32 lo, mid;
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index 33548771a0..465e9267b4 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -395,7 +395,6 @@ pm_put:
return ret;
}
-
/**
* cdns_wakeup_irq - interrupt handler for wakeup events
* @irq: irq number for cdns3/cdnsp core device
diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
index 04b6d12f2b..ee917f1b09 100644
--- a/drivers/usb/cdns3/drd.c
+++ b/drivers/usb/cdns3/drd.c
@@ -156,7 +156,8 @@ bool cdns_is_device(struct cdns *cdns)
*/
static void cdns_otg_disable_irq(struct cdns *cdns)
{
- writel(0, &cdns->otg_irq_regs->ien);
+ if (cdns->version)
+ writel(0, &cdns->otg_irq_regs->ien);
}
/**
@@ -422,15 +423,20 @@ int cdns_drd_init(struct cdns *cdns)
cdns->otg_regs = (void __iomem *)&cdns->otg_v1_regs->cmd;
- if (readl(&cdns->otg_cdnsp_regs->did) == OTG_CDNSP_DID) {
+ state = readl(&cdns->otg_cdnsp_regs->did);
+
+ if (OTG_CDNSP_CHECK_DID(state)) {
cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *)
&cdns->otg_cdnsp_regs->ien;
cdns->version = CDNSP_CONTROLLER_V2;
- } else {
+ } else if (OTG_CDNS3_CHECK_DID(state)) {
cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *)
&cdns->otg_v1_regs->ien;
writel(1, &cdns->otg_v1_regs->simulate);
cdns->version = CDNS3_CONTROLLER_V1;
+ } else {
+ dev_err(cdns->dev, "not supporte DID=0x%08x\n", state);
+ return -EINVAL;
}
dev_dbg(cdns->dev, "DRD version v1 (ID: %08x, rev: %08x)\n",
@@ -483,7 +489,6 @@ int cdns_drd_exit(struct cdns *cdns)
return 0;
}
-
/* Indicate the cdns3 core was power lost before */
bool cdns_power_is_lost(struct cdns *cdns)
{
diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
index cbdf94f73e..d72370c321 100644
--- a/drivers/usb/cdns3/drd.h
+++ b/drivers/usb/cdns3/drd.h
@@ -79,7 +79,11 @@ struct cdnsp_otg_regs {
__le32 susp_timing_ctrl;
};
-#define OTG_CDNSP_DID 0x0004034E
+/* CDNSP driver supports 0x000403xx Cadence USB controller family. */
+#define OTG_CDNSP_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040300)
+
+/* CDNS3 driver supports 0x000402xx Cadence USB controller family. */
+#define OTG_CDNS3_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040200)
/*
* Common registers interface for both CDNS3 and CDNSP version of DRD.
diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
index 6164fc4c96..ceca4d839d 100644
--- a/drivers/usb/cdns3/host.c
+++ b/drivers/usb/cdns3/host.c
@@ -18,6 +18,11 @@
#include "../host/xhci.h"
#include "../host/xhci-plat.h"
+/*
+ * The XECP_PORT_CAP_REG and XECP_AUX_CTRL_REG1 exist only
+ * in Cadence USB3 dual-role controller, so it can't be used
+ * with Cadence CDNSP dual-role controller.
+ */
#define XECP_PORT_CAP_REG 0x8000
#define XECP_AUX_CTRL_REG1 0x8120
@@ -57,6 +62,8 @@ static const struct xhci_plat_priv xhci_plat_cdns3_xhci = {
.resume_quirk = xhci_cdns3_resume_quirk,
};
+static const struct xhci_plat_priv xhci_plat_cdnsp_xhci;
+
static int __cdns_host_init(struct cdns *cdns)
{
struct platform_device *xhci;
@@ -81,8 +88,13 @@ static int __cdns_host_init(struct cdns *cdns)
goto err1;
}
- cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci,
- sizeof(struct xhci_plat_priv), GFP_KERNEL);
+ if (cdns->version < CDNSP_CONTROLLER_V2)
+ cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci,
+ sizeof(struct xhci_plat_priv), GFP_KERNEL);
+ else
+ cdns->xhci_plat_data = kmemdup(&xhci_plat_cdnsp_xhci,
+ sizeof(struct xhci_plat_priv), GFP_KERNEL);
+
if (!cdns->xhci_plat_data) {
ret = -ENOMEM;
goto err1;
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig
index c815824a0b..bab45bc623 100644
--- a/drivers/usb/chipidea/Kconfig
+++ b/drivers/usb/chipidea/Kconfig
@@ -43,6 +43,10 @@ config USB_CHIPIDEA_MSM
tristate "Enable MSM hsusb glue driver" if EXPERT
default USB_CHIPIDEA
+config USB_CHIPIDEA_NPCM
+ tristate "Enable NPCM hsusb glue driver" if EXPERT
+ default USB_CHIPIDEA
+
config USB_CHIPIDEA_IMX
tristate "Enable i.MX USB glue driver" if EXPERT
depends on OF
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index 71afeab97e..718cb24603 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -13,6 +13,7 @@ ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o
obj-$(CONFIG_USB_CHIPIDEA_GENERIC) += ci_hdrc_usb2.o
obj-$(CONFIG_USB_CHIPIDEA_MSM) += ci_hdrc_msm.o
+obj-$(CONFIG_USB_CHIPIDEA_NPCM) += ci_hdrc_npcm.o
obj-$(CONFIG_USB_CHIPIDEA_PCI) += ci_hdrc_pci.o
obj-$(CONFIG_USB_CHIPIDEA_IMX) += usbmisc_imx.o ci_hdrc_imx.o
obj-$(CONFIG_USB_CHIPIDEA_TEGRA) += ci_hdrc_tegra.o
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index d9bb3d3f02..2a38e1eb65 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -176,6 +176,7 @@ struct hw_bank {
* @enabled_otg_timer_bits: bits of enabled otg timers
* @next_otg_timer: next nearest enabled timer to be expired
* @work: work for role changing
+ * @power_lost_work: work for power lost handling
* @wq: workqueue thread
* @qh_pool: allocation pool for queue heads
* @td_pool: allocation pool for transfer descriptors
@@ -226,6 +227,7 @@ struct ci_hdrc {
enum otg_fsm_timer next_otg_timer;
struct usb_role_switch *role_switch;
struct work_struct work;
+ struct work_struct power_lost_work;
struct workqueue_struct *wq;
struct dma_pool *qh_pool;
diff --git a/drivers/usb/chipidea/ci_hdrc_npcm.c b/drivers/usb/chipidea/ci_hdrc_npcm.c
new file mode 100644
index 0000000000..e4a191e02c
--- /dev/null
+++ b/drivers/usb/chipidea/ci_hdrc_npcm.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2023 Nuvoton Technology corporation.
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/chipidea.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/reset-controller.h>
+#include <linux/of.h>
+
+#include "ci.h"
+
+struct npcm_udc_data {
+ struct platform_device *ci;
+ struct clk *core_clk;
+ struct ci_hdrc_platform_data pdata;
+};
+
+static int npcm_udc_notify_event(struct ci_hdrc *ci, unsigned event)
+{
+ struct device *dev = ci->dev->parent;
+
+ switch (event) {
+ case CI_HDRC_CONTROLLER_RESET_EVENT:
+ /* clear all mode bits */
+ hw_write(ci, OP_USBMODE, 0xffffffff, 0x0);
+ break;
+ default:
+ dev_dbg(dev, "unknown ci_hdrc event (%d)\n",event);
+ break;
+ }
+
+ return 0;
+}
+
+static int npcm_udc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct npcm_udc_data *ci;
+ struct platform_device *plat_ci;
+ struct device *dev = &pdev->dev;
+
+ ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
+ if (!ci)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, ci);
+
+ ci->core_clk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(ci->core_clk))
+ return PTR_ERR(ci->core_clk);
+
+ ret = clk_prepare_enable(ci->core_clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable the clock: %d\n", ret);
+
+ ci->pdata.name = dev_name(dev);
+ ci->pdata.capoffset = DEF_CAPOFFSET;
+ ci->pdata.flags = CI_HDRC_REQUIRES_ALIGNED_DMA |
+ CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS;
+ ci->pdata.phy_mode = USBPHY_INTERFACE_MODE_UTMI;
+ ci->pdata.notify_event = npcm_udc_notify_event;
+
+ plat_ci = ci_hdrc_add_device(dev, pdev->resource, pdev->num_resources,
+ &ci->pdata);
+ if (IS_ERR(plat_ci)) {
+ ret = PTR_ERR(plat_ci);
+ dev_err(dev, "failed to register HDRC NPCM device: %d\n", ret);
+ goto clk_err;
+ }
+
+ pm_runtime_no_callbacks(dev);
+ pm_runtime_enable(dev);
+
+ return 0;
+
+clk_err:
+ clk_disable_unprepare(ci->core_clk);
+ return ret;
+}
+
+static int npcm_udc_remove(struct platform_device *pdev)
+{
+ struct npcm_udc_data *ci = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+ ci_hdrc_remove_device(ci->ci);
+ clk_disable_unprepare(ci->core_clk);
+
+ return 0;
+}
+
+static const struct of_device_id npcm_udc_dt_match[] = {
+ { .compatible = "nuvoton,npcm750-udc", },
+ { .compatible = "nuvoton,npcm845-udc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, npcm_udc_dt_match);
+
+static struct platform_driver npcm_udc_driver = {
+ .probe = npcm_udc_probe,
+ .remove = npcm_udc_remove,
+ .driver = {
+ .name = "npcm_udc",
+ .of_match_table = npcm_udc_dt_match,
+ },
+};
+
+module_platform_driver(npcm_udc_driver);
+
+MODULE_DESCRIPTION("NPCM USB device controller driver");
+MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c
index 8e78bf643e..2cc3058032 100644
--- a/drivers/usb/chipidea/ci_hdrc_tegra.c
+++ b/drivers/usb/chipidea/ci_hdrc_tegra.c
@@ -293,14 +293,12 @@ static int tegra_usb_probe(struct platform_device *pdev)
usb->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0);
if (IS_ERR(usb->phy))
return dev_err_probe(&pdev->dev, PTR_ERR(usb->phy),
- "failed to get PHY\n");
+ "failed to get PHY");
usb->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(usb->clk)) {
- err = PTR_ERR(usb->clk);
- dev_err(&pdev->dev, "failed to get clock: %d\n", err);
- return err;
- }
+ if (IS_ERR(usb->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(usb->clk),
+ "failed to get clock");
err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
if (err)
@@ -316,7 +314,7 @@ static int tegra_usb_probe(struct platform_device *pdev)
err = tegra_usb_reset_controller(&pdev->dev);
if (err) {
- dev_err(&pdev->dev, "failed to reset controller: %d\n", err);
+ dev_err_probe(&pdev->dev, err, "failed to reset controller");
goto fail_power_off;
}
@@ -347,8 +345,8 @@ static int tegra_usb_probe(struct platform_device *pdev)
usb->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource,
pdev->num_resources, &usb->data);
if (IS_ERR(usb->dev)) {
- err = PTR_ERR(usb->dev);
- dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err);
+ err = dev_err_probe(&pdev->dev, PTR_ERR(usb->dev),
+ "failed to add HDRC device");
goto phy_shutdown;
}
diff --git a/drivers/usb/chipidea/ci_hdrc_usb2.c b/drivers/usb/chipidea/ci_hdrc_usb2.c
index 1321ee67f3..97379f653b 100644
--- a/drivers/usb/chipidea/ci_hdrc_usb2.c
+++ b/drivers/usb/chipidea/ci_hdrc_usb2.c
@@ -9,9 +9,9 @@
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/hcd.h>
#include <linux/usb/ulpi.h>
@@ -51,8 +51,8 @@ static int ci_hdrc_usb2_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct ci_hdrc_usb2_priv *priv;
struct ci_hdrc_platform_data *ci_pdata = dev_get_platdata(dev);
+ const struct ci_hdrc_platform_data *data;
int ret;
- const struct of_device_id *match;
if (!ci_pdata) {
ci_pdata = devm_kmalloc(dev, sizeof(*ci_pdata), GFP_KERNEL);
@@ -61,11 +61,10 @@ static int ci_hdrc_usb2_probe(struct platform_device *pdev)
*ci_pdata = ci_default_pdata; /* struct copy */
}
- match = of_match_device(ci_hdrc_usb2_of_match, &pdev->dev);
- if (match && match->data) {
+ data = device_get_match_data(&pdev->dev);
+ if (data)
/* struct copy */
- *ci_pdata = *(struct ci_hdrc_platform_data *)match->data;
- }
+ *ci_pdata = *data;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -120,7 +119,7 @@ static struct platform_driver ci_hdrc_usb2_driver = {
.remove_new = ci_hdrc_usb2_remove,
.driver = {
.name = "chipidea-usb2",
- .of_match_table = of_match_ptr(ci_hdrc_usb2_of_match),
+ .of_match_table = ci_hdrc_usb2_of_match,
},
};
module_platform_driver(ci_hdrc_usb2_driver);
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 85e9c3ab66..ca71df4f32 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -856,6 +856,27 @@ static int ci_extcon_register(struct ci_hdrc *ci)
return 0;
}
+static void ci_power_lost_work(struct work_struct *work)
+{
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc, power_lost_work);
+ enum ci_role role;
+
+ disable_irq_nosync(ci->irq);
+ pm_runtime_get_sync(ci->dev);
+ if (!ci_otg_is_fsm_mode(ci)) {
+ role = ci_get_role(ci);
+
+ if (ci->role != role) {
+ ci_handle_id_switch(ci);
+ } else if (role == CI_ROLE_GADGET) {
+ if (ci->is_otg && hw_read_otgsc(ci, OTGSC_BSV))
+ usb_gadget_vbus_connect(&ci->gadget);
+ }
+ }
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+}
+
static DEFINE_IDA(ci_ida);
struct platform_device *ci_hdrc_add_device(struct device *dev,
@@ -1045,6 +1066,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
spin_lock_init(&ci->lock);
mutex_init(&ci->mutex);
+ INIT_WORK(&ci->power_lost_work, ci_power_lost_work);
+
ci->dev = dev;
ci->platdata = dev_get_platdata(dev);
ci->imx28_write_fix = !!(ci->platdata->flags &
@@ -1396,25 +1419,6 @@ static int ci_suspend(struct device *dev)
return 0;
}
-static void ci_handle_power_lost(struct ci_hdrc *ci)
-{
- enum ci_role role;
-
- disable_irq_nosync(ci->irq);
- if (!ci_otg_is_fsm_mode(ci)) {
- role = ci_get_role(ci);
-
- if (ci->role != role) {
- ci_handle_id_switch(ci);
- } else if (role == CI_ROLE_GADGET) {
- if (ci->is_otg && hw_read_otgsc(ci, OTGSC_BSV))
- usb_gadget_vbus_connect(&ci->gadget);
- }
- }
-
- enable_irq(ci->irq);
-}
-
static int ci_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
@@ -1446,7 +1450,7 @@ static int ci_resume(struct device *dev)
ci_role(ci)->resume(ci, power_lost);
if (power_lost)
- ci_handle_power_lost(ci);
+ queue_work(system_freezable_wq, &ci->power_lost_work);
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index f5490f2a5b..647e98f4e3 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -130,8 +130,11 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci)
void ci_handle_vbus_change(struct ci_hdrc *ci)
{
- if (!ci->is_otg)
+ if (!ci->is_otg) {
+ if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS)
+ usb_gadget_vbus_connect(&ci->gadget);
return;
+ }
if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
usb_gadget_vbus_connect(&ci->gadget);
diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c
index 84d91b1c1e..0886b19d2e 100644
--- a/drivers/usb/common/ulpi.c
+++ b/drivers/usb/common/ulpi.c
@@ -301,7 +301,7 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi)
return ret;
}
- root = debugfs_create_dir(dev_name(dev), ulpi_root);
+ root = debugfs_create_dir(dev_name(&ulpi->dev), ulpi_root);
debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_fops);
dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 4f68f6ef3c..3beb6a862e 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -2642,21 +2642,24 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
snoop(&dev->dev, "%s: CONTROL\n", __func__);
ret = proc_control(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_BULK:
snoop(&dev->dev, "%s: BULK\n", __func__);
ret = proc_bulk(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_RESETEP:
snoop(&dev->dev, "%s: RESETEP\n", __func__);
ret = proc_resetep(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_RESET:
@@ -2668,7 +2671,8 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
snoop(&dev->dev, "%s: CLEAR_HALT\n", __func__);
ret = proc_clearhalt(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_GETDRIVER:
@@ -2695,7 +2699,8 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
snoop(&dev->dev, "%s: SUBMITURB\n", __func__);
ret = proc_submiturb(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
#ifdef CONFIG_COMPAT
@@ -2703,14 +2708,16 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
snoop(&dev->dev, "%s: CONTROL32\n", __func__);
ret = proc_control_compat(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_BULK32:
snoop(&dev->dev, "%s: BULK32\n", __func__);
ret = proc_bulk_compat(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_DISCSIGNAL32:
@@ -2722,7 +2729,8 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
snoop(&dev->dev, "%s: SUBMITURB32\n", __func__);
ret = proc_submiturb_compat(ps, p);
if (ret >= 0)
- inode->i_mtime = inode_set_ctime_current(inode);
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
break;
case USBDEVFS_IOCTL32:
@@ -2804,7 +2812,7 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
done:
usb_unlock_device(dev);
if (ret >= 0)
- inode->i_atime = current_time(inode);
+ inode_set_atime_to_ts(inode, current_time(inode));
return ret;
}
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index 990280688b..ee3156f495 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -206,8 +206,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct hc_driver *driver)
goto free_irq_vectors;
}
- hcd->amd_resume_bug = (usb_hcd_amd_remote_wakeup_quirk(dev) &&
- driver->flags & (HCD_USB11 | HCD_USB3)) ? 1 : 0;
+ hcd->amd_resume_bug = usb_hcd_amd_resume_bug(dev, driver);
if (driver->flags & HCD_MEMORY) {
/* EHCI, OHCI */
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index dfc30cebd4..4854d883e6 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -47,12 +47,18 @@
#define USB_VENDOR_TEXAS_INSTRUMENTS 0x0451
#define USB_PRODUCT_TUSB8041_USB3 0x8140
#define USB_PRODUCT_TUSB8041_USB2 0x8142
-#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01
-#define HUB_QUIRK_DISABLE_AUTOSUSPEND 0x02
+#define USB_VENDOR_MICROCHIP 0x0424
+#define USB_PRODUCT_USB4913 0x4913
+#define USB_PRODUCT_USB4914 0x4914
+#define USB_PRODUCT_USB4915 0x4915
+#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND BIT(0)
+#define HUB_QUIRK_DISABLE_AUTOSUSPEND BIT(1)
+#define HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL BIT(2)
#define USB_TP_TRANSMISSION_DELAY 40 /* ns */
#define USB_TP_TRANSMISSION_DELAY_MAX 65535 /* ns */
#define USB_PING_RESPONSE_TIME 400 /* ns */
+#define USB_REDUCE_FRAME_INTR_BINTERVAL 9
/* Protect struct usb_device->state and ->children members
* Note: Both are also protected by ->dev.sem, except that ->state can
@@ -1904,6 +1910,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
usb_autopm_get_interface_no_resume(intf);
}
+ if ((id->driver_info & HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL) &&
+ desc->endpoint[0].desc.bInterval > USB_REDUCE_FRAME_INTR_BINTERVAL) {
+ desc->endpoint[0].desc.bInterval =
+ USB_REDUCE_FRAME_INTR_BINTERVAL;
+ /* Tell the HCD about the interrupt ep's new bInterval */
+ usb_set_interface(hdev, 0, 0);
+ }
+
if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs);
@@ -2033,9 +2047,19 @@ static void update_port_device_state(struct usb_device *udev)
if (udev->parent) {
hub = usb_hub_to_struct_hub(udev->parent);
- port_dev = hub->ports[udev->portnum - 1];
- WRITE_ONCE(port_dev->state, udev->state);
- sysfs_notify_dirent(port_dev->state_kn);
+
+ /*
+ * The Link Layer Validation System Driver (lvstest)
+ * has a test step to unbind the hub before running the
+ * rest of the procedure. This triggers hub_disconnect
+ * which will set the hub's maxchild to 0, further
+ * resulting in usb_hub_to_struct_hub returning NULL.
+ */
+ if (hub) {
+ port_dev = hub->ports[udev->portnum - 1];
+ WRITE_ONCE(port_dev->state, udev->state);
+ sysfs_notify_dirent(port_dev->state_kn);
+ }
}
}
@@ -2251,6 +2275,8 @@ void usb_disconnect(struct usb_device **pdev)
*/
if (!test_and_set_bit(port1, hub->child_usage_bits))
pm_runtime_get_sync(&port_dev->dev);
+
+ typec_deattach(port_dev->connector, &udev->dev);
}
usb_remove_ep_devs(&udev->ep0);
@@ -2366,17 +2392,25 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
}
} else if (desc->bLength == sizeof
(struct usb_otg_descriptor)) {
- /* Set a_alt_hnp_support for legacy otg device */
- err = usb_control_msg(udev,
- usb_sndctrlpipe(udev, 0),
- USB_REQ_SET_FEATURE, 0,
- USB_DEVICE_A_ALT_HNP_SUPPORT,
- 0, NULL, 0,
- USB_CTRL_SET_TIMEOUT);
- if (err < 0)
- dev_err(&udev->dev,
- "set a_alt_hnp_support failed: %d\n",
- err);
+ /*
+ * We are operating on a legacy OTP device
+ * These should be told that they are operating
+ * on the wrong port if we have another port that does
+ * support HNP
+ */
+ if (bus->otg_port != 0) {
+ /* Set a_alt_hnp_support for legacy otg device */
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_A_ALT_HNP_SUPPORT,
+ 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (err < 0)
+ dev_err(&udev->dev,
+ "set a_alt_hnp_support failed: %d\n",
+ err);
+ }
}
}
#endif
@@ -2597,6 +2631,8 @@ int usb_new_device(struct usb_device *udev)
if (!test_and_set_bit(port1, hub->child_usage_bits))
pm_runtime_get_sync(&port_dev->dev);
+
+ typec_attach(port_dev->connector, &udev->dev);
}
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
@@ -5891,6 +5927,21 @@ static const struct usb_device_id hub_id_table[] = {
.idVendor = USB_VENDOR_TEXAS_INSTRUMENTS,
.idProduct = USB_PRODUCT_TUSB8041_USB3,
.driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
+ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+ | USB_DEVICE_ID_MATCH_PRODUCT,
+ .idVendor = USB_VENDOR_MICROCHIP,
+ .idProduct = USB_PRODUCT_USB4913,
+ .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
+ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+ | USB_DEVICE_ID_MATCH_PRODUCT,
+ .idVendor = USB_VENDOR_MICROCHIP,
+ .idProduct = USB_PRODUCT_USB4914,
+ .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
+ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+ | USB_DEVICE_ID_MATCH_PRODUCT,
+ .idVendor = USB_VENDOR_MICROCHIP,
+ .idProduct = USB_PRODUCT_USB4915,
+ .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
.bDeviceClass = USB_CLASS_HUB},
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index d44dd7f662..43ce21c96a 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -14,6 +14,7 @@
#include <linux/usb.h>
#include <linux/usb/ch11.h>
#include <linux/usb/hcd.h>
+#include <linux/usb/typec.h>
#include "usb.h"
struct usb_hub {
@@ -82,6 +83,7 @@ struct usb_hub {
* @dev: generic device interface
* @port_owner: port's owner
* @peer: related usb2 and usb3 ports (share the same connector)
+ * @connector: USB Type-C connector
* @req: default pm qos request for hubs without port power control
* @connect_type: port's connect type
* @state: device state of the usb device attached to the port
@@ -100,6 +102,7 @@ struct usb_port {
struct device dev;
struct usb_dev_state *port_owner;
struct usb_port *peer;
+ struct typec_connector *connector;
struct dev_pm_qos_request *req;
enum usb_port_connect_type connect_type;
enum usb_device_state state;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 77be0dc28d..c628c1abc9 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -653,6 +653,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
static int connector_bind(struct device *dev, struct device *connector, void *data)
{
+ struct usb_port *port_dev = to_usb_port(dev);
int ret;
ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");
@@ -660,16 +661,30 @@ static int connector_bind(struct device *dev, struct device *connector, void *da
return ret;
ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev));
- if (ret)
+ if (ret) {
sysfs_remove_link(&dev->kobj, "connector");
+ return ret;
+ }
+
+ port_dev->connector = data;
+
+ /*
+ * If there is already USB device connected to the port, letting the
+ * Type-C connector know about it immediately.
+ */
+ if (port_dev->child)
+ typec_attach(port_dev->connector, &port_dev->child->dev);
- return ret;
+ return 0;
}
static void connector_unbind(struct device *dev, struct device *connector, void *data)
{
+ struct usb_port *port_dev = to_usb_port(dev);
+
sysfs_remove_link(&connector->kobj, dev_name(dev));
sysfs_remove_link(&dev->kobj, "connector");
+ port_dev->connector = NULL;
}
static const struct component_ops connector_ops = {
@@ -698,6 +713,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
set_bit(port1, hub->power_bits);
port_dev->dev.parent = hub->intfdev;
if (hub_is_superspeed(hdev)) {
+ port_dev->is_superspeed = 1;
port_dev->usb3_lpm_u1_permit = 1;
port_dev->usb3_lpm_u2_permit = 1;
port_dev->dev.groups = port_dev_usb3_group;
@@ -705,8 +721,6 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
port_dev->dev.groups = port_dev_group;
port_dev->dev.type = &usb_port_device_type;
port_dev->dev.driver = &usb_port_driver;
- if (hub_is_superspeed(hub->hdev))
- port_dev->is_superspeed = 1;
dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev),
port1);
mutex_init(&port_dev->status_lock);
diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c
index 93f52e371c..fb03162ae9 100644
--- a/drivers/usb/dwc2/params.c
+++ b/drivers/usb/dwc2/params.c
@@ -5,7 +5,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/usb/of.h>
#include <linux/pci_ids.h>
#include <linux/pci.h>
@@ -968,26 +968,17 @@ typedef void (*set_params_cb)(struct dwc2_hsotg *data);
int dwc2_init_params(struct dwc2_hsotg *hsotg)
{
- const struct of_device_id *match;
set_params_cb set_params;
dwc2_set_default_params(hsotg);
dwc2_get_device_properties(hsotg);
- match = of_match_device(dwc2_of_match_table, hsotg->dev);
- if (match && match->data) {
- set_params = match->data;
+ set_params = device_get_match_data(hsotg->dev);
+ if (set_params) {
set_params(hsotg);
- } else if (!match) {
- const struct acpi_device_id *amatch;
- const struct pci_device_id *pmatch = NULL;
-
- amatch = acpi_match_device(dwc2_acpi_match, hsotg->dev);
- if (amatch && amatch->driver_data) {
- set_params = (set_params_cb)amatch->driver_data;
- set_params(hsotg);
- } else if (!amatch)
- pmatch = pci_match_id(dwc2_pci_ids, to_pci_dev(hsotg->dev->parent));
+ } else {
+ const struct pci_device_id *pmatch =
+ pci_match_id(dwc2_pci_ids, to_pci_dev(hsotg->dev->parent));
if (pmatch && pmatch->driver_data) {
set_params = (set_params_cb)pmatch->driver_data;
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 98efcbb76c..5fc27b20df 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -178,4 +178,15 @@ config USB_DWC3_OCTEON
Only the host mode is currently supported.
Say 'Y' or 'M' here if you have one such device.
+config USB_DWC3_RTK
+ tristate "Realtek DWC3 Platform Driver"
+ depends on OF && ARCH_REALTEK
+ default USB_DWC3
+ select USB_ROLE_SWITCH
+ help
+ RTK DHC RTD SoCs with DesignWare Core USB3 IP inside,
+ and IP Core configured for USB 2.0 and USB 3.0 in host
+ or dual-role mode.
+ Say 'Y' or 'M' if you have such device.
+
endif
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index fe1493d4bb..124eda2522 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -55,3 +55,4 @@ obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o
obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o
obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o
+obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index b73b79fea2..f50b5575d5 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -817,8 +817,20 @@ static int dwc3_clk_enable(struct dwc3 *dwc)
if (ret)
goto disable_ref_clk;
+ ret = clk_prepare_enable(dwc->utmi_clk);
+ if (ret)
+ goto disable_susp_clk;
+
+ ret = clk_prepare_enable(dwc->pipe_clk);
+ if (ret)
+ goto disable_utmi_clk;
+
return 0;
+disable_utmi_clk:
+ clk_disable_unprepare(dwc->utmi_clk);
+disable_susp_clk:
+ clk_disable_unprepare(dwc->susp_clk);
disable_ref_clk:
clk_disable_unprepare(dwc->ref_clk);
disable_bus_clk:
@@ -828,6 +840,8 @@ disable_bus_clk:
static void dwc3_clk_disable(struct dwc3 *dwc)
{
+ clk_disable_unprepare(dwc->pipe_clk);
+ clk_disable_unprepare(dwc->utmi_clk);
clk_disable_unprepare(dwc->susp_clk);
clk_disable_unprepare(dwc->ref_clk);
clk_disable_unprepare(dwc->bus_clk);
@@ -1836,6 +1850,20 @@ static int dwc3_get_clocks(struct dwc3 *dwc)
}
}
+ /* specific to Rockchip RK3588 */
+ dwc->utmi_clk = devm_clk_get_optional(dev, "utmi");
+ if (IS_ERR(dwc->utmi_clk)) {
+ return dev_err_probe(dev, PTR_ERR(dwc->utmi_clk),
+ "could not get utmi clock\n");
+ }
+
+ /* specific to Rockchip RK3588 */
+ dwc->pipe_clk = devm_clk_get_optional(dev, "pipe");
+ if (IS_ERR(dwc->pipe_clk)) {
+ return dev_err_probe(dev, PTR_ERR(dwc->pipe_clk),
+ "could not get pipe clock\n");
+ }
+
return 0;
}
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 6782ec8bfd..efe6caf4d0 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -996,6 +996,8 @@ struct dwc3_scratchpad_array {
* @bus_clk: clock for accessing the registers
* @ref_clk: reference clock
* @susp_clk: clock used when the SS phy is in low power (S3) state
+ * @utmi_clk: clock used for USB2 PHY communication
+ * @pipe_clk: clock used for USB3 PHY communication
* @reset: reset control
* @regs: base address for our registers
* @regs_size: address space size
@@ -1115,6 +1117,8 @@ struct dwc3_scratchpad_array {
* instances in park mode.
* @parkmode_disable_hs_quirk: set if we need to disable all HishSpeed
* instances in park mode.
+ * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter
+ * running based on ref_clk
* @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
* @tx_de_emphasis: Tx de-emphasis value
* 0 - -6dB de-emphasis
@@ -1165,6 +1169,8 @@ struct dwc3 {
struct clk *bus_clk;
struct clk *ref_clk;
struct clk *susp_clk;
+ struct clk *utmi_clk;
+ struct clk *pipe_clk;
struct reset_control *reset;
diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c
index 6604845c39..39564e17f3 100644
--- a/drivers/usb/dwc3/dwc3-pci.c
+++ b/drivers/usb/dwc3/dwc3-pci.c
@@ -51,6 +51,8 @@
#define PCI_DEVICE_ID_INTEL_MTLP 0x7ec1
#define PCI_DEVICE_ID_INTEL_MTLS 0x7f6f
#define PCI_DEVICE_ID_INTEL_MTL 0x7e7e
+#define PCI_DEVICE_ID_INTEL_ARLH 0x7ec1
+#define PCI_DEVICE_ID_INTEL_ARLH_PCH 0x777e
#define PCI_DEVICE_ID_INTEL_TGL 0x9a15
#define PCI_DEVICE_ID_AMD_MR 0x163a
@@ -421,6 +423,8 @@ static const struct pci_device_id dwc3_pci_id_table[] = {
{ PCI_DEVICE_DATA(INTEL, MTLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTLS, &dwc3_pci_intel_swnode) },
+ { PCI_DEVICE_DATA(INTEL, ARLH, &dwc3_pci_intel_swnode) },
+ { PCI_DEVICE_DATA(INTEL, ARLH_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, TGL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(AMD, NL_USB, &dwc3_pci_amd_swnode) },
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 8254437411..fdf6d5d3c2 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -546,10 +546,9 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
pdata ? pdata->hs_phy_irq_index : -1);
if (irq > 0) {
/* Keep wakeup interrupts disabled until suspend */
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
- IRQF_ONESHOT,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
"qcom_dwc3 HS", qcom);
if (ret) {
dev_err(qcom->dev, "hs_phy_irq failed: %d\n", ret);
@@ -561,10 +560,9 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
irq = dwc3_qcom_get_irq(pdev, "dp_hs_phy_irq",
pdata ? pdata->dp_hs_phy_irq_index : -1);
if (irq > 0) {
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
- IRQF_ONESHOT,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
"qcom_dwc3 DP_HS", qcom);
if (ret) {
dev_err(qcom->dev, "dp_hs_phy_irq failed: %d\n", ret);
@@ -576,10 +574,9 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
irq = dwc3_qcom_get_irq(pdev, "dm_hs_phy_irq",
pdata ? pdata->dm_hs_phy_irq_index : -1);
if (irq > 0) {
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
- IRQF_ONESHOT,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
"qcom_dwc3 DM_HS", qcom);
if (ret) {
dev_err(qcom->dev, "dm_hs_phy_irq failed: %d\n", ret);
@@ -591,10 +588,9 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
irq = dwc3_qcom_get_irq(pdev, "ss_phy_irq",
pdata ? pdata->ss_phy_irq_index : -1);
if (irq > 0) {
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
- IRQF_ONESHOT,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
"qcom_dwc3 SS", qcom);
if (ret) {
dev_err(qcom->dev, "ss_phy_irq failed: %d\n", ret);
diff --git a/drivers/usb/dwc3/dwc3-rtk.c b/drivers/usb/dwc3/dwc3-rtk.c
new file mode 100644
index 0000000000..3cd6b18455
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-rtk.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * dwc3-rtk.c - Realtek DWC3 Specific Glue layer
+ *
+ * Copyright (C) 2023 Realtek Semiconductor Corporation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/suspend.h>
+#include <linux/sys_soc.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/of.h>
+#include <linux/usb/role.h>
+
+#include "core.h"
+
+#define WRAP_CTR_REG 0x0
+#define DISABLE_MULTI_REQ BIT(1)
+#define DESC_R2W_MULTI_DISABLE BIT(9)
+#define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13)
+
+#define WRAP_USB2_PHY_UTMI_REG 0x8
+#define TXHSVM_EN BIT(3)
+
+#define WRAP_PHY_PIPE_REG 0xC
+#define RESET_DISABLE_PIPE3_P0 BIT(0)
+#define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1)
+
+#define WRAP_USB_HMAC_CTR0_REG 0x60
+#define U3PORT_DIS BIT(8)
+
+#define WRAP_USB2_PHY_REG 0x70
+#define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12)
+#define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13)
+#define USB2_PHY_SWITCH_MASK 0x707
+#define USB2_PHY_SWITCH_DEVICE 0x0
+#define USB2_PHY_SWITCH_HOST 0x606
+
+#define WRAP_APHY_REG 0x128
+#define USB3_MBIAS_ENABLE BIT(1)
+
+/* pm control */
+#define WRAP_USB_DBUS_PWR_CTRL_REG 0x160
+#define USB_DBUS_PWR_CTRL_REG 0x0
+#define DBUS_PWR_CTRL_EN BIT(0)
+
+struct dwc3_rtk {
+ struct device *dev;
+ void __iomem *regs;
+ size_t regs_size;
+ void __iomem *pm_base;
+
+ struct dwc3 *dwc;
+
+ enum usb_role cur_role;
+ struct usb_role_switch *role_switch;
+};
+
+static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+ void __iomem *reg;
+ int val;
+
+ reg = rtk->regs + WRAP_USB2_PHY_REG;
+ val = ~USB2_PHY_SWITCH_MASK & readl(reg);
+
+ switch (role) {
+ case USB_ROLE_DEVICE:
+ writel(USB2_PHY_SWITCH_DEVICE | val, reg);
+ break;
+ case USB_ROLE_HOST:
+ writel(USB2_PHY_SWITCH_HOST | val, reg);
+ break;
+ default:
+ dev_dbg(rtk->dev, "%s: role=%d\n", __func__, role);
+ break;
+ }
+}
+
+static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+ if (!rtk->dwc->role_sw)
+ return;
+
+ usb_role_switch_set_role(rtk->dwc->role_sw, role);
+}
+
+static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk)
+{
+ enum usb_role role;
+
+ role = rtk->cur_role;
+
+ if (rtk->dwc && rtk->dwc->role_sw)
+ role = usb_role_switch_get_role(rtk->dwc->role_sw);
+ else
+ dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n", __func__, role);
+
+ return role;
+}
+
+static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role)
+{
+ rtk->cur_role = role;
+
+ switch_dwc3_role(rtk, role);
+ mdelay(10);
+ switch_usb2_role(rtk, role);
+}
+
+#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)
+static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
+{
+ struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
+
+ dwc3_rtk_set_role(rtk, role);
+
+ return 0;
+}
+
+static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
+{
+ struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
+
+ return dwc3_rtk_get_role(rtk);
+}
+
+static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk)
+{
+ struct usb_role_switch_desc dwc3_role_switch = {NULL};
+
+ dwc3_role_switch.name = dev_name(rtk->dev);
+ dwc3_role_switch.driver_data = rtk;
+ dwc3_role_switch.allow_userspace_control = true;
+ dwc3_role_switch.fwnode = dev_fwnode(rtk->dev);
+ dwc3_role_switch.set = dwc3_usb_role_switch_set;
+ dwc3_role_switch.get = dwc3_usb_role_switch_get;
+ rtk->role_switch = usb_role_switch_register(rtk->dev, &dwc3_role_switch);
+ if (IS_ERR(rtk->role_switch))
+ return PTR_ERR(rtk->role_switch);
+
+ return 0;
+}
+
+static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk)
+{
+ if (rtk->role_switch)
+ usb_role_switch_unregister(rtk->role_switch);
+
+ rtk->role_switch = NULL;
+
+ return 0;
+}
+#else
+#define dwc3_rtk_setup_role_switch(x) 0
+#define dwc3_rtk_remove_role_switch(x) 0
+#endif
+
+static const char *const speed_names[] = {
+ [USB_SPEED_UNKNOWN] = "UNKNOWN",
+ [USB_SPEED_LOW] = "low-speed",
+ [USB_SPEED_FULL] = "full-speed",
+ [USB_SPEED_HIGH] = "high-speed",
+ [USB_SPEED_WIRELESS] = "wireless",
+ [USB_SPEED_SUPER] = "super-speed",
+ [USB_SPEED_SUPER_PLUS] = "super-speed-plus",
+};
+
+static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np)
+{
+ struct device_node *dwc3_np;
+ const char *maximum_speed;
+ int ret;
+
+ dwc3_np = of_get_compatible_child(np, "snps,dwc3");
+ if (!dwc3_np)
+ return USB_SPEED_UNKNOWN;
+
+ ret = of_property_read_string(dwc3_np, "maximum-speed", &maximum_speed);
+ if (ret < 0)
+ goto out;
+
+ ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
+
+out:
+ of_node_put(dwc3_np);
+
+ return (ret < 0) ? USB_SPEED_UNKNOWN : ret;
+}
+
+static int dwc3_rtk_init(struct dwc3_rtk *rtk)
+{
+ struct device *dev = rtk->dev;
+ void __iomem *reg;
+ int val;
+ enum usb_device_speed maximum_speed;
+ const struct soc_device_attribute rtk_soc_kylin_a00[] = {
+ { .family = "Realtek Kylin", .revision = "A00", },
+ { /* empty */ } };
+ const struct soc_device_attribute rtk_soc_hercules[] = {
+ { .family = "Realtek Hercules", }, { /* empty */ } };
+ const struct soc_device_attribute rtk_soc_thor[] = {
+ { .family = "Realtek Thor", }, { /* empty */ } };
+
+ if (soc_device_match(rtk_soc_kylin_a00)) {
+ reg = rtk->regs + WRAP_CTR_REG;
+ val = readl(reg);
+ writel(DISABLE_MULTI_REQ | val, reg);
+ dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus");
+ }
+
+ if (soc_device_match(rtk_soc_hercules)) {
+ reg = rtk->regs + WRAP_USB2_PHY_REG;
+ val = readl(reg);
+ writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, reg);
+ dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!");
+ }
+
+ reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG;
+ val = readl(reg);
+ writel(TXHSVM_EN | val, reg);
+
+ maximum_speed = __get_dwc3_maximum_speed(dev->of_node);
+ if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) {
+ if (soc_device_match(rtk_soc_thor)) {
+ reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG;
+ val = readl(reg);
+ writel(U3PORT_DIS | val, reg);
+ } else {
+ reg = rtk->regs + WRAP_CTR_REG;
+ val = readl(reg);
+ writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, reg);
+
+ reg = rtk->regs + WRAP_PHY_PIPE_REG;
+ val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(reg);
+ writel(RESET_DISABLE_PIPE3_P0 | val, reg);
+
+ reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG;
+ val = readl(reg);
+ writel(U3PORT_DIS | val, reg);
+
+ reg = rtk->regs + WRAP_APHY_REG;
+ val = readl(reg);
+ writel(~USB3_MBIAS_ENABLE & val, reg);
+
+ dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n", __func__);
+ }
+ }
+
+ reg = rtk->regs + WRAP_CTR_REG;
+ val = readl(reg);
+ writel(DESC_R2W_MULTI_DISABLE | val, reg);
+
+ /* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */
+ reg = rtk->regs + WRAP_USB2_PHY_REG;
+ val = ~USB2_PHY_SWITCH_MASK & readl(reg);
+ writel(USB2_PHY_SWITCH_HOST | val, reg);
+
+ if (rtk->pm_base) {
+ reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG;
+ val = DBUS_PWR_CTRL_EN | readl(reg);
+ writel(val, reg);
+ }
+
+ return 0;
+}
+
+static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk)
+{
+ struct device *dev = rtk->dev;
+ struct device_node *node = dev->of_node;
+ struct platform_device *dwc3_pdev;
+ struct device *dwc3_dev;
+ struct device_node *dwc3_node;
+ enum usb_dr_mode dr_mode;
+ int ret = 0;
+
+ ret = dwc3_rtk_init(rtk);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_platform_populate(node, NULL, NULL, dev);
+ if (ret) {
+ dev_err(dev, "failed to add dwc3 core\n");
+ return ret;
+ }
+
+ dwc3_node = of_get_compatible_child(node, "snps,dwc3");
+ if (!dwc3_node) {
+ dev_err(dev, "failed to find dwc3 core node\n");
+ ret = -ENODEV;
+ goto depopulate;
+ }
+
+ dwc3_pdev = of_find_device_by_node(dwc3_node);
+ if (!dwc3_pdev) {
+ dev_err(dev, "failed to find dwc3 core platform_device\n");
+ ret = -ENODEV;
+ goto err_node_put;
+ }
+
+ dwc3_dev = &dwc3_pdev->dev;
+ rtk->dwc = platform_get_drvdata(dwc3_pdev);
+ if (!rtk->dwc) {
+ dev_err(dev, "failed to find dwc3 core\n");
+ ret = -ENODEV;
+ goto err_pdev_put;
+ }
+
+ dr_mode = usb_get_dr_mode(dwc3_dev);
+ if (dr_mode != rtk->dwc->dr_mode) {
+ dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n",
+ dr_mode, rtk->dwc->dr_mode);
+ dr_mode = rtk->dwc->dr_mode;
+ }
+
+ switch (dr_mode) {
+ case USB_DR_MODE_PERIPHERAL:
+ rtk->cur_role = USB_ROLE_DEVICE;
+ break;
+ case USB_DR_MODE_HOST:
+ rtk->cur_role = USB_ROLE_HOST;
+ break;
+ default:
+ dev_dbg(rtk->dev, "%s: dr_mode=%d\n", __func__, dr_mode);
+ break;
+ }
+
+ if (device_property_read_bool(dwc3_dev, "usb-role-switch")) {
+ ret = dwc3_rtk_setup_role_switch(rtk);
+ if (ret) {
+ dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n", ret);
+ goto err_pdev_put;
+ }
+ rtk->cur_role = dwc3_rtk_get_role(rtk);
+ }
+
+ switch_usb2_role(rtk, rtk->cur_role);
+
+ platform_device_put(dwc3_pdev);
+ of_node_put(dwc3_node);
+
+ return 0;
+
+err_pdev_put:
+ platform_device_put(dwc3_pdev);
+err_node_put:
+ of_node_put(dwc3_node);
+depopulate:
+ of_platform_depopulate(dev);
+
+ return ret;
+}
+
+static int dwc3_rtk_probe(struct platform_device *pdev)
+{
+ struct dwc3_rtk *rtk;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ void __iomem *regs;
+ int ret = 0;
+
+ rtk = devm_kzalloc(dev, sizeof(*rtk), GFP_KERNEL);
+ if (!rtk) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ platform_set_drvdata(pdev, rtk);
+
+ rtk->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "missing memory resource\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ goto out;
+ }
+
+ rtk->regs = regs;
+ rtk->regs_size = resource_size(res);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ rtk->pm_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(rtk->pm_base)) {
+ ret = PTR_ERR(rtk->pm_base);
+ goto out;
+ }
+ }
+
+ ret = dwc3_rtk_probe_dwc3_core(rtk);
+
+out:
+ return ret;
+}
+
+static void dwc3_rtk_remove(struct platform_device *pdev)
+{
+ struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
+
+ rtk->dwc = NULL;
+
+ dwc3_rtk_remove_role_switch(rtk);
+
+ of_platform_depopulate(rtk->dev);
+}
+
+static void dwc3_rtk_shutdown(struct platform_device *pdev)
+{
+ struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
+
+ of_platform_depopulate(rtk->dev);
+}
+
+static const struct of_device_id rtk_dwc3_match[] = {
+ { .compatible = "realtek,rtd-dwc3" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rtk_dwc3_match);
+
+#ifdef CONFIG_PM_SLEEP
+static int dwc3_rtk_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int dwc3_rtk_resume(struct device *dev)
+{
+ struct dwc3_rtk *rtk = dev_get_drvdata(dev);
+
+ dwc3_rtk_init(rtk);
+
+ switch_usb2_role(rtk, rtk->cur_role);
+
+ /* runtime set active to reflect active state. */
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume)
+};
+
+#define DEV_PM_OPS (&dwc3_rtk_dev_pm_ops)
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct platform_driver dwc3_rtk_driver = {
+ .probe = dwc3_rtk_probe,
+ .remove_new = dwc3_rtk_remove,
+ .driver = {
+ .name = "rtk-dwc3",
+ .of_match_table = rtk_dwc3_match,
+ .pm = DEV_PM_OPS,
+ },
+ .shutdown = dwc3_rtk_shutdown,
+};
+
+module_platform_driver(dwc3_rtk_driver);
+
+MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>");
+MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer");
+MODULE_ALIAS("platform:rtk-dwc3");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3");
diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c
index 19307d24f3..5b7e92f476 100644
--- a/drivers/usb/dwc3/dwc3-xilinx.c
+++ b/drivers/usb/dwc3/dwc3-xilinx.c
@@ -32,9 +32,6 @@
#define XLNX_USB_TRAFFIC_ROUTE_CONFIG 0x005C
#define XLNX_USB_TRAFFIC_ROUTE_FPD 0x1
-/* Versal USB Reset ID */
-#define VERSAL_USB_RESET_ID 0xC104036
-
#define XLNX_USB_FPD_PIPE_CLK 0x7c
#define PIPE_CLK_DESELECT 1
#define PIPE_CLK_SELECT 0
@@ -72,20 +69,23 @@ static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask)
static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
{
struct device *dev = priv_data->dev;
+ struct reset_control *crst;
int ret;
+ crst = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(crst))
+ return dev_err_probe(dev, PTR_ERR(crst), "failed to get reset signal\n");
+
dwc3_xlnx_mask_phy_rst(priv_data, false);
/* Assert and De-assert reset */
- ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
- PM_RESET_ACTION_ASSERT);
+ ret = reset_control_assert(crst);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to assert Reset\n");
return ret;
}
- ret = zynqmp_pm_reset_assert(VERSAL_USB_RESET_ID,
- PM_RESET_ACTION_RELEASE);
+ ret = reset_control_deassert(crst);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to De-assert Reset\n");
return ret;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 89de363ecf..28f49400f3 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -2650,6 +2650,11 @@ static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc)
int ret;
spin_lock_irqsave(&dwc->lock, flags);
+ if (!dwc->pullups_connected) {
+ spin_unlock_irqrestore(&dwc->lock, flags);
+ return 0;
+ }
+
dwc->connected = false;
/*
@@ -4703,15 +4708,13 @@ int dwc3_gadget_suspend(struct dwc3 *dwc)
unsigned long flags;
int ret;
- if (!dwc->gadget_driver)
- return 0;
-
ret = dwc3_gadget_soft_disconnect(dwc);
if (ret)
goto err;
spin_lock_irqsave(&dwc->lock, flags);
- dwc3_disconnect_gadget(dwc);
+ if (dwc->gadget_driver)
+ dwc3_disconnect_gadget(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
return 0;
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 61f57fe5bb..4323091532 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -61,7 +61,7 @@ out:
int dwc3_host_init(struct dwc3 *dwc)
{
- struct property_entry props[4];
+ struct property_entry props[5];
struct platform_device *xhci;
int ret, irq;
int prop_idx = 0;
@@ -89,6 +89,8 @@ int dwc3_host_init(struct dwc3 *dwc)
memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props));
+ props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-sg-trb-cache-size-quirk");
+
if (dwc->usb3_lpm_capable)
props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable");
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 6e9ef35a43..efe3e3b857 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -202,7 +202,7 @@ struct ffs_epfile {
struct ffs_buffer {
size_t length;
char *data;
- char storage[];
+ char storage[] __counted_by(length);
};
/* ffs_io_data structure ***************************************************/
@@ -1383,8 +1383,8 @@ ffs_sb_make_inode(struct super_block *sb, void *data,
inode->i_mode = perms->mode;
inode->i_uid = perms->uid;
inode->i_gid = perms->gid;
- inode->i_atime = ts;
- inode->i_mtime = ts;
+ inode_set_atime_to_ts(inode, ts);
+ inode_set_mtime_to_ts(inode, ts);
inode->i_private = data;
if (fops)
inode->i_fop = fops;
diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c
index 722a3ab2b3..c265a1f62f 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -545,21 +545,37 @@ static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{
+ int rc;
+
if (!fsg_is_set(common))
return false;
bh->state = BUF_STATE_SENDING;
- if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq))
+ rc = start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq);
+ if (rc) {
bh->state = BUF_STATE_EMPTY;
+ if (rc == -ESHUTDOWN) {
+ common->running = 0;
+ return false;
+ }
+ }
return true;
}
static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
{
+ int rc;
+
if (!fsg_is_set(common))
return false;
bh->state = BUF_STATE_RECEIVING;
- if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq))
+ rc = start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq);
+ if (rc) {
bh->state = BUF_STATE_FULL;
+ if (rc == -ESHUTDOWN) {
+ common->running = 0;
+ return false;
+ }
+ }
return true;
}
diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
index 2d02f25f95..5335845d69 100644
--- a/drivers/usb/gadget/function/f_midi.c
+++ b/drivers/usb/gadget/function/f_midi.c
@@ -99,7 +99,7 @@ struct f_midi {
unsigned int in_last_port;
unsigned char free_ref;
- struct gmidi_in_port in_ports_array[/* in_ports */];
+ struct gmidi_in_port in_ports_array[] __counted_by(in_ports);
};
static inline struct f_midi *func_to_midi(struct usb_function *f)
@@ -1349,6 +1349,7 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
status = -ENOMEM;
goto setup_fail;
}
+ midi->in_ports = opts->in_ports;
for (i = 0; i < opts->in_ports; i++)
midi->in_ports_array[i].cable = i;
@@ -1359,7 +1360,6 @@ static struct usb_function *f_midi_alloc(struct usb_function_instance *fi)
status = -ENOMEM;
goto midi_free;
}
- midi->in_ports = opts->in_ports;
midi->out_ports = opts->out_ports;
midi->index = opts->index;
midi->buflen = opts->buflen;
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index cc0ed29a4a..5712883a75 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -1325,7 +1325,15 @@ parse_ntb:
"Parsed NTB with %d frames\n", dgram_counter);
to_process -= block_len;
- if (to_process != 0) {
+
+ /*
+ * Windows NCM driver avoids USB ZLPs by adding a 1-byte
+ * zero pad as needed.
+ */
+ if (to_process == 1 &&
+ (*(unsigned char *)(ntb_ptr + block_len) == 0x00)) {
+ to_process--;
+ } else if (to_process > 0) {
ntb_ptr = (unsigned char *)(ntb_ptr + block_len);
goto parse_ntb;
}
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 79ed2e6e57..ff33f31bcd 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -1687,6 +1687,9 @@ static const struct target_core_fabric_ops usbg_ops = {
.tfc_wwn_attrs = usbg_wwn_attrs,
.tfc_tpg_base_attrs = usbg_base_attrs,
+
+ .default_submit_type = TARGET_DIRECT_SUBMIT,
+ .direct_submit_supp = 1,
};
/* Start gadget.c code */
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 0219cd7949..f9a0f07a74 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -212,7 +212,7 @@ static struct uac2_input_terminal_descriptor io_in_it_desc = {
.bDescriptorSubtype = UAC_INPUT_TERMINAL,
/* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
+ /* .wTerminalType = DYNAMIC */
.bAssocTerminal = 0,
/* .bCSourceID = DYNAMIC */
.iChannelNames = 0,
@@ -240,7 +240,7 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = {
.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
/* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
+ /* .wTerminalType = DYNAMIC */
.bAssocTerminal = 0,
/* .bSourceID = DYNAMIC */
/* .bCSourceID = DYNAMIC */
@@ -977,6 +977,9 @@ static void setup_descriptor(struct f_uac2_opts *opts)
iad_desc.bInterfaceCount++;
}
+ io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type);
+ io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type);
+
setup_headers(opts, fs_audio_desc, USB_SPEED_FULL);
setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH);
setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER);
@@ -2095,6 +2098,9 @@ UAC2_ATTRIBUTE(s16, c_volume_res);
UAC2_ATTRIBUTE(u32, fb_max);
UAC2_ATTRIBUTE_STRING(function_name);
+UAC2_ATTRIBUTE(s16, p_terminal_type);
+UAC2_ATTRIBUTE(s16, c_terminal_type);
+
static struct configfs_attribute *f_uac2_attrs[] = {
&f_uac2_opts_attr_p_chmask,
&f_uac2_opts_attr_p_srate,
@@ -2122,6 +2128,9 @@ static struct configfs_attribute *f_uac2_attrs[] = {
&f_uac2_opts_attr_function_name,
+ &f_uac2_opts_attr_p_terminal_type,
+ &f_uac2_opts_attr_c_terminal_type,
+
NULL,
};
@@ -2180,6 +2189,9 @@ static struct usb_function_instance *afunc_alloc_inst(void)
snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink");
+ opts->p_terminal_type = UAC2_DEF_P_TERM_TYPE;
+ opts->c_terminal_type = UAC2_DEF_C_TERM_TYPE;
+
return &opts->func_inst;
}
diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 2e6bafb2a5..c6965e3894 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -516,6 +516,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
void *mem;
switch (speed) {
+ case USB_SPEED_SUPER_PLUS:
case USB_SPEED_SUPER:
uvc_control_desc = uvc->desc.ss_control;
uvc_streaming_cls = uvc->desc.ss_streaming;
@@ -564,7 +565,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
bytes += uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength;
n_desc += 2;
- if (speed == USB_SPEED_SUPER) {
+ if (speed == USB_SPEED_SUPER ||
+ speed == USB_SPEED_SUPER_PLUS) {
bytes += uvc_ss_interrupt_comp.bLength;
n_desc += 1;
}
@@ -619,7 +621,8 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed)
if (uvc->enable_interrupt_ep) {
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep);
- if (speed == USB_SPEED_SUPER)
+ if (speed == USB_SPEED_SUPER ||
+ speed == USB_SPEED_SUPER_PLUS)
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp);
UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep);
@@ -811,6 +814,13 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
goto error;
}
+ f->ssp_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER_PLUS);
+ if (IS_ERR(f->ssp_descriptors)) {
+ ret = PTR_ERR(f->ssp_descriptors);
+ f->ssp_descriptors = NULL;
+ goto error;
+ }
+
/* Preallocate control endpoint request. */
uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL);
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index 4bb0553da6..3c5a6f6ac3 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -1163,6 +1163,8 @@ struct net_device *gether_connect(struct gether *link)
if (netif_running(dev->net))
eth_start(dev, GFP_ATOMIC);
+ netif_device_attach(dev->net);
+
/* on error, disable any endpoints */
} else {
(void) usb_ep_disable(link->out_ep);
@@ -1200,7 +1202,7 @@ void gether_disconnect(struct gether *link)
DBG(dev, "%s\n", __func__);
- netif_stop_queue(dev->net);
+ netif_device_detach(dev->net);
netif_carrier_off(dev->net);
/* disable endpoints, forcing (synchronous) completion
diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h
index 0510c9bad5..5e81bdd6c5 100644
--- a/drivers/usb/gadget/function/u_uac2.h
+++ b/drivers/usb/gadget/function/u_uac2.h
@@ -35,6 +35,11 @@
#define UAC2_DEF_REQ_NUM 2
#define UAC2_DEF_INT_REQ_NUM 10
+#define UAC2_DEF_P_TERM_TYPE 0x301
+ /* UAC_OUTPUT_TERMINAL_SPEAKER */
+#define UAC2_DEF_C_TERM_TYPE 0x201
+ /* UAC_INPUT_TERMINAL_MICROPHONE*/
+
struct f_uac2_opts {
struct usb_function_instance func_inst;
int p_chmask;
@@ -65,6 +70,9 @@ struct f_uac2_opts {
char function_name[32];
+ s16 p_terminal_type;
+ s16 c_terminal_type;
+
struct mutex lock;
int refcnt;
};
diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
index ce9e31f3d2..03179b1880 100644
--- a/drivers/usb/gadget/legacy/inode.c
+++ b/drivers/usb/gadget/legacy/inode.c
@@ -31,6 +31,12 @@
#include <linux/usb/gadgetfs.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h> /* for USB_GADGET_DELAYED_STATUS */
+
+/* Undef helpers from linux/usb/composite.h as gadgetfs redefines them */
+#undef DBG
+#undef ERROR
+#undef INFO
/*
@@ -1511,7 +1517,16 @@ delegate:
event->u.setup = *ctrl;
ep0_readable (dev);
spin_unlock (&dev->lock);
- return 0;
+ /*
+ * Return USB_GADGET_DELAYED_STATUS as a workaround to
+ * stop some UDC drivers (e.g. dwc3) from automatically
+ * proceeding with the status stage for 0-length
+ * transfers.
+ * Should be removed once all UDC drivers are fixed to
+ * always delay the status stage until a response is
+ * queued to EP0.
+ */
+ return w_length == 0 ? USB_GADGET_DELAYED_STATUS : 0;
}
}
@@ -1969,7 +1984,7 @@ gadgetfs_make_inode (struct super_block *sb,
inode->i_mode = mode;
inode->i_uid = make_kuid(&init_user_ns, default_uid);
inode->i_gid = make_kgid(&init_user_ns, default_gid);
- inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode);
+ simple_inode_init_ts(inode);
inode->i_private = data;
inode->i_fop = fops;
}
diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c
index ea106ad665..399fca32a8 100644
--- a/drivers/usb/gadget/legacy/raw_gadget.c
+++ b/drivers/usb/gadget/legacy/raw_gadget.c
@@ -25,6 +25,7 @@
#include <linux/usb/ch9.h>
#include <linux/usb/ch11.h>
#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
#include <uapi/linux/usb/raw_gadget.h>
@@ -64,7 +65,7 @@ static int raw_event_queue_add(struct raw_event_queue *queue,
struct usb_raw_event *event;
spin_lock_irqsave(&queue->lock, flags);
- if (WARN_ON(queue->size >= RAW_EVENT_QUEUE_SIZE)) {
+ if (queue->size >= RAW_EVENT_QUEUE_SIZE) {
spin_unlock_irqrestore(&queue->lock, flags);
return -ENOMEM;
}
@@ -310,9 +311,10 @@ static int gadget_bind(struct usb_gadget *gadget,
dev->eps_num = i;
spin_unlock_irqrestore(&dev->lock, flags);
+ dev_dbg(&gadget->dev, "gadget connected\n");
ret = raw_queue_event(dev, USB_RAW_EVENT_CONNECT, 0, NULL);
if (ret < 0) {
- dev_err(&gadget->dev, "failed to queue event\n");
+ dev_err(&gadget->dev, "failed to queue connect event\n");
set_gadget_data(gadget, NULL);
return ret;
}
@@ -357,20 +359,65 @@ static int gadget_setup(struct usb_gadget *gadget,
ret = raw_queue_event(dev, USB_RAW_EVENT_CONTROL, sizeof(*ctrl), ctrl);
if (ret < 0)
- dev_err(&gadget->dev, "failed to queue event\n");
+ dev_err(&gadget->dev, "failed to queue control event\n");
goto out;
out_unlock:
spin_unlock_irqrestore(&dev->lock, flags);
out:
+ if (ret == 0 && ctrl->wLength == 0) {
+ /*
+ * Return USB_GADGET_DELAYED_STATUS as a workaround to stop
+ * some UDC drivers (e.g. dwc3) from automatically proceeding
+ * with the status stage for 0-length transfers.
+ * Should be removed once all UDC drivers are fixed to always
+ * delay the status stage until a response is queued to EP0.
+ */
+ return USB_GADGET_DELAYED_STATUS;
+ }
return ret;
}
-/* These are currently unused but present in case UDC driver requires them. */
-static void gadget_disconnect(struct usb_gadget *gadget) { }
-static void gadget_suspend(struct usb_gadget *gadget) { }
-static void gadget_resume(struct usb_gadget *gadget) { }
-static void gadget_reset(struct usb_gadget *gadget) { }
+static void gadget_disconnect(struct usb_gadget *gadget)
+{
+ struct raw_dev *dev = get_gadget_data(gadget);
+ int ret;
+
+ dev_dbg(&gadget->dev, "gadget disconnected\n");
+ ret = raw_queue_event(dev, USB_RAW_EVENT_DISCONNECT, 0, NULL);
+ if (ret < 0)
+ dev_err(&gadget->dev, "failed to queue disconnect event\n");
+}
+static void gadget_suspend(struct usb_gadget *gadget)
+{
+ struct raw_dev *dev = get_gadget_data(gadget);
+ int ret;
+
+ dev_dbg(&gadget->dev, "gadget suspended\n");
+ ret = raw_queue_event(dev, USB_RAW_EVENT_SUSPEND, 0, NULL);
+ if (ret < 0)
+ dev_err(&gadget->dev, "failed to queue suspend event\n");
+}
+static void gadget_resume(struct usb_gadget *gadget)
+{
+ struct raw_dev *dev = get_gadget_data(gadget);
+ int ret;
+
+ dev_dbg(&gadget->dev, "gadget resumed\n");
+ ret = raw_queue_event(dev, USB_RAW_EVENT_RESUME, 0, NULL);
+ if (ret < 0)
+ dev_err(&gadget->dev, "failed to queue resume event\n");
+}
+static void gadget_reset(struct usb_gadget *gadget)
+{
+ struct raw_dev *dev = get_gadget_data(gadget);
+ int ret;
+
+ dev_dbg(&gadget->dev, "gadget reset\n");
+ ret = raw_queue_event(dev, USB_RAW_EVENT_RESET, 0, NULL);
+ if (ret < 0)
+ dev_err(&gadget->dev, "failed to queue reset event\n");
+}
/*----------------------------------------------------------------------*/
@@ -682,7 +729,6 @@ static int raw_process_ep0_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
dev_err(&dev->gadget->dev,
"fail, usb_ep_queue returned %d\n", ret);
spin_lock_irqsave(&dev->lock, flags);
- dev->state = STATE_DEV_FAILED;
goto out_queue_failed;
}
@@ -1069,7 +1115,6 @@ static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
dev_err(&dev->gadget->dev,
"fail, usb_ep_queue returned %d\n", ret);
spin_lock_irqsave(&dev->lock, flags);
- dev->state = STATE_DEV_FAILED;
goto out_queue_failed;
}
diff --git a/drivers/usb/gadget/udc/aspeed_udc.c b/drivers/usb/gadget/udc/aspeed_udc.c
index 2ef89a442f..3916c8e2ba 100644
--- a/drivers/usb/gadget/udc/aspeed_udc.c
+++ b/drivers/usb/gadget/udc/aspeed_udc.c
@@ -1432,15 +1432,24 @@ static void ast_udc_init_hw(struct ast_udc_dev *udc)
ast_udc_write(udc, 0, AST_UDC_EP0_CTRL);
}
-static int ast_udc_remove(struct platform_device *pdev)
+static void ast_udc_remove(struct platform_device *pdev)
{
struct ast_udc_dev *udc = platform_get_drvdata(pdev);
unsigned long flags;
u32 ctrl;
usb_del_gadget_udc(&udc->gadget);
- if (udc->driver)
- return -EBUSY;
+ if (udc->driver) {
+ /*
+ * This is broken as only some cleanup is skipped, *udev is
+ * freed and the register mapping goes away. Any further usage
+ * probably crashes. Also the device is unbound, so the skipped
+ * cleanup is never catched up later.
+ */
+ dev_alert(&pdev->dev,
+ "Driver is busy and still going away. Fasten your seat belts!\n");
+ return;
+ }
spin_lock_irqsave(&udc->lock, flags);
@@ -1459,8 +1468,6 @@ static int ast_udc_remove(struct platform_device *pdev)
udc->ep0_buf_dma);
udc->ep0_buf = NULL;
-
- return 0;
}
static int ast_udc_probe(struct platform_device *pdev)
@@ -1581,7 +1588,7 @@ MODULE_DEVICE_TABLE(of, ast_udc_of_dt_ids);
static struct platform_driver ast_udc_driver = {
.probe = ast_udc_probe,
- .remove = ast_udc_remove,
+ .remove_new = ast_udc_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = ast_udc_of_dt_ids,
diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c
index 922b418700..30ea4a9d53 100644
--- a/drivers/usb/gadget/udc/at91_udc.c
+++ b/drivers/usb/gadget/udc/at91_udc.c
@@ -2000,6 +2000,7 @@ static int at91udc_resume(struct platform_device *pdev)
#endif
static struct platform_driver at91_udc_driver = {
+ .probe = at91udc_probe,
.remove = at91udc_remove,
.shutdown = at91udc_shutdown,
.suspend = at91udc_suspend,
@@ -2010,7 +2011,7 @@ static struct platform_driver at91_udc_driver = {
},
};
-module_platform_driver_probe(at91_udc_driver, at91udc_probe);
+module_platform_driver(at91_udc_driver);
MODULE_DESCRIPTION("AT91 udc driver");
MODULE_AUTHOR("Thomas Rathbone, David Brownell");
diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c
index 781c833854..d59f94464b 100644
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1126,12 +1126,12 @@ EXPORT_SYMBOL_GPL(usb_gadget_set_state);
/* ------------------------------------------------------------------------- */
/* Acquire connect_lock before calling this function. */
-static void usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&udc->connect_lock)
+static int usb_udc_connect_control_locked(struct usb_udc *udc) __must_hold(&udc->connect_lock)
{
if (udc->vbus)
- usb_gadget_connect_locked(udc->gadget);
+ return usb_gadget_connect_locked(udc->gadget);
else
- usb_gadget_disconnect_locked(udc->gadget);
+ return usb_gadget_disconnect_locked(udc->gadget);
}
static void vbus_event_work(struct work_struct *work)
@@ -1605,12 +1605,23 @@ static int gadget_bind_driver(struct device *dev)
}
usb_gadget_enable_async_callbacks(udc);
udc->allow_connect = true;
- usb_udc_connect_control_locked(udc);
+ ret = usb_udc_connect_control_locked(udc);
+ if (ret)
+ goto err_connect_control;
+
mutex_unlock(&udc->connect_lock);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
return 0;
+ err_connect_control:
+ udc->allow_connect = false;
+ usb_gadget_disable_async_callbacks(udc);
+ if (gadget->irq)
+ synchronize_irq(gadget->irq);
+ usb_gadget_udc_stop_locked(udc);
+ mutex_unlock(&udc->connect_lock);
+
err_start:
driver->unbind(udc->gadget);
diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c
index 4aae86b47e..4e88681a79 100644
--- a/drivers/usb/gadget/udc/fsl_qe_udc.c
+++ b/drivers/usb/gadget/udc/fsl_qe_udc.c
@@ -27,9 +27,10 @@
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
+#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
-#include <linux/of_platform.h>
+#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
@@ -2471,17 +2472,12 @@ static const struct of_device_id qe_udc_match[];
static int qe_udc_probe(struct platform_device *ofdev)
{
struct qe_udc *udc;
- const struct of_device_id *match;
struct device_node *np = ofdev->dev.of_node;
struct qe_ep *ep;
unsigned int ret = 0;
unsigned int i;
const void *prop;
- match = of_match_device(qe_udc_match, &ofdev->dev);
- if (!match)
- return -EINVAL;
-
prop = of_get_property(np, "mode", NULL);
if (!prop || strcmp(prop, "peripheral"))
return -ENODEV;
@@ -2493,7 +2489,7 @@ static int qe_udc_probe(struct platform_device *ofdev)
return -ENOMEM;
}
- udc->soc_type = (unsigned long)match->data;
+ udc->soc_type = (unsigned long)device_get_match_data(&ofdev->dev);
udc->usb_regs = of_iomap(np, 0);
if (!udc->usb_regs) {
ret = -ENOMEM;
diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c
index ee5705d336..2693a10eb0 100644
--- a/drivers/usb/gadget/udc/fsl_udc_core.c
+++ b/drivers/usb/gadget/udc/fsl_udc_core.c
@@ -2666,6 +2666,7 @@ static const struct platform_device_id fsl_udc_devtype[] = {
};
MODULE_DEVICE_TABLE(platform, fsl_udc_devtype);
static struct platform_driver udc_driver = {
+ .probe = fsl_udc_probe,
.remove = fsl_udc_remove,
.id_table = fsl_udc_devtype,
/* these suspend and resume are not usb suspend and resume */
@@ -2679,7 +2680,7 @@ static struct platform_driver udc_driver = {
},
};
-module_platform_driver_probe(udc_driver, fsl_udc_probe);
+module_platform_driver(udc_driver);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR(DRIVER_AUTHOR);
diff --git a/drivers/usb/gadget/udc/fusb300_udc.c b/drivers/usb/gadget/udc/fusb300_udc.c
index bd03d475f9..873265634c 100644
--- a/drivers/usb/gadget/udc/fusb300_udc.c
+++ b/drivers/usb/gadget/udc/fusb300_udc.c
@@ -1506,10 +1506,11 @@ clean_up:
}
static struct platform_driver fusb300_driver = {
- .remove_new = fusb300_remove,
- .driver = {
+ .probe = fusb300_probe,
+ .remove_new = fusb300_remove,
+ .driver = {
.name = udc_name,
},
};
-module_platform_driver_probe(fusb300_driver, fusb300_probe);
+module_platform_driver(fusb300_driver);
diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c
index fe62db32dd..a917cc9a32 100644
--- a/drivers/usb/gadget/udc/lpc32xx_udc.c
+++ b/drivers/usb/gadget/udc/lpc32xx_udc.c
@@ -3254,6 +3254,7 @@ MODULE_DEVICE_TABLE(of, lpc32xx_udc_of_match);
#endif
static struct platform_driver lpc32xx_udc_driver = {
+ .probe = lpc32xx_udc_probe,
.remove = lpc32xx_udc_remove,
.shutdown = lpc32xx_udc_shutdown,
.suspend = lpc32xx_udc_suspend,
@@ -3264,7 +3265,7 @@ static struct platform_driver lpc32xx_udc_driver = {
},
};
-module_platform_driver_probe(lpc32xx_udc_driver, lpc32xx_udc_probe);
+module_platform_driver(lpc32xx_udc_driver);
MODULE_DESCRIPTION("LPC32XX udc driver");
MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com>");
diff --git a/drivers/usb/gadget/udc/m66592-udc.c b/drivers/usb/gadget/udc/m66592-udc.c
index e05f45a4b5..bfaa5291e6 100644
--- a/drivers/usb/gadget/udc/m66592-udc.c
+++ b/drivers/usb/gadget/udc/m66592-udc.c
@@ -1687,10 +1687,11 @@ clean_up:
/*-------------------------------------------------------------------------*/
static struct platform_driver m66592_driver = {
+ .probe = m66592_probe,
.remove_new = m66592_remove,
.driver = {
.name = udc_name,
},
};
-module_platform_driver_probe(m66592_driver, m66592_probe);
+module_platform_driver(m66592_driver);
diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c
index 10c5d7f726..f90eeecf27 100644
--- a/drivers/usb/gadget/udc/omap_udc.c
+++ b/drivers/usb/gadget/udc/omap_udc.c
@@ -2036,7 +2036,8 @@ static irqreturn_t omap_udc_iso_irq(int irq, void *_dev)
static inline int machine_without_vbus_sense(void)
{
- return machine_is_omap_osk() || machine_is_sx1();
+ return machine_is_omap_osk() || machine_is_omap_palmte() ||
+ machine_is_sx1();
}
static int omap_udc_start(struct usb_gadget *g,
diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c
index 51b665f15c..db4a10a979 100644
--- a/drivers/usb/gadget/udc/r8a66597-udc.c
+++ b/drivers/usb/gadget/udc/r8a66597-udc.c
@@ -1964,13 +1964,14 @@ clean_up2:
/*-------------------------------------------------------------------------*/
static struct platform_driver r8a66597_driver = {
+ .probe = r8a66597_probe,
.remove_new = r8a66597_remove,
.driver = {
.name = udc_name,
},
};
-module_platform_driver_probe(r8a66597_driver, r8a66597_probe);
+module_platform_driver(r8a66597_driver);
MODULE_DESCRIPTION("R8A66597 USB gadget driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index aac6285b37..631dda6174 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -337,7 +337,7 @@ typedef struct urb_priv {
u16 length; // # tds in this request
u16 td_cnt; // tds already serviced
struct list_head pending;
- struct td *td[]; // all TDs in this request
+ struct td *td[] __counted_by(length); // all TDs in this request
} urb_priv_t;
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index 2665832f9a..1f9c1b1435 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -60,6 +60,23 @@
#define EHCI_USBLEGCTLSTS 4 /* legacy control/status */
#define EHCI_USBLEGCTLSTS_SOOE (1 << 13) /* SMI on ownership change */
+/* ASMEDIA quirk use */
+#define ASMT_DATA_WRITE0_REG 0xF8
+#define ASMT_DATA_WRITE1_REG 0xFC
+#define ASMT_CONTROL_REG 0xE0
+#define ASMT_CONTROL_WRITE_BIT 0x02
+#define ASMT_WRITEREG_CMD 0x10423
+#define ASMT_FLOWCTL_ADDR 0xFA30
+#define ASMT_FLOWCTL_DATA 0xBA
+#define ASMT_PSEUDO_DATA 0
+
+/* Intel quirk use */
+#define USB_INTEL_XUSB2PR 0xD0
+#define USB_INTEL_USB2PRM 0xD4
+#define USB_INTEL_USB3_PSSEN 0xD8
+#define USB_INTEL_USB3PRM 0xDC
+
+#ifdef CONFIG_USB_PCI_AMD
/* AMD quirk use */
#define AB_REG_BAR_LOW 0xe0
#define AB_REG_BAR_HIGH 0xe1
@@ -93,21 +110,6 @@
#define NB_PIF0_PWRDOWN_0 0x01100012
#define NB_PIF0_PWRDOWN_1 0x01100013
-#define USB_INTEL_XUSB2PR 0xD0
-#define USB_INTEL_USB2PRM 0xD4
-#define USB_INTEL_USB3_PSSEN 0xD8
-#define USB_INTEL_USB3PRM 0xDC
-
-/* ASMEDIA quirk use */
-#define ASMT_DATA_WRITE0_REG 0xF8
-#define ASMT_DATA_WRITE1_REG 0xFC
-#define ASMT_CONTROL_REG 0xE0
-#define ASMT_CONTROL_WRITE_BIT 0x02
-#define ASMT_WRITEREG_CMD 0x10423
-#define ASMT_FLOWCTL_ADDR 0xFA30
-#define ASMT_FLOWCTL_DATA 0xBA
-#define ASMT_PSEUDO_DATA 0
-
/*
* amd_chipset_gen values represent AMD different chipset generations
*/
@@ -458,50 +460,6 @@ void usb_amd_quirk_pll_disable(void)
}
EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable);
-static int usb_asmedia_wait_write(struct pci_dev *pdev)
-{
- unsigned long retry_count;
- unsigned char value;
-
- for (retry_count = 1000; retry_count > 0; --retry_count) {
-
- pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value);
-
- if (value == 0xff) {
- dev_err(&pdev->dev, "%s: check_ready ERROR", __func__);
- return -EIO;
- }
-
- if ((value & ASMT_CONTROL_WRITE_BIT) == 0)
- return 0;
-
- udelay(50);
- }
-
- dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__);
- return -ETIMEDOUT;
-}
-
-void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev)
-{
- if (usb_asmedia_wait_write(pdev) != 0)
- return;
-
- /* send command and address to device */
- pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD);
- pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR);
- pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
-
- if (usb_asmedia_wait_write(pdev) != 0)
- return;
-
- /* send data to device */
- pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA);
- pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA);
- pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
-}
-EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol);
-
void usb_amd_quirk_pll_enable(void)
{
usb_amd_quirk_pll(0);
@@ -630,7 +588,62 @@ bool usb_amd_pt_check_port(struct device *device, int port)
return !(value & BIT(port_shift));
}
EXPORT_SYMBOL_GPL(usb_amd_pt_check_port);
+#endif /* CONFIG_USB_PCI_AMD */
+
+static int usb_asmedia_wait_write(struct pci_dev *pdev)
+{
+ unsigned long retry_count;
+ unsigned char value;
+
+ for (retry_count = 1000; retry_count > 0; --retry_count) {
+
+ pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value);
+
+ if (value == 0xff) {
+ dev_err(&pdev->dev, "%s: check_ready ERROR", __func__);
+ return -EIO;
+ }
+
+ if ((value & ASMT_CONTROL_WRITE_BIT) == 0)
+ return 0;
+
+ udelay(50);
+ }
+
+ dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__);
+ return -ETIMEDOUT;
+}
+
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev)
+{
+ if (usb_asmedia_wait_write(pdev) != 0)
+ return;
+ /* send command and address to device */
+ pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD);
+ pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR);
+ pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+
+ if (usb_asmedia_wait_write(pdev) != 0)
+ return;
+
+ /* send data to device */
+ pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA);
+ pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA);
+ pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+}
+EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol);
+
+static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
+{
+ u16 cmd;
+
+ return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
+}
+
+#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
+
+#if defined(CONFIG_HAS_IOPORT) && IS_ENABLED(CONFIG_USB_UHCI_HCD)
/*
* Make sure the controller is completely inactive, unable to
* generate interrupts or do DMA.
@@ -712,14 +725,7 @@ reset_needed:
}
EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
-static inline int io_type_enabled(struct pci_dev *pdev, unsigned int mask)
-{
- u16 cmd;
- return !pci_read_config_word(pdev, PCI_COMMAND, &cmd) && (cmd & mask);
-}
-
#define pio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_IO)
-#define mmio_enabled(dev) io_type_enabled(dev, PCI_COMMAND_MEMORY)
static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
{
@@ -739,6 +745,12 @@ static void quirk_usb_handoff_uhci(struct pci_dev *pdev)
uhci_check_and_reset_hc(pdev, base);
}
+#else /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */
+
+static void quirk_usb_handoff_uhci(struct pci_dev *pdev) {}
+
+#endif /* defined(CONFIG_HAS_IOPORT && IS_ENABLED(CONFIG_USB_UHCI_HCD) */
+
static int mmio_resource_enabled(struct pci_dev *pdev, int idx)
{
return pci_resource_start(pdev, idx) && mmio_enabled(pdev);
diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h
index e729de21fa..a5230b0b9e 100644
--- a/drivers/usb/host/pci-quirks.h
+++ b/drivers/usb/host/pci-quirks.h
@@ -2,9 +2,7 @@
#ifndef __LINUX_USB_PCI_QUIRKS_H
#define __LINUX_USB_PCI_QUIRKS_H
-#ifdef CONFIG_USB_PCI
-void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
-int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+#ifdef CONFIG_USB_PCI_AMD
int usb_hcd_amd_remote_wakeup_quirk(struct pci_dev *pdev);
bool usb_amd_hang_symptom_quirk(void);
bool usb_amd_prefetch_quirk(void);
@@ -12,23 +10,41 @@ void usb_amd_dev_put(void);
bool usb_amd_quirk_pll_check(void);
void usb_amd_quirk_pll_disable(void);
void usb_amd_quirk_pll_enable(void);
-void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
-void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
-void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
void sb800_prefetch(struct device *dev, int on);
bool usb_amd_pt_check_port(struct device *device, int port);
#else
-struct pci_dev;
+static inline bool usb_amd_hang_symptom_quirk(void)
+{
+ return false;
+};
+static inline bool usb_amd_prefetch_quirk(void)
+{
+ return false;
+}
static inline void usb_amd_quirk_pll_disable(void) {}
static inline void usb_amd_quirk_pll_enable(void) {}
-static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
static inline void usb_amd_dev_put(void) {}
-static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
+static inline bool usb_amd_quirk_pll_check(void)
+{
+ return false;
+}
static inline void sb800_prefetch(struct device *dev, int on) {}
static inline bool usb_amd_pt_check_port(struct device *device, int port)
{
return false;
}
+#endif /* CONFIG_USB_PCI_AMD */
+
+#ifdef CONFIG_USB_PCI
+void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
+int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
+void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
+void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
+#else
+struct pci_dev;
+static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
+static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
#endif /* CONFIG_USB_PCI */
#endif /* __LINUX_USB_PCI_QUIRKS_H */
diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c
index 99baa60ef5..6d142cd61b 100644
--- a/drivers/usb/host/xhci-debugfs.c
+++ b/drivers/usb/host/xhci-debugfs.c
@@ -204,7 +204,7 @@ static void xhci_ring_dump_segment(struct seq_file *s,
for (i = 0; i < TRBS_PER_SEGMENT; i++) {
trb = &seg->trbs[i];
dma = seg->dma + i * sizeof(*trb);
- seq_printf(s, "%pad: %s\n", &dma,
+ seq_printf(s, "%2u %pad: %s\n", seg->num, &dma,
xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]),
le32_to_cpu(trb->generic.field[1]),
le32_to_cpu(trb->generic.field[2]),
diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index e8af0a125f..96eb36a587 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -79,6 +79,33 @@
/* true: Controller Not Ready to accept doorbell or op reg writes after reset */
#define XHCI_STS_CNR (1 << 11)
+/**
+ * struct xhci_protocol_caps
+ * @revision: major revision, minor revision, capability ID,
+ * and next capability pointer.
+ * @name_string: Four ASCII characters to say which spec this xHC
+ * follows, typically "USB ".
+ * @port_info: Port offset, count, and protocol-defined information.
+ */
+struct xhci_protocol_caps {
+ u32 revision;
+ u32 name_string;
+ u32 port_info;
+};
+
+#define XHCI_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff)
+#define XHCI_EXT_PORT_MINOR(x) (((x) >> 16) & 0xff)
+#define XHCI_EXT_PORT_PSIC(x) (((x) >> 28) & 0x0f)
+#define XHCI_EXT_PORT_OFF(x) ((x) & 0xff)
+#define XHCI_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff)
+
+#define XHCI_EXT_PORT_PSIV(x) (((x) >> 0) & 0x0f)
+#define XHCI_EXT_PORT_PSIE(x) (((x) >> 4) & 0x03)
+#define XHCI_EXT_PORT_PLT(x) (((x) >> 6) & 0x03)
+#define XHCI_EXT_PORT_PFD(x) (((x) >> 8) & 0x01)
+#define XHCI_EXT_PORT_LP(x) (((x) >> 14) & 0x03)
+#define XHCI_EXT_PORT_PSIM(x) (((x) >> 16) & 0xffff)
+
#include <linux/io.h>
/**
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 0df5d807a7..0980ade2a2 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1262,7 +1262,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
retval = -ENODEV;
break;
}
- trace_xhci_get_port_status(wIndex, temp);
+ trace_xhci_get_port_status(port, temp);
status = xhci_get_port_status(hcd, bus_state, wIndex, temp,
&flags);
if (status == 0xffffffff)
@@ -1687,7 +1687,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
retval = -ENODEV;
break;
}
- trace_xhci_hub_status_data(i, temp);
+ trace_xhci_hub_status_data(ports[i], temp);
if ((temp & mask) != 0 ||
(bus_state->port_c_suspend & 1 << i) ||
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 0a37f0d511..6211658684 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -29,6 +29,7 @@
static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci,
unsigned int cycle_state,
unsigned int max_packet,
+ unsigned int num,
gfp_t flags)
{
struct xhci_segment *seg;
@@ -60,6 +61,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci,
for (i = 0; i < TRBS_PER_SEGMENT; i++)
seg->trbs[i].link.control = cpu_to_le32(TRB_CYCLE);
}
+ seg->num = num;
seg->dma = dma;
seg->next = NULL;
@@ -128,7 +130,7 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
struct xhci_segment *first, struct xhci_segment *last,
unsigned int num_segs)
{
- struct xhci_segment *next;
+ struct xhci_segment *next, *seg;
bool chain_links;
if (!ring || !first || !last)
@@ -144,13 +146,18 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
xhci_link_segments(last, next, ring->type, chain_links);
ring->num_segs += num_segs;
- if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) {
- ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
- &= ~cpu_to_le32(LINK_TOGGLE);
- last->trbs[TRBS_PER_SEGMENT-1].link.control
- |= cpu_to_le32(LINK_TOGGLE);
+ if (ring->enq_seg == ring->last_seg) {
+ if (ring->type != TYPE_EVENT) {
+ ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
+ &= ~cpu_to_le32(LINK_TOGGLE);
+ last->trbs[TRBS_PER_SEGMENT-1].link.control
+ |= cpu_to_le32(LINK_TOGGLE);
+ }
ring->last_seg = last;
}
+
+ for (seg = last; seg != ring->last_seg; seg = seg->next)
+ seg->next->num = seg->num + 1;
}
/*
@@ -320,8 +327,9 @@ void xhci_initialize_ring_info(struct xhci_ring *ring,
/* Allocate segments and link them for a ring */
static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
struct xhci_segment **first, struct xhci_segment **last,
- unsigned int num_segs, unsigned int cycle_state,
- enum xhci_ring_type type, unsigned int max_packet, gfp_t flags)
+ unsigned int num_segs, unsigned int num,
+ unsigned int cycle_state, enum xhci_ring_type type,
+ unsigned int max_packet, gfp_t flags)
{
struct xhci_segment *prev;
bool chain_links;
@@ -331,16 +339,17 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
(type == TYPE_ISOC &&
(xhci->quirks & XHCI_AMD_0x96_HOST)));
- prev = xhci_segment_alloc(xhci, cycle_state, max_packet, flags);
+ prev = xhci_segment_alloc(xhci, cycle_state, max_packet, num, flags);
if (!prev)
return -ENOMEM;
- num_segs--;
+ num++;
*first = prev;
- while (num_segs > 0) {
+ while (num < num_segs) {
struct xhci_segment *next;
- next = xhci_segment_alloc(xhci, cycle_state, max_packet, flags);
+ next = xhci_segment_alloc(xhci, cycle_state, max_packet, num,
+ flags);
if (!next) {
prev = *first;
while (prev) {
@@ -353,7 +362,7 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
xhci_link_segments(prev, next, type, chain_links);
prev = next;
- num_segs--;
+ num++;
}
xhci_link_segments(prev, *first, type, chain_links);
*last = prev;
@@ -388,7 +397,7 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
return ring;
ret = xhci_alloc_segments_for_ring(xhci, &ring->first_seg,
- &ring->last_seg, num_segs, cycle_state, type,
+ &ring->last_seg, num_segs, 0, cycle_state, type,
max_packet, flags);
if (ret)
goto fail;
@@ -428,7 +437,8 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
int ret;
ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
- num_new_segs, ring->cycle_state, ring->type,
+ num_new_segs, ring->enq_seg->num + 1,
+ ring->cycle_state, ring->type,
ring->bounce_buf_len, flags);
if (ret)
return -ENOMEM;
@@ -1766,7 +1776,7 @@ void xhci_free_command(struct xhci_hcd *xhci,
kfree(command);
}
-int xhci_alloc_erst(struct xhci_hcd *xhci,
+static int xhci_alloc_erst(struct xhci_hcd *xhci,
struct xhci_ring *evt_ring,
struct xhci_erst *erst,
gfp_t flags)
@@ -1797,23 +1807,13 @@ int xhci_alloc_erst(struct xhci_hcd *xhci,
}
static void
-xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
+xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
{
- struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
- size_t erst_size;
- u64 tmp64;
u32 tmp;
if (!ir)
return;
- erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries;
- if (ir->erst.entries)
- dma_free_coherent(dev, erst_size,
- ir->erst.entries,
- ir->erst.erst_dma_addr);
- ir->erst.entries = NULL;
-
/*
* Clean out interrupter registers except ERSTBA. Clearing either the
* low or high 32 bits of ERSTBA immediately causes the controller to
@@ -1824,14 +1824,30 @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
tmp &= ERST_SIZE_MASK;
writel(tmp, &ir->ir_set->erst_size);
- tmp64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
- tmp64 &= (u64) ERST_PTR_MASK;
- xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue);
+ xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue);
}
+}
- /* free interrrupter event ring */
+static void
+xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
+{
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ size_t erst_size;
+
+ if (!ir)
+ return;
+
+ erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries;
+ if (ir->erst.entries)
+ dma_free_coherent(dev, erst_size,
+ ir->erst.entries,
+ ir->erst.erst_dma_addr);
+ ir->erst.entries = NULL;
+
+ /* free interrupter event ring */
if (ir->event_ring)
xhci_ring_free(xhci, ir->event_ring);
+
ir->event_ring = NULL;
kfree(ir);
@@ -1844,6 +1860,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
cancel_delayed_work_sync(&xhci->cmd_timer);
+ xhci_remove_interrupter(xhci, xhci->interrupter);
xhci_free_interrupter(xhci, xhci->interrupter);
xhci->interrupter = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring");
@@ -1933,7 +1950,6 @@ no_bw:
static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
{
- u64 temp;
dma_addr_t deq;
deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg,
@@ -1941,16 +1957,12 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter
if (!deq)
xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr.\n");
/* Update HC event ring dequeue pointer */
- temp = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
- temp &= ERST_PTR_MASK;
/* Don't clear the EHB bit (which is RW1C) because
* there might be more events to service.
*/
- temp &= ~ERST_EHB;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"// Write event ring dequeue pointer, preserving EHB bit");
- xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp,
- &ir->ir_set->erst_dequeue);
+ xhci_write_64(xhci, deq & ERST_PTR_MASK, &ir->ir_set->erst_dequeue);
}
static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports,
@@ -2238,14 +2250,18 @@ xhci_alloc_interrupter(struct xhci_hcd *xhci, gfp_t flags)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
struct xhci_interrupter *ir;
+ unsigned int num_segs;
int ret;
ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
if (!ir)
return NULL;
- ir->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT,
- 0, flags);
+ num_segs = min_t(unsigned int, 1 << HCS_ERST_MAX(xhci->hcs_params2),
+ ERST_MAX_SEGS);
+
+ ir->event_ring = xhci_ring_alloc(xhci, num_segs, 1, TYPE_EVENT, 0,
+ flags);
if (!ir->event_ring) {
xhci_warn(xhci, "Failed to allocate interrupter event ring\n");
kfree(ir);
@@ -2281,7 +2297,7 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
/* set ERST count with the number of entries in the segment table */
erst_size = readl(&ir->ir_set->erst_size);
erst_size &= ERST_SIZE_MASK;
- erst_size |= ERST_NUM_SEGS;
+ erst_size |= ir->event_ring->num_segs;
writel(erst_size, &ir->ir_set->erst_size);
erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c
index 579899eb24..61f3f8bbdc 100644
--- a/drivers/usb/host/xhci-mtk-sch.c
+++ b/drivers/usb/host/xhci-mtk-sch.c
@@ -19,6 +19,18 @@
#define HS_BW_BOUNDARY 6144
/* usb2 spec section11.18.1: at most 188 FS bytes per microframe */
#define FS_PAYLOAD_MAX 188
+#define LS_PAYLOAD_MAX 18
+/* section 11.18.1, per fs frame */
+#define FS_BW_BOUNDARY 1157
+#define LS_BW_BOUNDARY 144
+
+/*
+ * max number of microframes for split transfer, assume extra-cs budget is 0
+ * for fs isoc in : 1 ss + 1 idle + 6 cs (roundup(1023/188))
+ */
+#define TT_MICROFRAMES_MAX 8
+/* offset from SS for fs/ls isoc/intr ep (ss + idle) */
+#define CS_OFFSET 2
#define DBG_BUF_EN 64
@@ -237,17 +249,26 @@ static void drop_tt(struct usb_device *udev)
static struct mu3h_sch_ep_info *
create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+ struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx)
{
struct mu3h_sch_ep_info *sch_ep;
struct mu3h_sch_bw_info *bw_info;
struct mu3h_sch_tt *tt = NULL;
+ u32 len;
bw_info = get_bw_info(mtk, udev, ep);
if (!bw_info)
return ERR_PTR(-ENODEV);
- sch_ep = kzalloc(sizeof(*sch_ep), GFP_KERNEL);
+ if (is_fs_or_ls(udev->speed))
+ len = TT_MICROFRAMES_MAX;
+ else if ((udev->speed >= USB_SPEED_SUPER) &&
+ usb_endpoint_xfer_isoc(&ep->desc))
+ len = get_esit(ep_ctx);
+ else
+ len = 1;
+
+ sch_ep = kzalloc(struct_size(sch_ep, bw_budget_table, len), GFP_KERNEL);
if (!sch_ep)
return ERR_PTR(-ENOMEM);
@@ -279,7 +300,11 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
u32 mult;
u32 esit_pkts;
u32 max_esit_payload;
+ u32 bw_per_microframe;
+ u32 *bwb_table;
+ int i;
+ bwb_table = sch_ep->bw_budget_table;
ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2));
maxpkt = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2));
max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2));
@@ -313,7 +338,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
* opportunities per microframe
*/
sch_ep->pkts = max_burst + 1;
- sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
+ bwb_table[0] = maxpkt * sch_ep->pkts;
} else if (sch_ep->speed >= USB_SPEED_SUPER) {
/* usb3_r1 spec section4.4.7 & 4.4.8 */
sch_ep->cs_count = 0;
@@ -330,6 +355,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) {
sch_ep->pkts = esit_pkts;
sch_ep->num_budget_microframes = 1;
+ bwb_table[0] = maxpkt * sch_ep->pkts;
}
if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) {
@@ -346,18 +372,52 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx,
DIV_ROUND_UP(esit_pkts, sch_ep->pkts);
sch_ep->repeat = !!(sch_ep->num_budget_microframes > 1);
+ bw_per_microframe = maxpkt * sch_ep->pkts;
+
+ for (i = 0; i < sch_ep->num_budget_microframes - 1; i++)
+ bwb_table[i] = bw_per_microframe;
+
+ /* last one <= bw_per_microframe */
+ bwb_table[i] = maxpkt * esit_pkts - i * bw_per_microframe;
}
- sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts;
} else if (is_fs_or_ls(sch_ep->speed)) {
sch_ep->pkts = 1; /* at most one packet for each microframe */
/*
- * num_budget_microframes and cs_count will be updated when
+ * @cs_count will be updated to add extra-cs when
* check TT for INT_OUT_EP, ISOC/INT_IN_EP type
+ * @maxpkt <= 1023;
*/
sch_ep->cs_count = DIV_ROUND_UP(maxpkt, FS_PAYLOAD_MAX);
sch_ep->num_budget_microframes = sch_ep->cs_count;
- sch_ep->bw_cost_per_microframe = min_t(u32, maxpkt, FS_PAYLOAD_MAX);
+
+ /* init budget table */
+ if (ep_type == ISOC_OUT_EP) {
+ for (i = 0; i < sch_ep->cs_count - 1; i++)
+ bwb_table[i] = FS_PAYLOAD_MAX;
+
+ bwb_table[i] = maxpkt - i * FS_PAYLOAD_MAX;
+ } else if (ep_type == INT_OUT_EP) {
+ /* only first one used (maxpkt <= 64), others zero */
+ bwb_table[0] = maxpkt;
+ } else { /* INT_IN_EP or ISOC_IN_EP */
+ bwb_table[0] = 0; /* start split */
+ bwb_table[1] = 0; /* idle */
+ /*
+ * @cs_count will be updated according to cs position
+ * (add 1 or 2 extra-cs), but assume only first
+ * @num_budget_microframes elements will be used later,
+ * although in fact it does not (extra-cs budget many receive
+ * some data for IN ep);
+ * @cs_count is 1 for INT_IN_EP (maxpkt <= 64);
+ */
+ for (i = 0; i < sch_ep->cs_count - 1; i++)
+ bwb_table[i + CS_OFFSET] = FS_PAYLOAD_MAX;
+
+ bwb_table[i + CS_OFFSET] = maxpkt - i * FS_PAYLOAD_MAX;
+ /* ss + idle */
+ sch_ep->num_budget_microframes += CS_OFFSET;
+ }
}
}
@@ -374,7 +434,7 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
for (j = 0; j < sch_ep->num_budget_microframes; j++) {
k = XHCI_MTK_BW_INDEX(base + j);
- bw = sch_bw->bus_bw[k] + sch_ep->bw_cost_per_microframe;
+ bw = sch_bw->bus_bw[k] + sch_ep->bw_budget_table[j];
if (bw > max_bw)
max_bw = bw;
}
@@ -382,56 +442,152 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw,
return max_bw;
}
+/*
+ * for OUT: get first SS consumed bw;
+ * for IN: get first CS consumed bw;
+ */
+static u16 get_fs_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+ struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+ u16 fs_bw;
+
+ if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+ fs_bw = tt->fs_bus_bw_out[XHCI_MTK_BW_INDEX(offset)];
+ else /* skip ss + idle */
+ fs_bw = tt->fs_bus_bw_in[XHCI_MTK_BW_INDEX(offset + CS_OFFSET)];
+
+ return fs_bw;
+}
+
static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw,
struct mu3h_sch_ep_info *sch_ep, bool used)
{
- int bw_updated;
u32 base;
- int i, j;
-
- bw_updated = sch_ep->bw_cost_per_microframe * (used ? 1 : -1);
+ int i, j, k;
for (i = 0; i < sch_ep->num_esit; i++) {
base = sch_ep->offset + i * sch_ep->esit;
- for (j = 0; j < sch_ep->num_budget_microframes; j++)
- sch_bw->bus_bw[XHCI_MTK_BW_INDEX(base + j)] += bw_updated;
+ for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+ k = XHCI_MTK_BW_INDEX(base + j);
+ if (used)
+ sch_bw->bus_bw[k] += sch_ep->bw_budget_table[j];
+ else
+ sch_bw->bus_bw[k] -= sch_ep->bw_budget_table[j];
+ }
}
}
-static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+static int check_ls_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+ struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+ int i;
+
+ if (sch_ep->speed != USB_SPEED_LOW)
+ return 0;
+
+ if (sch_ep->ep_type == INT_OUT_EP)
+ i = XHCI_MTK_BW_INDEX(offset);
+ else if (sch_ep->ep_type == INT_IN_EP)
+ i = XHCI_MTK_BW_INDEX(offset + CS_OFFSET); /* skip ss + idle */
+ else
+ return -EINVAL;
+
+ if (tt->ls_bus_bw[i] + sch_ep->maxpkt > LS_PAYLOAD_MAX)
+ return -ESCH_BW_OVERFLOW;
+
+ return 0;
+}
+
+static int check_fs_budget_microframes(struct mu3h_sch_ep_info *sch_ep, int offset)
{
struct mu3h_sch_tt *tt = sch_ep->sch_tt;
u32 tmp;
- int base;
+ int i, k;
+
+ /*
+ * for OUT eps, will transfer exactly assigned length of data,
+ * so can't allocate more than 188 bytes;
+ * but it's not for IN eps, usually it can't receive full
+ * 188 bytes in a uframe, if it not assign full 188 bytes,
+ * can add another one;
+ */
+ for (i = 0; i < sch_ep->num_budget_microframes; i++) {
+ k = XHCI_MTK_BW_INDEX(offset + i);
+ if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+ tmp = tt->fs_bus_bw_out[k] + sch_ep->bw_budget_table[i];
+ else /* ep_type : ISOC IN / INTR IN */
+ tmp = tt->fs_bus_bw_in[k];
+
+ if (tmp > FS_PAYLOAD_MAX)
+ return -ESCH_BW_OVERFLOW;
+ }
+
+ return 0;
+}
+
+static int check_fs_budget_frames(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+ struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+ u32 head, tail;
int i, j, k;
+ /* bugdet scheduled may cross at most two fs frames */
+ j = XHCI_MTK_BW_INDEX(offset) / UFRAMES_PER_FRAME;
+ k = XHCI_MTK_BW_INDEX(offset + sch_ep->num_budget_microframes - 1) / UFRAMES_PER_FRAME;
+
+ if (j != k) {
+ head = tt->fs_frame_bw[j];
+ tail = tt->fs_frame_bw[k];
+ } else {
+ head = tt->fs_frame_bw[j];
+ tail = 0;
+ }
+
+ j = roundup(offset, UFRAMES_PER_FRAME);
+ for (i = 0; i < sch_ep->num_budget_microframes; i++) {
+ if ((offset + i) < j)
+ head += sch_ep->bw_budget_table[i];
+ else
+ tail += sch_ep->bw_budget_table[i];
+ }
+
+ if (head > FS_BW_BOUNDARY || tail > FS_BW_BOUNDARY)
+ return -ESCH_BW_OVERFLOW;
+
+ return 0;
+}
+
+static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset)
+{
+ int i, base;
+ int ret = 0;
+
for (i = 0; i < sch_ep->num_esit; i++) {
base = offset + i * sch_ep->esit;
- /*
- * Compared with hs bus, no matter what ep type,
- * the hub will always delay one uframe to send data
- */
- for (j = 0; j < sch_ep->num_budget_microframes; j++) {
- k = XHCI_MTK_BW_INDEX(base + j);
- tmp = tt->fs_bus_bw[k] + sch_ep->bw_cost_per_microframe;
- if (tmp > FS_PAYLOAD_MAX)
- return -ESCH_BW_OVERFLOW;
- }
+ ret = check_ls_budget_microframes(sch_ep, base);
+ if (ret)
+ goto err;
+
+ ret = check_fs_budget_microframes(sch_ep, base);
+ if (ret)
+ goto err;
+
+ ret = check_fs_budget_frames(sch_ep, base);
+ if (ret)
+ goto err;
}
- return 0;
+err:
+ return ret;
}
-static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+static int check_ss_and_cs(struct mu3h_sch_ep_info *sch_ep, u32 offset)
{
u32 start_ss, last_ss;
u32 start_cs, last_cs;
- if (!sch_ep->sch_tt)
- return 0;
-
- start_ss = offset % 8;
+ start_ss = offset % UFRAMES_PER_FRAME;
if (sch_ep->ep_type == ISOC_OUT_EP) {
last_ss = start_ss + sch_ep->cs_count - 1;
@@ -444,6 +600,7 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
return -ESCH_SS_Y6;
} else {
+ /* maxpkt <= 1023, cs <= 6 */
u32 cs_count = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX);
/*
@@ -454,44 +611,171 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset)
return -ESCH_SS_Y6;
/* one uframe for ss + one uframe for idle */
- start_cs = (start_ss + 2) % 8;
+ start_cs = (start_ss + CS_OFFSET) % UFRAMES_PER_FRAME;
last_cs = start_cs + cs_count - 1;
-
if (last_cs > 7)
return -ESCH_CS_OVERFLOW;
+ /* add extra-cs */
+ cs_count += (last_cs == 7) ? 1 : 2;
if (cs_count > 7)
cs_count = 7; /* HW limit */
sch_ep->cs_count = cs_count;
- /* ss, idle are ignored */
- sch_ep->num_budget_microframes = cs_count;
- /*
- * if interval=1, maxp >752, num_budge_micoframe is larger
- * than sch_ep->esit, will overstep boundary
- */
- if (sch_ep->num_budget_microframes > sch_ep->esit)
- sch_ep->num_budget_microframes = sch_ep->esit;
}
+ return 0;
+}
+
+/*
+ * when isoc-out transfers 188 bytes in a uframe, and send isoc/intr's
+ * ss token in the uframe, may cause 'bit stuff error' in downstream
+ * port;
+ * when isoc-out transfer less than 188 bytes in a uframe, shall send
+ * isoc-in's ss after isoc-out's ss (but hw can't ensure the sequence,
+ * so just avoid overlap).
+ */
+static int check_isoc_ss_overlap(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+{
+ struct mu3h_sch_tt *tt = sch_ep->sch_tt;
+ int base;
+ int i, j, k;
+
+ if (!tt)
+ return 0;
+
+ for (i = 0; i < sch_ep->num_esit; i++) {
+ base = offset + i * sch_ep->esit;
+
+ if (sch_ep->ep_type == ISOC_OUT_EP) {
+ for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+ k = XHCI_MTK_BW_INDEX(base + j);
+ if (tt->in_ss_cnt[k])
+ return -ESCH_SS_OVERLAP;
+ }
+ } else if (sch_ep->ep_type == ISOC_IN_EP || sch_ep->ep_type == INT_IN_EP) {
+ k = XHCI_MTK_BW_INDEX(base);
+ /* only check IN's ss */
+ if (tt->fs_bus_bw_out[k])
+ return -ESCH_SS_OVERLAP;
+ }
+ }
+
+ return 0;
+}
+
+static int check_sch_tt_budget(struct mu3h_sch_ep_info *sch_ep, u32 offset)
+{
+ int ret;
+
+ ret = check_ss_and_cs(sch_ep, offset);
+ if (ret)
+ return ret;
+
+ ret = check_isoc_ss_overlap(sch_ep, offset);
+ if (ret)
+ return ret;
+
return check_fs_bus_bw(sch_ep, offset);
}
+/* allocate microframes in the ls/fs frame */
+static int alloc_sch_portion_of_frame(struct mu3h_sch_ep_info *sch_ep)
+{
+ struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info;
+ const u32 bw_boundary = get_bw_boundary(sch_ep->speed);
+ u32 bw_max, fs_bw_min;
+ u32 offset, offset_min;
+ u16 fs_bw;
+ int frames;
+ int i, j;
+ int ret;
+
+ frames = sch_ep->esit / UFRAMES_PER_FRAME;
+
+ for (i = 0; i < UFRAMES_PER_FRAME; i++) {
+ fs_bw_min = FS_PAYLOAD_MAX;
+ offset_min = XHCI_MTK_MAX_ESIT;
+
+ for (j = 0; j < frames; j++) {
+ offset = (i + j * UFRAMES_PER_FRAME) % sch_ep->esit;
+
+ ret = check_sch_tt_budget(sch_ep, offset);
+ if (ret)
+ continue;
+
+ /* check hs bw domain */
+ bw_max = get_max_bw(sch_bw, sch_ep, offset);
+ if (bw_max > bw_boundary) {
+ ret = -ESCH_BW_OVERFLOW;
+ continue;
+ }
+
+ /* use best-fit between frames */
+ fs_bw = get_fs_bw(sch_ep, offset);
+ if (fs_bw < fs_bw_min) {
+ fs_bw_min = fs_bw;
+ offset_min = offset;
+ }
+
+ if (!fs_bw_min)
+ break;
+ }
+
+ /* use first-fit between microframes in a frame */
+ if (offset_min < XHCI_MTK_MAX_ESIT)
+ break;
+ }
+
+ if (offset_min == XHCI_MTK_MAX_ESIT)
+ return -ESCH_BW_OVERFLOW;
+
+ sch_ep->offset = offset_min;
+
+ return 0;
+}
+
static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used)
{
struct mu3h_sch_tt *tt = sch_ep->sch_tt;
- int bw_updated;
+ u16 *fs_bus_bw;
u32 base;
- int i, j;
+ int i, j, k, f;
- bw_updated = sch_ep->bw_cost_per_microframe * (used ? 1 : -1);
+ if (sch_ep->ep_type == ISOC_OUT_EP || sch_ep->ep_type == INT_OUT_EP)
+ fs_bus_bw = tt->fs_bus_bw_out;
+ else
+ fs_bus_bw = tt->fs_bus_bw_in;
for (i = 0; i < sch_ep->num_esit; i++) {
base = sch_ep->offset + i * sch_ep->esit;
- for (j = 0; j < sch_ep->num_budget_microframes; j++)
- tt->fs_bus_bw[XHCI_MTK_BW_INDEX(base + j)] += bw_updated;
+ for (j = 0; j < sch_ep->num_budget_microframes; j++) {
+ k = XHCI_MTK_BW_INDEX(base + j);
+ f = k / UFRAMES_PER_FRAME;
+ if (used) {
+ if (sch_ep->speed == USB_SPEED_LOW)
+ tt->ls_bus_bw[k] += (u8)sch_ep->bw_budget_table[j];
+
+ fs_bus_bw[k] += (u16)sch_ep->bw_budget_table[j];
+ tt->fs_frame_bw[f] += (u16)sch_ep->bw_budget_table[j];
+ } else {
+ if (sch_ep->speed == USB_SPEED_LOW)
+ tt->ls_bus_bw[k] -= (u8)sch_ep->bw_budget_table[j];
+
+ fs_bus_bw[k] -= (u16)sch_ep->bw_budget_table[j];
+ tt->fs_frame_bw[f] -= (u16)sch_ep->bw_budget_table[j];
+ }
+ }
+
+ if (sch_ep->ep_type == ISOC_IN_EP || sch_ep->ep_type == INT_IN_EP) {
+ k = XHCI_MTK_BW_INDEX(base);
+ if (used)
+ tt->in_ss_cnt[k]++;
+ else
+ tt->in_ss_cnt[k]--;
+ }
}
if (used)
@@ -513,7 +797,8 @@ static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw,
return 0;
}
-static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
+/* allocate microframes for hs/ss/ssp */
+static int alloc_sch_microframes(struct mu3h_sch_ep_info *sch_ep)
{
struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info;
const u32 bw_boundary = get_bw_boundary(sch_ep->speed);
@@ -521,16 +806,12 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
u32 worst_bw;
u32 min_bw = ~0;
int min_index = -1;
- int ret = 0;
/*
* Search through all possible schedule microframes.
* and find a microframe where its worst bandwidth is minimum.
*/
for (offset = 0; offset < sch_ep->esit; offset++) {
- ret = check_sch_tt(sch_ep, offset);
- if (ret)
- continue;
worst_bw = get_max_bw(sch_bw, sch_ep, offset);
if (worst_bw > bw_boundary)
@@ -540,21 +821,29 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
min_bw = worst_bw;
min_index = offset;
}
-
- /* use first-fit for LS/FS */
- if (sch_ep->sch_tt && min_index >= 0)
- break;
-
- if (min_bw == 0)
- break;
}
if (min_index < 0)
- return ret ? ret : -ESCH_BW_OVERFLOW;
+ return -ESCH_BW_OVERFLOW;
sch_ep->offset = min_index;
- return load_ep_bw(sch_bw, sch_ep, true);
+ return 0;
+}
+
+static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep)
+{
+ int ret;
+
+ if (sch_ep->sch_tt)
+ ret = alloc_sch_portion_of_frame(sch_ep);
+ else
+ ret = alloc_sch_microframes(sch_ep);
+
+ if (ret)
+ return ret;
+
+ return load_ep_bw(sch_ep->bw_info, sch_ep, true);
}
static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
@@ -651,7 +940,7 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed));
- sch_ep = create_sch_ep(mtk, udev, ep);
+ sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx);
if (IS_ERR_OR_NULL(sch_ep))
return -ENOMEM;
diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h
index ac042077db..f5e2bd66bb 100644
--- a/drivers/usb/host/xhci-mtk.h
+++ b/drivers/usb/host/xhci-mtk.h
@@ -30,12 +30,23 @@
#define XHCI_MTK_MAX_ESIT (1 << 6)
#define XHCI_MTK_BW_INDEX(x) ((x) & (XHCI_MTK_MAX_ESIT - 1))
+#define UFRAMES_PER_FRAME 8
+#define XHCI_MTK_FRAMES_CNT (XHCI_MTK_MAX_ESIT / UFRAMES_PER_FRAME)
+
/**
- * @fs_bus_bw: array to keep track of bandwidth already used for FS
+ * @fs_bus_bw_out: save bandwidth used by FS/LS OUT eps in each uframes
+ * @fs_bus_bw_in: save bandwidth used by FS/LS IN eps in each uframes
+ * @ls_bus_bw: save bandwidth used by LS eps in each uframes
+ * @fs_frame_bw: save bandwidth used by FS/LS eps in each FS frames
+ * @in_ss_cnt: the count of Start-Split for IN eps
* @ep_list: Endpoints using this TT
*/
struct mu3h_sch_tt {
- u32 fs_bus_bw[XHCI_MTK_MAX_ESIT];
+ u16 fs_bus_bw_out[XHCI_MTK_MAX_ESIT];
+ u16 fs_bus_bw_in[XHCI_MTK_MAX_ESIT];
+ u8 ls_bus_bw[XHCI_MTK_MAX_ESIT];
+ u16 fs_frame_bw[XHCI_MTK_FRAMES_CNT];
+ u8 in_ss_cnt[XHCI_MTK_MAX_ESIT];
struct list_head ep_list;
};
@@ -58,7 +69,6 @@ struct mu3h_sch_bw_info {
* @num_esit: number of @esit in a period
* @num_budget_microframes: number of continuous uframes
* (@repeat==1) scheduled within the interval
- * @bw_cost_per_microframe: bandwidth cost per microframe
* @hentry: hash table entry
* @endpoint: linked into bandwidth domain which it belongs to
* @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to
@@ -83,12 +93,12 @@ struct mu3h_sch_bw_info {
* times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets
* according to @pkts and @repeat. normal mode is used by
* default
+ * @bw_budget_table: table to record bandwidth budget per microframe
*/
struct mu3h_sch_ep_info {
u32 esit;
u32 num_esit;
u32 num_budget_microframes;
- u32 bw_cost_per_microframe;
struct list_head endpoint;
struct hlist_node hentry;
struct list_head tt_endpoint;
@@ -108,6 +118,7 @@ struct mu3h_sch_ep_info {
u32 pkts;
u32 cs_count;
u32 burst_mode;
+ u32 bw_budget_table[];
};
#define MU3C_U3_PORT_MAX 4
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 732cdeb739..d68e9abcdc 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -250,6 +250,9 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
if (device_property_read_bool(tmpdev, "quirk-broken-port-ped"))
xhci->quirks |= XHCI_BROKEN_PORT_PED;
+ if (device_property_read_bool(tmpdev, "xhci-sg-trb-cache-size-quirk"))
+ xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK;
+
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
}
@@ -433,7 +436,7 @@ void xhci_plat_remove(struct platform_device *dev)
}
EXPORT_SYMBOL_GPL(xhci_plat_remove);
-static int __maybe_unused xhci_plat_suspend(struct device *dev)
+static int xhci_plat_suspend(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
@@ -461,7 +464,7 @@ static int __maybe_unused xhci_plat_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused xhci_plat_resume(struct device *dev)
+static int xhci_plat_resume_common(struct device *dev, struct pm_message pmsg)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
@@ -483,7 +486,7 @@ static int __maybe_unused xhci_plat_resume(struct device *dev)
if (ret)
goto disable_clks;
- ret = xhci_resume(xhci, PMSG_RESUME);
+ ret = xhci_resume(xhci, pmsg);
if (ret)
goto disable_clks;
@@ -502,6 +505,16 @@ disable_clks:
return ret;
}
+static int xhci_plat_resume(struct device *dev)
+{
+ return xhci_plat_resume_common(dev, PMSG_RESUME);
+}
+
+static int xhci_plat_restore(struct device *dev)
+{
+ return xhci_plat_resume_common(dev, PMSG_RESTORE);
+}
+
static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev)
{
struct usb_hcd *hcd = dev_get_drvdata(dev);
@@ -524,7 +537,12 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
}
const struct dev_pm_ops xhci_plat_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
+ .suspend = pm_sleep_ptr(xhci_plat_suspend),
+ .resume = pm_sleep_ptr(xhci_plat_resume),
+ .freeze = pm_sleep_ptr(xhci_plat_suspend),
+ .thaw = pm_sleep_ptr(xhci_plat_resume),
+ .poweroff = pm_sleep_ptr(xhci_plat_suspend),
+ .restore = pm_sleep_ptr(xhci_plat_restore),
SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
xhci_plat_runtime_resume,
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 3e5dc0723a..9673354d70 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -144,7 +144,7 @@ static void next_trb(struct xhci_hcd *xhci,
struct xhci_segment **seg,
union xhci_trb **trb)
{
- if (trb_is_link(*trb)) {
+ if (trb_is_link(*trb) || last_trb_on_seg(*seg, *trb)) {
*seg = (*seg)->next;
*trb = ((*seg)->trbs);
} else {
@@ -450,8 +450,9 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags)
* In the future we should distinguish between -ENODEV and -ETIMEDOUT
* and try to recover a -ETIMEDOUT with a host controller reset.
*/
- ret = xhci_handshake(&xhci->op_regs->cmd_ring,
- CMD_RING_RUNNING, 0, 5 * 1000 * 1000);
+ ret = xhci_handshake_check_state(xhci, &xhci->op_regs->cmd_ring,
+ CMD_RING_RUNNING, 0, 5 * 1000 * 1000,
+ XHCI_STATE_REMOVING);
if (ret < 0) {
xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret);
xhci_halt(xhci);
@@ -1879,7 +1880,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
if ((port_id <= 0) || (port_id > max_ports)) {
xhci_warn(xhci, "Port change event with invalid port ID %d\n",
port_id);
- inc_deq(xhci, ir->event_ring);
return;
}
@@ -1906,7 +1906,7 @@ static void handle_port_status(struct xhci_hcd *xhci,
xhci_dbg(xhci, "Port change event, %d-%d, id %d, portsc: 0x%x\n",
hcd->self.busnum, hcd_portnum + 1, port_id, portsc);
- trace_xhci_handle_port_status(hcd_portnum, portsc);
+ trace_xhci_handle_port_status(port, portsc);
if (hcd->state == HC_STATE_SUSPENDED) {
xhci_dbg(xhci, "resume root hub\n");
@@ -2007,8 +2007,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
}
cleanup:
- /* Update event ring dequeue pointer before dropping the lock */
- inc_deq(xhci, ir->event_ring);
/* Don't make the USB core poll the roothub if we got a bad port status
* change event. Besides, at that point we can't tell which roothub
@@ -2377,6 +2375,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
/* handle completion code */
switch (trb_comp_code) {
case COMP_SUCCESS:
+ /* Don't overwrite status if TD had an error, see xHCI 4.9.1 */
+ if (td->error_mid_td)
+ break;
if (remaining) {
frame->status = short_framestatus;
if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
@@ -2392,9 +2393,13 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
case COMP_BANDWIDTH_OVERRUN_ERROR:
frame->status = -ECOMM;
break;
- case COMP_ISOCH_BUFFER_OVERRUN:
case COMP_BABBLE_DETECTED_ERROR:
+ sum_trbs_for_length = true;
+ fallthrough;
+ case COMP_ISOCH_BUFFER_OVERRUN:
frame->status = -EOVERFLOW;
+ if (ep_trb != td->last_trb)
+ td->error_mid_td = true;
break;
case COMP_INCOMPATIBLE_DEVICE_ERROR:
case COMP_STALL_ERROR:
@@ -2402,8 +2407,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
break;
case COMP_USB_TRANSACTION_ERROR:
frame->status = -EPROTO;
+ sum_trbs_for_length = true;
if (ep_trb != td->last_trb)
- return 0;
+ td->error_mid_td = true;
break;
case COMP_STOPPED:
sum_trbs_for_length = true;
@@ -2423,6 +2429,9 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
break;
}
+ if (td->urb_length_set)
+ goto finish_td;
+
if (sum_trbs_for_length)
frame->actual_length = sum_trb_lengths(xhci, ep->ring, ep_trb) +
ep_trb_len - remaining;
@@ -2431,6 +2440,14 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
td->urb->actual_length += frame->actual_length;
+finish_td:
+ /* Don't give back TD yet if we encountered an error mid TD */
+ if (td->error_mid_td && ep_trb != td->last_trb) {
+ xhci_dbg(xhci, "Error mid isoc TD, wait for final completion event\n");
+ td->urb_length_set = true;
+ return 0;
+ }
+
return finish_td(xhci, ep, ep_ring, td, trb_comp_code);
}
@@ -2809,17 +2826,51 @@ static int handle_tx_event(struct xhci_hcd *xhci,
}
if (!ep_seg) {
- if (!ep->skip ||
- !usb_endpoint_xfer_isoc(&td->urb->ep->desc)) {
- /* Some host controllers give a spurious
- * successful event after a short transfer.
- * Ignore it.
- */
- if ((xhci->quirks & XHCI_SPURIOUS_SUCCESS) &&
- ep_ring->last_td_was_short) {
- ep_ring->last_td_was_short = false;
- goto cleanup;
+
+ if (ep->skip && usb_endpoint_xfer_isoc(&td->urb->ep->desc)) {
+ skip_isoc_td(xhci, td, ep, status);
+ goto cleanup;
+ }
+
+ /*
+ * Some hosts give a spurious success event after a short
+ * transfer. Ignore it.
+ */
+ if ((xhci->quirks & XHCI_SPURIOUS_SUCCESS) &&
+ ep_ring->last_td_was_short) {
+ ep_ring->last_td_was_short = false;
+ goto cleanup;
+ }
+
+ /*
+ * xhci 4.10.2 states isoc endpoints should continue
+ * processing the next TD if there was an error mid TD.
+ * So host like NEC don't generate an event for the last
+ * isoc TRB even if the IOC flag is set.
+ * xhci 4.9.1 states that if there are errors in mult-TRB
+ * TDs xHC should generate an error for that TRB, and if xHC
+ * proceeds to the next TD it should genete an event for
+ * any TRB with IOC flag on the way. Other host follow this.
+ * So this event might be for the next TD.
+ */
+ if (td->error_mid_td &&
+ !list_is_last(&td->td_list, &ep_ring->td_list)) {
+ struct xhci_td *td_next = list_next_entry(td, td_list);
+
+ ep_seg = trb_in_td(xhci, td_next->start_seg, td_next->first_trb,
+ td_next->last_trb, ep_trb_dma, false);
+ if (ep_seg) {
+ /* give back previous TD, start handling new */
+ xhci_dbg(xhci, "Missing TD completion event after mid TD error\n");
+ ep_ring->dequeue = td->last_trb;
+ ep_ring->deq_seg = td->last_trb_seg;
+ inc_deq(xhci, ep_ring);
+ xhci_td_cleanup(xhci, td, ep_ring, td->status);
+ td = td_next;
}
+ }
+
+ if (!ep_seg) {
/* HC is busted, give up! */
xhci_err(xhci,
"ERROR Transfer event TRB DMA ptr not "
@@ -2831,9 +2882,6 @@ static int handle_tx_event(struct xhci_hcd *xhci,
ep_trb_dma, true);
return -ESHUTDOWN;
}
-
- skip_isoc_td(xhci, td, ep, status);
- goto cleanup;
}
if (trb_comp_code == COMP_SHORT_PACKET)
ep_ring->last_td_was_short = true;
@@ -2884,13 +2932,6 @@ cleanup:
trb_comp_code != COMP_MISSED_SERVICE_ERROR &&
trb_comp_code != COMP_NO_PING_RESPONSE_ERROR;
- /*
- * Do not update event ring dequeue pointer if we're in a loop
- * processing missed tds.
- */
- if (!handling_skipped_tds)
- inc_deq(xhci, ir->event_ring);
-
/*
* If ep->skip is set, it means there are missed tds on the
* endpoint ring need to take care of.
@@ -2922,9 +2963,7 @@ err_out:
static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
{
union xhci_trb *event;
- int update_ptrs = 1;
u32 trb_type;
- int ret;
/* Event ring hasn't been allocated yet. */
if (!ir || !ir->event_ring || !ir->event_ring->dequeue) {
@@ -2954,12 +2993,9 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
break;
case TRB_PORT_STATUS:
handle_port_status(xhci, ir, event);
- update_ptrs = 0;
break;
case TRB_TRANSFER:
- ret = handle_tx_event(xhci, ir, &event->trans_event);
- if (ret >= 0)
- update_ptrs = 0;
+ handle_tx_event(xhci, ir, &event->trans_event);
break;
case TRB_DEV_NOTE:
handle_device_notification(xhci, event);
@@ -2979,9 +3015,8 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
return 0;
}
- if (update_ptrs)
- /* Update SW event ring dequeue pointer */
- inc_deq(xhci, ir->event_ring);
+ /* Update SW event ring dequeue pointer */
+ inc_deq(xhci, ir->event_ring);
/* Are there more items on the event ring? Caller will call us again to
* check.
@@ -3013,13 +3048,12 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci,
* Per 4.9.4, Software writes to the ERDP register shall
* always advance the Event Ring Dequeue Pointer value.
*/
- if ((temp_64 & (u64) ~ERST_PTR_MASK) ==
- ((u64) deq & (u64) ~ERST_PTR_MASK))
+ if ((temp_64 & ERST_PTR_MASK) == (deq & ERST_PTR_MASK))
return;
/* Update HC event ring dequeue pointer */
- temp_64 &= ERST_DESI_MASK;
- temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK);
+ temp_64 = ir->event_ring->deq_seg->num & ERST_DESI_MASK;
+ temp_64 |= deq & ERST_PTR_MASK;
}
/* Clear the event handler busy flag (RW1C) */
diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h
index d6b32f2ad9..ac47b1c054 100644
--- a/drivers/usb/host/xhci-trace.h
+++ b/drivers/usb/host/xhci-trace.h
@@ -509,35 +509,38 @@ DEFINE_EVENT(xhci_log_ring, xhci_inc_deq,
);
DECLARE_EVENT_CLASS(xhci_log_portsc,
- TP_PROTO(u32 portnum, u32 portsc),
- TP_ARGS(portnum, portsc),
+ TP_PROTO(struct xhci_port *port, u32 portsc),
+ TP_ARGS(port, portsc),
TP_STRUCT__entry(
+ __field(u32, busnum)
__field(u32, portnum)
__field(u32, portsc)
),
TP_fast_assign(
- __entry->portnum = portnum;
+ __entry->busnum = port->rhub->hcd->self.busnum;
+ __entry->portnum = port->hcd_portnum;
__entry->portsc = portsc;
),
- TP_printk("port-%d: %s",
+ TP_printk("port %d-%d: %s",
+ __entry->busnum,
__entry->portnum,
xhci_decode_portsc(__get_buf(XHCI_MSG_MAX), __entry->portsc)
)
);
DEFINE_EVENT(xhci_log_portsc, xhci_handle_port_status,
- TP_PROTO(u32 portnum, u32 portsc),
- TP_ARGS(portnum, portsc)
+ TP_PROTO(struct xhci_port *port, u32 portsc),
+ TP_ARGS(port, portsc)
);
DEFINE_EVENT(xhci_log_portsc, xhci_get_port_status,
- TP_PROTO(u32 portnum, u32 portsc),
- TP_ARGS(portnum, portsc)
+ TP_PROTO(struct xhci_port *port, u32 portsc),
+ TP_ARGS(port, portsc)
);
DEFINE_EVENT(xhci_log_portsc, xhci_hub_status_data,
- TP_PROTO(u32 portnum, u32 portsc),
- TP_ARGS(portnum, portsc)
+ TP_PROTO(struct xhci_port *port, u32 portsc),
+ TP_ARGS(port, portsc)
);
DECLARE_EVENT_CLASS(xhci_log_doorbell,
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 132b76fa7c..884b0898d9 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -82,6 +82,29 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us)
}
/*
+ * xhci_handshake_check_state - same as xhci_handshake but takes an additional
+ * exit_state parameter, and bails out with an error immediately when xhc_state
+ * has exit_state flag set.
+ */
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+ u32 mask, u32 done, int usec, unsigned int exit_state)
+{
+ u32 result;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(ptr, result,
+ (result & mask) == done ||
+ result == U32_MAX ||
+ xhci->xhc_state & exit_state,
+ 1, usec);
+
+ if (result == U32_MAX || xhci->xhc_state & exit_state)
+ return -ENODEV;
+
+ return ret;
+}
+
+/*
* Disable interrupts and begin the xHCI halting process.
*/
void xhci_quiesce(struct xhci_hcd *xhci)
@@ -201,7 +224,8 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
if (xhci->quirks & XHCI_INTEL_HOST)
udelay(1000);
- ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us);
+ ret = xhci_handshake_check_state(xhci, &xhci->op_regs->command,
+ CMD_RESET, 0, timeout_us, XHCI_STATE_REMOVING);
if (ret)
return ret;
@@ -520,7 +544,7 @@ int xhci_run(struct usb_hcd *hcd)
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xhci_run");
temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
- temp_64 &= ~ERST_PTR_MASK;
+ temp_64 &= ERST_PTR_MASK;
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"ERST deq = 64'h%0lx", (long unsigned int) temp_64);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 5df3704825..6f53a950d9 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -525,7 +525,7 @@ struct xhci_intr_reg {
* a work queue (or delayed service routine)?
*/
#define ERST_EHB (1 << 3)
-#define ERST_PTR_MASK (0xf)
+#define ERST_PTR_MASK (GENMASK_ULL(63, 4))
/**
* struct xhci_run_regs
@@ -558,33 +558,6 @@ struct xhci_doorbell_array {
#define DB_VALUE(ep, stream) ((((ep) + 1) & 0xff) | ((stream) << 16))
#define DB_VALUE_HOST 0x00000000
-/**
- * struct xhci_protocol_caps
- * @revision: major revision, minor revision, capability ID,
- * and next capability pointer.
- * @name_string: Four ASCII characters to say which spec this xHC
- * follows, typically "USB ".
- * @port_info: Port offset, count, and protocol-defined information.
- */
-struct xhci_protocol_caps {
- u32 revision;
- u32 name_string;
- u32 port_info;
-};
-
-#define XHCI_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff)
-#define XHCI_EXT_PORT_MINOR(x) (((x) >> 16) & 0xff)
-#define XHCI_EXT_PORT_PSIC(x) (((x) >> 28) & 0x0f)
-#define XHCI_EXT_PORT_OFF(x) ((x) & 0xff)
-#define XHCI_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff)
-
-#define XHCI_EXT_PORT_PSIV(x) (((x) >> 0) & 0x0f)
-#define XHCI_EXT_PORT_PSIE(x) (((x) >> 4) & 0x03)
-#define XHCI_EXT_PORT_PLT(x) (((x) >> 6) & 0x03)
-#define XHCI_EXT_PORT_PFD(x) (((x) >> 8) & 0x01)
-#define XHCI_EXT_PORT_LP(x) (((x) >> 14) & 0x03)
-#define XHCI_EXT_PORT_PSIM(x) (((x) >> 16) & 0xffff)
-
#define PLT_MASK (0x03 << 6)
#define PLT_SYM (0x00 << 6)
#define PLT_ASYM_RX (0x02 << 6)
@@ -1545,6 +1518,7 @@ struct xhci_segment {
union xhci_trb *trbs;
/* private to HCD */
struct xhci_segment *next;
+ unsigned int num;
dma_addr_t dma;
/* Max packet sized bounce buffer for td-fragmant alignment */
dma_addr_t bounce_dma;
@@ -1573,6 +1547,7 @@ struct xhci_td {
struct xhci_segment *bounce_seg;
/* actual_length of the URB has already been set */
bool urb_length_set;
+ bool error_mid_td;
unsigned int num_trbs;
};
@@ -1666,15 +1641,11 @@ struct xhci_scratchpad {
struct urb_priv {
int num_tds;
int num_tds_done;
- struct xhci_td td[];
+ struct xhci_td td[] __counted_by(num_tds);
};
-/*
- * Each segment table entry is 4*32bits long. 1K seems like an ok size:
- * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table,
- * meaning 64 ring segments.
- * Initial allocated size of the ERST, in number of entries */
-#define ERST_NUM_SEGS 1
+/* Reasonable limit for number of Event Ring segments (spec allows 32k) */
+#define ERST_MAX_SEGS 2
/* Poll every 60 seconds */
#define POLL_TIMEOUT 60
/* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */
@@ -2078,13 +2049,8 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
unsigned int num_trbs, gfp_t flags);
-int xhci_alloc_erst(struct xhci_hcd *xhci,
- struct xhci_ring *evt_ring,
- struct xhci_erst *erst,
- gfp_t flags);
void xhci_initialize_ring_info(struct xhci_ring *ring,
unsigned int cycle_state);
-void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst);
void xhci_free_endpoint_ring(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
unsigned int ep_index);
@@ -2119,6 +2085,8 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci,
/* xHCI host controller glue */
typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us);
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+ u32 mask, u32 done, int usec, unsigned int exit_state);
void xhci_quiesce(struct xhci_hcd *xhci);
int xhci_halt(struct xhci_hcd *xhci);
int xhci_start(struct xhci_hcd *xhci);
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 99b15b77df..c510af7baa 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -165,6 +165,19 @@ config APPLE_MFI_FASTCHARGE
It is safe to say M here.
+config USB_LJCA
+ tristate "Intel La Jolla Cove Adapter support"
+ select AUXILIARY_BUS
+ depends on USB && ACPI
+ help
+ This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO
+ Master Adapter (LJCA). Additional drivers such as I2C_LJCA,
+ GPIO_LJCA and SPI_LJCA must be enabled in order to use the
+ functionality of the device.
+
+ This driver can also be built as a module. If so, the module
+ will be called usb-ljca.
+
source "drivers/usb/misc/sisusbvga/Kconfig"
config USB_LD
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 1992cc284d..0bc732bcb1 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_USB_EMI26) += emi26.o
obj-$(CONFIG_USB_EMI62) += emi62.o
obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o
obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o
+obj-$(CONFIG_USB_LJCA) += usb-ljca.o
obj-$(CONFIG_USB_IDMOUSE) += idmouse.o
obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o
obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o
diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c
index d72130eda5..2b45404e97 100644
--- a/drivers/usb/misc/onboard_usb_hub.c
+++ b/drivers/usb/misc/onboard_usb_hub.c
@@ -240,7 +240,6 @@ static void onboard_hub_attach_usb_driver(struct work_struct *work)
static int onboard_hub_probe(struct platform_device *pdev)
{
- const struct of_device_id *of_id;
struct device *dev = &pdev->dev;
struct onboard_hub *hub;
unsigned int i;
@@ -250,11 +249,7 @@ static int onboard_hub_probe(struct platform_device *pdev)
if (!hub)
return -ENOMEM;
- of_id = of_match_device(onboard_hub_match, &pdev->dev);
- if (!of_id)
- return -ENODEV;
-
- hub->pdata = of_id->data;
+ hub->pdata = device_get_match_data(&pdev->dev);
if (!hub->pdata)
return -EINVAL;
diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h
index 8af34e6d1a..292110e64a 100644
--- a/drivers/usb/misc/onboard_usb_hub.h
+++ b/drivers/usb/misc/onboard_usb_hub.h
@@ -64,6 +64,7 @@ static const struct of_device_id onboard_hub_match[] = {
{ .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
{ .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
{ .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
+ { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, },
{ .compatible = "usbbda,411", .data = &realtek_rts5411_data, },
{ .compatible = "usbbda,5411", .data = &realtek_rts5411_data, },
{ .compatible = "usbbda,414", .data = &realtek_rts5411_data, },
diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c
new file mode 100644
index 0000000000..35770e608c
--- /dev/null
+++ b/drivers/usb/misc/usb-ljca.c
@@ -0,0 +1,893 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/ljca.h>
+
+#include <asm/unaligned.h>
+
+/* command flags */
+#define LJCA_ACK_FLAG BIT(0)
+#define LJCA_RESP_FLAG BIT(1)
+#define LJCA_CMPL_FLAG BIT(2)
+
+#define LJCA_MAX_PACKET_SIZE 64u
+#define LJCA_MAX_PAYLOAD_SIZE \
+ (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg))
+
+#define LJCA_WRITE_TIMEOUT_MS 200
+#define LJCA_WRITE_ACK_TIMEOUT_MS 500
+#define LJCA_ENUM_CLIENT_TIMEOUT_MS 20
+
+/* ljca client type */
+enum ljca_client_type {
+ LJCA_CLIENT_MNG = 1,
+ LJCA_CLIENT_GPIO = 3,
+ LJCA_CLIENT_I2C = 4,
+ LJCA_CLIENT_SPI = 5,
+};
+
+/* MNG client commands */
+enum ljca_mng_cmd {
+ LJCA_MNG_RESET = 2,
+ LJCA_MNG_ENUM_GPIO = 4,
+ LJCA_MNG_ENUM_I2C = 5,
+ LJCA_MNG_ENUM_SPI = 8,
+};
+
+/* ljca client acpi _ADR */
+enum ljca_client_acpi_adr {
+ LJCA_GPIO_ACPI_ADR,
+ LJCA_I2C1_ACPI_ADR,
+ LJCA_I2C2_ACPI_ADR,
+ LJCA_SPI1_ACPI_ADR,
+ LJCA_SPI2_ACPI_ADR,
+ LJCA_CLIENT_ACPI_ADR_MAX,
+};
+
+/* ljca cmd message structure */
+struct ljca_msg {
+ u8 type;
+ u8 cmd;
+ u8 flags;
+ u8 len;
+ u8 data[] __counted_by(len);
+} __packed;
+
+struct ljca_i2c_ctr_info {
+ u8 id;
+ u8 capacity;
+ u8 intr_pin;
+} __packed;
+
+struct ljca_i2c_descriptor {
+ u8 num;
+ struct ljca_i2c_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_spi_ctr_info {
+ u8 id;
+ u8 capacity;
+ u8 intr_pin;
+} __packed;
+
+struct ljca_spi_descriptor {
+ u8 num;
+ struct ljca_spi_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_bank_descriptor {
+ u8 bank_id;
+ u8 pin_num;
+
+ /* 1 bit for each gpio, 1 means valid */
+ __le32 valid_pins;
+} __packed;
+
+struct ljca_gpio_descriptor {
+ u8 pins_per_bank;
+ u8 bank_num;
+ struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num);
+} __packed;
+
+/**
+ * struct ljca_adapter - represent a ljca adapter
+ *
+ * @intf: the usb interface for this ljca adapter
+ * @usb_dev: the usb device for this ljca adapter
+ * @dev: the specific device info of the usb interface
+ * @rx_pipe: bulk in pipe for receive data from firmware
+ * @tx_pipe: bulk out pipe for send data to firmware
+ * @rx_urb: urb used for the bulk in pipe
+ * @rx_buf: buffer used to receive command response and event
+ * @rx_len: length of rx buffer
+ * @ex_buf: external buffer to save command response
+ * @ex_buf_len: length of external buffer
+ * @actual_length: actual length of data copied to external buffer
+ * @tx_buf: buffer used to download command to firmware
+ * @tx_buf_len: length of tx buffer
+ * @lock: spinlock to protect tx_buf and ex_buf
+ * @cmd_completion: completion object as the command receives ack
+ * @mutex: mutex to avoid command download concurrently
+ * @client_list: client device list
+ * @disconnect: usb disconnect ongoing or not
+ * @reset_id: used to reset firmware
+ */
+struct ljca_adapter {
+ struct usb_interface *intf;
+ struct usb_device *usb_dev;
+ struct device *dev;
+
+ unsigned int rx_pipe;
+ unsigned int tx_pipe;
+
+ struct urb *rx_urb;
+ void *rx_buf;
+ unsigned int rx_len;
+
+ u8 *ex_buf;
+ u8 ex_buf_len;
+ u8 actual_length;
+
+ void *tx_buf;
+ u8 tx_buf_len;
+
+ spinlock_t lock;
+
+ struct completion cmd_completion;
+ struct mutex mutex;
+
+ struct list_head client_list;
+
+ bool disconnect;
+
+ u32 reset_id;
+};
+
+struct ljca_match_ids_walk_data {
+ const struct acpi_device_id *ids;
+ const char *uid;
+ struct acpi_device *adev;
+};
+
+static const struct acpi_device_id ljca_gpio_hids[] = {
+ { "INTC1074" },
+ { "INTC1096" },
+ { "INTC100B" },
+ { "INTC10D1" },
+ {},
+};
+
+static const struct acpi_device_id ljca_i2c_hids[] = {
+ { "INTC1075" },
+ { "INTC1097" },
+ { "INTC100C" },
+ { "INTC10D2" },
+ {},
+};
+
+static const struct acpi_device_id ljca_spi_hids[] = {
+ { "INTC1091" },
+ { "INTC1098" },
+ { "INTC100D" },
+ { "INTC10D3" },
+ {},
+};
+
+static void ljca_handle_event(struct ljca_adapter *adap,
+ struct ljca_msg *header)
+{
+ struct ljca_client *client;
+
+ list_for_each_entry(client, &adap->client_list, link) {
+ /*
+ * Currently only GPIO register event callback, but
+ * firmware message structure should include id when
+ * multiple same type clients register event callback.
+ */
+ if (client->type == header->type) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+ client->event_cb(client->context, header->cmd,
+ header->data, header->len);
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+ break;
+ }
+ }
+}
+
+/* process command ack and received data if available */
+static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header)
+{
+ struct ljca_msg *tx_header = adap->tx_buf;
+ u8 ibuf_len, actual_len = 0;
+ unsigned long flags;
+ u8 *ibuf;
+
+ spin_lock_irqsave(&adap->lock, flags);
+
+ if (tx_header->type != header->type || tx_header->cmd != header->cmd) {
+ spin_unlock_irqrestore(&adap->lock, flags);
+ dev_err(adap->dev, "cmd ack mismatch error\n");
+ return;
+ }
+
+ ibuf_len = adap->ex_buf_len;
+ ibuf = adap->ex_buf;
+
+ if (ibuf && ibuf_len) {
+ actual_len = min(header->len, ibuf_len);
+
+ /* copy received data to external buffer */
+ memcpy(ibuf, header->data, actual_len);
+ }
+ /* update copied data length */
+ adap->actual_length = actual_len;
+
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ complete(&adap->cmd_completion);
+}
+
+static void ljca_recv(struct urb *urb)
+{
+ struct ljca_msg *header = urb->transfer_buffer;
+ struct ljca_adapter *adap = urb->context;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ENOENT:
+ /*
+ * directly complete the possible ongoing transfer
+ * during disconnect
+ */
+ if (adap->disconnect)
+ complete(&adap->cmd_completion);
+ return;
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ case -EPIPE:
+ /* rx urb is terminated */
+ dev_dbg(adap->dev, "rx urb terminated with status: %d\n",
+ urb->status);
+ return;
+ default:
+ dev_dbg(adap->dev, "rx urb error: %d\n", urb->status);
+ goto resubmit;
+ }
+
+ if (header->len + sizeof(*header) != urb->actual_length)
+ goto resubmit;
+
+ if (header->flags & LJCA_ACK_FLAG)
+ ljca_handle_cmd_ack(adap, header);
+ else
+ ljca_handle_event(adap, header);
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret && ret != -EPERM)
+ dev_err(adap->dev, "resubmit rx urb error %d\n", ret);
+}
+
+static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd,
+ const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len,
+ bool ack, unsigned long timeout)
+{
+ unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len;
+ struct ljca_msg *header = adap->tx_buf;
+ unsigned int transferred;
+ unsigned long flags;
+ int ret;
+
+ if (adap->disconnect)
+ return -ENODEV;
+
+ if (msg_len > adap->tx_buf_len)
+ return -EINVAL;
+
+ mutex_lock(&adap->mutex);
+
+ spin_lock_irqsave(&adap->lock, flags);
+
+ header->type = type;
+ header->cmd = cmd;
+ header->len = obuf_len;
+ if (obuf)
+ memcpy(header->data, obuf, obuf_len);
+
+ header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0);
+
+ adap->ex_buf = ibuf;
+ adap->ex_buf_len = ibuf_len;
+ adap->actual_length = 0;
+
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ reinit_completion(&adap->cmd_completion);
+
+ ret = usb_autopm_get_interface(adap->intf);
+ if (ret < 0)
+ goto out;
+
+ ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header,
+ msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS);
+
+ usb_autopm_put_interface(adap->intf);
+
+ if (ret < 0)
+ goto out;
+ if (transferred != msg_len) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (ack) {
+ ret = wait_for_completion_timeout(&adap->cmd_completion,
+ timeout);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ }
+ ret = adap->actual_length;
+
+out:
+ spin_lock_irqsave(&adap->lock, flags);
+ adap->ex_buf = NULL;
+ adap->ex_buf_len = 0;
+
+ memset(header, 0, sizeof(*header));
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ mutex_unlock(&adap->mutex);
+
+ return ret;
+}
+
+int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf,
+ u8 obuf_len, u8 *ibuf, u8 ibuf_len)
+{
+ return ljca_send(client->adapter, client->type, cmd,
+ obuf, obuf_len, ibuf, ibuf_len, true,
+ LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer, LJCA);
+
+int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf,
+ u8 obuf_len)
+{
+ return ljca_send(client->adapter, client->type, cmd, obuf,
+ obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, LJCA);
+
+int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb,
+ void *context)
+{
+ unsigned long flags;
+
+ if (!event_cb)
+ return -EINVAL;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+
+ if (client->event_cb) {
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+ return -EALREADY;
+ }
+
+ client->event_cb = event_cb;
+ client->context = context;
+
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, LJCA);
+
+void ljca_unregister_event_cb(struct ljca_client *client)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+
+ client->event_cb = NULL;
+ client->context = NULL;
+
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, LJCA);
+
+static int ljca_match_device_ids(struct acpi_device *adev, void *data)
+{
+ struct ljca_match_ids_walk_data *wd = data;
+ const char *uid = acpi_device_uid(adev);
+
+ if (acpi_match_device_ids(adev, wd->ids))
+ return 0;
+
+ if (!wd->uid)
+ goto match;
+
+ if (!uid)
+ /*
+ * Some DSDTs have only one ACPI companion for the two I2C
+ * controllers and they don't set a UID at all (e.g. Dell
+ * Latitude 9420). On these platforms only the first I2C
+ * controller is used, so if a HID match has no UID we use
+ * "0" as the UID and assign ACPI companion to the first
+ * I2C controller.
+ */
+ uid = "0";
+ else
+ uid = strchr(uid, wd->uid[0]);
+
+ if (!uid || strcmp(uid, wd->uid))
+ return 0;
+
+match:
+ wd->adev = adev;
+
+ return 1;
+}
+
+/* bind auxiliary device to acpi device */
+static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap,
+ struct auxiliary_device *auxdev,
+ u64 adr, u8 id)
+{
+ struct ljca_match_ids_walk_data wd = { 0 };
+ struct device *dev = adap->dev;
+ struct acpi_device *parent;
+ char uid[4];
+
+ parent = ACPI_COMPANION(dev);
+ if (!parent)
+ return;
+
+ /*
+ * Currently LJCA hw doesn't use _ADR instead the shipped
+ * platforms use _HID to distinguish children devices.
+ */
+ switch (adr) {
+ case LJCA_GPIO_ACPI_ADR:
+ wd.ids = ljca_gpio_hids;
+ break;
+ case LJCA_I2C1_ACPI_ADR:
+ case LJCA_I2C2_ACPI_ADR:
+ snprintf(uid, sizeof(uid), "%d", id);
+ wd.uid = uid;
+ wd.ids = ljca_i2c_hids;
+ break;
+ case LJCA_SPI1_ACPI_ADR:
+ case LJCA_SPI2_ACPI_ADR:
+ wd.ids = ljca_spi_hids;
+ break;
+ default:
+ dev_warn(dev, "unsupported _ADR\n");
+ return;
+ }
+
+ acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+ if (wd.adev) {
+ ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+ return;
+ }
+
+ parent = ACPI_COMPANION(dev->parent->parent);
+ if (!parent)
+ return;
+
+ acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+ if (wd.adev)
+ ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+}
+
+static void ljca_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+ kfree(auxdev->dev.platform_data);
+}
+
+static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id,
+ char *name, void *data, u64 adr)
+{
+ struct auxiliary_device *auxdev;
+ struct ljca_client *client;
+ int ret;
+
+ client = kzalloc(sizeof *client, GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ client->type = type;
+ client->id = id;
+ client->adapter = adap;
+ spin_lock_init(&client->event_cb_lock);
+
+ auxdev = &client->auxdev;
+ auxdev->name = name;
+ auxdev->id = id;
+
+ auxdev->dev.parent = adap->dev;
+ auxdev->dev.platform_data = data;
+ auxdev->dev.release = ljca_auxdev_release;
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret)
+ goto err_free;
+
+ ljca_auxdev_acpi_bind(adap, auxdev, adr, id);
+
+ ret = auxiliary_device_add(auxdev);
+ if (ret)
+ goto err_uninit;
+
+ list_add_tail(&client->link, &adap->client_list);
+
+ return 0;
+
+err_uninit:
+ auxiliary_device_uninit(auxdev);
+
+err_free:
+ kfree(client);
+
+ return ret;
+}
+
+static int ljca_enumerate_gpio(struct ljca_adapter *adap)
+{
+ u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)];
+ struct ljca_gpio_descriptor *desc;
+ struct ljca_gpio_info *gpio_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ int ret, gpio_num;
+ unsigned int i;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ /* check firmware response */
+ desc = (struct ljca_gpio_descriptor *)buf;
+ if (ret != struct_size(desc, bank_desc, desc->bank_num))
+ return -EINVAL;
+
+ gpio_num = desc->pins_per_bank * desc->bank_num;
+ if (gpio_num > LJCA_MAX_GPIO_NUM)
+ return -EINVAL;
+
+ /* construct platform data */
+ gpio_info = kzalloc(sizeof *gpio_info, GFP_KERNEL);
+ if (!gpio_info)
+ return -ENOMEM;
+ gpio_info->num = gpio_num;
+
+ for (i = 0; i < desc->bank_num; i++)
+ valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins);
+ bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num);
+
+ ret = ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio",
+ gpio_info, LJCA_GPIO_ACPI_ADR);
+ if (ret)
+ kfree(gpio_info);
+
+ return ret;
+}
+
+static int ljca_enumerate_i2c(struct ljca_adapter *adap)
+{
+ struct ljca_i2c_descriptor *desc;
+ struct ljca_i2c_info *i2c_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ unsigned int i;
+ int ret;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ /* check firmware response */
+ desc = (struct ljca_i2c_descriptor *)buf;
+ if (ret != struct_size(desc, info, desc->num))
+ return -EINVAL;
+
+ for (i = 0; i < desc->num; i++) {
+ /* construct platform data */
+ i2c_info = kzalloc(sizeof *i2c_info, GFP_KERNEL);
+ if (!i2c_info)
+ return -ENOMEM;
+
+ i2c_info->id = desc->info[i].id;
+ i2c_info->capacity = desc->info[i].capacity;
+ i2c_info->intr_pin = desc->info[i].intr_pin;
+
+ ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i,
+ "ljca-i2c", i2c_info,
+ LJCA_I2C1_ACPI_ADR + i);
+ if (ret) {
+ kfree(i2c_info);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ljca_enumerate_spi(struct ljca_adapter *adap)
+{
+ struct ljca_spi_descriptor *desc;
+ struct ljca_spi_info *spi_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ unsigned int i;
+ int ret;
+
+ /* Not all LJCA chips implement SPI, a timeout reading the descriptors is normal */
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return (ret == -ETIMEDOUT) ? 0 : ret;
+
+ /* check firmware response */
+ desc = (struct ljca_spi_descriptor *)buf;
+ if (ret != struct_size(desc, info, desc->num))
+ return -EINVAL;
+
+ for (i = 0; i < desc->num; i++) {
+ /* construct platform data */
+ spi_info = kzalloc(sizeof *spi_info, GFP_KERNEL);
+ if (!spi_info)
+ return -ENOMEM;
+
+ spi_info->id = desc->info[i].id;
+ spi_info->capacity = desc->info[i].capacity;
+
+ ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i,
+ "ljca-spi", spi_info,
+ LJCA_SPI1_ACPI_ADR + i);
+ if (ret) {
+ kfree(spi_info);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ljca_reset_handshake(struct ljca_adapter *adap)
+{
+ __le32 reset_id = cpu_to_le32(adap->reset_id);
+ __le32 reset_id_ret = 0;
+ int ret;
+
+ adap->reset_id++;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id,
+ sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32),
+ true, LJCA_WRITE_ACK_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ if (reset_id_ret != reset_id)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ljca_enumerate_clients(struct ljca_adapter *adap)
+{
+ struct ljca_client *client, *next;
+ int ret;
+
+ ret = ljca_reset_handshake(adap);
+ if (ret)
+ goto err_kill;
+
+ ret = ljca_enumerate_gpio(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate GPIO error\n");
+ goto err_kill;
+ }
+
+ ret = ljca_enumerate_i2c(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate I2C error\n");
+ goto err_kill;
+ }
+
+ ret = ljca_enumerate_spi(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate SPI error\n");
+ goto err_kill;
+ }
+
+ return 0;
+
+err_kill:
+ adap->disconnect = true;
+
+ usb_kill_urb(adap->rx_urb);
+
+ list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+
+ list_del_init(&client->link);
+ kfree(client);
+ }
+
+ return ret;
+}
+
+static int ljca_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usb_dev = interface_to_usbdev(interface);
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
+ struct device *dev = &interface->dev;
+ struct ljca_adapter *adap;
+ int ret;
+
+ adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
+ if (!adap)
+ return -ENOMEM;
+
+ /* separate tx buffer allocation for alignment */
+ adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!adap->tx_buf)
+ return -ENOMEM;
+ adap->tx_buf_len = LJCA_MAX_PACKET_SIZE;
+
+ mutex_init(&adap->mutex);
+ spin_lock_init(&adap->lock);
+ init_completion(&adap->cmd_completion);
+ INIT_LIST_HEAD(&adap->client_list);
+
+ adap->intf = usb_get_intf(interface);
+ adap->usb_dev = usb_dev;
+ adap->dev = dev;
+
+ /*
+ * find the first bulk in and out endpoints.
+ * ignore any others.
+ */
+ ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL);
+ if (ret) {
+ dev_err(dev, "bulk endpoints not found\n");
+ goto err_put;
+ }
+ adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in));
+ adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out));
+
+ /* setup rx buffer */
+ adap->rx_len = usb_endpoint_maxp(ep_in);
+ adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL);
+ if (!adap->rx_buf) {
+ ret = -ENOMEM;
+ goto err_put;
+ }
+
+ /* alloc rx urb */
+ adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!adap->rx_urb) {
+ ret = -ENOMEM;
+ goto err_put;
+ }
+ usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe,
+ adap->rx_buf, adap->rx_len, ljca_recv, adap);
+
+ usb_set_intfdata(interface, adap);
+
+ /* submit rx urb before enumerate clients */
+ ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(dev, "submit rx urb failed: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = ljca_enumerate_clients(adap);
+ if (ret)
+ goto err_free;
+
+ usb_enable_autosuspend(usb_dev);
+
+ return 0;
+
+err_free:
+ usb_free_urb(adap->rx_urb);
+
+err_put:
+ usb_put_intf(adap->intf);
+
+ mutex_destroy(&adap->mutex);
+
+ return ret;
+}
+
+static void ljca_disconnect(struct usb_interface *interface)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+ struct ljca_client *client, *next;
+
+ adap->disconnect = true;
+
+ usb_kill_urb(adap->rx_urb);
+
+ list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+
+ list_del_init(&client->link);
+ kfree(client);
+ }
+
+ usb_free_urb(adap->rx_urb);
+
+ usb_put_intf(adap->intf);
+
+ mutex_destroy(&adap->mutex);
+}
+
+static int ljca_suspend(struct usb_interface *interface, pm_message_t message)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+ usb_kill_urb(adap->rx_urb);
+
+ return 0;
+}
+
+static int ljca_resume(struct usb_interface *interface)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+ return usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+}
+
+static const struct usb_device_id ljca_table[] = {
+ { USB_DEVICE(0x8086, 0x0b63) },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(usb, ljca_table);
+
+static struct usb_driver ljca_driver = {
+ .name = "ljca",
+ .id_table = ljca_table,
+ .probe = ljca_probe,
+ .disconnect = ljca_disconnect,
+ .suspend = ljca_suspend,
+ .resume = ljca_resume,
+ .supports_autosuspend = 1,
+};
+module_usb_driver(ljca_driver);
+
+MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c
index ac0d75ac2d..caf65f8294 100644
--- a/drivers/usb/misc/usbtest.c
+++ b/drivers/usb/misc/usbtest.c
@@ -705,7 +705,7 @@ static int is_good_config(struct usbtest_dev *tdev, int len)
{
struct usb_config_descriptor *config;
- if (len < sizeof(*config))
+ if (len < (int)sizeof(*config))
return 0;
config = (struct usb_config_descriptor *) tdev->buf;
diff --git a/drivers/usb/mon/mon_main.c b/drivers/usb/mon/mon_main.c
index 9812d102a0..824904abe7 100644
--- a/drivers/usb/mon/mon_main.c
+++ b/drivers/usb/mon/mon_main.c
@@ -81,15 +81,12 @@ void mon_reader_del(struct mon_bus *mbus, struct mon_reader *r)
static void mon_bus_submit(struct mon_bus *mbus, struct urb *urb)
{
unsigned long flags;
- struct list_head *pos;
struct mon_reader *r;
spin_lock_irqsave(&mbus->lock, flags);
mbus->cnt_events++;
- list_for_each (pos, &mbus->r_list) {
- r = list_entry(pos, struct mon_reader, r_link);
+ list_for_each_entry(r, &mbus->r_list, r_link)
r->rnf_submit(r->r_data, urb);
- }
spin_unlock_irqrestore(&mbus->lock, flags);
}
@@ -108,15 +105,12 @@ static void mon_submit(struct usb_bus *ubus, struct urb *urb)
static void mon_bus_submit_error(struct mon_bus *mbus, struct urb *urb, int error)
{
unsigned long flags;
- struct list_head *pos;
struct mon_reader *r;
spin_lock_irqsave(&mbus->lock, flags);
mbus->cnt_events++;
- list_for_each (pos, &mbus->r_list) {
- r = list_entry(pos, struct mon_reader, r_link);
+ list_for_each_entry(r, &mbus->r_list, r_link)
r->rnf_error(r->r_data, urb, error);
- }
spin_unlock_irqrestore(&mbus->lock, flags);
}
@@ -135,15 +129,12 @@ static void mon_submit_error(struct usb_bus *ubus, struct urb *urb, int error)
static void mon_bus_complete(struct mon_bus *mbus, struct urb *urb, int status)
{
unsigned long flags;
- struct list_head *pos;
struct mon_reader *r;
spin_lock_irqsave(&mbus->lock, flags);
mbus->cnt_events++;
- list_for_each (pos, &mbus->r_list) {
- r = list_entry(pos, struct mon_reader, r_link);
+ list_for_each_entry(r, &mbus->r_list, r_link)
r->rnf_complete(r->r_data, urb, status);
- }
spin_unlock_irqrestore(&mbus->lock, flags);
}
@@ -165,11 +156,9 @@ static void mon_complete(struct usb_bus *ubus, struct urb *urb, int status)
static void mon_stop(struct mon_bus *mbus)
{
struct usb_bus *ubus;
- struct list_head *p;
if (mbus == &mon_bus0) {
- list_for_each (p, &mon_buses) {
- mbus = list_entry(p, struct mon_bus, bus_link);
+ list_for_each_entry(mbus, &mon_buses, bus_link) {
/*
* We do not change nreaders here, so rely on mon_lock.
*/
@@ -332,14 +321,12 @@ static void mon_bus0_init(void)
*/
struct mon_bus *mon_bus_lookup(unsigned int num)
{
- struct list_head *p;
struct mon_bus *mbus;
if (num == 0) {
return &mon_bus0;
}
- list_for_each (p, &mon_buses) {
- mbus = list_entry(p, struct mon_bus, bus_link);
+ list_for_each_entry(mbus, &mon_buses, bus_link) {
if (mbus->u_bus->busnum == num) {
return mbus;
}
diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c
index 6f264b1292..6858ed9fc3 100644
--- a/drivers/usb/mtu3/mtu3_plat.c
+++ b/drivers/usb/mtu3/mtu3_plat.c
@@ -451,7 +451,7 @@ comm_init_err:
return ret;
}
-static int mtu3_remove(struct platform_device *pdev)
+static void mtu3_remove(struct platform_device *pdev)
{
struct ssusb_mtk *ssusb = platform_get_drvdata(pdev);
@@ -469,8 +469,16 @@ static int mtu3_remove(struct platform_device *pdev)
ssusb_gadget_exit(ssusb);
ssusb_host_exit(ssusb);
break;
- default:
- return -EINVAL;
+ case USB_DR_MODE_UNKNOWN:
+ /*
+ * This cannot happen because with dr_mode ==
+ * USB_DR_MODE_UNKNOWN, .probe() doesn't succeed and so
+ * .remove() wouldn't be called at all. However (little
+ * surprising) the compiler isn't smart enough to see that, so
+ * we explicitly have this case item to not make the compiler
+ * wail about an unhandled enumeration value.
+ */
+ break;
}
ssusb_rscs_exit(ssusb);
@@ -478,8 +486,6 @@ static int mtu3_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
-
- return 0;
}
static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg)
@@ -615,7 +621,7 @@ MODULE_DEVICE_TABLE(of, mtu3_of_match);
static struct platform_driver mtu3_driver = {
.probe = mtu3_probe,
- .remove = mtu3_remove,
+ .remove_new = mtu3_remove,
.driver = {
.name = MTU3_DRIVER_NAME,
.pm = DEV_PM_OPS,
diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c
index 912e32b78a..8abf3a567e 100644
--- a/drivers/usb/musb/da8xx.c
+++ b/drivers/usb/musb/da8xx.c
@@ -17,6 +17,7 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
+#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c
index 98b42dc04d..9c7a8bbc05 100644
--- a/drivers/usb/musb/musb_dsps.c
+++ b/drivers/usb/musb/musb_dsps.c
@@ -849,7 +849,7 @@ static int dsps_setup_optional_vbus_irq(struct platform_device *pdev,
error = devm_request_threaded_irq(glue->dev, glue->vbus_irq,
NULL, dsps_vbus_threaded_irq,
- IRQF_ONESHOT,
+ IRQF_SHARED,
"vbus", glue);
if (error) {
glue->vbus_irq = 0;
diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c
index ae41578bd0..70165dd86b 100644
--- a/drivers/usb/roles/class.c
+++ b/drivers/usb/roles/class.c
@@ -21,7 +21,9 @@ static const struct class role_class = {
struct usb_role_switch {
struct device dev;
struct mutex lock; /* device lock*/
+ struct module *module; /* the module this device depends on */
enum usb_role role;
+ bool registered;
/* From descriptor */
struct device *usb2_port;
@@ -48,6 +50,9 @@ int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role)
if (IS_ERR_OR_NULL(sw))
return 0;
+ if (!sw->registered)
+ return -EOPNOTSUPP;
+
mutex_lock(&sw->lock);
ret = sw->set(sw, role);
@@ -73,7 +78,7 @@ enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw)
{
enum usb_role role;
- if (IS_ERR_OR_NULL(sw))
+ if (IS_ERR_OR_NULL(sw) || !sw->registered)
return USB_ROLE_NONE;
mutex_lock(&sw->lock);
@@ -135,7 +140,7 @@ struct usb_role_switch *usb_role_switch_get(struct device *dev)
usb_role_switch_match);
if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
+ WARN_ON(!try_module_get(sw->module));
return sw;
}
@@ -157,7 +162,7 @@ struct usb_role_switch *fwnode_usb_role_switch_get(struct fwnode_handle *fwnode)
sw = fwnode_connection_find_match(fwnode, "usb-role-switch",
NULL, usb_role_switch_match);
if (!IS_ERR_OR_NULL(sw))
- WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
+ WARN_ON(!try_module_get(sw->module));
return sw;
}
@@ -172,7 +177,7 @@ EXPORT_SYMBOL_GPL(fwnode_usb_role_switch_get);
void usb_role_switch_put(struct usb_role_switch *sw)
{
if (!IS_ERR_OR_NULL(sw)) {
- module_put(sw->dev.parent->driver->owner);
+ module_put(sw->module);
put_device(&sw->dev);
}
}
@@ -189,15 +194,18 @@ struct usb_role_switch *
usb_role_switch_find_by_fwnode(const struct fwnode_handle *fwnode)
{
struct device *dev;
+ struct usb_role_switch *sw = NULL;
if (!fwnode)
return NULL;
dev = class_find_device_by_fwnode(&role_class, fwnode);
- if (dev)
- WARN_ON(!try_module_get(dev->parent->driver->owner));
+ if (dev) {
+ sw = to_role_switch(dev);
+ WARN_ON(!try_module_get(sw->module));
+ }
- return dev ? to_role_switch(dev) : NULL;
+ return sw;
}
EXPORT_SYMBOL_GPL(usb_role_switch_find_by_fwnode);
@@ -338,6 +346,7 @@ usb_role_switch_register(struct device *parent,
sw->set = desc->set;
sw->get = desc->get;
+ sw->module = parent->driver->owner;
sw->dev.parent = parent;
sw->dev.fwnode = desc->fwnode;
sw->dev.class = &role_class;
@@ -352,6 +361,8 @@ usb_role_switch_register(struct device *parent,
return ERR_PTR(ret);
}
+ sw->registered = true;
+
/* TODO: Symlinks for the host port and the device controller. */
return sw;
@@ -366,8 +377,10 @@ EXPORT_SYMBOL_GPL(usb_role_switch_register);
*/
void usb_role_switch_unregister(struct usb_role_switch *sw)
{
- if (!IS_ERR_OR_NULL(sw))
+ if (!IS_ERR_OR_NULL(sw)) {
+ sw->registered = false;
device_unregister(&sw->dev);
+ }
}
EXPORT_SYMBOL_GPL(usb_role_switch_unregister);
diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c
index 1e61fe0431..923e0ed854 100644
--- a/drivers/usb/serial/cp210x.c
+++ b/drivers/usb/serial/cp210x.c
@@ -146,6 +146,7 @@ static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x10C4, 0x85F8) }, /* Virtenio Preon32 */
{ USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */
{ USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */
+ { USB_DEVICE(0x10C4, 0x87ED) }, /* IMST USB-Stick for Smart Meter */
{ USB_DEVICE(0x10C4, 0x8856) }, /* CEL EM357 ZigBee USB Stick - LR */
{ USB_DEVICE(0x10C4, 0x8857) }, /* CEL EM357 ZigBee USB Stick */
{ USB_DEVICE(0x10C4, 0x88A4) }, /* MMB Networks ZigBee USB Device */
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
index 72390dbf07..2ae124c49d 100644
--- a/drivers/usb/serial/option.c
+++ b/drivers/usb/serial/option.c
@@ -2269,6 +2269,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0111, 0xff) }, /* Fibocom FM160 (MBIM mode) */
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a0, 0xff) }, /* Fibocom NL668-AM/NL652-EU (laptop MBIM) */
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a2, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a3, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a4, 0xff), /* Fibocom FM101-GL (laptop MBIM) */
.driver_info = RSVD(4) },
{ USB_DEVICE_INTERFACE_CLASS(0x2df3, 0x9d03, 0xff) }, /* LongSung M5710 */
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
index b1e844bf31..703a9c5635 100644
--- a/drivers/usb/serial/qcserial.c
+++ b/drivers/usb/serial/qcserial.c
@@ -184,6 +184,8 @@ static const struct usb_device_id id_table[] = {
{DEVICE_SWI(0x413c, 0x81d0)}, /* Dell Wireless 5819 */
{DEVICE_SWI(0x413c, 0x81d1)}, /* Dell Wireless 5818 */
{DEVICE_SWI(0x413c, 0x81d2)}, /* Dell Wireless 5818 */
+ {DEVICE_SWI(0x413c, 0x8217)}, /* Dell Wireless DW5826e */
+ {DEVICE_SWI(0x413c, 0x8218)}, /* Dell Wireless DW5826e QDL */
/* Huawei devices */
{DEVICE_HWI(0x03f0, 0x581d)}, /* HP lt4112 LTE/HSPA+ Gobi 4G Modem (Huawei me906e) */
diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c
index c54e9805da..12cf9940e5 100644
--- a/drivers/usb/storage/scsiglue.c
+++ b/drivers/usb/storage/scsiglue.c
@@ -180,6 +180,13 @@ static int slave_configure(struct scsi_device *sdev)
sdev->use_192_bytes_for_3f = 1;
/*
+ * Some devices report generic values until the media has been
+ * accessed. Force a READ(10) prior to querying device
+ * characteristics.
+ */
+ sdev->read_before_ms = 1;
+
+ /*
* Some devices don't like MODE SENSE with page=0x3f,
* which is the command used for checking if a device
* is write-protected. Now that we tell the sd driver
diff --git a/drivers/usb/storage/uas-detect.h b/drivers/usb/storage/uas-detect.h
index d73282c0ec..4d3b49e5b8 100644
--- a/drivers/usb/storage/uas-detect.h
+++ b/drivers/usb/storage/uas-detect.h
@@ -54,12 +54,12 @@ static int uas_find_endpoints(struct usb_host_interface *alt,
static int uas_use_uas_driver(struct usb_interface *intf,
const struct usb_device_id *id,
- unsigned long *flags_ret)
+ u64 *flags_ret)
{
struct usb_host_endpoint *eps[4] = { };
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
- unsigned long flags = id->driver_info;
+ u64 flags = id->driver_info;
struct usb_host_interface *alt;
int r;
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index 2583ee9815..299a6767b7 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -37,7 +37,7 @@ struct uas_dev_info {
struct usb_anchor cmd_urbs;
struct usb_anchor sense_urbs;
struct usb_anchor data_urbs;
- unsigned long flags;
+ u64 flags;
int qdepth, resetting;
unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe;
unsigned use_streams:1;
@@ -879,6 +879,13 @@ static int uas_slave_configure(struct scsi_device *sdev)
sdev->guess_capacity = 1;
/*
+ * Some devices report generic values until the media has been
+ * accessed. Force a READ(10) prior to querying device
+ * characteristics.
+ */
+ sdev->read_before_ms = 1;
+
+ /*
* Some devices don't like MODE SENSE with page=0x3f,
* which is the command used for checking if a device
* is write-protected. Now that we tell the sd driver
@@ -988,7 +995,7 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id)
struct Scsi_Host *shost = NULL;
struct uas_dev_info *devinfo;
struct usb_device *udev = interface_to_usbdev(intf);
- unsigned long dev_flags;
+ u64 dev_flags;
if (!uas_use_uas_driver(intf, id, &dev_flags))
return -ENODEV;
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
index 7b36a3334f..d1ad6a2509 100644
--- a/drivers/usb/storage/usb.c
+++ b/drivers/usb/storage/usb.c
@@ -110,17 +110,6 @@ MODULE_PARM_DESC(quirks, "supplemental list of device IDs and their quirks");
.useTransport = use_transport, \
}
-#define UNUSUAL_VENDOR_INTF(idVendor, cl, sc, pr, \
- vendor_name, product_name, use_protocol, use_transport, \
- init_function, Flags) \
-{ \
- .vendorName = vendor_name, \
- .productName = product_name, \
- .useProtocol = use_protocol, \
- .useTransport = use_transport, \
- .initFunction = init_function, \
-}
-
static const struct us_unusual_dev us_unusual_dev_list[] = {
# include "unusual_devs.h"
{ } /* Terminating entry */
@@ -132,7 +121,6 @@ static const struct us_unusual_dev for_dynamic_ids =
#undef UNUSUAL_DEV
#undef COMPLIANT_DEV
#undef USUAL_DEV
-#undef UNUSUAL_VENDOR_INTF
#ifdef CONFIG_LOCKDEP
@@ -472,13 +460,13 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf)
#define TOLOWER(x) ((x) | 0x20)
/* Adjust device flags based on the "quirks=" module parameter */
-void usb_stor_adjust_quirks(struct usb_device *udev, unsigned long *fflags)
+void usb_stor_adjust_quirks(struct usb_device *udev, u64 *fflags)
{
char *p;
u16 vid = le16_to_cpu(udev->descriptor.idVendor);
u16 pid = le16_to_cpu(udev->descriptor.idProduct);
- unsigned f = 0;
- unsigned int mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE |
+ u64 f = 0;
+ u64 mask = (US_FL_SANE_SENSE | US_FL_BAD_SENSE |
US_FL_FIX_CAPACITY | US_FL_IGNORE_UAS |
US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE |
US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 |
@@ -617,7 +605,7 @@ static int get_device_info(struct us_data *us, const struct usb_device_id *id,
us->fflags &= ~US_FL_GO_SLOW;
if (us->fflags)
- dev_info(pdev, "Quirks match for vid %04x pid %04x: %lx\n",
+ dev_info(pdev, "Quirks match for vid %04x pid %04x: %llx\n",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct),
us->fflags);
diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h
index fd3f326708..97c6196d63 100644
--- a/drivers/usb/storage/usb.h
+++ b/drivers/usb/storage/usb.h
@@ -95,7 +95,7 @@ struct us_data {
struct usb_interface *pusb_intf; /* this interface */
const struct us_unusual_dev *unusual_dev;
/* device-filter entry */
- unsigned long fflags; /* fixed flags from filter */
+ u64 fflags; /* fixed flags from filter */
unsigned long dflags; /* dynamic atomic bitflags */
unsigned int send_bulk_pipe; /* cached pipe values */
unsigned int recv_bulk_pipe;
@@ -192,7 +192,7 @@ extern int usb_stor_probe2(struct us_data *us);
extern void usb_stor_disconnect(struct usb_interface *intf);
extern void usb_stor_adjust_quirks(struct usb_device *dev,
- unsigned long *fflags);
+ u64 *fflags);
#define module_usb_stor_driver(__driver, __sht, __name) \
static int __init __driver##_init(void) \
diff --git a/drivers/usb/storage/usual-tables.c b/drivers/usb/storage/usual-tables.c
index 529512827d..a26029e43d 100644
--- a/drivers/usb/storage/usual-tables.c
+++ b/drivers/usb/storage/usual-tables.c
@@ -19,27 +19,13 @@
vendorName, productName, useProtocol, useTransport, \
initFunction, flags) \
{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \
- .driver_info = (flags) }
+ .driver_info = (kernel_ulong_t)(flags) }
#define COMPLIANT_DEV UNUSUAL_DEV
#define USUAL_DEV(useProto, useTrans) \
{ USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans) }
-/* Define the device is matched with Vendor ID and interface descriptors */
-#define UNUSUAL_VENDOR_INTF(id_vendor, cl, sc, pr, \
- vendorName, productName, useProtocol, useTransport, \
- initFunction, flags) \
-{ \
- .match_flags = USB_DEVICE_ID_MATCH_INT_INFO \
- | USB_DEVICE_ID_MATCH_VENDOR, \
- .idVendor = (id_vendor), \
- .bInterfaceClass = (cl), \
- .bInterfaceSubClass = (sc), \
- .bInterfaceProtocol = (pr), \
- .driver_info = (flags) \
-}
-
const struct usb_device_id usb_storage_usb_ids[] = {
# include "unusual_devs.h"
{ } /* Terminating entry */
@@ -49,7 +35,6 @@ MODULE_DEVICE_TABLE(usb, usb_storage_usb_ids);
#undef UNUSUAL_DEV
#undef COMPLIANT_DEV
#undef USUAL_DEV
-#undef UNUSUAL_VENDOR_INTF
/*
* The table of devices to ignore
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index 59e0218a8b..f81bec0c7b 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -86,8 +86,11 @@ static int dp_altmode_notify(struct dp_altmode *dp)
static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
{
- u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
u8 pin_assign = 0;
+ u32 conf;
+
+ /* DP Signalling */
+ conf = (dp->data.conf & DP_CONF_SIGNALLING_MASK) >> DP_CONF_SIGNALLING_SHIFT;
switch (con) {
case DP_STATUS_CON_DISABLED:
@@ -153,11 +156,11 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
}
}
} else {
- if (dp->hpd != hpd) {
- drm_connector_oob_hotplug_event(dp->connector_fwnode);
- dp->hpd = hpd;
- sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
- }
+ drm_connector_oob_hotplug_event(dp->connector_fwnode,
+ hpd ? connector_status_connected :
+ connector_status_disconnected);
+ dp->hpd = hpd;
+ sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
}
return ret;
@@ -173,7 +176,8 @@ static int dp_altmode_configured(struct dp_altmode *dp)
* configuration is complete to signal HPD.
*/
if (dp->pending_hpd) {
- drm_connector_oob_hotplug_event(dp->connector_fwnode);
+ drm_connector_oob_hotplug_event(dp->connector_fwnode,
+ connector_status_connected);
sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
dp->pending_hpd = false;
}
@@ -305,7 +309,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
dp->data.status = 0;
dp->data.conf = 0;
if (dp->hpd) {
- drm_connector_oob_hotplug_event(dp->connector_fwnode);
+ drm_connector_oob_hotplug_event(dp->connector_fwnode,
+ connector_status_disconnected);
dp->hpd = false;
sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
}
@@ -623,8 +628,8 @@ void dp_altmode_remove(struct typec_altmode *alt)
cancel_work_sync(&dp->work);
if (dp->connector_fwnode) {
- if (dp->hpd)
- drm_connector_oob_hotplug_event(dp->connector_fwnode);
+ drm_connector_oob_hotplug_event(dp->connector_fwnode,
+ connector_status_disconnected);
fwnode_handle_put(dp->connector_fwnode);
}
diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c
index 221604f933..b12a07edc7 100644
--- a/drivers/usb/typec/anx7411.c
+++ b/drivers/usb/typec/anx7411.c
@@ -1550,8 +1550,7 @@ static void anx7411_i2c_remove(struct i2c_client *client)
if (plat->workqueue)
destroy_workqueue(plat->workqueue);
- if (plat->spi_client)
- i2c_unregister_device(plat->spi_client);
+ i2c_unregister_device(plat->spi_client);
if (plat->typec.role_sw)
usb_role_switch_put(plat->typec.role_sw);
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 41382b1e29..2da19feacd 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -13,6 +13,7 @@
#include <linux/usb/pd_vdo.h>
#include <linux/usb/typec_mux.h>
#include <linux/usb/typec_retimer.h>
+#include <linux/usb.h>
#include "bus.h"
#include "class.h"
@@ -684,6 +685,33 @@ const struct device_type typec_partner_dev_type = {
.release = typec_partner_release,
};
+static void typec_partner_link_device(struct typec_partner *partner, struct device *dev)
+{
+ int ret;
+
+ ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec");
+ if (ret)
+ return;
+
+ ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev));
+ if (ret) {
+ sysfs_remove_link(&dev->kobj, "typec");
+ return;
+ }
+
+ if (partner->attach)
+ partner->attach(partner, dev);
+}
+
+static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev)
+{
+ sysfs_remove_link(&partner->dev.kobj, dev_name(dev));
+ sysfs_remove_link(&dev->kobj, "typec");
+
+ if (partner->deattach)
+ partner->deattach(partner, dev);
+}
+
/**
* typec_partner_set_identity - Report result from Discover Identity command
* @partner: The partner updated identity values
@@ -868,6 +896,8 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
partner->num_altmodes = -1;
partner->pd_revision = desc->pd_revision;
partner->svdm_version = port->cap->svdm_version;
+ partner->attach = desc->attach;
+ partner->deattach = desc->deattach;
if (desc->identity) {
/*
@@ -890,6 +920,11 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
return ERR_PTR(ret);
}
+ if (port->usb2_dev)
+ typec_partner_link_device(partner, port->usb2_dev);
+ if (port->usb3_dev)
+ typec_partner_link_device(partner, port->usb3_dev);
+
return partner;
}
EXPORT_SYMBOL_GPL(typec_register_partner);
@@ -902,8 +937,19 @@ EXPORT_SYMBOL_GPL(typec_register_partner);
*/
void typec_unregister_partner(struct typec_partner *partner)
{
- if (!IS_ERR_OR_NULL(partner))
- device_unregister(&partner->dev);
+ struct typec_port *port;
+
+ if (IS_ERR_OR_NULL(partner))
+ return;
+
+ port = to_typec_port(partner->dev.parent);
+
+ if (port->usb2_dev)
+ typec_partner_unlink_device(partner, port->usb2_dev);
+ if (port->usb3_dev)
+ typec_partner_unlink_device(partner, port->usb3_dev);
+
+ device_unregister(&partner->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_partner);
@@ -1778,6 +1824,50 @@ static int partner_match(struct device *dev, void *data)
return is_typec_partner(dev);
}
+static struct typec_partner *typec_get_partner(struct typec_port *port)
+{
+ struct device *dev;
+
+ dev = device_find_child(&port->dev, NULL, partner_match);
+ if (!dev)
+ return NULL;
+
+ return to_typec_partner(dev);
+}
+
+static void typec_partner_attach(struct typec_connector *con, struct device *dev)
+{
+ struct typec_port *port = container_of(con, struct typec_port, con);
+ struct typec_partner *partner = typec_get_partner(port);
+ struct usb_device *udev = to_usb_device(dev);
+
+ if (udev->speed < USB_SPEED_SUPER)
+ port->usb2_dev = dev;
+ else
+ port->usb3_dev = dev;
+
+ if (partner) {
+ typec_partner_link_device(partner, dev);
+ put_device(&partner->dev);
+ }
+}
+
+static void typec_partner_deattach(struct typec_connector *con, struct device *dev)
+{
+ struct typec_port *port = container_of(con, struct typec_port, con);
+ struct typec_partner *partner = typec_get_partner(port);
+
+ if (partner) {
+ typec_partner_unlink_device(partner, dev);
+ put_device(&partner->dev);
+ }
+
+ if (port->usb2_dev == dev)
+ port->usb2_dev = NULL;
+ else if (port->usb3_dev == dev)
+ port->usb3_dev = NULL;
+}
+
/**
* typec_set_data_role - Report data role change
* @port: The USB Type-C Port where the role was changed
@@ -1787,7 +1877,7 @@ static int partner_match(struct device *dev, void *data)
*/
void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
{
- struct device *partner_dev;
+ struct typec_partner *partner;
if (port->data_role == role)
return;
@@ -1796,14 +1886,14 @@ void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
sysfs_notify(&port->dev.kobj, NULL, "data_role");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
- partner_dev = device_find_child(&port->dev, NULL, partner_match);
- if (!partner_dev)
+ partner = typec_get_partner(port);
+ if (!partner)
return;
- if (to_typec_partner(partner_dev)->identity)
- typec_product_type_notify(partner_dev);
+ if (partner->identity)
+ typec_product_type_notify(&partner->dev);
- put_device(partner_dev);
+ put_device(&partner->dev);
}
EXPORT_SYMBOL_GPL(typec_set_data_role);
@@ -2254,6 +2344,8 @@ struct typec_port *typec_register_port(struct device *parent,
port->ops = cap->ops;
port->port_type = cap->type;
port->prefer_role = cap->prefer_role;
+ port->con.attach = typec_partner_attach;
+ port->con.deattach = typec_partner_deattach;
device_initialize(&port->dev);
port->dev.class = &typec_class;
diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h
index 673b2952b0..c36761ba3f 100644
--- a/drivers/usb/typec/class.h
+++ b/drivers/usb/typec/class.h
@@ -8,6 +8,7 @@
struct typec_mux;
struct typec_switch;
+struct usb_device;
struct typec_plug {
struct device dev;
@@ -35,6 +36,9 @@ struct typec_partner {
enum usb_pd_svdm_ver svdm_version;
struct usb_power_delivery *pd;
+
+ void (*attach)(struct typec_partner *partner, struct device *dev);
+ void (*deattach)(struct typec_partner *partner, struct device *dev);
};
struct typec_port {
@@ -59,6 +63,18 @@ struct typec_port {
const struct typec_capability *cap;
const struct typec_operations *ops;
+
+ struct typec_connector con;
+
+ /*
+ * REVISIT: Only USB devices for now. If there are others, these need to
+ * be converted into a list.
+ *
+ * NOTE: These may be registered first before the typec_partner, so they
+ * will always have to be kept here instead of struct typec_partner.
+ */
+ struct device *usb2_dev;
+ struct device *usb3_dev;
};
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index 65da61150b..816b9bd083 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -46,4 +46,14 @@ config TYPEC_MUX_NB7VPQ904M
Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C
redriver chip found on some devices with a Type-C port.
+config TYPEC_MUX_PTN36502
+ tristate "NXP PTN36502 Type-C redriver driver"
+ depends on I2C
+ depends on DRM || DRM=n
+ select DRM_PANEL_BRIDGE if DRM
+ select REGMAP_I2C
+ help
+ Say Y or M if your system has a NXP PTN36502 Type-C redriver chip
+ found on some devices with a Type-C port.
+
endmenu
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index 76196096ef..9d6a5557b0 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o
obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o
obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o
obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o
+obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o
diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c
index e0ee1f621a..cb7cdf90cb 100644
--- a/drivers/usb/typec/mux/fsa4480.c
+++ b/drivers/usb/typec/mux/fsa4480.c
@@ -60,6 +60,7 @@ struct fsa4480 {
unsigned int svid;
u8 cur_enable;
+ bool swap_sbu_lanes;
};
static const struct regmap_config fsa4480_regmap_config = {
@@ -76,6 +77,9 @@ static int fsa4480_set(struct fsa4480 *fsa)
u8 enable = FSA4480_ENABLE_DEVICE;
u8 sel = 0;
+ if (fsa->swap_sbu_lanes)
+ reverse = !reverse;
+
/* USB Mode */
if (fsa->mode < TYPEC_STATE_MODAL ||
(!fsa->svid && (fsa->mode == TYPEC_MODE_USB2 ||
@@ -179,12 +183,75 @@ static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *st
return ret;
}
+enum {
+ NORMAL_LANE_MAPPING,
+ INVERT_LANE_MAPPING,
+};
+
+#define DATA_LANES_COUNT 2
+
+static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
+ [NORMAL_LANE_MAPPING] = { 0, 1 },
+ [INVERT_LANE_MAPPING] = { 1, 0 },
+};
+
+static int fsa4480_parse_data_lanes_mapping(struct fsa4480 *fsa)
+{
+ struct fwnode_handle *ep;
+ u32 data_lanes[DATA_LANES_COUNT];
+ int ret, i, j;
+
+ ep = fwnode_graph_get_next_endpoint(dev_fwnode(&fsa->client->dev), NULL);
+ if (!ep)
+ return 0;
+
+ ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
+ if (ret == -EINVAL)
+ /* Property isn't here, consider default mapping */
+ goto out_done;
+ if (ret) {
+ dev_err(&fsa->client->dev, "invalid data-lanes property: %d\n", ret);
+ goto out_error;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
+ for (j = 0; j < DATA_LANES_COUNT; j++) {
+ if (data_lanes[j] != supported_data_lane_mapping[i][j])
+ break;
+ }
+
+ if (j == DATA_LANES_COUNT)
+ break;
+ }
+
+ switch (i) {
+ case NORMAL_LANE_MAPPING:
+ break;
+ case INVERT_LANE_MAPPING:
+ fsa->swap_sbu_lanes = true;
+ break;
+ default:
+ dev_err(&fsa->client->dev, "invalid data-lanes mapping\n");
+ ret = -EINVAL;
+ goto out_error;
+ }
+
+out_done:
+ ret = 0;
+
+out_error:
+ fwnode_handle_put(ep);
+
+ return ret;
+}
+
static int fsa4480_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct typec_switch_desc sw_desc = { };
struct typec_mux_desc mux_desc = { };
struct fsa4480 *fsa;
+ int ret;
fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL);
if (!fsa)
@@ -193,6 +260,10 @@ static int fsa4480_probe(struct i2c_client *client)
fsa->client = client;
mutex_init(&fsa->lock);
+ ret = fsa4480_parse_data_lanes_mapping(fsa);
+ if (ret)
+ return ret;
+
fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config);
if (IS_ERR(fsa->regmap))
return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n");
diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c
index 60ed1f8091..56989a0d0f 100644
--- a/drivers/usb/typec/mux/intel_pmc_mux.c
+++ b/drivers/usb/typec/mux/intel_pmc_mux.c
@@ -191,6 +191,12 @@ static int hsl_orientation(struct pmc_usb_port *port)
return port->orientation - 1;
}
+static bool is_pmc_mux_tbt(struct acpi_device *adev)
+{
+ return acpi_dev_hid_uid_match(adev, "INTC1072", NULL) ||
+ acpi_dev_hid_uid_match(adev, "INTC1079", NULL);
+}
+
static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len)
{
u8 response[4];
@@ -293,6 +299,24 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state)
req.mode_data |= (state->mode - TYPEC_STATE_MODAL) <<
PMC_USB_ALTMODE_DP_MODE_SHIFT;
+ if (!is_pmc_mux_tbt(port->pmc->iom_adev)) {
+ u8 cable_speed = (data->conf & DP_CONF_SIGNALLING_MASK) >>
+ DP_CONF_SIGNALLING_SHIFT;
+
+ u8 cable_type = (data->conf & DP_CONF_CABLE_TYPE_MASK) >>
+ DP_CONF_CABLE_TYPE_SHIFT;
+
+ req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed);
+
+ if (cable_type == DP_CONF_CABLE_TYPE_OPTICAL)
+ req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE;
+ else if (cable_type == DP_CONF_CABLE_TYPE_RE_TIMER)
+ req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE |
+ PMC_USB_ALTMODE_RETIMER_CABLE;
+ else if (cable_type == DP_CONF_CABLE_TYPE_RE_DRIVER)
+ req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE;
+ }
+
ret = pmc_usb_command(port, (void *)&req, sizeof(req));
if (ret)
return ret;
@@ -599,6 +623,7 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index,
desc.driver_data = port;
desc.name = fwnode_get_name(fwnode);
desc.set = pmc_usb_set_role;
+ desc.allow_userspace_control = true;
port->usb_sw = usb_role_switch_register(pmc->dev, &desc);
if (IS_ERR(port->usb_sw)) {
diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c
new file mode 100644
index 0000000000..72ae38a1b2
--- /dev/null
+++ b/drivers/usb/typec/mux/ptn36502.c
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NXP PTN36502 Type-C driver
+ *
+ * Copyright (C) 2023 Luca Weiss <luca.weiss@fairphone.com>
+ *
+ * Based on NB7VPQ904M driver:
+ * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+ */
+
+#include <drm/drm_bridge.h>
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_retimer.h>
+
+#define PTN36502_CHIP_ID_REG 0x00
+#define PTN36502_CHIP_ID 0x02
+
+#define PTN36502_CHIP_REVISION_REG 0x01
+#define PTN36502_CHIP_REVISION_BASE_MASK GENMASK(7, 4)
+#define PTN36502_CHIP_REVISION_METAL_MASK GENMASK(3, 0)
+
+#define PTN36502_DP_LINK_CTRL_REG 0x06
+#define PTN36502_DP_LINK_CTRL_LANES_MASK GENMASK(3, 2)
+#define PTN36502_DP_LINK_CTRL_LANES_2 (2)
+#define PTN36502_DP_LINK_CTRL_LANES_4 (3)
+#define PTN36502_DP_LINK_CTRL_LINK_RATE_MASK GENMASK(1, 0)
+#define PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS (2)
+
+/* Registers for lane 0 (0x07) to lane 3 (0x0a) have the same layout */
+#define PTN36502_DP_LANE_CTRL_REG(n) (0x07 + (n))
+#define PTN36502_DP_LANE_CTRL_RX_GAIN_MASK GENMASK(6, 4)
+#define PTN36502_DP_LANE_CTRL_RX_GAIN_3DB (2)
+#define PTN36502_DP_LANE_CTRL_TX_SWING_MASK GENMASK(3, 2)
+#define PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD (2)
+#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK GENMASK(1, 0)
+#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB (1)
+
+#define PTN36502_MODE_CTRL1_REG 0x0b
+#define PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK GENMASK(5, 5)
+#define PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE (1)
+#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK GENMASK(3, 3)
+#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON (1)
+#define PTN36502_MODE_CTRL1_MODE_MASK GENMASK(2, 0)
+#define PTN36502_MODE_CTRL1_MODE_OFF (0)
+#define PTN36502_MODE_CTRL1_MODE_USB_ONLY (1)
+#define PTN36502_MODE_CTRL1_MODE_USB_DP (2)
+#define PTN36502_MODE_CTRL1_MODE_DP (3)
+
+#define PTN36502_DEVICE_CTRL_REG 0x0d
+#define PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK GENMASK(7, 7)
+#define PTN36502_DEVICE_CTRL_AUX_MONITORING_EN (1)
+
+struct ptn36502 {
+ struct i2c_client *client;
+ struct regulator *vdd18_supply;
+ struct regmap *regmap;
+ struct typec_switch_dev *sw;
+ struct typec_retimer *retimer;
+
+ struct typec_switch *typec_switch;
+
+ struct drm_bridge bridge;
+
+ struct mutex lock; /* protect non-concurrent retimer & switch */
+
+ enum typec_orientation orientation;
+ unsigned long mode;
+ unsigned int svid;
+};
+
+static int ptn36502_set(struct ptn36502 *ptn)
+{
+ bool reverse = (ptn->orientation == TYPEC_ORIENTATION_REVERSE);
+ unsigned int ctrl1_val = 0;
+ unsigned int lane_ctrl_val = 0;
+ unsigned int link_ctrl_val = 0;
+
+ switch (ptn->mode) {
+ case TYPEC_STATE_SAFE:
+ /* Deep power saving state */
+ regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG,
+ FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+ PTN36502_MODE_CTRL1_MODE_OFF));
+ return 0;
+
+ case TYPEC_STATE_USB:
+ /*
+ * Normal Orientation (CC1)
+ * A -> USB RX
+ * B -> USB TX
+ * C -> X
+ * D -> X
+ * Flipped Orientation (CC2)
+ * A -> X
+ * B -> X
+ * C -> USB TX
+ * D -> USB RX
+ */
+
+ /* USB 3.1 Gen 1 only */
+ ctrl1_val = FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+ PTN36502_MODE_CTRL1_MODE_USB_ONLY);
+ if (reverse)
+ ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK,
+ PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE);
+
+ regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val);
+ return 0;
+
+ default:
+ if (ptn->svid != USB_TYPEC_DP_SID)
+ return -EINVAL;
+
+ break;
+ }
+
+ /* DP Altmode Setup */
+
+ switch (ptn->mode) {
+ case TYPEC_DP_STATE_C:
+ case TYPEC_DP_STATE_E:
+ /*
+ * Normal Orientation (CC1)
+ * A -> DP3
+ * B -> DP2
+ * C -> DP1
+ * D -> DP0
+ * Flipped Orientation (CC2)
+ * A -> DP0
+ * B -> DP1
+ * C -> DP2
+ * D -> DP3
+ */
+
+ /* 4-lane DP */
+ ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+ PTN36502_MODE_CTRL1_MODE_DP);
+ link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK,
+ PTN36502_DP_LINK_CTRL_LANES_4);
+ break;
+
+ case TYPEC_DP_STATE_D:
+ case TYPEC_DP_STATE_F: /* State F is deprecated */
+ /*
+ * Normal Orientation (CC1)
+ * A -> USB RX
+ * B -> USB TX
+ * C -> DP1
+ * D -> DP0
+ * Flipped Orientation (CC2)
+ * A -> DP0
+ * B -> DP1
+ * C -> USB TX
+ * D -> USB RX
+ */
+
+ /* USB 3.1 Gen 1 and 2-lane DP */
+ ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK,
+ PTN36502_MODE_CTRL1_MODE_USB_DP);
+ link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK,
+ PTN36502_DP_LINK_CTRL_LANES_2);
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Enable AUX monitoring */
+ regmap_write(ptn->regmap, PTN36502_DEVICE_CTRL_REG,
+ FIELD_PREP(PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK,
+ PTN36502_DEVICE_CTRL_AUX_MONITORING_EN));
+
+ /* Enable AUX switch path */
+ ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK,
+ PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON);
+ if (reverse)
+ ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK,
+ PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE);
+ regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val);
+
+ /* DP Link rate: 5.4 Gbps (HBR2) */
+ link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LINK_RATE_MASK,
+ PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS);
+ regmap_write(ptn->regmap, PTN36502_DP_LINK_CTRL_REG, link_ctrl_val);
+
+ /*
+ * For all lanes:
+ * - Rx equivalization gain: 3 dB
+ * - TX output swing control: 800 mVppd
+ * - Pre-emphasis control: 3.5 dB
+ */
+ lane_ctrl_val = FIELD_PREP(PTN36502_DP_LANE_CTRL_RX_GAIN_MASK,
+ PTN36502_DP_LANE_CTRL_RX_GAIN_3DB) |
+ FIELD_PREP(PTN36502_DP_LANE_CTRL_TX_SWING_MASK,
+ PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD) |
+ FIELD_PREP(PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK,
+ PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB);
+ regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(0), lane_ctrl_val);
+ regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(1), lane_ctrl_val);
+ regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(2), lane_ctrl_val);
+ regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(3), lane_ctrl_val);
+
+ return 0;
+}
+
+static int ptn36502_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
+{
+ struct ptn36502 *ptn = typec_switch_get_drvdata(sw);
+ int ret;
+
+ ret = typec_switch_set(ptn->typec_switch, orientation);
+ if (ret)
+ return ret;
+
+ mutex_lock(&ptn->lock);
+
+ if (ptn->orientation != orientation) {
+ ptn->orientation = orientation;
+
+ ret = ptn36502_set(ptn);
+ }
+
+ mutex_unlock(&ptn->lock);
+
+ return ret;
+}
+
+static int ptn36502_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
+{
+ struct ptn36502 *ptn = typec_retimer_get_drvdata(retimer);
+ int ret = 0;
+
+ mutex_lock(&ptn->lock);
+
+ if (ptn->mode != state->mode) {
+ ptn->mode = state->mode;
+
+ if (state->alt)
+ ptn->svid = state->alt->svid;
+ else
+ ptn->svid = 0; // No SVID
+
+ ret = ptn36502_set(ptn);
+ }
+
+ mutex_unlock(&ptn->lock);
+
+ return ret;
+}
+
+static int ptn36502_detect(struct ptn36502 *ptn)
+{
+ struct device *dev = &ptn->client->dev;
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(ptn->regmap, PTN36502_CHIP_ID_REG,
+ &reg_val);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read chip ID\n");
+
+ if (reg_val != PTN36502_CHIP_ID)
+ return dev_err_probe(dev, -ENODEV, "Unexpected chip ID: %x\n", reg_val);
+
+ ret = regmap_read(ptn->regmap, PTN36502_CHIP_REVISION_REG,
+ &reg_val);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to read chip revision\n");
+
+ dev_dbg(dev, "Chip revision: base layer version %lx, metal layer version %lx\n",
+ FIELD_GET(PTN36502_CHIP_REVISION_BASE_MASK, reg_val),
+ FIELD_GET(PTN36502_CHIP_REVISION_METAL_MASK, reg_val));
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE)
+static int ptn36502_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ptn36502 *ptn = container_of(bridge, struct ptn36502, bridge);
+ struct drm_bridge *next_bridge;
+
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+ return -EINVAL;
+
+ next_bridge = devm_drm_of_get_bridge(&ptn->client->dev, ptn->client->dev.of_node, 0, 0);
+ if (IS_ERR(next_bridge)) {
+ dev_err(&ptn->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge);
+ return PTR_ERR(next_bridge);
+ }
+
+ return drm_bridge_attach(bridge->encoder, next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+}
+
+static const struct drm_bridge_funcs ptn36502_bridge_funcs = {
+ .attach = ptn36502_bridge_attach,
+};
+
+static int ptn36502_register_bridge(struct ptn36502 *ptn)
+{
+ ptn->bridge.funcs = &ptn36502_bridge_funcs;
+ ptn->bridge.of_node = ptn->client->dev.of_node;
+
+ return devm_drm_bridge_add(&ptn->client->dev, &ptn->bridge);
+}
+#else
+static int ptn36502_register_bridge(struct ptn36502 *ptn)
+{
+ return 0;
+}
+#endif
+
+static const struct regmap_config ptn36502_regmap = {
+ .max_register = 0x0d,
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int ptn36502_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct typec_switch_desc sw_desc = { };
+ struct typec_retimer_desc retimer_desc = { };
+ struct ptn36502 *ptn;
+ int ret;
+
+ ptn = devm_kzalloc(dev, sizeof(*ptn), GFP_KERNEL);
+ if (!ptn)
+ return -ENOMEM;
+
+ ptn->client = client;
+
+ ptn->regmap = devm_regmap_init_i2c(client, &ptn36502_regmap);
+ if (IS_ERR(ptn->regmap)) {
+ dev_err(&client->dev, "Failed to allocate register map\n");
+ return PTR_ERR(ptn->regmap);
+ }
+
+ ptn->mode = TYPEC_STATE_SAFE;
+ ptn->orientation = TYPEC_ORIENTATION_NONE;
+
+ mutex_init(&ptn->lock);
+
+ ptn->vdd18_supply = devm_regulator_get_optional(dev, "vdd18");
+ if (IS_ERR(ptn->vdd18_supply))
+ return PTR_ERR(ptn->vdd18_supply);
+
+ ptn->typec_switch = fwnode_typec_switch_get(dev->fwnode);
+ if (IS_ERR(ptn->typec_switch))
+ return dev_err_probe(dev, PTR_ERR(ptn->typec_switch),
+ "Failed to acquire orientation-switch\n");
+
+ ret = regulator_enable(ptn->vdd18_supply);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable vdd18\n");
+
+ ret = ptn36502_detect(ptn);
+ if (ret)
+ goto err_disable_regulator;
+
+ ret = ptn36502_register_bridge(ptn);
+ if (ret)
+ goto err_disable_regulator;
+
+ sw_desc.drvdata = ptn;
+ sw_desc.fwnode = dev->fwnode;
+ sw_desc.set = ptn36502_sw_set;
+
+ ptn->sw = typec_switch_register(dev, &sw_desc);
+ if (IS_ERR(ptn->sw)) {
+ ret = dev_err_probe(dev, PTR_ERR(ptn->sw),
+ "Failed to register typec switch\n");
+ goto err_disable_regulator;
+ }
+
+ retimer_desc.drvdata = ptn;
+ retimer_desc.fwnode = dev->fwnode;
+ retimer_desc.set = ptn36502_retimer_set;
+
+ ptn->retimer = typec_retimer_register(dev, &retimer_desc);
+ if (IS_ERR(ptn->retimer)) {
+ ret = dev_err_probe(dev, PTR_ERR(ptn->retimer),
+ "Failed to register typec retimer\n");
+ goto err_switch_unregister;
+ }
+
+ return 0;
+
+err_switch_unregister:
+ typec_switch_unregister(ptn->sw);
+
+err_disable_regulator:
+ regulator_disable(ptn->vdd18_supply);
+
+ return ret;
+}
+
+static void ptn36502_remove(struct i2c_client *client)
+{
+ struct ptn36502 *ptn = i2c_get_clientdata(client);
+
+ typec_retimer_unregister(ptn->retimer);
+ typec_switch_unregister(ptn->sw);
+
+ regulator_disable(ptn->vdd18_supply);
+}
+
+static const struct i2c_device_id ptn36502_table[] = {
+ { "ptn36502" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ptn36502_table);
+
+static const struct of_device_id ptn36502_of_table[] = {
+ { .compatible = "nxp,ptn36502" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ptn36502_of_table);
+
+static struct i2c_driver ptn36502_driver = {
+ .driver = {
+ .name = "ptn36502",
+ .of_match_table = ptn36502_of_table,
+ },
+ .probe = ptn36502_probe,
+ .remove = ptn36502_remove,
+ .id_table = ptn36502_table,
+};
+module_i2c_driver(ptn36502_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca.weiss@fairphone.com>");
+MODULE_DESCRIPTION("NXP PTN36502 Type-C driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c
index 8cc66e4467..85d015cdbe 100644
--- a/drivers/usb/typec/pd.c
+++ b/drivers/usb/typec/pd.c
@@ -83,14 +83,12 @@ unchunked_extended_messages_supported_show(struct device *dev,
}
static DEVICE_ATTR_RO(unchunked_extended_messages_supported);
-/*
- * REVISIT: Peak Current requires access also to the RDO.
static ssize_t
peak_current_show(struct device *dev, struct device_attribute *attr, char *buf)
{
- ...
+ return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_PEAK_CURR_SHIFT) & 3);
}
-*/
+static DEVICE_ATTR_RO(peak_current);
static ssize_t
fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf)
@@ -135,7 +133,7 @@ static struct attribute *source_fixed_supply_attrs[] = {
&dev_attr_usb_communication_capable.attr,
&dev_attr_dual_role_data.attr,
&dev_attr_unchunked_extended_messages_supported.attr,
- /*&dev_attr_peak_current.attr,*/
+ &dev_attr_peak_current.attr,
&dev_attr_voltage.attr,
&maximum_current_attr.attr,
NULL
@@ -144,7 +142,7 @@ static struct attribute *source_fixed_supply_attrs[] = {
static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
{
if (to_pdo(kobj_to_dev(kobj))->object_position &&
- /*attr != &dev_attr_peak_current.attr &&*/
+ attr != &dev_attr_peak_current.attr &&
attr != &dev_attr_voltage.attr &&
attr != &maximum_current_attr.attr &&
attr != &operational_current_attr.attr)
diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c
index a929e000d0..d42da5720a 100644
--- a/drivers/usb/typec/port-mapper.c
+++ b/drivers/usb/typec/port-mapper.c
@@ -8,17 +8,22 @@
#include <linux/acpi.h>
#include <linux/component.h>
+#include <linux/usb.h>
#include "class.h"
static int typec_aggregate_bind(struct device *dev)
{
- return component_bind_all(dev, NULL);
+ struct typec_port *port = to_typec_port(dev);
+
+ return component_bind_all(dev, &port->con);
}
static void typec_aggregate_unbind(struct device *dev)
{
- component_unbind_all(dev, NULL);
+ struct typec_port *port = to_typec_port(dev);
+
+ component_unbind_all(dev, &port->con);
}
static const struct component_master_ops typec_aggregate_ops = {
diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c
index 17ebc5fb68..67422d45eb 100644
--- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c
+++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c
@@ -7,6 +7,7 @@
#include <linux/bits.h>
#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
@@ -51,13 +52,19 @@
/* 1b0 as fixed rx threshold of rd/rp 0.55V, 1b1 depends on RTCRTL4[0] */
#define BMCIO_RXDZEN BIT(0)
+struct rt1711h_chip_info {
+ u32 rxdz_sel;
+ u16 did;
+ bool enable_pd30_extended_message;
+};
+
struct rt1711h_chip {
struct tcpci_data data;
struct tcpci *tcpci;
struct device *dev;
struct regulator *vbus;
+ const struct rt1711h_chip_info *info;
bool src_en;
- u16 did;
};
static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val)
@@ -105,7 +112,7 @@ static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata)
return ret;
/* Enable PD30 extended message for RT1715 */
- if (chip->did == RT1715_DID) {
+ if (chip->info->enable_pd30_extended_message) {
ret = regmap_update_bits(regmap, RT1711H_RTCTRL8,
RT1711H_ENEXTMSG, RT1711H_ENEXTMSG);
if (ret < 0)
@@ -200,10 +207,7 @@ static inline int rt1711h_init_cc_params(struct rt1711h_chip *chip, u8 status)
if ((cc1 >= TYPEC_CC_RP_1_5 && cc2 < TYPEC_CC_RP_DEF) ||
(cc2 >= TYPEC_CC_RP_1_5 && cc1 < TYPEC_CC_RP_DEF)) {
rxdz_en = BMCIO_RXDZEN;
- if (chip->did == RT1715_DID)
- rxdz_sel = RT1711H_BMCIO_RXDZSEL;
- else
- rxdz_sel = 0;
+ rxdz_sel = chip->info->rxdz_sel;
} else {
rxdz_en = 0;
rxdz_sel = RT1711H_BMCIO_RXDZSEL;
@@ -319,7 +323,7 @@ static int rt1711h_check_revision(struct i2c_client *i2c, struct rt1711h_chip *c
ret = i2c_smbus_read_word_data(i2c, TCPC_BCD_DEV);
if (ret < 0)
return ret;
- if (ret != chip->did) {
+ if (ret != chip->info->did) {
dev_err(&i2c->dev, "did is not correct, 0x%04x\n", ret);
return -ENODEV;
}
@@ -336,7 +340,7 @@ static int rt1711h_probe(struct i2c_client *client)
if (!chip)
return -ENOMEM;
- chip->did = (size_t)device_get_match_data(&client->dev);
+ chip->info = i2c_get_match_data(client);
ret = rt1711h_check_revision(client, chip);
if (ret < 0) {
@@ -391,26 +395,34 @@ static void rt1711h_remove(struct i2c_client *client)
tcpci_unregister_port(chip->tcpci);
}
+static const struct rt1711h_chip_info rt1711h = {
+ .did = RT1711H_DID,
+};
+
+static const struct rt1711h_chip_info rt1715 = {
+ .rxdz_sel = RT1711H_BMCIO_RXDZSEL,
+ .did = RT1715_DID,
+ .enable_pd30_extended_message = true,
+};
+
static const struct i2c_device_id rt1711h_id[] = {
- { "rt1711h", 0 },
- { "rt1715", 0 },
- { }
+ { "rt1711h", (kernel_ulong_t)&rt1711h },
+ { "rt1715", (kernel_ulong_t)&rt1715 },
+ {}
};
MODULE_DEVICE_TABLE(i2c, rt1711h_id);
-#ifdef CONFIG_OF
static const struct of_device_id rt1711h_of_match[] = {
- { .compatible = "richtek,rt1711h", .data = (void *)RT1711H_DID },
- { .compatible = "richtek,rt1715", .data = (void *)RT1715_DID },
- {},
+ { .compatible = "richtek,rt1711h", .data = &rt1711h },
+ { .compatible = "richtek,rt1715", .data = &rt1715 },
+ {}
};
MODULE_DEVICE_TABLE(of, rt1711h_of_match);
-#endif
static struct i2c_driver rt1711h_i2c_driver = {
.driver = {
.name = "rt1711h",
- .of_match_table = of_match_ptr(rt1711h_of_match),
+ .of_match_table = rt1711h_of_match,
},
.probe = rt1711h_probe,
.remove = rt1711h_remove,
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 6d455ca761..a8e03cfde9 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -517,9 +517,9 @@ static const char * const pd_rev[] = {
((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \
(cc) == TYPEC_CC_RP_3_0)
+/* As long as cc is pulled up, we can consider it as sink. */
#define tcpm_port_is_sink(port) \
- ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \
- (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1)))
+ (tcpm_cc_is_sink((port)->cc1) || tcpm_cc_is_sink((port)->cc2))
#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD)
#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA)
@@ -3730,9 +3730,6 @@ static void tcpm_detach(struct tcpm_port *port)
if (tcpm_port_is_disconnected(port))
port->hard_reset_count = 0;
- port->try_src_count = 0;
- port->try_snk_count = 0;
-
if (!port->attached)
return;
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 37b56ce75f..196535ad99 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -17,6 +17,7 @@
#include <linux/usb/typec_altmode.h>
#include <linux/usb/role.h>
#include <linux/workqueue.h>
+#include <linux/firmware.h>
#include "tps6598x.h"
#include "trace.h"
@@ -36,13 +37,33 @@
#define TPS_REG_STATUS 0x1a
#define TPS_REG_SYSTEM_CONF 0x28
#define TPS_REG_CTRL_CONF 0x29
+#define TPS_REG_BOOT_STATUS 0x2D
#define TPS_REG_POWER_STATUS 0x3f
+#define TPS_REG_PD_STATUS 0x40
#define TPS_REG_RX_IDENTITY_SOP 0x48
#define TPS_REG_DATA_STATUS 0x5f
+#define TPS_REG_SLEEP_CONF 0x70
/* TPS_REG_SYSTEM_CONF bits */
#define TPS_SYSCONF_PORTINFO(c) ((c) & 7)
+/*
+ * BPMs task timeout, recommended 5 seconds
+ * pg.48 TPS2575 Host Interface Technical Reference
+ * Manual (Rev. A)
+ * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf
+ */
+#define TPS_BUNDLE_TIMEOUT 0x32
+
+/* BPMs return code */
+#define TPS_TASK_BPMS_INVALID_BUNDLE_SIZE 0x4
+#define TPS_TASK_BPMS_INVALID_SLAVE_ADDR 0x5
+#define TPS_TASK_BPMS_INVALID_TIMEOUT 0x6
+
+/* PBMc data out */
+#define TPS_PBMC_RC 0 /* Return code */
+#define TPS_PBMC_DPCS 2 /* device patch complete status */
+
enum {
TPS_PORTINFO_SINK,
TPS_PORTINFO_SINK_ACCESSORY,
@@ -68,6 +89,7 @@ enum {
TPS_MODE_BOOT,
TPS_MODE_BIST,
TPS_MODE_DISC,
+ TPS_MODE_PTCH,
};
static const char *const modes[] = {
@@ -75,11 +97,22 @@ static const char *const modes[] = {
[TPS_MODE_BOOT] = "BOOT",
[TPS_MODE_BIST] = "BIST",
[TPS_MODE_DISC] = "DISC",
+ [TPS_MODE_PTCH] = "PTCH",
};
/* Unrecognized commands will be replaced with "!CMD" */
#define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321)
+struct tps6598x;
+
+struct tipd_data {
+ irq_handler_t irq_handler;
+ int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node);
+ void (*trace_power_status)(u16 status);
+ void (*trace_status)(u32 status);
+ int (*apply_patch)(struct tps6598x *tps);
+};
+
struct tps6598x {
struct device *dev;
struct regmap *regmap;
@@ -97,9 +130,11 @@ struct tps6598x {
enum power_supply_usb_type usb_type;
int wakeup;
+ u32 status; /* status reg */
u16 pwr_status;
struct delayed_work wq_poll;
- irq_handler_t irq_handler;
+
+ const struct tipd_data *data;
};
static enum power_supply_property tps6598x_psy_props[] = {
@@ -180,6 +215,11 @@ static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val)
return tps6598x_block_read(tps, reg, val, sizeof(u64));
}
+static inline int tps6598x_write8(struct tps6598x *tps, u8 reg, u8 val)
+{
+ return tps6598x_block_write(tps, reg, &val, sizeof(u8));
+}
+
static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val)
{
return tps6598x_block_write(tps, reg, &val, sizeof(u64));
@@ -282,9 +322,10 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
power_supply_changed(tps->psy);
}
-static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
+static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd,
size_t in_len, u8 *in_data,
- size_t out_len, u8 *out_data)
+ size_t out_len, u8 *out_data,
+ u32 cmd_timeout_ms, u32 res_delay_ms)
{
unsigned long timeout;
u32 val;
@@ -307,8 +348,7 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
if (ret < 0)
return ret;
- /* XXX: Using 1s for now, but it may not be enough for every command. */
- timeout = jiffies + msecs_to_jiffies(1000);
+ timeout = jiffies + msecs_to_jiffies(cmd_timeout_ms);
do {
ret = tps6598x_read32(tps, TPS_REG_CMD1, &val);
@@ -321,6 +361,9 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
return -ETIMEDOUT;
} while (val);
+ /* some commands require delay for the result to be available */
+ mdelay(res_delay_ms);
+
if (out_len) {
ret = tps6598x_block_read(tps, TPS_REG_DATA1,
out_data, out_len);
@@ -345,6 +388,14 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
return 0;
}
+static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
+ size_t in_len, u8 *in_data,
+ size_t out_len, u8 *out_data)
+{
+ return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data,
+ out_len, out_data, 1000, 0);
+}
+
static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role)
{
const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF";
@@ -419,7 +470,9 @@ static bool tps6598x_read_status(struct tps6598x *tps, u32 *status)
dev_err(tps->dev, "%s: failed to read status\n", __func__);
return false;
}
- trace_tps6598x_status(*status);
+
+ if (tps->data->trace_status)
+ tps->data->trace_status(*status);
return true;
}
@@ -450,7 +503,9 @@ static bool tps6598x_read_power_status(struct tps6598x *tps)
return false;
}
tps->pwr_status = pwr_status;
- trace_tps6598x_power_status(pwr_status);
+
+ if (tps->data->trace_power_status)
+ tps->data->trace_power_status(pwr_status);
return true;
}
@@ -513,6 +568,65 @@ err_unlock:
return IRQ_NONE;
}
+static bool tps6598x_has_role_changed(struct tps6598x *tps, u32 status)
+{
+ status ^= tps->status;
+
+ return status & (TPS_STATUS_PORTROLE | TPS_STATUS_DATAROLE);
+}
+
+static irqreturn_t tps25750_interrupt(int irq, void *data)
+{
+ struct tps6598x *tps = data;
+ u64 event[2] = { };
+ u32 status;
+ int ret;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event, 11);
+ if (ret) {
+ dev_err(tps->dev, "%s: failed to read events\n", __func__);
+ goto err_unlock;
+ }
+ trace_tps25750_irq(event[0]);
+
+ if (!(event[0] | event[1]))
+ goto err_unlock;
+
+ if (!tps6598x_read_status(tps, &status))
+ goto err_clear_ints;
+
+ if ((event[0] | event[1]) & TPS_REG_INT_POWER_STATUS_UPDATE)
+ if (!tps6598x_read_power_status(tps))
+ goto err_clear_ints;
+
+ if ((event[0] | event[1]) & TPS_REG_INT_DATA_STATUS_UPDATE)
+ if (!tps6598x_read_data_status(tps))
+ goto err_clear_ints;
+
+ /*
+ * data/port roles could be updated independently after
+ * a plug event. Therefore, we need to check
+ * for pr/dr status change to set TypeC dr/pr accordingly.
+ */
+ if ((event[0] | event[1]) & TPS_REG_INT_PLUG_EVENT ||
+ tps6598x_has_role_changed(tps, status))
+ tps6598x_handle_plug_event(tps, status);
+
+ tps->status = status;
+
+err_clear_ints:
+ tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event, 11);
+
+err_unlock:
+ mutex_unlock(&tps->lock);
+
+ if (event[0] | event[1])
+ return IRQ_HANDLED;
+ return IRQ_NONE;
+}
+
static irqreturn_t tps6598x_interrupt(int irq, void *data)
{
struct tps6598x *tps = data;
@@ -568,7 +682,7 @@ static void tps6598x_poll_work(struct work_struct *work)
struct tps6598x *tps = container_of(to_delayed_work(work),
struct tps6598x, wq_poll);
- tps->irq_handler(0, tps);
+ tps->data->irq_handler(0, tps);
queue_delayed_work(system_power_efficient_wq,
&tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL));
}
@@ -582,12 +696,15 @@ static int tps6598x_check_mode(struct tps6598x *tps)
if (ret)
return ret;
- switch (match_string(modes, ARRAY_SIZE(modes), mode)) {
+ ret = match_string(modes, ARRAY_SIZE(modes), mode);
+
+ switch (ret) {
case TPS_MODE_APP:
- return 0;
+ case TPS_MODE_PTCH:
+ return ret;
case TPS_MODE_BOOT:
dev_warn(tps->dev, "dead-battery condition\n");
- return 0;
+ return ret;
case TPS_MODE_BIST:
case TPS_MODE_DISC:
default:
@@ -697,18 +814,375 @@ static int devm_tps6598_psy_register(struct tps6598x *tps)
return PTR_ERR_OR_ZERO(tps->psy);
}
+static int
+tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
+{
+ int ret;
+ u32 conf;
+ struct typec_capability typec_cap = { };
+
+ ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
+ if (ret)
+ return ret;
+
+ typec_cap.revision = USB_TYPEC_REV_1_2;
+ typec_cap.pd_revision = 0x200;
+ typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+ typec_cap.driver_data = tps;
+ typec_cap.ops = &tps6598x_ops;
+ typec_cap.fwnode = fwnode;
+
+ switch (TPS_SYSCONF_PORTINFO(conf)) {
+ case TPS_PORTINFO_SINK_ACCESSORY:
+ case TPS_PORTINFO_SINK:
+ typec_cap.type = TYPEC_PORT_SNK;
+ typec_cap.data = TYPEC_PORT_UFP;
+ break;
+ case TPS_PORTINFO_DRP_UFP_DRD:
+ case TPS_PORTINFO_DRP_DFP_DRD:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_DRD;
+ break;
+ case TPS_PORTINFO_DRP_UFP:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_UFP;
+ break;
+ case TPS_PORTINFO_DRP_DFP:
+ typec_cap.type = TYPEC_PORT_DRP;
+ typec_cap.data = TYPEC_PORT_DFP;
+ break;
+ case TPS_PORTINFO_SOURCE:
+ typec_cap.type = TYPEC_PORT_SRC;
+ typec_cap.data = TYPEC_PORT_DFP;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ tps->port = typec_register_port(tps->dev, &typec_cap);
+ if (IS_ERR(tps->port))
+ return PTR_ERR(tps->port);
+
+ return 0;
+}
+
+static int
+tps25750_write_firmware(struct tps6598x *tps,
+ u8 bpms_addr, const u8 *data, size_t len)
+{
+ struct i2c_client *client = to_i2c_client(tps->dev);
+ int ret;
+ u8 slave_addr;
+ int timeout;
+
+ slave_addr = client->addr;
+ timeout = client->adapter->timeout;
+
+ /*
+ * binary configuration size is around ~16Kbytes
+ * which might take some time to finish writing it
+ */
+ client->adapter->timeout = msecs_to_jiffies(5000);
+ client->addr = bpms_addr;
+
+ ret = regmap_raw_write(tps->regmap, data[0], &data[1], len - 1);
+
+ client->addr = slave_addr;
+ client->adapter->timeout = timeout;
+
+ return ret;
+}
+
+static int
+tps25750_exec_pbms(struct tps6598x *tps, u8 *in_data, size_t in_len)
+{
+ int ret;
+ u8 rc;
+
+ ret = tps6598x_exec_cmd_tmo(tps, "PBMs", in_len, in_data,
+ sizeof(rc), &rc, 4000, 0);
+ if (ret)
+ return ret;
+
+ switch (rc) {
+ case TPS_TASK_BPMS_INVALID_BUNDLE_SIZE:
+ dev_err(tps->dev, "%s: invalid fw size\n", __func__);
+ return -EINVAL;
+ case TPS_TASK_BPMS_INVALID_SLAVE_ADDR:
+ dev_err(tps->dev, "%s: invalid slave address\n", __func__);
+ return -EINVAL;
+ case TPS_TASK_BPMS_INVALID_TIMEOUT:
+ dev_err(tps->dev, "%s: timed out\n", __func__);
+ return -ETIMEDOUT;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int tps25750_abort_patch_process(struct tps6598x *tps)
+{
+ int ret;
+
+ ret = tps6598x_exec_cmd(tps, "PBMe", 0, NULL, 0, NULL);
+ if (ret)
+ return ret;
+
+ ret = tps6598x_check_mode(tps);
+ if (ret != TPS_MODE_PTCH)
+ dev_err(tps->dev, "failed to switch to \"PTCH\" mode\n");
+
+ return ret;
+}
+
+static int tps25750_start_patch_burst_mode(struct tps6598x *tps)
+{
+ int ret;
+ const struct firmware *fw;
+ const char *firmware_name;
+ struct {
+ u32 fw_size;
+ u8 addr;
+ u8 timeout;
+ } __packed bpms_data;
+ u32 addr;
+ struct device_node *np = tps->dev->of_node;
+
+ ret = device_property_read_string(tps->dev, "firmware-name",
+ &firmware_name);
+ if (ret)
+ return ret;
+
+ ret = request_firmware(&fw, firmware_name, tps->dev);
+ if (ret) {
+ dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name);
+ return ret;
+ }
+
+ if (fw->size == 0) {
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ ret = of_property_match_string(np, "reg-names", "patch-address");
+ if (ret < 0) {
+ dev_err(tps->dev, "failed to get patch-address %d\n", ret);
+ goto release_fw;
+ }
+
+ ret = of_property_read_u32_index(np, "reg", ret, &addr);
+ if (ret)
+ goto release_fw;
+
+ if (addr == 0 || (addr >= 0x20 && addr <= 0x23)) {
+ dev_err(tps->dev, "wrong patch address %u\n", addr);
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ bpms_data.addr = (u8)addr;
+ bpms_data.fw_size = fw->size;
+ bpms_data.timeout = TPS_BUNDLE_TIMEOUT;
+
+ ret = tps25750_exec_pbms(tps, (u8 *)&bpms_data, sizeof(bpms_data));
+ if (ret)
+ goto release_fw;
+
+ ret = tps25750_write_firmware(tps, bpms_data.addr, fw->data, fw->size);
+ if (ret) {
+ dev_err(tps->dev, "Failed to write patch %s of %zu bytes\n",
+ firmware_name, fw->size);
+ goto release_fw;
+ }
+
+ /*
+ * A delay of 500us is required after the firmware is written
+ * based on pg.62 in tps6598x Host Interface Technical
+ * Reference Manual
+ * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf
+ */
+ udelay(500);
+
+release_fw:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int tps25750_complete_patch_process(struct tps6598x *tps)
+{
+ int ret;
+ u8 out_data[40];
+ u8 dummy[2] = { };
+
+ /*
+ * Without writing something to DATA_IN, this command would
+ * return an error
+ */
+ ret = tps6598x_exec_cmd_tmo(tps, "PBMc", sizeof(dummy), dummy,
+ sizeof(out_data), out_data, 2000, 20);
+ if (ret)
+ return ret;
+
+ if (out_data[TPS_PBMC_RC]) {
+ dev_err(tps->dev,
+ "%s: pbmc failed: %u\n", __func__,
+ out_data[TPS_PBMC_RC]);
+ return -EIO;
+ }
+
+ if (out_data[TPS_PBMC_DPCS]) {
+ dev_err(tps->dev,
+ "%s: failed device patch complete status: %u\n",
+ __func__, out_data[TPS_PBMC_DPCS]);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int tps25750_apply_patch(struct tps6598x *tps)
+{
+ int ret;
+ unsigned long timeout;
+ u64 status = 0;
+
+ ret = tps6598x_block_read(tps, TPS_REG_BOOT_STATUS, &status, 5);
+ if (ret)
+ return ret;
+ /*
+ * Nothing to be done if the configuration
+ * is being loaded from EERPOM
+ */
+ if (status & TPS_BOOT_STATUS_I2C_EEPROM_PRESENT)
+ goto wait_for_app;
+
+ ret = tps25750_start_patch_burst_mode(tps);
+ if (ret) {
+ tps25750_abort_patch_process(tps);
+ return ret;
+ }
+
+ ret = tps25750_complete_patch_process(tps);
+ if (ret)
+ return ret;
+
+wait_for_app:
+ timeout = jiffies + msecs_to_jiffies(1000);
+
+ do {
+ ret = tps6598x_check_mode(tps);
+ if (ret < 0)
+ return ret;
+
+ if (time_is_before_jiffies(timeout))
+ return -ETIMEDOUT;
+
+ } while (ret != TPS_MODE_APP);
+
+ /*
+ * The dead battery flag may be triggered when the controller
+ * port is connected to a device that can source power and
+ * attempts to power up both the controller and the board it is on.
+ * To restore controller functionality, it is necessary to clear
+ * this flag
+ */
+ if (status & TPS_BOOT_STATUS_DEAD_BATTERY_FLAG) {
+ ret = tps6598x_exec_cmd(tps, "DBfg", 0, NULL, 0, NULL);
+ if (ret) {
+ dev_err(tps->dev, "failed to clear dead battery %d\n", ret);
+ return ret;
+ }
+ }
+
+ dev_info(tps->dev, "controller switched to \"APP\" mode\n");
+
+ return 0;
+};
+
+static int tps25750_init(struct tps6598x *tps)
+{
+ int ret;
+
+ ret = tps->data->apply_patch(tps);
+ if (ret)
+ return ret;
+
+ ret = tps6598x_write8(tps, TPS_REG_SLEEP_CONF,
+ TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED);
+ if (ret)
+ dev_warn(tps->dev,
+ "%s: failed to enable sleep mode: %d\n",
+ __func__, ret);
+
+ return 0;
+}
+
+static int
+tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
+{
+ struct typec_capability typec_cap = { };
+ const char *data_role;
+ u8 pd_status;
+ int ret;
+
+ ret = tps6598x_read8(tps, TPS_REG_PD_STATUS, &pd_status);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_string(fwnode, "data-role", &data_role);
+ if (ret) {
+ dev_err(tps->dev, "data-role not found: %d\n", ret);
+ return ret;
+ }
+
+ ret = typec_find_port_data_role(data_role);
+ if (ret < 0) {
+ dev_err(tps->dev, "unknown data-role: %s\n", data_role);
+ return ret;
+ }
+
+ typec_cap.data = ret;
+ typec_cap.revision = USB_TYPEC_REV_1_3;
+ typec_cap.pd_revision = 0x300;
+ typec_cap.driver_data = tps;
+ typec_cap.ops = &tps6598x_ops;
+ typec_cap.fwnode = fwnode;
+ typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+ switch (TPS_PD_STATUS_PORT_TYPE(pd_status)) {
+ case TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE:
+ case TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK:
+ typec_cap.type = TYPEC_PORT_DRP;
+ break;
+ case TPS_PD_STATUS_PORT_TYPE_SINK:
+ typec_cap.type = TYPEC_PORT_SNK;
+ break;
+ case TPS_PD_STATUS_PORT_TYPE_SOURCE:
+ typec_cap.type = TYPEC_PORT_SRC;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ tps->port = typec_register_port(tps->dev, &typec_cap);
+ if (IS_ERR(tps->port))
+ return PTR_ERR(tps->port);
+
+ return 0;
+}
+
static int tps6598x_probe(struct i2c_client *client)
{
- irq_handler_t irq_handler = tps6598x_interrupt;
struct device_node *np = client->dev.of_node;
- struct typec_capability typec_cap = { };
struct tps6598x *tps;
struct fwnode_handle *fwnode;
u32 status;
- u32 conf;
u32 vid;
int ret;
u64 mask1;
+ bool is_tps25750;
tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
if (!tps)
@@ -721,9 +1195,12 @@ static int tps6598x_probe(struct i2c_client *client)
if (IS_ERR(tps->regmap))
return PTR_ERR(tps->regmap);
- ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
- if (ret < 0 || !vid)
- return -ENODEV;
+ is_tps25750 = device_is_compatible(tps->dev, "ti,tps25750");
+ if (!is_tps25750) {
+ ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
+ if (ret < 0 || !vid)
+ return -ENODEV;
+ }
/*
* Checking can the adapter handle SMBus protocol. If it can not, the
@@ -743,7 +1220,6 @@ static int tps6598x_probe(struct i2c_client *client)
APPLE_CD_REG_INT_DATA_STATUS_UPDATE |
APPLE_CD_REG_INT_PLUG_EVENT;
- irq_handler = cd321x_interrupt;
} else {
/* Enable power status, data status and plug event interrupts */
mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
@@ -751,24 +1227,32 @@ static int tps6598x_probe(struct i2c_client *client)
TPS_REG_INT_PLUG_EVENT;
}
- tps->irq_handler = irq_handler;
+ if (dev_fwnode(tps->dev))
+ tps->data = device_get_match_data(tps->dev);
+ else
+ tps->data = i2c_get_match_data(client);
+ if (!tps->data)
+ return -EINVAL;
+
/* Make sure the controller has application firmware running */
ret = tps6598x_check_mode(tps);
- if (ret)
+ if (ret < 0)
return ret;
+ if (is_tps25750 && ret == TPS_MODE_PTCH) {
+ ret = tps25750_init(tps);
+ if (ret)
+ return ret;
+ }
+
ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1);
if (ret)
- return ret;
-
- ret = tps6598x_read32(tps, TPS_REG_STATUS, &status);
- if (ret < 0)
- goto err_clear_mask;
- trace_tps6598x_status(status);
+ goto err_reset_controller;
- ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf);
- if (ret < 0)
+ if (!tps6598x_read_status(tps, &status)) {
+ ret = -ENODEV;
goto err_clear_mask;
+ }
/*
* This fwnode has a "compatible" property, but is never populated as a
@@ -787,50 +1271,13 @@ static int tps6598x_probe(struct i2c_client *client)
goto err_fwnode_put;
}
- typec_cap.revision = USB_TYPEC_REV_1_2;
- typec_cap.pd_revision = 0x200;
- typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
- typec_cap.driver_data = tps;
- typec_cap.ops = &tps6598x_ops;
- typec_cap.fwnode = fwnode;
-
- switch (TPS_SYSCONF_PORTINFO(conf)) {
- case TPS_PORTINFO_SINK_ACCESSORY:
- case TPS_PORTINFO_SINK:
- typec_cap.type = TYPEC_PORT_SNK;
- typec_cap.data = TYPEC_PORT_UFP;
- break;
- case TPS_PORTINFO_DRP_UFP_DRD:
- case TPS_PORTINFO_DRP_DFP_DRD:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_DRD;
- break;
- case TPS_PORTINFO_DRP_UFP:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_UFP;
- break;
- case TPS_PORTINFO_DRP_DFP:
- typec_cap.type = TYPEC_PORT_DRP;
- typec_cap.data = TYPEC_PORT_DFP;
- break;
- case TPS_PORTINFO_SOURCE:
- typec_cap.type = TYPEC_PORT_SRC;
- typec_cap.data = TYPEC_PORT_DFP;
- break;
- default:
- ret = -ENODEV;
- goto err_role_put;
- }
-
ret = devm_tps6598_psy_register(tps);
if (ret)
goto err_role_put;
- tps->port = typec_register_port(&client->dev, &typec_cap);
- if (IS_ERR(tps->port)) {
- ret = PTR_ERR(tps->port);
+ ret = tps->data->register_port(tps, fwnode);
+ if (ret)
goto err_role_put;
- }
if (status & TPS_STATUS_PLUG_PRESENT) {
ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status);
@@ -845,7 +1292,7 @@ static int tps6598x_probe(struct i2c_client *client)
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
- irq_handler,
+ tps->data->irq_handler,
IRQF_SHARED | IRQF_ONESHOT,
dev_name(&client->dev), tps);
} else {
@@ -879,6 +1326,10 @@ err_fwnode_put:
fwnode_handle_put(fwnode);
err_clear_mask:
tps6598x_write64(tps, TPS_REG_INT_MASK1, 0);
+err_reset_controller:
+ /* Reset PD controller to remove any applied patch */
+ if (is_tps25750)
+ tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
return ret;
}
@@ -889,9 +1340,14 @@ static void tps6598x_remove(struct i2c_client *client)
if (!client->irq)
cancel_delayed_work_sync(&tps->wq_poll);
+ devm_free_irq(tps->dev, client->irq, tps);
tps6598x_disconnect(tps, 0);
typec_unregister_port(tps->port);
usb_role_switch_put(tps->role_sw);
+
+ /* Reset PD controller to remove any applied patch */
+ if (device_is_compatible(tps->dev, "ti,tps25750"))
+ tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
}
static int __maybe_unused tps6598x_suspend(struct device *dev)
@@ -914,6 +1370,17 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct tps6598x *tps = i2c_get_clientdata(client);
+ int ret;
+
+ ret = tps6598x_check_mode(tps);
+ if (ret < 0)
+ return ret;
+
+ if (device_is_compatible(tps->dev, "ti,tps25750") && ret == TPS_MODE_PTCH) {
+ ret = tps25750_init(tps);
+ if (ret)
+ return ret;
+ }
if (tps->wakeup) {
disable_irq_wake(client->irq);
@@ -931,15 +1398,38 @@ static const struct dev_pm_ops tps6598x_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(tps6598x_suspend, tps6598x_resume)
};
+static const struct tipd_data cd321x_data = {
+ .irq_handler = cd321x_interrupt,
+ .register_port = tps6598x_register_port,
+ .trace_power_status = trace_tps6598x_power_status,
+ .trace_status = trace_tps6598x_status,
+};
+
+static const struct tipd_data tps6598x_data = {
+ .irq_handler = tps6598x_interrupt,
+ .register_port = tps6598x_register_port,
+ .trace_power_status = trace_tps6598x_power_status,
+ .trace_status = trace_tps6598x_status,
+};
+
+static const struct tipd_data tps25750_data = {
+ .irq_handler = tps25750_interrupt,
+ .register_port = tps25750_register_port,
+ .trace_power_status = trace_tps25750_power_status,
+ .trace_status = trace_tps25750_status,
+ .apply_patch = tps25750_apply_patch,
+};
+
static const struct of_device_id tps6598x_of_match[] = {
- { .compatible = "ti,tps6598x", },
- { .compatible = "apple,cd321x", },
+ { .compatible = "ti,tps6598x", &tps6598x_data},
+ { .compatible = "apple,cd321x", &cd321x_data},
+ { .compatible = "ti,tps25750", &tps25750_data},
{}
};
MODULE_DEVICE_TABLE(of, tps6598x_of_match);
static const struct i2c_device_id tps6598x_id[] = {
- { "tps6598x" },
+ { "tps6598x", (kernel_ulong_t)&tps6598x_data },
{ }
};
MODULE_DEVICE_TABLE(i2c, tps6598x_id);
diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h
index 527857549d..01609bf509 100644
--- a/drivers/usb/typec/tipd/tps6598x.h
+++ b/drivers/usb/typec/tipd/tps6598x.h
@@ -161,6 +161,25 @@
#define TPS_POWER_STATUS_BC12_STATUS_CDP 2
#define TPS_POWER_STATUS_BC12_STATUS_DCP 3
+/* TPS25750_REG_POWER_STATUS bits */
+#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK GENMASK(7, 4)
+#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(p) \
+ TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK, (p))
+#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK GENMASK(9, 8)
+#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS(p) \
+ TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK, (p))
+
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED 0
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS 1
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE 2
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD 3
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD 4
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD 5
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP 6
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP 7
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP 8
+#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP 9
+
/* TPS_REG_DATA_STATUS bits */
#define TPS_DATA_STATUS_DATA_CONNECTION BIT(0)
#define TPS_DATA_STATUS_UPSIDE_DOWN BIT(1)
@@ -199,4 +218,21 @@
#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A BIT(2)
#define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B (BIT(2) | BIT(1))
+/* BOOT STATUS REG*/
+#define TPS_BOOT_STATUS_DEAD_BATTERY_FLAG BIT(2)
+#define TPS_BOOT_STATUS_I2C_EEPROM_PRESENT BIT(3)
+
+/* PD STATUS REG */
+#define TPS_REG_PD_STATUS_PORT_TYPE_MASK GENMASK(5, 4)
+#define TPS_PD_STATUS_PORT_TYPE(x) \
+ TPS_FIELD_GET(TPS_REG_PD_STATUS_PORT_TYPE_MASK, x)
+
+#define TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE 0
+#define TPS_PD_STATUS_PORT_TYPE_SINK 1
+#define TPS_PD_STATUS_PORT_TYPE_SOURCE 2
+#define TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK 3
+
+/* SLEEP CONF REG */
+#define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED BIT(0)
+
#endif /* __TPS6598X_H__ */
diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h
index 12cad1bde7..0669cca12e 100644
--- a/drivers/usb/typec/tipd/trace.h
+++ b/drivers/usb/typec/tipd/trace.h
@@ -74,6 +74,13 @@
{ APPLE_CD_REG_INT_DATA_STATUS_UPDATE, "DATA_STATUS_UPDATE" }, \
{ APPLE_CD_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" })
+#define show_tps25750_irq_flags(flags) \
+ __print_flags_u64(flags, "|", \
+ { TPS_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \
+ { TPS_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \
+ { TPS_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }, \
+ { TPS_REG_INT_PD_STATUS_UPDATE, "PD_STATUS_UPDATE" })
+
#define TPS6598X_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \
TPS_STATUS_PP_5V0_SWITCH_MASK | \
TPS_STATUS_PP_HV_SWITCH_MASK | \
@@ -84,6 +91,14 @@
TPS_STATUS_USB_HOST_PRESENT_MASK | \
TPS_STATUS_LEGACY_MASK))
+#define TPS25750_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \
+ GENMASK(19, 7) | \
+ TPS_STATUS_VBUS_STATUS_MASK | \
+ TPS_STATUS_USB_HOST_PRESENT_MASK | \
+ TPS_STATUS_LEGACY_MASK | \
+ BIT(26) | \
+ GENMASK(31, 28)))
+
#define show_status_conn_state(status) \
__print_symbolic(TPS_STATUS_CONN_STATE((status)), \
{ TPS_STATUS_CONN_STATE_CONN_WITH_R_A, "conn-Ra" }, \
@@ -141,6 +156,14 @@
{ TPS_STATUS_HIGH_VOLAGE_WARNING, "HIGH_VOLAGE_WARNING" }, \
{ TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" })
+#define show_tps25750_status_flags(flags) \
+ __print_flags((flags & TPS25750_STATUS_FLAGS_MASK), "|", \
+ { TPS_STATUS_PLUG_PRESENT, "PLUG_PRESENT" }, \
+ { TPS_STATUS_PLUG_UPSIDE_DOWN, "UPSIDE_DOWN" }, \
+ { TPS_STATUS_PORTROLE, "PORTROLE" }, \
+ { TPS_STATUS_DATAROLE, "DATAROLE" }, \
+ { TPS_STATUS_BIST, "BIST" })
+
#define show_power_status_source_sink(power_status) \
__print_symbolic(TPS_POWER_STATUS_SOURCESINK(power_status), \
{ 1, "sink" }, \
@@ -159,6 +182,19 @@
{ TPS_POWER_STATUS_BC12_STATUS_CDP, "cdp" }, \
{ TPS_POWER_STATUS_BC12_STATUS_SDP, "sdp" })
+#define show_tps25750_power_status_charger_detect_status(power_status) \
+ __print_symbolic(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(power_status), \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED, "disabled"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS, "in progress"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE, "none"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD, "spd"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD, "cpd"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD, "dpd"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP, "divider 1 dcp"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP, "divider 2 dcp"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP, "divider 3 dpc"}, \
+ { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP, "1.2V dpc"})
+
#define TPS_DATA_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK | \
TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK | \
TPS_DATA_STATUS_TBT_CABLE_GEN_MASK))
@@ -230,6 +266,21 @@ TRACE_EVENT(cd321x_irq,
show_cd321x_irq_flags(__entry->event))
);
+TRACE_EVENT(tps25750_irq,
+ TP_PROTO(u64 event),
+ TP_ARGS(event),
+
+ TP_STRUCT__entry(
+ __field(u64, event)
+ ),
+
+ TP_fast_assign(
+ __entry->event = event;
+ ),
+
+ TP_printk("event=%s", show_tps25750_irq_flags(__entry->event))
+);
+
TRACE_EVENT(tps6598x_status,
TP_PROTO(u32 status),
TP_ARGS(status),
@@ -257,6 +308,27 @@ TRACE_EVENT(tps6598x_status,
)
);
+TRACE_EVENT(tps25750_status,
+ TP_PROTO(u32 status),
+ TP_ARGS(status),
+
+ TP_STRUCT__entry(
+ __field(u32, status)
+ ),
+
+ TP_fast_assign(
+ __entry->status = status;
+ ),
+
+ TP_printk("conn: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s",
+ show_status_conn_state(__entry->status),
+ show_status_vbus_status(__entry->status),
+ show_status_usb_host_present(__entry->status),
+ show_status_legacy(__entry->status),
+ show_tps25750_status_flags(__entry->status)
+ )
+);
+
TRACE_EVENT(tps6598x_power_status,
TP_PROTO(u16 power_status),
TP_ARGS(power_status),
@@ -277,6 +349,26 @@ TRACE_EVENT(tps6598x_power_status,
)
);
+TRACE_EVENT(tps25750_power_status,
+ TP_PROTO(u16 power_status),
+ TP_ARGS(power_status),
+
+ TP_STRUCT__entry(
+ __field(u16, power_status)
+ ),
+
+ TP_fast_assign(
+ __entry->power_status = power_status;
+ ),
+
+ TP_printk("conn: %d, pwr-role: %s, typec: %s, charger detect: %s",
+ !!TPS_POWER_STATUS_CONNECTION(__entry->power_status),
+ show_power_status_source_sink(__entry->power_status),
+ show_power_status_typec_status(__entry->power_status),
+ show_tps25750_power_status_charger_detect_status(__entry->power_status)
+ )
+);
+
TRACE_EVENT(tps6598x_data_status,
TP_PROTO(u32 data_status),
TP_ARGS(data_status),
diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c
index 73cd5bf350..d9d3c91125 100644
--- a/drivers/usb/typec/ucsi/displayport.c
+++ b/drivers/usb/typec/ucsi/displayport.c
@@ -315,7 +315,7 @@ struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
struct ucsi_dp *dp;
/* We can't rely on the firmware with the capabilities. */
- desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
+ desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE;
/* Claiming that we support all pin assignments */
desc->vdo |= all_assignments << 8;
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 61b64558f9..8f9dff993b 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -935,7 +935,9 @@ static void ucsi_handle_connector_change(struct work_struct *work)
clear_bit(EVENT_PENDING, &con->ucsi->flags);
+ mutex_lock(&ucsi->ppm_lock);
ret = ucsi_acknowledge_connector_change(ucsi);
+ mutex_unlock(&ucsi->ppm_lock);
if (ret)
dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c
index 6bbf490ac4..928eacbeb2 100644
--- a/drivers/usb/typec/ucsi/ucsi_acpi.c
+++ b/drivers/usb/typec/ucsi/ucsi_acpi.c
@@ -25,6 +25,8 @@ struct ucsi_acpi {
unsigned long flags;
guid_t guid;
u64 cmd;
+ bool dell_quirk_probed;
+ bool dell_quirk_active;
};
static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func)
@@ -73,9 +75,13 @@ static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
+ bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI;
int ret;
- set_bit(COMMAND_PENDING, &ua->flags);
+ if (ack)
+ set_bit(ACK_PENDING, &ua->flags);
+ else
+ set_bit(COMMAND_PENDING, &ua->flags);
ret = ucsi_acpi_async_write(ucsi, offset, val, val_len);
if (ret)
@@ -85,7 +91,10 @@ static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset,
ret = -ETIMEDOUT;
out_clear_bit:
- clear_bit(COMMAND_PENDING, &ua->flags);
+ if (ack)
+ clear_bit(ACK_PENDING, &ua->flags);
+ else
+ clear_bit(COMMAND_PENDING, &ua->flags);
return ret;
}
@@ -119,12 +128,73 @@ static const struct ucsi_operations ucsi_zenbook_ops = {
.async_write = ucsi_acpi_async_write
};
-static const struct dmi_system_id zenbook_dmi_id[] = {
+/*
+ * Some Dell laptops expect that an ACK command with the
+ * UCSI_ACK_CONNECTOR_CHANGE bit set is followed by a (separate)
+ * ACK command that only has the UCSI_ACK_COMMAND_COMPLETE bit set.
+ * If this is not done events are not delivered to OSPM and
+ * subsequent commands will timeout.
+ */
+static int
+ucsi_dell_sync_write(struct ucsi *ucsi, unsigned int offset,
+ const void *val, size_t val_len)
+{
+ struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
+ u64 cmd = *(u64 *)val, ack = 0;
+ int ret;
+
+ if (UCSI_COMMAND(cmd) == UCSI_ACK_CC_CI &&
+ cmd & UCSI_ACK_CONNECTOR_CHANGE)
+ ack = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE;
+
+ ret = ucsi_acpi_sync_write(ucsi, offset, val, val_len);
+ if (ret != 0)
+ return ret;
+ if (ack == 0)
+ return ret;
+
+ if (!ua->dell_quirk_probed) {
+ ua->dell_quirk_probed = true;
+
+ cmd = UCSI_GET_CAPABILITY;
+ ret = ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &cmd,
+ sizeof(cmd));
+ if (ret == 0)
+ return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL,
+ &ack, sizeof(ack));
+ if (ret != -ETIMEDOUT)
+ return ret;
+
+ ua->dell_quirk_active = true;
+ dev_err(ua->dev, "Firmware bug: Additional ACK required after ACKing a connector change.\n");
+ dev_err(ua->dev, "Firmware bug: Enabling workaround\n");
+ }
+
+ if (!ua->dell_quirk_active)
+ return ret;
+
+ return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &ack, sizeof(ack));
+}
+
+static const struct ucsi_operations ucsi_dell_ops = {
+ .read = ucsi_acpi_read,
+ .sync_write = ucsi_dell_sync_write,
+ .async_write = ucsi_acpi_async_write
+};
+
+static const struct dmi_system_id ucsi_acpi_quirks[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UA_UM325UA"),
},
+ .driver_data = (void *)&ucsi_zenbook_ops,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ },
+ .driver_data = (void *)&ucsi_dell_ops,
},
{ }
};
@@ -142,8 +212,10 @@ static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data)
if (UCSI_CCI_CONNECTOR(cci))
ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci));
- if (test_bit(COMMAND_PENDING, &ua->flags) &&
- cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE))
+ if (cci & UCSI_CCI_ACK_COMPLETE && test_bit(ACK_PENDING, &ua->flags))
+ complete(&ua->complete);
+ if (cci & UCSI_CCI_COMMAND_COMPLETE &&
+ test_bit(COMMAND_PENDING, &ua->flags))
complete(&ua->complete);
}
@@ -151,6 +223,7 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
const struct ucsi_operations *ops = &ucsi_acpi_ops;
+ const struct dmi_system_id *id;
struct ucsi_acpi *ua;
struct resource *res;
acpi_status status;
@@ -180,8 +253,9 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
init_completion(&ua->complete);
ua->dev = &pdev->dev;
- if (dmi_check_system(zenbook_dmi_id))
- ops = &ucsi_zenbook_ops;
+ id = dmi_first_match(ucsi_acpi_quirks);
+ if (id)
+ ops = id->driver_data;
ua->ucsi = ucsi_create(&pdev->dev, ops);
if (IS_ERR(ua->ucsi))
diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c
index 607061a37e..449c125f6f 100644
--- a/drivers/usb/typec/ucsi/ucsi_ccg.c
+++ b/drivers/usb/typec/ucsi/ucsi_ccg.c
@@ -501,8 +501,8 @@ static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc,
case NVIDIA_FTB_DP_OFFSET:
if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO)
alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO |
- DP_CAP_DP_SIGNALING | DP_CAP_USB |
- DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E));
+ DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB |
+ DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E));
break;
case NVIDIA_FTB_DBG_OFFSET:
if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO)
diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h
index d8cbd2dfc2..282efca64a 100644
--- a/drivers/usb/usbip/usbip_common.h
+++ b/drivers/usb/usbip/usbip_common.h
@@ -298,12 +298,6 @@ struct usbip_device {
__k; \
})
-#define kthread_stop_put(k) \
- do { \
- kthread_stop(k); \
- put_task_struct(k); \
- } while (0)
-
/* usbip_common.c */
void usbip_dump_urb(struct urb *purb);
void usbip_dump_header(struct usbip_header *pdu);
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 37d1fc34e8..82650c11e4 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -1140,6 +1140,7 @@ static int hcd_name_to_id(const char *name)
static int vhci_setup(struct usb_hcd *hcd)
{
struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));
+
if (usb_hcd_is_primary_hcd(hcd)) {
vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
vhci->vhci_hcd_hs->vhci = vhci;
@@ -1493,13 +1494,10 @@ static struct platform_driver vhci_driver = {
static void del_platform_devices(void)
{
- struct platform_device *pdev;
int i;
for (i = 0; i < vhci_num_controllers; i++) {
- pdev = vhcis[i].pdev;
- if (pdev != NULL)
- platform_device_unregister(pdev);
+ platform_device_unregister(vhcis[i].pdev);
vhcis[i].pdev = NULL;
}
sysfs_remove_link(&platform_bus.kobj, driver_name);
@@ -1519,45 +1517,33 @@ static int __init vhci_hcd_init(void)
if (vhcis == NULL)
return -ENOMEM;
- for (i = 0; i < vhci_num_controllers; i++) {
- vhcis[i].pdev = platform_device_alloc(driver_name, i);
- if (!vhcis[i].pdev) {
- i--;
- while (i >= 0)
- platform_device_put(vhcis[i--].pdev);
- ret = -ENOMEM;
- goto err_device_alloc;
- }
- }
- for (i = 0; i < vhci_num_controllers; i++) {
- void *vhci = &vhcis[i];
- ret = platform_device_add_data(vhcis[i].pdev, &vhci, sizeof(void *));
- if (ret)
- goto err_driver_register;
- }
-
ret = platform_driver_register(&vhci_driver);
if (ret)
goto err_driver_register;
for (i = 0; i < vhci_num_controllers; i++) {
- ret = platform_device_add(vhcis[i].pdev);
+ void *vhci = &vhcis[i];
+ struct platform_device_info pdevinfo = {
+ .name = driver_name,
+ .id = i,
+ .data = &vhci,
+ .size_data = sizeof(void *),
+ };
+
+ vhcis[i].pdev = platform_device_register_full(&pdevinfo);
+ ret = PTR_ERR_OR_ZERO(vhcis[i].pdev);
if (ret < 0) {
- i--;
- while (i >= 0)
- platform_device_del(vhcis[i--].pdev);
+ while (i--)
+ platform_device_unregister(vhcis[i].pdev);
goto err_add_hcd;
}
}
- return ret;
+ return 0;
err_add_hcd:
platform_driver_unregister(&vhci_driver);
err_driver_register:
- for (i = 0; i < vhci_num_controllers; i++)
- platform_device_put(vhcis[i].pdev);
-err_device_alloc:
kfree(vhcis);
return ret;
}