From d31137619776f9c173a46a79bc7733a2b106061f Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Mon, 12 Dec 2022 11:29:36 +0100 Subject: thunderbolt: Use correct type in tb_port_is_clx_enabled() prototype tb_port_is_clx_enabled() generates a valid warning with gcc-13: drivers/thunderbolt/switch.c:1286:6: error: conflicting types for 'tb_port_is_clx_enabled' due to enum/integer mismatch; have 'bool(struct tb_port *, unsigned int)' ... drivers/thunderbolt/tb.h:1050:6: note: previous declaration of 'tb_port_is_clx_enabled' with type 'bool(struct tb_port *, enum tb_clx)' ... I.e. the type of the 2nd parameter of tb_port_is_clx_enabled() in the declaration is unsigned int, while the definition spells enum tb_clx. Synchronize them to the former as the parameter is in fact a mask of the enum values. Signed-off-by: Jiri Slaby (SUSE) Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index f9786976f5ec..6c4a26b1c37c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1047,7 +1047,7 @@ void tb_port_lane_bonding_disable(struct tb_port *port); int tb_port_wait_for_link_width(struct tb_port *port, int width, int timeout_msec); int tb_port_update_credits(struct tb_port *port); -bool tb_port_is_clx_enabled(struct tb_port *port, enum tb_clx clx); +bool tb_port_is_clx_enabled(struct tb_port *port, unsigned int clx); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec); int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap); -- cgit v1.2.3 From 953ff25fc9fb831a675259ce1e738c94fb6202b6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Jan 2023 21:24:04 +0200 Subject: thunderbolt: Refactor tb_acpi_add_link() Convert while loop into do-while with only a single call to acpi_get_first_physical_node(). No functional change intended. Signed-off-by: Andy Shevchenko Signed-off-by: Mika Westerberg --- drivers/thunderbolt/acpi.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 317e4f5fdb97..628225deb8fe 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -36,16 +36,13 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, * We need to do this because the xHCI driver might not yet be * bound so the USB3 SuperSpeed ports are not yet created. */ - dev = acpi_get_first_physical_node(adev); - while (!dev) { - adev = acpi_dev_parent(adev); - if (!adev) - break; + do { dev = acpi_get_first_physical_node(adev); - } + if (dev) + break; - if (!dev) - goto out_put; + adev = acpi_dev_parent(adev); + } while (adev); /* * Check that the device is PCIe. This is because USB3 -- cgit v1.2.3 From 001b0c780eac328bc48b70b8437f202a4ed785e4 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Sun, 11 Dec 2022 11:37:55 -0800 Subject: usb: typec: altmodes/displayport: Add hpd sysfs attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exporsing HotPlugDetect(HPD) helps userspace to infer HPD state as defined by VESA DisplayPort Alt Mode on USB Type-C Standard. This allows userspace to notify users for self help, for instance, to hint user that the display port cable is probably detached (or) the display port sink (viz., monitors ect.,) is un-powered. Also helps to debug issues reported from field. This change adds an additional attribute "hpd" to the existing "displayport" attributes. VESA DisplayPort Alt Mode on USB Type-C Standard defines how HotPlugDetect(HPD) shall be supported on the USB-C connector when operating in DisplayPort Alt Mode. This is a read only node which reflects the current state of HPD. Valid values: - 1 when HPD’s logical state is high (HPD_High) - 0 when HPD’s logical state is low (HPD_Low) Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20221211193755.1392128-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-driver-typec-displayport | 15 +++++++++++++++ drivers/usb/typec/altmodes/displayport.c | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-typec-displayport b/Documentation/ABI/testing/sysfs-driver-typec-displayport index 231471ad0d4b..256c87c5219a 100644 --- a/Documentation/ABI/testing/sysfs-driver-typec-displayport +++ b/Documentation/ABI/testing/sysfs-driver-typec-displayport @@ -47,3 +47,18 @@ Description: USB SuperSpeed protocol. From user perspective pin assignments C and E are equal, where all channels on the connector are used for carrying DisplayPort protocol (allowing higher resolutions). + +What: /sys/bus/typec/devices/.../displayport/hpd +Date: Dec 2022 +Contact: Badhri Jagan Sridharan +Description: + VESA DisplayPort Alt Mode on USB Type-C Standard defines how + HotPlugDetect(HPD) shall be supported on the USB-C connector when + operating in DisplayPort Alt Mode. This is a read only node which + reflects the current state of HPD. + + Valid values: + - 1: when HPD’s logical state is high (HPD_High) as defined + by VESA DisplayPort Alt Mode on USB Type-C Standard. + - 0 when HPD’s logical state is low (HPD_Low) as defined by + VESA DisplayPort Alt Mode on USB Type-C Standard. diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index de66a2949e33..06fb4732f8cd 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -146,6 +146,7 @@ static int dp_altmode_status_update(struct dp_altmode *dp) if (dp->hpd != hpd) { drm_connector_oob_hotplug_event(dp->connector_fwnode); dp->hpd = hpd; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } } @@ -508,9 +509,18 @@ static ssize_t pin_assignment_show(struct device *dev, } static DEVICE_ATTR_RW(pin_assignment); +static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", dp->hpd); +} +static DEVICE_ATTR_RO(hpd); + static struct attribute *dp_altmode_attrs[] = { &dev_attr_configuration.attr, &dev_attr_pin_assignment.attr, + &dev_attr_hpd.attr, NULL }; -- cgit v1.2.3 From a8d3392e0e5cfeb03f0cea1f2bc3f5f183c1deb4 Mon Sep 17 00:00:00 2001 From: Gaosheng Cui Date: Wed, 23 Nov 2022 09:41:21 +0800 Subject: usb: gadget: fusb300_udc: free irq on the error path in fusb300_probe() When request_irq(ires1->start) failed in w5300_hw_probe(), irq ires->start has not been freed, and on the clean_up3 error path, we also need to free ires1->start irq, fix it. In addition, We should add free_irq in fusb300_remove(), and give the lables a proper name so that they can be understood easily, so add free_irq in fusb300_remove(), and update clean_up3 to err_alloc_request. Fixes: 0fe6f1d1f612 ("usb: udc: add Faraday fusb300 driver") Signed-off-by: Gaosheng Cui Link: https://lore.kernel.org/r/20221123014121.1989721-1-cuigaosheng1@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/fusb300_udc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/fusb300_udc.c b/drivers/usb/gadget/udc/fusb300_udc.c index 9af8b415f303..5e9e8e56e2d0 100644 --- a/drivers/usb/gadget/udc/fusb300_udc.c +++ b/drivers/usb/gadget/udc/fusb300_udc.c @@ -1347,6 +1347,7 @@ static int fusb300_remove(struct platform_device *pdev) usb_del_gadget_udc(&fusb300->gadget); iounmap(fusb300->reg); free_irq(platform_get_irq(pdev, 0), fusb300); + free_irq(platform_get_irq(pdev, 1), fusb300); fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); for (i = 0; i < FUSB300_MAX_NUM_EP; i++) @@ -1432,7 +1433,7 @@ static int fusb300_probe(struct platform_device *pdev) IRQF_SHARED, udc_name, fusb300); if (ret < 0) { pr_err("request_irq1 error (%d)\n", ret); - goto clean_up; + goto err_request_irq1; } INIT_LIST_HEAD(&fusb300->gadget.ep_list); @@ -1471,7 +1472,7 @@ static int fusb300_probe(struct platform_device *pdev) GFP_KERNEL); if (fusb300->ep0_req == NULL) { ret = -ENOMEM; - goto clean_up3; + goto err_alloc_request; } init_controller(fusb300); @@ -1486,7 +1487,10 @@ static int fusb300_probe(struct platform_device *pdev) err_add_udc: fusb300_free_request(&fusb300->ep[0]->ep, fusb300->ep0_req); -clean_up3: +err_alloc_request: + free_irq(ires1->start, fusb300); + +err_request_irq1: free_irq(ires->start, fusb300); clean_up: -- cgit v1.2.3 From b566d38857fcb6777f25b674b90a831eec0817a2 Mon Sep 17 00:00:00 2001 From: John Keeping Date: Thu, 24 Nov 2022 17:04:28 +0000 Subject: usb: gadget: f_fs: use io_data->status consistently Commit fb1f16d74e26 ("usb: gadget: f_fs: change ep->status safe in ffs_epfile_io()") added a new ffs_io_data::status field to fix lifetime issues in synchronous requests. While there are no similar lifetime issues for asynchronous requests (the separate ep member in ffs_io_data avoids them) using the status field means the USB request can be freed earlier and that there is more consistency between the synchronous and asynchronous I/O paths. Cc: Linyu Yuan Signed-off-by: John Keeping Reviewed-by: Linyu Yuan Link: https://lore.kernel.org/r/20221124170430.3998755-1-john@metanate.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 73dc10a77cde..1221f0d1b1a9 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -825,8 +825,7 @@ static void ffs_user_copy_worker(struct work_struct *work) { struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, work); - int ret = io_data->req->status ? io_data->req->status : - io_data->req->actual; + int ret = io_data->status; bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { @@ -840,8 +839,6 @@ static void ffs_user_copy_worker(struct work_struct *work) if (io_data->ffs->ffs_eventfd && !kiocb_has_eventfd) eventfd_signal(io_data->ffs->ffs_eventfd, 1); - usb_ep_free_request(io_data->ep, io_data->req); - if (io_data->read) kfree(io_data->to_free); ffs_free_buffer(io_data); @@ -856,6 +853,9 @@ static void ffs_epfile_async_io_complete(struct usb_ep *_ep, ENTER(); + io_data->status = req->status ? req->status : req->actual; + usb_ep_free_request(_ep, req); + INIT_WORK(&io_data->work, ffs_user_copy_worker); queue_work(ffs->io_completion_wq, &io_data->work); } -- cgit v1.2.3 From 0376aa62320cab35d8532629fdf9cd3b8cb66c8d Mon Sep 17 00:00:00 2001 From: Wang Yufen Date: Sat, 26 Nov 2022 22:35:32 +0800 Subject: usb: musb: fix error return code in da8xx_musb_init() Fix to return a negative error code -ENODEV instead of 0 as before commit 09721ba6daa1 ("usb: musb: da8xx: Call earlier clk_prepare_enable()") did. Signed-off-by: Wang Yufen Link: https://lore.kernel.org/r/1669473332-14165-1-git-send-email-wangyufen@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/da8xx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index a4e55b0c52cf..d47e5c94587b 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -368,8 +368,10 @@ static int da8xx_musb_init(struct musb *musb) /* Returns zero if e.g. not clocked */ rev = musb_readl(reg_base, DA8XX_USB_REVISION_REG); - if (!rev) + if (!rev) { + ret = -ENODEV; goto fail; + } musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); if (IS_ERR_OR_NULL(musb->xceiv)) { -- cgit v1.2.3 From 9aa1afc8f62263ed064dc5d94fa7a7ee6054e2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 12 Dec 2022 22:27:17 +0100 Subject: usb: chipidea: imx: Drop empty platform remove function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A remove callback just returning 0 is equivalent to no remove callback at all. So drop the useless function. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20221212212717.3774606-1-u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/usbmisc_imx.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index acdb13316cd0..c57c1a71a513 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -1263,14 +1263,8 @@ static int usbmisc_imx_probe(struct platform_device *pdev) return 0; } -static int usbmisc_imx_remove(struct platform_device *pdev) -{ - return 0; -} - static struct platform_driver usbmisc_imx_driver = { .probe = usbmisc_imx_probe, - .remove = usbmisc_imx_remove, .driver = { .name = "usbmisc_imx", .of_match_table = usbmisc_imx_dt_ids, -- cgit v1.2.3 From b04e1747fbcc6bf4a93a95b5c2505bf2a6467ee8 Mon Sep 17 00:00:00 2001 From: Saranya Gopal Date: Mon, 2 Jan 2023 11:51:08 +0530 Subject: usb: typec: ucsi: Register USB Power Delivery Capabilities UCSI allows the USB PD capabilities to be read with the GET_PDO command. This will register those capabilities and make them visible to user space. Reviewed-by: Heikki Krogerus Co-developed-by: Rajaram Regupathy Signed-off-by: Rajaram Regupathy Signed-off-by: Saranya Gopal Link: https://lore.kernel.org/r/20230102062108.838423-1-saranya.gopal@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 163 ++++++++++++++++++++++++++++++++++++++---- drivers/usb/typec/ucsi/ucsi.h | 8 +++ 2 files changed, 158 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index eabe519013e7..d04809476f71 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -562,8 +562,9 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient) } } -static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner, - u32 *pdos, int offset, int num_pdos) +static int ucsi_read_pdos(struct ucsi_connector *con, + enum typec_role role, int is_partner, + u32 *pdos, int offset, int num_pdos) { struct ucsi *ucsi = con->ucsi; u64 command; @@ -573,7 +574,7 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner, command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner); command |= UCSI_GET_PDOS_PDO_OFFSET(offset); command |= UCSI_GET_PDOS_NUM_PDOS(num_pdos - 1); - command |= UCSI_GET_PDOS_SRC_PDOS; + command |= is_source(role) ? UCSI_GET_PDOS_SRC_PDOS : 0; ret = ucsi_send_command(ucsi, command, pdos + offset, num_pdos * sizeof(u32)); if (ret < 0 && ret != -ETIMEDOUT) @@ -582,30 +583,43 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner, return ret; } -static int ucsi_get_src_pdos(struct ucsi_connector *con) +static int ucsi_get_pdos(struct ucsi_connector *con, enum typec_role role, + int is_partner, u32 *pdos) { + u8 num_pdos; int ret; /* UCSI max payload means only getting at most 4 PDOs at a time */ - ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS); + ret = ucsi_read_pdos(con, role, is_partner, pdos, 0, UCSI_MAX_PDOS); if (ret < 0) return ret; - con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */ - if (con->num_pdos < UCSI_MAX_PDOS) - return 0; + num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */ + if (num_pdos < UCSI_MAX_PDOS) + return num_pdos; /* get the remaining PDOs, if any */ - ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS, - PDO_MAX_OBJECTS - UCSI_MAX_PDOS); + ret = ucsi_read_pdos(con, role, is_partner, pdos, UCSI_MAX_PDOS, + PDO_MAX_OBJECTS - UCSI_MAX_PDOS); if (ret < 0) return ret; - con->num_pdos += ret / sizeof(u32); + return ret / sizeof(u32) + num_pdos; +} + +static int ucsi_get_src_pdos(struct ucsi_connector *con) +{ + int ret; + + ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, con->src_pdos); + if (ret < 0) + return ret; + + con->num_pdos = ret; ucsi_port_psy_changed(con); - return 0; + return ret; } static int ucsi_check_altmodes(struct ucsi_connector *con) @@ -630,6 +644,72 @@ static int ucsi_check_altmodes(struct ucsi_connector *con) return ret; } +static int ucsi_register_partner_pdos(struct ucsi_connector *con) +{ + struct usb_power_delivery_desc desc = { con->ucsi->cap.pd_version }; + struct usb_power_delivery_capabilities_desc caps; + struct usb_power_delivery_capabilities *cap; + int ret; + + if (con->partner_pd) + return 0; + + con->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(con->partner_pd)) + return PTR_ERR(con->partner_pd); + + ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, caps.pdo); + if (ret > 0) { + if (ret < PDO_MAX_OBJECTS) + caps.pdo[ret] = 0; + + caps.role = TYPEC_SOURCE; + cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + con->partner_source_caps = cap; + + ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd); + if (ret) { + usb_power_delivery_unregister_capabilities(con->partner_source_caps); + return ret; + } + } + + ret = ucsi_get_pdos(con, TYPEC_SINK, 1, caps.pdo); + if (ret > 0) { + if (ret < PDO_MAX_OBJECTS) + caps.pdo[ret] = 0; + + caps.role = TYPEC_SINK; + + cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps); + if (IS_ERR(cap)) + return PTR_ERR(cap); + + con->partner_sink_caps = cap; + + ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd); + if (ret) { + usb_power_delivery_unregister_capabilities(con->partner_sink_caps); + return ret; + } + } + + return 0; +} + +static void ucsi_unregister_partner_pdos(struct ucsi_connector *con) +{ + usb_power_delivery_unregister_capabilities(con->partner_sink_caps); + con->partner_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->partner_source_caps); + con->partner_source_caps = NULL; + usb_power_delivery_unregister(con->partner_pd); + con->partner_pd = NULL; +} + static void ucsi_pwr_opmode_change(struct ucsi_connector *con) { switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { @@ -638,6 +718,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con) typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); ucsi_partner_task(con, ucsi_get_src_pdos, 30, 0); ucsi_partner_task(con, ucsi_check_altmodes, 30, 0); + ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ); break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: con->rdo = 0; @@ -696,6 +777,7 @@ static void ucsi_unregister_partner(struct ucsi_connector *con) if (!con->partner) return; + ucsi_unregister_partner_pdos(con); ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP); typec_unregister_partner(con->partner); con->partner = NULL; @@ -800,6 +882,10 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (con->status.flags & UCSI_CONSTAT_CONNECTED) { ucsi_register_partner(con); ucsi_partner_task(con, ucsi_check_connection, 1, HZ); + + if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) == + UCSI_CONSTAT_PWR_OPMODE_PD) + ucsi_partner_task(con, ucsi_register_partner_pdos, 1, HZ); } else { ucsi_unregister_partner(con); } @@ -1036,6 +1122,9 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) static int ucsi_register_port(struct ucsi *ucsi, int index) { + struct usb_power_delivery_desc desc = { ucsi->cap.pd_version}; + struct usb_power_delivery_capabilities_desc pd_caps; + struct usb_power_delivery_capabilities *pd_cap; struct ucsi_connector *con = &ucsi->connector[index]; struct typec_capability *cap = &con->typec_cap; enum typec_accessory *accessory = cap->accessory; @@ -1114,6 +1203,41 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) goto out; } + con->pd = usb_power_delivery_register(ucsi->dev, &desc); + + ret = ucsi_get_pdos(con, TYPEC_SOURCE, 0, pd_caps.pdo); + if (ret > 0) { + if (ret < PDO_MAX_OBJECTS) + pd_caps.pdo[ret] = 0; + + pd_caps.role = TYPEC_SOURCE; + pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps); + if (IS_ERR(pd_cap)) { + ret = PTR_ERR(pd_cap); + goto out; + } + + con->port_source_caps = pd_cap; + typec_port_set_usb_power_delivery(con->port, con->pd); + } + + memset(&pd_caps, 0, sizeof(pd_caps)); + ret = ucsi_get_pdos(con, TYPEC_SINK, 0, pd_caps.pdo); + if (ret > 0) { + if (ret < PDO_MAX_OBJECTS) + pd_caps.pdo[ret] = 0; + + pd_caps.role = TYPEC_SINK; + pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps); + if (IS_ERR(pd_cap)) { + ret = PTR_ERR(pd_cap); + goto out; + } + + con->port_sink_caps = pd_cap; + typec_port_set_usb_power_delivery(con->port, con->pd); + } + /* Alternate modes */ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON); if (ret) { @@ -1152,8 +1276,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) if (con->status.flags & UCSI_CONSTAT_CONNECTED) { typec_set_pwr_role(con->port, !!(con->status.flags & UCSI_CONSTAT_PWR_DIR)); - ucsi_pwr_opmode_change(con); ucsi_register_partner(con); + ucsi_pwr_opmode_change(con); ucsi_port_psy_changed(con); } @@ -1259,6 +1383,13 @@ err_unregister: ucsi_unregister_port_psy(con); if (con->wq) destroy_workqueue(con->wq); + + usb_power_delivery_unregister_capabilities(con->port_sink_caps); + con->port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(con->port_source_caps); + con->port_source_caps = NULL; + usb_power_delivery_unregister(con->pd); + con->pd = NULL; typec_unregister_port(con->port); con->port = NULL; } @@ -1422,6 +1553,12 @@ void ucsi_unregister(struct ucsi *ucsi) ucsi_unregister_port_psy(&ucsi->connector[i]); if (ucsi->connector[i].wq) destroy_workqueue(ucsi->connector[i].wq); + usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_sink_caps); + ucsi->connector[i].port_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(ucsi->connector[i].port_source_caps); + ucsi->connector[i].port_source_caps = NULL; + usb_power_delivery_unregister(ucsi->connector[i].pd); + ucsi->connector[i].pd = NULL; typec_unregister_port(ucsi->connector[i].port); } diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index c968474ee547..e2191e37be12 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -339,6 +339,14 @@ struct ucsi_connector { u32 src_pdos[PDO_MAX_OBJECTS]; int num_pdos; + /* USB PD objects */ + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *port_source_caps; + struct usb_power_delivery_capabilities *port_sink_caps; + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_source_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; + struct usb_role_switch *usb_role_sw; }; -- cgit v1.2.3 From 4d70c74659d9746502b23d055dba03d1d28ec388 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 30 Nov 2022 15:48:35 +0200 Subject: i915: Move list_count() to list.h as list_count_nodes() for broader use Some of the existing users, and definitely will be new ones, want to count existing nodes in the list. Provide a generic API for that by moving code from i915 to list.h. Reviewed-by: Lucas De Marchi Acked-by: Jani Nikula Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20221130134838.23805-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/i915/gt/intel_engine_cs.c | 15 ++------------- include/linux/list.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/i915/gt/intel_engine_cs.c b/drivers/gpu/drm/i915/gt/intel_engine_cs.c index c33e0d72d670..0dcf4ac92335 100644 --- a/drivers/gpu/drm/i915/gt/intel_engine_cs.c +++ b/drivers/gpu/drm/i915/gt/intel_engine_cs.c @@ -2094,17 +2094,6 @@ static void print_request_ring(struct drm_printer *m, struct i915_request *rq) } } -static unsigned long list_count(struct list_head *list) -{ - struct list_head *pos; - unsigned long count = 0; - - list_for_each(pos, list) - count++; - - return count; -} - static unsigned long read_ul(void *p, size_t x) { return *(unsigned long *)(p + x); @@ -2279,8 +2268,8 @@ void intel_engine_dump(struct intel_engine_cs *engine, spin_lock_irqsave(&engine->sched_engine->lock, flags); engine_dump_active_requests(engine, m); - drm_printf(m, "\tOn hold?: %lu\n", - list_count(&engine->sched_engine->hold)); + drm_printf(m, "\tOn hold?: %zu\n", + list_count_nodes(&engine->sched_engine->hold)); spin_unlock_irqrestore(&engine->sched_engine->lock, flags); drm_printf(m, "\tMMIO base: 0x%08x\n", engine->mmio_base); diff --git a/include/linux/list.h b/include/linux/list.h index 61762054b4be..f10344dbad4d 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -655,6 +655,21 @@ static inline void list_splice_tail_init(struct list_head *list, !list_is_head(pos, (head)); \ pos = n, n = pos->prev) +/** + * list_count_nodes - count nodes in the list + * @head: the head for your list. + */ +static inline size_t list_count_nodes(struct list_head *head) +{ + struct list_head *pos; + size_t count = 0; + + list_for_each(pos, head) + count++; + + return count; +} + /** * list_entry_is_head - test if the entry points to the head of the list * @pos: the type * to cursor -- cgit v1.2.3 From 94c6bcec9f6c4843a9a8d07dddc042ca7a93777c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 30 Nov 2022 15:48:36 +0200 Subject: usb: gadget: hid: Convert to use list_count_nodes() The list API provides the list_count_nodes() to help with counting existing nodes in the list. Utilise it. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20221130134838.23805-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/hid.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/legacy/hid.c b/drivers/usb/gadget/legacy/hid.c index 1187ee4f316a..133daf88162e 100644 --- a/drivers/usb/gadget/legacy/hid.c +++ b/drivers/usb/gadget/legacy/hid.c @@ -133,14 +133,11 @@ static struct usb_configuration config_driver = { static int hid_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; - struct list_head *tmp; struct hidg_func_node *n = NULL, *m, *iter_n; struct f_hid_opts *hid_opts; - int status, funcs = 0; - - list_for_each(tmp, &hidg_func_list) - funcs++; + int status, funcs; + funcs = list_count_nodes(&hidg_func_list); if (!funcs) return -ENODEV; -- cgit v1.2.3 From 66eccb5274c0b1cdca3bbd1b3627c52e94575808 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 30 Nov 2022 15:48:37 +0200 Subject: usb: gadget: udc: bcm63xx: Convert to use list_count_nodes() The list API provides the list_count_nodes() to help with counting existing nodes in the list. Utilise it. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20221130134838.23805-3-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bcm63xx_udc.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index 2cdb07905bde..771ba1ffce95 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -2172,7 +2172,6 @@ static int bcm63xx_iudma_dbg_show(struct seq_file *s, void *p) for (ch_idx = 0; ch_idx < BCM63XX_NUM_IUDMA; ch_idx++) { struct iudma_ch *iudma = &udc->iudma[ch_idx]; - struct list_head *pos; seq_printf(s, "IUDMA channel %d -- ", ch_idx); switch (iudma_defaults[ch_idx].ep_type) { @@ -2205,14 +2204,10 @@ static int bcm63xx_iudma_dbg_show(struct seq_file *s, void *p) seq_printf(s, " desc: %d/%d used", iudma->n_bds_used, iudma->n_bds); - if (iudma->bep) { - i = 0; - list_for_each(pos, &iudma->bep->queue) - i++; - seq_printf(s, "; %d queued\n", i); - } else { + if (iudma->bep) + seq_printf(s, "; %zu queued\n", list_count_nodes(&iudma->bep->queue)); + else seq_printf(s, "\n"); - } for (i = 0; i < iudma->n_bds; i++) { struct bcm_enet_desc *d = &iudma->bd_ring[i]; -- cgit v1.2.3 From 5220cb493bf418cc4ce5f3ba961dbd0207441731 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 30 Nov 2022 15:48:38 +0200 Subject: xhci: Convert to use list_count_nodes() The list API provides the list_count_nodes() to help with counting existing nodes in the list. Utilise it. Acked-by: Mathias Nyman Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20221130134838.23805-4-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ddc30037f9ce..aa4d34efecd2 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2528,7 +2528,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, union xhci_trb *ep_trb; int status = -EINPROGRESS; struct xhci_ep_ctx *ep_ctx; - struct list_head *tmp; u32 trb_comp_code; int td_num = 0; bool handling_skipped_tds = false; @@ -2582,10 +2581,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, } /* Count current td numbers if ep->skip is set */ - if (ep->skip) { - list_for_each(tmp, &ep_ring->td_list) - td_num++; - } + if (ep->skip) + td_num += list_count_nodes(&ep_ring->td_list); /* Look for common error cases */ switch (trb_comp_code) { -- cgit v1.2.3 From 50459f103edfe47c9a599d766a850ef6014936c5 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 15 Nov 2016 18:44:29 +0200 Subject: media: uvcvideo: Remove format descriptions The V4L2 core overwrites format descriptions in v4l_fill_fmtdesc(), there's no need to manually set the descriptions in the driver. This prepares for removal of the format descriptions from the uvc_fmts table. Unlike V4L2, UVC makes a distinction between the SD-DV, SDL-DV and HD-DV formats. It also indicates whether the DV format uses 50Hz or 60Hz. This information is parsed by the driver to construct a format name string that is printed in a debug message, but serves no other purpose as V4L2 has a single V4L2_PIX_FMT_DV pixel format that covers all those cases. As the information is available in the UVC descriptors, and thus accessible to users with lsusb if they really care, don't log it in a debug message and drop the format name string to simplify the code. Signed-off-by: Laurent Pinchart Reviewed-by: Ricardo Ribalda Reviewed-by: Michael Grzeschik --- drivers/media/usb/uvc/uvc_driver.c | 24 ++---------------------- drivers/media/usb/uvc/uvc_v4l2.c | 2 -- drivers/media/usb/uvc/uvcvideo.h | 2 -- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index e4bcb5011360..0a35d7c0d0a0 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -251,14 +251,10 @@ static int uvc_parse_format(struct uvc_device *dev, fmtdesc = uvc_format_by_guid(&buffer[5]); if (fmtdesc != NULL) { - strscpy(format->name, fmtdesc->name, - sizeof(format->name)); format->fcc = fmtdesc->fcc; } else { dev_info(&streaming->intf->dev, "Unknown video format %pUl\n", &buffer[5]); - snprintf(format->name, sizeof(format->name), "%pUl\n", - &buffer[5]); format->fcc = 0; } @@ -270,8 +266,6 @@ static int uvc_parse_format(struct uvc_device *dev, */ if (dev->quirks & UVC_QUIRK_FORCE_Y8) { if (format->fcc == V4L2_PIX_FMT_YUYV) { - strscpy(format->name, "Greyscale 8-bit (Y8 )", - sizeof(format->name)); format->fcc = V4L2_PIX_FMT_GREY; format->bpp = 8; width_multiplier = 2; @@ -312,7 +306,6 @@ static int uvc_parse_format(struct uvc_device *dev, return -EINVAL; } - strscpy(format->name, "MJPEG", sizeof(format->name)); format->fcc = V4L2_PIX_FMT_MJPEG; format->flags = UVC_FMT_FLAG_COMPRESSED; format->bpp = 0; @@ -328,17 +321,7 @@ static int uvc_parse_format(struct uvc_device *dev, return -EINVAL; } - switch (buffer[8] & 0x7f) { - case 0: - strscpy(format->name, "SD-DV", sizeof(format->name)); - break; - case 1: - strscpy(format->name, "SDL-DV", sizeof(format->name)); - break; - case 2: - strscpy(format->name, "HD-DV", sizeof(format->name)); - break; - default: + if ((buffer[8] & 0x7f) > 2) { uvc_dbg(dev, DESCR, "device %d videostreaming interface %d: unknown DV format %u\n", dev->udev->devnum, @@ -346,9 +329,6 @@ static int uvc_parse_format(struct uvc_device *dev, return -EINVAL; } - strlcat(format->name, buffer[8] & (1 << 7) ? " 60Hz" : " 50Hz", - sizeof(format->name)); - format->fcc = V4L2_PIX_FMT_DV; format->flags = UVC_FMT_FLAG_COMPRESSED | UVC_FMT_FLAG_STREAM; format->bpp = 0; @@ -375,7 +355,7 @@ static int uvc_parse_format(struct uvc_device *dev, return -EINVAL; } - uvc_dbg(dev, DESCR, "Found format %s\n", format->name); + uvc_dbg(dev, DESCR, "Found format %p4cc", &format->fcc); buflen -= buffer[0]; buffer += buffer[0]; diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index f4d4c33b6dfb..dcd178d249b6 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -660,8 +660,6 @@ static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream, fmt->flags = 0; if (format->flags & UVC_FMT_FLAG_COMPRESSED) fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; - strscpy(fmt->description, format->name, sizeof(fmt->description)); - fmt->description[sizeof(fmt->description) - 1] = 0; fmt->pixelformat = format->fcc; return 0; } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index df93db259312..b60e4ae95e81 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -264,8 +264,6 @@ struct uvc_format { u32 fcc; u32 flags; - char name[32]; - unsigned int nframes; struct uvc_frame *frame; }; -- cgit v1.2.3 From 41ddb251c68ac75c101d3a50a68c4629c9055e4c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 20 Sep 2022 16:04:55 +0200 Subject: media: uvcvideo: Handle cameras with invalid descriptors If the source entity does not contain any pads, do not create a link. Reported-by: syzbot Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_entity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_entity.c b/drivers/media/usb/uvc/uvc_entity.c index 7c4d2f93d351..cc68dd24eb42 100644 --- a/drivers/media/usb/uvc/uvc_entity.c +++ b/drivers/media/usb/uvc/uvc_entity.c @@ -37,7 +37,7 @@ static int uvc_mc_create_links(struct uvc_video_chain *chain, continue; remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]); - if (remote == NULL) + if (remote == NULL || remote->num_pads == 0) return -EINVAL; source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING) -- cgit v1.2.3 From 3bc22dc66a4f386393119118169ed0b78c389898 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 20 Sep 2022 16:09:50 +0200 Subject: media: uvcvideo: Only create input devs if hw supports it Examine the stream headers to figure out if the device has a button and can be used as an input. Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_status.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index 7518ffce22ed..cb90aff344bc 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -18,11 +18,34 @@ * Input device */ #ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV + +static bool uvc_input_has_button(struct uvc_device *dev) +{ + struct uvc_streaming *stream; + + /* + * The device has button events if both bTriggerSupport and + * bTriggerUsage are one. Otherwise the camera button does not + * exist or is handled automatically by the camera without host + * driver or client application intervention. + */ + list_for_each_entry(stream, &dev->streams, list) { + if (stream->header.bTriggerSupport == 1 && + stream->header.bTriggerUsage == 1) + return true; + } + + return false; +} + static int uvc_input_init(struct uvc_device *dev) { struct input_dev *input; int ret; + if (!uvc_input_has_button(dev)) + return 0; + input = input_allocate_device(); if (input == NULL) return -ENOMEM; -- cgit v1.2.3 From 4867bb590ae445bcfaa711a86b603c97e94574b3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 25 Oct 2022 16:41:01 +0200 Subject: media: uvcvideo: Handle errors from calls to usb_string On a Webcam from Quanta, we see the following error. usb 3-5: New USB device found, idVendor=0408, idProduct=30d2, bcdDevice= 0.03 usb 3-5: New USB device strings: Mfr=3, Product=1, SerialNumber=2 usb 3-5: Product: USB2.0 HD UVC WebCam usb 3-5: Manufacturer: Quanta usb 3-5: SerialNumber: 0x0001 ... uvcvideo: Found UVC 1.10 device USB2.0 HD UVC WebCam (0408:30d2) uvcvideo: Failed to initialize entity for entity 5 uvcvideo: Failed to register entities (-22). The Webcam reports an entity of type UVC_VC_EXTENSION_UNIT. It reports a string index of '7' associated with that entity. The attempt to read that string from the camera fails with error -32 (-EPIPE). usb_string() returns that error, but it is ignored. As result, the entity name is empty. This later causes v4l2_device_register_subdev() to return -EINVAL, and no entities are registered as result. While this appears to be a firmware problem with the camera, the kernel should still handle the situation gracefully. To do that, check the return value from usb_string(). If it reports an error, assign the entity's default name. Signed-off-by: Guenter Roeck Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 48 +++++++++++++++----------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 0a35d7c0d0a0..4c372965517d 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -859,10 +859,8 @@ static int uvc_parse_vendor_control(struct uvc_device *dev, + n; memcpy(unit->extension.bmControls, &buffer[23+p], 2*n); - if (buffer[24+p+2*n] != 0) - usb_string(udev, buffer[24+p+2*n], unit->name, - sizeof(unit->name)); - else + if (buffer[24+p+2*n] == 0 || + usb_string(udev, buffer[24+p+2*n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Extension %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -986,15 +984,15 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->media.bmTransportModes, &buffer[10+n], p); } - if (buffer[7] != 0) - usb_string(udev, buffer[7], term->name, - sizeof(term->name)); - else if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) - sprintf(term->name, "Camera %u", buffer[3]); - else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) - sprintf(term->name, "Media %u", buffer[3]); - else - sprintf(term->name, "Input %u", buffer[3]); + if (buffer[7] == 0 || + usb_string(udev, buffer[7], term->name, sizeof(term->name)) < 0) { + if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) + sprintf(term->name, "Camera %u", buffer[3]); + if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) + sprintf(term->name, "Media %u", buffer[3]); + else + sprintf(term->name, "Input %u", buffer[3]); + } list_add_tail(&term->list, &dev->entities); break; @@ -1027,10 +1025,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->baSourceID, &buffer[7], 1); - if (buffer[8] != 0) - usb_string(udev, buffer[8], term->name, - sizeof(term->name)); - else + if (buffer[8] == 0 || + usb_string(udev, buffer[8], term->name, sizeof(term->name)) < 0) sprintf(term->name, "Output %u", buffer[3]); list_add_tail(&term->list, &dev->entities); @@ -1052,10 +1048,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(unit->baSourceID, &buffer[5], p); - if (buffer[5+p] != 0) - usb_string(udev, buffer[5+p], unit->name, - sizeof(unit->name)); - else + if (buffer[5+p] == 0 || + usb_string(udev, buffer[5+p], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Selector %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -1085,10 +1079,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, if (dev->uvc_version >= 0x0110) unit->processing.bmVideoStandards = buffer[9+n]; - if (buffer[8+n] != 0) - usb_string(udev, buffer[8+n], unit->name, - sizeof(unit->name)); - else + if (buffer[8+n] == 0 || + usb_string(udev, buffer[8+n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Processing %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); @@ -1116,10 +1108,8 @@ static int uvc_parse_standard_control(struct uvc_device *dev, unit->extension.bmControls = (u8 *)unit + sizeof(*unit); memcpy(unit->extension.bmControls, &buffer[23+p], n); - if (buffer[23+p+n] != 0) - usb_string(udev, buffer[23+p+n], unit->name, - sizeof(unit->name)); - else + if (buffer[23+p+n] == 0 || + usb_string(udev, buffer[23+p+n], unit->name, sizeof(unit->name)) < 0) sprintf(unit->name, "Extension %u", buffer[3]); list_add_tail(&unit->list, &dev->entities); -- cgit v1.2.3 From 2a8c1952ed4bc85f64174def8c00c71c264dc3a2 Mon Sep 17 00:00:00 2001 From: Pedro Guilherme Siqueira Moreira Date: Tue, 25 Oct 2022 02:04:48 -0300 Subject: media: uvcvideo: Fix missing newline after declarations Fixes 'Missing a blank line after declarations' warning issued by scripts/checkpatch.pl on drivers/media/usb/uvc/uvc_driver.c Signed-off-by: Pedro Guilherme Siqueira Moreira Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 4c372965517d..8f649fa9ab22 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -712,6 +712,7 @@ static int uvc_parse_streaming(struct uvc_device *dev, /* Parse the alternate settings to find the maximum bandwidth. */ for (i = 0; i < intf->num_altsetting; ++i) { struct usb_host_endpoint *ep; + alts = &intf->altsetting[i]; ep = uvc_find_endpoint(alts, streaming->header.bEndpointAddress); @@ -1826,12 +1827,14 @@ static void uvc_delete(struct kref *kref) list_for_each_safe(p, n, &dev->chains) { struct uvc_video_chain *chain; + chain = list_entry(p, struct uvc_video_chain, list); kfree(chain); } list_for_each_safe(p, n, &dev->entities) { struct uvc_entity *entity; + entity = list_entry(p, struct uvc_entity, list); #ifdef CONFIG_MEDIA_CONTROLLER uvc_mc_cleanup_entity(entity); @@ -1841,6 +1844,7 @@ static void uvc_delete(struct kref *kref) list_for_each_safe(p, n, &dev->streams) { struct uvc_streaming *streaming; + streaming = list_entry(p, struct uvc_streaming, list); usb_driver_release_interface(&uvc_driver.driver, streaming->intf); -- cgit v1.2.3 From 7b78a8464d5701796a4e146b3973422350e56977 Mon Sep 17 00:00:00 2001 From: Pedro Guilherme Siqueira Moreira Date: Tue, 25 Oct 2022 02:04:49 -0300 Subject: media: uvcvideo: Fix assignment inside if condition Fixes 'do not use assignment in if condition' errors issued by scripts/checkpatch.pl on drivers/media/usb/uvc/uvc_driver.c Signed-off-by: Pedro Guilherme Siqueira Moreira Reviewed-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 8f649fa9ab22..d84e46fdb1b9 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1144,7 +1144,8 @@ static int uvc_parse_control(struct uvc_device *dev) buffer[1] != USB_DT_CS_INTERFACE) goto next_descriptor; - if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0) + ret = uvc_parse_standard_control(dev, buffer, buflen); + if (ret < 0) return ret; next_descriptor: @@ -2180,7 +2181,8 @@ static int uvc_probe(struct usb_interface *intf, usb_set_intfdata(intf, dev); /* Initialize the interrupt URB. */ - if ((ret = uvc_status_init(dev)) < 0) { + ret = uvc_status_init(dev); + if (ret < 0) { dev_info(&dev->udev->dev, "Unable to initialize the status endpoint (%d), status interrupt will not be supported.\n", ret); -- cgit v1.2.3 From 0ce75d5ecd9edaa027c0c2a7df0edcdf59ea7738 Mon Sep 17 00:00:00 2001 From: Pedro Guilherme Siqueira Moreira Date: Tue, 25 Oct 2022 02:04:50 -0300 Subject: media: uvcvideo: Fix usage of symbolic permissions to octal Change symbolic permissions to octal equivalents as recommended by scripts/checkpatch.pl on drivers/media/usb/uvc/uvc_driver.c. Signed-off-by: Pedro Guilherme Siqueira Moreira Reviewed-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index d84e46fdb1b9..4f59583bf516 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2329,17 +2329,17 @@ static int uvc_clock_param_set(const char *val, const struct kernel_param *kp) } module_param_call(clock, uvc_clock_param_set, uvc_clock_param_get, - &uvc_clock_param, S_IRUGO|S_IWUSR); + &uvc_clock_param, 0644); MODULE_PARM_DESC(clock, "Video buffers timestamp clock"); -module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, S_IRUGO|S_IWUSR); +module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, 0644); MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps"); -module_param_named(nodrop, uvc_no_drop_param, uint, S_IRUGO|S_IWUSR); +module_param_named(nodrop, uvc_no_drop_param, uint, 0644); MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames"); -module_param_named(quirks, uvc_quirks_param, uint, S_IRUGO|S_IWUSR); +module_param_named(quirks, uvc_quirks_param, uint, 0644); MODULE_PARM_DESC(quirks, "Forced device quirks"); -module_param_named(trace, uvc_dbg_param, uint, S_IRUGO|S_IWUSR); +module_param_named(trace, uvc_dbg_param, uint, 0644); MODULE_PARM_DESC(trace, "Trace level bitmask"); -module_param_named(timeout, uvc_timeout_param, uint, S_IRUGO|S_IWUSR); +module_param_named(timeout, uvc_timeout_param, uint, 0644); MODULE_PARM_DESC(timeout, "Streaming control requests timeout"); /* ------------------------------------------------------------------------ -- cgit v1.2.3 From adfd3910c27fbc0959ae5f1dafaec563f7d0bdd2 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 20 Dec 2022 23:56:44 +0100 Subject: media: uvcvideo: Remove void casting for the status endpoint Make the code more resilient, by replacing the castings with proper structure definitions and using offsetof() instead of open coding the location of the data. Suggested-by: Sergey Senozhatsky Signed-off-by: Ricardo Ribalda Reviewed-by: Andrzej Pietrasiewicz Reviewed-by: Laurent Pinchart Reviewed-by: Sergey Senozhatsky Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_status.c | 65 +++++++++++++------------------------- drivers/media/usb/uvc/uvcvideo.h | 25 +++++++++++++-- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index cb90aff344bc..602830a8023e 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -96,38 +96,23 @@ static void uvc_input_report_key(struct uvc_device *dev, unsigned int code, /* -------------------------------------------------------------------------- * Status interrupt endpoint */ -struct uvc_streaming_status { - u8 bStatusType; - u8 bOriginator; - u8 bEvent; - u8 bValue[]; -} __packed; - -struct uvc_control_status { - u8 bStatusType; - u8 bOriginator; - u8 bEvent; - u8 bSelector; - u8 bAttribute; - u8 bValue[]; -} __packed; - static void uvc_event_streaming(struct uvc_device *dev, - struct uvc_streaming_status *status, int len) + struct uvc_status *status, int len) { - if (len < 3) { + if (len <= offsetof(struct uvc_status, bEvent)) { uvc_dbg(dev, STATUS, "Invalid streaming status event received\n"); return; } if (status->bEvent == 0) { - if (len < 4) + if (len <= offsetof(struct uvc_status, streaming)) return; + uvc_dbg(dev, STATUS, "Button (intf %u) %s len %d\n", status->bOriginator, - status->bValue[0] ? "pressed" : "released", len); - uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]); + status->streaming.button ? "pressed" : "released", len); + uvc_input_report_key(dev, KEY_CAMERA, status->streaming.button); } else { uvc_dbg(dev, STATUS, "Stream %u error event %02x len %d\n", status->bOriginator, status->bEvent, len); @@ -154,7 +139,7 @@ static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity, } static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev, - const struct uvc_control_status *status, + const struct uvc_status *status, struct uvc_video_chain **chain) { list_for_each_entry((*chain), &dev->chains, list) { @@ -166,7 +151,7 @@ static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev, continue; ctrl = uvc_event_entity_find_ctrl(entity, - status->bSelector); + status->control.bSelector); if (ctrl) return ctrl; } @@ -176,7 +161,7 @@ static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev, } static bool uvc_event_control(struct urb *urb, - const struct uvc_control_status *status, int len) + const struct uvc_status *status, int len) { static const char *attrs[] = { "value", "info", "failure", "min", "max" }; struct uvc_device *dev = urb->context; @@ -184,24 +169,24 @@ static bool uvc_event_control(struct urb *urb, struct uvc_control *ctrl; if (len < 6 || status->bEvent != 0 || - status->bAttribute >= ARRAY_SIZE(attrs)) { + status->control.bAttribute >= ARRAY_SIZE(attrs)) { uvc_dbg(dev, STATUS, "Invalid control status event received\n"); return false; } uvc_dbg(dev, STATUS, "Control %u/%u %s change len %d\n", - status->bOriginator, status->bSelector, - attrs[status->bAttribute], len); + status->bOriginator, status->control.bSelector, + attrs[status->control.bAttribute], len); /* Find the control. */ ctrl = uvc_event_find_ctrl(dev, status, &chain); if (!ctrl) return false; - switch (status->bAttribute) { + switch (status->control.bAttribute) { case UVC_CTRL_VALUE_CHANGE: return uvc_ctrl_status_event_async(urb, chain, ctrl, - status->bValue); + status->control.bValue); case UVC_CTRL_INFO_CHANGE: case UVC_CTRL_FAILURE_CHANGE: @@ -237,28 +222,22 @@ static void uvc_status_complete(struct urb *urb) len = urb->actual_length; if (len > 0) { - switch (dev->status[0] & 0x0f) { + switch (dev->status->bStatusType & 0x0f) { case UVC_STATUS_TYPE_CONTROL: { - struct uvc_control_status *status = - (struct uvc_control_status *)dev->status; - - if (uvc_event_control(urb, status, len)) + if (uvc_event_control(urb, dev->status, len)) /* The URB will be resubmitted in work context. */ return; break; } case UVC_STATUS_TYPE_STREAMING: { - struct uvc_streaming_status *status = - (struct uvc_streaming_status *)dev->status; - - uvc_event_streaming(dev, status, len); + uvc_event_streaming(dev, dev->status, len); break; } default: uvc_dbg(dev, STATUS, "Unknown status event type %u\n", - dev->status[0]); + dev->status->bStatusType); break; } } @@ -282,12 +261,12 @@ int uvc_status_init(struct uvc_device *dev) uvc_input_init(dev); - dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL); - if (dev->status == NULL) + dev->status = kzalloc(sizeof(*dev->status), GFP_KERNEL); + if (!dev->status) return -ENOMEM; dev->int_urb = usb_alloc_urb(0, GFP_KERNEL); - if (dev->int_urb == NULL) { + if (!dev->int_urb) { kfree(dev->status); return -ENOMEM; } @@ -304,7 +283,7 @@ int uvc_status_init(struct uvc_device *dev) interval = fls(interval) - 1; usb_fill_int_urb(dev->int_urb, dev->udev, pipe, - dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete, + dev->status, sizeof(*dev->status), uvc_status_complete, dev, interval); return 0; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index b60e4ae95e81..cd5861cae3b0 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -51,8 +51,6 @@ #define UVC_URBS 5 /* Maximum number of packets per URB. */ #define UVC_MAX_PACKETS 32 -/* Maximum status buffer size in bytes of interrupt URB. */ -#define UVC_MAX_STATUS_SIZE 16 #define UVC_CTRL_CONTROL_TIMEOUT 5000 #define UVC_CTRL_STREAMING_TIMEOUT 5000 @@ -525,6 +523,26 @@ struct uvc_device_info { const struct uvc_control_mapping **mappings; }; +struct uvc_status_streaming { + u8 button; +} __packed; + +struct uvc_status_control { + u8 bSelector; + u8 bAttribute; + u8 bValue[11]; +} __packed; + +struct uvc_status { + u8 bStatusType; + u8 bOriginator; + u8 bEvent; + union { + struct uvc_status_control control; + struct uvc_status_streaming streaming; + }; +} __packed; + struct uvc_device { struct usb_device *udev; struct usb_interface *intf; @@ -557,7 +575,8 @@ struct uvc_device { /* Status Interrupt Endpoint */ struct usb_host_endpoint *int_ep; struct urb *int_urb; - u8 *status; + struct uvc_status *status; + struct input_dev *input; char input_phys[64]; -- cgit v1.2.3 From 16045708a3ff0266b1b8cca6198ea50eb80fff6a Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 6 Dec 2022 00:15:04 +0100 Subject: media: uvcvideo: Recover stalled ElGato devices Elgato Cam Link 4k can be in a stalled state if the resolution of the external source has changed while the firmware initializes. Once in this state, the device is useless until it receives a USB reset. It has even been observed that the stalled state will continue even after unplugging the device. lsusb -v Bus 002 Device 002: ID 0fd9:0066 Elgato Systems GmbH Cam Link 4K Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 3.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 bDeviceProtocol 1 Interface Association bMaxPacketSize0 9 idVendor 0x0fd9 Elgato Systems GmbH idProduct 0x0066 bcdDevice 0.00 iManufacturer 1 Elgato iProduct 2 Cam Link 4K iSerial 4 0005AC52FE000 bNumConfigurations 1 Reviewed-by: Sergey Senozhatsky Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index d2eb9066e4dc..503ef29211ca 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -129,12 +129,13 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, return -EPIPE; } +static const struct usb_device_id elgato_cam_link_4k = { + USB_DEVICE(0x0fd9, 0x0066) +}; + static void uvc_fixup_video_ctrl(struct uvc_streaming *stream, struct uvc_streaming_control *ctrl) { - static const struct usb_device_id elgato_cam_link_4k = { - USB_DEVICE(0x0fd9, 0x0066) - }; struct uvc_format *format = NULL; struct uvc_frame *frame = NULL; unsigned int i; @@ -297,7 +298,7 @@ static int uvc_get_video_ctrl(struct uvc_streaming *stream, dev_err(&stream->intf->dev, "Failed to query (%u) UVC %s control : %d (exp. %u).\n", query, probe ? "probe" : "commit", ret, size); - ret = -EIO; + ret = (ret == -EPROTO) ? -EPROTO : -EIO; goto out; } @@ -2121,6 +2122,21 @@ int uvc_video_init(struct uvc_streaming *stream) * request on the probe control, as required by the UVC specification. */ ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR); + + /* + * Elgato Cam Link 4k can be in a stalled state if the resolution of + * the external source has changed while the firmware initializes. + * Once in this state, the device is useless until it receives a + * USB reset. It has even been observed that the stalled state will + * continue even after unplugging the device. + */ + if (ret == -EPROTO && + usb_match_one_id(stream->dev->intf, &elgato_cam_link_4k)) { + dev_err(&stream->intf->dev, "Elgato Cam Link 4K firmware crash detected\n"); + dev_err(&stream->intf->dev, "Resetting the device, unplug and replug to recover\n"); + usb_reset_device(stream->dev->udev); + } + if (ret < 0) return ret; -- cgit v1.2.3 From 81e78a6fc320693c269990fb1af37b8c2c382cf2 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 2 Dec 2022 17:45:06 +0100 Subject: media: uvcvideo: Limit power line control for Acer EasyCamera The device does not implement the power line control correctly. Add a corresponding control mapping override. Bus 003 Device 002: ID 5986:1180 Acer, Inc EasyCamera Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x5986 Acer, Inc idProduct 0x1180 bcdDevice 56.04 iManufacturer 3 Bison iProduct 1 EasyCamera iSerial 2 bNumConfigurations 1 Signed-off-by: Ricardo Ribalda Reviewed-by: Sergey Senozhatsky Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 4f59583bf516..1de17db3356b 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2967,6 +2967,15 @@ static const struct usb_device_id uvc_ids[] = { .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited }, + /* Acer EasyCamera */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x5986, + .idProduct = 0x1180, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited }, /* Intel RealSense D4M */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, -- cgit v1.2.3 From 5f0e659d2a2ac6172d495915f2775fa592c7ab17 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Tue, 25 Oct 2022 21:42:21 +0300 Subject: media: uvcvideo: Factor out usb_string() calls When parsing UVC descriptors to instantiate entities, the driver calls usb_string() to retrieve the entity name from the device, and falls back to a default name if the string can't be retrieved. This code pattern occurs multiple times. Factor it out to a separate helper function. Signed-off-by: Laurent Pinchart Reviewed-by: Guenter Roeck --- drivers/media/usb/uvc/uvc_driver.c | 59 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 1de17db3356b..5f6e0da0ef90 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -794,6 +794,27 @@ static struct uvc_entity *uvc_alloc_entity(u16 type, u16 id, return entity; } +static void uvc_entity_set_name(struct uvc_device *dev, struct uvc_entity *entity, + const char *type_name, u8 string_id) +{ + int ret; + + /* + * First attempt to read the entity name from the device. If the entity + * has no associated string, or if reading the string fails (most + * likely due to a buggy firmware), fall back to default names based on + * the entity type. + */ + if (string_id) { + ret = usb_string(dev->udev, string_id, entity->name, + sizeof(entity->name)); + if (!ret) + return; + } + + sprintf(entity->name, "%s %u", type_name, entity->id); +} + /* Parse vendor-specific extensions. */ static int uvc_parse_vendor_control(struct uvc_device *dev, const unsigned char *buffer, int buflen) @@ -860,9 +881,7 @@ static int uvc_parse_vendor_control(struct uvc_device *dev, + n; memcpy(unit->extension.bmControls, &buffer[23+p], 2*n); - if (buffer[24+p+2*n] == 0 || - usb_string(udev, buffer[24+p+2*n], unit->name, sizeof(unit->name)) < 0) - sprintf(unit->name, "Extension %u", buffer[3]); + uvc_entity_set_name(dev, unit, "Extension", buffer[24+p+2*n]); list_add_tail(&unit->list, &dev->entities); handled = 1; @@ -880,6 +899,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, struct usb_interface *intf; struct usb_host_interface *alts = dev->intf->cur_altsetting; unsigned int i, n, p, len; + const char *type_name; u16 type; switch (buffer[2]) { @@ -985,15 +1005,14 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->media.bmTransportModes, &buffer[10+n], p); } - if (buffer[7] == 0 || - usb_string(udev, buffer[7], term->name, sizeof(term->name)) < 0) { - if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) - sprintf(term->name, "Camera %u", buffer[3]); - if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) - sprintf(term->name, "Media %u", buffer[3]); - else - sprintf(term->name, "Input %u", buffer[3]); - } + if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) + type_name = "Camera"; + else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT) + type_name = "Media"; + else + type_name = "Input"; + + uvc_entity_set_name(dev, term, type_name, buffer[7]); list_add_tail(&term->list, &dev->entities); break; @@ -1026,9 +1045,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(term->baSourceID, &buffer[7], 1); - if (buffer[8] == 0 || - usb_string(udev, buffer[8], term->name, sizeof(term->name)) < 0) - sprintf(term->name, "Output %u", buffer[3]); + uvc_entity_set_name(dev, term, "Output", buffer[8]); list_add_tail(&term->list, &dev->entities); break; @@ -1049,9 +1066,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, memcpy(unit->baSourceID, &buffer[5], p); - if (buffer[5+p] == 0 || - usb_string(udev, buffer[5+p], unit->name, sizeof(unit->name)) < 0) - sprintf(unit->name, "Selector %u", buffer[3]); + uvc_entity_set_name(dev, unit, "Selector", buffer[5+p]); list_add_tail(&unit->list, &dev->entities); break; @@ -1080,9 +1095,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, if (dev->uvc_version >= 0x0110) unit->processing.bmVideoStandards = buffer[9+n]; - if (buffer[8+n] == 0 || - usb_string(udev, buffer[8+n], unit->name, sizeof(unit->name)) < 0) - sprintf(unit->name, "Processing %u", buffer[3]); + uvc_entity_set_name(dev, unit, "Processing", buffer[8+n]); list_add_tail(&unit->list, &dev->entities); break; @@ -1109,9 +1122,7 @@ static int uvc_parse_standard_control(struct uvc_device *dev, unit->extension.bmControls = (u8 *)unit + sizeof(*unit); memcpy(unit->extension.bmControls, &buffer[23+p], n); - if (buffer[23+p+n] == 0 || - usb_string(udev, buffer[23+p+n], unit->name, sizeof(unit->name)) < 0) - sprintf(unit->name, "Extension %u", buffer[3]); + uvc_entity_set_name(dev, unit, "Extension", buffer[23+p+n]); list_add_tail(&unit->list, &dev->entities); break; -- cgit v1.2.3 From 9f582f0418ed1c18f92c9e4628075d6ec9a7d9fb Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 3 Jan 2023 15:36:19 +0100 Subject: media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check for inactive controls in uvc_ctrl_is_accessible(). Use the new value for the master_id controls if present, otherwise use the existing value to determine if it is OK to set the control. Doing this here avoids attempting to set an inactive control, which will return an error from the USB device, which returns an invalid errorcode. This fixes: warn: v4l2-test-controls.cpp(483): s_ctrl returned EIO   warn: v4l2-test-controls.cpp(483): s_ctrl returned EIO test VIDIOC_G/S_CTRL: OK   warn: v4l2-test-controls.cpp(739): s_ext_ctrls returned EIO   warn: v4l2-test-controls.cpp(739): s_ext_ctrls returned EIO   warn: v4l2-test-controls.cpp(816): s_ext_ctrls returned EIO test VIDIOC_G/S/TRY_EXT_CTRLS: OK Tested with: v4l2-ctl -c auto_exposure=1 OK v4l2-ctl -c exposure_time_absolute=251 OK v4l2-ctl -c auto_exposure=3 OK v4l2-ctl -c exposure_time_absolute=251 VIDIOC_S_EXT_CTRLS: failed: Input/output error exposure_time_absolute: Input/output error ERROR v4l2-ctl -c auto_exposure=3,exposure_time_absolute=251,auto_exposure=1 v4l2-ctl -C auto_exposure,exposure_time_absolute   auto_exposure: 1 exposure_time_absolute: 251 Reviewed-by: Laurent Pinchart Reviewed-by: Ricardo Ribalda Signed-off-by: Hans Verkuil Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 42 +++++++++++++++++++++++++++++++++++++++- drivers/media/usb/uvc/uvc_v4l2.c | 3 +-- drivers/media/usb/uvc/uvcvideo.h | 3 ++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index c95a2229f4fa..6f5aaaf09ee0 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1085,11 +1085,28 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id, return 0; } +/* + * Check if control @v4l2_id can be accessed by the given control @ioctl + * (VIDIOC_G_EXT_CTRLS, VIDIOC_TRY_EXT_CTRLS or VIDIOC_S_EXT_CTRLS). + * + * For set operations on slave controls, check if the master's value is set to + * manual, either in the others controls set in the same ioctl call, or from + * the master's current value. This catches VIDIOC_S_EXT_CTRLS calls that set + * both the master and slave control, such as for instance setting + * auto_exposure=1, exposure_time_absolute=251. + */ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, - bool read) + const struct v4l2_ext_controls *ctrls, + unsigned long ioctl) { + struct uvc_control_mapping *master_map = NULL; + struct uvc_control *master_ctrl = NULL; struct uvc_control_mapping *mapping; struct uvc_control *ctrl; + bool read = ioctl == VIDIOC_G_EXT_CTRLS; + s32 val; + int ret; + int i; if (__uvc_query_v4l2_class(chain, v4l2_id, 0) >= 0) return -EACCES; @@ -1104,6 +1121,29 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read) return -EACCES; + if (ioctl != VIDIOC_S_EXT_CTRLS || !mapping->master_id) + return 0; + + /* + * Iterate backwards in cases where the master control is accessed + * multiple times in the same ioctl. We want the last value. + */ + for (i = ctrls->count - 1; i >= 0; i--) { + if (ctrls->controls[i].id == mapping->master_id) + return ctrls->controls[i].value == + mapping->master_manual ? 0 : -EACCES; + } + + __uvc_find_control(ctrl->entity, mapping->master_id, &master_map, + &master_ctrl, 0); + + if (!master_ctrl || !(master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) + return 0; + + ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); + if (ret >= 0 && val != mapping->master_manual) + return -EACCES; + return 0; } diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index dcd178d249b6..4187e59680bb 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -1018,8 +1018,7 @@ static int uvc_ctrl_check_access(struct uvc_video_chain *chain, int ret = 0; for (i = 0; i < ctrls->count; ++ctrl, ++i) { - ret = uvc_ctrl_is_accessible(chain, ctrl->id, - ioctl == VIDIOC_G_EXT_CTRLS); + ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, ioctl); if (ret) break; } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index cd5861cae3b0..8c0df94374c9 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -778,7 +778,8 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle) int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl); int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl); int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, - bool read); + const struct v4l2_ext_controls *ctrls, + unsigned long ioctl); int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control_query *xqry); -- cgit v1.2.3 From 67a655be5748426a9bda7845fc264ccf71a76ea3 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 3 Jan 2023 15:36:20 +0100 Subject: media: uvcvideo: Improve error logging in uvc_query_ctrl() Standard use of the driver may result in error messages on the kernel log. This can hide other more important messages, and alert unnecessarily the user. Let's keep dev_err() for the important occasions. If __uvc_query_ctrl() failed with a non -EPIPE error, then report that with dev_err. If an error code is obtained, then report that with dev_dbg. Reviewed-by: Ricardo Ribalda Signed-off-by: Hans Verkuil Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 503ef29211ca..f35674fb8912 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -79,13 +79,14 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, if (likely(ret == size)) return 0; - dev_err(&dev->udev->dev, - "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n", - uvc_query_name(query), cs, unit, ret, size); - - if (ret != -EPIPE) + if (ret != -EPIPE) { + dev_err(&dev->udev->dev, + "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n", + uvc_query_name(query), cs, unit, ret, size); return ret; + } + /* Reuse data[0] to request the error code. */ tmp = *(u8 *)data; ret = __uvc_query_ctrl(dev, UVC_GET_CUR, 0, intfnum, -- cgit v1.2.3 From 44cdba4130ecdb59ff94c617b6cd8dc22b4f3c97 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 3 Jan 2023 15:36:21 +0100 Subject: media: uvcvideo: Return -EACCES for Wrong state error Error 2 is defined by UVC as: Wrong State: The device is in a state that disallows the specific request. The device will remain in this state until a specific action from the host or the user is completed. This is documented as happening when attempting to set the value of a manual control when the device is in auto mode. While V4L2 allows this, the closest error code defined by VIDIOC_S_CTRL is EACCES: EACCES: Attempt to set a read-only control or to get a write-only control. Or if there is an attempt to set an inactive control and the driver is not capable of caching the new value until the control is active again. Replace EILSEQ with EACCES. Reviewed-by: Laurent Pinchart Suggested-by: Hans Verkuil Signed-off-by: Ricardo Ribalda Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index f35674fb8912..3d3753a4c1f8 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -108,7 +108,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, case 1: /* Not ready */ return -EBUSY; case 2: /* Wrong state */ - return -EILSEQ; + return -EACCES; case 3: /* Power */ return -EREMOTE; case 4: /* Out of range */ -- cgit v1.2.3 From a763b9fb58be869e252a7d33acb0a6390b01c801 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 3 Jan 2023 15:36:22 +0100 Subject: media: uvcvideo: Do not return positive errors in uvc_query_ctrl() If the returned size of the query does not match the expected size or it is zero, return -EPIPE instead of 0 or a positive value. This will avoid confusing the caller (and ultimately userspace) that doesn't expect a positive or zero value. Reviewed-by: Laurent Pinchart Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 3d3753a4c1f8..e4b71df08216 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -83,7 +83,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, dev_err(&dev->udev->dev, "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n", uvc_query_name(query), cs, unit, ret, size); - return ret; + return ret < 0 ? ret : -EPIPE; } /* Reuse data[0] to request the error code. */ -- cgit v1.2.3 From 7faf8ae4277156da32eada72c07f5edeb82cd9e4 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 3 Jan 2023 15:36:23 +0100 Subject: media: uvcvideo: Fix handling on Bitmask controls Minimum and step values for V4L2_CTRL_TYPE_BITMASK controls should be 0. There is no need to query the camera firmware about this and maybe get invalid results. Also value should be masked to the max value advertised by the hardware. Finally, handle UVC 1.5 mask controls that use MAX instead of RES to describe the valid bits. Fixes v4l2-compliane: Control ioctls (Input 0): fail: v4l2-test-controls.cpp(97): minimum must be 0 for a bitmask control test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: FAIL Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 6f5aaaf09ee0..65d41946ef1a 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1161,6 +1161,25 @@ static const char *uvc_map_get_name(const struct uvc_control_mapping *map) return "Unknown Control"; } +static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl, + struct uvc_control_mapping *mapping) +{ + /* + * Some controls, like CT_AE_MODE_CONTROL, use GET_RES to represent + * the number of bits supported. Those controls do not list GET_MAX + * as supported. + */ + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES) + return mapping->get(mapping, UVC_GET_RES, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); + + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) + return mapping->get(mapping, UVC_GET_MAX, + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX)); + + return ~0; +} + static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, struct uvc_control *ctrl, struct uvc_control_mapping *mapping, @@ -1235,6 +1254,12 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, v4l2_ctrl->step = 0; return 0; + case V4L2_CTRL_TYPE_BITMASK: + v4l2_ctrl->minimum = 0; + v4l2_ctrl->maximum = uvc_get_ctrl_bitmap(ctrl, mapping); + v4l2_ctrl->step = 0; + return 0; + default: break; } @@ -1336,19 +1361,14 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain, menu_info = &mapping->menu_info[query_menu->index]; - if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK && - (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) { - s32 bitmap; - + if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) { if (!ctrl->cached) { ret = uvc_ctrl_populate_cache(chain, ctrl); if (ret < 0) goto done; } - bitmap = mapping->get(mapping, UVC_GET_RES, - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); - if (!(bitmap & menu_info->value)) { + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) { ret = -EINVAL; goto done; } @@ -1831,6 +1851,17 @@ int uvc_ctrl_set(struct uvc_fh *handle, value = xctrl->value; break; + case V4L2_CTRL_TYPE_BITMASK: + if (!ctrl->cached) { + ret = uvc_ctrl_populate_cache(chain, ctrl); + if (ret < 0) + return ret; + } + + xctrl->value &= uvc_get_ctrl_bitmap(ctrl, mapping); + value = xctrl->value; + break; + case V4L2_CTRL_TYPE_BOOLEAN: xctrl->value = clamp(xctrl->value, 0, 1); value = xctrl->value; @@ -1845,17 +1876,14 @@ int uvc_ctrl_set(struct uvc_fh *handle, * Valid menu indices are reported by the GET_RES request for * UVC controls that support it. */ - if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK && - (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) { + if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) { if (!ctrl->cached) { ret = uvc_ctrl_populate_cache(chain, ctrl); if (ret < 0) return ret; } - step = mapping->get(mapping, UVC_GET_RES, - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); - if (!(step & value)) + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & value)) return -EINVAL; } -- cgit v1.2.3 From 252d50da337ba97019574b1e830879d5dd5121d2 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 3 Jan 2023 15:36:25 +0100 Subject: media: uvcvideo: Refactor __uvc_ctrl_add_mapping Simplify the exit code with a common error tag freeing all the memory. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 65d41946ef1a..ffa0e2654264 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -2286,32 +2286,30 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, unsigned int i; /* - * Most mappings come from static kernel data and need to be duplicated. + * Most mappings come from static kernel data, and need to be duplicated. * Mappings that come from userspace will be unnecessarily duplicated, * this could be optimized. */ map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL); - if (map == NULL) + if (!map) return -ENOMEM; + map->name = NULL; + map->menu_info = NULL; + /* For UVCIOC_CTRL_MAP custom control */ if (mapping->name) { map->name = kstrdup(mapping->name, GFP_KERNEL); - if (!map->name) { - kfree(map); - return -ENOMEM; - } + if (!map->name) + goto err_nomem; } INIT_LIST_HEAD(&map->ev_subs); size = sizeof(*mapping->menu_info) * mapping->menu_count; map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL); - if (map->menu_info == NULL) { - kfree(map->name); - kfree(map); - return -ENOMEM; - } + if (!map->menu_info) + goto err_nomem; if (map->get == NULL) map->get = uvc_get_le_value; @@ -2332,6 +2330,12 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, ctrl->info.selector); return 0; + +err_nomem: + kfree(map->menu_info); + kfree(map->name); + kfree(map); + return -ENOMEM; } int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, -- cgit v1.2.3 From 5dd0eab84ae9a4b292baf1ad02e1a273c475cd04 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 3 Jan 2023 12:01:21 +0100 Subject: media: uvcvideo: Limit power line control for Acer EasyCamera The device does not implement the power line control correctly. Add a corresponding control mapping override. Bus 003 Device 002: ID 5986:1180 Acer, Inc EasyCamera Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x5986 Acer, Inc idProduct 0x1180 bcdDevice 56.04 iManufacturer 3 Bison iProduct 1 EasyCamera iSerial 2 bNumConfigurations 1 Reviewed-by: Sergey Senozhatsky Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 5f6e0da0ef90..80ccb42d8f4a 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2979,6 +2979,15 @@ static const struct usb_device_id uvc_ids[] = { .bInterfaceProtocol = 0, .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited }, /* Acer EasyCamera */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x5986, + .idProduct = 0x1180, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited }, + /* Acer EasyCamera */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, .idVendor = 0x5986, -- cgit v1.2.3 From 70fcaf92a39e6cb7542de0fc0ad4562f42f7f54a Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 4 Jan 2023 11:45:19 +0100 Subject: media: uvcvideo: Extend documentation of uvc_video_clock_decode() Make an explicit reference to UVC 1.5, explaining how the algorithm supports the different behaviour of UVC 1.1 and 1.5. Reviewed-by: Laurent Pinchart Tested-by: HungNien Chen Signed-off-by: Ricardo Ribalda Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index e4b71df08216..b1f7416858b7 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -518,7 +518,9 @@ uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf, /* * To limit the amount of data, drop SCRs with an SOF identical to the - * previous one. + * previous one. This filtering is also needed to support UVC 1.5, where + * all the data packets of the same frame contains the same SOF. In that + * case only the first one will match the host_sof. */ dev_sof = get_unaligned_le16(&data[header_size - 2]); if (dev_sof == stream->clock.last_sof) -- cgit v1.2.3 From 40140eda661ea4be219ef194a4f43b7b5e3bbd27 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 14:52:54 +0100 Subject: media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU Replace the count with a mask field that lets us choose not only the max value, but also the minimum value and what values are valid in between. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 33 +++++++++++++++++++++++---------- drivers/media/usb/uvc/uvc_driver.c | 4 +++- drivers/media/usb/uvc/uvc_v4l2.c | 3 ++- drivers/media/usb/uvc/uvcvideo.h | 2 +- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index ffa0e2654264..305cce26f567 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include #include #include #include @@ -525,7 +526,8 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = { .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_BITMASK, .menu_info = exposure_auto_controls, - .menu_count = ARRAY_SIZE(exposure_auto_controls), + .menu_mask = GENMASK(V4L2_EXPOSURE_APERTURE_PRIORITY, + V4L2_EXPOSURE_AUTO), .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, }, }, { @@ -731,7 +733,8 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = { .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, .menu_info = power_line_frequency_controls, - .menu_count = ARRAY_SIZE(power_line_frequency_controls) - 1, + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), }, }; @@ -745,7 +748,8 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = { .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, .menu_info = power_line_frequency_controls, - .menu_count = ARRAY_SIZE(power_line_frequency_controls), + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, + V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), }, }; @@ -975,7 +979,9 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping, const struct uvc_menu_info *menu = mapping->menu_info; unsigned int i; - for (i = 0; i < mapping->menu_count; ++i, ++menu) { + for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) { + if (!test_bit(i, &mapping->menu_mask)) + continue; if (menu->value == value) { value = i; break; @@ -1228,12 +1234,14 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, switch (mapping->v4l2_type) { case V4L2_CTRL_TYPE_MENU: - v4l2_ctrl->minimum = 0; - v4l2_ctrl->maximum = mapping->menu_count - 1; + v4l2_ctrl->minimum = ffs(mapping->menu_mask) - 1; + v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1; v4l2_ctrl->step = 1; menu = mapping->menu_info; - for (i = 0; i < mapping->menu_count; ++i, ++menu) { + for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) { + if (!test_bit(i, &mapping->menu_mask)) + continue; if (menu->value == v4l2_ctrl->default_value) { v4l2_ctrl->default_value = i; break; @@ -1354,7 +1362,7 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain, goto done; } - if (query_menu->index >= mapping->menu_count) { + if (!test_bit(query_menu->index, &mapping->menu_mask)) { ret = -EINVAL; goto done; } @@ -1868,8 +1876,13 @@ int uvc_ctrl_set(struct uvc_fh *handle, break; case V4L2_CTRL_TYPE_MENU: - if (xctrl->value < 0 || xctrl->value >= mapping->menu_count) + if (xctrl->value < (ffs(mapping->menu_mask) - 1) || + xctrl->value > (fls(mapping->menu_mask) - 1)) return -ERANGE; + + if (!test_bit(xctrl->value, &mapping->menu_mask)) + return -EINVAL; + value = mapping->menu_info[xctrl->value].value; /* @@ -2306,7 +2319,7 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, INIT_LIST_HEAD(&map->ev_subs); - size = sizeof(*mapping->menu_info) * mapping->menu_count; + size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask); map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL); if (!map->menu_info) goto err_nomem; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 80ccb42d8f4a..b4710e1aa0a5 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -2371,7 +2372,8 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = { .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, .menu_info = power_line_frequency_controls_limited, - .menu_count = ARRAY_SIZE(power_line_frequency_controls_limited), + .menu_mask = + GENMASK(ARRAY_SIZE(power_line_frequency_controls_limited) - 1, 0), }; static const struct uvc_device_info uvc_ctrl_power_line_limited = { diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 4187e59680bb..950b42d78a10 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include #include #include #include @@ -80,7 +81,7 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain, goto free_map; } - map->menu_count = xmap->menu_count; + map->menu_mask = GENMASK(xmap->menu_count - 1, 0); break; default: diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 8c0df94374c9..e8f277e380a5 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -115,7 +115,7 @@ struct uvc_control_mapping { u32 data_type; const struct uvc_menu_info *menu_info; - u32 menu_count; + unsigned long menu_mask; u32 master_id; s32 master_manual; -- cgit v1.2.3 From 96a160b068e09b4ed5a32e2179e5219fc3545eca Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 14:52:55 +0100 Subject: media: uvcvideo: Refactor uvc_ctrl_mappings_uvcXX Convert the array of structs into an array of pointers, that way the mappings can be reused. Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 74 ++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 305cce26f567..af765d4cae90 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -723,34 +723,40 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = { }, }; -static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = { - { - .id = V4L2_CID_POWER_LINE_FREQUENCY, - .entity = UVC_GUID_UVC_PROCESSING, - .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, - .size = 2, - .offset = 0, - .v4l2_type = V4L2_CTRL_TYPE_MENU, - .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls, - .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, - V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), - }, +static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = { + .id = V4L2_CID_POWER_LINE_FREQUENCY, + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .size = 2, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_MENU, + .data_type = UVC_CTRL_DATA_TYPE_ENUM, + .menu_info = power_line_frequency_controls, + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), }; -static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = { - { - .id = V4L2_CID_POWER_LINE_FREQUENCY, - .entity = UVC_GUID_UVC_PROCESSING, - .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, - .size = 2, - .offset = 0, - .v4l2_type = V4L2_CTRL_TYPE_MENU, - .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls, - .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, - V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), - }, +static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc11[] = { + &uvc_ctrl_power_line_mapping_uvc11, + NULL, /* Sentinel */ +}; + +static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = { + .id = V4L2_CID_POWER_LINE_FREQUENCY, + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .size = 2, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_MENU, + .data_type = UVC_CTRL_DATA_TYPE_ENUM, + .menu_info = power_line_frequency_controls, + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, + V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), +}; + +static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc15[] = { + &uvc_ctrl_power_line_mapping_uvc15, + NULL, /* Sentinel */ }; /* ------------------------------------------------------------------------ @@ -2506,8 +2512,7 @@ static void uvc_ctrl_prune_entity(struct uvc_device *dev, static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain, struct uvc_control *ctrl) { - const struct uvc_control_mapping *mappings; - unsigned int num_mappings; + const struct uvc_control_mapping **mappings; unsigned int i; /* @@ -2574,16 +2579,11 @@ static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain, } /* Finally process version-specific mappings. */ - if (chain->dev->uvc_version < 0x0150) { - mappings = uvc_ctrl_mappings_uvc11; - num_mappings = ARRAY_SIZE(uvc_ctrl_mappings_uvc11); - } else { - mappings = uvc_ctrl_mappings_uvc15; - num_mappings = ARRAY_SIZE(uvc_ctrl_mappings_uvc15); - } + mappings = chain->dev->uvc_version < 0x0150 + ? uvc_ctrl_mappings_uvc11 : uvc_ctrl_mappings_uvc15; - for (i = 0; i < num_mappings; ++i) { - const struct uvc_control_mapping *mapping = &mappings[i]; + for (i = 0; mappings[i]; ++i) { + const struct uvc_control_mapping *mapping = mappings[i]; if (uvc_entity_match_guid(ctrl->entity, mapping->entity) && ctrl->info.selector == mapping->selector) -- cgit v1.2.3 From 3aa8628eb78a63d0bf93e159846e9c92bccc8f69 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 14:52:56 +0100 Subject: media: uvcvideo: Refactor power_line_frequency_controls_limited Move the control mapping to uvc_ctrl.c. This way we do not have references to UVC controls or V4L2 controls in uvc_driver.c. This also fixes a bug introduced in commit 382075604a68 ("media: uvcvideo: Limit power line control for Quanta UVC Webcam"). The offending commit caused the power line control menu entries to have incorrect indices compared to the V4L2_CID_POWER_LINE_FREQUENCY_* enumeration. Now that the limited mapping reuses the correct menu_info array, the indices correctly map to the V4L2 control specification. Fixes: 382075604a68 ("media: uvcvideo: Limit power line control for Quanta UVC Webcam") Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 13 +++++++++++++ drivers/media/usb/uvc/uvc_driver.c | 18 ------------------ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index af765d4cae90..c15c38427cc3 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -723,6 +723,19 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = { }, }; +const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = { + .id = V4L2_CID_POWER_LINE_FREQUENCY, + .entity = UVC_GUID_UVC_PROCESSING, + .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, + .size = 2, + .offset = 0, + .v4l2_type = V4L2_CTRL_TYPE_MENU, + .data_type = UVC_CTRL_DATA_TYPE_ENUM, + .menu_info = power_line_frequency_controls, + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + V4L2_CID_POWER_LINE_FREQUENCY_50HZ), +}; + static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = { .id = V4L2_CID_POWER_LINE_FREQUENCY, .entity = UVC_GUID_UVC_PROCESSING, diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index b4710e1aa0a5..370b46c98108 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2358,24 +2358,6 @@ MODULE_PARM_DESC(timeout, "Streaming control requests timeout"); * Driver initialization and cleanup */ -static const struct uvc_menu_info power_line_frequency_controls_limited[] = { - { 1, "50 Hz" }, - { 2, "60 Hz" }, -}; - -static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = { - .id = V4L2_CID_POWER_LINE_FREQUENCY, - .entity = UVC_GUID_UVC_PROCESSING, - .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, - .size = 2, - .offset = 0, - .v4l2_type = V4L2_CTRL_TYPE_MENU, - .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls_limited, - .menu_mask = - GENMASK(ARRAY_SIZE(power_line_frequency_controls_limited) - 1, 0), -}; - static const struct uvc_device_info uvc_ctrl_power_line_limited = { .mappings = (const struct uvc_control_mapping *[]) { &uvc_ctrl_power_line_mapping_limited, diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index e8f277e380a5..6573df1e0a47 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -745,6 +745,7 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags); void uvc_status_stop(struct uvc_device *dev); /* Controls */ +extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited; extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops; int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, -- cgit v1.2.3 From a7c28150af42798263a30bedbe46f201b46fb486 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 14:52:57 +0100 Subject: media: uvcvideo: Fix power line control for Lenovo Integrated Camera The device does not implement the power line frequenyc control correctly. It is a UVC 1.5 device, but implements the control as a UVC 1.1 device. Add the corresponding control mapping override. Bus 003 Device 002: ID 30c9:0093 Lenovo Integrated Camera Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.01 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x30c9 idProduct 0x0093 bcdDevice 0.07 iManufacturer 3 Lenovo iProduct 1 Integrated Camera iSerial 2 8SSC21J75356V1SR2830069 bNumConfigurations 1 Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 2 +- drivers/media/usb/uvc/uvc_driver.c | 16 ++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index c15c38427cc3..6354efffe9b0 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -736,7 +736,7 @@ const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = { V4L2_CID_POWER_LINE_FREQUENCY_50HZ), }; -static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = { +const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = { .id = V4L2_CID_POWER_LINE_FREQUENCY, .entity = UVC_GUID_UVC_PROCESSING, .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL, diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 370b46c98108..6005e3c7eb96 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2365,6 +2365,13 @@ static const struct uvc_device_info uvc_ctrl_power_line_limited = { }, }; +static const struct uvc_device_info uvc_ctrl_power_line_uvc11 = { + .mappings = (const struct uvc_control_mapping *[]) { + &uvc_ctrl_power_line_mapping_uvc11, + NULL, /* Sentinel */ + }, +}; + static const struct uvc_device_info uvc_quirk_probe_minmax = { .quirks = UVC_QUIRK_PROBE_MINMAX, }; @@ -2944,6 +2951,15 @@ static const struct usb_device_id uvc_ids[] = { .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_FORCE_BPP) }, + /* Lenovo Integrated Camera */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x30c9, + .idProduct = 0x0093, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = UVC_PC_PROTOCOL_15, + .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_uvc11 }, /* Sonix Technology USB 2.0 Camera */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 6573df1e0a47..178729467967 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -746,6 +746,7 @@ void uvc_status_stop(struct uvc_device *dev); /* Controls */ extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited; +extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11; extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops; int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, -- cgit v1.2.3 From 716c330433e3ad6074d057092b98c09a989e17d8 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 14:52:58 +0100 Subject: media: uvcvideo: Use standard names for menus Instead of duplicating the menu info, use the one from the core. Also, do not use extra memory for 1:1 mappings. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 129 ++++++++++++++++++++++++++++----------- drivers/media/usb/uvc/uvc_v4l2.c | 105 +++++++++++++++++++++++-------- drivers/media/usb/uvc/uvcvideo.h | 3 +- include/uapi/linux/uvcvideo.h | 4 +- 4 files changed, 176 insertions(+), 65 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 6354efffe9b0..ee58f0db2763 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -364,19 +364,45 @@ static const u32 uvc_control_classes[] = { V4L2_CID_USER_CLASS, }; -static const struct uvc_menu_info power_line_frequency_controls[] = { - { 0, "Disabled" }, - { 1, "50 Hz" }, - { 2, "60 Hz" }, - { 3, "Auto" }, -}; +static const int exposure_auto_mapping[] = { 2, 1, 4, 8 }; -static const struct uvc_menu_info exposure_auto_controls[] = { - { 2, "Auto Mode" }, - { 1, "Manual Mode" }, - { 4, "Shutter Priority Mode" }, - { 8, "Aperture Priority Mode" }, -}; +/* + * This function translates the V4L2 menu index @idx, as exposed to userspace as + * the V4L2 control value, to the corresponding UVC control value used by the + * device. The custom menu_mapping in the control @mapping is used when + * available, otherwise the function assumes that the V4L2 and UVC values are + * identical. + * + * For controls of type UVC_CTRL_DATA_TYPE_BITMASK, the UVC control value is + * expressed as a bitmask and is thus guaranteed to have a single bit set. + * + * The function returns -EINVAL if the V4L2 menu index @idx isn't valid for the + * control, which includes all controls whose type isn't UVC_CTRL_DATA_TYPE_ENUM + * or UVC_CTRL_DATA_TYPE_BITMASK. + */ +static int uvc_mapping_get_menu_value(const struct uvc_control_mapping *mapping, + u32 idx) +{ + if (!test_bit(idx, &mapping->menu_mask)) + return -EINVAL; + + if (mapping->menu_mapping) + return mapping->menu_mapping[idx]; + + return idx; +} + +static const char * +uvc_mapping_get_menu_name(const struct uvc_control_mapping *mapping, u32 idx) +{ + if (!test_bit(idx, &mapping->menu_mask)) + return NULL; + + if (mapping->menu_names) + return mapping->menu_names[idx]; + + return v4l2_ctrl_get_menu(mapping->id)[idx]; +} static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping, u8 query, const u8 *data) @@ -525,7 +551,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = { .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_BITMASK, - .menu_info = exposure_auto_controls, + .menu_mapping = exposure_auto_mapping, .menu_mask = GENMASK(V4L2_EXPOSURE_APERTURE_PRIORITY, V4L2_EXPOSURE_AUTO), .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, }, @@ -731,7 +757,6 @@ const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = { .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls, .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, V4L2_CID_POWER_LINE_FREQUENCY_50HZ), }; @@ -744,7 +769,6 @@ const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = { .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls, .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), }; @@ -762,7 +786,6 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = { .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_ENUM, - .menu_info = power_line_frequency_controls, .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, V4L2_CID_POWER_LINE_FREQUENCY_DISABLED), }; @@ -995,13 +1018,17 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping, s32 value = mapping->get(mapping, UVC_GET_CUR, data); if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) { - const struct uvc_menu_info *menu = mapping->menu_info; unsigned int i; - for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) { + for (i = 0; BIT(i) <= mapping->menu_mask; ++i) { + u32 menu_value; + if (!test_bit(i, &mapping->menu_mask)) continue; - if (menu->value == value) { + + menu_value = uvc_mapping_get_menu_value(mapping, i); + + if (menu_value == value) { value = i; break; } @@ -1212,7 +1239,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, { struct uvc_control_mapping *master_map = NULL; struct uvc_control *master_ctrl = NULL; - const struct uvc_menu_info *menu; unsigned int i; memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl)); @@ -1257,11 +1283,15 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1; v4l2_ctrl->step = 1; - menu = mapping->menu_info; - for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) { + for (i = 0; BIT(i) <= mapping->menu_mask; ++i) { + u32 menu_value; + if (!test_bit(i, &mapping->menu_mask)) continue; - if (menu->value == v4l2_ctrl->default_value) { + + menu_value = uvc_mapping_get_menu_value(mapping, i); + + if (menu_value == v4l2_ctrl->default_value) { v4l2_ctrl->default_value = i; break; } @@ -1360,11 +1390,11 @@ done: int uvc_query_v4l2_menu(struct uvc_video_chain *chain, struct v4l2_querymenu *query_menu) { - const struct uvc_menu_info *menu_info; struct uvc_control_mapping *mapping; struct uvc_control *ctrl; u32 index = query_menu->index; u32 id = query_menu->id; + const char *name; int ret; memset(query_menu, 0, sizeof(*query_menu)); @@ -1386,22 +1416,34 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain, goto done; } - menu_info = &mapping->menu_info[query_menu->index]; - if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) { + int mask; + if (!ctrl->cached) { ret = uvc_ctrl_populate_cache(chain, ctrl); if (ret < 0) goto done; } - if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) { + mask = uvc_mapping_get_menu_value(mapping, query_menu->index); + if (mask < 0) { + ret = mask; + goto done; + } + + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & mask)) { ret = -EINVAL; goto done; } } - strscpy(query_menu->name, menu_info->name, sizeof(query_menu->name)); + name = uvc_mapping_get_menu_name(mapping, query_menu->index); + if (!name) { + ret = -EINVAL; + goto done; + } + + strscpy(query_menu->name, name, sizeof(query_menu->name)); done: mutex_unlock(&chain->ctrl_mutex); @@ -1902,7 +1944,7 @@ int uvc_ctrl_set(struct uvc_fh *handle, if (!test_bit(xctrl->value, &mapping->menu_mask)) return -EINVAL; - value = mapping->menu_info[xctrl->value].value; + value = uvc_mapping_get_menu_value(mapping, xctrl->value); /* * Valid menu indices are reported by the GET_RES request for @@ -2327,7 +2369,8 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, return -ENOMEM; map->name = NULL; - map->menu_info = NULL; + map->menu_names = NULL; + map->menu_mapping = NULL; /* For UVCIOC_CTRL_MAP custom control */ if (mapping->name) { @@ -2338,10 +2381,22 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, INIT_LIST_HEAD(&map->ev_subs); - size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask); - map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL); - if (!map->menu_info) - goto err_nomem; + if (mapping->menu_mapping && mapping->menu_mask) { + size = sizeof(mapping->menu_mapping[0]) + * fls(mapping->menu_mask); + map->menu_mapping = kmemdup(mapping->menu_mapping, size, + GFP_KERNEL); + if (!map->menu_mapping) + goto err_nomem; + } + if (mapping->menu_names && mapping->menu_mask) { + size = sizeof(mapping->menu_names[0]) + * fls(mapping->menu_mask); + map->menu_names = kmemdup(mapping->menu_names, size, + GFP_KERNEL); + if (!map->menu_names) + goto err_nomem; + } if (map->get == NULL) map->get = uvc_get_le_value; @@ -2364,7 +2419,8 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain, return 0; err_nomem: - kfree(map->menu_info); + kfree(map->menu_names); + kfree(map->menu_mapping); kfree(map->name); kfree(map); return -ENOMEM; @@ -2689,7 +2745,8 @@ static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev, list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) { list_del(&mapping->list); - kfree(mapping->menu_info); + kfree(mapping->menu_names); + kfree(mapping->menu_mapping); kfree(mapping->name); kfree(mapping); } diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 950b42d78a10..35453f81c1d9 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -26,14 +26,84 @@ #include "uvcvideo.h" +static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain, + struct uvc_control_mapping *map, + const struct uvc_xu_control_mapping *xmap) +{ + unsigned int i; + size_t size; + int ret; + + /* + * Prevent excessive memory consumption, as well as integer + * overflows. + */ + if (xmap->menu_count == 0 || + xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) + return -EINVAL; + + map->menu_names = NULL; + map->menu_mapping = NULL; + + map->menu_mask = BIT_MASK(xmap->menu_count); + + size = xmap->menu_count * sizeof(*map->menu_mapping); + map->menu_mapping = kzalloc(size, GFP_KERNEL); + if (!map->menu_mapping) { + ret = -ENOMEM; + goto done; + } + + for (i = 0; i < xmap->menu_count ; i++) { + if (copy_from_user((u32 *)&map->menu_mapping[i], + &xmap->menu_info[i].value, + sizeof(map->menu_mapping[i]))) { + ret = -EACCES; + goto done; + } + } + + /* + * Always use the standard naming if available, otherwise copy the + * names supplied by userspace. + */ + if (!v4l2_ctrl_get_menu(map->id)) { + size = xmap->menu_count * sizeof(map->menu_names[0]); + map->menu_names = kzalloc(size, GFP_KERNEL); + if (!map->menu_names) { + ret = -ENOMEM; + goto done; + } + + for (i = 0; i < xmap->menu_count ; i++) { + /* sizeof(names[i]) - 1: to take care of \0 */ + if (copy_from_user((char *)map->menu_names[i], + xmap->menu_info[i].name, + sizeof(map->menu_names[i]) - 1)) { + ret = -EACCES; + goto done; + } + } + } + + ret = uvc_ctrl_add_mapping(chain, map); + +done: + kfree(map->menu_names); + map->menu_names = NULL; + kfree(map->menu_mapping); + map->menu_mapping = NULL; + + return ret; +} + /* ------------------------------------------------------------------------ * UVC ioctls */ -static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain, - struct uvc_xu_control_mapping *xmap) +static int uvc_ioctl_xu_ctrl_map(struct uvc_video_chain *chain, + struct uvc_xu_control_mapping *xmap) { struct uvc_control_mapping *map; - unsigned int size; int ret; map = kzalloc(sizeof(*map), GFP_KERNEL); @@ -61,39 +131,20 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain, case V4L2_CTRL_TYPE_INTEGER: case V4L2_CTRL_TYPE_BOOLEAN: case V4L2_CTRL_TYPE_BUTTON: + ret = uvc_ctrl_add_mapping(chain, map); break; case V4L2_CTRL_TYPE_MENU: - /* - * Prevent excessive memory consumption, as well as integer - * overflows. - */ - if (xmap->menu_count == 0 || - xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) { - ret = -EINVAL; - goto free_map; - } - - size = xmap->menu_count * sizeof(*map->menu_info); - map->menu_info = memdup_user(xmap->menu_info, size); - if (IS_ERR(map->menu_info)) { - ret = PTR_ERR(map->menu_info); - goto free_map; - } - - map->menu_mask = GENMASK(xmap->menu_count - 1, 0); + ret = uvc_control_add_xu_mapping(chain, map, xmap); break; default: uvc_dbg(chain->dev, CONTROL, "Unsupported V4L2 control type %u\n", xmap->v4l2_type); ret = -ENOTTY; - goto free_map; + break; } - ret = uvc_ctrl_add_mapping(chain, map); - - kfree(map->menu_info); free_map: kfree(map); @@ -1314,7 +1365,7 @@ static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio, switch (cmd) { /* Dynamic controls. */ case UVCIOC_CTRL_MAP: - return uvc_ioctl_ctrl_map(chain, arg); + return uvc_ioctl_xu_ctrl_map(chain, arg); case UVCIOC_CTRL_QUERY: return uvc_xu_ctrl_query(chain, arg); @@ -1427,7 +1478,7 @@ static long uvc_v4l2_compat_ioctl32(struct file *file, ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up); if (ret) return ret; - ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap); + ret = uvc_ioctl_xu_ctrl_map(handle->chain, &karg.xmap); if (ret) return ret; ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up); diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 178729467967..e85df8deb965 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -114,7 +114,8 @@ struct uvc_control_mapping { enum v4l2_ctrl_type v4l2_type; u32 data_type; - const struct uvc_menu_info *menu_info; + const u32 *menu_mapping; + const char (*menu_names)[UVC_MENU_NAME_LEN]; unsigned long menu_mask; u32 master_id; diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h index 8288137387c0..d45d0c2ea252 100644 --- a/include/uapi/linux/uvcvideo.h +++ b/include/uapi/linux/uvcvideo.h @@ -36,9 +36,11 @@ UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES | \ UVC_CTRL_FLAG_GET_DEF) +#define UVC_MENU_NAME_LEN 32 + struct uvc_menu_info { __u32 value; - __u8 name[32]; + __u8 name[UVC_MENU_NAME_LEN]; }; struct uvc_xu_control_mapping { -- cgit v1.2.3 From 619d9b710cf06f7a00a17120ca92333684ac45a8 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Thu, 5 Jan 2023 15:31:29 +0100 Subject: media: uvcvideo: Fix race condition with usb_kill_urb usb_kill_urb warranties that all the handlers are finished when it returns, but does not protect against threads that might be handling asynchronously the urb. For UVC, the function uvc_ctrl_status_event_async() takes care of control changes asynchronously. If the code is executed in the following order: CPU 0 CPU 1 ===== ===== uvc_status_complete() uvc_status_stop() uvc_ctrl_status_event_work() uvc_status_start() -> FAIL Then uvc_status_start will keep failing and this error will be shown: <4>[ 5.540139] URB 0000000000000000 submitted while active drivers/usb/core/urb.c:378 usb_submit_urb+0x4c3/0x528 Let's improve the current situation, by not re-submiting the urb if we are stopping the status event. Also process the queued work (if any) during stop. CPU 0 CPU 1 ===== ===== uvc_status_complete() uvc_status_stop() uvc_status_start() uvc_ctrl_status_event_work() -> FAIL Hopefully, with the usb layer protection this should be enough to cover all the cases. Cc: stable@vger.kernel.org Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives") Reviewed-by: Yunke Cao Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_ctrl.c | 5 +++++ drivers/media/usb/uvc/uvc_status.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 43 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index ee58f0db2763..c3aeba3fe31b 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include #include #include #include @@ -1571,6 +1572,10 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + /* The barrier is needed to synchronize with uvc_status_stop(). */ + if (smp_load_acquire(&dev->flush_status)) + return; + /* Resubmit the URB. */ w->urb->interval = dev->int_ep->desc.bInterval; ret = usb_submit_urb(w->urb, GFP_KERNEL); diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index 602830a8023e..a78a88c710e2 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include #include #include #include @@ -311,5 +312,41 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags) void uvc_status_stop(struct uvc_device *dev) { + struct uvc_ctrl_work *w = &dev->async_ctrl; + + /* + * Prevent the asynchronous control handler from requeing the URB. The + * barrier is needed so the flush_status change is visible to other + * CPUs running the asynchronous handler before usb_kill_urb() is + * called below. + */ + smp_store_release(&dev->flush_status, true); + + /* + * Cancel any pending asynchronous work. If any status event was queued, + * process it synchronously. + */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* Kill the urb. */ usb_kill_urb(dev->int_urb); + + /* + * The URB completion handler may have queued asynchronous work. This + * won't resubmit the URB as flush_status is set, but it needs to be + * cancelled before returning or it could then race with a future + * uvc_status_start() call. + */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* + * From this point, there are no events on the queue and the status URB + * is dead. No events will be queued until uvc_status_start() is called. + * The barrier is needed to make sure that flush_status is visible to + * uvc_ctrl_status_event_work() when uvc_status_start() will be called + * again. + */ + smp_store_release(&dev->flush_status, false); } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index e85df8deb965..c0e706fcd2cb 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -577,6 +577,7 @@ struct uvc_device { struct usb_host_endpoint *int_ep; struct urb *int_urb; struct uvc_status *status; + bool flush_status; struct input_dev *input; char input_phys[64]; -- cgit v1.2.3 From 136effa754b57632f99574fc4a3433e0cfc031d9 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 4 Jan 2023 11:45:23 +0100 Subject: media: uvcvideo: Quirk for autosuspend in Logitech B910 and C910 Logitech B910 and C910 firmware are unable to recover from a USB autosuspend. When it resumes, the device is in a state where it only produces invalid frames. Eg: $ echo 0xFFFF > /sys/module/uvcvideo/parameters/trace # enable verbose log $ yavta -c1 -n1 --file='frame#.jpg' --format MJPEG --size=1920x1080 /dev/video1 [350438.435219] uvcvideo: uvc_v4l2_open [350438.529794] uvcvideo: Resuming interface 2 [350438.529801] uvcvideo: Resuming interface 3 [350438.529991] uvcvideo: Trying format 0x47504a4d (MJPG): 1920x1080. [350438.529996] uvcvideo: Using default frame interval 33333.3 us (30.0 fps). [350438.551496] uvcvideo: uvc_v4l2_mmap [350438.555890] uvcvideo: Device requested 3060 B/frame bandwidth. [350438.555896] uvcvideo: Selecting alternate setting 11 (3060 B/frame bandwidth). [350438.556362] uvcvideo: Allocated 5 URB buffers of 32x3060 bytes each. [350439.316468] uvcvideo: Marking buffer as bad (error bit set). [350439.316475] uvcvideo: Frame complete (EOF found). [350439.316477] uvcvideo: EOF in empty payload. [350439.316484] uvcvideo: frame 1 stats: 149/261/417 packets, 1/149/417 pts (early initial), 416/417 scr, last pts/stc/sof 2976325734/2978107243/249 [350439.384510] uvcvideo: Marking buffer as bad (error bit set). [350439.384516] uvcvideo: Frame complete (EOF found). [350439.384518] uvcvideo: EOF in empty payload. [350439.384525] uvcvideo: frame 2 stats: 265/379/533 packets, 1/265/533 pts (early initial), 532/533 scr, last pts/stc/sof 2979524454/2981305193/316 [350439.448472] uvcvideo: Marking buffer as bad (error bit set). [350439.448478] uvcvideo: Frame complete (EOF found). [350439.448480] uvcvideo: EOF in empty payload. [350439.448487] uvcvideo: frame 3 stats: 265/377/533 packets, 1/265/533 pts (early initial), 532/533 scr, last pts/stc/sof 2982723174/2984503144/382 ...(loop)... The devices can leave this invalid state if the alternate setting of the streaming interface is toggled. This patch adds a quirk for this device so it can be autosuspended properly. lsusb -v: Bus 001 Device 049: ID 046d:0821 Logitech, Inc. HD Webcam C910 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x046d Logitech, Inc. idProduct 0x0821 HD Webcam C910 bcdDevice 0.10 iManufacturer 0 iProduct 0 iSerial 1 390022B0 bNumConfigurations 1 Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_driver.c | 18 ++++++++++++++++++ drivers/media/usb/uvc/uvc_video.c | 11 +++++++++++ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 30 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 6005e3c7eb96..11f3d716b5bf 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2474,6 +2474,24 @@ static const struct usb_device_id uvc_ids[] = { .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax }, + /* Logitech, Webcam C910 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x046d, + .idProduct = 0x0821, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)}, + /* Logitech, Webcam B910 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x046d, + .idProduct = 0x0823, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)}, /* Logitech Quickcam Fusion */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index b1f7416858b7..98d34d20afe3 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1969,6 +1969,17 @@ static int uvc_video_start_transfer(struct uvc_streaming *stream, "Selecting alternate setting %u (%u B/frame bandwidth)\n", altsetting, best_psize); + /* + * Some devices, namely the Logitech C910 and B910, are unable + * to recover from a USB autosuspend, unless the alternate + * setting of the streaming interface is toggled. + */ + if (stream->dev->quirks & UVC_QUIRK_WAKE_AUTOSUSPEND) { + usb_set_interface(stream->dev->udev, intfnum, + altsetting); + usb_set_interface(stream->dev->udev, intfnum, 0); + } + ret = usb_set_interface(stream->dev->udev, intfnum, altsetting); if (ret < 0) return ret; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index c0e706fcd2cb..9a596c8d894a 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -72,6 +72,7 @@ #define UVC_QUIRK_RESTORE_CTRLS_ON_INIT 0x00000400 #define UVC_QUIRK_FORCE_Y8 0x00000800 #define UVC_QUIRK_FORCE_BPP 0x00001000 +#define UVC_QUIRK_WAKE_AUTOSUSPEND 0x00002000 /* Format flags */ #define UVC_FMT_FLAG_COMPRESSED 0x00000001 -- cgit v1.2.3 From b839212988575c701aab4d3d9ca15e44c87e383c Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 5 Jan 2023 22:17:04 -0800 Subject: media: uvcvideo: Silence memcpy() run-time false positive warnings The memcpy() in uvc_video_decode_meta() intentionally copies across the length and flags members and into the trailing buf flexible array. Split the copy so that the compiler can better reason about (the lack of) buffer overflows here. Avoid the run-time false positive warning: memcpy: detected field-spanning write (size 12) of single field "&meta->length" at drivers/media/usb/uvc/uvc_video.c:1355 (size 1) Additionally fix a typo in the documentation for struct uvc_meta_buf. Reported-by: ionut_n2001@yahoo.com Link: https://bugzilla.kernel.org/show_bug.cgi?id=216810 Signed-off-by: Kees Cook Reviewed-by: Laurent Pinchart Signed-off-by: Laurent Pinchart --- drivers/media/usb/uvc/uvc_video.c | 4 +++- include/uapi/linux/uvcvideo.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 98d34d20afe3..d4b023d4de7c 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1356,7 +1356,9 @@ static void uvc_video_decode_meta(struct uvc_streaming *stream, if (has_scr) memcpy(stream->clock.last_scr, scr, 6); - memcpy(&meta->length, mem, length); + meta->length = mem[0]; + meta->flags = mem[1]; + memcpy(meta->buf, &mem[2], length - 2); meta_buf->bytesused += length + sizeof(meta->ns) + sizeof(meta->sof); uvc_dbg(stream->dev, FRAME, diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h index d45d0c2ea252..f86185456dc5 100644 --- a/include/uapi/linux/uvcvideo.h +++ b/include/uapi/linux/uvcvideo.h @@ -88,7 +88,7 @@ struct uvc_xu_control_query { * struct. The first two fields are added by the driver, they can be used for * clock synchronisation. The rest is an exact copy of a UVC payload header. * Only complete objects with complete buffers are included. Therefore it's - * always sizeof(meta->ts) + sizeof(meta->sof) + meta->length bytes large. + * always sizeof(meta->ns) + sizeof(meta->sof) + meta->length bytes large. */ struct uvc_meta_buf { __u64 ns; -- cgit v1.2.3 From 49f2b350f330cf600cf0563fa3e55ba1c1799440 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 5 May 2022 13:58:04 +0300 Subject: thunderbolt: Use decimal port number in control and tunnel logs too Use decimal number instead of hex in port numbers as we have been doing with other logging functions too. This makes the output more consistent. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 2 +- drivers/thunderbolt/tunnel.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 0c661a706160..25f7868257de 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -754,7 +754,7 @@ int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug) .pg = unplug ? TB_CFG_ERROR_PG_HOT_UNPLUG : TB_CFG_ERROR_PG_HOT_PLUG, }; - tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%x\n", + tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%u\n", unplug ? "un" : "", route, port); return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR); } diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 2c3cf7fc3357..62a2d0eb1c5c 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -49,7 +49,7 @@ static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ do { \ struct tb_tunnel *__tunnel = (tunnel); \ - level(__tunnel->tb, "%llx:%x <-> %llx:%x (%s): " fmt, \ + level(__tunnel->tb, "%llx:%u <-> %llx:%u (%s): " fmt, \ tb_route(__tunnel->src_port->sw), \ __tunnel->src_port->port, \ tb_route(__tunnel->dst_port->sw), \ -- cgit v1.2.3 From b0ef48fc95cc2ce042fd5ad85d193e8a57502094 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 2 Jun 2022 12:50:20 +0300 Subject: thunderbolt: Log DP adapter type This makes it easier to see from the debug logs what type of DisplayPort adapter is in use or available. No functional changes. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 462845804427..3a541ebc7e3d 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -882,7 +882,7 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in) continue; if (tb_port_is_enabled(port)) { - tb_port_dbg(port, "in use\n"); + tb_port_dbg(port, "DP OUT in use\n"); continue; } @@ -931,7 +931,7 @@ static void tb_tunnel_dp(struct tb *tb) continue; if (tb_port_is_enabled(port)) { - tb_port_dbg(port, "in use\n"); + tb_port_dbg(port, "DP IN in use\n"); continue; } -- cgit v1.2.3 From 2426fdf77afb4d78316585531a4069905a5accc7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 5 May 2022 13:59:21 +0300 Subject: thunderbolt: Improve debug logging in tb_available_bandwidth() This makes it easier to see what is going on when bandwidth is being allocated for tunneling. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 3a541ebc7e3d..de7a2ba9d458 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -350,7 +350,9 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_tunnel *tunnel; struct tb_port *port; - tb_port_dbg(dst_port, "calculating available bandwidth\n"); + tb_dbg(tb, "calculating available bandwidth between %llx:%u <-> %llx:%u\n", + tb_route(src_port->sw), src_port->port, tb_route(dst_port->sw), + dst_port->port); tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); if (tunnel) { @@ -387,7 +389,8 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, up_bw -= up_bw / 10; down_bw = up_bw; - tb_port_dbg(port, "link total bandwidth %d Mb/s\n", up_bw); + tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw, + down_bw); /* * Find all DP tunnels that cross the port and reduce -- cgit v1.2.3 From e70a8f36987da50b9f443173f8800795f70266da Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 23 Mar 2022 16:13:32 +0200 Subject: thunderbolt: Take CL states into account when waiting for link to come up If CL states are enabled for the link it may be in these states too when reading the lane adapter state but it will enter CL0 as soon as there is traffic in the high-speed lanes. Upon discovery we want to make sure that is accounted as the link being up, otherwise we end up tearing down the topology with no good reason. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++----------------- drivers/thunderbolt/tb_regs.h | 4 ++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 363d712aa364..e1ad1b437291 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -513,36 +513,44 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) while (retries--) { state = tb_port_state(port); - if (state < 0) - return state; - if (state == TB_PORT_DISABLED) { + switch (state) { + case TB_PORT_DISABLED: tb_port_dbg(port, "is disabled (state: 0)\n"); return 0; - } - if (state == TB_PORT_UNPLUGGED) { + + case TB_PORT_UNPLUGGED: if (wait_if_unplugged) { /* used during resume */ tb_port_dbg(port, "is unplugged (state: 7), retrying...\n"); msleep(100); - continue; + break; } tb_port_dbg(port, "is unplugged (state: 7)\n"); return 0; - } - if (state == TB_PORT_UP) { - tb_port_dbg(port, "is connected, link is up (state: 2)\n"); + + case TB_PORT_UP: + case TB_PORT_TX_CL0S: + case TB_PORT_RX_CL0S: + case TB_PORT_CL1: + case TB_PORT_CL2: + tb_port_dbg(port, "is connected, link is up (state: %d)\n", state); return 1; + + default: + if (state < 0) + return state; + + /* + * After plug-in the state is TB_PORT_CONNECTING. Give it some + * time. + */ + tb_port_dbg(port, + "is connected, link is not up (state: %d), retrying...\n", + state); + msleep(100); } - /* - * After plug-in the state is TB_PORT_CONNECTING. Give it some - * time. - */ - tb_port_dbg(port, - "is connected, link is not up (state: %d), retrying...\n", - state); - msleep(100); } tb_port_warn(port, "failed to reach state TB_PORT_UP. Ignoring port...\n"); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 3c38b0cb8f74..f4a194cc0d63 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -50,6 +50,10 @@ enum tb_port_state { TB_PORT_DISABLED = 0, /* tb_cap_phy.disable == 1 */ TB_PORT_CONNECTING = 1, /* retry */ TB_PORT_UP = 2, + TB_PORT_TX_CL0S = 3, + TB_PORT_RX_CL0S = 4, + TB_PORT_CL1 = 5, + TB_PORT_CL2 = 6, TB_PORT_UNPLUGGED = 7, }; -- cgit v1.2.3 From fe1a1cf7c97028570127ac8d222c2b451ba04278 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Sun, 19 Jun 2022 18:02:48 +0300 Subject: thunderbolt: Increase timeout of DP OUT adapter handshake Sometimes the current timeout is not enough so increase it to 1500 ms and while there make the loop use ktime instead. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 62a2d0eb1c5c..04fbdbbc4ee2 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -339,9 +339,10 @@ static bool tb_dp_is_usb4(const struct tb_switch *sw) return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw); } -static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out) +static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out, + int timeout_msec) { - int timeout = 10; + ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec); u32 val; int ret; @@ -368,8 +369,8 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out) return ret; if (!(val & DP_STATUS_CTRL_CMHS)) return 0; - usleep_range(10, 100); - } while (timeout--); + usleep_range(100, 150); + } while (ktime_before(ktime_get(), timeout)); return -ETIMEDOUT; } @@ -519,7 +520,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) * Perform connection manager handshake between IN and OUT ports * before capabilities exchange can take place. */ - ret = tb_dp_cm_handshake(in, out); + ret = tb_dp_cm_handshake(in, out, 1500); if (ret) return ret; -- cgit v1.2.3 From e327380133d96a7a71baca65a809bf65609a1a69 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 23 Mar 2022 16:18:28 +0200 Subject: thunderbolt: Add functions to support DisplayPort bandwidth allocation mode USB4 spec defines an additional feature that DP IN adapters can implement (alongside with the graphics DPCD register set) to support more dynamic bandwidth management for DisplayPort tunnels. For the connection manager the communication happens through the DP IN adapter using a set of registers in the adapter config space allocated for this. Add functions that export this functionality for the rest of the driver. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 15 ++ drivers/thunderbolt/tb_regs.h | 32 +++ drivers/thunderbolt/usb4.c | 571 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 618 insertions(+) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 6c4a26b1c37c..cd6c9eeaf049 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1238,6 +1238,21 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw, int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw, int *downstream_bw); +int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id); +bool usb4_dp_port_bw_mode_supported(struct tb_port *port); +bool usb4_dp_port_bw_mode_enabled(struct tb_port *port); +int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported); +int usb4_dp_port_group_id(struct tb_port *port); +int usb4_dp_port_set_group_id(struct tb_port *port, int group_id); +int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes); +int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes); +int usb4_dp_port_granularity(struct tb_port *port); +int usb4_dp_port_set_granularity(struct tb_port *port, int granularity); +int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw); +int usb4_dp_port_allocated_bw(struct tb_port *port); +int usb4_dp_port_allocate_bw(struct tb_port *port, int bw); +int usb4_dp_port_requested_bw(struct tb_port *port); + static inline bool tb_is_usb4_port_device(const struct device *dev) { return dev->type == &usb4_port_device_type; diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index f4a194cc0d63..2636423748cd 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -385,15 +385,42 @@ struct tb_regs_port_header { #define ADP_DP_CS_1_AUX_RX_HOPID_MASK GENMASK(21, 11) #define ADP_DP_CS_1_AUX_RX_HOPID_SHIFT 11 #define ADP_DP_CS_2 0x02 +#define ADP_DP_CS_2_NRD_MLC_MASK GENMASK(2, 0) #define ADP_DP_CS_2_HDP BIT(6) +#define ADP_DP_CS_2_NRD_MLR_MASK GENMASK(9, 7) +#define ADP_DP_CS_2_NRD_MLR_SHIFT 7 +#define ADP_DP_CS_2_CA BIT(10) +#define ADP_DP_CS_2_GR_MASK GENMASK(12, 11) +#define ADP_DP_CS_2_GR_SHIFT 11 +#define ADP_DP_CS_2_GR_0_25G 0x0 +#define ADP_DP_CS_2_GR_0_5G 0x1 +#define ADP_DP_CS_2_GR_1G 0x2 +#define ADP_DP_CS_2_GROUP_ID_MASK GENMASK(15, 13) +#define ADP_DP_CS_2_GROUP_ID_SHIFT 13 +#define ADP_DP_CS_2_CM_ID_MASK GENMASK(19, 16) +#define ADP_DP_CS_2_CM_ID_SHIFT 16 +#define ADP_DP_CS_2_CMMS BIT(20) +#define ADP_DP_CS_2_ESTIMATED_BW_MASK GENMASK(31, 24) +#define ADP_DP_CS_2_ESTIMATED_BW_SHIFT 24 #define ADP_DP_CS_3 0x03 #define ADP_DP_CS_3_HDPC BIT(9) #define DP_LOCAL_CAP 0x04 #define DP_REMOTE_CAP 0x05 +/* For DP IN adapter */ +#define DP_STATUS 0x06 +#define DP_STATUS_ALLOCATED_BW_MASK GENMASK(31, 24) +#define DP_STATUS_ALLOCATED_BW_SHIFT 24 +/* For DP OUT adapter */ #define DP_STATUS_CTRL 0x06 #define DP_STATUS_CTRL_CMHS BIT(25) #define DP_STATUS_CTRL_UF BIT(26) #define DP_COMMON_CAP 0x07 +/* Only if DP IN supports BW allocation mode */ +#define ADP_DP_CS_8 0x08 +#define ADP_DP_CS_8_REQUESTED_BW_MASK GENMASK(7, 0) +#define ADP_DP_CS_8_DPME BIT(30) +#define ADP_DP_CS_8_DR BIT(31) + /* * DP_COMMON_CAP offsets work also for DP_LOCAL_CAP and DP_REMOTE_CAP * with exception of DPRX done. @@ -410,7 +437,12 @@ struct tb_regs_port_header { #define DP_COMMON_CAP_2_LANES 0x1 #define DP_COMMON_CAP_4_LANES 0x2 #define DP_COMMON_CAP_LTTPR_NS BIT(27) +#define DP_COMMON_CAP_BW_MODE BIT(28) #define DP_COMMON_CAP_DPRX_DONE BIT(31) +/* Only present if DP IN supports BW allocation mode */ +#define ADP_DP_CS_8 0x08 +#define ADP_DP_CS_8_DPME BIT(30) +#define ADP_DP_CS_8_DR BIT(31) /* PCIe adapter registers */ #define ADP_PCIE_CS_0 0x00 diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 2ed50fcbcca7..2a9266fb5c0f 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -2186,3 +2186,574 @@ err_request: usb4_usb3_port_clear_cm_request(port); return ret; } + +static bool is_usb4_dpin(const struct tb_port *port) +{ + if (!tb_port_is_dpin(port)) + return false; + if (!tb_switch_is_usb4(port->sw)) + return false; + return true; +} + +/** + * usb4_dp_port_set_cm_id() - Assign CM ID to the DP IN adapter + * @port: DP IN adapter + * @cm_id: CM ID to assign + * + * Sets CM ID for the @port. Returns %0 on success and negative errno + * otherwise. Speficially returns %-EOPNOTSUPP if the @port does not + * support this. + */ +int usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_CM_ID_MASK; + val |= cm_id << ADP_DP_CS_2_CM_ID_SHIFT; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_bw_mode_supported() - Is the bandwidth allocation mode supported + * @port: DP IN adapter to check + * + * Can be called to any DP IN adapter. Returns true if the adapter + * supports USB4 bandwidth allocation mode, false otherwise. + */ +bool usb4_dp_port_bw_mode_supported(struct tb_port *port) +{ + int ret; + u32 val; + + if (!is_usb4_dpin(port)) + return false; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + DP_LOCAL_CAP, 1); + if (ret) + return false; + + return !!(val & DP_COMMON_CAP_BW_MODE); +} + +/** + * usb4_dp_port_bw_mode_enabled() - Is the bandwidth allocation mode enabled + * @port: DP IN adapter to check + * + * Can be called to any DP IN adapter. Returns true if the bandwidth + * allocation mode has been enabled, false otherwise. + */ +bool usb4_dp_port_bw_mode_enabled(struct tb_port *port) +{ + int ret; + u32 val; + + if (!is_usb4_dpin(port)) + return false; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_8, 1); + if (ret) + return false; + + return !!(val & ADP_DP_CS_8_DPME); +} + +/** + * usb4_dp_port_set_cm_bw_mode_supported() - Set/clear CM support for bandwidth allocation mode + * @port: DP IN adapter + * @supported: Does the CM support bandwidth allocation mode + * + * Can be called to any DP IN adapter. Sets or clears the CM support bit + * of the DP IN adapter. Returns %0 in success and negative errno + * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter + * does not support this. + */ +int usb4_dp_port_set_cm_bw_mode_supported(struct tb_port *port, bool supported) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + if (supported) + val |= ADP_DP_CS_2_CMMS; + else + val &= ~ADP_DP_CS_2_CMMS; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_group_id() - Return Group ID assigned for the adapter + * @port: DP IN adapter + * + * Reads bandwidth allocation Group ID from the DP IN adapter and + * returns it. If the adapter does not support setting Group_ID + * %-EOPNOTSUPP is returned. + */ +int usb4_dp_port_group_id(struct tb_port *port) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + return (val & ADP_DP_CS_2_GROUP_ID_MASK) >> ADP_DP_CS_2_GROUP_ID_SHIFT; +} + +/** + * usb4_dp_port_set_group_id() - Set adapter Group ID + * @port: DP IN adapter + * @group_id: Group ID for the adapter + * + * Sets bandwidth allocation mode Group ID for the DP IN adapter. + * Returns %0 in case of success and negative errno otherwise. + * Specifically returns %-EOPNOTSUPP if the adapter does not support + * this. + */ +int usb4_dp_port_set_group_id(struct tb_port *port, int group_id) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_GROUP_ID_MASK; + val |= group_id << ADP_DP_CS_2_GROUP_ID_SHIFT; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_nrd() - Read non-reduced rate and lanes + * @port: DP IN adapter + * @rate: Non-reduced rate in Mb/s is placed here + * @lanes: Non-reduced lanes are placed here + * + * Reads the non-reduced rate and lanes from the DP IN adapter. Returns + * %0 in success and negative errno otherwise. Specifically returns + * %-EOPNOTSUPP if the adapter does not support this. + */ +int usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes) +{ + u32 val, tmp; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + tmp = (val & ADP_DP_CS_2_NRD_MLR_MASK) >> ADP_DP_CS_2_NRD_MLR_SHIFT; + switch (tmp) { + case DP_COMMON_CAP_RATE_RBR: + *rate = 1620; + break; + case DP_COMMON_CAP_RATE_HBR: + *rate = 2700; + break; + case DP_COMMON_CAP_RATE_HBR2: + *rate = 5400; + break; + case DP_COMMON_CAP_RATE_HBR3: + *rate = 8100; + break; + } + + tmp = val & ADP_DP_CS_2_NRD_MLC_MASK; + switch (tmp) { + case DP_COMMON_CAP_1_LANE: + *lanes = 1; + break; + case DP_COMMON_CAP_2_LANES: + *lanes = 2; + break; + case DP_COMMON_CAP_4_LANES: + *lanes = 4; + break; + } + + return 0; +} + +/** + * usb4_dp_port_set_nrd() - Set non-reduced rate and lanes + * @port: DP IN adapter + * @rate: Non-reduced rate in Mb/s + * @lanes: Non-reduced lanes + * + * Before the capabilities reduction this function can be used to set + * the non-reduced values for the DP IN adapter. Returns %0 in success + * and negative errno otherwise. If the adapter does not support this + * %-EOPNOTSUPP is returned. + */ +int usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_NRD_MLR_MASK; + + switch (rate) { + case 1620: + break; + case 2700: + val |= (DP_COMMON_CAP_RATE_HBR << ADP_DP_CS_2_NRD_MLR_SHIFT) + & ADP_DP_CS_2_NRD_MLR_MASK; + break; + case 5400: + val |= (DP_COMMON_CAP_RATE_HBR2 << ADP_DP_CS_2_NRD_MLR_SHIFT) + & ADP_DP_CS_2_NRD_MLR_MASK; + break; + case 8100: + val |= (DP_COMMON_CAP_RATE_HBR3 << ADP_DP_CS_2_NRD_MLR_SHIFT) + & ADP_DP_CS_2_NRD_MLR_MASK; + break; + default: + return -EINVAL; + } + + val &= ~ADP_DP_CS_2_NRD_MLC_MASK; + + switch (lanes) { + case 1: + break; + case 2: + val |= DP_COMMON_CAP_2_LANES; + break; + case 4: + val |= DP_COMMON_CAP_4_LANES; + break; + default: + return -EINVAL; + } + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_granularity() - Return granularity for the bandwidth values + * @port: DP IN adapter + * + * Reads the programmed granularity from @port. If the DP IN adapter does + * not support bandwidth allocation mode returns %-EOPNOTSUPP and negative + * errno in other error cases. + */ +int usb4_dp_port_granularity(struct tb_port *port) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ADP_DP_CS_2_GR_MASK; + val >>= ADP_DP_CS_2_GR_SHIFT; + + switch (val) { + case ADP_DP_CS_2_GR_0_25G: + return 250; + case ADP_DP_CS_2_GR_0_5G: + return 500; + case ADP_DP_CS_2_GR_1G: + return 1000; + } + + return -EINVAL; +} + +/** + * usb4_dp_port_set_granularity() - Set granularity for the bandwidth values + * @port: DP IN adapter + * @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250. + * + * Sets the granularity used with the estimated, allocated and requested + * bandwidth. Returns %0 in success and negative errno otherwise. If the + * adapter does not support this %-EOPNOTSUPP is returned. + */ +int usb4_dp_port_set_granularity(struct tb_port *port, int granularity) +{ + u32 val; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_GR_MASK; + + switch (granularity) { + case 250: + val |= ADP_DP_CS_2_GR_0_25G << ADP_DP_CS_2_GR_SHIFT; + break; + case 500: + val |= ADP_DP_CS_2_GR_0_5G << ADP_DP_CS_2_GR_SHIFT; + break; + case 1000: + val |= ADP_DP_CS_2_GR_1G << ADP_DP_CS_2_GR_SHIFT; + break; + default: + return -EINVAL; + } + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_set_estimated_bw() - Set estimated bandwidth + * @port: DP IN adapter + * @bw: Estimated bandwidth in Mb/s. + * + * Sets the estimated bandwidth to @bw. Set the granularity by calling + * usb4_dp_port_set_granularity() before calling this. The @bw is round + * down to the closest granularity multiplier. Returns %0 in success + * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if + * the adapter does not support this. + */ +int usb4_dp_port_set_estimated_bw(struct tb_port *port, int bw) +{ + u32 val, granularity; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = usb4_dp_port_granularity(port); + if (ret < 0) + return ret; + granularity = ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_ESTIMATED_BW_MASK; + val |= (bw / granularity) << ADP_DP_CS_2_ESTIMATED_BW_SHIFT; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_allocated_bw() - Return allocated bandwidth + * @port: DP IN adapter + * + * Reads and returns allocated bandwidth for @port in Mb/s (taking into + * account the programmed granularity). Returns negative errno in case + * of error. + */ +int usb4_dp_port_allocated_bw(struct tb_port *port) +{ + u32 val, granularity; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = usb4_dp_port_granularity(port); + if (ret < 0) + return ret; + granularity = ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + DP_STATUS, 1); + if (ret) + return ret; + + val &= DP_STATUS_ALLOCATED_BW_MASK; + val >>= DP_STATUS_ALLOCATED_BW_SHIFT; + + return val * granularity; +} + +static int __usb4_dp_port_set_cm_ack(struct tb_port *port, bool ack) +{ + u32 val; + int ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + if (ack) + val |= ADP_DP_CS_2_CA; + else + val &= ~ADP_DP_CS_2_CA; + + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +static inline int usb4_dp_port_set_cm_ack(struct tb_port *port) +{ + return __usb4_dp_port_set_cm_ack(port, true); +} + +static int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port, + int timeout_msec) +{ + ktime_t end; + u32 val; + int ret; + + ret = __usb4_dp_port_set_cm_ack(port, false); + if (ret) + return ret; + + end = ktime_add_ms(ktime_get(), timeout_msec); + do { + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_8, 1); + if (ret) + return ret; + + if (!(val & ADP_DP_CS_8_DR)) + break; + + usleep_range(50, 100); + } while (ktime_before(ktime_get(), end)); + + if (val & ADP_DP_CS_8_DR) + return -ETIMEDOUT; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); + if (ret) + return ret; + + val &= ~ADP_DP_CS_2_CA; + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_2, 1); +} + +/** + * usb4_dp_port_allocate_bw() - Set allocated bandwidth + * @port: DP IN adapter + * @bw: New allocated bandwidth in Mb/s + * + * Communicates the new allocated bandwidth with the DPCD (graphics + * driver). Takes into account the programmed granularity. Returns %0 in + * success and negative errno in case of error. + */ +int usb4_dp_port_allocate_bw(struct tb_port *port, int bw) +{ + u32 val, granularity; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = usb4_dp_port_granularity(port); + if (ret < 0) + return ret; + granularity = ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + DP_STATUS, 1); + if (ret) + return ret; + + val &= ~DP_STATUS_ALLOCATED_BW_MASK; + val |= (bw / granularity) << DP_STATUS_ALLOCATED_BW_SHIFT; + + ret = tb_port_write(port, &val, TB_CFG_PORT, + port->cap_adap + DP_STATUS, 1); + if (ret) + return ret; + + ret = usb4_dp_port_set_cm_ack(port); + if (ret) + return ret; + + return usb4_dp_port_wait_and_clear_cm_ack(port, 500); +} + +/** + * usb4_dp_port_requested_bw() - Read requested bandwidth + * @port: DP IN adapter + * + * Reads the DPCD (graphics driver) requested bandwidth and returns it + * in Mb/s. Takes the programmed granularity into account. In case of + * error returns negative errno. Specifically returns %-EOPNOTSUPP if + * the adapter does not support bandwidth allocation mode. + */ +int usb4_dp_port_requested_bw(struct tb_port *port) +{ + u32 val, granularity; + int ret; + + if (!is_usb4_dpin(port)) + return -EOPNOTSUPP; + + ret = usb4_dp_port_granularity(port); + if (ret < 0) + return ret; + granularity = ret; + + ret = tb_port_read(port, &val, TB_CFG_PORT, + port->cap_adap + ADP_DP_CS_8, 1); + if (ret) + return 0; + + if (!(val & ADP_DP_CS_8_DR)) + return 0; + + return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity; +} -- cgit v1.2.3 From 630f211be7c0a8cf693fef2b6d77d0ac357041e0 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 29 Apr 2022 17:14:57 +0300 Subject: thunderbolt: Include the additional DP IN double word in debugfs dump When DisplayPort bandwidth allocation mode is supported by the DP IN adapter it has an extra double word in the adapter config space. Include this in the debugfs register dump. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/debugfs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 834bcad42e9f..4339e706cc3a 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -1159,7 +1159,10 @@ static void port_cap_show(struct tb_port *port, struct seq_file *s, if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) { length = PORT_CAP_PCIE_LEN; } else if (tb_port_is_dpin(port) || tb_port_is_dpout(port)) { - length = PORT_CAP_DP_LEN; + if (usb4_dp_port_bw_mode_supported(port)) + length = PORT_CAP_DP_LEN + 1; + else + length = PORT_CAP_DP_LEN; } else if (tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) { length = PORT_CAP_USB3_LEN; -- cgit v1.2.3 From 6ce3563520be90a155706bafc186fc264a13850e Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 23 Mar 2022 16:45:39 +0200 Subject: thunderbolt: Add support for DisplayPort bandwidth allocation mode The USB4 spec defines an optional feature that allows the connection manager to negotiate with the graphics through DPCD registers changes in the bandwidth allocation dynamically. This is referred as "bandwidth allocation mode" in the spec. The connection manager uses DP IN adapters registers to communicate with the graphics, and also gets notifications from these adapters when the graphics wants to change the bandwidth allocation. Both the connection manager and the graphics driver needs to support this. We check if the DP IN adapter supports this and if it does enable it before establishing a DP tunnel. Then we react on DP_BW notifications coming from the DP IN adapter and update the bandwidth allocation accordingly (within the maximum common capabilities the DP IN/OUT support). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 50 ++++- drivers/thunderbolt/ctl.h | 2 + drivers/thunderbolt/tb.c | 493 ++++++++++++++++++++++++++++++++++++++++-- drivers/thunderbolt/tb.h | 22 ++ drivers/thunderbolt/tb_msgs.h | 11 +- drivers/thunderbolt/tunnel.c | 483 ++++++++++++++++++++++++++++++++++++++--- drivers/thunderbolt/tunnel.h | 18 ++ 7 files changed, 1023 insertions(+), 56 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 25f7868257de..6e7d28e8d81a 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -230,7 +230,6 @@ static int check_config_address(struct tb_cfg_address addr, static struct tb_cfg_result decode_error(const struct ctl_pkg *response) { struct cfg_error_pkg *pkg = response->buffer; - struct tb_ctl *ctl = response->ctl; struct tb_cfg_result res = { 0 }; res.response_route = tb_cfg_get_route(&pkg->header); res.response_port = 0; @@ -239,13 +238,6 @@ static struct tb_cfg_result decode_error(const struct ctl_pkg *response) if (res.err) return res; - if (pkg->zero1) - tb_ctl_warn(ctl, "pkg->zero1 is %#x\n", pkg->zero1); - if (pkg->zero2) - tb_ctl_warn(ctl, "pkg->zero2 is %#x\n", pkg->zero2); - if (pkg->zero3) - tb_ctl_warn(ctl, "pkg->zero3 is %#x\n", pkg->zero3); - res.err = 1; res.tb_error = pkg->error; res.response_port = pkg->port; @@ -416,6 +408,7 @@ static int tb_async_error(const struct ctl_pkg *pkg) case TB_CFG_ERROR_LINK_ERROR: case TB_CFG_ERROR_HEC_ERROR_DETECTED: case TB_CFG_ERROR_FLOW_CONTROL_ERROR: + case TB_CFG_ERROR_DP_BW: return true; default: @@ -735,6 +728,47 @@ void tb_ctl_stop(struct tb_ctl *ctl) /* public interface, commands */ +/** + * tb_cfg_ack_notification() - Ack notification + * @ctl: Control channel to use + * @route: Router that originated the event + * @error: Pointer to the notification package + * + * Call this as response for non-plug notification to ack it. Returns + * %0 on success or an error code on failure. + */ +int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, + const struct cfg_error_pkg *error) +{ + struct cfg_ack_pkg pkg = { + .header = tb_cfg_make_header(route), + }; + const char *name; + + switch (error->error) { + case TB_CFG_ERROR_LINK_ERROR: + name = "link error"; + break; + case TB_CFG_ERROR_HEC_ERROR_DETECTED: + name = "HEC error"; + break; + case TB_CFG_ERROR_FLOW_CONTROL_ERROR: + name = "flow control error"; + break; + case TB_CFG_ERROR_DP_BW: + name = "DP_BW"; + break; + default: + name = "unknown"; + break; + } + + tb_ctl_dbg(ctl, "acking %s (%#x) notification on %llx\n", name, + error->error, route); + + return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_NOTIFY_ACK); +} + /** * tb_cfg_ack_plug() - Ack hot plug/unplug event * @ctl: Control channel to use diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h index 7c7d80f96c0c..eec5c953c743 100644 --- a/drivers/thunderbolt/ctl.h +++ b/drivers/thunderbolt/ctl.h @@ -122,6 +122,8 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route) return header; } +int tb_cfg_ack_notification(struct tb_ctl *ctl, u64 route, + const struct cfg_error_pkg *error); int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug); struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route); struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index de7a2ba9d458..cdd1daaa5da1 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -16,7 +16,8 @@ #include "tb_regs.h" #include "tunnel.h" -#define TB_TIMEOUT 100 /* ms */ +#define TB_TIMEOUT 100 /* ms */ +#define MAX_GROUPS 7 /* max Group_ID is 7 */ /** * struct tb_cm - Simple Thunderbolt connection manager @@ -28,12 +29,14 @@ * after cfg has been paused. * @remove_work: Work used to remove any unplugged routers after * runtime resume + * @groups: Bandwidth groups used in this domain. */ struct tb_cm { struct list_head tunnel_list; struct list_head dp_resources; bool hotplug_active; struct delayed_work remove_work; + struct tb_bandwidth_group groups[MAX_GROUPS]; }; static inline struct tb *tcm_to_tb(struct tb_cm *tcm) @@ -49,6 +52,112 @@ struct tb_hotplug_event { bool unplug; }; +static void tb_init_bandwidth_groups(struct tb_cm *tcm) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) { + struct tb_bandwidth_group *group = &tcm->groups[i]; + + group->tb = tcm_to_tb(tcm); + group->index = i + 1; + INIT_LIST_HEAD(&group->ports); + } +} + +static void tb_bandwidth_group_attach_port(struct tb_bandwidth_group *group, + struct tb_port *in) +{ + if (!group || WARN_ON(in->group)) + return; + + in->group = group; + list_add_tail(&in->group_list, &group->ports); + + tb_port_dbg(in, "attached to bandwidth group %d\n", group->index); +} + +static struct tb_bandwidth_group *tb_find_free_bandwidth_group(struct tb_cm *tcm) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) { + struct tb_bandwidth_group *group = &tcm->groups[i]; + + if (list_empty(&group->ports)) + return group; + } + + return NULL; +} + +static struct tb_bandwidth_group * +tb_attach_bandwidth_group(struct tb_cm *tcm, struct tb_port *in, + struct tb_port *out) +{ + struct tb_bandwidth_group *group; + struct tb_tunnel *tunnel; + + /* + * Find all DP tunnels that go through all the same USB4 links + * as this one. Because we always setup tunnels the same way we + * can just check for the routers at both ends of the tunnels + * and if they are the same we have a match. + */ + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (!tb_tunnel_is_dp(tunnel)) + continue; + + if (tunnel->src_port->sw == in->sw && + tunnel->dst_port->sw == out->sw) { + group = tunnel->src_port->group; + if (group) { + tb_bandwidth_group_attach_port(group, in); + return group; + } + } + } + + /* Pick up next available group then */ + group = tb_find_free_bandwidth_group(tcm); + if (group) + tb_bandwidth_group_attach_port(group, in); + else + tb_port_warn(in, "no available bandwidth groups\n"); + + return group; +} + +static void tb_discover_bandwidth_group(struct tb_cm *tcm, struct tb_port *in, + struct tb_port *out) +{ + if (usb4_dp_port_bw_mode_enabled(in)) { + int index, i; + + index = usb4_dp_port_group_id(in); + for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) { + if (tcm->groups[i].index == index) { + tb_bandwidth_group_attach_port(&tcm->groups[i], in); + return; + } + } + } + + tb_attach_bandwidth_group(tcm, in, out); +} + +static void tb_detach_bandwidth_group(struct tb_port *in) +{ + struct tb_bandwidth_group *group = in->group; + + if (group) { + in->group = NULL; + list_del_init(&in->group_list); + + tb_port_dbg(in, "detached from bandwidth group %d\n", group->index); + } +} + static void tb_handle_hotplug(struct work_struct *work); static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) @@ -193,9 +302,14 @@ static void tb_discover_tunnels(struct tb *tb) parent = tb_switch_parent(parent); } } else if (tb_tunnel_is_dp(tunnel)) { + struct tb_port *in = tunnel->src_port; + struct tb_port *out = tunnel->dst_port; + /* Keep the domain from powering down */ - pm_runtime_get_sync(&tunnel->src_port->sw->dev); - pm_runtime_get_sync(&tunnel->dst_port->sw->dev); + pm_runtime_get_sync(&in->sw->dev); + pm_runtime_get_sync(&out->sw->dev); + + tb_discover_bandwidth_group(tcm, in, out); } } } @@ -355,7 +469,8 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, dst_port->port); tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port); - if (tunnel) { + if (tunnel && tunnel->src_port != src_port && + tunnel->dst_port != dst_port) { ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up, &usb3_consumed_down); if (ret) @@ -399,12 +514,24 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port, list_for_each_entry(tunnel, &tcm->tunnel_list, list) { int dp_consumed_up, dp_consumed_down; + if (tb_tunnel_is_invalid(tunnel)) + continue; + if (!tb_tunnel_is_dp(tunnel)) continue; if (!tb_tunnel_port_on_path(tunnel, port)) continue; + /* + * Ignore the DP tunnel between src_port and + * dst_port because it is the same tunnel and we + * may be re-calculating estimated bandwidth. + */ + if (tunnel->src_port == src_port && + tunnel->dst_port == dst_port) + continue; + ret = tb_tunnel_consumed_bandwidth(tunnel, &dp_consumed_up, &dp_consumed_down); @@ -755,6 +882,7 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel) switch (tunnel->type) { case TB_TUNNEL_DP: + tb_detach_bandwidth_group(src_port); /* * In case of DP tunnel make sure the DP IN resource is * deallocated properly. @@ -872,6 +1000,99 @@ out: return tb_find_unused_port(sw, TB_TYPE_PCIE_DOWN); } +static void +tb_recalc_estimated_bandwidth_for_group(struct tb_bandwidth_group *group) +{ + struct tb_tunnel *first_tunnel; + struct tb *tb = group->tb; + struct tb_port *in; + int ret; + + tb_dbg(tb, "re-calculating bandwidth estimation for group %u\n", + group->index); + + first_tunnel = NULL; + list_for_each_entry(in, &group->ports, group_list) { + int estimated_bw, estimated_up, estimated_down; + struct tb_tunnel *tunnel; + struct tb_port *out; + + if (!usb4_dp_port_bw_mode_enabled(in)) + continue; + + tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL); + if (WARN_ON(!tunnel)) + break; + + if (!first_tunnel) { + /* + * Since USB3 bandwidth is shared by all DP + * tunnels under the host router USB4 port, even + * if they do not begin from the host router, we + * can release USB3 bandwidth just once and not + * for each tunnel separately. + */ + first_tunnel = tunnel; + ret = tb_release_unused_usb3_bandwidth(tb, + first_tunnel->src_port, first_tunnel->dst_port); + if (ret) { + tb_port_warn(in, + "failed to release unused bandwidth\n"); + break; + } + } + + out = tunnel->dst_port; + ret = tb_available_bandwidth(tb, in, out, &estimated_up, + &estimated_down); + if (ret) { + tb_port_warn(in, + "failed to re-calculate estimated bandwidth\n"); + break; + } + + /* + * Estimated bandwidth includes: + * - already allocated bandwidth for the DP tunnel + * - available bandwidth along the path + * - bandwidth allocated for USB 3.x but not used. + */ + tb_port_dbg(in, "re-calculated estimated bandwidth %u/%u Mb/s\n", + estimated_up, estimated_down); + + if (in->sw->config.depth < out->sw->config.depth) + estimated_bw = estimated_down; + else + estimated_bw = estimated_up; + + if (usb4_dp_port_set_estimated_bw(in, estimated_bw)) + tb_port_warn(in, "failed to update estimated bandwidth\n"); + } + + if (first_tunnel) + tb_reclaim_usb3_bandwidth(tb, first_tunnel->src_port, + first_tunnel->dst_port); + + tb_dbg(tb, "bandwidth estimation for group %u done\n", group->index); +} + +static void tb_recalc_estimated_bandwidth(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + int i; + + tb_dbg(tb, "bandwidth consumption changed, re-calculating estimated bandwidth\n"); + + for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) { + struct tb_bandwidth_group *group = &tcm->groups[i]; + + if (!list_empty(&group->ports)) + tb_recalc_estimated_bandwidth_for_group(group); + } + + tb_dbg(tb, "bandwidth re-calculation done\n"); +} + static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in) { struct tb_port *host_port, *port; @@ -986,17 +1207,19 @@ static void tb_tunnel_dp(struct tb *tb) goto err_rpm_put; } + if (!tb_attach_bandwidth_group(tcm, in, out)) + goto err_dealloc_dp; + /* Make all unused USB3 bandwidth available for the new DP tunnel */ ret = tb_release_unused_usb3_bandwidth(tb, in, out); if (ret) { tb_warn(tb, "failed to release unused bandwidth\n"); - goto err_dealloc_dp; + goto err_detach_group; } - ret = tb_available_bandwidth(tb, in, out, &available_up, - &available_down); + ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down); if (ret) - goto err_reclaim; + goto err_reclaim_usb; tb_dbg(tb, "available bandwidth for new DP tunnel %u/%u Mb/s\n", available_up, available_down); @@ -1005,7 +1228,7 @@ static void tb_tunnel_dp(struct tb *tb) available_down); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); - goto err_reclaim; + goto err_reclaim_usb; } if (tb_tunnel_activate(tunnel)) { @@ -1015,6 +1238,10 @@ static void tb_tunnel_dp(struct tb *tb) list_add_tail(&tunnel->list, &tcm->tunnel_list); tb_reclaim_usb3_bandwidth(tb, in, out); + + /* Update the domain with the new bandwidth estimation */ + tb_recalc_estimated_bandwidth(tb); + /* * In case of DP tunnel exists, change host router's 1st children * TMU mode to HiFi for CL0s to work. @@ -1025,8 +1252,10 @@ static void tb_tunnel_dp(struct tb *tb) err_free: tb_tunnel_free(tunnel); -err_reclaim: +err_reclaim_usb: tb_reclaim_usb3_bandwidth(tb, in, out); +err_detach_group: + tb_detach_bandwidth_group(in); err_dealloc_dp: tb_switch_dealloc_dp_resource(in->sw, in); err_rpm_put: @@ -1059,6 +1288,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port) * See if there is another DP OUT port that can be used for * to create another tunnel. */ + tb_recalc_estimated_bandwidth(tb); tb_tunnel_dp(tb); } @@ -1306,6 +1536,7 @@ static void tb_handle_hotplug(struct work_struct *work) if (port->dual_link_port) port->dual_link_port->remote = NULL; /* Maybe we can create another DP tunnel */ + tb_recalc_estimated_bandwidth(tb); tb_tunnel_dp(tb); } else if (port->xdomain) { struct tb_xdomain *xd = tb_xdomain_get(port->xdomain); @@ -1363,6 +1594,235 @@ out: kfree(ev); } +static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up, + int *requested_down) +{ + int allocated_up, allocated_down, available_up, available_down, ret; + int requested_up_corrected, requested_down_corrected, granularity; + int max_up, max_down, max_up_rounded, max_down_rounded; + struct tb *tb = tunnel->tb; + struct tb_port *in, *out; + + ret = tb_tunnel_allocated_bandwidth(tunnel, &allocated_up, &allocated_down); + if (ret) + return ret; + + in = tunnel->src_port; + out = tunnel->dst_port; + + tb_port_dbg(in, "bandwidth allocated currently %d/%d Mb/s\n", + allocated_up, allocated_down); + + /* + * If we get rounded up request from graphics side, say HBR2 x 4 + * that is 17500 instead of 17280 (this is because of the + * granularity), we allow it too. Here the graphics has already + * negotiated with the DPRX the maximum possible rates (which is + * 17280 in this case). + * + * Since the link cannot go higher than 17280 we use that in our + * calculations but the DP IN adapter Allocated BW write must be + * the same value (17500) otherwise the adapter will mark it as + * failed for graphics. + */ + ret = tb_tunnel_maximum_bandwidth(tunnel, &max_up, &max_down); + if (ret) + return ret; + + ret = usb4_dp_port_granularity(in); + if (ret < 0) + return ret; + granularity = ret; + + max_up_rounded = roundup(max_up, granularity); + max_down_rounded = roundup(max_down, granularity); + + /* + * This will "fix" the request down to the maximum supported + * rate * lanes if it is at the maximum rounded up level. + */ + requested_up_corrected = *requested_up; + if (requested_up_corrected == max_up_rounded) + requested_up_corrected = max_up; + else if (requested_up_corrected < 0) + requested_up_corrected = 0; + requested_down_corrected = *requested_down; + if (requested_down_corrected == max_down_rounded) + requested_down_corrected = max_down; + else if (requested_down_corrected < 0) + requested_down_corrected = 0; + + tb_port_dbg(in, "corrected bandwidth request %d/%d Mb/s\n", + requested_up_corrected, requested_down_corrected); + + if ((*requested_up >= 0 && requested_up_corrected > max_up_rounded) || + (*requested_down >= 0 && requested_down_corrected > max_down_rounded)) { + tb_port_dbg(in, "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n", + requested_up_corrected, requested_down_corrected, + max_up_rounded, max_down_rounded); + return -ENOBUFS; + } + + if ((*requested_up >= 0 && requested_up_corrected <= allocated_up) || + (*requested_down >= 0 && requested_down_corrected <= allocated_down)) { + /* + * If requested bandwidth is less or equal than what is + * currently allocated to that tunnel we simply change + * the reservation of the tunnel. Since all the tunnels + * going out from the same USB4 port are in the same + * group the released bandwidth will be taken into + * account for the other tunnels automatically below. + */ + return tb_tunnel_alloc_bandwidth(tunnel, requested_up, + requested_down); + } + + /* + * More bandwidth is requested. Release all the potential + * bandwidth from USB3 first. + */ + ret = tb_release_unused_usb3_bandwidth(tb, in, out); + if (ret) + return ret; + + /* + * Then go over all tunnels that cross the same USB4 ports (they + * are also in the same group but we use the same function here + * that we use with the normal bandwidth allocation). + */ + ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down); + if (ret) + goto reclaim; + + tb_port_dbg(in, "bandwidth available for allocation %d/%d Mb/s\n", + available_up, available_down); + + if ((*requested_up >= 0 && available_up >= requested_up_corrected) || + (*requested_down >= 0 && available_down >= requested_down_corrected)) { + ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up, + requested_down); + } else { + ret = -ENOBUFS; + } + +reclaim: + tb_reclaim_usb3_bandwidth(tb, in, out); + return ret; +} + +static void tb_handle_dp_bandwidth_request(struct work_struct *work) +{ + struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); + int requested_bw, requested_up, requested_down, ret; + struct tb_port *in, *out; + struct tb_tunnel *tunnel; + struct tb *tb = ev->tb; + struct tb_cm *tcm = tb_priv(tb); + struct tb_switch *sw; + + pm_runtime_get_sync(&tb->dev); + + mutex_lock(&tb->lock); + if (!tcm->hotplug_active) + goto unlock; + + sw = tb_switch_find_by_route(tb, ev->route); + if (!sw) { + tb_warn(tb, "bandwidth request from non-existent router %llx\n", + ev->route); + goto unlock; + } + + in = &sw->ports[ev->port]; + if (!tb_port_is_dpin(in)) { + tb_port_warn(in, "bandwidth request to non-DP IN adapter\n"); + goto unlock; + } + + tb_port_dbg(in, "handling bandwidth allocation request\n"); + + if (!usb4_dp_port_bw_mode_enabled(in)) { + tb_port_warn(in, "bandwidth allocation mode not enabled\n"); + goto unlock; + } + + requested_bw = usb4_dp_port_requested_bw(in); + if (requested_bw < 0) { + tb_port_dbg(in, "no bandwidth request active\n"); + goto unlock; + } + + tb_port_dbg(in, "requested bandwidth %d Mb/s\n", requested_bw); + + tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL); + if (!tunnel) { + tb_port_warn(in, "failed to find tunnel\n"); + goto unlock; + } + + out = tunnel->dst_port; + + if (in->sw->config.depth < out->sw->config.depth) { + requested_up = -1; + requested_down = requested_bw; + } else { + requested_up = requested_bw; + requested_down = -1; + } + + ret = tb_alloc_dp_bandwidth(tunnel, &requested_up, &requested_down); + if (ret) { + if (ret == -ENOBUFS) + tb_port_warn(in, "not enough bandwidth available\n"); + else + tb_port_warn(in, "failed to change bandwidth allocation\n"); + } else { + tb_port_dbg(in, "bandwidth allocation changed to %d/%d Mb/s\n", + requested_up, requested_down); + + /* Update other clients about the allocation change */ + tb_recalc_estimated_bandwidth(tb); + } + +unlock: + mutex_unlock(&tb->lock); + + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); +} + +static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port) +{ + struct tb_hotplug_event *ev; + + ev = kmalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) + return; + + ev->tb = tb; + ev->route = route; + ev->port = port; + INIT_WORK(&ev->work, tb_handle_dp_bandwidth_request); + queue_work(tb->wq, &ev->work); +} + +static void tb_handle_notification(struct tb *tb, u64 route, + const struct cfg_error_pkg *error) +{ + if (tb_cfg_ack_notification(tb->ctl, route, error)) + tb_warn(tb, "could not ack notification on %llx\n", route); + + switch (error->error) { + case TB_CFG_ERROR_DP_BW: + tb_queue_dp_bandwidth_request(tb, route, error->port); + break; + + default: + /* Ack is enough */ + return; + } +} + /* * tb_schedule_hotplug_handler() - callback function for the control channel * @@ -1372,15 +1832,19 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, const void *buf, size_t size) { const struct cfg_event_pkg *pkg = buf; - u64 route; + u64 route = tb_cfg_get_route(&pkg->header); - if (type != TB_CFG_PKG_EVENT) { + switch (type) { + case TB_CFG_PKG_ERROR: + tb_handle_notification(tb, route, (const struct cfg_error_pkg *)buf); + return; + case TB_CFG_PKG_EVENT: + break; + default: tb_warn(tb, "unexpected event %#x, ignoring\n", type); return; } - route = tb_cfg_get_route(&pkg->header); - if (tb_cfg_ack_plug(tb->ctl, route, pkg->port, pkg->unplug)) { tb_warn(tb, "could not ack plug event on %llx:%x\n", route, pkg->port); @@ -1810,6 +2274,7 @@ struct tb *tb_probe(struct tb_nhi *nhi) INIT_LIST_HEAD(&tcm->tunnel_list); INIT_LIST_HEAD(&tcm->dp_resources); INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); + tb_init_bandwidth_groups(tcm); tb_dbg(tb, "using software connection manager\n"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index cd6c9eeaf049..2953cf38c29e 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -223,6 +223,23 @@ struct tb_switch { enum tb_clx clx; }; +/** + * struct tb_bandwidth_group - Bandwidth management group + * @tb: Pointer to the domain the group belongs to + * @index: Index of the group (aka Group_ID). Valid values %1-%7 + * @ports: DP IN adapters belonging to this group are linked here + * + * Any tunnel that requires isochronous bandwidth (that's DP for now) is + * attached to a bandwidth group. All tunnels going through the same + * USB4 links share the same group and can dynamically distribute the + * bandwidth within the group. + */ +struct tb_bandwidth_group { + struct tb *tb; + int index; + struct list_head ports; +}; + /** * struct tb_port - a thunderbolt port, part of a tb_switch * @config: Cached port configuration read from registers @@ -247,6 +264,9 @@ struct tb_switch { * @ctl_credits: Buffers reserved for control path * @dma_credits: Number of credits allocated for DMA tunneling for all * DMA paths through this port. + * @group: Bandwidth allocation group the adapter is assigned to. Only + * used for DP IN adapters for now. + * @group_list: The adapter is linked to the group's list of ports through this * * In USB4 terminology this structure represents an adapter (protocol or * lane adapter). @@ -272,6 +292,8 @@ struct tb_port { unsigned int total_credits; unsigned int ctl_credits; unsigned int dma_credits; + struct tb_bandwidth_group *group; + struct list_head group_list; }; /** diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index 33c4c7aed56d..3234bff07899 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -29,6 +29,7 @@ enum tb_cfg_error { TB_CFG_ERROR_HEC_ERROR_DETECTED = 12, TB_CFG_ERROR_FLOW_CONTROL_ERROR = 13, TB_CFG_ERROR_LOCK = 15, + TB_CFG_ERROR_DP_BW = 32, }; /* common header */ @@ -64,14 +65,16 @@ struct cfg_write_pkg { /* TB_CFG_PKG_ERROR */ struct cfg_error_pkg { struct tb_cfg_header header; - enum tb_cfg_error error:4; - u32 zero1:4; + enum tb_cfg_error error:8; u32 port:6; - u32 zero2:2; /* Both should be zero, still they are different fields. */ - u32 zero3:14; + u32 reserved:16; u32 pg:2; } __packed; +struct cfg_ack_pkg { + struct tb_cfg_header header; +}; + #define TB_CFG_ERROR_PG_HOT_PLUG 0x2 #define TB_CFG_ERROR_PG_HOT_UNPLUG 0x3 diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 04fbdbbc4ee2..e87d378ca2cd 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "tunnel.h" #include "tb.h" @@ -44,6 +45,11 @@ /* Minimum number of credits for DMA path */ #define TB_MIN_DMA_CREDITS 1U +static bool bw_alloc_mode = true; +module_param(bw_alloc_mode, bool, 0444); +MODULE_PARM_DESC(bw_alloc_mode, + "enable bandwidth allocation mode if supported (default: true)"); + static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ @@ -598,6 +604,133 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) in->cap_adap + DP_REMOTE_CAP, 1); } +static int tb_dp_bw_alloc_mode_enable(struct tb_tunnel *tunnel) +{ + int ret, estimated_bw, granularity, tmp; + struct tb_port *out = tunnel->dst_port; + struct tb_port *in = tunnel->src_port; + u32 out_dp_cap, out_rate, out_lanes; + u32 in_dp_cap, in_rate, in_lanes; + u32 rate, lanes; + + if (!bw_alloc_mode) + return 0; + + ret = usb4_dp_port_set_cm_bw_mode_supported(in, true); + if (ret) + return ret; + + ret = usb4_dp_port_set_group_id(in, in->group->index); + if (ret) + return ret; + + /* + * Get the non-reduced rate and lanes based on the lowest + * capability of both adapters. + */ + ret = tb_port_read(in, &in_dp_cap, TB_CFG_PORT, + in->cap_adap + DP_LOCAL_CAP, 1); + if (ret) + return ret; + + ret = tb_port_read(out, &out_dp_cap, TB_CFG_PORT, + out->cap_adap + DP_LOCAL_CAP, 1); + if (ret) + return ret; + + in_rate = tb_dp_cap_get_rate(in_dp_cap); + in_lanes = tb_dp_cap_get_lanes(in_dp_cap); + out_rate = tb_dp_cap_get_rate(out_dp_cap); + out_lanes = tb_dp_cap_get_lanes(out_dp_cap); + + rate = min(in_rate, out_rate); + lanes = min(in_lanes, out_lanes); + tmp = tb_dp_bandwidth(rate, lanes); + + tb_port_dbg(in, "non-reduced bandwidth %u Mb/s x%u = %u Mb/s\n", rate, + lanes, tmp); + + ret = usb4_dp_port_set_nrd(in, rate, lanes); + if (ret) + return ret; + + for (granularity = 250; tmp / granularity > 255 && granularity <= 1000; + granularity *= 2) + ; + + tb_port_dbg(in, "granularity %d Mb/s\n", granularity); + + /* + * Returns -EINVAL if granularity above is outside of the + * accepted ranges. + */ + ret = usb4_dp_port_set_granularity(in, granularity); + if (ret) + return ret; + + /* + * Bandwidth estimation is pretty much what we have in + * max_up/down fields. For discovery we just read what the + * estimation was set to. + */ + if (in->sw->config.depth < out->sw->config.depth) + estimated_bw = tunnel->max_down; + else + estimated_bw = tunnel->max_up; + + tb_port_dbg(in, "estimated bandwidth %d Mb/s\n", estimated_bw); + + ret = usb4_dp_port_set_estimated_bw(in, estimated_bw); + if (ret) + return ret; + + /* Initial allocation should be 0 according the spec */ + ret = usb4_dp_port_allocate_bw(in, 0); + if (ret) + return ret; + + tb_port_dbg(in, "bandwidth allocation mode enabled\n"); + return 0; +} + +static int tb_dp_init(struct tb_tunnel *tunnel) +{ + struct tb_port *in = tunnel->src_port; + struct tb_switch *sw = in->sw; + struct tb *tb = in->sw->tb; + int ret; + + ret = tb_dp_xchg_caps(tunnel); + if (ret) + return ret; + + if (!tb_switch_is_usb4(sw)) + return 0; + + if (!usb4_dp_port_bw_mode_supported(in)) + return 0; + + tb_port_dbg(in, "bandwidth allocation mode supported\n"); + + ret = usb4_dp_port_set_cm_id(in, tb->index); + if (ret) + return ret; + + return tb_dp_bw_alloc_mode_enable(tunnel); +} + +static void tb_dp_deinit(struct tb_tunnel *tunnel) +{ + struct tb_port *in = tunnel->src_port; + + if (!usb4_dp_port_bw_mode_supported(in)) + return; + if (usb4_dp_port_bw_mode_enabled(in)) { + usb4_dp_port_set_cm_bw_mode_supported(in, false); + tb_port_dbg(in, "bandwidth allocation mode disabled\n"); + } +} + static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) { int ret; @@ -635,49 +768,275 @@ static int tb_dp_activate(struct tb_tunnel *tunnel, bool active) return 0; } +/* max_bw is rounded up to next granularity */ +static int tb_dp_nrd_bandwidth(struct tb_tunnel *tunnel, int *max_bw) +{ + struct tb_port *in = tunnel->src_port; + int ret, rate, lanes, nrd_bw; + + ret = usb4_dp_port_nrd(in, &rate, &lanes); + if (ret) + return ret; + + nrd_bw = tb_dp_bandwidth(rate, lanes); + + if (max_bw) { + ret = usb4_dp_port_granularity(in); + if (ret < 0) + return ret; + *max_bw = roundup(nrd_bw, ret); + } + + return nrd_bw; +} + +static int tb_dp_bw_mode_consumed_bandwidth(struct tb_tunnel *tunnel, + int *consumed_up, int *consumed_down) +{ + struct tb_port *out = tunnel->dst_port; + struct tb_port *in = tunnel->src_port; + int ret, allocated_bw, max_bw; + + if (!usb4_dp_port_bw_mode_enabled(in)) + return -EOPNOTSUPP; + + if (!tunnel->bw_mode) + return -EOPNOTSUPP; + + /* Read what was allocated previously if any */ + ret = usb4_dp_port_allocated_bw(in); + if (ret < 0) + return ret; + allocated_bw = ret; + + ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + if (ret < 0) + return ret; + if (allocated_bw == max_bw) + allocated_bw = ret; + + tb_port_dbg(in, "consumed bandwidth through allocation mode %d Mb/s\n", + allocated_bw); + + if (in->sw->config.depth < out->sw->config.depth) { + *consumed_up = 0; + *consumed_down = allocated_bw; + } else { + *consumed_up = allocated_bw; + *consumed_down = 0; + } + + return 0; +} + +static int tb_dp_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, + int *allocated_down) +{ + struct tb_port *out = tunnel->dst_port; + struct tb_port *in = tunnel->src_port; + + /* + * If we have already set the allocated bandwidth then use that. + * Otherwise we read it from the DPRX. + */ + if (usb4_dp_port_bw_mode_enabled(in) && tunnel->bw_mode) { + int ret, allocated_bw, max_bw; + + ret = usb4_dp_port_allocated_bw(in); + if (ret < 0) + return ret; + allocated_bw = ret; + + ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + if (ret < 0) + return ret; + if (allocated_bw == max_bw) + allocated_bw = ret; + + if (in->sw->config.depth < out->sw->config.depth) { + *allocated_up = 0; + *allocated_down = allocated_bw; + } else { + *allocated_up = allocated_bw; + *allocated_down = 0; + } + return 0; + } + + return tunnel->consumed_bandwidth(tunnel, allocated_up, + allocated_down); +} + +static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, + int *alloc_down) +{ + struct tb_port *out = tunnel->dst_port; + struct tb_port *in = tunnel->src_port; + int max_bw, ret, tmp; + + if (!usb4_dp_port_bw_mode_enabled(in)) + return -EOPNOTSUPP; + + ret = tb_dp_nrd_bandwidth(tunnel, &max_bw); + if (ret < 0) + return ret; + + if (in->sw->config.depth < out->sw->config.depth) { + tmp = min(*alloc_down, max_bw); + ret = usb4_dp_port_allocate_bw(in, tmp); + if (ret) + return ret; + *alloc_down = tmp; + *alloc_up = 0; + } else { + tmp = min(*alloc_up, max_bw); + ret = usb4_dp_port_allocate_bw(in, tmp); + if (ret) + return ret; + *alloc_down = 0; + *alloc_up = tmp; + } + + /* Now we can use BW mode registers to figure out the bandwidth */ + /* TODO: need to handle discovery too */ + tunnel->bw_mode = true; + return 0; +} + +static int tb_dp_read_dprx(struct tb_tunnel *tunnel, u32 *rate, u32 *lanes, + int timeout_msec) +{ + ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec); + struct tb_port *in = tunnel->src_port; + + /* + * Wait for DPRX done. Normally it should be already set for + * active tunnel. + */ + do { + u32 val; + int ret; + + ret = tb_port_read(in, &val, TB_CFG_PORT, + in->cap_adap + DP_COMMON_CAP, 1); + if (ret) + return ret; + + if (val & DP_COMMON_CAP_DPRX_DONE) { + *rate = tb_dp_cap_get_rate(val); + *lanes = tb_dp_cap_get_lanes(val); + + tb_port_dbg(in, "consumed bandwidth through DPRX %d Mb/s\n", + tb_dp_bandwidth(*rate, *lanes)); + return 0; + } + usleep_range(100, 150); + } while (ktime_before(ktime_get(), timeout)); + + return -ETIMEDOUT; +} + +/* Read cap from tunnel DP IN */ +static int tb_dp_read_cap(struct tb_tunnel *tunnel, unsigned int cap, u32 *rate, + u32 *lanes) +{ + struct tb_port *in = tunnel->src_port; + u32 val; + int ret; + + switch (cap) { + case DP_LOCAL_CAP: + case DP_REMOTE_CAP: + break; + + default: + tb_tunnel_WARN(tunnel, "invalid capability index %#x\n", cap); + return -EINVAL; + } + + /* + * Read from the copied remote cap so that we take into account + * if capabilities were reduced during exchange. + */ + ret = tb_port_read(in, &val, TB_CFG_PORT, in->cap_adap + cap, 1); + if (ret) + return ret; + + *rate = tb_dp_cap_get_rate(val); + *lanes = tb_dp_cap_get_lanes(val); + + tb_port_dbg(in, "bandwidth from %#x capability %d Mb/s\n", cap, + tb_dp_bandwidth(*rate, *lanes)); + return 0; +} + +static int tb_dp_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, + int *max_down) +{ + struct tb_port *in = tunnel->src_port; + u32 rate, lanes; + int ret; + + /* + * DP IN adapter DP_LOCAL_CAP gets updated to the lowest AUX read + * parameter values so this so we can use this to determine the + * maximum possible bandwidth over this link. + */ + ret = tb_dp_read_cap(tunnel, DP_LOCAL_CAP, &rate, &lanes); + if (ret) + return ret; + + if (in->sw->config.depth < tunnel->dst_port->sw->config.depth) { + *max_up = 0; + *max_down = tb_dp_bandwidth(rate, lanes); + } else { + *max_up = tb_dp_bandwidth(rate, lanes); + *max_down = 0; + } + + return 0; +} + static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down) { struct tb_port *in = tunnel->src_port; const struct tb_switch *sw = in->sw; - u32 val, rate = 0, lanes = 0; + u32 rate = 0, lanes = 0; int ret; if (tb_dp_is_usb4(sw)) { - int timeout = 20; - /* - * Wait for DPRX done. Normally it should be already set - * for active tunnel. + * On USB4 routers check if the bandwidth allocation + * mode is enabled first and then read the bandwidth + * through those registers. */ - do { - ret = tb_port_read(in, &val, TB_CFG_PORT, - in->cap_adap + DP_COMMON_CAP, 1); - if (ret) + ret = tb_dp_bw_mode_consumed_bandwidth(tunnel, consumed_up, + consumed_down); + if (ret < 0) { + if (ret != -EOPNOTSUPP) return ret; - - if (val & DP_COMMON_CAP_DPRX_DONE) { - rate = tb_dp_cap_get_rate(val); - lanes = tb_dp_cap_get_lanes(val); - break; - } - msleep(250); - } while (timeout--); - - if (!timeout) - return -ETIMEDOUT; - } else if (sw->generation >= 2) { + } else if (!ret) { + return 0; + } /* - * Read from the copied remote cap so that we take into - * account if capabilities were reduced during exchange. + * Then see if the DPRX negotiation is ready and if yes + * return that bandwidth (it may be smaller than the + * reduced one). Otherwise return the remote (possibly + * reduced) caps. */ - ret = tb_port_read(in, &val, TB_CFG_PORT, - in->cap_adap + DP_REMOTE_CAP, 1); + ret = tb_dp_read_dprx(tunnel, &rate, &lanes, 150); + if (ret) { + if (ret == -ETIMEDOUT) + ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP, + &rate, &lanes); + if (ret) + return ret; + } + } else if (sw->generation >= 2) { + ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP, &rate, &lanes); if (ret) return ret; - - rate = tb_dp_cap_get_rate(val); - lanes = tb_dp_cap_get_lanes(val); } else { /* No bandwidth management for legacy devices */ *consumed_up = 0; @@ -799,8 +1158,12 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, if (!tunnel) return NULL; - tunnel->init = tb_dp_xchg_caps; + tunnel->init = tb_dp_init; + tunnel->deinit = tb_dp_deinit; tunnel->activate = tb_dp_activate; + tunnel->maximum_bandwidth = tb_dp_maximum_bandwidth; + tunnel->allocated_bandwidth = tb_dp_allocated_bandwidth; + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; tunnel->src_port = in; @@ -888,8 +1251,12 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, if (!tunnel) return NULL; - tunnel->init = tb_dp_xchg_caps; + tunnel->init = tb_dp_init; + tunnel->deinit = tb_dp_deinit; tunnel->activate = tb_dp_activate; + tunnel->maximum_bandwidth = tb_dp_maximum_bandwidth; + tunnel->allocated_bandwidth = tb_dp_allocated_bandwidth; + tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; tunnel->src_port = in; tunnel->dst_port = out; @@ -1714,6 +2081,62 @@ static bool tb_tunnel_is_active(const struct tb_tunnel *tunnel) return true; } +int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, + int *max_down) +{ + if (!tb_tunnel_is_active(tunnel)) + return -EINVAL; + + if (tunnel->maximum_bandwidth) + return tunnel->maximum_bandwidth(tunnel, max_up, max_down); + return -EOPNOTSUPP; +} + +/** + * tb_tunnel_allocated_bandwidth() - Return bandwidth allocated for the tunnel + * @tunnel: Tunnel to check + * @allocated_up: Currently allocated upstream bandwidth in Mb/s is stored here + * @allocated_down: Currently allocated downstream bandwidth in Mb/s is + * stored here + * + * Returns the bandwidth allocated for the tunnel. This may be higher + * than what the tunnel actually consumes. + */ +int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, + int *allocated_down) +{ + if (!tb_tunnel_is_active(tunnel)) + return -EINVAL; + + if (tunnel->allocated_bandwidth) + return tunnel->allocated_bandwidth(tunnel, allocated_up, + allocated_down); + return -EOPNOTSUPP; +} + +/** + * tb_tunnel_alloc_bandwidth() - Change tunnel bandwidth allocation + * @tunnel: Tunnel whose bandwidth allocation to change + * @alloc_up: New upstream bandwidth in Mb/s + * @alloc_down: New downstream bandwidth in Mb/s + * + * Tries to change tunnel bandwidth allocation. If succeeds returns %0 + * and updates @alloc_up and @alloc_down to that was actually allocated + * (it may not be the same as passed originally). Returns negative errno + * in case of failure. + */ +int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, + int *alloc_down) +{ + if (!tb_tunnel_is_active(tunnel)) + return -EINVAL; + + if (tunnel->alloc_bandwidth) + return tunnel->alloc_bandwidth(tunnel, alloc_up, alloc_down); + + return -EOPNOTSUPP; +} + /** * tb_tunnel_consumed_bandwidth() - Return bandwidth consumed by the tunnel * @tunnel: Tunnel to check diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index bb4d1f1d6d0b..d7bbdf8c8186 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -29,6 +29,9 @@ enum tb_tunnel_type { * @init: Optional tunnel specific initialization * @deinit: Optional tunnel specific de-initialization * @activate: Optional tunnel specific activation/deactivation + * @maximum_bandwidth: + * @allocated_bandwidth: Return how much bandwidth is allocated for the tunnel + * @alloc_bandwidth: Change tunnel bandwidth allocation * @consumed_bandwidth: Return how much bandwidth the tunnel consumes * @release_unused_bandwidth: Release all unused bandwidth * @reclaim_available_bandwidth: Reclaim back available bandwidth @@ -40,6 +43,8 @@ enum tb_tunnel_type { * Only set if the bandwidth needs to be limited. * @allocated_up: Allocated upstream bandwidth (only for USB3) * @allocated_down: Allocated downstream bandwidth (only for USB3) + * @bw_mode: DP bandwidth allocation mode registers can be used to + * determine consumed and allocated bandwidth */ struct tb_tunnel { struct tb *tb; @@ -50,6 +55,12 @@ struct tb_tunnel { int (*init)(struct tb_tunnel *tunnel); void (*deinit)(struct tb_tunnel *tunnel); int (*activate)(struct tb_tunnel *tunnel, bool activate); + int (*maximum_bandwidth)(struct tb_tunnel *tunnel, int *max_up, + int *max_down); + int (*allocated_bandwidth)(struct tb_tunnel *tunnel, int *allocated_up, + int *allocated_down); + int (*alloc_bandwidth)(struct tb_tunnel *tunnel, int *alloc_up, + int *alloc_down); int (*consumed_bandwidth)(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down); int (*release_unused_bandwidth)(struct tb_tunnel *tunnel); @@ -62,6 +73,7 @@ struct tb_tunnel { int max_down; int allocated_up; int allocated_down; + bool bw_mode; }; struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, @@ -92,6 +104,12 @@ void tb_tunnel_deactivate(struct tb_tunnel *tunnel); bool tb_tunnel_is_invalid(struct tb_tunnel *tunnel); bool tb_tunnel_port_on_path(const struct tb_tunnel *tunnel, const struct tb_port *port); +int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, + int *max_down); +int tb_tunnel_allocated_bandwidth(struct tb_tunnel *tunnel, int *allocated_up, + int *allocated_down); +int tb_tunnel_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up, + int *alloc_down); int tb_tunnel_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, int *consumed_down); int tb_tunnel_release_unused_bandwidth(struct tb_tunnel *tunnel); -- cgit v1.2.3 From bf58a687db232e738105244e34a85d21f5e2598d Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 9 Dec 2022 17:18:35 +0000 Subject: dt-bindings: usb: ti,hd3ss3220: Update interrupt property as optional On some platforms(for eg: RZ/V2M EVK), interrupt is not populated. Update the binding to make interrupt property as optional. Signed-off-by: Biju Das Acked-by: Rob Herring Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20221209171836.71610-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml index b86bf6bc9cd6..a1cffb70c621 100644 --- a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml +++ b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml @@ -46,7 +46,6 @@ properties: required: - compatible - reg - - interrupts additionalProperties: false -- cgit v1.2.3 From 569d23e9bf58464887c55aa0d2bdb0ead97f8592 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 9 Dec 2022 17:18:36 +0000 Subject: usb: typec: hd3ss3220: Add polling support Some platforms(for eg: RZ/V2M EVK) does not have interrupt pin connected to HD3SS3220. Add polling support for role detection. Signed-off-by: Biju Das Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20221209171836.71610-3-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/hd3ss3220.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index f128664cb130..746ef3a75b76 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -14,6 +14,7 @@ #include #include #include +#include #define HD3SS3220_REG_CN_STAT_CTRL 0x09 #define HD3SS3220_REG_GEN_CTRL 0x0A @@ -37,6 +38,9 @@ struct hd3ss3220 { struct regmap *regmap; struct usb_role_switch *role_sw; struct typec_port *port; + struct delayed_work output_poll_work; + enum usb_role role_state; + bool poll; }; static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int src_pref) @@ -118,6 +122,22 @@ static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) default: break; } + + hd3ss3220->role_state = role_state; +} + +static void output_poll_execute(struct work_struct *work) +{ + struct delayed_work *delayed_work = to_delayed_work(work); + struct hd3ss3220 *hd3ss3220 = container_of(delayed_work, + struct hd3ss3220, + output_poll_work); + enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); + + if (hd3ss3220->role_state != role_state) + hd3ss3220_set_role(hd3ss3220); + + schedule_delayed_work(&hd3ss3220->output_poll_work, HZ); } static irqreturn_t hd3ss3220_irq(struct hd3ss3220 *hd3ss3220) @@ -223,6 +243,9 @@ static int hd3ss3220_probe(struct i2c_client *client) "hd3ss3220", &client->dev); if (ret) goto err_unreg_port; + } else { + INIT_DELAYED_WORK(&hd3ss3220->output_poll_work, output_poll_execute); + hd3ss3220->poll = true; } ret = i2c_smbus_read_byte_data(client, HD3SS3220_REG_DEV_REV); @@ -231,6 +254,9 @@ static int hd3ss3220_probe(struct i2c_client *client) fwnode_handle_put(connector); + if (hd3ss3220->poll) + schedule_delayed_work(&hd3ss3220->output_poll_work, HZ); + dev_info(&client->dev, "probed revision=0x%x\n", ret); return 0; @@ -248,6 +274,9 @@ static void hd3ss3220_remove(struct i2c_client *client) { struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); + if (hd3ss3220->poll) + cancel_delayed_work_sync(&hd3ss3220->output_poll_work); + typec_unregister_port(hd3ss3220->port); usb_role_switch_put(hd3ss3220->role_sw); } -- cgit v1.2.3 From 2582d629c9e01687aee905ac0bce33615a8a004a Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Fri, 16 Dec 2022 12:20:26 +0800 Subject: usb: gadget: xudc: Refactor update data role work The notification call chain should be registered after the device gets ready. Otherwise, we might get errors when setting data roles in an incomplete system. This patch refactors update data role work and register the notifier call chain after the system gets ready. Signed-off-by: Wayne Chang Signed-off-by: Haotien Hsu Link: https://lore.kernel.org/r/20221216042026.98517-1-haotienh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 76919d7570d2..15c0b4837f80 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -796,21 +796,16 @@ static int tegra_xudc_get_phy_index(struct tegra_xudc *xudc, return -1; } -static int tegra_xudc_vbus_notify(struct notifier_block *nb, - unsigned long action, void *data) +static void tegra_xudc_update_data_role(struct tegra_xudc *xudc, + struct usb_phy *usbphy) { - struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc, - vbus_nb); - struct usb_phy *usbphy = (struct usb_phy *)data; int phy_index; - dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event); - if ((xudc->device_mode && usbphy->last_event == USB_EVENT_VBUS) || (!xudc->device_mode && usbphy->last_event != USB_EVENT_VBUS)) { dev_dbg(xudc->dev, "Same role(%d) received. Ignore", xudc->device_mode); - return NOTIFY_OK; + return; } xudc->device_mode = (usbphy->last_event == USB_EVENT_VBUS) ? true : @@ -826,6 +821,18 @@ static int tegra_xudc_vbus_notify(struct notifier_block *nb, xudc->curr_usbphy = usbphy; schedule_work(&xudc->usb_role_sw_work); } +} + +static int tegra_xudc_vbus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct tegra_xudc *xudc = container_of(nb, struct tegra_xudc, + vbus_nb); + struct usb_phy *usbphy = (struct usb_phy *)data; + + dev_dbg(xudc->dev, "%s(): event is %d\n", __func__, usbphy->last_event); + + tegra_xudc_update_data_role(xudc, usbphy); return NOTIFY_OK; } @@ -3521,7 +3528,7 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc) /* Get usb-phy, if utmi phy is available */ xudc->usbphy[i] = devm_usb_get_phy_by_node(xudc->dev, xudc->utmi_phy[i]->dev.of_node, - &xudc->vbus_nb); + NULL); if (IS_ERR(xudc->usbphy[i])) { err = PTR_ERR(xudc->usbphy[i]); dev_err_probe(xudc->dev, err, @@ -3856,6 +3863,14 @@ static int tegra_xudc_probe(struct platform_device *pdev) goto free_eps; } + for (i = 0; i < xudc->soc->num_phys; i++) { + if (!xudc->usbphy[i]) + continue; + + usb_register_notifier(xudc->usbphy[i], &xudc->vbus_nb); + tegra_xudc_update_data_role(xudc, xudc->usbphy[i]); + } + return 0; free_eps: -- cgit v1.2.3 From 28e1ff70a08d331703f115534bd4278e11451439 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 21 Dec 2022 20:34:51 +0100 Subject: USB: Improve usb_fill_* documentation Document the transfer buffer requirement. That is, the buffer must be DMAble - otherwise data corruption might occur. Acked-by: Randy Dunlap Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Acked-by: Alan Stern Link: https://lore.kernel.org/r/20221220-usb-dmadoc-v4-0-74a045bf14f4@chromium.org Signed-off-by: Greg Kroah-Hartman --- include/linux/usb.h | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/include/linux/usb.h b/include/linux/usb.h index 7d5325d47c45..4e98ebacec96 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -1626,14 +1626,25 @@ struct urb { * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe - * @setup_packet: pointer to the setup_packet buffer - * @transfer_buffer: pointer to the transfer buffer + * @setup_packet: pointer to the setup_packet buffer. The buffer must be + * suitable for DMA. + * @transfer_buffer: pointer to the transfer buffer. The buffer must be + * suitable for DMA. * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * * Initializes a control urb with the proper information needed to submit * it to a device. + * + * The transfer buffer and the setup_packet buffer will most likely be filled + * or read via DMA. The simplest way to get a buffer that can be DMAed to is + * allocating it via kmalloc() or equivalent, even for very small buffers. + * If the buffers are embedded in a bigger structure, there is a risk that + * the buffer itself, the previous fields and/or the next fields are corrupted + * due to cache incoherencies; or slowed down if they are evicted from the + * cache. For more information, check &struct urb. + * */ static inline void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, @@ -1658,13 +1669,17 @@ static inline void usb_fill_control_urb(struct urb *urb, * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe - * @transfer_buffer: pointer to the transfer buffer + * @transfer_buffer: pointer to the transfer buffer. The buffer must be + * suitable for DMA. * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. * * Initializes a bulk urb with the proper information needed to submit it * to a device. + * + * Refer to usb_fill_control_urb() for a description of the requirements for + * transfer_buffer. */ static inline void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, @@ -1687,7 +1702,8 @@ static inline void usb_fill_bulk_urb(struct urb *urb, * @urb: pointer to the urb to initialize. * @dev: pointer to the struct usb_device for this urb. * @pipe: the endpoint pipe - * @transfer_buffer: pointer to the transfer buffer + * @transfer_buffer: pointer to the transfer buffer. The buffer must be + * suitable for DMA. * @buffer_length: length of the transfer buffer * @complete_fn: pointer to the usb_complete_t function * @context: what to set the urb context to. @@ -1697,6 +1713,9 @@ static inline void usb_fill_bulk_urb(struct urb *urb, * Initializes a interrupt urb with the proper information needed to submit * it to a device. * + * Refer to usb_fill_control_urb() for a description of the requirements for + * transfer_buffer. + * * Note that High Speed and SuperSpeed(+) interrupt endpoints use a logarithmic * encoding of the endpoint interval, and express polling intervals in * microframes (eight per millisecond) rather than in frames (one per -- cgit v1.2.3 From 1c796d93b58903d2ba09441c23ba6df263b6df28 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 23 Dec 2022 11:10:11 +0800 Subject: dt-bindings: usb: ci-hdrc-usb2: add i.MX8MM compatible Add fsl,imx8mm-usb compatible for i.MX8MM Signed-off-by: Peng Fan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20221223031012.92932-1-peng.fan@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt index ba51fb1252b9..72ceea575d58 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt @@ -11,6 +11,7 @@ Required properties: "fsl,imx6ul-usb" "fsl,imx7d-usb" "fsl,imx7ulp-usb" + "fsl,imx8mm-usb" "lsi,zevio-usb" "qcom,ci-hdrc" "chipidea,usb2" -- cgit v1.2.3 From 2c03f7f1ad81da297e569d68d8b1219bbafcdfd1 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 23 Dec 2022 11:10:12 +0800 Subject: dt-bindings: usb: usbmisc-imx: add i.MX8MM usbmisc Add fsl,imx8mm-usbmisc compatible for i.MX8MM Signed-off-by: Peng Fan Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20221223031012.92932-2-peng.fan@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usbmisc-imx.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/usbmisc-imx.txt b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt index b796836d2ce7..29b8f65ff849 100644 --- a/Documentation/devicetree/bindings/usb/usbmisc-imx.txt +++ b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt @@ -8,6 +8,7 @@ Required properties: "fsl,imx6sx-usbmisc" for imx6sx "fsl,imx7d-usbmisc" for imx7d "fsl,imx7ulp-usbmisc" for imx7ulp + "fsl,imx8mm-usbmisc" for imx8mm - reg: Should contain registers location and length Examples: -- cgit v1.2.3 From ccb0beb43a57ee8dbc35a3314c4bf1db922c054f Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 14 Dec 2022 10:23:34 +0800 Subject: usb: typec: tcpci: Request IRQ with IRQF_SHARED Under resource constraints, this interrupt may use other interrupt line or this interrupt line may be shared with other devices as long as they meet the sharing requirements. Besides, This irq flag will not cause other side effect if tcpci driver is the only user. Signed-off-by: Xu Yang Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20221214022334.2520677-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index fe781a38dc82..c7796511695d 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -33,6 +33,7 @@ struct tcpci { struct tcpm_port *port; struct regmap *regmap; + unsigned int alert_mask; bool controls_vbus; @@ -632,6 +633,9 @@ static int tcpci_init(struct tcpc_dev *tcpc) if (ret < 0) return ret; } + + tcpci->alert_mask = reg; + return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); } @@ -715,7 +719,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) else if (status & TCPC_ALERT_TX_FAILED) tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED); - return IRQ_HANDLED; + return IRQ_RETVAL(status & tcpci->alert_mask); } EXPORT_SYMBOL_GPL(tcpci_irq); @@ -838,7 +842,7 @@ static int tcpci_probe(struct i2c_client *client) err = devm_request_threaded_irq(&client->dev, client->irq, NULL, _tcpci_irq, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, + IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW, dev_name(&client->dev), chip); if (err < 0) { tcpci_unregister_port(chip->tcpci); -- cgit v1.2.3 From c3194949ae8fcbe2b7e38670e7c6a5cfd2605edc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Jan 2023 22:29:32 +0200 Subject: usb: typec: intel_pmc_mux: Don't leak the ACPI device reference count When acpi_dev_get_memory_resources() fails, the reference count is left bumped. Drop it as it's done in the other error paths. Fixes: 43d596e32276 ("usb: typec: intel_pmc_mux: Check the port status before connect") Signed-off-by: Andy Shevchenko Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230102202933.15968-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index fdbf3694e21f..87e2c9130607 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -614,8 +614,10 @@ static int pmc_usb_probe_iom(struct pmc_usb *pmc) INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_memory_resources(adev, &resource_list); - if (ret < 0) + if (ret < 0) { + acpi_dev_put(adev); return ret; + } rentry = list_first_entry_or_null(&resource_list, struct resource_entry, node); if (rentry) -- cgit v1.2.3 From 8b9c6ab156b5a80d64e5a1d9c248cfcd61794dfc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Jan 2023 22:29:33 +0200 Subject: usb: typec: intel_pmc_mux: Deduplicate ACPI matching in probe There is no need to call acpi_dev_present() followed by acpi_dev_get_first_match_dev() as they both do the same with only difference in the returning value. Instead, call the latter and check its result. Signed-off-by: Andy Shevchenko Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230102202933.15968-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 87e2c9130607..34e4188a40ff 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -602,16 +602,15 @@ static int pmc_usb_probe_iom(struct pmc_usb *pmc) int ret; for (dev_id = &iom_acpi_ids[0]; dev_id->id[0]; dev_id++) { - if (acpi_dev_present(dev_id->id, NULL, -1)) { - pmc->iom_port_status_offset = (u32)dev_id->driver_data; - adev = acpi_dev_get_first_match_dev(dev_id->id, NULL, -1); + adev = acpi_dev_get_first_match_dev(dev_id->id, NULL, -1); + if (adev) break; - } } - if (!adev) return -ENODEV; + pmc->iom_port_status_offset = (u32)dev_id->driver_data; + INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_memory_resources(adev, &resource_list); if (ret < 0) { -- cgit v1.2.3 From a90498e5600eba40f534c4cba6f030d349e78c70 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Thu, 5 Jan 2023 16:22:53 +0100 Subject: dt-bindings: usb: add the Renesas RZ/N1 USBF controller The Renesas RZ/N1 USBF controller is an USB2.0 device controller (UDC) available in the Renesas r9a06g032 SoC (RZ/N1 family). Signed-off-by: Herve Codina Reviewed-by: Rob Herring Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230105152257.310642-2-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/renesas,rzn1-usbf.yaml | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml diff --git a/Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml b/Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml new file mode 100644 index 000000000000..b6e84a2a6925 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/renesas,rzn1-usbf.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/N1 SoCs USBF (USB Function) controller + +description: | + The Renesas USBF controller is an USB2.0 device + controller (UDC). + +maintainers: + - Herve Codina + +properties: + compatible: + items: + - enum: + - renesas,r9a06g032-usbf + - const: renesas,rzn1-usbf + + reg: + maxItems: 1 + + clocks: + items: + - description: Internal bus clock (AHB) for Function + - description: Internal bus clock (AHB) for Power Management + + clock-names: + items: + - const: hclkf + - const: hclkpm + + power-domains: + maxItems: 1 + + interrupts: + items: + - description: The USBF EPC interrupt + - description: The USBF AHB-EPC interrupt + +required: + - compatible + - reg + - clocks + - clock-names + - power-domains + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + usb@4001e000 { + compatible = "renesas,r9a06g032-usbf", "renesas,rzn1-usbf"; + reg = <0x4001e000 0x2000>; + interrupts = , + ; + clocks = <&sysctrl R9A06G032_HCLK_USBF>, + <&sysctrl R9A06G032_HCLK_USBPM>; + clock-names = "hclkf", "hclkpm"; + power-domains = <&sysctrl>; + }; -- cgit v1.2.3 From e9fee814b054e4f6f2faf3d9c1944869fe41c9dd Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Thu, 5 Jan 2023 16:22:54 +0100 Subject: soc: renesas: r9a06g032-sysctrl: Handle h2mode setting based on USBF presence The CFG_USB[H2MODE] allows to switch the USB configuration. The configuration supported are: - One host and one device or - Two hosts Set CFG_USB[H2MODE] based on the USBF controller (USB device) availability. Signed-off-by: Herve Codina Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230105152257.310642-3-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/clk/renesas/r9a06g032-clocks.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/clk/renesas/r9a06g032-clocks.c b/drivers/clk/renesas/r9a06g032-clocks.c index 983faa5707b9..087146f2ee06 100644 --- a/drivers/clk/renesas/r9a06g032-clocks.c +++ b/drivers/clk/renesas/r9a06g032-clocks.c @@ -25,6 +25,8 @@ #include #include +#define R9A06G032_SYSCTRL_USB 0x00 +#define R9A06G032_SYSCTRL_USB_H2MODE (1<<1) #define R9A06G032_SYSCTRL_DMAMUX 0xA0 struct r9a06g032_gate { @@ -918,6 +920,29 @@ static void r9a06g032_clocks_del_clk_provider(void *data) of_clk_del_provider(data); } +static void __init r9a06g032_init_h2mode(struct r9a06g032_priv *clocks) +{ + struct device_node *usbf_np = NULL; + u32 usb; + + while ((usbf_np = of_find_compatible_node(usbf_np, NULL, + "renesas,rzn1-usbf"))) { + if (of_device_is_available(usbf_np)) + break; + } + + usb = readl(clocks->reg + R9A06G032_SYSCTRL_USB); + if (usbf_np) { + /* 1 host and 1 device mode */ + usb &= ~R9A06G032_SYSCTRL_USB_H2MODE; + of_node_put(usbf_np); + } else { + /* 2 hosts mode */ + usb |= R9A06G032_SYSCTRL_USB_H2MODE; + } + writel(usb, clocks->reg + R9A06G032_SYSCTRL_USB); +} + static int __init r9a06g032_clocks_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -947,6 +972,9 @@ static int __init r9a06g032_clocks_probe(struct platform_device *pdev) clocks->reg = of_iomap(np, 0); if (WARN_ON(!clocks->reg)) return -ENOMEM; + + r9a06g032_init_h2mode(clocks); + for (i = 0; i < ARRAY_SIZE(r9a06g032_clocks); ++i) { const struct r9a06g032_clkdesc *d = &r9a06g032_clocks[i]; const char *parent_name = d->source ? -- cgit v1.2.3 From 3e6e14ffdea41ca91d4c9afd88a1f736cf50a1f3 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Thu, 5 Jan 2023 16:22:55 +0100 Subject: usb: gadget: udc: add Renesas RZ/N1 USBF controller support Add support for the Renesas USBF controller. This controller is an USB2.0 UDC controller available in the Renesas r9a06g032 SoC (RZ/N1 family). Signed-off-by: Herve Codina Link: https://lore.kernel.org/r/20230105152257.310642-4-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 11 + drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/renesas_usbf.c | 3406 +++++++++++++++++++++++++++++++++ 3 files changed, 3418 insertions(+) create mode 100644 drivers/usb/gadget/udc/renesas_usbf.c diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index b3006d8b04ab..12f7323869fe 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -193,6 +193,17 @@ config USB_RENESAS_USB3 dynamically linked module called "renesas_usb3" and force all gadget drivers to also be dynamically linked. +config USB_RENESAS_USBF + tristate 'Renesas USB Function controller' + depends on ARCH_RENESAS || COMPILE_TEST + help + Renesas USB Function controller is a USB peripheral controller + available on RZ/N1 Renesas SoCs. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usbf" and force all + gadget drivers to also be dynamically linked. + config USB_PXA27X tristate "PXA 27x" depends on HAS_IOMEM diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 39daf36a2baa..1e23627733eb 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_USB_TEGRA_XUDC) += tegra-xudc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o obj-$(CONFIG_USB_RENESAS_USB3) += renesas_usb3.o +obj-$(CONFIG_USB_RENESAS_USBF) += renesas_usbf.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o obj-$(CONFIG_USB_LPC32XX) += lpc32xx_udc.o diff --git a/drivers/usb/gadget/udc/renesas_usbf.c b/drivers/usb/gadget/udc/renesas_usbf.c new file mode 100644 index 000000000000..cb23e62e8a87 --- /dev/null +++ b/drivers/usb/gadget/udc/renesas_usbf.c @@ -0,0 +1,3406 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas USBF USB Function driver + * + * Copyright 2022 Schneider Electric + * Author: Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USBF_NUM_ENDPOINTS 16 +#define USBF_EP0_MAX_PCKT_SIZE 64 + +/* EPC registers */ +#define USBF_REG_USB_CONTROL 0x000 +#define USBF_USB_PUE2 BIT(2) +#define USBF_USB_CONNECTB BIT(3) +#define USBF_USB_DEFAULT BIT(4) +#define USBF_USB_CONF BIT(5) +#define USBF_USB_SUSPEND BIT(6) +#define USBF_USB_RSUM_IN BIT(7) +#define USBF_USB_SOF_RCV BIT(8) +#define USBF_USB_FORCEFS BIT(9) +#define USBF_USB_INT_SEL BIT(10) +#define USBF_USB_SOF_CLK_MODE BIT(11) + +#define USBF_REG_USB_STATUS 0x004 +#define USBF_USB_RSUM_OUT BIT(1) +#define USBF_USB_SPND_OUT BIT(2) +#define USBF_USB_USB_RST BIT(3) +#define USBF_USB_DEFAULT_ST BIT(4) +#define USBF_USB_CONF_ST BIT(5) +#define USBF_USB_SPEED_MODE BIT(6) +#define USBF_USB_SOF_DELAY_STATUS BIT(31) + +#define USBF_REG_USB_ADDRESS 0x008 +#define USBF_USB_SOF_STATUS BIT(15) +#define USBF_USB_SET_USB_ADDR(_a) ((_a) << 16) +#define USBF_USB_GET_FRAME(_r) ((_r) & 0x7FF) + +#define USBF_REG_SETUP_DATA0 0x018 +#define USBF_REG_SETUP_DATA1 0x01C +#define USBF_REG_USB_INT_STA 0x020 +#define USBF_USB_RSUM_INT BIT(1) +#define USBF_USB_SPND_INT BIT(2) +#define USBF_USB_USB_RST_INT BIT(3) +#define USBF_USB_SOF_INT BIT(4) +#define USBF_USB_SOF_ERROR_INT BIT(5) +#define USBF_USB_SPEED_MODE_INT BIT(6) +#define USBF_USB_EPN_INT(_n) (BIT(8) << (_n)) /* n=0..15 */ + +#define USBF_REG_USB_INT_ENA 0x024 +#define USBF_USB_RSUM_EN BIT(1) +#define USBF_USB_SPND_EN BIT(2) +#define USBF_USB_USB_RST_EN BIT(3) +#define USBF_USB_SOF_EN BIT(4) +#define USBF_USB_SOF_ERROR_EN BIT(5) +#define USBF_USB_SPEED_MODE_EN BIT(6) +#define USBF_USB_EPN_EN(_n) (BIT(8) << (_n)) /* n=0..15 */ + +#define USBF_BASE_EP0 0x028 +/* EP0 registers offsets from Base + USBF_BASE_EP0 (EP0 regs area) */ +#define USBF_REG_EP0_CONTROL 0x00 +#define USBF_EP0_ONAK BIT(0) +#define USBF_EP0_INAK BIT(1) +#define USBF_EP0_STL BIT(2) +#define USBF_EP0_PERR_NAK_CLR BIT(3) +#define USBF_EP0_INAK_EN BIT(4) +#define USBF_EP0_DW_MASK (0x3 << 5) +#define USBF_EP0_DW(_s) ((_s) << 5) +#define USBF_EP0_DEND BIT(7) +#define USBF_EP0_BCLR BIT(8) +#define USBF_EP0_PIDCLR BIT(9) +#define USBF_EP0_AUTO BIT(16) +#define USBF_EP0_OVERSEL BIT(17) +#define USBF_EP0_STGSEL BIT(18) + +#define USBF_REG_EP0_STATUS 0x04 +#define USBF_EP0_SETUP_INT BIT(0) +#define USBF_EP0_STG_START_INT BIT(1) +#define USBF_EP0_STG_END_INT BIT(2) +#define USBF_EP0_STALL_INT BIT(3) +#define USBF_EP0_IN_INT BIT(4) +#define USBF_EP0_OUT_INT BIT(5) +#define USBF_EP0_OUT_OR_INT BIT(6) +#define USBF_EP0_OUT_NULL_INT BIT(7) +#define USBF_EP0_IN_EMPTY BIT(8) +#define USBF_EP0_IN_FULL BIT(9) +#define USBF_EP0_IN_DATA BIT(10) +#define USBF_EP0_IN_NAK_INT BIT(11) +#define USBF_EP0_OUT_EMPTY BIT(12) +#define USBF_EP0_OUT_FULL BIT(13) +#define USBF_EP0_OUT_NULL BIT(14) +#define USBF_EP0_OUT_NAK_INT BIT(15) +#define USBF_EP0_PERR_NAK_INT BIT(16) +#define USBF_EP0_PERR_NAK BIT(17) +#define USBF_EP0_PID BIT(18) + +#define USBF_REG_EP0_INT_ENA 0x08 +#define USBF_EP0_SETUP_EN BIT(0) +#define USBF_EP0_STG_START_EN BIT(1) +#define USBF_EP0_STG_END_EN BIT(2) +#define USBF_EP0_STALL_EN BIT(3) +#define USBF_EP0_IN_EN BIT(4) +#define USBF_EP0_OUT_EN BIT(5) +#define USBF_EP0_OUT_OR_EN BIT(6) +#define USBF_EP0_OUT_NULL_EN BIT(7) +#define USBF_EP0_IN_NAK_EN BIT(11) +#define USBF_EP0_OUT_NAK_EN BIT(15) +#define USBF_EP0_PERR_NAK_EN BIT(16) + +#define USBF_REG_EP0_LENGTH 0x0C +#define USBF_EP0_LDATA (0x7FF << 0) +#define USBF_REG_EP0_READ 0x10 +#define USBF_REG_EP0_WRITE 0x14 + +#define USBF_BASE_EPN(_n) (0x040 + (_n) * 0x020) +/* EPn registers offsets from Base + USBF_BASE_EPN(n-1). n=1..15 */ +#define USBF_REG_EPN_CONTROL 0x000 +#define USBF_EPN_ONAK BIT(0) +#define USBF_EPN_OSTL BIT(2) +#define USBF_EPN_ISTL BIT(3) +#define USBF_EPN_OSTL_EN BIT(4) +#define USBF_EPN_DW_MASK (0x3 << 5) +#define USBF_EPN_DW(_s) ((_s) << 5) +#define USBF_EPN_DEND BIT(7) +#define USBF_EPN_CBCLR BIT(8) +#define USBF_EPN_BCLR BIT(9) +#define USBF_EPN_OPIDCLR BIT(10) +#define USBF_EPN_IPIDCLR BIT(11) +#define USBF_EPN_AUTO BIT(16) +#define USBF_EPN_OVERSEL BIT(17) +#define USBF_EPN_MODE_MASK (0x3 << 24) +#define USBF_EPN_MODE_BULK (0x0 << 24) +#define USBF_EPN_MODE_INTR (0x1 << 24) +#define USBF_EPN_MODE_ISO (0x2 << 24) +#define USBF_EPN_DIR0 BIT(26) +#define USBF_EPN_BUF_TYPE_DOUBLE BIT(30) +#define USBF_EPN_EN BIT(31) + +#define USBF_REG_EPN_STATUS 0x004 +#define USBF_EPN_IN_EMPTY BIT(0) +#define USBF_EPN_IN_FULL BIT(1) +#define USBF_EPN_IN_DATA BIT(2) +#define USBF_EPN_IN_INT BIT(3) +#define USBF_EPN_IN_STALL_INT BIT(4) +#define USBF_EPN_IN_NAK_ERR_INT BIT(5) +#define USBF_EPN_IN_END_INT BIT(7) +#define USBF_EPN_IPID BIT(10) +#define USBF_EPN_OUT_EMPTY BIT(16) +#define USBF_EPN_OUT_FULL BIT(17) +#define USBF_EPN_OUT_NULL_INT BIT(18) +#define USBF_EPN_OUT_INT BIT(19) +#define USBF_EPN_OUT_STALL_INT BIT(20) +#define USBF_EPN_OUT_NAK_ERR_INT BIT(21) +#define USBF_EPN_OUT_OR_INT BIT(22) +#define USBF_EPN_OUT_END_INT BIT(23) +#define USBF_EPN_ISO_CRC BIT(24) +#define USBF_EPN_ISO_OR BIT(26) +#define USBF_EPN_OUT_NOTKN BIT(27) +#define USBF_EPN_ISO_OPID BIT(28) +#define USBF_EPN_ISO_PIDERR BIT(29) + +#define USBF_REG_EPN_INT_ENA 0x008 +#define USBF_EPN_IN_EN BIT(3) +#define USBF_EPN_IN_STALL_EN BIT(4) +#define USBF_EPN_IN_NAK_ERR_EN BIT(5) +#define USBF_EPN_IN_END_EN BIT(7) +#define USBF_EPN_OUT_NULL_EN BIT(18) +#define USBF_EPN_OUT_EN BIT(19) +#define USBF_EPN_OUT_STALL_EN BIT(20) +#define USBF_EPN_OUT_NAK_ERR_EN BIT(21) +#define USBF_EPN_OUT_OR_EN BIT(22) +#define USBF_EPN_OUT_END_EN BIT(23) + +#define USBF_REG_EPN_DMA_CTRL 0x00C +#define USBF_EPN_DMAMODE0 BIT(0) +#define USBF_EPN_DMA_EN BIT(4) +#define USBF_EPN_STOP_SET BIT(8) +#define USBF_EPN_BURST_SET BIT(9) +#define USBF_EPN_DEND_SET BIT(10) +#define USBF_EPN_STOP_MODE BIT(11) + +#define USBF_REG_EPN_PCKT_ADRS 0x010 +#define USBF_EPN_MPKT(_l) ((_l) << 0) +#define USBF_EPN_BASEAD(_a) ((_a) << 16) + +#define USBF_REG_EPN_LEN_DCNT 0x014 +#define USBF_EPN_GET_LDATA(_r) ((_r) & 0x7FF) +#define USBF_EPN_SET_DMACNT(_c) ((_c) << 16) +#define USBF_EPN_GET_DMACNT(_r) (((_r) >> 16) & 0x1ff) + +#define USBF_REG_EPN_READ 0x018 +#define USBF_REG_EPN_WRITE 0x01C + +/* AHB-EPC Bridge registers */ +#define USBF_REG_AHBSCTR 0x1000 +#define USBF_REG_AHBMCTR 0x1004 +#define USBF_SYS_WBURST_TYPE BIT(2) +#define USBF_SYS_ARBITER_CTR BIT(31) + +#define USBF_REG_AHBBINT 0x1008 +#define USBF_SYS_ERR_MASTER (0x0F << 0) +#define USBF_SYS_SBUS_ERRINT0 BIT(4) +#define USBF_SYS_SBUS_ERRINT1 BIT(5) +#define USBF_SYS_MBUS_ERRINT BIT(6) +#define USBF_SYS_VBUS_INT BIT(13) +#define USBF_SYS_DMA_ENDINT_EPN(_n) (BIT(16) << (_n)) /* _n=1..15 */ + +#define USBF_REG_AHBBINTEN 0x100C +#define USBF_SYS_SBUS_ERRINT0EN BIT(4) +#define USBF_SYS_SBUS_ERRINT1EN BIT(5) +#define USBF_SYS_MBUS_ERRINTEN BIT(6) +#define USBF_SYS_VBUS_INTEN BIT(13) +#define USBF_SYS_DMA_ENDINTEN_EPN(_n) (BIT(16) << (_n)) /* _n=1..15 */ + +#define USBF_REG_EPCTR 0x1010 +#define USBF_SYS_EPC_RST BIT(0) +#define USBF_SYS_PLL_RST BIT(2) +#define USBF_SYS_PLL_LOCK BIT(4) +#define USBF_SYS_PLL_RESUME BIT(5) +#define USBF_SYS_VBUS_LEVEL BIT(8) +#define USBF_SYS_DIRPD BIT(12) + +#define USBF_REG_USBSSVER 0x1020 +#define USBF_REG_USBSSCONF 0x1024 +#define USBF_SYS_DMA_AVAILABLE(_n) (BIT(0) << (_n)) /* _n=0..15 */ +#define USBF_SYS_EP_AVAILABLE(_n) (BIT(16) << (_n)) /* _n=0..15 */ + +#define USBF_BASE_DMA_EPN(_n) (0x1110 + (_n) * 0x010) +/* EPn DMA registers offsets from Base USBF_BASE_DMA_EPN(n-1). n=1..15*/ +#define USBF_REG_DMA_EPN_DCR1 0x00 +#define USBF_SYS_EPN_REQEN BIT(0) +#define USBF_SYS_EPN_DIR0 BIT(1) +#define USBF_SYS_EPN_SET_DMACNT(_c) ((_c) << 16) +#define USBF_SYS_EPN_GET_DMACNT(_r) (((_r) >> 16) & 0x0FF) + +#define USBF_REG_DMA_EPN_DCR2 0x04 +#define USBF_SYS_EPN_MPKT(_s) ((_s) << 0) +#define USBF_SYS_EPN_LMPKT(_l) ((_l) << 16) + +#define USBF_REG_DMA_EPN_TADR 0x08 + +/* USB request */ +struct usbf_req { + struct usb_request req; + struct list_head queue; + unsigned int is_zero_sent : 1; + unsigned int is_mapped : 1; + enum { + USBF_XFER_START, + USBF_XFER_WAIT_DMA, + USBF_XFER_SEND_NULL, + USBF_XFER_WAIT_END, + USBF_XFER_WAIT_DMA_SHORT, + USBF_XFER_WAIT_BRIDGE, + } xfer_step; + size_t dma_size; +}; + +/* USB Endpoint */ +struct usbf_ep { + struct usb_ep ep; + char name[32]; + struct list_head queue; + unsigned int is_processing : 1; + unsigned int is_in : 1; + struct usbf_udc *udc; + void __iomem *regs; + void __iomem *dma_regs; + unsigned int id : 8; + unsigned int disabled : 1; + unsigned int is_wedged : 1; + unsigned int delayed_status : 1; + u32 status; + void (*bridge_on_dma_end)(struct usbf_ep *ep); +}; + +enum usbf_ep0state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_OUT_STATUS_START_PHASE, + EP0_OUT_STATUS_PHASE, + EP0_OUT_STATUS_END_PHASE, + EP0_IN_STATUS_START_PHASE, + EP0_IN_STATUS_PHASE, + EP0_IN_STATUS_END_PHASE, +}; + +struct usbf_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + void __iomem *regs; + spinlock_t lock; + bool is_remote_wakeup; + bool is_usb_suspended; + struct usbf_ep ep[USBF_NUM_ENDPOINTS]; + /* for EP0 control messages */ + enum usbf_ep0state ep0state; + struct usbf_req setup_reply; + u8 ep0_buf[USBF_EP0_MAX_PCKT_SIZE]; +}; + +struct usbf_ep_info { + const char *name; + struct usb_ep_caps caps; + u16 base_addr; + unsigned int is_double : 1; + u16 maxpacket_limit; +}; + +#define USBF_SINGLE_BUFFER 0 +#define USBF_DOUBLE_BUFFER 1 +#define USBF_EP_INFO(_name, _caps, _base_addr, _is_double, _maxpacket_limit) \ + { \ + .name = _name, \ + .caps = _caps, \ + .base_addr = _base_addr, \ + .is_double = _is_double, \ + .maxpacket_limit = _maxpacket_limit, \ + } + +/* This table is computed from the recommended values provided in the SOC + * datasheet. The buffer type (single/double) and the endpoint type cannot + * be changed. The mapping in internal RAM (base_addr and number of words) + * for each endpoints depends on the max packet size and the buffer type. + */ +static const struct usbf_ep_info usbf_ep_info[USBF_NUM_ENDPOINTS] = { + /* ep0: buf @0x0000 64 bytes, fixed 32 words */ + [0] = USBF_EP_INFO("ep0-ctrl", + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, + USB_EP_CAPS_DIR_ALL), + 0x0000, USBF_SINGLE_BUFFER, USBF_EP0_MAX_PCKT_SIZE), + /* ep1: buf @0x0020, 2 buffers 512 bytes -> (512 * 2 / 4) words */ + [1] = USBF_EP_INFO("ep1-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0020, USBF_DOUBLE_BUFFER, 512), + /* ep2: buf @0x0120, 2 buffers 512 bytes -> (512 * 2 / 4) words */ + [2] = USBF_EP_INFO("ep2-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0120, USBF_DOUBLE_BUFFER, 512), + /* ep3: buf @0x0220, 1 buffer 512 bytes -> (512 * 2 / 4) words */ + [3] = USBF_EP_INFO("ep3-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0220, USBF_SINGLE_BUFFER, 512), + /* ep4: buf @0x02A0, 1 buffer 512 bytes -> (512 * 1 / 4) words */ + [4] = USBF_EP_INFO("ep4-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x02A0, USBF_SINGLE_BUFFER, 512), + /* ep5: buf @0x0320, 1 buffer 512 bytes -> (512 * 2 / 4) words */ + [5] = USBF_EP_INFO("ep5-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, + USB_EP_CAPS_DIR_ALL), + 0x0320, USBF_SINGLE_BUFFER, 512), + /* ep6: buf @0x03A0, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [6] = USBF_EP_INFO("ep6-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x03A0, USBF_SINGLE_BUFFER, 1024), + /* ep7: buf @0x04A0, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [7] = USBF_EP_INFO("ep7-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x04A0, USBF_SINGLE_BUFFER, 1024), + /* ep8: buf @0x0520, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [8] = USBF_EP_INFO("ep8-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x0520, USBF_SINGLE_BUFFER, 1024), + /* ep9: buf @0x0620, 1 buffer 1024 bytes -> (1024 * 1 / 4) words */ + [9] = USBF_EP_INFO("ep9-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_INT, + USB_EP_CAPS_DIR_ALL), + 0x0620, USBF_SINGLE_BUFFER, 1024), + /* ep10: buf @0x0720, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [10] = USBF_EP_INFO("ep10-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0720, USBF_DOUBLE_BUFFER, 1024), + /* ep11: buf @0x0920, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [11] = USBF_EP_INFO("ep11-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0920, USBF_DOUBLE_BUFFER, 1024), + /* ep12: buf @0x0B20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [12] = USBF_EP_INFO("ep12-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0B20, USBF_DOUBLE_BUFFER, 1024), + /* ep13: buf @0x0D20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [13] = USBF_EP_INFO("ep13-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0D20, USBF_DOUBLE_BUFFER, 1024), + /* ep14: buf @0x0F20, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [14] = USBF_EP_INFO("ep14-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x0F20, USBF_DOUBLE_BUFFER, 1024), + /* ep15: buf @0x1120, 2 buffers 1024 bytes -> (1024 * 2 / 4) words */ + [15] = USBF_EP_INFO("ep15-iso", + USB_EP_CAPS(USB_EP_CAPS_TYPE_ISO, + USB_EP_CAPS_DIR_ALL), + 0x1120, USBF_DOUBLE_BUFFER, 1024), +}; + +static inline u32 usbf_reg_readl(struct usbf_udc *udc, uint offset) +{ + return readl(udc->regs + offset); +} + +static inline void usbf_reg_writel(struct usbf_udc *udc, uint offset, u32 val) +{ + writel(val, udc->regs + offset); +} + +static inline void usbf_reg_bitset(struct usbf_udc *udc, uint offset, u32 set) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp |= set; + usbf_reg_writel(udc, offset, tmp); +} + +static inline void usbf_reg_bitclr(struct usbf_udc *udc, uint offset, u32 clr) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp &= ~clr; + usbf_reg_writel(udc, offset, tmp); +} + +static inline void usbf_reg_clrset(struct usbf_udc *udc, uint offset, + u32 clr, u32 set) +{ + u32 tmp; + + tmp = usbf_reg_readl(udc, offset); + tmp &= ~clr; + tmp |= set; + usbf_reg_writel(udc, offset, tmp); +} + +static inline u32 usbf_ep_reg_readl(struct usbf_ep *ep, uint offset) +{ + return readl(ep->regs + offset); +} + +static inline void usbf_ep_reg_read_rep(struct usbf_ep *ep, uint offset, + void *dst, uint count) +{ + readsl(ep->regs + offset, dst, count); +} + +static inline void usbf_ep_reg_writel(struct usbf_ep *ep, uint offset, u32 val) +{ + writel(val, ep->regs + offset); +} + +static inline void usbf_ep_reg_write_rep(struct usbf_ep *ep, uint offset, + const void *src, uint count) +{ + writesl(ep->regs + offset, src, count); +} + +static inline void usbf_ep_reg_bitset(struct usbf_ep *ep, uint offset, u32 set) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp |= set; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_reg_bitclr(struct usbf_ep *ep, uint offset, u32 clr) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp &= ~clr; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_reg_clrset(struct usbf_ep *ep, uint offset, + u32 clr, u32 set) +{ + u32 tmp; + + tmp = usbf_ep_reg_readl(ep, offset); + tmp &= ~clr; + tmp |= set; + usbf_ep_reg_writel(ep, offset, tmp); +} + +static inline u32 usbf_ep_dma_reg_readl(struct usbf_ep *ep, uint offset) +{ + return readl(ep->dma_regs + offset); +} + +static inline void usbf_ep_dma_reg_writel(struct usbf_ep *ep, uint offset, + u32 val) +{ + writel(val, ep->dma_regs + offset); +} + +static inline void usbf_ep_dma_reg_bitset(struct usbf_ep *ep, uint offset, + u32 set) +{ + u32 tmp; + + tmp = usbf_ep_dma_reg_readl(ep, offset); + tmp |= set; + usbf_ep_dma_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_dma_reg_bitclr(struct usbf_ep *ep, uint offset, + u32 clr) +{ + u32 tmp; + + tmp = usbf_ep_dma_reg_readl(ep, offset); + tmp &= ~clr; + usbf_ep_dma_reg_writel(ep, offset, tmp); +} + +static inline void usbf_ep_dma_reg_clrset(struct usbf_ep *ep, uint offset, + u32 clr, u32 set) +{ + u32 tmp; + + tmp = usbf_ep_dma_reg_readl(ep, offset); + tmp &= ~clr; + tmp |= set; + usbf_ep_dma_reg_writel(ep, offset, tmp); +} + +static void usbf_ep0_send_null(struct usbf_ep *ep0, bool is_data1) +{ + u32 set; + + set = USBF_EP0_DEND; + if (is_data1) + set |= USBF_EP0_PIDCLR; + + usbf_ep_reg_bitset(ep0, USBF_REG_EP0_CONTROL, set); +} + +static int usbf_ep0_pio_in(struct usbf_ep *ep0, struct usbf_req *req) +{ + unsigned int left; + unsigned int nb; + const void *buf; + u32 ctrl; + u32 last; + + left = req->req.length - req->req.actual; + + if (left == 0) { + if (!req->is_zero_sent) { + if (req->req.length == 0) { + dev_dbg(ep0->udc->dev, "ep0 send null\n"); + usbf_ep0_send_null(ep0, false); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + if ((req->req.actual % ep0->ep.maxpacket) == 0) { + if (req->req.zero) { + dev_dbg(ep0->udc->dev, "ep0 send null\n"); + usbf_ep0_send_null(ep0, false); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + } + } + return 0; + } + + if (left > ep0->ep.maxpacket) + left = ep0->ep.maxpacket; + + buf = req->req.buf; + buf += req->req.actual; + + nb = left / sizeof(u32); + if (nb) { + usbf_ep_reg_write_rep(ep0, USBF_REG_EP0_WRITE, buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + left -= (nb * sizeof(u32)); + } + ctrl = usbf_ep_reg_readl(ep0, USBF_REG_EP0_CONTROL); + ctrl &= ~USBF_EP0_DW_MASK; + if (left) { + memcpy(&last, buf, left); + usbf_ep_reg_writel(ep0, USBF_REG_EP0_WRITE, last); + ctrl |= USBF_EP0_DW(left); + req->req.actual += left; + } + usbf_ep_reg_writel(ep0, USBF_REG_EP0_CONTROL, ctrl | USBF_EP0_DEND); + + dev_dbg(ep0->udc->dev, "ep0 send %u/%u\n", + req->req.actual, req->req.length); + + return -EINPROGRESS; +} + +static int usbf_ep0_pio_out(struct usbf_ep *ep0, struct usbf_req *req) +{ + int req_status = 0; + unsigned int count; + unsigned int recv; + unsigned int left; + unsigned int nb; + void *buf; + u32 last; + + if (ep0->status & USBF_EP0_OUT_INT) { + recv = usbf_ep_reg_readl(ep0, USBF_REG_EP0_LENGTH) & USBF_EP0_LDATA; + count = recv; + + buf = req->req.buf; + buf += req->req.actual; + + left = req->req.length - req->req.actual; + + dev_dbg(ep0->udc->dev, "ep0 recv %u, left %u\n", count, left); + + if (left > ep0->ep.maxpacket) + left = ep0->ep.maxpacket; + + if (count > left) { + req_status = -EOVERFLOW; + count = left; + } + + if (count) { + nb = count / sizeof(u32); + if (nb) { + usbf_ep_reg_read_rep(ep0, USBF_REG_EP0_READ, + buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + count -= (nb * sizeof(u32)); + } + if (count) { + last = usbf_ep_reg_readl(ep0, USBF_REG_EP0_READ); + memcpy(buf, &last, count); + req->req.actual += count; + } + } + dev_dbg(ep0->udc->dev, "ep0 recv %u/%u\n", + req->req.actual, req->req.length); + + if (req_status) { + dev_dbg(ep0->udc->dev, "ep0 req.status=%d\n", req_status); + req->req.status = req_status; + return 0; + } + + if (recv < ep0->ep.maxpacket) { + dev_dbg(ep0->udc->dev, "ep0 short packet\n"); + /* This is a short packet -> It is the end */ + req->req.status = 0; + return 0; + } + + /* The Data stage of a control transfer from an endpoint to the + * host is complete when the endpoint does one of the following: + * - Has transferred exactly the expected amount of data + * - Transfers a packet with a payload size less than + * wMaxPacketSize or transfers a zero-length packet + */ + if (req->req.actual == req->req.length) { + req->req.status = 0; + return 0; + } + } + + if (ep0->status & USBF_EP0_OUT_NULL_INT) { + /* NULL packet received */ + dev_dbg(ep0->udc->dev, "ep0 null packet\n"); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_ep0_fifo_flush(struct usbf_ep *ep0) +{ + u32 sts; + int ret; + + usbf_ep_reg_bitset(ep0, USBF_REG_EP0_CONTROL, USBF_EP0_BCLR); + + ret = readl_poll_timeout_atomic(ep0->regs + USBF_REG_EP0_STATUS, sts, + (sts & (USBF_EP0_IN_DATA | USBF_EP0_IN_EMPTY)) == USBF_EP0_IN_EMPTY, + 0, 10000); + if (ret) + dev_err(ep0->udc->dev, "ep0 flush fifo timed out\n"); + +} + +static void usbf_epn_send_null(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, USBF_EPN_DEND); +} + +static void usbf_epn_send_residue(struct usbf_ep *epn, const void *buf, + unsigned int size) +{ + u32 tmp; + + memcpy(&tmp, buf, size); + usbf_ep_reg_writel(epn, USBF_REG_EPN_WRITE, tmp); + + usbf_ep_reg_clrset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DW_MASK, + USBF_EPN_DW(size) | USBF_EPN_DEND); +} + +static int usbf_epn_pio_in(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int left; + unsigned int nb; + const void *buf; + + left = req->req.length - req->req.actual; + + if (left == 0) { + if (!req->is_zero_sent) { + if (req->req.length == 0) { + dev_dbg(epn->udc->dev, "ep%u send_null\n", epn->id); + usbf_epn_send_null(epn); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + if ((req->req.actual % epn->ep.maxpacket) == 0) { + if (req->req.zero) { + dev_dbg(epn->udc->dev, "ep%u send_null\n", + epn->id); + usbf_epn_send_null(epn); + req->is_zero_sent = 1; + return -EINPROGRESS; + } + } + } + return 0; + } + + if (left > epn->ep.maxpacket) + left = epn->ep.maxpacket; + + buf = req->req.buf; + buf += req->req.actual; + + nb = left / sizeof(u32); + if (nb) { + usbf_ep_reg_write_rep(epn, USBF_REG_EPN_WRITE, buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + left -= (nb * sizeof(u32)); + } + + if (left) { + usbf_epn_send_residue(epn, buf, left); + req->req.actual += left; + } else { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DW_MASK, + USBF_EPN_DEND); + } + + dev_dbg(epn->udc->dev, "ep%u send %u/%u\n", epn->id, req->req.actual, + req->req.length); + + return -EINPROGRESS; +} + +static void usbf_epn_enable_in_end_int(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_IN_END_EN); +} + +static int usbf_epn_dma_in(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int left; + u32 npkt; + u32 lastpkt; + int ret; + + if (!IS_ALIGNED((uintptr_t)req->req.buf, 4)) { + dev_dbg(epn->udc->dev, "ep%u buf unaligned -> fallback pio\n", + epn->id); + return usbf_epn_pio_in(epn, req); + } + + left = req->req.length - req->req.actual; + + switch (req->xfer_step) { + default: + case USBF_XFER_START: + if (left == 0) { + dev_dbg(epn->udc->dev, "ep%u send null\n", epn->id); + usbf_epn_send_null(epn); + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + if (left < 4) { + dev_dbg(epn->udc->dev, "ep%u send residue %u\n", epn->id, + left); + usbf_epn_send_residue(epn, + req->req.buf + req->req.actual, left); + req->req.actual += left; + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 1); + if (ret < 0) { + dev_err(epn->udc->dev, "usb_gadget_map_request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + npkt = DIV_ROUND_UP(left, epn->ep.maxpacket); + lastpkt = (left % epn->ep.maxpacket); + if (lastpkt == 0) + lastpkt = epn->ep.maxpacket; + lastpkt &= ~0x3; /* DMA is done on 32bit units */ + + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR2, + USBF_SYS_EPN_MPKT(epn->ep.maxpacket) | USBF_SYS_EPN_LMPKT(lastpkt)); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_TADR, + req->req.dma); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(npkt)); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, USBF_EPN_SET_DMACNT(npkt)); + + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + /* The end of DMA transfer at the USBF level needs to be handle + * after the detection of the end of DMA transfer at the brige + * level. + * To force this sequence, EPN_IN_END_EN will be set by the + * detection of the end of transfer at bridge level (ie. bridge + * interrupt). + */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_EN | USBF_EPN_IN_END_EN); + epn->bridge_on_dma_end = usbf_epn_enable_in_end_int; + + /* Clear any pending IN_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(u32)USBF_EPN_IN_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_BURST_SET | USBF_EPN_DMAMODE0); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); + + req->dma_size = (npkt - 1) * epn->ep.maxpacket + lastpkt; + + dev_dbg(epn->udc->dev, "ep%u dma xfer %zu\n", epn->id, + req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA; + break; + + case USBF_XFER_WAIT_DMA: + if (!(epn->status & USBF_EPN_IN_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma done\n", epn->id); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 1); + req->is_mapped = 0; + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_END_EN, + USBF_EPN_IN_EN); + + req->req.actual += req->dma_size; + + left = req->req.length - req->req.actual; + if (left) { + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(u32)USBF_EPN_IN_INT); + + dev_dbg(epn->udc->dev, "ep%u send residue %u\n", epn->id, + left); + usbf_epn_send_residue(epn, + req->req.buf + req->req.actual, left); + req->req.actual += left; + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + if (req->req.actual % epn->ep.maxpacket) { + /* last packet was a short packet. Tell the hardware to + * send it right now. + */ + dev_dbg(epn->udc->dev, "ep%u send short\n", epn->id); + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_IN_INT); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_DEND); + + req->xfer_step = USBF_XFER_WAIT_END; + break; + } + + /* Last packet size was a maxpacket size + * Send null packet if needed + */ + if (req->req.zero) { + req->xfer_step = USBF_XFER_SEND_NULL; + break; + } + + /* No more action to do. Wait for the end of the USB transfer */ + req->xfer_step = USBF_XFER_WAIT_END; + break; + + case USBF_XFER_SEND_NULL: + dev_dbg(epn->udc->dev, "ep%u send null\n", epn->id); + usbf_epn_send_null(epn); + req->xfer_step = USBF_XFER_WAIT_END; + break; + + case USBF_XFER_WAIT_END: + if (!(epn->status & USBF_EPN_IN_INT)) { + dev_dbg(epn->udc->dev, "ep%u end not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u send done %u/%u\n", epn->id, + req->req.actual, req->req.length); + req->xfer_step = USBF_XFER_START; + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_epn_recv_residue(struct usbf_ep *epn, void *buf, + unsigned int size) +{ + u32 last; + + last = usbf_ep_reg_readl(epn, USBF_REG_EPN_READ); + memcpy(buf, &last, size); +} + +static int usbf_epn_pio_out(struct usbf_ep *epn, struct usbf_req *req) +{ + int req_status = 0; + unsigned int count; + unsigned int recv; + unsigned int left; + unsigned int nb; + void *buf; + + if (epn->status & USBF_EPN_OUT_INT) { + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + count = recv; + + buf = req->req.buf; + buf += req->req.actual; + + left = req->req.length - req->req.actual; + + dev_dbg(epn->udc->dev, "ep%u recv %u, left %u, mpkt %u\n", epn->id, + recv, left, epn->ep.maxpacket); + + if (left > epn->ep.maxpacket) + left = epn->ep.maxpacket; + + if (count > left) { + req_status = -EOVERFLOW; + count = left; + } + + if (count) { + nb = count / sizeof(u32); + if (nb) { + usbf_ep_reg_read_rep(epn, USBF_REG_EPN_READ, + buf, nb); + buf += (nb * sizeof(u32)); + req->req.actual += (nb * sizeof(u32)); + count -= (nb * sizeof(u32)); + } + if (count) { + usbf_epn_recv_residue(epn, buf, count); + req->req.actual += count; + } + } + dev_dbg(epn->udc->dev, "ep%u recv %u/%u\n", epn->id, + req->req.actual, req->req.length); + + if (req_status) { + dev_dbg(epn->udc->dev, "ep%u req.status=%d\n", epn->id, + req_status); + req->req.status = req_status; + return 0; + } + + if (recv < epn->ep.maxpacket) { + dev_dbg(epn->udc->dev, "ep%u short packet\n", epn->id); + /* This is a short packet -> It is the end */ + req->req.status = 0; + return 0; + } + + /* Request full -> complete */ + if (req->req.actual == req->req.length) { + req->req.status = 0; + return 0; + } + } + + if (epn->status & USBF_EPN_OUT_NULL_INT) { + /* NULL packet received */ + dev_dbg(epn->udc->dev, "ep%u null packet\n", epn->id); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + return -EINPROGRESS; +} + +static void usbf_epn_enable_out_end_int(struct usbf_ep *epn) +{ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_OUT_END_EN); +} + +static void usbf_epn_process_queue(struct usbf_ep *epn); + +static void usbf_epn_dma_out_send_dma(struct usbf_ep *epn, dma_addr_t addr, u32 npkt, bool is_short) +{ + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR2, USBF_SYS_EPN_MPKT(epn->ep.maxpacket)); + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_TADR, addr); + + if (is_short) { + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(1) | USBF_SYS_EPN_DIR0); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, + USBF_EPN_SET_DMACNT(0)); + + /* The end of DMA transfer at the USBF level needs to be handled + * after the detection of the end of DMA transfer at the brige + * level. + * To force this sequence, enabling the OUT_END interrupt will + * be donee by the detection of the end of transfer at bridge + * level (ie. bridge interrupt). + */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN | USBF_EPN_OUT_END_EN); + epn->bridge_on_dma_end = usbf_epn_enable_out_end_int; + + /* Clear any pending OUT_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_STOP_MODE | USBF_EPN_STOP_SET | USBF_EPN_DMAMODE0); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); + return; + } + + usbf_ep_dma_reg_writel(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_SET_DMACNT(npkt) | USBF_SYS_EPN_DIR0); + usbf_ep_dma_reg_bitset(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_LEN_DCNT, + USBF_EPN_SET_DMACNT(npkt)); + + /* Here, the bridge may or may not generate an interrupt to signal the + * end of DMA transfer. + * Keep only OUT_END interrupt and let handle the bridge later during + * the OUT_END processing. + */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN, + USBF_EPN_OUT_END_EN); + + /* Disable bridge interrupt. It will be renabled later */ + usbf_reg_bitclr(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + /* Clear any pending DMA_END interrupt at bridge level */ + usbf_reg_writel(epn->udc, USBF_REG_AHBBINT, + USBF_SYS_DMA_ENDINT_EPN(epn->id)); + + /* Clear any pending OUT_END interrupt */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_END_INT); + + usbf_ep_reg_writel(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_STOP_MODE | USBF_EPN_STOP_SET | USBF_EPN_DMAMODE0 | USBF_EPN_BURST_SET); + usbf_ep_reg_bitset(epn, USBF_REG_EPN_DMA_CTRL, + USBF_EPN_DMA_EN); +} + +static size_t usbf_epn_dma_out_complete_dma(struct usbf_ep *epn, bool is_short) +{ + u32 dmacnt; + u32 tmp; + int ret; + + /* Restore interrupt mask */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + + if (is_short) { + /* Nothing more to do when the DMA was for a short packet */ + return 0; + } + + /* Enable the bridge interrupt */ + usbf_reg_bitset(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + tmp = usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT); + dmacnt = USBF_EPN_GET_DMACNT(tmp); + + if (dmacnt) { + /* Some packet were not received (halted by a short or a null + * packet. + * The bridge never raises an interrupt in this case. + * Wait for the end of transfer at bridge level + */ + ret = readl_poll_timeout_atomic( + epn->dma_regs + USBF_REG_DMA_EPN_DCR1, + tmp, (USBF_SYS_EPN_GET_DMACNT(tmp) == dmacnt), + 0, 10000); + if (ret) { + dev_err(epn->udc->dev, "ep%u wait bridge timed out\n", + epn->id); + } + + usbf_ep_dma_reg_bitclr(epn, USBF_REG_DMA_EPN_DCR1, + USBF_SYS_EPN_REQEN); + + /* The dmacnt value tells how many packet were not transferred + * from the maximum number of packet we set for the DMA transfer. + * Compute the left DMA size based on this value. + */ + return dmacnt * epn->ep.maxpacket; + } + + return 0; +} + +static int usbf_epn_dma_out(struct usbf_ep *epn, struct usbf_req *req) +{ + unsigned int dma_left; + unsigned int count; + unsigned int recv; + unsigned int left; + u32 npkt; + int ret; + + if (!IS_ALIGNED((uintptr_t)req->req.buf, 4)) { + dev_dbg(epn->udc->dev, "ep%u buf unaligned -> fallback pio\n", + epn->id); + return usbf_epn_pio_out(epn, req); + } + + switch (req->xfer_step) { + default: + case USBF_XFER_START: + if (epn->status & USBF_EPN_OUT_NULL_INT) { + dev_dbg(epn->udc->dev, "ep%u null packet\n", epn->id); + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + return 0; + } + + if (!(epn->status & USBF_EPN_OUT_INT)) { + dev_dbg(epn->udc->dev, "ep%u OUT_INT not set -> spurious\n", + epn->id); + break; + } + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + if (!recv) { + dev_dbg(epn->udc->dev, "ep%u recv = 0 -> spurious\n", + epn->id); + break; + } + + left = req->req.length - req->req.actual; + + dev_dbg(epn->udc->dev, "ep%u recv %u, left %u, mpkt %u\n", epn->id, + recv, left, epn->ep.maxpacket); + + if (recv > left) { + dev_err(epn->udc->dev, "ep%u overflow (%u/%u)\n", + epn->id, recv, left); + req->req.status = -EOVERFLOW; + return -EOVERFLOW; + } + + if (recv < epn->ep.maxpacket) { + /* Short packet received */ + dev_dbg(epn->udc->dev, "ep%u short packet\n", epn->id); + if (recv <= 3) { + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, recv); + req->req.actual += recv; + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", + epn->id, req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 0); + if (ret < 0) { + dev_err(epn->udc->dev, "map request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + 1, true); + req->dma_size = recv & ~0x3; + + dev_dbg(epn->udc->dev, "ep%u dma short xfer %zu\n", epn->id, + req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA_SHORT; + break; + } + + ret = usb_gadget_map_request(&epn->udc->gadget, &req->req, 0); + if (ret < 0) { + dev_err(epn->udc->dev, "map request failed (%d)\n", + ret); + return ret; + } + req->is_mapped = 1; + + /* Use the maximum DMA size according to the request buffer. + * We will adjust the received size later at the end of the DMA + * transfer with the left size computed from + * usbf_epn_dma_out_complete_dma(). + */ + npkt = left / epn->ep.maxpacket; + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + npkt, false); + req->dma_size = npkt * epn->ep.maxpacket; + + dev_dbg(epn->udc->dev, "ep%u dma xfer %zu (%u)\n", epn->id, + req->dma_size, npkt); + + req->xfer_step = USBF_XFER_WAIT_DMA; + break; + + case USBF_XFER_WAIT_DMA_SHORT: + if (!(epn->status & USBF_EPN_OUT_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma short not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma short done\n", epn->id); + + usbf_epn_dma_out_complete_dma(epn, true); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + req->req.actual += req->dma_size; + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + + count = recv & 0x3; + if (count) { + dev_dbg(epn->udc->dev, "ep%u recv residue %u\n", epn->id, + count); + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, count); + req->req.actual += count; + } + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + + case USBF_XFER_WAIT_DMA: + if (!(epn->status & USBF_EPN_OUT_END_INT)) { + dev_dbg(epn->udc->dev, "ep%u dma not done\n", epn->id); + break; + } + dev_dbg(epn->udc->dev, "ep%u dma done\n", epn->id); + + dma_left = usbf_epn_dma_out_complete_dma(epn, false); + if (dma_left) { + /* Adjust the final DMA size with */ + count = req->dma_size - dma_left; + + dev_dbg(epn->udc->dev, "ep%u dma xfer done %u\n", epn->id, + count); + + req->req.actual += count; + + if (epn->status & USBF_EPN_OUT_NULL_INT) { + /* DMA was stopped by a null packet reception */ + dev_dbg(epn->udc->dev, "ep%u dma stopped by null pckt\n", + epn->id); + usb_gadget_unmap_request(&epn->udc->gadget, + &req->req, 0); + req->is_mapped = 0; + + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + ~(u32)USBF_EPN_OUT_NULL_INT); + + if (req->req.actual != req->req.length) { + req->req.status = req->req.short_not_ok ? + -EREMOTEIO : 0; + } else { + req->req.status = 0; + } + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", + epn->id, req->req.actual, req->req.length); + req->xfer_step = USBF_XFER_START; + return 0; + } + + recv = USBF_EPN_GET_LDATA( + usbf_ep_reg_readl(epn, USBF_REG_EPN_LEN_DCNT)); + left = req->req.length - req->req.actual; + if (recv > left) { + dev_err(epn->udc->dev, + "ep%u overflow (%u/%u)\n", epn->id, + recv, left); + req->req.status = -EOVERFLOW; + usb_gadget_unmap_request(&epn->udc->gadget, + &req->req, 0); + req->is_mapped = 0; + + req->xfer_step = USBF_XFER_START; + return -EOVERFLOW; + } + + if (recv > 3) { + usbf_epn_dma_out_send_dma(epn, + req->req.dma + req->req.actual, + 1, true); + req->dma_size = recv & ~0x3; + + dev_dbg(epn->udc->dev, "ep%u dma short xfer %zu\n", + epn->id, req->dma_size); + + req->xfer_step = USBF_XFER_WAIT_DMA_SHORT; + break; + } + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + count = recv & 0x3; + if (count) { + dev_dbg(epn->udc->dev, "ep%u recv residue %u\n", + epn->id, count); + usbf_epn_recv_residue(epn, + req->req.buf + req->req.actual, count); + req->req.actual += count; + } + + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + + req->xfer_step = USBF_XFER_START; + return 0; + } + + /* Process queue at bridge interrupt only */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN | USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + epn->status = 0; + epn->bridge_on_dma_end = usbf_epn_process_queue; + + req->xfer_step = USBF_XFER_WAIT_BRIDGE; + break; + + case USBF_XFER_WAIT_BRIDGE: + dev_dbg(epn->udc->dev, "ep%u bridge transfers done\n", epn->id); + + /* Restore interrupt mask */ + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, 0); + req->is_mapped = 0; + + req->req.actual += req->dma_size; + + req->xfer_step = USBF_XFER_START; + left = req->req.length - req->req.actual; + if (!left) { + /* No more data can be added to the buffer */ + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u\n", epn->id, + req->req.actual, req->req.length); + return 0; + } + dev_dbg(epn->udc->dev, "ep%u recv done %u/%u, wait more data\n", + epn->id, req->req.actual, req->req.length); + break; + } + + return -EINPROGRESS; +} + +static void usbf_epn_dma_stop(struct usbf_ep *epn) +{ + usbf_ep_dma_reg_bitclr(epn, USBF_REG_DMA_EPN_DCR1, USBF_SYS_EPN_REQEN); + + /* In the datasheet: + * If EP[m]_REQEN = 0b is set during DMA transfer, AHB-EPC stops DMA + * after 1 packet transfer completed. + * Therefore, wait sufficient time for ensuring DMA transfer + * completion. The WAIT time depends on the system, especially AHB + * bus activity + * So arbitrary 10ms would be sufficient. + */ + mdelay(10); + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_DMA_CTRL, USBF_EPN_DMA_EN); +} + +static void usbf_epn_dma_abort(struct usbf_ep *epn, struct usbf_req *req) +{ + dev_dbg(epn->udc->dev, "ep%u %s dma abort\n", epn->id, + epn->is_in ? "in" : "out"); + + epn->bridge_on_dma_end = NULL; + + usbf_epn_dma_stop(epn); + + usb_gadget_unmap_request(&epn->udc->gadget, &req->req, + epn->is_in ? 1 : 0); + req->is_mapped = 0; + + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_AUTO); + + if (epn->is_in) { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_IN_END_EN, + USBF_EPN_IN_EN); + } else { + usbf_ep_reg_clrset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_END_EN, + USBF_EPN_OUT_EN | USBF_EPN_OUT_NULL_EN); + } + + /* As dma is stopped, be sure that no DMA interrupt are pending */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, + USBF_EPN_IN_END_INT | USBF_EPN_OUT_END_INT); + + usbf_reg_writel(epn->udc, USBF_REG_AHBBINT, USBF_SYS_DMA_ENDINT_EPN(epn->id)); + + /* Enable DMA interrupt the bridge level */ + usbf_reg_bitset(epn->udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(epn->id)); + + /* Reset transfer step */ + req->xfer_step = USBF_XFER_START; +} + +static void usbf_epn_fifo_flush(struct usbf_ep *epn) +{ + u32 ctrl; + u32 sts; + int ret; + + dev_dbg(epn->udc->dev, "ep%u %s fifo flush\n", epn->id, + epn->is_in ? "in" : "out"); + + ctrl = usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL); + usbf_ep_reg_writel(epn, USBF_REG_EPN_CONTROL, ctrl | USBF_EPN_BCLR); + + if (ctrl & USBF_EPN_DIR0) + return; + + ret = readl_poll_timeout_atomic(epn->regs + USBF_REG_EPN_STATUS, sts, + (sts & (USBF_EPN_IN_DATA | USBF_EPN_IN_EMPTY)) == USBF_EPN_IN_EMPTY, + 0, 10000); + if (ret) + dev_err(epn->udc->dev, "ep%u flush fifo timed out\n", epn->id); +} + +static void usbf_ep_req_done(struct usbf_ep *ep, struct usbf_req *req, + int status) +{ + list_del_init(&req->queue); + + if (status) { + req->req.status = status; + } else { + if (req->req.status == -EINPROGRESS) + req->req.status = status; + } + + dev_dbg(ep->udc->dev, "ep%u %s req done length %u/%u, status=%d\n", ep->id, + ep->is_in ? "in" : "out", + req->req.actual, req->req.length, req->req.status); + + if (req->is_mapped) + usbf_epn_dma_abort(ep, req); + + spin_unlock(&ep->udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&ep->udc->lock); +} + +static void usbf_ep_nuke(struct usbf_ep *ep, int status) +{ + struct usbf_req *req; + + dev_dbg(ep->udc->dev, "ep%u %s nuke status %d\n", ep->id, + ep->is_in ? "in" : "out", + status); + + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct usbf_req, queue); + usbf_ep_req_done(ep, req, status); + } + + if (ep->id == 0) + usbf_ep0_fifo_flush(ep); + else + usbf_epn_fifo_flush(ep); +} + +static bool usbf_ep_is_stalled(struct usbf_ep *ep) +{ + u32 ctrl; + + if (ep->id == 0) { + ctrl = usbf_ep_reg_readl(ep, USBF_REG_EP0_CONTROL); + return (ctrl & USBF_EP0_STL) ? true : false; + } + + ctrl = usbf_ep_reg_readl(ep, USBF_REG_EPN_CONTROL); + if (ep->is_in) + return (ctrl & USBF_EPN_ISTL) ? true : false; + + return (ctrl & USBF_EPN_OSTL) ? true : false; +} + +static int usbf_epn_start_queue(struct usbf_ep *epn) +{ + struct usbf_req *req; + int ret; + + if (usbf_ep_is_stalled(epn)) + return 0; + + req = list_first_entry_or_null(&epn->queue, struct usbf_req, queue); + + if (epn->is_in) { + if (req && !epn->is_processing) { + ret = epn->dma_regs ? + usbf_epn_dma_in(epn, req) : + usbf_epn_pio_in(epn, req); + if (ret != -EINPROGRESS) { + dev_err(epn->udc->dev, + "queued next request not in progress\n"); + /* The request cannot be completed (ie + * ret == 0) on the first call. + * stall and nuke the endpoint + */ + return ret ? ret : -EIO; + } + } + } else { + if (req) { + /* Clear ONAK to accept OUT tokens */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + + /* Enable interrupts */ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } else { + /* Disable incoming data and interrupt. + * They will be enable on next usb_eb_queue call + */ + usbf_ep_reg_bitset(epn, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } + } + return 0; +} + +static int usbf_ep_process_queue(struct usbf_ep *ep) +{ + int (*usbf_ep_xfer)(struct usbf_ep *ep, struct usbf_req *req); + struct usbf_req *req; + int is_processing; + int ret; + + if (ep->is_in) { + usbf_ep_xfer = usbf_ep0_pio_in; + if (ep->id) { + usbf_ep_xfer = ep->dma_regs ? + usbf_epn_dma_in : usbf_epn_pio_in; + } + } else { + usbf_ep_xfer = usbf_ep0_pio_out; + if (ep->id) { + usbf_ep_xfer = ep->dma_regs ? + usbf_epn_dma_out : usbf_epn_pio_out; + } + } + + req = list_first_entry_or_null(&ep->queue, struct usbf_req, queue); + if (!req) { + dev_err(ep->udc->dev, + "no request available for ep%u %s process\n", ep->id, + ep->is_in ? "in" : "out"); + return -ENOENT; + } + + do { + /* Were going to read the FIFO for this current request. + * NAK any other incoming data to avoid a race condition if no + * more request are available. + */ + if (!ep->is_in && ep->id != 0) { + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } + + ret = usbf_ep_xfer(ep, req); + if (ret == -EINPROGRESS) { + if (!ep->is_in && ep->id != 0) { + /* The current request needs more data. + * Allow incoming data + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } + return ret; + } + + is_processing = ep->is_processing; + ep->is_processing = 1; + usbf_ep_req_done(ep, req, ret); + ep->is_processing = is_processing; + + if (ret) { + /* An error was detected during the request transfer. + * Any pending DMA transfers were aborted by the + * usbf_ep_req_done() call. + * It's time to flush the fifo + */ + if (ep->id == 0) + usbf_ep0_fifo_flush(ep); + else + usbf_epn_fifo_flush(ep); + } + + req = list_first_entry_or_null(&ep->queue, struct usbf_req, + queue); + + if (ep->is_in) + continue; + + if (ep->id != 0) { + if (req) { + /* An other request is available. + * Allow incoming data + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ONAK); + } else { + /* No request queued. Disable interrupts. + * They will be enabled on usb_ep_queue + */ + usbf_ep_reg_bitclr(ep, USBF_REG_EPN_INT_ENA, + USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + } + } + /* Do not recall usbf_ep_xfer() */ + return req ? -EINPROGRESS : 0; + + } while (req); + + return 0; +} + +static void usbf_ep_stall(struct usbf_ep *ep, bool stall) +{ + struct usbf_req *first; + + dev_dbg(ep->udc->dev, "ep%u %s %s\n", ep->id, + ep->is_in ? "in" : "out", + stall ? "stall" : "unstall"); + + if (ep->id == 0) { + if (stall) + usbf_ep_reg_bitset(ep, USBF_REG_EP0_CONTROL, USBF_EP0_STL); + else + usbf_ep_reg_bitclr(ep, USBF_REG_EP0_CONTROL, USBF_EP0_STL); + return; + } + + if (stall) { + if (ep->is_in) + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ISTL); + else + usbf_ep_reg_bitset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_OSTL | USBF_EPN_OSTL_EN); + } else { + first = list_first_entry_or_null(&ep->queue, struct usbf_req, queue); + if (first && first->is_mapped) { + /* This can appear if the host halts an endpoint using + * SET_FEATURE and then un-halts the endpoint + */ + usbf_epn_dma_abort(ep, first); + } + usbf_epn_fifo_flush(ep); + if (ep->is_in) { + usbf_ep_reg_clrset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_ISTL, + USBF_EPN_IPIDCLR); + } else { + usbf_ep_reg_clrset(ep, USBF_REG_EPN_CONTROL, + USBF_EPN_OSTL, + USBF_EPN_OSTL_EN | USBF_EPN_OPIDCLR); + } + usbf_epn_start_queue(ep); + } +} + +static void usbf_ep0_enable(struct usbf_ep *ep0) +{ + usbf_ep_reg_writel(ep0, USBF_REG_EP0_CONTROL, USBF_EP0_INAK_EN | USBF_EP0_BCLR); + + usbf_ep_reg_writel(ep0, USBF_REG_EP0_INT_ENA, + USBF_EP0_SETUP_EN | USBF_EP0_STG_START_EN | USBF_EP0_STG_END_EN | + USBF_EP0_OUT_EN | USBF_EP0_OUT_NULL_EN | USBF_EP0_IN_EN); + + ep0->udc->ep0state = EP0_IDLE; + ep0->disabled = 0; + + /* enable interrupts for the ep0 */ + usbf_reg_bitset(ep0->udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(0)); +} + +static int usbf_epn_enable(struct usbf_ep *epn) +{ + u32 base_addr; + u32 ctrl; + + base_addr = usbf_ep_info[epn->id].base_addr; + usbf_ep_reg_writel(epn, USBF_REG_EPN_PCKT_ADRS, + USBF_EPN_BASEAD(base_addr) | USBF_EPN_MPKT(epn->ep.maxpacket)); + + /* OUT transfer interrupt are enabled during usb_ep_queue */ + if (epn->is_in) { + /* Will be changed in DMA processing */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_INT_ENA, USBF_EPN_IN_EN); + } + + /* Clear, set endpoint direction, set IN/OUT STL, and enable + * Send NAK for Data out as request are not queued yet + */ + ctrl = USBF_EPN_EN | USBF_EPN_BCLR; + if (epn->is_in) + ctrl |= USBF_EPN_OSTL | USBF_EPN_OSTL_EN; + else + ctrl |= USBF_EPN_DIR0 | USBF_EPN_ISTL | USBF_EPN_OSTL_EN | USBF_EPN_ONAK; + usbf_ep_reg_writel(epn, USBF_REG_EPN_CONTROL, ctrl); + + return 0; +} + +static int usbf_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s mpkts %d\n", ep->id, + usb_endpoint_dir_in(desc) ? "in" : "out", + usb_endpoint_maxp(desc)); + + spin_lock_irqsave(&ep->udc->lock, flags); + ep->is_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = usb_endpoint_maxp(desc); + + ret = usbf_epn_enable(ep); + if (ret) + goto end; + + ep->disabled = 0; + + /* enable interrupts for this endpoint */ + usbf_reg_bitset(udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(ep->id)); + + /* enable DMA interrupt at bridge level if DMA is used */ + if (ep->dma_regs) { + ep->bridge_on_dma_end = NULL; + usbf_reg_bitset(udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(ep->id)); + } + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static int usbf_epn_disable(struct usbf_ep *epn) +{ + /* Disable interrupts */ + usbf_ep_reg_writel(epn, USBF_REG_EPN_INT_ENA, 0); + + /* Disable endpoint */ + usbf_ep_reg_bitclr(epn, USBF_REG_EPN_CONTROL, USBF_EPN_EN); + + /* remove anything that was pending */ + usbf_ep_nuke(epn, -ESHUTDOWN); + + return 0; +} + +static int usbf_ep_disable(struct usb_ep *_ep) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s mpkts %d\n", ep->id, + ep->is_in ? "in" : "out", ep->ep.maxpacket); + + spin_lock_irqsave(&ep->udc->lock, flags); + ep->disabled = 1; + /* Disable DMA interrupt */ + if (ep->dma_regs) { + usbf_reg_bitclr(udc, USBF_REG_AHBBINTEN, + USBF_SYS_DMA_ENDINTEN_EPN(ep->id)); + ep->bridge_on_dma_end = NULL; + } + /* disable interrupts for this endpoint */ + usbf_reg_bitclr(udc, USBF_REG_USB_INT_ENA, USBF_USB_EPN_EN(ep->id)); + /* and the endpoint itself */ + ret = usbf_epn_disable(ep); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + return ret; +} + +static int usbf_ep0_queue(struct usbf_ep *ep0, struct usbf_req *req, + gfp_t gfp_flags) +{ + int ret; + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->is_zero_sent = 0; + + list_add_tail(&req->queue, &ep0->queue); + + if (ep0->udc->ep0state == EP0_IN_STATUS_START_PHASE) + return 0; + + if (!ep0->is_in) + return 0; + + if (ep0->udc->ep0state == EP0_IN_STATUS_PHASE) { + if (req->req.length) { + dev_err(ep0->udc->dev, + "request lng %u for ep0 in status phase\n", + req->req.length); + return -EINVAL; + } + ep0->delayed_status = 0; + } + if (!ep0->is_processing) { + ret = usbf_ep0_pio_in(ep0, req); + if (ret != -EINPROGRESS) { + dev_err(ep0->udc->dev, + "queued request not in progress\n"); + /* The request cannot be completed (ie + * ret == 0) on the first call + */ + return ret ? ret : -EIO; + } + } + + return 0; +} + +static int usbf_epn_queue(struct usbf_ep *ep, struct usbf_req *req, + gfp_t gfp_flags) +{ + int was_empty; + int ret; + + if (ep->disabled) { + dev_err(ep->udc->dev, "ep%u request queue while disable\n", + ep->id); + return -ESHUTDOWN; + } + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + req->is_zero_sent = 0; + req->xfer_step = USBF_XFER_START; + + was_empty = list_empty(&ep->queue); + list_add_tail(&req->queue, &ep->queue); + if (was_empty) { + ret = usbf_epn_start_queue(ep); + if (ret) + return ret; + } + return 0; +} + +static int usbf_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct usbf_req *req = container_of(_req, struct usbf_req, req); + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + struct usbf_udc *udc = ep->udc; + unsigned long flags; + int ret; + + if (!_req || !_req->buf) + return -EINVAL; + + if (!udc || !udc->driver) + return -EINVAL; + + dev_dbg(ep->udc->dev, "ep%u %s req queue length %u, zero %u, short_not_ok %u\n", + ep->id, ep->is_in ? "in" : "out", + req->req.length, req->req.zero, req->req.short_not_ok); + + spin_lock_irqsave(&ep->udc->lock, flags); + if (ep->id == 0) + ret = usbf_ep0_queue(ep, req, gfp_flags); + else + ret = usbf_epn_queue(ep, req, gfp_flags); + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static int usbf_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usbf_req *req = container_of(_req, struct usbf_req, req); + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int is_processing; + int first; + int ret; + + spin_lock_irqsave(&ep->udc->lock, flags); + + dev_dbg(ep->udc->dev, "ep%u %s req dequeue length %u/%u\n", + ep->id, ep->is_in ? "in" : "out", + req->req.actual, req->req.length); + + first = list_is_first(&req->queue, &ep->queue); + + /* Complete the request but avoid any operation that could be done + * if a new request is queued during the request completion + */ + is_processing = ep->is_processing; + ep->is_processing = 1; + usbf_ep_req_done(ep, req, -ECONNRESET); + ep->is_processing = is_processing; + + if (first) { + /* The first item in the list was dequeued. + * This item could already be submitted to the hardware. + * So, flush the fifo + */ + if (ep->id) + usbf_epn_fifo_flush(ep); + else + usbf_ep0_fifo_flush(ep); + } + + if (ep->id == 0) { + /* We dequeue a request on ep0. On this endpoint, we can have + * 1 request related to the data stage and/or 1 request + * related to the status stage. + * We dequeue one of them and so the USB control transaction + * is no more coherent. The simple way to be consistent after + * dequeuing is to stall and nuke the endpoint and wait the + * next SETUP packet. + */ + usbf_ep_stall(ep, true); + usbf_ep_nuke(ep, -ECONNRESET); + ep->udc->ep0state = EP0_IDLE; + goto end; + } + + if (!first) + goto end; + + ret = usbf_epn_start_queue(ep); + if (ret) { + usbf_ep_stall(ep, true); + usbf_ep_nuke(ep, -EIO); + } +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; +} + +static struct usb_request *usbf_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct usbf_req *req; + + if (!_ep) + return NULL; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void usbf_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct usbf_req *req; + unsigned long flags; + struct usbf_ep *ep; + + if (!_ep || !_req) + return; + + req = container_of(_req, struct usbf_req, req); + ep = container_of(_ep, struct usbf_ep, ep); + + spin_lock_irqsave(&ep->udc->lock, flags); + list_del_init(&req->queue); + spin_unlock_irqrestore(&ep->udc->lock, flags); + kfree(req); +} + +static int usbf_ep_set_halt(struct usb_ep *_ep, int halt) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto end; + } + + usbf_ep_stall(ep, halt); + if (!halt) + ep->is_wedged = 0; + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + + return ret; +} + +static int usbf_ep_set_wedge(struct usb_ep *_ep) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + unsigned long flags; + int ret; + + if (ep->id == 0) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->lock, flags); + if (!list_empty(&ep->queue)) { + ret = -EAGAIN; + goto end; + } + usbf_ep_stall(ep, 1); + ep->is_wedged = 1; + + ret = 0; +end: + spin_unlock_irqrestore(&ep->udc->lock, flags); + return ret; +} + +static struct usb_ep_ops usbf_ep_ops = { + .enable = usbf_ep_enable, + .disable = usbf_ep_disable, + .queue = usbf_ep_queue, + .dequeue = usbf_ep_dequeue, + .set_halt = usbf_ep_set_halt, + .set_wedge = usbf_ep_set_wedge, + .alloc_request = usbf_ep_alloc_request, + .free_request = usbf_ep_free_request, +}; + +static void usbf_ep0_req_complete(struct usb_ep *_ep, struct usb_request *_req) +{ +} + +static void usbf_ep0_fill_req(struct usbf_ep *ep0, struct usbf_req *req, + void *buf, unsigned int length, + void (*complete)(struct usb_ep *_ep, + struct usb_request *_req)) +{ + if (buf && length) + memcpy(ep0->udc->ep0_buf, buf, length); + + req->req.buf = ep0->udc->ep0_buf; + req->req.length = length; + req->req.dma = 0; + req->req.zero = true; + req->req.complete = complete ? complete : usbf_ep0_req_complete; + req->req.status = -EINPROGRESS; + req->req.context = NULL; + req->req.actual = 0; +} + +static struct usbf_ep *usbf_get_ep_by_addr(struct usbf_udc *udc, u8 address) +{ + struct usbf_ep *ep; + unsigned int i; + + if ((address & USB_ENDPOINT_NUMBER_MASK) == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (!ep->ep.desc) + continue; + + if (ep->ep.desc->bEndpointAddress == address) + return ep; + } + + return NULL; +} + +static int usbf_req_delegate(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + int ret; + + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, ctrlrequest); + spin_lock(&udc->lock); + if (ret < 0) { + dev_dbg(udc->dev, "udc driver setup failed %d\n", ret); + return ret; + } + if (ret == USB_GADGET_DELAYED_STATUS) { + dev_dbg(udc->dev, "delayed status set\n"); + udc->ep[0].delayed_status = 1; + return 0; + } + return ret; +} + +static int usbf_req_get_status(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + struct usbf_ep *ep; + u16 status_data; + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + switch (ctrlrequest->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD: + if ((wValue != 0) || (wIndex != 0) || (wLength != 2)) + goto delegate; + + status_data = 0; + if (udc->gadget.is_selfpowered) + status_data |= BIT(USB_DEVICE_SELF_POWERED); + + if (udc->is_remote_wakeup) + status_data |= BIT(USB_DEVICE_REMOTE_WAKEUP); + + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT | USB_TYPE_STANDARD: + if ((wValue != 0) || (wLength != 2)) + goto delegate; + + ep = usbf_get_ep_by_addr(udc, wIndex); + if (!ep) + return -EINVAL; + + status_data = 0; + if (usbf_ep_is_stalled(ep)) + status_data |= cpu_to_le16(1); + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_STANDARD: + if ((wValue != 0) || (wLength != 2)) + goto delegate; + status_data = 0; + break; + + default: + goto delegate; + } + + usbf_ep0_fill_req(&udc->ep[0], &udc->setup_reply, &status_data, + sizeof(status_data), NULL); + usbf_ep0_queue(&udc->ep[0], &udc->setup_reply, GFP_ATOMIC); + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static int usbf_req_clear_set_feature(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest, + bool is_set) +{ + struct usbf_ep *ep; + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + switch (ctrlrequest->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: + if ((wIndex != 0) || (wLength != 0)) + goto delegate; + + if (wValue != cpu_to_le16(USB_DEVICE_REMOTE_WAKEUP)) + goto delegate; + + udc->is_remote_wakeup = is_set; + break; + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: + if (wLength != 0) + goto delegate; + + ep = usbf_get_ep_by_addr(udc, wIndex); + if (!ep) + return -EINVAL; + + if ((ep->id == 0) && is_set) { + /* Endpoint 0 cannot be halted (stalled) + * Returning an error code leads to a STALL on this ep0 + * but keep the automate in a consistent state. + */ + return -EINVAL; + } + if (ep->is_wedged && !is_set) { + /* Ignore CLEAR_FEATURE(HALT ENDPOINT) when the + * endpoint is wedged + */ + break; + } + usbf_ep_stall(ep, is_set); + break; + + default: + goto delegate; + } + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static void usbf_ep0_req_set_address_complete(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct usbf_ep *ep = container_of(_ep, struct usbf_ep, ep); + + /* The status phase of the SET_ADDRESS request is completed ... */ + if (_req->status == 0) { + /* ... without any errors -> Signaled the state to the core. */ + usb_gadget_set_state(&ep->udc->gadget, USB_STATE_ADDRESS); + } + + /* In case of request failure, there is no need to revert the address + * value set to the hardware as the hardware will take care of the + * value only if the status stage is completed normally. + */ +} + +static int usbf_req_set_address(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + u32 addr; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + if (ctrlrequest->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + goto delegate; + + if ((wIndex != 0) || (wLength != 0) || (wValue > 127)) + return -EINVAL; + + addr = wValue; + /* The hardware will take care of this USB address after the status + * stage of the SET_ADDRESS request is completed normally. + * It is safe to write it now + */ + usbf_reg_writel(udc, USBF_REG_USB_ADDRESS, USBF_USB_SET_USB_ADDR(addr)); + + /* Queued the status request */ + usbf_ep0_fill_req(&udc->ep[0], &udc->setup_reply, NULL, 0, + usbf_ep0_req_set_address_complete); + usbf_ep0_queue(&udc->ep[0], &udc->setup_reply, GFP_ATOMIC); + + return 0; + +delegate: + return usbf_req_delegate(udc, ctrlrequest); +} + +static int usbf_req_set_configuration(struct usbf_udc *udc, + const struct usb_ctrlrequest *ctrlrequest) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + int ret; + + ret = usbf_req_delegate(udc, ctrlrequest); + if (ret) + return ret; + + wValue = le16_to_cpu(ctrlrequest->wValue); + wLength = le16_to_cpu(ctrlrequest->wLength); + wIndex = le16_to_cpu(ctrlrequest->wIndex); + + if ((ctrlrequest->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) || + (wIndex != 0) || (wLength != 0)) { + /* No error detected by driver->setup() but it is not an USB2.0 + * Ch9 SET_CONFIGURATION. + * Nothing more to do + */ + return 0; + } + + if (wValue & 0x00FF) { + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, USBF_USB_CONF); + } else { + usbf_reg_bitclr(udc, USBF_REG_USB_CONTROL, USBF_USB_CONF); + /* Go back to Address State */ + spin_unlock(&udc->lock); + usb_gadget_set_state(&udc->gadget, USB_STATE_ADDRESS); + spin_lock(&udc->lock); + } + + return 0; +} + +static int usbf_handle_ep0_setup(struct usbf_ep *ep0) +{ + union { + struct usb_ctrlrequest ctrlreq; + u32 raw[2]; + } crq; + struct usbf_udc *udc = ep0->udc; + int ret; + + /* Read setup data (ie the USB control request) */ + crq.raw[0] = usbf_reg_readl(udc, USBF_REG_SETUP_DATA0); + crq.raw[1] = usbf_reg_readl(udc, USBF_REG_SETUP_DATA1); + + dev_dbg(ep0->udc->dev, + "ep0 req%02x.%02x, wValue 0x%04x, wIndex 0x%04x, wLength 0x%04x\n", + crq.ctrlreq.bRequestType, crq.ctrlreq.bRequest, + crq.ctrlreq.wValue, crq.ctrlreq.wIndex, crq.ctrlreq.wLength); + + /* Set current EP0 state according to the received request */ + if (crq.ctrlreq.wLength) { + if (crq.ctrlreq.bRequestType & USB_DIR_IN) { + udc->ep0state = EP0_IN_DATA_PHASE; + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_INAK, + USBF_EP0_INAK_EN); + ep0->is_in = 1; + } else { + udc->ep0state = EP0_OUT_DATA_PHASE; + usbf_ep_reg_bitclr(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_ONAK); + ep0->is_in = 0; + } + } else { + udc->ep0state = EP0_IN_STATUS_START_PHASE; + ep0->is_in = 1; + } + + /* We starts a new control transfer -> Clear the delayed status flag */ + ep0->delayed_status = 0; + + if ((crq.ctrlreq.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) { + /* This is not a USB standard request -> delelate */ + goto delegate; + } + + switch (crq.ctrlreq.bRequest) { + case USB_REQ_GET_STATUS: + ret = usbf_req_get_status(udc, &crq.ctrlreq); + break; + + case USB_REQ_CLEAR_FEATURE: + ret = usbf_req_clear_set_feature(udc, &crq.ctrlreq, false); + break; + + case USB_REQ_SET_FEATURE: + ret = usbf_req_clear_set_feature(udc, &crq.ctrlreq, true); + break; + + case USB_REQ_SET_ADDRESS: + ret = usbf_req_set_address(udc, &crq.ctrlreq); + break; + + case USB_REQ_SET_CONFIGURATION: + ret = usbf_req_set_configuration(udc, &crq.ctrlreq); + break; + + default: + goto delegate; + } + + return ret; + +delegate: + return usbf_req_delegate(udc, &crq.ctrlreq); +} + +static int usbf_handle_ep0_data_status(struct usbf_ep *ep0, + const char *ep0state_name, + enum usbf_ep0state next_ep0state) +{ + struct usbf_udc *udc = ep0->udc; + int ret; + + ret = usbf_ep_process_queue(ep0); + switch (ret) { + case -ENOENT: + dev_err(udc->dev, + "no request available for ep0 %s phase\n", + ep0state_name); + break; + case -EINPROGRESS: + /* More data needs to be processed */ + ret = 0; + break; + case 0: + /* All requests in the queue are processed */ + udc->ep0state = next_ep0state; + break; + default: + dev_err(udc->dev, + "process queue failed for ep0 %s phase (%d)\n", + ep0state_name, ret); + break; + } + return ret; +} + +static int usbf_handle_ep0_out_status_start(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + struct usbf_req *req; + + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_ONAK, + USBF_EP0_PIDCLR); + ep0->is_in = 0; + + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + if (!req) { + usbf_ep0_fill_req(ep0, &udc->setup_reply, NULL, 0, NULL); + usbf_ep0_queue(ep0, &udc->setup_reply, GFP_ATOMIC); + } else { + if (req->req.length) { + dev_err(udc->dev, + "queued request length %u for ep0 out status phase\n", + req->req.length); + } + } + udc->ep0state = EP0_OUT_STATUS_PHASE; + return 0; +} + +static int usbf_handle_ep0_in_status_start(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + struct usbf_req *req; + int ret; + + usbf_ep_reg_clrset(ep0, USBF_REG_EP0_CONTROL, + USBF_EP0_INAK, + USBF_EP0_INAK_EN | USBF_EP0_PIDCLR); + ep0->is_in = 1; + + /* Queue request for status if needed */ + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + if (!req) { + if (ep0->delayed_status) { + dev_dbg(ep0->udc->dev, + "EP0_IN_STATUS_START_PHASE ep0->delayed_status set\n"); + udc->ep0state = EP0_IN_STATUS_PHASE; + return 0; + } + + usbf_ep0_fill_req(ep0, &udc->setup_reply, NULL, + 0, NULL); + usbf_ep0_queue(ep0, &udc->setup_reply, + GFP_ATOMIC); + + req = list_first_entry_or_null(&ep0->queue, struct usbf_req, queue); + } else { + if (req->req.length) { + dev_err(udc->dev, + "queued request length %u for ep0 in status phase\n", + req->req.length); + } + } + + ret = usbf_ep0_pio_in(ep0, req); + if (ret != -EINPROGRESS) { + usbf_ep_req_done(ep0, req, ret); + udc->ep0state = EP0_IN_STATUS_END_PHASE; + return 0; + } + + udc->ep0state = EP0_IN_STATUS_PHASE; + return 0; +} + +static void usbf_ep0_interrupt(struct usbf_ep *ep0) +{ + struct usbf_udc *udc = ep0->udc; + u32 sts, prev_sts; + int prev_ep0state; + int ret; + + ep0->status = usbf_ep_reg_readl(ep0, USBF_REG_EP0_STATUS); + usbf_ep_reg_writel(ep0, USBF_REG_EP0_STATUS, ~ep0->status); + + dev_dbg(ep0->udc->dev, "ep0 status=0x%08x, enable=%08x\n, ctrl=0x%08x\n", + ep0->status, + usbf_ep_reg_readl(ep0, USBF_REG_EP0_INT_ENA), + usbf_ep_reg_readl(ep0, USBF_REG_EP0_CONTROL)); + + sts = ep0->status & (USBF_EP0_SETUP_INT | USBF_EP0_IN_INT | USBF_EP0_OUT_INT | + USBF_EP0_OUT_NULL_INT | USBF_EP0_STG_START_INT | + USBF_EP0_STG_END_INT); + + ret = 0; + do { + dev_dbg(ep0->udc->dev, "udc->ep0state=%d\n", udc->ep0state); + + prev_sts = sts; + prev_ep0state = udc->ep0state; + switch (udc->ep0state) { + case EP0_IDLE: + if (!(sts & USBF_EP0_SETUP_INT)) + break; + + sts &= ~USBF_EP0_SETUP_INT; + dev_dbg(ep0->udc->dev, "ep0 handle setup\n"); + ret = usbf_handle_ep0_setup(ep0); + break; + + case EP0_IN_DATA_PHASE: + if (!(sts & USBF_EP0_IN_INT)) + break; + + sts &= ~USBF_EP0_IN_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in data phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "in data", EP0_OUT_STATUS_START_PHASE); + break; + + case EP0_OUT_STATUS_START_PHASE: + if (!(sts & USBF_EP0_STG_START_INT)) + break; + + sts &= ~USBF_EP0_STG_START_INT; + dev_dbg(ep0->udc->dev, "ep0 handle out status start phase\n"); + ret = usbf_handle_ep0_out_status_start(ep0); + break; + + case EP0_OUT_STATUS_PHASE: + if (!(sts & (USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT))) + break; + + sts &= ~(USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT); + dev_dbg(ep0->udc->dev, "ep0 handle out status phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "out status", + EP0_OUT_STATUS_END_PHASE); + break; + + case EP0_OUT_STATUS_END_PHASE: + if (!(sts & (USBF_EP0_STG_END_INT | USBF_EP0_SETUP_INT))) + break; + + sts &= ~USBF_EP0_STG_END_INT; + dev_dbg(ep0->udc->dev, "ep0 handle out status end phase\n"); + udc->ep0state = EP0_IDLE; + break; + + case EP0_OUT_DATA_PHASE: + if (!(sts & (USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT))) + break; + + sts &= ~(USBF_EP0_OUT_INT | USBF_EP0_OUT_NULL_INT); + dev_dbg(ep0->udc->dev, "ep0 handle out data phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "out data", EP0_IN_STATUS_START_PHASE); + break; + + case EP0_IN_STATUS_START_PHASE: + if (!(sts & USBF_EP0_STG_START_INT)) + break; + + sts &= ~USBF_EP0_STG_START_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status start phase\n"); + ret = usbf_handle_ep0_in_status_start(ep0); + break; + + case EP0_IN_STATUS_PHASE: + if (!(sts & USBF_EP0_IN_INT)) + break; + + sts &= ~USBF_EP0_IN_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status phase\n"); + ret = usbf_handle_ep0_data_status(ep0, + "in status", EP0_IN_STATUS_END_PHASE); + break; + + case EP0_IN_STATUS_END_PHASE: + if (!(sts & (USBF_EP0_STG_END_INT | USBF_EP0_SETUP_INT))) + break; + + sts &= ~USBF_EP0_STG_END_INT; + dev_dbg(ep0->udc->dev, "ep0 handle in status end\n"); + udc->ep0state = EP0_IDLE; + break; + + default: + udc->ep0state = EP0_IDLE; + break; + } + + if (ret) { + dev_dbg(ep0->udc->dev, "ep0 failed (%d)\n", ret); + /* Failure -> stall. + * This stall state will be automatically cleared when + * the IP receives the next SETUP packet + */ + usbf_ep_stall(ep0, true); + + /* Remove anything that was pending */ + usbf_ep_nuke(ep0, -EPROTO); + + udc->ep0state = EP0_IDLE; + break; + } + + } while ((prev_ep0state != udc->ep0state) || (prev_sts != sts)); + + dev_dbg(ep0->udc->dev, "ep0 done udc->ep0state=%d, status=0x%08x. next=0x%08x\n", + udc->ep0state, sts, + usbf_ep_reg_readl(ep0, USBF_REG_EP0_STATUS)); +} + +static void usbf_epn_process_queue(struct usbf_ep *epn) +{ + int ret; + + ret = usbf_ep_process_queue(epn); + switch (ret) { + case -ENOENT: + dev_warn(epn->udc->dev, "ep%u %s, no request available\n", + epn->id, epn->is_in ? "in" : "out"); + break; + case -EINPROGRESS: + /* More data needs to be processed */ + ret = 0; + break; + case 0: + /* All requests in the queue are processed */ + break; + default: + dev_err(epn->udc->dev, "ep%u %s, process queue failed (%d)\n", + epn->id, epn->is_in ? "in" : "out", ret); + break; + } + + if (ret) { + dev_dbg(epn->udc->dev, "ep%u %s failed (%d)\n", epn->id, + epn->is_in ? "in" : "out", ret); + usbf_ep_stall(epn, true); + usbf_ep_nuke(epn, ret); + } +} + +static void usbf_epn_interrupt(struct usbf_ep *epn) +{ + u32 sts; + u32 ena; + + epn->status = usbf_ep_reg_readl(epn, USBF_REG_EPN_STATUS); + ena = usbf_ep_reg_readl(epn, USBF_REG_EPN_INT_ENA); + usbf_ep_reg_writel(epn, USBF_REG_EPN_STATUS, ~(epn->status & ena)); + + dev_dbg(epn->udc->dev, "ep%u %s status=0x%08x, enable=%08x\n, ctrl=0x%08x\n", + epn->id, epn->is_in ? "in" : "out", epn->status, ena, + usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL)); + + if (epn->disabled) { + dev_warn(epn->udc->dev, "ep%u %s, interrupt while disabled\n", + epn->id, epn->is_in ? "in" : "out"); + return; + } + + sts = epn->status & ena; + + if (sts & (USBF_EPN_IN_END_INT | USBF_EPN_IN_INT)) { + sts &= ~(USBF_EPN_IN_END_INT | USBF_EPN_IN_INT); + dev_dbg(epn->udc->dev, "ep%u %s process queue (in interrupts)\n", + epn->id, epn->is_in ? "in" : "out"); + usbf_epn_process_queue(epn); + } + + if (sts & (USBF_EPN_OUT_END_INT | USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT)) { + sts &= ~(USBF_EPN_OUT_END_INT | USBF_EPN_OUT_INT | USBF_EPN_OUT_NULL_INT); + dev_dbg(epn->udc->dev, "ep%u %s process queue (out interrupts)\n", + epn->id, epn->is_in ? "in" : "out"); + usbf_epn_process_queue(epn); + } + + dev_dbg(epn->udc->dev, "ep%u %s done status=0x%08x. next=0x%08x\n", + epn->id, epn->is_in ? "in" : "out", + sts, usbf_ep_reg_readl(epn, USBF_REG_EPN_STATUS)); +} + +static void usbf_ep_reset(struct usbf_ep *ep) +{ + ep->status = 0; + /* Remove anything that was pending */ + usbf_ep_nuke(ep, -ESHUTDOWN); +} + +static void usbf_reset(struct usbf_udc *udc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + if (udc->ep[i].disabled) + continue; + + usbf_ep_reset(&udc->ep[i]); + } + + if (usbf_reg_readl(udc, USBF_REG_USB_STATUS) & USBF_USB_SPEED_MODE) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + + /* Remote wakeup feature must be disabled on USB bus reset */ + udc->is_remote_wakeup = false; + + /* Enable endpoint zero */ + usbf_ep0_enable(&udc->ep[0]); + + if (udc->driver) { + /* Signal the reset */ + spin_unlock(&udc->lock); + usb_gadget_udc_reset(&udc->gadget, udc->driver); + spin_lock(&udc->lock); + } +} + +static void usbf_driver_suspend(struct usbf_udc *udc) +{ + if (udc->is_usb_suspended) { + dev_dbg(udc->dev, "already suspended\n"); + return; + } + + dev_dbg(udc->dev, "do usb suspend\n"); + udc->is_usb_suspended = true; + + if (udc->driver && udc->driver->suspend) { + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + + /* The datasheet tells to set the USB_CONTROL register SUSPEND + * bit when the USB bus suspend is detected. + * This bit stops the clocks (clocks for EPC, SIE, USBPHY) but + * these clocks seems not used only by the USB device. Some + * UARTs can be lost ... + * So, do not set the USB_CONTROL register SUSPEND bit. + */ + } +} + +static void usbf_driver_resume(struct usbf_udc *udc) +{ + if (!udc->is_usb_suspended) + return; + + dev_dbg(udc->dev, "do usb resume\n"); + udc->is_usb_suspended = false; + + if (udc->driver && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + } +} + +static irqreturn_t usbf_epc_irq(int irq, void *_udc) +{ + struct usbf_udc *udc = (struct usbf_udc *)_udc; + unsigned long flags; + struct usbf_ep *ep; + u32 int_sts; + u32 int_en; + int i; + + spin_lock_irqsave(&udc->lock, flags); + + int_en = usbf_reg_readl(udc, USBF_REG_USB_INT_ENA); + int_sts = usbf_reg_readl(udc, USBF_REG_USB_INT_STA) & int_en; + usbf_reg_writel(udc, USBF_REG_USB_INT_STA, ~int_sts); + + dev_dbg(udc->dev, "int_sts=0x%08x\n", int_sts); + + if (int_sts & USBF_USB_RSUM_INT) { + dev_dbg(udc->dev, "handle resume\n"); + usbf_driver_resume(udc); + } + + if (int_sts & USBF_USB_USB_RST_INT) { + dev_dbg(udc->dev, "handle bus reset\n"); + usbf_driver_resume(udc); + usbf_reset(udc); + } + + if (int_sts & USBF_USB_SPEED_MODE_INT) { + if (usbf_reg_readl(udc, USBF_REG_USB_STATUS) & USBF_USB_SPEED_MODE) + udc->gadget.speed = USB_SPEED_HIGH; + else + udc->gadget.speed = USB_SPEED_FULL; + dev_dbg(udc->dev, "handle speed change (%s)\n", + udc->gadget.speed == USB_SPEED_HIGH ? "High" : "Full"); + } + + if (int_sts & USBF_USB_EPN_INT(0)) { + usbf_driver_resume(udc); + usbf_ep0_interrupt(&udc->ep[0]); + } + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (int_sts & USBF_USB_EPN_INT(i)) { + usbf_driver_resume(udc); + usbf_epn_interrupt(ep); + } + } + + if (int_sts & USBF_USB_SPND_INT) { + dev_dbg(udc->dev, "handle suspend\n"); + usbf_driver_suspend(udc); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t usbf_ahb_epc_irq(int irq, void *_udc) +{ + struct usbf_udc *udc = (struct usbf_udc *)_udc; + unsigned long flags; + struct usbf_ep *epn; + u32 sysbint; + void (*ep_action)(struct usbf_ep *epn); + int i; + + spin_lock_irqsave(&udc->lock, flags); + + /* Read and ack interrupts */ + sysbint = usbf_reg_readl(udc, USBF_REG_AHBBINT); + usbf_reg_writel(udc, USBF_REG_AHBBINT, sysbint); + + if ((sysbint & USBF_SYS_VBUS_INT) == USBF_SYS_VBUS_INT) { + if (usbf_reg_readl(udc, USBF_REG_EPCTR) & USBF_SYS_VBUS_LEVEL) { + dev_dbg(udc->dev, "handle vbus (1)\n"); + spin_unlock(&udc->lock); + usb_udc_vbus_handler(&udc->gadget, true); + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + spin_lock(&udc->lock); + } else { + dev_dbg(udc->dev, "handle vbus (0)\n"); + udc->is_usb_suspended = false; + spin_unlock(&udc->lock); + usb_udc_vbus_handler(&udc->gadget, false); + usb_gadget_set_state(&udc->gadget, + USB_STATE_NOTATTACHED); + spin_lock(&udc->lock); + } + } + + for (i = 1; i < ARRAY_SIZE(udc->ep); i++) { + if (sysbint & USBF_SYS_DMA_ENDINT_EPN(i)) { + epn = &udc->ep[i]; + dev_dbg(epn->udc->dev, + "ep%u handle DMA complete. action=%ps\n", + epn->id, epn->bridge_on_dma_end); + ep_action = epn->bridge_on_dma_end; + if (ep_action) { + epn->bridge_on_dma_end = NULL; + ep_action(epn); + } + } + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return IRQ_HANDLED; +} + +static int usbf_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + dev_info(udc->dev, "start (driver '%s')\n", driver->driver.name); + + spin_lock_irqsave(&udc->lock, flags); + + /* hook up the driver */ + udc->driver = driver; + + /* Enable VBUS interrupt */ + usbf_reg_writel(udc, USBF_REG_AHBBINTEN, USBF_SYS_VBUS_INTEN); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_stop(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* Disable VBUS interrupt */ + usbf_reg_writel(udc, USBF_REG_AHBBINTEN, 0); + + udc->driver = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + dev_info(udc->dev, "stopped\n"); + + return 0; +} + +static int usbf_get_frame(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + + return USBF_USB_GET_FRAME(usbf_reg_readl(udc, USBF_REG_USB_ADDRESS)); +} + +static void usbf_attach(struct usbf_udc *udc) +{ + /* Enable USB signal to Function PHY + * D+ signal Pull-up + * Disable endpoint 0, it will be automatically enable when a USB reset + * is received. + * Disable the other endpoints + */ + usbf_reg_clrset(udc, USBF_REG_USB_CONTROL, + USBF_USB_CONNECTB | USBF_USB_DEFAULT | USBF_USB_CONF, + USBF_USB_PUE2); + + /* Enable reset and mode change interrupts */ + usbf_reg_bitset(udc, USBF_REG_USB_INT_ENA, + USBF_USB_USB_RST_EN | USBF_USB_SPEED_MODE_EN | USBF_USB_RSUM_EN | USBF_USB_SPND_EN); +} + +static void usbf_detach(struct usbf_udc *udc) +{ + int i; + + /* Disable interrupts */ + usbf_reg_writel(udc, USBF_REG_USB_INT_ENA, 0); + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + if (udc->ep[i].disabled) + continue; + + usbf_ep_reset(&udc->ep[i]); + } + + /* Disable USB signal to Function PHY + * Do not Pull-up D+ signal + * Disable endpoint 0 + * Disable the other endpoints + */ + usbf_reg_clrset(udc, USBF_REG_USB_CONTROL, + USBF_USB_PUE2 | USBF_USB_DEFAULT | USBF_USB_CONF, + USBF_USB_CONNECTB); +} + +static int usbf_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + dev_dbg(udc->dev, "pullup %d\n", is_on); + + spin_lock_irqsave(&udc->lock, flags); + if (is_on) + usbf_attach(udc); + else + usbf_detach(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + gadget->is_selfpowered = (is_selfpowered != 0); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int usbf_udc_wakeup(struct usb_gadget *gadget) +{ + struct usbf_udc *udc = container_of(gadget, struct usbf_udc, gadget); + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + + if (!udc->is_remote_wakeup) { + dev_dbg(udc->dev, "remote wakeup not allowed\n"); + ret = -EINVAL; + goto end; + } + + dev_dbg(udc->dev, "do wakeup\n"); + + /* Send the resume signal */ + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, USBF_USB_RSUM_IN); + usbf_reg_bitclr(udc, USBF_REG_USB_CONTROL, USBF_USB_RSUM_IN); + + ret = 0; +end: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static struct usb_gadget_ops usbf_gadget_ops = { + .get_frame = usbf_get_frame, + .pullup = usbf_pullup, + .udc_start = usbf_udc_start, + .udc_stop = usbf_udc_stop, + .set_selfpowered = usbf_udc_set_selfpowered, + .wakeup = usbf_udc_wakeup, +}; + +static int usbf_epn_check(struct usbf_ep *epn) +{ + const char *type_txt; + const char *buf_txt; + int ret = 0; + u32 ctrl; + + ctrl = usbf_ep_reg_readl(epn, USBF_REG_EPN_CONTROL); + + switch (ctrl & USBF_EPN_MODE_MASK) { + case USBF_EPN_MODE_BULK: + type_txt = "bulk"; + if (epn->ep.caps.type_control || epn->ep.caps.type_iso || + !epn->ep.caps.type_bulk || epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, bulk expected\n", epn->id); + ret = -EINVAL; + } + break; + case USBF_EPN_MODE_INTR: + type_txt = "intr"; + if (epn->ep.caps.type_control || epn->ep.caps.type_iso || + epn->ep.caps.type_bulk || !epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, int expected\n", epn->id); + ret = -EINVAL; + } + break; + case USBF_EPN_MODE_ISO: + type_txt = "iso"; + if (epn->ep.caps.type_control || !epn->ep.caps.type_iso || + epn->ep.caps.type_bulk || epn->ep.caps.type_int) { + dev_err(epn->udc->dev, + "ep%u caps mismatch, iso expected\n", epn->id); + ret = -EINVAL; + } + break; + default: + type_txt = "unknown"; + dev_err(epn->udc->dev, "ep%u unknown type\n", epn->id); + ret = -EINVAL; + break; + } + + if (ctrl & USBF_EPN_BUF_TYPE_DOUBLE) { + buf_txt = "double"; + if (!usbf_ep_info[epn->id].is_double) { + dev_err(epn->udc->dev, + "ep%u buffer mismatch, double expected\n", + epn->id); + ret = -EINVAL; + } + } else { + buf_txt = "single"; + if (usbf_ep_info[epn->id].is_double) { + dev_err(epn->udc->dev, + "ep%u buffer mismatch, single expected\n", + epn->id); + ret = -EINVAL; + } + } + + dev_dbg(epn->udc->dev, "ep%u (%s) %s, %s buffer %u, checked %s\n", + epn->id, epn->ep.name, type_txt, buf_txt, + epn->ep.maxpacket_limit, ret ? "failed" : "ok"); + + return ret; +} + +static int usbf_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usbf_udc *udc; + struct usbf_ep *ep; + unsigned int i; + int irq; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + platform_set_drvdata(pdev, udc); + + udc->dev = dev; + spin_lock_init(&udc->lock); + + udc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + devm_pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + return ret; + + dev_info(dev, "USBF version: %08x\n", + usbf_reg_readl(udc, USBF_REG_USBSSVER)); + + /* Resetting the PLL is handled via the clock driver as it has common + * registers with USB Host + */ + usbf_reg_bitclr(udc, USBF_REG_EPCTR, USBF_SYS_EPC_RST); + + /* modify in register gadget process */ + udc->gadget.speed = USB_SPEED_FULL; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.ops = &usbf_gadget_ops; + + udc->gadget.name = dev->driver->name; + udc->gadget.dev.parent = dev; + udc->gadget.ep0 = &udc->ep[0].ep; + + /* The hardware DMA controller needs dma addresses aligned on 32bit. + * A fallback to pio is done if DMA addresses are not aligned. + */ + udc->gadget.quirk_avoids_skb_reserve = 1; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + /* we have a canned request structure to allow sending packets as reply + * to get_status requests + */ + INIT_LIST_HEAD(&udc->setup_reply.queue); + + for (i = 0; i < ARRAY_SIZE(udc->ep); i++) { + ep = &udc->ep[i]; + + if (!(usbf_reg_readl(udc, USBF_REG_USBSSCONF) & + USBF_SYS_EP_AVAILABLE(i))) { + continue; + } + + INIT_LIST_HEAD(&ep->queue); + + ep->id = i; + ep->disabled = 1; + ep->udc = udc; + ep->ep.ops = &usbf_ep_ops; + ep->ep.name = usbf_ep_info[i].name; + ep->ep.caps = usbf_ep_info[i].caps; + usb_ep_set_maxpacket_limit(&ep->ep, + usbf_ep_info[i].maxpacket_limit); + + if (ep->id == 0) { + ep->regs = ep->udc->regs + USBF_BASE_EP0; + } else { + ep->regs = ep->udc->regs + USBF_BASE_EPN(ep->id - 1); + ret = usbf_epn_check(ep); + if (ret) + return ret; + if (usbf_reg_readl(udc, USBF_REG_USBSSCONF) & + USBF_SYS_DMA_AVAILABLE(i)) { + ep->dma_regs = ep->udc->regs + + USBF_BASE_DMA_EPN(ep->id - 1); + } + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_irq(dev, irq, usbf_epc_irq, 0, "usbf-epc", udc); + if (ret) { + dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + return ret; + } + + irq = platform_get_irq(pdev, 1); + if (irq < 0) + return irq; + ret = devm_request_irq(dev, irq, usbf_ahb_epc_irq, 0, "usbf-ahb-epc", udc); + if (ret) { + dev_err(dev, "cannot request irq %d err %d\n", irq, ret); + return ret; + } + + usbf_reg_bitset(udc, USBF_REG_AHBMCTR, USBF_SYS_WBURST_TYPE); + + usbf_reg_bitset(udc, USBF_REG_USB_CONTROL, + USBF_USB_INT_SEL | USBF_USB_SOF_RCV | USBF_USB_SOF_CLK_MODE); + + ret = usb_add_gadget_udc(dev, &udc->gadget); + if (ret) + return ret; + + return 0; +} + +static int usbf_remove(struct platform_device *pdev) +{ + struct usbf_udc *udc = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&udc->gadget); + + pm_runtime_put(&pdev->dev); + + return 0; +} + +static const struct of_device_id usbf_match[] = { + { .compatible = "renesas,rzn1-usbf" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, usbf_match); + +static struct platform_driver udc_driver = { + .driver = { + .name = "usbf_renesas", + .owner = THIS_MODULE, + .of_match_table = usbf_match, + }, + .probe = usbf_probe, + .remove = usbf_remove, +}; + +module_platform_driver(udc_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("Renesas R-Car Gen3 & RZ/N1 USB Function driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From e12069043418532184c554f89a9002d3bc543782 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Thu, 5 Jan 2023 16:22:56 +0100 Subject: ARM: dts: r9a06g032: Add the USBF controller node Add the USBF controller available in the r9a06g032 SoC. Signed-off-by: Herve Codina Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230105152257.310642-5-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/r9a06g032.dtsi | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/arch/arm/boot/dts/r9a06g032.dtsi b/arch/arm/boot/dts/r9a06g032.dtsi index 41e19c0986ce..0fa565a1c3ad 100644 --- a/arch/arm/boot/dts/r9a06g032.dtsi +++ b/arch/arm/boot/dts/r9a06g032.dtsi @@ -117,6 +117,18 @@ }; }; + udc: usb@4001e000 { + compatible = "renesas,r9a06g032-usbf", "renesas,rzn1-usbf"; + reg = <0x4001e000 0x2000>; + interrupts = , + ; + clocks = <&sysctrl R9A06G032_HCLK_USBF>, + <&sysctrl R9A06G032_HCLK_USBPM>; + clock-names = "hclkf", "hclkpm"; + power-domains = <&sysctrl>; + status = "disabled"; + }; + pci_usb: pci@40030000 { compatible = "renesas,pci-r9a06g032", "renesas,pci-rzn1"; device_type = "pci"; -- cgit v1.2.3 From a6d7b7b095c4197c78de71a7876e74b6b4ef8af8 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Thu, 5 Jan 2023 16:22:57 +0100 Subject: MAINTAINERS: add the Renesas RZ/N1 USBF controller entry After contributing the driver, add myself as the maintainer for Renesas RZ/N1 USBF controller. Signed-off-by: Herve Codina Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230105152257.310642-6-herve.codina@bootlin.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a36df9ed283d..d6024dfe9ef5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17866,6 +17866,14 @@ S: Maintained F: Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml F: drivers/rtc/rtc-rzn1.c +RENESAS RZ/N1 USBF CONTROLLER DRIVER +M: Herve Codina +L: linux-renesas-soc@vger.kernel.org +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/usb/renesas,rzn1-usbf.yaml +F: drivers/usb/gadget/udc/renesas_usbf.c + RENESAS R-CAR GEN3 & RZ/N1 NAND CONTROLLER DRIVER M: Miquel Raynal L: linux-mtd@lists.infradead.org -- cgit v1.2.3 From 30374434edab20e25776f8ecb4bc9d1e54309487 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 6 Jan 2023 16:28:28 +0100 Subject: USB: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Alan Stern Cc: Jilin Yuan Link: https://lore.kernel.org/r/20230106152828.3790902-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 11b15d7b357a..a415206cab04 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -998,7 +998,7 @@ static void usb_debugfs_init(void) static void usb_debugfs_cleanup(void) { - debugfs_remove(debugfs_lookup("devices", usb_debug_root)); + debugfs_lookup_and_remove("devices", usb_debug_root); } /* -- cgit v1.2.3 From 26fe745063e2d7d091020022b96a145f3a82dff8 Mon Sep 17 00:00:00 2001 From: Jun Nie Date: Thu, 5 Jan 2023 15:50:57 +0800 Subject: dt-bindings: usb: tps6598x: Add wakeup property Add wakeup property description. People can enable it with adding the property. Signed-off-by: Jun Nie Reviewed-by: Bryan O'Donoghue Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230105075058.924680-1-jun.nie@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ti,tps6598x.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml index fef4acdc4773..348a715d61f4 100644 --- a/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml +++ b/Documentation/devicetree/bindings/usb/ti,tps6598x.yaml @@ -23,6 +23,8 @@ properties: reg: maxItems: 1 + wakeup-source: true + interrupts: maxItems: 1 @@ -48,6 +50,7 @@ examples: tps6598x: tps6598x@38 { compatible = "ti,tps6598x"; reg = <0x38>; + wakeup-source; interrupt-parent = <&msmgpio>; interrupts = <107 IRQ_TYPE_LEVEL_LOW>; -- cgit v1.2.3 From 481735d64794181916424a36b332bcd54bfb280b Mon Sep 17 00:00:00 2001 From: Jun Nie Date: Thu, 5 Jan 2023 15:50:58 +0800 Subject: usb: typec: tipd: Support wakeup Enable wakeup when pluging or unpluging USB cable. It is up to other components to hold system in active mode, such as display, so that user can receive the notification. Signed-off-by: Jun Nie Reviewed-by: Heikki Krogerus Reviewed-by: Bryan O'Donoghue Link: https://lore.kernel.org/r/20230105075058.924680-2-jun.nie@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 46a4d8b128f0..485b90c13078 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -95,6 +95,7 @@ struct tps6598x { struct power_supply_desc psy_desc; enum power_supply_usb_type usb_type; + int wakeup; u16 pwr_status; }; @@ -846,6 +847,12 @@ static int tps6598x_probe(struct i2c_client *client) i2c_set_clientdata(client, tps); fwnode_handle_put(fwnode); + tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); + if (tps->wakeup) { + device_init_wakeup(&client->dev, true); + enable_irq_wake(client->irq); + } + return 0; err_disconnect: @@ -870,6 +877,36 @@ static void tps6598x_remove(struct i2c_client *client) usb_role_switch_put(tps->role_sw); } +static int __maybe_unused tps6598x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + if (tps->wakeup) { + disable_irq(client->irq); + enable_irq_wake(client->irq); + } + + return 0; +} + +static int __maybe_unused tps6598x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + + if (tps->wakeup) { + disable_irq_wake(client->irq); + enable_irq(client->irq); + } + + return 0; +} + +static const struct dev_pm_ops tps6598x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tps6598x_suspend, tps6598x_resume) +}; + static const struct of_device_id tps6598x_of_match[] = { { .compatible = "ti,tps6598x", }, { .compatible = "apple,cd321x", }, @@ -886,6 +923,7 @@ MODULE_DEVICE_TABLE(i2c, tps6598x_id); static struct i2c_driver tps6598x_i2c_driver = { .driver = { .name = "tps6598x", + .pm = &tps6598x_pm_ops, .of_match_table = tps6598x_of_match, }, .probe_new = tps6598x_probe, -- cgit v1.2.3 From 263b628ff9e5ccf5d4b8290f2b3c35e76bcb8961 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Tue, 10 Jan 2023 19:08:04 +0100 Subject: dt-bindings: usb: dwc3-imx8mp: add power domain property The USB controllers in the i.MX8MP are located inside the HSIO power domain. Add the power-domains property to the DT binding to be able to describe the hardware properly. Signed-off-by: Lucas Stach Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230110180804.594462-1-l.stach@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml index 01ab0f922ae8..3fb4feb6d3d9 100644 --- a/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml @@ -71,6 +71,9 @@ properties: description: Power pad (PWR) polarity is active low. + power-domains: + maxItems: 1 + # Required child node: patternProperties: @@ -87,12 +90,14 @@ required: - clocks - clock-names - interrupts + - power-domains additionalProperties: false examples: - | #include + #include #include usb3_0: usb@32f10100 { compatible = "fsl,imx8mp-dwc3"; @@ -102,6 +107,7 @@ examples: <&clk IMX8MP_CLK_USB_ROOT>; clock-names = "hsio", "suspend"; interrupts = ; + power-domains = <&hsio_blk_ctrl IMX8MP_HSIOBLK_PD_USB>; #address-cells = <1>; #size-cells = <1>; dma-ranges = <0x40000000 0x40000000 0xc0000000>; -- cgit v1.2.3 From 4a0192c01e036b542d0f53ca2673a50b38f57d74 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 13 Dec 2022 08:37:31 +0000 Subject: usb: gadget: usb: Remove "default" from color matching attributes Color matching attributes in the configfs for UVC are named with the phrase "default". The implication of that is that they will only be used _with_ the default color matching descriptor, and that will shortly no longer be the case. Remove the "default" from the color matching descriptor attribute variables. Signed-off-by: Daniel Scally Reviewed-by: Laurent Pinchart Reviewed-by: Kieran Bingham Link: https://lore.kernel.org/r/20221213083736.2284536-2-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 76cb60d13049..e28becd435bf 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1783,8 +1783,8 @@ static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = { * streaming/color_matching/default */ -#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, bits) \ -static ssize_t uvcg_default_color_matching_##cname##_show( \ +#define UVCG_COLOR_MATCHING_ATTR(cname, aname, bits) \ +static ssize_t uvcg_color_matching_##cname##_show( \ struct config_item *item, char *page) \ { \ struct config_group *group = to_config_group(item); \ @@ -1808,26 +1808,25 @@ static ssize_t uvcg_default_color_matching_##cname##_show( \ return result; \ } \ \ -UVC_ATTR_RO(uvcg_default_color_matching_, cname, aname) +UVC_ATTR_RO(uvcg_color_matching_, cname, aname) -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics, - bTransferCharacteristics, 8); -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); +UVCG_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); +UVCG_COLOR_MATCHING_ATTR(b_transfer_characteristics, bTransferCharacteristics, 8); +UVCG_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); -#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR +#undef UVCG_COLOR_MATCHING_ATTR -static struct configfs_attribute *uvcg_default_color_matching_attrs[] = { - &uvcg_default_color_matching_attr_b_color_primaries, - &uvcg_default_color_matching_attr_b_transfer_characteristics, - &uvcg_default_color_matching_attr_b_matrix_coefficients, +static struct configfs_attribute *uvcg_color_matching_attrs[] = { + &uvcg_color_matching_attr_b_color_primaries, + &uvcg_color_matching_attr_b_transfer_characteristics, + &uvcg_color_matching_attr_b_matrix_coefficients, NULL, }; -static const struct uvcg_config_group_type uvcg_default_color_matching_type = { +static const struct uvcg_config_group_type uvcg_color_matching_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, - .ct_attrs = uvcg_default_color_matching_attrs, + .ct_attrs = uvcg_color_matching_attrs, .ct_owner = THIS_MODULE, }, .name = "default", @@ -1844,7 +1843,7 @@ static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { }, .name = "color_matching", .children = (const struct uvcg_config_group_type*[]) { - &uvcg_default_color_matching_type, + &uvcg_color_matching_type, NULL, }, }; -- cgit v1.2.3 From 2648f68bd0ac9ec0e1e17d1e4420570b35e8f175 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Wed, 11 Jan 2023 11:04:45 +0000 Subject: dt-bindings: usb: Add NVIDIA Tegra234 XUSB host controller binding Add device-tree binding documentation for the XUSB host controller present on Tegra234 SoC. This controller supports the USB 3.1 specification. Signed-off-by: Wayne Chang Signed-off-by: Thierry Reding Signed-off-by: Jon Hunter Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230111110450.24617-2-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/nvidia,tegra234-xusb.yaml | 159 +++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml new file mode 100644 index 000000000000..db761dcbf72a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra234-xusb.yaml @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/nvidia,tegra234-xusb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra234 xHCI controller + +maintainers: + - Thierry Reding + - Jon Hunter + +description: | + The Tegra xHCI controller supports both USB2 and USB3 interfaces exposed by + the Tegra XUSB pad controller. The xHCI controller controls up to eight + ports; there are four USB 2.0 ports and four USB 3.2 Gen1 x1 ports. + +properties: + compatible: + const: nvidia,tegra234-xusb + + reg: + items: + - description: xHCI host registers + - description: XUSB FPCI registers + - description: XUSB bar2 registers + + reg-names: + items: + - const: hcd + - const: fpci + - const: bar2 + + interrupts: + items: + - description: xHCI host interrupt + - description: mailbox interrupt + + clocks: + items: + - description: XUSB host clock + - description: XUSB Falcon source clock + - description: XUSB SuperSpeed clock + - description: XUSB SuperSpeed source clock + - description: XUSB HighSpeed clock source + - description: XUSB FullSpeed clock source + - description: USB PLL + - description: reference clock + - description: I/O PLL + + clock-names: + items: + - const: xusb_host + - const: xusb_falcon_src + - const: xusb_ss + - const: xusb_ss_src + - const: xusb_hs_src + - const: xusb_fs_src + - const: pll_u_480m + - const: clk_m + - const: pll_e + + interconnects: + items: + - description: read client + - description: write client + + interconnect-names: + items: + - const: dma-mem # read + - const: write + + iommus: + maxItems: 1 + + nvidia,xusb-padctl: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to the XUSB pad controller that is used to configure + the USB pads used by the XHCI controller + + phys: + minItems: 1 + maxItems: 8 + + phy-names: + minItems: 1 + maxItems: 8 + items: + enum: + - usb2-0 + - usb2-1 + - usb2-2 + - usb2-3 + - usb3-0 + - usb3-1 + - usb3-2 + - usb3-3 + + power-domains: + items: + - description: XUSBC power domain (for Host and USB 2.0) + - description: XUSBA power domain (for SuperSpeed) + + power-domain-names: + items: + - const: xusb_host + - const: xusb_ss + + dma-coherent: true + +allOf: + - $ref: usb-xhci.yaml + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + #include + + usb@3610000 { + compatible = "nvidia,tegra234-xusb"; + reg = <0x03610000 0x40000>, + <0x03600000 0x10000>, + <0x03650000 0x10000>; + reg-names = "hcd", "fpci", "bar2"; + + interrupts = , + ; + + clocks = <&bpmp TEGRA234_CLK_XUSB_CORE_HOST>, + <&bpmp TEGRA234_CLK_XUSB_FALCON>, + <&bpmp TEGRA234_CLK_XUSB_CORE_SS>, + <&bpmp TEGRA234_CLK_XUSB_SS>, + <&bpmp TEGRA234_CLK_CLK_M>, + <&bpmp TEGRA234_CLK_XUSB_FS>, + <&bpmp TEGRA234_CLK_UTMIP_PLL>, + <&bpmp TEGRA234_CLK_CLK_M>, + <&bpmp TEGRA234_CLK_PLLE>; + clock-names = "xusb_host", "xusb_falcon_src", + "xusb_ss", "xusb_ss_src", "xusb_hs_src", + "xusb_fs_src", "pll_u_480m", "clk_m", + "pll_e"; + interconnects = <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTR &emc>, + <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTW &emc>; + interconnect-names = "dma-mem", "write"; + iommus = <&smmu_niso1 TEGRA234_SID_XUSB_HOST>; + + power-domains = <&bpmp TEGRA234_POWER_DOMAIN_XUSBC>, + <&bpmp TEGRA234_POWER_DOMAIN_XUSBA>; + power-domain-names = "xusb_host", "xusb_ss"; + + nvidia,xusb-padctl = <&xusb_padctl>; + + phys = <&pad_lanes_usb2_0>; + phy-names = "usb2-0"; + }; -- cgit v1.2.3 From 1b17df99730ab63b49848e61ef19d8ee583684c5 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Wed, 11 Jan 2023 11:04:47 +0000 Subject: arm64: tegra: Enable XUSB host function on Jetson AGX Orin This commit enables XUSB host and pad controller on Jetson AGX Orin. Signed-off-by: Wayne Chang Signed-off-by: Thierry Reding Signed-off-by: Jon Hunter Link: https://lore.kernel.org/r/20230111110450.24617-4-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- .../arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi | 48 +++++++ .../dts/nvidia/tegra234-p3737-0000+p3701-0000.dts | 93 +++++++++++++ arch/arm64/boot/dts/nvidia/tegra234.dtsi | 145 +++++++++++++++++++++ 3 files changed, 286 insertions(+) diff --git a/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi b/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi index 8b86ea9cb50c..4fae2547e90e 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi @@ -67,6 +67,29 @@ non-removable; }; + padctl@3520000 { + vclamp-usb-supply = <&vdd_ao_1v8>; + avdd-usb-supply = <&vdd_ao_3v3>; + + ports { + usb2-0 { + vbus-supply = <&vdd_5v0_sys>; + }; + + usb2-1 { + vbus-supply = <&vdd_5v0_sys>; + }; + + usb2-2 { + vbus-supply = <&vdd_5v0_sys>; + }; + + usb2-3 { + vbus-supply = <&vdd_5v0_sys>; + }; + }; + }; + rtc@c2a0000 { status = "okay"; }; @@ -75,4 +98,29 @@ nvidia,invert-interrupt; }; }; + + vdd_5v0_sys: regulator-vdd-5v0-sys { + compatible = "regulator-fixed"; + regulator-name = "VIN_SYS_5V0"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-always-on; + regulator-boot-on; + }; + + vdd_ao_1v8: regulator-vdd-1v8-ao { + compatible = "regulator-fixed"; + regulator-name = "vdd-AO-1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + vdd_ao_3v3: regulator-vdd-3v3-ao { + compatible = "regulator-fixed"; + regulator-name = "vdd-AO-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; }; diff --git a/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts b/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts index 96aa2267b06d..32c58aa00035 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts +++ b/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts @@ -2022,6 +2022,99 @@ nvidia,model = "NVIDIA Jetson AGX Orin HDA"; status = "okay"; }; + + padctl@3520000 { + status = "okay"; + + pads { + usb2 { + lanes { + usb2-0 { + status = "okay"; + }; + + usb2-1 { + status = "okay"; + }; + + usb2-2 { + status = "okay"; + }; + + usb2-3 { + status = "okay"; + }; + }; + }; + + usb3 { + lanes { + usb3-0 { + status = "okay"; + }; + + usb3-1 { + status = "okay"; + }; + + usb3-2 { + status = "okay"; + }; + }; + }; + }; + + ports { + usb2-0 { + mode = "host"; + status = "okay"; + }; + + usb2-1 { + mode = "host"; + status = "okay"; + }; + + usb2-2 { + mode = "host"; + status = "okay"; + }; + + usb2-3 { + mode = "host"; + status = "okay"; + }; + + usb3-0 { + nvidia,usb2-companion = <1>; + status = "okay"; + }; + + usb3-1 { + nvidia,usb2-companion = <0>; + status = "okay"; + }; + + usb3-2 { + nvidia,usb2-companion = <3>; + status = "okay"; + }; + }; + }; + + usb@3610000 { + status = "okay"; + + phys = <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-0}>, + <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-1}>, + <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-2}>, + <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-3}>, + <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-0}>, + <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-1}>, + <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-2}>; + phy-names = "usb2-0", "usb2-1", "usb2-2", "usb2-3", + "usb3-0", "usb3-1", "usb3-2"; + }; }; chosen { diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi index eaf05ee9acd1..b1bc8b5dcd7d 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi @@ -1079,6 +1079,151 @@ status = "disabled"; }; + xusb_padctl: padctl@3520000 { + compatible = "nvidia,tegra234-xusb-padctl"; + reg = <0x03520000 0x20000>, + <0x03540000 0x10000>; + reg-names = "padctl", "ao"; + interrupts = ; + + resets = <&bpmp TEGRA234_RESET_XUSB_PADCTL>; + reset-names = "padctl"; + + status = "disabled"; + + pads { + usb2 { + clocks = <&bpmp TEGRA234_CLK_USB2_TRK>; + clock-names = "trk"; + + lanes { + usb2-0 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-1 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-2 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb2-3 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + }; + }; + + usb3 { + lanes { + usb3-0 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb3-1 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb3-2 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + + usb3-3 { + nvidia,function = "xusb"; + status = "disabled"; + #phy-cells = <0>; + }; + }; + }; + }; + + ports { + usb2-0 { + status = "disabled"; + }; + + usb2-1 { + status = "disabled"; + }; + + usb2-2 { + status = "disabled"; + }; + + usb2-3 { + status = "disabled"; + }; + + usb3-0 { + status = "disabled"; + }; + + usb3-1 { + status = "disabled"; + }; + + usb3-2 { + status = "disabled"; + }; + + usb3-3 { + status = "disabled"; + }; + }; + }; + + usb@3610000 { + compatible = "nvidia,tegra234-xusb"; + reg = <0x03610000 0x40000>, + <0x03600000 0x10000>, + <0x03650000 0x10000>; + reg-names = "hcd", "fpci", "bar2"; + + interrupts = , + ; + + clocks = <&bpmp TEGRA234_CLK_XUSB_CORE_HOST>, + <&bpmp TEGRA234_CLK_XUSB_FALCON>, + <&bpmp TEGRA234_CLK_XUSB_CORE_SS>, + <&bpmp TEGRA234_CLK_XUSB_SS>, + <&bpmp TEGRA234_CLK_CLK_M>, + <&bpmp TEGRA234_CLK_XUSB_FS>, + <&bpmp TEGRA234_CLK_UTMIP_PLL>, + <&bpmp TEGRA234_CLK_CLK_M>, + <&bpmp TEGRA234_CLK_PLLE>; + clock-names = "xusb_host", "xusb_falcon_src", + "xusb_ss", "xusb_ss_src", "xusb_hs_src", + "xusb_fs_src", "pll_u_480m", "clk_m", + "pll_e"; + interconnects = <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTR &emc>, + <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTW &emc>; + interconnect-names = "dma-mem", "write"; + iommus = <&smmu_niso1 TEGRA234_SID_XUSB_HOST>; + + power-domains = <&bpmp TEGRA234_POWER_DOMAIN_XUSBC>, + <&bpmp TEGRA234_POWER_DOMAIN_XUSBA>; + power-domain-names = "xusb_host", "xusb_ss"; + + nvidia,xusb-padctl = <&xusb_padctl>; + dma-coherent; + status = "disabled"; + }; + fuse@3810000 { compatible = "nvidia,tegra234-efuse"; reg = <0x03810000 0x10000>; -- cgit v1.2.3 From 71d9e899584e11bbd7eaf9934a619c69a15060d8 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Wed, 11 Jan 2023 11:04:48 +0000 Subject: phy: tegra: xusb: Disable trk clk when not in use Pad tracking is a one-time calibration for Tegra186 and Tegra194. Clk should be disabled after calibration. Disable clk after calibration. While at it add 100us delay for HW recording the calibration value. Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Link: https://lore.kernel.org/r/20230111110450.24617-5-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/phy/tegra/xusb-tegra186.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index 6a8bd87cfdbd..c00d14f27ab4 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -609,6 +609,10 @@ static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl) value &= ~USB2_PD_TRK; padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + udelay(100); + + clk_disable_unprepare(priv->usb2_trk_clk); + mutex_unlock(&padctl->lock); } @@ -633,8 +637,6 @@ static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl) value |= USB2_PD_TRK; padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); - clk_disable_unprepare(priv->usb2_trk_clk); - mutex_unlock(&padctl->lock); } -- cgit v1.2.3 From d8163a32ca95c6e23cd449868ad12008569ac17a Mon Sep 17 00:00:00 2001 From: Sing-Han Chen Date: Wed, 11 Jan 2023 11:04:49 +0000 Subject: phy: tegra: xusb: Add Tegra234 support Add support for the XUSB pad controller found on Tegra234 SoCs. It is mostly similar to the same IP found on Tegra194, because most of the Tegra234 XUSB PADCTL registers definition and programming sequence are the same as Tegra194, Tegra234 XUSB PADCTL can share the same driver with Tegra186 and Tegra194 XUSB PADCTL. Introduce a new feature, USB2 HW tracking, for Tegra234. The feature is to enable HW periodical PAD tracking which measure and capture the electric parameters of USB2.0 PAD. Signed-off-by: Sing-Han Chen Co-developed-by: Wayne Chang Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Link: https://lore.kernel.org/r/20230111110450.24617-6-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/phy/tegra/Makefile | 1 + drivers/phy/tegra/xusb-tegra186.c | 64 +++++++++++++++++++++++++++++++++++++-- drivers/phy/tegra/xusb.c | 6 ++++ drivers/phy/tegra/xusb.h | 23 ++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) diff --git a/drivers/phy/tegra/Makefile b/drivers/phy/tegra/Makefile index 89b84067cb4c..eeeea72de117 100644 --- a/drivers/phy/tegra/Makefile +++ b/drivers/phy/tegra/Makefile @@ -7,4 +7,5 @@ phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_132_SOC) += xusb-tegra124.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_210_SOC) += xusb-tegra210.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_186_SOC) += xusb-tegra186.o phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_194_SOC) += xusb-tegra186.o +phy-tegra-xusb-$(CONFIG_ARCH_TEGRA_234_SOC) += xusb-tegra186.o obj-$(CONFIG_PHY_TEGRA194_P2U) += phy-tegra194-p2u.o diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c index c00d14f27ab4..1aae8535f452 100644 --- a/drivers/phy/tegra/xusb-tegra186.c +++ b/drivers/phy/tegra/xusb-tegra186.c @@ -89,6 +89,11 @@ #define USB2_TRK_START_TIMER(x) (((x) & 0x7f) << 12) #define USB2_TRK_DONE_RESET_TIMER(x) (((x) & 0x7f) << 19) #define USB2_PD_TRK BIT(26) +#define USB2_TRK_COMPLETED BIT(31) + +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL2 0x28c +#define USB2_TRK_HW_MODE BIT(0) +#define CYA_TRK_CODE_UPDATE_ON_IDLE BIT(31) #define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20) #define HSIC_PD_TX_DATA0 BIT(1) @@ -609,9 +614,31 @@ static void tegra186_utmi_bias_pad_power_on(struct tegra_xusb_padctl *padctl) value &= ~USB2_PD_TRK; padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); - udelay(100); + if (padctl->soc->poll_trk_completed) { + err = padctl_readl_poll(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1, + USB2_TRK_COMPLETED, USB2_TRK_COMPLETED, 100); + if (err) { + /* The failure with polling on trk complete will not + * cause the failure of powering on the bias pad. + */ + dev_warn(dev, "failed to poll USB2 trk completed: %d\n", err); + } - clk_disable_unprepare(priv->usb2_trk_clk); + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + value |= USB2_TRK_COMPLETED; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + } else { + udelay(100); + } + + if (padctl->soc->trk_hw_mode) { + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL2); + value |= USB2_TRK_HW_MODE; + value &= ~CYA_TRK_CODE_UPDATE_ON_IDLE; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL2); + } else { + clk_disable_unprepare(priv->usb2_trk_clk); + } mutex_unlock(&padctl->lock); } @@ -637,6 +664,13 @@ static void tegra186_utmi_bias_pad_power_off(struct tegra_xusb_padctl *padctl) value |= USB2_PD_TRK; padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + if (padctl->soc->trk_hw_mode) { + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL2); + value &= ~USB2_TRK_HW_MODE; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL2); + clk_disable_unprepare(priv->usb2_trk_clk); + } + mutex_unlock(&padctl->lock); } @@ -1559,7 +1593,8 @@ const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc = { EXPORT_SYMBOL_GPL(tegra186_xusb_padctl_soc); #endif -#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) +#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \ + IS_ENABLED(CONFIG_ARCH_TEGRA_234_SOC) static const char * const tegra194_xusb_padctl_supply_names[] = { "avdd-usb", "vclamp-usb", @@ -1615,8 +1650,31 @@ const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = { .supply_names = tegra194_xusb_padctl_supply_names, .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), .supports_gen2 = true, + .poll_trk_completed = true, }; EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc); + +const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = { + .num_pads = ARRAY_SIZE(tegra194_pads), + .pads = tegra194_pads, + .ports = { + .usb2 = { + .ops = &tegra186_usb2_port_ops, + .count = 4, + }, + .usb3 = { + .ops = &tegra186_usb3_port_ops, + .count = 4, + }, + }, + .ops = &tegra186_xusb_padctl_ops, + .supply_names = tegra194_xusb_padctl_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names), + .supports_gen2 = true, + .poll_trk_completed = true, + .trk_hw_mode = true, +}; +EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc); #endif MODULE_AUTHOR("JC Kuo "); diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index ff4b930879f3..3707a0b5c1ae 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -71,6 +71,12 @@ static const struct of_device_id tegra_xusb_padctl_of_match[] = { .compatible = "nvidia,tegra194-xusb-padctl", .data = &tegra194_xusb_padctl_soc, }, +#endif +#if defined(CONFIG_ARCH_TEGRA_234_SOC) + { + .compatible = "nvidia,tegra234-xusb-padctl", + .data = &tegra234_xusb_padctl_soc, + }, #endif { } }; diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index c384734a61c2..8bd6cd281119 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -8,6 +8,7 @@ #define __PHY_TEGRA_XUSB_H #include +#include #include #include @@ -431,6 +432,8 @@ struct tegra_xusb_padctl_soc { unsigned int num_supplies; bool supports_gen2; bool need_fake_usb3_port; + bool poll_trk_completed; + bool trk_hw_mode; }; struct tegra_xusb_padctl { @@ -473,6 +476,23 @@ static inline u32 padctl_readl(struct tegra_xusb_padctl *padctl, return value; } +static inline u32 padctl_readl_poll(struct tegra_xusb_padctl *padctl, + unsigned long offset, u32 val, u32 mask, + int us) +{ + u32 regval; + int err; + + err = readl_poll_timeout(padctl->regs + offset, regval, + (regval & mask) == val, 1, us); + if (err) { + dev_err(padctl->dev, "%08lx poll timeout > %08x\n", offset, + regval); + } + + return err; +} + struct tegra_xusb_lane *tegra_xusb_find_lane(struct tegra_xusb_padctl *padctl, const char *name, unsigned int index); @@ -489,5 +509,8 @@ extern const struct tegra_xusb_padctl_soc tegra186_xusb_padctl_soc; #if defined(CONFIG_ARCH_TEGRA_194_SOC) extern const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc; #endif +#if defined(CONFIG_ARCH_TEGRA_234_SOC) +extern const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc; +#endif #endif /* __PHY_TEGRA_XUSB_H */ -- cgit v1.2.3 From ee0e40efc4d1bd87c795233c8ceadf4228479c06 Mon Sep 17 00:00:00 2001 From: Sing-Han Chen Date: Wed, 11 Jan 2023 11:04:50 +0000 Subject: usb: host: xhci-tegra: Add Tegra234 XHCI support This change adds Tegra234 XUSB host mode controller support. In Tegra234, some of the registers have moved to bar2 space. The new soc variable has_bar2 indicates the chip with bar2 area. This patch adds new reg helper to let the driver reuse the same code for those chips with bar2 support. Signed-off-by: Sing-Han Chen Co-developed-by: Wayne Chang Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Link: https://lore.kernel.org/r/20230111110450.24617-7-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 267 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 228 insertions(+), 39 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index bdb776553826..3b1ef120d4ca 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -44,6 +44,9 @@ #define XUSB_CFG_4 0x010 #define XUSB_BASE_ADDR_SHIFT 15 #define XUSB_BASE_ADDR_MASK 0x1ffff +#define XUSB_CFG_7 0x01c +#define XUSB_BASE2_ADDR_SHIFT 16 +#define XUSB_BASE2_ADDR_MASK 0xffff #define XUSB_CFG_16 0x040 #define XUSB_CFG_24 0x060 #define XUSB_CFG_AXI_CFG 0x0f8 @@ -75,6 +78,20 @@ #define MBOX_SMI_INTR_FW_HANG BIT(1) #define MBOX_SMI_INTR_EN BIT(3) +/* BAR2 registers */ +#define XUSB_BAR2_ARU_MBOX_CMD 0x004 +#define XUSB_BAR2_ARU_MBOX_DATA_IN 0x008 +#define XUSB_BAR2_ARU_MBOX_DATA_OUT 0x00c +#define XUSB_BAR2_ARU_MBOX_OWNER 0x010 +#define XUSB_BAR2_ARU_SMI_INTR 0x014 +#define XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0 0x01c +#define XUSB_BAR2_ARU_IFRDMA_CFG0 0x0e0 +#define XUSB_BAR2_ARU_IFRDMA_CFG1 0x0e4 +#define XUSB_BAR2_ARU_IFRDMA_STREAMID_FIELD 0x0e8 +#define XUSB_BAR2_ARU_C11_CSBRANGE 0x9c +#define XUSB_BAR2_ARU_FW_SCRATCH 0x1000 +#define XUSB_BAR2_CSB_BASE_ADDR 0x2000 + /* IPFS registers */ #define IPFS_XUSB_HOST_MSI_BAR_SZ_0 0x0c0 #define IPFS_XUSB_HOST_MSI_AXI_BAR_ST_0 0x0c4 @@ -111,6 +128,9 @@ #define IMFILLRNG1_TAG_HI_SHIFT 16 #define XUSB_FALC_IMFILLCTL 0x158 +/* CSB ARU registers */ +#define XUSB_CSB_ARU_SCRATCH0 0x100100 + /* MP CSB registers */ #define XUSB_CSB_MP_ILOAD_ATTR 0x101a00 #define XUSB_CSB_MP_ILOAD_BASE_LO 0x101a04 @@ -131,6 +151,9 @@ #define IMEM_BLOCK_SIZE 256 +#define FW_IOCTL_TYPE_SHIFT 24 +#define FW_IOCTL_CFGTBL_READ 17 + struct tegra_xusb_fw_header { __le32 boot_loadaddr_in_imem; __le32 boot_codedfi_offset; @@ -175,6 +198,7 @@ struct tegra_xusb_mbox_regs { u16 data_in; u16 data_out; u16 owner; + u16 smi_intr; }; struct tegra_xusb_context_soc { @@ -189,6 +213,14 @@ struct tegra_xusb_context_soc { } fpci; }; +struct tegra_xusb; +struct tegra_xusb_soc_ops { + u32 (*mbox_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*mbox_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); + u32 (*csb_reg_readl)(struct tegra_xusb *tegra, unsigned int offset); + void (*csb_reg_writel)(struct tegra_xusb *tegra, u32 value, unsigned int offset); +}; + struct tegra_xusb_soc { const char *firmware; const char * const *supply_names; @@ -205,11 +237,14 @@ struct tegra_xusb_soc { } ports; struct tegra_xusb_mbox_regs mbox; + const struct tegra_xusb_soc_ops *ops; bool scale_ss_clock; bool has_ipfs; bool lpm_support; bool otg_reset_sspi; + + bool has_bar2; }; struct tegra_xusb_context { @@ -230,6 +265,8 @@ struct tegra_xusb { void __iomem *ipfs_base; void __iomem *fpci_base; + void __iomem *bar2_base; + struct resource *bar2; const struct tegra_xusb_soc *soc; @@ -300,7 +337,33 @@ static inline void ipfs_writel(struct tegra_xusb *tegra, u32 value, writel(value, tegra->ipfs_base + offset); } +static inline u32 bar2_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + return readl(tegra->bar2_base + offset); +} + +static inline void bar2_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + writel(value, tegra->bar2_base + offset); +} + static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + return ops->csb_reg_readl(tegra, offset); +} + +static void csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; + + ops->csb_reg_writel(tegra, value, offset); +} + +static u32 fpci_csb_readl(struct tegra_xusb *tegra, unsigned int offset) { u32 page = CSB_PAGE_SELECT(offset); u32 ofs = CSB_PAGE_OFFSET(offset); @@ -310,8 +373,8 @@ static u32 csb_readl(struct tegra_xusb *tegra, unsigned int offset) return fpci_readl(tegra, XUSB_CFG_CSB_BASE_ADDR + ofs); } -static void csb_writel(struct tegra_xusb *tegra, u32 value, - unsigned int offset) +static void fpci_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) { u32 page = CSB_PAGE_SELECT(offset); u32 ofs = CSB_PAGE_OFFSET(offset); @@ -320,6 +383,26 @@ static void csb_writel(struct tegra_xusb *tegra, u32 value, fpci_writel(tegra, value, XUSB_CFG_CSB_BASE_ADDR + ofs); } +static u32 bar2_csb_readl(struct tegra_xusb *tegra, unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + + return bar2_readl(tegra, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + +static void bar2_csb_writel(struct tegra_xusb *tegra, u32 value, + unsigned int offset) +{ + u32 page = CSB_PAGE_SELECT(offset); + u32 ofs = CSB_PAGE_OFFSET(offset); + + bar2_writel(tegra, page, XUSB_BAR2_ARU_C11_CSBRANGE); + bar2_writel(tegra, value, XUSB_BAR2_CSB_BASE_ADDR + ofs); +} + static int tegra_xusb_set_ss_clk(struct tegra_xusb *tegra, unsigned long rate) { @@ -451,6 +534,7 @@ static bool tegra_xusb_mbox_cmd_requires_ack(enum tegra_xusb_mbox_cmd cmd) static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, const struct tegra_xusb_mbox_msg *msg) { + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; bool wait_for_idle = false; u32 value; @@ -459,15 +543,15 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, * ACK/NAK messages. */ if (!(msg->cmd == MBOX_CMD_ACK || msg->cmd == MBOX_CMD_NAK)) { - value = fpci_readl(tegra, tegra->soc->mbox.owner); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_NONE) { dev_err(tegra->dev, "mailbox is busy\n"); return -EBUSY; } - fpci_writel(tegra, MBOX_OWNER_SW, tegra->soc->mbox.owner); + ops->mbox_reg_writel(tegra, MBOX_OWNER_SW, tegra->soc->mbox.owner); - value = fpci_readl(tegra, tegra->soc->mbox.owner); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_SW) { dev_err(tegra->dev, "failed to acquire mailbox\n"); return -EBUSY; @@ -477,17 +561,17 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, } value = tegra_xusb_mbox_pack(msg); - fpci_writel(tegra, value, tegra->soc->mbox.data_in); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.data_in); - value = fpci_readl(tegra, tegra->soc->mbox.cmd); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); value |= MBOX_INT_EN | MBOX_DEST_FALC; - fpci_writel(tegra, value, tegra->soc->mbox.cmd); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); if (wait_for_idle) { unsigned long timeout = jiffies + msecs_to_jiffies(250); while (time_before(jiffies, timeout)) { - value = fpci_readl(tegra, tegra->soc->mbox.owner); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value == MBOX_OWNER_NONE) break; @@ -495,7 +579,7 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, } if (time_after(jiffies, timeout)) - value = fpci_readl(tegra, tegra->soc->mbox.owner); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.owner); if (value != MBOX_OWNER_NONE) return -ETIMEDOUT; @@ -507,11 +591,12 @@ static int tegra_xusb_mbox_send(struct tegra_xusb *tegra, static irqreturn_t tegra_xusb_mbox_irq(int irq, void *data) { struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; u32 value; /* clear mailbox interrupts */ - value = fpci_readl(tegra, XUSB_CFG_ARU_SMI_INTR); - fpci_writel(tegra, value, XUSB_CFG_ARU_SMI_INTR); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.smi_intr); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.smi_intr); if (value & MBOX_SMI_INTR_FW_HANG) dev_err(tegra->dev, "controller firmware hang\n"); @@ -664,6 +749,7 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) { struct tegra_xusb *tegra = data; + const struct tegra_xusb_soc_ops *ops = tegra->soc->ops; struct tegra_xusb_mbox_msg msg; u32 value; @@ -672,16 +758,16 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) if (pm_runtime_suspended(tegra->dev) || tegra->suspended) goto out; - value = fpci_readl(tegra, tegra->soc->mbox.data_out); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.data_out); tegra_xusb_mbox_unpack(&msg, value); - value = fpci_readl(tegra, tegra->soc->mbox.cmd); + value = ops->mbox_reg_readl(tegra, tegra->soc->mbox.cmd); value &= ~MBOX_DEST_SMI; - fpci_writel(tegra, value, tegra->soc->mbox.cmd); + ops->mbox_reg_writel(tegra, value, tegra->soc->mbox.cmd); /* clear mailbox owner if no ACK/NAK is required */ if (!tegra_xusb_mbox_cmd_requires_ack(msg.cmd)) - fpci_writel(tegra, MBOX_OWNER_NONE, tegra->soc->mbox.owner); + ops->mbox_reg_writel(tegra, MBOX_OWNER_NONE, tegra->soc->mbox.owner); tegra_xusb_mbox_handle(tegra, &msg); @@ -709,6 +795,15 @@ static void tegra_xusb_config(struct tegra_xusb *tegra) value |= regs & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); fpci_writel(tegra, value, XUSB_CFG_4); + /* Program BAR2 space */ + if (tegra->bar2) { + value = fpci_readl(tegra, XUSB_CFG_7); + value &= ~(XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + value |= tegra->bar2->start & + (XUSB_BASE2_ADDR_MASK << XUSB_BASE2_ADDR_SHIFT); + fpci_writel(tegra, value, XUSB_CFG_7); + } + usleep_range(100, 200); /* Enable bus master */ @@ -881,21 +976,36 @@ static int tegra_xusb_request_firmware(struct tegra_xusb *tegra) return 0; } -static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +static int tegra_xusb_wait_for_falcon(struct tegra_xusb *tegra) +{ + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + int ret; + u32 value; + + cap_regs = tegra->regs; + op_regs = tegra->regs + HC_LENGTH(readl(&cap_regs->hc_capbase)); + + ret = readl_poll_timeout(&op_regs->status, value, !(value & STS_CNR), 1000, 200000); + + if (ret) + dev_err(tegra->dev, "XHCI Controller not ready. Falcon state: 0x%x\n", + csb_readl(tegra, XUSB_FALC_CPUCTL)); + + return ret; +} + +static int tegra_xusb_load_firmware_rom(struct tegra_xusb *tegra) { unsigned int code_tag_blocks, code_size_blocks, code_blocks; - struct xhci_cap_regs __iomem *cap = tegra->regs; struct tegra_xusb_fw_header *header; struct device *dev = tegra->dev; - struct xhci_op_regs __iomem *op; - unsigned long timeout; time64_t timestamp; u64 address; u32 value; int err; header = (struct tegra_xusb_fw_header *)tegra->fw.virt; - op = tegra->regs + HC_LENGTH(readl(&cap->hc_capbase)); if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) { dev_info(dev, "Firmware already loaded, Falcon state %#x\n", @@ -968,30 +1078,54 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) /* Boot Falcon CPU and wait for USBSTS_CNR to get cleared. */ csb_writel(tegra, CPUCTL_STARTCPU, XUSB_FALC_CPUCTL); - timeout = jiffies + msecs_to_jiffies(200); + if (tegra_xusb_wait_for_falcon(tegra)) + return -EIO; + + timestamp = le32_to_cpu(header->fwimg_created_time); - do { - value = readl(&op->status); - if ((value & STS_CNR) == 0) - break; + dev_info(dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); + + return 0; +} + +static u32 tegra_xusb_read_firmware_header(struct tegra_xusb *tegra, u32 offset) +{ + /* + * We only accept reading the firmware config table + * The offset should not exceed the fw header structure + */ + if (offset >= sizeof(struct tegra_xusb_fw_header)) + return 0; - usleep_range(1000, 2000); - } while (time_is_after_jiffies(timeout)); + bar2_writel(tegra, (FW_IOCTL_CFGTBL_READ << FW_IOCTL_TYPE_SHIFT) | offset, + XUSB_BAR2_ARU_FW_SCRATCH); + return bar2_readl(tegra, XUSB_BAR2_ARU_SMI_ARU_FW_SCRATCH_DATA0); +} + +static int tegra_xusb_init_ifr_firmware(struct tegra_xusb *tegra) +{ + time64_t timestamp; - value = readl(&op->status); - if (value & STS_CNR) { - value = csb_readl(tegra, XUSB_FALC_CPUCTL); - dev_err(dev, "XHCI controller not read: %#010x\n", value); + if (tegra_xusb_wait_for_falcon(tegra)) return -EIO; - } - timestamp = le32_to_cpu(header->fwimg_created_time); +#define offsetof_32(X, Y) ((u8)(offsetof(X, Y) / sizeof(__le32))) + timestamp = tegra_xusb_read_firmware_header(tegra, offsetof_32(struct tegra_xusb_fw_header, + fwimg_created_time) << 2); - dev_info(dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); + dev_info(tegra->dev, "Firmware timestamp: %ptTs UTC\n", ×tamp); return 0; } +static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) +{ + if (!tegra->soc->firmware) + return tegra_xusb_init_ifr_firmware(tegra); + else + return tegra_xusb_load_firmware_rom(tegra); +} + static void tegra_xusb_powerdomain_remove(struct device *dev, struct tegra_xusb *tegra) { @@ -1435,6 +1569,10 @@ static int tegra_xusb_probe(struct platform_device *pdev) tegra->ipfs_base = devm_platform_ioremap_resource(pdev, 2); if (IS_ERR(tegra->ipfs_base)) return PTR_ERR(tegra->ipfs_base); + } else if (tegra->soc->has_bar2) { + tegra->bar2_base = devm_platform_get_and_ioremap_resource(pdev, 2, &tegra->bar2); + if (IS_ERR(tegra->bar2_base)) + return PTR_ERR(tegra->bar2_base); } tegra->xhci_irq = platform_get_irq(pdev, 0); @@ -1651,10 +1789,13 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto disable_phy; } - err = tegra_xusb_request_firmware(tegra); - if (err < 0) { - dev_err(&pdev->dev, "failed to request firmware: %d\n", err); - goto disable_phy; + if (tegra->soc->firmware) { + err = tegra_xusb_request_firmware(tegra); + if (err < 0) { + dev_err(&pdev->dev, + "failed to request firmware: %d\n", err); + goto disable_phy; + } } err = tegra_xusb_unpowergate_partitions(tegra); @@ -2271,6 +2412,13 @@ static const struct tegra_xusb_context_soc tegra124_xusb_context = { }, }; +static const struct tegra_xusb_soc_ops tegra124_ops = { + .mbox_reg_readl = &fpci_readl, + .mbox_reg_writel = &fpci_writel, + .csb_reg_readl = &fpci_csb_readl, + .csb_reg_writel = &fpci_csb_writel, +}; + static const struct tegra_xusb_soc tegra124_soc = { .firmware = "nvidia/tegra124/xusb.bin", .supply_names = tegra124_supply_names, @@ -2286,11 +2434,13 @@ static const struct tegra_xusb_soc tegra124_soc = { .scale_ss_clock = true, .has_ipfs = true, .otg_reset_sspi = false, + .ops = &tegra124_ops, .mbox = { .cmd = 0xe4, .data_in = 0xe8, .data_out = 0xec, .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, }; MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); @@ -2322,11 +2472,13 @@ static const struct tegra_xusb_soc tegra210_soc = { .scale_ss_clock = false, .has_ipfs = true, .otg_reset_sspi = true, + .ops = &tegra124_ops, .mbox = { .cmd = 0xe4, .data_in = 0xe8, .data_out = 0xec, .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, }; MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); @@ -2363,11 +2515,13 @@ static const struct tegra_xusb_soc tegra186_soc = { .scale_ss_clock = false, .has_ipfs = false, .otg_reset_sspi = false, + .ops = &tegra124_ops, .mbox = { .cmd = 0xe4, .data_in = 0xe8, .data_out = 0xec, .owner = 0xf0, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, .lpm_support = true, }; @@ -2394,21 +2548,56 @@ static const struct tegra_xusb_soc tegra194_soc = { .scale_ss_clock = false, .has_ipfs = false, .otg_reset_sspi = false, + .ops = &tegra124_ops, .mbox = { .cmd = 0x68, .data_in = 0x6c, .data_out = 0x70, .owner = 0x74, + .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, .lpm_support = true, }; MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); +static const struct tegra_xusb_soc_ops tegra234_ops = { + .mbox_reg_readl = &bar2_readl, + .mbox_reg_writel = &bar2_writel, + .csb_reg_readl = &bar2_csb_readl, + .csb_reg_writel = &bar2_csb_writel, +}; + +static const struct tegra_xusb_soc tegra234_soc = { + .supply_names = tegra194_supply_names, + .num_supplies = ARRAY_SIZE(tegra194_supply_names), + .phy_types = tegra194_phy_types, + .num_types = ARRAY_SIZE(tegra194_phy_types), + .context = &tegra186_xusb_context, + .ports = { + .usb3 = { .offset = 0, .count = 4, }, + .usb2 = { .offset = 4, .count = 4, }, + }, + .scale_ss_clock = false, + .has_ipfs = false, + .otg_reset_sspi = false, + .ops = &tegra234_ops, + .mbox = { + .cmd = XUSB_BAR2_ARU_MBOX_CMD, + .data_in = XUSB_BAR2_ARU_MBOX_DATA_IN, + .data_out = XUSB_BAR2_ARU_MBOX_DATA_OUT, + .owner = XUSB_BAR2_ARU_MBOX_OWNER, + .smi_intr = XUSB_BAR2_ARU_SMI_INTR, + }, + .lpm_support = true, + .has_bar2 = true, +}; + static const struct of_device_id tegra_xusb_of_match[] = { { .compatible = "nvidia,tegra124-xusb", .data = &tegra124_soc }, { .compatible = "nvidia,tegra210-xusb", .data = &tegra210_soc }, { .compatible = "nvidia,tegra186-xusb", .data = &tegra186_soc }, { .compatible = "nvidia,tegra194-xusb", .data = &tegra194_soc }, + { .compatible = "nvidia,tegra234-xusb", .data = &tegra234_soc }, { }, }; MODULE_DEVICE_TABLE(of, tegra_xusb_of_match); -- cgit v1.2.3 From 592338dde85411b1da74fa6b463e2a047c2545ab Mon Sep 17 00:00:00 2001 From: Jim Lin Date: Fri, 11 Nov 2022 18:18:11 +0800 Subject: xhci: Add hub_control to xhci_driver_overrides Add a hub_control() callback to the xhci_driver_overrides structure to allow host drivers to override the default hub_control function. This is required for Tegra which requires device specific actions for power management to be executed during USB state transitions. Signed-off-by: Jim Lin Reviewed-by: Jon Hunter Tested-by: Jon Hunter Acked-by: Thierry Reding Acked-by: Mathias Nyman Link: https://lore.kernel.org/r/20221111101813.32482-2-jilin@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 2 ++ drivers/usb/host/xhci.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 79d7931c048a..748c7158198f 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5502,6 +5502,8 @@ void xhci_init_driver(struct hc_driver *drv, drv->check_bandwidth = over->check_bandwidth; if (over->reset_bandwidth) drv->reset_bandwidth = over->reset_bandwidth; + if (over->hub_control) + drv->hub_control = over->hub_control; } } EXPORT_SYMBOL_GPL(xhci_init_driver); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c9f06c5e4e9d..f71841812f06 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1943,6 +1943,8 @@ struct xhci_driver_overrides { struct usb_host_endpoint *ep); int (*check_bandwidth)(struct usb_hcd *, struct usb_device *); void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *); + int (*hub_control)(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); }; #define XHCI_CFC_DELAY 10 -- cgit v1.2.3 From 2cbe475fe733a70eda30049ee8edf06f296c9402 Mon Sep 17 00:00:00 2001 From: Jim Lin Date: Fri, 11 Nov 2022 18:18:12 +0800 Subject: xhci: hub: export symbol on xhci_hub_control XHCI host drivers may override the default xhci_hub_control() with their own device specific function. To allow these host drivers to call the xhci_hub_control() function from within their own hub_control() callback and be built as a module, export the symbol for xhci_hub_control. Signed-off-by: Jim Lin Reviewed-by: Jon Hunter Tested-by: Jon Hunter Acked-by: Thierry Reding Acked-by: Mathias Nyman Link: https://lore.kernel.org/r/20221111101813.32482-3-jilin@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 94c94db3faf6..7750a5eed435 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1623,6 +1623,7 @@ error: spin_unlock_irqrestore(&xhci->lock, flags); return retval; } +EXPORT_SYMBOL_GPL(xhci_hub_control); /* * Returns 0 if the status hasn't changed, or the number of bytes in buf. -- cgit v1.2.3 From a30951d31b250bf3479c00e93646b6cc6fb42a56 Mon Sep 17 00:00:00 2001 From: Petlozu Pravareshwar Date: Fri, 11 Nov 2022 18:18:13 +0800 Subject: xhci: tegra: USB2 pad power controls Program USB2 pad PD controls during port connect/disconnect, port suspend/resume, and test mode, to reduce power consumption on disconnect or suspend. Signed-off-by: Petlozu Pravareshwar Co-developed-by: Jim Lin Signed-off-by: Jim Lin Acked-by: Thierry Reding Reviewed-by: Jon Hunter Tested-by: Jon Hunter Acked-by: Mathias Nyman Link: https://lore.kernel.org/r/20221111101813.32482-4-jilin@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 3b1ef120d4ca..1ff22f675930 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -311,6 +311,7 @@ struct tegra_xusb { bool suspended; struct tegra_xusb_context context; + u8 lp0_utmi_pad_mask; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; @@ -2092,10 +2093,24 @@ static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) struct tegra_xusb_padctl *padctl = tegra->padctl; unsigned int i; + for (i = 0; i < tegra->num_usb_phys; i++) { + struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); + + if (!phy) + continue; + + if (tegra_xusb_padctl_remote_wake_detected(padctl, phy)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + for (i = 0; i < tegra->num_phys; i++) { if (!tegra->phys[i]) continue; + if (tegra_xusb_padctl_remote_wake_detected(padctl, tegra->phys[i])) + dev_dbg(tegra->dev, "%pOF remote wake detected\n", + tegra->phys[i]->dev.of_node); + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); } } @@ -2113,6 +2128,28 @@ static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) } } +static void tegra_xhci_program_utmi_power_lp0_exit(struct tegra_xusb *tegra) +{ + unsigned int i, index_to_usb2; + struct phy *phy; + + for (i = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb2") == 0) + index_to_usb2 = i; + } + + for (i = 0; i < tegra->num_usb_phys; i++) { + if (!is_host_mode_phy(tegra, index_to_usb2, i)) + continue; + + phy = tegra_xusb_get_phy(tegra, "usb2", i); + if (tegra->lp0_utmi_pad_mask & BIT(i)) + tegra_phy_xusb_utmi_pad_power_on(phy); + else + tegra_phy_xusb_utmi_pad_power_down(phy); + } +} + static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) { struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); @@ -2121,6 +2158,7 @@ static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) unsigned int i; int err; u32 usbcmd; + u32 portsc; dev_dbg(dev, "entering ELPG\n"); @@ -2134,6 +2172,15 @@ static int tegra_xusb_enter_elpg(struct tegra_xusb *tegra, bool runtime) goto out; } + for (i = 0; i < tegra->num_usb_phys; i++) { + if (!xhci->usb2_rhub.ports[i]) + continue; + portsc = readl(xhci->usb2_rhub.ports[i]->addr); + tegra->lp0_utmi_pad_mask &= ~BIT(i); + if (((portsc & PORT_PLS_MASK) == XDEV_U3) || ((portsc & DEV_SPEED_MASK) == XDEV_FS)) + tegra->lp0_utmi_pad_mask |= BIT(i); + } + err = xhci_suspend(xhci, wakeup); if (err < 0) { dev_err(tegra->dev, "failed to suspend XHCI: %d\n", err); @@ -2207,6 +2254,8 @@ static int tegra_xusb_exit_elpg(struct tegra_xusb *tegra, bool runtime) phy_power_on(tegra->phys[i]); } + if (tegra->suspended) + tegra_xhci_program_utmi_power_lp0_exit(tegra); tegra_xusb_config(tegra); tegra_xusb_restore_context(tegra); @@ -2626,8 +2675,84 @@ static int tegra_xhci_setup(struct usb_hcd *hcd) return xhci_gen_setup(hcd, tegra_xhci_quirks); } +static int tegra_xhci_hub_control(struct usb_hcd *hcd, u16 type_req, u16 value, u16 index, + char *buf, u16 length) +{ + struct tegra_xusb *tegra = dev_get_drvdata(hcd->self.controller); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_hub *rhub; + struct xhci_bus_state *bus_state; + int port = (index & 0xff) - 1; + unsigned int i; + struct xhci_port **ports; + u32 portsc; + int ret; + struct phy *phy; + + rhub = &xhci->usb2_rhub; + bus_state = &rhub->bus_state; + if (bus_state->resuming_ports && hcd->speed == HCD_USB2) { + ports = rhub->ports; + i = rhub->num_ports; + while (i--) { + if (!test_bit(i, &bus_state->resuming_ports)) + continue; + portsc = readl(ports[i]->addr); + if ((portsc & PORT_PLS_MASK) == XDEV_RESUME) + tegra_phy_xusb_utmi_pad_power_on( + tegra_xusb_get_phy(tegra, "usb2", (int) i)); + } + } + + if (hcd->speed == HCD_USB2) { + phy = tegra_xusb_get_phy(tegra, "usb2", port); + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + tegra_phy_xusb_utmi_pad_power_on(phy); + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_RESET)) { + if (!index || index > rhub->num_ports) + return -EPIPE; + ports = rhub->ports; + portsc = readl(ports[port]->addr); + if (portsc & PORT_CONNECT) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + } + + ret = xhci_hub_control(hcd, type_req, value, index, buf, length); + if (ret < 0) + return ret; + + if (hcd->speed == HCD_USB2) { + /* Use phy where we set previously */ + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_SUSPEND)) + /* We don't suspend the PAD while HNP role swap happens on the OTG port */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + + if ((type_req == ClearPortFeature) && (value == USB_PORT_FEAT_C_CONNECTION)) { + ports = rhub->ports; + portsc = readl(ports[port]->addr); + if (!(portsc & PORT_CONNECT)) { + /* We don't suspend the PAD while HNP role swap happens on the OTG + * port + */ + if (!((hcd->self.otg_port == (port + 1)) && hcd->self.b_hnp_enable)) + tegra_phy_xusb_utmi_pad_power_down(phy); + } + } + if ((type_req == SetPortFeature) && (value == USB_PORT_FEAT_TEST)) + tegra_phy_xusb_utmi_pad_power_on(phy); + } + + return ret; +} + static const struct xhci_driver_overrides tegra_xhci_overrides __initconst = { .reset = tegra_xhci_setup, + .hub_control = tegra_xhci_hub_control, }; static int __init tegra_xusb_init(void) -- cgit v1.2.3 From 2476de8288cc552cbe98edf3f4d4cc2846cf4b8c Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Thu, 12 Jan 2023 22:16:06 +0000 Subject: usb: typec: Add retimer handle to port altmode Just like it does with muxes, the Type-C bus code can update the state of connected retimers (especially when altmode-related transitions occur). Add a retimer handle to the port altmode struct to enable this. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230112221609.540754-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.h | 2 ++ drivers/usb/typec/class.c | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h index 56dec268d4dd..c89168857417 100644 --- a/drivers/usb/typec/bus.h +++ b/drivers/usb/typec/bus.h @@ -7,11 +7,13 @@ struct bus_type; struct typec_mux; +struct typec_retimer; struct altmode { unsigned int id; struct typec_altmode adev; struct typec_mux *mux; + struct typec_retimer *retimer; enum typec_port_data roles; diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 5897905cb4f0..ed3d070b1ca4 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -583,6 +583,7 @@ void typec_unregister_altmode(struct typec_altmode *adev) { if (IS_ERR_OR_NULL(adev)) return; + typec_retimer_put(to_altmode(adev)->retimer); typec_mux_put(to_altmode(adev)->mux); device_unregister(&adev->dev); } @@ -2108,16 +2109,26 @@ typec_port_register_altmode(struct typec_port *port, { struct typec_altmode *adev; struct typec_mux *mux; + struct typec_retimer *retimer; mux = typec_mux_get(&port->dev, desc); if (IS_ERR(mux)) return ERR_CAST(mux); + retimer = typec_retimer_get(&port->dev); + if (IS_ERR(retimer)) { + typec_mux_put(mux); + return ERR_CAST(retimer); + } + adev = typec_register_altmode(&port->dev, desc); - if (IS_ERR(adev)) + if (IS_ERR(adev)) { + typec_retimer_put(retimer); typec_mux_put(mux); - else + } else { to_altmode(adev)->mux = mux; + to_altmode(adev)->retimer = retimer; + } return adev; } -- cgit v1.2.3 From 2c8cb236ed44d071ef2ed1fbb948eea404551788 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Thu, 12 Jan 2023 22:16:07 +0000 Subject: usb: typec: Add wrapper for bus switch set code Add a wrapper that calls the set() function for various switches associated with a port altmode. Right now, it just wraps the existing typec_mux_set() command, but it can be expanded to include other switches in future patches. No functional changes introduced by this patch. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230112221609.540754-3-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index 31c2a3130cad..9f1bbd26ca47 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -27,6 +27,13 @@ typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) return typec_mux_set(alt->mux, &state); } +/* Wrapper to set various Type-C port switches together. */ +static inline int +typec_altmode_set_switches(struct altmode *alt, unsigned long conf, void *data) +{ + return typec_altmode_set_mux(alt, conf, data); +} + static int typec_altmode_set_state(struct typec_altmode *adev, unsigned long conf, void *data) { @@ -35,7 +42,7 @@ static int typec_altmode_set_state(struct typec_altmode *adev, port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; - return typec_altmode_set_mux(port_altmode, conf, data); + return typec_altmode_set_switches(port_altmode, conf, data); } /* -------------------------------------------------------------------------- */ @@ -73,7 +80,7 @@ int typec_altmode_notify(struct typec_altmode *adev, is_port = is_typec_port(adev->dev.parent); partner = altmode->partner; - ret = typec_altmode_set_mux(is_port ? altmode : partner, conf, data); + ret = typec_altmode_set_switches(is_port ? altmode : partner, conf, data); if (ret) return ret; -- cgit v1.2.3 From 6681e43f5095a64bc3e37822f64d91d284a062dc Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Thu, 12 Jan 2023 22:16:08 +0000 Subject: usb: typec: Make bus switch code retimer-aware Since ports can have retimers associated with them, update the Type-C alternate mode bus code to also set retimer state when the switch state is updated. While we are here, make the typec_retimer_dev_type declaration in the retimer.h file as extern, so that the header file can be successfully included in the bus code without redeclaration compilation errors. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230112221609.540754-4-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/bus.c | 22 ++++++++++++++++++++++ drivers/usb/typec/retimer.h | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index 9f1bbd26ca47..0c8d554240be 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -11,6 +11,22 @@ #include "bus.h" #include "class.h" #include "mux.h" +#include "retimer.h" + +static inline int +typec_altmode_set_retimer(struct altmode *alt, unsigned long conf, void *data) +{ + struct typec_retimer_state state; + + if (!alt->retimer) + return 0; + + state.alt = &alt->adev; + state.mode = conf; + state.data = data; + + return typec_retimer_set(alt->retimer, &state); +} static inline int typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) @@ -31,6 +47,12 @@ typec_altmode_set_mux(struct altmode *alt, unsigned long conf, void *data) static inline int typec_altmode_set_switches(struct altmode *alt, unsigned long conf, void *data) { + int ret; + + ret = typec_altmode_set_retimer(alt, conf, data); + if (ret) + return ret; + return typec_altmode_set_mux(alt, conf, data); } diff --git a/drivers/usb/typec/retimer.h b/drivers/usb/typec/retimer.h index e34bd23323be..d6a5ef9881e1 100644 --- a/drivers/usb/typec/retimer.h +++ b/drivers/usb/typec/retimer.h @@ -12,7 +12,7 @@ struct typec_retimer { #define to_typec_retimer(_dev_) container_of(_dev_, struct typec_retimer, dev) -const struct device_type typec_retimer_dev_type; +extern const struct device_type typec_retimer_dev_type; #define is_typec_retimer(dev) ((dev)->type == &typec_retimer_dev_type) -- cgit v1.2.3 From 599f008c257d913674b2b2f2fa9e273c1058ec2e Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Sat, 14 Jan 2023 01:32:44 -0800 Subject: usb: typec: tcpm: Add callbacks to mitigate wakeups due to contaminant On some of the TCPC implementations, when the Type-C port is exposed to contaminants, such as water, TCPC stops toggling while reporting OPEN either by the time TCPM reads CC pin status or during CC debounce window. This causes TCPM to be stuck in TOGGLING state. If TCPM is made to restart toggling, the behavior recurs causing redundant CPU wakeups till the USB-C port is free of contaminant. [206199.287817] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [206199.640337] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [206199.985789] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] (or) [ 7853.867577] Start toggling [ 7853.889921] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 7855.698765] CC1: 0 -> 0, CC2: 0 -> 5 [state TOGGLING, polarity 0, connected] [ 7855.698790] state change TOGGLING -> SNK_ATTACH_WAIT [rev3 NONE_AMS] [ 7855.698826] pending state change SNK_ATTACH_WAIT -> SNK_DEBOUNCED @ 170 ms [rev3 NONE_AMS] [ 7855.703559] CC1: 0 -> 0, CC2: 5 -> 5 [state SNK_ATTACH_WAIT, polarity 0, connected] [ 7855.856555] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_ATTACH_WAIT, polarity 0, disconnected] [ 7855.856581] state change SNK_ATTACH_WAIT -> SNK_ATTACH_WAIT [rev3 NONE_AMS] [ 7855.856613] pending state change SNK_ATTACH_WAIT -> SNK_UNATTACHED @ 170 ms [rev3 NONE_AMS] [ 7856.027744] state change SNK_ATTACH_WAIT -> SNK_UNATTACHED [delayed 170 ms] [ 7856.181949] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 7856.187896] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 7857.645630] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 7857.647291] CC1: 0 -> 0, CC2: 0 -> 5 [state TOGGLING, polarity 0, connected] [ 7857.647298] state change TOGGLING -> SNK_ATTACH_WAIT [rev3 NONE_AMS] [ 7857.647310] pending state change SNK_ATTACH_WAIT -> SNK_DEBOUNCED @ 170 ms [rev3 NONE_AMS] [ 7857.808106] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_ATTACH_WAIT, polarity 0, disconnected] [ 7857.808123] state change SNK_ATTACH_WAIT -> SNK_ATTACH_WAIT [rev3 NONE_AMS] [ 7857.808150] pending state change SNK_ATTACH_WAIT -> SNK_UNATTACHED @ 170 ms [rev3 NONE_AMS] [ 7857.978727] state change SNK_ATTACH_WAIT -> SNK_UNATTACHED [delayed 170 ms] To mitigate redundant TCPM wakeups, TCPCs which do have the needed hardware can implement the check_contaminant callback which is invoked by TCPM to evaluate for presence of contaminant. Lower level TCPC driver can restart toggling through TCPM_PORT_CLEAN event when the driver detects that USB-C port is free of contaminant. check_contaminant callback also passes the disconnect_while_debounce flag which when true denotes that the CC pins transitioned to OPEN state during the CC debounce window. Signed-off-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230114093246.1933321-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 55 ++++++++++++++++++++++++++++++++++++++++++- include/linux/usb/tcpm.h | 8 +++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 904c7b4ce2f0..c624747f32df 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -36,6 +36,7 @@ #define FOREACH_STATE(S) \ S(INVALID_STATE), \ S(TOGGLING), \ + S(CHECK_CONTAMINANT), \ S(SRC_UNATTACHED), \ S(SRC_ATTACH_WAIT), \ S(SRC_ATTACHED), \ @@ -249,6 +250,7 @@ enum frs_typec_current { #define TCPM_RESET_EVENT BIT(2) #define TCPM_FRS_EVENT BIT(3) #define TCPM_SOURCING_VBUS BIT(4) +#define TCPM_PORT_CLEAN BIT(5) #define LOG_BUFFER_ENTRIES 1024 #define LOG_BUFFER_ENTRY_SIZE 128 @@ -483,6 +485,13 @@ struct tcpm_port { * SNK_READY for non-pd link. */ bool slow_charger_loop; + + /* + * When true indicates that the lower level drivers indicate potential presence + * of contaminant in the connector pins based on the tcpm state machine + * transitions. + */ + bool potential_contaminant; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -647,7 +656,7 @@ static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) /* Do not log while disconnected and unattached */ if (tcpm_port_is_disconnected(port) && (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || - port->state == TOGGLING)) + port->state == TOGGLING || port->state == CHECK_CONTAMINANT)) return; va_start(args, fmt); @@ -3904,15 +3913,28 @@ static void run_state_machine(struct tcpm_port *port) unsigned int msecs; enum tcpm_state upcoming_state; + if (port->tcpc->check_contaminant && port->state != CHECK_CONTAMINANT) + port->potential_contaminant = ((port->enter_state == SRC_ATTACH_WAIT && + port->state == SRC_UNATTACHED) || + (port->enter_state == SNK_ATTACH_WAIT && + port->state == SNK_UNATTACHED)); + port->enter_state = port->state; switch (port->state) { case TOGGLING: break; + case CHECK_CONTAMINANT: + port->tcpc->check_contaminant(port->tcpc); + break; /* SRC states */ case SRC_UNATTACHED: if (!port->non_pd_role_swap) tcpm_swap_complete(port, -ENOTCONN); tcpm_src_detach(port); + if (port->potential_contaminant) { + tcpm_set_state(port, CHECK_CONTAMINANT, 0); + break; + } if (tcpm_start_toggling(port, tcpm_rp_cc(port))) { tcpm_set_state(port, TOGGLING, 0); break; @@ -4150,6 +4172,10 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, -ENOTCONN); tcpm_pps_complete(port, -ENOTCONN); tcpm_snk_detach(port); + if (port->potential_contaminant) { + tcpm_set_state(port, CHECK_CONTAMINANT, 0); + break; + } if (tcpm_start_toggling(port, TYPEC_CC_RD)) { tcpm_set_state(port, TOGGLING, 0); break; @@ -4926,6 +4952,9 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, else if (tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_ATTACH_WAIT, 0); break; + case CHECK_CONTAMINANT: + /* Wait for Toggling to be resumed */ + break; case SRC_UNATTACHED: case ACC_UNATTACHED: if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || @@ -5425,6 +5454,15 @@ static void tcpm_pd_event_handler(struct kthread_work *work) port->vbus_source = true; _tcpm_pd_vbus_on(port); } + if (events & TCPM_PORT_CLEAN) { + tcpm_log(port, "port clean"); + if (port->state == CHECK_CONTAMINANT) { + if (tcpm_start_toggling(port, tcpm_rp_cc(port))) + tcpm_set_state(port, TOGGLING, 0); + else + tcpm_set_state(port, tcpm_default_state(port), 0); + } + } spin_lock(&port->pd_event_lock); } @@ -5477,6 +5515,21 @@ void tcpm_sourcing_vbus(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus); +void tcpm_port_clean(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_PORT_CLEAN; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_port_clean); + +bool tcpm_port_is_toggling(struct tcpm_port *port) +{ + return port->port_type == TYPEC_PORT_DRP && port->state == TOGGLING; +} +EXPORT_SYMBOL_GPL(tcpm_port_is_toggling); + static void tcpm_enable_frs_work(struct kthread_work *work) { struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index bffc8d3e14ad..ab7ca872950b 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -114,6 +114,11 @@ enum tcpm_transmit_type { * Optional; The USB Communications Capable bit indicates if port * partner is capable of communication over the USB data lines * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. + * @check_contaminant: + * Optional; The callback is called when CC pins report open status + * at the end of the deboumce period or when the port is still + * toggling. Chip level drivers are expected to check for contaminant + * and call tcpm_clean_port when the port is clean. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -148,6 +153,7 @@ struct tcpc_dev { bool pps_active, u32 requested_vbus_voltage); bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev); void (*set_partner_usb_comm_capable)(struct tcpc_dev *dev, bool enable); + void (*check_contaminant)(struct tcpc_dev *dev); }; struct tcpm_port; @@ -165,5 +171,7 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, enum tcpm_transmit_status status); void tcpm_pd_hard_reset(struct tcpm_port *port); void tcpm_tcpc_reset(struct tcpm_port *port); +void tcpm_port_clean(struct tcpm_port *port); +bool tcpm_port_is_toggling(struct tcpm_port *port); #endif /* __LINUX_USB_TCPM_H */ -- cgit v1.2.3 From abc028a270f47f86060ef479395d1bb8c2ab6e7e Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Sat, 14 Jan 2023 01:32:45 -0800 Subject: usb: typec: tcpci: Add callback for evaluating contaminant presence This change adds callback to evaluate presence of contaminant in the TCPCI layer. Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20230114093246.1933321-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 11 +++++++++++ include/linux/usb/tcpci.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index c7796511695d..8da23240afbe 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -404,6 +404,14 @@ static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev) tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data); } +static void tcpci_check_contaminant(struct tcpc_dev *dev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->check_contaminant) + tcpci->data->check_contaminant(tcpci, tcpci->data); +} + static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -782,6 +790,9 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; + if (tcpci->data->check_contaminant) + tcpci->tcpc.check_contaminant = tcpci_check_contaminant; + if (tcpci->data->auto_discharge_disconnect) { tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; tcpci->tcpc.set_auto_vbus_discharge_threshold = diff --git a/include/linux/usb/tcpci.h b/include/linux/usb/tcpci.h index 17657451c762..85e95a3251d3 100644 --- a/include/linux/usb/tcpci.h +++ b/include/linux/usb/tcpci.h @@ -188,6 +188,12 @@ struct tcpci; * Optional; The USB Communications Capable bit indicates if port * partner is capable of communication over the USB data lines * (e.g. D+/- or SS Tx/Rx). Called to notify the status of the bit. + * @check_contaminant: + * Optional; The callback is invoked when chiplevel drivers indicated + * that the USB port needs to be checked for contaminant presence. + * Chip level drivers are expected to check for contaminant and call + * tcpm_clean_port when the port is clean to put the port back into + * toggling state. */ struct tcpci_data { struct regmap *regmap; @@ -204,6 +210,7 @@ struct tcpci_data { void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); void (*set_partner_usb_comm_capable)(struct tcpci *tcpci, struct tcpci_data *data, bool capable); + void (*check_contaminant)(struct tcpci *tcpci, struct tcpci_data *data); }; struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); -- cgit v1.2.3 From 02b332a06397ef213790f9561c9c022ce1af1a97 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Sat, 14 Jan 2023 01:32:46 -0800 Subject: usb: typec: maxim_contaminant: Implement check_contaminant callback Maxim TCPC has additional ADCs and low current(1ua) current source to measure the impedance of CC and SBU pins. When tcpm invokes the check_contaminant callback, Maxim TCPC measures the impedance of the CC & SBU pins and when the impedance measured is less than 1MOhm, it is assumed that USB-C port is contaminated. CC comparators are also checked to differentiate between presence of sink and contaminant. Once USB-C is deemed to be contaminated, MAXIM TCPC has additional hardware to disable normal DRP toggling cycle and enable 1ua on CC pins once every 2.4secs/4.8secs. Maxim TCPC interrupts AP once the impedance on the CC pin is above the 1MOhm threshold. The Maxim tcpc driver then signals TCPM_PORT_CLEAN to restart toggling. Renaming tcpci_maxim.c to tcpci_maxim_core.c and moving reg read/write helper functions to the tcpci_maxim.h header file. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20230114093246.1933321-3-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/Makefile | 1 + drivers/usb/typec/tcpm/maxim_contaminant.c | 387 +++++++++++++++++++++ drivers/usb/typec/tcpm/tcpci_maxim.c | 530 ----------------------------- drivers/usb/typec/tcpm/tcpci_maxim.h | 89 +++++ drivers/usb/typec/tcpm/tcpci_maxim_core.c | 519 ++++++++++++++++++++++++++++ 5 files changed, 996 insertions(+), 530 deletions(-) create mode 100644 drivers/usb/typec/tcpm/maxim_contaminant.c delete mode 100644 drivers/usb/typec/tcpm/tcpci_maxim.c create mode 100644 drivers/usb/typec/tcpm/tcpci_maxim.h create mode 100644 drivers/usb/typec/tcpm/tcpci_maxim_core.c diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile index 906d9dced8e7..08e57bb499cb 100644 --- a/drivers/usb/typec/tcpm/Makefile +++ b/drivers/usb/typec/tcpm/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o obj-$(CONFIG_TYPEC_MT6360) += tcpci_mt6360.o obj-$(CONFIG_TYPEC_TCPCI_MT6370) += tcpci_mt6370.o obj-$(CONFIG_TYPEC_TCPCI_MAXIM) += tcpci_maxim.o +tcpci_maxim-y += tcpci_maxim_core.o maxim_contaminant.o diff --git a/drivers/usb/typec/tcpm/maxim_contaminant.c b/drivers/usb/typec/tcpm/maxim_contaminant.c new file mode 100644 index 000000000000..f8504a90da26 --- /dev/null +++ b/drivers/usb/typec/tcpm/maxim_contaminant.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 Google, Inc + * + * USB-C module to reduce wakeups due to contaminants. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "tcpci_maxim.h" + +enum fladc_select { + CC1_SCALE1 = 1, + CC1_SCALE2, + CC2_SCALE1, + CC2_SCALE2, + SBU1, + SBU2, +}; + +#define FLADC_1uA_LSB_MV 25 +/* High range CC */ +#define FLADC_CC_HIGH_RANGE_LSB_MV 208 +/* Low range CC */ +#define FLADC_CC_LOW_RANGE_LSB_MV 126 + +/* 1uA current source */ +#define FLADC_CC_SCALE1 1 +/* 5 uA current source */ +#define FLADC_CC_SCALE2 5 + +#define FLADC_1uA_CC_OFFSET_MV 300 +#define FLADC_CC_HIGH_RANGE_OFFSET_MV 624 +#define FLADC_CC_LOW_RANGE_OFFSET_MV 378 + +#define CONTAMINANT_THRESHOLD_SBU_K 1000 +#define CONTAMINANT_THRESHOLD_CC_K 1000 + +#define READ1_SLEEP_MS 10 +#define READ2_SLEEP_MS 5 + +#define STATUS_CHECK(reg, mask, val) (((reg) & (mask)) == (val)) + +#define IS_CC_OPEN(cc_status) \ + (STATUS_CHECK((cc_status), TCPC_CC_STATUS_CC1_MASK << TCPC_CC_STATUS_CC1_SHIFT, \ + TCPC_CC_STATE_SRC_OPEN) && STATUS_CHECK((cc_status), \ + TCPC_CC_STATUS_CC2_MASK << \ + TCPC_CC_STATUS_CC2_SHIFT, \ + TCPC_CC_STATE_SRC_OPEN)) + +static int max_contaminant_adc_to_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + bool ua_src, u8 fladc) +{ + /* SBU channels only have 1 scale with 1uA. */ + if ((ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2 || channel == SBU1 || + channel == SBU2))) + /* Mean of range */ + return FLADC_1uA_CC_OFFSET_MV + (fladc * FLADC_1uA_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE1 || channel == CC2_SCALE1)) + return FLADC_CC_HIGH_RANGE_OFFSET_MV + (fladc * FLADC_CC_HIGH_RANGE_LSB_MV); + else if (!ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2)) + return FLADC_CC_LOW_RANGE_OFFSET_MV + (fladc * FLADC_CC_LOW_RANGE_LSB_MV); + + dev_err_once(chip->dev, "ADC ERROR: SCALE UNKNOWN"); + + return -EINVAL; +} + +static int max_contaminant_read_adc_mv(struct max_tcpci_chip *chip, enum fladc_select channel, + int sleep_msec, bool raw, bool ua_src) +{ + struct regmap *regmap = chip->data.regmap; + u8 fladc; + int ret; + + /* Channel & scale select */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, + channel << ADC_CHANNEL_OFFSET); + if (ret < 0) + return ret; + + /* Enable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN); + if (ret < 0) + return ret; + + usleep_range(sleep_msec * 1000, (sleep_msec + 1) * 1000); + ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, &fladc); + if (ret < 0) + return ret; + + /* Disable ADC */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, 0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, 0); + if (ret < 0) + return ret; + + if (!raw) + return max_contaminant_adc_to_mv(chip, channel, ua_src, fladc); + else + return fladc; +} + +static int max_contaminant_read_resistance_kohm(struct max_tcpci_chip *chip, + enum fladc_select channel, int sleep_msec, bool raw) +{ + struct regmap *regmap = chip->data.regmap; + int mv; + int ret; + + if (channel == CC1_SCALE1 || channel == CC2_SCALE1 || channel == CC1_SCALE2 || + channel == CC2_SCALE2) { + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, + ULTRA_LOW_POWER_MODE); + if (ret < 0) + return ret; + + /* Enable 1uA current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_1_SRC); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, CCOVPDIS); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + if (mv < 0) + return ret; + + /* OVP enable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, 0); + if (ret < 0) + return ret; + /* returns KOhm as 1uA source is used. */ + return mv; + } + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, SBUOVPDIS); + if (ret < 0) + return ret; + + /* SBU switches auto configure when channel is selected. */ + /* Enable 1ua current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, SBURPCTRL); + if (ret < 0) + return ret; + + mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true); + if (mv < 0) + return ret; + /* Disable current source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, 0); + if (ret < 0) + return ret; + + /* OVP disable */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, 0); + if (ret < 0) + return ret; + + return mv; +} + +static int max_contaminant_read_comparators(struct max_tcpci_chip *chip, u8 *vendor_cc_status2_cc1, + u8 *vendor_cc_status2_cc2) +{ + struct regmap *regmap = chip->data.regmap; + int ret; + + /* Enable 80uA source */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_80_SRC); + if (ret < 0) + return ret; + + /* Enable comparators */ + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN); + if (ret < 0) + return ret; + + /* Sleep to allow comparators settle */ + usleep_range(5000, 6000); + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1); + if (ret < 0) + return ret; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc1); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2); + if (ret < 0) + return ret; + + usleep_range(5000, 6000); + ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc2); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, 0); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int max_contaminant_detect_contaminant(struct max_tcpci_chip *chip) +{ + int cc1_k, cc2_k, sbu1_k, sbu2_k, ret; + u8 vendor_cc_status2_cc1 = 0xff, vendor_cc_status2_cc2 = 0xff; + u8 role_ctrl = 0, role_ctrl_backup = 0; + int inferred_state = NOT_DETECTED; + + ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + if (ret < 0) + return NOT_DETECTED; + + role_ctrl_backup = role_ctrl; + role_ctrl = 0x0F; + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + if (ret < 0) + return NOT_DETECTED; + + cc1_k = max_contaminant_read_resistance_kohm(chip, CC1_SCALE2, READ1_SLEEP_MS, false); + if (cc1_k < 0) + goto exit; + + cc2_k = max_contaminant_read_resistance_kohm(chip, CC2_SCALE2, READ2_SLEEP_MS, false); + if (cc2_k < 0) + goto exit; + + sbu1_k = max_contaminant_read_resistance_kohm(chip, SBU1, READ1_SLEEP_MS, false); + if (sbu1_k < 0) + goto exit; + + sbu2_k = max_contaminant_read_resistance_kohm(chip, SBU2, READ2_SLEEP_MS, false); + if (sbu2_k < 0) + goto exit; + + ret = max_contaminant_read_comparators(chip, &vendor_cc_status2_cc1, + &vendor_cc_status2_cc2); + + if (ret < 0) + goto exit; + + if ((!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1) || + !(CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) && + !(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1 && CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) + inferred_state = SINK; + else if ((cc1_k < CONTAMINANT_THRESHOLD_CC_K || cc2_k < CONTAMINANT_THRESHOLD_CC_K) && + (sbu1_k < CONTAMINANT_THRESHOLD_SBU_K || sbu2_k < CONTAMINANT_THRESHOLD_SBU_K)) + inferred_state = DETECTED; + + if (inferred_state == NOT_DETECTED) + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + else + max_tcpci_write8(chip, TCPC_ROLE_CTRL, (TCPC_ROLE_CTRL_DRP | 0xA)); + + return inferred_state; +exit: + max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + return NOT_DETECTED; +} + +static int max_contaminant_enable_dry_detection(struct max_tcpci_chip *chip) +{ + struct regmap *regmap = chip->data.regmap; + u8 temp; + int ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL3, CCWTRDEB_MASK | CCWTRSEL_MASK + | WTRCYCLE_MASK, CCWTRDEB_1MS << CCWTRDEB_SHIFT | + CCWTRSEL_1V << CCWTRSEL_SHIFT | WTRCYCLE_4_8_S << + WTRCYCLE_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP, TCPC_ROLE_CTRL_DRP); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, &temp); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, + ULTRA_LOW_POWER_MODE); + if (ret < 0) + return ret; + ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL2, &temp); + if (ret < 0) + return ret; + + /* Enable Look4Connection before sending the command */ + ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_EN_LK4CONN_ALRT, + TCPC_TCPC_CTRL_EN_LK4CONN_ALRT); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION); + if (ret < 0) + return ret; + return 0; +} + +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce) +{ + u8 cc_status, pwr_cntl; + int ret; + + ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + if (ret < 0) + return false; + + ret = max_tcpci_read8(chip, TCPC_POWER_CTRL, &pwr_cntl); + if (ret < 0) + return false; + + if (chip->contaminant_state == NOT_DETECTED || chip->contaminant_state == SINK) { + if (!disconnect_while_debounce) + msleep(100); + + ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); + if (ret < 0) + return false; + + if (IS_CC_OPEN(cc_status)) { + u8 role_ctrl, role_ctrl_backup; + + ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); + if (ret < 0) + return false; + + role_ctrl_backup = role_ctrl; + role_ctrl |= 0x0F; + role_ctrl &= ~(TCPC_ROLE_CTRL_DRP); + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); + if (ret < 0) + return false; + + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + + ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); + if (ret < 0) + return false; + + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } + } + return false; + } else if (chip->contaminant_state == DETECTED) { + if (STATUS_CHECK(cc_status, TCPC_CC_STATUS_TOGGLING, 0)) { + chip->contaminant_state = max_contaminant_detect_contaminant(chip); + if (chip->contaminant_state == DETECTED) { + max_contaminant_enable_dry_detection(chip); + return true; + } + } + } + + return false; +} + +MODULE_DESCRIPTION("MAXIM TCPC CONTAMINANT Module"); +MODULE_AUTHOR("Badhri Jagan Sridharan "); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c deleted file mode 100644 index 83e140ffcc3e..000000000000 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ /dev/null @@ -1,530 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2020, Google LLC - * - * MAXIM TCPCI based TCPC driver - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PD_ACTIVITY_TIMEOUT_MS 10000 - -#define TCPC_VENDOR_ALERT 0x80 -#define TCPC_VENDOR_USBSW_CTRL 0x93 -#define TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA 0x9 -#define TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA 0 - -#define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0 -#define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1 -#define TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET 2 - -/* - * LongMessage not supported, hence 32 bytes for buf to be read from RECEIVE_BUFFER. - * DEVICE_CAPABILITIES_2.LongMessage = 0, the value in READABLE_BYTE_COUNT reg shall be - * less than or equal to 31. Since, RECEIVE_BUFFER len = 31 + 1(READABLE_BYTE_COUNT). - */ -#define TCPC_RECEIVE_BUFFER_LEN 32 - -#define MAX_BUCK_BOOST_SID 0x69 -#define MAX_BUCK_BOOST_OP 0xb9 -#define MAX_BUCK_BOOST_OFF 0 -#define MAX_BUCK_BOOST_SOURCE 0xa -#define MAX_BUCK_BOOST_SINK 0x5 - -struct max_tcpci_chip { - struct tcpci_data data; - struct tcpci *tcpci; - struct device *dev; - struct i2c_client *client; - struct tcpm_port *port; -}; - -static const struct regmap_range max_tcpci_tcpci_range[] = { - regmap_reg_range(0x00, 0x95) -}; - -static const struct regmap_access_table max_tcpci_tcpci_write_table = { - .yes_ranges = max_tcpci_tcpci_range, - .n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range), -}; - -static const struct regmap_config max_tcpci_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .max_register = 0x95, - .wr_table = &max_tcpci_tcpci_write_table, -}; - -static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata) -{ - return container_of(tdata, struct max_tcpci_chip, data); -} - -static int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) -{ - return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); -} - -static int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) -{ - return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); -} - -static int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) -{ - return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); -} - -static int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) -{ - return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); -} - -static void max_tcpci_init_regs(struct max_tcpci_chip *chip) -{ - u16 alert_mask = 0; - int ret; - - ret = max_tcpci_write16(chip, TCPC_ALERT, 0xffff); - if (ret < 0) { - dev_err(chip->dev, "Error writing to TCPC_ALERT ret:%d\n", ret); - return; - } - - ret = max_tcpci_write16(chip, TCPC_VENDOR_ALERT, 0xffff); - if (ret < 0) { - dev_err(chip->dev, "Error writing to TCPC_VENDOR_ALERT ret:%d\n", ret); - return; - } - - ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, 0xff); - if (ret < 0) { - dev_err(chip->dev, "Unable to clear TCPC_ALERT_EXTENDED ret:%d\n", ret); - return; - } - - /* Enable VSAFE0V detection */ - ret = max_tcpci_write8(chip, TCPC_EXTENDED_STATUS_MASK, TCPC_EXTENDED_STATUS_VSAFE0V); - if (ret < 0) { - dev_err(chip->dev, "Unable to unmask TCPC_EXTENDED_STATUS_VSAFE0V ret:%d\n", ret); - return; - } - - alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED | - TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS | - TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS | - /* Enable Extended alert for detecting Fast Role Swap Signal */ - TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS; - - ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); - if (ret < 0) { - dev_err(chip->dev, - "Error enabling TCPC_ALERT: TCPC_ALERT_MASK write failed ret:%d\n", ret); - return; - } - - /* Enable vbus voltage monitoring and voltage alerts */ - ret = max_tcpci_write8(chip, TCPC_POWER_CTRL, 0); - if (ret < 0) { - dev_err(chip->dev, "Error writing to TCPC_POWER_CTRL ret:%d\n", ret); - return; - } - - ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED_MASK, TCPC_SINK_FAST_ROLE_SWAP); - if (ret < 0) - return; -} - -static void process_rx(struct max_tcpci_chip *chip, u16 status) -{ - struct pd_message msg; - u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; - int ret, payload_index; - u8 *rx_buf_ptr; - - /* - * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers - * plus one (for the RX_BUF_FRAME_TYPE) Table 4-36. - * Read the count and frame type. - */ - ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2); - if (ret < 0) { - dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d\n", ret); - return; - } - - count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET]; - frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET]; - - if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) { - max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : - "error frame_type is not SOP"); - return; - } - - if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) { - dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d\n", count); - return; - } - - /* - * Read count + 1 as RX_BUF_BYTE_x is hidden and can only be read through - * TCPC_RX_BYTE_CNT - */ - count += 1; - ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count); - if (ret < 0) { - dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d\n", ret); - return; - } - - rx_buf_ptr = rx_buf + TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET; - msg.header = cpu_to_le16(*(u16 *)rx_buf_ptr); - rx_buf_ptr = rx_buf_ptr + sizeof(msg.header); - for (payload_index = 0; payload_index < pd_header_cnt_le(msg.header); payload_index++, - rx_buf_ptr += sizeof(msg.payload[0])) - msg.payload[payload_index] = cpu_to_le32(*(u32 *)rx_buf_ptr); - - /* - * Read complete, clear RX status alert bit. - * Clear overflow as well if set. - */ - ret = max_tcpci_write16(chip, TCPC_ALERT, status & TCPC_ALERT_RX_BUF_OVF ? - TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF : - TCPC_ALERT_RX_STATUS); - if (ret < 0) - return; - - tcpm_pd_receive(chip->port, &msg); -} - -static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) -{ - struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); - u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE}; - u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK}; - u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF}; - struct i2c_client *i2c = chip->client; - int ret; - - struct i2c_msg msgs[] = { - { - .addr = MAX_BUCK_BOOST_SID, - .flags = i2c->flags & I2C_M_TEN, - .len = 2, - .buf = source ? buffer_source : sink ? buffer_sink : buffer_none, - }, - }; - - if (source && sink) { - dev_err(chip->dev, "Both source and sink set\n"); - return -EINVAL; - } - - ret = i2c_transfer(i2c->adapter, msgs, 1); - - return ret < 0 ? ret : 1; -} - -static void process_power_status(struct max_tcpci_chip *chip) -{ - u8 pwr_status; - int ret; - - ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &pwr_status); - if (ret < 0) - return; - - if (pwr_status == 0xff) - max_tcpci_init_regs(chip); - else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) - tcpm_sourcing_vbus(chip->port); - else - tcpm_vbus_change(chip->port); -} - -static void max_tcpci_frs_sourcing_vbus(struct tcpci *tcpci, struct tcpci_data *tdata) -{ - /* - * For Fast Role Swap case, Boost turns on autonomously without - * AP intervention, but, needs AP to enable source mode explicitly - * for AP to regain control. - */ - max_tcpci_set_vbus(tcpci, tdata, true, false); -} - -static void process_tx(struct max_tcpci_chip *chip, u16 status) -{ - if (status & TCPC_ALERT_TX_SUCCESS) - tcpm_pd_transmit_complete(chip->port, TCPC_TX_SUCCESS); - else if (status & TCPC_ALERT_TX_DISCARDED) - tcpm_pd_transmit_complete(chip->port, TCPC_TX_DISCARDED); - else if (status & TCPC_ALERT_TX_FAILED) - tcpm_pd_transmit_complete(chip->port, TCPC_TX_FAILED); - - /* Reinit regs as Hard reset sets them to default value */ - if ((status & TCPC_ALERT_TX_SUCCESS) && (status & TCPC_ALERT_TX_FAILED)) - max_tcpci_init_regs(chip); -} - -/* Enable USB switches when partner is USB communications capable */ -static void max_tcpci_set_partner_usb_comm_capable(struct tcpci *tcpci, struct tcpci_data *data, - bool capable) -{ - struct max_tcpci_chip *chip = tdata_to_max_tcpci(data); - int ret; - - ret = max_tcpci_write8(chip, TCPC_VENDOR_USBSW_CTRL, capable ? - TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA : - TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA); - - if (ret < 0) - dev_err(chip->dev, "Failed to enable USB switches"); -} - -static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) -{ - u16 mask; - int ret; - u8 reg_status; - - /* - * Clear alert status for everything except RX_STATUS, which shouldn't - * be cleared until we have successfully retrieved message. - */ - if (status & ~TCPC_ALERT_RX_STATUS) { - mask = status & TCPC_ALERT_RX_BUF_OVF ? - status & ~(TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF) : - status & ~TCPC_ALERT_RX_STATUS; - ret = max_tcpci_write16(chip, TCPC_ALERT, mask); - if (ret < 0) { - dev_err(chip->dev, "ALERT clear failed\n"); - return ret; - } - } - - if (status & TCPC_ALERT_RX_BUF_OVF && !(status & TCPC_ALERT_RX_STATUS)) { - ret = max_tcpci_write16(chip, TCPC_ALERT, (TCPC_ALERT_RX_STATUS | - TCPC_ALERT_RX_BUF_OVF)); - if (ret < 0) { - dev_err(chip->dev, "ALERT clear failed\n"); - return ret; - } - } - - if (status & TCPC_ALERT_EXTND) { - ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, ®_status); - if (ret < 0) - return ret; - - ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, reg_status); - if (ret < 0) - return ret; - - if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) { - dev_info(chip->dev, "FRS Signal\n"); - tcpm_sink_frs(chip->port); - } - } - - if (status & TCPC_ALERT_EXTENDED_STATUS) { - ret = max_tcpci_read8(chip, TCPC_EXTENDED_STATUS, (u8 *)®_status); - if (ret >= 0 && (reg_status & TCPC_EXTENDED_STATUS_VSAFE0V)) - tcpm_vbus_change(chip->port); - } - - if (status & TCPC_ALERT_RX_STATUS) - process_rx(chip, status); - - if (status & TCPC_ALERT_VBUS_DISCNCT) - tcpm_vbus_change(chip->port); - - if (status & TCPC_ALERT_CC_STATUS) - tcpm_cc_change(chip->port); - - if (status & TCPC_ALERT_POWER_STATUS) - process_power_status(chip); - - if (status & TCPC_ALERT_RX_HARD_RST) { - tcpm_pd_hard_reset(chip->port); - max_tcpci_init_regs(chip); - } - - if (status & TCPC_ALERT_TX_SUCCESS || status & TCPC_ALERT_TX_DISCARDED || status & - TCPC_ALERT_TX_FAILED) - process_tx(chip, status); - - return IRQ_HANDLED; -} - -static irqreturn_t max_tcpci_irq(int irq, void *dev_id) -{ - struct max_tcpci_chip *chip = dev_id; - u16 status; - irqreturn_t irq_return = IRQ_HANDLED; - int ret; - - if (!chip->port) - return IRQ_HANDLED; - - ret = max_tcpci_read16(chip, TCPC_ALERT, &status); - if (ret < 0) { - dev_err(chip->dev, "ALERT read failed\n"); - return ret; - } - while (status) { - irq_return = _max_tcpci_irq(chip, status); - /* Do not return if the ALERT is already set. */ - ret = max_tcpci_read16(chip, TCPC_ALERT, &status); - if (ret < 0) - break; - } - - return irq_return; -} - -static irqreturn_t max_tcpci_isr(int irq, void *dev_id) -{ - struct max_tcpci_chip *chip = dev_id; - - pm_wakeup_event(chip->dev, PD_ACTIVITY_TIMEOUT_MS); - - if (!chip->port) - return IRQ_HANDLED; - - return IRQ_WAKE_THREAD; -} - -static int max_tcpci_init_alert(struct max_tcpci_chip *chip, struct i2c_client *client) -{ - int ret; - - ret = devm_request_threaded_irq(chip->dev, client->irq, max_tcpci_isr, max_tcpci_irq, - (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev), - chip); - - if (ret < 0) - return ret; - - enable_irq_wake(client->irq); - return 0; -} - -static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata, - enum typec_cc_status cc) -{ - struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); - - max_tcpci_init_regs(chip); - - return 0; -} - -static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data) -{ - /* - * Generic TCPCI overwrites the regs once this driver initializes - * them. Prevent this by returning -1. - */ - return -1; -} - -static int max_tcpci_probe(struct i2c_client *client) -{ - int ret; - struct max_tcpci_chip *chip; - u8 power_status; - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->client = client; - chip->data.regmap = devm_regmap_init_i2c(client, &max_tcpci_regmap_config); - if (IS_ERR(chip->data.regmap)) { - dev_err(&client->dev, "Regmap init failed\n"); - return PTR_ERR(chip->data.regmap); - } - - chip->dev = &client->dev; - i2c_set_clientdata(client, chip); - - ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &power_status); - if (ret < 0) - return ret; - - /* Chip level tcpci callbacks */ - chip->data.set_vbus = max_tcpci_set_vbus; - chip->data.start_drp_toggling = max_tcpci_start_toggling; - chip->data.TX_BUF_BYTE_x_hidden = true; - chip->data.init = tcpci_init; - chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; - chip->data.auto_discharge_disconnect = true; - chip->data.vbus_vsafe0v = true; - chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; - - max_tcpci_init_regs(chip); - chip->tcpci = tcpci_register_port(chip->dev, &chip->data); - if (IS_ERR(chip->tcpci)) { - dev_err(&client->dev, "TCPCI port registration failed\n"); - return PTR_ERR(chip->tcpci); - } - chip->port = tcpci_get_tcpm_port(chip->tcpci); - ret = max_tcpci_init_alert(chip, client); - if (ret < 0) - goto unreg_port; - - device_init_wakeup(chip->dev, true); - return 0; - -unreg_port: - tcpci_unregister_port(chip->tcpci); - - return ret; -} - -static void max_tcpci_remove(struct i2c_client *client) -{ - struct max_tcpci_chip *chip = i2c_get_clientdata(client); - - if (!IS_ERR_OR_NULL(chip->tcpci)) - tcpci_unregister_port(chip->tcpci); -} - -static const struct i2c_device_id max_tcpci_id[] = { - { "maxtcpc", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, max_tcpci_id); - -#ifdef CONFIG_OF -static const struct of_device_id max_tcpci_of_match[] = { - { .compatible = "maxim,max33359", }, - {}, -}; -MODULE_DEVICE_TABLE(of, max_tcpci_of_match); -#endif - -static struct i2c_driver max_tcpci_i2c_driver = { - .driver = { - .name = "maxtcpc", - .of_match_table = of_match_ptr(max_tcpci_of_match), - }, - .probe_new = max_tcpci_probe, - .remove = max_tcpci_remove, - .id_table = max_tcpci_id, -}; -module_i2c_driver(max_tcpci_i2c_driver); - -MODULE_AUTHOR("Badhri Jagan Sridharan "); -MODULE_DESCRIPTION("Maxim TCPCI based USB Type-C Port Controller Interface Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h new file mode 100644 index 000000000000..2c1c4d161b0d --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2022 Google, Inc + * + * MAXIM TCPC header file. + */ +#ifndef TCPCI_MAXIM_H_ +#define TCPCI_MAXIM_H_ + +#define VENDOR_CC_STATUS2 0x85 +#define CC1_VUFP_RD0P5 BIT(1) +#define CC2_VUFP_RD0P5 BIT(5) +#define TCPC_VENDOR_FLADC_STATUS 0x89 + +#define TCPC_VENDOR_CC_CTRL1 0x8c +#define CCCONNDRY BIT(7) +#define CCCOMPEN BIT(5) + +#define TCPC_VENDOR_CC_CTRL2 0x8d +#define SBUOVPDIS BIT(7) +#define CCOVPDIS BIT(6) +#define SBURPCTRL BIT(5) +#define CCLPMODESEL_MASK GENMASK(4, 3) +#define ULTRA_LOW_POWER_MODE BIT(3) +#define CCRPCTRL_MASK GENMASK(2, 0) +#define UA_1_SRC 1 +#define UA_80_SRC 3 + +#define TCPC_VENDOR_CC_CTRL3 0x8e +#define CCWTRDEB_MASK GENMASK(7, 6) +#define CCWTRDEB_SHIFT 6 +#define CCWTRDEB_1MS 1 +#define CCWTRSEL_MASK GENMASK(5, 3) +#define CCWTRSEL_SHIFT 3 +#define CCWTRSEL_1V 0x4 +#define CCLADDERDIS BIT(2) +#define WTRCYCLE_MASK BIT(0) +#define WTRCYCLE_SHIFT 0 +#define WTRCYCLE_2_4_S 0 +#define WTRCYCLE_4_8_S 1 + +#define TCPC_VENDOR_ADC_CTRL1 0x91 +#define ADCINSEL_MASK GENMASK(7, 5) +#define ADC_CHANNEL_OFFSET 5 +#define ADCEN BIT(0) + +enum contamiant_state { + NOT_DETECTED, + DETECTED, + SINK, +}; + +/* + * @potential_contaminant: + * Last returned result to tcpm indicating whether the TCPM port + * has potential contaminant. + */ +struct max_tcpci_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; + struct i2c_client *client; + struct tcpm_port *port; + enum contamiant_state contaminant_state; +}; + +static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static inline int max_tcpci_write16(struct max_tcpci_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static inline int max_tcpci_read8(struct max_tcpci_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static inline int max_tcpci_write8(struct max_tcpci_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce); + +#endif // TCPCI_MAXIM_H_ diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c new file mode 100644 index 000000000000..f32cda2a5e3a --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 - 2022, Google LLC + * + * MAXIM TCPCI based TCPC driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tcpci_maxim.h" + +#define PD_ACTIVITY_TIMEOUT_MS 10000 + +#define TCPC_VENDOR_ALERT 0x80 +#define TCPC_VENDOR_USBSW_CTRL 0x93 +#define TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA 0x9 +#define TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA 0 + +#define TCPC_RECEIVE_BUFFER_COUNT_OFFSET 0 +#define TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET 1 +#define TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET 2 + +/* + * LongMessage not supported, hence 32 bytes for buf to be read from RECEIVE_BUFFER. + * DEVICE_CAPABILITIES_2.LongMessage = 0, the value in READABLE_BYTE_COUNT reg shall be + * less than or equal to 31. Since, RECEIVE_BUFFER len = 31 + 1(READABLE_BYTE_COUNT). + */ +#define TCPC_RECEIVE_BUFFER_LEN 32 + +#define MAX_BUCK_BOOST_SID 0x69 +#define MAX_BUCK_BOOST_OP 0xb9 +#define MAX_BUCK_BOOST_OFF 0 +#define MAX_BUCK_BOOST_SOURCE 0xa +#define MAX_BUCK_BOOST_SINK 0x5 + +static const struct regmap_range max_tcpci_tcpci_range[] = { + regmap_reg_range(0x00, 0x95) +}; + +static const struct regmap_access_table max_tcpci_tcpci_write_table = { + .yes_ranges = max_tcpci_tcpci_range, + .n_yes_ranges = ARRAY_SIZE(max_tcpci_tcpci_range), +}; + +static const struct regmap_config max_tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x95, + .wr_table = &max_tcpci_tcpci_write_table, +}; + +static struct max_tcpci_chip *tdata_to_max_tcpci(struct tcpci_data *tdata) +{ + return container_of(tdata, struct max_tcpci_chip, data); +} + +static void max_tcpci_init_regs(struct max_tcpci_chip *chip) +{ + u16 alert_mask = 0; + int ret; + + ret = max_tcpci_write16(chip, TCPC_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write16(chip, TCPC_VENDOR_ALERT, 0xffff); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_VENDOR_ALERT ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, 0xff); + if (ret < 0) { + dev_err(chip->dev, "Unable to clear TCPC_ALERT_EXTENDED ret:%d\n", ret); + return; + } + + /* Enable VSAFE0V detection */ + ret = max_tcpci_write8(chip, TCPC_EXTENDED_STATUS_MASK, TCPC_EXTENDED_STATUS_VSAFE0V); + if (ret < 0) { + dev_err(chip->dev, "Unable to unmask TCPC_EXTENDED_STATUS_VSAFE0V ret:%d\n", ret); + return; + } + + alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED | + TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS | + TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS | + /* Enable Extended alert for detecting Fast Role Swap Signal */ + TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS; + + ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); + if (ret < 0) { + dev_err(chip->dev, + "Error enabling TCPC_ALERT: TCPC_ALERT_MASK write failed ret:%d\n", ret); + return; + } + + /* Enable vbus voltage monitoring and voltage alerts */ + ret = max_tcpci_write8(chip, TCPC_POWER_CTRL, 0); + if (ret < 0) { + dev_err(chip->dev, "Error writing to TCPC_POWER_CTRL ret:%d\n", ret); + return; + } + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED_MASK, TCPC_SINK_FAST_ROLE_SWAP); + if (ret < 0) + return; +} + +static void process_rx(struct max_tcpci_chip *chip, u16 status) +{ + struct pd_message msg; + u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; + int ret, payload_index; + u8 *rx_buf_ptr; + + /* + * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers + * plus one (for the RX_BUF_FRAME_TYPE) Table 4-36. + * Read the count and frame type. + */ + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, 2); + if (ret < 0) { + dev_err(chip->dev, "TCPC_RX_BYTE_CNT read failed ret:%d\n", ret); + return; + } + + count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET]; + frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET]; + + if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) { + max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : + "error frame_type is not SOP"); + return; + } + + if (count > sizeof(struct pd_message) || count + 1 > TCPC_RECEIVE_BUFFER_LEN) { + dev_err(chip->dev, "Invalid TCPC_RX_BYTE_CNT %d\n", count); + return; + } + + /* + * Read count + 1 as RX_BUF_BYTE_x is hidden and can only be read through + * TCPC_RX_BYTE_CNT + */ + count += 1; + ret = regmap_raw_read(chip->data.regmap, TCPC_RX_BYTE_CNT, rx_buf, count); + if (ret < 0) { + dev_err(chip->dev, "Error: TCPC_RX_BYTE_CNT read failed: %d\n", ret); + return; + } + + rx_buf_ptr = rx_buf + TCPC_RECEIVE_BUFFER_RX_BYTE_BUF_OFFSET; + msg.header = cpu_to_le16(*(u16 *)rx_buf_ptr); + rx_buf_ptr = rx_buf_ptr + sizeof(msg.header); + for (payload_index = 0; payload_index < pd_header_cnt_le(msg.header); payload_index++, + rx_buf_ptr += sizeof(msg.payload[0])) + msg.payload[payload_index] = cpu_to_le32(*(u32 *)rx_buf_ptr); + + /* + * Read complete, clear RX status alert bit. + * Clear overflow as well if set. + */ + ret = max_tcpci_write16(chip, TCPC_ALERT, status & TCPC_ALERT_RX_BUF_OVF ? + TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF : + TCPC_ALERT_RX_STATUS); + if (ret < 0) + return; + + tcpm_pd_receive(chip->port, &msg); +} + +static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE}; + u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK}; + u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF}; + struct i2c_client *i2c = chip->client; + int ret; + + struct i2c_msg msgs[] = { + { + .addr = MAX_BUCK_BOOST_SID, + .flags = i2c->flags & I2C_M_TEN, + .len = 2, + .buf = source ? buffer_source : sink ? buffer_sink : buffer_none, + }, + }; + + if (source && sink) { + dev_err(chip->dev, "Both source and sink set\n"); + return -EINVAL; + } + + ret = i2c_transfer(i2c->adapter, msgs, 1); + + return ret < 0 ? ret : 1; +} + +static void process_power_status(struct max_tcpci_chip *chip) +{ + u8 pwr_status; + int ret; + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &pwr_status); + if (ret < 0) + return; + + if (pwr_status == 0xff) + max_tcpci_init_regs(chip); + else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) + tcpm_sourcing_vbus(chip->port); + else + tcpm_vbus_change(chip->port); +} + +static void max_tcpci_frs_sourcing_vbus(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + /* + * For Fast Role Swap case, Boost turns on autonomously without + * AP intervention, but, needs AP to enable source mode explicitly + * for AP to regain control. + */ + max_tcpci_set_vbus(tcpci, tdata, true, false); +} + +static void process_tx(struct max_tcpci_chip *chip, u16 status) +{ + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(chip->port, TCPC_TX_FAILED); + + /* Reinit regs as Hard reset sets them to default value */ + if ((status & TCPC_ALERT_TX_SUCCESS) && (status & TCPC_ALERT_TX_FAILED)) + max_tcpci_init_regs(chip); +} + +/* Enable USB switches when partner is USB communications capable */ +static void max_tcpci_set_partner_usb_comm_capable(struct tcpci *tcpci, struct tcpci_data *data, + bool capable) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(data); + int ret; + + ret = max_tcpci_write8(chip, TCPC_VENDOR_USBSW_CTRL, capable ? + TCPC_VENDOR_USBSW_CTRL_ENABLE_USB_DATA : + TCPC_VENDOR_USBSW_CTRL_DISABLE_USB_DATA); + + if (ret < 0) + dev_err(chip->dev, "Failed to enable USB switches"); +} + +static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) +{ + u16 mask; + int ret; + u8 reg_status; + + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) { + mask = status & TCPC_ALERT_RX_BUF_OVF ? + status & ~(TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_BUF_OVF) : + status & ~TCPC_ALERT_RX_STATUS; + ret = max_tcpci_write16(chip, TCPC_ALERT, mask); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_RX_BUF_OVF && !(status & TCPC_ALERT_RX_STATUS)) { + ret = max_tcpci_write16(chip, TCPC_ALERT, (TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_BUF_OVF)); + if (ret < 0) { + dev_err(chip->dev, "ALERT clear failed\n"); + return ret; + } + } + + if (status & TCPC_ALERT_EXTND) { + ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, ®_status); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_ALERT_EXTENDED, reg_status); + if (ret < 0) + return ret; + + if (reg_status & TCPC_SINK_FAST_ROLE_SWAP) { + dev_info(chip->dev, "FRS Signal\n"); + tcpm_sink_frs(chip->port); + } + } + + if (status & TCPC_ALERT_EXTENDED_STATUS) { + ret = max_tcpci_read8(chip, TCPC_EXTENDED_STATUS, (u8 *)®_status); + if (ret >= 0 && (reg_status & TCPC_EXTENDED_STATUS_VSAFE0V)) + tcpm_vbus_change(chip->port); + } + + if (status & TCPC_ALERT_RX_STATUS) + process_rx(chip, status); + + if (status & TCPC_ALERT_VBUS_DISCNCT) + tcpm_vbus_change(chip->port); + + if (status & TCPC_ALERT_CC_STATUS) { + if (chip->contaminant_state == DETECTED || tcpm_port_is_toggling(chip->port)) { + if (!max_contaminant_is_contaminant(chip, false)) + tcpm_port_clean(chip->port); + } else { + tcpm_cc_change(chip->port); + } + } + + if (status & TCPC_ALERT_POWER_STATUS) + process_power_status(chip); + + if (status & TCPC_ALERT_RX_HARD_RST) { + tcpm_pd_hard_reset(chip->port); + max_tcpci_init_regs(chip); + } + + if (status & TCPC_ALERT_TX_SUCCESS || status & TCPC_ALERT_TX_DISCARDED || status & + TCPC_ALERT_TX_FAILED) + process_tx(chip, status); + + return IRQ_HANDLED; +} + +static irqreturn_t max_tcpci_irq(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + u16 status; + irqreturn_t irq_return = IRQ_HANDLED; + int ret; + + if (!chip->port) + return IRQ_HANDLED; + + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) { + dev_err(chip->dev, "ALERT read failed\n"); + return ret; + } + while (status) { + irq_return = _max_tcpci_irq(chip, status); + /* Do not return if the ALERT is already set. */ + ret = max_tcpci_read16(chip, TCPC_ALERT, &status); + if (ret < 0) + break; + } + + return irq_return; +} + +static irqreturn_t max_tcpci_isr(int irq, void *dev_id) +{ + struct max_tcpci_chip *chip = dev_id; + + pm_wakeup_event(chip->dev, PD_ACTIVITY_TIMEOUT_MS); + + if (!chip->port) + return IRQ_HANDLED; + + return IRQ_WAKE_THREAD; +} + +static int max_tcpci_init_alert(struct max_tcpci_chip *chip, struct i2c_client *client) +{ + int ret; + + ret = devm_request_threaded_irq(chip->dev, client->irq, max_tcpci_isr, max_tcpci_irq, + (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev), + chip); + + if (ret < 0) + return ret; + + enable_irq_wake(client->irq); + return 0; +} + +static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + max_tcpci_init_regs(chip); + + return 0; +} + +static int tcpci_init(struct tcpci *tcpci, struct tcpci_data *data) +{ + /* + * Generic TCPCI overwrites the regs once this driver initializes + * them. Prevent this by returning -1. + */ + return -1; +} + +static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + if (!max_contaminant_is_contaminant(chip, true)) + tcpm_port_clean(chip->port); +} + +static int max_tcpci_probe(struct i2c_client *client) +{ + int ret; + struct max_tcpci_chip *chip; + u8 power_status; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->data.regmap = devm_regmap_init_i2c(client, &max_tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) { + dev_err(&client->dev, "Regmap init failed\n"); + return PTR_ERR(chip->data.regmap); + } + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = max_tcpci_read8(chip, TCPC_POWER_STATUS, &power_status); + if (ret < 0) + return ret; + + /* Chip level tcpci callbacks */ + chip->data.set_vbus = max_tcpci_set_vbus; + chip->data.start_drp_toggling = max_tcpci_start_toggling; + chip->data.TX_BUF_BYTE_x_hidden = true; + chip->data.init = tcpci_init; + chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; + chip->data.auto_discharge_disconnect = true; + chip->data.vbus_vsafe0v = true; + chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; + chip->data.check_contaminant = max_tcpci_check_contaminant; + + max_tcpci_init_regs(chip); + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR(chip->tcpci)) { + dev_err(&client->dev, "TCPCI port registration failed\n"); + return PTR_ERR(chip->tcpci); + } + chip->port = tcpci_get_tcpm_port(chip->tcpci); + ret = max_tcpci_init_alert(chip, client); + if (ret < 0) + goto unreg_port; + + device_init_wakeup(chip->dev, true); + return 0; + +unreg_port: + tcpci_unregister_port(chip->tcpci); + + return ret; +} + +static void max_tcpci_remove(struct i2c_client *client) +{ + struct max_tcpci_chip *chip = i2c_get_clientdata(client); + + if (!IS_ERR_OR_NULL(chip->tcpci)) + tcpci_unregister_port(chip->tcpci); +} + +static const struct i2c_device_id max_tcpci_id[] = { + { "maxtcpc", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max_tcpci_id); + +#ifdef CONFIG_OF +static const struct of_device_id max_tcpci_of_match[] = { + { .compatible = "maxim,max33359", }, + {}, +}; +MODULE_DEVICE_TABLE(of, max_tcpci_of_match); +#endif + +static struct i2c_driver max_tcpci_i2c_driver = { + .driver = { + .name = "maxtcpc", + .of_match_table = of_match_ptr(max_tcpci_of_match), + }, + .probe_new = max_tcpci_probe, + .remove = max_tcpci_remove, + .id_table = max_tcpci_id, +}; +module_i2c_driver(max_tcpci_i2c_driver); + +MODULE_AUTHOR("Badhri Jagan Sridharan "); +MODULE_DESCRIPTION("Maxim TCPCI based USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 82b0417e4bfc5af8c1710996913374f09f4a2a03 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Thu, 22 Dec 2022 04:09:34 -0500 Subject: usb: cdnsp: : add scatter gather support for ISOC endpoint Patch implements scatter gather support for isochronous endpoint. This fix is forced by 'commit e81e7f9a0eb9 ("usb: gadget: uvc: add scatter gather support")'. After this fix CDNSP driver stop working with UVC class. Signed-off-by: Pawel Laszczak Reviewed-by: Peter Chen Link: https://lore.kernel.org/r/20221222090934.145140-1-pawell@cadence.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-gadget.c | 2 +- drivers/usb/cdns3/cdnsp-gadget.h | 4 +- drivers/usb/cdns3/cdnsp-ring.c | 110 +++++++++++++++++++++------------------ 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index f9aa50ff14d4..fff9ec9c391f 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -378,7 +378,7 @@ int cdnsp_ep_enqueue(struct cdnsp_ep *pep, struct cdnsp_request *preq) ret = cdnsp_queue_bulk_tx(pdev, preq); break; case USB_ENDPOINT_XFER_ISOC: - ret = cdnsp_queue_isoc_tx_prepare(pdev, preq); + ret = cdnsp_queue_isoc_tx(pdev, preq); } if (ret) diff --git a/drivers/usb/cdns3/cdnsp-gadget.h b/drivers/usb/cdns3/cdnsp-gadget.h index f740fa6089d8..e1b5801fdddf 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.h +++ b/drivers/usb/cdns3/cdnsp-gadget.h @@ -1532,8 +1532,8 @@ void cdnsp_queue_stop_endpoint(struct cdnsp_device *pdev, unsigned int ep_index); int cdnsp_queue_ctrl_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); int cdnsp_queue_bulk_tx(struct cdnsp_device *pdev, struct cdnsp_request *preq); -int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev, - struct cdnsp_request *preq); +int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, + struct cdnsp_request *preq); void cdnsp_queue_configure_endpoint(struct cdnsp_device *pdev, dma_addr_t in_ctx_ptr); void cdnsp_queue_reset_ep(struct cdnsp_device *pdev, unsigned int ep_index); diff --git a/drivers/usb/cdns3/cdnsp-ring.c b/drivers/usb/cdns3/cdnsp-ring.c index b23e543b3a3d..07f6068342d4 100644 --- a/drivers/usb/cdns3/cdnsp-ring.c +++ b/drivers/usb/cdns3/cdnsp-ring.c @@ -1333,6 +1333,20 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, ep_ring->dequeue, td->last_trb, ep_trb_dma); + desc = td->preq->pep->endpoint.desc; + + if (ep_seg) { + ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) + / sizeof(*ep_trb)]; + + trace_cdnsp_handle_transfer(ep_ring, + (struct cdnsp_generic_trb *)ep_trb); + + if (pep->skip && usb_endpoint_xfer_isoc(desc) && + td->last_trb != ep_trb) + return -EAGAIN; + } + /* * Skip the Force Stopped Event. The event_trb(ep_trb_dma) * of FSE is not in the current TD pointed by ep_ring->dequeue @@ -1347,7 +1361,6 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, goto cleanup; } - desc = td->preq->pep->endpoint.desc; if (!ep_seg) { if (!pep->skip || !usb_endpoint_xfer_isoc(desc)) { /* Something is busted, give up! */ @@ -1374,12 +1387,6 @@ static int cdnsp_handle_tx_event(struct cdnsp_device *pdev, goto cleanup; } - ep_trb = &ep_seg->trbs[(ep_trb_dma - ep_seg->dma) - / sizeof(*ep_trb)]; - - trace_cdnsp_handle_transfer(ep_ring, - (struct cdnsp_generic_trb *)ep_trb); - if (cdnsp_trb_is_noop(ep_trb)) goto cleanup; @@ -1726,11 +1733,6 @@ static unsigned int count_sg_trbs_needed(struct cdnsp_request *preq) return num_trbs; } -static unsigned int count_isoc_trbs_needed(struct cdnsp_request *preq) -{ - return cdnsp_count_trbs(preq->request.dma, preq->request.length); -} - static void cdnsp_check_trb_math(struct cdnsp_request *preq, int running_total) { if (running_total != preq->request.length) @@ -2192,28 +2194,48 @@ static unsigned int } /* Queue function isoc transfer */ -static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, - struct cdnsp_request *preq) +int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, + struct cdnsp_request *preq) { - int trb_buff_len, td_len, td_remain_len, ret; + unsigned int trb_buff_len, td_len, td_remain_len, block_len; unsigned int burst_count, last_burst_pkt; unsigned int total_pkt_count, max_pkt; struct cdnsp_generic_trb *start_trb; + struct scatterlist *sg = NULL; bool more_trbs_coming = true; struct cdnsp_ring *ep_ring; + unsigned int num_sgs = 0; int running_total = 0; u32 field, length_field; + u64 addr, send_addr; int start_cycle; int trbs_per_td; - u64 addr; - int i; + int i, sent_len, ret; ep_ring = preq->pep->ring; + + td_len = preq->request.length; + + if (preq->request.num_sgs) { + num_sgs = preq->request.num_sgs; + sg = preq->request.sg; + addr = (u64)sg_dma_address(sg); + block_len = sg_dma_len(sg); + trbs_per_td = count_sg_trbs_needed(preq); + } else { + addr = (u64)preq->request.dma; + block_len = td_len; + trbs_per_td = count_trbs_needed(preq); + } + + ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td); + if (ret) + return ret; + start_trb = &ep_ring->enqueue->generic; start_cycle = ep_ring->cycle_state; - td_len = preq->request.length; - addr = (u64)preq->request.dma; td_remain_len = td_len; + send_addr = addr; max_pkt = usb_endpoint_maxp(preq->pep->endpoint.desc); total_pkt_count = DIV_ROUND_UP(td_len, max_pkt); @@ -2225,11 +2247,6 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, burst_count = cdnsp_get_burst_count(pdev, preq, total_pkt_count); last_burst_pkt = cdnsp_get_last_burst_packet_count(pdev, preq, total_pkt_count); - trbs_per_td = count_isoc_trbs_needed(preq); - - ret = cdnsp_prepare_transfer(pdev, preq, trbs_per_td); - if (ret) - goto cleanup; /* * Set isoc specific data for the first TRB in a TD. @@ -2248,6 +2265,7 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, /* Calculate TRB length. */ trb_buff_len = TRB_BUFF_LEN_UP_TO_BOUNDARY(addr); + trb_buff_len = min(trb_buff_len, block_len); if (trb_buff_len > td_remain_len) trb_buff_len = td_remain_len; @@ -2256,7 +2274,8 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, trb_buff_len, td_len, preq, more_trbs_coming, 0); - length_field = TRB_LEN(trb_buff_len) | TRB_INTR_TARGET(0); + length_field = TRB_LEN(trb_buff_len) | TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); /* Only first TRB is isoc, overwrite otherwise. */ if (i) { @@ -2281,12 +2300,27 @@ static int cdnsp_queue_isoc_tx(struct cdnsp_device *pdev, } cdnsp_queue_trb(pdev, ep_ring, more_trbs_coming, - lower_32_bits(addr), upper_32_bits(addr), + lower_32_bits(send_addr), upper_32_bits(send_addr), length_field, field); running_total += trb_buff_len; addr += trb_buff_len; td_remain_len -= trb_buff_len; + + sent_len = trb_buff_len; + while (sg && sent_len >= block_len) { + /* New sg entry */ + --num_sgs; + sent_len -= block_len; + if (num_sgs != 0) { + sg = sg_next(sg); + block_len = sg_dma_len(sg); + addr = (u64)sg_dma_address(sg); + addr += sent_len; + } + } + block_len -= sent_len; + send_addr = addr; } /* Check TD length */ @@ -2324,30 +2358,6 @@ cleanup: return ret; } -int cdnsp_queue_isoc_tx_prepare(struct cdnsp_device *pdev, - struct cdnsp_request *preq) -{ - struct cdnsp_ring *ep_ring; - u32 ep_state; - int num_trbs; - int ret; - - ep_ring = preq->pep->ring; - ep_state = GET_EP_CTX_STATE(preq->pep->out_ctx); - num_trbs = count_isoc_trbs_needed(preq); - - /* - * Check the ring to guarantee there is enough room for the whole - * request. Do not insert any td of the USB Request to the ring if the - * check failed. - */ - ret = cdnsp_prepare_ring(pdev, ep_ring, ep_state, num_trbs, GFP_ATOMIC); - if (ret) - return ret; - - return cdnsp_queue_isoc_tx(pdev, preq); -} - /**** Command Ring Operations ****/ /* * Generic function for queuing a command TRB on the command ring. -- cgit v1.2.3 From 8867258e706acb1b36f2ce648fa702ae5800aa70 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:15 +0100 Subject: dt-bindings: usb: Correct and extend FOTG210 schema It turns out that this IP block exists in at least two incarnations: FOTG200 and FOTG210. The one in the Gemini is FOTG200, so add the variants and rectify the binding for Gemini. This affects things such as the placement of certain registers. It remains to be seen how similar this block is to the third USB block from Faraday, FUSB220. Signed-off-by: Linus Walleij Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-1-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/faraday,fotg210.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/faraday,fotg210.yaml b/Documentation/devicetree/bindings/usb/faraday,fotg210.yaml index 84b3b69256b1..3fe4d1564dfe 100644 --- a/Documentation/devicetree/bindings/usb/faraday,fotg210.yaml +++ b/Documentation/devicetree/bindings/usb/faraday,fotg210.yaml @@ -5,7 +5,7 @@ $id: http://devicetree.org/schemas/usb/faraday,fotg210.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Faraday Technology FOTG210 HS OTG USB 2.0 controller +title: Faraday Technology FOTG200 series HS OTG USB 2.0 controller maintainers: - Linus Walleij @@ -17,10 +17,11 @@ allOf: properties: compatible: oneOf: + - const: faraday,fotg200 - const: faraday,fotg210 - items: - const: cortina,gemini-usb - - const: faraday,fotg210 + - const: faraday,fotg200 reg: maxItems: 1 @@ -66,7 +67,7 @@ examples: #include #include usb0: usb@68000000 { - compatible = "cortina,gemini-usb", "faraday,fotg210"; + compatible = "cortina,gemini-usb", "faraday,fotg200"; reg = <0x68000000 0x1000>; interrupts = <10 IRQ_TYPE_LEVEL_HIGH>; resets = <&syscon GEMINI_RESET_USB0>; -- cgit v1.2.3 From 170da81aab077c9e85fc2b786413ca07942774a0 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:16 +0100 Subject: usb: fotg210: List different variants There are at least two variants of the FOTG: FOTG200 and FOTG210. Handle them in this driver and let's add more quirks as we go along. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-2-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index ee740a6da463..da9ea5957ccf 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -127,7 +127,9 @@ static int fotg210_remove(struct platform_device *pdev) #ifdef CONFIG_OF static const struct of_device_id fotg210_of_match[] = { + { .compatible = "faraday,fotg200" }, { .compatible = "faraday,fotg210" }, + /* TODO: can we also handle FUSB220? */ {}, }; MODULE_DEVICE_TABLE(of, fotg210_of_match); -- cgit v1.2.3 From baef5330d35b477056c0304ce1283f0aed4d5d20 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:17 +0100 Subject: usb: fotg210: Acquire memory resource in core The subdrivers are obtaining and mapping the memory resource separately. Create a common state container for the shared resources and start populating this by acquiring the IO memory resource and remap it and pass this to the subdrivers for host and peripheral. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-3-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 29 ++++++++++++++++++++++------- drivers/usb/fotg210/fotg210-hcd.c | 15 +++++---------- drivers/usb/fotg210/fotg210-hcd.h | 1 + drivers/usb/fotg210/fotg210-udc.c | 16 +++------------- drivers/usb/fotg210/fotg210-udc.h | 1 + drivers/usb/fotg210/fotg210.h | 24 ++++++++++++++++++++---- 6 files changed, 52 insertions(+), 34 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index da9ea5957ccf..0dd4957ad70a 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -33,9 +33,10 @@ #define GEMINI_MISC_USB0_MINI_B BIT(29) #define GEMINI_MISC_USB1_MINI_B BIT(30) -static int fotg210_gemini_init(struct device *dev, struct resource *res, +static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, enum usb_dr_mode mode) { + struct device *dev = fotg->dev; struct device_node *np = dev->of_node; struct regmap *map; bool wakeup; @@ -47,6 +48,7 @@ static int fotg210_gemini_init(struct device *dev, struct resource *res, dev_err(dev, "no syscon\n"); return PTR_ERR(map); } + fotg->map = map; wakeup = of_property_read_bool(np, "wakeup-source"); /* @@ -55,6 +57,7 @@ static int fotg210_gemini_init(struct device *dev, struct resource *res, */ mask = 0; if (res->start == 0x69000000) { + fotg->port = GEMINI_PORT_1; mask = GEMINI_MISC_USB1_VBUS_ON | GEMINI_MISC_USB1_MINI_B | GEMINI_MISC_USB1_WAKEUP; if (mode == USB_DR_MODE_HOST) @@ -64,6 +67,7 @@ static int fotg210_gemini_init(struct device *dev, struct resource *res, if (wakeup) val |= GEMINI_MISC_USB1_WAKEUP; } else { + fotg->port = GEMINI_PORT_0; mask = GEMINI_MISC_USB0_VBUS_ON | GEMINI_MISC_USB0_MINI_B | GEMINI_MISC_USB0_WAKEUP; if (mode == USB_DR_MODE_HOST) @@ -89,23 +93,34 @@ static int fotg210_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; enum usb_dr_mode mode; + struct fotg210 *fotg; int ret; + fotg = devm_kzalloc(dev, sizeof(*fotg), GFP_KERNEL); + if (!fotg) + return -ENOMEM; + fotg->dev = dev; + + fotg->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!fotg->res) + return -ENODEV; + + fotg->base = devm_ioremap_resource(dev, fotg->res); + if (!fotg->base) + return -ENOMEM; + mode = usb_get_dr_mode(dev); if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { - struct resource *res; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - ret = fotg210_gemini_init(dev, res, mode); + ret = fotg210_gemini_init(fotg, fotg->res, mode); if (ret) return ret; } if (mode == USB_DR_MODE_PERIPHERAL) - ret = fotg210_udc_probe(pdev); + ret = fotg210_udc_probe(pdev, fotg); else - ret = fotg210_hcd_probe(pdev); + ret = fotg210_hcd_probe(pdev, fotg); return ret; } diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 51ac93a2eb98..15ba5b1618e1 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -5557,11 +5557,10 @@ static void fotg210_init(struct fotg210_hcd *fotg210) * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. */ -int fotg210_hcd_probe(struct platform_device *pdev) +int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) { struct device *dev = &pdev->dev; struct usb_hcd *hcd; - struct resource *res; int irq; int retval; struct fotg210_hcd *fotg210; @@ -5585,18 +5584,14 @@ int fotg210_hcd_probe(struct platform_device *pdev) hcd->has_tt = 1; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(hcd->regs)) { - retval = PTR_ERR(hcd->regs); - goto failed_put_hcd; - } + hcd->regs = fotg->base; - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); + hcd->rsrc_start = fotg->res->start; + hcd->rsrc_len = resource_size(fotg->res); fotg210 = hcd_to_fotg210(hcd); + fotg210->fotg = fotg; fotg210->caps = hcd->regs; /* It's OK not to supply this clock */ diff --git a/drivers/usb/fotg210/fotg210-hcd.h b/drivers/usb/fotg210/fotg210-hcd.h index 0781442b7a24..13c9342982ee 100644 --- a/drivers/usb/fotg210/fotg210-hcd.h +++ b/drivers/usb/fotg210/fotg210-hcd.h @@ -182,6 +182,7 @@ struct fotg210_hcd { /* one per controller */ # define INCR(x) do {} while (0) #endif + struct fotg210 *fotg; /* Overarching FOTG210 device */ /* silicon clock */ struct clk *pclk; }; diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 87cca81bf4ac..a4a6f57c3190 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -1142,21 +1142,14 @@ int fotg210_udc_remove(struct platform_device *pdev) return 0; } -int fotg210_udc_probe(struct platform_device *pdev) +int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) { - struct resource *res; struct fotg210_udc *fotg210 = NULL; struct device *dev = &pdev->dev; int irq; int ret = 0; int i; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - pr_err("platform_get_resource error.\n"); - return -ENODEV; - } - irq = platform_get_irq(pdev, 0); if (irq < 0) { pr_err("could not get irq\n"); @@ -1169,6 +1162,7 @@ int fotg210_udc_probe(struct platform_device *pdev) return -ENOMEM; fotg210->dev = dev; + fotg210->fotg = fotg; /* It's OK not to supply this clock */ fotg210->pclk = devm_clk_get(dev, "PCLK"); @@ -1209,11 +1203,7 @@ int fotg210_udc_probe(struct platform_device *pdev) goto err_alloc; } - fotg210->reg = ioremap(res->start, resource_size(res)); - if (fotg210->reg == NULL) { - dev_err(dev, "ioremap error\n"); - goto err_alloc; - } + fotg210->reg = fotg->base; spin_lock_init(&fotg210->lock); diff --git a/drivers/usb/fotg210/fotg210-udc.h b/drivers/usb/fotg210/fotg210-udc.h index fadb57ca8d78..20335a38a410 100644 --- a/drivers/usb/fotg210/fotg210-udc.h +++ b/drivers/usb/fotg210/fotg210-udc.h @@ -236,6 +236,7 @@ struct fotg210_udc { unsigned long irq_trigger; struct device *dev; + struct fotg210 *fotg; struct usb_phy *phy; struct usb_gadget gadget; struct usb_gadget_driver *driver; diff --git a/drivers/usb/fotg210/fotg210.h b/drivers/usb/fotg210/fotg210.h index ef79d8323d89..50436cc16538 100644 --- a/drivers/usb/fotg210/fotg210.h +++ b/drivers/usb/fotg210/fotg210.h @@ -2,13 +2,28 @@ #ifndef __FOTG210_H #define __FOTG210_H +enum gemini_port { + GEMINI_PORT_NONE = 0, + GEMINI_PORT_0, + GEMINI_PORT_1, +}; + +struct fotg210 { + struct device *dev; + struct resource *res; + void __iomem *base; + struct regmap *map; + enum gemini_port port; +}; + #ifdef CONFIG_USB_FOTG210_HCD -int fotg210_hcd_probe(struct platform_device *pdev); +int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg); int fotg210_hcd_remove(struct platform_device *pdev); int fotg210_hcd_init(void); void fotg210_hcd_cleanup(void); #else -static inline int fotg210_hcd_probe(struct platform_device *pdev) +static inline int fotg210_hcd_probe(struct platform_device *pdev, + struct fotg210 *fotg) { return 0; } @@ -26,10 +41,11 @@ static inline void fotg210_hcd_cleanup(void) #endif #ifdef CONFIG_USB_FOTG210_UDC -int fotg210_udc_probe(struct platform_device *pdev); +int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg); int fotg210_udc_remove(struct platform_device *pdev); #else -static inline int fotg210_udc_probe(struct platform_device *pdev) +static inline int fotg210_udc_probe(struct platform_device *pdev, + struct fotg210 *fotg) { return 0; } -- cgit v1.2.3 From faaca436699603355c5c9711942c6441eec38b0e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:18 +0100 Subject: usb: fotg210: Move clock handling to core Grab the optional silicon block clock, prepare and enable it in the core before proceeding to prepare the host or peripheral driver. This saves duplicate code and also uses the simple devm_clk_get_optional_enabled() to do everything we really want to do. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-4-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 5 +++++ drivers/usb/fotg210/fotg210-hcd.c | 33 ++------------------------------- drivers/usb/fotg210/fotg210-udc.c | 30 +++--------------------------- drivers/usb/fotg210/fotg210-udc.h | 1 - drivers/usb/fotg210/fotg210.h | 1 + 5 files changed, 11 insertions(+), 59 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index 0dd4957ad70a..f90b2ae9ed9d 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -6,6 +6,7 @@ * driver. */ #include +#include #include #include #include @@ -109,6 +110,10 @@ static int fotg210_probe(struct platform_device *pdev) if (!fotg->base) return -ENOMEM; + fotg->pclk = devm_clk_get_optional_enabled(dev, "PCLK"); + if (IS_ERR(fotg->pclk)) + return PTR_ERR(fotg->pclk); + mode = usb_get_dr_mode(dev); if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 15ba5b1618e1..7bd1e8f3080d 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include @@ -5594,44 +5593,22 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) fotg210->fotg = fotg; fotg210->caps = hcd->regs; - /* It's OK not to supply this clock */ - fotg210->pclk = clk_get(dev, "PCLK"); - if (!IS_ERR(fotg210->pclk)) { - retval = clk_prepare_enable(fotg210->pclk); - if (retval) { - dev_err(dev, "failed to enable PCLK\n"); - goto failed_put_hcd; - } - } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { - /* - * Percolate deferrals, for anything else, - * just live without the clocking. - */ - retval = PTR_ERR(fotg210->pclk); - goto failed_dis_clk; - } - retval = fotg210_setup(hcd); if (retval) - goto failed_dis_clk; + goto failed_put_hcd; fotg210_init(fotg210); retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval) { dev_err(dev, "failed to add hcd with err %d\n", retval); - goto failed_dis_clk; + goto failed_put_hcd; } device_wakeup_enable(hcd->self.controller); platform_set_drvdata(pdev, hcd); return retval; -failed_dis_clk: - if (!IS_ERR(fotg210->pclk)) { - clk_disable_unprepare(fotg210->pclk); - clk_put(fotg210->pclk); - } failed_put_hcd: usb_put_hcd(hcd); fail_create_hcd: @@ -5647,12 +5624,6 @@ fail_create_hcd: int fotg210_hcd_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); - - if (!IS_ERR(fotg210->pclk)) { - clk_disable_unprepare(fotg210->pclk); - clk_put(fotg210->pclk); - } usb_remove_hcd(hcd); usb_put_hcd(hcd); diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index a4a6f57c3190..195dc5050046 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -1134,9 +1133,6 @@ int fotg210_udc_remove(struct platform_device *pdev) for (i = 0; i < FOTG210_MAX_NUM_EP; i++) kfree(fotg210->ep[i]); - if (!IS_ERR(fotg210->pclk)) - clk_disable_unprepare(fotg210->pclk); - kfree(fotg210); return 0; @@ -1164,34 +1160,17 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) fotg210->dev = dev; fotg210->fotg = fotg; - /* It's OK not to supply this clock */ - fotg210->pclk = devm_clk_get(dev, "PCLK"); - if (!IS_ERR(fotg210->pclk)) { - ret = clk_prepare_enable(fotg210->pclk); - if (ret) { - dev_err(dev, "failed to enable PCLK\n"); - goto err; - } - } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { - /* - * Percolate deferrals, for anything else, - * just live without the clocking. - */ - ret = -EPROBE_DEFER; - goto err; - } - fotg210->phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); if (IS_ERR(fotg210->phy)) { ret = PTR_ERR(fotg210->phy); if (ret == -EPROBE_DEFER) - goto err_pclk; + goto err_free; dev_info(dev, "no PHY found\n"); fotg210->phy = NULL; } else { ret = usb_phy_init(fotg210->phy); if (ret) - goto err_pclk; + goto err_free; dev_info(dev, "found and initialized PHY\n"); } @@ -1288,11 +1267,8 @@ err_map: err_alloc: for (i = 0; i < FOTG210_MAX_NUM_EP; i++) kfree(fotg210->ep[i]); -err_pclk: - if (!IS_ERR(fotg210->pclk)) - clk_disable_unprepare(fotg210->pclk); -err: +err_free: kfree(fotg210); return ret; } diff --git a/drivers/usb/fotg210/fotg210-udc.h b/drivers/usb/fotg210/fotg210-udc.h index 20335a38a410..22b72caf498c 100644 --- a/drivers/usb/fotg210/fotg210-udc.h +++ b/drivers/usb/fotg210/fotg210-udc.h @@ -231,7 +231,6 @@ struct fotg210_ep { struct fotg210_udc { spinlock_t lock; /* protect the struct */ void __iomem *reg; - struct clk *pclk; unsigned long irq_trigger; diff --git a/drivers/usb/fotg210/fotg210.h b/drivers/usb/fotg210/fotg210.h index 50436cc16538..4d0d4ae1a957 100644 --- a/drivers/usb/fotg210/fotg210.h +++ b/drivers/usb/fotg210/fotg210.h @@ -12,6 +12,7 @@ struct fotg210 { struct device *dev; struct resource *res; void __iomem *base; + struct clk *pclk; struct regmap *map; enum gemini_port port; }; -- cgit v1.2.3 From bb5fe85609c6e21e96fc5669ae200c68a1e05a9b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:19 +0100 Subject: usb: fotg210: Check role register in core Read the role register and check that we are in host/peripheral mode and issue warnings if we're not in the right role when probing respective driver. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-5-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index f90b2ae9ed9d..b69167bb4b99 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -18,6 +18,11 @@ #include "fotg210.h" +/* Role Register 0x80 */ +#define FOTG210_RR 0x80 +#define FOTG210_RR_ID BIT(21) /* 1 = B-device, 0 = A-device */ +#define FOTG210_RR_CROLE BIT(20) /* 1 = device, 0 = host */ + /* * Gemini-specific initialization function, only executed on the * Gemini SoC using the global misc control register. @@ -95,6 +100,7 @@ static int fotg210_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; enum usb_dr_mode mode; struct fotg210 *fotg; + u32 val; int ret; fotg = devm_kzalloc(dev, sizeof(*fotg), GFP_KERNEL); @@ -122,10 +128,16 @@ static int fotg210_probe(struct platform_device *pdev) return ret; } - if (mode == USB_DR_MODE_PERIPHERAL) + val = readl(fotg->base + FOTG210_RR); + if (mode == USB_DR_MODE_PERIPHERAL) { + if (!(val & FOTG210_RR_CROLE)) + dev_err(dev, "block not in device role\n"); ret = fotg210_udc_probe(pdev, fotg); - else + } else { + if (val & FOTG210_RR_CROLE) + dev_err(dev, "block not in host role\n"); ret = fotg210_hcd_probe(pdev, fotg); + } return ret; } -- cgit v1.2.3 From 816f518df20531a01eb8ddd2e84adc9791e16539 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:20 +0100 Subject: usb: fotg210-udc: Assign of_node and speed on start Follow the example set by other drivers to assign of_node and speed to the driver when binding, also print bound info akin to other UDC drivers. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-6-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 195dc5050046..7740df670b75 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -1015,6 +1015,10 @@ static int fotg210_udc_start(struct usb_gadget *g, /* hook up the driver */ driver->driver.bus = NULL; fotg210->driver = driver; + fotg210->gadget.dev.of_node = fotg210->dev->of_node; + fotg210->gadget.speed = USB_SPEED_UNKNOWN; + + dev_info(fotg210->dev, "bound driver %s\n", driver->driver.name); if (!IS_ERR_OR_NULL(fotg210->phy)) { ret = otg_set_peripheral(fotg210->phy->otg, @@ -1071,6 +1075,7 @@ static int fotg210_udc_stop(struct usb_gadget *g) fotg210_init(fotg210); fotg210->driver = NULL; + fotg210->gadget.speed = USB_SPEED_UNKNOWN; spin_unlock_irqrestore(&fotg210->lock, flags); -- cgit v1.2.3 From 3e679bde529e892a59e89d3a0728cc153e8ecefe Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 18 Jan 2023 08:09:21 +0100 Subject: usb: fotg210-udc: Implement VBUS session Implement VBUS session handling for FOTG210. This is mainly used by the UDC driver which needs to call down to the FOTG210 core and enable/disable VBUS, as this needs to be handled outside of the HCD and UDC drivers, by platform specific glue code. The Gemini has a special bit in a system register to turn VBUS on and off so we implement this in the FOTG210 core. Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-7-100388af9810@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 29 +++++++++++++++++++++++++++++ drivers/usb/fotg210/fotg210-udc.c | 17 +++++++++++++++++ drivers/usb/fotg210/fotg210.h | 2 ++ 3 files changed, 48 insertions(+) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index b69167bb4b99..c06f8eb3acbd 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -95,6 +95,35 @@ static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, return 0; } +/** + * fotg210_vbus() - Called by gadget driver to enable/disable VBUS + * @enable: true to enable VBUS, false to disable VBUS + */ +void fotg210_vbus(struct fotg210 *fotg, bool enable) +{ + u32 mask; + u32 val; + int ret; + + switch (fotg->port) { + case GEMINI_PORT_0: + mask = GEMINI_MISC_USB0_VBUS_ON; + val = enable ? GEMINI_MISC_USB0_VBUS_ON : 0; + break; + case GEMINI_PORT_1: + mask = GEMINI_MISC_USB1_VBUS_ON; + val = enable ? GEMINI_MISC_USB1_VBUS_ON : 0; + break; + default: + return; + } + ret = regmap_update_bits(fotg->map, GEMINI_GLOBAL_MISC_CTRL, mask, val); + if (ret) + dev_err(fotg->dev, "failed to %s VBUS\n", + enable ? "enable" : "disable"); + dev_info(fotg->dev, "%s: %s VBUS\n", __func__, enable ? "enable" : "disable"); +} + static int fotg210_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 7740df670b75..4334504fccc8 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -1082,9 +1082,26 @@ static int fotg210_udc_stop(struct usb_gadget *g) return 0; } +/** + * fotg210_vbus_session - Called by external transceiver to enable/disable udc + * @_gadget: usb gadget + * @is_active: 0 if should disable UDC VBUS, 1 if should enable + * + * Returns 0 + */ +static int fotg210_vbus_session(struct usb_gadget *g, int is_active) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + + /* Call down to core integration layer to drive or disable VBUS */ + fotg210_vbus(fotg210->fotg, is_active); + return 0; +} + static const struct usb_gadget_ops fotg210_gadget_ops = { .udc_start = fotg210_udc_start, .udc_stop = fotg210_udc_stop, + .vbus_session = fotg210_vbus_session, }; /** diff --git a/drivers/usb/fotg210/fotg210.h b/drivers/usb/fotg210/fotg210.h index 4d0d4ae1a957..c44c0afe2956 100644 --- a/drivers/usb/fotg210/fotg210.h +++ b/drivers/usb/fotg210/fotg210.h @@ -17,6 +17,8 @@ struct fotg210 { enum gemini_port port; }; +void fotg210_vbus(struct fotg210 *fotg, bool enable); + #ifdef CONFIG_USB_FOTG210_HCD int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg); int fotg210_hcd_remove(struct platform_device *pdev); -- cgit v1.2.3 From 5aba179c34291ef67206bce6fa0fe0873c8dfc4e Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 19 Jan 2023 11:33:22 +0800 Subject: usb: mtu3: fix the failure of qmu stop This happens when do stress test of uvc stream on/off which will enable/disable endpoints. uvc has four tx requests, and may disable endpoint between queue tx requests as following: enable ep --> start qmu queue tx request0 queue tx request1 queue tx request2 --> resume qmu disable ep --> stop qmu may fail [1] queue tx request3 --> will resume qmu, may cause qmu can't work when enable ep next time [2] [1]: when the tx fifo has some data to transmit, and try to stop qmu (stop ep) meanwhile resume qmu (queue tx request), it may cause stop qmu timeout, then can be fixed by flushing fifo when stop qmu. [2]: it resumes qmu again, shall stop qmu again. Signed-off-by: Chunfeng Yun Reported-by: Min Guo Link: https://lore.kernel.org/r/20230119033322.21426-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_gadget.c | 3 +-- drivers/usb/mtu3/mtu3_hw_regs.h | 1 + drivers/usb/mtu3/mtu3_qmu.c | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 80236e7b0895..c0264d5426bf 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -133,10 +133,9 @@ static int mtu3_ep_disable(struct mtu3_ep *mep) { struct mtu3 *mtu = mep->mtu; - mtu3_qmu_stop(mep); - /* abort all pending requests */ nuke(mep, -ESHUTDOWN); + mtu3_qmu_stop(mep); mtu3_deconfig_ep(mtu, mep); mtu3_gpd_ring_free(mep); diff --git a/drivers/usb/mtu3/mtu3_hw_regs.h b/drivers/usb/mtu3/mtu3_hw_regs.h index 519a58301f45..ee30ae0a4b54 100644 --- a/drivers/usb/mtu3/mtu3_hw_regs.h +++ b/drivers/usb/mtu3/mtu3_hw_regs.h @@ -128,6 +128,7 @@ #define TX_FIFOEMPTY BIT(24) #define TX_SENTSTALL BIT(22) #define TX_SENDSTALL BIT(21) +#define TX_FLUSHFIFO BIT(20) #define TX_TXPKTRDY BIT(16) #define TX_TXMAXPKTSZ_MSK GENMASK(10, 0) #define TX_TXMAXPKTSZ(x) ((x) & TX_TXMAXPKTSZ_MSK) diff --git a/drivers/usb/mtu3/mtu3_qmu.c b/drivers/usb/mtu3/mtu3_qmu.c index 2ea3157ddb6e..a2fdab8b63b2 100644 --- a/drivers/usb/mtu3/mtu3_qmu.c +++ b/drivers/usb/mtu3/mtu3_qmu.c @@ -388,6 +388,9 @@ void mtu3_qmu_stop(struct mtu3_ep *mep) } mtu3_writel(mbase, qcsr, QMU_Q_STOP); + if (mep->is_in) + mtu3_setbits(mbase, MU3D_EP_TXCR0(epnum), TX_FLUSHFIFO); + ret = readl_poll_timeout_atomic(mbase + qcsr, value, !(value & QMU_Q_ACTIVE), 1, 1000); if (ret) { @@ -395,6 +398,10 @@ void mtu3_qmu_stop(struct mtu3_ep *mep) return; } + /* flush fifo again to make sure the fifo is empty */ + if (mep->is_in) + mtu3_setbits(mbase, MU3D_EP_TXCR0(epnum), TX_FLUSHFIFO); + dev_dbg(mtu->dev, "%s's qmu stop now!\n", mep->name); } -- cgit v1.2.3 From b72654148e34c181f532275d03ef6f37de288f24 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:09 +0000 Subject: dt-bindings: usb: Add device id for Genesys Logic hub controller Add usb hub device id for Genesys Logic, Inc. GL852G Hub USB 2.0 root hub. Signed-off-by: Anand Moon Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230118044418.875-2-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/genesys,gl850g.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml index a9f831448cca..cc4cf92b70d1 100644 --- a/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml +++ b/Documentation/devicetree/bindings/usb/genesys,gl850g.yaml @@ -16,6 +16,7 @@ properties: compatible: enum: - usb5e3,608 + - usb5e3,610 reg: true -- cgit v1.2.3 From 3325f3e4534c3b93b52bf23c02854eff67fdbdf8 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:10 +0000 Subject: ARM: dts: amlogic: Used onboard usb hub reset to enable usb hub On Odroid c1 previously use gpio-hog to reset the usb hub, switch to used on board usb hub reset to enable the usb hub and enable power to usb hub. Add usb hub regulator as per the schematic. Signed-off-by: Anand Moon Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20230118044418.875-3-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/meson8b-odroidc1.dts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/arch/arm/boot/dts/meson8b-odroidc1.dts b/arch/arm/boot/dts/meson8b-odroidc1.dts index 04356bc639fa..d1f9ce4742a8 100644 --- a/arch/arm/boot/dts/meson8b-odroidc1.dts +++ b/arch/arm/boot/dts/meson8b-odroidc1.dts @@ -281,19 +281,6 @@ "J7 Header Pin 6", "J7 Header Pin 5", "J7 Header Pin 7", "HDMI_CEC", "SYS_LED", "", ""; - - /* - * WARNING: The USB Hub on the Odroid-C1/C1+ needs a reset signal - * to be turned high in order to be detected by the USB Controller. - * This signal should be handled by a USB specific power sequence - * in order to reset the Hub when USB bus is powered down. - */ - usb-hub { - gpio-hog; - gpios = ; - output-high; - line-name = "usb-hub-reset"; - }; }; &ir_receiver { @@ -381,5 +368,16 @@ }; &usb1 { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; status = "okay"; + + hub@1 { + /* Genesys Logic GL852G usb hub */ + compatible = "usb5e3,610"; + reg = <1>; + vdd-supply = <&p5v0>; + reset-gpio = <&gpio_ao GPIOAO_4 GPIO_ACTIVE_LOW>; + }; }; -- cgit v1.2.3 From f24859bbec8a5226211656cd293caed5ad3c7326 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:11 +0000 Subject: arm64: dts: amlogic: Used onboard usb hub reset on odroid c2 On Odroid c2 previously use gpio-hog to reset the usb hub, switch to used on-board usb hub reset to enable the usb hub and enable power to hub. Signed-off-by: Anand Moon Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20230118044418.875-4-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts index 201596247fd9..01356437a077 100644 --- a/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts +++ b/arch/arm64/boot/dts/amlogic/meson-gxbb-odroidc2.dts @@ -250,21 +250,6 @@ }; }; -&gpio_ao { - /* - * WARNING: The USB Hub on the Odroid-C2 needs a reset signal - * to be turned high in order to be detected by the USB Controller - * This signal should be handled by a USB specific power sequence - * in order to reset the Hub when USB bus is powered down. - */ - hog-0 { - gpio-hog; - gpios = ; - output-high; - line-name = "usb-hub-reset"; - }; -}; - &hdmi_tx { status = "okay"; pinctrl-0 = <&hdmi_hpd_pins>, <&hdmi_i2c_pins>; @@ -414,5 +399,16 @@ }; &usb1 { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; status = "okay"; + + hub@1 { + /* Genesys Logic GL852G USB 2.0 hub */ + compatible = "usb5e3,610"; + reg = <1>; + vdd-supply = <&p5v0>; + reset-gpio = <&gpio_ao GPIOAO_4 GPIO_ACTIVE_LOW>; + }; }; -- cgit v1.2.3 From db7cab26c3d1382ec85d8cadf642f57250edea58 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:12 +0000 Subject: usb: misc: onboard_usb_hub: add Genesys Logic GL852G hub support Genesys Logic GL852G is a 4-port USB 2.0 STT hub that has a reset pin to toggle and a 5.0V core supply exported though an integrated LDO is available for powering it. Add the support for this hub, for controlling the reset pin and the core power supply. Signed-off-by: Anand Moon Acked-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20230118044418.875-5-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 1 + drivers/usb/misc/onboard_usb_hub.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index 94e7966e199d..9bf59792fb94 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -409,6 +409,7 @@ static void onboard_hub_usbdev_disconnect(struct usb_device *udev) static const struct usb_device_id onboard_hub_id_table[] = { { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index 62129a6a1ba5..163fc07abf25 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -26,11 +26,16 @@ static const struct onboard_hub_pdata genesys_gl850g_data = { .reset_us = 3, }; +static const struct onboard_hub_pdata genesys_gl852g_data = { + .reset_us = 50, +}; + static const struct of_device_id onboard_hub_match[] = { { .compatible = "usb424,2514", .data = µchip_usb424_data, }, { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, + { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, -- cgit v1.2.3 From 5e86e1a5076b88807f8b5215d93e96274996dcb9 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:13 +0000 Subject: vendor-prefixes: Add VIA Labs, Inc. Add the vendor prefix for VIA Labs, Inc. (VLI) is a supplier of USB and USB Power Delivery controllers for multi-functional devices and platforms. Website: https://www.via-labs.com/ Acked-by: Krzysztof Kozlowski Signed-off-by: Anand Moon Link: https://lore.kernel.org/r/20230118044418.875-6-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 70ffb3780621..d19b7f9cef4c 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1398,6 +1398,8 @@ patternProperties: description: Vertexcom Technologies, Inc. "^via,.*": description: VIA Technologies, Inc. + "^vialab,.*": + description: VIA Labs, Inc. "^vicor,.*": description: Vicor Corporation "^videostrong,.*": -- cgit v1.2.3 From 31360c28dfdd18d77496bcceee44c10ab86cf7a0 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:14 +0000 Subject: dt-bindings: usb: Add binding for Via lab VL817 hub controller The VIA Lab VL817 is a USB 3.1 Gen 1 hub and USB 2.0 hub controller that features 4 downstream ports and 1 otg, with an internal 5V regulator and has external reset pin. Add a device tree binding for its USB protocol part. The internal LDO is not covered by this and can just be modelled as a fixed regulator. Add combo of USB 2.0 and USB 3.0 root hub using peer-hub. Signed-off-by: Anand Moon Link: https://lore.kernel.org/r/20230118044418.875-7-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/vialab,vl817.yaml | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/vialab,vl817.yaml diff --git a/Documentation/devicetree/bindings/usb/vialab,vl817.yaml b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml new file mode 100644 index 000000000000..5f9771e22058 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/vialab,vl817.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Via labs VL817 USB 3.1 hub controller + +maintainers: + - Anand Moon + +allOf: + - $ref: usb-device.yaml# + +properties: + compatible: + items: + - enum: + - usb2109,2817 + - usb2109,817 + + reg: true + + reset-gpios: + description: GPIO controlling the RESET# pin. + + vdd-supply: + description: + phandle to the regulator that provides power to the hub. + + peer-hub: + $ref: '/schemas/types.yaml#/definitions/phandle' + description: + phandle to the peer hub on the controller. + +required: + - peer-hub + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + + usb { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + vdd-supply = <&vcc_5v>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio 20 GPIO_ACTIVE_LOW>; + }; + + /* 3.1 hub on port 4 */ + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + vdd-supply = <&vcc_5v>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio 20 GPIO_ACTIVE_LOW>; + }; + }; -- cgit v1.2.3 From 71593b2020b37fd39ac91efae64d79962eb6c867 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:15 +0000 Subject: arm64: dts: amlogic: Used onboard usb hub reset on odroid c4 On Odroid c4 previously use gpio-hog to reset the usb hub, switch to used on-board usb hub reset to enable the usb hub and enable power to hub. USB hub is combination of USB 2.0 and USB 3.0 root hub so use peer-hub node to link then. Signed-off-by: Anand Moon Link: https://lore.kernel.org/r/20230118044418.875-8-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../arm64/boot/dts/amlogic/meson-sm1-odroid-c4.dts | 36 ++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-c4.dts b/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-c4.dts index 8c30ce63686e..d04768a66bfe 100644 --- a/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-c4.dts +++ b/arch/arm64/boot/dts/amlogic/meson-sm1-odroid-c4.dts @@ -26,20 +26,30 @@ sound { model = "ODROID-C4"; }; -}; -&gpio { - /* - * WARNING: The USB Hub on the Odroid-C4 needs a reset signal - * to be turned high in order to be detected by the USB Controller - * This signal should be handled by a USB specific power sequence - * in order to reset the Hub when USB bus is powered down. - */ - hog-0 { - gpio-hog; - gpios = ; - output-high; - line-name = "usb-hub-reset"; + /* USB hub supports both USB 2.0 and USB 3.0 root hub */ + usb-hub { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub_2_0: hub@1 { + compatible = "usb2109,2817"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_LOW>; + vdd-supply = <&vcc_5v>; + }; + + /* 3.1 hub on port 4 */ + hub_3_0: hub@2 { + compatible = "usb2109,817"; + reg = <2>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio GPIOH_4 GPIO_ACTIVE_LOW>; + vdd-supply = <&vcc_5v>; + }; }; }; -- cgit v1.2.3 From 143307adcf55a3bb2ed53e012dfc56a07cf5193f Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:16 +0000 Subject: usb: misc: onboard_usb_hub: add VIA LAB VL817 hub support VIA LAB VL817 is a 4-port USB 3.1 hub and USB 2.0 root hub that has a reset pin to toggle and a 5.0V core supply exported though an integrated LDO is available for powering it. Add the support for this hub, for controlling the reset pin and the core power supply. Add USB device id's for USB 2.0 and USB 3.0 root hub. Signed-off-by: Anand Moon Acked-by: Matthias Kaehlcke Link: https://lore.kernel.org/r/20230118044418.875-9-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_hub.c | 3 +++ drivers/usb/misc/onboard_usb_hub.h | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index 9bf59792fb94..945d4e7125ea 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -335,6 +335,7 @@ static struct platform_driver onboard_hub_driver = { #define VENDOR_ID_MICROCHIP 0x0424 #define VENDOR_ID_REALTEK 0x0bda #define VENDOR_ID_TI 0x0451 +#define VENDOR_ID_VIA 0x2109 /* * Returns the onboard_hub platform device that is associated with the USB @@ -417,6 +418,8 @@ static const struct usb_device_id onboard_hub_id_table[] = { { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 */ {} }; MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index 163fc07abf25..0a943a154649 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -30,6 +30,10 @@ static const struct onboard_hub_pdata genesys_gl852g_data = { .reset_us = 50, }; +static const struct onboard_hub_pdata vialab_vl817_data = { + .reset_us = 10, +}; + static const struct of_device_id onboard_hub_match[] = { { .compatible = "usb424,2514", .data = µchip_usb424_data, }, { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, @@ -40,6 +44,8 @@ static const struct of_device_id onboard_hub_match[] = { { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, + { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, {} }; -- cgit v1.2.3 From e02e6ca588b88cf0d5eeecb3dffbe6bf10e6fa53 Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Wed, 18 Jan 2023 04:44:17 +0000 Subject: arm64: defconfig: Enable USB onboard HUB driver Enable the USB onboard HUB driver, used on Amlogic boards. Signed-off-by: Anand Moon Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20230118044418.875-10-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 851e8f9be06d..42c3528a2473 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -923,6 +923,7 @@ CONFIG_USB_SERIAL_CP210X=m CONFIG_USB_SERIAL_FTDI_SIO=m CONFIG_USB_SERIAL_OPTION=m CONFIG_USB_HSIC_USB3503=y +CONFIG_USB_ONBOARD_HUB=m CONFIG_NOP_USB_XCEIV=y CONFIG_USB_GADGET=y CONFIG_USB_RENESAS_USBHS_UDC=m -- cgit v1.2.3 From 93c473948c588978cd55d9a3adad8b3e8057aa21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3=20=C3=81gila=20Bitsch?= Date: Fri, 13 Jan 2023 01:53:19 +0100 Subject: usb: gadget: add WebUSB landing page support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a custom (non-USB IF) extension to the USB standard: https://wicg.github.io/webusb/ This specification is published under the W3C Community Contributor Agreement, which in particular allows to implement the specification without any royalties. The specification allows USB gadgets to announce an URL to landing page and describes a Javascript interface for websites to interact with the USB gadget, if the user allows it. It is currently supported by Chromium-based browsers, such as Chrome, Edge and Opera on all major operating systems including Linux. This patch adds optional support for Linux-based USB gadgets wishing to expose such a landing page. During device enumeration, a host recognizes that the announced USB version is at least 2.01, which means, that there are BOS descriptors available. The device than announces WebUSB support using a platform device capability. This includes a vendor code under which the landing page URL can be retrieved using a vendor-specific request. Previously, the BOS descriptors would unconditionally include an LPM related descriptor, as BOS descriptors were only ever sent when the device was LPM capable. As this is no longer the case, this patch puts this descriptor behind a lpm_capable condition. Usage is modeled after os_desc descriptors: echo 1 > webusb/use echo "https://www.kernel.org" > webusb/landingPage lsusb will report the device with the following lines: Platform Device Capability: bLength 24 bDescriptorType 16 bDevCapabilityType 5 bReserved 0 PlatformCapabilityUUID {3408b638-09a9-47a0-8bfd-a0768815b665} WebUSB: bcdVersion 1.00 bVendorCode 0 iLandingPage 1 https://www.kernel.org Signed-off-by: Jó Ágila Bitsch Link: https://lore.kernel.org/r/Y8Crf8P2qAWuuk/F@jo-einhundert Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget | 13 ++ drivers/usb/gadget/composite.c | 102 ++++++++++++++-- drivers/usb/gadget/configfs.c | 166 ++++++++++++++++++++++++++ include/linux/usb/composite.h | 7 ++ include/linux/usb/webusb.h | 83 +++++++++++++ include/uapi/linux/usb/ch9.h | 16 +++ 6 files changed, 377 insertions(+), 10 deletions(-) create mode 100644 include/linux/usb/webusb.h diff --git a/Documentation/ABI/testing/configfs-usb-gadget b/Documentation/ABI/testing/configfs-usb-gadget index b7943aa7e997..a8bb896def54 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget +++ b/Documentation/ABI/testing/configfs-usb-gadget @@ -143,3 +143,16 @@ Description: qw_sign an identifier to be reported as "OS String" proper ============= =============================================== + +What: /config/usb-gadget/gadget/webusb +Date: Dec 2022 +KernelVersion: 6.3 +Description: + This group contains "WebUSB" extension handling attributes. + + ============= =============================================== + use flag turning "WebUSB" support on/off + bcdVersion bcd WebUSB specification version number + bVendorCode one-byte value used for custom per-device + landingPage UTF-8 encoded URL of the device's landing page + ============= =============================================== diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 403563c06477..8e2603688016 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -14,9 +14,11 @@ #include #include #include +#include #include #include +#include #include #include "u_os_desc.h" @@ -713,14 +715,16 @@ static int bos_desc(struct usb_composite_dev *cdev) * A SuperSpeed device shall include the USB2.0 extension descriptor * and shall support LPM when operating in USB2.0 HS mode. */ - usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); - usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; - usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; - usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; - usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT | - USB_BESL_SUPPORT | besl); + if (cdev->gadget->lpm_capable) { + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT | + USB_BESL_SUPPORT | besl); + } /* * The Superspeed USB Capability descriptor shall be implemented by all @@ -821,6 +825,37 @@ static int bos_desc(struct usb_composite_dev *cdev) } } + /* The WebUSB Platform Capability descriptor */ + if (cdev->use_webusb) { + struct usb_plat_dev_cap_descriptor *webusb_cap; + struct usb_webusb_cap_data *webusb_cap_data; + uuid_t webusb_uuid = WEBUSB_UUID; + + webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + webusb_cap_data = (struct usb_webusb_cap_data *) webusb_cap->CapabilityData; + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, + USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE)); + + webusb_cap->bLength = USB_DT_USB_PLAT_DEV_CAP_SIZE(USB_WEBUSB_CAP_DATA_SIZE); + webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE; + webusb_cap->bReserved = 0; + export_uuid(webusb_cap->UUID, &webusb_uuid); + + if (cdev->bcd_webusb_version != 0) + webusb_cap_data->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version); + else + webusb_cap_data->bcdVersion = WEBUSB_VERSION_1_00; + + webusb_cap_data->bVendorCode = cdev->b_webusb_vendor_code; + + if (strnlen(cdev->landing_page, sizeof(cdev->landing_page)) > 0) + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_PRESENT; + else + webusb_cap_data->iLandingPage = WEBUSB_LANDING_PAGE_NOT_PRESENT; + } + return le16_to_cpu(bos->wTotalLength); } @@ -1744,7 +1779,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) cdev->desc.bcdUSB = cpu_to_le16(0x0210); } } else { - if (gadget->lpm_capable) + if (gadget->lpm_capable || cdev->use_webusb) cdev->desc.bcdUSB = cpu_to_le16(0x0201); else cdev->desc.bcdUSB = cpu_to_le16(0x0200); @@ -1779,7 +1814,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) break; case USB_DT_BOS: if (gadget_is_superspeed(gadget) || - gadget->lpm_capable) { + gadget->lpm_capable || cdev->use_webusb) { value = bos_desc(cdev); value = min(w_length, (u16) value); } @@ -2013,6 +2048,53 @@ unknown: goto check_value; } + /* + * WebUSB URL descriptor handling, following: + * https://wicg.github.io/webusb/#device-requests + */ + if (cdev->use_webusb && + ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) && + w_index == WEBUSB_GET_URL && + w_value == WEBUSB_LANDING_PAGE_PRESENT && + ctrl->bRequest == cdev->b_webusb_vendor_code) { + unsigned int landing_page_length; + unsigned int landing_page_offset; + struct webusb_url_descriptor *url_descriptor = + (struct webusb_url_descriptor *)cdev->req->buf; + + url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE; + + if (strncasecmp(cdev->landing_page, "https://", 8) == 0) { + landing_page_offset = 8; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS; + } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) { + landing_page_offset = 7; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP; + } else { + landing_page_offset = 0; + url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE; + } + + landing_page_length = strnlen(cdev->landing_page, + sizeof(url_descriptor->URL) + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset); + + if (ctrl->wLength < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + + landing_page_length) + landing_page_length = ctrl->wLength + - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset; + + memcpy(url_descriptor->URL, + cdev->landing_page + landing_page_offset, + landing_page_length - landing_page_offset); + url_descriptor->bLength = landing_page_length + - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH; + + value = url_descriptor->bLength; + + goto check_value; + } + VDBG(cdev, "non-core control req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 96121d1c8df4..4a1236063c19 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "configfs.h" #include "u_f.h" #include "u_os_desc.h" @@ -39,6 +40,7 @@ struct gadget_info { struct config_group configs_group; struct config_group strings_group; struct config_group os_desc_group; + struct config_group webusb_group; struct mutex lock; struct usb_gadget_strings *gstrings[MAX_USB_STRING_LANGS + 1]; @@ -50,6 +52,11 @@ struct gadget_info { bool use_os_desc; char b_vendor_code; char qw_sign[OS_STRING_QW_SIGN_LEN]; + bool use_webusb; + u16 bcd_webusb_version; + u8 b_webusb_vendor_code; + char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; + spinlock_t spinlock; bool unbind; }; @@ -780,6 +787,154 @@ static void gadget_strings_attr_release(struct config_item *item) USB_CONFIG_STRING_RW_OPS(gadget_strings); USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info); +static inline struct gadget_info *webusb_item_to_gadget_info( + struct config_item *item) +{ + return container_of(to_config_group(item), + struct gadget_info, webusb_group); +} + +static ssize_t webusb_use_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "%d\n", + webusb_item_to_gadget_info(item)->use_webusb); +} + +static ssize_t webusb_use_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + int ret; + bool use; + + mutex_lock(&gi->lock); + ret = kstrtobool(page, &use); + if (!ret) { + gi->use_webusb = use; + ret = len; + } + mutex_unlock(&gi->lock); + + return ret; +} + +static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "0x%04x\n", + webusb_item_to_gadget_info(item)->bcd_webusb_version); +} + +static ssize_t webusb_bcdVersion_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + u16 bcdVersion; + int ret; + + mutex_lock(&gi->lock); + ret = kstrtou16(page, 0, &bcdVersion); + if (ret) + goto out; + ret = is_valid_bcd(bcdVersion); + if (ret) + goto out; + + gi->bcd_webusb_version = bcdVersion; + ret = len; + +out: + mutex_unlock(&gi->lock); + + return ret; +} + +static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "0x%02x\n", + webusb_item_to_gadget_info(item)->b_webusb_vendor_code); +} + +static ssize_t webusb_bVendorCode_store(struct config_item *item, + const char *page, size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + int ret; + u8 b_vendor_code; + + mutex_lock(&gi->lock); + ret = kstrtou8(page, 0, &b_vendor_code); + if (!ret) { + gi->b_webusb_vendor_code = b_vendor_code; + ret = len; + } + mutex_unlock(&gi->lock); + + return ret; +} + +static ssize_t webusb_landingPage_show(struct config_item *item, char *page) +{ + return sysfs_emit(page, "%s\n", webusb_item_to_gadget_info(item)->landing_page); +} + +static ssize_t webusb_landingPage_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_info *gi = webusb_item_to_gadget_info(item); + unsigned int bytes_to_strip = 0; + int l = len; + + if (page[l - 1] == '\n') { + --l; + ++bytes_to_strip; + } + + if (l > sizeof(gi->landing_page)) { + pr_err("webusb: landingPage URL too long\n"); + return -EINVAL; + } + + // validation + if (strncasecmp(page, "https://", 8) == 0) + bytes_to_strip = 8; + else if (strncasecmp(page, "http://", 7) == 0) + bytes_to_strip = 7; + else + bytes_to_strip = 0; + + if (l > U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + bytes_to_strip) { + pr_err("webusb: landingPage URL %d bytes too long for given URL scheme\n", + l - U8_MAX + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH - bytes_to_strip); + return -EINVAL; + } + + mutex_lock(&gi->lock); + // ensure 0 bytes are set, in case the new landing page is shorter then the old one. + memset(gi->landing_page, 0, sizeof(gi->landing_page)); + memcpy(gi->landing_page, page, l); + mutex_unlock(&gi->lock); + + return len; +} + +CONFIGFS_ATTR(webusb_, use); +CONFIGFS_ATTR(webusb_, bVendorCode); +CONFIGFS_ATTR(webusb_, bcdVersion); +CONFIGFS_ATTR(webusb_, landingPage); + +static struct configfs_attribute *webusb_attrs[] = { + &webusb_attr_use, + &webusb_attr_bcdVersion, + &webusb_attr_bVendorCode, + &webusb_attr_landingPage, + NULL, +}; + +static struct config_item_type webusb_type = { + .ct_attrs = webusb_attrs, + .ct_owner = THIS_MODULE, +}; + static inline struct gadget_info *os_desc_item_to_gadget_info( struct config_item *item) { @@ -1341,6 +1496,13 @@ static int configfs_composite_bind(struct usb_gadget *gadget, gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id; } + if (gi->use_webusb) { + cdev->use_webusb = true; + cdev->bcd_webusb_version = gi->bcd_webusb_version; + cdev->b_webusb_vendor_code = gi->b_webusb_vendor_code; + memcpy(cdev->landing_page, gi->landing_page, WEBUSB_URL_RAW_MAX_LENGTH); + } + if (gi->use_os_desc) { cdev->use_os_string = true; cdev->b_vendor_code = gi->b_vendor_code; @@ -1605,6 +1767,10 @@ static struct config_group *gadgets_make( &os_desc_type); configfs_add_default_group(&gi->os_desc_group, &gi->group); + config_group_init_type_name(&gi->webusb_group, "webusb", + &webusb_type); + configfs_add_default_group(&gi->webusb_group, &gi->group); + gi->composite.bind = configfs_do_nothing; gi->composite.unbind = configfs_do_nothing; gi->composite.suspend = NULL; diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 43ac3fa760db..91d22c3ed458 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -474,6 +475,12 @@ struct usb_composite_dev { struct usb_configuration *os_desc_config; unsigned int use_os_string:1; + /* WebUSB */ + u16 bcd_webusb_version; + u8 b_webusb_vendor_code; + char landing_page[WEBUSB_URL_RAW_MAX_LENGTH]; + unsigned int use_webusb:1; + /* private: */ /* internals */ unsigned int suspended:1; diff --git a/include/linux/usb/webusb.h b/include/linux/usb/webusb.h new file mode 100644 index 000000000000..b430d84357f3 --- /dev/null +++ b/include/linux/usb/webusb.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * WebUSB descriptors and constants + * + * Copyright (C) 2023 Jó Ágila Bitsch + */ + +#ifndef __LINUX_USB_WEBUSB_H +#define __LINUX_USB_WEBUSB_H + +#include "uapi/linux/usb/ch9.h" + +/* + * little endian PlatformCapablityUUID for WebUSB + * 3408b638-09a9-47a0-8bfd-a0768815b665 + * to identify Platform Device Capability descriptors as referring to WebUSB + * + * the UUID above MUST be sent over the wire as the byte sequence: + * {0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}. + */ +#define WEBUSB_UUID \ + UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65) + +/* + * WebUSB Platform Capability data + * + * A device announces support for the + * WebUSB command set by including the following Platform Descriptor Data in its + * Binary Object Store associated with the WebUSB_UUID above. + * See: https://wicg.github.io/webusb/#webusb-platform-capability-descriptor + */ +struct usb_webusb_cap_data { + __le16 bcdVersion; +#define WEBUSB_VERSION_1_00 cpu_to_le16(0x0100) /* currently only version 1.00 is defined */ + u8 bVendorCode; + u8 iLandingPage; +#define WEBUSB_LANDING_PAGE_NOT_PRESENT 0 +#define WEBUSB_LANDING_PAGE_PRESENT 1 /* we chose the fixed index 1 for the URL descriptor */ +} __packed; + +#define USB_WEBUSB_CAP_DATA_SIZE 4 + +/* + * Get URL Request + * + * The request to fetch an URL is defined in https://wicg.github.io/webusb/#get-url as: + * bmRequestType: (USB_DIR_IN | USB_TYPE_VENDOR) = 11000000B + * bRequest: bVendorCode + * wValue: iLandingPage + * wIndex: GET_URL = 2 + * wLength: Descriptor Length (typically U8_MAX = 255) + * Data: URL Descriptor + */ +#define WEBUSB_GET_URL 2 + +/* + * This descriptor contains a single URL and is returned by the Get URL request. + * + * See: https://wicg.github.io/webusb/#url-descriptor + */ +struct webusb_url_descriptor { + u8 bLength; +#define WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH 3 + u8 bDescriptorType; +#define WEBUSB_URL_DESCRIPTOR_TYPE 3 + u8 bScheme; +#define WEBUSB_URL_SCHEME_HTTP 0 +#define WEBUSB_URL_SCHEME_HTTPS 1 +#define WEBUSB_URL_SCHEME_NONE 255 + u8 URL[U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH]; +} __packed; + +/* + * Buffer size to hold the longest URL that can be in an URL descriptor + * + * The descriptor can be U8_MAX bytes long. + * WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH bytes are used for a header. + * Since the longest prefix that might be stripped is "https://", we may accommodate an additional + * 8 bytes. + */ +#define WEBUSB_URL_RAW_MAX_LENGTH (U8_MAX - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + 8) + +#endif /* __LINUX_USB_USBNET_H */ diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index 31fcfa084e63..b17e3a21b15f 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -947,6 +947,22 @@ struct usb_ss_container_id_descriptor { #define USB_DT_USB_SS_CONTN_ID_SIZE 20 +/* + * Platform Device Capability descriptor: Defines platform specific device + * capabilities + */ +#define USB_PLAT_DEV_CAP_TYPE 5 +struct usb_plat_dev_cap_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDevCapabilityType; + __u8 bReserved; + __u8 UUID[16]; + __u8 CapabilityData[]; +} __attribute__((packed)); + +#define USB_DT_USB_PLAT_DEV_CAP_SIZE(capability_data_size) (20 + capability_data_size) + /* * SuperSpeed Plus USB Capability descriptor: Defines the set of * SuperSpeed Plus USB specific device level capabilities -- cgit v1.2.3 From dd2f003e4e85b154754d5a83e0c3b1b517d1f802 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 20 Jan 2023 15:02:41 +0100 Subject: Revert "arm64: tegra: Enable XUSB host function on Jetson AGX Orin" This reverts commit 1b17df99730ab63b49848e61ef19d8ee583684c5. It causes merge issues in linux-next and was asked to be dropped from this tree. Reported-by: Thierry Reding Cc: Wayne Chang Cc: Thierry Reding Cc: Jon Hunter Link: https://lore.kernel.org/r/Y8lEhwT3VWEn9w+R@orome Signed-off-by: Greg Kroah-Hartman --- .../arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi | 48 ------- .../dts/nvidia/tegra234-p3737-0000+p3701-0000.dts | 93 ------------- arch/arm64/boot/dts/nvidia/tegra234.dtsi | 145 --------------------- 3 files changed, 286 deletions(-) diff --git a/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi b/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi index 4fae2547e90e..8b86ea9cb50c 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234-p3701-0000.dtsi @@ -67,29 +67,6 @@ non-removable; }; - padctl@3520000 { - vclamp-usb-supply = <&vdd_ao_1v8>; - avdd-usb-supply = <&vdd_ao_3v3>; - - ports { - usb2-0 { - vbus-supply = <&vdd_5v0_sys>; - }; - - usb2-1 { - vbus-supply = <&vdd_5v0_sys>; - }; - - usb2-2 { - vbus-supply = <&vdd_5v0_sys>; - }; - - usb2-3 { - vbus-supply = <&vdd_5v0_sys>; - }; - }; - }; - rtc@c2a0000 { status = "okay"; }; @@ -98,29 +75,4 @@ nvidia,invert-interrupt; }; }; - - vdd_5v0_sys: regulator-vdd-5v0-sys { - compatible = "regulator-fixed"; - regulator-name = "VIN_SYS_5V0"; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - regulator-always-on; - regulator-boot-on; - }; - - vdd_ao_1v8: regulator-vdd-1v8-ao { - compatible = "regulator-fixed"; - regulator-name = "vdd-AO-1v8"; - regulator-min-microvolt = <1800000>; - regulator-max-microvolt = <1800000>; - regulator-always-on; - }; - - vdd_ao_3v3: regulator-vdd-3v3-ao { - compatible = "regulator-fixed"; - regulator-name = "vdd-AO-3v3"; - regulator-min-microvolt = <3300000>; - regulator-max-microvolt = <3300000>; - regulator-always-on; - }; }; diff --git a/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts b/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts index 32c58aa00035..96aa2267b06d 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts +++ b/arch/arm64/boot/dts/nvidia/tegra234-p3737-0000+p3701-0000.dts @@ -2022,99 +2022,6 @@ nvidia,model = "NVIDIA Jetson AGX Orin HDA"; status = "okay"; }; - - padctl@3520000 { - status = "okay"; - - pads { - usb2 { - lanes { - usb2-0 { - status = "okay"; - }; - - usb2-1 { - status = "okay"; - }; - - usb2-2 { - status = "okay"; - }; - - usb2-3 { - status = "okay"; - }; - }; - }; - - usb3 { - lanes { - usb3-0 { - status = "okay"; - }; - - usb3-1 { - status = "okay"; - }; - - usb3-2 { - status = "okay"; - }; - }; - }; - }; - - ports { - usb2-0 { - mode = "host"; - status = "okay"; - }; - - usb2-1 { - mode = "host"; - status = "okay"; - }; - - usb2-2 { - mode = "host"; - status = "okay"; - }; - - usb2-3 { - mode = "host"; - status = "okay"; - }; - - usb3-0 { - nvidia,usb2-companion = <1>; - status = "okay"; - }; - - usb3-1 { - nvidia,usb2-companion = <0>; - status = "okay"; - }; - - usb3-2 { - nvidia,usb2-companion = <3>; - status = "okay"; - }; - }; - }; - - usb@3610000 { - status = "okay"; - - phys = <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-0}>, - <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-1}>, - <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-2}>, - <&{/bus@0/padctl@3520000/pads/usb2/lanes/usb2-3}>, - <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-0}>, - <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-1}>, - <&{/bus@0/padctl@3520000/pads/usb3/lanes/usb3-2}>; - phy-names = "usb2-0", "usb2-1", "usb2-2", "usb2-3", - "usb3-0", "usb3-1", "usb3-2"; - }; }; chosen { diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi index b1bc8b5dcd7d..eaf05ee9acd1 100644 --- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi @@ -1079,151 +1079,6 @@ status = "disabled"; }; - xusb_padctl: padctl@3520000 { - compatible = "nvidia,tegra234-xusb-padctl"; - reg = <0x03520000 0x20000>, - <0x03540000 0x10000>; - reg-names = "padctl", "ao"; - interrupts = ; - - resets = <&bpmp TEGRA234_RESET_XUSB_PADCTL>; - reset-names = "padctl"; - - status = "disabled"; - - pads { - usb2 { - clocks = <&bpmp TEGRA234_CLK_USB2_TRK>; - clock-names = "trk"; - - lanes { - usb2-0 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb2-1 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb2-2 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb2-3 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - }; - }; - - usb3 { - lanes { - usb3-0 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb3-1 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb3-2 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - - usb3-3 { - nvidia,function = "xusb"; - status = "disabled"; - #phy-cells = <0>; - }; - }; - }; - }; - - ports { - usb2-0 { - status = "disabled"; - }; - - usb2-1 { - status = "disabled"; - }; - - usb2-2 { - status = "disabled"; - }; - - usb2-3 { - status = "disabled"; - }; - - usb3-0 { - status = "disabled"; - }; - - usb3-1 { - status = "disabled"; - }; - - usb3-2 { - status = "disabled"; - }; - - usb3-3 { - status = "disabled"; - }; - }; - }; - - usb@3610000 { - compatible = "nvidia,tegra234-xusb"; - reg = <0x03610000 0x40000>, - <0x03600000 0x10000>, - <0x03650000 0x10000>; - reg-names = "hcd", "fpci", "bar2"; - - interrupts = , - ; - - clocks = <&bpmp TEGRA234_CLK_XUSB_CORE_HOST>, - <&bpmp TEGRA234_CLK_XUSB_FALCON>, - <&bpmp TEGRA234_CLK_XUSB_CORE_SS>, - <&bpmp TEGRA234_CLK_XUSB_SS>, - <&bpmp TEGRA234_CLK_CLK_M>, - <&bpmp TEGRA234_CLK_XUSB_FS>, - <&bpmp TEGRA234_CLK_UTMIP_PLL>, - <&bpmp TEGRA234_CLK_CLK_M>, - <&bpmp TEGRA234_CLK_PLLE>; - clock-names = "xusb_host", "xusb_falcon_src", - "xusb_ss", "xusb_ss_src", "xusb_hs_src", - "xusb_fs_src", "pll_u_480m", "clk_m", - "pll_e"; - interconnects = <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTR &emc>, - <&mc TEGRA234_MEMORY_CLIENT_XUSB_HOSTW &emc>; - interconnect-names = "dma-mem", "write"; - iommus = <&smmu_niso1 TEGRA234_SID_XUSB_HOST>; - - power-domains = <&bpmp TEGRA234_POWER_DOMAIN_XUSBC>, - <&bpmp TEGRA234_POWER_DOMAIN_XUSBA>; - power-domain-names = "xusb_host", "xusb_ss"; - - nvidia,xusb-padctl = <&xusb_padctl>; - dma-coherent; - status = "disabled"; - }; - fuse@3810000 { compatible = "nvidia,tegra234-efuse"; reg = <0x03810000 0x10000>; -- cgit v1.2.3 From 4ba2e7cd986270dbb44eb693199a9d3dd3edf984 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 23 Jan 2023 11:00:07 +0100 Subject: usb: host: ehci-fsl: Use DRV_NAME "fsl-ehci" is used for both MODULE_ALIAS and driver name. As they have to match use DRV_NAME in both locations. Acked-by: Alan Stern Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20230123100007.1479090-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-fsl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 38d06e5abfbb..d74fa5ba845b 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -712,7 +712,7 @@ static struct platform_driver ehci_fsl_driver = { .remove = fsl_ehci_drv_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { - .name = "fsl-ehci", + .name = DRV_NAME, .pm = EHCI_FSL_PM_OPS, }, }; -- cgit v1.2.3 From e55f67391fa986f7357edba0ca59e668d99c3a5f Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Mon, 23 Jan 2023 08:35:06 +0100 Subject: fotg210-udc: Add missing completion handler This is used when responding to GET_STATUS requests. Without this, it crashes on completion. Fixes: b84a8dee23fd ("usb: gadget: add Faraday fotg210_udc driver") Signed-off-by: Fabian Vogt Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230123073508.2350402-2-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 4334504fccc8..53b7d078a54d 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -709,6 +709,20 @@ static int fotg210_is_epnstall(struct fotg210_ep *ep) return value & INOUTEPMPSR_STL_EP ? 1 : 0; } +/* For EP0 requests triggered by this driver (currently GET_STATUS response) */ +static void fotg210_ep0_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct fotg210_ep *ep; + struct fotg210_udc *fotg210; + + ep = container_of(_ep, struct fotg210_ep, ep); + fotg210 = ep->fotg210; + + if (req->status || req->actual != req->length) { + dev_warn(&fotg210->gadget.dev, "EP0 request failed: %d\n", req->status); + } +} + static void fotg210_get_status(struct fotg210_udc *fotg210, struct usb_ctrlrequest *ctrl) { @@ -1253,6 +1267,8 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) if (fotg210->ep0_req == NULL) goto err_map; + fotg210->ep0_req->complete = fotg210_ep0_complete; + fotg210_init(fotg210); fotg210_disable_unplug(fotg210); -- cgit v1.2.3 From 76d62981b5bc2397bff3478a62514c7ec488dc60 Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Mon, 23 Jan 2023 08:35:07 +0100 Subject: fotg210-udc: Introduce and use a fotg210_ack_int function This is in preparation of support for devices where interrupts are acked differently. Signed-off-by: Fabian Vogt Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230123073508.2350402-3-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 54 +++++++++++++-------------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 53b7d078a54d..c0518ae35034 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -28,6 +28,14 @@ static const char udc_name[] = "fotg210_udc"; static const char * const fotg210_ep_name[] = { "ep0", "ep1", "ep2", "ep3", "ep4"}; +static void fotg210_ack_int(struct fotg210_udc *fotg210, u32 offset, u32 mask) +{ + u32 value = ioread32(fotg210->reg + offset); + + value &= ~mask; + iowrite32(value, fotg210->reg + offset); +} + static void fotg210_disable_fifo_int(struct fotg210_ep *ep) { u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR1); @@ -303,8 +311,7 @@ static void fotg210_wait_dma_done(struct fotg210_ep *ep) goto dma_reset; } while (!(value & DISGR2_DMA_CMPLT)); - value &= ~DISGR2_DMA_CMPLT; - iowrite32(value, ep->fotg210->reg + FOTG210_DISGR2); + fotg210_ack_int(ep->fotg210, FOTG210_DISGR2, DISGR2_DMA_CMPLT); return; dma_reset: @@ -844,14 +851,6 @@ static void fotg210_ep0in(struct fotg210_udc *fotg210) } } -static void fotg210_clear_comabt_int(struct fotg210_udc *fotg210) -{ - u32 value = ioread32(fotg210->reg + FOTG210_DISGR0); - - value &= ~DISGR0_CX_COMABT_INT; - iowrite32(value, fotg210->reg + FOTG210_DISGR0); -} - static void fotg210_in_fifo_handler(struct fotg210_ep *ep) { struct fotg210_request *req = list_entry(ep->queue.next, @@ -893,60 +892,43 @@ static irqreturn_t fotg210_irq(int irq, void *_fotg210) void __iomem *reg = fotg210->reg + FOTG210_DISGR2; u32 int_grp2 = ioread32(reg); u32 int_msk2 = ioread32(fotg210->reg + FOTG210_DMISGR2); - u32 value; int_grp2 &= ~int_msk2; if (int_grp2 & DISGR2_USBRST_INT) { usb_gadget_udc_reset(&fotg210->gadget, fotg210->driver); - value = ioread32(reg); - value &= ~DISGR2_USBRST_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_USBRST_INT); pr_info("fotg210 udc reset\n"); } if (int_grp2 & DISGR2_SUSP_INT) { - value = ioread32(reg); - value &= ~DISGR2_SUSP_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_SUSP_INT); pr_info("fotg210 udc suspend\n"); } if (int_grp2 & DISGR2_RESM_INT) { - value = ioread32(reg); - value &= ~DISGR2_RESM_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RESM_INT); pr_info("fotg210 udc resume\n"); } if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { - value = ioread32(reg); - value &= ~DISGR2_ISO_SEQ_ERR_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_ISO_SEQ_ERR_INT); pr_info("fotg210 iso sequence error\n"); } if (int_grp2 & DISGR2_ISO_SEQ_ABORT_INT) { - value = ioread32(reg); - value &= ~DISGR2_ISO_SEQ_ABORT_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_ISO_SEQ_ABORT_INT); pr_info("fotg210 iso sequence abort\n"); } if (int_grp2 & DISGR2_TX0BYTE_INT) { fotg210_clear_tx0byte(fotg210); - value = ioread32(reg); - value &= ~DISGR2_TX0BYTE_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_TX0BYTE_INT); pr_info("fotg210 transferred 0 byte\n"); } if (int_grp2 & DISGR2_RX0BYTE_INT) { fotg210_clear_rx0byte(fotg210); - value = ioread32(reg); - value &= ~DISGR2_RX0BYTE_INT; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RX0BYTE_INT); pr_info("fotg210 received 0 byte\n"); } if (int_grp2 & DISGR2_DMA_ERROR) { - value = ioread32(reg); - value &= ~DISGR2_DMA_ERROR; - iowrite32(value, reg); + fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_DMA_ERROR); } } @@ -960,7 +942,7 @@ static irqreturn_t fotg210_irq(int irq, void *_fotg210) /* the highest priority in this source register */ if (int_grp0 & DISGR0_CX_COMABT_INT) { - fotg210_clear_comabt_int(fotg210); + fotg210_ack_int(fotg210, FOTG210_DISGR0, DISGR0_CX_COMABT_INT); pr_info("fotg210 CX command abort\n"); } -- cgit v1.2.3 From c5d4297f0c20dd8650019786ab7e94296e6b8647 Mon Sep 17 00:00:00 2001 From: Fabian Vogt Date: Mon, 23 Jan 2023 08:35:08 +0100 Subject: fotg210-udc: Improve device initialization Reset the device explicitly to get into a known state and also set the chip enable bit. Additionally, mask interrupts which aren't handled. Signed-off-by: Fabian Vogt Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20230123073508.2350402-4-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 15 +++++++++++++++ drivers/usb/fotg210/fotg210-udc.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index c0518ae35034..ae29787de0fd 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -7,6 +7,7 @@ * Author : Yuan-Hsin Chen */ +#include #include #include #include @@ -1023,6 +1024,11 @@ static int fotg210_udc_start(struct usb_gadget *g, dev_err(fotg210->dev, "can't bind to phy\n"); } + /* chip enable */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value |= DMCR_CHIP_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + /* enable device global interrupt */ value = ioread32(fotg210->reg + FOTG210_DMCR); value |= DMCR_GLINT_EN; @@ -1039,6 +1045,15 @@ static void fotg210_init(struct fotg210_udc *fotg210) iowrite32(GMIR_MHC_INT | GMIR_MOTG_INT | GMIR_INT_POLARITY, fotg210->reg + FOTG210_GMIR); + /* mask interrupts for groups other than 0-2 */ + iowrite32(~(DMIGR_MINT_G0 | DMIGR_MINT_G1 | DMIGR_MINT_G2), + fotg210->reg + FOTG210_DMIGR); + + /* udc software reset */ + iowrite32(DMCR_SFRST, fotg210->reg + FOTG210_DMCR); + /* Better wait a bit, but without a datasheet, no idea how long. */ + usleep_range(100, 200); + /* disable device global interrupt */ value = ioread32(fotg210->reg + FOTG210_DMCR); value &= ~DMCR_GLINT_EN; diff --git a/drivers/usb/fotg210/fotg210-udc.h b/drivers/usb/fotg210/fotg210-udc.h index 22b72caf498c..252cb2b8e2fe 100644 --- a/drivers/usb/fotg210/fotg210-udc.h +++ b/drivers/usb/fotg210/fotg210-udc.h @@ -58,6 +58,8 @@ /* Device Mask of Interrupt Group Register (0x130) */ #define FOTG210_DMIGR 0x130 +#define DMIGR_MINT_G2 (1 << 2) +#define DMIGR_MINT_G1 (1 << 1) #define DMIGR_MINT_G0 (1 << 0) /* Device Mask of Interrupt Source Group 0(0x134) */ -- cgit v1.2.3 From 861fa1c3fafffe47e6845ce95c847b2422792fcc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 17:44:33 +0200 Subject: usb: fotg210-hcd: use sysfs_emit() to instead of scnprintf() Follow the advice of the Documentation/filesystems/sysfs.rst and show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120154437.22025-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-hcd.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 7bd1e8f3080d..46752a75c428 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -4686,14 +4686,11 @@ static ssize_t uframe_periodic_max_show(struct device *dev, struct device_attribute *attr, char *buf) { struct fotg210_hcd *fotg210; - int n; fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); - n = scnprintf(buf, PAGE_SIZE, "%d\n", fotg210->uframe_periodic_max); - return n; + return sysfs_emit(buf, "%d\n", fotg210->uframe_periodic_max); } - static ssize_t uframe_periodic_max_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { -- cgit v1.2.3 From 7159deb7622741efc1730ffb24df707384db73bb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 17:44:34 +0200 Subject: usb: fotg210-hcd: Don't shadow error codes in store() kstrtox() along with regmap API can return different error codes based on circumstances. Don't shadow them when returning to the caller. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120154437.22025-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-hcd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 46752a75c428..5a934f5343a7 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -4702,8 +4702,10 @@ static ssize_t uframe_periodic_max_store(struct device *dev, ssize_t ret; fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); - if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) - return -EINVAL; + + ret = kstrtouint(buf, 0, &uframe_periodic_max); + if (ret) + return ret; if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { fotg210_info(fotg210, "rejecting invalid request for uframe_periodic_max=%u\n", -- cgit v1.2.3 From 6a426eb418791975c39680b7abd5f26dc2c4f66e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 17:44:35 +0200 Subject: usb: fotg210-udc: remove redundant error logging A call to platform_get_irq() already prints an error on failure within its own implementation. So printing another error based on its return value in the caller is redundant and should be removed. The clean up also makes if condition block braces unnecessary. Remove that as well. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120154437.22025-3-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-udc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index ae29787de0fd..21f31123cd61 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -1180,10 +1180,8 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) int i; irq = platform_get_irq(pdev, 0); - if (irq < 0) { - pr_err("could not get irq\n"); - return -ENODEV; - } + if (irq < 0) + return irq; /* initialize udc */ fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL); -- cgit v1.2.3 From 6df3d3aadb6474b9cda105576d042d5d31adec0c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 17:44:36 +0200 Subject: usb: fotg210: Switch to use dev_err_probe() Switch to use dev_err_probe() to simplify the error paths and unify message template. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120154437.22025-4-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 6 ++---- drivers/usb/fotg210/fotg210-hcd.c | 8 +++----- drivers/usb/fotg210/fotg210-udc.c | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index c06f8eb3acbd..ce00d9407ce5 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -50,10 +50,8 @@ static int fotg210_gemini_init(struct fotg210 *fotg, struct resource *res, int ret; map = syscon_regmap_lookup_by_phandle(np, "syscon"); - if (IS_ERR(map)) { - dev_err(dev, "no syscon\n"); - return PTR_ERR(map); - } + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), "no syscon\n"); fotg->map = map; wakeup = of_property_read_bool(np, "wakeup-source"); diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 5a934f5343a7..613d29f04bcb 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -5575,8 +5575,7 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) hcd = usb_create_hcd(&fotg210_fotg210_hc_driver, dev, dev_name(dev)); if (!hcd) { - dev_err(dev, "failed to create hcd\n"); - retval = -ENOMEM; + retval = dev_err_probe(dev, -ENOMEM, "failed to create hcd\n"); goto fail_create_hcd; } @@ -5600,7 +5599,7 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval) { - dev_err(dev, "failed to add hcd with err %d\n", retval); + dev_err_probe(dev, retval, "failed to add hcd\n"); goto failed_put_hcd; } device_wakeup_enable(hcd->self.controller); @@ -5611,8 +5610,7 @@ int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) failed_put_hcd: usb_put_hcd(hcd); fail_create_hcd: - dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); - return retval; + return dev_err_probe(dev, retval, "init %s fail\n", dev_name(dev)); } /* diff --git a/drivers/usb/fotg210/fotg210-udc.c b/drivers/usb/fotg210/fotg210-udc.c index 21f31123cd61..9034eaf773ad 100644 --- a/drivers/usb/fotg210/fotg210-udc.c +++ b/drivers/usb/fotg210/fotg210-udc.c @@ -1271,7 +1271,7 @@ int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) ret = request_irq(irq, fotg210_irq, IRQF_SHARED, udc_name, fotg210); if (ret < 0) { - dev_err(dev, "request_irq error (%d)\n", ret); + dev_err_probe(dev, ret, "request_irq error\n"); goto err_req; } -- cgit v1.2.3 From c05ad0fb639c10cce0a92757f9777ec48ceafef2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 17:44:37 +0200 Subject: usb: fotg210: use devm_platform_get_and_ioremap_resource() Convert platform_get_resource(), devm_ioremap_resource() to a single call to devm_platform_get_and_ioremap_resource(), as this is exactly what this function does. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120154437.22025-5-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index ce00d9407ce5..202d80adca2c 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -135,11 +135,7 @@ static int fotg210_probe(struct platform_device *pdev) return -ENOMEM; fotg->dev = dev; - fotg->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!fotg->res) - return -ENODEV; - - fotg->base = devm_ioremap_resource(dev, fotg->res); + fotg->base = devm_platform_get_and_ioremap_resource(pdev, 0, &fotg->res); if (!fotg->base) return -ENOMEM; -- cgit v1.2.3 From b39483d66af1a6244927e02e4433569a8fb90b17 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 12 Jan 2023 20:11:14 -0800 Subject: dt-bindings: usb: Introduce GPIO-based SBU mux Introduce a binding for GPIO-based mux hardware used for connecting, disconnecting and switching orientation of the SBU lines in USB Type-C applications. Signed-off-by: Bjorn Andersson Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20230113041115.4189210-1-quic_bjorande@quicinc.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/gpio-sbu-mux.yaml | 110 +++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml diff --git a/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml new file mode 100644 index 000000000000..bf4b1d016e1f --- /dev/null +++ b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/usb/gpio-sbu-mux.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: GPIO-based SBU mux + +maintainers: + - Bjorn Andersson + +description: + In USB Type-C applications the SBU lines needs to be connected, disconnected + and swapped depending on the altmode and orientation. This binding describes + a family of hardware solutions which switches between these modes using GPIO + signals. + +properties: + compatible: + items: + - enum: + - onnn,fsusb43l10x + - pericom,pi3usb102 + - const: gpio-sbu-mux + + enable-gpios: + description: Switch enable GPIO + + select-gpios: + description: Orientation select + + vcc-supply: + description: power supply + + mode-switch: + description: Flag the port as possible handle of altmode switching + type: boolean + + orientation-switch: + description: Flag the port as possible handler of orientation switching + type: boolean + + port: + $ref: /schemas/graph.yaml#/properties/port + description: + A port node to link the SBU mux to a TypeC controller for the purpose of + handling altmode muxing and orientation switching. + +required: + - compatible + - enable-gpios + - select-gpios + - mode-switch + - orientation-switch + - port + +additionalProperties: false + +examples: + - | + #include + + tcpm { + connector { + compatible = "usb-c-connector"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + tcpm_hs_out: endpoint { + remote-endpoint = <&usb_hs_phy_in>; + }; + }; + + port@1 { + reg = <1>; + tcpm_ss_out: endpoint { + remote-endpoint = <&usb_ss_phy_in>; + }; + }; + + port@2 { + reg = <2>; + tcpm_sbu_out: endpoint { + remote-endpoint = <&sbu_mux_in>; + }; + }; + }; + }; + }; + + sbu-mux { + compatible = "pericom,pi3usb102", "gpio-sbu-mux"; + + enable-gpios = <&tlmm 101 GPIO_ACTIVE_LOW>; + select-gpios = <&tlmm 164 GPIO_ACTIVE_HIGH>; + + mode-switch; + orientation-switch; + + port { + sbu_mux_in: endpoint { + remote-endpoint = <&tcpm_sbu_out>; + }; + }; + }; +... -- cgit v1.2.3 From 065ded319d39ce91a3785fa15020cd5a17c6c5d2 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 12 Jan 2023 20:11:15 -0800 Subject: usb: typec: mux: Introduce GPIO-based SBU mux A design found in various Qualcomm-based boards is to use a USB switch, controlled through a pair of GPIO lines to connect, disconnect and switch the orientation of the SBU lines in USB Type-C applications. This introduces a generic driver, which implements the typec_switch and typec_mux interfaces to perform these operations. Reviewed-by: Heikki Krogerus Signed-off-by: Bjorn Andersson Signed-off-by: Bjorn Andersson Link: https://lore.kernel.org/r/20230113041115.4189210-2-quic_bjorande@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/Kconfig | 6 ++ drivers/usb/typec/mux/Makefile | 1 + drivers/usb/typec/mux/gpio-sbu-mux.c | 172 +++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/usb/typec/mux/gpio-sbu-mux.c diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 5eb2c17d72c1..c46fa4f9d3df 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -12,6 +12,12 @@ config TYPEC_MUX_FSA4480 common USB Type-C connector. If compiled as a module, the module will be named fsa4480. +config TYPEC_MUX_GPIO_SBU + tristate "Generic GPIO based SBU mux for USB Type-C applications" + help + Say Y or M if your system uses a GPIO based mux for managing the + connected state and the swapping of the SBU lines in a Type-C port. + 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 e52a56c16bfb..dda67e19b58b 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o +obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o diff --git a/drivers/usb/typec/mux/gpio-sbu-mux.c b/drivers/usb/typec/mux/gpio-sbu-mux.c new file mode 100644 index 000000000000..f62516dafe8f --- /dev/null +++ b/drivers/usb/typec/mux/gpio-sbu-mux.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_sbu_mux { + struct gpio_desc *enable_gpio; + struct gpio_desc *select_gpio; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct mutex lock; /* protect enabled and swapped */ + bool enabled; + bool swapped; +}; + +static int gpio_sbu_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct gpio_sbu_mux *sbu_mux = typec_switch_get_drvdata(sw); + bool enabled; + bool swapped; + + mutex_lock(&sbu_mux->lock); + + enabled = sbu_mux->enabled; + swapped = sbu_mux->swapped; + + switch (orientation) { + case TYPEC_ORIENTATION_NONE: + enabled = false; + break; + case TYPEC_ORIENTATION_NORMAL: + swapped = false; + break; + case TYPEC_ORIENTATION_REVERSE: + swapped = true; + break; + } + + if (enabled != sbu_mux->enabled) + gpiod_set_value(sbu_mux->enable_gpio, enabled); + + if (swapped != sbu_mux->swapped) + gpiod_set_value(sbu_mux->select_gpio, swapped); + + sbu_mux->enabled = enabled; + sbu_mux->swapped = swapped; + + mutex_unlock(&sbu_mux->lock); + + return 0; +} + +static int gpio_sbu_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct gpio_sbu_mux *sbu_mux = typec_mux_get_drvdata(mux); + + mutex_lock(&sbu_mux->lock); + + switch (state->mode) { + case TYPEC_STATE_SAFE: + case TYPEC_STATE_USB: + sbu_mux->enabled = false; + break; + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_E: + sbu_mux->enabled = true; + break; + default: + break; + } + + gpiod_set_value(sbu_mux->enable_gpio, sbu_mux->enabled); + + mutex_unlock(&sbu_mux->lock); + + return 0; +} + +static int gpio_sbu_mux_probe(struct platform_device *pdev) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &pdev->dev; + struct gpio_sbu_mux *sbu_mux; + + sbu_mux = devm_kzalloc(dev, sizeof(*sbu_mux), GFP_KERNEL); + if (!sbu_mux) + return -ENOMEM; + + mutex_init(&sbu_mux->lock); + + sbu_mux->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(sbu_mux->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->enable_gpio), + "unable to acquire enable gpio\n"); + + sbu_mux->select_gpio = devm_gpiod_get(dev, "select", GPIOD_OUT_LOW); + if (IS_ERR(sbu_mux->select_gpio)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->select_gpio), + "unable to acquire select gpio\n"); + + sw_desc.drvdata = sbu_mux; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = gpio_sbu_switch_set; + + sbu_mux->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(sbu_mux->sw)) + return dev_err_probe(dev, PTR_ERR(sbu_mux->sw), + "failed to register typec switch\n"); + + mux_desc.drvdata = sbu_mux; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = gpio_sbu_mux_set; + + sbu_mux->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(sbu_mux->mux)) { + typec_switch_unregister(sbu_mux->sw); + return dev_err_probe(dev, PTR_ERR(sbu_mux->mux), + "failed to register typec mux\n"); + } + + platform_set_drvdata(pdev, sbu_mux); + + return 0; +} + +static int gpio_sbu_mux_remove(struct platform_device *pdev) +{ + struct gpio_sbu_mux *sbu_mux = platform_get_drvdata(pdev); + + gpiod_set_value(sbu_mux->enable_gpio, 0); + + typec_mux_unregister(sbu_mux->mux); + typec_switch_unregister(sbu_mux->sw); + + return 0; +} + +static const struct of_device_id gpio_sbu_mux_match[] = { + { .compatible = "gpio-sbu-mux", }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_sbu_mux_match); + +static struct platform_driver gpio_sbu_mux_driver = { + .probe = gpio_sbu_mux_probe, + .remove = gpio_sbu_mux_remove, + .driver = { + .name = "gpio_sbu_mux", + .of_match_table = gpio_sbu_mux_match, + }, +}; +module_platform_driver(gpio_sbu_mux_driver); + +MODULE_DESCRIPTION("GPIO based SBU mux driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6f7fb48d2478091e5d7a49d331c230715c4dc65e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 20 Jan 2023 20:24:34 +0200 Subject: usb: gadget: Move kstrtox() out of lock The kstrtox() calls operate on local (to the function) variables and do not need to be serialized. We may call them out of the lock. Reviewed-by: John Keeping Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230120182434.24245-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 78e7353e397b..63dc15b4f6d8 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -808,15 +808,15 @@ static ssize_t webusb_use_store(struct config_item *item, const char *page, int ret; bool use; - mutex_lock(&gi->lock); ret = kstrtobool(page, &use); - if (!ret) { - gi->use_webusb = use; - ret = len; - } + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->use_webusb = use; mutex_unlock(&gi->lock); - return ret; + return len; } static ssize_t webusb_bcdVersion_show(struct config_item *item, char *page) @@ -832,10 +832,12 @@ static ssize_t webusb_bcdVersion_store(struct config_item *item, u16 bcdVersion; int ret; - mutex_lock(&gi->lock); ret = kstrtou16(page, 0, &bcdVersion); if (ret) - goto out; + return ret; + + mutex_lock(&gi->lock); + ret = is_valid_bcd(bcdVersion); if (ret) goto out; @@ -862,15 +864,15 @@ static ssize_t webusb_bVendorCode_store(struct config_item *item, int ret; u8 b_vendor_code; - mutex_lock(&gi->lock); ret = kstrtou8(page, 0, &b_vendor_code); - if (!ret) { - gi->b_webusb_vendor_code = b_vendor_code; - ret = len; - } + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->b_webusb_vendor_code = b_vendor_code; mutex_unlock(&gi->lock); - return ret; + return len; } static ssize_t webusb_landingPage_show(struct config_item *item, char *page) @@ -956,15 +958,15 @@ static ssize_t os_desc_use_store(struct config_item *item, const char *page, int ret; bool use; - mutex_lock(&gi->lock); ret = kstrtobool(page, &use); - if (!ret) { - gi->use_os_desc = use; - ret = len; - } + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->use_os_desc = use; mutex_unlock(&gi->lock); - return ret; + return len; } static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page) @@ -980,15 +982,15 @@ static ssize_t os_desc_b_vendor_code_store(struct config_item *item, int ret; u8 b_vendor_code; - mutex_lock(&gi->lock); ret = kstrtou8(page, 0, &b_vendor_code); - if (!ret) { - gi->b_vendor_code = b_vendor_code; - ret = len; - } + if (ret) + return ret; + + mutex_lock(&gi->lock); + gi->b_vendor_code = b_vendor_code; mutex_unlock(&gi->lock); - return ret; + return len; } static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) @@ -1113,15 +1115,15 @@ static ssize_t ext_prop_type_store(struct config_item *item, u8 type; int ret; - if (desc->opts_mutex) - mutex_lock(desc->opts_mutex); ret = kstrtou8(page, 0, &type); if (ret) - goto end; - if (type < USB_EXT_PROP_UNICODE || type > USB_EXT_PROP_UNICODE_MULTI) { - ret = -EINVAL; - goto end; - } + return ret; + + if (type < USB_EXT_PROP_UNICODE || type > USB_EXT_PROP_UNICODE_MULTI) + return -EINVAL; + + if (desc->opts_mutex) + mutex_lock(desc->opts_mutex); if ((ext_prop->type == USB_EXT_PROP_BINARY || ext_prop->type == USB_EXT_PROP_LE32 || @@ -1138,12 +1140,10 @@ static ssize_t ext_prop_type_store(struct config_item *item, type == USB_EXT_PROP_BE32)) ext_prop->data_len >>= 1; ext_prop->type = type; - ret = len; -end: if (desc->opts_mutex) mutex_unlock(desc->opts_mutex); - return ret; + return len; } static ssize_t ext_prop_data_show(struct config_item *item, char *page) -- cgit v1.2.3 From 25d6d1bfc213bce03d2e34c9e43477e01ffba7c3 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Fri, 20 Jan 2023 20:58:26 +0000 Subject: usb: typec: altmodes/displayport: Update active state Update the altmode "active" state when we receive Acks for Enter and Exit Mode commands. Having the right state is necessary to change Pin Assignments using the 'pin_assignment" sysfs file. Cc: Heikki Krogerus Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20230120205827.740900-1-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/altmodes/displayport.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 746bfbf3d557..20db51471c98 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -277,9 +277,11 @@ static int dp_altmode_vdm(struct typec_altmode *alt, case CMDT_RSP_ACK: switch (cmd) { case CMD_ENTER_MODE: + typec_altmode_update_active(alt, true); dp->state = DP_STATE_UPDATE; break; case CMD_EXIT_MODE: + typec_altmode_update_active(alt, false); dp->data.status = 0; dp->data.conf = 0; break; -- cgit v1.2.3 From 9e6f4c8b880bb34851c21db3869e3096d113ccbf Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Fri, 20 Jan 2023 20:58:28 +0000 Subject: usb: typec: tcpm: Remove altmode active state updates Since the "active" state for partner altmodes is now being taken care of by the altmode driver itself (specifically, DisplayPort altmode), we no longer need to do so from the port driver. So remove the calls to typec_altmode_update_active() from TCPM. Suggested-by: Heikki Krogerus Reviewed-by: Benson Leung Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20230120205827.740900-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 5928461b3fad..a0d943d78580 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1702,14 +1702,11 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, } break; case CMD_ENTER_MODE: - if (adev && pdev) { - typec_altmode_update_active(pdev, true); + if (adev && pdev) *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; - } return 0; case CMD_EXIT_MODE: if (adev && pdev) { - typec_altmode_update_active(pdev, false); /* Back to USB Operation */ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; return 0; -- cgit v1.2.3 From e538e3614c5c5a9a692c96a76e9b06e552dc9b6c Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 24 Jan 2023 15:35:11 -0800 Subject: usb: fotg210: fix a Kconfig spelling mistake Correct a spelling mistake (reported by codespell). Signed-off-by: Randy Dunlap Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20230124233511.23616-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/fotg210/Kconfig b/drivers/usb/fotg210/Kconfig index 2b05968735ba..87a16258274e 100644 --- a/drivers/usb/fotg210/Kconfig +++ b/drivers/usb/fotg210/Kconfig @@ -29,7 +29,7 @@ config USB_FOTG210_UDC bool "Faraday FOTG210 USB Peripheral Controller support" help Faraday USB2.0 OTG controller which can be configured as - high speed or full speed USB device. This driver suppports + high speed or full speed USB device. This driver supports Bulk Transfer so far. Say "y" to link the driver statically, or "m" to build a -- cgit v1.2.3 From 353a17ec62f48495a0ba8ffaac7f5ea3cebe5039 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 20:59:35 -0600 Subject: dt-bindings: usb: snps,dwc3: Allow power-domains property The Rockchip RK3399 DWC3 node has 'power-domains' property which isn't allowed by the schema: usb@fe900000: Unevaluated properties are not allowed ('power-domains' was unexpected) Allow DWC3 nodes to have a power-domains entry. We could instead move the power-domains property to the parent wrapper node, but the could be an ABI break (Linux shouldn't care). Also, we don't want to encourage the pattern of wrapper nodes just to define resources such as clocks, resets, power-domains, etc. when not necessary. Reviewed-by: Thinh Nguyen Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230124025936.3256213-1-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index 6d78048c4613..be36956af53b 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -91,6 +91,16 @@ properties: - usb2-phy - usb3-phy + power-domains: + description: + The DWC3 has 2 power-domains. The power management unit (PMU) and + everything else. The PMU is typically always powered and may not have an + entry. + minItems: 1 + items: + - description: Core + - description: Power management unit + resets: minItems: 1 -- cgit v1.2.3 From 5442e7912050bff9f866d03ee5005731dbd7a708 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 20:59:36 -0600 Subject: dt-bindings: usb: rockchip,dwc3: Move RK3399 to its own schema The rockchip,dwc3.yaml schema defines a single DWC3 node, but the RK3399 uses the discouraged parent wrapper node and child 'generic' DWC3 node. The intent was to modify the RK3399 DTs to use a single node, but the DT changes were rejected for ABI reasons. However, the schema was accepted as-is. To fix this, we need to move the RK3399 binding to its own schema file. The RK3328 and RK3568 bindings are correct and use a single node. Cc: Johan Jonker Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230124025936.3256213-2-robh@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/rockchip,dwc3.yaml | 10 +- .../bindings/usb/rockchip,rk3399-dwc3.yaml | 115 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/rockchip,rk3399-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml index b3798d94d2fd..291844c8f3e1 100644 --- a/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml @@ -29,7 +29,6 @@ select: contains: enum: - rockchip,rk3328-dwc3 - - rockchip,rk3399-dwc3 - rockchip,rk3568-dwc3 required: - compatible @@ -39,7 +38,6 @@ properties: items: - enum: - rockchip,rk3328-dwc3 - - rockchip,rk3399-dwc3 - rockchip,rk3568-dwc3 - const: snps,dwc3 @@ -90,7 +88,7 @@ required: examples: - | - #include + #include #include bus { @@ -98,11 +96,11 @@ examples: #size-cells = <2>; usbdrd3_0: usb@fe800000 { - compatible = "rockchip,rk3399-dwc3", "snps,dwc3"; + compatible = "rockchip,rk3328-dwc3", "snps,dwc3"; reg = <0x0 0xfe800000 0x0 0x100000>; interrupts = ; - clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>, - <&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_GRF>; + clocks = <&cru SCLK_USB3OTG_REF>, <&cru SCLK_USB3OTG_SUSPEND>, + <&cru ACLK_USB3OTG>; clock-names = "ref_clk", "suspend_clk", "bus_clk", "grf_clk"; dr_mode = "otg"; diff --git a/Documentation/devicetree/bindings/usb/rockchip,rk3399-dwc3.yaml b/Documentation/devicetree/bindings/usb/rockchip,rk3399-dwc3.yaml new file mode 100644 index 000000000000..3159f9a6a0f7 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/rockchip,rk3399-dwc3.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/rockchip,rk3399-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip RK3399 SuperSpeed DWC3 USB SoC controller + +maintainers: + - Heiko Stuebner + +properties: + compatible: + const: rockchip,rk3399-dwc3 + + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + + ranges: true + + clocks: + items: + - description: + Controller reference clock, must to be 24 MHz + - description: + Controller suspend clock, must to be 24 MHz or 32 KHz + - description: + Master/Core clock, must to be >= 62.5 MHz for SS + operation and >= 30MHz for HS operation + - description: + USB3 aclk peri + - description: + USB3 aclk + - description: + Controller grf clock + + clock-names: + items: + - const: ref_clk + - const: suspend_clk + - const: bus_clk + - const: aclk_usb3_rksoc_axi_perf + - const: aclk_usb3 + - const: grf_clk + + resets: + maxItems: 1 + + reset-names: + const: usb3-otg + +patternProperties: + '^usb@': + $ref: snps,dwc3.yaml# + +additionalProperties: false + +required: + - compatible + - '#address-cells' + - '#size-cells' + - ranges + - clocks + - clock-names + - resets + - reset-names + +examples: + - | + #include + #include + #include + + bus { + #address-cells = <2>; + #size-cells = <2>; + + usb { + compatible = "rockchip,rk3399-dwc3"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + clocks = <&cru SCLK_USB3OTG0_REF>, <&cru SCLK_USB3OTG0_SUSPEND>, + <&cru ACLK_USB3OTG0>, <&cru ACLK_USB3_RKSOC_AXI_PERF>, + <&cru ACLK_USB3>, <&cru ACLK_USB3_GRF>; + clock-names = "ref_clk", "suspend_clk", + "bus_clk", "aclk_usb3_rksoc_axi_perf", + "aclk_usb3", "grf_clk"; + resets = <&cru SRST_A_USB3_OTG0>; + reset-names = "usb3-otg"; + + usb@fe800000 { + compatible = "snps,dwc3"; + reg = <0x0 0xfe800000 0x0 0x100000>; + interrupts = ; + clocks = <&cru SCLK_USB3OTG0_REF>, <&cru ACLK_USB3OTG0>, + <&cru SCLK_USB3OTG0_SUSPEND>; + clock-names = "ref", "bus_early", "suspend"; + dr_mode = "otg"; + phys = <&u2phy0_otg>, <&tcphy0_usb3>; + phy-names = "usb2-phy", "usb3-phy"; + phy_type = "utmi_wide"; + snps,dis_enblslpm_quirk; + snps,dis-u2-freeclk-exists-quirk; + snps,dis_u2_susphy_quirk; + snps,dis-del-phy-power-chg-quirk; + snps,dis-tx-ipgap-linecheck-quirk; + power-domains = <&power RK3399_PD_USB3>; + }; + }; + }; +... -- cgit v1.2.3 From 60c4da9f3c3c6e8fcd3a12452bcd14181e17cb2c Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 21:05:15 -0600 Subject: dt-bindings: usb: Remove obsolete brcm,bcm3384-usb.txt The "brcm,bcm3384-ohci" and "brcm,bcm3384-ehci" compatibles are already documented in generic-ohci.yaml and generic-ehci.yaml, respectively, so remove the old txt binding. Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230110-dt-usb-v3-1-5af0541fcf8c@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/brcm,bcm3384-usb.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/brcm,bcm3384-usb.txt diff --git a/Documentation/devicetree/bindings/usb/brcm,bcm3384-usb.txt b/Documentation/devicetree/bindings/usb/brcm,bcm3384-usb.txt deleted file mode 100644 index 452c45c7bf29..000000000000 --- a/Documentation/devicetree/bindings/usb/brcm,bcm3384-usb.txt +++ /dev/null @@ -1,11 +0,0 @@ -* Broadcom USB controllers - -Required properties: -- compatible: "brcm,bcm3384-ohci", "brcm,bcm3384-ehci" - - These currently use the generic-ohci and generic-ehci drivers. On some - systems, special handling may be needed in the following cases: - - - Restoring state after systemwide power save modes - - Sharing PHYs with the USBD (UDC) hardware - - Figuring out which controllers are disabled on ASIC bondout variants -- cgit v1.2.3 From 4aa466190a2d49a90fa7a6f9dac2dd10e46fdece Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 21:05:16 -0600 Subject: dt-bindings: usb: Convert multiple "usb-ohci" bindings to DT schema "usb-ohci" is another "generic" OHCI controller compatible string used by several platforms. Add it to the generic-ohci.yaml schema and remove all the old binding docs. Marvell pxa-usb.txt has "usb-ohci" in the example, but actual users don't, so drop it. Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230110-dt-usb-v3-2-5af0541fcf8c@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/powerpc/nintendo/wii.txt | 10 ------- .../devicetree/bindings/usb/generic-ohci.yaml | 28 +++++++++++++++-- Documentation/devicetree/bindings/usb/ohci-nxp.txt | 24 --------------- Documentation/devicetree/bindings/usb/pxa-usb.txt | 2 +- .../devicetree/bindings/usb/spear-usb.txt | 35 ---------------------- 5 files changed, 26 insertions(+), 73 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/ohci-nxp.txt delete mode 100644 Documentation/devicetree/bindings/usb/spear-usb.txt diff --git a/Documentation/devicetree/bindings/powerpc/nintendo/wii.txt b/Documentation/devicetree/bindings/powerpc/nintendo/wii.txt index c4d78f28d23c..3ff6ebbb4998 100644 --- a/Documentation/devicetree/bindings/powerpc/nintendo/wii.txt +++ b/Documentation/devicetree/bindings/powerpc/nintendo/wii.txt @@ -97,16 +97,6 @@ Nintendo Wii device tree - reg : should contain the EXI registers location and length - interrupts : should contain the EXI interrupt -1.g) The Open Host Controller Interface (OHCI) nodes - - Represent the USB 1.x Open Host Controller Interfaces. - - Required properties: - - - compatible : should be "nintendo,hollywood-usb-ohci","usb-ohci" - - reg : should contain the OHCI registers location and length - - interrupts : should contain the OHCI interrupt - 1.h) The Enhanced Host Controller Interface (EHCI) node Represents the USB 2.0 Enhanced Host Controller Interface. diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 4fcbd0add49d..8492d809ba40 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -6,9 +6,6 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: USB OHCI Controller -allOf: - - $ref: "usb-hcd.yaml" - maintainers: - Greg Kroah-Hartman @@ -50,6 +47,13 @@ properties: - snps,hsdk-v1.0-ohci - const: generic-ohci - const: generic-ohci + - items: + - enum: + - cavium,octeon-6335-ohci + - nintendo,hollywood-usb-ohci + - nxp,ohci-nxp + - st,spear600-ohci + - const: usb-ohci reg: maxItems: 1 @@ -119,11 +123,29 @@ properties: - host - otg + transceiver: + $ref: /schemas/types.yaml#/definitions/phandle + description: + The associated ISP1301 device. Necessary for the UDC controller for + connecting to the USB physical layer. + required: - compatible - reg - interrupts +allOf: + - $ref: usb-hcd.yaml + - if: + not: + properties: + compatible: + contains: + const: nxp,ohci-nxp + then: + properties: + transceiver: false + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/usb/ohci-nxp.txt b/Documentation/devicetree/bindings/usb/ohci-nxp.txt deleted file mode 100644 index 71e28c1017ed..000000000000 --- a/Documentation/devicetree/bindings/usb/ohci-nxp.txt +++ /dev/null @@ -1,24 +0,0 @@ -* OHCI controller, NXP ohci-nxp variant - -Required properties: -- compatible: must be "nxp,ohci-nxp" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: The OHCI interrupt -- transceiver: phandle of the associated ISP1301 device - this is necessary for - the UDC controller for connecting to the USB physical layer - -Example (LPC32xx): - - isp1301: usb-transceiver@2c { - compatible = "nxp,isp1301"; - reg = <0x2c>; - }; - - ohci@31020000 { - compatible = "nxp,ohci-nxp"; - reg = <0x31020000 0x300>; - interrupt-parent = <&mic>; - interrupts = <0x3b 0>; - transceiver = <&isp1301>; - }; diff --git a/Documentation/devicetree/bindings/usb/pxa-usb.txt b/Documentation/devicetree/bindings/usb/pxa-usb.txt index 9c331799b87c..53fdae4fa6f6 100644 --- a/Documentation/devicetree/bindings/usb/pxa-usb.txt +++ b/Documentation/devicetree/bindings/usb/pxa-usb.txt @@ -22,7 +22,7 @@ Optional properties: Example: usb0: ohci@4c000000 { - compatible = "marvell,pxa-ohci", "usb-ohci"; + compatible = "marvell,pxa-ohci"; reg = <0x4c000000 0x100000>; interrupts = <18>; marvell,enable-port1; diff --git a/Documentation/devicetree/bindings/usb/spear-usb.txt b/Documentation/devicetree/bindings/usb/spear-usb.txt deleted file mode 100644 index 1dc91cc459c0..000000000000 --- a/Documentation/devicetree/bindings/usb/spear-usb.txt +++ /dev/null @@ -1,35 +0,0 @@ -ST SPEAr SoC USB controllers: ------------------------------ - -EHCI: ------ - -Required properties: -- compatible: "st,spear600-ehci" -- interrupts: Should contain the EHCI interrupt - -Example: - - ehci@e1800000 { - compatible = "st,spear600-ehci", "usb-ehci"; - reg = <0xe1800000 0x1000>; - interrupt-parent = <&vic1>; - interrupts = <27>; - }; - - -OHCI: ------ - -Required properties: -- compatible: "st,spear600-ohci" -- interrupts: Should contain the OHCI interrupt - -Example: - - ohci@e1900000 { - compatible = "st,spear600-ohci", "usb-ohci"; - reg = <0xe1800000 0x1000>; - interrupt-parent = <&vic1>; - interrupts = <26>; - }; -- cgit v1.2.3 From 76ea4926dc8d1d68d611ebcd8a2790bc846589e6 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 21:05:17 -0600 Subject: dt-bindings: usb: Convert OMAP OHCI/EHCI bindings to schema The OMAP OHCI and EHCI USB host bindings follow the generic binding, so add the compatibles and remove the old txt binding docs. The examples in omap-usb-host.txt don't match actual users, so update them dropping the fallback compatible. Signed-off-by: Rob Herring Acked-by: Lee Jones Link: https://lore.kernel.org/r/20230110-dt-usb-v3-3-5af0541fcf8c@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/mfd/omap-usb-host.txt | 8 +++--- .../devicetree/bindings/usb/ehci-omap.txt | 31 ---------------------- .../devicetree/bindings/usb/generic-ehci.yaml | 1 + .../devicetree/bindings/usb/generic-ohci.yaml | 4 ++- .../devicetree/bindings/usb/ohci-omap3.txt | 15 ----------- 5 files changed, 8 insertions(+), 51 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/ehci-omap.txt delete mode 100644 Documentation/devicetree/bindings/usb/ohci-omap3.txt diff --git a/Documentation/devicetree/bindings/mfd/omap-usb-host.txt b/Documentation/devicetree/bindings/mfd/omap-usb-host.txt index aa1eaa59581b..a0d8c30c2631 100644 --- a/Documentation/devicetree/bindings/mfd/omap-usb-host.txt +++ b/Documentation/devicetree/bindings/mfd/omap-usb-host.txt @@ -64,8 +64,8 @@ Required properties if child node exists: Properties for children: The OMAP HS USB Host subsystem contains EHCI and OHCI controllers. -See Documentation/devicetree/bindings/usb/ehci-omap.txt and -Documentation/devicetree/bindings/usb/ohci-omap3.txt. +See Documentation/devicetree/bindings/usb/generic-ehci.yaml and +Documentation/devicetree/bindings/usb/generic-ohci.yaml. Example for OMAP4: @@ -78,14 +78,14 @@ usbhshost: usbhshost@4a064000 { ranges; usbhsohci: ohci@4a064800 { - compatible = "ti,ohci-omap3", "usb-ohci"; + compatible = "ti,ohci-omap3"; reg = <0x4a064800 0x400>; interrupt-parent = <&gic>; interrupts = <0 76 0x4>; }; usbhsehci: ehci@4a064c00 { - compatible = "ti,ehci-omap", "usb-ehci"; + compatible = "ti,ehci-omap"; reg = <0x4a064c00 0x400>; interrupt-parent = <&gic>; interrupts = <0 77 0x4>; diff --git a/Documentation/devicetree/bindings/usb/ehci-omap.txt b/Documentation/devicetree/bindings/usb/ehci-omap.txt deleted file mode 100644 index d77e11a975a2..000000000000 --- a/Documentation/devicetree/bindings/usb/ehci-omap.txt +++ /dev/null @@ -1,31 +0,0 @@ -OMAP HS USB EHCI controller - -This device is usually the child of the omap-usb-host -Documentation/devicetree/bindings/mfd/omap-usb-host.txt - -Required properties: - -- compatible: should be "ti,ehci-omap" -- reg: should contain one register range i.e. start and length -- interrupts: description of the interrupt line - -Optional properties: - -- phys: list of phandles to PHY nodes. - This property is required if at least one of the ports are in - PHY mode i.e. OMAP_EHCI_PORT_MODE_PHY - -To specify the port mode, see -Documentation/devicetree/bindings/mfd/omap-usb-host.txt - -Example for OMAP4: - -usbhsehci: ehci@4a064c00 { - compatible = "ti,ehci-omap"; - reg = <0x4a064c00 0x400>; - interrupts = <0 77 0x4>; -}; - -&usbhsehci { - phys = <&hsusb1_phy 0 &hsusb3_phy>; -}; diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 994818cb6044..2d382ae424da 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -74,6 +74,7 @@ properties: - const: usb-ehci - enum: - generic-ehci + - ti,ehci-omap - usb-ehci reg: diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 8492d809ba40..a9ba7257b884 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -46,7 +46,9 @@ properties: - ingenic,jz4740-ohci - snps,hsdk-v1.0-ohci - const: generic-ohci - - const: generic-ohci + - enum: + - generic-ohci + - ti,ohci-omap3 - items: - enum: - cavium,octeon-6335-ohci diff --git a/Documentation/devicetree/bindings/usb/ohci-omap3.txt b/Documentation/devicetree/bindings/usb/ohci-omap3.txt deleted file mode 100644 index ce8c47cff6d0..000000000000 --- a/Documentation/devicetree/bindings/usb/ohci-omap3.txt +++ /dev/null @@ -1,15 +0,0 @@ -OMAP HS USB OHCI controller (OMAP3 and later) - -Required properties: - -- compatible: should be "ti,ohci-omap3" -- reg: should contain one register range i.e. start and length -- interrupts: description of the interrupt line - -Example for OMAP4: - -usbhsohci: ohci@4a064800 { - compatible = "ti,ohci-omap3"; - reg = <0x4a064800 0x400>; - interrupts = <0 76 0x4>; -}; -- cgit v1.2.3 From c1140ebfd7bf64dbd8dae25a92a6bf8aa97276f8 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 21:05:18 -0600 Subject: dt-bindings: usb: Convert Marvell Orion EHCI to DT schema The Marvell Orion EHCI binding is just some compatible strings, so add it to the generic-ehci.yaml schema. Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230110-dt-usb-v3-4-5af0541fcf8c@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ehci-orion.txt | 22 ---------------------- .../devicetree/bindings/usb/generic-ehci.yaml | 2 ++ 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/ehci-orion.txt diff --git a/Documentation/devicetree/bindings/usb/ehci-orion.txt b/Documentation/devicetree/bindings/usb/ehci-orion.txt deleted file mode 100644 index 2855bae79fda..000000000000 --- a/Documentation/devicetree/bindings/usb/ehci-orion.txt +++ /dev/null @@ -1,22 +0,0 @@ -* EHCI controller, Orion Marvell variants - -Required properties: -- compatible: must be one of the following - "marvell,orion-ehci" - "marvell,armada-3700-ehci" -- reg: physical base address of the controller and length of memory mapped - region. -- interrupts: The EHCI interrupt - -Optional properties: -- clocks: reference to the clock -- phys: reference to the USB PHY -- phy-names: name of the USB PHY, should be "usb" - -Example: - - ehci@50000 { - compatible = "marvell,orion-ehci"; - reg = <0x50000 0x1000>; - interrupts = <19>; - }; diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 2d382ae424da..ebbb01b39a92 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -74,6 +74,8 @@ properties: - const: usb-ehci - enum: - generic-ehci + - marvell,armada-3700-ehci + - marvell,orion-ehci - ti,ehci-omap - usb-ehci -- cgit v1.2.3 From ec13100fcf2374f013ac836f806d1eb0abf06194 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 23 Jan 2023 21:05:19 -0600 Subject: dt-bindings: usb: Convert Nuvoton EHCI to DT schema The Nuvoton EHCI binding is just some compatible strings, so add it to the generic-ehci.yaml schema. Signed-off-by: Rob Herring Link: https://lore.kernel.org/r/20230110-dt-usb-v3-5-5af0541fcf8c@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/generic-ehci.yaml | 2 ++ .../devicetree/bindings/usb/npcm7xx-usb.txt | 20 -------------------- 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/npcm7xx-usb.txt diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index ebbb01b39a92..050cfd5acdaa 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -76,6 +76,8 @@ properties: - generic-ehci - marvell,armada-3700-ehci - marvell,orion-ehci + - nuvoton,npcm750-ehci + - nuvoton,npcm845-ehci - ti,ehci-omap - usb-ehci diff --git a/Documentation/devicetree/bindings/usb/npcm7xx-usb.txt b/Documentation/devicetree/bindings/usb/npcm7xx-usb.txt deleted file mode 100644 index 352a0a1e2f76..000000000000 --- a/Documentation/devicetree/bindings/usb/npcm7xx-usb.txt +++ /dev/null @@ -1,20 +0,0 @@ -Nuvoton NPCM7XX SoC USB controllers: ------------------------------ - -EHCI: ------ - -Required properties: -- compatible: should be one of - "nuvoton,npcm750-ehci" - "nuvoton,npcm845-ehci" -- interrupts: Should contain the EHCI interrupt -- reg: Physical address and length of the register set for the device - -Example: - - ehci1: usb@f0806000 { - compatible = "nuvoton,npcm750-ehci"; - reg = <0xf0806000 0x1000>; - interrupts = <0 61 4>; - }; -- cgit v1.2.3 From 173bee52a0f15572e4df8e2d2b4d53c34f0bee9c Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Thu, 19 Jan 2023 10:42:04 +0000 Subject: dt-bindings: usb: tegra-xudc: Add dma-coherent for Tegra194 DMA operations for XUSB device controller are coherent for Tegra194 and so update the device-tree binding to add this property. Signed-off-by: Jon Hunter Acked-by: Thierry Reding Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230119104208.28726-2-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml index f6cb19efd98b..4ef88d38fa3a 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml @@ -112,6 +112,8 @@ properties: hvdd-usb-supply: description: USB controller power supply. Must supply 3.3 V. + dma-coherent: true + required: - compatible - reg @@ -164,6 +166,16 @@ allOf: clock-names: maxItems: 4 + - if: + properties: + compatible: + contains: + enum: + - nvidia,tegra194-xudc + then: + required: + - dma-coherent + additionalProperties: false examples: -- cgit v1.2.3 From f816267867f01ea7e8b80941ec7ba853e2901002 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Thu, 19 Jan 2023 10:42:06 +0000 Subject: dt-bindings: usb: tegra-xudc: Add Tegra234 XUDC support Extend the Tegra XUSB controller device (XUDC) tree binding with Tegra234 support. Signed-off-by: Wayne Chang Co-developed-by: Jon Hunter Signed-off-by: Jon Hunter Acked-by: Krzysztof Kozlowski Acked-by: Thierry Reding Link: https://lore.kernel.org/r/20230119104208.28726-4-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml index 4ef88d38fa3a..e638f77658fc 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml @@ -22,6 +22,7 @@ properties: - nvidia,tegra210-xudc # For Tegra210 - nvidia,tegra186-xudc # For Tegra186 - nvidia,tegra194-xudc # For Tegra194 + - nvidia,tegra234-xudc # For Tegra234 reg: minItems: 2 @@ -155,6 +156,7 @@ allOf: enum: - nvidia,tegra186-xudc - nvidia,tegra194-xudc + - nvidia,tegra234-xudc then: properties: reg: @@ -172,6 +174,7 @@ allOf: contains: enum: - nvidia,tegra194-xudc + - nvidia,tegra234-xudc then: required: - dma-coherent -- cgit v1.2.3 From 331df1f3bbbc5e783dbf6f6100daabd40e12fb87 Mon Sep 17 00:00:00 2001 From: Sing-Han Chen Date: Thu, 19 Jan 2023 10:42:07 +0000 Subject: usb: gadget: tegra-xudc: Add Tegra234 support This commit adds support for XUSB device mode controller support on Tegra234 SoC. This is very similar to the existing Tegra194 XUDC. Signed-off-by: Sing-Han Chen Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Acked-by: Thierry Reding Link: https://lore.kernel.org/r/20230119104208.28726-5-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 15c0b4837f80..2b71b33725f1 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -3667,6 +3667,19 @@ static struct tegra_xudc_soc tegra194_xudc_soc_data = { .has_ipfs = false, }; +static struct tegra_xudc_soc tegra234_xudc_soc_data = { + .clock_names = tegra186_xudc_clock_names, + .num_clks = ARRAY_SIZE(tegra186_xudc_clock_names), + .num_phys = 4, + .u1_enable = true, + .u2_enable = true, + .lpm_enable = true, + .invalid_seq_num = false, + .pls_quirk = false, + .port_reset_quirk = false, + .has_ipfs = false, +}; + static const struct of_device_id tegra_xudc_of_match[] = { { .compatible = "nvidia,tegra210-xudc", @@ -3680,6 +3693,10 @@ static const struct of_device_id tegra_xudc_of_match[] = { .compatible = "nvidia,tegra194-xudc", .data = &tegra194_xudc_soc_data }, + { + .compatible = "nvidia,tegra234-xudc", + .data = &tegra234_xudc_soc_data + }, { } }; MODULE_DEVICE_TABLE(of, tegra_xudc_of_match); -- cgit v1.2.3 From e696d70f600abbe2b7fe81008bcb24d239a74672 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:42 +0000 Subject: dt-bindings: usb: renesas,usb-xhci: Document RZ/V2M support Document the RZ/V2M SoC bindings. The RZ/V2M SoC is a little different to the R-Car implementations. You can access the registers associated with the currently set DRD mode, therefore as part of init, we have to set the DRD mode to host. Signed-off-by: Biju Das Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230121145853.4792-2-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/renesas,usb-xhci.yaml | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml b/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml index 4c5efaf02308..1a07c0d2b1b1 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usb-xhci.yaml @@ -10,9 +10,6 @@ maintainers: - Lad Prabhakar - Yoshihiro Shimoda -allOf: - - $ref: "usb-xhci.yaml" - properties: compatible: oneOf: @@ -37,6 +34,11 @@ properties: - renesas,xhci-r8a77965 # R-Car M3-N - renesas,xhci-r8a77990 # R-Car E3 - const: renesas,rcar-gen3-xhci # R-Car Gen3 and RZ/G2 + - items: + - enum: + - renesas,r9a09g011-xhci # RZ/V2M + - renesas,r9a09g055-xhci # RZ/V2MA + - const: renesas,rzv2m-xhci # RZ/{V2M, V2MA} reg: maxItems: 1 @@ -45,7 +47,16 @@ properties: maxItems: 1 clocks: - maxItems: 1 + minItems: 1 + items: + - description: Main clock for host + - description: Register access clock + + clock-names: + minItems: 1 + items: + - const: axi + - const: reg phys: maxItems: 1 @@ -68,6 +79,28 @@ required: - power-domains - resets +allOf: + - $ref: usb-xhci.yaml + + - if: + properties: + compatible: + contains: + enum: + - renesas,rzv2m-xhci + then: + properties: + clocks: + minItems: 2 + clock-names: + minItems: 2 + required: + - clock-names + else: + properties: + clocks: + maxItems: 1 + unevaluatedProperties: false examples: -- cgit v1.2.3 From 29218d6ce4da741f8a1a86974999e8790683c23b Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:43 +0000 Subject: dt-bindings: usb: renesas,usb3-peri: Update reset, clock-name and interrupts properties On RZ/V2M, USB3DRD module manages the drd_reset. Moreover, the interrupts drd, gpi and bc are part of USB3DRD block. This patch removes drd_reset and the interrupts drd, bc and gpi from usb3_peri bindings. After this, there is only one reset and interrupts and therefore removing reset-names and interrupt-names as well. Whilst, Update the clock-name "aclk"->"axi" to make it consistent with DRD and host blocks. There is any harm in making such a change as, no users of renesas,r9a09g011-usb3-peri yet in kernel release. Signed-off-by: Biju Das Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230121145853.4792-3-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/renesas,usb3-peri.yaml | 39 ++-------------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml index 55dfd121b555..4ba36ebf0f1e 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml @@ -34,20 +34,7 @@ properties: maxItems: 1 interrupts: - minItems: 1 - items: - - description: Combined interrupt for DMA, SYS and ERR - - description: Dual Role Device (DRD) - - description: Battery Charging - - description: Global Purpose Input - - interrupt-names: - minItems: 1 - items: - - const: all_p - - const: drd - - const: bc - - const: gpi + maxItems: 1 clocks: minItems: 1 @@ -58,7 +45,7 @@ properties: clock-names: minItems: 1 items: - - const: aclk + - const: axi - const: reg phys: @@ -71,15 +58,7 @@ properties: maxItems: 1 resets: - minItems: 1 - items: - - description: Peripheral reset - - description: DRD reset - - reset-names: - items: - - const: aresetn_p - - const: drd_reset + maxItems: 1 usb-role-switch: $ref: /schemas/types.yaml#/definitions/flag @@ -127,25 +106,13 @@ allOf: minItems: 2 clock-names: minItems: 2 - interrupts: - minItems: 4 - interrupt-names: - minItems: 4 - resets: - minItems: 2 required: - clock-names - - interrupt-names - resets - - reset-names else: properties: clocks: maxItems: 1 - interrupts: - maxItems: 1 - resets: - maxItems: 1 additionalProperties: false -- cgit v1.2.3 From 2c5502a4dc9ffb9145ef80e642be0f1f03c037e8 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:44 +0000 Subject: dt-bindings: usb: renesas,usb3-peri: Document RZ/V2MA bindings Document RZ/V2MA usb3-peri bindings. RZ/V2MA usb3-peri is identical to one found on the RZ/V2M SoC. No driver changes are required as generic compatible string "renesas,rzv2m-usb3-peri" will be used as a fallback. Signed-off-by: Biju Das Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230121145853.4792-4-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml index 4ba36ebf0f1e..b2b811a0ade8 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml @@ -28,6 +28,7 @@ properties: - items: - enum: - renesas,r9a09g011-usb3-peri # RZ/V2M + - renesas,r9a09g055-usb3-peri # RZ/V2MA - const: renesas,rzv2m-usb3-peri reg: -- cgit v1.2.3 From 9486e56c49be7dc771ecae9bf67b1034cd440d10 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:45 +0000 Subject: dt-bindings: usb: Add RZ/V2M USB3DRD binding Add device tree bindings for the RZ/V2{M, MA} USB3DRD module. Signed-off-by: Biju Das Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230121145853.4792-5-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/renesas,rzv2m-usb3drd.yaml | 129 +++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml diff --git a/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml b/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml new file mode 100644 index 000000000000..ff625600d9af --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/renesas,rzv2m-usb3drd.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/V2M USB 3.1 DRD controller + +maintainers: + - Biju Das + +description: | + The RZ/V2{M, MA} USB3.1 DRD module supports the following functions + * Role swapping function by the ID pin of the Micro-AB receptacle + * Battery Charging Specification Revision 1.2 + +properties: + compatible: + items: + - enum: + - renesas,r9a09g011-usb3drd # RZ/V2M + - renesas,r9a09g055-usb3drd # RZ/V2MA + - const: renesas,rzv2m-usb3drd + + reg: + maxItems: 1 + + interrupts: + items: + - description: Dual Role Device (DRD) + - description: Battery Charging + - description: Global Purpose Input + + interrupt-names: + items: + - const: drd + - const: bc + - const: gpi + + clocks: + items: + - description: Peripheral AXI clock + - description: APB clock + + clock-names: + items: + - const: axi + - const: reg + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + ranges: true + + '#address-cells': + enum: [ 1, 2 ] + + '#size-cells': + enum: [ 1, 2 ] + +patternProperties: + "^usb3peri@[0-9a-f]+$": + type: object + $ref: /schemas/usb/renesas,usb3-peri.yaml + + "^usb@[0-9a-f]+$": + type: object + $ref: renesas,usb-xhci.yaml# + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - power-domains + - resets + +additionalProperties: false + +examples: + - | + #include + #include + + usb3drd: usb@85070400 { + compatible = "renesas,r9a09g011-usb3drd", "renesas,rzv2m-usb3drd"; + reg = <0x85070400 0x100>; + interrupts = , + , + ; + interrupt-names = "drd", "bc", "gpi"; + clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>, + <&cpg CPG_MOD R9A09G011_USB_PCLK>; + clock-names = "axi", "reg"; + power-domains = <&cpg>; + resets = <&cpg R9A09G011_USB_DRD_RESET>; + ranges; + #address-cells = <1>; + #size-cells = <1>; + + usb3host: usb@85060000 { + compatible = "renesas,r9a09g011-xhci", + "renesas,rzv2m-xhci"; + reg = <0x85060000 0x2000>; + interrupts = ; + clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_H>, + <&cpg CPG_MOD R9A09G011_USB_PCLK>; + clock-names = "axi", "reg"; + power-domains = <&cpg>; + resets = <&cpg R9A09G011_USB_ARESETN_H>; + }; + + usb3peri: usb3peri@85070000 { + compatible = "renesas,r9a09g011-usb3-peri", + "renesas,rzv2m-usb3-peri"; + reg = <0x85070000 0x400>; + interrupts = ; + clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>, + <&cpg CPG_MOD R9A09G011_USB_PCLK>; + clock-names = "axi", "reg"; + power-domains = <&cpg>; + resets = <&cpg R9A09G011_USB_ARESETN_P>; + }; + }; -- cgit v1.2.3 From 9cad72dfc5567c66ab0e5a0a2474c5f36c268694 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:46 +0000 Subject: usb: gadget: Add support for RZ/V2M USB3DRD driver The RZ/V2M USB3.1 Gen1 Interface (USB) composed of a USB3.1 Gen1 Dual Role Device controller (USB3DRD), a USB3.1 Gen1 Host controller (USB3HOST), a USB3.1 Gen1 Peripheral controller (USB3PERI). The reset for both host and peri are located in USB3DRD block. The USB3DRD registers are mapped in the AXI address space of the Peripheral module. Add USB3DRD driver to handle reset for both host and peri modules. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20230121145853.4792-6-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 13 +++ drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/renesas_usb3.c | 102 +++++++++++++++--------- drivers/usb/gadget/udc/rzv2m_usb3drd.c | 139 +++++++++++++++++++++++++++++++++ include/linux/usb/rzv2m_usb3drd.h | 20 +++++ 5 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 drivers/usb/gadget/udc/rzv2m_usb3drd.c create mode 100644 include/linux/usb/rzv2m_usb3drd.h diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 12f7323869fe..1e32d1d7dfb7 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -180,10 +180,23 @@ config USB_RENESAS_USBHS_UDC dynamically linked module called "renesas_usbhs" and force all gadget drivers to also be dynamically linked. +config USB_RZV2M_USB3DRD + tristate 'Renesas USB3.1 DRD controller' + depends on ARCH_R9A09G011 || COMPILE_TEST + default USB_XHCI_RZV2M + default USB_RENESAS_USB3 + help + Renesas USB3.1 DRD controller is a USB DRD controller + that supports both host and device switching. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "rzv2m_usb3drd". + config USB_RENESAS_USB3 tristate 'Renesas USB3.0 Peripheral controller' depends on ARCH_RENESAS || COMPILE_TEST depends on EXTCON + select USB_RZV2M_USB3DRD if ARCH_R9A09G011 select USB_ROLE_SWITCH help Renesas USB3.0 Peripheral controller is a USB peripheral controller diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 1e23627733eb..d40822a12bad 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_USB_TEGRA_XUDC) += tegra-xudc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o obj-$(CONFIG_USB_RENESAS_USB3) += renesas_usb3.o +obj-$(CONFIG_USB_RZV2M_USB3DRD) += rzv2m_usb3drd.o obj-$(CONFIG_USB_RENESAS_USBF) += renesas_usbf.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 615ba0a6fbee..8d694501bf02 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -27,6 +27,7 @@ #include #include #include +#include /* register definitions */ #define USB3_AXI_INT_STA 0x008 @@ -334,7 +335,7 @@ struct renesas_usb3_priv { struct renesas_usb3 { void __iomem *reg; - struct reset_control *drd_rstc; + void __iomem *drd_reg; struct reset_control *usbp_rstc; struct usb_gadget gadget; @@ -426,6 +427,46 @@ static void usb3_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) usb3_write(usb3, val, offs); } +static void usb3_drd_write(struct renesas_usb3 *usb3, u32 data, u32 offs) +{ + void __iomem *reg; + + if (usb3->is_rzv2m) + reg = usb3->drd_reg + offs - USB3_DRD_CON(usb3); + else + reg = usb3->reg + offs; + + iowrite32(data, reg); +} + +static u32 usb3_drd_read(struct renesas_usb3 *usb3, u32 offs) +{ + void __iomem *reg; + + if (usb3->is_rzv2m) + reg = usb3->drd_reg + offs - USB3_DRD_CON(usb3); + else + reg = usb3->reg + offs; + + return ioread32(reg); +} + +static void usb3_drd_set_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_drd_read(usb3, offs); + + val |= bits; + usb3_drd_write(usb3, val, offs); +} + +static void usb3_drd_clear_bit(struct renesas_usb3 *usb3, u32 bits, u32 offs) +{ + u32 val = usb3_drd_read(usb3, offs); + + val &= ~bits; + usb3_drd_write(usb3, val, offs); +} + static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask, u32 expected) { @@ -474,7 +515,7 @@ static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num) static bool usb3_is_host(struct renesas_usb3 *usb3) { - return !(usb3_read(usb3, USB3_DRD_CON(usb3)) & DRD_CON_PERI_CON); + return !(usb3_drd_read(usb3, USB3_DRD_CON(usb3)) & DRD_CON_PERI_CON); } static void usb3_init_axi_bridge(struct renesas_usb3 *usb3) @@ -683,18 +724,18 @@ static void usb3_set_mode(struct renesas_usb3 *usb3, bool host) { if (usb3->is_rzv2m) { if (host) { - usb3_set_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); - usb3_clear_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); + usb3_drd_set_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); } else { - usb3_set_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); - usb3_clear_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); + usb3_drd_set_bit(usb3, DRD_CON_HOST_RST, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_PERI_RST, USB3_DRD_CON(usb3)); } } if (host) - usb3_clear_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); else - usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); + usb3_drd_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON(usb3)); } static void usb3_set_mode_by_role_sw(struct renesas_usb3 *usb3, bool host) @@ -710,9 +751,9 @@ static void usb3_set_mode_by_role_sw(struct renesas_usb3 *usb3, bool host) static void usb3_vbus_out(struct renesas_usb3 *usb3, bool enable) { if (enable) - usb3_set_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); + usb3_drd_set_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); else - usb3_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); + usb3_drd_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON(usb3)); } static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev) @@ -733,7 +774,7 @@ static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev) static bool usb3_is_a_device(struct renesas_usb3 *usb3) { - return !(usb3_read(usb3, USB3_USB_OTG_STA(usb3)) & USB_OTG_IDMON(usb3)); + return !(usb3_drd_read(usb3, USB3_USB_OTG_STA(usb3)) & USB_OTG_IDMON(usb3)); } static void usb3_check_id(struct renesas_usb3 *usb3) @@ -756,8 +797,8 @@ static void renesas_usb3_init_controller(struct renesas_usb3 *usb3) usb3_set_bit(usb3, USB_COM_CON_PN_WDATAIF_NL | USB_COM_CON_PN_RDATAIF_NL | USB_COM_CON_PN_LSTTR_PP, USB3_USB_COM_CON); - usb3_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_STA(usb3)); - usb3_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_ENA(usb3)); + usb3_drd_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_STA(usb3)); + usb3_drd_write(usb3, USB_OTG_IDMON(usb3), USB3_USB_OTG_INT_ENA(usb3)); usb3_check_id(usb3); usb3_check_vbus(usb3); @@ -767,7 +808,7 @@ static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3) { usb3_disconnect(usb3); usb3_write(usb3, 0, USB3_P0_INT_ENA); - usb3_write(usb3, 0, USB3_USB_OTG_INT_ENA(usb3)); + usb3_drd_write(usb3, 0, USB3_USB_OTG_INT_ENA(usb3)); usb3_write(usb3, 0, USB3_USB_INT_ENA_1); usb3_write(usb3, 0, USB3_USB_INT_ENA_2); usb3_write(usb3, 0, USB3_AXI_INT_ENA); @@ -2024,11 +2065,11 @@ static void usb3_irq_idmon_change(struct renesas_usb3 *usb3) static void usb3_irq_otg_int(struct renesas_usb3 *usb3) { - u32 otg_int_sta = usb3_read(usb3, USB3_USB_OTG_INT_STA(usb3)); + u32 otg_int_sta = usb3_drd_read(usb3, USB3_USB_OTG_INT_STA(usb3)); - otg_int_sta &= usb3_read(usb3, USB3_USB_OTG_INT_ENA(usb3)); + otg_int_sta &= usb3_drd_read(usb3, USB3_USB_OTG_INT_ENA(usb3)); if (otg_int_sta) - usb3_write(usb3, otg_int_sta, USB3_USB_OTG_INT_STA(usb3)); + usb3_drd_write(usb3, otg_int_sta, USB3_USB_OTG_INT_STA(usb3)); if (otg_int_sta & USB_OTG_IDMON(usb3)) usb3_irq_idmon_change(usb3); @@ -2600,7 +2641,6 @@ static int renesas_usb3_remove(struct platform_device *pdev) usb_del_gadget_udc(&usb3->gadget); reset_control_assert(usb3->usbp_rstc); - reset_control_assert(usb3->drd_rstc); renesas_usb3_dma_free_prd(usb3, &pdev->dev); __renesas_usb3_ep_free_request(usb3->ep0_req); @@ -2788,7 +2828,7 @@ static struct usb_role_switch_desc renesas_usb3_role_switch_desc = { static int renesas_usb3_probe(struct platform_device *pdev) { struct renesas_usb3 *usb3; - int irq, drd_irq, ret; + int irq, ret; const struct renesas_usb3_priv *priv; const struct soc_device_attribute *attr; @@ -2802,12 +2842,6 @@ static int renesas_usb3_probe(struct platform_device *pdev) if (irq < 0) return irq; - if (priv->is_rzv2m) { - drd_irq = platform_get_irq_byname(pdev, "drd"); - if (drd_irq < 0) - return drd_irq; - } - usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); if (!usb3) return -ENOMEM; @@ -2836,9 +2870,12 @@ static int renesas_usb3_probe(struct platform_device *pdev) return ret; if (usb3->is_rzv2m) { - ret = devm_request_irq(&pdev->dev, drd_irq, + struct rzv2m_usb3drd *ddata = dev_get_drvdata(pdev->dev.parent); + + usb3->drd_reg = ddata->reg; + ret = devm_request_irq(ddata->dev, ddata->drd_irq, renesas_usb3_otg_irq, 0, - dev_name(&pdev->dev), usb3); + dev_name(ddata->dev), usb3); if (ret < 0) return ret; } @@ -2873,21 +2910,13 @@ static int renesas_usb3_probe(struct platform_device *pdev) goto err_add_udc; } - usb3->drd_rstc = devm_reset_control_get_optional_shared(&pdev->dev, - "drd_reset"); - if (IS_ERR(usb3->drd_rstc)) { - ret = PTR_ERR(usb3->drd_rstc); - goto err_add_udc; - } - usb3->usbp_rstc = devm_reset_control_get_optional_shared(&pdev->dev, - "aresetn_p"); + NULL); if (IS_ERR(usb3->usbp_rstc)) { ret = PTR_ERR(usb3->usbp_rstc); goto err_add_udc; } - reset_control_deassert(usb3->drd_rstc); reset_control_deassert(usb3->usbp_rstc); pm_runtime_enable(&pdev->dev); @@ -2933,7 +2962,6 @@ err_dev_create: err_reset: reset_control_assert(usb3->usbp_rstc); - reset_control_assert(usb3->drd_rstc); err_add_udc: renesas_usb3_dma_free_prd(usb3, &pdev->dev); diff --git a/drivers/usb/gadget/udc/rzv2m_usb3drd.c b/drivers/usb/gadget/udc/rzv2m_usb3drd.c new file mode 100644 index 000000000000..3c8bbf843038 --- /dev/null +++ b/drivers/usb/gadget/udc/rzv2m_usb3drd.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/V2M USB3DRD driver + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include +#include +#include +#include +#include + +#define USB_PERI_DRD_CON 0x000 + +#define USB_PERI_DRD_CON_PERI_RST BIT(31) +#define USB_PERI_DRD_CON_HOST_RST BIT(30) +#define USB_PERI_DRD_CON_PERI_CON BIT(24) + +static void rzv2m_usb3drd_set_bit(struct rzv2m_usb3drd *usb3, u32 bits, + u32 offs) +{ + u32 val = readl(usb3->reg + offs); + + val |= bits; + writel(val, usb3->reg + offs); +} + +static void rzv2m_usb3drd_clear_bit(struct rzv2m_usb3drd *usb3, u32 bits, + u32 offs) +{ + u32 val = readl(usb3->reg + offs); + + val &= ~bits; + writel(val, usb3->reg + offs); +} + +void rzv2m_usb3drd_reset(struct device *dev, bool host) +{ + struct rzv2m_usb3drd *usb3 = dev_get_drvdata(dev); + + if (host) { + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_PERI_CON, + USB_PERI_DRD_CON); + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_HOST_RST, + USB_PERI_DRD_CON); + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_PERI_RST, + USB_PERI_DRD_CON); + } else { + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_PERI_CON, + USB_PERI_DRD_CON); + rzv2m_usb3drd_set_bit(usb3, USB_PERI_DRD_CON_HOST_RST, + USB_PERI_DRD_CON); + rzv2m_usb3drd_clear_bit(usb3, USB_PERI_DRD_CON_PERI_RST, + USB_PERI_DRD_CON); + } +} +EXPORT_SYMBOL_GPL(rzv2m_usb3drd_reset); + +static int rzv2m_usb3drd_remove(struct platform_device *pdev) +{ + struct rzv2m_usb3drd *usb3 = platform_get_drvdata(pdev); + + of_platform_depopulate(usb3->dev); + pm_runtime_put(usb3->dev); + pm_runtime_disable(&pdev->dev); + reset_control_assert(usb3->drd_rstc); + + return 0; +} + +static int rzv2m_usb3drd_probe(struct platform_device *pdev) +{ + struct rzv2m_usb3drd *usb3; + int ret; + + usb3 = devm_kzalloc(&pdev->dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = &pdev->dev; + + usb3->drd_irq = platform_get_irq_byname(pdev, "drd"); + if (usb3->drd_irq < 0) + return usb3->drd_irq; + + usb3->reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(usb3->reg)) + return PTR_ERR(usb3->reg); + + platform_set_drvdata(pdev, usb3); + + usb3->drd_rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(usb3->drd_rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(usb3->drd_rstc), + "failed to get drd reset"); + + reset_control_deassert(usb3->drd_rstc); + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(usb3->dev); + if (ret) + goto err_rst; + + ret = of_platform_populate(usb3->dev->of_node, NULL, NULL, usb3->dev); + if (ret) + goto err_pm; + + return 0; + +err_pm: + pm_runtime_put(usb3->dev); + +err_rst: + pm_runtime_disable(&pdev->dev); + reset_control_assert(usb3->drd_rstc); + return ret; +} + +static const struct of_device_id rzv2m_usb3drd_of_match[] = { + { .compatible = "renesas,rzv2m-usb3drd", }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzv2m_usb3drd_of_match); + +static struct platform_driver rzv2m_usb3drd_driver = { + .driver = { + .name = "rzv2m-usb3drd", + .of_match_table = of_match_ptr(rzv2m_usb3drd_of_match), + }, + .probe = rzv2m_usb3drd_probe, + .remove = rzv2m_usb3drd_remove, +}; +module_platform_driver(rzv2m_usb3drd_driver); + +MODULE_AUTHOR("Biju Das "); +MODULE_DESCRIPTION("Renesas RZ/V2M USB3DRD driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rzv2m_usb3drd"); diff --git a/include/linux/usb/rzv2m_usb3drd.h b/include/linux/usb/rzv2m_usb3drd.h new file mode 100644 index 000000000000..4cc848de229f --- /dev/null +++ b/include/linux/usb/rzv2m_usb3drd.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __RZV2M_USB3DRD_H +#define __RZV2M_USB3DRD_H + +#include + +struct rzv2m_usb3drd { + void __iomem *reg; + int drd_irq; + struct device *dev; + struct reset_control *drd_rstc; +}; + +#if IS_ENABLED(CONFIG_USB_RZV2M_USB3DRD) +void rzv2m_usb3drd_reset(struct device *dev, bool host); +#else +static inline void rzv2m_usb3drd_reset(struct device *dev, bool host) { } +#endif + +#endif /* __RZV2M_USB3DRD_H */ -- cgit v1.2.3 From 3827fa1ef38f52d9de7ec6e52b4f724dd7b60bb2 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:47 +0000 Subject: usb: gadget: udc: renesas_usb3: Add role switch support for RZ/V2M As RZ/V2M has both HOST and PERI reset module, we need to do reset release before accessing registers in respective IP module. This patch adds role switch support for RZ/V2M. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20230121145853.4792-7-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/renesas_usb3.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 8d694501bf02..bee6bceafc4f 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -2366,6 +2367,9 @@ static int renesas_usb3_start(struct usb_gadget *gadget, usb3 = gadget_to_renesas_usb3(gadget); + if (usb3->is_rzv2m && usb3_is_a_device(usb3)) + return -EBUSY; + /* hook up the driver */ usb3->driver = driver; @@ -2374,6 +2378,10 @@ static int renesas_usb3_start(struct usb_gadget *gadget, pm_runtime_get_sync(usb3_to_dev(usb3)); + /* Peripheral Reset */ + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(usb3_to_dev(usb3)->parent, false); + renesas_usb3_init_controller(usb3); return 0; @@ -2386,8 +2394,10 @@ static int renesas_usb3_stop(struct usb_gadget *gadget) usb3->softconnect = false; usb3->gadget.speed = USB_SPEED_UNKNOWN; usb3->driver = NULL; - renesas_usb3_stop_controller(usb3); + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(usb3_to_dev(usb3)->parent, false); + renesas_usb3_stop_controller(usb3); if (usb3->phy) phy_exit(usb3->phy); @@ -2447,18 +2457,29 @@ static void handle_ext_role_switch_states(struct device *dev, switch (role) { case USB_ROLE_NONE: usb3->connection_state = USB_ROLE_NONE; - if (cur_role == USB_ROLE_HOST) + if (!usb3->is_rzv2m && cur_role == USB_ROLE_HOST) device_release_driver(host); - if (usb3->driver) + if (usb3->driver) { + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(dev->parent, false); usb3_disconnect(usb3); + } usb3_vbus_out(usb3, false); + + if (usb3->is_rzv2m) { + rzv2m_usb3drd_reset(dev->parent, true); + device_release_driver(host); + } break; case USB_ROLE_DEVICE: if (usb3->connection_state == USB_ROLE_NONE) { usb3->connection_state = USB_ROLE_DEVICE; usb3_set_mode(usb3, false); - if (usb3->driver) + if (usb3->driver) { + if (usb3->is_rzv2m) + renesas_usb3_init_controller(usb3); usb3_connect(usb3); + } } else if (cur_role == USB_ROLE_HOST) { device_release_driver(host); usb3_set_mode(usb3, false); @@ -2469,8 +2490,11 @@ static void handle_ext_role_switch_states(struct device *dev, break; case USB_ROLE_HOST: if (usb3->connection_state == USB_ROLE_NONE) { - if (usb3->driver) + if (usb3->driver) { + if (usb3->is_rzv2m) + rzv2m_usb3drd_reset(dev->parent, false); usb3_disconnect(usb3); + } usb3->connection_state = USB_ROLE_HOST; usb3_set_mode(usb3, true); -- cgit v1.2.3 From 8c6e8b09617915e8af3ab7dbfecd8f0a9c7cf94f Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:48 +0000 Subject: usb: host: xhci-plat: Improve clock handling in probe() It is always better to acquire all the clock resources first and then do the clock operations. This patch acquires all the optional clocks first and then calls corresponding prepare_enable(). There is no functional change. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230121145853.4792-8-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 5fb55bf19493..11b3a0d6722d 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -257,16 +257,16 @@ static int xhci_plat_probe(struct platform_device *pdev) goto put_hcd; } - ret = clk_prepare_enable(xhci->reg_clk); - if (ret) - goto put_hcd; - xhci->clk = devm_clk_get_optional(&pdev->dev, NULL); if (IS_ERR(xhci->clk)) { ret = PTR_ERR(xhci->clk); - goto disable_reg_clk; + goto put_hcd; } + ret = clk_prepare_enable(xhci->reg_clk); + if (ret) + goto put_hcd; + ret = clk_prepare_enable(xhci->clk); if (ret) goto disable_reg_clk; -- cgit v1.2.3 From 224eb5311d6a8c180932465873d809b48a2470bf Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:49 +0000 Subject: usb: host: xhci-plat: Add reset support Add optional reset support. This is in preparation to adding USB xHCI support for RZ/V2M SoC. Signed-off-by: Biju Das Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20230121145853.4792-9-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 11b3a0d6722d..c5fc175a5fd1 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "xhci.h" #include "xhci-plat.h" @@ -263,10 +264,20 @@ static int xhci_plat_probe(struct platform_device *pdev) goto put_hcd; } - ret = clk_prepare_enable(xhci->reg_clk); + xhci->reset = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(xhci->reset)) { + ret = PTR_ERR(xhci->reset); + goto put_hcd; + } + + ret = reset_control_deassert(xhci->reset); if (ret) goto put_hcd; + ret = clk_prepare_enable(xhci->reg_clk); + if (ret) + goto err_reset; + ret = clk_prepare_enable(xhci->clk); if (ret) goto disable_reg_clk; @@ -377,6 +388,9 @@ disable_clk: disable_reg_clk: clk_disable_unprepare(xhci->reg_clk); +err_reset: + reset_control_assert(xhci->reset); + put_hcd: usb_put_hcd(hcd); @@ -412,6 +426,7 @@ static int xhci_plat_remove(struct platform_device *dev) clk_disable_unprepare(clk); clk_disable_unprepare(reg_clk); + reset_control_assert(xhci->reset); usb_put_hcd(hcd); pm_runtime_disable(&dev->dev); -- cgit v1.2.3 From c52c9acc415eb6ff54f658492f8c53da0fc3528a Mon Sep 17 00:00:00 2001 From: Biju Das Date: Sat, 21 Jan 2023 14:58:50 +0000 Subject: xhci: host: Add Renesas RZ/V2M SoC support RZ/V2M is similar to R-Car XHCI but it doesn't require any firmware, we need to reset the USB Host reset release in DRD Module before accessing host registers. Signed-off-by: Biju Das Link: https://lore.kernel.org/r/20230121145853.4792-10-biju.das.jz@bp.renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 10 ++++++++++ drivers/usb/host/Makefile | 3 +++ drivers/usb/host/xhci-plat.c | 11 +++++++++++ drivers/usb/host/xhci-rzv2m.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-rzv2m.h | 16 ++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 drivers/usb/host/xhci-rzv2m.c create mode 100644 drivers/usb/host/xhci-rzv2m.h diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 8d799d23c476..662a8bd9a3af 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -54,6 +54,7 @@ config USB_XHCI_PCI_RENESAS config USB_XHCI_PLATFORM tristate "Generic xHCI driver for a platform device" select USB_XHCI_RCAR if ARCH_RENESAS + select USB_XHCI_RZV2M if ARCH_R9A09G011 help Adds an xHCI host driver for a generic platform device, which provides a memory space and an irq. @@ -95,6 +96,15 @@ config USB_XHCI_RCAR Say 'Y' to enable the support for the xHCI host controller found in Renesas R-Car ARM SoCs. +config USB_XHCI_RZV2M + tristate "xHCI support for Renesas RZ/V2M SoC" + depends on USB_XHCI_PLATFORM + depends on ARCH_R9A09G011 || COMPILE_TEST + select USB_RZV2M_USB3DRD + help + Say 'Y' to enable the support for the xHCI host controller + found in Renesas RZ/V2M SoC. + config USB_XHCI_TEGRA tristate "xHCI support for NVIDIA Tegra SoCs" depends on PHY_TEGRA_XUSB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 6d8ee264c9b2..6b1f9317f116 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -28,6 +28,9 @@ endif ifneq ($(CONFIG_USB_XHCI_RCAR), ) xhci-plat-hcd-y += xhci-rcar.o endif +ifneq ($(CONFIG_USB_XHCI_RZV2M), ) + xhci-plat-hcd-y += xhci-rzv2m.o +endif ifneq ($(CONFIG_DEBUG_FS),) xhci-hcd-y += xhci-debugfs.o diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index c5fc175a5fd1..57269f1f318e 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -25,6 +25,7 @@ #include "xhci-plat.h" #include "xhci-mvebu.h" #include "xhci-rcar.h" +#include "xhci-rzv2m.h" static struct hc_driver __read_mostly xhci_plat_hc_driver; @@ -123,6 +124,13 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3) }; +static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | + XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rzv2m_init_quirk, + .plat_start = xhci_rzv2m_start, +}; + static const struct xhci_plat_priv xhci_plat_brcm = { .quirks = XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, }; @@ -162,6 +170,9 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,rcar-gen3-xhci", .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,rzv2m-xhci", + .data = &xhci_plat_renesas_rzv2m, }, { .compatible = "brcm,xhci-brcm-v2", .data = &xhci_plat_brcm, diff --git a/drivers/usb/host/xhci-rzv2m.c b/drivers/usb/host/xhci-rzv2m.c new file mode 100644 index 000000000000..ec65b24eafa8 --- /dev/null +++ b/drivers/usb/host/xhci-rzv2m.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * xHCI host controller driver for RZ/V2M + * + * Copyright (C) 2022 Renesas Electronics Corporation + */ + +#include +#include "xhci-plat.h" +#include "xhci-rzv2m.h" + +#define RZV2M_USB3_INTEN 0x1044 /* Interrupt Enable */ + +#define RZV2M_USB3_INT_XHC_ENA BIT(0) +#define RZV2M_USB3_INT_HSE_ENA BIT(2) +#define RZV2M_USB3_INT_ENA_VAL (RZV2M_USB3_INT_XHC_ENA \ + | RZV2M_USB3_INT_HSE_ENA) + +int xhci_rzv2m_init_quirk(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + + rzv2m_usb3drd_reset(dev->parent, true); + + return 0; +} + +void xhci_rzv2m_start(struct usb_hcd *hcd) +{ + u32 int_en; + + if (hcd->regs) { + /* Interrupt Enable */ + int_en = readl(hcd->regs + RZV2M_USB3_INTEN); + int_en |= RZV2M_USB3_INT_ENA_VAL; + writel(int_en, hcd->regs + RZV2M_USB3_INTEN); + } +} diff --git a/drivers/usb/host/xhci-rzv2m.h b/drivers/usb/host/xhci-rzv2m.h new file mode 100644 index 000000000000..12448b0e8d5b --- /dev/null +++ b/drivers/usb/host/xhci-rzv2m.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __XHCI_RZV2M_H +#define __XHCI_RZV2M_H + +#if IS_ENABLED(CONFIG_USB_XHCI_RZV2M) +void xhci_rzv2m_start(struct usb_hcd *hcd); +int xhci_rzv2m_init_quirk(struct usb_hcd *hcd); +#else +static inline void xhci_rzv2m_start(struct usb_hcd *hcd) {} +static inline int xhci_rzv2m_init_quirk(struct usb_hcd *hcd) +{ + return -EINVAL; +} +#endif + +#endif /* __XHCI_RZV2M_H */ -- cgit v1.2.3 From ace75e18e736bffda1eaf9fd7eab596ecccb4877 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 23 Jan 2023 08:28:20 +0200 Subject: thunderbolt: Handle bandwidth allocation mode enablement notification When the graphics side enables bandwidth allocation mode the DP IN adapter sends notification to the connection manager about this. Currently the handler misses this and tries to allocate 0 Mb/s that then makes the graphics side to think the request failed. Fix this by properly handling the enablement notification. Fixes: 6ce3563520be ("thunderbolt: Add support for DisplayPort bandwidth allocation mode") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 10 +++++++--- drivers/thunderbolt/usb4.c | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index cdd1daaa5da1..bd5d119d7f64 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1746,11 +1746,15 @@ static void tb_handle_dp_bandwidth_request(struct work_struct *work) goto unlock; } - requested_bw = usb4_dp_port_requested_bw(in); - if (requested_bw < 0) { - tb_port_dbg(in, "no bandwidth request active\n"); + ret = usb4_dp_port_requested_bw(in); + if (ret < 0) { + if (ret == -ENODATA) + tb_port_dbg(in, "no bandwidth request active\n"); + else + tb_port_warn(in, "failed to read requested bandwidth\n"); goto unlock; } + requested_bw = ret; tb_port_dbg(in, "requested bandwidth %d Mb/s\n", requested_bw); diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 2a9266fb5c0f..1e5e9c147a31 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -2732,7 +2732,8 @@ int usb4_dp_port_allocate_bw(struct tb_port *port, int bw) * Reads the DPCD (graphics driver) requested bandwidth and returns it * in Mb/s. Takes the programmed granularity into account. In case of * error returns negative errno. Specifically returns %-EOPNOTSUPP if - * the adapter does not support bandwidth allocation mode. + * the adapter does not support bandwidth allocation mode, and %ENODATA + * if there is no active bandwidth request from the graphics driver. */ int usb4_dp_port_requested_bw(struct tb_port *port) { @@ -2750,10 +2751,10 @@ int usb4_dp_port_requested_bw(struct tb_port *port) ret = tb_port_read(port, &val, TB_CFG_PORT, port->cap_adap + ADP_DP_CS_8, 1); if (ret) - return 0; + return ret; if (!(val & ADP_DP_CS_8_DR)) - return 0; + return -ENODATA; return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity; } -- cgit v1.2.3 From 06cbcbfaa6510eed406e3b6d5d071386b9830689 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 18 Jan 2023 14:44:10 +0200 Subject: thunderbolt: Add missing kernel-doc comment to tb_tunnel_maximum_bandwidth() These were missing from the original commit so add them now. No functional changes. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 10 ++++++++++ drivers/thunderbolt/tunnel.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index e87d378ca2cd..554c44d4fab0 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -2081,6 +2081,16 @@ static bool tb_tunnel_is_active(const struct tb_tunnel *tunnel) return true; } +/** + * tb_tunnel_maximum_bandwidth() - Return maximum possible bandwidth + * @tunnel: Tunnel to check + * @max_up: Maximum upstream bandwidth in Mb/s + * @max_down: Maximum downstream bandwidth in Mb/s + * + * Returns maximum possible bandwidth this tunnel can go if not limited + * by other bandwidth clients. If the tunnel does not support this + * returns %-EOPNOTSUPP. + */ int tb_tunnel_maximum_bandwidth(struct tb_tunnel *tunnel, int *max_up, int *max_down) { diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index d7bbdf8c8186..bf690f7beeee 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -29,7 +29,7 @@ enum tb_tunnel_type { * @init: Optional tunnel specific initialization * @deinit: Optional tunnel specific de-initialization * @activate: Optional tunnel specific activation/deactivation - * @maximum_bandwidth: + * @maximum_bandwidth: Returns maximum possible bandwidth for this tunnel * @allocated_bandwidth: Return how much bandwidth is allocated for the tunnel * @alloc_bandwidth: Change tunnel bandwidth allocation * @consumed_bandwidth: Return how much bandwidth the tunnel consumes -- cgit v1.2.3 From 015d44c2b700ba9639dd29672ba362796cc0be54 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 27 Jan 2023 00:14:52 +0100 Subject: media: uvcvideo: Add GUID for BGRA/X 8:8:8:8 The Cypress EZUSB FX3 UVC example can be configured to report pixel format "e436eb7e-524f-11ce-9f53-0020af0ba770". This is its GUID for BGRA/X 8:8:8:8. The UVC 1.5 spec [1] only defines GUIDs for YUY2, NV12, M420 and I420. This seems to be an extension documented in the Microsoft Windows Media Format SDK[2]. This Media Format SDK defines this GUID as corresponding to `MEDIASUBTYPE_RGB32`, which is confirmed by [4] as `MEDIASUBTYPE_ARGB32` has different GUID. Note that in my case, the FX3 UVC can output either channel order, BGR or RGB or any other mix for that matter. Since Linux commit 1b8dc32286a1a ("[media] uvcvideo: Add GUID for BGR 8:8:8") defined a GUID for `MEDIASUBTYPE_RGB24` channel order as BGR, keep this change consistent and define `MEDIASUBTYPE_RGB32` as BGR as well. Document [3] also indicates the channel order is BGR. [1] https://www.usb.org/document-library/video-class-v15-document-set [2] https://learn.microsoft.com/en-us/windows/win32/wmformat/media-type-identifiers [3] https://learn.microsoft.com/en-us/windows/win32/directshow/uncompressed-rgb-video-subtypes [4] https://gix.github.io/media-types/ Signed-off-by: Marek Vasut Reviewed-by: Laurent Pinchart Reviewed-by: Ricardo Ribalda Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20230126231456.3402323-2-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- include/media/v4l2-uvc.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/media/v4l2-uvc.h b/include/media/v4l2-uvc.h index f83e31661333..b010a36fc1d9 100644 --- a/include/media/v4l2-uvc.h +++ b/include/media/v4l2-uvc.h @@ -99,6 +99,9 @@ #define UVC_GUID_FORMAT_BGR3 \ { 0x7d, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} +#define UVC_GUID_FORMAT_BGR4 \ + { 0x7e, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ + 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} #define UVC_GUID_FORMAT_M420 \ { 'M', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} @@ -266,6 +269,11 @@ static struct uvc_format_desc uvc_fmts[] = { .guid = UVC_GUID_FORMAT_BGR3, .fcc = V4L2_PIX_FMT_BGR24, }, + { + .name = "BGRA/X 8:8:8:8 (BGR4)", + .guid = UVC_GUID_FORMAT_BGR4, + .fcc = V4L2_PIX_FMT_XBGR32, + }, { .name = "H.264", .guid = UVC_GUID_FORMAT_H264, -- cgit v1.2.3 From e1d5d71d189f290343fb1f18eecf77335c5d1ef3 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 27 Jan 2023 00:14:53 +0100 Subject: usb: uvc: move media/v4l2-uvc.h to usb/uvc.h Since the headerfile is only used in usb devices it is better placed with the other usb files. Reviewed-by: Laurent Pinchart Reviewed-by: Daniel Scally Tested-by: Daniel Scally Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20230126231456.3402323-3-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/media/usb/uvc/uvc_ctrl.c | 2 +- drivers/media/usb/uvc/uvc_driver.c | 2 +- drivers/usb/gadget/function/uvc_v4l2.c | 2 +- include/linux/usb/uvc.h | 367 +++++++++++++++++++++++++++++++++ include/media/v4l2-uvc.h | 367 --------------------------------- 5 files changed, 370 insertions(+), 370 deletions(-) create mode 100644 include/linux/usb/uvc.h delete mode 100644 include/media/v4l2-uvc.h diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index c3aeba3fe31b..5e9d3da862dd 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -14,13 +14,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "uvcvideo.h" diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 11f3d716b5bf..edf8c4a201a8 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,6 @@ #include #include -#include #include "uvcvideo.h" diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index a189b08bba80..7435df0cf2a8 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,6 @@ #include #include #include -#include #include "f_uvc.h" #include "uvc.h" diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h new file mode 100644 index 000000000000..b010a36fc1d9 --- /dev/null +++ b/include/linux/usb/uvc.h @@ -0,0 +1,367 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * v4l2 uvc internal API header + * + * Some commonly needed functions for uvc drivers + */ + +#ifndef __LINUX_V4L2_UVC_H +#define __LINUX_V4L2_UVC_H + +/* ------------------------------------------------------------------------ + * GUIDs + */ +#define UVC_GUID_UVC_CAMERA \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01} +#define UVC_GUID_UVC_OUTPUT \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02} +#define UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03} +#define UVC_GUID_UVC_PROCESSING \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01} +#define UVC_GUID_UVC_SELECTOR \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02} +#define UVC_GUID_EXT_GPIO_CONTROLLER \ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03} + +#define UVC_GUID_FORMAT_MJPEG \ + { 'M', 'J', 'P', 'G', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_YUY2 \ + { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_YUY2_ISIGHT \ + { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_NV12 \ + { 'N', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_YV12 \ + { 'Y', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_I420 \ + { 'I', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_UYVY \ + { 'U', 'Y', 'V', 'Y', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y800 \ + { 'Y', '8', '0', '0', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y8 \ + { 'Y', '8', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y10 \ + { 'Y', '1', '0', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y12 \ + { 'Y', '1', '2', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y16 \ + { 'Y', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_BY8 \ + { 'B', 'Y', '8', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_BA81 \ + { 'B', 'A', '8', '1', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_GBRG \ + { 'G', 'B', 'R', 'G', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_GRBG \ + { 'G', 'R', 'B', 'G', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_RGGB \ + { 'R', 'G', 'G', 'B', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_BG16 \ + { 'B', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_GB16 \ + { 'G', 'B', '1', '6', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_RG16 \ + { 'R', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_GR16 \ + { 'G', 'R', '1', '6', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_RGBP \ + { 'R', 'G', 'B', 'P', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_BGR3 \ + { 0x7d, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ + 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} +#define UVC_GUID_FORMAT_BGR4 \ + { 0x7e, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ + 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} +#define UVC_GUID_FORMAT_M420 \ + { 'M', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} + +#define UVC_GUID_FORMAT_H264 \ + { 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_H265 \ + { 'H', '2', '6', '5', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y8I \ + { 'Y', '8', 'I', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Y12I \ + { 'Y', '1', '2', 'I', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_Z16 \ + { 'Z', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_RW10 \ + { 'R', 'W', '1', '0', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_INVZ \ + { 'I', 'N', 'V', 'Z', 0x90, 0x2d, 0x58, 0x4a, \ + 0x92, 0x0b, 0x77, 0x3f, 0x1f, 0x2c, 0x55, 0x6b} +#define UVC_GUID_FORMAT_INZI \ + { 'I', 'N', 'Z', 'I', 0x66, 0x1a, 0x42, 0xa2, \ + 0x90, 0x65, 0xd0, 0x18, 0x14, 0xa8, 0xef, 0x8a} +#define UVC_GUID_FORMAT_INVI \ + { 'I', 'N', 'V', 'I', 0xdb, 0x57, 0x49, 0x5e, \ + 0x8e, 0x3f, 0xf4, 0x79, 0x53, 0x2b, 0x94, 0x6f} +#define UVC_GUID_FORMAT_CNF4 \ + { 'C', ' ', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} + +#define UVC_GUID_FORMAT_D3DFMT_L8 \ + {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_KSMEDIA_L8_IR \ + {0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} + +#define UVC_GUID_FORMAT_HEVC \ + { 'H', 'E', 'V', 'C', 0x00, 0x00, 0x10, 0x00, \ + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} + +/* ------------------------------------------------------------------------ + * Video formats + */ + +struct uvc_format_desc { + char *name; + u8 guid[16]; + u32 fcc; +}; + +static struct uvc_format_desc uvc_fmts[] = { + { + .name = "YUV 4:2:2 (YUYV)", + .guid = UVC_GUID_FORMAT_YUY2, + .fcc = V4L2_PIX_FMT_YUYV, + }, + { + .name = "YUV 4:2:2 (YUYV)", + .guid = UVC_GUID_FORMAT_YUY2_ISIGHT, + .fcc = V4L2_PIX_FMT_YUYV, + }, + { + .name = "YUV 4:2:0 (NV12)", + .guid = UVC_GUID_FORMAT_NV12, + .fcc = V4L2_PIX_FMT_NV12, + }, + { + .name = "MJPEG", + .guid = UVC_GUID_FORMAT_MJPEG, + .fcc = V4L2_PIX_FMT_MJPEG, + }, + { + .name = "YVU 4:2:0 (YV12)", + .guid = UVC_GUID_FORMAT_YV12, + .fcc = V4L2_PIX_FMT_YVU420, + }, + { + .name = "YUV 4:2:0 (I420)", + .guid = UVC_GUID_FORMAT_I420, + .fcc = V4L2_PIX_FMT_YUV420, + }, + { + .name = "YUV 4:2:0 (M420)", + .guid = UVC_GUID_FORMAT_M420, + .fcc = V4L2_PIX_FMT_M420, + }, + { + .name = "YUV 4:2:2 (UYVY)", + .guid = UVC_GUID_FORMAT_UYVY, + .fcc = V4L2_PIX_FMT_UYVY, + }, + { + .name = "Greyscale 8-bit (Y800)", + .guid = UVC_GUID_FORMAT_Y800, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 8-bit (Y8 )", + .guid = UVC_GUID_FORMAT_Y8, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 8-bit (D3DFMT_L8)", + .guid = UVC_GUID_FORMAT_D3DFMT_L8, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "IR 8-bit (L8_IR)", + .guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 10-bit (Y10 )", + .guid = UVC_GUID_FORMAT_Y10, + .fcc = V4L2_PIX_FMT_Y10, + }, + { + .name = "Greyscale 12-bit (Y12 )", + .guid = UVC_GUID_FORMAT_Y12, + .fcc = V4L2_PIX_FMT_Y12, + }, + { + .name = "Greyscale 16-bit (Y16 )", + .guid = UVC_GUID_FORMAT_Y16, + .fcc = V4L2_PIX_FMT_Y16, + }, + { + .name = "BGGR Bayer (BY8 )", + .guid = UVC_GUID_FORMAT_BY8, + .fcc = V4L2_PIX_FMT_SBGGR8, + }, + { + .name = "BGGR Bayer (BA81)", + .guid = UVC_GUID_FORMAT_BA81, + .fcc = V4L2_PIX_FMT_SBGGR8, + }, + { + .name = "GBRG Bayer (GBRG)", + .guid = UVC_GUID_FORMAT_GBRG, + .fcc = V4L2_PIX_FMT_SGBRG8, + }, + { + .name = "GRBG Bayer (GRBG)", + .guid = UVC_GUID_FORMAT_GRBG, + .fcc = V4L2_PIX_FMT_SGRBG8, + }, + { + .name = "RGGB Bayer (RGGB)", + .guid = UVC_GUID_FORMAT_RGGB, + .fcc = V4L2_PIX_FMT_SRGGB8, + }, + { + .name = "RGB565", + .guid = UVC_GUID_FORMAT_RGBP, + .fcc = V4L2_PIX_FMT_RGB565, + }, + { + .name = "BGR 8:8:8 (BGR3)", + .guid = UVC_GUID_FORMAT_BGR3, + .fcc = V4L2_PIX_FMT_BGR24, + }, + { + .name = "BGRA/X 8:8:8:8 (BGR4)", + .guid = UVC_GUID_FORMAT_BGR4, + .fcc = V4L2_PIX_FMT_XBGR32, + }, + { + .name = "H.264", + .guid = UVC_GUID_FORMAT_H264, + .fcc = V4L2_PIX_FMT_H264, + }, + { + .name = "H.265", + .guid = UVC_GUID_FORMAT_H265, + .fcc = V4L2_PIX_FMT_HEVC, + }, + { + .name = "Greyscale 8 L/R (Y8I)", + .guid = UVC_GUID_FORMAT_Y8I, + .fcc = V4L2_PIX_FMT_Y8I, + }, + { + .name = "Greyscale 12 L/R (Y12I)", + .guid = UVC_GUID_FORMAT_Y12I, + .fcc = V4L2_PIX_FMT_Y12I, + }, + { + .name = "Depth data 16-bit (Z16)", + .guid = UVC_GUID_FORMAT_Z16, + .fcc = V4L2_PIX_FMT_Z16, + }, + { + .name = "Bayer 10-bit (SRGGB10P)", + .guid = UVC_GUID_FORMAT_RW10, + .fcc = V4L2_PIX_FMT_SRGGB10P, + }, + { + .name = "Bayer 16-bit (SBGGR16)", + .guid = UVC_GUID_FORMAT_BG16, + .fcc = V4L2_PIX_FMT_SBGGR16, + }, + { + .name = "Bayer 16-bit (SGBRG16)", + .guid = UVC_GUID_FORMAT_GB16, + .fcc = V4L2_PIX_FMT_SGBRG16, + }, + { + .name = "Bayer 16-bit (SRGGB16)", + .guid = UVC_GUID_FORMAT_RG16, + .fcc = V4L2_PIX_FMT_SRGGB16, + }, + { + .name = "Bayer 16-bit (SGRBG16)", + .guid = UVC_GUID_FORMAT_GR16, + .fcc = V4L2_PIX_FMT_SGRBG16, + }, + { + .name = "Depth data 16-bit (Z16)", + .guid = UVC_GUID_FORMAT_INVZ, + .fcc = V4L2_PIX_FMT_Z16, + }, + { + .name = "Greyscale 10-bit (Y10 )", + .guid = UVC_GUID_FORMAT_INVI, + .fcc = V4L2_PIX_FMT_Y10, + }, + { + .name = "IR:Depth 26-bit (INZI)", + .guid = UVC_GUID_FORMAT_INZI, + .fcc = V4L2_PIX_FMT_INZI, + }, + { + .name = "4-bit Depth Confidence (Packed)", + .guid = UVC_GUID_FORMAT_CNF4, + .fcc = V4L2_PIX_FMT_CNF4, + }, + { + .name = "HEVC", + .guid = UVC_GUID_FORMAT_HEVC, + .fcc = V4L2_PIX_FMT_HEVC, + }, +}; + +static inline struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) +{ + unsigned int len = ARRAY_SIZE(uvc_fmts); + unsigned int i; + + for (i = 0; i < len; ++i) { + if (memcmp(guid, uvc_fmts[i].guid, 16) == 0) + return &uvc_fmts[i]; + } + + return NULL; +} + +#endif /* __LINUX_V4L2_UVC_H */ diff --git a/include/media/v4l2-uvc.h b/include/media/v4l2-uvc.h deleted file mode 100644 index b010a36fc1d9..000000000000 --- a/include/media/v4l2-uvc.h +++ /dev/null @@ -1,367 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * v4l2 uvc internal API header - * - * Some commonly needed functions for uvc drivers - */ - -#ifndef __LINUX_V4L2_UVC_H -#define __LINUX_V4L2_UVC_H - -/* ------------------------------------------------------------------------ - * GUIDs - */ -#define UVC_GUID_UVC_CAMERA \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01} -#define UVC_GUID_UVC_OUTPUT \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02} -#define UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03} -#define UVC_GUID_UVC_PROCESSING \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01} -#define UVC_GUID_UVC_SELECTOR \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02} -#define UVC_GUID_EXT_GPIO_CONTROLLER \ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03} - -#define UVC_GUID_FORMAT_MJPEG \ - { 'M', 'J', 'P', 'G', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_YUY2 \ - { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_YUY2_ISIGHT \ - { 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_NV12 \ - { 'N', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_YV12 \ - { 'Y', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_I420 \ - { 'I', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_UYVY \ - { 'U', 'Y', 'V', 'Y', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y800 \ - { 'Y', '8', '0', '0', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y8 \ - { 'Y', '8', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y10 \ - { 'Y', '1', '0', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y12 \ - { 'Y', '1', '2', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y16 \ - { 'Y', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_BY8 \ - { 'B', 'Y', '8', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_BA81 \ - { 'B', 'A', '8', '1', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_GBRG \ - { 'G', 'B', 'R', 'G', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_GRBG \ - { 'G', 'R', 'B', 'G', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_RGGB \ - { 'R', 'G', 'G', 'B', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_BG16 \ - { 'B', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_GB16 \ - { 'G', 'B', '1', '6', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_RG16 \ - { 'R', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_GR16 \ - { 'G', 'R', '1', '6', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_RGBP \ - { 'R', 'G', 'B', 'P', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_BGR3 \ - { 0x7d, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ - 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} -#define UVC_GUID_FORMAT_BGR4 \ - { 0x7e, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \ - 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70} -#define UVC_GUID_FORMAT_M420 \ - { 'M', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} - -#define UVC_GUID_FORMAT_H264 \ - { 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_H265 \ - { 'H', '2', '6', '5', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y8I \ - { 'Y', '8', 'I', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Y12I \ - { 'Y', '1', '2', 'I', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_Z16 \ - { 'Z', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_RW10 \ - { 'R', 'W', '1', '0', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_INVZ \ - { 'I', 'N', 'V', 'Z', 0x90, 0x2d, 0x58, 0x4a, \ - 0x92, 0x0b, 0x77, 0x3f, 0x1f, 0x2c, 0x55, 0x6b} -#define UVC_GUID_FORMAT_INZI \ - { 'I', 'N', 'Z', 'I', 0x66, 0x1a, 0x42, 0xa2, \ - 0x90, 0x65, 0xd0, 0x18, 0x14, 0xa8, 0xef, 0x8a} -#define UVC_GUID_FORMAT_INVI \ - { 'I', 'N', 'V', 'I', 0xdb, 0x57, 0x49, 0x5e, \ - 0x8e, 0x3f, 0xf4, 0x79, 0x53, 0x2b, 0x94, 0x6f} -#define UVC_GUID_FORMAT_CNF4 \ - { 'C', ' ', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} - -#define UVC_GUID_FORMAT_D3DFMT_L8 \ - {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -#define UVC_GUID_FORMAT_KSMEDIA_L8_IR \ - {0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} - -#define UVC_GUID_FORMAT_HEVC \ - { 'H', 'E', 'V', 'C', 0x00, 0x00, 0x10, 0x00, \ - 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} - -/* ------------------------------------------------------------------------ - * Video formats - */ - -struct uvc_format_desc { - char *name; - u8 guid[16]; - u32 fcc; -}; - -static struct uvc_format_desc uvc_fmts[] = { - { - .name = "YUV 4:2:2 (YUYV)", - .guid = UVC_GUID_FORMAT_YUY2, - .fcc = V4L2_PIX_FMT_YUYV, - }, - { - .name = "YUV 4:2:2 (YUYV)", - .guid = UVC_GUID_FORMAT_YUY2_ISIGHT, - .fcc = V4L2_PIX_FMT_YUYV, - }, - { - .name = "YUV 4:2:0 (NV12)", - .guid = UVC_GUID_FORMAT_NV12, - .fcc = V4L2_PIX_FMT_NV12, - }, - { - .name = "MJPEG", - .guid = UVC_GUID_FORMAT_MJPEG, - .fcc = V4L2_PIX_FMT_MJPEG, - }, - { - .name = "YVU 4:2:0 (YV12)", - .guid = UVC_GUID_FORMAT_YV12, - .fcc = V4L2_PIX_FMT_YVU420, - }, - { - .name = "YUV 4:2:0 (I420)", - .guid = UVC_GUID_FORMAT_I420, - .fcc = V4L2_PIX_FMT_YUV420, - }, - { - .name = "YUV 4:2:0 (M420)", - .guid = UVC_GUID_FORMAT_M420, - .fcc = V4L2_PIX_FMT_M420, - }, - { - .name = "YUV 4:2:2 (UYVY)", - .guid = UVC_GUID_FORMAT_UYVY, - .fcc = V4L2_PIX_FMT_UYVY, - }, - { - .name = "Greyscale 8-bit (Y800)", - .guid = UVC_GUID_FORMAT_Y800, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 8-bit (Y8 )", - .guid = UVC_GUID_FORMAT_Y8, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 8-bit (D3DFMT_L8)", - .guid = UVC_GUID_FORMAT_D3DFMT_L8, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "IR 8-bit (L8_IR)", - .guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 10-bit (Y10 )", - .guid = UVC_GUID_FORMAT_Y10, - .fcc = V4L2_PIX_FMT_Y10, - }, - { - .name = "Greyscale 12-bit (Y12 )", - .guid = UVC_GUID_FORMAT_Y12, - .fcc = V4L2_PIX_FMT_Y12, - }, - { - .name = "Greyscale 16-bit (Y16 )", - .guid = UVC_GUID_FORMAT_Y16, - .fcc = V4L2_PIX_FMT_Y16, - }, - { - .name = "BGGR Bayer (BY8 )", - .guid = UVC_GUID_FORMAT_BY8, - .fcc = V4L2_PIX_FMT_SBGGR8, - }, - { - .name = "BGGR Bayer (BA81)", - .guid = UVC_GUID_FORMAT_BA81, - .fcc = V4L2_PIX_FMT_SBGGR8, - }, - { - .name = "GBRG Bayer (GBRG)", - .guid = UVC_GUID_FORMAT_GBRG, - .fcc = V4L2_PIX_FMT_SGBRG8, - }, - { - .name = "GRBG Bayer (GRBG)", - .guid = UVC_GUID_FORMAT_GRBG, - .fcc = V4L2_PIX_FMT_SGRBG8, - }, - { - .name = "RGGB Bayer (RGGB)", - .guid = UVC_GUID_FORMAT_RGGB, - .fcc = V4L2_PIX_FMT_SRGGB8, - }, - { - .name = "RGB565", - .guid = UVC_GUID_FORMAT_RGBP, - .fcc = V4L2_PIX_FMT_RGB565, - }, - { - .name = "BGR 8:8:8 (BGR3)", - .guid = UVC_GUID_FORMAT_BGR3, - .fcc = V4L2_PIX_FMT_BGR24, - }, - { - .name = "BGRA/X 8:8:8:8 (BGR4)", - .guid = UVC_GUID_FORMAT_BGR4, - .fcc = V4L2_PIX_FMT_XBGR32, - }, - { - .name = "H.264", - .guid = UVC_GUID_FORMAT_H264, - .fcc = V4L2_PIX_FMT_H264, - }, - { - .name = "H.265", - .guid = UVC_GUID_FORMAT_H265, - .fcc = V4L2_PIX_FMT_HEVC, - }, - { - .name = "Greyscale 8 L/R (Y8I)", - .guid = UVC_GUID_FORMAT_Y8I, - .fcc = V4L2_PIX_FMT_Y8I, - }, - { - .name = "Greyscale 12 L/R (Y12I)", - .guid = UVC_GUID_FORMAT_Y12I, - .fcc = V4L2_PIX_FMT_Y12I, - }, - { - .name = "Depth data 16-bit (Z16)", - .guid = UVC_GUID_FORMAT_Z16, - .fcc = V4L2_PIX_FMT_Z16, - }, - { - .name = "Bayer 10-bit (SRGGB10P)", - .guid = UVC_GUID_FORMAT_RW10, - .fcc = V4L2_PIX_FMT_SRGGB10P, - }, - { - .name = "Bayer 16-bit (SBGGR16)", - .guid = UVC_GUID_FORMAT_BG16, - .fcc = V4L2_PIX_FMT_SBGGR16, - }, - { - .name = "Bayer 16-bit (SGBRG16)", - .guid = UVC_GUID_FORMAT_GB16, - .fcc = V4L2_PIX_FMT_SGBRG16, - }, - { - .name = "Bayer 16-bit (SRGGB16)", - .guid = UVC_GUID_FORMAT_RG16, - .fcc = V4L2_PIX_FMT_SRGGB16, - }, - { - .name = "Bayer 16-bit (SGRBG16)", - .guid = UVC_GUID_FORMAT_GR16, - .fcc = V4L2_PIX_FMT_SGRBG16, - }, - { - .name = "Depth data 16-bit (Z16)", - .guid = UVC_GUID_FORMAT_INVZ, - .fcc = V4L2_PIX_FMT_Z16, - }, - { - .name = "Greyscale 10-bit (Y10 )", - .guid = UVC_GUID_FORMAT_INVI, - .fcc = V4L2_PIX_FMT_Y10, - }, - { - .name = "IR:Depth 26-bit (INZI)", - .guid = UVC_GUID_FORMAT_INZI, - .fcc = V4L2_PIX_FMT_INZI, - }, - { - .name = "4-bit Depth Confidence (Packed)", - .guid = UVC_GUID_FORMAT_CNF4, - .fcc = V4L2_PIX_FMT_CNF4, - }, - { - .name = "HEVC", - .guid = UVC_GUID_FORMAT_HEVC, - .fcc = V4L2_PIX_FMT_HEVC, - }, -}; - -static inline struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) -{ - unsigned int len = ARRAY_SIZE(uvc_fmts); - unsigned int i; - - for (i = 0; i < len; ++i) { - if (memcmp(guid, uvc_fmts[i].guid, 16) == 0) - return &uvc_fmts[i]; - } - - return NULL; -} - -#endif /* __LINUX_V4L2_UVC_H */ -- cgit v1.2.3 From 466be4c9a6f0b7810991b4ac6c3e55345ea63954 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 27 Jan 2023 00:14:54 +0100 Subject: usb: uvc: move uvc_fmts and uvc_format_by_guid to own compile unit The media driver USB_VIDEO_CLASS and USB_F_UVC are using the same function uvc_format_by_guid. Since the function is inline, every user will get a copy of the used uvc_fmts array and the function. This patch moves the code to an own compile unit and add this dependency as UVC_COMMON to both users. Reviewed-by: Laurent Pinchart Reviewed-by: Daniel Scally Tested-by: Daniel Scally Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20230126231456.3402323-4-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/media/common/Kconfig | 3 + drivers/media/common/Makefile | 1 + drivers/media/common/uvc.c | 221 ++++++++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/Kconfig | 1 + drivers/usb/gadget/Kconfig | 1 + include/linux/usb/uvc.h | 210 +-------------------------------------- 6 files changed, 228 insertions(+), 209 deletions(-) create mode 100644 drivers/media/common/uvc.c diff --git a/drivers/media/common/Kconfig b/drivers/media/common/Kconfig index 852b7d92fbdd..b1bc58da27fc 100644 --- a/drivers/media/common/Kconfig +++ b/drivers/media/common/Kconfig @@ -14,6 +14,9 @@ config TTPCI_EEPROM tristate depends on I2C +config UVC_COMMON + tristate + config VIDEO_CX2341X tristate diff --git a/drivers/media/common/Makefile b/drivers/media/common/Makefile index d78a0df15478..3f17d696feb2 100644 --- a/drivers/media/common/Makefile +++ b/drivers/media/common/Makefile @@ -5,5 +5,6 @@ obj-y += b2c2/ siano/ v4l2-tpg/ videobuf2/ # (e. g. LC_ALL=C sort Makefile) obj-$(CONFIG_CYPRESS_FIRMWARE) += cypress_firmware.o obj-$(CONFIG_TTPCI_EEPROM) += ttpci-eeprom.o +obj-$(CONFIG_UVC_COMMON) += uvc.o obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o diff --git a/drivers/media/common/uvc.c b/drivers/media/common/uvc.c new file mode 100644 index 000000000000..bb96d9e26d80 --- /dev/null +++ b/drivers/media/common/uvc.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------------ + * Video formats + */ + +static struct uvc_format_desc uvc_fmts[] = { + { + .name = "YUV 4:2:2 (YUYV)", + .guid = UVC_GUID_FORMAT_YUY2, + .fcc = V4L2_PIX_FMT_YUYV, + }, + { + .name = "YUV 4:2:2 (YUYV)", + .guid = UVC_GUID_FORMAT_YUY2_ISIGHT, + .fcc = V4L2_PIX_FMT_YUYV, + }, + { + .name = "YUV 4:2:0 (NV12)", + .guid = UVC_GUID_FORMAT_NV12, + .fcc = V4L2_PIX_FMT_NV12, + }, + { + .name = "MJPEG", + .guid = UVC_GUID_FORMAT_MJPEG, + .fcc = V4L2_PIX_FMT_MJPEG, + }, + { + .name = "YVU 4:2:0 (YV12)", + .guid = UVC_GUID_FORMAT_YV12, + .fcc = V4L2_PIX_FMT_YVU420, + }, + { + .name = "YUV 4:2:0 (I420)", + .guid = UVC_GUID_FORMAT_I420, + .fcc = V4L2_PIX_FMT_YUV420, + }, + { + .name = "YUV 4:2:0 (M420)", + .guid = UVC_GUID_FORMAT_M420, + .fcc = V4L2_PIX_FMT_M420, + }, + { + .name = "YUV 4:2:2 (UYVY)", + .guid = UVC_GUID_FORMAT_UYVY, + .fcc = V4L2_PIX_FMT_UYVY, + }, + { + .name = "Greyscale 8-bit (Y800)", + .guid = UVC_GUID_FORMAT_Y800, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 8-bit (Y8 )", + .guid = UVC_GUID_FORMAT_Y8, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 8-bit (D3DFMT_L8)", + .guid = UVC_GUID_FORMAT_D3DFMT_L8, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "IR 8-bit (L8_IR)", + .guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR, + .fcc = V4L2_PIX_FMT_GREY, + }, + { + .name = "Greyscale 10-bit (Y10 )", + .guid = UVC_GUID_FORMAT_Y10, + .fcc = V4L2_PIX_FMT_Y10, + }, + { + .name = "Greyscale 12-bit (Y12 )", + .guid = UVC_GUID_FORMAT_Y12, + .fcc = V4L2_PIX_FMT_Y12, + }, + { + .name = "Greyscale 16-bit (Y16 )", + .guid = UVC_GUID_FORMAT_Y16, + .fcc = V4L2_PIX_FMT_Y16, + }, + { + .name = "BGGR Bayer (BY8 )", + .guid = UVC_GUID_FORMAT_BY8, + .fcc = V4L2_PIX_FMT_SBGGR8, + }, + { + .name = "BGGR Bayer (BA81)", + .guid = UVC_GUID_FORMAT_BA81, + .fcc = V4L2_PIX_FMT_SBGGR8, + }, + { + .name = "GBRG Bayer (GBRG)", + .guid = UVC_GUID_FORMAT_GBRG, + .fcc = V4L2_PIX_FMT_SGBRG8, + }, + { + .name = "GRBG Bayer (GRBG)", + .guid = UVC_GUID_FORMAT_GRBG, + .fcc = V4L2_PIX_FMT_SGRBG8, + }, + { + .name = "RGGB Bayer (RGGB)", + .guid = UVC_GUID_FORMAT_RGGB, + .fcc = V4L2_PIX_FMT_SRGGB8, + }, + { + .name = "RGB565", + .guid = UVC_GUID_FORMAT_RGBP, + .fcc = V4L2_PIX_FMT_RGB565, + }, + { + .name = "BGR 8:8:8 (BGR3)", + .guid = UVC_GUID_FORMAT_BGR3, + .fcc = V4L2_PIX_FMT_BGR24, + }, + { + .name = "BGRA/X 8:8:8:8 (BGR4)", + .guid = UVC_GUID_FORMAT_BGR4, + .fcc = V4L2_PIX_FMT_XBGR32, + }, + { + .name = "H.264", + .guid = UVC_GUID_FORMAT_H264, + .fcc = V4L2_PIX_FMT_H264, + }, + { + .name = "H.265", + .guid = UVC_GUID_FORMAT_H265, + .fcc = V4L2_PIX_FMT_HEVC, + }, + { + .name = "Greyscale 8 L/R (Y8I)", + .guid = UVC_GUID_FORMAT_Y8I, + .fcc = V4L2_PIX_FMT_Y8I, + }, + { + .name = "Greyscale 12 L/R (Y12I)", + .guid = UVC_GUID_FORMAT_Y12I, + .fcc = V4L2_PIX_FMT_Y12I, + }, + { + .name = "Depth data 16-bit (Z16)", + .guid = UVC_GUID_FORMAT_Z16, + .fcc = V4L2_PIX_FMT_Z16, + }, + { + .name = "Bayer 10-bit (SRGGB10P)", + .guid = UVC_GUID_FORMAT_RW10, + .fcc = V4L2_PIX_FMT_SRGGB10P, + }, + { + .name = "Bayer 16-bit (SBGGR16)", + .guid = UVC_GUID_FORMAT_BG16, + .fcc = V4L2_PIX_FMT_SBGGR16, + }, + { + .name = "Bayer 16-bit (SGBRG16)", + .guid = UVC_GUID_FORMAT_GB16, + .fcc = V4L2_PIX_FMT_SGBRG16, + }, + { + .name = "Bayer 16-bit (SRGGB16)", + .guid = UVC_GUID_FORMAT_RG16, + .fcc = V4L2_PIX_FMT_SRGGB16, + }, + { + .name = "Bayer 16-bit (SGRBG16)", + .guid = UVC_GUID_FORMAT_GR16, + .fcc = V4L2_PIX_FMT_SGRBG16, + }, + { + .name = "Depth data 16-bit (Z16)", + .guid = UVC_GUID_FORMAT_INVZ, + .fcc = V4L2_PIX_FMT_Z16, + }, + { + .name = "Greyscale 10-bit (Y10 )", + .guid = UVC_GUID_FORMAT_INVI, + .fcc = V4L2_PIX_FMT_Y10, + }, + { + .name = "IR:Depth 26-bit (INZI)", + .guid = UVC_GUID_FORMAT_INZI, + .fcc = V4L2_PIX_FMT_INZI, + }, + { + .name = "4-bit Depth Confidence (Packed)", + .guid = UVC_GUID_FORMAT_CNF4, + .fcc = V4L2_PIX_FMT_CNF4, + }, + { + .name = "HEVC", + .guid = UVC_GUID_FORMAT_HEVC, + .fcc = V4L2_PIX_FMT_HEVC, + }, +}; + +struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) +{ + unsigned int len = ARRAY_SIZE(uvc_fmts); + unsigned int i; + + for (i = 0; i < len; ++i) { + if (memcmp(guid, uvc_fmts[i].guid, 16) == 0) + return &uvc_fmts[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(uvc_format_by_guid); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/usb/uvc/Kconfig b/drivers/media/usb/uvc/Kconfig index ca51ee8e45f3..579532272fd6 100644 --- a/drivers/media/usb/uvc/Kconfig +++ b/drivers/media/usb/uvc/Kconfig @@ -3,6 +3,7 @@ config USB_VIDEO_CLASS tristate "USB Video Class (UVC)" depends on VIDEO_DEV select VIDEOBUF2_VMALLOC + select UVC_COMMON help Support for the USB Video Class (UVC). Currently only video input devices, such as webcams, are supported. diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 4fa2ddf322b4..336db8f92afa 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -203,6 +203,7 @@ config USB_F_UAC2 config USB_F_UVC tristate + select UVC_COMMON config USB_F_MIDI tristate diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index b010a36fc1d9..8cebb46602a1 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -148,220 +148,12 @@ { 'H', 'E', 'V', 'C', 0x00, 0x00, 0x10, 0x00, \ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} -/* ------------------------------------------------------------------------ - * Video formats - */ - struct uvc_format_desc { char *name; u8 guid[16]; u32 fcc; }; -static struct uvc_format_desc uvc_fmts[] = { - { - .name = "YUV 4:2:2 (YUYV)", - .guid = UVC_GUID_FORMAT_YUY2, - .fcc = V4L2_PIX_FMT_YUYV, - }, - { - .name = "YUV 4:2:2 (YUYV)", - .guid = UVC_GUID_FORMAT_YUY2_ISIGHT, - .fcc = V4L2_PIX_FMT_YUYV, - }, - { - .name = "YUV 4:2:0 (NV12)", - .guid = UVC_GUID_FORMAT_NV12, - .fcc = V4L2_PIX_FMT_NV12, - }, - { - .name = "MJPEG", - .guid = UVC_GUID_FORMAT_MJPEG, - .fcc = V4L2_PIX_FMT_MJPEG, - }, - { - .name = "YVU 4:2:0 (YV12)", - .guid = UVC_GUID_FORMAT_YV12, - .fcc = V4L2_PIX_FMT_YVU420, - }, - { - .name = "YUV 4:2:0 (I420)", - .guid = UVC_GUID_FORMAT_I420, - .fcc = V4L2_PIX_FMT_YUV420, - }, - { - .name = "YUV 4:2:0 (M420)", - .guid = UVC_GUID_FORMAT_M420, - .fcc = V4L2_PIX_FMT_M420, - }, - { - .name = "YUV 4:2:2 (UYVY)", - .guid = UVC_GUID_FORMAT_UYVY, - .fcc = V4L2_PIX_FMT_UYVY, - }, - { - .name = "Greyscale 8-bit (Y800)", - .guid = UVC_GUID_FORMAT_Y800, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 8-bit (Y8 )", - .guid = UVC_GUID_FORMAT_Y8, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 8-bit (D3DFMT_L8)", - .guid = UVC_GUID_FORMAT_D3DFMT_L8, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "IR 8-bit (L8_IR)", - .guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR, - .fcc = V4L2_PIX_FMT_GREY, - }, - { - .name = "Greyscale 10-bit (Y10 )", - .guid = UVC_GUID_FORMAT_Y10, - .fcc = V4L2_PIX_FMT_Y10, - }, - { - .name = "Greyscale 12-bit (Y12 )", - .guid = UVC_GUID_FORMAT_Y12, - .fcc = V4L2_PIX_FMT_Y12, - }, - { - .name = "Greyscale 16-bit (Y16 )", - .guid = UVC_GUID_FORMAT_Y16, - .fcc = V4L2_PIX_FMT_Y16, - }, - { - .name = "BGGR Bayer (BY8 )", - .guid = UVC_GUID_FORMAT_BY8, - .fcc = V4L2_PIX_FMT_SBGGR8, - }, - { - .name = "BGGR Bayer (BA81)", - .guid = UVC_GUID_FORMAT_BA81, - .fcc = V4L2_PIX_FMT_SBGGR8, - }, - { - .name = "GBRG Bayer (GBRG)", - .guid = UVC_GUID_FORMAT_GBRG, - .fcc = V4L2_PIX_FMT_SGBRG8, - }, - { - .name = "GRBG Bayer (GRBG)", - .guid = UVC_GUID_FORMAT_GRBG, - .fcc = V4L2_PIX_FMT_SGRBG8, - }, - { - .name = "RGGB Bayer (RGGB)", - .guid = UVC_GUID_FORMAT_RGGB, - .fcc = V4L2_PIX_FMT_SRGGB8, - }, - { - .name = "RGB565", - .guid = UVC_GUID_FORMAT_RGBP, - .fcc = V4L2_PIX_FMT_RGB565, - }, - { - .name = "BGR 8:8:8 (BGR3)", - .guid = UVC_GUID_FORMAT_BGR3, - .fcc = V4L2_PIX_FMT_BGR24, - }, - { - .name = "BGRA/X 8:8:8:8 (BGR4)", - .guid = UVC_GUID_FORMAT_BGR4, - .fcc = V4L2_PIX_FMT_XBGR32, - }, - { - .name = "H.264", - .guid = UVC_GUID_FORMAT_H264, - .fcc = V4L2_PIX_FMT_H264, - }, - { - .name = "H.265", - .guid = UVC_GUID_FORMAT_H265, - .fcc = V4L2_PIX_FMT_HEVC, - }, - { - .name = "Greyscale 8 L/R (Y8I)", - .guid = UVC_GUID_FORMAT_Y8I, - .fcc = V4L2_PIX_FMT_Y8I, - }, - { - .name = "Greyscale 12 L/R (Y12I)", - .guid = UVC_GUID_FORMAT_Y12I, - .fcc = V4L2_PIX_FMT_Y12I, - }, - { - .name = "Depth data 16-bit (Z16)", - .guid = UVC_GUID_FORMAT_Z16, - .fcc = V4L2_PIX_FMT_Z16, - }, - { - .name = "Bayer 10-bit (SRGGB10P)", - .guid = UVC_GUID_FORMAT_RW10, - .fcc = V4L2_PIX_FMT_SRGGB10P, - }, - { - .name = "Bayer 16-bit (SBGGR16)", - .guid = UVC_GUID_FORMAT_BG16, - .fcc = V4L2_PIX_FMT_SBGGR16, - }, - { - .name = "Bayer 16-bit (SGBRG16)", - .guid = UVC_GUID_FORMAT_GB16, - .fcc = V4L2_PIX_FMT_SGBRG16, - }, - { - .name = "Bayer 16-bit (SRGGB16)", - .guid = UVC_GUID_FORMAT_RG16, - .fcc = V4L2_PIX_FMT_SRGGB16, - }, - { - .name = "Bayer 16-bit (SGRBG16)", - .guid = UVC_GUID_FORMAT_GR16, - .fcc = V4L2_PIX_FMT_SGRBG16, - }, - { - .name = "Depth data 16-bit (Z16)", - .guid = UVC_GUID_FORMAT_INVZ, - .fcc = V4L2_PIX_FMT_Z16, - }, - { - .name = "Greyscale 10-bit (Y10 )", - .guid = UVC_GUID_FORMAT_INVI, - .fcc = V4L2_PIX_FMT_Y10, - }, - { - .name = "IR:Depth 26-bit (INZI)", - .guid = UVC_GUID_FORMAT_INZI, - .fcc = V4L2_PIX_FMT_INZI, - }, - { - .name = "4-bit Depth Confidence (Packed)", - .guid = UVC_GUID_FORMAT_CNF4, - .fcc = V4L2_PIX_FMT_CNF4, - }, - { - .name = "HEVC", - .guid = UVC_GUID_FORMAT_HEVC, - .fcc = V4L2_PIX_FMT_HEVC, - }, -}; - -static inline struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) -{ - unsigned int len = ARRAY_SIZE(uvc_fmts); - unsigned int i; - - for (i = 0; i < len; ++i) { - if (memcmp(guid, uvc_fmts[i].guid, 16) == 0) - return &uvc_fmts[i]; - } - - return NULL; -} +struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]); #endif /* __LINUX_V4L2_UVC_H */ -- cgit v1.2.3 From 8ecb17a86c0fbb86ea9fb4fa26e742600e945794 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 27 Jan 2023 00:14:55 +0100 Subject: usb: uvc: make uvc_format_desc table const Since the uvc_fmts array can not be modified we declare it const and change every user of the uvc_format_by_guid function aswell. Reviewed-by: Laurent Pinchart Reviewed-by: Daniel Scally Tested-by: Daniel Scally Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20230126231456.3402323-5-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/media/common/uvc.c | 4 ++-- drivers/media/usb/uvc/uvc_driver.c | 2 +- drivers/usb/gadget/function/uvc_v4l2.c | 8 ++++---- include/linux/usb/uvc.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/media/common/uvc.c b/drivers/media/common/uvc.c index bb96d9e26d80..2b4df3e8f48a 100644 --- a/drivers/media/common/uvc.c +++ b/drivers/media/common/uvc.c @@ -11,7 +11,7 @@ * Video formats */ -static struct uvc_format_desc uvc_fmts[] = { +static const struct uvc_format_desc uvc_fmts[] = { { .name = "YUV 4:2:2 (YUYV)", .guid = UVC_GUID_FORMAT_YUY2, @@ -204,7 +204,7 @@ static struct uvc_format_desc uvc_fmts[] = { }, }; -struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) +const struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]) { unsigned int len = ARRAY_SIZE(uvc_fmts); unsigned int i; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index edf8c4a201a8..7aefa76a42b3 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -225,7 +225,7 @@ static int uvc_parse_format(struct uvc_device *dev, { struct usb_interface *intf = streaming->intf; struct usb_host_interface *alts = intf->cur_altsetting; - struct uvc_format_desc *fmtdesc; + const struct uvc_format_desc *fmtdesc; struct uvc_frame *frame; const unsigned char *start = buffer; unsigned int width_multiplier = 1; diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 7435df0cf2a8..21e573e628f4 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -27,10 +27,10 @@ #include "uvc_v4l2.h" #include "uvc_configfs.h" -static struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) +static const struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat) { char guid[16] = UVC_GUID_FORMAT_MJPEG; - struct uvc_format_desc *format; + const struct uvc_format_desc *format; struct uvcg_uncompressed *unc; if (uformat->type == UVCG_UNCOMPRESSED) { @@ -119,7 +119,7 @@ static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc, struct uvcg_format *uformat = NULL; list_for_each_entry(format, &uvc->header->formats, entry) { - struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); + const struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt); if (fmtdesc->fcc == pixelformat) { uformat = format->fmt; @@ -364,7 +364,7 @@ uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct video_device *vdev = video_devdata(file); struct uvc_device *uvc = video_get_drvdata(vdev); - struct uvc_format_desc *fmtdesc; + const struct uvc_format_desc *fmtdesc; struct uvcg_format *uformat; if (f->index >= uvc->header->num_fmt) diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index 8cebb46602a1..b0210c5c5406 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -154,6 +154,6 @@ struct uvc_format_desc { u32 fcc; }; -struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]); +const struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16]); #endif /* __LINUX_V4L2_UVC_H */ -- cgit v1.2.3 From 2d83eb5d24e1c8dba386928fcbf76d3b581a632d Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Fri, 27 Jan 2023 00:14:56 +0100 Subject: usb: uvc: use v4l2_fill_fmtdesc instead of open coded format name Since v4l2_fill_fmtdesc will be called in the ioctl v4l_enum_fmt anyway. We can set the format description and compressed flag from v4l_fill_fmtdesc and can remove the extra name field in uvc_format_desc. Reviewed-by: Daniel Scally Tested-by: Daniel Scally Signed-off-by: Michael Grzeschik Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20230126231456.3402323-6-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/media/common/uvc.c | 38 ---------------------------------- drivers/usb/gadget/function/uvc_v4l2.c | 6 ------ include/linux/usb/uvc.h | 1 - 3 files changed, 45 deletions(-) diff --git a/drivers/media/common/uvc.c b/drivers/media/common/uvc.c index 2b4df3e8f48a..9c0ba7a6c185 100644 --- a/drivers/media/common/uvc.c +++ b/drivers/media/common/uvc.c @@ -13,192 +13,154 @@ static const struct uvc_format_desc uvc_fmts[] = { { - .name = "YUV 4:2:2 (YUYV)", .guid = UVC_GUID_FORMAT_YUY2, .fcc = V4L2_PIX_FMT_YUYV, }, { - .name = "YUV 4:2:2 (YUYV)", .guid = UVC_GUID_FORMAT_YUY2_ISIGHT, .fcc = V4L2_PIX_FMT_YUYV, }, { - .name = "YUV 4:2:0 (NV12)", .guid = UVC_GUID_FORMAT_NV12, .fcc = V4L2_PIX_FMT_NV12, }, { - .name = "MJPEG", .guid = UVC_GUID_FORMAT_MJPEG, .fcc = V4L2_PIX_FMT_MJPEG, }, { - .name = "YVU 4:2:0 (YV12)", .guid = UVC_GUID_FORMAT_YV12, .fcc = V4L2_PIX_FMT_YVU420, }, { - .name = "YUV 4:2:0 (I420)", .guid = UVC_GUID_FORMAT_I420, .fcc = V4L2_PIX_FMT_YUV420, }, { - .name = "YUV 4:2:0 (M420)", .guid = UVC_GUID_FORMAT_M420, .fcc = V4L2_PIX_FMT_M420, }, { - .name = "YUV 4:2:2 (UYVY)", .guid = UVC_GUID_FORMAT_UYVY, .fcc = V4L2_PIX_FMT_UYVY, }, { - .name = "Greyscale 8-bit (Y800)", .guid = UVC_GUID_FORMAT_Y800, .fcc = V4L2_PIX_FMT_GREY, }, { - .name = "Greyscale 8-bit (Y8 )", .guid = UVC_GUID_FORMAT_Y8, .fcc = V4L2_PIX_FMT_GREY, }, { - .name = "Greyscale 8-bit (D3DFMT_L8)", .guid = UVC_GUID_FORMAT_D3DFMT_L8, .fcc = V4L2_PIX_FMT_GREY, }, { - .name = "IR 8-bit (L8_IR)", .guid = UVC_GUID_FORMAT_KSMEDIA_L8_IR, .fcc = V4L2_PIX_FMT_GREY, }, { - .name = "Greyscale 10-bit (Y10 )", .guid = UVC_GUID_FORMAT_Y10, .fcc = V4L2_PIX_FMT_Y10, }, { - .name = "Greyscale 12-bit (Y12 )", .guid = UVC_GUID_FORMAT_Y12, .fcc = V4L2_PIX_FMT_Y12, }, { - .name = "Greyscale 16-bit (Y16 )", .guid = UVC_GUID_FORMAT_Y16, .fcc = V4L2_PIX_FMT_Y16, }, { - .name = "BGGR Bayer (BY8 )", .guid = UVC_GUID_FORMAT_BY8, .fcc = V4L2_PIX_FMT_SBGGR8, }, { - .name = "BGGR Bayer (BA81)", .guid = UVC_GUID_FORMAT_BA81, .fcc = V4L2_PIX_FMT_SBGGR8, }, { - .name = "GBRG Bayer (GBRG)", .guid = UVC_GUID_FORMAT_GBRG, .fcc = V4L2_PIX_FMT_SGBRG8, }, { - .name = "GRBG Bayer (GRBG)", .guid = UVC_GUID_FORMAT_GRBG, .fcc = V4L2_PIX_FMT_SGRBG8, }, { - .name = "RGGB Bayer (RGGB)", .guid = UVC_GUID_FORMAT_RGGB, .fcc = V4L2_PIX_FMT_SRGGB8, }, { - .name = "RGB565", .guid = UVC_GUID_FORMAT_RGBP, .fcc = V4L2_PIX_FMT_RGB565, }, { - .name = "BGR 8:8:8 (BGR3)", .guid = UVC_GUID_FORMAT_BGR3, .fcc = V4L2_PIX_FMT_BGR24, }, { - .name = "BGRA/X 8:8:8:8 (BGR4)", .guid = UVC_GUID_FORMAT_BGR4, .fcc = V4L2_PIX_FMT_XBGR32, }, { - .name = "H.264", .guid = UVC_GUID_FORMAT_H264, .fcc = V4L2_PIX_FMT_H264, }, { - .name = "H.265", .guid = UVC_GUID_FORMAT_H265, .fcc = V4L2_PIX_FMT_HEVC, }, { - .name = "Greyscale 8 L/R (Y8I)", .guid = UVC_GUID_FORMAT_Y8I, .fcc = V4L2_PIX_FMT_Y8I, }, { - .name = "Greyscale 12 L/R (Y12I)", .guid = UVC_GUID_FORMAT_Y12I, .fcc = V4L2_PIX_FMT_Y12I, }, { - .name = "Depth data 16-bit (Z16)", .guid = UVC_GUID_FORMAT_Z16, .fcc = V4L2_PIX_FMT_Z16, }, { - .name = "Bayer 10-bit (SRGGB10P)", .guid = UVC_GUID_FORMAT_RW10, .fcc = V4L2_PIX_FMT_SRGGB10P, }, { - .name = "Bayer 16-bit (SBGGR16)", .guid = UVC_GUID_FORMAT_BG16, .fcc = V4L2_PIX_FMT_SBGGR16, }, { - .name = "Bayer 16-bit (SGBRG16)", .guid = UVC_GUID_FORMAT_GB16, .fcc = V4L2_PIX_FMT_SGBRG16, }, { - .name = "Bayer 16-bit (SRGGB16)", .guid = UVC_GUID_FORMAT_RG16, .fcc = V4L2_PIX_FMT_SRGGB16, }, { - .name = "Bayer 16-bit (SGRBG16)", .guid = UVC_GUID_FORMAT_GR16, .fcc = V4L2_PIX_FMT_SGRBG16, }, { - .name = "Depth data 16-bit (Z16)", .guid = UVC_GUID_FORMAT_INVZ, .fcc = V4L2_PIX_FMT_Z16, }, { - .name = "Greyscale 10-bit (Y10 )", .guid = UVC_GUID_FORMAT_INVI, .fcc = V4L2_PIX_FMT_Y10, }, { - .name = "IR:Depth 26-bit (INZI)", .guid = UVC_GUID_FORMAT_INZI, .fcc = V4L2_PIX_FMT_INZI, }, { - .name = "4-bit Depth Confidence (Packed)", .guid = UVC_GUID_FORMAT_CNF4, .fcc = V4L2_PIX_FMT_CNF4, }, { - .name = "HEVC", .guid = UVC_GUID_FORMAT_HEVC, .fcc = V4L2_PIX_FMT_HEVC, }, diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 21e573e628f4..3f0a9795c0d4 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -374,15 +374,9 @@ uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) if (!uformat) return -EINVAL; - if (uformat->type != UVCG_UNCOMPRESSED) - f->flags |= V4L2_FMT_FLAG_COMPRESSED; - fmtdesc = to_uvc_format(uformat); f->pixelformat = fmtdesc->fcc; - strscpy(f->description, fmtdesc->name, sizeof(f->description)); - f->description[strlen(fmtdesc->name) - 1] = 0; - return 0; } diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index b0210c5c5406..88d96095bcb1 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -149,7 +149,6 @@ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} struct uvc_format_desc { - char *name; u8 guid[16]; u32 fcc; }; -- cgit v1.2.3 From e4157519ad46c7bd81b1c1e76d634aa0033d00e5 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 26 Jan 2023 22:40:02 -0800 Subject: Documentation: usb: correct spelling Correct spelling problems for Documentation/usb/ as reported by codespell. Signed-off-by: Randy Dunlap Cc: linux-usb@vger.kernel.org Cc: Jonathan Corbet Cc: linux-doc@vger.kernel.org Link: https://lore.kernel.org/r/20230127064005.1558-33-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/chipidea.rst | 19 ++++++++++--------- Documentation/usb/gadget-testing.rst | 2 +- Documentation/usb/mass-storage.rst | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Documentation/usb/chipidea.rst b/Documentation/usb/chipidea.rst index 68473abe2823..d9920c24eca0 100644 --- a/Documentation/usb/chipidea.rst +++ b/Documentation/usb/chipidea.rst @@ -35,10 +35,10 @@ which can show otg fsm variables and some controller registers value:: 1) Power up 2 Freescale i.MX6Q sabre SD boards with gadget class driver loaded (e.g. g_mass_storage). -2) Connect 2 boards with usb cable with one end is micro A plug, the other end +2) Connect 2 boards with usb cable: one end is micro A plug, the other end is micro B plug. - The A-device(with micro A plug inserted) should enumerate B-device. + The A-device (with micro A plug inserted) should enumerate B-device. 3) Role switch @@ -54,18 +54,19 @@ which can show otg fsm variables and some controller registers value:: echo 0 > /sys/bus/platform/devices/ci_hdrc.0/inputs/b_bus_req - or, by introducing HNP polling, B-Host can know when A-peripheral wish - to be host role, so this role switch also can be trigged in A-peripheral - side by answering the polling from B-Host, this can be done on A-device:: + or, by introducing HNP polling, B-Host can know when A-peripheral wishes to + be in the host role, so this role switch also can be triggered in + A-peripheral side by answering the polling from B-Host. This can be done on + A-device:: echo 1 > /sys/bus/platform/devices/ci_hdrc.0/inputs/a_bus_req A-device should switch back to host and enumerate B-device. -5) Remove B-device(unplug micro B plug) and insert again in 10 seconds, +5) Remove B-device (unplug micro B plug) and insert again in 10 seconds; A-device should enumerate B-device again. -6) Remove B-device(unplug micro B plug) and insert again after 10 seconds, +6) Remove B-device (unplug micro B plug) and insert again after 10 seconds; A-device should NOT enumerate B-device. if A-device wants to use bus: @@ -105,7 +106,7 @@ July 27, 2012 Revision 2.0 version 1.1a" 2. How to enable USB as system wakeup source -------------------------------------------- Below is the example for how to enable USB as system wakeup source -at imx6 platform. +on an imx6 platform. 2.1 Enable core's wakeup:: @@ -128,6 +129,6 @@ at imx6 platform. echo enabled > /sys/bus/usb/devices/1-1/power/wakeup If the system has only one usb port, and you want usb wakeup at this port, you -can use below script to enable usb wakeup:: +can use the below script to enable usb wakeup:: for i in $(find /sys -name wakeup | grep usb);do echo enabled > $i;done; diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 2278c9ffb74a..2fca40443dc9 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -813,7 +813,7 @@ the user must provide the following: ================== ==================================================== Each frame description contains frame interval specification, and each -such specification consists of a number of lines with an inverval value +such specification consists of a number of lines with an interval value in each line. The rules stated above are best illustrated with an example:: # mkdir functions/uvc.usb0/control/header/h diff --git a/Documentation/usb/mass-storage.rst b/Documentation/usb/mass-storage.rst index f399ec631599..80a601a60931 100644 --- a/Documentation/usb/mass-storage.rst +++ b/Documentation/usb/mass-storage.rst @@ -150,7 +150,7 @@ Module parameters - bcdDevice -- USB Device version (BCD) (16 bit integer) - iManufacturer -- USB Manufacturer string (string) - iProduct -- USB Product string (string) - - iSerialNumber -- SerialNumber string (sting) + - iSerialNumber -- SerialNumber string (string) sysfs entries ============= -- cgit v1.2.3 From 2bf40502badf9bc15a487244dd23ce0b08c306c0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 25 Jan 2023 16:34:25 +0200 Subject: usb: gadget: Use correct APIs and data types for UUID handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have two types for UUIDs depending on the byte ordering. Instead of explaining how bytes should go over the wire, use dedicated APIs and data types. This removes a confusion over the byte ordering. Signed-off-by: Andy Shevchenko Tested-By: Jó Ágila Bitsch Link: https://lore.kernel.org/r/20230125143425.85268-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 4 ++-- include/linux/usb/webusb.h | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 8e2603688016..fa7dd6cf014d 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -829,7 +829,7 @@ static int bos_desc(struct usb_composite_dev *cdev) if (cdev->use_webusb) { struct usb_plat_dev_cap_descriptor *webusb_cap; struct usb_webusb_cap_data *webusb_cap_data; - uuid_t webusb_uuid = WEBUSB_UUID; + guid_t webusb_uuid = WEBUSB_UUID; webusb_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); webusb_cap_data = (struct usb_webusb_cap_data *) webusb_cap->CapabilityData; @@ -841,7 +841,7 @@ static int bos_desc(struct usb_composite_dev *cdev) webusb_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; webusb_cap->bDevCapabilityType = USB_PLAT_DEV_CAP_TYPE; webusb_cap->bReserved = 0; - export_uuid(webusb_cap->UUID, &webusb_uuid); + export_guid(webusb_cap->UUID, &webusb_uuid); if (cdev->bcd_webusb_version != 0) webusb_cap_data->bcdVersion = cpu_to_le16(cdev->bcd_webusb_version); diff --git a/include/linux/usb/webusb.h b/include/linux/usb/webusb.h index b430d84357f3..fe43020b4a48 100644 --- a/include/linux/usb/webusb.h +++ b/include/linux/usb/webusb.h @@ -11,15 +11,12 @@ #include "uapi/linux/usb/ch9.h" /* - * little endian PlatformCapablityUUID for WebUSB + * Little Endian PlatformCapablityUUID for WebUSB * 3408b638-09a9-47a0-8bfd-a0768815b665 - * to identify Platform Device Capability descriptors as referring to WebUSB - * - * the UUID above MUST be sent over the wire as the byte sequence: - * {0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}. + * to identify Platform Device Capability descriptors as referring to WebUSB. */ #define WEBUSB_UUID \ - UUID_INIT(0x38b60834, 0xa909, 0xa047, 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65) + GUID_INIT(0x3408b638, 0x09a9, 0x47a0, 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65) /* * WebUSB Platform Capability data -- cgit v1.2.3 From 582cef438551ca6373f8c3901a15947b2c9643b9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 27 Jan 2023 13:26:38 +0200 Subject: usg: gadget: Move validation out of lock in webusb_bcdVersion_store() Validation has nothing to do with any protected data, move it out of the lock and make code neater. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230127112638.84806-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 63dc15b4f6d8..1346b330b358 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -836,19 +836,15 @@ static ssize_t webusb_bcdVersion_store(struct config_item *item, if (ret) return ret; - mutex_lock(&gi->lock); - ret = is_valid_bcd(bcdVersion); if (ret) - goto out; + return ret; + mutex_lock(&gi->lock); gi->bcd_webusb_version = bcdVersion; - ret = len; - -out: mutex_unlock(&gi->lock); - return ret; + return len; } static ssize_t webusb_bVendorCode_show(struct config_item *item, char *page) -- cgit v1.2.3 From 7194e5e0907b802ca76c9297399ab540fbf0513d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 27 Jan 2023 13:11:22 +0100 Subject: dt-bindings: usb: qcom,dwc3: allow required-opps Few Qualcomm SoCs require minimum performance level of power domain, so allow it: sm8550-mtp.dtb: usb@a6f8800: 'required-opps' does not match any of the regexes: '^usb@[0-9a-f]+$', 'pinctrl-[0-9]+' Signed-off-by: Krzysztof Kozlowski Reviewed-by: Abel Vesa Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230127121122.342191-1-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index a3f8a3f49852..4875c5b7d5b5 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -58,6 +58,9 @@ properties: description: specifies a phandle to PM domain provider node maxItems: 1 + required-opps: + maxItems: 1 + clocks: description: | Several clocks are used, depending on the variant. Typical ones are:: -- cgit v1.2.3 From ff826648e1059606f8418f12b69a4b15a1eed1ba Mon Sep 17 00:00:00 2001 From: Mark Tomlinson Date: Fri, 27 Jan 2023 15:47:34 +1300 Subject: USB: MAX3421: Handle USB NAK correctly A USB peripheral can respond with a NAK if it is not yet ready to send/receive data. In this case, the transaction should be retried. The MAX3421 driver did do this, and switched to a different type of retry after a number of 'fast' retries. On at least some USB flash devices, this second type of retry never succeeds. This patch changes the behaviour so that 'fast' retries continue. Signed-off-by: Mark Tomlinson Link: https://lore.kernel.org/r/20230127024734.8777-1-mark.tomlinson@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/max3421-hcd.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 352e3ac2b377..9a87056fc738 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -72,12 +72,6 @@ #define USB_MAX_FRAME_NUMBER 0x7ff #define USB_MAX_RETRIES 3 /* # of retries before error is reported */ -/* - * Max. # of times we're willing to retransmit a request immediately in - * resposne to a NAK. Afterwards, we fall back on trying once a frame. - */ -#define NAK_MAX_FAST_RETRANSMITS 2 - #define POWER_BUDGET 500 /* in mA; use 8 for low-power port testing */ /* Port-change mask: */ @@ -924,11 +918,8 @@ max3421_handle_error(struct usb_hcd *hcd, u8 hrsl) * Device wasn't ready for data or has no data * available: retry the packet again. */ - if (max3421_ep->naks++ < NAK_MAX_FAST_RETRANSMITS) { - max3421_next_transfer(hcd, 1); - switch_sndfifo = 0; - } else - max3421_slow_retransmit(hcd); + max3421_next_transfer(hcd, 1); + switch_sndfifo = 0; break; } if (switch_sndfifo) -- cgit v1.2.3 From 8cb9c36b812591e36405708d0ee693b1c135fcbd Mon Sep 17 00:00:00 2001 From: Anand Moon Date: Mon, 30 Jan 2023 08:47:43 +0000 Subject: dt-bindings: usb: vialab,vl817: Cleanup compatible, reset-gpios and required Cleanup by removing unneeded quotes from refs and add maxItems to reset-gpios and fix the required list. Fixes: 31360c28dfdd ("dt-bindings: usb: Add binding for Via lab VL817 hub controller") Signed-off-by: Anand Moon Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230130084744.2539-5-linux.amoon@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/vialab,vl817.yaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/vialab,vl817.yaml b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml index 5f9771e22058..23a13e1d5c7a 100644 --- a/Documentation/devicetree/bindings/usb/vialab,vl817.yaml +++ b/Documentation/devicetree/bindings/usb/vialab,vl817.yaml @@ -14,29 +14,32 @@ allOf: properties: compatible: - items: - - enum: - - usb2109,2817 - - usb2109,817 + enum: + - usb2109,2817 + - usb2109,817 reg: true reset-gpios: - description: GPIO controlling the RESET# pin. + maxItems: 1 + description: + GPIO controlling the RESET# pin. vdd-supply: description: phandle to the regulator that provides power to the hub. peer-hub: - $ref: '/schemas/types.yaml#/definitions/phandle' + $ref: /schemas/types.yaml#/definitions/phandle description: phandle to the peer hub on the controller. required: - - peer-hub - compatible - reg + - reset-gpios + - vdd-supply + - peer-hub additionalProperties: false @@ -45,7 +48,6 @@ examples: #include usb { - dr_mode = "host"; #address-cells = <1>; #size-cells = <0>; -- cgit v1.2.3 From 21ef9c91f0ab4f11d31fddbdc6d886fed114be74 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Mon, 30 Jan 2023 20:06:33 +0800 Subject: usb: fotg210: fix return value check in fotg210_probe() devm_platform_get_and_ioremap_resource() never returns NULL pointer, it will return ERR_PTR() when it fails, so replace the check with IS_ERR(). Fixes: baef5330d35b ("usb: fotg210: Acquire memory resource in core") Signed-off-by: Yang Yingliang Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20230130120633.3342285-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/fotg210/fotg210-core.c b/drivers/usb/fotg210/fotg210-core.c index 202d80adca2c..cb75464ab290 100644 --- a/drivers/usb/fotg210/fotg210-core.c +++ b/drivers/usb/fotg210/fotg210-core.c @@ -136,8 +136,8 @@ static int fotg210_probe(struct platform_device *pdev) fotg->dev = dev; fotg->base = devm_platform_get_and_ioremap_resource(pdev, 0, &fotg->res); - if (!fotg->base) - return -ENOMEM; + if (IS_ERR(fotg->base)) + return PTR_ERR(fotg->base); fotg->pclk = devm_clk_get_optional_enabled(dev, "PCLK"); if (IS_ERR(fotg->pclk)) -- cgit v1.2.3 From d4f6b987f3986ea67bf8ac5fbf5923bd681c8761 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 27 Jan 2023 22:17:48 +0100 Subject: dt-bindings: usb: samsung,exynos-dwc3: allow unit address in DTS The Samsung Exynos SoC USB 3.0 DWC3 Controller is a simple wrapper of actual DWC3 Controller device node. It handles necessary Samsung Exynos-specific resources (regulators, clocks), but does not have its own MMIO address space. However neither simple-bus bindings nor dtc W=1 accept device nodes in soc@ node which do not have unit address. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230127211748.260718-1-krzysztof.kozlowski@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml index 6b9a3bcb3926..42ceaf13cd5d 100644 --- a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml @@ -108,19 +108,19 @@ examples: #include #include - usb { + usb@12000000 { compatible = "samsung,exynos5250-dwusb3"; #address-cells = <1>; #size-cells = <1>; - ranges; + ranges = <0x0 0x12000000 0x10000>; clocks = <&clock CLK_USBD300>; clock-names = "usbdrd30"; vdd33-supply = <&ldo9_reg>; vdd10-supply = <&ldo11_reg>; - usb@12000000 { + usb@0 { compatible = "snps,dwc3"; - reg = <0x12000000 0x10000>; + reg = <0x0 0x10000>; interrupts = ; phys = <&usbdrd_phy0 0>, <&usbdrd_phy0 1>; phy-names = "usb2-phy", "usb3-phy"; -- cgit v1.2.3 From c2c304dfc983060085c92153071d0e0f4cb12a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Neusch=C3=A4fer?= Date: Sun, 29 Jan 2023 13:42:58 +0100 Subject: dt-bindings: usb: phy: nop: Fix a typo ("specifiy") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spell it correctly as "specify". Signed-off-by: Jonathan Neuschäfer Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230129124258.1295503-1-j.neuschaefer@gmx.net Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-nop-xceiv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.yaml b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.yaml index 326131dcf14d..921b986adc47 100644 --- a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.yaml +++ b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.yaml @@ -35,7 +35,7 @@ properties: maxItems: 1 vbus-regulator: - description: Should specifiy the regulator supplying current drawn from + description: Should specify the regulator supplying current drawn from the VBus line. $ref: /schemas/types.yaml#/definitions/phandle -- cgit v1.2.3 From e225947035bcd15f95e6f340e708fa37f309c2c3 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Mon, 30 Jan 2023 12:31:50 +0100 Subject: dt-bindings: usb: fsa4480: Use generic node name Node names should be generic. Change fsa4480@ to typec-mux@. Fixes: 01afa882f12d ("dt-bindings: usb: Add binding for fcs,fsa4480") Signed-off-by: Konrad Dybcio Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230130113151.2130063-1-konrad.dybcio@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml b/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml index 9473f26b0621..51120fe90322 100644 --- a/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml +++ b/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml @@ -51,7 +51,7 @@ examples: #address-cells = <1>; #size-cells = <0>; - fsa4480@42 { + typec-mux@42 { compatible = "fcs,fsa4480"; reg = <0x42>; -- cgit v1.2.3 From 3a1bd0494352bd89ec50ee595ababfe180f2d63e Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 30 Jan 2023 10:41:51 +0100 Subject: usb: chipidea: ci_hdrc_imx: use dev_err_probe Add error message if finding USB PHY fails or is deferred. Signed-off-by: Alexander Stein Link: https://lore.kernel.org/r/20230130094151.95174-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 0dc482542d85..2eeccf4ec9d6 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -413,15 +413,19 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0); if (IS_ERR(data->phy)) { ret = PTR_ERR(data->phy); - if (ret != -ENODEV) + if (ret != -ENODEV) { + dev_err_probe(dev, ret, "Failed to parse fsl,usbphy\n"); goto err_clk; + } data->phy = devm_usb_get_phy_by_phandle(dev, "phys", 0); if (IS_ERR(data->phy)) { ret = PTR_ERR(data->phy); - if (ret == -ENODEV) + if (ret == -ENODEV) { data->phy = NULL; - else + } else { + dev_err_probe(dev, ret, "Failed to parse phys\n"); goto err_clk; + } } } -- cgit v1.2.3 From 903261c68b947cd0c70a32dd671839e9034c0fdb Mon Sep 17 00:00:00 2001 From: Fabien Parent Date: Wed, 25 Jan 2023 15:34:59 +0100 Subject: dt-bindings: usb: mediatek,mtu3: add MT8365 SoC bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add binding documentation for the MT8365 SoC. Signed-off-by: Fabien Parent Acked-by: Krzysztof Kozlowski Signed-off-by: Bernhard Rosenkränzer Reviewed-by: Matthias Brugger Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Chunfeng Yun Link: https://lore.kernel.org/r/20230125143503.1015424-6-bero@baylibre.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml index 7168110e2f9d..d2655173e108 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtu3.yaml @@ -28,6 +28,7 @@ properties: - mediatek,mt8188-mtu3 - mediatek,mt8192-mtu3 - mediatek,mt8195-mtu3 + - mediatek,mt8365-mtu3 - const: mediatek,mtu3 reg: -- cgit v1.2.3 From 33bb1a9459989ef501bda9b127b38173fb2224c9 Mon Sep 17 00:00:00 2001 From: Fabien Parent Date: Wed, 25 Jan 2023 15:35:00 +0100 Subject: dt-bindings: usb: mediatek,mtk-xhci: add MT8365 SoC bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add binding documentation for the MT8365 SoC. Signed-off-by: Fabien Parent [bero@baylibre.com: Cleanups suggested by reviewers] Signed-off-by: Bernhard Rosenkränzer Acked-by: Krzysztof Kozlowski Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Matthias Brugger Reviewed-by: Chunfeng Yun Link: https://lore.kernel.org/r/20230125143503.1015424-7-bero@baylibre.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml index a3c37944c630..c119caa9ad16 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -35,6 +35,7 @@ properties: - mediatek,mt8188-xhci - mediatek,mt8192-xhci - mediatek,mt8195-xhci + - mediatek,mt8365-xhci - const: mediatek,mtk-xhci reg: -- cgit v1.2.3 From a4a97ab3db5c081eb6e7dba91306adefb461e0bd Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 29 Jan 2023 19:23:08 +0100 Subject: usb: early: xhci-dbc: Fix a potential out-of-bound memory access If xdbc_bulk_write() fails, the values in 'buf' can be anything. So the string is not guaranteed to be NULL terminated when xdbc_trace() is called. Reserve an extra byte, which will be zeroed automatically because 'buf' is a static variable, in order to avoid troubles, should it happen. Fixes: aeb9dd1de98c ("usb/early: Add driver for xhci debug capability") Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/d6a7562c5e839a195cee85db6dc81817f9372cb1.1675016180.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/early/xhci-dbc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index 797047154820..f3e23be227d4 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -874,7 +874,8 @@ retry: static void early_xdbc_write(struct console *con, const char *str, u32 n) { - static char buf[XDBC_MAX_PACKET]; + /* static variables are zeroed, so buf is always NULL terminated */ + static char buf[XDBC_MAX_PACKET + 1]; int chunk, ret; int use_cr = 0; -- cgit v1.2.3 From e662c16f822fe93ae11769cffb8bf0d867417633 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 29 Jan 2023 19:23:09 +0100 Subject: usb: early: xhci-dbc: Optimize early_xdbc_write() There is no point in zeroing 'buf'. It would be cleared only once, and if the 'while' loop is executed several times, all but the first run would have a 'dirty' buffer. Moreover, the size of the chunk is computed in the loop and this size is passed to xdbc_bulk_write(). So remove this useless memset(). Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/687bbcd940c59fbddd0e3a8b578fd3422962e50f.1675016180.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/early/xhci-dbc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index f3e23be227d4..965a24e47c0f 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -881,7 +881,7 @@ static void early_xdbc_write(struct console *con, const char *str, u32 n) if (!xdbc.xdbc_reg) return; - memset(buf, 0, XDBC_MAX_PACKET); + while (n > 0) { for (chunk = 0; chunk < XDBC_MAX_PACKET && n > 0; str++, chunk++, n--) { -- cgit v1.2.3 From 49814e2c9c5776c7dc7cfd151aba15bd91804c3c Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 29 Jan 2023 19:23:10 +0100 Subject: usb: early: xhci-dbc: Use memcpy_and_pad() Instead of zeroing some memory and then copying data in part or all of it, use memcpy_and_pad(). This avoids writing some memory twice and should save a few cycles. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/b447a7e9778d3f9e6997eb9494f1687dc2d5d3bf.1675016180.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/early/xhci-dbc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index 965a24e47c0f..341408410ed9 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -499,8 +499,7 @@ static int xdbc_bulk_transfer(void *data, int size, bool read) addr = xdbc.in_dma; xdbc.flags |= XDBC_FLAGS_IN_PROCESS; } else { - memset(xdbc.out_buf, 0, XDBC_MAX_PACKET); - memcpy(xdbc.out_buf, data, size); + memcpy_and_pad(xdbc.out_buf, XDBC_MAX_PACKET, data, size, 0); addr = xdbc.out_dma; xdbc.flags |= XDBC_FLAGS_OUT_PROCESS; } -- cgit v1.2.3 From 45bf39f8df7f05efb83b302c65ae3b9bc92b7065 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 31 Jan 2023 15:49:04 -0500 Subject: USB: core: Don't hold device lock while reading the "descriptors" sysfs file Ever since commit 83e83ecb79a8 ("usb: core: get config and string descriptors for unauthorized devices") was merged in 2013, there has been no mechanism for reallocating the rawdescriptors buffers in struct usb_device after the initial enumeration. Before that commit, the buffers would be deallocated when a device was deauthorized and reallocated when it was authorized and enumerated. This means that the locking in the read_descriptors() routine is not needed, since the buffers it reads will never be reallocated while the routine is running. This locking can interfere with user programs trying to read a hub's descriptors via sysfs while new child devices of the hub are being initialized, since the hub is locked during this procedure. Since the locking in read_descriptors() hasn't been needed for over nine years, we can remove it. Reported-and-tested-by: Troels Liebe Bentsen Signed-off-by: Alan Stern CC: stable@vger.kernel.org Link: https://lore.kernel.org/r/Y9l+wDTRbuZABzsE@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 5 ++--- drivers/usb/core/sysfs.c | 5 ----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9eca403af2a8..97a0f8faea6e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2389,9 +2389,8 @@ static int usb_enumerate_device_otg(struct usb_device *udev) * usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal) * @udev: newly addressed device (in ADDRESS state) * - * This is only called by usb_new_device() and usb_authorize_device() - * and FIXME -- all comments that apply to them apply here wrt to - * environment. + * This is only called by usb_new_device() -- all comments that apply there + * apply here wrt to environment. * * If the device is WUSB and not authorized, we don't attempt to read * the string descriptors, as they will be errored out by the device diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 8217032dfb85..b63f78e48c74 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -869,11 +869,7 @@ read_descriptors(struct file *filp, struct kobject *kobj, size_t srclen, n; int cfgno; void *src; - int retval; - retval = usb_lock_device_interruptible(udev); - if (retval < 0) - return -EINTR; /* The binary attribute begins with the device descriptor. * Following that are the raw descriptor entries for all the * configurations (config plus subsidiary descriptors). @@ -898,7 +894,6 @@ read_descriptors(struct file *filp, struct kobject *kobj, off -= srclen; } } - usb_unlock_device(udev); return count - nleft; } -- cgit v1.2.3 From ba883de971d1ad018f3083d9195b8abe54d87407 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 24 Jan 2023 18:20:46 +0300 Subject: usb: musb: mediatek: don't unregister something that wasn't registered This function only calls mtk_otg_switch_init() when the ->port_mode is MUSB_OTG so the clean up code should only call mtk_otg_switch_exit() for that mode. Fixes: 0990366bab3c ("usb: musb: Add support for MediaTek musb controller") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Y8/3TqpqiSr0RxFH@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/mediatek.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/musb/mediatek.c b/drivers/usb/musb/mediatek.c index cad991380b0c..27b9bd258340 100644 --- a/drivers/usb/musb/mediatek.c +++ b/drivers/usb/musb/mediatek.c @@ -294,7 +294,8 @@ static int mtk_musb_init(struct musb *musb) err_phy_power_on: phy_exit(glue->phy); err_phy_init: - mtk_otg_switch_exit(glue); + if (musb->port_mode == MUSB_OTG) + mtk_otg_switch_exit(glue); return ret; } -- cgit v1.2.3 From ec5499d338ece9db9b7590649d3cfcc4d7f9603d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 31 Jan 2023 16:04:31 +0100 Subject: xhci: split out rcar/rz support from xhci-plat.c The USB_XHCI_RZV2M and USB_RENESAS_USB3 select other drivers based on the enabled SoC types, which leads to build failures when the dependencies are not met: WARNING: unmet direct dependencies detected for USB_RZV2M_USB3DRD Depends on [n]: USB_SUPPORT [=y] && USB_GADGET [=n] && (ARCH_R9A09G011 [=n] || COMPILE_TEST [=y]) Selected by [m]: - USB_XHCI_RZV2M [=m] && USB_SUPPORT [=y] && USB [=y] && USB_XHCI_HCD [=m] && USB_XHCI_PLATFORM [=m] && (ARCH_R9A09G011 [=n] || COMPILE_TEST [=y]) ERROR: modpost: "rzv2m_usb3drd_reset" [drivers/usb/host/xhci-plat-hcd.ko] undefined! The xhci-rcar driver has a reverse dependency with the xhci core, and it depends on the UDC driver in turn. To untangle this, make the xhci-rcar.ko driver a standalone module that just calls into the xhci-plat.ko module like other drivers do. This allows handling the dependency on the USB_RZV2M_USB3DRD driver to only affect the xhci-rcar module and simplify the xhci-plat module. It also allows leaving out the hacks for broken dma mask and nested devices from the rcar side and keep that only in the generic xhci driver. As a future cleanup, the marvell and dwc3 specific bits of xhci-plat.c could be moved out as well, but that is not required for this bugfix. Fixes: c52c9acc415e ("xhci: host: Add Renesas RZ/V2M SoC support") Signed-off-by: Arnd Bergmann Tested-by: Biju Das Link: https://lore.kernel.org/r/20230131150531.12347-1-arnd@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 4 +- drivers/usb/host/Kconfig | 9 ++- drivers/usb/host/Makefile | 11 ++-- drivers/usb/host/xhci-plat.c | 127 ++++++++++++++++------------------------- drivers/usb/host/xhci-plat.h | 7 +++ drivers/usb/host/xhci-rcar.c | 102 +++++++++++++++++++++++++++++++-- drivers/usb/host/xhci-rcar.h | 55 ------------------ 7 files changed, 164 insertions(+), 151 deletions(-) delete mode 100644 drivers/usb/host/xhci-rcar.h diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 1e32d1d7dfb7..22a9f5082116 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -183,8 +183,6 @@ config USB_RENESAS_USBHS_UDC config USB_RZV2M_USB3DRD tristate 'Renesas USB3.1 DRD controller' depends on ARCH_R9A09G011 || COMPILE_TEST - default USB_XHCI_RZV2M - default USB_RENESAS_USB3 help Renesas USB3.1 DRD controller is a USB DRD controller that supports both host and device switching. @@ -195,8 +193,8 @@ config USB_RZV2M_USB3DRD config USB_RENESAS_USB3 tristate 'Renesas USB3.0 Peripheral controller' depends on ARCH_RENESAS || COMPILE_TEST + depends on USB_RZV2M_USB3DRD || !USB_RZV2M_USB3DRD depends on EXTCON - select USB_RZV2M_USB3DRD if ARCH_R9A09G011 select USB_ROLE_SWITCH help Renesas USB3.0 Peripheral controller is a USB peripheral controller diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 662a8bd9a3af..0a54190bb097 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -53,8 +53,6 @@ config USB_XHCI_PCI_RENESAS config USB_XHCI_PLATFORM tristate "Generic xHCI driver for a platform device" - select USB_XHCI_RCAR if ARCH_RENESAS - select USB_XHCI_RZV2M if ARCH_R9A09G011 help Adds an xHCI host driver for a generic platform device, which provides a memory space and an irq. @@ -92,15 +90,16 @@ config USB_XHCI_RCAR tristate "xHCI support for Renesas R-Car SoCs" depends on USB_XHCI_PLATFORM depends on ARCH_RENESAS || COMPILE_TEST + default ARCH_RENESAS help Say 'Y' to enable the support for the xHCI host controller found in Renesas R-Car ARM SoCs. config USB_XHCI_RZV2M - tristate "xHCI support for Renesas RZ/V2M SoC" - depends on USB_XHCI_PLATFORM + bool "xHCI support for Renesas RZ/V2M SoC" + depends on USB_XHCI_RCAR depends on ARCH_R9A09G011 || COMPILE_TEST - select USB_RZV2M_USB3DRD + depends on USB_RZV2M_USB3DRD=y || (USB_RZV2M_USB3DRD=USB_XHCI_RCAR) help Say 'Y' to enable the support for the xHCI host controller found in Renesas RZ/V2M SoC. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 6b1f9317f116..5a13712f367d 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -25,17 +25,13 @@ xhci-plat-hcd-y := xhci-plat.o ifneq ($(CONFIG_USB_XHCI_MVEBU), ) xhci-plat-hcd-y += xhci-mvebu.o endif -ifneq ($(CONFIG_USB_XHCI_RCAR), ) - xhci-plat-hcd-y += xhci-rcar.o -endif -ifneq ($(CONFIG_USB_XHCI_RZV2M), ) - xhci-plat-hcd-y += xhci-rzv2m.o -endif - ifneq ($(CONFIG_DEBUG_FS),) xhci-hcd-y += xhci-debugfs.o endif +xhci-rcar-hcd-y += xhci-rcar.o +xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o + obj-$(CONFIG_USB_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o @@ -75,6 +71,7 @@ obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o +obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar-hcd.o obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk-hcd.o obj-$(CONFIG_USB_XHCI_TEGRA) += xhci-tegra.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 57269f1f318e..cd17ccab6e00 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -24,8 +24,6 @@ #include "xhci.h" #include "xhci-plat.h" #include "xhci-mvebu.h" -#include "xhci-rcar.h" -#include "xhci-rzv2m.h" static struct hc_driver __read_mostly xhci_plat_hc_driver; @@ -116,21 +114,6 @@ static const struct xhci_plat_priv xhci_plat_marvell_armada3700 = { .init_quirk = xhci_mvebu_a3700_init_quirk, }; -static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { - SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1) -}; - -static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { - SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3) -}; - -static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { - .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | - XHCI_SLOW_SUSPEND, - .init_quirk = xhci_rzv2m_init_quirk, - .plat_start = xhci_rzv2m_start, -}; - static const struct xhci_plat_priv xhci_plat_brcm = { .quirks = XHCI_RESET_ON_RESUME | XHCI_SUSPEND_RESUME_CLKS, }; @@ -149,30 +132,6 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "marvell,armada3700-xhci", .data = &xhci_plat_marvell_armada3700, - }, { - .compatible = "renesas,xhci-r8a7790", - .data = &xhci_plat_renesas_rcar_gen2, - }, { - .compatible = "renesas,xhci-r8a7791", - .data = &xhci_plat_renesas_rcar_gen2, - }, { - .compatible = "renesas,xhci-r8a7793", - .data = &xhci_plat_renesas_rcar_gen2, - }, { - .compatible = "renesas,xhci-r8a7795", - .data = &xhci_plat_renesas_rcar_gen3, - }, { - .compatible = "renesas,xhci-r8a7796", - .data = &xhci_plat_renesas_rcar_gen3, - }, { - .compatible = "renesas,rcar-gen2-xhci", - .data = &xhci_plat_renesas_rcar_gen2, - }, { - .compatible = "renesas,rcar-gen3-xhci", - .data = &xhci_plat_renesas_rcar_gen3, - }, { - .compatible = "renesas,rzv2m-xhci", - .data = &xhci_plat_renesas_rzv2m, }, { .compatible = "brcm,xhci-brcm-v2", .data = &xhci_plat_brcm, @@ -185,11 +144,10 @@ static const struct of_device_id usb_xhci_of_match[] = { MODULE_DEVICE_TABLE(of, usb_xhci_of_match); #endif -static int xhci_plat_probe(struct platform_device *pdev) +int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const struct xhci_plat_priv *priv_match) { - const struct xhci_plat_priv *priv_match; const struct hc_driver *driver; - struct device *sysdev, *tmpdev; + struct device *tmpdev; struct xhci_hcd *xhci; struct resource *res; struct usb_hcd *hcd, *usb3_hcd; @@ -207,31 +165,10 @@ static int xhci_plat_probe(struct platform_device *pdev) if (irq < 0) return irq; - /* - * sysdev must point to a device that is known to the system firmware - * or PCI hardware. We handle these three cases here: - * 1. xhci_plat comes from firmware - * 2. xhci_plat is child of a device from firmware (dwc3-plat) - * 3. xhci_plat is grandchild of a pci device (dwc3-pci) - */ - for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) { - if (is_of_node(sysdev->fwnode) || - is_acpi_device_node(sysdev->fwnode)) - break; -#ifdef CONFIG_PCI - else if (sysdev->bus == &pci_bus_type) - break; -#endif - } - if (!sysdev) sysdev = &pdev->dev; - if (WARN_ON(!sysdev->dma_mask)) - /* Platform did not initialize dma_mask */ - ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); - else - ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); if (ret) return ret; @@ -293,11 +230,6 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto disable_reg_clk; - if (pdev->dev.of_node) - priv_match = of_device_get_match_data(&pdev->dev); - else - priv_match = dev_get_platdata(&pdev->dev); - if (priv_match) { priv = hcd_to_xhci_priv(hcd); /* Just copy data for now */ @@ -411,8 +343,47 @@ disable_runtime: return ret; } +EXPORT_SYMBOL_GPL(xhci_plat_probe); + +static int xhci_generic_plat_probe(struct platform_device *pdev) +{ + const struct xhci_plat_priv *priv_match; + struct device *sysdev; + int ret; + + /* + * sysdev must point to a device that is known to the system firmware + * or PCI hardware. We handle these three cases here: + * 1. xhci_plat comes from firmware + * 2. xhci_plat is child of a device from firmware (dwc3-plat) + * 3. xhci_plat is grandchild of a pci device (dwc3-pci) + */ + for (sysdev = &pdev->dev; sysdev; sysdev = sysdev->parent) { + if (is_of_node(sysdev->fwnode) || + is_acpi_device_node(sysdev->fwnode)) + break; +#ifdef CONFIG_PCI + else if (sysdev->bus == &pci_bus_type) + break; +#endif + } + + if (WARN_ON(!sysdev->dma_mask)) { + /* Platform did not initialize dma_mask */ + ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); + if (ret) + return ret; + } + + if (pdev->dev.of_node) + priv_match = of_device_get_match_data(&pdev->dev); + else + priv_match = dev_get_platdata(&pdev->dev); + + return xhci_plat_probe(pdev, sysdev, priv_match); +} -static int xhci_plat_remove(struct platform_device *dev) +int xhci_plat_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -446,6 +417,7 @@ static int xhci_plat_remove(struct platform_device *dev) return 0; } +EXPORT_SYMBOL_GPL(xhci_plat_remove); static int __maybe_unused xhci_plat_suspend(struct device *dev) { @@ -522,13 +494,14 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev) return xhci_resume(xhci, 0); } -static const struct dev_pm_ops xhci_plat_pm_ops = { +const struct dev_pm_ops xhci_plat_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume) SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, xhci_plat_runtime_resume, NULL) }; +EXPORT_SYMBOL_GPL(xhci_plat_pm_ops); #ifdef CONFIG_ACPI static const struct acpi_device_id usb_xhci_acpi_match[] = { @@ -539,8 +512,8 @@ static const struct acpi_device_id usb_xhci_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, usb_xhci_acpi_match); #endif -static struct platform_driver usb_xhci_driver = { - .probe = xhci_plat_probe, +static struct platform_driver usb_generic_xhci_driver = { + .probe = xhci_generic_plat_probe, .remove = xhci_plat_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { @@ -555,13 +528,13 @@ MODULE_ALIAS("platform:xhci-hcd"); static int __init xhci_plat_init(void) { xhci_init_driver(&xhci_plat_hc_driver, &xhci_plat_overrides); - return platform_driver_register(&usb_xhci_driver); + return platform_driver_register(&usb_generic_xhci_driver); } module_init(xhci_plat_init); static void __exit xhci_plat_exit(void) { - platform_driver_unregister(&usb_xhci_driver); + platform_driver_unregister(&usb_generic_xhci_driver); } module_exit(xhci_plat_exit); diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 1fb149d1fbce..83b5b5aa9f8e 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -21,4 +21,11 @@ struct xhci_plat_priv { #define hcd_to_xhci_priv(h) ((struct xhci_plat_priv *)hcd_to_xhci(h)->priv) #define xhci_to_priv(x) ((struct xhci_plat_priv *)(x)->priv) + +int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, + const struct xhci_plat_priv *priv_match); + +int xhci_plat_remove(struct platform_device *dev); +extern const struct dev_pm_ops xhci_plat_pm_ops; + #endif /* _XHCI_PLAT_H */ diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index aef0258a7160..7f18509a1d39 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -10,12 +10,17 @@ #include #include #include +#include #include #include #include "xhci.h" #include "xhci-plat.h" -#include "xhci-rcar.h" +#include "xhci-rzv2m.h" + +#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" +#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem" +#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem" /* * - The V3 firmware is for almost all R-Car Gen3 (except r8a7795 ES1.x) @@ -108,7 +113,7 @@ static int xhci_rcar_is_gen2(struct device *dev) of_device_is_compatible(node, "renesas,rcar-gen2-xhci"); } -void xhci_rcar_start(struct usb_hcd *hcd) +static void xhci_rcar_start(struct usb_hcd *hcd) { u32 temp; @@ -203,7 +208,7 @@ static bool xhci_rcar_wait_for_pll_active(struct usb_hcd *hcd) } /* This function needs to initialize a "phy" of usb before */ -int xhci_rcar_init_quirk(struct usb_hcd *hcd) +static int xhci_rcar_init_quirk(struct usb_hcd *hcd) { /* If hcd->regs is NULL, we don't just call the following function */ if (!hcd->regs) @@ -215,7 +220,7 @@ int xhci_rcar_init_quirk(struct usb_hcd *hcd) return xhci_rcar_download_firmware(hcd); } -int xhci_rcar_resume_quirk(struct usb_hcd *hcd) +static int xhci_rcar_resume_quirk(struct usb_hcd *hcd) { int ret; @@ -225,3 +230,92 @@ int xhci_rcar_resume_quirk(struct usb_hcd *hcd) return ret; } + +/* + * On R-Car Gen2 and Gen3, the AC64 bit (bit 0) of HCCPARAMS1 is set + * to 1. However, these SoCs don't support 64-bit address memory + * pointers. So, this driver clears the AC64 bit of xhci->hcc_params + * to call dma_set_coherent_mask(dev, DMA_BIT_MASK(32)) in + * xhci_gen_setup() by using the XHCI_NO_64BIT_SUPPORT quirk. + * + * And, since the firmware/internal CPU control the USBSTS.STS_HALT + * and the process speed is down when the roothub port enters U3, + * long delay for the handshake of STS_HALT is neeed in xhci_suspend() + * by using the XHCI_SLOW_SUSPEND quirk. + */ +#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \ + .firmware_name = firmware, \ + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | \ + XHCI_SLOW_SUSPEND, \ + .init_quirk = xhci_rcar_init_quirk, \ + .plat_start = xhci_rcar_start, \ + .resume_quirk = xhci_rcar_resume_quirk, + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen2 = { + SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V1) +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { + SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3) +}; + +static const struct xhci_plat_priv xhci_plat_renesas_rzv2m = { + .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | + XHCI_SLOW_SUSPEND, + .init_quirk = xhci_rzv2m_init_quirk, + .plat_start = xhci_rzv2m_start, +}; + +static const struct of_device_id usb_xhci_of_match[] = { + { + .compatible = "renesas,xhci-r8a7790", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7791", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7793", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,xhci-r8a7795", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,xhci-r8a7796", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,rcar-gen2-xhci", + .data = &xhci_plat_renesas_rcar_gen2, + }, { + .compatible = "renesas,rcar-gen3-xhci", + .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "renesas,rzv2m-xhci", + .data = &xhci_plat_renesas_rzv2m, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, usb_xhci_of_match); + +static int xhci_renesas_probe(struct platform_device *pdev) +{ + const struct xhci_plat_priv *priv_match; + + priv_match = of_device_get_match_data(&pdev->dev); + + return xhci_plat_probe(pdev, NULL, priv_match); +} + +static struct platform_driver usb_xhci_renesas_driver = { + .probe = xhci_renesas_probe, + .remove = xhci_plat_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "xhci-renesas-hcd", + .pm = &xhci_plat_pm_ops, + .of_match_table = of_match_ptr(usb_xhci_of_match), + }, +}; +module_platform_driver(usb_xhci_renesas_driver); + +MODULE_DESCRIPTION("xHCI Platform Host Controller Driver for Renesas R-Car and RZ"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h deleted file mode 100644 index 048ad3b8a6c7..000000000000 --- a/drivers/usb/host/xhci-rcar.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * drivers/usb/host/xhci-rcar.h - * - * Copyright (C) 2014 Renesas Electronics Corporation - */ - -#ifndef _XHCI_RCAR_H -#define _XHCI_RCAR_H - -#define XHCI_RCAR_FIRMWARE_NAME_V1 "r8a779x_usb3_v1.dlmem" -#define XHCI_RCAR_FIRMWARE_NAME_V2 "r8a779x_usb3_v2.dlmem" -#define XHCI_RCAR_FIRMWARE_NAME_V3 "r8a779x_usb3_v3.dlmem" - -#if IS_ENABLED(CONFIG_USB_XHCI_RCAR) -void xhci_rcar_start(struct usb_hcd *hcd); -int xhci_rcar_init_quirk(struct usb_hcd *hcd); -int xhci_rcar_resume_quirk(struct usb_hcd *hcd); -#else -static inline void xhci_rcar_start(struct usb_hcd *hcd) -{ -} - -static inline int xhci_rcar_init_quirk(struct usb_hcd *hcd) -{ - return 0; -} - -static inline int xhci_rcar_resume_quirk(struct usb_hcd *hcd) -{ - return 0; -} -#endif - -/* - * On R-Car Gen2 and Gen3, the AC64 bit (bit 0) of HCCPARAMS1 is set - * to 1. However, these SoCs don't support 64-bit address memory - * pointers. So, this driver clears the AC64 bit of xhci->hcc_params - * to call dma_set_coherent_mask(dev, DMA_BIT_MASK(32)) in - * xhci_gen_setup() by using the XHCI_NO_64BIT_SUPPORT quirk. - * - * And, since the firmware/internal CPU control the USBSTS.STS_HALT - * and the process speed is down when the roothub port enters U3, - * long delay for the handshake of STS_HALT is neeed in xhci_suspend() - * by using the XHCI_SLOW_SUSPEND quirk. - */ -#define SET_XHCI_PLAT_PRIV_FOR_RCAR(firmware) \ - .firmware_name = firmware, \ - .quirks = XHCI_NO_64BIT_SUPPORT | XHCI_TRUST_TX_LENGTH | \ - XHCI_SLOW_SUSPEND, \ - .init_quirk = xhci_rcar_init_quirk, \ - .plat_start = xhci_rcar_start, \ - .resume_quirk = xhci_rcar_resume_quirk, - -#endif /* _XHCI_RCAR_H */ -- cgit v1.2.3 From 56774e274574af8396d7b16618363aabe3a5582d Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Wed, 1 Feb 2023 10:53:46 +0000 Subject: dt-bindings: usb: sunxi-musb: add F1C100s MUSB compatible string Allwinner F1C100s has a hybrid MUSB controller between the A10 one and the A33 one. Add a compatible string for it. Signed-off-by: Icenowy Zheng Acked-by: Krzysztof Kozlowski Reviewed-by: Samuel Holland Signed-off-by: Andre Przywara Link: https://lore.kernel.org/r/20230201105348.1815461-2-andre.przywara@arm.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/allwinner,sun4i-a10-musb.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/allwinner,sun4i-a10-musb.yaml b/Documentation/devicetree/bindings/usb/allwinner,sun4i-a10-musb.yaml index 8992eff6ce38..f972ce976e86 100644 --- a/Documentation/devicetree/bindings/usb/allwinner,sun4i-a10-musb.yaml +++ b/Documentation/devicetree/bindings/usb/allwinner,sun4i-a10-musb.yaml @@ -13,10 +13,12 @@ maintainers: properties: compatible: oneOf: - - const: allwinner,sun4i-a10-musb - - const: allwinner,sun6i-a31-musb - - const: allwinner,sun8i-a33-musb - - const: allwinner,sun8i-h3-musb + - enum: + - allwinner,sun4i-a10-musb + - allwinner,sun6i-a31-musb + - allwinner,sun8i-a33-musb + - allwinner,sun8i-h3-musb + - allwinner,suniv-f1c100s-musb - items: - enum: - allwinner,sun8i-a83t-musb -- cgit v1.2.3 From d4b2c2852091c9e1c1fa03553e81f5712471578c Mon Sep 17 00:00:00 2001 From: Icenowy Zheng Date: Wed, 1 Feb 2023 10:53:47 +0000 Subject: usb: musb: sunxi: add support for the F1C100s MUSB controller The suniv SoC has a MUSB controller like the one in A33, but with a SRAM region to be claimed. Add support for it. Signed-off-by: Icenowy Zheng Acked-by: Jernej Skrabec Signed-off-by: Andre Przywara Link: https://lore.kernel.org/r/20230201105348.1815461-3-andre.przywara@arm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/sunxi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/musb/sunxi.c b/drivers/usb/musb/sunxi.c index 7f9a999cd5ff..4b368d16a73a 100644 --- a/drivers/usb/musb/sunxi.c +++ b/drivers/usb/musb/sunxi.c @@ -722,14 +722,17 @@ static int sunxi_musb_probe(struct platform_device *pdev) INIT_WORK(&glue->work, sunxi_musb_work); glue->host_nb.notifier_call = sunxi_musb_host_notifier; - if (of_device_is_compatible(np, "allwinner,sun4i-a10-musb")) + if (of_device_is_compatible(np, "allwinner,sun4i-a10-musb") || + of_device_is_compatible(np, "allwinner,suniv-f1c100s-musb")) { set_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags); + } if (of_device_is_compatible(np, "allwinner,sun6i-a31-musb")) set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags); if (of_device_is_compatible(np, "allwinner,sun8i-a33-musb") || - of_device_is_compatible(np, "allwinner,sun8i-h3-musb")) { + of_device_is_compatible(np, "allwinner,sun8i-h3-musb") || + of_device_is_compatible(np, "allwinner,suniv-f1c100s-musb")) { set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags); set_bit(SUNXI_MUSB_FL_NO_CONFIGDATA, &glue->flags); } @@ -815,6 +818,7 @@ static const struct of_device_id sunxi_musb_match[] = { { .compatible = "allwinner,sun6i-a31-musb", }, { .compatible = "allwinner,sun8i-a33-musb", }, { .compatible = "allwinner,sun8i-h3-musb", }, + { .compatible = "allwinner,suniv-f1c100s-musb", }, {} }; MODULE_DEVICE_TABLE(of, sunxi_musb_match); -- cgit v1.2.3 From 196774960ba176f1f5a506dc6b5b51fd0b2e0e9b Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Wed, 1 Feb 2023 10:53:48 +0000 Subject: usb: musb: sunxi: Introduce config struct Currently the probe routine explicitly compares the compatible string of the device node to figure out which features and quirks a certain Allwinner MUSB model requires. This gets harder to maintain for new SoCs. Add a struct sunxi_musb_cfg that names the features and quirks explicitly, and create instances of this struct for every type of MUSB device we support. Then bind this to the compatible strings via the OF data feature. Signed-off-by: Andre Przywara Reviewed-by: Jernej Skrabec Link: https://lore.kernel.org/r/20230201105348.1815461-4-andre.przywara@arm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/sunxi.c | 103 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/drivers/usb/musb/sunxi.c b/drivers/usb/musb/sunxi.c index 4b368d16a73a..9b622cd9b2bd 100644 --- a/drivers/usb/musb/sunxi.c +++ b/drivers/usb/musb/sunxi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,13 @@ #define SUNXI_MUSB_FL_NO_CONFIGDATA 7 #define SUNXI_MUSB_FL_PHY_MODE_PEND 8 +struct sunxi_musb_cfg { + const struct musb_hdrc_config *hdrc_config; + bool has_sram; + bool has_reset; + bool no_configdata; +}; + /* Our read/write methods need access and do not get passed in a musb ref :| */ static struct musb *sunxi_musb; @@ -621,11 +629,10 @@ static const struct musb_platform_ops sunxi_musb_ops = { .post_root_reset_end = sunxi_musb_post_root_reset_end, }; -/* Allwinner OTG supports up to 5 endpoints */ -#define SUNXI_MUSB_MAX_EP_NUM 6 #define SUNXI_MUSB_RAM_BITS 11 -static struct musb_fifo_cfg sunxi_musb_mode_cfg[] = { +/* Allwinner OTG supports up to 5 endpoints */ +static struct musb_fifo_cfg sunxi_musb_mode_cfg_5eps[] = { MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512), MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512), MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512), @@ -639,9 +646,7 @@ static struct musb_fifo_cfg sunxi_musb_mode_cfg[] = { }; /* H3/V3s OTG supports only 4 endpoints */ -#define SUNXI_MUSB_MAX_EP_NUM_H3 5 - -static struct musb_fifo_cfg sunxi_musb_mode_cfg_h3[] = { +static struct musb_fifo_cfg sunxi_musb_mode_cfg_4eps[] = { MUSB_EP_FIFO_SINGLE(1, FIFO_TX, 512), MUSB_EP_FIFO_SINGLE(1, FIFO_RX, 512), MUSB_EP_FIFO_SINGLE(2, FIFO_TX, 512), @@ -652,31 +657,33 @@ static struct musb_fifo_cfg sunxi_musb_mode_cfg_h3[] = { MUSB_EP_FIFO_SINGLE(4, FIFO_RX, 512), }; -static const struct musb_hdrc_config sunxi_musb_hdrc_config = { - .fifo_cfg = sunxi_musb_mode_cfg, - .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg), +static const struct musb_hdrc_config sunxi_musb_hdrc_config_5eps = { + .fifo_cfg = sunxi_musb_mode_cfg_5eps, + .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_5eps), .multipoint = true, .dyn_fifo = true, - .num_eps = SUNXI_MUSB_MAX_EP_NUM, + /* Two FIFOs per endpoint, plus ep_0. */ + .num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_5eps) / 2) + 1, .ram_bits = SUNXI_MUSB_RAM_BITS, }; -static struct musb_hdrc_config sunxi_musb_hdrc_config_h3 = { - .fifo_cfg = sunxi_musb_mode_cfg_h3, - .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_h3), +static const struct musb_hdrc_config sunxi_musb_hdrc_config_4eps = { + .fifo_cfg = sunxi_musb_mode_cfg_4eps, + .fifo_cfg_size = ARRAY_SIZE(sunxi_musb_mode_cfg_4eps), .multipoint = true, .dyn_fifo = true, - .num_eps = SUNXI_MUSB_MAX_EP_NUM_H3, + /* Two FIFOs per endpoint, plus ep_0. */ + .num_eps = (ARRAY_SIZE(sunxi_musb_mode_cfg_4eps) / 2) + 1, .ram_bits = SUNXI_MUSB_RAM_BITS, }; - static int sunxi_musb_probe(struct platform_device *pdev) { struct musb_hdrc_platform_data pdata; struct platform_device_info pinfo; struct sunxi_glue *glue; struct device_node *np = pdev->dev.of_node; + const struct sunxi_musb_cfg *cfg; int ret; if (!np) { @@ -713,29 +720,25 @@ static int sunxi_musb_probe(struct platform_device *pdev) return -EINVAL; } pdata.platform_ops = &sunxi_musb_ops; - if (!of_device_is_compatible(np, "allwinner,sun8i-h3-musb")) - pdata.config = &sunxi_musb_hdrc_config; - else - pdata.config = &sunxi_musb_hdrc_config_h3; + + cfg = of_device_get_match_data(&pdev->dev); + if (!cfg) + return -EINVAL; + + pdata.config = cfg->hdrc_config; glue->dev = &pdev->dev; INIT_WORK(&glue->work, sunxi_musb_work); glue->host_nb.notifier_call = sunxi_musb_host_notifier; - if (of_device_is_compatible(np, "allwinner,sun4i-a10-musb") || - of_device_is_compatible(np, "allwinner,suniv-f1c100s-musb")) { + if (cfg->has_sram) set_bit(SUNXI_MUSB_FL_HAS_SRAM, &glue->flags); - } - if (of_device_is_compatible(np, "allwinner,sun6i-a31-musb")) + if (cfg->has_reset) set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags); - if (of_device_is_compatible(np, "allwinner,sun8i-a33-musb") || - of_device_is_compatible(np, "allwinner,sun8i-h3-musb") || - of_device_is_compatible(np, "allwinner,suniv-f1c100s-musb")) { - set_bit(SUNXI_MUSB_FL_HAS_RESET, &glue->flags); + if (cfg->no_configdata) set_bit(SUNXI_MUSB_FL_NO_CONFIGDATA, &glue->flags); - } glue->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(glue->clk)) { @@ -813,12 +816,46 @@ static int sunxi_musb_remove(struct platform_device *pdev) return 0; } +static const struct sunxi_musb_cfg sun4i_a10_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_sram = true, +}; + +static const struct sunxi_musb_cfg sun6i_a31_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_reset = true, +}; + +static const struct sunxi_musb_cfg sun8i_a33_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_reset = true, + .no_configdata = true, +}; + +static const struct sunxi_musb_cfg sun8i_h3_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_4eps, + .has_reset = true, + .no_configdata = true, +}; + +static const struct sunxi_musb_cfg suniv_f1c100s_musb_cfg = { + .hdrc_config = &sunxi_musb_hdrc_config_5eps, + .has_sram = true, + .has_reset = true, + .no_configdata = true, +}; + static const struct of_device_id sunxi_musb_match[] = { - { .compatible = "allwinner,sun4i-a10-musb", }, - { .compatible = "allwinner,sun6i-a31-musb", }, - { .compatible = "allwinner,sun8i-a33-musb", }, - { .compatible = "allwinner,sun8i-h3-musb", }, - { .compatible = "allwinner,suniv-f1c100s-musb", }, + { .compatible = "allwinner,sun4i-a10-musb", + .data = &sun4i_a10_musb_cfg, }, + { .compatible = "allwinner,sun6i-a31-musb", + .data = &sun6i_a31_musb_cfg, }, + { .compatible = "allwinner,sun8i-a33-musb", + .data = &sun8i_a33_musb_cfg, }, + { .compatible = "allwinner,sun8i-h3-musb", + .data = &sun8i_h3_musb_cfg, }, + { .compatible = "allwinner,suniv-f1c100s-musb", + .data = &suniv_f1c100s_musb_cfg, }, {} }; MODULE_DEVICE_TABLE(of, sunxi_musb_match); -- cgit v1.2.3 From 89e7252d6c7e7eeb31971cd7df987316ecc64ff5 Mon Sep 17 00:00:00 2001 From: Udipto Goswami Date: Wed, 1 Feb 2023 18:53:08 +0530 Subject: usb: gadget: configfs: Restrict symlink creation is UDC already binded During enumeration or composition switch,a userspace process agnostic of the conventions of configs can try to create function symlinks even after the UDC is bound to current config which is not correct. Potentially it can create duplicates within the current config. Prevent this by adding a check if udc_name already exists, then bail out of cfg_link. Following is an example: Step1: ln -s X1 ffs.a -->cfg_link --> usb_get_function(ffs.a) ->ffs_alloc CFG->FUNC_LIST: C->FUNCTION: Step2: echo udc.name > /config/usb_gadget/g1/UDC --> UDC_store ->composite_bind ->usb_add_function CFG->FUNC_LIST: C->FUNCTION: Step3: ln -s Y1 ffs.a -->cfg_link -->usb_get_function(ffs.a) ->ffs_alloc CFG->FUNC_LIST: C->FUNCTION: both the lists corresponds to the same function instance ffs.a but the usb_function* pointer is different because in step 3 ffs_alloc has created a new reference to usb_function* for ffs.a and added it to cfg_list. Step4: Now a composition switch involving is executed. the composition switch will involve 3 things: 1. unlinking the previous functions existing 2. creating new symlinks 3. writing UDC However, the composition switch is generally taken care by userspace process which creates the symlinks in its own nomenclature(X*) and removes only those. So it won't be able to remove Y1 which user had created by own. Due to this the new symlinks cannot be created for ffs.a since the entry already exists in CFG->FUNC_LIST. The state of the CFG->FUNC_LIST is as follows: CFG->FUNC_LIST: Fixes: 88af8bbe4ef7 ("usb: gadget: the start of the configfs interface") Signed-off-by: Krishna Kurapati PSSNV Signed-off-by: Udipto Goswami Link: https://lore.kernel.org/r/20230201132308.31523-1-quic_ugoswami@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 1346b330b358..c102adbcd4e1 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -437,6 +437,12 @@ static int config_usb_cfg_link( * from another gadget or a random directory. * Also a function instance can only be linked once. */ + + if (gi->composite.gadget_driver.udc_name) { + ret = -EINVAL; + goto out; + } + list_for_each_entry(iter, &gi->available_func, cfs_list) { if (iter != fi) continue; -- cgit v1.2.3 From fb9a1b80e68b2a16ff41b644e2a2e559461c6440 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Tue, 31 Jan 2023 17:57:43 +0000 Subject: dt-bindings: usb: Add Cypress cypd4226 Type-C controller Add the device-tree binding documentation for Cypress cypd4226 dual Type-C controller. Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230131175748.256423-2-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/cypress,cypd4226.yaml | 98 ++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/cypress,cypd4226.yaml diff --git a/Documentation/devicetree/bindings/usb/cypress,cypd4226.yaml b/Documentation/devicetree/bindings/usb/cypress,cypd4226.yaml new file mode 100644 index 000000000000..75eec4a9a020 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/cypress,cypd4226.yaml @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/cypress,cypd4226.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cypress cypd4226 Type-C Controller + +maintainers: + - Wayne Chang + +description: + The Cypress cypd4226 is a dual Type-C controller that is controlled + via an I2C interface. + +properties: + compatible: + const: cypress,cypd4226 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + reg: + const: 0x08 + + interrupts: + items: + - description: cypd4226 host interrupt + + firmware-name: + enum: + - nvidia,gpu + - nvidia,jetson-agx-xavier + description: | + The name of the CCGx firmware built for product series. + should be set one of following: + - "nvidia,gpu" for the NVIDIA RTX product series + - "nvidia,jetson-agx-xavier" for the NVIDIA Jetson product series + +patternProperties: + '^connector@[01]$': + $ref: /schemas/connector/usb-connector.yaml# + unevaluatedProperties: false + properties: + reg: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +anyOf: + - required: + - connector@0 + - required: + - connector@1 + +additionalProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + #interrupt-cells = <2>; + + typec@8 { + compatible = "cypress,cypd4226"; + reg = <0x08>; + interrupt-parent = <&gpio_aon>; + interrupts = ; + firmware-name = "nvidia,jetson-agx-xavier"; + #address-cells = <1>; + #size-cells = <0>; + connector@0 { + compatible = "usb-c-connector"; + reg = <0>; + label = "USB-C"; + data-role = "dual"; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + endpoint { + remote-endpoint = <&usb_role_switch0>; + }; + }; + }; + }; + }; + }; -- cgit v1.2.3 From f510b0a3565b9231e828e23a7e0f9790b97edf96 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Tue, 31 Jan 2023 17:57:44 +0000 Subject: i2c: nvidia-gpu: Add ACPI property to align with device-tree Device-tree uses the 'firmware-name' string property to pass a name of the firmware build to the Cypress CCGx driver. Add a new ACPI string property to the NVIDIA GPU I2C driver to align with device-tree so that we can migrate to using a common property name for both ACPI and device-tree. Signed-off-by: Wayne Chang Co-developed-by: Jon Hunter Signed-off-by: Jon Hunter Reviewed-by: Heikki Krogerus Acked-by: Ajay Gupta Acked-by: Wolfram Sang Link: https://lore.kernel.org/r/20230131175748.256423-3-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-nvidia-gpu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/i2c/busses/i2c-nvidia-gpu.c b/drivers/i2c/busses/i2c-nvidia-gpu.c index 12e330cd7635..6d81ea530a83 100644 --- a/drivers/i2c/busses/i2c-nvidia-gpu.c +++ b/drivers/i2c/busses/i2c-nvidia-gpu.c @@ -261,6 +261,7 @@ MODULE_DEVICE_TABLE(pci, gpu_i2c_ids); static const struct property_entry ccgx_props[] = { /* Use FW built for NVIDIA (nv) only */ PROPERTY_ENTRY_U16("ccgx,firmware-build", ('n' << 8) | 'v'), + PROPERTY_ENTRY_STRING("firmware-name", "nvidia,gpu"), { } }; -- cgit v1.2.3 From 6d9e0669099f59473799f529b3c19a55fa164c92 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Tue, 31 Jan 2023 17:57:45 +0000 Subject: usb: typec: ucsi_ccg: Add OF support Add device-tree support for the Cypress CCG UCSI driver. The device-tree binding for the Cypress CCG device uses the standard device-tree 'firmware-name' string property to indicate the firmware build that is used. The NVIDIA GPU I2C driver has been updated to use an ACPI string property that is also named 'firmware-build' and given that this was the only users of the 'ccgx,firmware-build' property, we can now remove support for this legacy property. Signed-off-by: Wayne Chang Co-developed-by: Jon Hunter Signed-off-by: Jon Hunter Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230131175748.256423-4-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_ccg.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 46441f1477f2..e0ed465bd518 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -643,7 +643,7 @@ static int ccg_request_irq(struct ucsi_ccg *uc) { unsigned long flags = IRQF_ONESHOT; - if (!has_acpi_companion(uc->dev)) + if (!dev_fwnode(uc->dev)) flags |= IRQF_TRIGGER_HIGH; return request_threaded_irq(uc->irq, NULL, ccg_irq_handler, flags, dev_name(uc->dev), uc); @@ -1342,6 +1342,7 @@ static int ucsi_ccg_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct ucsi_ccg *uc; + const char *fw_name; int status; uc = devm_kzalloc(dev, sizeof(*uc), GFP_KERNEL); @@ -1357,9 +1358,15 @@ static int ucsi_ccg_probe(struct i2c_client *client) INIT_WORK(&uc->pm_work, ccg_pm_workaround_work); /* Only fail FW flashing when FW build information is not provided */ - status = device_property_read_u16(dev, "ccgx,firmware-build", - &uc->fw_build); - if (status) + status = device_property_read_string(dev, "firmware-name", &fw_name); + if (!status) { + if (!strcmp(fw_name, "nvidia,jetson-agx-xavier")) + uc->fw_build = CCG_FW_BUILD_NVIDIA_TEGRA; + else if (!strcmp(fw_name, "nvidia,gpu")) + uc->fw_build = CCG_FW_BUILD_NVIDIA; + } + + if (!uc->fw_build) dev_err(uc->dev, "failed to get FW build information\n"); /* reset ccg device and initialize ucsi */ @@ -1426,6 +1433,12 @@ static void ucsi_ccg_remove(struct i2c_client *client) free_irq(uc->irq, uc); } +static const struct of_device_id ucsi_ccg_of_match_table[] = { + { .compatible = "cypress,cypd4226", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ucsi_ccg_of_match_table); + static const struct i2c_device_id ucsi_ccg_device_id[] = { {"ccgx-ucsi", 0}, {} @@ -1480,6 +1493,7 @@ static struct i2c_driver ucsi_ccg_driver = { .pm = &ucsi_ccg_pm, .dev_groups = ucsi_ccg_groups, .acpi_match_table = amd_i2c_ucsi_match, + .of_match_table = ucsi_ccg_of_match_table, }, .probe_new = ucsi_ccg_probe, .remove = ucsi_ccg_remove, -- cgit v1.2.3 From 430b38764fbb931c6dbd1af13c8b2e4508994662 Mon Sep 17 00:00:00 2001 From: Wayne Chang Date: Tue, 31 Jan 2023 17:57:46 +0000 Subject: i2c: nvidia-gpu: Remove ccgx,firmware-build property Now the Cypress CCG driver has been updated to support the 'firmware-name' property to align with device-tree, remove the 'ccgx,firmware-build' property as this is no longer needed. Signed-off-by: Wayne Chang Signed-off-by: Jon Hunter Acked-by: Ajay Gupta Acked-by: Wolfram Sang Link: https://lore.kernel.org/r/20230131175748.256423-5-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-nvidia-gpu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-nvidia-gpu.c b/drivers/i2c/busses/i2c-nvidia-gpu.c index 6d81ea530a83..a8b99e7f6262 100644 --- a/drivers/i2c/busses/i2c-nvidia-gpu.c +++ b/drivers/i2c/busses/i2c-nvidia-gpu.c @@ -259,8 +259,7 @@ static const struct pci_device_id gpu_i2c_ids[] = { MODULE_DEVICE_TABLE(pci, gpu_i2c_ids); static const struct property_entry ccgx_props[] = { - /* Use FW built for NVIDIA (nv) only */ - PROPERTY_ENTRY_U16("ccgx,firmware-build", ('n' << 8) | 'v'), + /* Use FW built for NVIDIA GPU only */ PROPERTY_ENTRY_STRING("firmware-name", "nvidia,gpu"), { } }; -- cgit v1.2.3 From 1f6d59f7f82d3b0a629326c6d043273a84bfc61f Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Tue, 31 Jan 2023 17:57:48 +0000 Subject: arm64: defconfig: Enable UCSI support Enable the TYPEC UCSI support and the Cypress UCSI driver that is used on the NVIDIA Jetson platforms. Signed-off-by: Jon Hunter Link: https://lore.kernel.org/r/20230131175748.256423-7-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/configs/defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 42c3528a2473..7eb5b6df594e 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -946,6 +946,8 @@ CONFIG_TYPEC_TCPCI=m CONFIG_TYPEC_FUSB302=m CONFIG_TYPEC_TPS6598X=m CONFIG_TYPEC_HD3SS3220=m +CONFIG_TYPEC_UCSI=m +CONFIG_UCSI_CCG=m CONFIG_MMC=y CONFIG_MMC_BLOCK_MINORS=32 CONFIG_MMC_ARMMMCI=y -- cgit v1.2.3 From 3078212cafaece5dfebc7bd57d8c395be7862a5c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 30 Jan 2023 10:50:43 +0000 Subject: usb: gadget: uvc: Rename uvc_control_ep The f_uvc code defines an endpoint named "uvc_control_ep" but it is configured with a non-zero endpoint address and has its bmAttributes flagged as USB_ENDPOINT_XFER_INT - this cannot be the VideoControl interface's control endpoint, as the default endpoint 0 is used for that purpose. This is instead the optional interrupt endpoint that can be contained by a VideoControl interface. There is also a Class-specific VC Interrupt Endpoint Descriptor and a SuperSpeed companion descriptor that are also for the VC interface's interrupt endpoint but are named as though they are for the control endpoint. Rename the variables to make that clear. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230130105045.120886-2-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 40 ++++++++++++++++++------------------- drivers/usb/gadget/function/uvc.h | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 32f2c1645467..a673001f5271 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -83,7 +83,7 @@ static struct usb_interface_descriptor uvc_control_intf = { .iInterface = 0, }; -static struct usb_endpoint_descriptor uvc_control_ep = { +static struct usb_endpoint_descriptor uvc_interrupt_ep = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN, @@ -92,8 +92,8 @@ static struct usb_endpoint_descriptor uvc_control_ep = { .bInterval = 8, }; -static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = { - .bLength = sizeof(uvc_ss_control_comp), +static struct usb_ss_ep_comp_descriptor uvc_ss_interrupt_comp = { + .bLength = sizeof(uvc_ss_interrupt_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, /* The following 3 values can be tweaked if necessary. */ .bMaxBurst = 0, @@ -101,7 +101,7 @@ static struct usb_ss_ep_comp_descriptor uvc_ss_control_comp = { .wBytesPerInterval = cpu_to_le16(UVC_STATUS_MAX_PACKET_SIZE), }; -static struct uvc_control_endpoint_descriptor uvc_control_cs_ep = { +static struct uvc_control_endpoint_descriptor uvc_interrupt_cs_ep = { .bLength = UVC_DT_CONTROL_ENDPOINT_SIZE, .bDescriptorType = USB_DT_CS_ENDPOINT, .bDescriptorSubType = UVC_EP_INTERRUPT, @@ -300,14 +300,14 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) if (alt) return -EINVAL; - uvcg_info(f, "reset UVC Control\n"); - usb_ep_disable(uvc->control_ep); + uvcg_info(f, "reset UVC interrupt endpoint\n"); + usb_ep_disable(uvc->interrupt_ep); - if (!uvc->control_ep->desc) - if (config_ep_by_speed(cdev->gadget, f, uvc->control_ep)) + if (!uvc->interrupt_ep->desc) + if (config_ep_by_speed(cdev->gadget, f, uvc->interrupt_ep)) return -EINVAL; - usb_ep_enable(uvc->control_ep); + usb_ep_enable(uvc->interrupt_ep); if (uvc->state == UVC_STATE_DISCONNECTED) { memset(&v4l2_event, 0, sizeof(v4l2_event)); @@ -385,7 +385,7 @@ uvc_function_disable(struct usb_function *f) uvc->state = UVC_STATE_DISCONNECTED; usb_ep_disable(uvc->video.ep); - usb_ep_disable(uvc->control_ep); + usb_ep_disable(uvc->interrupt_ep); } /* -------------------------------------------------------------------------- @@ -521,9 +521,9 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) * uvc_iad * uvc_control_intf * Class-specific UVC control descriptors - * uvc_control_ep - * uvc_control_cs_ep - * uvc_ss_control_comp (for SS only) + * uvc_interrupt_ep + * uvc_interrupt_cs_ep + * uvc_ss_interrupt_comp (for SS only) * uvc_streaming_intf_alt0 * Class-specific UVC streaming descriptors * uvc_{fs|hs}_streaming @@ -533,11 +533,11 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) control_size = 0; streaming_size = 0; bytes = uvc_iad.bLength + uvc_control_intf.bLength - + uvc_control_ep.bLength + uvc_control_cs_ep.bLength + + uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength + uvc_streaming_intf_alt0.bLength; if (speed == USB_SPEED_SUPER) { - bytes += uvc_ss_control_comp.bLength; + bytes += uvc_ss_interrupt_comp.bLength; n_desc = 6; } else { n_desc = 5; @@ -579,11 +579,11 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) uvc_control_header->bInCollection = 1; uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_ep); + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep); if (speed == USB_SPEED_SUPER) - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_control_comp); + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp); - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_control_cs_ep); + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep); UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0); uvc_streaming_header = mem; @@ -666,12 +666,12 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) (opts->streaming_maxburst + 1)); /* Allocate endpoints. */ - ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep); + ep = usb_ep_autoconfig(cdev->gadget, &uvc_interrupt_ep); if (!ep) { uvcg_info(f, "Unable to allocate control EP\n"); goto error; } - uvc->control_ep = ep; + uvc->interrupt_ep = ep; if (gadget_is_superspeed(c->cdev->gadget)) ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep, diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 40226b1f7e14..48b71e04c2b1 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -146,7 +146,7 @@ struct uvc_device { } desc; unsigned int control_intf; - struct usb_ep *control_ep; + struct usb_ep *interrupt_ep; struct usb_request *control_req; void *control_buf; -- cgit v1.2.3 From a36afe7804612c524396e59f9521ed06e39bf62c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 30 Jan 2023 10:50:44 +0000 Subject: usb: gadget: uvc: Add new enable_interrupt_ep attribute Add a new attribute to the default control config group that allows users to specify whether they want to enable the optional interrupt endpoint for the VideoControl interface. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230130105045.120886-3-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 4 +- drivers/usb/gadget/function/u_uvc.h | 2 + drivers/usb/gadget/function/uvc_configfs.c | 53 +++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index f00cff6d8c5c..eb13cc5d363a 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -15,11 +15,13 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Control descriptors - All attributes read only: + All attributes read only except enable_interrupt_ep: ================ ============================= bInterfaceNumber USB interface number for this streaming interface + enable_interrupt_ep flag to enable the interrupt + endpoint for the VC interface ================ ============================= What: /config/usb-gadget/gadget/functions/uvc.name/control/class diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 24b8681b0d6f..9d15bc2c7045 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -29,6 +29,8 @@ struct f_uvc_opts { unsigned int streaming_interface; char function_name[32]; + bool enable_interrupt_ep; + /* * Control descriptors array pointers for full-/high-speed and * super-speed. They point by default to the uvc_fs_control_cls and diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index e28becd435bf..9ff4b1921ee2 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -716,8 +716,61 @@ static ssize_t uvcg_default_control_b_interface_number_show( UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber); +static ssize_t uvcg_default_control_enable_interrupt_ep_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->enable_interrupt_ep); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_control_enable_interrupt_ep_store( + struct config_item *item, const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + ssize_t ret; + u8 num; + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + opts->enable_interrupt_ep = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_control_, enable_interrupt_ep, enable_interrupt_ep); + static struct configfs_attribute *uvcg_default_control_attrs[] = { &uvcg_default_control_attr_b_interface_number, + &uvcg_default_control_attr_enable_interrupt_ep, NULL, }; -- cgit v1.2.3 From 130c4dcbe8c79595a6ca2e69be9f01411201aa92 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 30 Jan 2023 10:50:45 +0000 Subject: usb: gadget: uvc: Disable interrupt endpoint by default The f_uvc code includes an interrupt endpoint against the VideoControl interface. According to section 2.4.2 of the UVC specification however this endpoint is optional in at least some cases: "This endpoint is optional, but may be mandatory under certain conditions" The conditions enumerated are whether... 1. The device supports hardware triggers 2. The device implements any AutoUpdate controls 3. The device implements any Asynchronous controls As all of those things are implementation dependent, this endpoint might be unnecessary for some users. Further to that it is unusable in the current implementation as there is no mechanism within the UVC gadget driver that allows data to be sent over that endpoint. Disable the interrupt endpoint by default, but check whether the user has asked for it to be enabled in configfs and continue to generate it if so. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230130105045.120886-4-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 60 +++++++++++++++++++++++-------------- drivers/usb/gadget/function/uvc.h | 1 + 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index a673001f5271..5250805153c7 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -76,7 +76,7 @@ static struct usb_interface_descriptor uvc_control_intf = { .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = UVC_INTF_VIDEO_CONTROL, .bAlternateSetting = 0, - .bNumEndpoints = 1, + .bNumEndpoints = 0, .bInterfaceClass = USB_CLASS_VIDEO, .bInterfaceSubClass = UVC_SC_VIDEOCONTROL, .bInterfaceProtocol = 0x00, @@ -300,14 +300,17 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) if (alt) return -EINVAL; - uvcg_info(f, "reset UVC interrupt endpoint\n"); - usb_ep_disable(uvc->interrupt_ep); + if (uvc->enable_interrupt_ep) { + uvcg_info(f, "reset UVC interrupt endpoint\n"); + usb_ep_disable(uvc->interrupt_ep); - if (!uvc->interrupt_ep->desc) - if (config_ep_by_speed(cdev->gadget, f, uvc->interrupt_ep)) - return -EINVAL; + if (!uvc->interrupt_ep->desc) + if (config_ep_by_speed(cdev->gadget, f, + uvc->interrupt_ep)) + return -EINVAL; - usb_ep_enable(uvc->interrupt_ep); + usb_ep_enable(uvc->interrupt_ep); + } if (uvc->state == UVC_STATE_DISCONNECTED) { memset(&v4l2_event, 0, sizeof(v4l2_event)); @@ -385,7 +388,8 @@ uvc_function_disable(struct usb_function *f) uvc->state = UVC_STATE_DISCONNECTED; usb_ep_disable(uvc->video.ep); - usb_ep_disable(uvc->interrupt_ep); + if (uvc->enable_interrupt_ep) + usb_ep_disable(uvc->interrupt_ep); } /* -------------------------------------------------------------------------- @@ -533,14 +537,17 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) control_size = 0; streaming_size = 0; bytes = uvc_iad.bLength + uvc_control_intf.bLength - + uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength + uvc_streaming_intf_alt0.bLength; - if (speed == USB_SPEED_SUPER) { - bytes += uvc_ss_interrupt_comp.bLength; - n_desc = 6; - } else { - n_desc = 5; + n_desc = 3; + if (uvc->enable_interrupt_ep) { + bytes += uvc_interrupt_ep.bLength + uvc_interrupt_cs_ep.bLength; + n_desc += 2; + + if (speed == USB_SPEED_SUPER) { + bytes += uvc_ss_interrupt_comp.bLength; + n_desc += 1; + } } for (src = (const struct usb_descriptor_header **)uvc_control_desc; @@ -579,11 +586,14 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) uvc_control_header->bInCollection = 1; uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep); - if (speed == USB_SPEED_SUPER) - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp); + if (uvc->enable_interrupt_ep) { + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_ep); + if (speed == USB_SPEED_SUPER) + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_ss_interrupt_comp); + + UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep); + } - UVC_COPY_DESCRIPTOR(mem, dst, &uvc_interrupt_cs_ep); UVC_COPY_DESCRIPTOR(mem, dst, &uvc_streaming_intf_alt0); uvc_streaming_header = mem; @@ -666,12 +676,16 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) (opts->streaming_maxburst + 1)); /* Allocate endpoints. */ - ep = usb_ep_autoconfig(cdev->gadget, &uvc_interrupt_ep); - if (!ep) { - uvcg_info(f, "Unable to allocate control EP\n"); - goto error; + if (opts->enable_interrupt_ep) { + ep = usb_ep_autoconfig(cdev->gadget, &uvc_interrupt_ep); + if (!ep) { + uvcg_info(f, "Unable to allocate interrupt EP\n"); + goto error; + } + uvc->interrupt_ep = ep; + uvc_control_intf.bNumEndpoints = 1; } - uvc->interrupt_ep = ep; + uvc->enable_interrupt_ep = opts->enable_interrupt_ep; if (gadget_is_superspeed(c->cdev->gadget)) ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep, diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 48b71e04c2b1..daf226610f49 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -149,6 +149,7 @@ struct uvc_device { struct usb_ep *interrupt_ep; struct usb_request *control_req; void *control_buf; + bool enable_interrupt_ep; unsigned int streaming_intf; -- cgit v1.2.3 From 8c1cbec9db1ab044167a7594c88bb5906c9d3ee4 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:04:55 +0200 Subject: xhci: fix event ring segment table related masks and variables in header xHC controller can supports up to 1024 interrupters. To fit these change the max_interrupters varable from u8 to u16. Add a separate mask for the reserve and preserve bits [5:0] in the erst base register and use it instead of the ERST_PRT_MASK. ERSR_PTR_MASK [3:0] is intended for masking bits in the event ring dequeue pointer register. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 4 ++-- drivers/usb/host/xhci.h | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 81ca2bc1f0be..679befa97c7a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2529,8 +2529,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) "// Set ERST base address for ir_set 0 = 0x%llx", (unsigned long long)xhci->erst.erst_dma_addr); val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base); - val_64 &= ERST_PTR_MASK; - val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK); + val_64 &= ERST_BASE_RSVDP; + val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_BASE_RSVDP); xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base); /* Set the event ring dequeue address */ diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 7547037b57bf..0dd92f9089fe 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -513,6 +513,9 @@ struct xhci_intr_reg { /* Preserve bits 16:31 of erst_size */ #define ERST_SIZE_MASK (0xffff << 16) +/* erst_base bitmasks */ +#define ERST_BASE_RSVDP (0x3f) + /* erst_dequeue bitmasks */ /* Dequeue ERST Segment Index (DESI) - Segment number (or alias) * where the current dequeue pointer lies. This is an optional HW hint. @@ -1774,7 +1777,7 @@ struct xhci_hcd { u8 sbrn; u16 hci_version; u8 max_slots; - u8 max_interrupters; + u16 max_interrupters; u8 max_ports; u8 isoc_threshold; /* imod_interval in ns (I * 250ns) */ -- cgit v1.2.3 From 54f9927dfe2266402a226d5f51d38236bdca0590 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:04:56 +0200 Subject: xhci: remove xhci_test_trb_in_td_math early development check Time to remove this test trb in td math check that was added in early stage of xhci driver development. It verified that the size, alignment and boundaries of the event and command rings allocated by the driver itself are correct. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 160 -------------------------------------------- 1 file changed, 160 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 679befa97c7a..bf9bb29f924b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1929,164 +1929,6 @@ no_bw: xhci->usb3_rhub.bus_state.bus_suspended = 0; } -static int xhci_test_trb_in_td(struct xhci_hcd *xhci, - struct xhci_segment *input_seg, - union xhci_trb *start_trb, - union xhci_trb *end_trb, - dma_addr_t input_dma, - struct xhci_segment *result_seg, - char *test_name, int test_number) -{ - unsigned long long start_dma; - unsigned long long end_dma; - struct xhci_segment *seg; - - start_dma = xhci_trb_virt_to_dma(input_seg, start_trb); - end_dma = xhci_trb_virt_to_dma(input_seg, end_trb); - - seg = trb_in_td(xhci, input_seg, start_trb, end_trb, input_dma, false); - if (seg != result_seg) { - xhci_warn(xhci, "WARN: %s TRB math test %d failed!\n", - test_name, test_number); - xhci_warn(xhci, "Tested TRB math w/ seg %p and " - "input DMA 0x%llx\n", - input_seg, - (unsigned long long) input_dma); - xhci_warn(xhci, "starting TRB %p (0x%llx DMA), " - "ending TRB %p (0x%llx DMA)\n", - start_trb, start_dma, - end_trb, end_dma); - xhci_warn(xhci, "Expected seg %p, got seg %p\n", - result_seg, seg); - trb_in_td(xhci, input_seg, start_trb, end_trb, input_dma, - true); - return -1; - } - return 0; -} - -/* TRB math checks for xhci_trb_in_td(), using the command and event rings. */ -static int xhci_check_trb_in_td_math(struct xhci_hcd *xhci) -{ - struct { - dma_addr_t input_dma; - struct xhci_segment *result_seg; - } simple_test_vector [] = { - /* A zeroed DMA field should fail */ - { 0, NULL }, - /* One TRB before the ring start should fail */ - { xhci->event_ring->first_seg->dma - 16, NULL }, - /* One byte before the ring start should fail */ - { xhci->event_ring->first_seg->dma - 1, NULL }, - /* Starting TRB should succeed */ - { xhci->event_ring->first_seg->dma, xhci->event_ring->first_seg }, - /* Ending TRB should succeed */ - { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16, - xhci->event_ring->first_seg }, - /* One byte after the ring end should fail */ - { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 1)*16 + 1, NULL }, - /* One TRB after the ring end should fail */ - { xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT)*16, NULL }, - /* An address of all ones should fail */ - { (dma_addr_t) (~0), NULL }, - }; - struct { - struct xhci_segment *input_seg; - union xhci_trb *start_trb; - union xhci_trb *end_trb; - dma_addr_t input_dma; - struct xhci_segment *result_seg; - } complex_test_vector [] = { - /* Test feeding a valid DMA address from a different ring */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = xhci->event_ring->first_seg->trbs, - .end_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], - .input_dma = xhci->cmd_ring->first_seg->dma, - .result_seg = NULL, - }, - /* Test feeding a valid end TRB from a different ring */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = xhci->event_ring->first_seg->trbs, - .end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], - .input_dma = xhci->cmd_ring->first_seg->dma, - .result_seg = NULL, - }, - /* Test feeding a valid start and end TRB from a different ring */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = xhci->cmd_ring->first_seg->trbs, - .end_trb = &xhci->cmd_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], - .input_dma = xhci->cmd_ring->first_seg->dma, - .result_seg = NULL, - }, - /* TRB in this ring, but after this TD */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = &xhci->event_ring->first_seg->trbs[0], - .end_trb = &xhci->event_ring->first_seg->trbs[3], - .input_dma = xhci->event_ring->first_seg->dma + 4*16, - .result_seg = NULL, - }, - /* TRB in this ring, but before this TD */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = &xhci->event_ring->first_seg->trbs[3], - .end_trb = &xhci->event_ring->first_seg->trbs[6], - .input_dma = xhci->event_ring->first_seg->dma + 2*16, - .result_seg = NULL, - }, - /* TRB in this ring, but after this wrapped TD */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], - .end_trb = &xhci->event_ring->first_seg->trbs[1], - .input_dma = xhci->event_ring->first_seg->dma + 2*16, - .result_seg = NULL, - }, - /* TRB in this ring, but before this wrapped TD */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], - .end_trb = &xhci->event_ring->first_seg->trbs[1], - .input_dma = xhci->event_ring->first_seg->dma + (TRBS_PER_SEGMENT - 4)*16, - .result_seg = NULL, - }, - /* TRB not in this ring, and we have a wrapped TD */ - { .input_seg = xhci->event_ring->first_seg, - .start_trb = &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 3], - .end_trb = &xhci->event_ring->first_seg->trbs[1], - .input_dma = xhci->cmd_ring->first_seg->dma + 2*16, - .result_seg = NULL, - }, - }; - - unsigned int num_tests; - int i, ret; - - num_tests = ARRAY_SIZE(simple_test_vector); - for (i = 0; i < num_tests; i++) { - ret = xhci_test_trb_in_td(xhci, - xhci->event_ring->first_seg, - xhci->event_ring->first_seg->trbs, - &xhci->event_ring->first_seg->trbs[TRBS_PER_SEGMENT - 1], - simple_test_vector[i].input_dma, - simple_test_vector[i].result_seg, - "Simple", i); - if (ret < 0) - return ret; - } - - num_tests = ARRAY_SIZE(complex_test_vector); - for (i = 0; i < num_tests; i++) { - ret = xhci_test_trb_in_td(xhci, - complex_test_vector[i].input_seg, - complex_test_vector[i].start_trb, - complex_test_vector[i].end_trb, - complex_test_vector[i].input_dma, - complex_test_vector[i].result_seg, - "Complex", i); - if (ret < 0) - return ret; - } - xhci_dbg(xhci, "TRB math tests passed.\n"); - return 0; -} - static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) { u64 temp; @@ -2506,8 +2348,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) 0, flags); if (!xhci->event_ring) goto fail; - if (xhci_check_trb_in_td_math(xhci) < 0) - goto fail; ret = xhci_alloc_erst(xhci, xhci->event_ring, &xhci->erst, flags); if (ret) -- cgit v1.2.3 From b17a57f89f69069458d0a9d9b04281ce48da7ebb Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:04:57 +0200 Subject: xhci: Refactor interrupter code for initial multi interrupter support. xHC supports several interrupters, each with its own mmio register set, event ring and MSI/MSI-X vector. Transfers can be assigned different interrupters when queued. See xhci 4.17 for details. Current driver only supports one interrupter. Create a xhci_interrupter structure containing an event ring, pointer to mmio registers for this interrupter, variables to store registers over s3 suspend, erst, etc. Add functions to create and free an interrupter, and pass an interrupter pointer to functions that deal with events. Secondary interrupters are also useful without having an interrupt vector. One use case is the xHCI audio sideband offloading where a DSP can take care of specific audio endpoints. When all transfer events of an offloaded endpoint can be mapped to a separate interrupter event ring the DSP can poll this ring, and we can mask these events preventing waking up the CPU. Only minor functional changes such as clearing some of the interrupter registers when freeing the interrupter. Still create only one primary interrupter. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-debugfs.c | 2 +- drivers/usb/host/xhci-mem.c | 168 ++++++++++++++++++++++++++-------------- drivers/usb/host/xhci-ring.c | 68 ++++++++-------- drivers/usb/host/xhci.c | 54 ++++++++----- drivers/usb/host/xhci.h | 24 +++--- 5 files changed, 196 insertions(+), 120 deletions(-) diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index dc832ddf7033..0bc7fe11f749 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -692,7 +692,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci) "command-ring", xhci->debugfs_root); - xhci_debugfs_create_ring_dir(xhci, &xhci->event_ring, + xhci_debugfs_create_ring_dir(xhci, &xhci->interrupter->event_ring, "event-ring", xhci->debugfs_root); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index bf9bb29f924b..6b83c5c35cf8 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1819,17 +1819,43 @@ int xhci_alloc_erst(struct xhci_hcd *xhci, return 0; } -void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst) +static void +xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { - size_t size; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + size_t erst_size; + u64 tmp64; + u32 tmp; - size = sizeof(struct xhci_erst_entry) * (erst->num_entries); - if (erst->entries) - dma_free_coherent(dev, size, - erst->entries, - erst->erst_dma_addr); - erst->entries = NULL; + if (!ir) + return; + + erst_size = sizeof(struct xhci_erst_entry) * (ir->erst.num_entries); + if (ir->erst.entries) + dma_free_coherent(dev, erst_size, + ir->erst.entries, + ir->erst.erst_dma_addr); + ir->erst.entries = NULL; + + /* + * Clean out interrupter registers except ERSTBA. Clearing either the + * low or high 32 bits of ERSTBA immediately causes the controller to + * dereference the partially cleared 64 bit address, causing IOMMU error. + */ + tmp = readl(&ir->ir_set->erst_size); + tmp &= ERST_SIZE_MASK; + writel(tmp, &ir->ir_set->erst_size); + + tmp64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + tmp64 &= (u64) ERST_PTR_MASK; + xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue); + + /* free interrrupter event ring */ + if (ir->event_ring) + xhci_ring_free(xhci, ir->event_ring); + ir->event_ring = NULL; + + kfree(ir); } void xhci_mem_cleanup(struct xhci_hcd *xhci) @@ -1839,12 +1865,9 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) cancel_delayed_work_sync(&xhci->cmd_timer); - xhci_free_erst(xhci, &xhci->erst); - - if (xhci->event_ring) - xhci_ring_free(xhci, xhci->event_ring); - xhci->event_ring = NULL; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring"); + xhci_free_interrupter(xhci, xhci->interrupter); + xhci->interrupter = NULL; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring"); if (xhci->cmd_ring) xhci_ring_free(xhci, xhci->cmd_ring); @@ -1929,18 +1952,18 @@ no_bw: xhci->usb3_rhub.bus_state.bus_suspended = 0; } -static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) +static void xhci_set_hc_event_deq(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { u64 temp; dma_addr_t deq; - deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, - xhci->event_ring->dequeue); + deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg, + ir->event_ring->dequeue); if (!deq) xhci_warn(xhci, "WARN something wrong with SW event ring " "dequeue ptr.\n"); /* Update HC event ring dequeue pointer */ - temp = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); temp &= ERST_PTR_MASK; /* Don't clear the EHB bit (which is RW1C) because * there might be more events to service. @@ -1950,7 +1973,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) "// Write event ring dequeue pointer, " "preserving EHB bit"); xhci_write_64(xhci, ((u64) deq & (u64) ~ERST_PTR_MASK) | temp, - &xhci->ir_set->erst_dequeue); + &ir->ir_set->erst_dequeue); } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, @@ -2217,6 +2240,68 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) return 0; } +static struct xhci_interrupter * +xhci_alloc_interrupter(struct xhci_hcd *xhci, unsigned int intr_num, gfp_t flags) +{ + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; + struct xhci_interrupter *ir; + u64 erst_base; + u32 erst_size; + int ret; + + if (intr_num > xhci->max_interrupters) { + xhci_warn(xhci, "Can't allocate interrupter %d, max interrupters %d\n", + intr_num, xhci->max_interrupters); + return NULL; + } + + if (xhci->interrupter) { + xhci_warn(xhci, "Can't allocate already set up interrupter %d\n", intr_num); + return NULL; + } + + ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev)); + if (!ir) + return NULL; + + ir->ir_set = &xhci->run_regs->ir_set[intr_num]; + ir->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, + 0, flags); + if (!ir->event_ring) { + xhci_warn(xhci, "Failed to allocate interrupter %d event ring\n", intr_num); + goto fail_ir; + } + + ret = xhci_alloc_erst(xhci, ir->event_ring, &ir->erst, flags); + if (ret) { + xhci_warn(xhci, "Failed to allocate interrupter %d erst\n", intr_num); + goto fail_ev; + + } + /* set ERST count with the number of entries in the segment table */ + erst_size = readl(&ir->ir_set->erst_size); + erst_size &= ERST_SIZE_MASK; + erst_size |= ERST_NUM_SEGS; + writel(erst_size, &ir->ir_set->erst_size); + + erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); + erst_base &= ERST_PTR_MASK; + erst_base |= (ir->erst.erst_dma_addr & (u64) ~ERST_PTR_MASK); + xhci_write_64(xhci, erst_base, &ir->ir_set->erst_base); + + /* Set the event ring dequeue address of this interrupter */ + xhci_set_hc_event_deq(xhci, ir); + + return ir; + +fail_ev: + xhci_ring_free(xhci, ir->event_ring); +fail_ir: + kfree(ir); + + return NULL; +} + int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { dma_addr_t dma; @@ -2224,7 +2309,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) unsigned int val, val2; u64 val_64; u32 page_size, temp; - int i, ret; + int i; INIT_LIST_HEAD(&xhci->cmd_list); @@ -2337,46 +2422,13 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) " from cap regs base addr", val); xhci->dba = (void __iomem *) xhci->cap_regs + val; /* Set ir_set to interrupt register set 0 */ - xhci->ir_set = &xhci->run_regs->ir_set[0]; - - /* - * Event ring setup: Allocate a normal ring, but also setup - * the event ring segment table (ERST). Section 4.9.3. - */ - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Allocating event ring"); - xhci->event_ring = xhci_ring_alloc(xhci, ERST_NUM_SEGS, 1, TYPE_EVENT, - 0, flags); - if (!xhci->event_ring) - goto fail; - - ret = xhci_alloc_erst(xhci, xhci->event_ring, &xhci->erst, flags); - if (ret) - goto fail; - - /* set ERST count with the number of entries in the segment table */ - val = readl(&xhci->ir_set->erst_size); - val &= ERST_SIZE_MASK; - val |= ERST_NUM_SEGS; - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Write ERST size = %i to ir_set 0 (some bits preserved)", - val); - writel(val, &xhci->ir_set->erst_size); + /* allocate and set up primary interrupter with an event ring. */ xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Set ERST entries to point to event ring."); - /* set the segment table base address */ - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Set ERST base address for ir_set 0 = 0x%llx", - (unsigned long long)xhci->erst.erst_dma_addr); - val_64 = xhci_read_64(xhci, &xhci->ir_set->erst_base); - val_64 &= ERST_BASE_RSVDP; - val_64 |= (xhci->erst.erst_dma_addr & (u64) ~ERST_BASE_RSVDP); - xhci_write_64(xhci, val_64, &xhci->ir_set->erst_base); - - /* Set the event ring dequeue address */ - xhci_set_hc_event_deq(xhci); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Wrote ERST address to ir_set 0."); + "Allocating primary event ring"); + xhci->interrupter = xhci_alloc_interrupter(xhci, 0, flags); + if (!xhci->interrupter) + goto fail; xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 5f1ecdee2c1c..451d48b87cf7 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1833,7 +1833,8 @@ static void xhci_cavium_reset_phy_quirk(struct xhci_hcd *xhci) } static void handle_port_status(struct xhci_hcd *xhci, - union xhci_trb *event) + struct xhci_interrupter *ir, + union xhci_trb *event) { struct usb_hcd *hcd; u32 port_id; @@ -1856,7 +1857,7 @@ static void handle_port_status(struct xhci_hcd *xhci, if ((port_id <= 0) || (port_id > max_ports)) { xhci_warn(xhci, "Port change event with invalid port ID %d\n", port_id); - inc_deq(xhci, xhci->event_ring); + inc_deq(xhci, ir->event_ring); return; } @@ -1986,7 +1987,7 @@ static void handle_port_status(struct xhci_hcd *xhci, cleanup: /* Update event ring dequeue pointer before dropping the lock */ - inc_deq(xhci, xhci->event_ring); + inc_deq(xhci, ir->event_ring); /* Don't make the USB core poll the roothub if we got a bad port status * change event. Besides, at that point we can't tell which roothub @@ -2519,7 +2520,8 @@ finish_td: * At this point, the host controller is probably hosed and should be reset. */ static int handle_tx_event(struct xhci_hcd *xhci, - struct xhci_transfer_event *event) + struct xhci_interrupter *ir, + struct xhci_transfer_event *event) { struct xhci_virt_ep *ep; struct xhci_ring *ep_ring; @@ -2868,7 +2870,7 @@ cleanup: * processing missed tds. */ if (!handling_skipped_tds) - inc_deq(xhci, xhci->event_ring); + inc_deq(xhci, ir->event_ring); /* * If ep->skip is set, it means there are missed tds on the @@ -2883,8 +2885,8 @@ cleanup: err_out: xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", (unsigned long long) xhci_trb_virt_to_dma( - xhci->event_ring->deq_seg, - xhci->event_ring->dequeue), + ir->event_ring->deq_seg, + ir->event_ring->dequeue), lower_32_bits(le64_to_cpu(event->buffer)), upper_32_bits(le64_to_cpu(event->buffer)), le32_to_cpu(event->transfer_len), @@ -2898,7 +2900,7 @@ err_out: * Returns >0 for "possibly more events to process" (caller should call again), * otherwise 0 if done. In future, <0 returns should indicate error code. */ -static int xhci_handle_event(struct xhci_hcd *xhci) +static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { union xhci_trb *event; int update_ptrs = 1; @@ -2906,18 +2908,18 @@ static int xhci_handle_event(struct xhci_hcd *xhci) int ret; /* Event ring hasn't been allocated yet. */ - if (!xhci->event_ring || !xhci->event_ring->dequeue) { - xhci_err(xhci, "ERROR event ring not ready\n"); + if (!ir || !ir->event_ring || !ir->event_ring->dequeue) { + xhci_err(xhci, "ERROR interrupter not ready\n"); return -ENOMEM; } - event = xhci->event_ring->dequeue; + event = ir->event_ring->dequeue; /* Does the HC or OS own the TRB? */ if ((le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE) != - xhci->event_ring->cycle_state) + ir->event_ring->cycle_state) return 0; - trace_xhci_handle_event(xhci->event_ring, &event->generic); + trace_xhci_handle_event(ir->event_ring, &event->generic); /* * Barrier between reading the TRB_CYCLE (valid) flag above and any @@ -2932,11 +2934,11 @@ static int xhci_handle_event(struct xhci_hcd *xhci) handle_cmd_completion(xhci, &event->event_cmd); break; case TRB_PORT_STATUS: - handle_port_status(xhci, event); + handle_port_status(xhci, ir, event); update_ptrs = 0; break; case TRB_TRANSFER: - ret = handle_tx_event(xhci, &event->trans_event); + ret = handle_tx_event(xhci, ir, &event->trans_event); if (ret >= 0) update_ptrs = 0; break; @@ -2960,7 +2962,7 @@ static int xhci_handle_event(struct xhci_hcd *xhci) if (update_ptrs) /* Update SW event ring dequeue pointer */ - inc_deq(xhci, xhci->event_ring); + inc_deq(xhci, ir->event_ring); /* Are there more items on the event ring? Caller will call us again to * check. @@ -2974,16 +2976,17 @@ static int xhci_handle_event(struct xhci_hcd *xhci) * - To avoid "Event Ring Full Error" condition */ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, - union xhci_trb *event_ring_deq) + struct xhci_interrupter *ir, + union xhci_trb *event_ring_deq) { u64 temp_64; dma_addr_t deq; - temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); /* If necessary, update the HW's version of the event ring deq ptr. */ - if (event_ring_deq != xhci->event_ring->dequeue) { - deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, - xhci->event_ring->dequeue); + if (event_ring_deq != ir->event_ring->dequeue) { + deq = xhci_trb_virt_to_dma(ir->event_ring->deq_seg, + ir->event_ring->dequeue); if (deq == 0) xhci_warn(xhci, "WARN something wrong with SW event ring dequeue ptr\n"); /* @@ -3001,7 +3004,7 @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, /* Clear the event handler busy flag (RW1C) */ temp_64 |= ERST_EHB; - xhci_write_64(xhci, temp_64, &xhci->ir_set->erst_dequeue); + xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue); } /* @@ -3013,6 +3016,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); union xhci_trb *event_ring_deq; + struct xhci_interrupter *ir; irqreturn_t ret = IRQ_NONE; u64 temp_64; u32 status; @@ -3050,11 +3054,13 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) status |= STS_EINT; writel(status, &xhci->op_regs->status); + /* This is the handler of the primary interrupter */ + ir = xhci->interrupter; if (!hcd->msi_enabled) { u32 irq_pending; - irq_pending = readl(&xhci->ir_set->irq_pending); + irq_pending = readl(&ir->ir_set->irq_pending); irq_pending |= IMAN_IP; - writel(irq_pending, &xhci->ir_set->irq_pending); + writel(irq_pending, &ir->ir_set->irq_pending); } if (xhci->xhc_state & XHCI_STATE_DYING || @@ -3064,22 +3070,22 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) /* Clear the event handler busy flag (RW1C); * the event ring should be empty. */ - temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); xhci_write_64(xhci, temp_64 | ERST_EHB, - &xhci->ir_set->erst_dequeue); + &ir->ir_set->erst_dequeue); ret = IRQ_HANDLED; goto out; } - event_ring_deq = xhci->event_ring->dequeue; + event_ring_deq = ir->event_ring->dequeue; /* FIXME this should be a delayed service routine * that clears the EHB. */ - while (xhci_handle_event(xhci) > 0) { + while (xhci_handle_event(xhci, ir) > 0) { if (event_loop++ < TRBS_PER_SEGMENT / 2) continue; - xhci_update_erst_dequeue(xhci, event_ring_deq); - event_ring_deq = xhci->event_ring->dequeue; + xhci_update_erst_dequeue(xhci, ir, event_ring_deq); + event_ring_deq = ir->event_ring->dequeue; /* ring is half-full, force isoc trbs to interrupt more often */ if (xhci->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN) @@ -3088,7 +3094,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) event_loop = 0; } - xhci_update_erst_dequeue(xhci, event_ring_deq); + xhci_update_erst_dequeue(xhci, ir, event_ring_deq); ret = IRQ_HANDLED; out: diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index f61fda4715cc..8dc3f2c00577 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -613,6 +613,7 @@ static int xhci_init(struct usb_hcd *hcd) static int xhci_run_finished(struct xhci_hcd *xhci) { + struct xhci_interrupter *ir = xhci->interrupter; unsigned long flags; u32 temp; @@ -628,8 +629,8 @@ static int xhci_run_finished(struct xhci_hcd *xhci) writel(temp, &xhci->op_regs->command); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable primary interrupter"); - temp = readl(&xhci->ir_set->irq_pending); - writel(ER_IRQ_ENABLE(temp), &xhci->ir_set->irq_pending); + temp = readl(&ir->ir_set->irq_pending); + writel(ER_IRQ_ENABLE(temp), &ir->ir_set->irq_pending); if (xhci_start(xhci)) { xhci_halt(xhci); @@ -665,7 +666,7 @@ int xhci_run(struct usb_hcd *hcd) u64 temp_64; int ret; struct xhci_hcd *xhci = hcd_to_xhci(hcd); - + struct xhci_interrupter *ir = xhci->interrupter; /* Start the xHCI host controller running only after the USB 2.0 roothub * is setup. */ @@ -680,17 +681,17 @@ int xhci_run(struct usb_hcd *hcd) if (ret) return ret; - temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); + temp_64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); temp_64 &= ~ERST_PTR_MASK; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "ERST deq = 64'h%0lx", (long unsigned int) temp_64); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Set the interrupt modulation register"); - temp = readl(&xhci->ir_set->irq_control); + temp = readl(&ir->ir_set->irq_control); temp &= ~ER_IRQ_INTERVAL_MASK; temp |= (xhci->imod_interval / 250) & ER_IRQ_INTERVAL_MASK; - writel(temp, &xhci->ir_set->irq_control); + writel(temp, &ir->ir_set->irq_control); if (xhci->quirks & XHCI_NEC_HOST) { struct xhci_command *command; @@ -769,8 +770,8 @@ static void xhci_stop(struct usb_hcd *hcd) "// Disabling event ring interrupts"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - temp = readl(&xhci->ir_set->irq_pending); - writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); + temp = readl(&xhci->interrupter->ir_set->irq_pending); + writel(ER_IRQ_DISABLE(temp), &xhci->interrupter->ir_set->irq_pending); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory"); xhci_mem_cleanup(xhci); @@ -832,28 +833,36 @@ EXPORT_SYMBOL_GPL(xhci_shutdown); #ifdef CONFIG_PM static void xhci_save_registers(struct xhci_hcd *xhci) { + struct xhci_interrupter *ir = xhci->interrupter; + xhci->s3.command = readl(&xhci->op_regs->command); xhci->s3.dev_nt = readl(&xhci->op_regs->dev_notification); xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); xhci->s3.config_reg = readl(&xhci->op_regs->config_reg); - xhci->s3.erst_size = readl(&xhci->ir_set->erst_size); - xhci->s3.erst_base = xhci_read_64(xhci, &xhci->ir_set->erst_base); - xhci->s3.erst_dequeue = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue); - xhci->s3.irq_pending = readl(&xhci->ir_set->irq_pending); - xhci->s3.irq_control = readl(&xhci->ir_set->irq_control); + + if (!ir) + return; + + ir->s3_erst_size = readl(&ir->ir_set->erst_size); + ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); + ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); + ir->s3_irq_pending = readl(&ir->ir_set->irq_pending); + ir->s3_irq_control = readl(&ir->ir_set->irq_control); } static void xhci_restore_registers(struct xhci_hcd *xhci) { + struct xhci_interrupter *ir = xhci->interrupter; + writel(xhci->s3.command, &xhci->op_regs->command); writel(xhci->s3.dev_nt, &xhci->op_regs->dev_notification); xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); writel(xhci->s3.config_reg, &xhci->op_regs->config_reg); - writel(xhci->s3.erst_size, &xhci->ir_set->erst_size); - xhci_write_64(xhci, xhci->s3.erst_base, &xhci->ir_set->erst_base); - xhci_write_64(xhci, xhci->s3.erst_dequeue, &xhci->ir_set->erst_dequeue); - writel(xhci->s3.irq_pending, &xhci->ir_set->irq_pending); - writel(xhci->s3.irq_control, &xhci->ir_set->irq_control); + writel(ir->s3_erst_size, &ir->ir_set->erst_size); + xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); + xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); + writel(ir->s3_irq_pending, &ir->ir_set->irq_pending); + writel(ir->s3_irq_control, &ir->ir_set->irq_control); } static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) @@ -1218,8 +1227,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "// Disabling event ring interrupts\n"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - temp = readl(&xhci->ir_set->irq_pending); - writel(ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); + temp = readl(&xhci->interrupter->ir_set->irq_pending); + writel(ER_IRQ_DISABLE(temp), &xhci->interrupter->ir_set->irq_pending); xhci_dbg(xhci, "cleaning up memory\n"); xhci_mem_cleanup(xhci); @@ -5334,6 +5343,11 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); + /* xhci-plat or xhci-pci might have set max_interrupters already */ + if ((!xhci->max_interrupters) || + xhci->max_interrupters > HCS_MAX_INTRS(xhci->hcs_params1)) + xhci->max_interrupters = HCS_MAX_INTRS(xhci->hcs_params1); + xhci->quirks |= quirks; get_quirks(dev, xhci); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 0dd92f9089fe..95eb235d1f70 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1687,11 +1687,6 @@ struct s3_save { u32 dev_nt; u64 dcbaa_ptr; u32 config_reg; - u32 irq_pending; - u32 irq_control; - u32 erst_size; - u64 erst_base; - u64 erst_dequeue; }; /* Use for lpm */ @@ -1718,7 +1713,18 @@ struct xhci_bus_state { struct completion u3exit_done[USB_MAXCHILDREN]; }; - +struct xhci_interrupter { + struct xhci_ring *event_ring; + struct xhci_erst erst; + struct xhci_intr_reg __iomem *ir_set; + unsigned int intr_num; + /* For interrupter registers save and restore over suspend/resume */ + u32 s3_irq_pending; + u32 s3_irq_control; + u32 s3_erst_size; + u64 s3_erst_base; + u64 s3_erst_dequeue; +}; /* * It can take up to 20 ms to transition from RExit to U0 on the * Intel Lynx Point LP xHCI host. @@ -1761,8 +1767,6 @@ struct xhci_hcd { struct xhci_op_regs __iomem *op_regs; struct xhci_run_regs __iomem *run_regs; struct xhci_doorbell_array __iomem *dba; - /* Our HCD's current interrupter register set */ - struct xhci_intr_reg __iomem *ir_set; /* Cached register copies of read-only HC data */ __u32 hcs_params1; @@ -1797,6 +1801,7 @@ struct xhci_hcd { struct reset_control *reset; /* data structures */ struct xhci_device_context_array *dcbaa; + struct xhci_interrupter *interrupter; struct xhci_ring *cmd_ring; unsigned int cmd_ring_state; #define CMD_RING_STATE_RUNNING (1 << 0) @@ -1807,8 +1812,7 @@ struct xhci_hcd { struct delayed_work cmd_timer; struct completion cmd_ring_stop_completion; struct xhci_command *current_cmd; - struct xhci_ring *event_ring; - struct xhci_erst erst; + /* Scratchpad */ struct xhci_scratchpad *scratchpad; -- cgit v1.2.3 From 52dd0483e822d097fd6f522af2438a9b6f6eb0a9 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:04:58 +0200 Subject: xhci: add helpers for enabling and disabling interrupters Simple helpers to set and clear the IE (interrupter enable) bit for an interrupter. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 8dc3f2c00577..6183ce8574b1 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -292,6 +292,32 @@ static void xhci_zero_64b_regs(struct xhci_hcd *xhci) xhci_info(xhci, "Fault detected\n"); } +static int xhci_enable_interrupter(struct xhci_interrupter *ir) +{ + u32 iman; + + if (!ir || !ir->ir_set) + return -EINVAL; + + iman = readl(&ir->ir_set->irq_pending); + writel(ER_IRQ_ENABLE(iman), &ir->ir_set->irq_pending); + + return 0; +} + +static int xhci_disable_interrupter(struct xhci_interrupter *ir) +{ + u32 iman; + + if (!ir || !ir->ir_set) + return -EINVAL; + + iman = readl(&ir->ir_set->irq_pending); + writel(ER_IRQ_DISABLE(iman), &ir->ir_set->irq_pending); + + return 0; +} + #ifdef CONFIG_USB_PCI /* * Set up MSI @@ -610,7 +636,6 @@ static int xhci_init(struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ - static int xhci_run_finished(struct xhci_hcd *xhci) { struct xhci_interrupter *ir = xhci->interrupter; @@ -629,8 +654,7 @@ static int xhci_run_finished(struct xhci_hcd *xhci) writel(temp, &xhci->op_regs->command); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable primary interrupter"); - temp = readl(&ir->ir_set->irq_pending); - writel(ER_IRQ_ENABLE(temp), &ir->ir_set->irq_pending); + xhci_enable_interrupter(ir); if (xhci_start(xhci)) { xhci_halt(xhci); @@ -734,6 +758,7 @@ static void xhci_stop(struct usb_hcd *hcd) { u32 temp; struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_interrupter *ir = xhci->interrupter; mutex_lock(&xhci->mutex); @@ -770,8 +795,7 @@ static void xhci_stop(struct usb_hcd *hcd) "// Disabling event ring interrupts"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - temp = readl(&xhci->interrupter->ir_set->irq_pending); - writel(ER_IRQ_DISABLE(temp), &xhci->interrupter->ir_set->irq_pending); + xhci_disable_interrupter(ir); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory"); xhci_mem_cleanup(xhci); @@ -1227,8 +1251,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "// Disabling event ring interrupts\n"); temp = readl(&xhci->op_regs->status); writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - temp = readl(&xhci->interrupter->ir_set->irq_pending); - writel(ER_IRQ_DISABLE(temp), &xhci->interrupter->ir_set->irq_pending); + xhci_disable_interrupter(xhci->interrupter); xhci_dbg(xhci, "cleaning up memory\n"); xhci_mem_cleanup(xhci); -- cgit v1.2.3 From faaae0190dcd1e230616c85bbc3b339f27ba5b81 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:04:59 +0200 Subject: xhci: cleanup xhci_hub_control port references Both port number and port structure of a port are referred to several times when handing hub requests in xhci. Use more suitable data types and readable names for these. Cleanup only, no functional changes Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 123 +++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 7750a5eed435..181c070d6a99 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1209,11 +1209,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 test_mode = 0; struct xhci_hub *rhub; struct xhci_port **ports; + struct xhci_port *port; + int portnum1; rhub = xhci_get_rhub(hcd); ports = rhub->ports; max_ports = rhub->num_ports; bus_state = &rhub->bus_state; + portnum1 = wIndex & 0xff; spin_lock_irqsave(&xhci->lock, flags); switch (typeReq) { @@ -1247,10 +1250,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_unlock_irqrestore(&xhci->lock, flags); return retval; case GetPortStatus: - if (!wIndex || wIndex > max_ports) + if (!portnum1 || portnum1 > max_ports) goto error; + wIndex--; - temp = readl(ports[wIndex]->addr); + port = ports[portnum1 - 1]; + temp = readl(port->addr); if (temp == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; @@ -1263,7 +1268,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x", - hcd->self.busnum, wIndex + 1, temp, status); + hcd->self.busnum, portnum1, temp, status); put_unaligned(cpu_to_le32(status), (__le32 *) buf); /* if USB 3.1 extended port status return additional 4 bytes */ @@ -1275,7 +1280,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, retval = -EINVAL; break; } - port_li = readl(ports[wIndex]->addr + PORTLI); + port_li = readl(port->addr + PORTLI); status = xhci_get_ext_port_status(temp, port_li); put_unaligned_le32(status, &buf[4]); } @@ -1289,11 +1294,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, test_mode = (wIndex & 0xff00) >> 8; /* The MSB of wIndex is the U1/U2 timeout */ timeout = (wIndex & 0xff00) >> 8; + wIndex &= 0xff; - if (!wIndex || wIndex > max_ports) + if (!portnum1 || portnum1 > max_ports) goto error; + + port = ports[portnum1 - 1]; wIndex--; - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); if (temp == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; @@ -1303,11 +1311,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* FIXME: What new port features do we need to support? */ switch (wValue) { case USB_PORT_FEAT_SUSPEND: - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); if ((temp & PORT_PLS_MASK) != XDEV_U0) { /* Resume the port to U0 first */ - xhci_set_link_state(xhci, ports[wIndex], - XDEV_U0); + xhci_set_link_state(xhci, port, XDEV_U0); spin_unlock_irqrestore(&xhci->lock, flags); msleep(10); spin_lock_irqsave(&xhci->lock, flags); @@ -1316,16 +1323,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * a port unless the port reports that it is in the * enabled (PED = ‘1’,PLS < ‘3’) state. */ - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) || (temp & PORT_PLS_MASK) >= XDEV_U3) { xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n", - hcd->self.busnum, wIndex + 1); + hcd->self.busnum, portnum1); goto error; } slot_id = xhci_find_slot_id_by_port(hcd, xhci, - wIndex + 1); + portnum1); if (!slot_id) { xhci_warn(xhci, "slot_id is zero\n"); goto error; @@ -1335,21 +1342,21 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_stop_device(xhci, slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); - xhci_set_link_state(xhci, ports[wIndex], XDEV_U3); + xhci_set_link_state(xhci, port, XDEV_U3); spin_unlock_irqrestore(&xhci->lock, flags); msleep(10); /* wait device to enter */ spin_lock_irqsave(&xhci->lock, flags); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); bus_state->suspended_ports |= 1 << wIndex; break; case USB_PORT_FEAT_LINK_STATE: - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); /* Disable port */ if (link_state == USB_SS_PORT_LS_SS_DISABLED) { xhci_dbg(xhci, "Disable port %d-%d\n", - hcd->self.busnum, wIndex + 1); + hcd->self.busnum, portnum1); temp = xhci_port_state_to_neutral(temp); /* * Clear all change bits, so that we get a new @@ -1358,18 +1365,17 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp |= PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | PORT_RC | PORT_PLC | PORT_CEC; - writel(temp | PORT_PE, ports[wIndex]->addr); - temp = readl(ports[wIndex]->addr); + writel(temp | PORT_PE, port->addr); + temp = readl(port->addr); break; } /* Put link in RxDetect (enable port) */ if (link_state == USB_SS_PORT_LS_RX_DETECT) { xhci_dbg(xhci, "Enable port %d-%d\n", - hcd->self.busnum, wIndex + 1); - xhci_set_link_state(xhci, ports[wIndex], - link_state); - temp = readl(ports[wIndex]->addr); + hcd->self.busnum, portnum1); + xhci_set_link_state(xhci, port, link_state); + temp = readl(port->addr); break; } @@ -1399,11 +1405,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n", - hcd->self.busnum, wIndex + 1); - xhci_set_link_state(xhci, ports[wIndex], - link_state); + hcd->self.busnum, portnum1); + xhci_set_link_state(xhci, port, link_state); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); break; } /* Port must be enabled */ @@ -1414,8 +1419,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Can't set port link state above '3' (U3) */ if (link_state > USB_SS_PORT_LS_U3) { xhci_warn(xhci, "Cannot set port %d-%d link state %d\n", - hcd->self.busnum, wIndex + 1, - link_state); + hcd->self.busnum, portnum1, link_state); goto error; } @@ -1440,8 +1444,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, reinit_completion(&bus_state->u3exit_done[wIndex]); } if (pls <= XDEV_U3) /* U1, U2, U3 */ - xhci_set_link_state(xhci, ports[wIndex], - USB_SS_PORT_LS_U0); + xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U0); if (!wait_u0) { if (pls > XDEV_U3) goto error; @@ -1451,16 +1454,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex], msecs_to_jiffies(500))) xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", - hcd->self.busnum, wIndex + 1); + hcd->self.busnum, portnum1); spin_lock_irqsave(&xhci->lock, flags); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); break; } if (link_state == USB_SS_PORT_LS_U3) { int retries = 16; slot_id = xhci_find_slot_id_by_port(hcd, xhci, - wIndex + 1); + portnum1); if (slot_id) { /* unlock to execute stop endpoint * commands */ @@ -1469,16 +1472,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_stop_device(xhci, slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); } - xhci_set_link_state(xhci, ports[wIndex], USB_SS_PORT_LS_U3); + xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U3); spin_unlock_irqrestore(&xhci->lock, flags); while (retries--) { usleep_range(4000, 8000); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); if ((temp & PORT_PLS_MASK) == XDEV_U3) break; } spin_lock_irqsave(&xhci->lock, flags); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); bus_state->suspended_ports |= 1 << wIndex; } break; @@ -1493,39 +1496,38 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case USB_PORT_FEAT_RESET: temp = (temp | PORT_RESET); - writel(temp, ports[wIndex]->addr); + writel(temp, port->addr); - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); xhci_dbg(xhci, "set port reset, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, wIndex + 1, temp); + hcd->self.busnum, portnum1, temp); break; case USB_PORT_FEAT_REMOTE_WAKE_MASK: - xhci_set_remote_wake_mask(xhci, ports[wIndex], - wake_mask); - temp = readl(ports[wIndex]->addr); + xhci_set_remote_wake_mask(xhci, port, wake_mask); + temp = readl(port->addr); xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, wIndex + 1, temp); + hcd->self.busnum, portnum1, temp); break; case USB_PORT_FEAT_BH_PORT_RESET: temp |= PORT_WR; - writel(temp, ports[wIndex]->addr); - temp = readl(ports[wIndex]->addr); + writel(temp, port->addr); + temp = readl(port->addr); break; case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed < HCD_USB3) goto error; - temp = readl(ports[wIndex]->addr + PORTPMSC); + temp = readl(port->addr + PORTPMSC); temp &= ~PORT_U1_TIMEOUT_MASK; temp |= PORT_U1_TIMEOUT(timeout); - writel(temp, ports[wIndex]->addr + PORTPMSC); + writel(temp, port->addr + PORTPMSC); break; case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed < HCD_USB3) goto error; - temp = readl(ports[wIndex]->addr + PORTPMSC); + temp = readl(port->addr + PORTPMSC); temp &= ~PORT_U2_TIMEOUT_MASK; temp |= PORT_U2_TIMEOUT(timeout); - writel(temp, ports[wIndex]->addr + PORTPMSC); + writel(temp, port->addr + PORTPMSC); break; case USB_PORT_FEAT_TEST: /* 4.19.6 Port Test Modes (USB2 Test Mode) */ @@ -1541,13 +1543,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; } /* unblock any posted writes */ - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); break; case ClearPortFeature: - if (!wIndex || wIndex > max_ports) + if (!portnum1 || portnum1 > max_ports) goto error; + + port = ports[portnum1 - 1]; + wIndex--; - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); if (temp == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; @@ -1557,7 +1562,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_port_state_to_neutral(temp); switch (wValue) { case USB_PORT_FEAT_SUSPEND: - temp = readl(ports[wIndex]->addr); + temp = readl(port->addr); xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); xhci_dbg(xhci, "PORTSC %04x\n", temp); if (temp & PORT_RESET) @@ -1568,20 +1573,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, set_bit(wIndex, &bus_state->resuming_ports); usb_hcd_start_port_resume(&hcd->self, wIndex); - xhci_set_link_state(xhci, ports[wIndex], - XDEV_RESUME); + xhci_set_link_state(xhci, port, XDEV_RESUME); spin_unlock_irqrestore(&xhci->lock, flags); msleep(USB_RESUME_TIMEOUT); spin_lock_irqsave(&xhci->lock, flags); - xhci_set_link_state(xhci, ports[wIndex], - XDEV_U0); + xhci_set_link_state(xhci, port, XDEV_U0); clear_bit(wIndex, &bus_state->resuming_ports); usb_hcd_end_port_resume(&hcd->self, wIndex); } bus_state->port_c_suspend |= 1 << wIndex; slot_id = xhci_find_slot_id_by_port(hcd, xhci, - wIndex + 1); + portnum1); if (!slot_id) { xhci_dbg(xhci, "slot_id is zero\n"); goto error; @@ -1599,11 +1602,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_C_PORT_LINK_STATE: case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: xhci_clear_port_change_bit(xhci, wValue, wIndex, - ports[wIndex]->addr, temp); + port->addr, temp); break; case USB_PORT_FEAT_ENABLE: xhci_disable_port(hcd, xhci, wIndex, - ports[wIndex]->addr, temp); + port->addr, temp); break; case USB_PORT_FEAT_POWER: xhci_set_port_power(xhci, hcd, wIndex, false, &flags); -- cgit v1.2.3 From a66095a957ce3ce2a5154f7981845942f26e477d Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:00 +0200 Subject: xhci: pass port pointer as parameter to xhci_set_port_power() Pass the port structure pointer directly to xhci_set_port_power() instead of hcd and port index. cleanup Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 181c070d6a99..238d05206d2c 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -666,20 +666,18 @@ struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) * It will release and re-aquire the lock while calling ACPI * method. */ -static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd, - u16 index, bool on, unsigned long *flags) +static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port, + bool on, unsigned long *flags) __must_hold(&xhci->lock) { - struct xhci_hub *rhub; - struct xhci_port *port; + struct usb_hcd *hcd; u32 temp; - rhub = xhci_get_rhub(hcd); - port = rhub->ports[index]; + hcd = port->rhub->hcd; temp = readl(port->addr); xhci_dbg(xhci, "set port power %d-%d %s, portsc: 0x%x\n", - hcd->self.busnum, index + 1, on ? "ON" : "OFF", temp); + hcd->self.busnum, port->hcd_portnum + 1, on ? "ON" : "OFF", temp); temp = xhci_port_state_to_neutral(temp); @@ -694,10 +692,10 @@ static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd, spin_unlock_irqrestore(&xhci->lock, *flags); temp = usb_acpi_power_manageable(hcd->self.root_hub, - index); + port->hcd_portnum); if (temp) usb_acpi_set_power_state(hcd->self.root_hub, - index, on); + port->hcd_portnum, on); spin_lock_irqsave(&xhci->lock, *flags); } @@ -721,7 +719,6 @@ 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 */ @@ -742,10 +739,10 @@ 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, usb3_hcd, i, false, flags); + xhci_set_port_power(xhci, xhci->usb3_rhub.ports[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); + xhci_set_port_power(xhci, xhci->usb2_rhub.ports[i], false, flags); /* Stop the controller */ xhci_dbg(xhci, "Stop controller\n"); retval = xhci_halt(xhci); @@ -1492,7 +1489,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * However, hub_wq will ignore the roothub events until * the roothub is registered. */ - xhci_set_port_power(xhci, hcd, wIndex, true, &flags); + xhci_set_port_power(xhci, port, true, &flags); break; case USB_PORT_FEAT_RESET: temp = (temp | PORT_RESET); @@ -1609,7 +1606,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, port->addr, temp); break; case USB_PORT_FEAT_POWER: - xhci_set_port_power(xhci, hcd, wIndex, false, &flags); + xhci_set_port_power(xhci, port, false, &flags); break; case USB_PORT_FEAT_TEST: retval = xhci_exit_test_mode(xhci); -- cgit v1.2.3 From 2996e9fc00c378987c18ecbafe5624581b18c0d6 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:01 +0200 Subject: xhci: move port specific items such as state completions to port structure Now that we have a port structure for each port it makes sense to move per port variables, timestamps and completions there. Get rid of storing bitfileds and arrays of port specific items per bus. Move unsigned long resume_done; insigned long rexit_ports struct completion rexit_done; struct completion u3exit_done; Rename rexit_ports to rexit_active, and remove a redundant hcd speed check while checking if rexit_active is set. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 31 +++++++++++++++---------------- drivers/usb/host/xhci-mem.c | 10 +++------- drivers/usb/host/xhci-ring.c | 13 ++++++------- drivers/usb/host/xhci.h | 9 ++++----- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 238d05206d2c..75c9609f32f0 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -936,7 +936,7 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, return -EINVAL; } /* did port event handler already start resume timing? */ - if (!bus_state->resume_done[wIndex]) { + if (!port->resume_done) { /* If not, maybe we are in a host initated resume? */ if (test_bit(wIndex, &bus_state->resuming_ports)) { /* Host initated resume doesn't time the resume @@ -953,28 +953,27 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, msecs_to_jiffies(USB_RESUME_TIMEOUT); set_bit(wIndex, &bus_state->resuming_ports); - bus_state->resume_done[wIndex] = timeout; + port->resume_done = timeout; mod_timer(&hcd->rh_timer, timeout); usb_hcd_start_port_resume(&hcd->self, wIndex); } /* Has resume been signalled for USB_RESUME_TIME yet? */ - } else if (time_after_eq(jiffies, bus_state->resume_done[wIndex])) { + } else if (time_after_eq(jiffies, port->resume_done)) { int time_left; xhci_dbg(xhci, "resume USB2 port %d-%d\n", hcd->self.busnum, wIndex + 1); - bus_state->resume_done[wIndex] = 0; + port->resume_done = 0; clear_bit(wIndex, &bus_state->resuming_ports); - - set_bit(wIndex, &bus_state->rexit_ports); + port->rexit_active = true; xhci_test_and_clear_bit(xhci, port, PORT_PLC); xhci_set_link_state(xhci, port, XDEV_U0); spin_unlock_irqrestore(&xhci->lock, *flags); time_left = wait_for_completion_timeout( - &bus_state->rexit_done[wIndex], + &port->rexit_done, msecs_to_jiffies(XHCI_MAX_REXIT_TIMEOUT_MS)); spin_lock_irqsave(&xhci->lock, *flags); @@ -993,7 +992,7 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n", hcd->self.busnum, wIndex + 1, port_status); *status |= USB_PORT_STAT_SUSPEND; - clear_bit(wIndex, &bus_state->rexit_ports); + port->rexit_active = false; } usb_hcd_end_port_resume(&hcd->self, wIndex); @@ -1100,10 +1099,10 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, if (link_state == XDEV_U2) *status |= USB_PORT_STAT_L1; if (link_state == XDEV_U0) { - if (bus_state->resume_done[portnum]) + if (port->resume_done) usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); - bus_state->resume_done[portnum] = 0; + port->resume_done = 0; clear_bit(portnum, &bus_state->resuming_ports); if (bus_state->suspended_ports & (1 << portnum)) { bus_state->suspended_ports &= ~(1 << portnum); @@ -1175,11 +1174,11 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, * Clear stale usb2 resume signalling variables in case port changed * state during resume signalling. For example on error */ - if ((bus_state->resume_done[wIndex] || + if ((port->resume_done || test_bit(wIndex, &bus_state->resuming_ports)) && (raw_port_status & PORT_PLS_MASK) != XDEV_U3 && (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) { - bus_state->resume_done[wIndex] = 0; + port->resume_done = 0; clear_bit(wIndex, &bus_state->resuming_ports); usb_hcd_end_port_resume(&hcd->self, wIndex); } @@ -1438,7 +1437,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, pls == XDEV_RESUME || pls == XDEV_RECOVERY) { wait_u0 = true; - reinit_completion(&bus_state->u3exit_done[wIndex]); + reinit_completion(&port->u3exit_done); } if (pls <= XDEV_U3) /* U1, U2, U3 */ xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U0); @@ -1448,7 +1447,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; } spin_unlock_irqrestore(&xhci->lock, flags); - if (!wait_for_completion_timeout(&bus_state->u3exit_done[wIndex], + if (!wait_for_completion_timeout(&port->u3exit_done, msecs_to_jiffies(500))) xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", hcd->self.busnum, portnum1); @@ -1688,8 +1687,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) if ((temp & mask) != 0 || (bus_state->port_c_suspend & 1 << i) || - (bus_state->resume_done[i] && time_after_eq( - jiffies, bus_state->resume_done[i]))) { + (ports[i]->resume_done && time_after_eq( + jiffies, ports[i]->resume_done))) { buf[(i + 1) / 8] |= 1 << (i + 1) % 8; status = 1; } diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 6b83c5c35cf8..d0a9467aa5fc 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2154,6 +2154,9 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) xhci->hw_ports[i].addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS * i; xhci->hw_ports[i].hw_portnum = i; + + init_completion(&xhci->hw_ports[i].rexit_done); + init_completion(&xhci->hw_ports[i].u3exit_done); } xhci->rh_bw = kcalloc_node(num_ports, sizeof(*xhci->rh_bw), flags, @@ -2439,13 +2442,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) */ for (i = 0; i < MAX_HC_SLOTS; i++) xhci->devs[i] = NULL; - for (i = 0; i < USB_MAXCHILDREN; i++) { - xhci->usb2_rhub.bus_state.resume_done[i] = 0; - xhci->usb3_rhub.bus_state.resume_done[i] = 0; - /* Only the USB 2.0 completions will ever be used. */ - init_completion(&xhci->usb2_rhub.bus_state.rexit_done[i]); - init_completion(&xhci->usb3_rhub.bus_state.u3exit_done[i]); - } if (scratchpad_alloc(xhci, flags)) goto fail; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 451d48b87cf7..611580d4adad 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1924,7 +1924,7 @@ static void handle_port_status(struct xhci_hcd *xhci, goto cleanup; } else if (!test_bit(hcd_portnum, &bus_state->resuming_ports)) { xhci_dbg(xhci, "resume HS port %d\n", port_id); - bus_state->resume_done[hcd_portnum] = jiffies + + port->resume_done = jiffies + msecs_to_jiffies(USB_RESUME_TIMEOUT); set_bit(hcd_portnum, &bus_state->resuming_ports); /* Do the rest in GetPortStatus after resume time delay. @@ -1933,7 +1933,7 @@ static void handle_port_status(struct xhci_hcd *xhci, */ set_bit(HCD_FLAG_POLL_RH, &hcd->flags); mod_timer(&hcd->rh_timer, - bus_state->resume_done[hcd_portnum]); + port->resume_done); usb_hcd_start_port_resume(&hcd->self, hcd_portnum); bogus_port_status = true; } @@ -1945,7 +1945,7 @@ static void handle_port_status(struct xhci_hcd *xhci, (portsc & PORT_PLS_MASK) == XDEV_U1 || (portsc & PORT_PLS_MASK) == XDEV_U2)) { xhci_dbg(xhci, "resume SS port %d finished\n", port_id); - complete(&bus_state->u3exit_done[hcd_portnum]); + complete(&port->u3exit_done); /* We've just brought the device into U0/1/2 through either the * Resume state after a device remote wakeup, or through the * U3Exit state after a host-initiated resume. If it's a device @@ -1970,10 +1970,9 @@ static void handle_port_status(struct xhci_hcd *xhci, * RExit to a disconnect state). If so, let the driver know it's * out of the RExit state. */ - if (!DEV_SUPERSPEED_ANY(portsc) && hcd->speed < HCD_USB3 && - test_and_clear_bit(hcd_portnum, - &bus_state->rexit_ports)) { - complete(&bus_state->rexit_done[hcd_portnum]); + if (hcd->speed < HCD_USB3 && port->rexit_active) { + complete(&port->rexit_done); + port->rexit_active = false; bogus_port_status = true; goto cleanup; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 95eb235d1f70..578e219292fd 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1704,13 +1704,8 @@ struct xhci_bus_state { u32 port_c_suspend; u32 suspended_ports; u32 port_remote_wakeup; - unsigned long resume_done[USB_MAXCHILDREN]; /* which ports have started to resume */ unsigned long resuming_ports; - /* Which ports are waiting on RExit to U0 transition. */ - unsigned long rexit_ports; - struct completion rexit_done[USB_MAXCHILDREN]; - struct completion u3exit_done[USB_MAXCHILDREN]; }; struct xhci_interrupter { @@ -1745,6 +1740,10 @@ struct xhci_port { struct xhci_hub *rhub; struct xhci_port_cap *port_cap; unsigned int lpm_incapable:1; + unsigned long resume_done; + bool rexit_active; + struct completion rexit_done; + struct completion u3exit_done; }; struct xhci_hub { -- cgit v1.2.3 From 6baf7e749ab3aa4fcfeef3a26e8ec2306572cd05 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:02 +0200 Subject: xhci: Pass port structure as parameter to xhci_disable_port(). Pass the port structure to xhci_disable_port() instead of address, index, and value. re-read the port portsc value before disabling the port. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 75c9609f32f0..b27969e3cdcf 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -578,13 +578,16 @@ void xhci_ring_device(struct xhci_hcd *xhci, int slot_id) return; } -static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, - u16 wIndex, __le32 __iomem *addr, u32 port_status) +static void xhci_disable_port(struct xhci_hcd *xhci, struct xhci_port *port) { + struct usb_hcd *hcd; + u32 portsc; + + hcd = port->rhub->hcd; + /* Don't allow the USB core to disable SuperSpeed ports. */ if (hcd->speed >= HCD_USB3) { - xhci_dbg(xhci, "Ignoring request to disable " - "SuperSpeed port.\n"); + xhci_dbg(xhci, "Ignoring request to disable SuperSpeed port.\n"); return; } @@ -594,11 +597,15 @@ static void xhci_disable_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, return; } + portsc = readl(port->addr); + portsc = xhci_port_state_to_neutral(portsc); + /* Write 1 to disable the port */ - writel(port_status | PORT_PE, addr); - port_status = readl(addr); + writel(portsc | PORT_PE, port->addr); + + portsc = readl(port->addr); xhci_dbg(xhci, "disable port %d-%d, portsc: 0x%x\n", - hcd->self.busnum, wIndex + 1, port_status); + hcd->self.busnum, port->hcd_portnum + 1, portsc); } static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, @@ -1601,8 +1608,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, port->addr, temp); break; case USB_PORT_FEAT_ENABLE: - xhci_disable_port(hcd, xhci, wIndex, - port->addr, temp); + xhci_disable_port(xhci, port); break; case USB_PORT_FEAT_POWER: xhci_set_port_power(xhci, port, false, &flags); -- cgit v1.2.3 From a909d629ae77b97b6288bc3cfe68560454bf79c6 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:03 +0200 Subject: xhci: rename resume_done to resume_timestamp resume_done is just a timestamp, avoid confusing it with completions related to port state transitions that are named *_done Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 20 ++++++++++---------- drivers/usb/host/xhci-ring.c | 4 ++-- drivers/usb/host/xhci.h | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b27969e3cdcf..a2053aa9b4a9 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -943,7 +943,7 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, return -EINVAL; } /* did port event handler already start resume timing? */ - if (!port->resume_done) { + if (!port->resume_timestamp) { /* If not, maybe we are in a host initated resume? */ if (test_bit(wIndex, &bus_state->resuming_ports)) { /* Host initated resume doesn't time the resume @@ -960,18 +960,18 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, msecs_to_jiffies(USB_RESUME_TIMEOUT); set_bit(wIndex, &bus_state->resuming_ports); - port->resume_done = timeout; + port->resume_timestamp = timeout; mod_timer(&hcd->rh_timer, timeout); usb_hcd_start_port_resume(&hcd->self, wIndex); } /* Has resume been signalled for USB_RESUME_TIME yet? */ - } else if (time_after_eq(jiffies, port->resume_done)) { + } else if (time_after_eq(jiffies, port->resume_timestamp)) { int time_left; xhci_dbg(xhci, "resume USB2 port %d-%d\n", hcd->self.busnum, wIndex + 1); - port->resume_done = 0; + port->resume_timestamp = 0; clear_bit(wIndex, &bus_state->resuming_ports); port->rexit_active = true; @@ -1106,10 +1106,10 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, if (link_state == XDEV_U2) *status |= USB_PORT_STAT_L1; if (link_state == XDEV_U0) { - if (port->resume_done) + if (port->resume_timestamp) usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); - port->resume_done = 0; + port->resume_timestamp = 0; clear_bit(portnum, &bus_state->resuming_ports); if (bus_state->suspended_ports & (1 << portnum)) { bus_state->suspended_ports &= ~(1 << portnum); @@ -1181,11 +1181,11 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, * Clear stale usb2 resume signalling variables in case port changed * state during resume signalling. For example on error */ - if ((port->resume_done || + if ((port->resume_timestamp || test_bit(wIndex, &bus_state->resuming_ports)) && (raw_port_status & PORT_PLS_MASK) != XDEV_U3 && (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) { - port->resume_done = 0; + port->resume_timestamp = 0; clear_bit(wIndex, &bus_state->resuming_ports); usb_hcd_end_port_resume(&hcd->self, wIndex); } @@ -1693,8 +1693,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) if ((temp & mask) != 0 || (bus_state->port_c_suspend & 1 << i) || - (ports[i]->resume_done && time_after_eq( - jiffies, ports[i]->resume_done))) { + (ports[i]->resume_timestamp && time_after_eq( + jiffies, ports[i]->resume_timestamp))) { buf[(i + 1) / 8] |= 1 << (i + 1) % 8; status = 1; } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 611580d4adad..eb788c60c1c0 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1924,7 +1924,7 @@ static void handle_port_status(struct xhci_hcd *xhci, goto cleanup; } else if (!test_bit(hcd_portnum, &bus_state->resuming_ports)) { xhci_dbg(xhci, "resume HS port %d\n", port_id); - port->resume_done = jiffies + + port->resume_timestamp = jiffies + msecs_to_jiffies(USB_RESUME_TIMEOUT); set_bit(hcd_portnum, &bus_state->resuming_ports); /* Do the rest in GetPortStatus after resume time delay. @@ -1933,7 +1933,7 @@ static void handle_port_status(struct xhci_hcd *xhci, */ set_bit(HCD_FLAG_POLL_RH, &hcd->flags); mod_timer(&hcd->rh_timer, - port->resume_done); + port->resume_timestamp); usb_hcd_start_port_resume(&hcd->self, hcd_portnum); bogus_port_status = true; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 578e219292fd..786002bb35db 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1740,7 +1740,7 @@ struct xhci_port { struct xhci_hub *rhub; struct xhci_port_cap *port_cap; unsigned int lpm_incapable:1; - unsigned long resume_done; + unsigned long resume_timestamp; bool rexit_active; struct completion rexit_done; struct completion u3exit_done; -- cgit v1.2.3 From 0e6275452ce26d7ff274a5c1b15ed581a26f7986 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:04 +0200 Subject: xhci: clear usb2 resume related variables in one place. Initially resume related USB2 variables were cleared once port successfully resumed to U0. Later code was added to clean up stale resume variables in case of port failed to resume to U0. Clear the variables in one place after port is no longer resuming or in suspended U3 state. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index a2053aa9b4a9..541ccc45ea51 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1090,7 +1090,6 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, struct xhci_bus_state *bus_state; u32 link_state; u32 portnum; - int ret; bus_state = &port->rhub->bus_state; link_state = portsc & PORT_PLS_MASK; @@ -1106,23 +1105,30 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, if (link_state == XDEV_U2) *status |= USB_PORT_STAT_L1; if (link_state == XDEV_U0) { - if (port->resume_timestamp) - usb_hcd_end_port_resume(&port->rhub->hcd->self, - portnum); - port->resume_timestamp = 0; - clear_bit(portnum, &bus_state->resuming_ports); if (bus_state->suspended_ports & (1 << portnum)) { bus_state->suspended_ports &= ~(1 << portnum); bus_state->port_c_suspend |= 1 << portnum; } } if (link_state == XDEV_RESUME) { - ret = xhci_handle_usb2_port_link_resume(port, status, - portsc, flags); - if (ret) - return; + xhci_handle_usb2_port_link_resume(port, status, portsc, + flags); } } + + /* + * Clear usb2 resume signalling variables if port is no longer suspended + * or resuming. Port either resumed to U0/U1/U2, disconnected, or in a + * error state. Resume related variables should be cleared in all those cases. + */ + if ((link_state != XDEV_U3 && + link_state != XDEV_RESUME) && + (port->resume_timestamp || + test_bit(portnum, &bus_state->resuming_ports))) { + port->resume_timestamp = 0; + clear_bit(portnum, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); + } } /* @@ -1177,18 +1183,6 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, else xhci_get_usb2_port_status(port, &status, raw_port_status, flags); - /* - * Clear stale usb2 resume signalling variables in case port changed - * state during resume signalling. For example on error - */ - if ((port->resume_timestamp || - test_bit(wIndex, &bus_state->resuming_ports)) && - (raw_port_status & PORT_PLS_MASK) != XDEV_U3 && - (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) { - port->resume_timestamp = 0; - clear_bit(wIndex, &bus_state->resuming_ports); - usb_hcd_end_port_resume(&hcd->self, wIndex); - } if (bus_state->port_c_suspend & (1 << wIndex)) status |= USB_PORT_STAT_C_SUSPEND << 16; -- cgit v1.2.3 From b0425784b942fffbbdb804896197f1dbccda37c5 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 2 Feb 2023 17:05:05 +0200 Subject: xhci: decouple usb2 port resume and get_port_status request handling The get port status hub request code in xhci-hub.c will complete usb2 port resume signalling if signalling has been going on for long enough. The code that completes the resume signalling, and the code that returns the port status have gotten too intertwined, so separate them a bit. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20230202150505.618915-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 47 ++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 541ccc45ea51..0054d02239e2 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -924,7 +924,7 @@ static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, } static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, - u32 *status, u32 portsc, + u32 portsc, unsigned long *flags) { struct xhci_bus_state *bus_state; @@ -939,7 +939,6 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, wIndex = port->hcd_portnum; if ((portsc & PORT_RESET) || !(portsc & PORT_PE)) { - *status = 0xffffffff; return -EINVAL; } /* did port event handler already start resume timing? */ @@ -973,6 +972,8 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, port->resume_timestamp = 0; clear_bit(wIndex, &bus_state->resuming_ports); + + reinit_completion(&port->rexit_done); port->rexit_active = true; xhci_test_and_clear_bit(xhci, port, PORT_PLC); @@ -989,7 +990,6 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, wIndex + 1); if (!slot_id) { xhci_dbg(xhci, "slot_id is zero\n"); - *status = 0xffffffff; return -ENODEV; } xhci_ring_device(xhci, slot_id); @@ -998,22 +998,19 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n", hcd->self.busnum, wIndex + 1, port_status); - *status |= USB_PORT_STAT_SUSPEND; - port->rexit_active = false; + /* + * keep rexit_active set if U0 transition failed so we + * know to report PORT_STAT_SUSPEND status back to + * usbcore. It will be cleared later once the port is + * out of RESUME/U3 state + */ } usb_hcd_end_port_resume(&hcd->self, wIndex); bus_state->port_c_suspend |= 1 << wIndex; bus_state->suspended_ports &= ~(1 << wIndex); - } else { - /* - * The resume has been signaling for less than - * USB_RESUME_TIME. Report the port status as SUSPEND, - * let the usbcore check port status again and clear - * resume signaling later. - */ - *status |= USB_PORT_STAT_SUSPEND; } + return 0; } @@ -1090,6 +1087,7 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, struct xhci_bus_state *bus_state; u32 link_state; u32 portnum; + int err; bus_state = &port->rhub->bus_state; link_state = portsc & PORT_PLS_MASK; @@ -1111,8 +1109,12 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, } } if (link_state == XDEV_RESUME) { - xhci_handle_usb2_port_link_resume(port, status, portsc, - flags); + err = xhci_handle_usb2_port_link_resume(port, portsc, + flags); + if (err < 0) + *status = 0xffffffff; + else if (port->resume_timestamp || port->rexit_active) + *status |= USB_PORT_STAT_SUSPEND; } } @@ -1121,13 +1123,14 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, * or resuming. Port either resumed to U0/U1/U2, disconnected, or in a * error state. Resume related variables should be cleared in all those cases. */ - if ((link_state != XDEV_U3 && - link_state != XDEV_RESUME) && - (port->resume_timestamp || - test_bit(portnum, &bus_state->resuming_ports))) { - port->resume_timestamp = 0; - clear_bit(portnum, &bus_state->resuming_ports); - usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); + if (link_state != XDEV_U3 && link_state != XDEV_RESUME) { + if (port->resume_timestamp || + test_bit(portnum, &bus_state->resuming_ports)) { + port->resume_timestamp = 0; + clear_bit(portnum, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&port->rhub->hcd->self, portnum); + } + port->rexit_active = 0; } } -- cgit v1.2.3 From 4f6dfc2136fb2e8dc3f571a5caff6b6e88281fc0 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 13 Jan 2023 07:23:19 +0100 Subject: usb: remove the dead USB_OHCI_SH option USB_OHCI_SH is a dummy option that never builds any code, remove it. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20230113062339.1909087-3-hch@lst.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 0a54190bb097..1b6b83e79607 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -557,17 +557,6 @@ config USB_OHCI_HCD_SSB If unsure, say N. -config USB_OHCI_SH - bool "OHCI support for SuperH USB controller (DEPRECATED)" - depends on SUPERH || COMPILE_TEST - select USB_OHCI_HCD_PLATFORM - help - This option is deprecated now and the driver was removed, use - USB_OHCI_HCD_PLATFORM instead. - - Enables support for the on-chip OHCI controller on the SuperH. - If you use the PCI OHCI controller, this option is not necessary. - config USB_OHCI_EXYNOS tristate "OHCI support for Samsung S5P/Exynos SoC Series" depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST -- cgit v1.2.3 From fb6211f1584aad12c267c3333273f42f69438ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3=20=C3=81gila=20Bitsch?= Date: Sat, 4 Feb 2023 13:15:01 +0100 Subject: usb: gadget: add doc to struct usb_composite_dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added documentation to new struct members for WebUSB: * bcd_webusb_version * b_webusb_vendor_code * landing_page * use_webusb to avoid warnings in the build of htmldocs Fixes: 93c473948c58 ("usb: gadget: add WebUSB landing page support") Reported-by: Stephen Rothwell Signed-off-by: Jó Ágila Bitsch Link: https://lore.kernel.org/r/Y95MRZZz3yC5lETB@jo-einhundert Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/composite.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 91d22c3ed458..7ef8cea67f50 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -432,6 +432,10 @@ static inline struct usb_composite_driver *to_cdriver( * @qw_sign: qwSignature part of the OS string * @b_vendor_code: bMS_VendorCode part of the OS string * @use_os_string: false by default, interested gadgets set it + * @bcd_webusb_version: 0x0100 by default, WebUSB specification version + * @b_webusb_vendor_code: 0x0 by default, vendor code for WebUSB + * @landing_page: empty by default, landing page to announce in WebUSB + * @use_webusb:: false by default, interested gadgets set it * @os_desc_config: the configuration to be used with OS descriptors * @setup_pending: true when setup request is queued but not completed * @os_desc_pending: true when os_desc request is queued but not completed -- cgit v1.2.3 From 617c331d91077f896111044628c096802551dc66 Mon Sep 17 00:00:00 2001 From: Florian Zumbiehl Date: Mon, 6 Feb 2023 02:04:28 +0100 Subject: USB: serial: option: add support for VW/Skoda "Carstick LTE" Add support for VW/Skoda "Carstick LTE" D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=1c9e ProdID=7605 Rev=02.00 S: Manufacturer=USB Modem S: Product=USB Modem C: #Ifs= 4 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#=0x0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#=0x1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#=0x2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#=0x3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) The stick has AT command interfaces on interfaces 1, 2, and 3, and does PPP on interface 3. Signed-off-by: Florian Zumbiehl Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index ee5ac4ef7e16..e6d8d9b35ad0 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -402,6 +402,8 @@ static void option_instat_callback(struct urb *urb); #define LONGCHEER_VENDOR_ID 0x1c9e /* 4G Systems products */ +/* This one was sold as the VW and Skoda "Carstick LTE" */ +#define FOUR_G_SYSTEMS_PRODUCT_CARSTICK_LTE 0x7605 /* This is the 4G XS Stick W14 a.k.a. Mobilcom Debitel Surf-Stick * * It seems to contain a Qualcomm QSC6240/6290 chipset */ #define FOUR_G_SYSTEMS_PRODUCT_W14 0x9603 @@ -1976,6 +1978,8 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(2) }, { USB_DEVICE(AIRPLUS_VENDOR_ID, AIRPLUS_PRODUCT_MCD650) }, { USB_DEVICE(TLAYTECH_VENDOR_ID, TLAYTECH_PRODUCT_TEU800) }, + { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_CARSTICK_LTE), + .driver_info = RSVD(0) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W14), .driver_info = NCTRL(0) | NCTRL(1) }, { USB_DEVICE(LONGCHEER_VENDOR_ID, FOUR_G_SYSTEMS_PRODUCT_W100), -- cgit v1.2.3 From 25746a3fa2dad79a6dfc42522b5bb38b4bdec844 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 6 Feb 2023 13:44:22 +0100 Subject: drm/i915: fix up merge with usb-next branch In the manual fixup of the list_count_nodes() logic in drivers/gpu/drm/i915/gt/intel_execlists_submission.c in the usb-next branch, I missed that the print modifier was incorrect, resulting in loads of build warnings on 32bit systems. Fix this up by using "%su" instead of "%lu". Reported-by: kernel test robot Fixes: 924fb3ec50f5 ("Merge 6.2-rc7 into usb-next") Link: https://lore.kernel.org/r/20230206124422.2266892-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/i915/gt/intel_execlists_submission.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c index 178ff20648c5..ee71cde2ffc6 100644 --- a/drivers/gpu/drm/i915/gt/intel_execlists_submission.c +++ b/drivers/gpu/drm/i915/gt/intel_execlists_submission.c @@ -4158,7 +4158,7 @@ void intel_execlists_dump_active_requests(struct intel_engine_cs *engine, intel_engine_dump_active_requests(&engine->sched_engine->requests, hung_rq, m); - drm_printf(m, "\tOn hold?: %lu\n", + drm_printf(m, "\tOn hold?: %zu\n", list_count_nodes(&engine->sched_engine->hold)); spin_unlock_irqrestore(&engine->sched_engine->lock, flags); -- cgit v1.2.3 From be308d68785b205e483b3a0c61ba3a82da468f2c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:28:20 +0100 Subject: USB: dwc3: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Note, the root dentry for the debugfs directory for the device needs to be saved so we don't have to keep looking it up, which required a bit more refactoring to properly create and remove it when needed. Reported-by: Bruce Chen Reported-by: Cixi Geng Tested-by: Cixi Geng Acked-by: Thinh Nguyen Link: https://lore.kernel.org/r/20230202152820.2409908-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 ++ drivers/usb/dwc3/debug.h | 3 +++ drivers/usb/dwc3/debugfs.c | 19 ++++++++----------- drivers/usb/dwc3/gadget.c | 4 +--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 8f9959ba9fd4..582ebd9cf9c2 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1117,6 +1117,7 @@ struct dwc3_scratchpad_array { * address. * @num_ep_resized: carries the current number endpoints which have had its tx * fifo resized. + * @debug_root: root debugfs directory for this device to put its files in. */ struct dwc3 { struct work_struct drd_work; @@ -1332,6 +1333,7 @@ struct dwc3 { int max_cfg_eps; int last_fifo_depth; int num_ep_resized; + struct dentry *debug_root; }; #define INCRX_BURST_MODE 0 diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index 48b44b88dc25..8bb2c9e3b9ac 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -414,11 +414,14 @@ static inline const char *dwc3_gadget_generic_cmd_status_string(int status) #ifdef CONFIG_DEBUG_FS extern void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep); +extern void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep); extern void dwc3_debugfs_init(struct dwc3 *d); extern void dwc3_debugfs_exit(struct dwc3 *d); #else static inline void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep) { } +static inline void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep) +{ } static inline void dwc3_debugfs_init(struct dwc3 *d) { } static inline void dwc3_debugfs_exit(struct dwc3 *d) diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index f2b7675c7f62..850df0e6bcab 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -873,27 +873,23 @@ static const struct dwc3_ep_file_map dwc3_ep_file_map[] = { { "GDBGEPINFO", &dwc3_ep_info_register_fops, }, }; -static void dwc3_debugfs_create_endpoint_files(struct dwc3_ep *dep, - struct dentry *parent) +void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep) { + struct dentry *dir; int i; + dir = debugfs_create_dir(dep->name, dep->dwc->debug_root); for (i = 0; i < ARRAY_SIZE(dwc3_ep_file_map); i++) { const struct file_operations *fops = dwc3_ep_file_map[i].fops; const char *name = dwc3_ep_file_map[i].name; - debugfs_create_file(name, 0444, parent, dep, fops); + debugfs_create_file(name, 0444, dir, dep, fops); } } -void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep) +void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep) { - struct dentry *dir; - struct dentry *root; - - root = debugfs_lookup(dev_name(dep->dwc->dev), usb_debug_root); - dir = debugfs_create_dir(dep->name, root); - dwc3_debugfs_create_endpoint_files(dep, dir); + debugfs_lookup_and_remove(dep->name, dep->dwc->debug_root); } void dwc3_debugfs_init(struct dwc3 *dwc) @@ -911,6 +907,7 @@ void dwc3_debugfs_init(struct dwc3 *dwc) dwc->regset->base = dwc->regs - DWC3_GLOBALS_REGS_START; root = debugfs_create_dir(dev_name(dwc->dev), usb_debug_root); + dwc->debug_root = root; debugfs_create_regset32("regdump", 0444, root, dwc->regset); debugfs_create_file("lsp_dump", 0644, root, dwc, &dwc3_lsp_fops); @@ -929,6 +926,6 @@ void dwc3_debugfs_init(struct dwc3 *dwc) void dwc3_debugfs_exit(struct dwc3 *dwc) { - debugfs_remove(debugfs_lookup(dev_name(dwc->dev), usb_debug_root)); + debugfs_lookup_and_remove(dev_name(dwc->dev), usb_debug_root); kfree(dwc->regset); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 89dcfac01235..3c63fa97a680 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -3194,9 +3194,7 @@ static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) list_del(&dep->endpoint.ep_list); } - debugfs_remove_recursive(debugfs_lookup(dep->name, - debugfs_lookup(dev_name(dep->dwc->dev), - usb_debug_root))); + dwc3_debugfs_remove_endpoint_dir(dep); kfree(dep); } } -- cgit v1.2.3 From 9c0e6fbda803dfd66c05c1f683069b46bd4cbba0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 2 Feb 2023 17:17:36 +0200 Subject: usb: gadget: configfs: Use memcpy_and_pad() Instead of zeroing some memory and then copying data in part or all of it, use memcpy_and_pad(). This avoids writing some memory twice and should save a few cycles. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230202151736.64552-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index c102adbcd4e1..e89aa2877a33 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -915,8 +915,7 @@ static ssize_t webusb_landingPage_store(struct config_item *item, const char *pa mutex_lock(&gi->lock); // ensure 0 bytes are set, in case the new landing page is shorter then the old one. - memset(gi->landing_page, 0, sizeof(gi->landing_page)); - memcpy(gi->landing_page, page, l); + memcpy_and_pad(gi->landing_page, sizeof(gi->landing_page), page, l, 0); mutex_unlock(&gi->lock); return len; -- cgit v1.2.3 From 93fd565919cf897adf7e1da81cace1a46e4db7f4 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 3 Feb 2023 14:32:09 -0500 Subject: net: USB: Fix wrong-direction WARNING in plusb.c The syzbot fuzzer detected a bug in the plusb network driver: A zero-length control-OUT transfer was treated as a read instead of a write. In modern kernels this error provokes a WARNING: usb 1-1: BOGUS control dir, pipe 80000280 doesn't match bRequestType c0 WARNING: CPU: 0 PID: 4645 at drivers/usb/core/urb.c:411 usb_submit_urb+0x14a7/0x1880 drivers/usb/core/urb.c:411 Modules linked in: CPU: 1 PID: 4645 Comm: dhcpcd Not tainted 6.2.0-rc6-syzkaller-00050-g9f266ccaa2f5 #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/12/2023 RIP: 0010:usb_submit_urb+0x14a7/0x1880 drivers/usb/core/urb.c:411 ... Call Trace: usb_start_wait_urb+0x101/0x4b0 drivers/usb/core/message.c:58 usb_internal_control_msg drivers/usb/core/message.c:102 [inline] usb_control_msg+0x320/0x4a0 drivers/usb/core/message.c:153 __usbnet_read_cmd+0xb9/0x390 drivers/net/usb/usbnet.c:2010 usbnet_read_cmd+0x96/0xf0 drivers/net/usb/usbnet.c:2068 pl_vendor_req drivers/net/usb/plusb.c:60 [inline] pl_set_QuickLink_features drivers/net/usb/plusb.c:75 [inline] pl_reset+0x2f/0xf0 drivers/net/usb/plusb.c:85 usbnet_open+0xcc/0x5d0 drivers/net/usb/usbnet.c:889 __dev_open+0x297/0x4d0 net/core/dev.c:1417 __dev_change_flags+0x587/0x750 net/core/dev.c:8530 dev_change_flags+0x97/0x170 net/core/dev.c:8602 devinet_ioctl+0x15a2/0x1d70 net/ipv4/devinet.c:1147 inet_ioctl+0x33f/0x380 net/ipv4/af_inet.c:979 sock_do_ioctl+0xcc/0x230 net/socket.c:1169 sock_ioctl+0x1f8/0x680 net/socket.c:1286 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:870 [inline] __se_sys_ioctl fs/ioctl.c:856 [inline] __x64_sys_ioctl+0x197/0x210 fs/ioctl.c:856 do_syscall_x64 arch/x86/entry/common.c:50 [inline] do_syscall_64+0x39/0xb0 arch/x86/entry/common.c:80 entry_SYSCALL_64_after_hwframe+0x63/0xcd The fix is to call usbnet_write_cmd() instead of usbnet_read_cmd() and remove the USB_DIR_IN flag. Reported-and-tested-by: syzbot+2a0e7abd24f1eb90ce25@syzkaller.appspotmail.com Signed-off-by: Alan Stern Fixes: 090ffa9d0e90 ("[PATCH] USB: usbnet (9/9) module for pl2301/2302 cables") CC: stable@vger.kernel.org Link: https://lore.kernel.org/r/00000000000052099f05f3b3e298@google.com/ Link: https://lore.kernel.org/r/Y91hOew3nW56Ki4O@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/net/usb/plusb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/usb/plusb.c b/drivers/net/usb/plusb.c index 2c82fbcaab22..7a2b0094de51 100644 --- a/drivers/net/usb/plusb.c +++ b/drivers/net/usb/plusb.c @@ -57,9 +57,7 @@ static inline int pl_vendor_req(struct usbnet *dev, u8 req, u8 val, u8 index) { - return usbnet_read_cmd(dev, req, - USB_DIR_IN | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, + return usbnet_write_cmd(dev, req, USB_TYPE_VENDOR | USB_RECIP_DEVICE, val, index, NULL, 0); } -- cgit v1.2.3 From ff35f3ea3baba5b81416ac02d005cfbf6dd182fa Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:23 +0100 Subject: USB: chipidea: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Peter Chen Link: https://lore.kernel.org/r/20230202153235.2412790-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index faf6b078b6c4..bbc610e5bd69 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -364,5 +364,5 @@ void dbg_create_files(struct ci_hdrc *ci) */ void dbg_remove_files(struct ci_hdrc *ci) { - debugfs_remove(debugfs_lookup(dev_name(ci->dev), usb_debug_root)); + debugfs_lookup_and_remove(dev_name(ci->dev), usb_debug_root); } -- cgit v1.2.3 From 8f4d25eba599c4bd4b5ea8ae8752cda480a9d563 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:24 +0100 Subject: USB: ULPI: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Acked-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230202153235.2412790-2-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/ulpi.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index d7c8461976ce..38703781ee2d 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -271,7 +271,7 @@ static int ulpi_regs_show(struct seq_file *seq, void *data) } DEFINE_SHOW_ATTRIBUTE(ulpi_regs); -#define ULPI_ROOT debugfs_lookup(KBUILD_MODNAME, NULL) +static struct dentry *ulpi_root; static int ulpi_register(struct device *dev, struct ulpi *ulpi) { @@ -301,7 +301,7 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi) return ret; } - root = debugfs_create_dir(dev_name(dev), ULPI_ROOT); + root = debugfs_create_dir(dev_name(dev), ulpi_root); debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_fops); dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", @@ -349,8 +349,7 @@ EXPORT_SYMBOL_GPL(ulpi_register_interface); */ void ulpi_unregister_interface(struct ulpi *ulpi) { - debugfs_remove_recursive(debugfs_lookup(dev_name(&ulpi->dev), - ULPI_ROOT)); + debugfs_lookup_and_remove(dev_name(&ulpi->dev), ulpi_root); device_unregister(&ulpi->dev); } EXPORT_SYMBOL_GPL(ulpi_unregister_interface); @@ -360,12 +359,11 @@ EXPORT_SYMBOL_GPL(ulpi_unregister_interface); static int __init ulpi_init(void) { int ret; - struct dentry *root; - root = debugfs_create_dir(KBUILD_MODNAME, NULL); + ulpi_root = debugfs_create_dir(KBUILD_MODNAME, NULL); ret = bus_register(&ulpi_bus); if (ret) - debugfs_remove(root); + debugfs_remove(ulpi_root); return ret; } subsys_initcall(ulpi_init); @@ -373,7 +371,7 @@ subsys_initcall(ulpi_init); static void __exit ulpi_exit(void) { bus_unregister(&ulpi_bus); - debugfs_remove_recursive(ULPI_ROOT); + debugfs_remove(ulpi_root); } module_exit(ulpi_exit); -- cgit v1.2.3 From 0a3f82c79c86278e7f144564b1cb6cc5c3657144 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:25 +0100 Subject: USB: uhci: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Alan Stern Link: https://lore.kernel.org/r/20230202153235.2412790-3-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/uhci-hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index c22b51af83fc..7cdc2fa7c28f 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -536,8 +536,8 @@ static void release_uhci(struct uhci_hcd *uhci) uhci->is_initialized = 0; spin_unlock_irq(&uhci->lock); - debugfs_remove(debugfs_lookup(uhci_to_hcd(uhci)->self.bus_name, - uhci_debugfs_root)); + debugfs_lookup_and_remove(uhci_to_hcd(uhci)->self.bus_name, + uhci_debugfs_root); for (i = 0; i < UHCI_NUM_SKELQH; i++) uhci_free_qh(uhci, uhci->skelqh[i]); @@ -700,7 +700,7 @@ err_alloc_frame_cpu: uhci->frame, uhci->frame_dma_handle); err_alloc_frame: - debugfs_remove(debugfs_lookup(hcd->self.bus_name, uhci_debugfs_root)); + debugfs_lookup_and_remove(hcd->self.bus_name, uhci_debugfs_root); return retval; } -- cgit v1.2.3 From e1523c4dbc54e164638ff8729d511cf91e27be04 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:26 +0100 Subject: USB: sl811: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Vincent Mailhol Link: https://lore.kernel.org/r/20230202153235.2412790-4-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/sl811-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index d206bd95c7bb..b8b90eec9107 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -1501,7 +1501,7 @@ static void create_debug_file(struct sl811 *sl811) static void remove_debug_file(struct sl811 *sl811) { - debugfs_remove(debugfs_lookup("sl811h", usb_debug_root)); + debugfs_lookup_and_remove("sl811h", usb_debug_root); } /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From 6b4040f452037a7e95472577891d57c6b18c89c5 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:27 +0100 Subject: USB: fotg210: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20230202153235.2412790-5-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/fotg210/fotg210-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/fotg210/fotg210-hcd.c b/drivers/usb/fotg210/fotg210-hcd.c index 613d29f04bcb..929106c16b29 100644 --- a/drivers/usb/fotg210/fotg210-hcd.c +++ b/drivers/usb/fotg210/fotg210-hcd.c @@ -861,7 +861,7 @@ static inline void remove_debug_files(struct fotg210_hcd *fotg210) { struct usb_bus *bus = &fotg210_to_hcd(fotg210)->self; - debugfs_remove(debugfs_lookup(bus->bus_name, fotg210_debug_root)); + debugfs_lookup_and_remove(bus->bus_name, fotg210_debug_root); } /* handshake - spin reading hc until handshake completes or fails -- cgit v1.2.3 From a95f62d5813facbec20ec087472eb313ee5fa8af Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:28 +0100 Subject: USB: isp116x: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Olav Kongas Link: https://lore.kernel.org/r/20230202153235.2412790-6-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp116x-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 4f564d71bb0b..49ae01487af4 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1205,7 +1205,7 @@ static void create_debug_file(struct isp116x *isp116x) static void remove_debug_file(struct isp116x *isp116x) { - debugfs_remove(debugfs_lookup(hcd_name, usb_debug_root)); + debugfs_lookup_and_remove(hcd_name, usb_debug_root); } #else -- cgit v1.2.3 From c26e682afc14caa87d44beed271eec8991e93c65 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:29 +0100 Subject: USB: isp1362: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Vincent Mailhol Link: https://lore.kernel.org/r/20230202153235.2412790-7-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1362-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/isp1362-hcd.c b/drivers/usb/host/isp1362-hcd.c index 0e14d1d07709..b0da143ef4be 100644 --- a/drivers/usb/host/isp1362-hcd.c +++ b/drivers/usb/host/isp1362-hcd.c @@ -2170,7 +2170,7 @@ static void create_debug_file(struct isp1362_hcd *isp1362_hcd) static void remove_debug_file(struct isp1362_hcd *isp1362_hcd) { - debugfs_remove(debugfs_lookup("isp1362", usb_debug_root)); + debugfs_lookup_and_remove("isp1362", usb_debug_root); } /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From 73f4451368663ad28daa67980c6dd11d83b303eb Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:30 +0100 Subject: USB: gadget: gr_udc: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Jakob Koschel Link: https://lore.kernel.org/r/20230202153235.2412790-8-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/gr_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/gr_udc.c b/drivers/usb/gadget/udc/gr_udc.c index 85cdc0af3bf9..09762559912d 100644 --- a/drivers/usb/gadget/udc/gr_udc.c +++ b/drivers/usb/gadget/udc/gr_udc.c @@ -215,7 +215,7 @@ static void gr_dfs_create(struct gr_udc *dev) static void gr_dfs_delete(struct gr_udc *dev) { - debugfs_remove(debugfs_lookup(dev_name(dev->dev), usb_debug_root)); + debugfs_lookup_and_remove(dev_name(dev->dev), usb_debug_root); } #else /* !CONFIG_USB_GADGET_DEBUG_FS */ -- cgit v1.2.3 From a91c99b1fe5c6f7e52fb932ad9e57ec7cfe913ec Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:31 +0100 Subject: USB: gadget: bcm63xx_udc: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Kevin Cernekee Link: https://lore.kernel.org/r/20230202153235.2412790-9-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bcm63xx_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index c03afd354797..a3055dd4acfb 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -2253,7 +2253,7 @@ static void bcm63xx_udc_init_debugfs(struct bcm63xx_udc *udc) */ static void bcm63xx_udc_cleanup_debugfs(struct bcm63xx_udc *udc) { - debugfs_remove(debugfs_lookup(udc->gadget.name, usb_debug_root)); + debugfs_lookup_and_remove(udc->gadget.name, usb_debug_root); } /*********************************************************************** -- cgit v1.2.3 From e3965acaf3739fde9d74ad82979b46d37c6c208f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:32 +0100 Subject: USB: gadget: lpc32xx_udc: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Jakob Koschel Cc: Miaoqian Lin Acked-by: Vladimir Zapolskiy Link: https://lore.kernel.org/r/20230202153235.2412790-10-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/lpc32xx_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index cea10cdb83ae..fe62db32dd0e 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -532,7 +532,7 @@ static void create_debug_file(struct lpc32xx_udc *udc) static void remove_debug_file(struct lpc32xx_udc *udc) { - debugfs_remove(debugfs_lookup(debug_filename, NULL)); + debugfs_lookup_and_remove(debug_filename, NULL); } #else -- cgit v1.2.3 From 7a038a681b7df78362d9fc7013e5395a694a9d3a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:33 +0100 Subject: USB: gadget: pxa25x_udc: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Daniel Mack Cc: Haojian Zhuang Cc: Robert Jarzmik Link: https://lore.kernel.org/r/20230202153235.2412790-11-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa25x_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index c593fc383481..9e01ddf2b417 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -1340,7 +1340,7 @@ DEFINE_SHOW_ATTRIBUTE(udc_debug); debugfs_create_file(dev->gadget.name, \ S_IRUGO, NULL, dev, &udc_debug_fops); \ } while (0) -#define remove_debug_files(dev) debugfs_remove(debugfs_lookup(dev->gadget.name, NULL)) +#define remove_debug_files(dev) debugfs_lookup_and_remove(dev->gadget.name, NULL) #else /* !CONFIG_USB_GADGET_DEBUG_FILES */ -- cgit v1.2.3 From 7a6952fa0366d4408eb8695af1a0578c39ec718a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 2 Feb 2023 16:32:34 +0100 Subject: USB: gadget: pxa27x_udc: fix memory leak with using debugfs_lookup() When calling debugfs_lookup() the result must have dput() called on it, otherwise the memory will leak over time. To make things simpler, just call debugfs_lookup_and_remove() instead which handles all of the logic at once. Cc: Daniel Mack Cc: Haojian Zhuang Cc: Robert Jarzmik Link: https://lore.kernel.org/r/20230202153235.2412790-12-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index ac980d6a4740..0ecdfd2ba9e9 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -215,7 +215,7 @@ static void pxa_init_debugfs(struct pxa_udc *udc) static void pxa_cleanup_debugfs(struct pxa_udc *udc) { - debugfs_remove(debugfs_lookup(udc->gadget.name, usb_debug_root)); + debugfs_lookup_and_remove(udc->gadget.name, usb_debug_root); } #else -- cgit v1.2.3 From 0fbd2cda92cdb00f72080665554a586f88bca821 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 4 Feb 2023 10:36:52 -0800 Subject: usb: host: xhci: mvebu: Iterate over array indexes instead of using pointer math Walking the dram->cs array was seen as accesses beyond the first array item by the compiler. Instead, use the array index directly. This allows for run-time bounds checking under CONFIG_UBSAN_BOUNDS as well. Seen with GCC 13 with -fstrict-flex-arrays: In function 'xhci_mvebu_mbus_config', inlined from 'xhci_mvebu_mbus_init_quirk' at ../drivers/usb/host/xhci-mvebu.c:66:2: ../drivers/usb/host/xhci-mvebu.c:37:28: warning: array subscript 0 is outside array bounds of 'const struct mbus_dram_window[0]' [-Warray-bounds=] 37 | writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) | | ~~^~~~~~ Cc: Mathias Nyman Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20230204183651.never.663-kees@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mvebu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-mvebu.c b/drivers/usb/host/xhci-mvebu.c index 60651a50770f..87f1597a0e5a 100644 --- a/drivers/usb/host/xhci-mvebu.c +++ b/drivers/usb/host/xhci-mvebu.c @@ -32,7 +32,7 @@ static void xhci_mvebu_mbus_config(void __iomem *base, /* Program each DRAM CS in a seperate window */ for (win = 0; win < dram->num_cs; win++) { - const struct mbus_dram_window *cs = dram->cs + win; + const struct mbus_dram_window *cs = &dram->cs[win]; writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) | (dram->mbus_dram_target_id << 4) | 1, -- cgit v1.2.3 From ce33e64c1788912976b61314b56935abd4bc97ef Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 4 Feb 2023 10:35:46 -0800 Subject: USB: ene_usb6250: Allocate enough memory for full object The allocation of PageBuffer is 512 bytes in size, but the dereferencing of struct ms_bootblock_idi (also size 512) happens at a calculated offset within the allocation, which means the object could potentially extend beyond the end of the allocation. Avoid this case by just allocating enough space to catch any accesses beyond the end. Seen with GCC 13: ../drivers/usb/storage/ene_ub6250.c: In function 'ms_lib_process_bootblock': ../drivers/usb/storage/ene_ub6250.c:1050:44: warning: array subscript 'struct ms_bootblock_idi[0]' is partly outside array bounds of 'unsigned char[512]' [-Warray-bounds=] 1050 | if (le16_to_cpu(idi->wIDIgeneralConfiguration) != MS_IDI_GENERAL_CONF) | ^~ ../include/uapi/linux/byteorder/little_endian.h:37:51: note: in definition of macro '__le16_to_cpu' 37 | #define __le16_to_cpu(x) ((__force __u16)(__le16)(x)) | ^ ../drivers/usb/storage/ene_ub6250.c:1050:29: note: in expansion of macro 'le16_to_cpu' 1050 | if (le16_to_cpu(idi->wIDIgeneralConfiguration) != MS_IDI_GENERAL_CONF) | ^~~~~~~~~~~ In file included from ../drivers/usb/storage/ene_ub6250.c:5: In function 'kmalloc', inlined from 'ms_lib_process_bootblock' at ../drivers/usb/storage/ene_ub6250.c:942:15: ../include/linux/slab.h:580:24: note: at offset [256, 512] into object of size 512 allocated by 'kmalloc_trace' 580 | return kmalloc_trace( | ^~~~~~~~~~~~~~ 581 | kmalloc_caches[kmalloc_type(flags)][index], | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 582 | flags, size); | ~~~~~~~~~~~~ Cc: Alan Stern Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20230204183546.never.849-kees@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 6012603f3630..97c66c0d91f4 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -939,7 +939,7 @@ static int ms_lib_process_bootblock(struct us_data *us, u16 PhyBlock, u8 *PageDa struct ms_lib_type_extdat ExtraData; struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; - PageBuffer = kmalloc(MS_BYTES_PER_PAGE, GFP_KERNEL); + PageBuffer = kzalloc(MS_BYTES_PER_PAGE * 2, GFP_KERNEL); if (PageBuffer == NULL) return (u32)-1; -- cgit v1.2.3 From e16cab9c1596e251761d2bfb5e1467950d616963 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:37 +0000 Subject: usb: uvc: Enumerate valid values for color matching The color matching descriptors defined in the UVC Specification contain 3 fields with discrete numeric values representing particular settings. Enumerate those values so that later code setting them can be more readable. Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-2-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/usb/video.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/include/uapi/linux/usb/video.h b/include/uapi/linux/usb/video.h index 6e8e572c2980..2ff0e8a3a683 100644 --- a/include/uapi/linux/usb/video.h +++ b/include/uapi/linux/usb/video.h @@ -179,6 +179,36 @@ #define UVC_CONTROL_CAP_AUTOUPDATE (1 << 3) #define UVC_CONTROL_CAP_ASYNCHRONOUS (1 << 4) +/* 3.9.2.6 Color Matching Descriptor Values */ +enum uvc_color_primaries_values { + UVC_COLOR_PRIMARIES_UNSPECIFIED, + UVC_COLOR_PRIMARIES_BT_709_SRGB, + UVC_COLOR_PRIMARIES_BT_470_2_M, + UVC_COLOR_PRIMARIES_BT_470_2_B_G, + UVC_COLOR_PRIMARIES_SMPTE_170M, + UVC_COLOR_PRIMARIES_SMPTE_240M, +}; + +enum uvc_transfer_characteristics_values { + UVC_TRANSFER_CHARACTERISTICS_UNSPECIFIED, + UVC_TRANSFER_CHARACTERISTICS_BT_709, + UVC_TRANSFER_CHARACTERISTICS_BT_470_2_M, + UVC_TRANSFER_CHARACTERISTICS_BT_470_2_B_G, + UVC_TRANSFER_CHARACTERISTICS_SMPTE_170M, + UVC_TRANSFER_CHARACTERISTICS_SMPTE_240M, + UVC_TRANSFER_CHARACTERISTICS_LINEAR, + UVC_TRANSFER_CHARACTERISTICS_SRGB, +}; + +enum uvc_matrix_coefficients { + UVC_MATRIX_COEFFICIENTS_UNSPECIFIED, + UVC_MATRIX_COEFFICIENTS_BT_709, + UVC_MATRIX_COEFFICIENTS_FCC, + UVC_MATRIX_COEFFICIENTS_BT_470_2_B_G, + UVC_MATRIX_COEFFICIENTS_SMPTE_170M, + UVC_MATRIX_COEFFICIENTS_SMPTE_240M, +}; + /* ------------------------------------------------------------------------ * UVC structures */ -- cgit v1.2.3 From 744eb7b821f61eceed60eb4f64227162853c9d5e Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:38 +0000 Subject: usb: gadget: uvc: Add struct for color matching in configs Color matching descriptors are meant to be a per-format piece of data and we need to be able to support different descriptors for different formats. As a preliminary step towards that goal, switch the default color matching configfs functionality to point to an instance of a new struct uvcg_color_matching. Use the same default values for its attributes as the currently hard-coded ones so that the interface to userspace is consistent. Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-3-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 58 ++++++++++++++++++++++-------- drivers/usb/gadget/function/uvc_configfs.h | 8 +++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 9ff4b1921ee2..dd14c02dd7ae 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -13,6 +13,7 @@ #include "uvc_configfs.h" #include +#include /* ----------------------------------------------------------------------------- * Global Utility Structures and Macros @@ -1841,20 +1842,21 @@ static ssize_t uvcg_color_matching_##cname##_show( \ struct config_item *item, char *page) \ { \ struct config_group *group = to_config_group(item); \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ - struct uvc_color_matching_descriptor *cd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ opts = to_f_uvc_opts(opts_item); \ - cd = &opts->uvc_color_matching; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ + result = sprintf(page, "%u\n", \ + le##bits##_to_cpu(color_match->desc.aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1876,29 +1878,57 @@ static struct configfs_attribute *uvcg_color_matching_attrs[] = { NULL, }; -static const struct uvcg_config_group_type uvcg_color_matching_type = { - .type = { - .ct_item_ops = &uvcg_config_item_ops, - .ct_attrs = uvcg_color_matching_attrs, - .ct_owner = THIS_MODULE, - }, - .name = "default", +static void uvcg_color_matching_release(struct config_item *item) +{ + struct uvcg_color_matching *color_match = + to_uvcg_color_matching(to_config_group(item)); + + kfree(color_match); +} + +static struct configfs_item_operations uvcg_color_matching_item_ops = { + .release = uvcg_color_matching_release, +}; + +static const struct config_item_type uvcg_color_matching_type = { + .ct_item_ops = &uvcg_color_matching_item_ops, + .ct_attrs = uvcg_color_matching_attrs, + .ct_owner = THIS_MODULE, }; /* ----------------------------------------------------------------------------- * streaming/color_matching */ +static int uvcg_color_matching_create_children(struct config_group *parent) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc(sizeof(*color_match), GFP_KERNEL); + if (!color_match) + return -ENOMEM; + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + color_match->desc.bColorPrimaries = UVC_COLOR_PRIMARIES_BT_709_SRGB; + color_match->desc.bTransferCharacteristics = UVC_TRANSFER_CHARACTERISTICS_BT_709; + color_match->desc.bMatrixCoefficients = UVC_MATRIX_COEFFICIENTS_SMPTE_170M; + + config_group_init_type_name(&color_match->group, "default", + &uvcg_color_matching_type); + configfs_add_default_group(&color_match->group, parent); + + return 0; +} + static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, .ct_owner = THIS_MODULE, }, .name = "color_matching", - .children = (const struct uvcg_config_group_type*[]) { - &uvcg_color_matching_type, - NULL, - }, + .create_children = uvcg_color_matching_create_children, }; /* ----------------------------------------------------------------------------- diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index ad2ec8c4c78c..18b9931c85b1 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -37,6 +37,14 @@ static inline struct uvcg_control_header *to_uvcg_control_header(struct config_i return container_of(item, struct uvcg_control_header, item); } +struct uvcg_color_matching { + struct config_group group; + struct uvc_color_matching_descriptor desc; +}; + +#define to_uvcg_color_matching(group_ptr) \ +container_of(group_ptr, struct uvcg_color_matching, group) + enum uvcg_format_type { UVCG_UNCOMPRESSED = 0, UVCG_MJPEG, -- cgit v1.2.3 From e187408cc1de933851d03eb128c25a742bc92ecc Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:39 +0000 Subject: usb: gadget: uvc: Copy color matching descriptor for each frame As currently implemented the default color matching descriptor is appended after _all_ the formats and frames that the gadget is configured with. According to the UVC specifications however this is supposed to be on a per-format basis (section 3.9.2.6): "Only one instance is allowed for a given format and if present, the Color Matching descriptor shall be placed following the Video and Still Image Frame descriptors for that format." Associate the default color matching descriptor with struct uvcg_format and copy it once-per-format instead of once only. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-4-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 65 ++++++++++++++++++++++++++++-- drivers/usb/gadget/function/uvc_configfs.h | 14 ++++--- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index dd14c02dd7ae..56e23b80d63f 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -801,6 +801,29 @@ static const char * const uvcg_format_names[] = { "mjpeg", }; +static struct uvcg_color_matching * +uvcg_format_get_default_color_match(struct config_item *streaming) +{ + struct config_item *color_matching_item, *cm_default; + struct uvcg_color_matching *color_match; + + color_matching_item = config_group_find_item(to_config_group(streaming), + "color_matching"); + if (!color_matching_item) + return NULL; + + cm_default = config_group_find_item(to_config_group(color_matching_item), + "default"); + config_item_put(color_matching_item); + if (!cm_default) + return NULL; + + color_match = to_uvcg_color_matching(to_config_group(cm_default)); + config_item_put(cm_default); + + return color_match; +} + static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) { struct f_uvc_opts *opts; @@ -1614,8 +1637,15 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }; + struct uvcg_color_matching *color_match; + struct config_item *streaming; struct uvcg_uncompressed *h; + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) return ERR_PTR(-ENOMEM); @@ -1633,6 +1663,8 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_UNCOMPRESSED; + h->fmt.color_matching = color_match; + color_match->refcnt++; config_group_init_type_name(&h->fmt.group, name, &uvcg_uncompressed_type); @@ -1797,8 +1829,15 @@ static const struct config_item_type uvcg_mjpeg_type = { static struct config_group *uvcg_mjpeg_make(struct config_group *group, const char *name) { + struct uvcg_color_matching *color_match; + struct config_item *streaming; struct uvcg_mjpeg *h; + streaming = group->cg_item.ci_parent; + color_match = uvcg_format_get_default_color_match(streaming); + if (!color_match) + return ERR_PTR(-EINVAL); + h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) return ERR_PTR(-ENOMEM); @@ -1814,6 +1853,8 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, INIT_LIST_HEAD(&h->fmt.frames); h->fmt.type = UVCG_MJPEG; + h->fmt.color_matching = color_match; + color_match->refcnt++; config_group_init_type_name(&h->fmt.group, name, &uvcg_mjpeg_type); @@ -1962,7 +2003,8 @@ static inline struct uvc_descriptor_header enum uvcg_strm_type { UVCG_HEADER = 0, UVCG_FORMAT, - UVCG_FRAME + UVCG_FRAME, + UVCG_COLOR_MATCHING, }; /* @@ -2012,6 +2054,11 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, if (ret) return ret; } + + ret = fun(f->fmt->color_matching, priv2, priv3, 0, + UVCG_COLOR_MATCHING); + if (ret) + return ret; } return ret; @@ -2067,6 +2114,12 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, *size += frm->frame.b_frame_interval_type * sz; } break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + *size += sizeof(color_match->desc); + } + break; } ++*count; @@ -2152,6 +2205,13 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, frm->frame.b_frame_interval_type); } break; + case UVCG_COLOR_MATCHING: { + struct uvcg_color_matching *color_match = priv1; + + memcpy(*dest, &color_match->desc, sizeof(color_match->desc)); + *dest += sizeof(color_match->desc); + } + break; } return 0; @@ -2191,7 +2251,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, if (ret) goto unlock; - count += 2; /* color_matching, NULL */ + count += 1; /* NULL */ *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); if (!*class_array) { ret = -ENOMEM; @@ -2218,7 +2278,6 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, kfree(data_save); goto unlock; } - *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; ++target_hdr->linked; ret = 0; diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 18b9931c85b1..174ee691302b 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -40,6 +40,7 @@ static inline struct uvcg_control_header *to_uvcg_control_header(struct config_i struct uvcg_color_matching { struct config_group group; struct uvc_color_matching_descriptor desc; + unsigned int refcnt; }; #define to_uvcg_color_matching(group_ptr) \ @@ -51,12 +52,13 @@ enum uvcg_format_type { }; 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]; + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + struct list_head frames; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; + struct uvcg_color_matching *color_matching; }; struct uvcg_format_ptr { -- cgit v1.2.3 From 4e8a720e2ed24324d2e84daad86874c47c3cbd4d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:40 +0000 Subject: usb: gadget: uvc: Remove the hardcoded default color matching A hardcoded default color matching descriptor is embedded in struct f_uvc_opts but no longer has any use - remove it. Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-5-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 9 --------- drivers/usb/gadget/function/u_uvc.h | 1 - 2 files changed, 10 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 5250805153c7..835e121a806f 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -817,7 +817,6 @@ static struct usb_function_instance *uvc_alloc_inst(void) struct uvc_camera_terminal_descriptor *cd; struct uvc_processing_unit_descriptor *pd; struct uvc_output_terminal_descriptor *od; - struct uvc_color_matching_descriptor *md; struct uvc_descriptor_header **ctl_cls; int ret; @@ -866,14 +865,6 @@ static struct usb_function_instance *uvc_alloc_inst(void) od->bSourceID = 2; od->iTerminal = 0; - md = &opts->uvc_color_matching; - md->bLength = UVC_DT_COLOR_MATCHING_SIZE; - md->bDescriptorType = USB_DT_CS_INTERFACE; - md->bDescriptorSubType = UVC_VS_COLORFORMAT; - md->bColorPrimaries = 1; - md->bTransferCharacteristics = 1; - md->bMatrixCoefficients = 4; - /* Prepare fs control class descriptors for configfs-based gadgets */ ctl_cls = opts->uvc_fs_control_cls; ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 9d15bc2c7045..67cf319e9c2d 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -54,7 +54,6 @@ struct f_uvc_opts { struct uvc_camera_terminal_descriptor uvc_camera_terminal; struct uvc_processing_unit_descriptor uvc_processing; struct uvc_output_terminal_descriptor uvc_output_terminal; - struct uvc_color_matching_descriptor uvc_color_matching; /* * Control descriptors pointers arrays for full-/high-speed and -- cgit v1.2.3 From 58f227871f798825ba44d149d578e8ffbd0d3d6d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:41 +0000 Subject: usb: gadget: uvc: Make color matching attributes read/write In preparation for allowing more than the default color matching descriptor, make the color matching attributes writeable. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-6-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 2 +- drivers/usb/gadget/function/uvc_configfs.c | 39 ++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index eb13cc5d363a..ef3b8b852cd2 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -167,7 +167,7 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Default color matching descriptors - All attributes read only: + All attributes read/write: ======================== ====================================== bMatrixCoefficients matrix used to compute luma and diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 56e23b80d63f..a210b1990080 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1904,7 +1904,44 @@ static ssize_t uvcg_color_matching_##cname##_show( \ return result; \ } \ \ -UVC_ATTR_RO(uvcg_color_matching_, cname, aname) +static ssize_t uvcg_color_matching_##cname##_store( \ + struct config_item *item, const char *page, size_t len) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_color_matching *color_match = \ + to_uvcg_color_matching(group); \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + int ret; \ + u##bits num; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + if (color_match->refcnt) { \ + ret = -EBUSY; \ + goto unlock_su; \ + } \ + \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + \ + color_match->desc.aname = num; \ + ret = len; \ + \ + mutex_unlock(&opts->lock); \ +unlock_su: \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR(uvcg_color_matching_, cname, aname) UVCG_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); UVCG_COLOR_MATCHING_ATTR(b_transfer_characteristics, bTransferCharacteristics, 8); -- cgit v1.2.3 From f5e7bdd34aca0ed92a2bef913151dd234e86cb33 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Feb 2023 11:41:42 +0000 Subject: usb: gadget: uvc: Allow creating new color matching descriptors Allow users to create new color matching descriptors in addition to the default one. These must be associated with a UVC format in order to be transmitted to the host, which is achieved by symlinking from the format to the newly created color matching descriptor - extend the uncompressed and mjpeg formats to support that linking operation. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230202114142.300858-7-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 17 ++++ drivers/usb/gadget/function/uvc_configfs.c | 99 ++++++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index ef3b8b852cd2..fec205044c87 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -179,6 +179,23 @@ Description: Default color matching descriptors white ======================== ====================================== +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching/name +Date: Dec 2022 +KernelVersion: 6.3 +Description: Additional color matching descriptors + + All attributes read/write: + + ======================== ====================================== + bMatrixCoefficients matrix used to compute luma and + chroma values from the color primaries + bTransferCharacteristics optoelectronic transfer + characteristic of the source picture, + also called the gamma function + bColorPrimaries color primaries and the reference + white + ======================== ====================================== + What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg Date: Dec 2014 KernelVersion: 4.0 diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index a210b1990080..e2ffddb969fd 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -824,6 +824,77 @@ uvcg_format_get_default_color_match(struct config_item *streaming) return color_match; } +static int uvcg_format_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming, *color_matching; + struct uvcg_format *fmt; + int ret = 0; + + mutex_lock(su_mutex); + + streaming = src->ci_parent->ci_parent; + color_matching = config_group_find_item(to_config_group(streaming), "color_matching"); + if (!color_matching || color_matching != tgt->ci_parent) { + ret = -EINVAL; + goto out_put_cm; + } + + fmt = to_uvcg_format(src); + + /* + * There's always a color matching descriptor associated with the format + * but without a symlink it should only ever be the default one. If it's + * not the default, there's already a symlink and we should bail out. + */ + color_matching_desc = uvcg_format_get_default_color_match(streaming); + if (fmt->color_matching != color_matching_desc) { + ret = -EBUSY; + goto out_put_cm; + } + + color_matching_desc->refcnt--; + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + +out_put_cm: + config_item_put(color_matching); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_format_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_color_matching *color_matching_desc; + struct config_item *streaming; + struct uvcg_format *fmt; + + mutex_lock(su_mutex); + + color_matching_desc = to_uvcg_color_matching(to_config_group(tgt)); + color_matching_desc->refcnt--; + + streaming = src->ci_parent->ci_parent; + color_matching_desc = uvcg_format_get_default_color_match(streaming); + + fmt = to_uvcg_format(src); + fmt->color_matching = color_matching_desc; + color_matching_desc->refcnt++; + + mutex_unlock(su_mutex); +} + +static struct configfs_item_operations uvcg_format_item_operations = { + .release = uvcg_config_item_release, + .allow_link = uvcg_format_allow_link, + .drop_link = uvcg_format_drop_link, +}; + static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) { struct f_uvc_opts *opts; @@ -1624,7 +1695,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = { }; static const struct config_item_type uvcg_uncompressed_type = { - .ct_item_ops = &uvcg_config_item_ops, + .ct_item_ops = &uvcg_format_item_operations, .ct_group_ops = &uvcg_uncompressed_group_ops, .ct_attrs = uvcg_uncompressed_attrs, .ct_owner = THIS_MODULE, @@ -1820,7 +1891,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = { }; static const struct config_item_type uvcg_mjpeg_type = { - .ct_item_ops = &uvcg_config_item_ops, + .ct_item_ops = &uvcg_format_item_operations, .ct_group_ops = &uvcg_mjpeg_group_ops, .ct_attrs = uvcg_mjpeg_attrs, .ct_owner = THIS_MODULE, @@ -1978,6 +2049,29 @@ static const struct config_item_type uvcg_color_matching_type = { * streaming/color_matching */ +static struct config_group *uvcg_color_matching_make(struct config_group *group, + const char *name) +{ + struct uvcg_color_matching *color_match; + + color_match = kzalloc(sizeof(*color_match), GFP_KERNEL); + if (!color_match) + return ERR_PTR(-ENOMEM); + + color_match->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + color_match->desc.bDescriptorType = USB_DT_CS_INTERFACE; + color_match->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + + config_group_init_type_name(&color_match->group, name, + &uvcg_color_matching_type); + + return &color_match->group; +} + +static struct configfs_group_operations uvcg_color_matching_grp_group_ops = { + .make_group = uvcg_color_matching_make, +}; + static int uvcg_color_matching_create_children(struct config_group *parent) { struct uvcg_color_matching *color_match; @@ -2003,6 +2097,7 @@ static int uvcg_color_matching_create_children(struct config_group *parent) static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_color_matching_grp_group_ops, .ct_owner = THIS_MODULE, }, .name = "color_matching", -- cgit v1.2.3 From 41070a7027e9c4493791266fa38e59ded6aea7b4 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 15:11:31 +0000 Subject: usb: gadget: uvc: Correct documentation formatting The documentation table added in a36afe780461 ("usb: gadget: uvc: Add new enable_interrupt_ep attribute") was incorrect, resulting in a new warning when compiling the documentation. Correct the formatting to resolve the warning. Fixes: a36afe780461 ("usb: gadget: uvc: Add new enable_interrupt_ep attribute") Reported-by: Stephen Rothwell Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206151131.863960-1-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index fec205044c87..9c716dd3ae6f 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -17,12 +17,12 @@ Description: Control descriptors All attributes read only except enable_interrupt_ep: - ================ ============================= + =================== ============================= bInterfaceNumber USB interface number for this streaming interface enable_interrupt_ep flag to enable the interrupt endpoint for the VC interface - ================ ============================= + =================== ============================= What: /config/usb-gadget/gadget/functions/uvc.name/control/class Date: Dec 2014 -- cgit v1.2.3 From a7efe3fc7cbe27c6eb2c2a3ab612194f8f800f4c Mon Sep 17 00:00:00 2001 From: Mark Tomlinson Date: Tue, 7 Feb 2023 16:33:37 +1300 Subject: usb: max-3421: Fix setting of I/O pins To update the I/O pins, the registers are read/modified/written. The read operation incorrectly always read the first register. Although wrong, there wasn't any impact as all the output pins are always written, and the inputs are read only anyway. Fixes: 2d53139f3162 ("Add support for using a MAX3421E chip as a host driver.") Signed-off-by: Mark Tomlinson Link: https://lore.kernel.org/r/20230207033337.18112-1-mark.tomlinson@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/max3421-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 9a87056fc738..28d1524ee2fa 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1427,7 +1427,7 @@ max3421_spi_thread(void *dev_id) * use spi_wr_buf(). */ for (i = 0; i < ARRAY_SIZE(max3421_hcd->iopins); ++i) { - u8 val = spi_rd8(hcd, MAX3421_REG_IOPINS1); + u8 val = spi_rd8(hcd, MAX3421_REG_IOPINS1 + i); val = ((val & 0xf0) | (max3421_hcd->iopins[i] & 0x0f)); -- cgit v1.2.3 From b3c839bd8a07d303bc59a900d55dd35c7826562c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:52 +0000 Subject: usb: gadget: uvc: Make bSourceID read/write At the moment, the UVC function graph is hardcoded IT -> PU -> OT. To add XU support we need the ability to insert the XU descriptors into the chain. To facilitate that, make the output terminal's bSourceID attribute writeable so that we can configure its source. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-2-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 2 +- drivers/usb/gadget/function/uvc_configfs.c | 59 ++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index 9c716dd3ae6f..c2323f2b069b 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -54,7 +54,7 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Default output terminal descriptors - All attributes read only: + All attributes read only except bSourceID: ============== ============================================= iTerminal index of string descriptor diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index e2ffddb969fd..b52aae924d66 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -484,11 +484,68 @@ UVC_ATTR_RO(uvcg_default_output_, cname, aname) UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8); UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16); UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8); -UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, 8); UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8); #undef UVCG_DEFAULT_OUTPUT_ATTR +static ssize_t uvcg_default_output_b_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + mutex_lock(&opts->lock); + result = sprintf(page, "%u\n", le8_to_cpu(cd->bSourceID)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static ssize_t uvcg_default_output_b_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvc_output_terminal_descriptor *cd; + int result; + u8 num; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = group->cg_item.ci_parent->ci_parent-> + ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_output_terminal; + + result = kstrtou8(page, 0, &num); + if (result) + return result; + + mutex_lock(&opts->lock); + cd->bSourceID = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVC_ATTR(uvcg_default_output_, b_source_id, bSourceID); + static struct configfs_attribute *uvcg_default_output_attrs[] = { &uvcg_default_output_attr_b_terminal_id, &uvcg_default_output_attr_w_terminal_type, -- cgit v1.2.3 From 0df28607c5cb4fe60bba591e9858a8f7ba39aa4a Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:53 +0000 Subject: usb: gadget: uvc: Generalise helper functions for reuse The __uvcg_*frm_intrv() helper functions can be helpful when adding support for similar attributes. Generalise the functions and move them higher in the file for better coverage. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-3-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 120 ++++++++++++++++------------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index b52aae924d66..b32ecbdfd88d 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -47,6 +47,71 @@ static int uvcg_config_compare_u32(const void *l, const void *r) return li < ri ? -1 : li == ri ? 0 : 1; } +static inline int __uvcg_count_item_entries(char *buf, void *priv, unsigned int size) +{ + ++*((int *)priv); + return 0; +} + +static inline int __uvcg_fill_item_entries(char *buf, void *priv, unsigned int size) +{ + unsigned int num; + u8 **values; + int ret; + + ret = kstrtouint(buf, 0, &num); + if (ret) + return ret; + + if (num != (num & GENMASK((size * 8) - 1, 0))) + return -ERANGE; + + values = priv; + memcpy(*values, &num, size); + *values += size; + + return 0; +} + +static int __uvcg_iter_item_entries(const char *page, size_t len, + int (*fun)(char *, void *, unsigned int), + void *priv, unsigned int size) +{ + /* sign, base 2 representation, newline, terminator */ + unsigned int bufsize = 1 + size * 8 + 1 + 1; + const char *pg = page; + int i, ret = 0; + char *buf; + + if (!fun) + return -EINVAL; + + buf = kzalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + while (pg - page < len) { + i = 0; + while (i < sizeof(buf) && (pg - page < len) && + *pg != '\0' && *pg != '\n') + buf[i++] = *pg++; + if (i == sizeof(buf)) { + ret = -EINVAL; + goto out_free_buf; + } + while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) + ++pg; + buf[i] = '\0'; + ret = fun(buf, priv, size); + if (ret) + goto out_free_buf; + } + +out_free_buf: + kfree(buf); + return ret; +} + struct uvcg_config_group_type { struct config_item_type type; const char *name; @@ -1336,57 +1401,6 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, return result; } -static inline int __uvcg_count_frm_intrv(char *buf, void *priv) -{ - ++*((int *)priv); - return 0; -} - -static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) -{ - u32 num, **interv; - int ret; - - ret = kstrtou32(buf, 0, &num); - if (ret) - return ret; - - interv = priv; - **interv = num; - ++*interv; - - return 0; -} - -static int __uvcg_iter_frm_intrv(const char *page, size_t len, - int (*fun)(char *, void *), void *priv) -{ - /* sign, base 2 representation, newline, terminator */ - char buf[1 + sizeof(u32) * 8 + 1 + 1]; - const char *pg = page; - int i, ret; - - if (!fun) - return -EINVAL; - - while (pg - page < len) { - i = 0; - while (i < sizeof(buf) && (pg - page < len) && - *pg != '\0' && *pg != '\n') - buf[i++] = *pg++; - if (i == sizeof(buf)) - return -EINVAL; - while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) - ++pg; - buf[i] = '\0'; - ret = fun(buf, priv); - if (ret) - return ret; - } - - return 0; -} - static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, const char *page, size_t len) { @@ -1410,7 +1424,7 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, goto end; } - ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n); + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, sizeof(u32)); if (ret) goto end; @@ -1420,7 +1434,7 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, goto end; } - ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp); + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &tmp, sizeof(u32)); if (ret) { kfree(frm_intrv); goto end; -- cgit v1.2.3 From 0525210c9840229e42c6b68e886c72a75a67cf8e Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:54 +0000 Subject: usb: gadget: uvc: Allow definition of XUs in configfs The UVC gadget at present has no support for extension units. Add the infrastructure to uvc_configfs.c that allows users to create XUs via configfs. These will be stored in a new child of uvcg_control_grp_type with the name "extensions". Reported-by: kernel test robot Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-4-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 28 ++ drivers/usb/gadget/function/f_uvc.c | 9 + drivers/usb/gadget/function/u_uvc.h | 7 + drivers/usb/gadget/function/uvc_configfs.c | 480 ++++++++++++++++++++++ drivers/usb/gadget/function/uvc_configfs.h | 29 ++ 5 files changed, 553 insertions(+) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index c2323f2b069b..80b98a4a4d0f 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -113,6 +113,34 @@ Description: Default processing unit descriptors bUnitID a non-zero id of this unit =============== ======================================== +What: /config/usb-gadget/gadget/functions/uvc.name/control/extensions +Date: Nov 2022 +KernelVersion: 6.1 +Description: Extension unit descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/extensions/name +Date: Nov 2022 +KernelVersion: 6.1 +Description: Extension Unit (XU) Descriptor + + bLength, bUnitID and iExtension are read-only. All others are + read-write. + + ================= ======================================== + bLength size of the descriptor in bytes + bUnitID non-zero ID of this unit + guidExtensionCode Vendor-specific code identifying the XU + bNumControls number of controls in this XU + bNrInPins number of input pins for this unit + baSourceID list of the IDs of the units or terminals + to which this XU is connected + bControlSize size of the bmControls field in bytes + bmControls list of bitmaps detailing which vendor + specific controls are supported + iExtension index of a string descriptor that describes + this extension unit + ================= ======================================== + What: /config/usb-gadget/gadget/functions/uvc.name/control/header Date: Dec 2014 KernelVersion: 4.0 diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 835e121a806f..443333471b4d 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -865,6 +865,13 @@ static struct usb_function_instance *uvc_alloc_inst(void) od->bSourceID = 2; od->iTerminal = 0; + /* + * With the ability to add XUs to the UVC function graph, we need to be + * able to allocate unique unit IDs to them. The IDs are 1-based, with + * the CT, PU and OT above consuming the first 3. + */ + opts->last_unit_id = 3; + /* Prepare fs control class descriptors for configfs-based gadgets */ ctl_cls = opts->uvc_fs_control_cls; ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ @@ -885,6 +892,8 @@ static struct usb_function_instance *uvc_alloc_inst(void) opts->ss_control = (const struct uvc_descriptor_header * const *)ctl_cls; + INIT_LIST_HEAD(&opts->extension_units); + opts->streaming_interval = 1; opts->streaming_maxpacket = 1024; snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera"); diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 67cf319e9c2d..0345b8fc36ff 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -28,6 +28,7 @@ struct f_uvc_opts { unsigned int control_interface; unsigned int streaming_interface; char function_name[32]; + unsigned int last_unit_id; bool enable_interrupt_ep; @@ -65,6 +66,12 @@ struct f_uvc_opts { struct uvc_descriptor_header *uvc_fs_control_cls[5]; struct uvc_descriptor_header *uvc_ss_control_cls[5]; + /* + * Control descriptors for extension units. There could be any number + * of these, including none at all. + */ + struct list_head extension_units; + /* * Streaming descriptors for full-speed, high-speed and super-speed. * Used by configfs only, must not be touched by legacy gadgets. The diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index b32ecbdfd88d..c365f323af45 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -662,6 +662,485 @@ static const struct uvcg_config_group_type uvcg_terminal_grp_type = { }, }; +/* ----------------------------------------------------------------------------- + * control/extensions + */ + +#define UVCG_EXTENSION_ATTR(cname, aname, ro...) \ +static ssize_t uvcg_extension_##cname##_show(struct config_item *item, \ + char *page) \ +{ \ + struct config_group *group = to_config_group(item->ci_parent); \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ + struct uvcg_extension *xu = to_uvcg_extension(item); \ + struct config_item *opts_item; \ + struct f_uvc_opts *opts; \ + int ret; \ + \ + mutex_lock(su_mutex); \ + \ + opts_item = item->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + ret = sprintf(page, "%u\n", xu->desc.aname); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return ret; \ +} \ +UVC_ATTR##ro(uvcg_extension_, cname, aname) + +UVCG_EXTENSION_ATTR(b_length, bLength, _RO); +UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO); +UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO); + +static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + u8 num; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(&opts->lock); + xu->desc.bNumControls = num; + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return len; +} +UVCG_EXTENSION_ATTR(b_num_controls, bNumControls); + +/* + * In addition to storing bNrInPins, this function needs to realloc the + * memory for the baSourceID array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(&opts->lock); + + if (num == xu->desc.bNrInPins) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.baSourceID = tmp_buf; + xu->desc.bNrInPins = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins); + +/* + * In addition to storing bControlSize, this function needs to realloc the + * memory for the bmControls array and additionally expand bLength. + */ +static ssize_t uvcg_extension_b_control_size_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + void *tmp_buf; + int ret; + u8 num; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + + mutex_lock(&opts->lock); + + if (num == xu->desc.bControlSize) { + ret = len; + goto unlock; + } + + tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8), + GFP_KERNEL | __GFP_ZERO); + if (!tmp_buf) { + ret = -ENOMEM; + goto unlock; + } + + xu->desc.bmControls = tmp_buf; + xu->desc.bControlSize = num; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVCG_EXTENSION_ATTR(b_control_size, bControlSize); + +static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(xu->desc.guidExtensionCode); +} + +static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + int ret; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(xu->desc.guidExtensionCode, page, + min(sizeof(xu->desc.guidExtensionCode), len)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + ret = sizeof(xu->desc.guidExtensionCode); + + return ret; +} + +UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode); + +static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) { + ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *source_ids, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!source_ids) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(source_ids); + goto unlock; + } + + kfree(xu->desc.baSourceID); + xu->desc.baSourceID = source_ids; + xu->desc.bNrInPins = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} +UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID); + +static ssize_t uvcg_extension_bm_controls_show(struct config_item *item, + char *page) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + char *pg = page; + int ret, i; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) { + ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]); + pg = page + ret; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return ret; +} + +static ssize_t uvcg_extension_bm_controls_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item->ci_parent); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + u8 *bm_controls, *iter; + int ret, n = 0; + + mutex_lock(su_mutex); + + opts_item = item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n, + sizeof(u8)); + if (ret) + goto unlock; + + iter = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL); + if (!bm_controls) { + ret = -ENOMEM; + goto unlock; + } + + ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter, + sizeof(u8)); + if (ret) { + kfree(bm_controls); + goto unlock; + } + + kfree(xu->desc.bmControls); + xu->desc.bmControls = bm_controls; + xu->desc.bControlSize = n; + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins, + xu->desc.bControlSize); + + ret = len; + +unlock: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +UVC_ATTR(uvcg_extension_, bm_controls, bmControls); + +static struct configfs_attribute *uvcg_extension_attrs[] = { + &uvcg_extension_attr_b_length, + &uvcg_extension_attr_b_unit_id, + &uvcg_extension_attr_b_num_controls, + &uvcg_extension_attr_b_nr_in_pins, + &uvcg_extension_attr_b_control_size, + &uvcg_extension_attr_guid_extension_code, + &uvcg_extension_attr_ba_source_id, + &uvcg_extension_attr_bm_controls, + &uvcg_extension_attr_i_extension, + NULL, +}; + +static void uvcg_extension_release(struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + + kfree(xu); +} + +static struct configfs_item_operations uvcg_extension_item_ops = { + .release = uvcg_extension_release, +}; + +static const struct config_item_type uvcg_extension_type = { + .ct_item_ops = &uvcg_extension_item_ops, + .ct_attrs = uvcg_extension_attrs, + .ct_owner = THIS_MODULE, +}; + +static void uvcg_extension_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + config_item_put(item); + list_del(&xu->list); + kfree(xu->desc.baSourceID); + kfree(xu->desc.bmControls); + + mutex_unlock(&opts->lock); +} + +static struct config_item *uvcg_extension_make(struct config_group *group, const char *name) +{ + struct config_item *opts_item; + struct uvcg_extension *xu; + struct f_uvc_opts *opts; + + opts_item = group->cg_item.ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + xu = kzalloc(sizeof(*xu), GFP_KERNEL); + if (!xu) + return ERR_PTR(-ENOMEM); + + xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0); + xu->desc.bDescriptorType = USB_DT_CS_INTERFACE; + xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT; + xu->desc.bNumControls = 0; + xu->desc.bNrInPins = 0; + xu->desc.baSourceID = NULL; + xu->desc.bControlSize = 0; + xu->desc.bmControls = NULL; + + mutex_lock(&opts->lock); + + xu->desc.bUnitID = ++opts->last_unit_id; + + config_item_init_type_name(&xu->item, name, &uvcg_extension_type); + list_add_tail(&xu->list, &opts->extension_units); + + mutex_unlock(&opts->lock); + + return &xu->item; +} + +static struct configfs_group_operations uvcg_extensions_grp_ops = { + .make_item = uvcg_extension_make, + .drop_item = uvcg_extension_drop, +}; + +static const struct uvcg_config_group_type uvcg_extensions_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_extensions_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "extensions", +}; + /* ----------------------------------------------------------------------------- * control/class/{fs|ss} */ @@ -909,6 +1388,7 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = { &uvcg_processing_grp_type, &uvcg_terminal_grp_type, &uvcg_control_class_grp_type, + &uvcg_extensions_grp_type, NULL, }, }; diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 174ee691302b..5557813bcca9 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -142,6 +142,35 @@ static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt); } +/* ----------------------------------------------------------------------------- + * control/extensions/ + */ + +struct uvcg_extension_unit_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bUnitID; + u8 guidExtensionCode[16]; + u8 bNumControls; + u8 bNrInPins; + u8 *baSourceID; + u8 bControlSize; + u8 *bmControls; + u8 iExtension; +} __packed; + +struct uvcg_extension { + struct config_item item; + struct list_head list; + struct uvcg_extension_unit_descriptor desc; +}; + +static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item) +{ + return container_of(item, struct uvcg_extension, item); +} + int uvcg_attach_configfs(struct f_uvc_opts *opts); #endif /* UVC_CONFIGFS_H */ -- cgit v1.2.3 From a7289452699644b429fc82a9663b7a85bd0af51d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:55 +0000 Subject: usb: gadget: uvc: Copy XU descriptors during .bind() Now that extension unit support is available through configfs we need to copy the descriptors for the XUs during uvc_function_bind() so that they're exposed to the usb subsystem. Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-5-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 33 +++++++++++++++++++++++++++++++++ drivers/usb/gadget/function/uvc.h | 1 + 2 files changed, 34 insertions(+) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 443333471b4d..f6fd5decdcb7 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -478,6 +478,25 @@ uvc_register_video(struct uvc_device *uvc) } \ } while (0) +#define UVC_COPY_XU_DESCRIPTOR(mem, dst, desc) \ + do { \ + *(dst)++ = mem; \ + memcpy(mem, desc, 22); /* bLength to bNrInPins */ \ + mem += 22; \ + \ + memcpy(mem, (desc)->baSourceID, (desc)->bNrInPins); \ + mem += (desc)->bNrInPins; \ + \ + memcpy(mem, &(desc)->bControlSize, 1); \ + mem++; \ + \ + memcpy(mem, (desc)->bmControls, (desc)->bControlSize); \ + mem += (desc)->bControlSize; \ + \ + memcpy(mem, &(desc)->iExtension, 1); \ + mem++; \ + } while (0) + static struct usb_descriptor_header ** uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) { @@ -489,6 +508,7 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) const struct usb_descriptor_header * const *src; struct usb_descriptor_header **dst; struct usb_descriptor_header **hdr; + struct uvcg_extension *xu; unsigned int control_size; unsigned int streaming_size; unsigned int n_desc; @@ -556,6 +576,13 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) bytes += (*src)->bLength; n_desc++; } + + list_for_each_entry(xu, uvc->desc.extension_units, list) { + control_size += xu->desc.bLength; + bytes += xu->desc.bLength; + n_desc++; + } + for (src = (const struct usb_descriptor_header **)uvc_streaming_cls; *src; ++src) { streaming_size += (*src)->bLength; @@ -582,6 +609,10 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) uvc_control_header = mem; UVC_COPY_DESCRIPTORS(mem, dst, (const struct usb_descriptor_header **)uvc_control_desc); + + list_for_each_entry(xu, uvc->desc.extension_units, list) + UVC_COPY_XU_DESCRIPTOR(mem, dst, &xu->desc); + uvc_control_header->wTotalLength = cpu_to_le16(control_size); uvc_control_header->bInCollection = 1; uvc_control_header->baInterfaceNr[0] = uvc->streaming_intf; @@ -1025,6 +1056,8 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) return ERR_PTR(-EBUSY); } + uvc->desc.extension_units = &opts->extension_units; + ++opts->refcnt; mutex_unlock(&opts->lock); diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index daf226610f49..100475b1363e 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -143,6 +143,7 @@ struct uvc_device { const struct uvc_descriptor_header * const *fs_streaming; const struct uvc_descriptor_header * const *hs_streaming; const struct uvc_descriptor_header * const *ss_streaming; + struct list_head *extension_units; } desc; unsigned int control_intf; -- cgit v1.2.3 From 6e2a512d9532c51889a2601cba338c4673a09374 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:56 +0000 Subject: usb: gadget: configfs: Rename struct gadget_strings The struct gadget_strings really represents a single language in configfs. Rename it to make that more clear. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-6-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index e89aa2877a33..3beeafcf2e3b 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -86,7 +86,7 @@ static inline struct gadget_info *cfg_to_gadget_info(struct config_usb_cfg *cfg) return container_of(cfg->c.cdev, struct gadget_info, cdev); } -struct gadget_strings { +struct gadget_language { struct usb_gadget_strings stringtab_dev; struct usb_string strings[USB_GADGET_FIRST_AVAIL_IDX]; char *manufacturer; @@ -372,9 +372,9 @@ static struct configfs_attribute *gadget_root_attrs[] = { NULL, }; -static inline struct gadget_strings *to_gadget_strings(struct config_item *item) +static inline struct gadget_language *to_gadget_language(struct config_item *item) { - return container_of(to_config_group(item), struct gadget_strings, + return container_of(to_config_group(item), struct gadget_language, group); } @@ -768,20 +768,20 @@ static const struct config_item_type config_desc_type = { .ct_owner = THIS_MODULE, }; -GS_STRINGS_RW(gadget_strings, manufacturer); -GS_STRINGS_RW(gadget_strings, product); -GS_STRINGS_RW(gadget_strings, serialnumber); +GS_STRINGS_RW(gadget_language, manufacturer); +GS_STRINGS_RW(gadget_language, product); +GS_STRINGS_RW(gadget_language, serialnumber); -static struct configfs_attribute *gadget_strings_langid_attrs[] = { - &gadget_strings_attr_manufacturer, - &gadget_strings_attr_product, - &gadget_strings_attr_serialnumber, +static struct configfs_attribute *gadget_language_langid_attrs[] = { + &gadget_language_attr_manufacturer, + &gadget_language_attr_product, + &gadget_language_attr_serialnumber, NULL, }; -static void gadget_strings_attr_release(struct config_item *item) +static void gadget_language_attr_release(struct config_item *item) { - struct gadget_strings *gs = to_gadget_strings(item); + struct gadget_language *gs = to_gadget_language(item); kfree(gs->manufacturer); kfree(gs->product); @@ -791,8 +791,8 @@ static void gadget_strings_attr_release(struct config_item *item) kfree(gs); } -USB_CONFIG_STRING_RW_OPS(gadget_strings); -USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info); +USB_CONFIG_STRING_RW_OPS(gadget_language); +USB_CONFIG_STRINGS_LANG(gadget_language, gadget_info); static inline struct gadget_info *webusb_item_to_gadget_info( struct config_item *item) @@ -1472,7 +1472,7 @@ static int configfs_composite_bind(struct usb_gadget *gadget, /* init all strings */ if (!list_empty(&gi->string_list)) { - struct gadget_strings *gs; + struct gadget_language *gs; i = 0; list_for_each_entry(gs, &gi->string_list, list) { @@ -1761,7 +1761,7 @@ static struct config_group *gadgets_make( configfs_add_default_group(&gi->configs_group, &gi->group); config_group_init_type_name(&gi->strings_group, "strings", - &gadget_strings_strings_type); + &gadget_language_strings_type); configfs_add_default_group(&gi->strings_group, &gi->group); config_group_init_type_name(&gi->os_desc_group, "os_desc", -- cgit v1.2.3 From 15a7cf8caabee4613764abe7814dd3162cb64137 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:57 +0000 Subject: usb: gadget: configfs: Support arbitrary string descriptors Add a framework to allow users to define arbitrary string descriptors for a USB Gadget. This is modelled as a new type of config item rather than as hardcoded attributes so as to be as flexible as possible. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-7-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/gadget_configfs.rst | 10 ++ drivers/usb/gadget/configfs.c | 172 +++++++++++++++++++++++++++++++++- include/linux/usb/gadget.h | 11 +++ 3 files changed, 191 insertions(+), 2 deletions(-) diff --git a/Documentation/usb/gadget_configfs.rst b/Documentation/usb/gadget_configfs.rst index e4566ffb223f..868e118a2644 100644 --- a/Documentation/usb/gadget_configfs.rst +++ b/Documentation/usb/gadget_configfs.rst @@ -90,6 +90,16 @@ Then the strings can be specified:: $ echo > strings/0x409/manufacturer $ echo > strings/0x409/product +Further custom string descriptors can be created as directories within the +language's directory, with the string text being written to the "s" attribute +within the string's directory: + + $ mkdir strings/0x409/xu.0 + $ echo > strings/0x409/xu.0/s + +Where function drivers support it, functions may allow symlinks to these custom +string descriptors to associate those strings with class descriptors. + 2. Creating the configurations ------------------------------ diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 3beeafcf2e3b..ac275855eeb6 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -95,6 +95,8 @@ struct gadget_language { struct config_group group; struct list_head list; + struct list_head gadget_strings; + unsigned int nstrings; }; struct gadget_config_name { @@ -791,8 +793,174 @@ static void gadget_language_attr_release(struct config_item *item) kfree(gs); } -USB_CONFIG_STRING_RW_OPS(gadget_language); -USB_CONFIG_STRINGS_LANG(gadget_language, gadget_info); +static struct configfs_item_operations gadget_language_langid_item_ops = { + .release = gadget_language_attr_release, +}; + +static ssize_t gadget_string_id_show(struct config_item *item, char *page) +{ + struct gadget_string *string = to_gadget_string(item); + int ret; + + ret = sprintf(page, "%u\n", string->usb_string.id); + return ret; +} +CONFIGFS_ATTR_RO(gadget_string_, id); + +static ssize_t gadget_string_s_show(struct config_item *item, char *page) +{ + struct gadget_string *string = to_gadget_string(item); + int ret; + + ret = snprintf(page, sizeof(string->string), "%s\n", string->string); + return ret; +} + +static ssize_t gadget_string_s_store(struct config_item *item, const char *page, + size_t len) +{ + struct gadget_string *string = to_gadget_string(item); + int size = min(sizeof(string->string), len + 1); + int ret; + + if (len > USB_MAX_STRING_LEN) + return -EINVAL; + + ret = strscpy(string->string, page, size); + return len; +} +CONFIGFS_ATTR(gadget_string_, s); + +static struct configfs_attribute *gadget_string_attrs[] = { + &gadget_string_attr_id, + &gadget_string_attr_s, + NULL, +}; + +static void gadget_string_release(struct config_item *item) +{ + struct gadget_string *string = to_gadget_string(item); + + kfree(string); +} + +static struct configfs_item_operations gadget_string_item_ops = { + .release = gadget_string_release, +}; + +static const struct config_item_type gadget_string_type = { + .ct_item_ops = &gadget_string_item_ops, + .ct_attrs = gadget_string_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *gadget_language_string_make(struct config_group *group, + const char *name) +{ + struct gadget_language *language; + struct gadget_string *string; + + language = to_gadget_language(&group->cg_item); + + string = kzalloc(sizeof(*string), GFP_KERNEL); + if (!string) + return ERR_PTR(-ENOMEM); + + string->usb_string.id = language->nstrings++; + string->usb_string.s = string->string; + list_add_tail(&string->list, &language->gadget_strings); + + config_item_init_type_name(&string->item, name, &gadget_string_type); + + return &string->item; +} + +static void gadget_language_string_drop(struct config_group *group, + struct config_item *item) +{ + struct gadget_language *language; + struct gadget_string *string; + unsigned int i = USB_GADGET_FIRST_AVAIL_IDX; + + language = to_gadget_language(&group->cg_item); + string = to_gadget_string(item); + + list_del(&string->list); + language->nstrings--; + + /* Reset the ids for the language's strings to guarantee a continuous set */ + list_for_each_entry(string, &language->gadget_strings, list) + string->usb_string.id = i++; +} + +static struct configfs_group_operations gadget_language_langid_group_ops = { + .make_item = gadget_language_string_make, + .drop_item = gadget_language_string_drop, +}; + +static struct config_item_type gadget_language_type = { + .ct_item_ops = &gadget_language_langid_item_ops, + .ct_group_ops = &gadget_language_langid_group_ops, + .ct_attrs = gadget_language_langid_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *gadget_language_make(struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + struct gadget_language *gs; + struct gadget_language *new; + int langs = 0; + int ret; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return ERR_PTR(-ENOMEM); + + ret = check_user_usb_string(name, &new->stringtab_dev); + if (ret) + goto err; + config_group_init_type_name(&new->group, name, + &gadget_language_type); + + gi = container_of(group, struct gadget_info, strings_group); + ret = -EEXIST; + list_for_each_entry(gs, &gi->string_list, list) { + if (gs->stringtab_dev.language == new->stringtab_dev.language) + goto err; + langs++; + } + ret = -EOVERFLOW; + if (langs >= MAX_USB_STRING_LANGS) + goto err; + + list_add_tail(&new->list, &gi->string_list); + INIT_LIST_HEAD(&new->gadget_strings); + + /* We have the default manufacturer, product and serialnumber strings */ + new->nstrings = 3; + return &new->group; +err: + kfree(new); + return ERR_PTR(ret); +} + +static void gadget_language_drop(struct config_group *group, + struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations gadget_language_group_ops = { + .make_group = &gadget_language_make, + .drop_item = &gadget_language_drop, +}; + +static struct config_item_type gadget_language_strings_type = { + .ct_group_ops = &gadget_language_group_ops, + .ct_owner = THIS_MODULE, +}; static inline struct gadget_info *webusb_item_to_gadget_info( struct config_item *item) diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index dc3092cea99e..00750f7020f3 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -15,6 +15,7 @@ #ifndef __LINUX_USB_GADGET_H #define __LINUX_USB_GADGET_H +#include #include #include #include @@ -821,6 +822,16 @@ int usb_gadget_get_string(const struct usb_gadget_strings *table, int id, u8 *bu /* check if the given language identifier is valid */ bool usb_validate_langid(u16 langid); +struct gadget_string { + struct config_item item; + struct list_head list; + char string[USB_MAX_STRING_LEN]; + struct usb_string usb_string; +}; + +#define to_gadget_string(str_item)\ +container_of(str_item, struct gadget_string, item) + /*-------------------------------------------------------------------------*/ /* utility to simplify managing config descriptors */ -- cgit v1.2.3 From c033563220e0f7a82f4ae8d698284cced94fd6cf Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:58 +0000 Subject: usb: gadget: configfs: Attach arbitrary strings to cdev Attach any arbitrary strings that are defined to the composite dev. We handle the old-style manufacturer, product and serialnumbers strings in the same function for simplicity. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-8-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 93 +++++++++++++++++++++++++++++++++++-------- include/linux/usb/composite.h | 1 + 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index ac275855eeb6..06a0b73e0546 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1597,6 +1597,80 @@ static void purge_configs_funcs(struct gadget_info *gi) } } +static struct usb_string * +configfs_attach_gadget_strings(struct gadget_info *gi) +{ + struct usb_gadget_strings **gadget_strings; + struct gadget_language *language; + struct gadget_string *string; + unsigned int nlangs = 0; + struct list_head *iter; + struct usb_string *us; + unsigned int i = 0; + int nstrings = -1; + unsigned int j; + + list_for_each(iter, &gi->string_list) + nlangs++; + + /* Bail out early if no languages are configured */ + if (!nlangs) + return NULL; + + gadget_strings = kcalloc(nlangs + 1, /* including NULL terminator */ + sizeof(struct usb_gadget_strings *), GFP_KERNEL); + if (!gadget_strings) + return ERR_PTR(-ENOMEM); + + list_for_each_entry(language, &gi->string_list, list) { + struct usb_string *stringtab; + + if (nstrings == -1) { + nstrings = language->nstrings; + } else if (nstrings != language->nstrings) { + pr_err("languages must contain the same number of strings\n"); + us = ERR_PTR(-EINVAL); + goto cleanup; + } + + stringtab = kcalloc(language->nstrings + 1, sizeof(struct usb_string), + GFP_KERNEL); + if (!stringtab) { + us = ERR_PTR(-ENOMEM); + goto cleanup; + } + + stringtab[USB_GADGET_MANUFACTURER_IDX].id = USB_GADGET_MANUFACTURER_IDX; + stringtab[USB_GADGET_MANUFACTURER_IDX].s = language->manufacturer; + stringtab[USB_GADGET_PRODUCT_IDX].id = USB_GADGET_PRODUCT_IDX; + stringtab[USB_GADGET_PRODUCT_IDX].s = language->product; + stringtab[USB_GADGET_SERIAL_IDX].id = USB_GADGET_SERIAL_IDX; + stringtab[USB_GADGET_SERIAL_IDX].s = language->serialnumber; + + j = USB_GADGET_FIRST_AVAIL_IDX; + list_for_each_entry(string, &language->gadget_strings, list) { + memcpy(&stringtab[j], &string->usb_string, sizeof(struct usb_string)); + j++; + } + + language->stringtab_dev.strings = stringtab; + gadget_strings[i] = &language->stringtab_dev; + i++; + } + + us = usb_gstrings_attach(&gi->cdev, gadget_strings, nstrings); + +cleanup: + list_for_each_entry(language, &gi->string_list, list) { + kfree(language->stringtab_dev.strings); + language->stringtab_dev.strings = NULL; + } + + kfree(gadget_strings); + + return us; +} + static int configfs_composite_bind(struct usb_gadget *gadget, struct usb_gadget_driver *gdriver) { @@ -1640,22 +1714,7 @@ static int configfs_composite_bind(struct usb_gadget *gadget, /* init all strings */ if (!list_empty(&gi->string_list)) { - struct gadget_language *gs; - - i = 0; - list_for_each_entry(gs, &gi->string_list, list) { - - gi->gstrings[i] = &gs->stringtab_dev; - gs->stringtab_dev.strings = gs->strings; - gs->strings[USB_GADGET_MANUFACTURER_IDX].s = - gs->manufacturer; - gs->strings[USB_GADGET_PRODUCT_IDX].s = gs->product; - gs->strings[USB_GADGET_SERIAL_IDX].s = gs->serialnumber; - i++; - } - gi->gstrings[i] = NULL; - s = usb_gstrings_attach(&gi->cdev, gi->gstrings, - USB_GADGET_FIRST_AVAIL_IDX); + s = configfs_attach_gadget_strings(gi); if (IS_ERR(s)) { ret = PTR_ERR(s); goto err_comp_cleanup; @@ -1664,6 +1723,8 @@ static int configfs_composite_bind(struct usb_gadget *gadget, gi->cdev.desc.iManufacturer = s[USB_GADGET_MANUFACTURER_IDX].id; gi->cdev.desc.iProduct = s[USB_GADGET_PRODUCT_IDX].id; gi->cdev.desc.iSerialNumber = s[USB_GADGET_SERIAL_IDX].id; + + gi->cdev.usb_strings = s; } if (gi->use_webusb) { diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index 7ef8cea67f50..608dc962748b 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -494,6 +494,7 @@ struct usb_composite_dev { struct usb_composite_driver *driver; u8 next_string_id; char *def_manufacturer; + struct usb_string *usb_strings; /* the gadget driver won't enable the data pullup * while the deactivation count is nonzero. -- cgit v1.2.3 From 08ddd71bf020c3b713d343959df39bf8a48ddd0b Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:17:59 +0000 Subject: usb: gadget: uvc: Allow linking XUs to string descriptors Add .allow_link() and .drop_link() callbacks to allow users to link an extension unit descriptor to a string descriptor. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-9-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 52 ++++++++++++++++++++++++++++++ drivers/usb/gadget/function/uvc_configfs.h | 1 + 2 files changed, 53 insertions(+) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index c365f323af45..3ac27838514c 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1064,8 +1064,60 @@ static void uvcg_extension_release(struct config_item *item) kfree(xu); } +static int uvcg_extension_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *gadget_item; + struct gadget_string *string; + struct config_item *strings; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target of the link is an entry in strings/ */ + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent->ci_parent; + strings = config_group_find_item(to_config_group(gadget_item), "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + xu->string_descriptor_index = string->usb_string.id; + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvcg_extension_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvcg_extension *xu = to_uvcg_extension(src); + struct config_item *opts_item; + struct f_uvc_opts *opts; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + xu->string_descriptor_index = 0; + + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); +} + static struct configfs_item_operations uvcg_extension_item_ops = { .release = uvcg_extension_release, + .allow_link = uvcg_extension_allow_link, + .drop_link = uvcg_extension_drop_link, }; static const struct config_item_type uvcg_extension_type = { diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h index 5557813bcca9..c6a690158138 100644 --- a/drivers/usb/gadget/function/uvc_configfs.h +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -163,6 +163,7 @@ struct uvcg_extension_unit_descriptor { struct uvcg_extension { struct config_item item; struct list_head list; + u8 string_descriptor_index; struct uvcg_extension_unit_descriptor desc; }; -- cgit v1.2.3 From 9963f7440f4044bd4262d99fdd0a5827131bd934 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:18:00 +0000 Subject: usb: gadget: uvc: Pick up custom string descriptor IDs If any custom string descriptors have been linked to from the extension unit, pick up the string ID that was returned when the strings were attached to the composite dev and use it to set the iExtension field of the Extension Unit Descriptor. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-10-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index f6fd5decdcb7..7588ab21f952 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -644,6 +644,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct uvc_device *uvc = to_uvc(f); + struct uvcg_extension *xu; struct usb_string *us; unsigned int max_packet_mult; unsigned int max_packet_size; @@ -736,6 +737,14 @@ 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; + /* + * XUs can have an arbitrary string descriptor describing them. If they + * have one pick up the ID. + */ + list_for_each_entry(xu, &opts->extension_units, list) + if (xu->string_descriptor_index) + xu->desc.iExtension = cdev->usb_strings[xu->string_descriptor_index].id; + 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)); -- cgit v1.2.3 From fe625755370be6e3945c53bd0ffb4f4db0c4a73c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:18:01 +0000 Subject: usb: gadget: uvc: Allow linking function to string descs Currently the string descriptors for the IAD and VideoStreaming Interfaces are hardcoded into f_uvc. Now that we can create arbitrary string descriptors, add a mechanism to define string descriptors for the IAD, VC and VS interfaces by linking to the appropriate directory at function level. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-11-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_uvc.h | 8 ++++ drivers/usb/gadget/function/uvc_configfs.c | 60 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 0345b8fc36ff..1ce58f61253c 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -82,6 +82,14 @@ struct f_uvc_opts { struct uvc_descriptor_header **uvc_hs_streaming_cls; struct uvc_descriptor_header **uvc_ss_streaming_cls; + /* + * Indexes into the function's string descriptors allowing users to set + * custom descriptions rather than the hard-coded defaults. + */ + u8 iad_index; + u8 vs0_index; + u8 vs1_index; + /* * Read/write access to configfs attributes is handled by configfs. * diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 3ac27838514c..18c6a1461b7e 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -3174,8 +3174,68 @@ static void uvc_func_item_release(struct config_item *item) usb_put_function_instance(&opts->func_inst); } +static int uvc_func_allow_link(struct config_item *src, struct config_item *tgt) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct gadget_string *string; + struct config_item *strings; + struct f_uvc_opts *opts; + int ret = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + /* Validate that the target is an entry in strings/ */ + strings = config_group_find_item(to_config_group(src->ci_parent->ci_parent), + "strings"); + if (!strings || tgt->ci_parent->ci_parent != strings) { + ret = -EINVAL; + goto put_strings; + } + + string = to_gadget_string(tgt); + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = string->usb_string.id; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = string->usb_string.id; + else + ret = -EINVAL; + + mutex_unlock(&opts->lock); + +put_strings: + config_item_put(strings); + mutex_unlock(su_mutex); + + return ret; +} + +static void uvc_func_drop_link(struct config_item *src, struct config_item *tgt) +{ + struct f_uvc_opts *opts; + + opts = to_f_uvc_opts(src); + mutex_lock(&opts->lock); + + if (!strcmp(tgt->ci_name, "iad_desc")) + opts->iad_index = 0; + else if (!strcmp(tgt->ci_name, "vs0_desc")) + opts->vs0_index = 0; + else if (!strcmp(tgt->ci_name, "vs1_desc")) + opts->vs1_index = 0; + + mutex_unlock(&opts->lock); +} + static struct configfs_item_operations uvc_func_item_ops = { .release = uvc_func_item_release, + .allow_link = uvc_func_allow_link, + .drop_link = uvc_func_drop_link, }; #define UVCG_OPTS_ATTR(cname, aname, limit) \ -- cgit v1.2.3 From cf13d6e4a9aa6639c173fd630d82d586a2322ff9 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 6 Feb 2023 16:18:02 +0000 Subject: usb: gadget: uvc: Use custom strings if available If the user has defined a custom string descriptor for the IAD or the VideoStreaming interfaces then set their index field to point to the custom descriptor instead of the hardcoded defaults. If no custom descriptors have been linked to, then use the default ones. Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230206161802.892954-12-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 7588ab21f952..5e919fb65833 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -745,6 +745,10 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) if (xu->string_descriptor_index) xu->desc.iExtension = cdev->usb_strings[xu->string_descriptor_index].id; + /* + * We attach the hard-coded defaults incase the user does not provide + * any more appropriate strings through configfs. + */ 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)); @@ -752,11 +756,15 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) ret = PTR_ERR(us); goto error; } - uvc_iad.iFunction = us[UVC_STRING_CONTROL_IDX].id; - uvc_control_intf.iInterface = us[UVC_STRING_CONTROL_IDX].id; - ret = us[UVC_STRING_STREAMING_IDX].id; - uvc_streaming_intf_alt0.iInterface = ret; - uvc_streaming_intf_alt1.iInterface = ret; + + uvc_iad.iFunction = opts->iad_index ? cdev->usb_strings[opts->iad_index].id : + us[UVC_STRING_CONTROL_IDX].id; + uvc_streaming_intf_alt0.iInterface = opts->vs0_index ? + cdev->usb_strings[opts->vs0_index].id : + us[UVC_STRING_STREAMING_IDX].id; + uvc_streaming_intf_alt1.iInterface = opts->vs1_index ? + cdev->usb_strings[opts->vs1_index].id : + us[UVC_STRING_STREAMING_IDX].id; /* Allocate interface IDs. */ if ((ret = usb_interface_id(c, f)) < 0) -- cgit v1.2.3 From 8488a831e0c4d59528d20713a14cb8958af15bfe Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 9 Feb 2023 09:43:59 +0000 Subject: usb: gadget: configfs: Fix set but not used variable warning Fix a -Wunused-but-set-variable warning in gadget_string_s_store() Fixes: 15a7cf8caabe ("usb: gadget: configfs: Support arbitrary string descriptors") Reported-by: kernel test robot Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20230209094359.1549629-1-dan.scally@ideasonboard.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 06a0b73e0546..b9f1136aa0a2 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -821,13 +821,11 @@ static ssize_t gadget_string_s_store(struct config_item *item, const char *page, { struct gadget_string *string = to_gadget_string(item); int size = min(sizeof(string->string), len + 1); - int ret; if (len > USB_MAX_STRING_LEN) return -EINVAL; - ret = strscpy(string->string, page, size); - return len; + return strscpy(string->string, page, size); } CONFIGFS_ATTR(gadget_string_, s); -- cgit v1.2.3 From 553bd29700145e1849698985e9800f14e967da49 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 7 Feb 2023 12:05:29 +0100 Subject: of: device: Ignore modalias of reused nodes If of_node is reused, do not use that node's modalias. This will hide the name of the actual device. This is rather prominent in USB glue drivers creating a platform device for the host controller. Signed-off-by: Alexander Stein Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230207110531.1060252-2-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/of/device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/of/device.c b/drivers/of/device.c index c674a13c3055..3caaaf107076 100644 --- a/drivers/of/device.c +++ b/drivers/of/device.c @@ -256,7 +256,7 @@ static ssize_t of_device_get_modalias(struct device *dev, char *str, ssize_t len ssize_t csize; ssize_t tsize; - if ((!dev) || (!dev->of_node)) + if ((!dev) || (!dev->of_node) || dev->of_node_reused) return -ENODEV; /* Name & Type */ @@ -376,7 +376,7 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env) { int sl; - if ((!dev) || (!dev->of_node)) + if ((!dev) || (!dev->of_node) || dev->of_node_reused) return -ENODEV; /* Devicetree modalias is tricky, we add it in 2 steps */ -- cgit v1.2.3 From 2295bed9bebe8d1eef276194fed5b5fbe89c5363 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 7 Feb 2023 12:05:30 +0100 Subject: of: device: Do not ignore error code in of_device_uevent_modalias of_device_get_modalias might return an error code, propagate that one. Otherwise the negative, signed integer is propagated to unsigned integer for the comparison resulting in a huge 'sl' size. Signed-off-by: Alexander Stein Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230207110531.1060252-3-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/of/device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/of/device.c b/drivers/of/device.c index 3caaaf107076..8271793ef379 100644 --- a/drivers/of/device.c +++ b/drivers/of/device.c @@ -385,6 +385,8 @@ int of_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env) sl = of_device_get_modalias(dev, &env->buf[env->buflen-1], sizeof(env->buf) - env->buflen); + if (sl < 0) + return sl; if (sl >= (sizeof(env->buf) - env->buflen)) return -ENOMEM; env->buflen += sl; -- cgit v1.2.3 From e2ffae3ed92a9f768902c1cf82642c3a09cd0345 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Tue, 7 Feb 2023 12:05:31 +0100 Subject: usb: host: fsl-mph-dr-of: reuse device_set_of_node_from_dev This sets both of_node fields and takes a of_node reference as well. Fixes: bb160ee61c04 ("drivers/usb/host/ehci-fsl: Fix interrupt setup in host mode.") Signed-off-by: Alexander Stein Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20230207110531.1060252-4-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fsl-mph-dr-of.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/host/fsl-mph-dr-of.c b/drivers/usb/host/fsl-mph-dr-of.c index e5df17522892..46c6a152b865 100644 --- a/drivers/usb/host/fsl-mph-dr-of.c +++ b/drivers/usb/host/fsl-mph-dr-of.c @@ -112,8 +112,7 @@ static struct platform_device *fsl_usb2_device_register( goto error; } - pdev->dev.of_node = ofdev->dev.of_node; - pdev->dev.of_node_reused = true; + device_set_of_node_from_dev(&pdev->dev, &ofdev->dev); retval = platform_device_add(pdev); if (retval) -- cgit v1.2.3 From f87b564686ee47c480ccacc3922b38a8c54a6945 Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Tue, 7 Feb 2023 16:02:04 +0100 Subject: dt-bindings: usb: amlogic,meson-g12a-usb-ctrl: make G12A usb3-phy0 optional On the G12A USB complex, the USB3 PHY is shared with the PCIe controller, thus on designs without PCIe enabled the USB3 PHY entry can be ommited from the PHY list. Fixes: cdff2c946f06 ("dt-bindings: usb: amlogic,meson-g12a-usb-ctrl: add the Amlogic AXG Families USB Glue Bindings") Signed-off-by: Neil Armstrong Acked-by: Rob Herring Link: https://lore.kernel.org/r/20230207-b4-amlogic-g12a-usb-ctrl-bindings-fix-v1-1-c310293da7a2@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml index daf2a859418d..f38a2be07eda 100644 --- a/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml +++ b/Documentation/devicetree/bindings/usb/amlogic,meson-g12a-usb-ctrl.yaml @@ -108,6 +108,7 @@ allOf: then: properties: phy-names: + minItems: 2 items: - const: usb2-phy0 # USB2 PHY0 if USBHOST_A port is used - const: usb2-phy1 # USB2 PHY1 if USBOTG_B port is used -- cgit v1.2.3 From 77191db5ba7bd321fbbf4315675ee774a2b5a362 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 9 Feb 2023 16:43:45 +0300 Subject: xhci: host: potential NULL dereference in xhci_generic_plat_probe() It's possible to exit the loop with "sysdev" set to NULL. In that case we should use "&pdev->dev". Fixes: ec5499d338ec ("xhci: split out rcar/rz support from xhci-plat.c") Signed-off-by: Dan Carpenter Acked-by: Arnd Bergmann Link: https://lore.kernel.org/r/Y+T4kTcJwRwxNHJq@kili Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index cd17ccab6e00..b9f9625467d6 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -368,6 +368,9 @@ static int xhci_generic_plat_probe(struct platform_device *pdev) #endif } + if (!sysdev) + sysdev = &pdev->dev; + if (WARN_ON(!sysdev->dma_mask)) { /* Platform did not initialize dma_mask */ ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); -- cgit v1.2.3 From 7ebb605d2283fb2647b4fa82030307ce00bee436 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Mon, 13 Feb 2023 15:09:26 +0800 Subject: usb: gadget: uvc: fix missing mutex_unlock() if kstrtou8() fails If kstrtou8() fails, the mutex_unlock() is missed, move kstrtou8() before mutex_lock() to fix it up. Fixes: 0525210c9840 ("usb: gadget: uvc: Allow definition of XUs in configfs") Fixes: b3c839bd8a07 ("usb: gadget: uvc: Make bSourceID read/write") Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20230213070926.776447-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc_configfs.c | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 18c6a1461b7e..62b759bb7613 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -590,6 +590,10 @@ static ssize_t uvcg_default_output_b_source_id_store(struct config_item *item, int result; u8 num; + result = kstrtou8(page, 0, &num); + if (result) + return result; + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ opts_item = group->cg_item.ci_parent->ci_parent-> @@ -597,10 +601,6 @@ static ssize_t uvcg_default_output_b_source_id_store(struct config_item *item, opts = to_f_uvc_opts(opts_item); cd = &opts->uvc_output_terminal; - result = kstrtou8(page, 0, &num); - if (result) - return result; - mutex_lock(&opts->lock); cd->bSourceID = num; mutex_unlock(&opts->lock); @@ -707,15 +707,15 @@ static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item, int ret; u8 num; + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + mutex_lock(su_mutex); opts_item = item->ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); - ret = kstrtou8(page, 0, &num); - if (ret) - return ret; - mutex_lock(&opts->lock); xu->desc.bNumControls = num; mutex_unlock(&opts->lock); @@ -742,15 +742,15 @@ static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item, int ret; u8 num; + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + mutex_lock(su_mutex); opts_item = item->ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); - ret = kstrtou8(page, 0, &num); - if (ret) - return ret; - mutex_lock(&opts->lock); if (num == xu->desc.bNrInPins) { @@ -795,15 +795,15 @@ static ssize_t uvcg_extension_b_control_size_store(struct config_item *item, int ret; u8 num; + ret = kstrtou8(page, 0, &num); + if (ret) + return ret; + mutex_lock(su_mutex); opts_item = item->ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); - ret = kstrtou8(page, 0, &num); - if (ret) - return ret; - mutex_lock(&opts->lock); if (num == xu->desc.bControlSize) { -- cgit v1.2.3 From 5ec63fdbca604568890c577753c6f66c5b3ef0b5 Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Mon, 13 Feb 2023 23:00:38 +0530 Subject: usb: gadget: u_serial: Add null pointer check in gserial_resume Consider a case where gserial_disconnect has already cleared gser->ioport. And if a wakeup interrupt triggers afterwards, gserial_resume gets called, which will lead to accessing of gser->ioport and thus causing null pointer dereference.Add a null pointer check to prevent this. Added a static spinlock to prevent gser->ioport from becoming null after the newly added check. Fixes: aba3a8d01d62 ("usb: gadget: u_serial: add suspend resume callbacks") Cc: stable Signed-off-by: Prashanth K Acked-by: Alan Stern Link: https://lore.kernel.org/r/1676309438-14922-1-git-send-email-quic_prashk@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_serial.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 840626e064e1..a0ca47fbff0f 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -82,6 +82,9 @@ #define WRITE_BUF_SIZE 8192 /* TX only */ #define GS_CONSOLE_BUF_SIZE 8192 +/* Prevents race conditions while accessing gser->ioport */ +static DEFINE_SPINLOCK(serial_port_lock); + /* console info */ struct gs_console { struct console console; @@ -1375,8 +1378,10 @@ void gserial_disconnect(struct gserial *gser) if (!port) return; + spin_lock_irqsave(&serial_port_lock, flags); + /* tell the TTY glue not to do I/O here any more */ - spin_lock_irqsave(&port->port_lock, flags); + spin_lock(&port->port_lock); gs_console_disconnect(port); @@ -1391,7 +1396,8 @@ void gserial_disconnect(struct gserial *gser) tty_hangup(port->port.tty); } port->suspended = false; - spin_unlock_irqrestore(&port->port_lock, flags); + spin_unlock(&port->port_lock); + spin_unlock_irqrestore(&serial_port_lock, flags); /* disable endpoints, aborting down any active I/O */ usb_ep_disable(gser->out); @@ -1425,10 +1431,19 @@ EXPORT_SYMBOL_GPL(gserial_suspend); void gserial_resume(struct gserial *gser) { - struct gs_port *port = gser->ioport; + struct gs_port *port; unsigned long flags; - spin_lock_irqsave(&port->port_lock, flags); + spin_lock_irqsave(&serial_port_lock, flags); + port = gser->ioport; + + if (!port) { + spin_unlock_irqrestore(&serial_port_lock, flags); + return; + } + + spin_lock(&port->port_lock); + spin_unlock(&serial_port_lock); port->suspended = false; if (!port->start_delayed) { spin_unlock_irqrestore(&port->port_lock, flags); -- cgit v1.2.3 From 938fc645317632d79c048608689683b5437496ea Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Thu, 9 Feb 2023 12:53:18 +0000 Subject: usb: gadget: u_ether: Convert prints to device prints The USB ethernet gadget driver implements its own print macros which call printk. Device drivers should use the device prints that print the device name. Fortunately, the same macro names are defined in the header file 'linux/usb/composite.h' and these use the device prints. Therefore, remove the local definitions in the USB ethernet gadget driver and use those in 'linux/usb/composite.h'. The only difference is that now the device name is printed instead of the ethernet interface name. Tested using ethernet gadget on Jetson AGX Orin. Signed-off-by: Jon Hunter Tested-by: Jon Hunter Link: https://lore.kernel.org/r/20230209125319.18589-1-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_ether.c | 36 +---------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index e06022873df1..c8862a7820ce 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "u_ether.h" @@ -103,41 +104,6 @@ static inline int qlen(struct usb_gadget *gadget, unsigned qmult) /*-------------------------------------------------------------------------*/ -/* REVISIT there must be a better way than having two sets - * of debug calls ... - */ - -#undef DBG -#undef VDBG -#undef ERROR -#undef INFO - -#define xprintk(d, level, fmt, args...) \ - printk(level "%s: " fmt , (d)->net->name , ## args) - -#ifdef DEBUG -#undef DEBUG -#define DBG(dev, fmt, args...) \ - xprintk(dev , KERN_DEBUG , fmt , ## args) -#else -#define DBG(dev, fmt, args...) \ - do { } while (0) -#endif /* DEBUG */ - -#ifdef VERBOSE_DEBUG -#define VDBG DBG -#else -#define VDBG(dev, fmt, args...) \ - do { } while (0) -#endif /* DEBUG */ - -#define ERROR(dev, fmt, args...) \ - xprintk(dev , KERN_ERR , fmt , ## args) -#define INFO(dev, fmt, args...) \ - xprintk(dev , KERN_INFO , fmt , ## args) - -/*-------------------------------------------------------------------------*/ - /* NETWORK DRIVER HOOKUP (to the layer above this driver) */ static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) -- cgit v1.2.3 From 19ac99072e679f1e3807603206e3f3b1a7c14729 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Thu, 9 Feb 2023 12:53:19 +0000 Subject: usb: gadget: u_ether: Don't warn in gether_setup_name_default() The function gether_setup_name_default() is called by various USB ethernet gadget drivers. Calling this function will select a random host and device MAC addresses. A properly working driver should be silent and not warn the user about default MAC addresses selection. Given that the MAC addresses are also printed when the function gether_register_netdev() is called, remove these unnecessary warnings. Signed-off-by: Jon Hunter Reviewed-by: Andrzej Pietrasiewicz Link: https://lore.kernel.org/r/20230209125319.18589-2-jonathanh@nvidia.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_ether.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index c8862a7820ce..f259975dfba4 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -812,13 +812,11 @@ struct net_device *gether_setup_name_default(const char *netname) snprintf(net->name, sizeof(net->name), "%s%%d", netname); eth_random_addr(dev->dev_mac); - pr_warn("using random %s ethernet address\n", "self"); /* by default we always have a random MAC address */ net->addr_assign_type = NET_ADDR_RANDOM; eth_random_addr(dev->host_mac); - pr_warn("using random %s ethernet address\n", "host"); net->netdev_ops = ð_netdev_ops; -- cgit v1.2.3 From 8e5248c3a8778f3e394e9a19195bc7a48f567ca2 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Wed, 15 Feb 2023 15:27:11 +0200 Subject: usb: dwc3: pci: add support for the Intel Meteor Lake-M This patch adds the necessary PCI IDs for Intel Meteor Lake-M devices. Signed-off-by: Heikki Krogerus Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20230215132711.35668-1-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 89c9ab2b19f8..a23ddbb81979 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -47,6 +47,7 @@ #define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1 #define PCI_DEVICE_ID_INTEL_RPL 0xa70e #define PCI_DEVICE_ID_INTEL_RPLS 0x7a61 +#define PCI_DEVICE_ID_INTEL_MTLM 0x7eb1 #define PCI_DEVICE_ID_INTEL_MTLP 0x7ec1 #define PCI_DEVICE_ID_INTEL_MTL 0x7e7e #define PCI_DEVICE_ID_INTEL_TGL 0x9a15 @@ -467,6 +468,9 @@ static const struct pci_device_id dwc3_pci_id_table[] = { { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPLS), (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTLM), + (kernel_ulong_t) &dwc3_pci_intel_swnode, }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTLP), (kernel_ulong_t) &dwc3_pci_intel_swnode, }, -- cgit v1.2.3 From e4e7b2dc27c4bb877d850eaff69d41410b2f4237 Mon Sep 17 00:00:00 2001 From: Saranya Gopal Date: Tue, 14 Feb 2023 17:15:42 +0530 Subject: usb: typec: pd: Remove usb_suspend_supported sysfs from sink PDO As per USB PD specification, 28th bit of fixed supply sink PDO represents "higher capability" attribute and not "usb suspend supported" attribute. So, this patch removes the usb_suspend_supported attribute from sink PDO. Fixes: 662a60102c12 ("usb: typec: Separate USB Power Delivery from USB Type-C") Cc: stable Reported-by: Rajaram Regupathy Signed-off-by: Saranya Gopal Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230214114543.205103-1-saranya.gopal@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/pd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c index dc72005d68db..b5ab26422c34 100644 --- a/drivers/usb/typec/pd.c +++ b/drivers/usb/typec/pd.c @@ -161,7 +161,6 @@ static struct device_type source_fixed_supply_type = { static struct attribute *sink_fixed_supply_attrs[] = { &dev_attr_dual_role_power.attr, - &dev_attr_usb_suspend_supported.attr, &dev_attr_unconstrained_power.attr, &dev_attr_usb_communication_capable.attr, &dev_attr_dual_role_data.attr, -- cgit v1.2.3 From c620f4d5b25bcbb851daa1f88edc764cf5f29cb6 Mon Sep 17 00:00:00 2001 From: Saranya Gopal Date: Tue, 14 Feb 2023 17:15:43 +0530 Subject: usb: typec: pd: Add higher capability sysfs for sink PDO 28th bit of fixed supply sink PDO represents higher capability. When this bit is set, the sink device needs more than vsafe5V (eg: 12 V) to provide full functionality. This patch adds this higher capability sysfs interface for sink PDO. 28th bit of fixed supply source PDO represents usb_suspend_supported attribute. This usb_suspend_supported sysfs is already exposed for source PDOs. This patch adds 'source-capabilities' in usb_suspend_supported sysfs documentation for additional clarity. Signed-off-by: Saranya Gopal Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20230214114543.205103-2-saranya.gopal@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-usb_power_delivery | 11 ++++++++++- drivers/usb/typec/pd.c | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-usb_power_delivery b/Documentation/ABI/testing/sysfs-class-usb_power_delivery index ce2b1b563cb3..1bf9d1d7902c 100644 --- a/Documentation/ABI/testing/sysfs-class-usb_power_delivery +++ b/Documentation/ABI/testing/sysfs-class-usb_power_delivery @@ -69,7 +69,7 @@ Description: This file contains boolean value that tells does the device support both source and sink power roles. -What: /sys/class/usb_power_delivery/...//1:fixed_supply/usb_suspend_supported +What: /sys/class/usb_power_delivery/.../source-capabilities/1:fixed_supply/usb_suspend_supported Date: May 2022 Contact: Heikki Krogerus Description: @@ -78,6 +78,15 @@ Description: will follow the USB 2.0 and USB 3.2 rules for suspend and resume. +What: /sys/class/usb_power_delivery/.../sink-capabilities/1:fixed_supply/higher_capability +Date: February 2023 +Contact: Saranya Gopal +Description: + This file shows the value of the Higher capability bit in + vsafe5V Fixed Supply Object. If the bit is set, then the sink + needs more than vsafe5V(eg. 12 V) to provide full functionality. + Valid values: 0, 1 + What: /sys/class/usb_power_delivery/...//1:fixed_supply/unconstrained_power Date: May 2022 Contact: Heikki Krogerus diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c index b5ab26422c34..59c537a5e600 100644 --- a/drivers/usb/typec/pd.c +++ b/drivers/usb/typec/pd.c @@ -48,6 +48,13 @@ usb_suspend_supported_show(struct device *dev, struct device_attribute *attr, ch } static DEVICE_ATTR_RO(usb_suspend_supported); +static ssize_t +higher_capability_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%u\n", !!(to_pdo(dev)->pdo & PDO_FIXED_HIGHER_CAP)); +} +static DEVICE_ATTR_RO(higher_capability); + static ssize_t unconstrained_power_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -161,6 +168,7 @@ static struct device_type source_fixed_supply_type = { static struct attribute *sink_fixed_supply_attrs[] = { &dev_attr_dual_role_power.attr, + &dev_attr_higher_capability.attr, &dev_attr_unconstrained_power.attr, &dev_attr_usb_communication_capable.attr, &dev_attr_dual_role_data.attr, -- cgit v1.2.3 From e3eafcf0fabe67d0f854b238c0c247a4b0187e38 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 15 Feb 2023 18:52:39 +0200 Subject: usb: dwc3: xilinx: Remove unused of_gpio,h of_gpio.h provides a single function, which is not used in this driver. Remove unused header. Signed-off-by: Andy Shevchenko Reviewed-by: Michal Simek Link: https://lore.kernel.org/r/20230215165239.83806-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-xilinx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-xilinx.c b/drivers/usb/dwc3/dwc3-xilinx.c index 0745e9f11b2e..2c36f97652ca 100644 --- a/drivers/usb/dwc3/dwc3-xilinx.c +++ b/drivers/usb/dwc3/dwc3-xilinx.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include -- cgit v1.2.3