From 03941ed91c7231e4973fb50de6c349974405df4e Mon Sep 17 00:00:00 2001 From: Jakob Koschel Date: Thu, 24 Mar 2022 08:27:00 +0100 Subject: thunderbolt: Replace usage of found with dedicated list iterator variable To move the list iterator variable into the list_for_each_entry_*() macro in the future it should be avoided to use the list iterator variable after the loop body. To *never* use the list iterator variable after the loop it was concluded to use a separate iterator variable instead of a found boolean [1]. This removes the need to use a found variable and simply checking if the variable was set, can determine if the break/goto was hit. Link: https://lore.kernel.org/all/CAHk-=wgRr_D8CB-D9Kg-c=EHreAsk5SqXPwr9Y7k9sA6cWXJ6w@mail.gmail.com/ Signed-off-by: Jakob Koschel Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 4986edfbdf67..e92c658dba1c 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -158,21 +158,20 @@ static bool tb_cfg_request_is_active(struct tb_cfg_request *req) static struct tb_cfg_request * tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) { - struct tb_cfg_request *req; - bool found = false; + struct tb_cfg_request *req = NULL, *iter; mutex_lock(&pkg->ctl->request_queue_lock); - list_for_each_entry(req, &pkg->ctl->request_queue, list) { - tb_cfg_request_get(req); - if (req->match(req, pkg)) { - found = true; + list_for_each_entry(iter, &pkg->ctl->request_queue, list) { + tb_cfg_request_get(iter); + if (iter->match(iter, pkg)) { + req = iter; break; } - tb_cfg_request_put(req); + tb_cfg_request_put(iter); } mutex_unlock(&pkg->ctl->request_queue_lock); - return found ? req : NULL; + return req; } /* utility functions */ -- cgit v1.2.3 From ca319f5565193b7c51533852083ab44837eb2709 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 20 Jan 2022 17:23:04 +0200 Subject: thunderbolt: Fix typo in comment Should be 'in' instead of 'bin'. Fix it. Signed-off-by: Mika Westerberg Tested-by: Brad Campbell --- drivers/thunderbolt/nhi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 4a582183f675..6221ca4ea287 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1207,7 +1207,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi->pdev = pdev; nhi->ops = (const struct tb_nhi_ops *)id->driver_data; - /* cannot fail - table is allocated bin pcim_iomap_regions */ + /* cannot fail - table is allocated in pcim_iomap_regions */ nhi->iobase = pcim_iomap_table(pdev)[0]; nhi->hop_count = ioread32(nhi->iobase + REG_HOP_COUNT) & 0x3ff; dev_dbg(&pdev->dev, "total paths: %d\n", nhi->hop_count); -- cgit v1.2.3 From ebe99c0f297dfc0f349f54dae850b83985539de5 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 1 Apr 2022 13:36:47 +0300 Subject: thunderbolt: Use decimal number with port numbers This makes it consistent with the other logging functions. Signed-off-by: Mika Westerberg Tested-by: Brad Campbell --- drivers/thunderbolt/tb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index b6fcd8d45324..ad025ff142ba 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -674,7 +674,7 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, #define __TB_PORT_PRINT(level, _port, fmt, arg...) \ do { \ const struct tb_port *__port = (_port); \ - level(__port->sw->tb, "%llx:%x: " fmt, \ + level(__port->sw->tb, "%llx:%u: " fmt, \ tb_route(__port->sw), __port->port, ## arg); \ } while (0) #define tb_port_WARN(port, fmt, arg...) \ -- cgit v1.2.3 From 259e0c71e552557294d4820c840f62185e135c3a Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 31 Mar 2022 18:21:24 +0300 Subject: thunderbolt: Dump path config space entries during discovery This is useful when debugging possible issues during tunnel discovery. Signed-off-by: Mika Westerberg Tested-by: Brad Campbell --- drivers/thunderbolt/path.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 299712accfe9..ee03fd75a472 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -166,6 +166,9 @@ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, return NULL; } + tb_dbg(path->tb, "discovering %s path starting from %llx:%u\n", + path->name, tb_route(src->sw), src->port); + p = src; h = src_hopid; @@ -198,10 +201,13 @@ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, path->hops[i].out_port = out_port; path->hops[i].next_hop_index = next_hop; + tb_dump_hop(&path->hops[i], &hop); + h = next_hop; p = out_port->remote; } + tb_dbg(path->tb, "path discovery complete\n"); return path; err: -- cgit v1.2.3 From 9d2d0a5cf0ca063f417681cc33e767ce52615286 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 1 Apr 2022 17:24:28 +0300 Subject: thunderbolt: Use different lane for second DisplayPort tunnel Brad reported that on Apple hardware with Light Ridge or Falcon Ridge controller, plugging in a chain of Thunderbolt displays (Light Ridge based controllers) causes all kinds of tearing and flickering. The reason for this is that on Thunderbolt 1 hardware there is no lane bonding so we have two independent 10 Gb/s lanes, and currently Linux tunnels both displays through the lane 1. This makes the displays to share the 10 Gb/s bandwidth which may not be enough for higher resolutions. For this reason make the second tunnel go through the lane 0 instead. This seems to match what the macOS connection manager is also doing. Reported-by: Brad Campbell Signed-off-by: Mika Westerberg Tested-by: Brad Campbell --- drivers/thunderbolt/tb.c | 19 +++++++++++++++++-- drivers/thunderbolt/test.c | 16 ++++++++-------- drivers/thunderbolt/tunnel.c | 11 ++++++----- drivers/thunderbolt/tunnel.h | 4 ++-- 4 files changed, 33 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 9beb47b31c75..44d04b651a8b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -867,7 +867,7 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in) static void tb_tunnel_dp(struct tb *tb) { - int available_up, available_down, ret; + int available_up, available_down, ret, link_nr; struct tb_cm *tcm = tb_priv(tb); struct tb_port *port, *in, *out; struct tb_tunnel *tunnel; @@ -912,6 +912,20 @@ static void tb_tunnel_dp(struct tb *tb) return; } + /* + * This is only applicable to links that are not bonded (so + * when Thunderbolt 1 hardware is involved somewhere in the + * topology). For these try to share the DP bandwidth between + * the two lanes. + */ + link_nr = 1; + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (tb_tunnel_is_dp(tunnel)) { + link_nr = 0; + break; + } + } + /* * DP stream needs the domain to be active so runtime resume * both ends of the tunnel. @@ -943,7 +957,8 @@ static void tb_tunnel_dp(struct tb *tb) tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n", available_up, available_down); - tunnel = tb_tunnel_alloc_dp(tb, in, out, available_up, available_down); + tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, + available_down); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim; diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 1f69bab236ee..66b6e665e96f 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -1348,7 +1348,7 @@ static void tb_test_tunnel_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, tunnel != NULL); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1394,7 +1394,7 @@ static void tb_test_tunnel_dp_chain(struct kunit *test) in = &host->ports[5]; out = &dev4->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, tunnel != NULL); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1444,7 +1444,7 @@ static void tb_test_tunnel_dp_tree(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, tunnel != NULL); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1509,7 +1509,7 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test) in = &dev6->ports[13]; out = &dev12->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, tunnel != NULL); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1627,7 +1627,7 @@ static void tb_test_tunnel_port_on_path(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, dp_tunnel != NULL); KUNIT_EXPECT_TRUE(test, tb_tunnel_port_on_path(dp_tunnel, in)); @@ -2009,7 +2009,7 @@ static void tb_test_credit_alloc_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, tunnel != NULL); KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3); @@ -2245,7 +2245,7 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL1(struct kunit *test, in = &host->ports[5]; out = &dev->ports[13]; - dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, dp_tunnel1 != NULL); KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3); @@ -2282,7 +2282,7 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL2(struct kunit *test, in = &host->ports[6]; out = &dev->ports[14]; - dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 0, 0); + dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0); KUNIT_ASSERT_TRUE(test, dp_tunnel2 != NULL); KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 118742ec93ed..8ccd70920b6a 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -858,6 +858,7 @@ err_free: * @tb: Pointer to the domain structure * @in: DP in adapter port * @out: DP out adapter port + * @link_nr: Preferred lane adapter when the link is not bonded * @max_up: Maximum available upstream bandwidth for the DP tunnel (%0 * if not limited) * @max_down: Maximum available downstream bandwidth for the DP tunnel @@ -869,8 +870,8 @@ err_free: * Return: Returns a tb_tunnel on success or NULL on failure. */ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, - struct tb_port *out, int max_up, - int max_down) + struct tb_port *out, int link_nr, + int max_up, int max_down) { struct tb_tunnel *tunnel; struct tb_path **paths; @@ -894,21 +895,21 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, paths = tunnel->paths; path = tb_path_alloc(tb, in, TB_DP_VIDEO_HOPID, out, TB_DP_VIDEO_HOPID, - 1, "Video"); + link_nr, "Video"); if (!path) goto err_free; tb_dp_init_video_path(path); paths[TB_DP_VIDEO_PATH_OUT] = path; path = tb_path_alloc(tb, in, TB_DP_AUX_TX_HOPID, out, - TB_DP_AUX_TX_HOPID, 1, "AUX TX"); + TB_DP_AUX_TX_HOPID, link_nr, "AUX TX"); if (!path) goto err_free; tb_dp_init_aux_path(path); paths[TB_DP_AUX_PATH_OUT] = path; path = tb_path_alloc(tb, out, TB_DP_AUX_RX_HOPID, in, - TB_DP_AUX_RX_HOPID, 1, "AUX RX"); + TB_DP_AUX_RX_HOPID, link_nr, "AUX RX"); if (!path) goto err_free; tb_dp_init_aux_path(path); diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 03e56076b5bc..bb4d1f1d6d0b 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -71,8 +71,8 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, bool alloc_hopid); struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, - struct tb_port *out, int max_up, - int max_down); + struct tb_port *out, int link_nr, + int max_up, int max_down); struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, struct tb_port *dst, int transmit_path, int transmit_ring, int receive_path, -- cgit v1.2.3 From 324e4f85070f89b58c5f9926370ad19dea907dc7 Mon Sep 17 00:00:00 2001 From: Dan Vacura Date: Fri, 1 Apr 2022 11:04:45 -0500 Subject: usb: gadget: uvc: allow changing interface name via configfs Add a configfs entry, "function_name", to change the iInterface field for VideoControl. This name is used on host devices for user selection, useful when multiple cameras are present. The default will remain "UVC Camera". Signed-off-by: Dan Vacura Link: https://lore.kernel.org/r/20220401160447.5919-1-w36195@motorola.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 1 + Documentation/usb/gadget-testing.rst | 1 + drivers/usb/gadget/function/f_uvc.c | 4 ++- drivers/usb/gadget/function/u_uvc.h | 1 + drivers/usb/gadget/function/uvc_configfs.c | 41 +++++++++++++++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index 889ed45be4ca..611b23e6488d 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -7,6 +7,7 @@ Description: UVC function directory streaming_maxburst 0..15 (ss only) streaming_maxpacket 1..1023 (fs), 1..3072 (hs/ss) streaming_interval 1..16 + function_name string [32] =================== ============================= What: /config/usb-gadget/gadget/functions/uvc.name/control diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index c6d034abce3a..1c37159fa171 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -787,6 +787,7 @@ The uvc function provides these attributes in its function directory: streaming_maxpacket maximum packet size this endpoint is capable of sending or receiving when this configuration is selected + function_name name of the interface =================== ================================================ There are also "control" and "streaming" subdirectories, each of which contain diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 71bb5e477dba..50e6e7a58b41 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -44,7 +44,7 @@ MODULE_PARM_DESC(trace, "Trace level bitmask"); #define UVC_STRING_STREAMING_IDX 1 static struct usb_string uvc_en_us_strings[] = { - [UVC_STRING_CONTROL_IDX].s = "UVC Camera", + /* [UVC_STRING_CONTROL_IDX].s = DYNAMIC, */ [UVC_STRING_STREAMING_IDX].s = "Video Streaming", { } }; @@ -676,6 +676,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address; uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address; + uvc_en_us_strings[UVC_STRING_CONTROL_IDX].s = opts->function_name; us = usb_gstrings_attach(cdev, uvc_function_strings, ARRAY_SIZE(uvc_en_us_strings)); if (IS_ERR(us)) { @@ -866,6 +867,7 @@ static struct usb_function_instance *uvc_alloc_inst(void) opts->streaming_interval = 1; opts->streaming_maxpacket = 1024; + snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera"); ret = uvcg_attach_configfs(opts); if (ret < 0) { diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 9a01a7d4f17f..24b8681b0d6f 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -27,6 +27,7 @@ struct f_uvc_opts { unsigned int control_interface; unsigned int streaming_interface; + char function_name[32]; /* * Control descriptors array pointers for full-/high-speed and diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 77d64031aa9c..63b8d3758b38 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -2425,10 +2425,51 @@ UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, 15); #undef UVCG_OPTS_ATTR +#define UVCG_OPTS_STRING_ATTR(cname, aname) \ +static ssize_t f_uvc_opts_string_##cname##_show(struct config_item *item,\ + char *page) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = snprintf(page, sizeof(opts->aname), "%s", opts->aname);\ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uvc_opts_string_##cname##_store(struct config_item *item,\ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = snprintf(opts->aname, min(sizeof(opts->aname), len), \ + "%s", page); \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +UVC_ATTR(f_uvc_opts_string_, cname, aname) + +UVCG_OPTS_STRING_ATTR(function_name, function_name); + +#undef UVCG_OPTS_STRING_ATTR + static struct configfs_attribute *uvc_attrs[] = { &f_uvc_opts_attr_streaming_interval, &f_uvc_opts_attr_streaming_maxpacket, &f_uvc_opts_attr_streaming_maxburst, + &f_uvc_opts_string_attr_function_name, NULL, }; -- cgit v1.2.3 From be30e3502771ac0b5080ec61223d915354cde1cc Mon Sep 17 00:00:00 2001 From: Dan Vacura Date: Wed, 13 Apr 2022 14:24:02 -0500 Subject: usb: gadget: uvc: remove pause flag use The pause flag doesn't appear to serve a purpose as it is possible for the pump worker thread to not be running when new buffers are queued. Before the following change was introduced: "43cd0023872e usb: gadget: uvc_video: add worker to handle the frame pumping", the pause flag status was discarded and never returned. This masked a current issues of the userspace receiving non-zero values (considered unsuccessful) and prevents trace_v4l2_qbuf() from being called. Signed-off-by: Dan Vacura Link: https://lore.kernel.org/r/20220413192402.26063-1-w36195@motorola.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_queue.c | 15 +-------------- drivers/usb/gadget/function/uvc_queue.h | 1 - 2 files changed, 1 insertion(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index d852ac9e47e7..8fd2f4a3dc4c 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -185,18 +185,7 @@ int uvcg_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf) int uvcg_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf) { - unsigned long flags; - int ret; - - ret = vb2_qbuf(&queue->queue, NULL, buf); - if (ret < 0) - return ret; - - spin_lock_irqsave(&queue->irqlock, flags); - ret = (queue->flags & UVC_QUEUE_PAUSED) != 0; - queue->flags &= ~UVC_QUEUE_PAUSED; - spin_unlock_irqrestore(&queue->irqlock, flags); - return ret; + return vb2_qbuf(&queue->queue, NULL, buf); } /* @@ -362,8 +351,6 @@ struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue) if (!list_empty(&queue->irqqueue)) buf = list_first_entry(&queue->irqqueue, struct uvc_buffer, queue); - else - queue->flags |= UVC_QUEUE_PAUSED; return buf; } diff --git a/drivers/usb/gadget/function/uvc_queue.h b/drivers/usb/gadget/function/uvc_queue.h index 05360a0767f6..42aaf8b227dd 100644 --- a/drivers/usb/gadget/function/uvc_queue.h +++ b/drivers/usb/gadget/function/uvc_queue.h @@ -43,7 +43,6 @@ struct uvc_buffer { #define UVC_QUEUE_DISCONNECTED (1 << 0) #define UVC_QUEUE_DROP_INCOMPLETE (1 << 1) -#define UVC_QUEUE_PAUSED (1 << 2) struct uvc_video_queue { struct vb2_queue queue; -- cgit v1.2.3 From 61aa709ca58a0dbeeb817bfa9230c1a92979f2c6 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Sun, 3 Apr 2022 01:27:42 +0200 Subject: usb: gadget: uvc: rework uvcg_queue_next_buffer to uvcg_complete_buffer The function uvcg_queue_next_buffer is used different than its name suggests. The return value nextbuf is never used by any caller. This patch reworks the function to its actual purpose, by removing the unused code and renaming it. The function name uvcg_complete_buffer makes it more clear that it is actually marking the current video buffer as complete. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220402232744.3622565-2-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_queue.c | 13 ++----------- drivers/usb/gadget/function/uvc_queue.h | 2 +- drivers/usb/gadget/function/uvc_video.c | 6 +++--- 3 files changed, 6 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 8fd2f4a3dc4c..43acc071f0ad 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -315,24 +315,17 @@ int uvcg_queue_enable(struct uvc_video_queue *queue, int enable) } /* called with &queue_irqlock held.. */ -struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue, +void uvcg_complete_buffer(struct uvc_video_queue *queue, struct uvc_buffer *buf) { - struct uvc_buffer *nextbuf; - if ((queue->flags & UVC_QUEUE_DROP_INCOMPLETE) && buf->length != buf->bytesused) { buf->state = UVC_BUF_STATE_QUEUED; vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0); - return buf; + return; } list_del(&buf->queue); - if (!list_empty(&queue->irqqueue)) - nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer, - queue); - else - nextbuf = NULL; buf->buf.field = V4L2_FIELD_NONE; buf->buf.sequence = queue->sequence++; @@ -340,8 +333,6 @@ struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue, vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused); vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE); - - return nextbuf; } struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue) diff --git a/drivers/usb/gadget/function/uvc_queue.h b/drivers/usb/gadget/function/uvc_queue.h index 42aaf8b227dd..41f87b917f6b 100644 --- a/drivers/usb/gadget/function/uvc_queue.h +++ b/drivers/usb/gadget/function/uvc_queue.h @@ -92,7 +92,7 @@ void uvcg_queue_cancel(struct uvc_video_queue *queue, int disconnect); int uvcg_queue_enable(struct uvc_video_queue *queue, int enable); -struct uvc_buffer *uvcg_queue_next_buffer(struct uvc_video_queue *queue, +void uvcg_complete_buffer(struct uvc_video_queue *queue, struct uvc_buffer *buf); struct uvc_buffer *uvcg_queue_head(struct uvc_video_queue *queue); diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 7f59a0c47402..0c8d146b8403 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -112,7 +112,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video, if (buf->bytesused == video->queue.buf_used) { video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; - uvcg_queue_next_buffer(&video->queue, buf); + uvcg_complete_buffer(&video->queue, buf); video->fid ^= UVC_STREAM_FID; video->payload_size = 0; @@ -183,7 +183,7 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; buf->offset = 0; - uvcg_queue_next_buffer(&video->queue, buf); + uvcg_complete_buffer(&video->queue, buf); video->fid ^= UVC_STREAM_FID; } } @@ -210,7 +210,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, if (buf->bytesused == video->queue.buf_used) { video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; - uvcg_queue_next_buffer(&video->queue, buf); + uvcg_complete_buffer(&video->queue, buf); video->fid ^= UVC_STREAM_FID; } } -- cgit v1.2.3 From 9b969f93bcef9b3d9e92f1810e22bbd6c344a0e5 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Sun, 3 Apr 2022 01:27:43 +0200 Subject: usb: gadget: uvc: giveback vb2 buffer on req complete On uvc_video_encode_isoc_sg the mapped vb2 buffer is returned to early. Only after the last usb_request worked with the buffer it is allowed to give it back to vb2. This patch fixes that. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220402232744.3622565-3-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc.h | 1 + drivers/usb/gadget/function/uvc_queue.c | 2 -- drivers/usb/gadget/function/uvc_video.c | 11 ++++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index c3607a32b986..9eec104b3666 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -79,6 +79,7 @@ struct uvc_request { struct uvc_video *video; struct sg_table sgt; u8 header[UVCG_REQUEST_HEADER_LEN]; + struct uvc_buffer *last_buf; }; struct uvc_video { diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 43acc071f0ad..f83500614954 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -325,8 +325,6 @@ void uvcg_complete_buffer(struct uvc_video_queue *queue, return; } - list_del(&buf->queue); - buf->buf.field = V4L2_FIELD_NONE; buf->buf.sequence = queue->sequence++; buf->buf.vb2_buf.timestamp = ktime_get_ns(); diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 0c8d146b8403..ceeabc4ba3b1 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -112,6 +112,7 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video, if (buf->bytesused == video->queue.buf_used) { video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; + list_del(&buf->queue); uvcg_complete_buffer(&video->queue, buf); video->fid ^= UVC_STREAM_FID; @@ -183,8 +184,9 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; buf->offset = 0; - uvcg_complete_buffer(&video->queue, buf); + list_del(&buf->queue); video->fid ^= UVC_STREAM_FID; + ureq->last_buf = buf; } } @@ -210,6 +212,7 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, if (buf->bytesused == video->queue.buf_used) { video->queue.buf_used = 0; buf->state = UVC_BUF_STATE_DONE; + list_del(&buf->queue); uvcg_complete_buffer(&video->queue, buf); video->fid ^= UVC_STREAM_FID; } @@ -264,6 +267,11 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) uvcg_queue_cancel(queue, 0); } + if (ureq->last_buf) { + uvcg_complete_buffer(&video->queue, ureq->last_buf); + ureq->last_buf = NULL; + } + spin_lock_irqsave(&video->req_lock, flags); list_add_tail(&req->list, &video->req_free); spin_unlock_irqrestore(&video->req_lock, flags); @@ -332,6 +340,7 @@ uvc_video_alloc_requests(struct uvc_video *video) video->ureq[i].req->complete = uvc_video_complete; video->ureq[i].req->context = &video->ureq[i]; video->ureq[i].video = video; + video->ureq[i].last_buf = NULL; list_add_tail(&video->ureq[i].req->list, &video->req_free); /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */ -- cgit v1.2.3 From aef11279888c00e1841a3533a35d279285af3a51 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Sun, 3 Apr 2022 01:27:44 +0200 Subject: usb: gadget: uvc: improve sg exit condition The exit condition to quit iterating over the sg_list, while encoding the sg entries, has to consider the case that the dma_len of the entry could be zero. This patch takes this condition to account. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220402232744.3622565-4-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index ceeabc4ba3b1..a9bb4553db84 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -155,7 +155,7 @@ uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, sg = sg_next(sg); for_each_sg(sg, iter, ureq->sgt.nents - 1, i) { - if (!len || !buf->sg) + if (!len || !buf->sg || !sg_dma_len(buf->sg)) break; sg_left = sg_dma_len(buf->sg) - buf->offset; -- cgit v1.2.3 From 38c695e1e2a2caaacb3b2dc5cddbba5e8024412f Mon Sep 17 00:00:00 2001 From: Tanveer Alam Date: Mon, 28 Mar 2022 16:21:37 +0530 Subject: usb: typec: mux: intel_pmc_mux: Add retry logic to a PMC command There are few scenerio when PMC reports 'busy condition' and command fail. If PMC receives a high priority command while servicing a low priority command then it discards the low priority command and start servicing the high priority command. The lower priority command fail and driver returns error. If the same command resend to the PMC then PMC latches the command and service it accordingly. Thus adds the retry logic for the PMC command. Reviewed-by: Heikki Krogerus Signed-off-by: Tanveer Alam Link: https://lore.kernel.org/r/20220328105137.6223-1-tanveer1.alam@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 2cdd22130834..da6b381ddf00 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -173,7 +173,7 @@ static int hsl_orientation(struct pmc_usb_port *port) return port->orientation - 1; } -static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) +static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len) { u8 response[4]; u8 status_res; @@ -184,7 +184,7 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) * Status can be checked from the response message if the * function intel_scu_ipc_dev_command succeeds. */ - ret = intel_scu_ipc_dev_command(port->pmc->ipc, PMC_USBC_CMD, 0, msg, + ret = intel_scu_ipc_dev_command(ipc, PMC_USBC_CMD, 0, msg, len, response, sizeof(response)); if (ret) @@ -203,6 +203,23 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) return 0; } +static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) +{ + int retry_count = 3; + int ret; + + /* + * If PMC is busy then retry the command once again + */ + while (retry_count--) { + ret = pmc_usb_send_command(port->pmc->ipc, msg, len); + if (ret != -EBUSY) + break; + } + + return ret; +} + static int pmc_usb_mux_dp_hpd(struct pmc_usb_port *port, struct typec_displayport_data *dp) { -- cgit v1.2.3 From 92440202a88009ef66a236001981513e03a300fb Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Thu, 17 Mar 2022 16:45:12 +0100 Subject: usb: typec: tipd: Only update power status on IRQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of refetching power status cache it and only update it when a change is signalled via irq. This simplifies tracing and adding more supply properties in follow up patches. Signed-off-by: Guido Günther Signed-off-by: Sebastian Krzyszkowiak Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220317154518.4082046-2-sebastian.krzyszkowiak@puri.sm Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 16b4560216ba..dfbba5ae9487 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -93,6 +93,8 @@ struct tps6598x { struct power_supply *psy; struct power_supply_desc psy_desc; enum power_supply_usb_type usb_type; + + u16 pwr_status; }; static enum power_supply_property tps6598x_psy_props[] = { @@ -230,17 +232,12 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) { struct typec_partner_desc desc; enum typec_pwr_opmode mode; - u16 pwr_status; int ret; if (tps->partner) return 0; - ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); - if (ret < 0) - return ret; - - mode = TPS_POWER_STATUS_PWROPMODE(pwr_status); + mode = TPS_POWER_STATUS_PWROPMODE(tps->pwr_status); desc.usb_pd = mode == TYPEC_PWR_MODE_PD; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ @@ -455,6 +452,7 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) dev_err(tps->dev, "failed to read power status: %d\n", ret); return false; } + tps->pwr_status = pwr_status; trace_tps6598x_power_status(pwr_status); return true; @@ -601,15 +599,8 @@ static const struct regmap_config tps6598x_regmap_config = { static int tps6598x_psy_get_online(struct tps6598x *tps, union power_supply_propval *val) { - int ret; - u16 pwr_status; - - ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); - if (ret < 0) - return ret; - - if (TPS_POWER_STATUS_CONNECTION(pwr_status) && - TPS_POWER_STATUS_SOURCESINK(pwr_status)) { + if (TPS_POWER_STATUS_CONNECTION(tps->pwr_status) && + TPS_POWER_STATUS_SOURCESINK(tps->pwr_status)) { val->intval = 1; } else { val->intval = 0; @@ -622,15 +613,11 @@ static int tps6598x_psy_get_prop(struct power_supply *psy, union power_supply_propval *val) { struct tps6598x *tps = power_supply_get_drvdata(psy); - u16 pwr_status; int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_USB_TYPE: - ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); - if (ret < 0) - return ret; - if (TPS_POWER_STATUS_PWROPMODE(pwr_status) == TYPEC_PWR_MODE_PD) + if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD) val->intval = POWER_SUPPLY_USB_TYPE_PD; else val->intval = POWER_SUPPLY_USB_TYPE_C; @@ -837,6 +824,11 @@ static int tps6598x_probe(struct i2c_client *client) fwnode_handle_put(fwnode); if (status & TPS_STATUS_PLUG_PRESENT) { + ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status); + if (ret < 0) { + dev_err(tps->dev, "failed to read power status: %d\n", ret); + goto err_role_put; + } ret = tps6598x_connect(tps, status); if (ret) dev_err(&client->dev, "failed to register partner\n"); -- cgit v1.2.3 From e8784c0aec03a2581ee55827ba694e129d6a57ad Mon Sep 17 00:00:00 2001 From: Aswath Govindraju Date: Thu, 14 Apr 2022 16:02:09 +0530 Subject: drivers: usb: dwc3: Add AM62 USB wrapper driver In AM62 SoC, the Designware Core USB3 IP is progammed to operate in USB2.0 only mode. Add driver for AM62 USB DWC3 Wrapper. Signed-off-by: Aswath Govindraju Link: https://lore.kernel.org/r/20220414103211.16202-3-a-govindraju@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/Kconfig | 9 ++ drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-am62.c | 332 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-am62.c (limited to 'drivers') diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index c483f28b695d..cd9a734522a7 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -159,4 +159,13 @@ config USB_DWC3_XILINX This driver handles both ZynqMP and Versal SoC operations. Say 'Y' or 'M' if you have one such device. +config USB_DWC3_AM62 + tristate "Texas Instruments AM62 Platforms" + depends on ARCH_K3 || COMPILE_TEST + default USB_DWC3 + help + Support TI's AM62 platforms with DesignWare Core USB3 IP. + The Designware Core USB3 IP is progammed to operate in + in USB 2.0 mode only. + Say 'Y' or 'M' here if you have one such device endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 2d499de6f66a..9f66bd82b639 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -42,6 +42,7 @@ endif # and allyesconfig builds. ## +obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c new file mode 100644 index 000000000000..fea7aca35dc8 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-am62.c - TI specific Glue layer for AM62 DWC3 USB Controller + * + * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* USB WRAPPER register offsets */ +#define USBSS_PID 0x0 +#define USBSS_OVERCURRENT_CTRL 0x4 +#define USBSS_PHY_CONFIG 0x8 +#define USBSS_PHY_TEST 0xc +#define USBSS_CORE_STAT 0x14 +#define USBSS_HOST_VBUS_CTRL 0x18 +#define USBSS_MODE_CONTROL 0x1c +#define USBSS_WAKEUP_CONFIG 0x30 +#define USBSS_WAKEUP_STAT 0x34 +#define USBSS_OVERRIDE_CONFIG 0x38 +#define USBSS_IRQ_MISC_STATUS_RAW 0x430 +#define USBSS_IRQ_MISC_STATUS 0x434 +#define USBSS_IRQ_MISC_ENABLE_SET 0x438 +#define USBSS_IRQ_MISC_ENABLE_CLR 0x43c +#define USBSS_IRQ_MISC_EOI 0x440 +#define USBSS_INTR_TEST 0x490 +#define USBSS_VBUS_FILTER 0x614 +#define USBSS_VBUS_STAT 0x618 +#define USBSS_DEBUG_CFG 0x708 +#define USBSS_DEBUG_DATA 0x70c +#define USBSS_HOST_HUB_CTRL 0x714 + +/* PHY CONFIG register bits */ +#define USBSS_PHY_VBUS_SEL_MASK GENMASK(2, 1) +#define USBSS_PHY_VBUS_SEL_SHIFT 1 +#define USBSS_PHY_LANE_REVERSE BIT(0) + +/* MODE CONTROL register bits */ +#define USBSS_MODE_VALID BIT(0) + +/* WAKEUP CONFIG register bits */ +#define USBSS_WAKEUP_CFG_OVERCURRENT_EN BIT(3) +#define USBSS_WAKEUP_CFG_LINESTATE_EN BIT(2) +#define USBSS_WAKEUP_CFG_SESSVALID_EN BIT(1) +#define USBSS_WAKEUP_CFG_VBUSVALID_EN BIT(0) + +/* WAKEUP STAT register bits */ +#define USBSS_WAKEUP_STAT_OVERCURRENT BIT(4) +#define USBSS_WAKEUP_STAT_LINESTATE BIT(3) +#define USBSS_WAKEUP_STAT_SESSVALID BIT(2) +#define USBSS_WAKEUP_STAT_VBUSVALID BIT(1) +#define USBSS_WAKEUP_STAT_CLR BIT(0) + +/* IRQ_MISC_STATUS_RAW register bits */ +#define USBSS_IRQ_MISC_RAW_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_RAW_SESSVALID BIT(20) + +/* IRQ_MISC_STATUS register bits */ +#define USBSS_IRQ_MISC_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_SESSVALID BIT(20) + +/* IRQ_MISC_ENABLE_SET register bits */ +#define USBSS_IRQ_MISC_ENABLE_SET_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_ENABLE_SET_SESSVALID BIT(20) + +/* IRQ_MISC_ENABLE_CLR register bits */ +#define USBSS_IRQ_MISC_ENABLE_CLR_VBUSVALID BIT(22) +#define USBSS_IRQ_MISC_ENABLE_CLR_SESSVALID BIT(20) + +/* IRQ_MISC_EOI register bits */ +#define USBSS_IRQ_MISC_EOI_VECTOR BIT(0) + +/* VBUS_STAT register bits */ +#define USBSS_VBUS_STAT_SESSVALID BIT(2) +#define USBSS_VBUS_STAT_VBUSVALID BIT(0) + +/* Mask for PHY PLL REFCLK */ +#define PHY_PLL_REFCLK_MASK GENMASK(3, 0) + +#define DWC3_AM62_AUTOSUSPEND_DELAY 100 + +struct dwc3_data { + struct device *dev; + void __iomem *usbss; + struct clk *usb2_refclk; + int rate_code; + struct regmap *syscon; + unsigned int offset; + unsigned int vbus_divider; +}; + +static const int dwc3_ti_rate_table[] = { /* in KHZ */ + 9600, + 10000, + 12000, + 19200, + 20000, + 24000, + 25000, + 26000, + 38400, + 40000, + 58000, + 50000, + 52000, +}; + +static inline u32 dwc3_ti_readl(struct dwc3_data *data, u32 offset) +{ + return readl((data->usbss) + offset); +} + +static inline void dwc3_ti_writel(struct dwc3_data *data, u32 offset, u32 value) +{ + writel(value, (data->usbss) + offset); +} + +static int phy_syscon_pll_refclk(struct dwc3_data *data) +{ + struct device *dev = data->dev; + struct device_node *node = dev->of_node; + struct of_phandle_args args; + struct regmap *syscon; + int ret; + + syscon = syscon_regmap_lookup_by_phandle(node, "ti,syscon-phy-pll-refclk"); + if (IS_ERR(syscon)) { + dev_err(dev, "unable to get ti,syscon-phy-pll-refclk regmap\n"); + return PTR_ERR(syscon); + } + + data->syscon = syscon; + + ret = of_parse_phandle_with_fixed_args(node, "ti,syscon-phy-pll-refclk", 1, + 0, &args); + if (ret) + return ret; + + data->offset = args.args[0]; + + ret = regmap_update_bits(data->syscon, data->offset, PHY_PLL_REFCLK_MASK, data->rate_code); + if (ret) { + dev_err(dev, "failed to set phy pll reference clock rate\n"); + return ret; + } + + return 0; +} + +static int dwc3_ti_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct dwc3_data *data; + int i, ret; + unsigned long rate; + u32 reg; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + platform_set_drvdata(pdev, data); + + data->usbss = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->usbss)) { + dev_err(dev, "can't map IOMEM resource\n"); + return PTR_ERR(data->usbss); + } + + data->usb2_refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(data->usb2_refclk)) { + dev_err(dev, "can't get usb2_refclk\n"); + return PTR_ERR(data->usb2_refclk); + } + + /* Calculate the rate code */ + rate = clk_get_rate(data->usb2_refclk); + rate /= 1000; // To KHz + for (i = 0; i < ARRAY_SIZE(dwc3_ti_rate_table); i++) { + if (dwc3_ti_rate_table[i] == rate) + break; + } + + if (i == ARRAY_SIZE(dwc3_ti_rate_table)) { + dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate); + ret = -EINVAL; + goto err_clk_disable; + } + + data->rate_code = i; + + /* Read the syscon property and set the rate code */ + ret = phy_syscon_pll_refclk(data); + if (ret) + goto err_clk_disable; + + /* VBUS divider select */ + data->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); + reg = dwc3_ti_readl(data, USBSS_PHY_CONFIG); + if (data->vbus_divider) + reg |= 1 << USBSS_PHY_VBUS_SEL_SHIFT; + + dwc3_ti_writel(data, USBSS_PHY_CONFIG, reg); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + /* + * Don't ignore its dependencies with its children + */ + pm_suspend_ignore_children(dev, false); + clk_prepare_enable(data->usb2_refclk); + pm_runtime_get_noresume(dev); + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to create dwc3 core: %d\n", ret); + goto err_pm_disable; + } + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(data, USBSS_MODE_CONTROL); + reg |= USBSS_MODE_VALID; + dwc3_ti_writel(data, USBSS_MODE_CONTROL, reg); + + /* Setting up autosuspend */ + pm_runtime_set_autosuspend_delay(dev, DWC3_AM62_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return 0; + +err_pm_disable: + clk_disable_unprepare(data->usb2_refclk); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); +err_clk_disable: + clk_put(data->usb2_refclk); + return ret; +} + +static int dwc3_ti_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + return 0; +} + +static int dwc3_ti_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_data *data = platform_get_drvdata(pdev); + u32 reg; + + device_for_each_child(dev, NULL, dwc3_ti_remove_core); + + /* Clear mode valid bit */ + reg = dwc3_ti_readl(data, USBSS_MODE_CONTROL); + reg &= ~USBSS_MODE_VALID; + dwc3_ti_writel(data, USBSS_MODE_CONTROL, reg); + + pm_runtime_put_sync(dev); + clk_disable_unprepare(data->usb2_refclk); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + + clk_put(data->usb2_refclk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int dwc3_ti_suspend_common(struct device *dev) +{ + struct dwc3_data *data = dev_get_drvdata(dev); + + clk_disable_unprepare(data->usb2_refclk); + + return 0; +} + +static int dwc3_ti_resume_common(struct device *dev) +{ + struct dwc3_data *data = dev_get_drvdata(dev); + + clk_prepare_enable(data->usb2_refclk); + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(dwc3_ti_pm_ops, dwc3_ti_suspend_common, + dwc3_ti_resume_common, NULL); + +#define DEV_PM_OPS (&dwc3_ti_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +static const struct of_device_id dwc3_ti_of_match[] = { + { .compatible = "ti,am62-usb"}, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_ti_of_match); + +static struct platform_driver dwc3_ti_driver = { + .probe = dwc3_ti_probe, + .remove = dwc3_ti_remove, + .driver = { + .name = "dwc3-am62", + .pm = DEV_PM_OPS, + .of_match_table = dwc3_ti_of_match, + }, +}; + +module_platform_driver(dwc3_ti_driver); + +MODULE_ALIAS("platform:dwc3-am62"); +MODULE_AUTHOR("Aswath Govindraju "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 TI Glue Layer"); -- cgit v1.2.3 From 9ec4cbf1cc55d126759051acfe328d489c5d6e60 Mon Sep 17 00:00:00 2001 From: Hangyu Hua Date: Tue, 12 Apr 2022 10:02:57 +0800 Subject: usb: usbip: fix a refcount leak in stub_probe() usb_get_dev() is called in stub_device_alloc(). When stub_probe() fails after that, usb_put_dev() needs to be called to release the reference. Fix this by moving usb_put_dev() to sdev_free error path handling. Find this by code review. Fixes: 3ff67445750a ("usbip: fix error handling in stub_probe()") Reviewed-by: Shuah Khan Signed-off-by: Hangyu Hua Link: https://lore.kernel.org/r/20220412020257.9767-1-hbh25y@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c index d8d3892e5a69..3c6d452e3bf4 100644 --- a/drivers/usb/usbip/stub_dev.c +++ b/drivers/usb/usbip/stub_dev.c @@ -393,7 +393,6 @@ static int stub_probe(struct usb_device *udev) err_port: dev_set_drvdata(&udev->dev, NULL); - usb_put_dev(udev); /* we already have busid_priv, just lock busid_lock */ spin_lock(&busid_priv->busid_lock); @@ -408,6 +407,7 @@ call_put_busid_priv: put_busid_priv(busid_priv); sdev_free: + usb_put_dev(udev); stub_device_free(sdev); return rc; -- cgit v1.2.3 From d088fabace2ca337b275d1d4b36db4fe7771e44f Mon Sep 17 00:00:00 2001 From: Niels Dossche Date: Tue, 12 Apr 2022 18:50:55 +0200 Subject: usb: usbip: add missing device lock on tweak configuration cmd The function documentation of usb_set_configuration says that its callers should hold the device lock. This lock is held for all callsites except tweak_set_configuration_cmd. The code path can be executed for example when attaching a remote USB device. The solution is to surround the call by the device lock. This bug was found using my experimental own-developed static analysis tool, which reported the missing lock on v5.17.2. I manually verified this bug report by doing code review as well. I runtime checked that the required lock is not held. I compiled and runtime tested this on x86_64 with a USB mouse. After applying this patch, my analyser no longer reports this potential bug. Fixes: 2c8c98158946 ("staging: usbip: let client choose device configuration") Reviewed-by: Shuah Khan Signed-off-by: Niels Dossche Link: https://lore.kernel.org/r/20220412165055.257113-1-dossche.niels@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_rx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c index 325c22008e53..5dd41e8215e0 100644 --- a/drivers/usb/usbip/stub_rx.c +++ b/drivers/usb/usbip/stub_rx.c @@ -138,7 +138,9 @@ static int tweak_set_configuration_cmd(struct urb *urb) req = (struct usb_ctrlrequest *) urb->setup_packet; config = le16_to_cpu(req->wValue); + usb_lock_device(sdev->udev); err = usb_set_configuration(sdev->udev, config); + usb_unlock_device(sdev->udev); if (err && err != -ENODEV) dev_err(&sdev->udev->dev, "can't set config #%d, error %d\n", config, err); -- cgit v1.2.3 From eafc69a488111aa3b18794602ecc52acbef84288 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 15 Apr 2022 10:34:12 +0200 Subject: usb: Prepare cleanup of powerpc's asm/prom.h powerpc's asm/prom.h brings some headers that it doesn't need itself. In order to clean it up in a further step, first clean all files that include asm/prom.h Some files don't need asm/prom.h at all. For those ones, just remove inclusion of asm/prom.h Some files don't need any of the items provided by asm/prom.h, but need some of the headers included by asm/prom.h. For those ones, add the needed headers that are brought by asm/prom.h at the moment, then remove asm/prom.h Some files really need asm/prom.h but also need some of the headers included by asm/prom.h. For those one, leave asm/prom.h but also add the needed headers so that they can be removed from asm/prom.h in a later step. Signed-off-by: Christophe Leroy Link: https://lore.kernel.org/r/295e87a3094a92784657f7060fb0927e762a2e3c.1650011506.git.christophe.leroy@csgroup.eu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd-pci.c | 1 - drivers/usb/host/ohci-ppc-of.c | 3 --- 2 files changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 8176bc81a635..f192925f74f7 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -15,7 +15,6 @@ #ifdef CONFIG_PPC_PMAC #include #include -#include #endif #include "usb.h" diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 45f7cceb6df3..1960b8dfdba5 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -19,9 +19,6 @@ #include #include -#include - - static int ohci_ppc_of_start(struct usb_hcd *hcd) { -- cgit v1.2.3 From 17b2fb9c231ea418fa6c8643f2c786ec991b56f5 Mon Sep 17 00:00:00 2001 From: Lin Ma Date: Thu, 7 Apr 2022 10:20:58 +0800 Subject: usb-storage: alauda: fix initFunction error return The initFunction is called when probing a new device, its call relation is like: USB core: probe() -> usb_stor_probe2() -> usb_stor_acquire_resources() -> init_alauda() That is, the error return of the initFunction should tell USB core what happened instead of using error code like USB_STOR_TRANSPORT_ERROR. Signed-off-by: Lin Ma Link: https://lore.kernel.org/r/20220407022058.3741-1-linma@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/alauda.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index 20b857e97e60..747be69e5e69 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -1104,7 +1104,7 @@ static int init_alauda(struct us_data *us) us->extra = kzalloc(sizeof(struct alauda_info), GFP_NOIO); if (!us->extra) - return USB_STOR_TRANSPORT_ERROR; + return -ENOMEM; info = (struct alauda_info *) us->extra; us->extra_destructor = alauda_info_destructor; @@ -1113,7 +1113,7 @@ static int init_alauda(struct us_data *us) altsetting->endpoint[0].desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); - return USB_STOR_TRANSPORT_GOOD; + return 0; } static int alauda_transport(struct scsi_cmnd *srb, struct us_data *us) -- cgit v1.2.3 From 1abf67983a4fd74133b3d2b43722704c744621a6 Mon Sep 17 00:00:00 2001 From: Lin Ma Date: Thu, 7 Apr 2022 10:21:15 +0800 Subject: usb-storage: shuttle_usbat: fix initFunction error return The initFunction is called when probing a new device, its call relation is like: USB core: probe() -> usb_stor_probe2() -> usb_stor_acquire_resources() -> init_usbat_cd() or init_usbat_flash() -> init_usbat() That is, the error return of the initFunction should tell USB core what happened instead of using constant or error code like USB_STOR_TRANSPORT_FAILED. Signed-off-by: Lin Ma Link: https://lore.kernel.org/r/20220407022115.3773-1-linma@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/shuttle_usbat.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/shuttle_usbat.c b/drivers/usb/storage/shuttle_usbat.c index 54aa1392c9ca..f0d0ca37163d 100644 --- a/drivers/usb/storage/shuttle_usbat.c +++ b/drivers/usb/storage/shuttle_usbat.c @@ -1456,7 +1456,7 @@ static int init_usbat(struct us_data *us, int devicetype) us->extra = kzalloc(sizeof(struct usbat_info), GFP_NOIO); if (!us->extra) - return 1; + return -ENOMEM; info = (struct usbat_info *) (us->extra); @@ -1465,7 +1465,7 @@ static int init_usbat(struct us_data *us, int devicetype) USBAT_UIO_OE1 | USBAT_UIO_OE0, USBAT_UIO_EPAD | USBAT_UIO_1); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 1\n"); @@ -1473,42 +1473,42 @@ static int init_usbat(struct us_data *us, int devicetype) rc = usbat_read_user_io(us, status); if (rc != USB_STOR_TRANSPORT_GOOD) - return rc; + return -EIO; usb_stor_dbg(us, "INIT 2\n"); rc = usbat_read_user_io(us, status); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; rc = usbat_read_user_io(us, status); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 3\n"); rc = usbat_select_and_test_registers(us); if (rc != USB_STOR_TRANSPORT_GOOD) - return rc; + return -EIO; usb_stor_dbg(us, "INIT 4\n"); rc = usbat_read_user_io(us, status); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 5\n"); /* Enable peripheral control signals and card detect */ rc = usbat_device_enable_cdt(us); if (rc != USB_STOR_TRANSPORT_GOOD) - return rc; + return -EIO; usb_stor_dbg(us, "INIT 6\n"); rc = usbat_read_user_io(us, status); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 7\n"); @@ -1516,19 +1516,19 @@ static int init_usbat(struct us_data *us, int devicetype) rc = usbat_read_user_io(us, status); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 8\n"); rc = usbat_select_and_test_registers(us); if (rc != USB_STOR_TRANSPORT_GOOD) - return rc; + return -EIO; usb_stor_dbg(us, "INIT 9\n"); /* At this point, we need to detect which device we are using */ if (usbat_set_transport(us, info, devicetype)) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 10\n"); @@ -1539,11 +1539,11 @@ static int init_usbat(struct us_data *us, int devicetype) rc = usbat_set_shuttle_features(us, (USBAT_FEAT_ETEN | USBAT_FEAT_ET2 | USBAT_FEAT_ET1), 0x00, 0x88, 0x08, subcountH, subcountL); if (rc != USB_STOR_XFER_GOOD) - return USB_STOR_TRANSPORT_ERROR; + return -EIO; usb_stor_dbg(us, "INIT 11\n"); - return USB_STOR_TRANSPORT_GOOD; + return 0; } /* -- cgit v1.2.3 From ba6df3ea68196d54a1e8299cea1bf4565fc755c5 Mon Sep 17 00:00:00 2001 From: Lin Ma Date: Thu, 7 Apr 2022 10:21:10 +0800 Subject: usb-storage: isd200: fix initFunction error return The initFunction is called when probing a new device, its call relation is like: USB core: probe() -> usb_stor_probe2() -> usb_stor_acquire_resources() -> isd200_init_info() That is, the error return of the initFunction should tell USB core what happened instead of using custom error code like ISD200_ERROR. Signed-off-by: Lin Ma Link: https://lore.kernel.org/r/20220407022110.3757-1-linma@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/isd200.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c index 05429f1f69f9..4e0eef1440b7 100644 --- a/drivers/usb/storage/isd200.c +++ b/drivers/usb/storage/isd200.c @@ -1449,7 +1449,7 @@ static void isd200_free_info_ptrs(void *info_) * Allocates (if necessary) and initializes the driver structure. * * RETURNS: - * ISD status code + * error status code */ static int isd200_init_info(struct us_data *us) { @@ -1457,7 +1457,7 @@ static int isd200_init_info(struct us_data *us) info = kzalloc(sizeof(struct isd200_info), GFP_KERNEL); if (!info) - return ISD200_ERROR; + return -ENOMEM; info->id = kzalloc(ATA_ID_WORDS * 2, GFP_KERNEL); info->RegsBuf = kmalloc(sizeof(info->ATARegs), GFP_KERNEL); @@ -1466,13 +1466,13 @@ static int isd200_init_info(struct us_data *us) if (!info->id || !info->RegsBuf || !info->srb.sense_buffer) { isd200_free_info_ptrs(info); kfree(info); - return ISD200_ERROR; + return -ENOMEM; } us->extra = info; us->extra_destructor = isd200_free_info_ptrs; - return ISD200_GOOD; + return 0; } /************************************************************************** -- cgit v1.2.3 From 5bf4b20dc19090fef115f20d63d0adc6267d2928 Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Wed, 9 Mar 2022 05:36:11 +0000 Subject: usb:oxu210hp-hcd: Use platform_get_irq() to get the interrupt It is not recommened to use platform_get_resource(pdev, IORESOURCE_IRQ) for requesting IRQ's resources any more, as they can be not ready yet in case of DT-booting. platform_get_irq() instead is a recommended way for getting IRQ even if it was not retrieved earlier. It also makes code simpler because we're getting "int" value right away and no conversion from resource to int is required. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Link: https://lore.kernel.org/r/20220309053611.2081191-1-chi.minghao@zte.com.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/oxu210hp-hcd.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index b741670525e3..ddc155850bfc 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -4223,13 +4223,9 @@ static int oxu_drv_probe(struct platform_device *pdev) /* * Get the platform resources */ - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res) { - dev_err(&pdev->dev, - "no IRQ! Check %s setup!\n", dev_name(&pdev->dev)); - return -ENODEV; - } - irq = res->start; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -- cgit v1.2.3 From 134a3408c2d3f7e23eb0e4556e0a2d9f36c2614e Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Wed, 2 Mar 2022 11:37:16 +0800 Subject: USB: host: isp116x: check return value after calling platform_get_resource() It will cause null-ptr-deref if platform_get_resource() returns NULL, we need check the return value. Signed-off-by: Zhen Ni Link: https://lore.kernel.org/r/20220302033716.31272-1-nizhen@uniontech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp116x-hcd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 8835f6bd528e..8c7f0991c21b 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1541,10 +1541,12 @@ static int isp116x_remove(struct platform_device *pdev) iounmap(isp116x->data_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - release_mem_region(res->start, 2); + if (res) + release_mem_region(res->start, 2); iounmap(isp116x->addr_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(res->start, 2); + if (res) + release_mem_region(res->start, 2); usb_put_hcd(hcd); return 0; -- cgit v1.2.3 From b8a19881337678c02bb3d72ae821602e1a4c377d Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Thu, 7 Apr 2022 14:24:10 +0800 Subject: usb: gadget: u_audio: clean up some inconsistent indenting Eliminate the follow smatch warning: drivers/usb/gadget/function/u_audio.c:1182 g_audio_setup() warn: inconsistent indenting. Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/20220407062410.89214-1-jiapeng.chong@linux.alibaba.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 2bb569895a90..c1f62e91b012 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -1179,8 +1179,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (c_chmask) { struct uac_rtd_params *prm = &uac->c_prm; - spin_lock_init(&prm->lock); - uac->c_prm.uac = uac; + spin_lock_init(&prm->lock); + uac->c_prm.uac = uac; prm->max_psize = g_audio->out_ep_maxpsize; prm->srate = params->c_srates[0]; -- cgit v1.2.3 From 67ec2c75f37780e0e825d0beb2fb4892c8a93c6a Mon Sep 17 00:00:00 2001 From: Guo Zhengkui Date: Thu, 7 Apr 2022 18:04:52 +0800 Subject: usb: gadget: net2280: use swap() instead of open coding it Address the following coccicheck warning: drivers/usb/gadget/udc/net2280.c:940:20-21: WARNING opportunity for swap(). drivers/usb/gadget/udc/net2280.c:944:25-26: WARNING opportunity for swap(). by using swap() for the swapping of variable values and drop the tmp variables (`tmp` and `end`) that are not needed any more. Signed-off-by: Guo Zhengkui Link: https://lore.kernel.org/r/20220407100459.3605-1-guozhengkui@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/net2280.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 051d024b369e..d6a68631354a 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -932,19 +932,11 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) static inline void queue_dma(struct net2280_ep *ep, struct net2280_request *req, int valid) { - struct net2280_dma *end; - dma_addr_t tmp; - /* swap new dummy for old, link; fill and maybe activate */ - end = ep->dummy; - ep->dummy = req->td; - req->td = end; - - tmp = ep->td_dma; - ep->td_dma = req->td_dma; - req->td_dma = tmp; + swap(ep->dummy, req->td); + swap(ep->td_dma, req->td_dma); - end->dmadesc = cpu_to_le32 (ep->td_dma); + req->td->dmadesc = cpu_to_le32 (ep->td_dma); fill_dma_desc(ep, req, valid); } -- cgit v1.2.3 From b92ffb1eddd9a66a90defc556dcbf65a43c196c7 Mon Sep 17 00:00:00 2001 From: Lin Ma Date: Tue, 12 Apr 2022 22:43:59 +0800 Subject: USB: storage: karma: fix rio_karma_init return The function rio_karam_init() should return -ENOMEM instead of value 0 (USB_STOR_TRANSPORT_GOOD) when allocation fails. Similarly, it should return -EIO when rio_karma_send_command() fails. Fixes: dfe0d3ba20e8 ("USB Storage: add rio karma eject support") Acked-by: Alan Stern Signed-off-by: Lin Ma Link: https://lore.kernel.org/r/20220412144359.28447-1-linma@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/karma.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/storage/karma.c b/drivers/usb/storage/karma.c index 05cec81dcd3f..38ddfedef629 100644 --- a/drivers/usb/storage/karma.c +++ b/drivers/usb/storage/karma.c @@ -174,24 +174,25 @@ static void rio_karma_destructor(void *extra) static int rio_karma_init(struct us_data *us) { - int ret = 0; struct karma_data *data = kzalloc(sizeof(struct karma_data), GFP_NOIO); if (!data) - goto out; + return -ENOMEM; data->recv = kmalloc(RIO_RECV_LEN, GFP_NOIO); if (!data->recv) { kfree(data); - goto out; + return -ENOMEM; } us->extra = data; us->extra_destructor = rio_karma_destructor; - ret = rio_karma_send_command(RIO_ENTER_STORAGE, us); - data->in_storage = (ret == 0); -out: - return ret; + if (rio_karma_send_command(RIO_ENTER_STORAGE, us)) + return -EIO; + + data->in_storage = 1; + + return 0; } static struct scsi_host_template karma_host_template; -- cgit v1.2.3 From 0fee30ab63512b4cc3ed0136ffb042399f9493ab Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Sun, 27 Mar 2022 16:26:33 +0530 Subject: USB / dwc3: Fix three doc-build warnings Two kerneldoc comments in gadget.c have excess function parameter description or wrong prototype name and one kerneldoc comment in core.c has an excess function parameter description, resulting in these three doc-build warnings: 1. ./drivers/usb/dwc3/gadget.c:675: warning: Excess function parameter 'nfifos' description in 'dwc3_gadget_calc_tx_fifo_size' 2. ./drivers/usb/dwc3/gadget.c:700: warning: expecting prototype for dwc3_gadget_clear_tx_fifo_size(). Prototype was for dwc3_gadget_clear_tx_fifos() instead 3. ./drivers/usb/dwc3/core.c:347: warning: Excess function parameter 'ref_clk_per' description in 'dwc3_ref_clk_period' Fix the warnings by correcting the prototype name and removing excess parameter descriptions. Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/SI2PR01MB392995043CACD80884A13764F81C9@SI2PR01MB3929.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 1 - drivers/usb/dwc3/gadget.c | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 1170b800acdc..87a6cb37dfd0 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -341,7 +341,6 @@ static void dwc3_frame_length_adjustment(struct dwc3 *dwc) * from the default, this will set clock period in DWC3_GUCTL * register. * @dwc: Pointer to our controller context structure - * @ref_clk_per: reference clock period in ns */ static void dwc3_ref_clk_period(struct dwc3 *dwc) { diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index ab725d2262d6..66a256c47514 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -657,7 +657,6 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) /** * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value * @dwc: pointer to the DWC3 context - * @nfifos: number of fifos to calculate for * * Calculates the size value based on the equation below: * @@ -690,7 +689,7 @@ static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult) } /** - * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation + * dwc3_gadget_clear_tx_fifos - Clears txfifo allocation * @dwc: pointer to the DWC3 context * * Iterates through all the endpoint registers and clears the previous txfifo -- cgit v1.2.3 From 24a5d34d7b01b2bff4ddb65af0731ac230169153 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Sat, 2 Apr 2022 13:26:58 +0200 Subject: usb: gadget: omap_udc: Make it CCF clk API compatible The driver, OMAP1 specific, now omits clk_prepare/unprepare() steps, not supported by OMAP1 custom implementation of clock API. However, non-CCF stubs of those functions exist for use on such platforms until converted to CCF. Update the driver to be compatible with CCF implementation of clock API. Signed-off-by: Janusz Krzysztofik Link: https://lore.kernel.org/r/20220402112658.130191-1-jmkrzyszt@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/omap_udc.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index 2d9815dad2ff..d81af616fddc 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -2606,6 +2606,8 @@ static void omap_udc_release(struct device *dev) if (udc->dc_clk) { if (udc->clk_requested) omap_udc_enable_clock(0); + clk_unprepare(udc->hhc_clk); + clk_unprepare(udc->dc_clk); clk_put(udc->hhc_clk); clk_put(udc->dc_clk); } @@ -2770,8 +2772,8 @@ static int omap_udc_probe(struct platform_device *pdev) hhc_clk = clk_get(&pdev->dev, "usb_hhc_ck"); BUG_ON(IS_ERR(dc_clk) || IS_ERR(hhc_clk)); /* can't use omap_udc_enable_clock yet */ - clk_enable(dc_clk); - clk_enable(hhc_clk); + clk_prepare_enable(dc_clk); + clk_prepare_enable(hhc_clk); udelay(100); } @@ -2780,8 +2782,8 @@ static int omap_udc_probe(struct platform_device *pdev) hhc_clk = clk_get(&pdev->dev, "l3_ocpi_ck"); BUG_ON(IS_ERR(dc_clk) || IS_ERR(hhc_clk)); /* can't use omap_udc_enable_clock yet */ - clk_enable(dc_clk); - clk_enable(hhc_clk); + clk_prepare_enable(dc_clk); + clk_prepare_enable(hhc_clk); udelay(100); } @@ -2929,8 +2931,8 @@ cleanup0: usb_put_phy(xceiv); if (cpu_is_omap16xx() || cpu_is_omap7xx()) { - clk_disable(hhc_clk); - clk_disable(dc_clk); + clk_disable_unprepare(hhc_clk); + clk_disable_unprepare(dc_clk); clk_put(hhc_clk); clk_put(dc_clk); } -- cgit v1.2.3 From a85f023022f0e094b82ab43c44cb69cc19603631 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Sat, 2 Apr 2022 13:43:53 +0200 Subject: usb: host: ohci-omap: Make it CCF clk API compatible The driver, OMAP1 specific, now omits clk_prepare/unprepare() steps, not supported by OMAP1 custom implementation of clock API. However, non-CCF stubs of those functions exist for use on such platforms until converted to CCF. Update the driver to be compatible with CCF implementation of clock API. Acked-by: Alan Stern Signed-off-by: Janusz Krzysztofik Link: https://lore.kernel.org/r/20220402114353.130775-1-jmkrzyszt@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-omap.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 45dcf8292072..2ab2e089a2b7 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -281,6 +281,10 @@ static int ohci_hcd_omap_probe(struct platform_device *pdev) goto err_put_hcd; } + retval = clk_prepare(priv->usb_host_ck); + if (retval) + goto err_put_host_ck; + if (!cpu_is_omap15xx()) priv->usb_dc_ck = clk_get(&pdev->dev, "usb_dc_ck"); else @@ -288,13 +292,17 @@ static int ohci_hcd_omap_probe(struct platform_device *pdev) if (IS_ERR(priv->usb_dc_ck)) { retval = PTR_ERR(priv->usb_dc_ck); - goto err_put_host_ck; + goto err_unprepare_host_ck; } + retval = clk_prepare(priv->usb_dc_ck); + if (retval) + goto err_put_dc_ck; + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { dev_dbg(&pdev->dev, "request_mem_region failed\n"); retval = -EBUSY; - goto err_put_dc_ck; + goto err_unprepare_dc_ck; } hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); @@ -319,8 +327,12 @@ err3: iounmap(hcd->regs); err2: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_unprepare_dc_ck: + clk_unprepare(priv->usb_dc_ck); err_put_dc_ck: clk_put(priv->usb_dc_ck); +err_unprepare_host_ck: + clk_unprepare(priv->usb_host_ck); err_put_host_ck: clk_put(priv->usb_host_ck); err_put_hcd: @@ -355,7 +367,9 @@ static int ohci_hcd_omap_remove(struct platform_device *pdev) } iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + clk_unprepare(priv->usb_dc_ck); clk_put(priv->usb_dc_ck); + clk_unprepare(priv->usb_host_ck); clk_put(priv->usb_host_ck); usb_put_hcd(hcd); return 0; -- cgit v1.2.3 From 6a7c7df993bd3c90014453ec59a7b455613cf0e4 Mon Sep 17 00:00:00 2001 From: Dan Vacura Date: Wed, 20 Apr 2022 13:33:38 -0500 Subject: usb: dwc3: gadget: increase tx fifo size for ss isoc endpoints Improve performance of isoc transfers in superspeed by increasing the number of possible endpoints available, matching that of what was configured for bulk transfers. Signed-off-by: Dan Vacura Link: https://lore.kernel.org/r/20220420183338.445622-1-w36195@motorola.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 66a256c47514..c3d95f9b010b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -782,7 +782,8 @@ static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep) num_fifos = 3; if (dep->endpoint.maxburst > 6 && - usb_endpoint_xfer_bulk(dep->endpoint.desc) && DWC3_IP_IS(DWC31)) + (usb_endpoint_xfer_bulk(dep->endpoint.desc) || + usb_endpoint_xfer_isoc(dep->endpoint.desc)) && DWC3_IP_IS(DWC31)) num_fifos = dwc->tx_fifo_resize_max_num; /* FIFO size for a single buffer */ -- cgit v1.2.3 From 7e824f28c9d7275471aa98509a7e3c8369fae379 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Wed, 6 Apr 2022 21:29:14 +0200 Subject: usb: gadget: f_acm: add support for USB_CDC_REQ_SEND_BREAK Currently the usb side setting of the USB_CDC_REQ_SEND_BREAK control is not supported. This patch adds the support. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220406192914.3302636-1-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_acm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 349945e064bb..411eb489e0ff 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -333,6 +333,8 @@ static void acm_complete_set_line_coding(struct usb_ep *ep, } } +static int acm_send_break(struct gserial *port, int duration); + static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_acm *acm = func_to_acm(f); @@ -391,6 +393,14 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) acm->port_handshake_bits = w_value; break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SEND_BREAK: + if (w_index != acm->ctrl_id) + goto invalid; + + acm_send_break(&acm->port, w_value); + break; + default: invalid: dev_vdbg(&cdev->gadget->dev, -- cgit v1.2.3 From 424bef51fa530389b0b9008c9e144e40c10e8458 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Wed, 9 Mar 2022 11:10:33 +0000 Subject: usb: musb: Fix missing of_node_put() in omap2430_probe The device_node pointer is returned by of_parse_phandle() with refcount incremented. We should use of_node_put() on it when done. Fixes: 8934d3e4d0e7 ("usb: musb: omap2430: Don't use omap_get_control_dev()") Signed-off-by: Miaoqian Lin Link: https://lore.kernel.org/r/20220309111033.24487-1-linmq006@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/omap2430.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index d2b7e613eb34..f571a65ae6ee 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -362,6 +362,7 @@ static int omap2430_probe(struct platform_device *pdev) control_node = of_parse_phandle(np, "ctrl-module", 0); if (control_node) { control_pdev = of_find_device_by_node(control_node); + of_node_put(control_node); if (!control_pdev) { dev_err(&pdev->dev, "Failed to get control device\n"); ret = -EINVAL; -- cgit v1.2.3 From 4f8cfe675f4666117c93420783049eff04c3ceba Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 18 Apr 2022 23:33:55 +0300 Subject: usb: core: devices: remove dead code under #ifdef PROC_EXTRA usb_dump_hub_descriptor() and usb_dump_string() are defined under #ifdef PROC_EXTRA (while PROC_EXTRA doesn't seem to have ever been #define'd) since the dawn of the git era -- remove this dead code at last... Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/ec08915b-faf2-2f0b-dfb1-048dfa2c67f3@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 35 ----------------------------------- 1 file changed, 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index d8b0041de612..cc12cdd5559f 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -390,41 +390,6 @@ static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) return start; } - -#ifdef PROC_EXTRA /* TBD: may want to add this code later */ - -static char *usb_dump_hub_descriptor(char *start, char *end, - const struct usb_hub_descriptor *desc) -{ - int leng = USB_DT_HUB_NONVAR_SIZE; - unsigned char *ptr = (unsigned char *)desc; - - if (start > end) - return start; - start += sprintf(start, "Interface:"); - while (leng && start <= end) { - start += sprintf(start, " %02x", *ptr); - ptr++; leng--; - } - *start++ = '\n'; - return start; -} - -static char *usb_dump_string(char *start, char *end, - const struct usb_device *dev, char *id, int index) -{ - if (start > end) - return start; - start += sprintf(start, "Interface:"); - if (index <= dev->maxstring && dev->stringindex && - dev->stringindex[index]) - start += sprintf(start, "%s: %.100s ", id, - dev->stringindex[index]); - return start; -} - -#endif /* PROC_EXTRA */ - /*****************************************************************/ /* This is a recursive function. Parameters: -- cgit v1.2.3 From 2a0a71d92d150548360150cb8b57c82409d8d9c6 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 18 Apr 2022 10:39:06 -0400 Subject: USB: omap_udc: clean up comment Spelling replacement reactiviting to reactivating Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220418143906.475932-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/omap_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index d81af616fddc..54c33637a863 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -1467,7 +1467,7 @@ static void ep0_irq(struct omap_udc *udc, u16 irq_src) if (!udc->ep0_in) { stat = 0; /* read next OUT packet of request, maybe - * reactiviting the fifo; stall on errors. + * reactivating the fifo; stall on errors. */ stat = read_fifo(ep0, req); if (!req || stat < 0) { -- cgit v1.2.3 From 77e00508c29626500c2c70655b66aaa323a03b33 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 18 Apr 2022 09:48:38 -0400 Subject: usb: gadget: pxa27x_udc: clean up comment Spelling replacement endpoitn to endpoint Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220418134838.1236023-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/pxa27x_udc.h b/drivers/usb/gadget/udc/pxa27x_udc.h index 0a6bc18a1264..31bf79ce931c 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.h +++ b/drivers/usb/gadget/udc/pxa27x_udc.h @@ -326,7 +326,7 @@ struct udc_usb_ep { * @addr: usb endpoint number * @config: configuration in which this endpoint is active * @interface: interface in which this endpoint is active - * @alternate: altsetting in which this endpoitn is active + * @alternate: altsetting in which this endpoint is active * @fifo_size: max packet size in the endpoint fifo * @type: endpoint type (bulk, iso, int, ...) * @udccsr_value: save register of UDCCSR0 for suspend/resume -- cgit v1.2.3 From e707ec4d043502308209e3cce568f40f09c86d27 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 18 Apr 2022 09:36:19 -0400 Subject: usb: gadget: s3c-hsudc: clean up comments Spelling replacements occured to occurred currenty to currently Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220418133619.2852678-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/s3c-hsudc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/s3c-hsudc.c b/drivers/usb/gadget/udc/s3c-hsudc.c index bf803e013458..4b7eb7701470 100644 --- a/drivers/usb/gadget/udc/s3c-hsudc.c +++ b/drivers/usb/gadget/udc/s3c-hsudc.c @@ -126,7 +126,7 @@ struct s3c_hsudc_req { /** * struct s3c_hsudc - Driver's abstraction of the device controller. * @gadget: Instance of usb_gadget which is referenced by gadget driver. - * @driver: Reference to currenty active gadget driver. + * @driver: Reference to currently active gadget driver. * @dev: The device reference used by probe function. * @lock: Lock to synchronize the usage of Endpoints (EP's are indexed). * @regs: Remapped base address of controller's register space. @@ -633,7 +633,7 @@ static void s3c_hsudc_process_setup(struct s3c_hsudc *hsudc) } /** s3c_hsudc_handle_ep0_intr - Handle endpoint 0 interrupt. - * @hsudc: Device controller on which endpoint 0 interrupt has occured. + * @hsudc: Device controller on which endpoint 0 interrupt has occurred. * * Handle endpoint 0 interrupt when it occurs. EP0 interrupt could occur * when a stall handshake is sent to host or data is sent/received on -- cgit v1.2.3 From 53b0c69f35bd71a8f7abc5411ef23c0588bbb328 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Sun, 17 Apr 2022 09:07:12 -0400 Subject: usb: gadget: tegra-xudc: clean up comments Spelling replacements endpiont to endpoint Compliacne to Compliance Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220417130712.3932247-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index d9c406bdb680..6d31ccf6aee5 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -1434,7 +1434,7 @@ __tegra_xudc_ep_dequeue(struct tegra_xudc_ep *ep, return 0; } - /* Halt DMA for this endpiont. */ + /* Halt DMA for this endpoint. */ if (ep_ctx_read_state(ep->context) == EP_STATE_RUNNING) { ep_pause(xudc, ep->index); ep_wait_for_inactive(xudc, ep->index); @@ -3423,7 +3423,7 @@ static void tegra_xudc_device_params_init(struct tegra_xudc *xudc) } /* - * Compliacne suite appears to be violating polling LFPS tBurst max + * Compliance suite appears to be violating polling LFPS tBurst max * of 1.4us. Send 1.45us instead. */ val = xudc_readl(xudc, SSPX_CORE_CNT32); -- cgit v1.2.3 From 8666009c5f8327d13d4c91a2139b361b41ab43c2 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Tue, 19 Apr 2022 14:21:42 -0400 Subject: usb: gadget: net2272: clean up comments Spelling replacements definiton to definition acceptible to acceptable seperately to separately Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220419182142.1805918-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/net2272.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index 6a8884632273..c97cd4bc817c 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -71,7 +71,7 @@ static ushort dma_ep = 1; module_param(dma_ep, ushort, 0644); /* - * dma_mode: net2272 dma mode setting (see LOCCTL1 definiton): + * dma_mode: net2272 dma mode setting (see LOCCTL1 definition): * mode 0 == Slow DREQ mode * mode 1 == Fast DREQ mode * mode 2 == Burst mode @@ -97,7 +97,7 @@ module_param(fifo_mode, ushort, 0644); /* * enable_suspend: When enabled, the driver will respond to * USB suspend requests by powering down the NET2272. Otherwise, - * USB suspend requests will be ignored. This is acceptible for + * USB suspend requests will be ignored. This is acceptable for * self-powered devices. For bus powered devices set this to 1. */ static ushort enable_suspend = 0; @@ -288,7 +288,7 @@ static void net2272_ep_reset(struct net2272_ep *ep) | (1 << LOCAL_OUT_ZLP) | (1 << BUFFER_FLUSH)); - /* fifo size is handled seperately */ + /* fifo size is handled separately */ } static int net2272_disable(struct usb_ep *_ep) -- cgit v1.2.3 From d712b725b84dc1623d826b3232d706c7e9e53a4f Mon Sep 17 00:00:00 2001 From: "周琰杰 (Zhou Yanjie)" Date: Fri, 15 Apr 2022 03:25:36 +0800 Subject: USB: dwc2: Add OTG support for Ingenic SoCs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add OTG support for the JZ4775 SoC, the JZ4780 SoC, the X1000 SoC, the X1600 SoC, the X1700 SoC, the X1830 SoC, and the X2000 SoC from Ingenic. Introduce support for disable Ingenic overcurrent detection, once selected, it enables the GOTGCTL register bits VbvalidOvEn and VbvalidOvVal to disable the VBUS overcurrent detection. This patch is derived from Dragan Čečavac (in the kernel 3.18.3 tree of CI20). It is very useful for the MIPS Creator CI20 (r1). Without this patch, OTG port of CI20 has a great probability to face overcurrent warning, which breaks the OTG functionality. Acked-by: Minas Harutyunyan Signed-off-by: Dragan Čečavac Signed-off-by: 周琰杰 (Zhou Yanjie) Link: https://lore.kernel.org/r/1649964337-114337-3-git-send-email-zhouyanjie@wanyeetech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core.c | 9 +++++++++ drivers/usb/dwc2/core.h | 5 +++++ drivers/usb/dwc2/params.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index cf0bcd0dc320..dc4fc72ab1b6 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -1153,6 +1153,7 @@ static void dwc2_set_turnaround_time(struct dwc2_hsotg *hsotg) int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) { u32 usbcfg; + u32 otgctl; int retval = 0; if ((hsotg->params.speed == DWC2_SPEED_PARAM_FULL || @@ -1187,6 +1188,14 @@ int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) dwc2_writel(hsotg, usbcfg, GUSBCFG); } + if (!hsotg->params.activate_ingenic_overcurrent_detection) { + if (dwc2_is_host_mode(hsotg)) { + otgctl = readl(hsotg->regs + GOTGCTL); + otgctl |= GOTGCTL_VBVALOEN | GOTGCTL_VBVALOVAL; + writel(otgctl, hsotg->regs + GOTGCTL); + } + } + return retval; } diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 88c337bf564f..0683852e47e4 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -426,6 +426,10 @@ enum dwc2_ep0_state { * detection using GGPIO register. * 0 - Deactivate the external level detection (default) * 1 - Activate the external level detection + * @activate_ingenic_overcurrent_detection: Activate Ingenic overcurrent + * detection. + * 0 - Deactivate the overcurrent detection + * 1 - Activate the overcurrent detection (default) * @g_dma: Enables gadget dma usage (default: autodetect). * @g_dma_desc: Enables gadget descriptor DMA (default: autodetect). * @g_rx_fifo_size: The periodic rx fifo size for the device, in @@ -494,6 +498,7 @@ struct dwc2_core_params { u8 hird_threshold; bool activate_stm_fs_transceiver; bool activate_stm_id_vb_detection; + bool activate_ingenic_overcurrent_detection; bool ipg_isoc_en; u16 max_packet_count; u32 max_transfer_size; diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 1306f4ec788d..fdb8a42fff86 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -73,6 +73,47 @@ static void dwc2_set_his_params(struct dwc2_hsotg *hsotg) p->power_down = DWC2_POWER_DOWN_PARAM_NONE; } +static void dwc2_set_jz4775_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_x1600_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + +static void dwc2_set_x2000_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->otg_caps.hnp_support = false; + p->speed = DWC2_SPEED_PARAM_HIGH; + p->host_rx_fifo_size = 1024; + p->host_nperio_tx_fifo_size = 1024; + p->host_perio_tx_fifo_size = 1024; + p->host_channels = 16; + p->phy_type = DWC2_PHY_TYPE_PARAM_UTMI; + p->phy_utmi_width = 16; + p->activate_ingenic_overcurrent_detection = + !device_property_read_bool(hsotg->dev, "disable-over-current"); +} + static void dwc2_set_s3c6400_params(struct dwc2_hsotg *hsotg) { struct dwc2_core_params *p = &hsotg->params; @@ -221,7 +262,14 @@ static void dwc2_set_stm32mp15_hsotg_params(struct dwc2_hsotg *hsotg) const struct of_device_id dwc2_of_match_table[] = { { .compatible = "brcm,bcm2835-usb", .data = dwc2_set_bcm_params }, - { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, + { .compatible = "hisilicon,hi6220-usb", .data = dwc2_set_his_params }, + { .compatible = "ingenic,jz4775-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,jz4780-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1000-otg", .data = dwc2_set_jz4775_params }, + { .compatible = "ingenic,x1600-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1700-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x1830-otg", .data = dwc2_set_x1600_params }, + { .compatible = "ingenic,x2000-otg", .data = dwc2_set_x2000_params }, { .compatible = "rockchip,rk3066-usb", .data = dwc2_set_rk_params }, { .compatible = "lantiq,arx100-usb", .data = dwc2_set_ltq_params }, { .compatible = "lantiq,xrx200-usb", .data = dwc2_set_ltq_params }, -- cgit v1.2.3 From 5c29e864999763baec9eedb9ea5bd557aa4cbd77 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Mon, 4 Apr 2022 16:55:58 +0200 Subject: usb: musb: mediatek: Use clk_bulk API to simplify clock operations This driver uses three clocks and there's no special handling, they're either enabled or disabled sequentially: migrate to the clk_bulk API to simplify clock handling. This patch brings no functional changes. Signed-off-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20220404145558.93340-1-angelogioacchino.delregno@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/mediatek.c | 73 +++++++-------------------------------------- 1 file changed, 10 insertions(+), 63 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/mediatek.c b/drivers/usb/musb/mediatek.c index 1aeb34dbe24f..cad991380b0c 100644 --- a/drivers/usb/musb/mediatek.c +++ b/drivers/usb/musb/mediatek.c @@ -36,6 +36,8 @@ #define DMA_INTR_STATUS_MSK GENMASK(7, 0) #define DMA_INTR_UNMASK_SET_MSK GENMASK(31, 24) +#define MTK_MUSB_CLKS_NUM 3 + struct mtk_glue { struct device *dev; struct musb *musb; @@ -44,9 +46,7 @@ struct mtk_glue { struct phy *phy; struct usb_phy *xceiv; enum phy_mode phy_mode; - struct clk *main; - struct clk *mcu; - struct clk *univpll; + struct clk_bulk_data clks[MTK_MUSB_CLKS_NUM]; enum usb_role role; struct usb_role_switch *role_sw; }; @@ -55,64 +55,11 @@ static int mtk_musb_clks_get(struct mtk_glue *glue) { struct device *dev = glue->dev; - glue->main = devm_clk_get(dev, "main"); - if (IS_ERR(glue->main)) { - dev_err(dev, "fail to get main clock\n"); - return PTR_ERR(glue->main); - } - - glue->mcu = devm_clk_get(dev, "mcu"); - if (IS_ERR(glue->mcu)) { - dev_err(dev, "fail to get mcu clock\n"); - return PTR_ERR(glue->mcu); - } - - glue->univpll = devm_clk_get(dev, "univpll"); - if (IS_ERR(glue->univpll)) { - dev_err(dev, "fail to get univpll clock\n"); - return PTR_ERR(glue->univpll); - } - - return 0; -} + glue->clks[0].id = "main"; + glue->clks[1].id = "mcu"; + glue->clks[2].id = "univpll"; -static int mtk_musb_clks_enable(struct mtk_glue *glue) -{ - int ret; - - ret = clk_prepare_enable(glue->main); - if (ret) { - dev_err(glue->dev, "failed to enable main clock\n"); - goto err_main_clk; - } - - ret = clk_prepare_enable(glue->mcu); - if (ret) { - dev_err(glue->dev, "failed to enable mcu clock\n"); - goto err_mcu_clk; - } - - ret = clk_prepare_enable(glue->univpll); - if (ret) { - dev_err(glue->dev, "failed to enable univpll clock\n"); - goto err_univpll_clk; - } - - return 0; - -err_univpll_clk: - clk_disable_unprepare(glue->mcu); -err_mcu_clk: - clk_disable_unprepare(glue->main); -err_main_clk: - return ret; -} - -static void mtk_musb_clks_disable(struct mtk_glue *glue) -{ - clk_disable_unprepare(glue->univpll); - clk_disable_unprepare(glue->mcu); - clk_disable_unprepare(glue->main); + return devm_clk_bulk_get(dev, MTK_MUSB_CLKS_NUM, glue->clks); } static int mtk_otg_switch_set(struct mtk_glue *glue, enum usb_role role) @@ -390,7 +337,7 @@ static int mtk_musb_exit(struct musb *musb) mtk_otg_switch_exit(glue); phy_power_off(glue->phy); phy_exit(glue->phy); - mtk_musb_clks_disable(glue); + clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks); pm_runtime_put_sync(dev); pm_runtime_disable(dev); @@ -528,7 +475,7 @@ static int mtk_musb_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_sync(dev); - ret = mtk_musb_clks_enable(glue); + ret = clk_bulk_prepare_enable(MTK_MUSB_CLKS_NUM, glue->clks); if (ret) goto err_enable_clk; @@ -551,7 +498,7 @@ static int mtk_musb_probe(struct platform_device *pdev) return 0; err_device_register: - mtk_musb_clks_disable(glue); + clk_bulk_disable_unprepare(MTK_MUSB_CLKS_NUM, glue->clks); err_enable_clk: pm_runtime_put_sync(dev); pm_runtime_disable(dev); -- cgit v1.2.3 From 0f01017191384e3962fa31520a9fd9846c3d352f Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Sun, 3 Apr 2022 09:49:07 -0700 Subject: usb: dwc3: Don't switch OTG -> peripheral if extcon is present If the extcon device exists, get the mode from the extcon device. If the controller is DRD and the driver is unable to determine the mode, only then default the dr_mode to USB_DR_MODE_PERIPHERAL. Cc: Felipe Balbi Cc: Thinh Nguyen Cc: linux-usb@vger.kernel.org Cc: linux-kernel@vger.kernel.org Reviewed-by: Thinh Nguyen Signed-off-by: Andrey Smirnov Link: https://lore.kernel.org/r/20220403164907.662860-1-andrew.smirnov@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++- drivers/usb/dwc3/drd.c | 50 -------------------------------------------- 2 files changed, 54 insertions(+), 51 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 87a6cb37dfd0..364c64559ca4 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -85,7 +86,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) * mode. If the controller supports DRD but the dr_mode is not * specified or set to OTG, then set the mode to peripheral. */ - if (mode == USB_DR_MODE_OTG && + if (mode == USB_DR_MODE_OTG && !dwc->edev && (!IS_ENABLED(CONFIG_USB_ROLE_SWITCH) || !device_property_read_bool(dwc->dev, "usb-role-switch")) && !DWC3_VER_IS_PRIOR(DWC3, 330A)) @@ -1631,6 +1632,51 @@ static void dwc3_check_params(struct dwc3 *dwc) } } +static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + struct device_node *np_phy; + struct extcon_dev *edev = NULL; + const char *name; + + if (device_property_read_bool(dev, "extcon")) + return extcon_get_edev_by_phandle(dev, 0); + + /* + * Device tree platforms should get extcon via phandle. + * On ACPI platforms, we get the name from a device property. + * This device property is for kernel internal use only and + * is expected to be set by the glue code. + */ + if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) { + edev = extcon_get_extcon_dev(name); + if (!edev) + return ERR_PTR(-EPROBE_DEFER); + + return edev; + } + + /* + * Try to get an extcon device from the USB PHY controller's "port" + * node. Check if it has the "port" node first, to avoid printing the + * error message from underlying code, as it's a valid case: extcon + * device (and "port" node) may be missing in case of "usb-role-switch" + * or OTG mode. + */ + np_phy = of_parse_phandle(dev->of_node, "phys", 0); + if (of_graph_is_present(np_phy)) { + struct device_node *np_conn; + + np_conn = of_graph_get_remote_node(np_phy, -1, -1); + if (np_conn) + edev = extcon_find_edev_by_node(np_conn); + of_node_put(np_conn); + } + of_node_put(np_phy); + + return edev; +} + static int dwc3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1743,6 +1789,13 @@ static int dwc3_probe(struct platform_device *pdev) goto err2; } + dwc->edev = dwc3_get_extcon(dwc); + if (IS_ERR(dwc->edev)) { + ret = PTR_ERR(dwc->edev); + dev_err_probe(dwc->dev, ret, "failed to get extcon\n"); + goto err3; + } + ret = dwc3_get_dr_mode(dwc); if (ret) goto err3; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index b60b5f7b6dff..f277bebdaa09 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -439,51 +438,6 @@ static int dwc3_drd_notifier(struct notifier_block *nb, return NOTIFY_DONE; } -static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) -{ - struct device *dev = dwc->dev; - struct device_node *np_phy; - struct extcon_dev *edev = NULL; - const char *name; - - if (device_property_read_bool(dev, "extcon")) - return extcon_get_edev_by_phandle(dev, 0); - - /* - * Device tree platforms should get extcon via phandle. - * On ACPI platforms, we get the name from a device property. - * This device property is for kernel internal use only and - * is expected to be set by the glue code. - */ - if (device_property_read_string(dev, "linux,extcon-name", &name) == 0) { - edev = extcon_get_extcon_dev(name); - if (!edev) - return ERR_PTR(-EPROBE_DEFER); - - return edev; - } - - /* - * Try to get an extcon device from the USB PHY controller's "port" - * node. Check if it has the "port" node first, to avoid printing the - * error message from underlying code, as it's a valid case: extcon - * device (and "port" node) may be missing in case of "usb-role-switch" - * or OTG mode. - */ - np_phy = of_parse_phandle(dev->of_node, "phys", 0); - if (of_graph_is_present(np_phy)) { - struct device_node *np_conn; - - np_conn = of_graph_get_remote_node(np_phy, -1, -1); - if (np_conn) - edev = extcon_find_edev_by_node(np_conn); - of_node_put(np_conn); - } - of_node_put(np_phy); - - return edev; -} - #if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) #define ROLE_SWITCH 1 static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, @@ -584,10 +538,6 @@ int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; - dwc->edev = dwc3_get_extcon(dwc); - if (IS_ERR(dwc->edev)) - return PTR_ERR(dwc->edev); - if (ROLE_SWITCH && device_property_read_bool(dwc->dev, "usb-role-switch")) { ret = dwc3_setup_role_switch(dwc); -- cgit v1.2.3 From 2840d6dfcf4306878f7d17ac16d4a0a6422728cc Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Thu, 14 Apr 2022 00:39:02 -0700 Subject: usb: dwc3: EP clear halt leading to clearing of delayed_status The usb_ep_clear_halt() API can be called from the function driver, and translates to dwc3_gadget_ep_set_halt(). This routine is shared with when the host issues a clear feature ENDPOINT_HALT, and is differentiated by the protocol argument. If the following sequence occurs, there can be a situation where the delayed_status flag is improperly cleared for the wrong SETUP transaction: 1. Vendor specific control transfer returns USB_GADGET_DELAYED_STATUS. 2. DWC3 gadget sets dwc->delayed_status to '1'. 3. Another function driver issues a usb_ep_clear_halt() call. 4. DWC3 gadget issues dwc3_stop_active_transfer() and sets DWC3_EP_PENDING_CLEAR_STALL. 5. EP command complete interrupt triggers for the end transfer, and dwc3_ep0_send_delayed_status() is allowed to run, as delayed_status is '1' due to step#1. 6. STATUS phase is sent, and delayed_status is cleared. 7. Vendor specific control transfer is finished being handled, and issues usb_composite_setup_continue(). This results in queuing of a data phase. Cache the protocol flag so that DWC3 gadget is aware of when the clear halt is due to a SETUP request from the host versus when it is sourced from a function driver. This allows for the EP command complete interrupt to know if it needs to issue a delayed status phase. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/20220414073902.21960-1-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 ++ drivers/usb/dwc3/ep0.c | 1 + drivers/usb/dwc3/gadget.c | 5 ++++- 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 5c9d467195a6..81c486b3941c 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1046,6 +1046,7 @@ struct dwc3_scratchpad_array { * @tx_thr_num_pkt_prd: periodic ESS transmit packet count * @tx_max_burst_prd: max periodic ESS transmit burst size * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize + * @clear_stall_protocol: endpoint number that requires a delayed status phase * @hsphy_interface: "utmi" or "ulpi" * @connected: true when we're connected to a host, false otherwise * @softconnect: true when gadget connect is called, false when disconnect runs @@ -1266,6 +1267,7 @@ struct dwc3 { u8 tx_thr_num_pkt_prd; u8 tx_max_burst_prd; u8 tx_fifo_resize_max_num; + u8 clear_stall_protocol; const char *hsphy_interface; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 1064be5518f6..aa8476da222d 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -1080,6 +1080,7 @@ void dwc3_ep0_send_delayed_status(struct dwc3 *dwc) unsigned int direction = !dwc->ep0_expect_in; dwc->delayed_status = false; + dwc->clear_stall_protocol = 0; if (dwc->ep0state != EP0_STATUS_PHASE) return; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index c3d95f9b010b..efaa63df9336 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2152,6 +2152,9 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) if (dep->flags & DWC3_EP_END_TRANSFER_PENDING || (dep->flags & DWC3_EP_DELAY_STOP)) { dep->flags |= DWC3_EP_PENDING_CLEAR_STALL; + if (protocol) + dwc->clear_stall_protocol = dep->number; + return 0; } @@ -3483,7 +3486,7 @@ static void dwc3_gadget_endpoint_command_complete(struct dwc3_ep *dep, } dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); - if (dwc->delayed_status) + if (dwc->clear_stall_protocol == dep->number) dwc3_ep0_send_delayed_status(dwc); } -- cgit v1.2.3 From bf594d1d0c1d7b895954018043536ffd327844f9 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Thu, 14 Apr 2022 11:35:21 -0700 Subject: usb: dwc3: gadget: Replace list_for_each_entry_safe() if using giveback The list_for_each_entry_safe() macro saves the current item (n) and the item after (n+1), so that n can be safely removed without corrupting the list. However, when traversing the list and removing items using gadget giveback, the DWC3 lock is briefly released, allowing other routines to execute. There is a situation where, while items are being removed from the cancelled_list using dwc3_gadget_ep_cleanup_cancelled_requests(), the pullup disable routine is running in parallel (due to UDC unbind). As the cleanup routine removes n, and the pullup disable removes n+1, once the cleanup retakes the DWC3 lock, it references a request who was already removed/handled. With list debug enabled, this leads to a panic. Ensure all instances of the macro are replaced where gadget giveback is used. Example call stack: Thread#1: __dwc3_gadget_ep_set_halt() - CLEAR HALT -> dwc3_gadget_ep_cleanup_cancelled_requests() ->list_for_each_entry_safe() ->dwc3_gadget_giveback(n) ->dwc3_gadget_del_and_unmap_request()- n deleted[cancelled_list] ->spin_unlock ->Thread#2 executes ... ->dwc3_gadget_giveback(n+1) ->Already removed! Thread#2: dwc3_gadget_pullup() ->waiting for dwc3 spin_lock ... ->Thread#1 released lock ->dwc3_stop_active_transfers() ->dwc3_remove_requests() ->fetches n+1 item from cancelled_list (n removed by Thread#1) ->dwc3_gadget_giveback() ->dwc3_gadget_del_and_unmap_request()- n+1 deleted[cancelled_list] ->spin_unlock Fixes: d4f1afe5e896 ("usb: dwc3: gadget: move requests to cancelled_list") Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/20220414183521.23451-1-quic_wcheng@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index efaa63df9336..19477f4bbf54 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2001,10 +2001,10 @@ static void dwc3_gadget_ep_skip_trbs(struct dwc3_ep *dep, struct dwc3_request *r static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep) { struct dwc3_request *req; - struct dwc3_request *tmp; struct dwc3 *dwc = dep->dwc; - list_for_each_entry_safe(req, tmp, &dep->cancelled_list, list) { + while (!list_empty(&dep->cancelled_list)) { + req = next_request(&dep->cancelled_list); dwc3_gadget_ep_skip_trbs(dep, req); switch (req->status) { case DWC3_REQUEST_STATUS_DISCONNECTED: @@ -2021,6 +2021,12 @@ static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep) dwc3_gadget_giveback(dep, req, -ECONNRESET); break; } + /* + * The endpoint is disabled, let the dwc3_remove_requests() + * handle the cleanup. + */ + if (!dep->endpoint.desc) + break; } } @@ -3307,15 +3313,21 @@ static void dwc3_gadget_ep_cleanup_completed_requests(struct dwc3_ep *dep, const struct dwc3_event_depevt *event, int status) { struct dwc3_request *req; - struct dwc3_request *tmp; - list_for_each_entry_safe(req, tmp, &dep->started_list, list) { + while (!list_empty(&dep->started_list)) { int ret; + req = next_request(&dep->started_list); ret = dwc3_gadget_ep_cleanup_completed_request(dep, event, req, status); if (ret) break; + /* + * The endpoint is disabled, let the dwc3_remove_requests() + * handle the cleanup. + */ + if (!dep->endpoint.desc) + break; } } -- cgit v1.2.3 From 4d378f2ae58138d4c55684e1d274e7dd94aa6524 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Sun, 17 Apr 2022 20:03:05 +0800 Subject: drivers: usb: host: Fix deadlock in oxu_bus_suspend() There is a deadlock in oxu_bus_suspend(), which is shown below: (Thread 1) | (Thread 2) | timer_action() oxu_bus_suspend() | mod_timer() spin_lock_irq() //(1) | (wait a time) ... | oxu_watchdog() del_timer_sync() | spin_lock_irq() //(2) (wait timer to stop) | ... We hold oxu->lock in position (1) of thread 1, and use del_timer_sync() to wait timer to stop, but timer handler also need oxu->lock in position (2) of thread 2. As a result, oxu_bus_suspend() will block forever. This patch extracts del_timer_sync() from the protection of spin_lock_irq(), which could let timer handler to obtain the needed lock. Signed-off-by: Duoming Zhou Link: https://lore.kernel.org/r/20220417120305.64577-1-duoming@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/oxu210hp-hcd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index ddc155850bfc..8b73c61f86c9 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -3909,8 +3909,10 @@ static int oxu_bus_suspend(struct usb_hcd *hcd) } } + spin_unlock_irq(&oxu->lock); /* turn off now-idle HC */ del_timer_sync(&oxu->watchdog); + spin_lock_irq(&oxu->lock); ehci_halt(oxu); hcd->state = HC_STATE_SUSPENDED; -- cgit v1.2.3 From a03e2ddab8e735e2cc315609b297b300e9cc60d2 Mon Sep 17 00:00:00 2001 From: Zheng Yongjun Date: Fri, 22 Apr 2022 06:26:52 +0000 Subject: usb: dwc3: pci: Fix pm_runtime_get_sync() error checking If the device is already in a runtime PM enabled state pm_runtime_get_sync() will return 1, so a test for negative value should be used to check for errors. Fixes: 8eed00b237a28 ("usb: dwc3: pci: Runtime resume child device from wq") Signed-off-by: Zheng Yongjun Link: https://lore.kernel.org/r/20220422062652.10575-1-zhengyongjun3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 33f657d83246..491b5b05d196 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -286,7 +286,7 @@ static void dwc3_pci_resume_work(struct work_struct *work) int ret; ret = pm_runtime_get_sync(&dwc3->dev); - if (ret) { + if (ret < 0) { pm_runtime_put_sync_autosuspend(&dwc3->dev); return; } -- cgit v1.2.3 From 9671d55037065e9b83f84f1943bf65fc31036439 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 21 Apr 2022 10:39:26 -0700 Subject: USB: core: Disable remote wakeup for freeze/quiesce The PM_EVENT_FREEZE and PM_EVENT_QUIESCE messages should cause the device to stop generating interrupts. USB core was previously allowing devices that were already runtime suspended to keep remote wakeup enabled if they had gone down that way. This violates the contract with pm, and can potentially cause MSI interrupts to be lost. Change that so that if a device is runtime suspended with remote wakeups enabled, it will be resumed to ensure remote wakeup is always disabled across a freeze. Acked-by: Alan Stern Signed-off-by: Evan Green Link: https://lore.kernel.org/r/20220421103751.v3.1.I2c636c4decc358f5e6c27b810748904cc69beada@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/driver.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 355ed33a2179..b87452e22835 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1533,22 +1533,23 @@ static void choose_wakeup(struct usb_device *udev, pm_message_t msg) { int w; - /* Remote wakeup is needed only when we actually go to sleep. - * For things like FREEZE and QUIESCE, if the device is already - * autosuspended then its current wakeup setting is okay. + /* + * For FREEZE/QUIESCE, disable remote wakeups so no interrupts get + * generated. */ if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_QUIESCE) { - if (udev->state != USB_STATE_SUSPENDED) - udev->do_remote_wakeup = 0; - return; - } + w = 0; - /* Enable remote wakeup if it is allowed, even if no interface drivers - * actually want it. - */ - w = device_may_wakeup(&udev->dev); + } else { + /* + * Enable remote wakeup if it is allowed, even if no interface + * drivers actually want it. + */ + w = device_may_wakeup(&udev->dev); + } - /* If the device is autosuspended with the wrong wakeup setting, + /* + * If the device is autosuspended with the wrong wakeup setting, * autoresume now so the setting can be changed. */ if (udev->state == USB_STATE_SUSPENDED && w != udev->do_remote_wakeup) -- cgit v1.2.3 From 63acaa8e9c65dc34dc249440216f8e977f5d2748 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 21 Apr 2022 10:39:27 -0700 Subject: USB: hcd-pci: Fully suspend across freeze/thaw cycle The documentation for the freeze() method says that it "should quiesce the device so that it doesn't generate IRQs or DMA". The unspoken consequence of not doing this is that MSIs aimed at non-boot CPUs may get fully lost if they're sent during the period where the target CPU is offline. The current callbacks for USB HCD do not fully quiesce interrupts, specifically on XHCI. Change to use the full suspend/resume flow for freeze/thaw to ensure interrupts are fully quiesced. This fixes issues where USB devices fail to thaw during hibernation because XHCI misses its interrupt and cannot recover. Acked-by: Alan Stern Signed-off-by: Evan Green Link: https://lore.kernel.org/r/20220421103751.v3.2.I8226c7fdae88329ef70957b96a39b346c69a914e@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd-pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index f192925f74f7..482dae72ef1c 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -615,10 +615,10 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = { .suspend_noirq = hcd_pci_suspend_noirq, .resume_noirq = hcd_pci_resume_noirq, .resume = hcd_pci_resume, - .freeze = check_root_hub_suspended, + .freeze = hcd_pci_suspend, .freeze_noirq = check_root_hub_suspended, .thaw_noirq = NULL, - .thaw = NULL, + .thaw = hcd_pci_resume, .poweroff = hcd_pci_suspend, .poweroff_noirq = hcd_pci_suspend_noirq, .restore_noirq = hcd_pci_resume_noirq, -- cgit v1.2.3 From b45cde330ba7b52e4c9ea9b36b4a58d32b387441 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 17 Mar 2022 12:55:07 +0900 Subject: HID: usbhid: remove third argument of usb_maxpacket() The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Jiri Kosina CC: Benjamin Tissoires Signed-off-by: Vincent Mailhol Link: https://lore.kernel.org/r/20220317035514.6378-3-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/hid/usbhid/hid-core.c | 2 +- drivers/hid/usbhid/usbkbd.c | 2 +- drivers/hid/usbhid/usbmouse.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 54752c85604b..4490e2f7252a 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -387,7 +387,7 @@ static int hid_submit_ctrl(struct hid_device *hid) usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0); maxpacket = usb_maxpacket(hid_to_usb_dev(hid), - usbhid->urbctrl->pipe, 0); + usbhid->urbctrl->pipe); len += (len == 0); /* Don't allow 0-length reports */ len = round_up(len, maxpacket); if (len > usbhid->bufsize) diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index df02002066ce..b4b007c4beb6 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -279,7 +279,7 @@ static int usb_kbd_probe(struct usb_interface *iface, return -ENODEV; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(dev, pipe); kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); input_dev = input_allocate_device(); diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c index c89332017d5d..fb1d7d1f6999 100644 --- a/drivers/hid/usbhid/usbmouse.c +++ b/drivers/hid/usbhid/usbmouse.c @@ -123,7 +123,7 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i return -ENODEV; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(dev, pipe); mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); input_dev = input_allocate_device(); -- cgit v1.2.3 From 948bf187694fc1f4c20cf972fa18b1a6fb3d7603 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 17 Mar 2022 12:55:08 +0900 Subject: Input: remove third argument of usb_maxpacket() The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Ville Syrjala CC: Dmitry Torokhov CC: Henk Vergonet Signed-off-by: Vincent Mailhol Link: https://lore.kernel.org/r/20220317035514.6378-4-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/input/misc/ati_remote2.c | 2 +- drivers/input/misc/cm109.c | 2 +- drivers/input/misc/powermate.c | 2 +- drivers/input/misc/yealink.c | 2 +- drivers/input/tablet/acecad.c | 2 +- drivers/input/tablet/pegasus_notetaker.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c index 8a36d78fed63..946bf75aa106 100644 --- a/drivers/input/misc/ati_remote2.c +++ b/drivers/input/misc/ati_remote2.c @@ -639,7 +639,7 @@ static int ati_remote2_urb_init(struct ati_remote2 *ar2) return -ENOMEM; pipe = usb_rcvintpipe(udev, ar2->ep[i]->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); maxp = maxp > 4 ? 4 : maxp; usb_fill_int_urb(ar2->urb[i], udev, pipe, ar2->buf[i], maxp, diff --git a/drivers/input/misc/cm109.c b/drivers/input/misc/cm109.c index f515fae465c3..728325a2d574 100644 --- a/drivers/input/misc/cm109.c +++ b/drivers/input/misc/cm109.c @@ -745,7 +745,7 @@ static int cm109_usb_probe(struct usb_interface *intf, /* get a handle to the interrupt data pipe */ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); - ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + ret = usb_maxpacket(udev, pipe); if (ret != USB_PKT_LEN) dev_err(&intf->dev, "invalid payload size %d, expected %d\n", ret, USB_PKT_LEN); diff --git a/drivers/input/misc/powermate.c b/drivers/input/misc/powermate.c index c4e0e1886061..c1c733a9cb89 100644 --- a/drivers/input/misc/powermate.c +++ b/drivers/input/misc/powermate.c @@ -374,7 +374,7 @@ static int powermate_probe(struct usb_interface *intf, const struct usb_device_i /* get a handle to the interrupt data pipe */ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); if (maxp < POWERMATE_PAYLOAD_SIZE_MIN || maxp > POWERMATE_PAYLOAD_SIZE_MAX) { printk(KERN_WARNING "powermate: Expected payload of %d--%d bytes, found %d bytes!\n", diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c index 8ab01c7601b1..69420781db30 100644 --- a/drivers/input/misc/yealink.c +++ b/drivers/input/misc/yealink.c @@ -905,7 +905,7 @@ static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id) /* get a handle to the interrupt data pipe */ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); - ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + ret = usb_maxpacket(udev, pipe); if (ret != USB_PKT_LEN) dev_err(&intf->dev, "invalid payload size %d, expected %zd\n", ret, USB_PKT_LEN); diff --git a/drivers/input/tablet/acecad.c b/drivers/input/tablet/acecad.c index a38d1fe97334..56c7e471ac32 100644 --- a/drivers/input/tablet/acecad.c +++ b/drivers/input/tablet/acecad.c @@ -130,7 +130,7 @@ static int usb_acecad_probe(struct usb_interface *intf, const struct usb_device_ return -ENODEV; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(dev, pipe); acecad = kzalloc(sizeof(struct usb_acecad), GFP_KERNEL); input_dev = input_allocate_device(); diff --git a/drivers/input/tablet/pegasus_notetaker.c b/drivers/input/tablet/pegasus_notetaker.c index 749edbdb7ffa..c608ac505d1b 100644 --- a/drivers/input/tablet/pegasus_notetaker.c +++ b/drivers/input/tablet/pegasus_notetaker.c @@ -296,7 +296,7 @@ static int pegasus_probe(struct usb_interface *intf, pegasus->intf = intf; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); - pegasus->data_len = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + pegasus->data_len = usb_maxpacket(dev, pipe); pegasus->data = usb_alloc_coherent(dev, pegasus->data_len, GFP_KERNEL, &pegasus->data_dma); -- cgit v1.2.3 From 61f879ab755b7dd6f468c55358a3061092682041 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 17 Mar 2022 12:55:09 +0900 Subject: media: remove third argument of usb_maxpacket() The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Sean Young CC: Mauro Carvalho Chehab CC: Benjamin Valentin Signed-off-by: Vincent Mailhol Link: https://lore.kernel.org/r/20220317035514.6378-5-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/media/rc/ati_remote.c | 4 ++-- drivers/media/rc/mceusb.c | 2 +- drivers/media/rc/streamzap.c | 2 +- drivers/media/rc/xbox_remote.c | 2 +- drivers/media/usb/tm6000/tm6000-dvb.c | 2 +- drivers/media/usb/tm6000/tm6000-input.c | 2 +- drivers/media/usb/tm6000/tm6000-video.c | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/media/rc/ati_remote.c b/drivers/media/rc/ati_remote.c index c12dda73cdd5..3155e876616d 100644 --- a/drivers/media/rc/ati_remote.c +++ b/drivers/media/rc/ati_remote.c @@ -773,7 +773,7 @@ static int ati_remote_initialize(struct ati_remote *ati_remote) /* Set up irq_urb */ pipe = usb_rcvintpipe(udev, ati_remote->endpoint_in->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; usb_fill_int_urb(ati_remote->irq_urb, udev, pipe, ati_remote->inbuf, @@ -784,7 +784,7 @@ static int ati_remote_initialize(struct ati_remote *ati_remote) /* Set up out_urb */ pipe = usb_sndintpipe(udev, ati_remote->endpoint_out->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; usb_fill_int_urb(ati_remote->out_urb, udev, pipe, ati_remote->outbuf, diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c index 2dc810f5a73f..0834d5f866fd 100644 --- a/drivers/media/rc/mceusb.c +++ b/drivers/media/rc/mceusb.c @@ -1728,7 +1728,7 @@ static int mceusb_dev_probe(struct usb_interface *intf, pipe = usb_rcvintpipe(dev, ep_in->bEndpointAddress); else pipe = usb_rcvbulkpipe(dev, ep_in->bEndpointAddress); - maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(dev, pipe); ir = kzalloc(sizeof(struct mceusb_dev), GFP_KERNEL); if (!ir) diff --git a/drivers/media/rc/streamzap.c b/drivers/media/rc/streamzap.c index 16ba85d7c090..deb85330c940 100644 --- a/drivers/media/rc/streamzap.c +++ b/drivers/media/rc/streamzap.c @@ -307,7 +307,7 @@ static int streamzap_probe(struct usb_interface *intf, } pipe = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(usbdev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(usbdev, pipe); if (maxp == 0) { dev_err(&intf->dev, "%s: endpoint Max Packet Size is 0!?!\n", diff --git a/drivers/media/rc/xbox_remote.c b/drivers/media/rc/xbox_remote.c index 98d0b43608ad..7424b2031152 100644 --- a/drivers/media/rc/xbox_remote.c +++ b/drivers/media/rc/xbox_remote.c @@ -171,7 +171,7 @@ static int xbox_remote_initialize(struct xbox_remote *xbox_remote, /* Set up irq_urb */ pipe = usb_rcvintpipe(udev, endpoint_in->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; usb_fill_int_urb(xbox_remote->irq_urb, udev, pipe, xbox_remote->inbuf, diff --git a/drivers/media/usb/tm6000/tm6000-dvb.c b/drivers/media/usb/tm6000/tm6000-dvb.c index 8c2725e4105b..ee04973cbf93 100644 --- a/drivers/media/usb/tm6000/tm6000-dvb.c +++ b/drivers/media/usb/tm6000/tm6000-dvb.c @@ -120,7 +120,7 @@ static int tm6000_start_stream(struct tm6000_core *dev) pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in.endp->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); - size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + size = usb_maxpacket(dev->udev, pipe); size = size * 15; /* 512 x 8 or 12 or 15 */ dvb->bulk_urb->transfer_buffer = kzalloc(size, GFP_KERNEL); diff --git a/drivers/media/usb/tm6000/tm6000-input.c b/drivers/media/usb/tm6000/tm6000-input.c index 84602edf3fe8..5136e9e202f1 100644 --- a/drivers/media/usb/tm6000/tm6000-input.c +++ b/drivers/media/usb/tm6000/tm6000-input.c @@ -340,7 +340,7 @@ static int __tm6000_ir_int_start(struct rc_dev *rc) dev->int_in.endp->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); - size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + size = usb_maxpacket(dev->udev, pipe); dprintk(1, "IR max size: %d\n", size); ir->int_urb->transfer_buffer = kzalloc(size, GFP_ATOMIC); diff --git a/drivers/media/usb/tm6000/tm6000-video.c b/drivers/media/usb/tm6000/tm6000-video.c index e293f6f3d1bc..d855a19551f3 100644 --- a/drivers/media/usb/tm6000/tm6000-video.c +++ b/drivers/media/usb/tm6000/tm6000-video.c @@ -570,7 +570,7 @@ static int tm6000_prepare_isoc(struct tm6000_core *dev) dev->isoc_in.endp->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); - size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + size = usb_maxpacket(dev->udev, pipe); if (size > dev->isoc_in.maxsize) size = dev->isoc_in.maxsize; -- cgit v1.2.3 From e13adbfa391264673bd3ff29ca966af0147b6f4d Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 17 Mar 2022 12:55:10 +0900 Subject: net: remove third argument of usb_maxpacket() The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Oliver Neukum CC: David S. Miller CC: Jakub Kicinski CC: Woojung Huh CC: Felix Fietkau CC: Lorenzo Bianconi CC: Ryder Lee CC: Kalle Valo CC: Matthias Brugger CC: Stanislaw Gruszka CC: Helmut Schaa Signed-off-by: Vincent Mailhol Link: https://lore.kernel.org/r/20220317035514.6378-6-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/net/usb/cdc_ncm.c | 4 ++-- drivers/net/usb/lan78xx.c | 4 ++-- drivers/net/usb/rndis_host.c | 2 +- drivers/net/usb/usbnet.c | 4 ++-- drivers/net/wireless/mediatek/mt76/usb.c | 2 +- drivers/net/wireless/ralink/rt2x00/rt2x00usb.c | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 15f91d691bba..63ce9d81f61b 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -441,7 +441,7 @@ static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx) * .bind which is called before usbnet sets up dev->maxpacket */ if (val != le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize) && - val % usb_maxpacket(dev->udev, dev->out, 1) == 0) + val % usb_maxpacket(dev->udev, dev->out) == 0) val++; /* we might need to flush any pending tx buffers if running */ @@ -465,7 +465,7 @@ static void cdc_ncm_update_rxtx_max(struct usbnet *dev, u32 new_rx, u32 new_tx) usbnet_update_max_qlen(dev); /* never pad more than 3 full USB packets per transfer */ - ctx->min_tx_pkt = clamp_t(u16, ctx->tx_max - 3 * usb_maxpacket(dev->udev, dev->out, 1), + ctx->min_tx_pkt = clamp_t(u16, ctx->tx_max - 3 * usb_maxpacket(dev->udev, dev->out), CDC_NCM_MIN_TX_PKT, ctx->tx_max); } diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 415f16662f88..a7433939d091 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -4423,7 +4423,7 @@ static int lan78xx_probe(struct usb_interface *intf, goto out4; period = ep_intr->desc.bInterval; - maxp = usb_maxpacket(dev->udev, dev->pipe_intr, 0); + maxp = usb_maxpacket(dev->udev, dev->pipe_intr); buf = kmalloc(maxp, GFP_KERNEL); if (!buf) { ret = -ENOMEM; @@ -4441,7 +4441,7 @@ static int lan78xx_probe(struct usb_interface *intf, dev->urb_intr->transfer_flags |= URB_FREE_BUFFER; } - dev->maxpacket = usb_maxpacket(dev->udev, dev->pipe_out, 1); + dev->maxpacket = usb_maxpacket(dev->udev, dev->pipe_out); /* Reject broken descriptors. */ if (dev->maxpacket == 0) { diff --git a/drivers/net/usb/rndis_host.c b/drivers/net/usb/rndis_host.c index 247f58cb0f84..de0b00bd2eca 100644 --- a/drivers/net/usb/rndis_host.c +++ b/drivers/net/usb/rndis_host.c @@ -333,7 +333,7 @@ generic_rndis_bind(struct usbnet *dev, struct usb_interface *intf, int flags) net->hard_header_len += sizeof (struct rndis_data_hdr); dev->hard_mtu = net->mtu + net->hard_header_len; - dev->maxpacket = usb_maxpacket(dev->udev, dev->out, 1); + dev->maxpacket = usb_maxpacket(dev->udev, dev->out); if (dev->maxpacket == 0) { netif_dbg(dev, probe, dev->net, "dev->maxpacket can't be 0\n"); diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 9a6450f796dc..d5bf5733b277 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -229,7 +229,7 @@ static int init_status (struct usbnet *dev, struct usb_interface *intf) pipe = usb_rcvintpipe (dev->udev, dev->status->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); - maxp = usb_maxpacket (dev->udev, pipe, 0); + maxp = usb_maxpacket(dev->udev, pipe); /* avoid 1 msec chatter: min 8 msec poll rate */ period = max ((int) dev->status->desc.bInterval, @@ -1789,7 +1789,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) if (!dev->rx_urb_size) dev->rx_urb_size = dev->hard_mtu; - dev->maxpacket = usb_maxpacket (dev->udev, dev->out, 1); + dev->maxpacket = usb_maxpacket(dev->udev, dev->out); if (dev->maxpacket == 0) { /* that is a broken device */ status = -ENODEV; diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index a85e192c9d59..1bb92ca7451b 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -1068,7 +1068,7 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf, INIT_WORK(&usb->stat_work, mt76u_tx_status_data); - usb->data_len = usb_maxpacket(udev, usb_sndctrlpipe(udev, 0), 1); + usb->data_len = usb_maxpacket(udev, usb_sndctrlpipe(udev, 0)); if (usb->data_len < 32) usb->data_len = 32; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c index 74c3d8cb3100..0827bc860bf8 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c @@ -586,10 +586,10 @@ static void rt2x00usb_assign_endpoint(struct data_queue *queue, if (queue->qid == QID_RX) { pipe = usb_rcvbulkpipe(usb_dev, queue->usb_endpoint); - queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe, 0); + queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe); } else { pipe = usb_sndbulkpipe(usb_dev, queue->usb_endpoint); - queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe, 1); + queue->usb_maxpacket = usb_maxpacket(usb_dev, pipe); } if (!queue->usb_maxpacket) -- cgit v1.2.3 From dcd2e49b550e928164ccefb5bdf90988ed4df00e Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Thu, 17 Mar 2022 12:55:11 +0900 Subject: usb: remove third argument of usb_maxpacket() The third argument of usb_maxpacket(): in_out has been deprecated because it could be derived from the second argument (e.g. using usb_pipeout(pipe)). N.B. function usb_maxpacket() was made variadic to accommodate the transition from the old prototype with three arguments to the new one with only two arguments (so that no renaming is needed). The variadic argument is to be removed once all users of usb_maxpacket() get migrated. CC: Duncan Sands CC: Alan Stern CC: Olav Kongas CC: Rui Miguel Silva Signed-off-by: Vincent Mailhol Link: https://lore.kernel.org/r/20220317035514.6378-7-mailhol.vincent@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/usbatm.c | 2 +- drivers/usb/c67x00/c67x00-sched.c | 4 ++-- drivers/usb/core/hub.c | 2 +- drivers/usb/host/ehci-q.c | 4 ++-- drivers/usb/host/fhci-hcd.c | 3 +-- drivers/usb/host/fotg210-hcd.c | 2 +- drivers/usb/host/isp116x-hcd.c | 5 ++--- drivers/usb/host/isp1362-hcd.c | 6 +++--- drivers/usb/host/max3421-hcd.c | 6 +++--- drivers/usb/host/ohci-hcd.c | 3 +-- drivers/usb/host/oxu210hp-hcd.c | 4 ++-- drivers/usb/host/r8a66597-hcd.c | 3 +-- drivers/usb/host/sl811-hcd.c | 6 +++--- drivers/usb/isp1760/isp1760-hcd.c | 6 ++---- drivers/usb/misc/ftdi-elan.c | 15 +++++---------- drivers/usb/misc/lvstest.c | 2 +- drivers/usb/storage/onetouch.c | 2 +- drivers/usb/storage/transport.c | 2 +- 18 files changed, 33 insertions(+), 44 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index e3a49d837609..362217189ef3 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c @@ -1091,7 +1091,7 @@ int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id, snd_buf_bytes - (snd_buf_bytes % instance->tx_channel.stride)); /* rx buffer size must be a positive multiple of the endpoint maxpacket */ - maxpacket = usb_maxpacket(usb_dev, instance->rx_channel.endpoint, 0); + maxpacket = usb_maxpacket(usb_dev, instance->rx_channel.endpoint); if ((maxpacket < 1) || (maxpacket > UDSL_MAX_BUF_SIZE)) { dev_err(dev, "%s: invalid endpoint %02x!\n", __func__, diff --git a/drivers/usb/c67x00/c67x00-sched.c b/drivers/usb/c67x00/c67x00-sched.c index c7d3e907be81..a09fa68a6ce7 100644 --- a/drivers/usb/c67x00/c67x00-sched.c +++ b/drivers/usb/c67x00/c67x00-sched.c @@ -655,7 +655,7 @@ static int c67x00_add_data_urb(struct c67x00_hcd *c67x00, struct urb *urb) usb_pipeout(urb->pipe)); remaining = urb->transfer_buffer_length - urb->actual_length; - maxps = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + maxps = usb_maxpacket(urb->dev, urb->pipe); need_empty = (urb->transfer_flags & URB_ZERO_PACKET) && usb_pipeout(urb->pipe) && !(remaining % maxps); @@ -866,7 +866,7 @@ static inline int c67x00_end_of_data(struct c67x00_td *td) if (unlikely(!act_bytes)) return 1; /* This was an empty packet */ - maxps = usb_maxpacket(td_udev(td), td->pipe, usb_pipeout(td->pipe)); + maxps = usb_maxpacket(td_udev(td), td->pipe); if (unlikely(act_bytes < maxps)) return 1; /* Smaller then full packet */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 1460857026e0..7781b2d31473 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1635,7 +1635,7 @@ static int hub_configure(struct usb_hub *hub, * maxpktsize is defined in hcd.c's fake endpoint descriptors * to be big enough for at least USB_MAXCHILDREN ports. */ pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(hdev, pipe); if (maxp > sizeof(*hub->buffer)) maxp = sizeof(*hub->buffer); diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index a2a5c2996350..1163af6fad77 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -645,7 +645,7 @@ qh_urb_transaction ( token |= (1 /* "in" */ << 8); /* else it's already initted to "out" pid (0 << 8) */ - maxpacket = usb_maxpacket(urb->dev, urb->pipe, !is_input); + maxpacket = usb_maxpacket(urb->dev, urb->pipe); /* * buffer gets wrapped in one or more qtds; @@ -1218,7 +1218,7 @@ static int ehci_submit_single_step_set_feature( token |= (1 /* "in" */ << 8); /*This is IN stage*/ - maxpacket = usb_maxpacket(urb->dev, urb->pipe, 0); + maxpacket = usb_maxpacket(urb->dev, urb->pipe); qtd_fill(ehci, qtd, buf, len, token, maxpacket); diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c index a8e1048278d0..2ba09c3fbc2f 100644 --- a/drivers/usb/host/fhci-hcd.c +++ b/drivers/usb/host/fhci-hcd.c @@ -408,8 +408,7 @@ static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, size++; else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 && (urb->transfer_buffer_length - % usb_maxpacket(urb->dev, pipe, - usb_pipeout(pipe))) != 0) + % usb_maxpacket(urb->dev, pipe)) != 0) size++; break; case PIPE_ISOCHRONOUS: diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index c3fd375b4778..f8c111e08a0d 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -2596,7 +2596,7 @@ static struct list_head *qh_urb_transaction(struct fotg210_hcd *fotg210, token |= (1 /* "in" */ << 8); /* else it's already initted to "out" pid (0 << 8) */ - maxpacket = usb_maxpacket(urb->dev, urb->pipe, !is_input); + maxpacket = usb_maxpacket(urb->dev, urb->pipe); /* * buffer gets wrapped in one or more qtds; diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 8c7f0991c21b..4f564d71bb0b 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -726,7 +726,7 @@ static int isp116x_urb_enqueue(struct usb_hcd *hcd, INIT_LIST_HEAD(&ep->schedule); ep->udev = udev; ep->epnum = epnum; - ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + ep->maxpacket = usb_maxpacket(udev, urb->pipe); usb_settoggle(udev, epnum, is_out, 0); if (type == PIPE_CONTROL) { @@ -757,8 +757,7 @@ static int isp116x_urb_enqueue(struct usb_hcd *hcd, ep->load = usb_calc_bus_time(udev->speed, !is_out, (type == PIPE_ISOCHRONOUS), - usb_maxpacket(udev, pipe, - is_out)) / + usb_maxpacket(udev, pipe)) / 1000; } hep->hcpriv = ep; diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c index d8610ce8f2ec..0e14d1d07709 100644 --- a/drivers/usb/host/isp1362-hcd.c +++ b/drivers/usb/host/isp1362-hcd.c @@ -1279,7 +1279,7 @@ static int isp1362_urb_enqueue(struct usb_hcd *hcd, ep->udev = usb_get_dev(udev); ep->hep = hep; ep->epnum = epnum; - ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + ep->maxpacket = usb_maxpacket(udev, urb->pipe); ep->ptd_offset = -EINVAL; ep->ptd_index = -EINVAL; usb_settoggle(udev, epnum, is_out, 0); @@ -1299,8 +1299,8 @@ static int isp1362_urb_enqueue(struct usb_hcd *hcd, ep->interval = urb->interval; ep->branch = PERIODIC_SIZE; ep->load = usb_calc_bus_time(udev->speed, !is_out, - (type == PIPE_ISOCHRONOUS), - usb_maxpacket(udev, pipe, is_out)) / 1000; + type == PIPE_ISOCHRONOUS, + usb_maxpacket(udev, pipe)) / 1000; break; } hep->hcpriv = ep; diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 99a5523a79fb..502a3ac5e35b 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -546,7 +546,7 @@ max3421_transfer_out(struct usb_hcd *hcd, struct urb *urb, int fast_retransmit) return MAX3421_HXFR_BULK_OUT(epnum); } - max_packet = usb_maxpacket(urb->dev, urb->pipe, 1); + max_packet = usb_maxpacket(urb->dev, urb->pipe); if (max_packet > MAX3421_FIFO_SIZE) { /* @@ -952,7 +952,7 @@ max3421_transfer_in_done(struct usb_hcd *hcd, struct urb *urb) * USB 2.0 Section 5.3.2 Pipes: packets must be full size * except for last one. */ - max_packet = usb_maxpacket(urb->dev, urb->pipe, 0); + max_packet = usb_maxpacket(urb->dev, urb->pipe); if (max_packet > MAX3421_FIFO_SIZE) { /* * We do not support isochronous transfers at this @@ -998,7 +998,7 @@ max3421_transfer_out_done(struct usb_hcd *hcd, struct urb *urb) * max_packet as an indicator that the end of the * packet has been reached). */ - u32 max_packet = usb_maxpacket(urb->dev, urb->pipe, 1); + u32 max_packet = usb_maxpacket(urb->dev, urb->pipe); if (max3421_hcd->curr_len == max_packet) return 0; diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 666b1c665188..c4c821c2288c 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -181,8 +181,7 @@ static int ohci_urb_enqueue ( size++; else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 && (urb->transfer_buffer_length - % usb_maxpacket (urb->dev, pipe, - usb_pipeout (pipe))) == 0) + % usb_maxpacket(urb->dev, pipe)) == 0) size++; break; case PIPE_ISOCHRONOUS: /* number of packets from URB */ diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 8b73c61f86c9..3a441310c713 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -1685,7 +1685,7 @@ static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, token |= (1 /* "in" */ << 8); /* else it's already initted to "out" pid (0 << 8) */ - maxpacket = usb_maxpacket(urb->dev, urb->pipe, !is_input); + maxpacket = usb_maxpacket(urb->dev, urb->pipe); /* * buffer gets wrapped in one or more qtds; @@ -1796,7 +1796,7 @@ static struct ehci_qh *qh_make(struct oxu_hcd *oxu, is_input = usb_pipein(urb->pipe); type = usb_pipetype(urb->pipe); - maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + maxp = usb_maxpacket(urb->dev, urb->pipe); /* Compute interrupt scheduling parameters just once, and save. * - allowing for high bandwidth, how many nsec/uframe are used? diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 63719cdf6a4e..abb88dd40d4e 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -1867,8 +1867,7 @@ static struct r8a66597_td *r8a66597_make_td(struct r8a66597 *r8a66597, td->pipe = hep->hcpriv; td->urb = urb; td->address = get_urb_to_r8a66597_addr(r8a66597, urb); - td->maxpacket = usb_maxpacket(urb->dev, urb->pipe, - !usb_pipein(urb->pipe)); + td->maxpacket = usb_maxpacket(urb->dev, urb->pipe); if (usb_pipecontrol(urb->pipe)) td->type = USB_PID_SETUP; else if (usb_pipein(urb->pipe)) diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index 85623731a516..d206bd95c7bb 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -842,7 +842,7 @@ static int sl811h_urb_enqueue( INIT_LIST_HEAD(&ep->schedule); ep->udev = udev; ep->epnum = epnum; - ep->maxpacket = usb_maxpacket(udev, urb->pipe, is_out); + ep->maxpacket = usb_maxpacket(udev, urb->pipe); ep->defctrl = SL11H_HCTLMASK_ARM | SL11H_HCTLMASK_ENABLE; usb_settoggle(udev, epnum, is_out, 0); @@ -878,8 +878,8 @@ static int sl811h_urb_enqueue( if (type == PIPE_ISOCHRONOUS) ep->defctrl |= SL11H_HCTLMASK_ISOCH; ep->load = usb_calc_bus_time(udev->speed, !is_out, - (type == PIPE_ISOCHRONOUS), - usb_maxpacket(udev, pipe, is_out)) + type == PIPE_ISOCHRONOUS, + usb_maxpacket(udev, pipe)) / 1000; break; } diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 893becb077d3..76862ba40f35 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -825,8 +825,7 @@ static void create_ptd_atl(struct isp1760_qh *qh, memset(ptd, 0, sizeof(*ptd)); /* according to 3.6.2, max packet len can not be > 0x400 */ - maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, - usb_pipeout(qtd->urb->pipe)); + maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe); multi = 1 + ((maxpacket >> 11) & 0x3); maxpacket &= 0x7ff; @@ -1808,8 +1807,7 @@ static void packetize_urb(struct usb_hcd *hcd, packet_type = IN_PID; } - maxpacketsize = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + maxpacketsize = usb_maxpacket(urb->dev, urb->pipe); /* * buffer gets wrapped in one or more qtds; diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c index 6c38c62d29b2..b2f980409d0b 100644 --- a/drivers/usb/misc/ftdi-elan.c +++ b/drivers/usb/misc/ftdi-elan.c @@ -1449,8 +1449,7 @@ wait:if (ftdi->disconnected > 0) { command->length = 0x8007; command->address = (toggle_bits << 6) | (ep_number << 2) | (address << 0); - command->width = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + command->width = usb_maxpacket(urb->dev, urb->pipe); command->follows = 8; command->value = 0; command->buffer = urb->setup_packet; @@ -1514,8 +1513,7 @@ wait:if (ftdi->disconnected > 0) { 1); command->address = (toggle_bits << 6) | (ep_number << 2) | (address << 0); - command->width = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + command->width = usb_maxpacket(urb->dev, urb->pipe); command->follows = 0; command->value = 0; command->buffer = NULL; @@ -1571,8 +1569,7 @@ wait:if (ftdi->disconnected > 0) { command->length = 0x0000; command->address = (toggle_bits << 6) | (ep_number << 2) | (address << 0); - command->width = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + command->width = usb_maxpacket(urb->dev, urb->pipe); command->follows = 0; command->value = 0; command->buffer = NULL; @@ -1634,8 +1631,7 @@ wait:if (ftdi->disconnected > 0) { command->header = 0x81 | (ed << 5); command->address = (toggle_bits << 6) | (ep_number << 2) | (address << 0); - command->width = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + command->width = usb_maxpacket(urb->dev, urb->pipe); command->follows = min_t(u32, 1024, urb->transfer_buffer_length - urb->actual_length); @@ -1715,8 +1711,7 @@ wait:if (ftdi->disconnected > 0) { 1); command->address = (toggle_bits << 6) | (ep_number << 2) | (address << 0); - command->width = usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe)); + command->width = usb_maxpacket(urb->dev, urb->pipe); command->follows = 0; command->value = 0; command->buffer = NULL; diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c index f8686139d6f3..25ec5666a75e 100644 --- a/drivers/usb/misc/lvstest.c +++ b/drivers/usb/misc/lvstest.c @@ -437,7 +437,7 @@ static int lvs_rh_probe(struct usb_interface *intf, INIT_WORK(&lvs->rh_work, lvs_rh_work); pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(hdev, pipe); usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp, lvs_rh_irq, lvs, endpoint->bInterval); diff --git a/drivers/usb/storage/onetouch.c b/drivers/usb/storage/onetouch.c index a989fe930e21..1db2eefeea22 100644 --- a/drivers/usb/storage/onetouch.c +++ b/drivers/usb/storage/onetouch.c @@ -180,7 +180,7 @@ static int onetouch_connect_input(struct us_data *ss) return -ENODEV; pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); - maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(udev, pipe); maxp = min(maxp, ONETOUCH_PKT_LEN); onetouch = kzalloc(sizeof(struct usb_onetouch), GFP_KERNEL); diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 1928b3918242..64d96d210e02 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -363,7 +363,7 @@ static int usb_stor_intr_transfer(struct us_data *us, void *buf, usb_stor_dbg(us, "xfer %u bytes\n", length); /* calculate the max packet size */ - maxp = usb_maxpacket(us->pusb_dev, pipe, usb_pipeout(pipe)); + maxp = usb_maxpacket(us->pusb_dev, pipe); if (maxp > length) maxp = length; -- cgit v1.2.3 From 87d0e2f41b8cc2018499be4e8003fa8c09b6f2fb Mon Sep 17 00:00:00 2001 From: Linyu Yuan Date: Mon, 25 Apr 2022 18:18:05 +0800 Subject: usb: typec: ucsi: add a common function ucsi_unregister_connectors() In error path of ucsi_init(), it will unregister all valid ucsi connectors, and similar operation also happen in ucsi_unregister(), add a common function ucsi_unregister_connectors() for two places, inside this function, if con->wq is NULL, it will break the loop, if other kind of error happen after con->wq allocated, ucsi/typec related API is safe to unregister. Also in ucsi_init(), it allocate number of (ucsi->cap.num_connectors + 1) connectors, there is one extra as the ending, ucsi_unregister_connectors() is safe to unregister all ucsi connectors according ucsi->cap.num_connectors, remove the extra one connector to save memory. Reviewed-by: Heikki Krogerus Signed-off-by: Linyu Yuan Link: https://lore.kernel.org/r/1650881886-25530-2-git-send-email-quic_linyyuan@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 53 +++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f0c2fa19f3e0..a168510675da 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1186,6 +1186,32 @@ out_unlock: return ret; } +static void ucsi_unregister_connectors(struct ucsi *ucsi) +{ + struct ucsi_connector *con; + int i; + + if (!ucsi->connector) + return; + + for (i = 0; i < ucsi->cap.num_connectors; i++) { + con = &ucsi->connector[i]; + + if (!con->wq) + break; + + cancel_work_sync(&con->work); + ucsi_unregister_partner(con); + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(con); + destroy_workqueue(con->wq); + typec_unregister_port(con->port); + } + + kfree(ucsi->connector); + ucsi->connector = NULL; +} + /** * ucsi_init - Initialize UCSI interface * @ucsi: UCSI to be initialized @@ -1194,7 +1220,6 @@ out_unlock: */ static int ucsi_init(struct ucsi *ucsi) { - struct ucsi_connector *con; u64 command; int ret; int i; @@ -1225,7 +1250,7 @@ static int ucsi_init(struct ucsi *ucsi) } /* Allocate the connectors. Released in ucsi_unregister() */ - ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1, + ucsi->connector = kcalloc(ucsi->cap.num_connectors, sizeof(*ucsi->connector), GFP_KERNEL); if (!ucsi->connector) { ret = -ENOMEM; @@ -1249,15 +1274,7 @@ static int ucsi_init(struct ucsi *ucsi) return 0; err_unregister: - for (con = ucsi->connector; con->port; con++) { - ucsi_unregister_partner(con); - ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(con); - if (con->wq) - destroy_workqueue(con->wq); - typec_unregister_port(con->port); - con->port = NULL; - } + ucsi_unregister_connectors(ucsi); err_reset: memset(&ucsi->cap, 0, sizeof(ucsi->cap)); @@ -1363,7 +1380,6 @@ EXPORT_SYMBOL_GPL(ucsi_register); void ucsi_unregister(struct ucsi *ucsi) { u64 cmd = UCSI_SET_NOTIFICATION_ENABLE; - int i; /* Make sure that we are not in the middle of driver initialization */ cancel_work_sync(&ucsi->work); @@ -1371,18 +1387,7 @@ void ucsi_unregister(struct ucsi *ucsi) /* Disable notifications */ ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd)); - for (i = 0; i < ucsi->cap.num_connectors; i++) { - cancel_work_sync(&ucsi->connector[i].work); - ucsi_unregister_partner(&ucsi->connector[i]); - ucsi_unregister_altmodes(&ucsi->connector[i], - UCSI_RECIPIENT_CON); - ucsi_unregister_port_psy(&ucsi->connector[i]); - if (ucsi->connector[i].wq) - destroy_workqueue(ucsi->connector[i].wq); - typec_unregister_port(ucsi->connector[i].port); - } - - kfree(ucsi->connector); + ucsi_unregister_connectors(ucsi); } EXPORT_SYMBOL_GPL(ucsi_unregister); -- cgit v1.2.3 From 3c162511530c234f95091bdc7225f641e5f35090 Mon Sep 17 00:00:00 2001 From: Linyu Yuan Date: Mon, 25 Apr 2022 18:18:06 +0800 Subject: usb: typec: ucsi: Wait for the USB role switches When role switch module probe late than ucsi module, fwnode_usb_role_switch_get() will return -EPROBE_DEFER, it is better to restart ucsi init work to find it again every 100ms, total wait time is 10 second. It also means change ucsi init work to delayed_work. Reviewed-by: Heikki Krogerus Signed-off-by: Linyu Yuan Link: https://lore.kernel.org/r/1650881886-25530-3-git-send-email-quic_linyyuan@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 32 ++++++++++++++++++++------------ drivers/usb/typec/ucsi/ucsi.h | 6 +++++- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index a168510675da..7574bb9a2968 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1053,6 +1053,14 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) con->num = index + 1; con->ucsi = ucsi; + cap->fwnode = ucsi_find_fwnode(con); + con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); + if (IS_ERR(con->usb_role_sw)) { + dev_err(ucsi->dev, "con%d: failed to get usb role switch\n", + con->num); + return PTR_ERR(con->usb_role_sw); + } + /* Delay other interactions with the con until registration is complete */ mutex_lock(&con->lock); @@ -1088,7 +1096,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY) *accessory = TYPEC_ACCESSORY_DEBUG; - cap->fwnode = ucsi_find_fwnode(con); cap->driver_data = con; cap->ops = &ucsi_ops; @@ -1146,13 +1153,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) ucsi_port_psy_changed(con); } - con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); - if (IS_ERR(con->usb_role_sw)) { - dev_err(ucsi->dev, "con%d: failed to get usb role switch\n", - con->num); - con->usb_role_sw = NULL; - } - /* Only notify USB controller if partner supports USB data */ if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) u_role = USB_ROLE_NONE; @@ -1285,12 +1285,20 @@ err: static void ucsi_init_work(struct work_struct *work) { - struct ucsi *ucsi = container_of(work, struct ucsi, work); + struct ucsi *ucsi = container_of(work, struct ucsi, work.work); int ret; ret = ucsi_init(ucsi); if (ret) dev_err(ucsi->dev, "PPM init failed (%d)\n", ret); + + if (ret == -EPROBE_DEFER) { + if (ucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT) + return; + + queue_delayed_work(system_long_wq, &ucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } } /** @@ -1330,7 +1338,7 @@ struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops) if (!ucsi) return ERR_PTR(-ENOMEM); - INIT_WORK(&ucsi->work, ucsi_init_work); + INIT_DELAYED_WORK(&ucsi->work, ucsi_init_work); mutex_init(&ucsi->ppm_lock); ucsi->dev = dev; ucsi->ops = ops; @@ -1365,7 +1373,7 @@ int ucsi_register(struct ucsi *ucsi) if (!ucsi->version) return -ENODEV; - queue_work(system_long_wq, &ucsi->work); + queue_delayed_work(system_long_wq, &ucsi->work, 0); return 0; } @@ -1382,7 +1390,7 @@ void ucsi_unregister(struct ucsi *ucsi) u64 cmd = UCSI_SET_NOTIFICATION_ENABLE; /* Make sure that we are not in the middle of driver initialization */ - cancel_work_sync(&ucsi->work); + cancel_delayed_work_sync(&ucsi->work); /* Disable notifications */ ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd)); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 280f1e1bda2c..8eb391e3e592 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -287,7 +287,11 @@ struct ucsi { struct ucsi_capability cap; struct ucsi_connector *connector; - struct work_struct work; + struct delayed_work work; + int work_count; +#define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10 +#define UCSI_ROLE_SWITCH_INTERVAL (HZ / UCSI_ROLE_SWITCH_RETRY_PER_HZ) +#define UCSI_ROLE_SWITCH_WAIT_COUNT (10 * UCSI_ROLE_SWITCH_RETRY_PER_HZ) /* PPM Communication lock */ struct mutex ppm_lock; -- cgit v1.2.3 From 7a20917d30fb627bebff6bacbe860ad4eb3a04d6 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:45 -0700 Subject: device property: Add helper to match multiple connections In some cases multiple connections with the same connection id needs to be resolved from a fwnode graph. One such example is when separate hardware is used for performing muxing and/or orientation switching of the SuperSpeed and SBU lines in a USB Type-C connector. In this case the connector needs to belong to a graph with multiple matching remote endpoints, and the Type-C controller needs to be able to resolve them both. Add a new API that allows this kind of lookup. Reviewed-by: Andy Shevchenko Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-2-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/property.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/property.h | 5 +++ 2 files changed, 114 insertions(+) (limited to 'drivers') diff --git a/drivers/base/property.c b/drivers/base/property.c index c0e94cce9c29..08da5ca49e9c 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -1218,6 +1218,40 @@ fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id, return NULL; } +static unsigned int fwnode_graph_devcon_matches(struct fwnode_handle *fwnode, + const char *con_id, void *data, + devcon_match_fn_t match, + void **matches, + unsigned int matches_len) +{ + struct fwnode_handle *node; + struct fwnode_handle *ep; + unsigned int count = 0; + void *ret; + + fwnode_graph_for_each_endpoint(fwnode, ep) { + if (matches && count >= matches_len) { + fwnode_handle_put(ep); + break; + } + + node = fwnode_graph_get_remote_port_parent(ep); + if (!fwnode_device_is_available(node)) { + fwnode_handle_put(node); + continue; + } + + ret = match(node, con_id, data); + fwnode_handle_put(node); + if (ret) { + if (matches) + matches[count] = ret; + count++; + } + } + return count; +} + static void * fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id, void *data, devcon_match_fn_t match) @@ -1240,6 +1274,37 @@ fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id, return NULL; } +static unsigned int fwnode_devcon_matches(struct fwnode_handle *fwnode, + const char *con_id, void *data, + devcon_match_fn_t match, + void **matches, + unsigned int matches_len) +{ + struct fwnode_handle *node; + unsigned int count = 0; + unsigned int i; + void *ret; + + for (i = 0; ; i++) { + if (matches && count >= matches_len) + break; + + node = fwnode_find_reference(fwnode, con_id, i); + if (IS_ERR(node)) + break; + + ret = match(node, NULL, data); + fwnode_handle_put(node); + if (ret) { + if (matches) + matches[count] = ret; + count++; + } + } + + return count; +} + /** * fwnode_connection_find_match - Find connection from a device node * @fwnode: Device node with the connection @@ -1267,3 +1332,47 @@ void *fwnode_connection_find_match(struct fwnode_handle *fwnode, return fwnode_devcon_match(fwnode, con_id, data, match); } EXPORT_SYMBOL_GPL(fwnode_connection_find_match); + +/** + * fwnode_connection_find_matches - Find connections from a device node + * @fwnode: Device node with the connection + * @con_id: Identifier for the connection + * @data: Data for the match function + * @match: Function to check and convert the connection description + * @matches: (Optional) array of pointers to fill with matches + * @matches_len: Length of @matches + * + * Find up to @matches_len connections with unique identifier @con_id between + * @fwnode and other device nodes. @match will be used to convert the + * connection description to data the caller is expecting to be returned + * through the @matches array. + * If @matches is NULL @matches_len is ignored and the total number of resolved + * matches is returned. + * + * Return: Number of matches resolved, or negative errno. + */ +int fwnode_connection_find_matches(struct fwnode_handle *fwnode, + const char *con_id, void *data, + devcon_match_fn_t match, + void **matches, unsigned int matches_len) +{ + unsigned int count_graph; + unsigned int count_ref; + + if (!fwnode || !match) + return -EINVAL; + + count_graph = fwnode_graph_devcon_matches(fwnode, con_id, data, match, + matches, matches_len); + + if (matches) { + matches += count_graph; + matches_len -= count_graph; + } + + count_ref = fwnode_devcon_matches(fwnode, con_id, data, match, + matches, matches_len); + + return count_graph + count_ref; +} +EXPORT_SYMBOL_GPL(fwnode_connection_find_matches); diff --git a/include/linux/property.h b/include/linux/property.h index 4cd4b326941f..de7ff336d2c8 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -447,6 +447,11 @@ static inline void *device_connection_find_match(struct device *dev, return fwnode_connection_find_match(dev_fwnode(dev), con_id, data, match); } +int fwnode_connection_find_matches(struct fwnode_handle *fwnode, + const char *con_id, void *data, + devcon_match_fn_t match, + void **matches, unsigned int matches_len); + /* -------------------------------------------------------------------------- */ /* Software fwnode support - when HW description is incomplete or missing */ -- cgit v1.2.3 From bcd6a517aacae7e708a1be019a0c3ab9f4371c2b Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:46 -0700 Subject: device property: Use multi-connection matchers for single case The newly introduced helpers for searching for matches in the case of multiple connections can be resused by the single-connection case, so do this to save some duplication. Reviewed-by: Andy Shevchenko Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-3-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/property.c | 55 +++++-------------------------------------------- 1 file changed, 5 insertions(+), 50 deletions(-) (limited to 'drivers') diff --git a/drivers/base/property.c b/drivers/base/property.c index 08da5ca49e9c..47659764c8a1 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -1193,31 +1193,6 @@ const void *device_get_match_data(struct device *dev) } EXPORT_SYMBOL_GPL(device_get_match_data); -static void * -fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id, - void *data, devcon_match_fn_t match) -{ - struct fwnode_handle *node; - struct fwnode_handle *ep; - void *ret; - - fwnode_graph_for_each_endpoint(fwnode, ep) { - node = fwnode_graph_get_remote_port_parent(ep); - if (!fwnode_device_is_available(node)) { - fwnode_handle_put(node); - continue; - } - - ret = match(node, con_id, data); - fwnode_handle_put(node); - if (ret) { - fwnode_handle_put(ep); - return ret; - } - } - return NULL; -} - static unsigned int fwnode_graph_devcon_matches(struct fwnode_handle *fwnode, const char *con_id, void *data, devcon_match_fn_t match, @@ -1252,28 +1227,6 @@ static unsigned int fwnode_graph_devcon_matches(struct fwnode_handle *fwnode, return count; } -static void * -fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id, - void *data, devcon_match_fn_t match) -{ - struct fwnode_handle *node; - void *ret; - int i; - - for (i = 0; ; i++) { - node = fwnode_find_reference(fwnode, con_id, i); - if (IS_ERR(node)) - break; - - ret = match(node, NULL, data); - fwnode_handle_put(node); - if (ret) - return ret; - } - - return NULL; -} - static unsigned int fwnode_devcon_matches(struct fwnode_handle *fwnode, const char *con_id, void *data, devcon_match_fn_t match, @@ -1320,16 +1273,18 @@ void *fwnode_connection_find_match(struct fwnode_handle *fwnode, const char *con_id, void *data, devcon_match_fn_t match) { + unsigned int count; void *ret; if (!fwnode || !match) return NULL; - ret = fwnode_graph_devcon_match(fwnode, con_id, data, match); - if (ret) + count = fwnode_graph_devcon_matches(fwnode, con_id, data, match, &ret, 1); + if (count) return ret; - return fwnode_devcon_match(fwnode, con_id, data, match); + count = fwnode_devcon_matches(fwnode, con_id, data, match, &ret, 1); + return count ? ret : NULL; } EXPORT_SYMBOL_GPL(fwnode_connection_find_match); -- cgit v1.2.3 From b9fa0292490db39d6542f514117333d366ec0011 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:47 -0700 Subject: usb: typec: mux: Check dev_set_name() return value It's possible that dev_set_name() returns -ENOMEM, catch and handle this. Fixes: 3370db35193b ("usb: typec: Registering real device entries for the muxes") Reported-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Acked-by: Heikki Krogerus Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-4-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index c8340de0ed49..d2aaf294b649 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -131,8 +131,11 @@ typec_switch_register(struct device *parent, sw->dev.class = &typec_mux_class; sw->dev.type = &typec_switch_dev_type; sw->dev.driver_data = desc->drvdata; - dev_set_name(&sw->dev, "%s-switch", - desc->name ? desc->name : dev_name(parent)); + ret = dev_set_name(&sw->dev, "%s-switch", desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&sw->dev); + return ERR_PTR(ret); + } ret = device_add(&sw->dev); if (ret) { @@ -338,8 +341,11 @@ typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) mux->dev.class = &typec_mux_class; mux->dev.type = &typec_mux_dev_type; mux->dev.driver_data = desc->drvdata; - dev_set_name(&mux->dev, "%s-mux", - desc->name ? desc->name : dev_name(parent)); + ret = dev_set_name(&mux->dev, "%s-mux", desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&mux->dev); + return ERR_PTR(ret); + } ret = device_add(&mux->dev); if (ret) { -- cgit v1.2.3 From 713fd49b430c37263c6cae2c82954f4e1cbcd90d Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:48 -0700 Subject: usb: typec: mux: Introduce indirection Rather than directly exposing the implementation's representation of the typec muxes to the controller/clients, introduce an indirection object. This enables the introduction of turning this relationship into a one-to-many in the following patch. Acked-by: Heikki Krogerus Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-5-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.c | 2 +- drivers/usb/typec/mux.c | 193 +++++++++++++++++++++------------- drivers/usb/typec/mux.h | 12 +-- drivers/usb/typec/mux/intel_pmc_mux.c | 8 +- drivers/usb/typec/mux/pi3usb30532.c | 8 +- include/linux/usb/typec_mux.h | 22 ++-- 6 files changed, 148 insertions(+), 97 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index 78e0e78954f2..26ea2fdec17d 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -24,7 +24,7 @@ typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) state.mode = conf; state.data = data; - return alt->mux->set(alt->mux, &state); + return typec_mux_set(alt->mux, &state); } static int typec_altmode_set_state(struct typec_altmode *adev, diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index d2aaf294b649..bb6c095b4af9 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -17,9 +17,13 @@ #include "class.h" #include "mux.h" +struct typec_switch { + struct typec_switch_dev *sw_dev; +}; + static int switch_fwnode_match(struct device *dev, const void *fwnode) { - if (!is_typec_switch(dev)) + if (!is_typec_switch_dev(dev)) return 0; return dev_fwnode(dev) == fwnode; @@ -49,7 +53,7 @@ static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id, dev = class_find_device(&typec_mux_class, NULL, fwnode, switch_fwnode_match); - return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER); + return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER); } /** @@ -63,12 +67,23 @@ static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id, */ struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) { + struct typec_switch_dev *sw_dev; struct typec_switch *sw; - sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL, - typec_switch_match); - if (!IS_ERR_OR_NULL(sw)) - WARN_ON(!try_module_get(sw->dev.parent->driver->owner)); + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return ERR_PTR(-ENOMEM); + + sw_dev = fwnode_connection_find_match(fwnode, "orientation-switch", NULL, + typec_switch_match); + if (IS_ERR_OR_NULL(sw_dev)) { + kfree(sw); + return ERR_CAST(sw_dev); + } + + WARN_ON(!try_module_get(sw_dev->dev.parent->driver->owner)); + + sw->sw_dev = sw_dev; return sw; } @@ -82,16 +97,22 @@ EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); */ void typec_switch_put(struct typec_switch *sw) { - if (!IS_ERR_OR_NULL(sw)) { - module_put(sw->dev.parent->driver->owner); - put_device(&sw->dev); - } + struct typec_switch_dev *sw_dev; + + if (IS_ERR_OR_NULL(sw)) + return; + + sw_dev = sw->sw_dev; + + module_put(sw_dev->dev.parent->driver->owner); + put_device(&sw_dev->dev); + kfree(sw); } EXPORT_SYMBOL_GPL(typec_switch_put); static void typec_switch_release(struct device *dev) { - kfree(to_typec_switch(dev)); + kfree(to_typec_switch_dev(dev)); } const struct device_type typec_switch_dev_type = { @@ -109,85 +130,93 @@ const struct device_type typec_switch_dev_type = { * connector to the USB controllers. USB Type-C plugs can be inserted * right-side-up or upside-down. */ -struct typec_switch * +struct typec_switch_dev * typec_switch_register(struct device *parent, const struct typec_switch_desc *desc) { - struct typec_switch *sw; + struct typec_switch_dev *sw_dev; int ret; if (!desc || !desc->set) return ERR_PTR(-EINVAL); - sw = kzalloc(sizeof(*sw), GFP_KERNEL); - if (!sw) + sw_dev = kzalloc(sizeof(*sw_dev), GFP_KERNEL); + if (!sw_dev) return ERR_PTR(-ENOMEM); - sw->set = desc->set; + sw_dev->set = desc->set; - device_initialize(&sw->dev); - sw->dev.parent = parent; - sw->dev.fwnode = desc->fwnode; - sw->dev.class = &typec_mux_class; - sw->dev.type = &typec_switch_dev_type; - sw->dev.driver_data = desc->drvdata; - ret = dev_set_name(&sw->dev, "%s-switch", desc->name ? desc->name : dev_name(parent)); + device_initialize(&sw_dev->dev); + sw_dev->dev.parent = parent; + sw_dev->dev.fwnode = desc->fwnode; + sw_dev->dev.class = &typec_mux_class; + sw_dev->dev.type = &typec_switch_dev_type; + sw_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent)); if (ret) { - put_device(&sw->dev); + put_device(&sw_dev->dev); return ERR_PTR(ret); } - ret = device_add(&sw->dev); + ret = device_add(&sw_dev->dev); if (ret) { dev_err(parent, "failed to register switch (%d)\n", ret); - put_device(&sw->dev); + put_device(&sw_dev->dev); return ERR_PTR(ret); } - return sw; + return sw_dev; } EXPORT_SYMBOL_GPL(typec_switch_register); int typec_switch_set(struct typec_switch *sw, enum typec_orientation orientation) { + struct typec_switch_dev *sw_dev; + if (IS_ERR_OR_NULL(sw)) return 0; - return sw->set(sw, orientation); + sw_dev = sw->sw_dev; + + return sw_dev->set(sw_dev, orientation); } EXPORT_SYMBOL_GPL(typec_switch_set); /** * typec_switch_unregister - Unregister USB Type-C orientation switch - * @sw: USB Type-C orientation switch + * @sw_dev: USB Type-C orientation switch * * Unregister switch that was registered with typec_switch_register(). */ -void typec_switch_unregister(struct typec_switch *sw) +void typec_switch_unregister(struct typec_switch_dev *sw_dev) { - if (!IS_ERR_OR_NULL(sw)) - device_unregister(&sw->dev); + if (!IS_ERR_OR_NULL(sw_dev)) + device_unregister(&sw_dev->dev); } EXPORT_SYMBOL_GPL(typec_switch_unregister); -void typec_switch_set_drvdata(struct typec_switch *sw, void *data) +void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data) { - dev_set_drvdata(&sw->dev, data); + dev_set_drvdata(&sw_dev->dev, data); } EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); -void *typec_switch_get_drvdata(struct typec_switch *sw) +void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev) { - return dev_get_drvdata(&sw->dev); + return dev_get_drvdata(&sw_dev->dev); } EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); /* ------------------------------------------------------------------------- */ +struct typec_mux { + struct typec_mux_dev *mux_dev; +}; + static int mux_fwnode_match(struct device *dev, const void *fwnode) { - if (!is_typec_mux(dev)) + if (!is_typec_mux_dev(dev)) return 0; return dev_fwnode(dev) == fwnode; @@ -249,7 +278,7 @@ find_mux: dev = class_find_device(&typec_mux_class, NULL, fwnode, mux_fwnode_match); - return dev ? to_typec_mux(dev) : ERR_PTR(-EPROBE_DEFER); + return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER); } /** @@ -265,12 +294,23 @@ find_mux: struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, const struct typec_altmode_desc *desc) { + struct typec_mux_dev *mux_dev; struct typec_mux *mux; - mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc, - typec_mux_match); - if (!IS_ERR_OR_NULL(mux)) - WARN_ON(!try_module_get(mux->dev.parent->driver->owner)); + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux_dev = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc, + typec_mux_match); + if (IS_ERR_OR_NULL(mux_dev)) { + kfree(mux); + return ERR_CAST(mux_dev); + } + + WARN_ON(!try_module_get(mux_dev->dev.parent->driver->owner)); + + mux->mux_dev = mux_dev; return mux; } @@ -284,25 +324,34 @@ EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); */ void typec_mux_put(struct typec_mux *mux) { - if (!IS_ERR_OR_NULL(mux)) { - module_put(mux->dev.parent->driver->owner); - put_device(&mux->dev); - } + struct typec_mux_dev *mux_dev; + + if (IS_ERR_OR_NULL(mux)) + return; + + mux_dev = mux->mux_dev; + module_put(mux_dev->dev.parent->driver->owner); + put_device(&mux_dev->dev); + kfree(mux); } EXPORT_SYMBOL_GPL(typec_mux_put); int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) { + struct typec_mux_dev *mux_dev; + if (IS_ERR_OR_NULL(mux)) return 0; - return mux->set(mux, state); + mux_dev = mux->mux_dev; + + return mux_dev->set(mux_dev, state); } EXPORT_SYMBOL_GPL(typec_mux_set); static void typec_mux_release(struct device *dev) { - kfree(to_typec_mux(dev)); + kfree(to_typec_mux_dev(dev)); } const struct device_type typec_mux_dev_type = { @@ -320,66 +369,66 @@ const struct device_type typec_mux_dev_type = { * the pins on the connector need to be reconfigured. This function registers * multiplexer switches routing the pins on the connector. */ -struct typec_mux * +struct typec_mux_dev * typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) { - struct typec_mux *mux; + struct typec_mux_dev *mux_dev; int ret; if (!desc || !desc->set) return ERR_PTR(-EINVAL); - mux = kzalloc(sizeof(*mux), GFP_KERNEL); - if (!mux) + mux_dev = kzalloc(sizeof(*mux_dev), GFP_KERNEL); + if (!mux_dev) return ERR_PTR(-ENOMEM); - mux->set = desc->set; + mux_dev->set = desc->set; - device_initialize(&mux->dev); - mux->dev.parent = parent; - mux->dev.fwnode = desc->fwnode; - mux->dev.class = &typec_mux_class; - mux->dev.type = &typec_mux_dev_type; - mux->dev.driver_data = desc->drvdata; - ret = dev_set_name(&mux->dev, "%s-mux", desc->name ? desc->name : dev_name(parent)); + device_initialize(&mux_dev->dev); + mux_dev->dev.parent = parent; + mux_dev->dev.fwnode = desc->fwnode; + mux_dev->dev.class = &typec_mux_class; + mux_dev->dev.type = &typec_mux_dev_type; + mux_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent)); if (ret) { - put_device(&mux->dev); + put_device(&mux_dev->dev); return ERR_PTR(ret); } - ret = device_add(&mux->dev); + ret = device_add(&mux_dev->dev); if (ret) { dev_err(parent, "failed to register mux (%d)\n", ret); - put_device(&mux->dev); + put_device(&mux_dev->dev); return ERR_PTR(ret); } - return mux; + return mux_dev; } EXPORT_SYMBOL_GPL(typec_mux_register); /** * typec_mux_unregister - Unregister Multiplexer Switch - * @mux: USB Type-C Connector Multiplexer/DeMultiplexer + * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer * * Unregister mux that was registered with typec_mux_register(). */ -void typec_mux_unregister(struct typec_mux *mux) +void typec_mux_unregister(struct typec_mux_dev *mux_dev) { - if (!IS_ERR_OR_NULL(mux)) - device_unregister(&mux->dev); + if (!IS_ERR_OR_NULL(mux_dev)) + device_unregister(&mux_dev->dev); } EXPORT_SYMBOL_GPL(typec_mux_unregister); -void typec_mux_set_drvdata(struct typec_mux *mux, void *data) +void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data) { - dev_set_drvdata(&mux->dev, data); + dev_set_drvdata(&mux_dev->dev, data); } EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); -void *typec_mux_get_drvdata(struct typec_mux *mux) +void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) { - return dev_get_drvdata(&mux->dev); + return dev_get_drvdata(&mux_dev->dev); } EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h index b1d6e837cb74..58f0f28b6dc8 100644 --- a/drivers/usb/typec/mux.h +++ b/drivers/usb/typec/mux.h @@ -5,23 +5,23 @@ #include -struct typec_switch { +struct typec_switch_dev { struct device dev; typec_switch_set_fn_t set; }; -struct typec_mux { +struct typec_mux_dev { struct device dev; typec_mux_set_fn_t set; }; -#define to_typec_switch(_dev_) container_of(_dev_, struct typec_switch, dev) -#define to_typec_mux(_dev_) container_of(_dev_, struct typec_mux, dev) +#define to_typec_switch_dev(_dev_) container_of(_dev_, struct typec_switch_dev, dev) +#define to_typec_mux_dev(_dev_) container_of(_dev_, struct typec_mux_dev, dev) extern const struct device_type typec_switch_dev_type; extern const struct device_type typec_mux_dev_type; -#define is_typec_switch(dev) ((dev)->type == &typec_switch_dev_type) -#define is_typec_mux(dev) ((dev)->type == &typec_mux_dev_type) +#define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type) +#define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type) #endif /* __USB_TYPEC_MUX__ */ diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index da6b381ddf00..47b733f78fb0 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -121,8 +121,8 @@ struct pmc_usb_port { int num; u32 iom_status; struct pmc_usb *pmc; - struct typec_mux *typec_mux; - struct typec_switch *typec_sw; + struct typec_mux_dev *typec_mux; + struct typec_switch_dev *typec_sw; struct usb_role_switch *usb_sw; enum typec_orientation orientation; @@ -433,7 +433,7 @@ static int pmc_usb_connect(struct pmc_usb_port *port, enum usb_role role) } static int -pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +pmc_usb_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct pmc_usb_port *port = typec_mux_get_drvdata(mux); @@ -469,7 +469,7 @@ pmc_usb_mux_set(struct typec_mux *mux, struct typec_mux_state *state) return -EOPNOTSUPP; } -static int pmc_usb_set_orientation(struct typec_switch *sw, +static int pmc_usb_set_orientation(struct typec_switch_dev *sw, enum typec_orientation orientation) { struct pmc_usb_port *port = typec_switch_get_drvdata(sw); diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c index 7afe275b17d0..6ce9f282594e 100644 --- a/drivers/usb/typec/mux/pi3usb30532.c +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -23,8 +23,8 @@ struct pi3usb30532 { struct i2c_client *client; struct mutex lock; /* protects the cached conf register */ - struct typec_switch *sw; - struct typec_mux *mux; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; u8 conf; }; @@ -45,7 +45,7 @@ static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf) return 0; } -static int pi3usb30532_sw_set(struct typec_switch *sw, +static int pi3usb30532_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) { struct pi3usb30532 *pi = typec_switch_get_drvdata(sw); @@ -74,7 +74,7 @@ static int pi3usb30532_sw_set(struct typec_switch *sw, } static int -pi3usb30532_mux_set(struct typec_mux *mux, struct typec_mux_state *state) +pi3usb30532_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct pi3usb30532 *pi = typec_mux_get_drvdata(mux); u8 new_conf; diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index a9d9957933dc..ee57781dcf28 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -8,11 +8,13 @@ struct device; struct typec_mux; +struct typec_mux_dev; struct typec_switch; +struct typec_switch_dev; struct typec_altmode; struct fwnode_handle; -typedef int (*typec_switch_set_fn_t)(struct typec_switch *sw, +typedef int (*typec_switch_set_fn_t)(struct typec_switch_dev *sw, enum typec_orientation orientation); struct typec_switch_desc { @@ -32,13 +34,13 @@ static inline struct typec_switch *typec_switch_get(struct device *dev) return fwnode_typec_switch_get(dev_fwnode(dev)); } -struct typec_switch * +struct typec_switch_dev * typec_switch_register(struct device *parent, const struct typec_switch_desc *desc); -void typec_switch_unregister(struct typec_switch *sw); +void typec_switch_unregister(struct typec_switch_dev *sw); -void typec_switch_set_drvdata(struct typec_switch *sw, void *data); -void *typec_switch_get_drvdata(struct typec_switch *sw); +void typec_switch_set_drvdata(struct typec_switch_dev *sw, void *data); +void *typec_switch_get_drvdata(struct typec_switch_dev *sw); struct typec_mux_state { struct typec_altmode *alt; @@ -46,7 +48,7 @@ struct typec_mux_state { void *data; }; -typedef int (*typec_mux_set_fn_t)(struct typec_mux *mux, +typedef int (*typec_mux_set_fn_t)(struct typec_mux_dev *mux, struct typec_mux_state *state); struct typec_mux_desc { @@ -67,11 +69,11 @@ typec_mux_get(struct device *dev, const struct typec_altmode_desc *desc) return fwnode_typec_mux_get(dev_fwnode(dev), desc); } -struct typec_mux * +struct typec_mux_dev * typec_mux_register(struct device *parent, const struct typec_mux_desc *desc); -void typec_mux_unregister(struct typec_mux *mux); +void typec_mux_unregister(struct typec_mux_dev *mux); -void typec_mux_set_drvdata(struct typec_mux *mux, void *data); -void *typec_mux_get_drvdata(struct typec_mux *mux); +void typec_mux_set_drvdata(struct typec_mux_dev *mux, void *data); +void *typec_mux_get_drvdata(struct typec_mux_dev *mux); #endif /* __USB_TYPEC_MUX */ -- cgit v1.2.3 From 71793b579ba6825d221445e3cfcd50da427b4f0e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:49 -0700 Subject: usb: typec: mux: Allow multiple mux_devs per mux In the Qualcomm platforms the USB/DP PHY handles muxing and orientation switching of the SuperSpeed lines, but the SBU lines needs to be connected and switched by external (to the SoC) hardware. It's therefor necessary to be able to have the TypeC controller operate multiple TypeC muxes and switches. Use the newly introduced indirection object to handle this, to avoid having to taint the TypeC controllers with knowledge about the downstream hardware configuration. The max number of devs per indirection is set to 3, which account for being able to mux/switch the USB HS, SS and SBU lines, as per defined defined in the usb-c-connector binding. This number could be grown if need arrises at a later point in time. Acked-by: Heikki Krogerus Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-6-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 128 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index bb6c095b4af9..fd55c2c516a5 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -17,8 +17,11 @@ #include "class.h" #include "mux.h" +#define TYPEC_MUX_MAX_DEVS 3 + struct typec_switch { - struct typec_switch_dev *sw_dev; + struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; + unsigned int num_sw_devs; }; static int switch_fwnode_match(struct device *dev, const void *fwnode) @@ -67,25 +70,50 @@ static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id, */ struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) { - struct typec_switch_dev *sw_dev; + struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; struct typec_switch *sw; + int count; + int err; + int i; sw = kzalloc(sizeof(*sw), GFP_KERNEL); if (!sw) return ERR_PTR(-ENOMEM); - sw_dev = fwnode_connection_find_match(fwnode, "orientation-switch", NULL, - typec_switch_match); - if (IS_ERR_OR_NULL(sw_dev)) { + count = fwnode_connection_find_matches(fwnode, "orientation-switch", NULL, + typec_switch_match, + (void **)sw_devs, + ARRAY_SIZE(sw_devs)); + if (count <= 0) { kfree(sw); - return ERR_CAST(sw_dev); + return NULL; } - WARN_ON(!try_module_get(sw_dev->dev.parent->driver->owner)); + for (i = 0; i < count; i++) { + if (IS_ERR(sw_devs[i])) { + err = PTR_ERR(sw_devs[i]); + goto put_sw_devs; + } + } - sw->sw_dev = sw_dev; + for (i = 0; i < count; i++) { + WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner)); + sw->sw_devs[i] = sw_devs[i]; + } + + sw->num_sw_devs = count; return sw; + +put_sw_devs: + for (i = 0; i < count; i++) { + if (!IS_ERR(sw_devs[i])) + put_device(&sw_devs[i]->dev); + } + + kfree(sw); + + return ERR_PTR(err); } EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); @@ -98,14 +126,17 @@ EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); void typec_switch_put(struct typec_switch *sw) { struct typec_switch_dev *sw_dev; + unsigned int i; if (IS_ERR_OR_NULL(sw)) return; - sw_dev = sw->sw_dev; + for (i = 0; i < sw->num_sw_devs; i++) { + sw_dev = sw->sw_devs[i]; - module_put(sw_dev->dev.parent->driver->owner); - put_device(&sw_dev->dev); + module_put(sw_dev->dev.parent->driver->owner); + put_device(&sw_dev->dev); + } kfree(sw); } EXPORT_SYMBOL_GPL(typec_switch_put); @@ -173,13 +204,21 @@ int typec_switch_set(struct typec_switch *sw, enum typec_orientation orientation) { struct typec_switch_dev *sw_dev; + unsigned int i; + int ret; if (IS_ERR_OR_NULL(sw)) return 0; - sw_dev = sw->sw_dev; + for (i = 0; i < sw->num_sw_devs; i++) { + sw_dev = sw->sw_devs[i]; + + ret = sw_dev->set(sw_dev, orientation); + if (ret) + return ret; + } - return sw_dev->set(sw_dev, orientation); + return 0; } EXPORT_SYMBOL_GPL(typec_switch_set); @@ -211,7 +250,8 @@ EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); /* ------------------------------------------------------------------------- */ struct typec_mux { - struct typec_mux_dev *mux_dev; + struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; + unsigned int num_mux_devs; }; static int mux_fwnode_match(struct device *dev, const void *fwnode) @@ -294,25 +334,50 @@ find_mux: struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, const struct typec_altmode_desc *desc) { - struct typec_mux_dev *mux_dev; + struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; struct typec_mux *mux; + int count; + int err; + int i; mux = kzalloc(sizeof(*mux), GFP_KERNEL); if (!mux) return ERR_PTR(-ENOMEM); - mux_dev = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc, - typec_mux_match); - if (IS_ERR_OR_NULL(mux_dev)) { + count = fwnode_connection_find_matches(fwnode, "mode-switch", + (void *)desc, typec_mux_match, + (void **)mux_devs, + ARRAY_SIZE(mux_devs)); + if (count <= 0) { kfree(mux); - return ERR_CAST(mux_dev); + return NULL; } - WARN_ON(!try_module_get(mux_dev->dev.parent->driver->owner)); + for (i = 0; i < count; i++) { + if (IS_ERR(mux_devs[i])) { + err = PTR_ERR(mux_devs[i]); + goto put_mux_devs; + } + } + + for (i = 0; i < count; i++) { + WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner)); + mux->mux_devs[i] = mux_devs[i]; + } - mux->mux_dev = mux_dev; + mux->num_mux_devs = count; return mux; + +put_mux_devs: + for (i = 0; i < count; i++) { + if (!IS_ERR(mux_devs[i])) + put_device(&mux_devs[i]->dev); + } + + kfree(mux); + + return ERR_PTR(err); } EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); @@ -325,13 +390,16 @@ EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); void typec_mux_put(struct typec_mux *mux) { struct typec_mux_dev *mux_dev; + unsigned int i; if (IS_ERR_OR_NULL(mux)) return; - mux_dev = mux->mux_dev; - module_put(mux_dev->dev.parent->driver->owner); - put_device(&mux_dev->dev); + for (i = 0; i < mux->num_mux_devs; i++) { + mux_dev = mux->mux_devs[i]; + module_put(mux_dev->dev.parent->driver->owner); + put_device(&mux_dev->dev); + } kfree(mux); } EXPORT_SYMBOL_GPL(typec_mux_put); @@ -339,13 +407,21 @@ EXPORT_SYMBOL_GPL(typec_mux_put); int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) { struct typec_mux_dev *mux_dev; + unsigned int i; + int ret; if (IS_ERR_OR_NULL(mux)) return 0; - mux_dev = mux->mux_dev; + for (i = 0; i < mux->num_mux_devs; i++) { + mux_dev = mux->mux_devs[i]; + + ret = mux_dev->set(mux_dev, state); + if (ret) + return ret; + } - return mux_dev->set(mux_dev, state); + return 0; } EXPORT_SYMBOL_GPL(typec_mux_set); -- cgit v1.2.3 From 1dc246320c6b3e2a5492168159e1822c70251efa Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 22 Apr 2022 15:23:51 -0700 Subject: usb: typec: mux: Add On Semi fsa4480 driver The ON Semiconductor FSA4480 is a USB Type-C port multimedia switch with support for analog audio headsets. It allows sharing a common USB Type-C port to pass USB2.0 signal, analog audio, sideband use wires and analog microphone signal. Due to lacking upstream audio support for testing, the audio muxing is left untouched, but implementation of muxing the SBU lines is provided as a pair of Type-C mux and switch devices. This provides the necessary support for enabling the DisplayPort altmode on devices with this circuit. Reviewed-by: Heikki Krogerus Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20220422222351.1297276-8-bjorn.andersson@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/Kconfig | 10 ++ drivers/usb/typec/mux/Makefile | 1 + drivers/usb/typec/mux/fsa4480.c | 218 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 drivers/usb/typec/mux/fsa4480.c (limited to 'drivers') diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index edead555835e..5eb2c17d72c1 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -2,6 +2,16 @@ menu "USB Type-C Multiplexer/DeMultiplexer Switch support" +config TYPEC_MUX_FSA4480 + tristate "ON Semi FSA4480 Analog Audio Switch driver" + depends on I2C + select REGMAP_I2C + help + Driver for the ON Semiconductor FSA4480 Analog Audio Switch, which + provides support for muxing analog audio and sideband signals on a + common USB Type-C connector. + If compiled as a module, the module will be named fsa4480. + config TYPEC_MUX_PI3USB30532 tristate "Pericom PI3USB30532 Type-C cross switch driver" depends on I2C diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 280a6f553115..e52a56c16bfb 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c new file mode 100644 index 000000000000..6184f5367190 --- /dev/null +++ b/drivers/usb/typec/mux/fsa4480.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2022 Linaro Ltd. + * Copyright (C) 2018-2020 The Linux Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FSA4480_SWITCH_ENABLE 0x04 +#define FSA4480_SWITCH_SELECT 0x05 +#define FSA4480_SWITCH_STATUS1 0x07 +#define FSA4480_SLOW_L 0x08 +#define FSA4480_SLOW_R 0x09 +#define FSA4480_SLOW_MIC 0x0a +#define FSA4480_SLOW_SENSE 0x0b +#define FSA4480_SLOW_GND 0x0c +#define FSA4480_DELAY_L_R 0x0d +#define FSA4480_DELAY_L_MIC 0x0e +#define FSA4480_DELAY_L_SENSE 0x0f +#define FSA4480_DELAY_L_AGND 0x10 +#define FSA4480_RESET 0x1e +#define FSA4480_MAX_REGISTER 0x1f + +#define FSA4480_ENABLE_DEVICE BIT(7) +#define FSA4480_ENABLE_SBU GENMASK(6, 5) +#define FSA4480_ENABLE_USB GENMASK(4, 3) + +#define FSA4480_SEL_SBU_REVERSE GENMASK(6, 5) +#define FSA4480_SEL_USB GENMASK(4, 3) + +struct fsa4480 { + struct i2c_client *client; + + /* used to serialize concurrent change requests */ + struct mutex lock; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct regmap *regmap; + + u8 cur_enable; + u8 cur_select; +}; + +static const struct regmap_config fsa4480_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = FSA4480_MAX_REGISTER, + /* Accesses only done under fsa4480->lock */ + .disable_locking = true, +}; + +static int fsa4480_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct fsa4480 *fsa = typec_switch_get_drvdata(sw); + u8 new_sel; + + mutex_lock(&fsa->lock); + new_sel = FSA4480_SEL_USB; + if (orientation == TYPEC_ORIENTATION_REVERSE) + new_sel |= FSA4480_SEL_SBU_REVERSE; + + if (new_sel == fsa->cur_select) + goto out_unlock; + + if (fsa->cur_enable & FSA4480_ENABLE_SBU) { + /* Disable SBU output while re-configuring the switch */ + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, + fsa->cur_enable & ~FSA4480_ENABLE_SBU); + + /* 35us to allow the SBU switch to turn off */ + usleep_range(35, 1000); + } + + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, new_sel); + fsa->cur_select = new_sel; + + if (fsa->cur_enable & FSA4480_ENABLE_SBU) { + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable); + + /* 15us to allow the SBU switch to turn on again */ + usleep_range(15, 1000); + } + +out_unlock: + mutex_unlock(&fsa->lock); + + return 0; +} + +static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct fsa4480 *fsa = typec_mux_get_drvdata(mux); + u8 new_enable; + + mutex_lock(&fsa->lock); + + new_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB; + if (state->mode >= TYPEC_DP_STATE_A) + new_enable |= FSA4480_ENABLE_SBU; + + if (new_enable == fsa->cur_enable) + goto out_unlock; + + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, new_enable); + fsa->cur_enable = new_enable; + + if (new_enable & FSA4480_ENABLE_SBU) { + /* 15us to allow the SBU switch to turn off */ + usleep_range(15, 1000); + } + +out_unlock: + mutex_unlock(&fsa->lock); + + return 0; +} + +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; + + fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL); + if (!fsa) + return -ENOMEM; + + fsa->client = client; + mutex_init(&fsa->lock); + + 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"); + + fsa->cur_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB; + fsa->cur_select = FSA4480_SEL_USB; + + /* set default settings */ + regmap_write(fsa->regmap, FSA4480_SLOW_L, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_R, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_MIC, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_SENSE, 0x00); + regmap_write(fsa->regmap, FSA4480_SLOW_GND, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_R, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_MIC, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_SENSE, 0x00); + regmap_write(fsa->regmap, FSA4480_DELAY_L_AGND, 0x09); + regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, fsa->cur_select); + regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable); + + sw_desc.drvdata = fsa; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = fsa4480_switch_set; + + fsa->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(fsa->sw)) + return dev_err_probe(dev, PTR_ERR(fsa->sw), "failed to register typec switch\n"); + + mux_desc.drvdata = fsa; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = fsa4480_mux_set; + + fsa->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(fsa->mux)) { + typec_switch_unregister(fsa->sw); + return dev_err_probe(dev, PTR_ERR(fsa->mux), "failed to register typec mux\n"); + } + + i2c_set_clientdata(client, fsa); + return 0; +} + +static int fsa4480_remove(struct i2c_client *client) +{ + struct fsa4480 *fsa = i2c_get_clientdata(client); + + typec_mux_unregister(fsa->mux); + typec_switch_unregister(fsa->sw); + + return 0; +} + +static const struct i2c_device_id fsa4480_table[] = { + { "fsa4480" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, fsa4480_table); + +static const struct of_device_id fsa4480_of_table[] = { + { .compatible = "fcs,fsa4480" }, + { } +}; +MODULE_DEVICE_TABLE(of, fsa4480_of_table); + +static struct i2c_driver fsa4480_driver = { + .driver = { + .name = "fsa4480", + .of_match_table = fsa4480_of_table, + }, + .probe_new = fsa4480_probe, + .remove = fsa4480_remove, + .id_table = fsa4480_table, +}; +module_i2c_driver(fsa4480_driver); + +MODULE_DESCRIPTION("ON Semiconductor FSA4480 driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 08908fa1547d0550a058d0ce2d613e570f51e5f9 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Fri, 22 Apr 2022 22:23:55 +0300 Subject: usb: core: devices: drop redundant buffer overflow checks The USB device dump code often checks for the buffer overflow just before calling the functions that do that first thing anyways. Such checks are redundant and may be dropped... Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/0453cb0d-7b2b-25e6-50e3-091610951e58@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devices.c | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index cc12cdd5559f..2c14a9636056 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -228,8 +228,6 @@ static char *usb_dump_interface(int speed, char *start, char *end, start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); for (i = 0; i < desc->desc.bNumEndpoints; i++) { - if (start > end) - return start; start = usb_dump_endpoint_descriptor(speed, start, end, &desc->endpoint[i].desc); } @@ -302,8 +300,6 @@ static char *usb_dump_config(int speed, char *start, char *end, intfc = config->intf_cache[i]; interface = config->interface[i]; for (j = 0; j < intfc->num_altsetting; j++) { - if (start > end) - return start; start = usb_dump_interface(speed, start, end, intfc, interface, j); } @@ -369,19 +365,11 @@ static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) { int i; - if (start > end) - return start; - start = usb_dump_device_descriptor(start, end, &dev->descriptor); - if (start > end) - return start; - start = usb_dump_device_strings(start, end, dev); for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { - if (start > end) - return start; start = usb_dump_config(dev->speed, start, end, dev->config + i, /* active ? */ -- cgit v1.2.3 From af1969a2d734d6272c0640b50c3ed31e59e203a9 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sat, 23 Apr 2022 20:42:03 -0400 Subject: USB: gadget: Rename usb_gadget_probe_driver() In preparation for adding a "gadget" bus, this patch renames usb_gadget_probe_driver() to usb_gadget_register_driver(). The new name will be more accurate, since gadget drivers will be registered on the gadget bus and the probing will be done by the driver core, not the UDC core. Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/YmSc29YZvxgT5fEJ@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 2 +- drivers/usb/gadget/configfs.c | 2 +- drivers/usb/gadget/legacy/dbgp.c | 2 +- drivers/usb/gadget/legacy/inode.c | 2 +- drivers/usb/gadget/legacy/raw_gadget.c | 4 ++-- drivers/usb/gadget/udc/core.c | 4 ++-- include/linux/usb/gadget.h | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 2eaeaae96759..403563c06477 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -2505,7 +2505,7 @@ int usb_composite_probe(struct usb_composite_driver *driver) gadget_driver->driver.name = driver->name; gadget_driver->max_speed = driver->max_speed; - return usb_gadget_probe_driver(gadget_driver); + return usb_gadget_register_driver(gadget_driver); } EXPORT_SYMBOL_GPL(usb_composite_probe); diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 1fb837d9271e..4141206bb0ed 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -284,7 +284,7 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item, goto err; } gi->composite.gadget_driver.udc_name = name; - ret = usb_gadget_probe_driver(&gi->composite.gadget_driver); + ret = usb_gadget_register_driver(&gi->composite.gadget_driver); if (ret) { gi->composite.gadget_driver.udc_name = NULL; goto err; diff --git a/drivers/usb/gadget/legacy/dbgp.c b/drivers/usb/gadget/legacy/dbgp.c index 6bcbad382580..b62e45235e8e 100644 --- a/drivers/usb/gadget/legacy/dbgp.c +++ b/drivers/usb/gadget/legacy/dbgp.c @@ -422,7 +422,7 @@ static struct usb_gadget_driver dbgp_driver = { static int __init dbgp_init(void) { - return usb_gadget_probe_driver(&dbgp_driver); + return usb_gadget_register_driver(&dbgp_driver); } static void __exit dbgp_exit(void) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index 0c01e749f9ea..79990597c39f 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1873,7 +1873,7 @@ dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) else gadgetfs_driver.max_speed = USB_SPEED_FULL; - value = usb_gadget_probe_driver(&gadgetfs_driver); + value = usb_gadget_register_driver(&gadgetfs_driver); if (value != 0) { spin_lock_irq(&dev->lock); goto fail; diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c index 8d40a1f2ec57..b3be8db1ff63 100644 --- a/drivers/usb/gadget/legacy/raw_gadget.c +++ b/drivers/usb/gadget/legacy/raw_gadget.c @@ -510,12 +510,12 @@ static int raw_ioctl_run(struct raw_dev *dev, unsigned long value) } spin_unlock_irqrestore(&dev->lock, flags); - ret = usb_gadget_probe_driver(&dev->driver); + ret = usb_gadget_register_driver(&dev->driver); spin_lock_irqsave(&dev->lock, flags); if (ret) { dev_err(dev->dev, - "fail, usb_gadget_probe_driver returned %d\n", ret); + "fail, usb_gadget_register_driver returned %d\n", ret); dev->state = STATE_DEV_FAILED; goto out_unlock; } diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 85b194011a16..f0cce482b74a 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1523,7 +1523,7 @@ err1: return ret; } -int usb_gadget_probe_driver(struct usb_gadget_driver *driver) +int usb_gadget_register_driver(struct usb_gadget_driver *driver) { struct usb_udc *udc = NULL, *iter; int ret = -ENODEV; @@ -1572,7 +1572,7 @@ found: mutex_unlock(&udc_lock); return ret; } -EXPORT_SYMBOL_GPL(usb_gadget_probe_driver); +EXPORT_SYMBOL_GPL(usb_gadget_register_driver); int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) { diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 10fe57cf40be..5830b8a903da 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -745,7 +745,7 @@ struct usb_gadget_driver { */ /** - * usb_gadget_probe_driver - probe a gadget driver + * usb_gadget_register_driver - register a gadget driver * @driver: the driver being registered * Context: can sleep * @@ -755,7 +755,7 @@ struct usb_gadget_driver { * registration call returns. It's expected that the @bind() function will * be in init sections. */ -int usb_gadget_probe_driver(struct usb_gadget_driver *driver); +int usb_gadget_register_driver(struct usb_gadget_driver *driver); /** * usb_gadget_unregister_driver - unregister a gadget driver -- cgit v1.2.3 From 6ebb449f9f25e0da804d1247b4ffcc361321494d Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sat, 23 Apr 2022 21:33:29 -0400 Subject: USB: gadget: Register udc before gadget In preparation for adding a "gadget" bus, this patch reverses the order of registration of udc and gadget devices in usb_add_gadget(). The current code adds the gadget device first, probably because that was more convenient at the time and the order didn't really matter. But with the upcoming change, adding the gadget will cause driver probing to occur. Unwinding that on the error pathway will become much more obtrusive, not to mention the fact that a gadget driver might not work properly before the udc is registered. It's better to register the udc device first, particularly since that doesn't involve a bus or driver binding and therefore is simpler to unwind. For symmetry, the order of unregistration in usb_del_gadget() is likewise reversed. Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/YmSo6fU1FlNq8cOZ@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index f0cce482b74a..014daa07eb8c 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1308,10 +1308,6 @@ int usb_add_gadget(struct usb_gadget *gadget) if (ret) goto err_put_udc; - ret = device_add(&gadget->dev); - if (ret) - goto err_put_udc; - udc->gadget = gadget; gadget->udc = udc; @@ -1327,15 +1323,22 @@ int usb_add_gadget(struct usb_gadget *gadget) usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); udc->vbus = true; + ret = device_add(&gadget->dev); + if (ret) + goto err_del_udc; + /* pick up one of pending gadget drivers */ ret = check_pending_gadget_drivers(udc); if (ret) - goto err_del_udc; + goto err_del_gadget; mutex_unlock(&udc_lock); return 0; + err_del_gadget: + device_del(&gadget->dev); + err_del_udc: flush_work(&gadget->work); device_del(&udc->dev); @@ -1344,8 +1347,6 @@ int usb_add_gadget(struct usb_gadget *gadget) list_del(&udc->list); mutex_unlock(&udc_lock); - device_del(&gadget->dev); - err_put_udc: put_device(&udc->dev); @@ -1469,8 +1470,8 @@ void usb_del_gadget(struct usb_gadget *gadget) kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); - device_unregister(&udc->dev); device_del(&gadget->dev); + device_unregister(&udc->dev); } EXPORT_SYMBOL_GPL(usb_del_gadget); -- cgit v1.2.3 From d59f6d958596b54b722605657c3b56a79843695a Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sat, 23 Apr 2022 21:34:34 -0400 Subject: USB: gadget: Fix mistakes in UDC core kerneldoc This patch fixes some minor mistakes in the UDC core's kerneldoc. Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/YmSpKpnWR8WWEk/p@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 014daa07eb8c..a878e7afacdd 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1262,9 +1262,6 @@ static int check_pending_gadget_drivers(struct usb_udc *udc) * device. * @gadget: the gadget to be initialized. * @release: a gadget release function. - * - * Returns zero on success, negative errno otherwise. - * Calls the gadget release function in the latter case. */ void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) @@ -1441,11 +1438,10 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) } /** - * usb_del_gadget - deletes @udc from udc_list - * @gadget: the gadget to be removed. + * usb_del_gadget - deletes a gadget and unregisters its udc + * @gadget: the gadget to be deleted. * - * This will call usb_gadget_unregister_driver() if - * the @udc is still busy. + * This will unbind @gadget, if it is bound. * It will not do a final usb_put_gadget(). */ void usb_del_gadget(struct usb_gadget *gadget) @@ -1476,8 +1472,8 @@ void usb_del_gadget(struct usb_gadget *gadget) EXPORT_SYMBOL_GPL(usb_del_gadget); /** - * usb_del_gadget_udc - deletes @udc from udc_list - * @gadget: the gadget to be removed. + * usb_del_gadget_udc - unregisters a gadget + * @gadget: the gadget to be unregistered. * * Calls usb_del_gadget() and does a final usb_put_gadget(). */ -- cgit v1.2.3 From fc274c1e997314bf47f6a62c79b5d7e554ed59c4 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sat, 23 Apr 2022 21:35:51 -0400 Subject: USB: gadget: Add a new bus for gadgets This patch adds a "gadget" bus and uses it for registering gadgets and their drivers. From now on, bindings will be managed by the driver core rather than through ad-hoc manipulations in the UDC core. As part of this change, the driver_pending_list is removed. The UDC core won't need to keep track of unbound drivers for later binding, because the driver core handles all of that for us. However, we do need one new feature: a way to prevent gadget drivers from being bound to more than one gadget at a time. The existing code does this automatically, but the driver core doesn't -- it's perfectly happy to bind a single driver to all the matching devices on the bus. The patch adds a new bitflag to the usb_gadget_driver structure for this purpose. A nice side effect of this change is a reduction in the total lines of code, since now the driver core will do part of the work that the UDC used to do. A possible future patch could add udc devices to the gadget bus, say as a separate device type. Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/YmSpdxaDNeC2BBOf@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 256 ++++++++++++++++++++---------------------- include/linux/usb/gadget.h | 26 +++-- 2 files changed, 139 insertions(+), 143 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index a878e7afacdd..61790592b2c8 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -23,6 +23,8 @@ #include "trace.h" +static struct bus_type gadget_bus_type; + /** * struct usb_udc - describes one usb device controller * @driver: the gadget driver pointer. For use by the class code @@ -47,11 +49,9 @@ struct usb_udc { static struct class *udc_class; static LIST_HEAD(udc_list); -static LIST_HEAD(gadget_driver_pending_list); -static DEFINE_MUTEX(udc_lock); -static int udc_bind_to_driver(struct usb_udc *udc, - struct usb_gadget_driver *driver); +/* Protects udc_list, udc->driver, driver->is_bound, and related calls */ +static DEFINE_MUTEX(udc_lock); /* ------------------------------------------------------------------------- */ @@ -1238,24 +1238,6 @@ static void usb_udc_nop_release(struct device *dev) dev_vdbg(dev, "%s\n", __func__); } -/* should be called with udc_lock held */ -static int check_pending_gadget_drivers(struct usb_udc *udc) -{ - struct usb_gadget_driver *driver; - int ret = 0; - - list_for_each_entry(driver, &gadget_driver_pending_list, pending) - if (!driver->udc_name || strcmp(driver->udc_name, - dev_name(&udc->dev)) == 0) { - ret = udc_bind_to_driver(udc, driver); - if (ret != -EPROBE_DEFER) - list_del_init(&driver->pending); - break; - } - - return ret; -} - /** * usb_initialize_gadget - initialize a gadget and its embedded struct device * @parent: the parent device to this udc. Usually the controller driver's @@ -1276,6 +1258,7 @@ void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, gadget->dev.release = usb_udc_nop_release; device_initialize(&gadget->dev); + gadget->dev.bus = &gadget_bus_type; } EXPORT_SYMBOL_GPL(usb_initialize_gadget); @@ -1312,6 +1295,7 @@ int usb_add_gadget(struct usb_gadget *gadget) mutex_lock(&udc_lock); list_add_tail(&udc->list, &udc_list); + mutex_unlock(&udc_lock); ret = device_add(&udc->dev); if (ret) @@ -1324,23 +1308,14 @@ int usb_add_gadget(struct usb_gadget *gadget) if (ret) goto err_del_udc; - /* pick up one of pending gadget drivers */ - ret = check_pending_gadget_drivers(udc); - if (ret) - goto err_del_gadget; - - mutex_unlock(&udc_lock); - return 0; - err_del_gadget: - device_del(&gadget->dev); - err_del_udc: flush_work(&gadget->work); device_del(&udc->dev); err_unlist_udc: + mutex_lock(&udc_lock); list_del(&udc->list); mutex_unlock(&udc_lock); @@ -1419,24 +1394,6 @@ int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) } EXPORT_SYMBOL_GPL(usb_add_gadget_udc); -static void usb_gadget_remove_driver(struct usb_udc *udc) -{ - dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n", - udc->driver->function); - - kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); - - usb_gadget_disconnect(udc->gadget); - usb_gadget_disable_async_callbacks(udc); - if (udc->gadget->irq) - synchronize_irq(udc->gadget->irq); - udc->driver->unbind(udc->gadget); - usb_gadget_udc_stop(udc); - - udc->driver = NULL; - udc->gadget->dev.driver = NULL; -} - /** * usb_del_gadget - deletes a gadget and unregisters its udc * @gadget: the gadget to be deleted. @@ -1455,13 +1412,6 @@ void usb_del_gadget(struct usb_gadget *gadget) mutex_lock(&udc_lock); list_del(&udc->list); - - if (udc->driver) { - struct usb_gadget_driver *driver = udc->driver; - - usb_gadget_remove_driver(udc); - list_add(&driver->pending, &gadget_driver_pending_list); - } mutex_unlock(&udc_lock); kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); @@ -1486,123 +1436,147 @@ EXPORT_SYMBOL_GPL(usb_del_gadget_udc); /* ------------------------------------------------------------------------- */ -static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver) +static int gadget_match_driver(struct device *dev, struct device_driver *drv) { - int ret; + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + struct usb_gadget_driver *driver = container_of(drv, + struct usb_gadget_driver, driver); + + /* If the driver specifies a udc_name, it must match the UDC's name */ + if (driver->udc_name && + strcmp(driver->udc_name, dev_name(&udc->dev)) != 0) + return 0; + + /* If the driver is already bound to a gadget, it doesn't match */ + if (driver->is_bound) + return 0; + + /* Otherwise any gadget driver matches any UDC */ + return 1; +} - dev_dbg(&udc->dev, "registering UDC driver [%s]\n", - driver->function); +static int gadget_bind_driver(struct device *dev) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + struct usb_gadget_driver *driver = container_of(dev->driver, + struct usb_gadget_driver, driver); + int ret = 0; + mutex_lock(&udc_lock); + if (driver->is_bound) { + mutex_unlock(&udc_lock); + return -ENXIO; /* Driver binds to only one gadget */ + } + driver->is_bound = true; udc->driver = driver; - udc->gadget->dev.driver = &driver->driver; + mutex_unlock(&udc_lock); + + dev_dbg(&udc->dev, "binding gadget driver [%s]\n", driver->function); usb_gadget_udc_set_speed(udc, driver->max_speed); + mutex_lock(&udc_lock); ret = driver->bind(udc->gadget, driver); if (ret) - goto err1; + goto err_bind; + ret = usb_gadget_udc_start(udc); - if (ret) { - driver->unbind(udc->gadget); - goto err1; - } + if (ret) + goto err_start; usb_gadget_enable_async_callbacks(udc); usb_udc_connect_control(udc); + mutex_unlock(&udc_lock); kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); return 0; -err1: + + err_start: + driver->unbind(udc->gadget); + + err_bind: if (ret != -EISNAM) dev_err(&udc->dev, "failed to start %s: %d\n", - udc->driver->function, ret); + driver->function, ret); + udc->driver = NULL; - udc->gadget->dev.driver = NULL; + driver->is_bound = false; + mutex_unlock(&udc_lock); + return ret; } -int usb_gadget_register_driver(struct usb_gadget_driver *driver) +static void gadget_unbind_driver(struct device *dev) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_udc *udc = gadget->udc; + struct usb_gadget_driver *driver = udc->driver; + + dev_dbg(&udc->dev, "unbinding gadget driver [%s]\n", driver->function); + + kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); + + mutex_lock(&udc_lock); + usb_gadget_disconnect(gadget); + usb_gadget_disable_async_callbacks(udc); + if (gadget->irq) + synchronize_irq(gadget->irq); + udc->driver->unbind(gadget); + usb_gadget_udc_stop(udc); + + driver->is_bound = false; + udc->driver = NULL; + mutex_unlock(&udc_lock); +} + +/* ------------------------------------------------------------------------- */ + +int usb_gadget_register_driver_owner(struct usb_gadget_driver *driver, + struct module *owner, const char *mod_name) { - struct usb_udc *udc = NULL, *iter; - int ret = -ENODEV; + int ret; if (!driver || !driver->bind || !driver->setup) return -EINVAL; + driver->driver.bus = &gadget_bus_type; + driver->driver.owner = owner; + driver->driver.mod_name = mod_name; + ret = driver_register(&driver->driver); + if (ret) { + pr_warn("%s: driver registration failed: %d\n", + driver->function, ret); + return ret; + } + mutex_lock(&udc_lock); - if (driver->udc_name) { - list_for_each_entry(iter, &udc_list, list) { - ret = strcmp(driver->udc_name, dev_name(&iter->dev)); - if (ret) - continue; - udc = iter; - break; - } - if (ret) - ret = -ENODEV; - else if (udc->driver) + if (!driver->is_bound) { + if (driver->match_existing_only) { + pr_warn("%s: couldn't find an available UDC or it's busy\n", + driver->function); ret = -EBUSY; - else - goto found; - } else { - list_for_each_entry(iter, &udc_list, list) { - /* For now we take the first one */ - if (iter->driver) - continue; - udc = iter; - goto found; + } else { + pr_info("%s: couldn't find an available UDC\n", + driver->function); } - } - - if (!driver->match_existing_only) { - list_add_tail(&driver->pending, &gadget_driver_pending_list); - pr_info("couldn't find an available UDC - added [%s] to list of pending drivers\n", - driver->function); ret = 0; } - mutex_unlock(&udc_lock); + if (ret) - pr_warn("couldn't find an available UDC or it's busy: %d\n", ret); - return ret; -found: - ret = udc_bind_to_driver(udc, driver); - mutex_unlock(&udc_lock); + driver_unregister(&driver->driver); return ret; } -EXPORT_SYMBOL_GPL(usb_gadget_register_driver); +EXPORT_SYMBOL_GPL(usb_gadget_register_driver_owner); int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) { - struct usb_udc *udc = NULL; - int ret = -ENODEV; - if (!driver || !driver->unbind) return -EINVAL; - mutex_lock(&udc_lock); - list_for_each_entry(udc, &udc_list, list) { - if (udc->driver == driver) { - usb_gadget_remove_driver(udc); - usb_gadget_set_state(udc->gadget, - USB_STATE_NOTATTACHED); - - /* Maybe there is someone waiting for this UDC? */ - check_pending_gadget_drivers(udc); - /* - * For now we ignore bind errors as probably it's - * not a valid reason to fail other's gadget unbind - */ - ret = 0; - break; - } - } - - if (ret) { - list_del(&driver->pending); - ret = 0; - } - mutex_unlock(&udc_lock); - return ret; + driver_unregister(&driver->driver); + return 0; } EXPORT_SYMBOL_GPL(usb_gadget_unregister_driver); @@ -1754,8 +1728,17 @@ static int usb_udc_uevent(struct device *dev, struct kobj_uevent_env *env) return 0; } +static struct bus_type gadget_bus_type = { + .name = "gadget", + .probe = gadget_bind_driver, + .remove = gadget_unbind_driver, + .match = gadget_match_driver, +}; + static int __init usb_udc_init(void) { + int rc; + udc_class = class_create(THIS_MODULE, "udc"); if (IS_ERR(udc_class)) { pr_err("failed to create udc class --> %ld\n", @@ -1764,12 +1747,17 @@ static int __init usb_udc_init(void) } udc_class->dev_uevent = usb_udc_uevent; - return 0; + + rc = bus_register(&gadget_bus_type); + if (rc) + class_destroy(udc_class); + return rc; } subsys_initcall(usb_udc_init); static void __exit usb_udc_exit(void) { + bus_unregister(&gadget_bus_type); class_destroy(udc_class); } module_exit(usb_udc_exit); diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 5830b8a903da..cf7af8a0a6e9 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -664,9 +664,9 @@ static inline int usb_gadget_check_config(struct usb_gadget *gadget) * @driver: Driver model state for this driver. * @udc_name: A name of UDC this driver should be bound to. If udc_name is NULL, * this driver will be bound to any available UDC. - * @pending: UDC core private data used for deferred probe of this driver. - * @match_existing_only: If udc is not found, return an error and don't add this - * gadget driver to list of pending driver + * @match_existing_only: If udc is not found, return an error and fail + * the driver registration + * @is_bound: Allow a driver to be bound to only one gadget * * Devices are disabled till a gadget driver successfully bind()s, which * means the driver will handle setup() requests needed to enumerate (and @@ -729,8 +729,8 @@ struct usb_gadget_driver { struct device_driver driver; char *udc_name; - struct list_head pending; unsigned match_existing_only:1; + bool is_bound:1; }; @@ -740,22 +740,30 @@ struct usb_gadget_driver { /* driver modules register and unregister, as usual. * these calls must be made in a context that can sleep. * - * these will usually be implemented directly by the hardware-dependent - * usb bus interface driver, which will only support a single driver. + * A gadget driver can be bound to only one gadget at a time. */ /** - * usb_gadget_register_driver - register a gadget driver + * usb_gadget_register_driver_owner - register a gadget driver * @driver: the driver being registered + * @owner: the driver module + * @mod_name: the driver module's build name * Context: can sleep * * Call this in your gadget driver's module initialization function, - * to tell the underlying usb controller driver about your driver. + * to tell the underlying UDC controller driver about your driver. * The @bind() function will be called to bind it to a gadget before this * registration call returns. It's expected that the @bind() function will * be in init sections. + * + * Use the macro defined below instead of calling this directly. */ -int usb_gadget_register_driver(struct usb_gadget_driver *driver); +int usb_gadget_register_driver_owner(struct usb_gadget_driver *driver, + struct module *owner, const char *mod_name); + +/* use a define to avoid include chaining to get THIS_MODULE & friends */ +#define usb_gadget_register_driver(driver) \ + usb_gadget_register_driver_owner(driver, THIS_MODULE, KBUILD_MODNAME) /** * usb_gadget_unregister_driver - unregister a gadget driver -- cgit v1.2.3 From 0298b4b95cb373c21e6323c905589f8dac42c5b4 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Thu, 17 Feb 2022 10:42:53 -0800 Subject: usb: misc: Add onboard_usb_hub driver The main issue this driver addresses is that a USB hub needs to be powered before it can be discovered. For discrete onboard hubs (an example for such a hub is the Realtek RTS5411) this is often solved by supplying the hub with an 'always-on' regulator, which is kind of a hack. Some onboard hubs may require further initialization steps, like changing the state of a GPIO or enabling a clock, which requires even more hacks. This driver creates a platform device representing the hub which performs the necessary initialization. Currently it only supports switching on a single regulator, support for multiple regulators or other actions can be added as needed. Different initialization sequences can be supported based on the compatible string. Besides performing the initialization the driver can be configured to power the hub off during system suspend. This can help to extend battery life on battery powered devices which have no requirements to keep the hub powered during suspend. The driver can also be configured to leave the hub powered when a wakeup capable USB device is connected when suspending, and power it off otherwise. Technically the driver consists of two drivers, the platform driver described above and a very thin USB driver that subclasses the generic driver. The purpose of this driver is to provide the platform driver with the USB devices corresponding to the hub(s) (a hub controller may provide multiple 'logical' hubs, e.g. one to support USB 2.0 and another for USB 3.x). Note: the current series only supports hubs connected directly to a root hub, support for other configurations could be added if needed. Co-developed-by: Ravi Chandra Sadineni Reviewed-by: Douglas Anderson Acked-by: Alan Stern Signed-off-by: Ravi Chandra Sadineni Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20220217104219.v21.2.I7c9a1f1d6ced41dd8310e8a03da666a32364e790@changeid Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-platform-onboard-usb-hub | 8 + MAINTAINERS | 7 + drivers/usb/misc/Kconfig | 23 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/onboard_usb_hub.c | 510 +++++++++++++++++++++ include/linux/usb/onboard_hub.h | 18 + 6 files changed, 567 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub create mode 100644 drivers/usb/misc/onboard_usb_hub.c create mode 100644 include/linux/usb/onboard_hub.h (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub new file mode 100644 index 000000000000..af818d9d90c1 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub @@ -0,0 +1,8 @@ +What: /sys/bus/platform/devices//always_powered_in_suspend +Date: February 2022 +KernelVersion: 5.18 +Contact: Matthias Kaehlcke + linux-usb@vger.kernel.org +Description: + (RW) Controls whether the USB hub remains always powered + during system suspend or not. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index fd768d43e048..38d82f592b4f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14622,6 +14622,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov9734.c +ONBOARD USB HUB DRIVER +M: Matthias Kaehlcke +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub +F: drivers/usb/misc/onboard_usb_hub.c + ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 4c5ddbd75b7e..993d07d2ffe5 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -295,3 +295,26 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. + +config USB_ONBOARD_HUB + bool "Onboard USB hub support" + depends on OF || COMPILE_TEST + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some non-trivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + This driver can be used as a module but its state (module vs + builtin) must match the state of the USB subsystem. Enabling + this config will enable the driver and it will automatically + match the state of the USB subsystem. If this driver is a + module it will be called onboard_usb_hub. + +if USB_ONBOARD_HUB +config USB_ONBOARD_HUB_ACTUAL + tristate + default m if USB=m + default y if USB=y +endif diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 35bdb4b6c3b6..ea65e202a055 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -33,3 +33,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_HUB_ACTUAL) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 000000000000..34f7e4885b2e --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2022, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct usb_device_driver onboard_hub_usbdev_driver; + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator *vdd; + struct device *dev; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_enable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to enable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + err = regulator_disable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to disable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (hub->always_powered_in_suspend) + return 0; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (!power_off) + return 0; + + return onboard_hub_power_off(hub); +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + if (hub->is_powered_on) + return 0; + + return onboard_hub_power_on(hub); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + + mutex_unlock(&hub->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); + + return 0; + +error: + mutex_unlock(&hub->lock); + + return err; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&hub->dev->kobj, link_name); + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const struct onboard_hub *hub = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_hub_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; +ATTRIBUTE_GROUPS(onboard_hub); + +static int onboard_hub_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_power_on(hub); + if (err) + return err; + + /* + * The USB driver might have been detached from the USB devices by + * onboard_hub_remove(), make sure to re-attach it if needed. + */ + err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); + if (err) { + onboard_hub_power_off(hub); + return err; + } + + return 0; +} + +static int onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + return onboard_hub_power_off(hub); +} + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usbbda,411" }, + { .compatible = "usbbda,5411" }, + { .compatible = "usbbda,414" }, + { .compatible = "usbbda,5414" }, + {} +}; +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static bool of_is_onboard_usb_hub(const struct device_node *np) +{ + return !!of_match_node(onboard_hub_match, np); +} + +static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) +}; + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + .dev_groups = onboard_hub_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_REALTEK 0x0bda + +/* + * Returns the onboard_hub platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_hub *hub; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "companion-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for companion hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + hub = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata ('hub') indicates that the platform driver + * finished probing. This handles the case where (conceivably) we could + * be running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!hub) + return ERR_PTR(-EPROBE_DEFER); + + return hub; +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + int err; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_add_usbdev(hub, udev); + if (err) + return err; + + return 0; +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +/*** Helpers for creating/destroying platform devices for onboard hubs ***/ + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +/** + * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs + * @parent_hub : parent hub to scan for connected onboard hubs + * @pdev_list : list of onboard hub platform devices owned by the parent hub + * + * Creates a platform device for each supported onboard hub that is connected to + * the given parent hub. To keep track of the platform devices they are added to + * a list that is owned by the parent hub. + */ +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + struct device_node *np, *npc; + struct platform_device *pdev = NULL; + struct pdev_list_entry *pdle; + + INIT_LIST_HEAD(pdev_list); + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_hub(np)) + goto node_put; + + npc = of_parse_phandle(np, "companion-hub", 0); + if (npc) { + pdev = of_find_device_by_node(npc); + of_node_put(npc); + } + + if (pdev) { + /* the companion hub already has a platform device, nothing to do here */ + put_device(&pdev->dev); + goto node_put; + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (!pdev) { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard hub '%pOF'\n", np); + goto node_put; + } + + pdle = devm_kzalloc(&pdev->dev, sizeof(*pdle), GFP_KERNEL); + if (!pdle) { + of_platform_device_destroy(&pdev->dev, NULL); + goto node_put; + } + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); + +/** + * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices + * @pdev_list : list of onboard hub platform devices + * + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + list_del(&pdle->node); + of_platform_device_destroy(&pdle->pdev->dev, NULL); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); + +/************************** Driver (de)registration **************************/ + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + return ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + platform_driver_unregister(&onboard_hub_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke "); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/onboard_hub.h b/include/linux/usb/onboard_hub.h new file mode 100644 index 000000000000..d9373230556e --- /dev/null +++ b/include/linux/usb/onboard_hub.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LINUX_USB_ONBOARD_HUB_H +#define __LINUX_USB_ONBOARD_HUB_H + +struct usb_device; +struct list_head; + +#if IS_ENABLED(CONFIG_USB_ONBOARD_HUB) +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list); +void onboard_hub_destroy_pdevs(struct list_head *pdev_list); +#else +static inline void onboard_hub_create_pdevs(struct usb_device *parent_hub, + struct list_head *pdev_list) {} +static inline void onboard_hub_destroy_pdevs(struct list_head *pdev_list) {} +#endif + +#endif /* __LINUX_USB_ONBOARD_HUB_H */ -- cgit v1.2.3 From c40b62216c1aecc0dc00faf33d71bd71cb440337 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Thu, 17 Feb 2022 10:42:54 -0800 Subject: usb: core: hcd: Create platform devices for onboard hubs in probe() Call onboard_hub_create/destroy_pdevs() from usb_add/remove_hcd() for primary HCDs to create/destroy platform devices for onboard USB hubs that may be connected to the root hub of the controller. These functions are a NOP unless CONFIG_USB_ONBOARD_HUB=y/m. Also add a field to struct usb_hcd to keep track of the onboard hub platform devices that are owned by the HCD. Reviewed-by: Douglas Anderson Reviewed-by: Stephen Boyd Signed-off-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20220217104219.v21.3.I7a3a7d9d2126c34079b1cab87aa0b2ec3030f9b7@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 6 ++++++ include/linux/usb/hcd.h | 1 + 2 files changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index d9712c2602af..81785072fafb 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "usb.h" @@ -2984,6 +2985,9 @@ int usb_add_hcd(struct usb_hcd *hcd, if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) usb_hcd_poll_rh_status(hcd); + if (usb_hcd_is_primary_hcd(hcd)) + onboard_hub_create_pdevs(hcd->self.root_hub, &hcd->onboard_hub_devs); + return retval; err_register_root_hub: @@ -3062,6 +3066,8 @@ void usb_remove_hcd(struct usb_hcd *hcd) if (usb_hcd_is_primary_hcd(hcd)) { if (hcd->irq > 0) free_irq(hcd->irq, hcd); + + onboard_hub_destroy_pdevs(&hcd->onboard_hub_devs); } usb_deregister_bus(&hcd->self); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 548a028f2dab..4ebc91c09182 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -198,6 +198,7 @@ struct usb_hcd { struct usb_hcd *shared_hcd; struct usb_hcd *primary_hcd; + struct list_head onboard_hub_devs; #define HCD_BUFFER_POOLS 4 struct dma_pool *pool[HCD_BUFFER_POOLS]; -- cgit v1.2.3 From 84d4333c1e28cedfad37c5347b4a94a3565a454f Mon Sep 17 00:00:00 2001 From: Won Chung Date: Mon, 18 Apr 2022 17:59:31 +0000 Subject: misc/mei: Add NULL check to component match callback functions Currently, component_match callback functions used in mei refers to the driver name, assuming that the component device being matched has a driver bound. It can cause a NULL pointer dereference when a device without a driver bound registers a component. This is due to the nature of the component framework where all registered components are matched in any component_match callback functions. So even if a component is registered by a totally irrelevant device, that component is also shared to these callbacks for i915 driver. To prevent totally irrelevant device being matched for i915 and causing a NULL pointer dereference for checking driver name, add a NULL check on dev->driver to check if there is a driver bound before checking the driver name. In the future, the string compare on the driver name, "i915" may need to be refactored too. Reviewed-by: Heikki Krogerus Reviewed-by: Prashant Malani Signed-off-by: Won Chung Acked-by: Tomas Winkler Signed-off-by: Mika Westerberg --- drivers/misc/mei/hdcp/mei_hdcp.c | 2 +- drivers/misc/mei/pxp/mei_pxp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/misc/mei/hdcp/mei_hdcp.c b/drivers/misc/mei/hdcp/mei_hdcp.c index ec2a4fce8581..e889a8bd7ac8 100644 --- a/drivers/misc/mei/hdcp/mei_hdcp.c +++ b/drivers/misc/mei/hdcp/mei_hdcp.c @@ -784,7 +784,7 @@ static int mei_hdcp_component_match(struct device *dev, int subcomponent, { struct device *base = data; - if (strcmp(dev->driver->name, "i915") || + if (!dev->driver || strcmp(dev->driver->name, "i915") || subcomponent != I915_COMPONENT_HDCP) return 0; diff --git a/drivers/misc/mei/pxp/mei_pxp.c b/drivers/misc/mei/pxp/mei_pxp.c index f7380d387bab..5c39457e3f53 100644 --- a/drivers/misc/mei/pxp/mei_pxp.c +++ b/drivers/misc/mei/pxp/mei_pxp.c @@ -131,7 +131,7 @@ static int mei_pxp_component_match(struct device *dev, int subcomponent, { struct device *base = data; - if (strcmp(dev->driver->name, "i915") || + if (!dev->driver || strcmp(dev->driver->name, "i915") || subcomponent != I915_COMPONENT_PXP) return 0; -- cgit v1.2.3 From 5dddb41692849594862473e4ddf57fb52e1249d6 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 18 Apr 2022 17:59:32 +0000 Subject: thunderbolt: Link USB4 ports to their USB Type-C connectors Creating a symlink pointing to the correct USB Type-C connector for the on-board USB4 ports when they are created. The link will be created only if the firmware is able to describe the connection between the port and its connector. Signed-off-by: Heikki Krogerus Signed-off-by: Won Chung Signed-off-by: Mika Westerberg --- Documentation/ABI/testing/sysfs-bus-thunderbolt | 10 +++++++ drivers/thunderbolt/usb4_port.c | 38 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index b7e87f6c7d47..f7570c240ce8 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -293,6 +293,16 @@ Contact: thunderbolt-software@lists.01.org Description: This contains XDomain service specific settings as bitmask. Format: %x +What: /sys/bus/thunderbolt/devices/usb4_portX/connector +Date: April 2022 +Contact: Heikki Krogerus +Description: + Symlink to the USB Type-C connector. This link is only + created when USB Type-C Connector Class is enabled, + and only if the system firmware is capable of + describing the connection between a port and its + connector. + What: /sys/bus/thunderbolt/devices/usb4_portX/link Date: Sep 2021 KernelVersion: v5.14 diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index 29e2a4f9c9f5..6b02945624ee 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -7,9 +7,37 @@ */ #include +#include +#include #include "tb.h" +static int connector_bind(struct device *dev, struct device *connector, void *data) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); + if (ret) + return ret; + + ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev)); + if (ret) + sysfs_remove_link(&dev->kobj, "connector"); + + return ret; +} + +static void connector_unbind(struct device *dev, struct device *connector, void *data) +{ + sysfs_remove_link(&connector->kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "connector"); +} + +static const struct component_ops connector_ops = { + .bind = connector_bind, + .unbind = connector_unbind, +}; + static ssize_t link_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -246,6 +274,14 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port) return ERR_PTR(ret); } + if (dev_fwnode(&usb4->dev)) { + ret = component_add(&usb4->dev, &connector_ops); + if (ret) { + dev_err(&usb4->dev, "failed to add component\n"); + device_unregister(&usb4->dev); + } + } + pm_runtime_no_callbacks(&usb4->dev); pm_runtime_set_active(&usb4->dev); pm_runtime_enable(&usb4->dev); @@ -265,6 +301,8 @@ struct usb4_port *usb4_port_device_add(struct tb_port *port) */ void usb4_port_device_remove(struct usb4_port *usb4) { + if (dev_fwnode(&usb4->dev)) + component_del(&usb4->dev, &connector_ops); device_unregister(&usb4->dev); } -- cgit v1.2.3 From f2b6e79c7378542e03e42fd47c0c8bf00a2fcf6b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 3 May 2022 16:44:02 +0200 Subject: Revert "usb: core: hcd: Create platform devices for onboard hubs in probe()" This reverts commit c40b62216c1aecc0dc00faf33d71bd71cb440337. The series still has built errors as reported in linux-next, so revert it for now. Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220502210728.0b36f3cd@canb.auug.org.au Cc: Stephen Boyd Cc: Douglas Anderson Cc: Matthias Kaehlcke Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 6 ------ include/linux/usb/hcd.h | 1 - 2 files changed, 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 81785072fafb..d9712c2602af 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -36,7 +36,6 @@ #include #include #include -#include #include #include "usb.h" @@ -2985,9 +2984,6 @@ int usb_add_hcd(struct usb_hcd *hcd, if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) usb_hcd_poll_rh_status(hcd); - if (usb_hcd_is_primary_hcd(hcd)) - onboard_hub_create_pdevs(hcd->self.root_hub, &hcd->onboard_hub_devs); - return retval; err_register_root_hub: @@ -3066,8 +3062,6 @@ void usb_remove_hcd(struct usb_hcd *hcd) if (usb_hcd_is_primary_hcd(hcd)) { if (hcd->irq > 0) free_irq(hcd->irq, hcd); - - onboard_hub_destroy_pdevs(&hcd->onboard_hub_devs); } usb_deregister_bus(&hcd->self); diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 4ebc91c09182..548a028f2dab 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -198,7 +198,6 @@ struct usb_hcd { struct usb_hcd *shared_hcd; struct usb_hcd *primary_hcd; - struct list_head onboard_hub_devs; #define HCD_BUFFER_POOLS 4 struct dma_pool *pool[HCD_BUFFER_POOLS]; -- cgit v1.2.3 From 67a7570ad31f2a7f02550153e774bf4c630f0fef Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 3 May 2022 16:44:11 +0200 Subject: Revert "usb: misc: Add onboard_usb_hub driver" This reverts commit 0298b4b95cb373c21e6323c905589f8dac42c5b4. The series still has built errors as reported in linux-next, so revert it for now. Reported-by: Stephen Rothwell Link: https://lore.kernel.org/r/20220502210728.0b36f3cd@canb.auug.org.au Cc: Alan Stern Cc: Douglas Anderson Cc: Matthias Kaehlcke Cc: Ravi Chandra Sadineni Cc: Stephen Boyd Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-platform-onboard-usb-hub | 8 - MAINTAINERS | 7 - drivers/usb/misc/Kconfig | 23 - drivers/usb/misc/Makefile | 1 - drivers/usb/misc/onboard_usb_hub.c | 510 --------------------- include/linux/usb/onboard_hub.h | 18 - 6 files changed, 567 deletions(-) delete mode 100644 Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub delete mode 100644 drivers/usb/misc/onboard_usb_hub.c delete mode 100644 include/linux/usb/onboard_hub.h (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub b/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub deleted file mode 100644 index af818d9d90c1..000000000000 --- a/Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub +++ /dev/null @@ -1,8 +0,0 @@ -What: /sys/bus/platform/devices//always_powered_in_suspend -Date: February 2022 -KernelVersion: 5.18 -Contact: Matthias Kaehlcke - linux-usb@vger.kernel.org -Description: - (RW) Controls whether the USB hub remains always powered - during system suspend or not. \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS index 4ed0456ec129..edc96cdb85e8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14631,13 +14631,6 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov9734.c -ONBOARD USB HUB DRIVER -M: Matthias Kaehlcke -L: linux-usb@vger.kernel.org -S: Maintained -F: Documentation/ABI/testing/sysfs-bus-platform-onboard-usb-hub -F: drivers/usb/misc/onboard_usb_hub.c - ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 993d07d2ffe5..4c5ddbd75b7e 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -295,26 +295,3 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. - -config USB_ONBOARD_HUB - bool "Onboard USB hub support" - depends on OF || COMPILE_TEST - help - Say Y here if you want to support discrete onboard USB hubs that - don't require an additional control bus for initialization, but - need some non-trivial form of initialization, such as enabling a - power regulator. An example for such a hub is the Realtek - RTS5411. - - This driver can be used as a module but its state (module vs - builtin) must match the state of the USB subsystem. Enabling - this config will enable the driver and it will automatically - match the state of the USB subsystem. If this driver is a - module it will be called onboard_usb_hub. - -if USB_ONBOARD_HUB -config USB_ONBOARD_HUB_ACTUAL - tristate - default m if USB=m - default y if USB=y -endif diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index ea65e202a055..35bdb4b6c3b6 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -33,4 +33,3 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o -obj-$(CONFIG_USB_ONBOARD_HUB_ACTUAL) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c deleted file mode 100644 index 34f7e4885b2e..000000000000 --- a/drivers/usb/misc/onboard_usb_hub.c +++ /dev/null @@ -1,510 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for onboard USB hubs - * - * Copyright (c) 2022, Google LLC - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct usb_device_driver onboard_hub_usbdev_driver; - -/************************** Platform driver **************************/ - -struct usbdev_node { - struct usb_device *udev; - struct list_head list; -}; - -struct onboard_hub { - struct regulator *vdd; - struct device *dev; - bool always_powered_in_suspend; - bool is_powered_on; - bool going_away; - struct list_head udev_list; - struct mutex lock; -}; - -static int onboard_hub_power_on(struct onboard_hub *hub) -{ - int err; - - err = regulator_enable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to enable regulator: %d\n", err); - return err; - } - - hub->is_powered_on = true; - - return 0; -} - -static int onboard_hub_power_off(struct onboard_hub *hub) -{ - int err; - - err = regulator_disable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to disable regulator: %d\n", err); - return err; - } - - hub->is_powered_on = false; - - return 0; -} - -static int __maybe_unused onboard_hub_suspend(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - struct usbdev_node *node; - bool power_off = true; - - if (hub->always_powered_in_suspend) - return 0; - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (!device_may_wakeup(node->udev->bus->controller)) - continue; - - if (usb_wakeup_enabled_descendants(node->udev)) { - power_off = false; - break; - } - } - - mutex_unlock(&hub->lock); - - if (!power_off) - return 0; - - return onboard_hub_power_off(hub); -} - -static int __maybe_unused onboard_hub_resume(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - - if (hub->is_powered_on) - return 0; - - return onboard_hub_power_on(hub); -} - -static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) -{ - snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); -} - -static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) -{ - struct usbdev_node *node; - char link_name[64]; - int err; - - mutex_lock(&hub->lock); - - if (hub->going_away) { - err = -EINVAL; - goto error; - } - - node = kzalloc(sizeof(*node), GFP_KERNEL); - if (!node) { - err = -ENOMEM; - goto error; - } - - node->udev = udev; - - list_add(&node->list, &hub->udev_list); - - mutex_unlock(&hub->lock); - - get_udev_link_name(udev, link_name, sizeof(link_name)); - WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); - - return 0; - -error: - mutex_unlock(&hub->lock); - - return err; -} - -static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) -{ - struct usbdev_node *node; - char link_name[64]; - - get_udev_link_name(udev, link_name, sizeof(link_name)); - sysfs_remove_link(&hub->dev->kobj, link_name); - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (node->udev == udev) { - list_del(&node->list); - kfree(node); - break; - } - } - - mutex_unlock(&hub->lock); -} - -static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - const struct onboard_hub *hub = dev_get_drvdata(dev); - - return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); -} - -static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - bool val; - int ret; - - ret = kstrtobool(buf, &val); - if (ret < 0) - return ret; - - hub->always_powered_in_suspend = val; - - return count; -} -static DEVICE_ATTR_RW(always_powered_in_suspend); - -static struct attribute *onboard_hub_attrs[] = { - &dev_attr_always_powered_in_suspend.attr, - NULL, -}; -ATTRIBUTE_GROUPS(onboard_hub); - -static int onboard_hub_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct onboard_hub *hub; - int err; - - hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - - hub->vdd = devm_regulator_get(dev, "vdd"); - if (IS_ERR(hub->vdd)) - return PTR_ERR(hub->vdd); - - hub->dev = dev; - mutex_init(&hub->lock); - INIT_LIST_HEAD(&hub->udev_list); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_power_on(hub); - if (err) - return err; - - /* - * The USB driver might have been detached from the USB devices by - * onboard_hub_remove(), make sure to re-attach it if needed. - */ - err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); - if (err) { - onboard_hub_power_off(hub); - return err; - } - - return 0; -} - -static int onboard_hub_remove(struct platform_device *pdev) -{ - struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); - struct usbdev_node *node; - struct usb_device *udev; - - hub->going_away = true; - - mutex_lock(&hub->lock); - - /* unbind the USB devices to avoid dangling references to this device */ - while (!list_empty(&hub->udev_list)) { - node = list_first_entry(&hub->udev_list, struct usbdev_node, list); - udev = node->udev; - - /* - * Unbinding the driver will call onboard_hub_remove_usbdev(), - * which acquires hub->lock. We must release the lock first. - */ - get_device(&udev->dev); - mutex_unlock(&hub->lock); - device_release_driver(&udev->dev); - put_device(&udev->dev); - mutex_lock(&hub->lock); - } - - mutex_unlock(&hub->lock); - - return onboard_hub_power_off(hub); -} - -static const struct of_device_id onboard_hub_match[] = { - { .compatible = "usbbda,411" }, - { .compatible = "usbbda,5411" }, - { .compatible = "usbbda,414" }, - { .compatible = "usbbda,5414" }, - {} -}; -MODULE_DEVICE_TABLE(of, onboard_hub_match); - -static bool of_is_onboard_usb_hub(const struct device_node *np) -{ - return !!of_match_node(onboard_hub_match, np); -} - -static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) -}; - -static struct platform_driver onboard_hub_driver = { - .probe = onboard_hub_probe, - .remove = onboard_hub_remove, - - .driver = { - .name = "onboard-usb-hub", - .of_match_table = onboard_hub_match, - .pm = pm_ptr(&onboard_hub_pm_ops), - .dev_groups = onboard_hub_groups, - }, -}; - -/************************** USB driver **************************/ - -#define VENDOR_ID_REALTEK 0x0bda - -/* - * Returns the onboard_hub platform device that is associated with the USB - * device passed as parameter. - */ -static struct onboard_hub *_find_onboard_hub(struct device *dev) -{ - struct platform_device *pdev; - struct device_node *np; - struct onboard_hub *hub; - - pdev = of_find_device_by_node(dev->of_node); - if (!pdev) { - np = of_parse_phandle(dev->of_node, "companion-hub", 0); - if (!np) { - dev_err(dev, "failed to find device node for companion hub\n"); - return ERR_PTR(-EINVAL); - } - - pdev = of_find_device_by_node(np); - of_node_put(np); - - if (!pdev) - return ERR_PTR(-ENODEV); - } - - hub = dev_get_drvdata(&pdev->dev); - put_device(&pdev->dev); - - /* - * The presence of drvdata ('hub') indicates that the platform driver - * finished probing. This handles the case where (conceivably) we could - * be running at the exact same time as the platform driver's probe. If - * we detect the race we request probe deferral and we'll come back and - * try again. - */ - if (!hub) - return ERR_PTR(-EPROBE_DEFER); - - return hub; -} - -static int onboard_hub_usbdev_probe(struct usb_device *udev) -{ - struct device *dev = &udev->dev; - struct onboard_hub *hub; - int err; - - /* ignore supported hubs without device tree node */ - if (!dev->of_node) - return -ENODEV; - - hub = _find_onboard_hub(dev); - if (IS_ERR(hub)) - return PTR_ERR(hub); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_add_usbdev(hub, udev); - if (err) - return err; - - return 0; -} - -static void onboard_hub_usbdev_disconnect(struct usb_device *udev) -{ - struct onboard_hub *hub = dev_get_drvdata(&udev->dev); - - onboard_hub_remove_usbdev(hub, udev); -} - -static const struct usb_device_id onboard_hub_id_table[] = { - { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ - {} -}; -MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); - -static struct usb_device_driver onboard_hub_usbdev_driver = { - .name = "onboard-usb-hub", - .probe = onboard_hub_usbdev_probe, - .disconnect = onboard_hub_usbdev_disconnect, - .generic_subclass = 1, - .supports_autosuspend = 1, - .id_table = onboard_hub_id_table, -}; - -/*** Helpers for creating/destroying platform devices for onboard hubs ***/ - -struct pdev_list_entry { - struct platform_device *pdev; - struct list_head node; -}; - -/** - * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs - * @parent_hub : parent hub to scan for connected onboard hubs - * @pdev_list : list of onboard hub platform devices owned by the parent hub - * - * Creates a platform device for each supported onboard hub that is connected to - * the given parent hub. To keep track of the platform devices they are added to - * a list that is owned by the parent hub. - */ -void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) -{ - int i; - struct device_node *np, *npc; - struct platform_device *pdev = NULL; - struct pdev_list_entry *pdle; - - INIT_LIST_HEAD(pdev_list); - - for (i = 1; i <= parent_hub->maxchild; i++) { - np = usb_of_get_device_node(parent_hub, i); - if (!np) - continue; - - if (!of_is_onboard_usb_hub(np)) - goto node_put; - - npc = of_parse_phandle(np, "companion-hub", 0); - if (npc) { - pdev = of_find_device_by_node(npc); - of_node_put(npc); - } - - if (pdev) { - /* the companion hub already has a platform device, nothing to do here */ - put_device(&pdev->dev); - goto node_put; - } - - pdev = of_platform_device_create(np, NULL, &parent_hub->dev); - if (!pdev) { - dev_err(&parent_hub->dev, - "failed to create platform device for onboard hub '%pOF'\n", np); - goto node_put; - } - - pdle = devm_kzalloc(&pdev->dev, sizeof(*pdle), GFP_KERNEL); - if (!pdle) { - of_platform_device_destroy(&pdev->dev, NULL); - goto node_put; - } - - pdle->pdev = pdev; - list_add(&pdle->node, pdev_list); - -node_put: - of_node_put(np); - } -} -EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); - -/** - * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices - * @pdev_list : list of onboard hub platform devices - * - * Destroys the platform devices in the given list and frees the memory associated - * with the list entry. - */ -void onboard_hub_destroy_pdevs(struct list_head *pdev_list) -{ - struct pdev_list_entry *pdle, *tmp; - - list_for_each_entry_safe(pdle, tmp, pdev_list, node) { - list_del(&pdle->node); - of_platform_device_destroy(&pdle->pdev->dev, NULL); - } -} -EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); - -/************************** Driver (de)registration **************************/ - -static int __init onboard_hub_init(void) -{ - int ret; - - ret = platform_driver_register(&onboard_hub_driver); - if (ret) - return ret; - - ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); - if (ret) - platform_driver_unregister(&onboard_hub_driver); - - return ret; -} -module_init(onboard_hub_init); - -static void __exit onboard_hub_exit(void) -{ - usb_deregister_device_driver(&onboard_hub_usbdev_driver); - platform_driver_unregister(&onboard_hub_driver); -} -module_exit(onboard_hub_exit); - -MODULE_AUTHOR("Matthias Kaehlcke "); -MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); -MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/onboard_hub.h b/include/linux/usb/onboard_hub.h deleted file mode 100644 index d9373230556e..000000000000 --- a/include/linux/usb/onboard_hub.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef __LINUX_USB_ONBOARD_HUB_H -#define __LINUX_USB_ONBOARD_HUB_H - -struct usb_device; -struct list_head; - -#if IS_ENABLED(CONFIG_USB_ONBOARD_HUB) -void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list); -void onboard_hub_destroy_pdevs(struct list_head *pdev_list); -#else -static inline void onboard_hub_create_pdevs(struct usb_device *parent_hub, - struct list_head *pdev_list) {} -static inline void onboard_hub_destroy_pdevs(struct list_head *pdev_list) {} -#endif - -#endif /* __LINUX_USB_ONBOARD_HUB_H */ -- cgit v1.2.3 From 90f720d2292f52de51df2307272a8f8cf7ef7134 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 13 Feb 2022 11:42:23 +0200 Subject: thunderbolt: Add debug logging when lane is enabled/disabled This is useful when debugging possible issues. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index ac87e8b50e52..2d8a0fd3469c 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -693,8 +693,14 @@ static int __tb_port_enable(struct tb_port *port, bool enable) else phy |= LANE_ADP_CS_1_LD; - return tb_port_write(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); + + ret = tb_port_write(port, &phy, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + tb_port_dbg(port, "lane %sabled\n", enable ? "en" : "dis"); + return 0; } /** -- cgit v1.2.3 From 94581b25d81f8f16b48e0b61a13f81469d6e5bc0 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 13 Feb 2022 11:45:39 +0200 Subject: thunderbolt: Move tb_port_state() prototype to correct place This should be before tb_wait_for_port() following how the functions in switch.c are organized. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ad025ff142ba..8848e8de1fc3 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -991,6 +991,7 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw); int tb_switch_xhci_connect(struct tb_switch *sw); void tb_switch_xhci_disconnect(struct tb_switch *sw); +int tb_port_state(struct tb_port *port); int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); int tb_port_add_nfc_credits(struct tb_port *port, int credits); int tb_port_clear_counter(struct tb_port *port, int counter); @@ -1023,7 +1024,6 @@ static inline bool tb_port_use_credit_allocation(const struct tb_port *port) int tb_port_get_link_speed(struct tb_port *port); int tb_port_get_link_width(struct tb_port *port); -int tb_port_state(struct tb_port *port); int tb_port_lane_bonding_enable(struct tb_port *port); void tb_port_lane_bonding_disable(struct tb_port *port); int tb_port_wait_for_link_width(struct tb_port *port, int width, -- cgit v1.2.3 From 0e14dd5e14d697e2489c7bf0fe35947831de3975 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 13 Feb 2022 11:54:15 +0200 Subject: thunderbolt: Split setting link width and lane bonding into own functions When bonding lanes over XDomain the host that has "higher" UUID triggers link re-train for bonding, and the host that has "lower" UUID just waits for this to happen. To support this split setting the link width and triggering the actual bonding a separate functions that can be called as needed. While there remove duplicated empty line in the kernel-doc comment of tb_port_lane_bonding_disable(). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 86 ++++++++++++++++++++++++++++++++++++-------- drivers/thunderbolt/tb.h | 2 ++ 2 files changed, 74 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 2d8a0fd3469c..525be2aa3ad9 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -999,7 +999,17 @@ static bool tb_port_is_width_supported(struct tb_port *port, int width) return !!(widths & width); } -static int tb_port_set_link_width(struct tb_port *port, unsigned int width) +/** + * tb_port_set_link_width() - Set target link width of the lane adapter + * @port: Lane adapter + * @width: Target link width (%1 or %2) + * + * Sets the target link width of the lane adapter to @width. Does not + * enable/disable lane bonding. For that call tb_port_set_lane_bonding(). + * + * Return: %0 in case of success and negative errno in case of error + */ +int tb_port_set_link_width(struct tb_port *port, unsigned int width) { u32 val; int ret; @@ -1026,12 +1036,58 @@ static int tb_port_set_link_width(struct tb_port *port, unsigned int width) return -EINVAL; } - val |= LANE_ADP_CS_1_LB; - return tb_port_write(port, &val, TB_CFG_PORT, port->cap_phy + LANE_ADP_CS_1, 1); } +/** + * tb_port_set_lane_bonding() - Enable/disable lane bonding + * @port: Lane adapter + * @bonding: enable/disable bonding + * + * Enables or disables lane bonding. This should be called after target + * link width has been set (tb_port_set_link_width()). Note in most + * cases one should use tb_port_lane_bonding_enable() instead to enable + * lane bonding. + * + * As a side effect sets @port->bonding accordingly (and does the same + * for lane 1 too). + * + * Return: %0 in case of success and negative errno in case of error + */ +int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) +{ + u32 val; + int ret; + + if (!port->cap_phy) + return -EINVAL; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + if (bonding) + val |= LANE_ADP_CS_1_LB; + else + val &= ~LANE_ADP_CS_1_LB; + + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + + /* + * When lane 0 bonding is set it will affect lane 1 too so + * update both. + */ + port->bonded = bonding; + port->dual_link_port->bonded = bonding; + + return 0; +} + /** * tb_port_lane_bonding_enable() - Enable bonding on port * @port: port to enable @@ -1056,22 +1112,27 @@ int tb_port_lane_bonding_enable(struct tb_port *port) if (ret == 1) { ret = tb_port_set_link_width(port, 2); if (ret) - return ret; + goto err_lane0; } ret = tb_port_get_link_width(port->dual_link_port); if (ret == 1) { ret = tb_port_set_link_width(port->dual_link_port, 2); - if (ret) { - tb_port_set_link_width(port, 1); - return ret; - } + if (ret) + goto err_lane0; } - port->bonded = true; - port->dual_link_port->bonded = true; + ret = tb_port_set_lane_bonding(port, true); + if (ret) + goto err_lane1; return 0; + +err_lane1: + tb_port_set_link_width(port->dual_link_port, 1); +err_lane0: + tb_port_set_link_width(port, 1); + return ret; } /** @@ -1080,13 +1141,10 @@ int tb_port_lane_bonding_enable(struct tb_port *port) * * Disable bonding by setting the link width of the port and the * other port in case of dual link port. - * */ void tb_port_lane_bonding_disable(struct tb_port *port) { - port->dual_link_port->bonded = false; - port->bonded = false; - + tb_port_set_lane_bonding(port, false); tb_port_set_link_width(port->dual_link_port, 1); tb_port_set_link_width(port, 1); } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 8848e8de1fc3..4602c69913fa 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1024,6 +1024,8 @@ static inline bool tb_port_use_credit_allocation(const struct tb_port *port) int tb_port_get_link_speed(struct tb_port *port); int tb_port_get_link_width(struct tb_port *port); +int tb_port_set_link_width(struct tb_port *port, unsigned int width); +int tb_port_set_lane_bonding(struct tb_port *port, bool bonding); int tb_port_lane_bonding_enable(struct tb_port *port); void tb_port_lane_bonding_disable(struct tb_port *port); int tb_port_wait_for_link_width(struct tb_port *port, int width, -- cgit v1.2.3 From 0a2e1667a73fe0c4374ddace925d85a4072d509c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 13 Feb 2022 17:16:24 +0200 Subject: thunderbolt: Ignore port locked error in tb_port_wait_for_link_width() Sometimes when polling for the port after target link width is changed we get back port locked notification (because the link actually was reset and then re-trained). Instead of bailing out we can ignore these when polling for the width change as this is expected. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 525be2aa3ad9..561e1d77240e 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1168,10 +1168,17 @@ int tb_port_wait_for_link_width(struct tb_port *port, int width, do { ret = tb_port_get_link_width(port); - if (ret < 0) - return ret; - else if (ret == width) + if (ret < 0) { + /* + * Sometimes we get port locked error when + * polling the lanes so we can ignore it and + * retry. + */ + if (ret != -EACCES) + return ret; + } else if (ret == width) { return 0; + } usleep_range(1000, 2000); } while (ktime_before(ktime_get(), timeout)); -- cgit v1.2.3 From 8e1de7042596abb7cb277ea751fc13a4c2b65aea Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 13 Feb 2022 16:44:45 +0200 Subject: thunderbolt: Add support for XDomain lane bonding The USB4 Inter-Domain Service specification defines a protocol that can be used to establish lane bonding between two USB4 domains (hosts). So far we have not implemented it because the host controller DMA was not fast enough to be able to go over 20 Gbits/s even if lanes were bonded. However, starting from Intel Alder Lake CPUs the DMA can go over 20 Gbits/s so now it makes more sense to add this support to the driver. Because both ends need to negotiate the bonding we add a simple state machine that tracks the connection state and does the necessary steps described by the USB4 Inter-Domain Service specification. We only establish lane bonding when both sides of the link support it. Otherwise we default to use the single lane. Also this is only done when software connection manager is used. On systems with firmware based connection manager, it handles the high-speed tunneling so bonding lanes is specific to the implementation (Intel firmware based connection manager does not support lane bonding). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 6 - drivers/thunderbolt/tb_msgs.h | 39 +++ drivers/thunderbolt/tb_regs.h | 5 + drivers/thunderbolt/xdomain.c | 609 +++++++++++++++++++++++++++++++++++++----- include/linux/thunderbolt.h | 19 +- 5 files changed, 595 insertions(+), 83 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 44d04b651a8b..9a3214fb5038 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -169,12 +169,6 @@ static void tb_discover_tunnels(struct tb *tb) static int tb_port_configure_xdomain(struct tb_port *port) { - /* - * XDomain paths currently only support single lane so we must - * disable the other lane according to USB4 spec. - */ - tb_port_disable(port->dual_link_port); - if (tb_switch_is_usb4(port->sw)) return usb4_port_configure_xdomain(port); return tb_lc_configure_xdomain(port); diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index fe1afa44c56d..33c4c7aed56d 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -527,6 +527,10 @@ enum tb_xdp_type { PROPERTIES_CHANGED_RESPONSE, ERROR_RESPONSE, UUID_REQUEST = 12, + LINK_STATE_STATUS_REQUEST = 15, + LINK_STATE_STATUS_RESPONSE, + LINK_STATE_CHANGE_REQUEST, + LINK_STATE_CHANGE_RESPONSE, }; struct tb_xdp_header { @@ -540,6 +544,41 @@ struct tb_xdp_error_response { u32 error; }; +struct tb_xdp_link_state_status { + struct tb_xdp_header hdr; +}; + +struct tb_xdp_link_state_status_response { + union { + struct tb_xdp_error_response err; + struct { + struct tb_xdp_header hdr; + u32 status; + u8 slw; + u8 tlw; + u8 sls; + u8 tls; + }; + }; +}; + +struct tb_xdp_link_state_change { + struct tb_xdp_header hdr; + u8 tlw; + u8 tls; + u16 reserved; +}; + +struct tb_xdp_link_state_change_response { + union { + struct tb_xdp_error_response err; + struct { + struct tb_xdp_header hdr; + u32 status; + }; + }; +}; + struct tb_xdp_uuid { struct tb_xdp_header hdr; }; diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index b301eeb0c89b..6a16f61a72a1 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -311,11 +311,16 @@ struct tb_regs_port_header { /* Lane adapter registers */ #define LANE_ADP_CS_0 0x00 +#define LANE_ADP_CS_0_SUPPORTED_SPEED_MASK GENMASK(19, 16) +#define LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT 16 #define LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK GENMASK(25, 20) #define LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT 20 +#define LANE_ADP_CS_0_SUPPORTED_WIDTH_DUAL 0x2 #define LANE_ADP_CS_0_CL0S_SUPPORT BIT(26) #define LANE_ADP_CS_0_CL1_SUPPORT BIT(27) #define LANE_ADP_CS_1 0x01 +#define LANE_ADP_CS_1_TARGET_SPEED_MASK GENMASK(3, 0) +#define LANE_ADP_CS_1_TARGET_SPEED_GEN3 0xc #define LANE_ADP_CS_1_TARGET_WIDTH_MASK GENMASK(9, 4) #define LANE_ADP_CS_1_TARGET_WIDTH_SHIFT 4 #define LANE_ADP_CS_1_TARGET_WIDTH_SINGLE 0x1 diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 01d6b724ca51..c31c0d94d8b3 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -19,13 +19,38 @@ #include "tb.h" -#define XDOMAIN_DEFAULT_TIMEOUT 1000 /* ms */ -#define XDOMAIN_UUID_RETRIES 10 -#define XDOMAIN_PROPERTIES_RETRIES 10 -#define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 -#define XDOMAIN_BONDING_WAIT 100 /* ms */ +#define XDOMAIN_SHORT_TIMEOUT 100 /* ms */ +#define XDOMAIN_DEFAULT_TIMEOUT 1000 /* ms */ +#define XDOMAIN_BONDING_TIMEOUT 10000 /* ms */ +#define XDOMAIN_RETRIES 10 #define XDOMAIN_DEFAULT_MAX_HOPID 15 +enum { + XDOMAIN_STATE_INIT, + XDOMAIN_STATE_UUID, + XDOMAIN_STATE_LINK_STATUS, + XDOMAIN_STATE_LINK_STATE_CHANGE, + XDOMAIN_STATE_LINK_STATUS2, + XDOMAIN_STATE_BONDING_UUID_LOW, + XDOMAIN_STATE_BONDING_UUID_HIGH, + XDOMAIN_STATE_PROPERTIES, + XDOMAIN_STATE_ENUMERATED, + XDOMAIN_STATE_ERROR, +}; + +static const char * const state_names[] = { + [XDOMAIN_STATE_INIT] = "INIT", + [XDOMAIN_STATE_UUID] = "UUID", + [XDOMAIN_STATE_LINK_STATUS] = "LINK_STATUS", + [XDOMAIN_STATE_LINK_STATE_CHANGE] = "LINK_STATE_CHANGE", + [XDOMAIN_STATE_LINK_STATUS2] = "LINK_STATUS2", + [XDOMAIN_STATE_BONDING_UUID_LOW] = "BONDING_UUID_LOW", + [XDOMAIN_STATE_BONDING_UUID_HIGH] = "BONDING_UUID_HIGH", + [XDOMAIN_STATE_PROPERTIES] = "PROPERTIES", + [XDOMAIN_STATE_ENUMERATED] = "ENUMERATED", + [XDOMAIN_STATE_ERROR] = "ERROR", +}; + struct xdomain_request_work { struct work_struct work; struct tb_xdp_header *pkg; @@ -235,7 +260,7 @@ static int tb_xdp_handle_error(const struct tb_xdp_error_response *res) } static int tb_xdp_uuid_request(struct tb_ctl *ctl, u64 route, int retry, - uuid_t *uuid) + uuid_t *uuid, u64 *remote_route) { struct tb_xdp_uuid_response res; struct tb_xdp_uuid req; @@ -258,6 +283,8 @@ static int tb_xdp_uuid_request(struct tb_ctl *ctl, u64 route, int retry, return ret; uuid_copy(uuid, &res.src_uuid); + *remote_route = (u64)res.src_route_hi << 32 | res.src_route_lo; + return 0; } @@ -473,6 +500,112 @@ tb_xdp_properties_changed_response(struct tb_ctl *ctl, u64 route, u8 sequence) TB_CFG_PKG_XDOMAIN_RESP); } +static int tb_xdp_link_state_status_request(struct tb_ctl *ctl, u64 route, + u8 sequence, u8 *slw, u8 *tlw, + u8 *sls, u8 *tls) +{ + struct tb_xdp_link_state_status_response res; + struct tb_xdp_link_state_status req; + int ret; + + memset(&req, 0, sizeof(req)); + tb_xdp_fill_header(&req.hdr, route, sequence, LINK_STATE_STATUS_REQUEST, + sizeof(req)); + + memset(&res, 0, sizeof(res)); + ret = __tb_xdomain_request(ctl, &req, sizeof(req), TB_CFG_PKG_XDOMAIN_REQ, + &res, sizeof(res), TB_CFG_PKG_XDOMAIN_RESP, + XDOMAIN_DEFAULT_TIMEOUT); + if (ret) + return ret; + + ret = tb_xdp_handle_error(&res.err); + if (ret) + return ret; + + if (res.status != 0) + return -EREMOTEIO; + + *slw = res.slw; + *tlw = res.tlw; + *sls = res.sls; + *tls = res.tls; + + return 0; +} + +static int tb_xdp_link_state_status_response(struct tb *tb, struct tb_ctl *ctl, + struct tb_xdomain *xd, u8 sequence) +{ + struct tb_switch *sw = tb_to_switch(xd->dev.parent); + struct tb_xdp_link_state_status_response res; + struct tb_port *port = tb_port_at(xd->route, sw); + u32 val[2]; + int ret; + + memset(&res, 0, sizeof(res)); + tb_xdp_fill_header(&res.hdr, xd->route, sequence, + LINK_STATE_STATUS_RESPONSE, sizeof(res)); + + ret = tb_port_read(port, val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_0, ARRAY_SIZE(val)); + if (ret) + return ret; + + res.slw = (val[0] & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >> + LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT; + res.sls = (val[0] & LANE_ADP_CS_0_SUPPORTED_SPEED_MASK) >> + LANE_ADP_CS_0_SUPPORTED_SPEED_SHIFT; + res.tls = val[1] & LANE_ADP_CS_1_TARGET_SPEED_MASK; + res.tlw = (val[1] & LANE_ADP_CS_1_TARGET_WIDTH_MASK) >> + LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; + + return __tb_xdomain_response(ctl, &res, sizeof(res), + TB_CFG_PKG_XDOMAIN_RESP); +} + +static int tb_xdp_link_state_change_request(struct tb_ctl *ctl, u64 route, + u8 sequence, u8 tlw, u8 tls) +{ + struct tb_xdp_link_state_change_response res; + struct tb_xdp_link_state_change req; + int ret; + + memset(&req, 0, sizeof(req)); + tb_xdp_fill_header(&req.hdr, route, sequence, LINK_STATE_CHANGE_REQUEST, + sizeof(req)); + req.tlw = tlw; + req.tls = tls; + + memset(&res, 0, sizeof(res)); + ret = __tb_xdomain_request(ctl, &req, sizeof(req), TB_CFG_PKG_XDOMAIN_REQ, + &res, sizeof(res), TB_CFG_PKG_XDOMAIN_RESP, + XDOMAIN_DEFAULT_TIMEOUT); + if (ret) + return ret; + + ret = tb_xdp_handle_error(&res.err); + if (ret) + return ret; + + return res.status != 0 ? -EREMOTEIO : 0; +} + +static int tb_xdp_link_state_change_response(struct tb_ctl *ctl, u64 route, + u8 sequence, u32 status) +{ + struct tb_xdp_link_state_change_response res; + + memset(&res, 0, sizeof(res)); + tb_xdp_fill_header(&res.hdr, route, sequence, LINK_STATE_CHANGE_RESPONSE, + sizeof(res)); + + res.status = status; + + return __tb_xdomain_response(ctl, &res, sizeof(res), + TB_CFG_PKG_XDOMAIN_RESP); +} + /** * tb_register_protocol_handler() - Register protocol handler * @handler: Handler to register @@ -600,14 +733,13 @@ static void tb_xdp_handle_request(struct work_struct *work) goto out; } - tb_dbg(tb, "%llx: received XDomain request %#x\n", route, pkg->type); - xd = tb_xdomain_find_by_route_locked(tb, route); if (xd) update_property_block(xd); switch (pkg->type) { case PROPERTIES_REQUEST: + tb_dbg(tb, "%llx: received XDomain properties request\n", route); if (xd) { ret = tb_xdp_properties_response(tb, ctl, xd, sequence, (const struct tb_xdp_properties *)pkg); @@ -615,6 +747,9 @@ static void tb_xdp_handle_request(struct work_struct *work) break; case PROPERTIES_CHANGED_REQUEST: + tb_dbg(tb, "%llx: received XDomain properties changed request\n", + route); + ret = tb_xdp_properties_changed_response(ctl, route, sequence); /* @@ -622,18 +757,51 @@ static void tb_xdp_handle_request(struct work_struct *work) * the xdomain related to this connection as well in * case there is a change in services it offers. */ - if (xd && device_is_registered(&xd->dev)) { - queue_delayed_work(tb->wq, &xd->get_properties_work, - msecs_to_jiffies(50)); - } + if (xd && device_is_registered(&xd->dev)) + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); break; case UUID_REQUEST_OLD: case UUID_REQUEST: + tb_dbg(tb, "%llx: received XDomain UUID request\n", route); ret = tb_xdp_uuid_response(ctl, route, sequence, uuid); break; + case LINK_STATE_STATUS_REQUEST: + tb_dbg(tb, "%llx: received XDomain link state status request\n", + route); + + if (xd) { + ret = tb_xdp_link_state_status_response(tb, ctl, xd, + sequence); + } else { + tb_xdp_error_response(ctl, route, sequence, + ERROR_NOT_READY); + } + break; + + case LINK_STATE_CHANGE_REQUEST: + tb_dbg(tb, "%llx: received XDomain link state change request\n", + route); + + if (xd && xd->state == XDOMAIN_STATE_BONDING_UUID_HIGH) { + const struct tb_xdp_link_state_change *lsc = + (const struct tb_xdp_link_state_change *)pkg; + + ret = tb_xdp_link_state_change_response(ctl, route, + sequence, 0); + xd->target_link_width = lsc->tlw; + queue_delayed_work(tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); + } else { + tb_xdp_error_response(ctl, route, sequence, + ERROR_NOT_READY); + } + break; + default: + tb_dbg(tb, "%llx: unknown XDomain request %#x\n", route, pkg->type); tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_SUPPORTED); break; @@ -1000,32 +1168,38 @@ static int tb_xdomain_update_link_attributes(struct tb_xdomain *xd) return 0; } -static void tb_xdomain_get_uuid(struct work_struct *work) +static int tb_xdomain_get_uuid(struct tb_xdomain *xd) { - struct tb_xdomain *xd = container_of(work, typeof(*xd), - get_uuid_work.work); struct tb *tb = xd->tb; uuid_t uuid; + u64 route; int ret; dev_dbg(&xd->dev, "requesting remote UUID\n"); - ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->uuid_retries, &uuid); + ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->state_retries, &uuid, + &route); if (ret < 0) { - if (xd->uuid_retries-- > 0) { + if (xd->state_retries-- > 0) { dev_dbg(&xd->dev, "failed to request UUID, retrying\n"); - queue_delayed_work(xd->tb->wq, &xd->get_uuid_work, - msecs_to_jiffies(100)); + return -EAGAIN; } else { dev_dbg(&xd->dev, "failed to read remote UUID\n"); } - return; + return ret; } dev_dbg(&xd->dev, "got remote UUID %pUb\n", &uuid); - if (uuid_equal(&uuid, xd->local_uuid)) - dev_dbg(&xd->dev, "intra-domain loop detected\n"); + if (uuid_equal(&uuid, xd->local_uuid)) { + if (route == xd->route) + dev_dbg(&xd->dev, "loop back detected\n"); + else + dev_dbg(&xd->dev, "intra-domain loop detected\n"); + + /* Don't bond lanes automatically for loops */ + xd->bonding_possible = false; + } /* * If the UUID is different, there is another domain connected @@ -1035,27 +1209,152 @@ static void tb_xdomain_get_uuid(struct work_struct *work) if (xd->remote_uuid && !uuid_equal(&uuid, xd->remote_uuid)) { dev_dbg(&xd->dev, "remote UUID is different, unplugging\n"); xd->is_unplugged = true; - return; + return -ENODEV; } /* First time fill in the missing UUID */ if (!xd->remote_uuid) { xd->remote_uuid = kmemdup(&uuid, sizeof(uuid_t), GFP_KERNEL); if (!xd->remote_uuid) - return; + return -ENOMEM; } - /* Now we can start the normal properties exchange */ - queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, - msecs_to_jiffies(100)); - queue_delayed_work(xd->tb->wq, &xd->get_properties_work, - msecs_to_jiffies(1000)); + return 0; } -static void tb_xdomain_get_properties(struct work_struct *work) +static int tb_xdomain_get_link_status(struct tb_xdomain *xd) +{ + struct tb *tb = xd->tb; + u8 slw, tlw, sls, tls; + int ret; + + dev_dbg(&xd->dev, "sending link state status request to %pUb\n", + xd->remote_uuid); + + ret = tb_xdp_link_state_status_request(tb->ctl, xd->route, + xd->state_retries, &slw, &tlw, &sls, + &tls); + if (ret) { + if (ret != -EOPNOTSUPP && xd->state_retries-- > 0) { + dev_dbg(&xd->dev, + "failed to request remote link status, retrying\n"); + return -EAGAIN; + } + dev_dbg(&xd->dev, "failed to receive remote link status\n"); + return ret; + } + + dev_dbg(&xd->dev, "remote link supports width %#x speed %#x\n", slw, sls); + + if (slw < LANE_ADP_CS_0_SUPPORTED_WIDTH_DUAL) { + dev_dbg(&xd->dev, "remote adapter is single lane only\n"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int tb_xdomain_link_state_change(struct tb_xdomain *xd, + unsigned int width) +{ + struct tb_switch *sw = tb_to_switch(xd->dev.parent); + struct tb_port *port = tb_port_at(xd->route, sw); + struct tb *tb = xd->tb; + u8 tlw, tls; + u32 val; + int ret; + + if (width == 2) + tlw = LANE_ADP_CS_1_TARGET_WIDTH_DUAL; + else if (width == 1) + tlw = LANE_ADP_CS_1_TARGET_WIDTH_SINGLE; + else + return -EINVAL; + + /* Use the current target speed */ + ret = tb_port_read(port, &val, TB_CFG_PORT, port->cap_phy + LANE_ADP_CS_1, 1); + if (ret) + return ret; + tls = val & LANE_ADP_CS_1_TARGET_SPEED_MASK; + + dev_dbg(&xd->dev, "sending link state change request with width %#x speed %#x\n", + tlw, tls); + + ret = tb_xdp_link_state_change_request(tb->ctl, xd->route, + xd->state_retries, tlw, tls); + if (ret) { + if (ret != -EOPNOTSUPP && xd->state_retries-- > 0) { + dev_dbg(&xd->dev, + "failed to change remote link state, retrying\n"); + return -EAGAIN; + } + dev_err(&xd->dev, "failed request link state change, aborting\n"); + return ret; + } + + dev_dbg(&xd->dev, "received link state change response\n"); + return 0; +} + +static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd) +{ + struct tb_port *port; + int ret, width; + + if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_SINGLE) { + width = 1; + } else if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_DUAL) { + width = 2; + } else { + if (xd->state_retries-- > 0) { + dev_dbg(&xd->dev, + "link state change request not received yet, retrying\n"); + return -EAGAIN; + } + dev_dbg(&xd->dev, "timeout waiting for link change request\n"); + return -ETIMEDOUT; + } + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + + /* + * We can't use tb_xdomain_lane_bonding_enable() here because it + * is the other side that initiates lane bonding. So here we + * just set the width to both lane adapters and wait for the + * link to transition bonded. + */ + ret = tb_port_set_link_width(port->dual_link_port, width); + if (ret) { + tb_port_warn(port->dual_link_port, + "failed to set link width to %d\n", width); + return ret; + } + + ret = tb_port_set_link_width(port, width); + if (ret) { + tb_port_warn(port, "failed to set link width to %d\n", width); + return ret; + } + + ret = tb_port_wait_for_link_width(port, width, XDOMAIN_BONDING_TIMEOUT); + if (ret) { + dev_warn(&xd->dev, "error waiting for link width to become %d\n", + width); + return ret; + } + + port->bonded = width == 2; + port->dual_link_port->bonded = width == 2; + + tb_port_update_credits(port); + tb_xdomain_update_link_attributes(xd); + + dev_dbg(&xd->dev, "lane bonding %sabled\n", width == 2 ? "en" : "dis"); + return 0; +} + +static int tb_xdomain_get_properties(struct tb_xdomain *xd) { - struct tb_xdomain *xd = container_of(work, typeof(*xd), - get_properties_work.work); struct tb_property_dir *dir; struct tb *tb = xd->tb; bool update = false; @@ -1066,34 +1365,35 @@ static void tb_xdomain_get_properties(struct work_struct *work) dev_dbg(&xd->dev, "requesting remote properties\n"); ret = tb_xdp_properties_request(tb->ctl, xd->route, xd->local_uuid, - xd->remote_uuid, xd->properties_retries, + xd->remote_uuid, xd->state_retries, &block, &gen); if (ret < 0) { - if (xd->properties_retries-- > 0) { + if (xd->state_retries-- > 0) { dev_dbg(&xd->dev, "failed to request remote properties, retrying\n"); - queue_delayed_work(xd->tb->wq, &xd->get_properties_work, - msecs_to_jiffies(1000)); + return -EAGAIN; } else { /* Give up now */ dev_err(&xd->dev, "failed read XDomain properties from %pUb\n", xd->remote_uuid); } - return; - } - xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; + return ret; + } mutex_lock(&xd->lock); /* Only accept newer generation properties */ - if (xd->remote_properties && gen <= xd->remote_property_block_gen) + if (xd->remote_properties && gen <= xd->remote_property_block_gen) { + ret = 0; goto err_free_block; + } dir = tb_property_parse_dir(block, ret); if (!dir) { dev_err(&xd->dev, "failed to parse XDomain properties\n"); + ret = -ENOMEM; goto err_free_block; } @@ -1124,9 +1424,16 @@ static void tb_xdomain_get_properties(struct work_struct *work) * registered, we notify the userspace that it has changed. */ if (!update) { + struct tb_port *port; + + /* Now disable lane 1 if bonding was not enabled */ + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + if (!port->bonded) + tb_port_disable(port->dual_link_port); + if (device_add(&xd->dev)) { dev_err(&xd->dev, "failed to add XDomain device\n"); - return; + return -ENODEV; } dev_info(&xd->dev, "new host found, vendor=%#x device=%#x\n", xd->vendor, xd->device); @@ -1138,13 +1445,193 @@ static void tb_xdomain_get_properties(struct work_struct *work) } enumerate_services(xd); - return; + return 0; err_free_dir: tb_property_free_dir(dir); err_free_block: kfree(block); mutex_unlock(&xd->lock); + + return ret; +} + +static void tb_xdomain_queue_uuid(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_UUID; + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); +} + +static void tb_xdomain_queue_link_status(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_LINK_STATUS; + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + +static void tb_xdomain_queue_link_status2(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_LINK_STATUS2; + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + +static void tb_xdomain_queue_bonding(struct tb_xdomain *xd) +{ + if (memcmp(xd->local_uuid, xd->remote_uuid, UUID_SIZE) > 0) { + dev_dbg(&xd->dev, "we have higher UUID, other side bonds the lanes\n"); + xd->state = XDOMAIN_STATE_BONDING_UUID_HIGH; + } else { + dev_dbg(&xd->dev, "we have lower UUID, bonding lanes\n"); + xd->state = XDOMAIN_STATE_LINK_STATE_CHANGE; + } + + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + +static void tb_xdomain_queue_bonding_uuid_low(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_BONDING_UUID_LOW; + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + +static void tb_xdomain_queue_properties(struct tb_xdomain *xd) +{ + xd->state = XDOMAIN_STATE_PROPERTIES; + xd->state_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); +} + +static void tb_xdomain_queue_properties_changed(struct tb_xdomain *xd) +{ + xd->properties_changed_retries = XDOMAIN_RETRIES; + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); +} + +static void tb_xdomain_state_work(struct work_struct *work) +{ + struct tb_xdomain *xd = container_of(work, typeof(*xd), state_work.work); + int ret, state = xd->state; + + if (WARN_ON_ONCE(state < XDOMAIN_STATE_INIT || + state > XDOMAIN_STATE_ERROR)) + return; + + dev_dbg(&xd->dev, "running state %s\n", state_names[state]); + + switch (state) { + case XDOMAIN_STATE_INIT: + if (xd->needs_uuid) { + tb_xdomain_queue_uuid(xd); + } else { + tb_xdomain_queue_properties_changed(xd); + tb_xdomain_queue_properties(xd); + } + break; + + case XDOMAIN_STATE_UUID: + ret = tb_xdomain_get_uuid(xd); + if (ret) { + if (ret == -EAGAIN) + goto retry_state; + xd->state = XDOMAIN_STATE_ERROR; + } else { + tb_xdomain_queue_properties_changed(xd); + if (xd->bonding_possible) + tb_xdomain_queue_link_status(xd); + else + tb_xdomain_queue_properties(xd); + } + break; + + case XDOMAIN_STATE_LINK_STATUS: + ret = tb_xdomain_get_link_status(xd); + if (ret) { + if (ret == -EAGAIN) + goto retry_state; + + /* + * If any of the lane bonding states fail we skip + * bonding completely and try to continue from + * reading properties. + */ + tb_xdomain_queue_properties(xd); + } else { + tb_xdomain_queue_bonding(xd); + } + break; + + case XDOMAIN_STATE_LINK_STATE_CHANGE: + ret = tb_xdomain_link_state_change(xd, 2); + if (ret) { + if (ret == -EAGAIN) + goto retry_state; + tb_xdomain_queue_properties(xd); + } else { + tb_xdomain_queue_link_status2(xd); + } + break; + + case XDOMAIN_STATE_LINK_STATUS2: + ret = tb_xdomain_get_link_status(xd); + if (ret) { + if (ret == -EAGAIN) + goto retry_state; + tb_xdomain_queue_properties(xd); + } else { + tb_xdomain_queue_bonding_uuid_low(xd); + } + break; + + case XDOMAIN_STATE_BONDING_UUID_LOW: + tb_xdomain_lane_bonding_enable(xd); + tb_xdomain_queue_properties(xd); + break; + + case XDOMAIN_STATE_BONDING_UUID_HIGH: + if (tb_xdomain_bond_lanes_uuid_high(xd) == -EAGAIN) + goto retry_state; + tb_xdomain_queue_properties(xd); + break; + + case XDOMAIN_STATE_PROPERTIES: + ret = tb_xdomain_get_properties(xd); + if (ret) { + if (ret == -EAGAIN) + goto retry_state; + xd->state = XDOMAIN_STATE_ERROR; + } else { + xd->state = XDOMAIN_STATE_ENUMERATED; + } + break; + + case XDOMAIN_STATE_ENUMERATED: + tb_xdomain_queue_properties(xd); + break; + + case XDOMAIN_STATE_ERROR: + break; + + default: + dev_warn(&xd->dev, "unexpected state %d\n", state); + break; + } + + return; + +retry_state: + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); } static void tb_xdomain_properties_changed(struct work_struct *work) @@ -1163,13 +1650,13 @@ static void tb_xdomain_properties_changed(struct work_struct *work) "failed to send properties changed notification, retrying\n"); queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, - msecs_to_jiffies(1000)); + msecs_to_jiffies(XDOMAIN_DEFAULT_TIMEOUT)); } dev_err(&xd->dev, "failed to send properties changed notification\n"); return; } - xd->properties_changed_retries = XDOMAIN_PROPERTIES_CHANGED_RETRIES; + xd->properties_changed_retries = XDOMAIN_RETRIES; } static ssize_t device_show(struct device *dev, struct device_attribute *attr, @@ -1304,31 +1791,17 @@ static void tb_xdomain_release(struct device *dev) static void start_handshake(struct tb_xdomain *xd) { - xd->uuid_retries = XDOMAIN_UUID_RETRIES; - xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; - xd->properties_changed_retries = XDOMAIN_PROPERTIES_CHANGED_RETRIES; - - if (xd->needs_uuid) { - queue_delayed_work(xd->tb->wq, &xd->get_uuid_work, - msecs_to_jiffies(100)); - } else { - /* Start exchanging properties with the other host */ - queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, - msecs_to_jiffies(100)); - queue_delayed_work(xd->tb->wq, &xd->get_properties_work, - msecs_to_jiffies(1000)); - } + xd->state = XDOMAIN_STATE_INIT; + queue_delayed_work(xd->tb->wq, &xd->state_work, + msecs_to_jiffies(XDOMAIN_SHORT_TIMEOUT)); } static void stop_handshake(struct tb_xdomain *xd) { - xd->uuid_retries = 0; - xd->properties_retries = 0; - xd->properties_changed_retries = 0; - - cancel_delayed_work_sync(&xd->get_uuid_work); - cancel_delayed_work_sync(&xd->get_properties_work); cancel_delayed_work_sync(&xd->properties_changed_work); + cancel_delayed_work_sync(&xd->state_work); + xd->properties_changed_retries = 0; + xd->state_retries = 0; } static int __maybe_unused tb_xdomain_suspend(struct device *dev) @@ -1389,8 +1862,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, ida_init(&xd->in_hopids); ida_init(&xd->out_hopids); mutex_init(&xd->lock); - INIT_DELAYED_WORK(&xd->get_uuid_work, tb_xdomain_get_uuid); - INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties); + INIT_DELAYED_WORK(&xd->state_work, tb_xdomain_state_work); INIT_DELAYED_WORK(&xd->properties_changed_work, tb_xdomain_properties_changed); @@ -1405,6 +1877,7 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, goto err_free_local_uuid; } else { xd->needs_uuid = true; + xd->bonding_possible = !!down->dual_link_port; } device_initialize(&xd->dev); @@ -1523,9 +1996,9 @@ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) return ret; } - ret = tb_port_wait_for_link_width(port, 2, 100); + ret = tb_port_wait_for_link_width(port, 2, XDOMAIN_BONDING_TIMEOUT); if (ret) { - tb_port_warn(port, "timeout enabling lane bonding\n"); + tb_port_warn(port, "failed to enable lane bonding\n"); return ret; } diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 124e13cb1469..e13fe15e6a51 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -198,15 +198,15 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir); * @local_property_block_len: Length of the @local_property_block in dwords * @remote_properties: Properties exported by the remote domain * @remote_property_block_gen: Generation of @remote_properties - * @get_uuid_work: Work used to retrieve @remote_uuid - * @uuid_retries: Number of times left @remote_uuid is requested before - * giving up - * @get_properties_work: Work used to get remote domain properties - * @properties_retries: Number of times left to read properties + * @state: Next XDomain discovery state to run + * @state_work: Work used to run the next state + * @state_retries: Number of retries remain for the state * @properties_changed_work: Work used to notify the remote domain that * our properties have changed * @properties_changed_retries: Number of times left to send properties * changed notification + * @bonding_possible: True if lane bonding is possible on local side + * @target_link_width: Target link width from the remote host * @link: Root switch link the remote domain is connected (ICM only) * @depth: Depth in the chain the remote domain is connected (ICM only) * @@ -244,12 +244,13 @@ struct tb_xdomain { u32 local_property_block_len; struct tb_property_dir *remote_properties; u32 remote_property_block_gen; - struct delayed_work get_uuid_work; - int uuid_retries; - struct delayed_work get_properties_work; - int properties_retries; + int state; + struct delayed_work state_work; + int state_retries; struct delayed_work properties_changed_work; int properties_changed_retries; + bool bonding_possible; + u8 target_link_width; u8 link; u8 depth; }; -- cgit v1.2.3 From 1b30499ace5bdc1f5e84427f88d883784f61be4c Mon Sep 17 00:00:00 2001 From: Daniels Umanovskis Date: Tue, 26 Apr 2022 12:37:35 +0000 Subject: USB: serial: ftdi_sio: clean up printk format specifier The latency is an unsigned int and should be printed as such (even if it only holds values in the range 0..255). Signed-off-by: Daniels Umanovskis Link: https://lore.kernel.org/r/20220426123714.2000-1-du@axentia.se [ johan: amend commit message ] Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 49c08f07c969..b440d338a895 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1671,7 +1671,7 @@ static ssize_t latency_timer_show(struct device *dev, if (priv->flags & ASYNC_LOW_LATENCY) return sprintf(buf, "1\n"); else - return sprintf(buf, "%i\n", priv->latency); + return sprintf(buf, "%u\n", priv->latency); } /* Write a new value of the latency timer, in units of milliseconds. */ -- cgit v1.2.3 From 3120aac6d0ecd9accf56894aeac0e265f74d3d5a Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Thu, 5 May 2022 12:46:18 +0200 Subject: usb: dwc2: gadget: don't reset gadget's driver->bus UDC driver should not touch gadget's driver internals, especially it should not reset driver->bus. This wasn't harmful so far, but since commit fc274c1e9973 ("USB: gadget: Add a new bus for gadgets") gadget subsystem got it's own bus and messing with ->bus triggers the following NULL pointer dereference: dwc2 12480000.hsotg: bound driver g_ether 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000000 [00000000] *pgd=00000000 Internal error: Oops: 5 [#1] SMP ARM Modules linked in: ... CPU: 0 PID: 620 Comm: modprobe Not tainted 5.18.0-rc5-next-20220504 #11862 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at module_add_driver+0x44/0xe8 LR is at sysfs_do_create_link_sd+0x84/0xe0 ... Process modprobe (pid: 620, stack limit = 0x(ptrval)) ... module_add_driver from bus_add_driver+0xf4/0x1e4 bus_add_driver from driver_register+0x78/0x10c driver_register from usb_gadget_register_driver_owner+0x40/0xb4 usb_gadget_register_driver_owner from do_one_initcall+0x44/0x1e0 do_one_initcall from do_init_module+0x44/0x1c8 do_init_module from load_module+0x19b8/0x1b9c load_module from sys_finit_module+0xdc/0xfc sys_finit_module from ret_fast_syscall+0x0/0x54 Exception stack(0xf1771fa8 to 0xf1771ff0) ... dwc2 12480000.hsotg: new device is high-speed ---[ end trace 0000000000000000 ]--- Fix this by removing driver->bus entry reset. Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20220505104618.22729-1-m.szyprowski@samsung.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index eee3504397e6..fe2a58c75861 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4544,7 +4544,6 @@ static int dwc2_hsotg_udc_start(struct usb_gadget *gadget, WARN_ON(hsotg->driver); - driver->driver.bus = NULL; hsotg->driver = driver; hsotg->gadget.dev.of_node = hsotg->dev->of_node; hsotg->gadget.speed = USB_SPEED_UNKNOWN; -- cgit v1.2.3 From d7c90d9f9a5b6a85c09d37c5616d880d849a0c8b Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 4 May 2022 14:58:40 +0100 Subject: USB: gadget: Fix return of -EBUSY Currently when driver->match_existing_only is true, the error return is set to -EBUSY however ret is then set to 0 at the end of the if/else statement. I believe the ret = 0 statement should be set in the else part of the if statement and not at the end to ensure -EBUSY is being returned correctly. Detected by clang scan: drivers/usb/gadget/udc/core.c:1558:4: warning: Value stored to 'ret' is never read [deadcode.DeadStores] Fixes: fc274c1e9973 ("USB: gadget: Add a new bus for gadgets") Acked-by: Alan Stern Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20220504135840.232209-1-colin.i.king@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 61790592b2c8..3281d8a3dae7 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1559,8 +1559,8 @@ int usb_gadget_register_driver_owner(struct usb_gadget_driver *driver, } else { pr_info("%s: couldn't find an available UDC\n", driver->function); + ret = 0; } - ret = 0; } mutex_unlock(&udc_lock); -- cgit v1.2.3 From 0cb46f8c7c72cb56badcf1f6b5f0ba1138a0f98c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 28 Apr 2022 12:22:58 +0200 Subject: usb: ehci-omap: drop unused ehci_read() function After moving the omap1 platform into generalized multiplatform support on ARM, the kernel test robot points out a W=1 warning that now shows up in more configurations: drivers/usb/host/ehci-omap.c:64:19: warning: unused function 'ehci_read' The function was last used 9 years ago and can just be removed. Fixes: 87425ad36330 ("USB: ehci-omap: Remove PHY reset handling code") Reported-by: kernel test robot Acked-by: Alan Stern Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20220428102314.950323-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-omap.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 7f4a03e8647a..8c45bc17a580 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -61,11 +61,6 @@ static inline void ehci_write(void __iomem *base, u32 reg, u32 val) __raw_writel(val, base + reg); } -static inline u32 ehci_read(void __iomem *base, u32 reg) -{ - return __raw_readl(base + reg); -} - /* configure so an HC device and id are always provided */ /* always called with process context; sleeping is OK */ -- cgit v1.2.3 From f7ffaa6f62f6bca00f3e7b1cfe0cf1d1ab4306bb Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 28 Apr 2022 14:10:55 +0300 Subject: usb: core: acpi: Use the sysdev pointer instead of controller device The controller device (hcd) does not always have the ACPI companion assigned to it at all. We can not rely on it when finding the ACPI companion for the root hub. Instead we need to use the sysdev pointer. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220428111056.3558-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb-acpi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index bb1da35eb891..d4dcaefd0ea4 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -205,8 +205,11 @@ usb_acpi_find_companion_for_device(struct usb_device *udev) struct usb_hub *hub; if (!udev->parent) { - /* root hub is only child (_ADR=0) under its parent, the HC */ - adev = ACPI_COMPANION(udev->dev.parent); + /* + * root hub is only child (_ADR=0) under its parent, the HC. + * sysdev pointer is the HC as seen from firmware. + */ + adev = ACPI_COMPANION(udev->bus->sysdev); return acpi_find_child_device(adev, 0, false); } -- cgit v1.2.3 From 7fd069d65da2e20b1caec3b7bcf9dfbe28c04bb2 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 28 Apr 2022 14:10:56 +0300 Subject: usb: dwc3: host: Stop setting the ACPI companion It is no longer needed. The sysdev pointer is now used when assigning the ACPI companions to the xHCI ports and USB devices. Assigning the ACPI companion here resulted in the fwnode->secondary pointer to be replaced also for the parent dwc3 device since the primary fwnode (the ACPI companion) was shared. That was unintentional and it created potential side effects like resource leaks. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220428111056.3558-3-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/host.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index eda871973d6c..f56c30cf151e 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -7,7 +7,6 @@ * Authors: Felipe Balbi , */ -#include #include #include #include @@ -83,7 +82,6 @@ int dwc3_host_init(struct dwc3 *dwc) } xhci->dev.parent = dwc->dev; - ACPI_COMPANION_SET(&xhci->dev, ACPI_COMPANION(dwc->dev)); dwc->xhci = xhci; -- cgit v1.2.3 From 4ea150889ad70dfae540e761c485b28bd1009486 Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Mon, 2 May 2022 14:37:14 +0530 Subject: usb: dwc3: remove a possible unnecessary 'out of memory' message This patch removes a possible unnecessary out of memory message from core.c as reported by checkpatch.pl: WARNING: Possible unnecessary 'out of memory' message Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/SI2PR01MB392926A7AFB821410770D7CFF8C19@SI2PR01MB3929.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 2345a54b848b..7d023130e145 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -964,10 +964,8 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc) return; vals = kcalloc(ntype, sizeof(u32), GFP_KERNEL); - if (!vals) { - dev_err(dev, "Error to get memory\n"); + if (!vals) return; - } /* Get INCR burst type, and parse it */ ret = device_property_read_u32_array(dev, -- cgit v1.2.3 From ca05b38252d7bf6b0d42e80b3d3d98a642f4143b Mon Sep 17 00:00:00 2001 From: Piyush Mehta Date: Wed, 4 May 2022 13:23:09 +0530 Subject: usb: dwc3: xilinx: Add gpio-reset support This patch adds a USB GPIO based reset for dwc3-xilinx driver. The PHY needs to be reset after the completion of phy initialization. As part of the reset, check for gpio-reset binding before toggling the pin. This feature is advantageous when the user toggle GPIO to trigger the ULPI-PHY reset. Delay of milliseconds is added in between low and high to meet the setup and hold time requirement of the reset. The reset-gpio error handling is added for error notification. Some GPIO controllers must be accessed using message-based buses, like I2C or SPI, to address this problem, updates GPIO access with sleep API. This reset is specific to the zynqMp. Signed-off-by: Piyush Mehta Link: https://lore.kernel.org/r/20220504075309.6244-3-piyush.mehta@xilinx.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-xilinx.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index a6f3a9b38789..67b237c7a76a 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,7 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) { struct device *dev = priv_data->dev; struct reset_control *crst, *hibrst, *apbrst; + struct gpio_desc *reset_gpio; struct phy *usb3_phy; int ret = 0; u32 reg; @@ -201,6 +203,21 @@ static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data) } skip_usb3_phy: + /* ulpi reset via gpio-modepin or gpio-framework driver */ + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) { + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to request reset GPIO\n"); + } + + if (reset_gpio) { + /* Toggle ulpi to reset the phy. */ + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(5000, 10000); + } + /* * This routes the USB DMA traffic to go through FPD path instead * of reaching DDR directly. This traffic routing is needed to -- cgit v1.2.3 From 69e131d1ac4e52a59ec181ab4f8aa8c48cd8fb64 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:22:31 -0700 Subject: usb: dwc3: gadget: Prevent repeat pullup() Don't do soft-disconnect if it's previously done. Likewise, don't do soft-connect if the device is currently connected and running. It would break normal operation. Currently the caller of pullup() (udc's sysfs soft_connect) only checks if it had initiated disconnect to prevent repeating soft-disconnect. It doesn't check for soft-connect. To be safe, let's keep the check here regardless whether the udc core is fixed. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/1c1345bd66c97a9d32f77d63aaadd04b7b037143.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 5b1d79528da3..e9c992be8472 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2514,6 +2514,10 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) int ret; is_on = !!is_on; + + if (dwc->pullups_connected == is_on) + return 0; + dwc->softconnect = is_on; /* * Per databook, when we want to stop the gadget, if a control transfer -- cgit v1.2.3 From 861c010a2ee1bc4a66d23f0da4aa22e75d8eaa24 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:22:38 -0700 Subject: usb: dwc3: gadget: Refactor pullup() Move soft-disconnect sequence out of dwc3_gadget_pullup(). No functional change here. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/4c0f259b17d95acaaa931f90276683a48a32fe22.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 65 ++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e9c992be8472..a53460eae789 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2507,6 +2507,40 @@ static void dwc3_gadget_disable_irq(struct dwc3 *dwc); static void __dwc3_gadget_stop(struct dwc3 *dwc); static int __dwc3_gadget_start(struct dwc3 *dwc); +static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) +{ + u32 count; + + dwc->connected = false; + + /* + * In the Synopsys DesignWare Cores USB3 Databook Rev. 3.30a + * Section 4.1.8 Table 4-7, it states that for a device-initiated + * disconnect, the SW needs to ensure that it sends "a DEPENDXFER + * command for any active transfers" before clearing the RunStop + * bit. + */ + dwc3_stop_active_transfers(dwc); + __dwc3_gadget_stop(dwc); + + /* + * In the Synopsys DesignWare Cores USB3 Databook Rev. 3.30a + * Section 1.3.4, it mentions that for the DEVCTRLHLT bit, the + * "software needs to acknowledge the events that are generated + * (by writing to GEVNTCOUNTn) while it is waiting for this bit + * to be set to '1'." + */ + count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0)); + count &= DWC3_GEVNTCOUNT_MASK; + if (count > 0) { + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count); + dwc->ev_buf->lpos = (dwc->ev_buf->lpos + count) % + dwc->ev_buf->length; + } + + return dwc3_gadget_run_stop(dwc, false, false); +} + static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) { struct dwc3 *dwc = gadget_to_dwc(g); @@ -2563,33 +2597,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) spin_lock_irqsave(&dwc->lock, flags); if (!is_on) { - u32 count; - - dwc->connected = false; - /* - * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a - * Section 4.1.8 Table 4-7, it states that for a device-initiated - * disconnect, the SW needs to ensure that it sends "a DEPENDXFER - * command for any active transfers" before clearing the RunStop - * bit. - */ - dwc3_stop_active_transfers(dwc); - __dwc3_gadget_stop(dwc); - - /* - * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a - * Section 1.3.4, it mentions that for the DEVCTRLHLT bit, the - * "software needs to acknowledge the events that are generated - * (by writing to GEVNTCOUNTn) while it is waiting for this bit - * to be set to '1'." - */ - count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0)); - count &= DWC3_GEVNTCOUNT_MASK; - if (count > 0) { - dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count); - dwc->ev_buf->lpos = (dwc->ev_buf->lpos + count) % - dwc->ev_buf->length; - } + ret = dwc3_gadget_soft_disconnect(dwc); } else { /* * In the Synopsys DWC_usb31 1.90a programming guide section @@ -2603,9 +2611,8 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) dwc3_event_buffers_setup(dwc); __dwc3_gadget_start(dwc); + ret = dwc3_gadget_run_stop(dwc, true, false); } - - ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); enable_irq(dwc->irq_gadget); -- cgit v1.2.3 From 8f8034f493b5eb1ad21ff392fd30c0cf9e71f73f Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:22:44 -0700 Subject: usb: dwc3: gadget: Don't modify GEVNTCOUNT in pullup() If the GEVNTCOUNT indicates events in the event buffer, the driver needs to acknowledge them before the controller can halt. Simply let the interrupt handler acknowledges the remaining event generated by the controller while polling for DSTS.DEVCTLHLT. This avoids disabling irq and taking care of race condition between the interrupt handlers and pullup(). Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/ea306ec93c41ccafbdb5d16404ff3b6eca299613.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index a53460eae789..3c0a6c83ea43 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2509,8 +2509,9 @@ static int __dwc3_gadget_start(struct dwc3 *dwc); static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) { - u32 count; + unsigned long flags; + spin_lock_irqsave(&dwc->lock, flags); dwc->connected = false; /* @@ -2522,29 +2523,21 @@ static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) */ dwc3_stop_active_transfers(dwc); __dwc3_gadget_stop(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); /* - * In the Synopsys DesignWare Cores USB3 Databook Rev. 3.30a - * Section 1.3.4, it mentions that for the DEVCTRLHLT bit, the - * "software needs to acknowledge the events that are generated - * (by writing to GEVNTCOUNTn) while it is waiting for this bit - * to be set to '1'." + * Note: if the GEVNTCOUNT indicates events in the event buffer, the + * driver needs to acknowledge them before the controller can halt. + * Simply let the interrupt handler acknowledges and handle the + * remaining event generated by the controller while polling for + * DSTS.DEVCTLHLT. */ - count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0)); - count &= DWC3_GEVNTCOUNT_MASK; - if (count > 0) { - dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), count); - dwc->ev_buf->lpos = (dwc->ev_buf->lpos + count) % - dwc->ev_buf->length; - } - return dwc3_gadget_run_stop(dwc, false, false); } static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) { struct dwc3 *dwc = gadget_to_dwc(g); - unsigned long flags; int ret; is_on = !!is_on; @@ -2588,14 +2581,6 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) return 0; } - /* - * Synchronize and disable any further event handling while controller - * is being enabled/disabled. - */ - disable_irq(dwc->irq_gadget); - - spin_lock_irqsave(&dwc->lock, flags); - if (!is_on) { ret = dwc3_gadget_soft_disconnect(dwc); } else { @@ -2605,16 +2590,12 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) * device-initiated disconnect requires a core soft reset * (DCTL.CSftRst) before enabling the run/stop bit. */ - spin_unlock_irqrestore(&dwc->lock, flags); dwc3_core_soft_reset(dwc); - spin_lock_irqsave(&dwc->lock, flags); dwc3_event_buffers_setup(dwc); __dwc3_gadget_start(dwc); ret = dwc3_gadget_run_stop(dwc, true, false); } - spin_unlock_irqrestore(&dwc->lock, flags); - enable_irq(dwc->irq_gadget); pm_runtime_put(dwc->dev); -- cgit v1.2.3 From c96683798e272366866a5c0ce3073c0b5a256db7 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:22:50 -0700 Subject: usb: dwc3: ep0: Don't prepare beyond Setup stage Since we can't guarantee that the host won't send new Setup packet before going through the device-initiated disconnect, don't prepare beyond the Setup stage and keep the device in EP0_SETUP_PHASE. This ensures that the device-initated disconnect sequence can go through gracefully. Note that the controller won't service the End Transfer command if it can't DMA out the Setup packet. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/6bacec56ecabb2c6e49a09cedfcac281fdc97de0.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/ep0.c | 2 +- drivers/usb/dwc3/gadget.c | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index aa8476da222d..6615f641b34f 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -813,7 +813,7 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, int ret = -EINVAL; u32 len; - if (!dwc->gadget_driver) + if (!dwc->gadget_driver || !dwc->connected) goto out; trace_dwc3_ctrl_req(ctrl); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3c0a6c83ea43..5e3311e3cc16 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2514,6 +2514,23 @@ static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) spin_lock_irqsave(&dwc->lock, flags); dwc->connected = false; + /* + * Per databook, when we want to stop the gadget, if a control transfer + * is still in process, complete it and get the core into setup phase. + */ + if (dwc->ep0state != EP0_SETUP_PHASE) { + int ret; + + reinit_completion(&dwc->ep0_in_setup); + + spin_unlock_irqrestore(&dwc->lock, flags); + ret = wait_for_completion_timeout(&dwc->ep0_in_setup, + msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT)); + spin_lock_irqsave(&dwc->lock, flags); + if (ret == 0) + dev_warn(dwc->dev, "timed out waiting for SETUP phase\n"); + } + /* * In the Synopsys DesignWare Cores USB3 Databook Rev. 3.30a * Section 4.1.8 Table 4-7, it states that for a device-initiated @@ -2546,18 +2563,6 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) return 0; dwc->softconnect = is_on; - /* - * Per databook, when we want to stop the gadget, if a control transfer - * is still in process, complete it and get the core into setup phase. - */ - if (!is_on && dwc->ep0state != EP0_SETUP_PHASE) { - reinit_completion(&dwc->ep0_in_setup); - - ret = wait_for_completion_timeout(&dwc->ep0_in_setup, - msecs_to_jiffies(DWC3_PULL_UP_TIMEOUT)); - if (ret == 0) - dev_warn(dwc->dev, "timed out waiting for SETUP phase\n"); - } /* * Avoid issuing a runtime resume if the device is already in the -- cgit v1.2.3 From ace17b6ee4f92ab0375d12a1b42494f8590a96b6 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:22:57 -0700 Subject: usb: dwc3: gadget: Only End Transfer for ep0 data phase The driver shouldn't be able to issue End Transfer to the control endpoint at anytime. Typically we should only do so in error cases such as invalid/unexpected direction of Data Phase as described in the control transfer flow of the programming guide. It _may_ end started data phase during controller deinitialization from soft disconnect or driver removal. However, that should not happen because the driver should be maintained in EP0_SETUP_PHASE during driver tear-down. On soft-connect, the controller should be reset from a soft-reset and there should be no issue starting the control endpoint. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/3c6643678863a26702e4115e9e19d7d94a30d49c.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 5e3311e3cc16..4f445f836d32 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3685,6 +3685,17 @@ static void dwc3_reset_gadget(struct dwc3 *dwc) void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt) { + struct dwc3 *dwc = dep->dwc; + + /* + * Only issue End Transfer command to the control endpoint of a started + * Data Phase. Typically we should only do so in error cases such as + * invalid/unexpected direction as described in the control transfer + * flow of the programming guide. + */ + if (dep->number <= 1 && dwc->ep0state != EP0_DATA_PHASE) + return; + if (!(dep->flags & DWC3_EP_TRANSFER_STARTED) || (dep->flags & DWC3_EP_DELAY_STOP) || (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) -- cgit v1.2.3 From f66eef8fb8989a7193cafc3870f7c7b2b97f16cb Mon Sep 17 00:00:00 2001 From: Thinh Nguyen Date: Thu, 21 Apr 2022 19:23:03 -0700 Subject: usb: dwc3: gadget: Delay issuing End Transfer If the controller hasn't DMA'ed the Setup data from its fifo, it won't process the End Transfer command. Polling for the command completion may block the driver from servicing the Setup phase and cause a timeout. Previously we only check and delay issuing End Transfer in the case of endpoint dequeue. Let's do that for all End Transfer scenarios. Signed-off-by: Thinh Nguyen Link: https://lore.kernel.org/r/2fcf3b5d90068d549589a57a27a79f76c6769b04.1650593829.git.Thinh.Nguyen@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 4f445f836d32..4f54f0ef21df 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2062,16 +2062,6 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, if (r == req) { struct dwc3_request *t; - /* - * If a Setup packet is received but yet to DMA out, the controller will - * not process the End Transfer command of any endpoint. Polling of its - * DEPCMD.CmdAct may block setting up TRB for Setup packet, causing a - * timeout. Delay issuing the End Transfer command until the Setup TRB is - * prepared. - */ - if (dwc->ep0state != EP0_SETUP_PHASE && !dwc->delayed_status) - dep->flags |= DWC3_EP_DELAY_STOP; - /* wait until it is processed */ dwc3_stop_active_transfer(dep, true, true); @@ -3701,6 +3691,18 @@ void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, (dep->flags & DWC3_EP_END_TRANSFER_PENDING)) return; + /* + * If a Setup packet is received but yet to DMA out, the controller will + * not process the End Transfer command of any endpoint. Polling of its + * DEPCMD.CmdAct may block setting up TRB for Setup packet, causing a + * timeout. Delay issuing the End Transfer command until the Setup TRB is + * prepared. + */ + if (dwc->ep0state != EP0_SETUP_PHASE && !dwc->delayed_status) { + dep->flags |= DWC3_EP_DELAY_STOP; + return; + } + /* * NOTICE: We are violating what the Databook says about the * EndTransfer command. Ideally we would _always_ wait for the -- cgit v1.2.3 From a350cfb6537f94be8a6423bc68a7a7883263c306 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Thu, 21 Apr 2022 23:14:23 +0200 Subject: usb: gadget: uvc: prevent index variables to start from 0 Some configfs variables like bDefaultFrameIndex are always starting by 1. This patch adds a check to prevent setting those variables to 0. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220421211427.3400834-4-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 63b8d3758b38..cffa615b6db7 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1565,6 +1565,12 @@ uvcg_uncompressed_##cname##_store(struct config_item *item, \ if (ret) \ goto end; \ \ + /* index values in uvc are never 0 */ \ + if (!num) { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ u->desc.aname = num; \ ret = len; \ end: \ @@ -1758,6 +1764,12 @@ uvcg_mjpeg_##cname##_store(struct config_item *item, \ if (ret) \ goto end; \ \ + /* index values in uvc are never 0 */ \ + if (!num) { \ + ret = -EINVAL; \ + goto end; \ + } \ + \ u->desc.aname = num; \ ret = len; \ end: \ -- cgit v1.2.3 From e2fa7b36de90de26da11b7f37fe0ce16935ab8a8 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Thu, 21 Apr 2022 23:14:24 +0200 Subject: usb: gadget: uvc: move structs to common header The functions and structs of the configfs interface should also be used by the uvc gadget driver. This patch prepares the stack by moving the common structs and functions to the common header file. Reviewed-by: Paul Elder Reviewed-by: Laurent Pinchart Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220421211427.3400834-5-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 1 - drivers/usb/gadget/function/uvc_configfs.c | 111 +--------------------------- drivers/usb/gadget/function/uvc_configfs.h | 114 ++++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 111 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 50e6e7a58b41..0334c8a414e9 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -24,7 +24,6 @@ #include #include -#include "u_uvc.h" #include "uvc.h" #include "uvc_configfs.h" #include "uvc_v4l2.h" diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index cffa615b6db7..e979a1bc54ce 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -10,17 +10,14 @@ * Author: Andrzej Pietrasiewicz */ -#include - -#include "u_uvc.h" #include "uvc_configfs.h" +#include + /* ----------------------------------------------------------------------------- * Global Utility Structures and Macros */ -#define UVCG_STREAMING_CONTROL_SIZE 1 - #define UVC_ATTR(prefix, cname, aname) \ static struct configfs_attribute prefix##attr_##cname = { \ .ca_name = __stringify(aname), \ @@ -49,12 +46,6 @@ static int uvcg_config_compare_u32(const void *l, const void *r) return li < ri ? -1 : li == ri ? 0 : 1; } -static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) -{ - return container_of(to_config_group(item), struct f_uvc_opts, - func_inst.group); -} - struct uvcg_config_group_type { struct config_item_type type; const char *name; @@ -125,19 +116,6 @@ static void uvcg_config_remove_children(struct config_group *group) * control/header */ -DECLARE_UVC_HEADER_DESCRIPTOR(1); - -struct uvcg_control_header { - struct config_item item; - struct UVC_HEADER_DESCRIPTOR(1) desc; - unsigned linked; -}; - -static struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) -{ - return container_of(item, struct uvcg_control_header, item); -} - #define UVCG_CTRL_HDR_ATTR(cname, aname, bits, limit) \ static ssize_t uvcg_control_header_##cname##_show( \ struct config_item *item, char *page) \ @@ -769,24 +747,6 @@ static const char * const uvcg_format_names[] = { "mjpeg", }; -enum uvcg_format_type { - UVCG_UNCOMPRESSED = 0, - UVCG_MJPEG, -}; - -struct uvcg_format { - struct config_group group; - enum uvcg_format_type type; - unsigned linked; - unsigned num_frames; - __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; -}; - -static struct uvcg_format *to_uvcg_format(struct config_item *item) -{ - return container_of(to_config_group(item), struct uvcg_format, group); -} - static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) { struct f_uvc_opts *opts; @@ -845,29 +805,11 @@ end: return ret; } -struct uvcg_format_ptr { - struct uvcg_format *fmt; - struct list_head entry; -}; - /* ----------------------------------------------------------------------------- * streaming/header/ * streaming/header */ -struct uvcg_streaming_header { - struct config_item item; - struct uvc_input_header_descriptor desc; - unsigned linked; - struct list_head formats; - unsigned num_fmt; -}; - -static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) -{ - return container_of(item, struct uvcg_streaming_header, item); -} - static void uvcg_format_set_indices(struct config_group *fmt); static int uvcg_streaming_header_allow_link(struct config_item *src, @@ -1059,31 +1001,6 @@ static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = { * streaming/// */ -struct uvcg_frame { - struct config_item item; - enum uvcg_format_type fmt_type; - struct { - u8 b_length; - u8 b_descriptor_type; - u8 b_descriptor_subtype; - u8 b_frame_index; - u8 bm_capabilities; - u16 w_width; - u16 w_height; - u32 dw_min_bit_rate; - u32 dw_max_bit_rate; - u32 dw_max_video_frame_buffer_size; - u32 dw_default_frame_interval; - u8 b_frame_interval_type; - } __attribute__((packed)) frame; - u32 *dw_frame_interval; -}; - -static struct uvcg_frame *to_uvcg_frame(struct config_item *item) -{ - return container_of(item, struct uvcg_frame, item); -} - #define UVCG_FRAME_ATTR(cname, aname, bits) \ static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\ { \ @@ -1420,18 +1337,6 @@ static void uvcg_format_set_indices(struct config_group *fmt) * streaming/uncompressed/ */ -struct uvcg_uncompressed { - struct uvcg_format fmt; - struct uvc_format_uncompressed desc; -}; - -static struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) -{ - return container_of( - container_of(to_config_group(item), struct uvcg_format, group), - struct uvcg_uncompressed, fmt); -} - static struct configfs_group_operations uvcg_uncompressed_group_ops = { .make_item = uvcg_frame_make, .drop_item = uvcg_frame_drop, @@ -1675,18 +1580,6 @@ static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = { * streaming/mjpeg/ */ -struct uvcg_mjpeg { - struct uvcg_format fmt; - struct uvc_format_mjpeg desc; -}; - -static struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) -{ - return container_of( - container_of(to_config_group(item), struct uvcg_format, group), - struct uvcg_mjpeg, fmt); -} - static struct configfs_group_operations uvcg_mjpeg_group_ops = { .make_item = uvcg_frame_make, .drop_item = uvcg_frame_drop, diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 7e1d7ca29bf2..1ec8529ff1e4 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -12,7 +12,119 @@ #ifndef UVC_CONFIGFS_H #define UVC_CONFIGFS_H -struct f_uvc_opts; +#include + +#include "u_uvc.h" + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uvc_opts, + func_inst.group); +} + +#define UVCG_STREAMING_CONTROL_SIZE 1 + +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +struct uvcg_control_header { + struct config_item item; + struct UVC_HEADER_DESCRIPTOR(1) desc; + unsigned linked; +}; + +static inline struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +{ + return container_of(item, struct uvcg_control_header, item); +} + +enum uvcg_format_type { + UVCG_UNCOMPRESSED = 0, + UVCG_MJPEG, +}; + +struct uvcg_format { + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; +}; + +struct uvcg_format_ptr { + struct uvcg_format *fmt; + struct list_head entry; +}; + +static inline struct uvcg_format *to_uvcg_format(struct config_item *item) +{ + return container_of(to_config_group(item), struct uvcg_format, group); +} + +struct uvcg_streaming_header { + struct config_item item; + struct uvc_input_header_descriptor desc; + unsigned linked; + struct list_head formats; + unsigned num_fmt; +}; + +static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +{ + return container_of(item, struct uvcg_streaming_header, item); +} + +struct uvcg_frame { + struct config_item item; + enum uvcg_format_type fmt_type; + struct { + u8 b_length; + u8 b_descriptor_type; + u8 b_descriptor_subtype; + u8 b_frame_index; + u8 bm_capabilities; + u16 w_width; + u16 w_height; + u32 dw_min_bit_rate; + u32 dw_max_bit_rate; + u32 dw_max_video_frame_buffer_size; + u32 dw_default_frame_interval; + u8 b_frame_interval_type; + } __attribute__((packed)) frame; + u32 *dw_frame_interval; +}; + +static inline struct uvcg_frame *to_uvcg_frame(struct config_item *item) +{ + return container_of(item, struct uvcg_frame, item); +} + +/* ----------------------------------------------------------------------------- + * streaming/uncompressed/ + */ + +struct uvcg_uncompressed { + struct uvcg_format fmt; + struct uvc_format_uncompressed desc; +}; + +static inline struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_uncompressed, fmt); +} + +/* ----------------------------------------------------------------------------- + * streaming/mjpeg/ + */ + +struct uvcg_mjpeg { + struct uvcg_format fmt; + struct uvc_format_mjpeg desc; +}; + +static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +{ + return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt); +} int uvcg_attach_configfs(struct f_uvc_opts *opts); -- cgit v1.2.3 From a2673d570bd648851e00ac531bdf695f4ebb4f78 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Thu, 21 Apr 2022 23:14:25 +0200 Subject: usb: gadget: uvc: track frames in format entries Just like the header is tracking the formats in a linked list, in this patch we track the frames in a linked list of the formats. It simplifies the parsing of the configfs structure. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20220421211427.3400834-6-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 25 ++++++++++++++++++++++++- drivers/usb/gadget/function/uvc_configfs.h | 6 ++++++ 2 files changed, 30 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index e979a1bc54ce..e5a6b6e36b3d 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1262,6 +1262,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, struct uvcg_format *fmt; struct f_uvc_opts *opts; struct config_item *opts_item; + struct uvcg_frame_ptr *frame_ptr; h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) @@ -1292,6 +1293,16 @@ static struct config_item *uvcg_frame_make(struct config_group *group, kfree(h); return ERR_PTR(-EINVAL); } + + frame_ptr = kzalloc(sizeof(*frame_ptr), GFP_KERNEL); + if (!frame_ptr) { + mutex_unlock(&opts->lock); + kfree(h); + return ERR_PTR(-ENOMEM); + } + + frame_ptr->frm = h; + list_add_tail(&frame_ptr->entry, &fmt->frames); ++fmt->num_frames; mutex_unlock(&opts->lock); @@ -1305,13 +1316,23 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item struct uvcg_format *fmt; struct f_uvc_opts *opts; struct config_item *opts_item; + struct uvcg_frame *target_frm = NULL; + struct uvcg_frame_ptr *frame_ptr, *tmp; opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); mutex_lock(&opts->lock); + target_frm = container_of(item, struct uvcg_frame, item); fmt = to_uvcg_format(&group->cg_item); - --fmt->num_frames; + + list_for_each_entry_safe(frame_ptr, tmp, &fmt->frames, entry) + if (frame_ptr->frm == target_frm) { + list_del(&frame_ptr->entry); + kfree(frame_ptr); + --fmt->num_frames; + break; + } mutex_unlock(&opts->lock); config_item_put(item); @@ -1556,6 +1577,7 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, h->desc.bmInterfaceFlags = 0; h->desc.bCopyProtect = 0; + INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_UNCOMPRESSED; config_group_init_type_name(&h->fmt.group, name, &uvcg_uncompressed_type); @@ -1736,6 +1758,7 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, h->desc.bmInterfaceFlags = 0; h->desc.bCopyProtect = 0; + INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_MJPEG; config_group_init_type_name(&h->fmt.group, name, &uvcg_mjpeg_type); diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 1ec8529ff1e4..ad2ec8c4c78c 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -46,6 +46,7 @@ struct uvcg_format { struct config_group group; enum uvcg_format_type type; unsigned linked; + struct list_head frames; unsigned num_frames; __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; }; @@ -73,6 +74,11 @@ static inline struct uvcg_streaming_header *to_uvcg_streaming_header(struct conf return container_of(item, struct uvcg_streaming_header, item); } +struct uvcg_frame_ptr { + struct uvcg_frame *frm; + struct list_head entry; +}; + struct uvcg_frame { struct config_item item; enum uvcg_format_type fmt_type; -- cgit v1.2.3 From f9d76d15072caf1ec5558fa7cc6d93c7b9d33488 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 10 May 2022 11:51:29 -0400 Subject: USB: gadget: Add ID numbers to gadget names Putting USB gadgets on a new bus of their own encounters a problem when multiple gadgets are present: They all have the same name! The driver core fails with a "sys: cannot create duplicate filename" error when creating any of the /sys/bus/gadget/devices/ symbolic links after the first. This patch fixes the problem by adding a ".N" suffix to each gadget's name when the gadget is registered (where N is a unique ID number), thus making the names distinct. Reported-and-tested-by: Geert Uytterhoeven Signed-off-by: Alan Stern Reviewed-by: Geert Uytterhoeven Fixes: fc274c1e9973 ("USB: gadget: Add a new bus for gadgets") Link: https://lore.kernel.org/r/YnqKAXKyp9Vq/pbn@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 16 ++++++++++++++-- include/linux/usb/gadget.h | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 3281d8a3dae7..7886497253cc 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,8 @@ #include "trace.h" +static DEFINE_IDA(gadget_id_numbers); + static struct bus_type gadget_bus_type; /** @@ -1248,7 +1251,6 @@ static void usb_udc_nop_release(struct device *dev) void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) { - dev_set_name(&gadget->dev, "gadget"); INIT_WORK(&gadget->work, usb_gadget_state_work); gadget->dev.parent = parent; @@ -1304,12 +1306,21 @@ int usb_add_gadget(struct usb_gadget *gadget) usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); udc->vbus = true; + ret = ida_alloc(&gadget_id_numbers, GFP_KERNEL); + if (ret < 0) + goto err_del_udc; + gadget->id_number = ret; + dev_set_name(&gadget->dev, "gadget.%d", ret); + ret = device_add(&gadget->dev); if (ret) - goto err_del_udc; + goto err_free_id; return 0; + err_free_id: + ida_free(&gadget_id_numbers, gadget->id_number); + err_del_udc: flush_work(&gadget->work); device_del(&udc->dev); @@ -1417,6 +1428,7 @@ void usb_del_gadget(struct usb_gadget *gadget) kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); device_del(&gadget->dev); + ida_free(&gadget_id_numbers, gadget->id_number); device_unregister(&udc->dev); } EXPORT_SYMBOL_GPL(usb_del_gadget); diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index cf7af8a0a6e9..3ad58b7a0824 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -386,6 +386,7 @@ struct usb_gadget_ops { * @lpm_capable: If the gadget max_speed is FULL or HIGH, this flag * indicates that it supports LPM as per the LPM ECN & errata. * @irq: the interrupt number for device controller. + * @id_number: a unique ID number for ensuring that gadget names are distinct * * Gadgets have a mostly-portable "gadget driver" implementing device * functions, handling all usb configurations and interfaces. Gadget @@ -446,6 +447,7 @@ struct usb_gadget { unsigned connected:1; unsigned lpm_capable:1; int irq; + int id_number; }; #define work_to_gadget(w) (container_of((w), struct usb_gadget, work)) -- cgit v1.2.3 From 93bf344f66995ef816bd63c165fa9ac1ea4fcb3d Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Mon, 9 May 2022 23:49:03 +0300 Subject: thunderbolt: Fix buffer allocation of devices with no DisplayPort adapters For the case of a device without DisplayPort adapters we calculate incorrectly the amount of buffers. Fix the calculation for this case. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 8ccd70920b6a..2c3cf7fc3357 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -102,8 +102,11 @@ static unsigned int tb_available_credits(const struct tb_port *port, * Maximum number of DP streams possible through the * lane adapter. */ - ndp = (credits - (usb3 + pcie + spare)) / - (sw->min_dp_aux_credits + sw->min_dp_main_credits); + if (sw->min_dp_aux_credits + sw->min_dp_main_credits) + ndp = (credits - (usb3 + pcie + spare)) / + (sw->min_dp_aux_credits + sw->min_dp_main_credits); + else + ndp = 0; } else { ndp = 0; } -- cgit v1.2.3 From c7c99a09ef0e2615d13e13b19c74428ca43b7dcf Mon Sep 17 00:00:00 2001 From: Gil Fine Date: Mon, 9 May 2022 23:49:04 +0300 Subject: thunderbolt: Add KUnit test for devices with no DisplayPort adapters Add a KUnit test to check that buffer allocation works also for devices with no DP adapters. Signed-off-by: Gil Fine Signed-off-by: Mika Westerberg --- drivers/thunderbolt/test.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'drivers') diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 66b6e665e96f..99b30f2624fc 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -341,6 +341,47 @@ static struct tb_switch *alloc_dev_with_dpin(struct kunit *test, return sw; } +static struct tb_switch *alloc_dev_without_dp(struct kunit *test, + struct tb_switch *parent, + u64 route, bool bonded) +{ + struct tb_switch *sw; + int i; + + sw = alloc_dev_default(test, parent, route, bonded); + if (!sw) + return NULL; + /* + * Device with: + * 2x USB4 Adapters (adapters 1,2 and 3,4), + * 1x PCIe Upstream (adapter 9), + * 1x PCIe Downstream (adapter 10), + * 1x USB3 Upstream (adapter 16), + * 1x USB3 Downstream (adapter 17) + */ + for (i = 5; i <= 8; i++) + sw->ports[i].disabled = true; + + for (i = 11; i <= 14; i++) + sw->ports[i].disabled = true; + + sw->ports[13].cap_adap = 0; + sw->ports[14].cap_adap = 0; + + for (i = 18; i <= 19; i++) + sw->ports[i].disabled = true; + + sw->generation = 4; + sw->credit_allocation = true; + sw->max_usb3_credits = 109; + sw->min_dp_aux_credits = 0; + sw->min_dp_main_credits = 0; + sw->max_pcie_credits = 30; + sw->max_dma_credits = 1; + + return sw; +} + static struct tb_switch *alloc_dev_usb4(struct kunit *test, struct tb_switch *parent, u64 route, bool bonded) @@ -1996,6 +2037,56 @@ static void tb_test_credit_alloc_pcie(struct kunit *test) tb_tunnel_free(tunnel); } +static void tb_test_credit_alloc_without_dp(struct kunit *test) +{ + struct tb_switch *host, *dev; + struct tb_port *up, *down; + struct tb_tunnel *tunnel; + struct tb_path *path; + + host = alloc_host_usb4(test); + dev = alloc_dev_without_dp(test, host, 0x1, true); + + /* + * The device has no DP therefore baMinDPmain = baMinDPaux = 0 + * + * Create PCIe path with buffers less than baMaxPCIe. + * + * For a device with buffers configurations: + * baMaxUSB3 = 109 + * baMinDPaux = 0 + * baMinDPmain = 0 + * baMaxPCIe = 30 + * baMaxHI = 1 + * Remaining Buffers = Total - (CP + DP) = 120 - (2 + 0) = 118 + * PCIe Credits = Max(6, Min(baMaxPCIe, Remaining Buffers - baMaxUSB3) + * = Max(6, Min(30, 9) = 9 + */ + down = &host->ports[8]; + up = &dev->ports[9]; + tunnel = tb_tunnel_alloc_pci(NULL, up, down); + KUNIT_ASSERT_TRUE(test, tunnel != NULL); + KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)2); + + /* PCIe downstream path */ + path = tunnel->paths[0]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 9U); + + /* PCIe upstream path */ + path = tunnel->paths[1]; + KUNIT_ASSERT_EQ(test, path->path_length, 2); + KUNIT_EXPECT_EQ(test, path->hops[0].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[0].initial_credits, 7U); + KUNIT_EXPECT_EQ(test, path->hops[1].nfc_credits, 0U); + KUNIT_EXPECT_EQ(test, path->hops[1].initial_credits, 64U); + + tb_tunnel_free(tunnel); +} + static void tb_test_credit_alloc_dp(struct kunit *test) { struct tb_switch *host, *dev; @@ -2709,6 +2800,7 @@ static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_credit_alloc_legacy_not_bonded), KUNIT_CASE(tb_test_credit_alloc_legacy_bonded), KUNIT_CASE(tb_test_credit_alloc_pcie), + KUNIT_CASE(tb_test_credit_alloc_without_dp), KUNIT_CASE(tb_test_credit_alloc_dp), KUNIT_CASE(tb_test_credit_alloc_usb3), KUNIT_CASE(tb_test_credit_alloc_dma), -- cgit v1.2.3 From d090c7a2ab84663185e4abda21d7d83880937c8a Mon Sep 17 00:00:00 2001 From: Kushagra Verma Date: Wed, 11 May 2022 21:19:36 +0530 Subject: USB / dwc3: Fix a checkpatch warning in core.c This patch fixes the following checkpatch.pl warning in core.c: WARNING: braces {} are not necessary for any arm of this statement Signed-off-by: Kushagra Verma Link: https://lore.kernel.org/r/TYZPR01MB39354534E2F9EE4E022DDAFFF8C89@TYZPR01MB3935.apcprd01.prod.exchangelabs.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 7d023130e145..c78205c5e19f 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1266,40 +1266,36 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) if (IS_ERR(dwc->usb2_phy)) { ret = PTR_ERR(dwc->usb2_phy); - if (ret == -ENXIO || ret == -ENODEV) { + if (ret == -ENXIO || ret == -ENODEV) dwc->usb2_phy = NULL; - } else { + else return dev_err_probe(dev, ret, "no usb2 phy configured\n"); - } } if (IS_ERR(dwc->usb3_phy)) { ret = PTR_ERR(dwc->usb3_phy); - if (ret == -ENXIO || ret == -ENODEV) { + if (ret == -ENXIO || ret == -ENODEV) dwc->usb3_phy = NULL; - } else { + else return dev_err_probe(dev, ret, "no usb3 phy configured\n"); - } } dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy"); if (IS_ERR(dwc->usb2_generic_phy)) { ret = PTR_ERR(dwc->usb2_generic_phy); - if (ret == -ENOSYS || ret == -ENODEV) { + if (ret == -ENOSYS || ret == -ENODEV) dwc->usb2_generic_phy = NULL; - } else { + else return dev_err_probe(dev, ret, "no usb2 phy configured\n"); - } } dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy"); if (IS_ERR(dwc->usb3_generic_phy)) { ret = PTR_ERR(dwc->usb3_generic_phy); - if (ret == -ENOSYS || ret == -ENODEV) { + if (ret == -ENOSYS || ret == -ENODEV) dwc->usb3_generic_phy = NULL; - } else { + else return dev_err_probe(dev, ret, "no usb3 phy configured\n"); - } } return 0; -- cgit v1.2.3 From 84db2ee2370332240a5e562779862b564fb9d49a Mon Sep 17 00:00:00 2001 From: Piyush Mehta Date: Tue, 10 May 2022 18:52:52 +0530 Subject: usb: host: ehci-xilinx: Fix quoted string split across lines This patch resolves checkpatch warnings for xilinx EHCI driver. Addressed warnings: WARNING: quoted string split across lines 50: FILE: drivers/usb/host/ehci-xilinx-of.c:50: + "The USB host controller does not support full speed " + "nor low speed devices\n"); WARNING: quoted string split across lines 53: FILE: drivers/usb/host/ehci-xilinx-of.c:53: + "You can reconfigure the host controller to have " + "full speed support\n"); Signed-off-by: Piyush Mehta Link: https://lore.kernel.org/r/20220510132252.26001-1-piyush.mehta@xilinx.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-xilinx-of.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index 67a6ee8cb5d8..13386c9d2820 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -46,11 +46,9 @@ static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum) dev_warn(hcd->self.controller, "Maybe your device is not a high speed device?\n"); dev_warn(hcd->self.controller, - "The USB host controller does not support full speed " - "nor low speed devices\n"); + "The USB host controller does not support full speed nor low speed devices\n"); dev_warn(hcd->self.controller, - "You can reconfigure the host controller to have " - "full speed support\n"); + "You can reconfigure the host controller to have full speed support\n"); } return 0; -- cgit v1.2.3 From b92d8a6e935fbd586397341ac1cb6762b041c6d5 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 10 May 2022 19:28:04 +0800 Subject: USB: c67x00: remove unnecessary check of res The resource is checked in probe function, so there is no need do this check in remove function. Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20220510112804.2401150-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/c67x00/c67x00-drv.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/c67x00/c67x00-drv.c b/drivers/usb/c67x00/c67x00-drv.c index 53838e7d4eef..6db5cb1b2dbb 100644 --- a/drivers/usb/c67x00/c67x00-drv.c +++ b/drivers/usb/c67x00/c67x00-drv.c @@ -189,14 +189,12 @@ static int c67x00_drv_remove(struct platform_device *pdev) c67x00_ll_release(c67x00); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (res) - free_irq(res->start, c67x00); + free_irq(res->start, c67x00); iounmap(c67x00->hpi.base); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res) - release_mem_region(res->start, resource_size(res)); + release_mem_region(res->start, resource_size(res)); kfree(c67x00); -- cgit v1.2.3 From b4b44897ad3f4c1ad4ffe910e7b141c61522726c Mon Sep 17 00:00:00 2001 From: Piyush Mehta Date: Fri, 6 May 2022 23:23:49 +0530 Subject: usb: gadget: udc: Remove useless variable assignment in xudc_read_fifo() This patch removes the unused variables assignment warning. Value assigned to variable bufferspace is overwritten, before it can be used. This makes such variable assignment useless. Reported Coverity warning: UNUSED_VALUE Signed-off-by: Piyush Mehta Link: https://lore.kernel.org/r/20220506175349.10102-1-piyush.mehta@xilinx.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/udc-xilinx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 428c755cf2e1..4827e3cd3834 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -632,7 +632,7 @@ top: dev_dbg(udc->dev, "read %s, %d bytes%s req %p %d/%d\n", ep->ep_usb.name, count, is_short ? "/S" : "", req, req->usb_req.actual, req->usb_req.length); - bufferspace -= count; + /* Completion */ if ((req->usb_req.actual == req->usb_req.length) || is_short) { if (udc->dma_enabled && req->usb_req.length) -- cgit v1.2.3 From 72e28b70499ac3b5d720c9cb9ad0d4229fdc5e0d Mon Sep 17 00:00:00 2001 From: Piyush Mehta Date: Mon, 9 May 2022 22:32:52 +0530 Subject: usb: host: ehci-xilinx: adding description for return value This patch resolves kernel-doc warnings to add return value description in function comments. Addressed warnings: drivers/usb/host/ehci-xilinx-of.c:37: warning: No description found for return value of 'ehci_xilinx_port_handed_over' drivers/usb/host/ehci-xilinx-of.c:117: warning: No description found for return value of 'ehci_hcd_xilinx_of_probe' drivers/usb/host/ehci-xilinx-of.c:201: warning: No description found for return value of 'ehci_hcd_xilinx_of_remove' Signed-off-by: Piyush Mehta Link: https://lore.kernel.org/r/20220509170252.28271-1-piyush.mehta@xilinx.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-xilinx-of.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index 13386c9d2820..3d7893747835 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -32,6 +32,8 @@ * There are cases when the host controller fails to enable the port due to, * for example, insufficient power that can be supplied to the device from * the USB bus. In those cases, the messages printed here are not helpful. + * + * Return: Always return 0 */ static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum) { @@ -110,6 +112,8 @@ static const struct hc_driver ehci_xilinx_of_hc_driver = { * host controller. Because the Xilinx USB host controller can be configured * as HS only or HS/FS only, it checks the configuration in the device tree * entry, and sets an appropriate value for hcd->has_tt. + * + * Return: zero on success, negative error code otherwise */ static int ehci_hcd_xilinx_of_probe(struct platform_device *op) { @@ -194,6 +198,8 @@ err_irq: * * Remove the hcd structure, and release resources that has been requested * during probe. + * + * Return: Always return 0 */ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) { -- cgit v1.2.3 From a44623d9279086c89f631201d993aa332f7c9e66 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Tue, 10 May 2022 14:46:29 +0530 Subject: usb: core: hcd: Add support for deferring roothub registration It has been observed with certain PCIe USB cards (like Inateck connected to AM64 EVM or J7200 EVM) that as soon as the primary roothub is registered, port status change is handled even before xHC is running leading to cold plug USB devices not detected. For such cases, registering both the root hubs along with the second HCD is required. Add support for deferring roothub registration in usb_add_hcd(), so that both primary and secondary roothubs are registered along with the second HCD. This patch has been added and reverted earier as it triggered a race in usb device enumeration. That race is now fixed in 5.16-rc3, and in stable back to 5.4 commit 6cca13de26ee ("usb: hub: Fix locking issues with address0_mutex") commit 6ae6dc22d2d1 ("usb: hub: Fix usb enumeration issue due to address0 race") CC: stable@vger.kernel.org # 5.4+ Suggested-by: Mathias Nyman Tested-by: Chris Chiu Acked-by: Alan Stern Signed-off-by: Kishon Vijay Abraham I Link: https://lore.kernel.org/r/20220510091630.16564-2-kishon@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 29 +++++++++++++++++++++++------ include/linux/usb/hcd.h | 2 ++ 2 files changed, 25 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index d9712c2602af..06eea8848ccc 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2816,6 +2816,7 @@ int usb_add_hcd(struct usb_hcd *hcd, { int retval; struct usb_device *rhdev; + struct usb_hcd *shared_hcd; if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); @@ -2976,13 +2977,26 @@ int usb_add_hcd(struct usb_hcd *hcd, goto err_hcd_driver_start; } + /* starting here, usbcore will pay attention to the shared HCD roothub */ + shared_hcd = hcd->shared_hcd; + if (!usb_hcd_is_primary_hcd(hcd) && shared_hcd && HCD_DEFER_RH_REGISTER(shared_hcd)) { + retval = register_root_hub(shared_hcd); + if (retval != 0) + goto err_register_root_hub; + + if (shared_hcd->uses_new_polling && HCD_POLL_RH(shared_hcd)) + usb_hcd_poll_rh_status(shared_hcd); + } + /* starting here, usbcore will pay attention to this root hub */ - retval = register_root_hub(hcd); - if (retval != 0) - goto err_register_root_hub; + if (!HCD_DEFER_RH_REGISTER(hcd)) { + retval = register_root_hub(hcd); + if (retval != 0) + goto err_register_root_hub; - if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) - usb_hcd_poll_rh_status(hcd); + if (hcd->uses_new_polling && HCD_POLL_RH(hcd)) + usb_hcd_poll_rh_status(hcd); + } return retval; @@ -3020,6 +3034,7 @@ EXPORT_SYMBOL_GPL(usb_add_hcd); void usb_remove_hcd(struct usb_hcd *hcd) { struct usb_device *rhdev = hcd->self.root_hub; + bool rh_registered; dev_info(hcd->self.controller, "remove, state %x\n", hcd->state); @@ -3030,6 +3045,7 @@ void usb_remove_hcd(struct usb_hcd *hcd) dev_dbg(hcd->self.controller, "roothub graceful disconnect\n"); spin_lock_irq (&hcd_root_hub_lock); + rh_registered = hcd->rh_registered; hcd->rh_registered = 0; spin_unlock_irq (&hcd_root_hub_lock); @@ -3039,7 +3055,8 @@ void usb_remove_hcd(struct usb_hcd *hcd) cancel_work_sync(&hcd->died_work); mutex_lock(&usb_bus_idr_lock); - usb_disconnect(&rhdev); /* Sets rhdev to NULL */ + if (rh_registered) + usb_disconnect(&rhdev); /* Sets rhdev to NULL */ mutex_unlock(&usb_bus_idr_lock); /* diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 548a028f2dab..2c1fc9212cf2 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -124,6 +124,7 @@ struct usb_hcd { #define HCD_FLAG_RH_RUNNING 5 /* root hub is running? */ #define HCD_FLAG_DEAD 6 /* controller has died? */ #define HCD_FLAG_INTF_AUTHORIZED 7 /* authorize interfaces? */ +#define HCD_FLAG_DEFER_RH_REGISTER 8 /* Defer roothub registration */ /* The flags can be tested using these macros; they are likely to * be slightly faster than test_bit(). @@ -134,6 +135,7 @@ struct usb_hcd { #define HCD_WAKEUP_PENDING(hcd) ((hcd)->flags & (1U << HCD_FLAG_WAKEUP_PENDING)) #define HCD_RH_RUNNING(hcd) ((hcd)->flags & (1U << HCD_FLAG_RH_RUNNING)) #define HCD_DEAD(hcd) ((hcd)->flags & (1U << HCD_FLAG_DEAD)) +#define HCD_DEFER_RH_REGISTER(hcd) ((hcd)->flags & (1U << HCD_FLAG_DEFER_RH_REGISTER)) /* * Specifies if interfaces are authorized by default -- cgit v1.2.3 From b7a4f9b5d0e4b6dd937678c546c0b322dd1a4054 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Tue, 10 May 2022 14:46:30 +0530 Subject: xhci: Set HCD flag to defer primary roothub registration Set "HCD_FLAG_DEFER_RH_REGISTER" to hcd->flags in xhci_run() to defer registering primary roothub in usb_add_hcd() if xhci has two roothubs. This will make sure both primary roothub and secondary roothub will be registered along with the second HCD. This is required for cold plugged USB devices to be detected in certain PCIe USB cards (like Inateck USB card connected to AM64 EVM or J7200 EVM). This patch has been added and reverted earier as it triggered a race in usb device enumeration. That race is now fixed in 5.16-rc3, and in stable back to 5.4 commit 6cca13de26ee ("usb: hub: Fix locking issues with address0_mutex") commit 6ae6dc22d2d1 ("usb: hub: Fix usb enumeration issue due to address0 race") [minor rebase change, and commit message update -Mathias] CC: stable@vger.kernel.org # 5.4+ Suggested-by: Mathias Nyman Tested-by: Chris Chiu Signed-off-by: Kishon Vijay Abraham I Link: https://lore.kernel.org/r/20220510091630.16564-3-kishon@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 25b87e99b4dd..2be38d9de8df 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -696,6 +696,8 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished xhci_run for USB2 roothub"); + set_bit(HCD_FLAG_DEFER_RH_REGISTER, &hcd->flags); + xhci_create_dbc_dev(xhci); xhci_debugfs_init(xhci); -- cgit v1.2.3 From 57f23cd0bf2f56d339f810a913a9c0c5abfcfc7e Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 12 May 2022 01:04:42 +0300 Subject: xhci: factor out parts of xhci_gen_setup() Factoring out parts of xhci_gen_setup() has two motivations: - When adding functionaliy to omit shared hcd if not needed in a subsequent patch, we'll have to call xhci_hcd_init_usb3_data() from two places. - It reduces size of xhci_gen_setup() and makes it better readable. Signed-off-by: Heiner Kallweit Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 104 +++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 50 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2be38d9de8df..9f801de6d9e0 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5209,6 +5209,57 @@ static int xhci_get_frame(struct usb_hcd *hcd) return readl(&xhci->run_regs->microframe_index) >> 3; } +static void xhci_hcd_init_usb2_data(struct xhci_hcd *xhci, struct usb_hcd *hcd) +{ + xhci->usb2_rhub.hcd = hcd; + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + /* + * USB 2.0 roothub under xHCI has an integrated TT, + * (rate matching hub) as opposed to having an OHCI/UHCI + * companion controller. + */ + hcd->has_tt = 1; +} + +static void xhci_hcd_init_usb3_data(struct xhci_hcd *xhci, struct usb_hcd *hcd) +{ + unsigned int minor_rev; + + /* + * Early xHCI 1.1 spec did not mention USB 3.1 capable hosts + * should return 0x31 for sbrn, or that the minor revision + * is a two digit BCD containig minor and sub-minor numbers. + * This was later clarified in xHCI 1.2. + * + * Some USB 3.1 capable hosts therefore have sbrn 0x30, and + * minor revision set to 0x1 instead of 0x10. + */ + if (xhci->usb3_rhub.min_rev == 0x1) + minor_rev = 1; + else + minor_rev = xhci->usb3_rhub.min_rev / 0x10; + + switch (minor_rev) { + case 2: + hcd->speed = HCD_USB32; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + hcd->self.root_hub->rx_lanes = 2; + hcd->self.root_hub->tx_lanes = 2; + hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x2; + break; + case 1: + hcd->speed = HCD_USB31; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x1; + break; + } + xhci_info(xhci, "Host supports USB 3.%x %sSuperSpeed\n", + minor_rev, minor_rev ? "Enhanced " : ""); + + xhci->usb3_rhub.hcd = hcd; +} + int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) { struct xhci_hcd *xhci; @@ -5217,7 +5268,6 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) * quirks */ struct device *dev = hcd->self.sysdev; - unsigned int minor_rev; int retval; /* Accept arbitrarily long scatter-gather lists */ @@ -5232,60 +5282,14 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci = hcd_to_xhci(hcd); if (usb_hcd_is_primary_hcd(hcd)) { - xhci->main_hcd = hcd; - xhci->usb2_rhub.hcd = hcd; - /* Mark the first roothub as being USB 2.0. - * The xHCI driver will register the USB 3.0 roothub. - */ - hcd->speed = HCD_USB2; - hcd->self.root_hub->speed = USB_SPEED_HIGH; - /* - * USB 2.0 roothub under xHCI has an integrated TT, - * (rate matching hub) as opposed to having an OHCI/UHCI - * companion controller. - */ - hcd->has_tt = 1; + xhci_hcd_init_usb2_data(xhci, hcd); } else { - /* - * Early xHCI 1.1 spec did not mention USB 3.1 capable hosts - * should return 0x31 for sbrn, or that the minor revision - * is a two digit BCD containig minor and sub-minor numbers. - * This was later clarified in xHCI 1.2. - * - * Some USB 3.1 capable hosts therefore have sbrn 0x30, and - * minor revision set to 0x1 instead of 0x10. - */ - if (xhci->usb3_rhub.min_rev == 0x1) - minor_rev = 1; - else - minor_rev = xhci->usb3_rhub.min_rev / 0x10; - - switch (minor_rev) { - case 2: - hcd->speed = HCD_USB32; - hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; - hcd->self.root_hub->rx_lanes = 2; - hcd->self.root_hub->tx_lanes = 2; - hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x2; - break; - case 1: - hcd->speed = HCD_USB31; - hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; - hcd->self.root_hub->ssp_rate = USB_SSP_GEN_2x1; - break; - } - xhci_info(xhci, "Host supports USB 3.%x %sSuperSpeed\n", - minor_rev, - minor_rev ? "Enhanced " : ""); - - xhci->usb3_rhub.hcd = hcd; - /* xHCI private pointer was set in xhci_pci_probe for the second - * registered roothub. - */ + xhci_hcd_init_usb3_data(xhci, hcd); return 0; } mutex_init(&xhci->mutex); + xhci->main_hcd = hcd; xhci->cap_regs = hcd->regs; xhci->op_regs = hcd->regs + HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)); -- cgit v1.2.3 From 873f323618c202cd0675324a1027dcecc7745d0b Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 12 May 2022 01:04:43 +0300 Subject: xhci: prepare for operation w/o shared hcd This patch prepares xhci for the following scenario: - If either of the root hubs has no ports, then omit shared hcd - Main hcd can be USB3 if there are no USB2 ports Signed-off-by: Heiner Kallweit Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 3 ++- drivers/usb/host/xhci-mem.c | 11 +++++----- drivers/usb/host/xhci.c | 53 +++++++++++++++++++++++++++++---------------- drivers/usb/host/xhci.h | 26 ++++++++++++++++++++++ 4 files changed, 68 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index f65f1ba2b592..c54f2bc23d3f 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -707,6 +707,7 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, u16 wIndex, unsigned long *flags) __must_hold(&xhci->lock) { + struct usb_hcd *usb3_hcd = xhci_get_usb3_hcd(xhci); int i, retval; /* Disable all Device Slots */ @@ -727,7 +728,7 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci, xhci_dbg(xhci, "Disable all port (PP = 0)\n"); /* Power off USB3 ports*/ for (i = 0; i < xhci->usb3_rhub.num_ports; i++) - xhci_set_port_power(xhci, xhci->shared_hcd, i, false, flags); + xhci_set_port_power(xhci, usb3_hcd, i, false, flags); /* Power off USB2 ports*/ for (i = 0; i < xhci->usb2_rhub.num_ports; i++) xhci_set_port_power(xhci, xhci->main_hcd, i, false, flags); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index bbb27ee2c6a3..50bf64dcb186 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1072,7 +1072,7 @@ static u32 xhci_find_real_port_number(struct xhci_hcd *xhci, struct usb_hcd *hcd; if (udev->speed >= USB_SPEED_SUPER) - hcd = xhci->shared_hcd; + hcd = xhci_get_usb3_hcd(xhci); else hcd = xhci->main_hcd; @@ -2362,10 +2362,11 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) xhci->usb2_rhub.num_ports = USB_MAXCHILDREN; } - /* - * Note we could have all USB 3.0 ports, or all USB 2.0 ports. - * Not sure how the USB core will handle a hub with no ports... - */ + if (!xhci->usb2_rhub.num_ports) + xhci_info(xhci, "USB2 root hub has no ports\n"); + + if (!xhci->usb3_rhub.num_ports) + xhci_info(xhci, "USB3 root hub has no ports\n"); xhci_create_rhub_port_array(xhci, &xhci->usb2_rhub, flags); xhci_create_rhub_port_array(xhci, &xhci->usb3_rhub, flags); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9f801de6d9e0..80db65f19e47 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -486,6 +486,10 @@ static void compliance_mode_recovery(struct timer_list *t) xhci = from_timer(xhci, t, comp_mode_recovery_timer); rhub = &xhci->usb3_rhub; + hcd = rhub->hcd; + + if (!hcd) + return; for (i = 0; i < rhub->num_ports; i++) { temp = readl(rhub->ports[i]->addr); @@ -499,7 +503,6 @@ static void compliance_mode_recovery(struct timer_list *t) i + 1); xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, "Attempting compliance mode recovery"); - hcd = xhci->shared_hcd; if (hcd->state == HC_STATE_SUSPENDED) usb_hcd_resume_root_hub(hcd); @@ -612,14 +615,11 @@ static int xhci_run_finished(struct xhci_hcd *xhci) xhci_halt(xhci); return -ENODEV; } - xhci->shared_hcd->state = HC_STATE_RUNNING; xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; if (xhci->quirks & XHCI_NEC_HOST) xhci_ring_cmd_db(xhci); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Finished xhci_run for USB3 roothub"); return 0; } @@ -694,7 +694,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_free_command(xhci, command); } xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Finished xhci_run for USB2 roothub"); + "Finished %s for main hcd", __func__); set_bit(HCD_FLAG_DEFER_RH_REGISTER, &hcd->flags); @@ -702,6 +702,9 @@ int xhci_run(struct usb_hcd *hcd) xhci_debugfs_init(xhci); + if (xhci_has_one_roothub(xhci)) + return xhci_run_finished(xhci); + return 0; } EXPORT_SYMBOL_GPL(xhci_run); @@ -994,7 +997,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) return 0; if (hcd->state != HC_STATE_SUSPENDED || - xhci->shared_hcd->state != HC_STATE_SUSPENDED) + (xhci->shared_hcd && xhci->shared_hcd->state != HC_STATE_SUSPENDED)) return -EINVAL; /* Clear root port wake on bits if wakeup not allowed. */ @@ -1011,15 +1014,18 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) __func__, hcd->self.busnum); clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); del_timer_sync(&hcd->rh_timer); - clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); - del_timer_sync(&xhci->shared_hcd->rh_timer); + if (xhci->shared_hcd) { + clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + del_timer_sync(&xhci->shared_hcd->rh_timer); + } if (xhci->quirks & XHCI_SUSPEND_DELAY) usleep_range(1000, 1500); spin_lock_irq(&xhci->lock); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + if (xhci->shared_hcd) + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); /* step 1: stop endpoint */ /* skipped assuming that port suspend has done */ @@ -1119,7 +1125,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) msleep(100); set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - set_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); + if (xhci->shared_hcd) + set_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags); spin_lock_irq(&xhci->lock); @@ -1179,7 +1186,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) /* Let the USB core know _both_ roothubs lost power. */ usb_root_hub_lost_power(xhci->main_hcd->self.root_hub); - usb_root_hub_lost_power(xhci->shared_hcd->self.root_hub); + if (xhci->shared_hcd) + usb_root_hub_lost_power(xhci->shared_hcd->self.root_hub); xhci_dbg(xhci, "Stop HCD\n"); xhci_halt(xhci); @@ -1219,12 +1227,13 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "Start the primary HCD\n"); retval = xhci_run(hcd->primary_hcd); - if (!retval) { + if (!retval && secondary_hcd) { xhci_dbg(xhci, "Start the secondary HCD\n"); retval = xhci_run(secondary_hcd); } hcd->state = HC_STATE_SUSPENDED; - xhci->shared_hcd->state = HC_STATE_SUSPENDED; + if (xhci->shared_hcd) + xhci->shared_hcd->state = HC_STATE_SUSPENDED; goto done; } @@ -1262,7 +1271,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) } if (pending_portevent) { - usb_hcd_resume_root_hub(xhci->shared_hcd); + if (xhci->shared_hcd) + usb_hcd_resume_root_hub(xhci->shared_hcd); usb_hcd_resume_root_hub(hcd); } } @@ -1281,8 +1291,10 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) /* Re-enable port polling. */ xhci_dbg(xhci, "%s: starting usb%d port polling.\n", __func__, hcd->self.busnum); - set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); - usb_hcd_poll_rh_status(xhci->shared_hcd); + if (xhci->shared_hcd) { + set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); + usb_hcd_poll_rh_status(xhci->shared_hcd); + } set_bit(HCD_FLAG_POLL_RH, &hcd->flags); usb_hcd_poll_rh_status(hcd); @@ -5281,9 +5293,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci = hcd_to_xhci(hcd); - if (usb_hcd_is_primary_hcd(hcd)) { - xhci_hcd_init_usb2_data(xhci, hcd); - } else { + if (!usb_hcd_is_primary_hcd(hcd)) { xhci_hcd_init_usb3_data(xhci, hcd); return 0; } @@ -5364,6 +5374,11 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) return retval; xhci_dbg(xhci, "Called HCD init\n"); + if (xhci_hcd_is_usb3(hcd)) + xhci_hcd_init_usb3_data(xhci, hcd); + else + xhci_hcd_init_usb2_data(xhci, hcd); + xhci_info(xhci, "hcc params 0x%08x hci version 0x%x quirks 0x%016llx\n", xhci->hcc_params, xhci->hci_version, xhci->quirks); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 473a33ce299e..c792a3148e49 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1911,6 +1911,8 @@ struct xhci_hcd { unsigned hw_lpm_support:1; /* Broken Suspend flag for SNPS Suspend resume issue */ unsigned broken_suspend:1; + /* Indicates that omitting hcd is supported if root hub has no ports */ + unsigned allow_single_roothub:1; /* cached usb2 extened protocol capabilites */ u32 *ext_caps; unsigned int num_ext_caps; @@ -1966,6 +1968,30 @@ static inline struct usb_hcd *xhci_to_hcd(struct xhci_hcd *xhci) return xhci->main_hcd; } +static inline struct usb_hcd *xhci_get_usb3_hcd(struct xhci_hcd *xhci) +{ + if (xhci->shared_hcd) + return xhci->shared_hcd; + + if (!xhci->usb2_rhub.num_ports) + return xhci->main_hcd; + + return NULL; +} + +static inline bool xhci_hcd_is_usb3(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + return hcd == xhci_get_usb3_hcd(xhci); +} + +static inline bool xhci_has_one_roothub(struct xhci_hcd *xhci) +{ + return xhci->allow_single_roothub && + (!xhci->usb2_rhub.num_ports || !xhci->usb3_rhub.num_ports); +} + #define xhci_dbg(xhci, fmt, args...) \ dev_dbg(xhci_to_hcd(xhci)->self.controller , fmt , ## args) #define xhci_err(xhci, fmt, args...) \ -- cgit v1.2.3 From 0cf1ea040a7e2cae4776216d16d33d3898ea58de Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 12 May 2022 01:04:44 +0300 Subject: usb: host: xhci-plat: create shared hcd after having added main hcd This patch is in preparation of an extension where in case of a root hub with no ports no shared hcd will be created. Whether one of the root hubs has no ports we figure our in usb_add_hcd() for the primary hcd. Therefore create the shared hcd only after this call. Signed-off-by: Heiner Kallweit Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 649ffd861b44..5d752b384de2 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -283,12 +283,6 @@ static int xhci_plat_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); xhci->main_hcd = hcd; - xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, - dev_name(&pdev->dev), hcd); - if (!xhci->shared_hcd) { - ret = -ENOMEM; - goto disable_clk; - } /* imod_interval is the interrupt moderation value in nanoseconds. */ xhci->imod_interval = 40000; @@ -313,16 +307,16 @@ static int xhci_plat_probe(struct platform_device *pdev) if (IS_ERR(hcd->usb_phy)) { ret = PTR_ERR(hcd->usb_phy); if (ret == -EPROBE_DEFER) - goto put_usb3_hcd; + goto disable_clk; hcd->usb_phy = NULL; } else { ret = usb_phy_init(hcd->usb_phy); if (ret) - goto put_usb3_hcd; + goto disable_clk; } hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); - xhci->shared_hcd->tpl_support = hcd->tpl_support; + if (priv && (priv->quirks & XHCI_SKIP_PHY_INIT)) hcd->skip_phy_initialization = 1; @@ -333,12 +327,21 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto disable_usb_phy; + xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto dealloc_usb2_hcd; + } + + xhci->shared_hcd->tpl_support = hcd->tpl_support; + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) xhci->shared_hcd->can_do_streams = 1; ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); if (ret) - goto dealloc_usb2_hcd; + goto put_usb3_hcd; device_enable_async_suspend(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); @@ -352,15 +355,15 @@ static int xhci_plat_probe(struct platform_device *pdev) return 0; +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); + dealloc_usb2_hcd: usb_remove_hcd(hcd); disable_usb_phy: usb_phy_shutdown(hcd->usb_phy); -put_usb3_hcd: - usb_put_hcd(xhci->shared_hcd); - disable_clk: clk_disable_unprepare(xhci->clk); -- cgit v1.2.3 From e0fe986972f5b6b12086c73569206dd29c520be9 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 12 May 2022 01:04:45 +0300 Subject: usb: host: xhci-plat: prepare operation w/o shared hcd This patch prepares xhci-plat for the following scenario - If either of the root hubs has no ports, then omit shared hcd - Main hcd can be USB3 if there are no USB2 ports Signed-off-by: Heiner Kallweit Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 5d752b384de2..c512ec2148ae 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -180,7 +180,7 @@ static int xhci_plat_probe(struct platform_device *pdev) struct device *sysdev, *tmpdev; struct xhci_hcd *xhci; struct resource *res; - struct usb_hcd *hcd; + struct usb_hcd *hcd, *usb3_hcd; int ret; int irq; struct xhci_plat_priv *priv = NULL; @@ -327,21 +327,26 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto disable_usb_phy; - xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, - dev_name(&pdev->dev), hcd); - if (!xhci->shared_hcd) { - ret = -ENOMEM; - goto dealloc_usb2_hcd; - } + if (!xhci_has_one_roothub(xhci)) { + xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto dealloc_usb2_hcd; + } - xhci->shared_hcd->tpl_support = hcd->tpl_support; + xhci->shared_hcd->tpl_support = hcd->tpl_support; + } - if (HCC_MAX_PSA(xhci->hcc_params) >= 4) - xhci->shared_hcd->can_do_streams = 1; + usb3_hcd = xhci_get_usb3_hcd(xhci); + if (usb3_hcd && HCC_MAX_PSA(xhci->hcc_params) >= 4) + usb3_hcd->can_do_streams = 1; - ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); - if (ret) - goto put_usb3_hcd; + if (xhci->shared_hcd) { + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); + if (ret) + goto put_usb3_hcd; + } device_enable_async_suspend(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); -- cgit v1.2.3 From 4736ebd7fcaff1eb8481c140ba494962847d6e0a Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 12 May 2022 01:04:46 +0300 Subject: usb: host: xhci-plat: omit shared hcd if either root hub has no ports Activate the just added extension for xhci-plat and omit the shared hcd if either of the root hubs has no ports. Signed-off-by: Heiner Kallweit Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index c512ec2148ae..044855818cb1 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -245,6 +245,8 @@ static int xhci_plat_probe(struct platform_device *pdev) xhci = hcd_to_xhci(hcd); + xhci->allow_single_roothub = 1; + /* * Not all platforms have clks so it is not an error if the * clock do not exist. -- cgit v1.2.3 From 25355e046d295bc15bb0d235eff019da67e245d3 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 12 May 2022 01:04:47 +0300 Subject: xhci: use generic command timer for stop endpoint commands. The 'stop endpoint' command timer was started when a 'stop endpoint' command was added to the command queue. This can trigger unwanted timeouts if there are several pending commands in the queue that xHC needs to handle first. The generic command timer, which was added later than the 'stop endpoint' timeout timer, times each command currently being handled by xHC hardware. A timed out stop endpoint command was treated as a more severe issue than other failed commands, so the separate stop endpoint timer was left unchanged. Use the generic command timer for stop endpoint commands. Identify if the timed out command was a stop endpoint command in the generic handler, and treat it with the same severity as earlier. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 12 +---- drivers/usb/host/xhci-ring.c | 107 ++++++++++++++----------------------------- drivers/usb/host/xhci.c | 7 +-- drivers/usb/host/xhci.h | 2 - 4 files changed, 37 insertions(+), 91 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 50bf64dcb186..8c19e151a945 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -782,14 +782,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, /***************** Device context manipulation *************************/ -static void xhci_init_endpoint_timer(struct xhci_hcd *xhci, - struct xhci_virt_ep *ep) -{ - timer_setup(&ep->stop_cmd_timer, xhci_stop_endpoint_command_watchdog, - 0); - ep->xhci = xhci; -} - static void xhci_free_tt_info(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, int slot_id) @@ -994,11 +986,11 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, xhci_dbg(xhci, "Slot %d input ctx = 0x%llx (dma)\n", slot_id, (unsigned long long)dev->in_ctx->dma); - /* Initialize the cancellation list and watchdog timers for each ep */ + /* Initialize the cancellation and bandwidth list for each ep */ for (i = 0; i < 31; i++) { dev->eps[i].ep_index = i; dev->eps[i].vdev = dev; - xhci_init_endpoint_timer(xhci, &dev->eps[i]); + dev->eps[i].xhci = xhci; INIT_LIST_HEAD(&dev->eps[i].cancelled_td_list); INIT_LIST_HEAD(&dev->eps[i].bw_endpoint_list); } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f9707997969d..373e0e152854 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -740,14 +740,6 @@ static void td_to_noop(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, } } -static void xhci_stop_watchdog_timer_in_irq(struct xhci_hcd *xhci, - struct xhci_virt_ep *ep) -{ - ep->ep_state &= ~EP_STOP_CMD_PENDING; - /* Can't del_timer_sync in interrupt */ - del_timer(&ep->stop_cmd_timer); -} - /* * Must be called with xhci->lock held in interrupt context, * releases and re-acquires xhci->lock @@ -1122,18 +1114,17 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, reset_type); if (err) break; - xhci_stop_watchdog_timer_in_irq(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; return; case EP_STATE_RUNNING: /* Race, HW handled stop ep cmd before ep was running */ xhci_dbg(xhci, "Stop ep completion ctx error, ep is running\n"); command = xhci_alloc_command(xhci, false, GFP_ATOMIC); - if (!command) - xhci_stop_watchdog_timer_in_irq(xhci, ep); - - mod_timer(&ep->stop_cmd_timer, - jiffies + XHCI_STOP_EP_CMD_TIMEOUT * HZ); + if (!command) { + ep->ep_state &= ~EP_STOP_CMD_PENDING; + return; + } xhci_queue_stop_endpoint(xhci, command, slot_id, ep_index, 0); xhci_ring_cmd_db(xhci); @@ -1142,9 +1133,10 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, break; } } + /* will queue a set TR deq if stopped on a cancelled, uncleared TD */ xhci_invalidate_cancelled_tds(ep); - xhci_stop_watchdog_timer_in_irq(xhci, ep); + ep->ep_state &= ~EP_STOP_CMD_PENDING; /* Otherwise ring the doorbell(s) to restart queued transfers */ xhci_giveback_invalidated_tds(ep); @@ -1248,61 +1240,6 @@ void xhci_hc_died(struct xhci_hcd *xhci) usb_hc_died(xhci_to_hcd(xhci)); } -/* Watchdog timer function for when a stop endpoint command fails to complete. - * In this case, we assume the host controller is broken or dying or dead. The - * host may still be completing some other events, so we have to be careful to - * let the event ring handler and the URB dequeueing/enqueueing functions know - * through xhci->state. - * - * The timer may also fire if the host takes a very long time to respond to the - * command, and the stop endpoint command completion handler cannot delete the - * timer before the timer function is called. Another endpoint cancellation may - * sneak in before the timer function can grab the lock, and that may queue - * another stop endpoint command and add the timer back. So we cannot use a - * simple flag to say whether there is a pending stop endpoint command for a - * particular endpoint. - * - * Instead we use a combination of that flag and checking if a new timer is - * pending. - */ -void xhci_stop_endpoint_command_watchdog(struct timer_list *t) -{ - struct xhci_virt_ep *ep = from_timer(ep, t, stop_cmd_timer); - struct xhci_hcd *xhci = ep->xhci; - unsigned long flags; - u32 usbsts; - char str[XHCI_MSG_MAX]; - - spin_lock_irqsave(&xhci->lock, flags); - - /* bail out if cmd completed but raced with stop ep watchdog timer.*/ - if (!(ep->ep_state & EP_STOP_CMD_PENDING) || - timer_pending(&ep->stop_cmd_timer)) { - spin_unlock_irqrestore(&xhci->lock, flags); - xhci_dbg(xhci, "Stop EP timer raced with cmd completion, exit"); - return; - } - usbsts = readl(&xhci->op_regs->status); - - xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n"); - xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(str, usbsts)); - - ep->ep_state &= ~EP_STOP_CMD_PENDING; - - xhci_halt(xhci); - - /* - * handle a stop endpoint cmd timeout as if host died (-ENODEV). - * In the future we could distinguish between -ENODEV and -ETIMEDOUT - * and try to recover a -ETIMEDOUT with a host controller reset - */ - xhci_hc_died(xhci); - - spin_unlock_irqrestore(&xhci->lock, flags); - xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "xHCI host controller is dead."); -} - static void update_ring_for_set_deq_completion(struct xhci_hcd *xhci, struct xhci_virt_device *dev, struct xhci_ring *ep_ring, @@ -1650,9 +1587,12 @@ void xhci_cleanup_command_queue(struct xhci_hcd *xhci) void xhci_handle_command_timeout(struct work_struct *work) { - struct xhci_hcd *xhci; - unsigned long flags; - u64 hw_ring_state; + struct xhci_hcd *xhci; + unsigned long flags; + char str[XHCI_MSG_MAX]; + u64 hw_ring_state; + u32 cmd_field3; + u32 usbsts; xhci = container_of(to_delayed_work(work), struct xhci_hcd, cmd_timer); @@ -1666,6 +1606,27 @@ void xhci_handle_command_timeout(struct work_struct *work) spin_unlock_irqrestore(&xhci->lock, flags); return; } + + cmd_field3 = le32_to_cpu(xhci->current_cmd->command_trb->generic.field[3]); + usbsts = readl(&xhci->op_regs->status); + xhci_dbg(xhci, "Command timeout, USBSTS:%s\n", xhci_decode_usbsts(str, usbsts)); + + /* Bail out and tear down xhci if a stop endpoint command failed */ + if (TRB_FIELD_TO_TYPE(cmd_field3) == TRB_STOP_RING) { + struct xhci_virt_ep *ep; + + xhci_warn(xhci, "xHCI host not responding to stop endpoint command\n"); + + ep = xhci_get_virt_ep(xhci, TRB_TO_SLOT_ID(cmd_field3), + TRB_TO_EP_INDEX(cmd_field3)); + if (ep) + ep->ep_state &= ~EP_STOP_CMD_PENDING; + + xhci_halt(xhci); + xhci_hc_died(xhci); + goto time_out_completed; + } + /* mark this command to be cancelled */ xhci->current_cmd->status = COMP_COMMAND_ABORTED; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 80db65f19e47..e66a031de5a3 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1874,9 +1874,6 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) goto done; } ep->ep_state |= EP_STOP_CMD_PENDING; - ep->stop_cmd_timer.expires = jiffies + - XHCI_STOP_EP_CMD_TIMEOUT * HZ; - add_timer(&ep->stop_cmd_timer); xhci_queue_stop_endpoint(xhci, command, urb->dev->slot_id, ep_index, 0); xhci_ring_cmd_db(xhci); @@ -3986,10 +3983,8 @@ static void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) trace_xhci_free_dev(slot_ctx); /* Stop any wayward timer functions (which may grab the lock) */ - for (i = 0; i < 31; i++) { + for (i = 0; i < 31; i++) virt_dev->eps[i].ep_state &= ~EP_STOP_CMD_PENDING; - del_timer_sync(&virt_dev->eps[i].stop_cmd_timer); - } virt_dev->udev = NULL; xhci_disable_slot(xhci, udev->slot_id); xhci_free_virt_device(xhci, udev->slot_id); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c792a3148e49..1e3ef8d01567 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -948,8 +948,6 @@ struct xhci_virt_ep { #define EP_CLEARING_TT (1 << 8) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - /* Watchdog timer for stop endpoint command to cancel URBs */ - struct timer_list stop_cmd_timer; struct xhci_hcd *xhci; /* Dequeue pointer and dequeue segment for a submitted Set TR Dequeue * command. We'll need to update the ring's dequeue segment and dequeue -- cgit v1.2.3 From 424140d31ba1c613e7a087caf87723d0273f4dae Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 12 May 2022 01:04:48 +0300 Subject: xhci: prevent U2 link power state if Intel tier policy prevented U1 Don't enable U1 or U2 Link powermanagenet (LPM) states for USB3 devices connected to tier 2 or further hubs. For unknown reasons we previously only prevented U1. Be consistent, and prevent both U1/U2 states if tier policy doesn't allow LPM. Also check the tier policy a bit earlier, and return if U1/U2 is not allowed. This avoids unnecessary xhci MEL commands. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index e66a031de5a3..d957eac59ab3 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4888,9 +4888,6 @@ static int xhci_check_intel_tier_policy(struct usb_device *udev, struct usb_device *parent; unsigned int num_hubs; - if (state == USB3_LPM_U2) - return 0; - /* Don't enable U1 if the device is on a 2nd tier hub or lower. */ for (parent = udev->parent, num_hubs = 0; parent->parent; parent = parent->parent) @@ -4899,7 +4896,7 @@ static int xhci_check_intel_tier_policy(struct usb_device *udev, if (num_hubs < 2) return 0; - dev_dbg(&udev->dev, "Disabling U1 link state for device" + dev_dbg(&udev->dev, "Disabling U1/U2 link state for device" " below second-tier hub.\n"); dev_dbg(&udev->dev, "Plug device into first-tier hub " "to decrease power consumption.\n"); @@ -4940,9 +4937,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd, return timeout; } - if (xhci_check_tier_policy(xhci, udev, state) < 0) - return timeout; - /* Gather some information about the currently installed configuration * and alternate interface settings. */ @@ -5049,6 +5043,9 @@ static int xhci_enable_usb3_lpm_timeout(struct usb_hcd *hcd, !xhci->devs[udev->slot_id]) return USB3_LPM_DISABLED; + if (xhci_check_tier_policy(xhci, udev, state) < 0) + return USB3_LPM_DISABLED; + hub_encoded_timeout = xhci_calculate_lpm_timeout(hcd, udev, state); mel = calculate_max_exit_latency(udev, state, hub_encoded_timeout); if (mel < 0) { -- cgit v1.2.3 From 15ad5b615f7078290b97b64750ee37e4a9ddd4f6 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 12 May 2022 01:04:49 +0300 Subject: xhci: Remove quirk for over 10 year old evaluation hardware The XHCI_RESET_EP_QUIRK was added in 2009 to support prototype xHC hardware from Fresco Logic that needed an additional configure endpoint command after a reset endpoint. That hardware should not have made it to the market. Now, 13 years later its about time we get rid of it. quirk was added in commit ac9d8fe7c6a8 ("USB: xhci: Add quirk for Fresco Logic xHCI hardware.") Print a debug message about the removed quirk if against all odds we run into this controller. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 3 +-- drivers/usb/host/xhci-ring.c | 37 +++++-------------------------------- drivers/usb/host/xhci.h | 2 +- 3 files changed, 7 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index d7e0e6ebf080..49ba94de7459 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -129,8 +129,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->revision == 0x0) { xhci->quirks |= XHCI_RESET_EP_QUIRK; xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, - "QUIRK: Fresco Logic xHC needs configure" - " endpoint cmd after reset endpoint"); + "XHCI_RESET_EP_QUIRK for this evaluation HW is deprecated"); } if (pdev->device == PCI_DEVICE_ID_FRESCO_LOGIC_PDK && pdev->revision == 0x4) { diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 373e0e152854..46d0b9ad6f74 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1426,8 +1426,6 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, /* Cleanup cancelled TDs as ep is stopped. May queue a Set TR Deq cmd */ xhci_invalidate_cancelled_tds(ep); - if (xhci->quirks & XHCI_RESET_EP_QUIRK) - xhci_dbg(xhci, "Note: Removed workaround to queue config ep for this hw"); /* Clear our internal halted state */ ep->ep_state &= ~EP_HALTED; @@ -1471,17 +1469,13 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_input_control_ctx *ctrl_ctx; struct xhci_ep_ctx *ep_ctx; unsigned int ep_index; - unsigned int ep_state; - u32 add_flags, drop_flags; + u32 add_flags; /* - * Configure endpoint commands can come from the USB core - * configuration or alt setting changes, or because the HW - * needed an extra configure endpoint command after a reset - * endpoint command or streams were being configured. - * If the command was for a halted endpoint, the xHCI driver - * is not waiting on the configure endpoint command. + * Configure endpoint commands can come from the USB core configuration + * or alt setting changes, or when streams were being configured. */ + virt_dev = xhci->devs[slot_id]; if (!virt_dev) return; @@ -1492,34 +1486,13 @@ static void xhci_handle_cmd_config_ep(struct xhci_hcd *xhci, int slot_id, } add_flags = le32_to_cpu(ctrl_ctx->add_flags); - drop_flags = le32_to_cpu(ctrl_ctx->drop_flags); + /* Input ctx add_flags are the endpoint index plus one */ ep_index = xhci_last_valid_endpoint(add_flags) - 1; ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, ep_index); trace_xhci_handle_cmd_config_ep(ep_ctx); - /* A usb_set_interface() call directly after clearing a halted - * condition may race on this quirky hardware. Not worth - * worrying about, since this is prototype hardware. Not sure - * if this will work for streams, but streams support was - * untested on this prototype. - */ - if (xhci->quirks & XHCI_RESET_EP_QUIRK && - ep_index != (unsigned int) -1 && - add_flags - SLOT_FLAG == drop_flags) { - ep_state = virt_dev->eps[ep_index].ep_state; - if (!(ep_state & EP_HALTED)) - return; - xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, - "Completed config ep cmd - " - "last ep index = %d, state = %d", - ep_index, ep_state); - /* Clear internal halted state and restart ring(s) */ - virt_dev->eps[ep_index].ep_state &= ~EP_HALTED; - ring_doorbell_for_active_rings(xhci, slot_id, ep_index); - return; - } return; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 1e3ef8d01567..0bd76c94a4b1 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1846,7 +1846,7 @@ struct xhci_hcd { #define XHCI_STATE_REMOVING (1 << 2) unsigned long long quirks; #define XHCI_LINK_TRB_QUIRK BIT_ULL(0) -#define XHCI_RESET_EP_QUIRK BIT_ULL(1) +#define XHCI_RESET_EP_QUIRK BIT_ULL(1) /* Deprecated */ #define XHCI_NEC_HOST BIT_ULL(2) #define XHCI_AMD_PLL_FIX BIT_ULL(3) #define XHCI_SPURIOUS_SUCCESS BIT_ULL(4) -- cgit v1.2.3 From 74f55a62c4c354f43a6d75f77dd184c4f57b9a26 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 12 May 2022 01:04:50 +0300 Subject: xhci: Allow host runtime PM as default for Intel Alder Lake N xHCI Alder Lake N TCSS xHCI needs to be runtime suspended whenever possible to allow the TCSS hardware block to enter D3 and thus save energy Cc: stable@kernel.org Suggested-by: Gopal Vamshi Krishna Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220511220450.85367-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 49ba94de7459..fac9492a8bda 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -59,6 +59,7 @@ #define PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI 0x9a13 #define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI 0x1138 #define PCI_DEVICE_ID_INTEL_ALDER_LAKE_XHCI 0x461e +#define PCI_DEVICE_ID_INTEL_ALDER_LAKE_N_XHCI 0x464e #define PCI_DEVICE_ID_INTEL_ALDER_LAKE_PCH_XHCI 0x51ed #define PCI_DEVICE_ID_AMD_RENOIR_XHCI 0x1639 @@ -267,6 +268,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ALDER_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALDER_LAKE_N_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ALDER_LAKE_PCH_XHCI)) xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; -- cgit v1.2.3 From e82e7c6dde91acd6748d672a44dc1980ce239f86 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 17 May 2022 18:17:36 +0200 Subject: USB: serial: pl2303: fix type detection for odd device At least one pl2303 device has a bcdUSB of 1.0.1 which most likely was was intended as 1.1. Allow bcdDevice 1.0.1 but interpret it as 1.1. Fixes: 1e9faef4d26d ("USB: serial: pl2303: fix HX type detection") Cc: stable@vger.kernel.org # 5.13 Link: https://lore.kernel.org/linux-usb/CAJixRzqf4a9-ZKZDgWxicc_BpfdZVE9qqGmkiO7xEstOXUbGvQ@mail.gmail.com Reported-by: Gary van der Merwe Link: https://lore.kernel.org/r/20220517161736.13313-1-johan@kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/pl2303.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 88b284d61681..e0555d313c2b 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -420,6 +420,9 @@ static int pl2303_detect_type(struct usb_serial *serial) bcdUSB = le16_to_cpu(desc->bcdUSB); switch (bcdUSB) { + case 0x101: + /* USB 1.0.1? Let's assume they meant 1.1... */ + fallthrough; case 0x110: switch (bcdDevice) { case 0x300: -- cgit v1.2.3 From 33b7af2f459df453feb0d44628d820c47fefe7a8 Mon Sep 17 00:00:00 2001 From: "Carl Yin(殷张成)" Date: Thu, 19 May 2022 02:34:43 +0000 Subject: USB: serial: option: add Quectel BG95 modem The BG95 modem has 3 USB configurations that are configurable via the AT command AT+QCFGEXT="usbnet",["ecm"|"modem"|"rmnet"] which make the modem enumerate with the following interfaces, respectively: "modem": Diag + GNSS + Modem + Modem "ecm" : Diag + GNSS + Modem + ECM "rmnet": Diag + GNSS + Modem + QMI Don't support Full QMI messages (e.g WDS_START_NETWORK_INTERFACE) A detailed description of the USB configuration for each mode follows: +QCFGEXT: "usbnet","modem" -------------------------- T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#= 3 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=2c7c ProdID=0700 Rev= 0.00 S: Manufacturer=Quectel, Incorporated S: Product=Quectel LPWA Module S: SerialNumber=884328a2 C:* #Ifs= 4 Cfg#= 1 Atr=e0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 64 Ivl=2ms E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=fe Prot=ff Driver=option E: Ad=85(I) Atr=03(Int.) MxPS= 64 Ivl=2ms E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms +QCFGEXT: "usbnet","ecm" ------------------------ T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#= 4 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=2c7c ProdID=0700 Rev= 0.00 S: Manufacturer=Quectel, Incorporated S: Product=Quectel LPWA Module S: SerialNumber=884328a2 C:* #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr=500mA A: FirstIf#= 3 IfCount= 2 Cls=02(comm.) Sub=00 Prot=00 I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 64 Ivl=2ms E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=06 Prot=00 Driver=cdc_ether E: Ad=85(I) Atr=03(Int.) MxPS= 64 Ivl=2ms I: If#= 4 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_ether I:* If#= 4 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=cdc_ether E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms +QCFGEXT: "usbnet","rmnet" -------------------------- T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=01 Dev#= 4 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=2c7c ProdID=0700 Rev= 0.00 S: Manufacturer=Quectel, Incorporated S: Product=Quectel LPWA Module S: SerialNumber=884328a2 C:* #Ifs= 4 Cfg#= 1 Atr=e0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 64 Ivl=2ms E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=qmi_wwan E: Ad=85(I) Atr=03(Int.) MxPS= 64 Ivl=2ms E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Signed-off-by: Carl Yin Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 1364ce7f0abf..ac444a66e781 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1137,6 +1137,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM12, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0xff, 0x30) }, /* EM160R-GL */ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, 0x0620, 0xff, 0, 0) }, + { USB_DEVICE_INTERFACE_CLASS(QUECTEL_VENDOR_ID, 0x0700, 0xff), /* BG95 */ + .driver_info = RSVD(3) | ZLP }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500Q, 0xff, 0xff, 0x10), -- cgit v1.2.3 From 1bd8bb7d2dfc44509acf729e636523c3c2b729df Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Mon, 16 May 2022 12:48:50 +0300 Subject: xhci: Don't defer primary roothub registration if there is only one roothub The support for xHCI controllers with only one roothub, and the code to defer primary roothub registation until second roothub got merged to usb-next for 5.19 at the same time. commit 873f323618c2 ("xhci: prepare for operation w/o shared hcd") commit b7a4f9b5d0e4 ("xhci: Set HCD flag to defer primary roothub registration") These got merged in such a way that the flag to defer primary roothub registration is set even for xHC controllers with just one roothub. Fix this by setting the defer flag in a codepath taken only if we have two roothubs Fixes: 873f323618c2 ("xhci: prepare for operation w/o shared hcd") Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20220516094850.19788-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index d957eac59ab3..f0ab63138016 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -696,8 +696,6 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s for main hcd", __func__); - set_bit(HCD_FLAG_DEFER_RH_REGISTER, &hcd->flags); - xhci_create_dbc_dev(xhci); xhci_debugfs_init(xhci); @@ -705,6 +703,8 @@ int xhci_run(struct usb_hcd *hcd) if (xhci_has_one_roothub(xhci)) return xhci_run_finished(xhci); + set_bit(HCD_FLAG_DEFER_RH_REGISTER, &hcd->flags); + return 0; } EXPORT_SYMBOL_GPL(xhci_run); -- cgit v1.2.3 From 26ae2c942b5702f2e43d36b2a4389cfb7d616b6a Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 16 May 2022 11:14:24 +0200 Subject: usb: isp1760: Fix out-of-bounds array access Running the driver through kasan gives an interesting splat: BUG: KASAN: global-out-of-bounds in isp1760_register+0x180/0x70c Read of size 20 at addr f1db2e64 by task swapper/0/1 (...) isp1760_register from isp1760_plat_probe+0x1d8/0x220 (...) This happens because the loop reading the regmap fields for the different ISP1760 variants look like this: for (i = 0; i < HC_FIELD_MAX; i++) { ... } Meaning it expects the arrays to be at least HC_FIELD_MAX - 1 long. However the arrays isp1760_hc_reg_fields[], isp1763_hc_reg_fields[], isp1763_hc_volatile_ranges[] and isp1763_dc_volatile_ranges[] are dynamically sized during compilation. Fix this by putting an empty assignment to the [HC_FIELD_MAX] and [DC_FIELD_MAX] array member at the end of each array. This will make the array one member longer than it needs to be, but avoids the risk of overwriting whatever is inside [HC_FIELD_MAX - 1] and is simple and intuitive to read. Also add comments explaining what is going on. Fixes: 1da9e1c06873 ("usb: isp1760: move to regmap for register access") Cc: stable@vger.kernel.org Cc: Rui Miguel Silva Cc: Dietmar Eggemann Reviewed-by: Rui Miguel Silva Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20220516091424.391209-1-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-core.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index d1d9a7d5da17..af88f4fe00d2 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -251,6 +251,8 @@ static const struct reg_field isp1760_hc_reg_fields[] = { [HW_DM_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 2, 2), [HW_DP_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 1, 1), [HW_DP_PULLUP] = REG_FIELD(ISP176x_HC_OTG_CTRL, 0, 0), + /* Make sure the array is sized properly during compilation */ + [HC_FIELD_MAX] = {}, }; static const struct reg_field isp1763_hc_reg_fields[] = { @@ -321,6 +323,8 @@ static const struct reg_field isp1763_hc_reg_fields[] = { [HW_DM_PULLDOWN_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 2, 2), [HW_DP_PULLDOWN_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 1, 1), [HW_DP_PULLUP_CLEAR] = REG_FIELD(ISP1763_HC_OTG_CTRL_CLEAR, 0, 0), + /* Make sure the array is sized properly during compilation */ + [HC_FIELD_MAX] = {}, }; static const struct regmap_range isp1763_hc_volatile_ranges[] = { @@ -405,6 +409,8 @@ static const struct reg_field isp1761_dc_reg_fields[] = { [DC_CHIP_ID_HIGH] = REG_FIELD(ISP176x_DC_CHIPID, 16, 31), [DC_CHIP_ID_LOW] = REG_FIELD(ISP176x_DC_CHIPID, 0, 15), [DC_SCRATCH] = REG_FIELD(ISP176x_DC_SCRATCH, 0, 15), + /* Make sure the array is sized properly during compilation */ + [DC_FIELD_MAX] = {}, }; static const struct regmap_range isp1763_dc_volatile_ranges[] = { @@ -458,6 +464,8 @@ static const struct reg_field isp1763_dc_reg_fields[] = { [DC_CHIP_ID_HIGH] = REG_FIELD(ISP1763_DC_CHIPID_HIGH, 0, 15), [DC_CHIP_ID_LOW] = REG_FIELD(ISP1763_DC_CHIPID_LOW, 0, 15), [DC_SCRATCH] = REG_FIELD(ISP1763_DC_SCRATCH, 0, 15), + /* Make sure the array is sized properly during compilation */ + [DC_FIELD_MAX] = {}, }; static const struct regmap_config isp1763_dc_regmap_conf = { -- cgit v1.2.3 From 69a1c9a9b273271f2a2674bcc117336a9bb0a4b4 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 May 2022 15:02:51 -0700 Subject: usb: Probe EHCI, OHCI controllers asynchronously initcall_debug shows that OHCI controllers take ~60ms to probe on Rockchip RK3399 systems: probe of fe3a0000.usb returned 1 after 58941 usecs A few of these can add up to waste non-trivial amounts of time at boot. These host controllers don't provide resources to other drivers, so this shouldn't contribute to exposing race conditions. Chrome OS kernels have carried this patch on some systems for a while without issues. Similar patches have been merged for a variety of (e)MMC host controllers for similar reasons. [Brian: rewrote commit message, refreshed, but retained dtor's original authorship ] Acked-by: Alan Stern Signed-off-by: Dmitry Torokhov Signed-off-by: Brian Norris Link: https://lore.kernel.org/r/20220518150150.1.Ie8ea0e945a9c15066237014be219eed60066d493@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-platform.c | 1 + drivers/usb/host/ohci-platform.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index 1115431a255d..f343967443e2 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -518,6 +518,7 @@ static struct platform_driver ehci_platform_driver = { .pm = pm_ptr(&ehci_platform_pm_ops), .of_match_table = vt8500_ehci_ids, .acpi_match_table = ACPI_PTR(ehci_acpi_match), + .probe_type = PROBE_PREFER_ASYNCHRONOUS, } }; diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 4a8456f12a73..47dfbfe9e519 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -334,6 +334,7 @@ static struct platform_driver ohci_platform_driver = { .name = "ohci-platform", .pm = &ohci_platform_pm_ops, .of_match_table = ohci_platform_ids, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, } }; -- cgit v1.2.3 From 9d778f0c5f95ca5aa2ff628ea281978697e8d89b Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Wed, 4 May 2022 12:36:41 -0700 Subject: usb: dwc3: Fix ep0 handling when getting reset while doing control transfer According to the databook ep0 should be in setup phase during reset. If host issues reset between control transfers, ep0 will be in an invalid state. Fix this by issuing stall and restart on ep0 if it is not in setup phase. Also SW needs to complete pending control transfer and setup core for next setup stage as per data book. Hence check ep0 state during reset interrupt handling and make sure active transfers on ep0 out/in endpoint are stopped by queuing ENDXFER command for that endpoint and restart ep0 out again to receive next setup packet. Signed-off-by: Mayank Rana Link: https://lore.kernel.org/r/1651693001-29891-1-git-send-email-quic_mrana@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/ep0.c | 11 ++++++++--- drivers/usb/dwc3/gadget.c | 27 +++++++++++++++++++++++++-- drivers/usb/dwc3/gadget.h | 2 ++ 3 files changed, 35 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 6615f641b34f..5d642660fd15 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -218,7 +218,7 @@ out: return ret; } -static void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) +void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) { struct dwc3_ep *dep; @@ -1088,13 +1088,18 @@ void dwc3_ep0_send_delayed_status(struct dwc3 *dwc) __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); } -static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) { struct dwc3_gadget_ep_cmd_params params; u32 cmd; int ret; - if (!dep->resource_index) + /* + * For status/DATA OUT stage, TRB will be queued on ep0 out + * endpoint for which resource index is zero. Hence allow + * queuing ENDXFER command for ep0 out endpoint. + */ + if (!dep->resource_index && dep->number) return; cmd = DWC3_DEPCMD_ENDTRANSFER; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 4f54f0ef21df..8c366fa82105 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -882,12 +882,13 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) reg |= DWC3_DALEPENA_EP(dep->number); dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + dep->trb_dequeue = 0; + dep->trb_enqueue = 0; + if (usb_endpoint_xfer_control(desc)) goto out; /* Initialize the TRB ring */ - dep->trb_dequeue = 0; - dep->trb_enqueue = 0; memset(dep->trb_pool, 0, sizeof(struct dwc3_trb) * DWC3_TRB_NUM); @@ -2741,6 +2742,7 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) /* begin to receive SETUP packets */ dwc->ep0state = EP0_SETUP_PHASE; + dwc->ep0_bounced = false; dwc->link_state = DWC3_LINK_STATE_SS_DIS; dwc->delayed_status = false; dwc3_ep0_out_start(dwc); @@ -3820,6 +3822,27 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) } dwc3_reset_gadget(dwc); + + /* + * From SNPS databook section 8.1.2, the EP0 should be in setup + * phase. So ensure that EP0 is in setup phase by issuing a stall + * and restart if EP0 is not in setup phase. + */ + if (dwc->ep0state != EP0_SETUP_PHASE) { + unsigned int dir; + + dir = !!dwc->ep0_expect_in; + if (dwc->ep0state == EP0_DATA_PHASE) + dwc3_ep0_end_control_data(dwc, dwc->eps[dir]); + else + dwc3_ep0_end_control_data(dwc, dwc->eps[!dir]); + + dwc->eps[0]->trb_enqueue = 0; + dwc->eps[1]->trb_enqueue = 0; + + dwc3_ep0_stall_and_restart(dwc); + } + /* * In the Synopsis DesignWare Cores USB3 Databook Rev. 3.30a * Section 4.1.2 Table 4-2, it states that during a USB reset, the SW diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index f763380e672e..55a56cf67d73 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -110,6 +110,8 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, void dwc3_ep0_interrupt(struct dwc3 *dwc, const struct dwc3_event_depevt *event); void dwc3_ep0_out_start(struct dwc3 *dwc); +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep); +void dwc3_ep0_stall_and_restart(struct dwc3 *dwc); int __dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value); int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value); int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, -- cgit v1.2.3 From dce49449e04ff150838a31386ee65917beb9ebb5 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 9 May 2022 11:40:55 -0500 Subject: usb: cdns3: allocate TX FIFO size according to composite EP number Some devices have USB compositions which may require multiple endpoints. To get better performance, need bigger CDNS3_EP_BUF_SIZE. But bigger CDNS3_EP_BUF_SIZE may exceed total hardware FIFO size when multiple endpoints. By introducing the check_config() callback, calculate CDNS3_EP_BUF_SIZE. Move CDNS3_EP_BUF_SIZE into cnds3_device: ep_buf_size Combine CDNS3_EP_ISO_SS_BURST and CDNS3_EP_ISO_HS_MULT into cnds3_device:ep_iso_burst Using a simple algorithm to calculate ep_buf_size. ep_buf_size = ep_iso_burst = (onchip_buffers - 2k) / (number of IN EP + 1). Test at 8qxp: Gadget ep_buf_size RNDIS: 5 RNDIS+ACM: 3 Mass Storage + NCM + ACM 2 Previous CDNS3_EP_BUF_SIZE is 4, RNDIS + ACM will be failure because exceed FIFO memory. Acked-by: Peter Chen Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20220509164055.1815081-1-Frank.Li@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 47 ++++++++++++++++++++++++++++++++++++---- drivers/usb/cdns3/cdns3-gadget.h | 9 +++++--- 2 files changed, 49 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index d6d515d598dc..5c15c48952a6 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -2038,7 +2038,7 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) u8 mult = 0; int ret; - buffering = CDNS3_EP_BUF_SIZE - 1; + buffering = priv_dev->ep_buf_size - 1; cdns3_configure_dmult(priv_dev, priv_ep); @@ -2057,7 +2057,7 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) break; default: ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC); - mult = CDNS3_EP_ISO_HS_MULT - 1; + mult = priv_dev->ep_iso_burst - 1; buffering = mult + 1; } @@ -2073,14 +2073,14 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) mult = 0; max_packet_size = 1024; if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) { - maxburst = CDNS3_EP_ISO_SS_BURST - 1; + maxburst = priv_dev->ep_iso_burst - 1; buffering = (mult + 1) * (maxburst + 1); if (priv_ep->interval > 1) buffering++; } else { - maxburst = CDNS3_EP_BUF_SIZE - 1; + maxburst = priv_dev->ep_buf_size - 1; } break; default: @@ -2095,6 +2095,10 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable) else priv_ep->trb_burst_size = 16; + mult = min_t(u8, mult, EP_CFG_MULT_MAX); + buffering = min_t(u8, buffering, EP_CFG_BUFFERING_MAX); + maxburst = min_t(u8, maxburst, EP_CFG_MAXBURST_MAX); + /* onchip buffer is only allocated before configuration */ if (!priv_dev->hw_configured_flag) { ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1, @@ -2961,6 +2965,40 @@ static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) return 0; } +/** + * cdns3_gadget_check_config - ensure cdns3 can support the USB configuration + * @gadget: pointer to the USB gadget + * + * Used to record the maximum number of endpoints being used in a USB composite + * device. (across all configurations) This is to be used in the calculation + * of the TXFIFO sizes when resizing internal memory for individual endpoints. + * It will help ensured that the resizing logic reserves enough space for at + * least one max packet. + */ +static int cdns3_gadget_check_config(struct usb_gadget *gadget) +{ + struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + struct usb_ep *ep; + int n_in = 0; + int total; + + list_for_each_entry(ep, &gadget->ep_list, ep_list) { + if (ep->claimed && (ep->address & USB_DIR_IN)) + n_in++; + } + + /* 2KB are reserved for EP0, 1KB for out*/ + total = 2 + n_in + 1; + + if (total > priv_dev->onchip_buffers) + return -ENOMEM; + + priv_dev->ep_buf_size = priv_dev->ep_iso_burst = + (priv_dev->onchip_buffers - 2) / (n_in + 1); + + return 0; +} + static const struct usb_gadget_ops cdns3_gadget_ops = { .get_frame = cdns3_gadget_get_frame, .wakeup = cdns3_gadget_wakeup, @@ -2969,6 +3007,7 @@ static const struct usb_gadget_ops cdns3_gadget_ops = { .udc_start = cdns3_gadget_udc_start, .udc_stop = cdns3_gadget_udc_stop, .match_ep = cdns3_gadget_match_ep, + .check_config = cdns3_gadget_check_config, }; static void cdns3_free_all_eps(struct cdns3_device *priv_dev) diff --git a/drivers/usb/cdns3/cdns3-gadget.h b/drivers/usb/cdns3/cdns3-gadget.h index c5660f2c4293..fbe4a8e3aa89 100644 --- a/drivers/usb/cdns3/cdns3-gadget.h +++ b/drivers/usb/cdns3/cdns3-gadget.h @@ -562,15 +562,18 @@ struct cdns3_usb_regs { /* Max burst size (used only in SS mode). */ #define EP_CFG_MAXBURST_MASK GENMASK(11, 8) #define EP_CFG_MAXBURST(p) (((p) << 8) & EP_CFG_MAXBURST_MASK) +#define EP_CFG_MAXBURST_MAX 15 /* ISO max burst. */ #define EP_CFG_MULT_MASK GENMASK(15, 14) #define EP_CFG_MULT(p) (((p) << 14) & EP_CFG_MULT_MASK) +#define EP_CFG_MULT_MAX 2 /* ISO max burst. */ #define EP_CFG_MAXPKTSIZE_MASK GENMASK(26, 16) #define EP_CFG_MAXPKTSIZE(p) (((p) << 16) & EP_CFG_MAXPKTSIZE_MASK) /* Max number of buffered packets. */ #define EP_CFG_BUFFERING_MASK GENMASK(31, 27) #define EP_CFG_BUFFERING(p) (((p) << 27) & EP_CFG_BUFFERING_MASK) +#define EP_CFG_BUFFERING_MAX 15 /* EP_CMD - bitmasks */ /* Endpoint reset. */ @@ -1094,9 +1097,6 @@ struct cdns3_trb { #define CDNS3_ENDPOINTS_MAX_COUNT 32 #define CDNS3_EP_ZLP_BUF_SIZE 1024 -#define CDNS3_EP_BUF_SIZE 4 /* KB */ -#define CDNS3_EP_ISO_HS_MULT 3 -#define CDNS3_EP_ISO_SS_BURST 3 #define CDNS3_MAX_NUM_DESCMISS_BUF 32 #define CDNS3_DESCMIS_BUF_SIZE 2048 /* Bytes */ #define CDNS3_WA2_NUM_BUFFERS 128 @@ -1333,6 +1333,9 @@ struct cdns3_device { /*in KB */ u16 onchip_buffers; u16 onchip_used_size; + + u16 ep_buf_size; + u16 ep_iso_burst; }; void cdns3_set_register_bit(void __iomem *ptr, u32 mask); -- cgit v1.2.3 From 6c5ba7395b1badce5e0b8e597907751e952646cb Mon Sep 17 00:00:00 2001 From: Bhuvanesh Surachari Date: Thu, 12 May 2022 17:37:14 +0200 Subject: usb: hub: Simplify error and success path in port_over_current_notify kasprintf() returns NULL or valid pointer. Since kfree() can handle NULL pointer condition, simplify error and success paths in function port_over_current_notify() by removing multiple error path labels. Signed-off-by: Bhuvanesh Surachari Signed-off-by: Eugeniu Rosca Link: https://lore.kernel.org/r/1652369834-4480-1-git-send-email-erosca@de.adit-jv.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7781b2d31473..68e9121c1878 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5511,7 +5511,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, /* Handle notifying userspace about hub over-current events */ static void port_over_current_notify(struct usb_port *port_dev) { - char *envp[3]; + char *envp[3] = { NULL, NULL, NULL }; struct device *hub_dev; char *port_dev_path; @@ -5528,20 +5528,18 @@ static void port_over_current_notify(struct usb_port *port_dev) envp[0] = kasprintf(GFP_KERNEL, "OVER_CURRENT_PORT=%s", port_dev_path); if (!envp[0]) - goto exit_path; + goto exit; envp[1] = kasprintf(GFP_KERNEL, "OVER_CURRENT_COUNT=%u", port_dev->over_current_count); if (!envp[1]) goto exit; - envp[2] = NULL; kobject_uevent_env(&hub_dev->kobj, KOBJ_CHANGE, envp); - kfree(envp[1]); exit: + kfree(envp[1]); kfree(envp[0]); -exit_path: kfree(port_dev_path); } -- cgit v1.2.3 From 3c5880745b4439ac64eccdb040e37fc1cc4c5406 Mon Sep 17 00:00:00 2001 From: Albert Wang Date: Wed, 18 May 2022 14:13:15 +0800 Subject: usb: dwc3: gadget: Move null pinter check to proper place When dwc3_gadget_ep_cleanup_completed_requests() called to dwc3_gadget_giveback() where the dwc3 lock is released, other thread is able to execute. In this situation, usb_ep_disable() gets the chance to clear endpoint descriptor pointer which leds to the null pointer dereference problem. So needs to move the null pointer check to a proper place. Example call stack: Thread#1: dwc3_thread_interrupt() spin_lock -> dwc3_process_event_buf() -> dwc3_process_event_entry() -> dwc3_endpoint_interrupt() -> dwc3_gadget_endpoint_trbs_complete() -> dwc3_gadget_ep_cleanup_completed_requests() ... -> dwc3_giveback() spin_unlock Thread#2 executes Thread#2: configfs_composite_disconnect() -> __composite_disconnect() -> ffs_func_disable() -> ffs_func_set_alt() -> ffs_func_eps_disable() -> usb_ep_disable() wait for dwc3 spin_lock Thread#1 released lock clear endpoint.desc Fixes: 26288448120b ("usb: dwc3: gadget: Fix null pointer exception") Cc: stable Signed-off-by: Albert Wang Link: https://lore.kernel.org/r/20220518061315.3359198-1-albertccwang@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 8c366fa82105..00427d108ab9 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3384,14 +3384,14 @@ static bool dwc3_gadget_endpoint_trbs_complete(struct dwc3_ep *dep, struct dwc3 *dwc = dep->dwc; bool no_started_trb = true; - if (!dep->endpoint.desc) - return no_started_trb; - dwc3_gadget_ep_cleanup_completed_requests(dep, event, status); if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) goto out; + if (!dep->endpoint.desc) + return no_started_trb; + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && list_empty(&dep->started_list) && (list_empty(&dep->pending_list) || status == -EXDEV)) -- cgit v1.2.3 From 859bdc359567f5fa8e8dc780d7b5e53ea43d9ce9 Mon Sep 17 00:00:00 2001 From: Mayank Rana Date: Wed, 18 May 2022 11:12:52 -0700 Subject: usb: dwc3: core: Add error log when core soft reset failed DWC3 controller soft reset is important operation for USB functionality. In case when it fails, currently there is no failure log. Hence add error log when core soft reset failed. Signed-off-by: Mayank Rana Link: https://lore.kernel.org/r/1652897572-14461-1-git-send-email-quic_mrana@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c78205c5e19f..e027c0420dc3 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -298,6 +298,7 @@ int dwc3_core_soft_reset(struct dwc3 *dwc) udelay(1); } while (--retries); + dev_warn(dwc->dev, "DWC3 controller soft reset failed.\n"); return -ETIMEDOUT; done: -- cgit v1.2.3 From 97fa5887cf283bb75ffff5f6b2c0e71794c02400 Mon Sep 17 00:00:00 2001 From: Monish Kumar R Date: Fri, 20 May 2022 18:30:44 +0530 Subject: USB: new quirk for Dell Gen 2 devices Add USB_QUIRK_NO_LPM and USB_QUIRK_RESET_RESUME quirks for Dell usb gen 2 device to not fail during enumeration. Found this bug on own testing Signed-off-by: Monish Kumar R Cc: stable Link: https://lore.kernel.org/r/20220520130044.17303-1-monish.kumar.r@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 97b44a68668a..f99a65a64588 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -510,6 +510,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* DJI CineSSD */ { USB_DEVICE(0x2ca3, 0x0031), .driver_info = USB_QUIRK_NO_LPM }, + /* DELL USB GEN2 */ + { USB_DEVICE(0x413c, 0xb062), .driver_info = USB_QUIRK_NO_LPM | USB_QUIRK_RESET_RESUME }, + /* VCOM device */ { USB_DEVICE(0x4296, 0x7570), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, -- cgit v1.2.3