* [PATCH] usb: rtl8150: avoid using uninitialized CSCR value
From: Morduan Zang @ 2026-04-02 7:07 UTC (permalink / raw)
To: Petko Manolov
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, linux-usb, netdev, linux-kernel,
syzbot+9db6c624635564ad813c, Morduan Zang
Check get_registers() when reading CSCR in set_carrier().
If the control transfer fails, don't use the stack value.
Reported-by: syzbot+9db6c624635564ad813c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=9db6c624635564ad813c
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Morduan Zang <zhangdandan@uniontech.com>
---
drivers/net/usb/rtl8150.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/usb/rtl8150.c b/drivers/net/usb/rtl8150.c
index 4cda0643afb6..7e32726d3e6f 100644
--- a/drivers/net/usb/rtl8150.c
+++ b/drivers/net/usb/rtl8150.c
@@ -722,7 +722,11 @@ static void set_carrier(struct net_device *netdev)
rtl8150_t *dev = netdev_priv(netdev);
short tmp;
- get_registers(dev, CSCR, 2, &tmp);
+ if (get_registers(dev, CSCR, 2, &tmp) < 0) {
+ netif_carrier_off(netdev);
+ return;
+ }
+
if (tmp & CSCR_LINK_STATUS)
netif_carrier_on(netdev);
else
--
2.50.1
^ permalink raw reply related
* Re: [PATCH] usb: core: Fix bandwidth for devices with invalid wBytesPerInterval
From: Greg KH @ 2026-04-02 7:10 UTC (permalink / raw)
To: Xuetao (kirin); +Cc: linux-usb, linux-kernel, caiyadong, stable, stern
In-Reply-To: <c463f9ed-22ed-4ee6-b4fa-2933770e9c4c@huawei.com>
On Thu, Apr 02, 2026 at 02:59:35PM +0800, Xuetao (kirin) wrote:
>
>
> 在 2026/4/2 11:51, Greg KH 写道:
> > On Thu, Apr 02, 2026 at 10:14:00AM +0800, Tao Xue wrote:
> > > As specified in Section 4.14.2 of the xHCI Specification, the xHC
> > > reserves bandwidth for periodic endpoints according to bInterval and
> > > wBytesPerInterval (Max ESIT Payload).
> > >
> > > Some peripherals report an invalid wBytesPerInterval in their device
> > > descriptor, which is either 0 or smaller than the actual data length
> > > transmitted. This issue is observed on ASIX AX88179 series USB 3.0
> > > Ethernet adapters.
> > >
> > > These errors may lead to unexpected behavior on certain USB host
> > > controllers, causing USB peripherals to malfunction.
> > >
> > > To address the issue, return max(wBytesPerInterval, max_payload) when
> > > calculating bandwidth reservation.
> > >
> > > Fixes: 9238f25d5d32 ("USB: xhci: properly set endpoint context fields for periodic eps.")
> > > Cc: <stable@kernel.org>
> > > Signed-off-by: Tao Xue <xuetao09@huawei.com>
> > > ---
> > > drivers/usb/core/usb.c | 9 ++++++++-
> > > 1 file changed, 8 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
> > > index e9a10a33534c..8f2e05a5a015 100644
> > > --- a/drivers/usb/core/usb.c
> > > +++ b/drivers/usb/core/usb.c
> > > @@ -1125,6 +1125,8 @@ EXPORT_SYMBOL_GPL(usb_free_noncoherent);
> > > u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
> > > const struct usb_host_endpoint *ep)
> > > {
> > > + u32 max_payload;
> > > +
> > > if (!usb_endpoint_xfer_isoc(&ep->desc) &&
> > > !usb_endpoint_xfer_int(&ep->desc))
> > > return 0;
> > > @@ -1135,7 +1137,12 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
> > > return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
> > > fallthrough;
> > > case USB_SPEED_SUPER:
> > > - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
> > > + max_payload = usb_endpoint_maxp(&ep->desc) * (ep->ss_ep_comp.bMaxBurst + 1);
> > > + if (usb_endpoint_xfer_isoc(&ep->desc))
> > > + return max_t(u32, max_payload * USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
> > > + ep->ss_ep_comp.wBytesPerInterval);
> > > + else
> > > + return max_t(u32, max_payload, ep->ss_ep_comp.wBytesPerInterval);
> >
> > You dropped the conversion from le16 to cpu? Why?
> >
> > thanks,
> >
> > greg k-h
>
> Hi Greg,
>
> Thank you for the review.
>
> 1、You're right, that was an oversight. I should keep the le16_to_cpu().
> Here's the corrected version:
>
> max_payload = usb_endpoint_maxp(&ep->desc) * (ep->ss_ep_comp.bMaxBurst +
> 1);
> if (usb_endpoint_xfer_isoc(&ep->desc))
> return max_t(u32, max_payload *
> USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
> le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval));
> else
> return max_t(u32, max_payload,
> le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval));
That's hard to follow as it is line-wrapped, just fix it up for your
next version and I'll be glad to review it then.
> 2、Following Alan's suggestion in another email, should I check whether
> wBytesPerInterval is a valid value and handle it in the
> usb_parse_ss_endpoint_companion() ?
>
> However, when parsing the device descriptor, we do not know whether the
> actual data length transmitted by the peripheral is greater than
> wBytesPerInterval.
>
> Therefore, would it be sufficient to only add a check for whether
> wBytesPerInterval is 0 in the existing flow, and if it is 0, set
> wBytesPerInterval to cpu_to_le16(max_tx) by default?
No, don't override a value given by a device. Mark the descriptor as
invalid and fail attaching to the device.
> For example, modify it in the following way:
>
> if (le16_to_cpu(desc->wBytesPerInterval) > max_tx ||
> le16_to_cpu(desc->wBytesPerInterval) == 0) {
> dev_notice(ddev, "%s endpoint with wBytesPerInterval of %d in "
> "config %d interface %d altsetting %d ep %d: "
> "setting to %d\n",
> usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int",
> le16_to_cpu(desc->wBytesPerInterval),
> cfgno, inum, asnum, ep->desc.bEndpointAddress,
> max_tx);
> ep->ss_ep_comp.wBytesPerInterval = cpu_to_le16(max_tx);
> }
There's nothing a user can do with this type of error, and yet the
kernel is supposed to fix it up? We should just fail it and tell the
user then, like we do for other broken descriptors.
Did you find this issue with a real device, or is this just due to
fuzzing invalid descriptor values?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH] usb: core: Fix bandwidth for devices with invalid wBytesPerInterval
From: Xuetao (kirin) @ 2026-04-02 6:59 UTC (permalink / raw)
To: Greg KH; +Cc: linux-usb, linux-kernel, caiyadong, stable, stern
In-Reply-To: <2026040241-purveyor-bakery-a9f1@gregkh>
在 2026/4/2 11:51, Greg KH 写道:
> On Thu, Apr 02, 2026 at 10:14:00AM +0800, Tao Xue wrote:
>> As specified in Section 4.14.2 of the xHCI Specification, the xHC
>> reserves bandwidth for periodic endpoints according to bInterval and
>> wBytesPerInterval (Max ESIT Payload).
>>
>> Some peripherals report an invalid wBytesPerInterval in their device
>> descriptor, which is either 0 or smaller than the actual data length
>> transmitted. This issue is observed on ASIX AX88179 series USB 3.0
>> Ethernet adapters.
>>
>> These errors may lead to unexpected behavior on certain USB host
>> controllers, causing USB peripherals to malfunction.
>>
>> To address the issue, return max(wBytesPerInterval, max_payload) when
>> calculating bandwidth reservation.
>>
>> Fixes: 9238f25d5d32 ("USB: xhci: properly set endpoint context fields for periodic eps.")
>> Cc: <stable@kernel.org>
>> Signed-off-by: Tao Xue <xuetao09@huawei.com>
>> ---
>> drivers/usb/core/usb.c | 9 ++++++++-
>> 1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
>> index e9a10a33534c..8f2e05a5a015 100644
>> --- a/drivers/usb/core/usb.c
>> +++ b/drivers/usb/core/usb.c
>> @@ -1125,6 +1125,8 @@ EXPORT_SYMBOL_GPL(usb_free_noncoherent);
>> u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
>> const struct usb_host_endpoint *ep)
>> {
>> + u32 max_payload;
>> +
>> if (!usb_endpoint_xfer_isoc(&ep->desc) &&
>> !usb_endpoint_xfer_int(&ep->desc))
>> return 0;
>> @@ -1135,7 +1137,12 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
>> return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
>> fallthrough;
>> case USB_SPEED_SUPER:
>> - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
>> + max_payload = usb_endpoint_maxp(&ep->desc) * (ep->ss_ep_comp.bMaxBurst + 1);
>> + if (usb_endpoint_xfer_isoc(&ep->desc))
>> + return max_t(u32, max_payload * USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
>> + ep->ss_ep_comp.wBytesPerInterval);
>> + else
>> + return max_t(u32, max_payload, ep->ss_ep_comp.wBytesPerInterval);
>
> You dropped the conversion from le16 to cpu? Why?
>
> thanks,
>
> greg k-h
Hi Greg,
Thank you for the review.
1、You're right, that was an oversight. I should keep the le16_to_cpu().
Here's the corrected version:
max_payload = usb_endpoint_maxp(&ep->desc) *
(ep->ss_ep_comp.bMaxBurst + 1);
if (usb_endpoint_xfer_isoc(&ep->desc))
return max_t(u32, max_payload *
USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval));
else
return max_t(u32, max_payload,
le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval));
2、Following Alan's suggestion in another email, should I check whether
wBytesPerInterval is a valid value and handle it in the
usb_parse_ss_endpoint_companion() ?
However, when parsing the device descriptor, we do not know whether the
actual data length transmitted by the peripheral is greater than
wBytesPerInterval.
Therefore, would it be sufficient to only add a check for whether
wBytesPerInterval is 0 in the existing flow, and if it is 0, set
wBytesPerInterval to cpu_to_le16(max_tx) by default?
For example, modify it in the following way:
if (le16_to_cpu(desc->wBytesPerInterval) > max_tx ||
le16_to_cpu(desc->wBytesPerInterval) == 0) {
dev_notice(ddev, "%s endpoint with wBytesPerInterval of %d in "
"config %d interface %d altsetting %d ep %d: "
"setting to %d\n",
usb_endpoint_xfer_isoc(&ep->desc) ? "Isoc" : "Int",
le16_to_cpu(desc->wBytesPerInterval),
cfgno, inum, asnum, ep->desc.bEndpointAddress,
max_tx);
ep->ss_ep_comp.wBytesPerInterval = cpu_to_le16(max_tx);
}
Could you please give me some advice? Thanks.
^ permalink raw reply
* Re: [PATCH net-next 3/3] net: usb: smsc95xx: suspend PHY during USB suspend
From: Parthiban.Veerasooran @ 2026-04-02 5:18 UTC (permalink / raw)
To: oneukum
Cc: netdev, linux-usb, piergiorgio.beruto, andrew, hkallweit1, linux,
davem, edumazet, kuba, pabeni, steve.glendinning, UNGLinuxDriver
In-Reply-To: <8fe179bd-417d-4624-9e35-04c75978dfc8@suse.com>
Hi Oliver,
On 01/04/26 10:54 pm, Oliver Neukum wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know
> the content is safe
>
> Hi,
>
> On 01.04.26 05:18, Parthiban.Veerasooran@microchip.com wrote:
>
>> Thank you for pointing it out. I agree with you. I didn’t note it
>> earlier since the issue did not occur during my testing. I will move the
>> phy_suspend() to the appropriate place.
>
> Thank you.
>
>>> And, as a question of principle: Why do you suspend the phy
>>> in suspend(), but take no action in resume()?
>>
>> In resume(), I did not call phy_resume() because the resume path already
>> invokes phy_init_hw(), which internally calls
>> phydev->drv->config_init(). This reinitializes and reconfigures the PHY.
>
> Thank you for the explanation. May I suggest that you add a comment
> to that effect to the driver with your patch? This needs to be pointed
> out. Your code as such is fine. It just needs a comment.
Thank you for the suggestion. I agree and I'll add an explanatory
comment in the driver and update the patch accordingly.
Best regards,
Parthiban V
>
> Regards
> Oliver
>
>
^ permalink raw reply
* [PATCH v1 2/2] mfd: Add Host Interface (HIF) support for Nuvoton NCT6694
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20260402051442.1426672-1-a0282524688@gmail.com>
From: Ming Yu <a0282524688@gmail.com>
The Nuvoton NCT6694 also provides a Host Interface (HIF) via eSPI
to the host to access its features.
Sub-devices can use the common functions nct6694_read_msg() and
nct6694_write_msg() to issue a command. They can also request
interrupts that will be called when the HIF device triggers a
shared memory interrupt.
To support multiple transports, the driver configuration is
updated to allow selecting between the USB and HIF interfaces.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
MAINTAINERS | 1 +
drivers/gpio/gpio-nct6694.c | 7 -
drivers/hwmon/nct6694-hwmon.c | 21 -
drivers/i2c/busses/i2c-nct6694.c | 7 -
drivers/mfd/Kconfig | 47 +-
drivers/mfd/Makefile | 3 +-
drivers/mfd/nct6694-hif.c | 649 ++++++++++++++++++++++++++++
drivers/mfd/nct6694.c | 97 +++--
drivers/net/can/usb/nct6694_canfd.c | 6 -
drivers/rtc/rtc-nct6694.c | 7 -
drivers/watchdog/nct6694_wdt.c | 7 -
include/linux/mfd/nct6694.h | 51 ++-
12 files changed, 787 insertions(+), 116 deletions(-)
create mode 100644 drivers/mfd/nct6694-hif.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..7b6241faa6df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18899,6 +18899,7 @@ S: Supported
F: drivers/gpio/gpio-nct6694.c
F: drivers/hwmon/nct6694-hwmon.c
F: drivers/i2c/busses/i2c-nct6694.c
+F: drivers/mfd/nct6694-hif.c
F: drivers/mfd/nct6694.c
F: drivers/net/can/usb/nct6694_canfd.c
F: drivers/rtc/rtc-nct6694.c
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index 3703a61209e6..a279510ece89 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -12,13 +12,6 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-/*
- * USB command module type for NCT6694 GPIO controller.
- * This defines the module type used for communication with the NCT6694
- * GPIO controller over the USB interface.
- */
-#define NCT6694_GPIO_MOD 0xFF
-
#define NCT6694_GPIO_VER 0x90
#define NCT6694_GPIO_VALID 0x110
#define NCT6694_GPI_DATA 0x120
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
index 6dcf22ca5018..581451875f2c 100644
--- a/drivers/hwmon/nct6694-hwmon.c
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -15,13 +15,6 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
-/*
- * USB command module type for NCT6694 report channel
- * This defines the module type used for communication with the NCT6694
- * report channel over the USB interface.
- */
-#define NCT6694_RPT_MOD 0xFF
-
/* Report channel */
/*
* The report channel is used to report the status of the hardware monitor
@@ -38,13 +31,6 @@
#define NCT6694_TIN_STS(x) (0x6A + (x))
#define NCT6694_FIN_STS(x) (0x6E + (x))
-/*
- * USB command module type for NCT6694 HWMON controller.
- * This defines the module type used for communication with the NCT6694
- * HWMON controller over the USB interface.
- */
-#define NCT6694_HWMON_MOD 0x00
-
/* Command 00h - Hardware Monitor Control */
#define NCT6694_HWMON_CONTROL 0x00
#define NCT6694_HWMON_CONTROL_SEL 0x00
@@ -53,13 +39,6 @@
#define NCT6694_HWMON_ALARM 0x02
#define NCT6694_HWMON_ALARM_SEL 0x00
-/*
- * USB command module type for NCT6694 PWM controller.
- * This defines the module type used for communication with the NCT6694
- * PWM controller over the USB interface.
- */
-#define NCT6694_PWM_MOD 0x01
-
/* PWM Command - Manual Control */
#define NCT6694_PWM_CONTROL 0x01
#define NCT6694_PWM_CONTROL_SEL 0x00
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index 7d8ad997f6d2..7ee209a04d16 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -11,13 +11,6 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-/*
- * USB command module type for NCT6694 I2C controller.
- * This defines the module type used for communication with the NCT6694
- * I2C controller over the USB interface.
- */
-#define NCT6694_I2C_MOD 0x03
-
/* Command 00h - I2C Deliver */
#define NCT6694_I2C_DELIVER 0x00
#define NCT6694_I2C_DELIVER_SEL 0x00
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..8a715ec2f79f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1164,19 +1164,46 @@ config MFD_MENF21BMC
will be called menf21bmc.
config MFD_NCT6694
- tristate "Nuvoton NCT6694 support"
+ tristate
select MFD_CORE
+ help
+ Core MFD support for the Nuvoton NCT6694 peripheral expander.
+ This provides the common APIs and shared structures used by all
+ interfaces (USB, HIF) to access the NCT6694 hardware features
+ such as GPIO, I2C, CAN-FD, Watchdog, ADC, PWM, and RTC.
+
+ It is selected automatically by the transport interface drivers.
+
+config MFD_NCT6694_HIF
+ tristate "Nuvoton NCT6694 HIF (eSPI) interface support"
+ depends on HAS_IOPORT && ACPI
+ select MFD_NCT6694
+ select REGMAP_MMIO
+ help
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the Host Interface (HIF) using eSPI transport.
+
+ The transport driver uses Super-I/O mapping and shared memory to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 over an eSPI interface on an ACPI platform.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694-hif.
+
+config MFD_NCT6694_USB
+ tristate "Nuvoton NCT6694 USB interface support"
+ select MFD_NCT6694
depends on USB
help
- This enables support for the Nuvoton USB device NCT6694, which shares
- peripherals.
- The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
- 6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
- PWM, and RTC.
- This driver provides core APIs to access the NCT6694 hardware
- monitoring and control features.
- Additional drivers must be enabled to utilize the specific
- functionalities of the device.
+ This enables support for the Nuvoton NCT6694 peripheral expander
+ connected via the USB interface.
+
+ The transport driver uses USB bulk and interrupt transfers to
+ communicate with the NCT6694 firmware. Enable this option if you
+ are using the NCT6694 via a USB connection.
+
+ To compile this driver as a module, choose M here: the module
+ will be called nct6694.
config MFD_OCELOT
tristate "Microsemi Ocelot External Control Support"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..4cee9b74978c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -124,7 +124,8 @@ obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
-obj-$(CONFIG_MFD_NCT6694) += nct6694.o
+obj-$(CONFIG_MFD_NCT6694_HIF) += nct6694-hif.o
+obj-$(CONFIG_MFD_NCT6694_USB) += nct6694.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
diff --git a/drivers/mfd/nct6694-hif.c b/drivers/mfd/nct6694-hif.c
new file mode 100644
index 000000000000..a5953c951eb5
--- /dev/null
+++ b/drivers/mfd/nct6694-hif.c
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Nuvoton Technology Corp.
+ *
+ * Nuvoton NCT6694 host-interface (eSPI) transport driver.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/unaligned.h>
+
+#define DRVNAME "nct6694-hif"
+
+#define NCT6694_POLL_INTERVAL_US 10
+#define NCT6694_POLL_TIMEOUT_US 10000
+
+/*
+ * Super-I/O registers
+ */
+#define SIO_REG_LDSEL 0x07 /* Logical device select */
+#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
+#define SIO_REG_LD_SHM 0x0F /* Logical device shared memory control */
+
+#define SIO_REG_SHM_ENABLE 0x30 /* Enable shared memory */
+#define SIO_REG_SHM_BASE_ADDR 0x60 /* Shared memory base address (2 bytes) */
+#define SIO_REG_SHM_IRQ_NR 0x70 /* Shared memory interrupt number */
+
+#define SIO_REG_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
+#define SIO_REG_LOCK_KEY 0xAA /* Key to disable Super-I/O */
+
+#define SIO_NCT6694B_ID 0xD029
+#define SIO_NCT6694D_ID 0x5832
+
+/*
+ * Super-I/O Shared Memory Logical Device registers
+ */
+#define NCT6694_SHM_COFS_STS 0x2E
+#define NCT6694_SHM_COFS_STS_COFS4W BIT(7)
+
+#define NCT6694_SHM_COFS_CTL2 0x3B
+#define NCT6694_SHM_COFS_CTL2_COFS4W_IE BIT(3)
+
+#define NCT6694_SHM_INTR_STATUS 0x9C /* Interrupt status register (4 bytes) */
+
+enum nct6694_chips {
+ NCT6694B = 0,
+ NCT6694D,
+};
+
+enum nct6694_module_id {
+ NCT6694_GPIO0 = 0,
+ NCT6694_GPIO1,
+ NCT6694_GPIO2,
+ NCT6694_GPIO3,
+ NCT6694_GPIO4,
+ NCT6694_GPIO5,
+ NCT6694_GPIO6,
+ NCT6694_GPIO7,
+ NCT6694_GPIO8,
+ NCT6694_GPIO9,
+ NCT6694_GPIOA,
+ NCT6694_GPIOB,
+ NCT6694_GPIOC,
+ NCT6694_GPIOD,
+ NCT6694_GPIOE,
+ NCT6694_GPIOF,
+ NCT6694_I2C0,
+ NCT6694_I2C1,
+ NCT6694_I2C2,
+ NCT6694_I2C3,
+ NCT6694_I2C4,
+ NCT6694_I2C5,
+ NCT6694_CAN0,
+ NCT6694_CAN1,
+};
+
+struct __packed nct6694_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+ unsigned char *data;
+};
+
+struct nct6694_sio_data {
+ enum nct6694_chips chip;
+ int sioreg; /* Super-I/O index port */
+
+ /* Super-I/O access functions */
+ int (*sio_enter)(struct nct6694_sio_data *sio_data);
+ void (*sio_exit)(struct nct6694_sio_data *sio_data);
+ void (*sio_select)(struct nct6694_sio_data *sio_data, int ld);
+ int (*sio_inb)(struct nct6694_sio_data *sio_data, int reg);
+ int (*sio_inw)(struct nct6694_sio_data *sio_data, int reg);
+ void (*sio_outb)(struct nct6694_sio_data *sio_data, int reg, int val);
+};
+
+struct nct6694_hif_data {
+ struct regmap *regmap;
+ struct mutex msg_lock;
+ struct nct6694_sio_data *sio_data;
+ void __iomem *msg_base;
+ unsigned int shm_base;
+};
+
+static const char * const nct6694_chip_names[] = {
+ "NCT6694D",
+ "NCT6694B"
+};
+
+/*
+ * Super-I/O functions.
+ */
+static int superio_enter(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ /*
+ * Try to reserve <ioreg> and <ioreg + 1> for exclusive access.
+ */
+ if (!request_muxed_region(ioreg, 2, DRVNAME))
+ return -EBUSY;
+
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+ outb(SIO_REG_UNLOCK_KEY, ioreg);
+
+ return 0;
+}
+
+static void superio_exit(struct nct6694_sio_data *sio_data)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LOCK_KEY, ioreg);
+
+ release_region(ioreg, 2);
+}
+
+static void superio_select(struct nct6694_sio_data *sio_data, int ld)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(SIO_REG_LDSEL, ioreg);
+ outb(ld, ioreg + 1);
+}
+
+static int superio_inb(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ return inb(ioreg + 1);
+}
+
+static int superio_inw(struct nct6694_sio_data *sio_data, int reg)
+{
+ int ioreg = sio_data->sioreg;
+ int val;
+
+ outb(reg++, ioreg);
+ val = inb(ioreg + 1) << 8;
+ outb(reg, ioreg);
+ val |= inb(ioreg + 1);
+
+ return val;
+}
+
+static void superio_outb(struct nct6694_sio_data *sio_data, int reg, int val)
+{
+ int ioreg = sio_data->sioreg;
+
+ outb(reg, ioreg);
+ outb(val, ioreg + 1);
+}
+
+static int nct6694_sio_find(struct nct6694_sio_data *sio_data, u8 sioreg)
+{
+ int ret;
+ u16 devid;
+
+ sio_data->sioreg = sioreg;
+
+ ret = sio_data->sio_enter(sio_data);
+ if (ret)
+ return ret;
+
+ /* Check Chip ID */
+ devid = sio_data->sio_inw(sio_data, SIO_REG_DEVID);
+ switch (devid) {
+ case SIO_NCT6694B_ID:
+ sio_data->chip = NCT6694B;
+ break;
+ case SIO_NCT6694D_ID:
+ sio_data->chip = NCT6694D;
+ break;
+ default:
+ pr_err("Unsupported device 0x%04x\n", devid);
+ goto err;
+ }
+
+ pr_info("Found %s at %#x\n", nct6694_chip_names[sio_data->chip], sio_data->sioreg);
+
+ sio_data->sio_exit(sio_data);
+
+ return 0;
+
+err:
+ sio_data->sio_exit(sio_data);
+ return -ENODEV;
+}
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_gpio[] = {
+ { .adr = NCT6694_GPIO0 },
+ { .adr = NCT6694_GPIO1 },
+ { .adr = NCT6694_GPIO2 },
+ { .adr = NCT6694_GPIO3 },
+ { .adr = NCT6694_GPIO4 },
+ { .adr = NCT6694_GPIO5 },
+ { .adr = NCT6694_GPIO6 },
+ { .adr = NCT6694_GPIO7 },
+ { .adr = NCT6694_GPIO8 },
+ { .adr = NCT6694_GPIO9 },
+ { .adr = NCT6694_GPIOA },
+ { .adr = NCT6694_GPIOB },
+ { .adr = NCT6694_GPIOC },
+ { .adr = NCT6694_GPIOD },
+ { .adr = NCT6694_GPIOE },
+ { .adr = NCT6694_GPIOF },
+};
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_i2c[] = {
+ { .adr = NCT6694_I2C0 },
+ { .adr = NCT6694_I2C1 },
+ { .adr = NCT6694_I2C2 },
+ { .adr = NCT6694_I2C3 },
+ { .adr = NCT6694_I2C4 },
+ { .adr = NCT6694_I2C5 },
+};
+
+static const struct mfd_cell_acpi_match nct6694_acpi_match_can[] = {
+ { .adr = NCT6694_CAN0 },
+ { .adr = NCT6694_CAN1 },
+};
+
+static const struct mfd_cell nct6694_devs[] = {
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 0, &nct6694_acpi_match_gpio[0]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 1, &nct6694_acpi_match_gpio[1]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 2, &nct6694_acpi_match_gpio[2]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 3, &nct6694_acpi_match_gpio[3]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 4, &nct6694_acpi_match_gpio[4]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 5, &nct6694_acpi_match_gpio[5]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 6, &nct6694_acpi_match_gpio[6]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 7, &nct6694_acpi_match_gpio[7]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 8, &nct6694_acpi_match_gpio[8]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 9, &nct6694_acpi_match_gpio[9]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 10, &nct6694_acpi_match_gpio[10]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 11, &nct6694_acpi_match_gpio[11]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 12, &nct6694_acpi_match_gpio[12]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 13, &nct6694_acpi_match_gpio[13]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 14, &nct6694_acpi_match_gpio[14]),
+ MFD_CELL_ACPI("nct6694-gpio", NULL, NULL, 0, 15, &nct6694_acpi_match_gpio[15]),
+
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 0, &nct6694_acpi_match_i2c[0]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 1, &nct6694_acpi_match_i2c[1]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 2, &nct6694_acpi_match_i2c[2]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 3, &nct6694_acpi_match_i2c[3]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 4, &nct6694_acpi_match_i2c[4]),
+ MFD_CELL_ACPI("nct6694-i2c", NULL, NULL, 0, 5, &nct6694_acpi_match_i2c[5]),
+
+ MFD_CELL_ACPI("nct6694-canfd", NULL, NULL, 0, 0, &nct6694_acpi_match_can[0]),
+ MFD_CELL_ACPI("nct6694-canfd", NULL, NULL, 0, 1, &nct6694_acpi_match_can[1]),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+{
+ switch (err_status) {
+ case NCT6694_NO_ERROR:
+ return 0;
+ case NCT6694_NOT_SUPPORT_ERROR:
+ dev_err(nct6694->dev, "Command is not supported!\n");
+ break;
+ case NCT6694_NO_RESPONSE_ERROR:
+ dev_warn(nct6694->dev, "Command received no response!\n");
+ break;
+ case NCT6694_TIMEOUT_ERROR:
+ dev_warn(nct6694->dev, "Command timed out!\n");
+ break;
+ case NCT6694_PENDING:
+ dev_err(nct6694->dev, "Command is pending!\n");
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return -EIO;
+}
+
+static int nct6694_xfer_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ u8 hctrl, void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ void __iomem *hdr = hdata->msg_base + offsetof(struct nct6694_msg, cmd_header);
+ struct nct6694_cmd_header cmd = *cmd_hd;
+ struct nct6694_response_header resp;
+ u16 len = le16_to_cpu(cmd.len);
+ u8 status;
+ int ret;
+
+ guard(mutex)(&hdata->msg_lock);
+
+ /* Wait until the previous command is completed */
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ /*
+ * Write cmd header fields, but skip hctrl — writing to it triggers
+ * firmware command processing and must be deferred until data is ready.
+ */
+ memcpy_toio(hdr, &cmd, offsetof(struct nct6694_cmd_header, hctrl));
+ memcpy_toio(hdr + offsetof(struct nct6694_cmd_header, rsv2), &cmd.rsv2,
+ sizeof(cmd) - offsetof(struct nct6694_cmd_header, rsv2));
+
+ if (hctrl == NCT6694_HCTRL_SET && len)
+ memcpy_toio(hdata->msg_base + offsetof(struct nct6694_msg, data),
+ buf, len);
+
+ /* Write hctrl last to trigger command processing */
+ writeb(hctrl, hdr + offsetof(struct nct6694_cmd_header, hctrl));
+
+ ret = readb_poll_timeout(hdr + offsetof(struct nct6694_cmd_header, hctrl),
+ status, status == 0, NCT6694_POLL_INTERVAL_US,
+ NCT6694_POLL_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ memcpy_fromio(&resp, hdata->msg_base + offsetof(struct nct6694_msg, response_header),
+ sizeof(resp));
+
+ ret = nct6694_response_err_handling(nct6694, resp.sts);
+ if (ret)
+ return ret;
+
+ if (le16_to_cpu(resp.len))
+ memcpy_fromio(buf, hdata->msg_base + offsetof(struct nct6694_msg, data),
+ min(len, le16_to_cpu(resp.len)));
+
+ return 0;
+}
+
+/**
+ * nct6694_hif_read_msg() - Send a command and read response data via HIF
+ * @nct6694: NCT6694 device data
+ * @cmd_hd: command header
+ * @buf: buffer to store response data
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+static int nct6694_hif_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+
+ if (cmd_hd->mod == NCT6694_RPT_MOD)
+ return regmap_bulk_read(hdata->regmap,
+ le16_to_cpu(cmd_hd->offset),
+ buf, le16_to_cpu(cmd_hd->len));
+ return nct6694_xfer_msg(nct6694, cmd_hd, NCT6694_HCTRL_GET, buf);
+}
+
+/**
+ * nct6694_hif_write_msg() - Send a command with data payload via HIF
+ * @nct6694: NCT6694 device data
+ * @cmd_hd: command header
+ * @buf: buffer containing data to send
+ *
+ * Return: 0 on success or negative errno on failure.
+ */
+static int nct6694_hif_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+
+ if (cmd_hd->mod == NCT6694_RPT_MOD)
+ return regmap_bulk_write(hdata->regmap,
+ le16_to_cpu(cmd_hd->offset),
+ buf, le16_to_cpu(cmd_hd->len));
+ return nct6694_xfer_msg(nct6694, cmd_hd, NCT6694_HCTRL_SET, buf);
+}
+
+static const struct regmap_config nct6694_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+};
+
+static irqreturn_t nct6694_irq_handler(int irq, void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ u8 reg_data[4];
+ u32 intr_status;
+ int ret;
+
+ /* Check interrupt status is set */
+ if (!(inb(hdata->shm_base + NCT6694_SHM_COFS_STS) & NCT6694_SHM_COFS_STS_COFS4W))
+ return IRQ_NONE;
+
+ /* Clear interrupt status */
+ outb(NCT6694_SHM_COFS_STS_COFS4W, hdata->shm_base + NCT6694_SHM_COFS_STS);
+
+ ret = regmap_bulk_read(hdata->regmap, NCT6694_SHM_INTR_STATUS,
+ reg_data, ARRAY_SIZE(reg_data));
+ if (ret)
+ return IRQ_NONE;
+
+ intr_status = get_unaligned_le32(reg_data);
+
+ while (intr_status) {
+ int irq = __ffs(intr_status);
+
+ generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+ intr_status &= ~BIT(irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void nct6694_irq_release(void *data)
+{
+ struct nct6694 *nct6694 = data;
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ unsigned char cofs_ctl2;
+
+ /* Disable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 &= ~NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+}
+
+static int nct6694_irq_init(struct nct6694 *nct6694, int irq)
+{
+ struct nct6694_hif_data *hdata = nct6694->priv;
+ struct nct6694_sio_data *sio_data = hdata->sio_data;
+ unsigned char cofs_ctl2;
+
+ /* Set SIRQ number */
+ sio_data->sio_enter(sio_data);
+ sio_data->sio_select(sio_data, SIO_REG_LD_SHM);
+ if (!sio_data->sio_inb(sio_data, SIO_REG_SHM_ENABLE)) {
+ sio_data->sio_exit(sio_data);
+ return -EIO;
+ }
+ hdata->shm_base = sio_data->sio_inw(sio_data, SIO_REG_SHM_BASE_ADDR);
+
+ sio_data->sio_outb(sio_data, SIO_REG_SHM_IRQ_NR, irq);
+
+ sio_data->sio_exit(sio_data);
+
+ /* Enable SIRQ interrupt */
+ cofs_ctl2 = inb(hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+ cofs_ctl2 |= NCT6694_SHM_COFS_CTL2_COFS4W_IE;
+ outb(cofs_ctl2, hdata->shm_base + NCT6694_SHM_COFS_CTL2);
+
+ return 0;
+}
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+ struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+ guard(spinlock_irqsave)(&nct6694->irq_lock);
+
+ nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+ .name = "nct6694-irq",
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+ .irq_enable = nct6694_irq_enable,
+ .irq_disable = nct6694_irq_disable,
+};
+
+static void nct6694_irq_domain_remove(void *data)
+{
+ struct nct6694 *nct6694 = data;
+
+ irq_domain_remove(nct6694->domain);
+}
+
+static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+{
+ struct nct6694 *nct6694 = d->host_data;
+
+ irq_set_chip_data(irq, nct6694);
+ irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+ return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+ .map = nct6694_irq_domain_map,
+ .unmap = nct6694_irq_domain_unmap,
+};
+
+static const u8 sio_addrs[] = { 0x2e, 0x4e };
+
+static int nct6694_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct nct6694_sio_data *sio_data;
+ struct nct6694_hif_data *hdata;
+ struct nct6694 *data;
+ void __iomem *rpt_base, *msg_base;
+ int ret, i, irq;
+
+ sio_data = devm_kzalloc(dev, sizeof(*sio_data), GFP_KERNEL);
+ if (!sio_data)
+ return -ENOMEM;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ hdata = devm_kzalloc(dev, sizeof(*hdata), GFP_KERNEL);
+ if (!hdata)
+ return -ENOMEM;
+
+ rpt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rpt_base))
+ return PTR_ERR(rpt_base);
+ msg_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(msg_base))
+ return PTR_ERR(msg_base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ sio_data->sio_enter = superio_enter;
+ sio_data->sio_exit = superio_exit;
+ sio_data->sio_select = superio_select;
+ sio_data->sio_inb = superio_inb;
+ sio_data->sio_inw = superio_inw;
+ sio_data->sio_outb = superio_outb;
+
+ for (i = 0; i < ARRAY_SIZE(sio_addrs); i++) {
+ ret = nct6694_sio_find(sio_data, sio_addrs[i]);
+ if (!ret)
+ break;
+ }
+ if (ret)
+ return ret;
+
+ hdata->sio_data = sio_data;
+ hdata->msg_base = msg_base;
+ hdata->regmap = devm_regmap_init_mmio(dev, rpt_base,
+ &nct6694_regmap_config);
+ if (IS_ERR(hdata->regmap))
+ return PTR_ERR(hdata->regmap);
+
+ data->dev = dev;
+ data->priv = hdata;
+ data->read_msg = nct6694_hif_read_msg;
+ data->write_msg = nct6694_hif_write_msg;
+
+ spin_lock_init(&data->irq_lock);
+
+ data->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
+ &nct6694_irq_domain_ops,
+ data);
+ if (!data->domain)
+ return -ENODEV;
+
+ ret = devm_add_action_or_reset(dev, nct6694_irq_domain_remove, data);
+ if (ret)
+ return ret;
+
+ ret = nct6694_irq_init(data, irq);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, nct6694_irq_release, data);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ dev_name(dev), data);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &hdata->msg_lock);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, data);
+
+ return devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL);
+}
+
+static const struct acpi_device_id nct6694_acpi_ids[] = {
+ { "NTN0538", 0 },
+ {}
+};
+
+static struct platform_driver nct6694_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .acpi_match_table = nct6694_acpi_ids,
+ },
+ .probe = nct6694_probe,
+};
+module_platform_driver(nct6694_driver);
+
+MODULE_DESCRIPTION("Nuvoton NCT6694 host-interface transport driver");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 8ce2c4985aab..903a0a7f0694 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -21,6 +21,27 @@
#include <linux/spinlock.h>
#include <linux/usb.h>
+#define NCT6694_VENDOR_ID 0x0416
+#define NCT6694_PRODUCT_ID 0x200B
+#define NCT6694_INT_IN_EP 0x81
+#define NCT6694_BULK_IN_EP 0x02
+#define NCT6694_BULK_OUT_EP 0x03
+
+#define NCT6694_URB_TIMEOUT 1000
+
+union __packed nct6694_usb_msg {
+ struct nct6694_cmd_header cmd_header;
+ struct nct6694_response_header response_header;
+};
+
+struct nct6694_usb_data {
+ struct mutex access_lock;
+ struct urb *int_in_urb;
+ struct usb_device *udev;
+ union nct6694_usb_msg *usb_msg;
+ __le32 *int_buffer;
+};
+
static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
@@ -57,7 +78,8 @@ static const struct mfd_cell nct6694_devs[] = {
MFD_CELL_NAME("nct6694-rtc"),
};
-static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
+static int nct6694_usb_err_handling(struct nct6694 *nct6694,
+ unsigned char err_status)
{
switch (err_status) {
case NCT6694_NO_ERROR:
@@ -82,7 +104,7 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
}
/**
- * nct6694_read_msg() - Read message from NCT6694 device
+ * nct6694_usb_read_msg() - Read message from NCT6694 device via USB
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer to store the response data
@@ -93,13 +115,16 @@ static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+static int nct6694_usb_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
@@ -128,12 +153,11 @@ int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *c
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_read_msg);
/**
- * nct6694_write_msg() - Write message to NCT6694 device
+ * nct6694_usb_write_msg() - Write message to NCT6694 device via USB
* @nct6694: NCT6694 device pointer
* @cmd_hd: command header structure
* @buf: buffer containing the data to be sent
@@ -143,13 +167,16 @@ EXPORT_SYMBOL_GPL(nct6694_read_msg);
*
* Return: Negative value on error or 0 on success.
*/
-int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
+static int nct6694_usb_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
{
- union nct6694_usb_msg *msg = nct6694->usb_msg;
- struct usb_device *udev = nct6694->udev;
+ struct nct6694_usb_data *udata = nct6694->priv;
+ union nct6694_usb_msg *msg = udata->usb_msg;
+ struct usb_device *udev = udata->udev;
int tx_len, rx_len, ret;
- guard(mutex)(&nct6694->access_lock);
+ guard(mutex)(&udata->access_lock);
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
@@ -184,9 +211,8 @@ int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *
return -EIO;
}
- return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+ return nct6694_usb_err_handling(nct6694, msg->response_header.sts);
}
-EXPORT_SYMBOL_GPL(nct6694_write_msg);
static void usb_int_callback(struct urb *urb)
{
@@ -276,6 +302,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
struct usb_endpoint_descriptor *int_endpoint;
struct usb_host_interface *interface;
struct device *dev = &iface->dev;
+ struct nct6694_usb_data *udata;
struct nct6694 *nct6694;
int ret;
@@ -283,18 +310,28 @@ static int nct6694_usb_probe(struct usb_interface *iface,
if (!nct6694)
return -ENOMEM;
- nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
- if (!nct6694->usb_msg)
+ udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL);
+ if (!udata)
+ return -ENOMEM;
+
+ udata->usb_msg = devm_kzalloc(dev, sizeof(*udata->usb_msg), GFP_KERNEL);
+ if (!udata->usb_msg)
return -ENOMEM;
- nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
- if (!nct6694->int_buffer)
+ udata->int_buffer = devm_kzalloc(dev, sizeof(*udata->int_buffer), GFP_KERNEL);
+ if (!udata->int_buffer)
return -ENOMEM;
- nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!nct6694->int_in_urb)
+ udata->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!udata->int_in_urb)
return -ENOMEM;
+ udata->udev = udev;
+
+ nct6694->priv = udata;
+ nct6694->read_msg = nct6694_usb_read_msg;
+ nct6694->write_msg = nct6694_usb_write_msg;
+
nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
&nct6694_irq_domain_ops,
nct6694);
@@ -304,11 +341,10 @@ static int nct6694_usb_probe(struct usb_interface *iface,
}
nct6694->dev = dev;
- nct6694->udev = udev;
spin_lock_init(&nct6694->irq_lock);
- ret = devm_mutex_init(dev, &nct6694->access_lock);
+ ret = devm_mutex_init(dev, &udata->access_lock);
if (ret)
goto err_irq_domain;
@@ -320,11 +356,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
goto err_irq_domain;
}
- usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
- nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
+ usb_fill_int_urb(udata->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
+ udata->int_buffer, sizeof(*udata->int_buffer), usb_int_callback,
nct6694, int_endpoint->bInterval);
- ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+ ret = usb_submit_urb(udata->int_in_urb, GFP_KERNEL);
if (ret)
goto err_irq_domain;
@@ -337,21 +373,22 @@ static int nct6694_usb_probe(struct usb_interface *iface,
return 0;
err_mfd:
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->int_in_urb);
err_irq_domain:
irq_domain_remove(nct6694->domain);
err_urb:
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(udata->int_in_urb);
return ret;
}
static void nct6694_usb_disconnect(struct usb_interface *iface)
{
struct nct6694 *nct6694 = usb_get_intfdata(iface);
+ struct nct6694_usb_data *udata = nct6694->priv;
- usb_kill_urb(nct6694->int_in_urb);
+ usb_kill_urb(udata->int_in_urb);
irq_domain_remove(nct6694->domain);
- usb_free_urb(nct6694->int_in_urb);
+ usb_free_urb(udata->int_in_urb);
}
static const struct usb_device_id nct6694_ids[] = {
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index 29282c56430f..05db00455f63 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -17,12 +17,6 @@
#define DEVICE_NAME "nct6694-canfd"
-/* USB command module type for NCT6694 CANfd controller.
- * This defines the module type used for communication with the NCT6694
- * CANfd controller over the USB interface.
- */
-#define NCT6694_CANFD_MOD 0x05
-
/* Command 00h - CAN Setting and Initialization */
#define NCT6694_CANFD_SETTING 0x00
#define NCT6694_CANFD_SETTING_ACTIVE_CTRL1 BIT(0)
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
index 35401a0d9cf5..c06902f150c9 100644
--- a/drivers/rtc/rtc-nct6694.c
+++ b/drivers/rtc/rtc-nct6694.c
@@ -14,13 +14,6 @@
#include <linux/rtc.h>
#include <linux/slab.h>
-/*
- * USB command module type for NCT6694 RTC controller.
- * This defines the module type used for communication with the NCT6694
- * RTC controller over the USB interface.
- */
-#define NCT6694_RTC_MOD 0x08
-
/* Command 00h - RTC Time */
#define NCT6694_RTC_TIME 0x0000
#define NCT6694_RTC_TIME_SEL 0x00
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
index 2b4b804a1739..847d8f1d1830 100644
--- a/drivers/watchdog/nct6694_wdt.c
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -19,13 +19,6 @@
#define NCT6694_WDT_MAX_DEVS 2
-/*
- * USB command module type for NCT6694 WDT controller.
- * This defines the module type used for communication with the NCT6694
- * WDT controller over the USB interface.
- */
-#define NCT6694_WDT_MOD 0x07
-
/* Command 00h - WDT Setup */
#define NCT6694_WDT_SETUP 0x00
#define NCT6694_WDT_SETUP_SEL(idx) (idx ? 0x01 : 0x00)
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 496da72949d9..ff0814dc82d4 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -2,7 +2,8 @@
/*
* Copyright (C) 2025 Nuvoton Technology Corp.
*
- * Nuvoton NCT6694 USB transaction and data structure.
+ * Nuvoton NCT6694 core definitions shared by all transport drivers
+ * and sub-device drivers.
*/
#ifndef __MFD_NCT6694_H
@@ -12,16 +13,17 @@
#include <linux/spinlock.h>
#include <linux/types.h>
-#define NCT6694_VENDOR_ID 0x0416
-#define NCT6694_PRODUCT_ID 0x200B
-#define NCT6694_INT_IN_EP 0x81
-#define NCT6694_BULK_IN_EP 0x02
-#define NCT6694_BULK_OUT_EP 0x03
-
#define NCT6694_HCTRL_SET 0x40
#define NCT6694_HCTRL_GET 0x80
-#define NCT6694_URB_TIMEOUT 1000
+#define NCT6694_HWMON_MOD 0x00
+#define NCT6694_PWM_MOD 0x01
+#define NCT6694_I2C_MOD 0x03
+#define NCT6694_CANFD_MOD 0x05
+#define NCT6694_WDT_MOD 0x07
+#define NCT6694_RTC_MOD 0x08
+#define NCT6694_RPT_MOD 0xFF
+#define NCT6694_GPIO_MOD NCT6694_RPT_MOD
enum nct6694_irq_id {
NCT6694_IRQ_GPIO0 = 0,
@@ -79,24 +81,33 @@ struct __packed nct6694_response_header {
__le16 len;
};
-union __packed nct6694_usb_msg {
- struct nct6694_cmd_header cmd_header;
- struct nct6694_response_header response_header;
-};
-
struct nct6694 {
struct device *dev;
struct irq_domain *domain;
- struct mutex access_lock;
spinlock_t irq_lock;
- struct urb *int_in_urb;
- struct usb_device *udev;
- union nct6694_usb_msg *usb_msg;
- __le32 *int_buffer;
unsigned int irq_enable;
+
+ void *priv;
+ int (*read_msg)(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
+ int (*write_msg)(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf);
};
-int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
-int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
+static inline int nct6694_read_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694->read_msg(nct6694, cmd_hd, buf);
+}
+
+static inline int nct6694_write_msg(struct nct6694 *nct6694,
+ const struct nct6694_cmd_header *cmd_hd,
+ void *buf)
+{
+ return nct6694->write_msg(nct6694, cmd_hd, buf);
+}
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v1 1/2] mfd: nct6694: Switch to devm_mfd_add_devices() and drop IDA
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20260402051442.1426672-1-a0282524688@gmail.com>
From: Ming Yu <a0282524688@gmail.com>
Currently, the nct6694 core driver uses mfd_add_hotplug_devices()
and an IDA to manage subdevice IDs.
Switch the core implementation to use the managed
devm_mfd_add_devices() API, which simplifies the error handling and
device lifecycle management. Concurrently, drop the custom IDA
implementation and transition to using pdev->id.
Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
drivers/gpio/gpio-nct6694.c | 19 +------
drivers/i2c/busses/i2c-nct6694.c | 19 +------
drivers/mfd/nct6694.c | 83 ++++++++++++-----------------
drivers/net/can/usb/nct6694_canfd.c | 12 +----
drivers/watchdog/nct6694_wdt.c | 20 +------
include/linux/mfd/nct6694.h | 8 +--
6 files changed, 43 insertions(+), 118 deletions(-)
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
index a8607f0d9915..3703a61209e6 100644
--- a/drivers/gpio/gpio-nct6694.c
+++ b/drivers/gpio/gpio-nct6694.c
@@ -7,7 +7,6 @@
#include <linux/bits.h>
#include <linux/gpio/driver.h>
-#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -381,14 +380,6 @@ static void nct6694_irq_dispose_mapping(void *d)
irq_dispose_mapping(data->irq);
}
-static void nct6694_gpio_ida_free(void *d)
-{
- struct nct6694_gpio_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->gpio_ida, data->group);
-}
-
static int nct6694_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -403,15 +394,7 @@ static int nct6694_gpio_probe(struct platform_device *pdev)
return -ENOMEM;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->gpio_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->group = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_gpio_ida_free, data);
- if (ret)
- return ret;
+ data->group = pdev->id;
names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
GFP_KERNEL);
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
index 1413ab6f9462..7d8ad997f6d2 100644
--- a/drivers/i2c/busses/i2c-nct6694.c
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -6,7 +6,6 @@
*/
#include <linux/i2c.h>
-#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -134,14 +133,6 @@ static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data)
return 0;
}
-static void nct6694_i2c_ida_free(void *d)
-{
- struct nct6694_i2c_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->i2c_ida, data->port);
-}
-
static int nct6694_i2c_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -155,15 +146,7 @@ static int nct6694_i2c_probe(struct platform_device *pdev)
data->dev = dev;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->port = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_i2c_ida_free, data);
- if (ret)
- return ret;
+ data->port = pdev->id;
ret = nct6694_i2c_set_baudrate(data);
if (ret)
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
index 308b2fda3055..8ce2c4985aab 100644
--- a/drivers/mfd/nct6694.c
+++ b/drivers/mfd/nct6694.c
@@ -11,7 +11,6 @@
#include <linux/bits.h>
#include <linux/interrupt.h>
-#include <linux/idr.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
@@ -23,35 +22,35 @@
#include <linux/usb.h>
static const struct mfd_cell nct6694_devs[] = {
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
- MFD_CELL_NAME("nct6694-gpio"),
-
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
- MFD_CELL_NAME("nct6694-i2c"),
-
- MFD_CELL_NAME("nct6694-canfd"),
- MFD_CELL_NAME("nct6694-canfd"),
-
- MFD_CELL_NAME("nct6694-wdt"),
- MFD_CELL_NAME("nct6694-wdt"),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 2),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 3),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 4),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 5),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 6),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 7),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 8),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 9),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 10),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 11),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 12),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 13),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 14),
+ MFD_CELL_BASIC("nct6694-gpio", NULL, NULL, 0, 15),
+
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 2),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 3),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 4),
+ MFD_CELL_BASIC("nct6694-i2c", NULL, NULL, 0, 5),
+
+ MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-canfd", NULL, NULL, 0, 1),
+
+ MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 0),
+ MFD_CELL_BASIC("nct6694-wdt", NULL, NULL, 0, 1),
MFD_CELL_NAME("nct6694-hwmon"),
@@ -307,23 +306,18 @@ static int nct6694_usb_probe(struct usb_interface *iface,
nct6694->dev = dev;
nct6694->udev = udev;
- ida_init(&nct6694->gpio_ida);
- ida_init(&nct6694->i2c_ida);
- ida_init(&nct6694->canfd_ida);
- ida_init(&nct6694->wdt_ida);
-
spin_lock_init(&nct6694->irq_lock);
ret = devm_mutex_init(dev, &nct6694->access_lock);
if (ret)
- goto err_ida;
+ goto err_irq_domain;
interface = iface->cur_altsetting;
int_endpoint = &interface->endpoint[0].desc;
if (!usb_endpoint_is_int_in(int_endpoint)) {
ret = -ENODEV;
- goto err_ida;
+ goto err_irq_domain;
}
usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
@@ -332,11 +326,11 @@ static int nct6694_usb_probe(struct usb_interface *iface,
ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
if (ret)
- goto err_ida;
+ goto err_irq_domain;
usb_set_intfdata(iface, nct6694);
- ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
+ ret = devm_mfd_add_devices(dev, 0, nct6694_devs, ARRAY_SIZE(nct6694_devs), NULL, 0, NULL);
if (ret)
goto err_mfd;
@@ -344,11 +338,7 @@ static int nct6694_usb_probe(struct usb_interface *iface,
err_mfd:
usb_kill_urb(nct6694->int_in_urb);
-err_ida:
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
+err_irq_domain:
irq_domain_remove(nct6694->domain);
err_urb:
usb_free_urb(nct6694->int_in_urb);
@@ -359,12 +349,7 @@ static void nct6694_usb_disconnect(struct usb_interface *iface)
{
struct nct6694 *nct6694 = usb_get_intfdata(iface);
- mfd_remove_devices(nct6694->dev);
usb_kill_urb(nct6694->int_in_urb);
- ida_destroy(&nct6694->wdt_ida);
- ida_destroy(&nct6694->canfd_ida);
- ida_destroy(&nct6694->i2c_ida);
- ida_destroy(&nct6694->gpio_ida);
irq_domain_remove(nct6694->domain);
usb_free_urb(nct6694->int_in_urb);
}
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
index e5f7f8849a73..29282c56430f 100644
--- a/drivers/net/can/usb/nct6694_canfd.c
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -8,7 +8,6 @@
#include <linux/can/dev.h>
#include <linux/can/rx-offload.h>
#include <linux/ethtool.h>
-#include <linux/idr.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
@@ -725,15 +724,13 @@ static int nct6694_canfd_probe(struct platform_device *pdev)
struct net_device *ndev;
int port, irq, ret, can_clk;
- port = ida_alloc(&nct6694->canfd_ida, GFP_KERNEL);
- if (port < 0)
- return port;
+ port = pdev->id;
irq = irq_create_mapping(nct6694->domain,
NCT6694_IRQ_CAN0 + port);
if (!irq) {
ret = -EINVAL;
- goto free_ida;
+ return ret;
}
ndev = alloc_candev(sizeof(struct nct6694_canfd_priv), 1);
@@ -796,24 +793,19 @@ static int nct6694_canfd_probe(struct platform_device *pdev)
free_candev(ndev);
dispose_irq:
irq_dispose_mapping(irq);
-free_ida:
- ida_free(&nct6694->canfd_ida, port);
return ret;
}
static void nct6694_canfd_remove(struct platform_device *pdev)
{
struct nct6694_canfd_priv *priv = platform_get_drvdata(pdev);
- struct nct6694 *nct6694 = priv->nct6694;
struct net_device *ndev = priv->ndev;
- int port = ndev->dev_port;
int irq = ndev->irq;
unregister_candev(ndev);
can_rx_offload_del(&priv->offload);
free_candev(ndev);
irq_dispose_mapping(irq);
- ida_free(&nct6694->canfd_ida, port);
}
static struct platform_driver nct6694_canfd_driver = {
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
index bc3689bd4b6b..2b4b804a1739 100644
--- a/drivers/watchdog/nct6694_wdt.c
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -5,7 +5,6 @@
* Copyright (C) 2025 Nuvoton Technology Corp.
*/
-#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/mfd/nct6694.h>
#include <linux/module.h>
@@ -233,21 +232,12 @@ static const struct watchdog_ops nct6694_wdt_ops = {
.ping = nct6694_wdt_ping,
};
-static void nct6694_wdt_ida_free(void *d)
-{
- struct nct6694_wdt_data *data = d;
- struct nct6694 *nct6694 = data->nct6694;
-
- ida_free(&nct6694->wdt_ida, data->wdev_idx);
-}
-
static int nct6694_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
struct nct6694_wdt_data *data;
struct watchdog_device *wdev;
- int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -260,15 +250,7 @@ static int nct6694_wdt_probe(struct platform_device *pdev)
data->dev = dev;
data->nct6694 = nct6694;
-
- ret = ida_alloc(&nct6694->wdt_ida, GFP_KERNEL);
- if (ret < 0)
- return ret;
- data->wdev_idx = ret;
-
- ret = devm_add_action_or_reset(dev, nct6694_wdt_ida_free, data);
- if (ret)
- return ret;
+ data->wdev_idx = pdev->id;
wdev = &data->wdev;
wdev->info = &nct6694_wdt_info;
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
index 6eb9be2cd4a0..496da72949d9 100644
--- a/include/linux/mfd/nct6694.h
+++ b/include/linux/mfd/nct6694.h
@@ -8,6 +8,10 @@
#ifndef __MFD_NCT6694_H
#define __MFD_NCT6694_H
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
#define NCT6694_VENDOR_ID 0x0416
#define NCT6694_PRODUCT_ID 0x200B
#define NCT6694_INT_IN_EP 0x81
@@ -82,10 +86,6 @@ union __packed nct6694_usb_msg {
struct nct6694 {
struct device *dev;
- struct ida gpio_ida;
- struct ida i2c_ida;
- struct ida canfd_ida;
- struct ida wdt_ida;
struct irq_domain *domain;
struct mutex access_lock;
spinlock_t irq_lock;
--
2.34.1
^ permalink raw reply related
* [PATCH v1 0/2] mfd: nct6694: Refactor transport layer and add HIF (eSPI) support
From: a0282524688 @ 2026-04-02 5:14 UTC (permalink / raw)
To: tmyu0, linusw, brgl, linux, andi.shyti, lee, mkl, mailhol,
alexandre.belloni, wim
Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
From: Ming Yu <a0282524688@gmail.com>
The Nuvoton NCT6694 is a peripheral expander that provides GPIO, I2C,
CAN-FD, Watchdog, HWMON, PWM, and RTC sub-devices. Currently, the
driver only supports USB as the host transport interface.
This series refactors the NCT6694 MFD core to support multiple transport
backends and adds a new Host Interface (HIF) transport driver that
communicates over eSPI using Super-I/O shared memory.
Ming Yu (2):
mfd: nct6694: Switch to devm_mfd_add_devices() and drop IDA
mfd: Add Host Interface (HIF) support for Nuvoton NCT6694
MAINTAINERS | 1 +
drivers/gpio/gpio-nct6694.c | 26 +-
drivers/hwmon/nct6694-hwmon.c | 21 -
drivers/i2c/busses/i2c-nct6694.c | 26 +-
drivers/mfd/Kconfig | 47 +-
drivers/mfd/Makefile | 3 +-
drivers/mfd/nct6694-hif.c | 649 ++++++++++++++++++++++++++++
drivers/mfd/nct6694.c | 180 ++++----
drivers/net/can/usb/nct6694_canfd.c | 18 +-
drivers/rtc/rtc-nct6694.c | 7 -
drivers/watchdog/nct6694_wdt.c | 27 +-
include/linux/mfd/nct6694.h | 57 ++-
12 files changed, 829 insertions(+), 233 deletions(-)
create mode 100644 drivers/mfd/nct6694-hif.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH] usb: core: Fix bandwidth for devices with invalid wBytesPerInterval
From: Greg KH @ 2026-04-02 3:51 UTC (permalink / raw)
To: Tao Xue; +Cc: linux-usb, linux-kernel, caiyadong, stable
In-Reply-To: <20260402021400.28853-1-xuetao09@huawei.com>
On Thu, Apr 02, 2026 at 10:14:00AM +0800, Tao Xue wrote:
> As specified in Section 4.14.2 of the xHCI Specification, the xHC
> reserves bandwidth for periodic endpoints according to bInterval and
> wBytesPerInterval (Max ESIT Payload).
>
> Some peripherals report an invalid wBytesPerInterval in their device
> descriptor, which is either 0 or smaller than the actual data length
> transmitted. This issue is observed on ASIX AX88179 series USB 3.0
> Ethernet adapters.
>
> These errors may lead to unexpected behavior on certain USB host
> controllers, causing USB peripherals to malfunction.
>
> To address the issue, return max(wBytesPerInterval, max_payload) when
> calculating bandwidth reservation.
>
> Fixes: 9238f25d5d32 ("USB: xhci: properly set endpoint context fields for periodic eps.")
> Cc: <stable@kernel.org>
> Signed-off-by: Tao Xue <xuetao09@huawei.com>
> ---
> drivers/usb/core/usb.c | 9 ++++++++-
> 1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
> index e9a10a33534c..8f2e05a5a015 100644
> --- a/drivers/usb/core/usb.c
> +++ b/drivers/usb/core/usb.c
> @@ -1125,6 +1125,8 @@ EXPORT_SYMBOL_GPL(usb_free_noncoherent);
> u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
> const struct usb_host_endpoint *ep)
> {
> + u32 max_payload;
> +
> if (!usb_endpoint_xfer_isoc(&ep->desc) &&
> !usb_endpoint_xfer_int(&ep->desc))
> return 0;
> @@ -1135,7 +1137,12 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
> return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
> fallthrough;
> case USB_SPEED_SUPER:
> - return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
> + max_payload = usb_endpoint_maxp(&ep->desc) * (ep->ss_ep_comp.bMaxBurst + 1);
> + if (usb_endpoint_xfer_isoc(&ep->desc))
> + return max_t(u32, max_payload * USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
> + ep->ss_ep_comp.wBytesPerInterval);
> + else
> + return max_t(u32, max_payload, ep->ss_ep_comp.wBytesPerInterval);
You dropped the conversion from le16 to cpu? Why?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH net-next v5 0/2] r8152: Add support for the RTL8157 5Gbit USB Ethernet chip
From: Jakub Kicinski @ 2026-04-02 3:37 UTC (permalink / raw)
To: Birger Koblitz
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni,
linux-usb, netdev, linux-kernel
In-Reply-To: <20260331-rtl8157_next-v5-0-deb3095f8380@birger-koblitz.de>
On Tue, 31 Mar 2026 17:55:52 +0200 Birger Koblitz wrote:
> Add support for the RTL8157, which is a 5GBit USB-Ethernet adapter
> chip in the RTL815x family of chips.
>
> The RTL8157 uses a different frame descriptor format, and different
> SRAM/ADV access methods, plus offers 5GBit/s Ethernet, so support for these
> features is added in addition to chip initialization and configuration.
This version does not seem to apply to net-next.
Please make sure you base it on net-next not linux-next.
^ permalink raw reply
* Re: [PATCH] usb: core: Fix bandwidth for devices with invalid wBytesPerInterval
From: Alan Stern @ 2026-04-02 2:45 UTC (permalink / raw)
To: Tao Xue; +Cc: gregkh, linux-usb, linux-kernel, caiyadong, stable
In-Reply-To: <20260402021400.28853-1-xuetao09@huawei.com>
On Thu, Apr 02, 2026 at 10:14:00AM +0800, Tao Xue wrote:
> As specified in Section 4.14.2 of the xHCI Specification, the xHC
> reserves bandwidth for periodic endpoints according to bInterval and
> wBytesPerInterval (Max ESIT Payload).
>
> Some peripherals report an invalid wBytesPerInterval in their device
> descriptor, which is either 0 or smaller than the actual data length
> transmitted. This issue is observed on ASIX AX88179 series USB 3.0
> Ethernet adapters.
Do we log these invalid values when we parse the endpoint and their
companion descriptors? We should -- and we can fix up any errors then,
just once.
Alan Stern
^ permalink raw reply
* Re: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
From: stern @ 2026-04-02 2:36 UTC (permalink / raw)
To: Mathias Nyman
Cc: Thinh Nguyen, linux-usb@vger.kernel.org, michal.pecio@gmail.com,
oneukum@suse.com, niklas.neronin@linux.intel.com
In-Reply-To: <50e61cf7-cce9-45b4-884e-ac65f5e771d7@linux.intel.com>
On Thu, Apr 02, 2026 at 01:08:31AM +0300, Mathias Nyman wrote:
> On 3/31/26 18:31, stern@rowland.harvard.edu wrote:
> >
> > How about this instead? We add a "halted" flag to the usb_host_endpoint
> > structure, and the core will set this flag whenever a bulk or interrupt
> > URB gets a status other than 0 (before putting the URB on the bh list).
> > If an URB has one of these statuses, when its completion handler returns
> > the core will unlink all the URBs queued to the same endpoint. Finally,
> > the "halted" flag should be cleared after a completion handler returns
> > if there are no more unlinked URBs still in the queue or URBs waiting on
> > the bh list to be given back.
> >
> > The result of this is that any URB remaining in the queue when the flag
> > is cleared must have been submitted by the class driver _after_ the
> > failing URB's completion handler has run. We can assume the class
> > driver knows what it's doing in this case.
> >
> > The endpoint queue shouldn't be restarted until the "halted" flag is
> > cleared. Either right away, if there are any URBs in the queue, or not
> > until the next URB is submitted. Doing this might require a new HCD
> > callback. (It would also mean the kerneldoc for usb_unlink_urb() would
> > need to be updated, because the endpoint might restart before all the
> > completion handlers for the unlinked URBs have run.)
> >
> > What I'm trying to do here is come up with a single, consistent proposal
> > for exactly when halted endpoint queues should restart. Maybe someone
> > else has a better suggestion.
>
> Sounds like a possible solution to me.
>
> Just to clarify, core should unlink the remaining URBs queued to that endpoint
> after setting the "halted" flag, but before URB completion is called.
Above I wrote that the remaining URBs should be unlinked _after_ the
completion handler is called. If we did the unlinks before then the
class driver might submit a new URB after the unlinks were finished and
before the completion handler learned about the transaction error, and
this new URB then wouldn't get unlinked.
It's a race between completion of one URB and submission of another.
> "Halted" flag should be cleared after URB completion returns, and endpoint
> should be restarted if there are any pending URBs.
To be clear, the flag should be cleared after the completion handlers
for _all_ the unlinked URBs (as well as the URB getting the original
error) have returned.
> This allows the class driver URB completion handler to re-queue the halted URB
> without core unlinking it.
The completion handler shouldn't do this, because it would mean
resubmitting without doing a clear-halt first. (Completion handlers
can't do clear-halts because they run in atomic context.) If it does
try to do this anyway, I see nothing wrong with the core unlinking the
resubmitted URB.
(Are you going to ask about verification tests that set the endpoint's
Halt feature, submit an URB, wait for it to fail with -EPIPE, and then
submit another URB from within the completion handler? :-) )
> > > > Here's a troubling consequence for people to consider: Suppose an
> > > > endpoint queue stops with -EPROTO or -EPIPE, and before the class driver
> > > > gets around to calling usb_clear_halt(), it is unbound. What happens
> > > > the next time a driver binds to the device and tries to use the
> > > > endpoint?
> > >
> > > The disable/enable interface and set config calls during unbind/bind should,
> > > if I remember correctly flush pending URBs and drop and re-add the endpoint,
> > > clearing the xhci side halt and reset toggle.
> >
> > usb_probe_interface() doesn't do any of that stuff, other than a
> > deferred switch to altsetting 0 if needed.
> >
> > usb_unbind_interface() does call usb_enable_interface() if the interface
> > is already in altsetting 0, but the reset_ep argument is false so the
> > endpoint state doesn't get affected. Should that be changed?
>
> Looks like this needs more attention. Interface driver unbind/bind with
> halted endpoint could be an issue. I don't have an answer right now.
We can ponder it...
Alan Stern
^ permalink raw reply
* Re: [PATCH v2] usb: cdns3: gadget: fix state inconsistency on gadget init failure
From: Peter Chen (CIX) @ 2026-04-02 2:23 UTC (permalink / raw)
To: Yongchao Wu; +Cc: pawell, rogerq, gregkh, linux-usb, linux-kernel, stable
In-Reply-To: <20260401001000.5761-1-yongchao.wu@autochips.com>
On 26-04-01 08:10:00, Yongchao Wu wrote:
> When cdns3_gadget_start() fails, the DRD hardware is left in gadget mode
> while software state remains INACTIVE, creating hardware/software state
> inconsistency.
>
> When switching to host mode via sysfs:
> echo host > /sys/class/usb_role/13180000.usb-role-switch/role
>
> The role state is not set to CDNS_ROLE_STATE_ACTIVE due to the error,
> so cdns_role_stop() skips cleanup because state is still INACTIVE.
> This violates the DRD controller design specification (Figure22),
> which requires returning to idle state before switching roles.
>
> This leads to a synchronous external abort in xhci_gen_setup() when
> setting up the host controller:
>
> [ 516.440698] configfs-gadget 13180000.usb: failed to start g1: -19
> [ 516.442035] cdns-usb3 13180000.usb: Failed to add gadget
> [ 516.443278] cdns-usb3 13180000.usb: set role 2 has failed
> ...
> [ 1301.375722] xhci-hcd xhci-hcd.1.auto: xHCI Host Controller
> [ 1301.377716] Internal error: synchronous external abort: 96000010 [#1] PREEMPT SMP
> [ 1301.382485] pc : xhci_gen_setup+0xa4/0x408
> [ 1301.393391] backtrace:
> ...
> xhci_gen_setup+0xa4/0x408 <-- CRASH
> xhci_plat_setup+0x44/0x58
> usb_add_hcd+0x284/0x678
> ...
> cdns_role_set+0x9c/0xbc <-- Role switch
>
> Fix by calling cdns_drd_gadget_off() in the error path to properly
> clean up the DRD gadget state.
>
> Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver")
> Cc: stable@kernel.org
> Signed-off-by: Yongchao Wu <yongchao.wu@autochips.com>
Acked-by: Peter Chen <peter.chen@kernel.org>
Peter
>
> ---
> Changes in v2:
> - Update commit title to reflect state inconsistency
> - Add more detailed description with error logs
> - Add Fixes tag and Cc to stable
> ---
> drivers/usb/cdns3/cdns3-gadget.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c
> index d59a60a16..4cc3f2ffb 100644
> --- a/drivers/usb/cdns3/cdns3-gadget.c
> +++ b/drivers/usb/cdns3/cdns3-gadget.c
> @@ -3428,6 +3428,7 @@ static int __cdns3_gadget_init(struct cdns *cdns)
> ret = cdns3_gadget_start(cdns);
> if (ret) {
> pm_runtime_put_sync(cdns->dev);
> + cdns_drd_gadget_off(cdns);
> return ret;
> }
>
> --
> 2.43.0
>
>
--
Best regards,
Peter
^ permalink raw reply
* Re: correctly handling EPROTO
From: Alan Stern @ 2026-04-02 2:20 UTC (permalink / raw)
To: Michal Pecio; +Cc: Thinh Nguyen, Oliver Neukum, Bjørn Mork, USB list
In-Reply-To: <20260401235022.67037c98.michal.pecio@gmail.com>
On Wed, Apr 01, 2026 at 11:50:22PM +0200, Michal Pecio wrote:
> On Mon, 30 Mar 2026 14:36:00 +0200, Michal Pecio wrote:
> > UVC allows both isoc and bulk transport. I have one bulk device and I
> > found that if I randomly change urb->status to -EPROTO, the URB is not
> > resubmitted but (on xHCI) the endpoint keeps going, until it stops after
> > 5 errors (no URBs left). EHCI would presumably never restart at all.
>
> I tried this again with real transaction errors (dodgy cable) on EHCI
> with disabled XactErr retries and I see identical result. Failed URBs
> are never submitted again (checked with usbmon) but the endpoint keeps
> going as long as there are other URBs remaining.
> Not sure why it happens, maybe it's just the async giveback race,
> but it worked like that reliably several times.
Well, once we have settled the issues surrounding transaction errors and
fixed up the HCDs, someone will have to implement the class driver's
side of the solution in uvcvideo.
Alan Stern
^ permalink raw reply
* [PATCH] usb: core: Fix bandwidth for devices with invalid wBytesPerInterval
From: Tao Xue @ 2026-04-02 2:14 UTC (permalink / raw)
To: gregkh; +Cc: linux-usb, linux-kernel, caiyadong, Tao Xue, stable
As specified in Section 4.14.2 of the xHCI Specification, the xHC
reserves bandwidth for periodic endpoints according to bInterval and
wBytesPerInterval (Max ESIT Payload).
Some peripherals report an invalid wBytesPerInterval in their device
descriptor, which is either 0 or smaller than the actual data length
transmitted. This issue is observed on ASIX AX88179 series USB 3.0
Ethernet adapters.
These errors may lead to unexpected behavior on certain USB host
controllers, causing USB peripherals to malfunction.
To address the issue, return max(wBytesPerInterval, max_payload) when
calculating bandwidth reservation.
Fixes: 9238f25d5d32 ("USB: xhci: properly set endpoint context fields for periodic eps.")
Cc: <stable@kernel.org>
Signed-off-by: Tao Xue <xuetao09@huawei.com>
---
drivers/usb/core/usb.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index e9a10a33534c..8f2e05a5a015 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -1125,6 +1125,8 @@ EXPORT_SYMBOL_GPL(usb_free_noncoherent);
u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
const struct usb_host_endpoint *ep)
{
+ u32 max_payload;
+
if (!usb_endpoint_xfer_isoc(&ep->desc) &&
!usb_endpoint_xfer_int(&ep->desc))
return 0;
@@ -1135,7 +1137,12 @@ u32 usb_endpoint_max_periodic_payload(struct usb_device *udev,
return le32_to_cpu(ep->ssp_isoc_ep_comp.dwBytesPerInterval);
fallthrough;
case USB_SPEED_SUPER:
- return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
+ max_payload = usb_endpoint_maxp(&ep->desc) * (ep->ss_ep_comp.bMaxBurst + 1);
+ if (usb_endpoint_xfer_isoc(&ep->desc))
+ return max_t(u32, max_payload * USB_SS_MULT(ep->ss_ep_comp.bmAttributes),
+ ep->ss_ep_comp.wBytesPerInterval);
+ else
+ return max_t(u32, max_payload, ep->ss_ep_comp.wBytesPerInterval);
default:
if (usb_endpoint_is_hs_isoc_double(udev, ep))
return le32_to_cpu(ep->eusb2_isoc_ep_comp.dwBytesPerInterval);
--
2.17.1
^ permalink raw reply related
* Re: [PATCH v10 5/6] power: supply: max77759: add charger driver
From: Amit Sunil Dhamne @ 2026-04-02 1:25 UTC (permalink / raw)
To: Sebastian Reichel
Cc: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, Jagan Sridharan, Mark Brown,
Matti Vaittinen, Andrew Morton, Heikki Krogerus, Peter Griffin,
Tudor Ambarus, Alim Akhtar, linux-kernel, devicetree, linux-usb,
linux-pm, linux-arm-kernel, linux-samsung-soc, RD Babiera,
Kyle Tso
In-Reply-To: <ac2jYUA2F5oQsA2g@venus>
Hi Sebastian,
Thanks for the review!
On 4/1/26 4:17 PM, Sebastian Reichel wrote:
> Hi,
>
> On Tue, Mar 31, 2026 at 11:22:20PM +0000, Amit Sunil Dhamne via B4 Relay wrote:
>> +/* Charge Termination Voltage Limits (in mV) */
>> +static const struct linear_range chg_cv_prm_ranges[] = {
>> + LINEAR_RANGE(3800, 0x38, 0x39, 100),
>> + LINEAR_RANGE(4000, 0x0, 0x32, 10),
>> +};
> Let me quote from include/linux/power_supply.h:
>
> * All voltages, currents, charges, energies, time and temperatures in uV,
> * µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise
> * stated. It's driver's job to convert its raw values to units in which
> * this class operates.
>
> What makes you think that CONSTANT_CHARGE_VOLTAGE_MAX is
> special?
>
> [...]
It was an oversight, I will fix it.
>
>> +static int max77759_charger_get_property(struct power_supply *psy,
>> + enum power_supply_property psp,
>> + union power_supply_propval *pval)
>> +{
>> + struct max77759_charger *chg = power_supply_get_drvdata(psy);
>> + int ret;
>> +
>> + switch (psp) {
>> + case POWER_SUPPLY_PROP_ONLINE:
>> + ret = get_online(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_PRESENT:
>> + ret = charger_input_valid(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_STATUS:
>> + ret = get_status(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_CHARGE_TYPE:
>> + ret = get_charge_type(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_HEALTH:
>> + ret = get_health(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
>> + ret = get_fast_charge_current(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
>> + ret = get_float_voltage(chg);
>> + break;
>> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
>> + ret = get_input_current_limit(chg);
>> + break;
>> + default:
>> + ret = -EINVAL;
>> + }
>> +
>> + pval->intval = ret;
>> + return ret < 0 ? ret : 0;
> As people like to use existing drivers as reference this definitely
> needs a comment, that none of the properties used by this driver
> support negative values. This is not a general thing as e.g. the
> CHARGE current may be negative depending on the battery being
> charged or discharged (OTG mode).
Ah okay, thanks for letting me know. I will add a comment.
As these patches are already in flight and part of usb-next of the usb
tree, I can send the suggested improvements as a separate patch, if that
works for you and Greg.
BR,
Amit
>
> Greetings,
>
> -- Sebastian
^ permalink raw reply
* [PATCH v1 1/1] usb: typec: tipd: Restore generic TPS6598x contract interrupts
From: Vincent Cloutier @ 2026-04-02 0:09 UTC (permalink / raw)
To: heikki.krogerus, gregkh
Cc: linux-usb, linux-kernel, sven, Vincent Cloutier, stable
From: Vincent Cloutier <vincent@cloutier.co>
The generic TPS6598x interrupt handler still relies on
PP_SWITCH_CHANGED, NEW_CONTRACT_AS_CONSUMER, HARD_RESET, and
STATUS_UPDATE, but the irq_mask1 refactor only kept
POWER_STATUS_UPDATE, DATA_STATUS_UPDATE, and PLUG_EVENT in
tps6598x_data.
On the librem5 that leaves PD partners stuck at the 500 mA fallback
because the active contract is never refreshed after attach.
Restore the missing interrupt bits so the existing handler paths are
reachable again. This fixes USB-C charging negotiation on the librem5:
after a replug the TPS6598x source power supply reports 3 A instead of
500 mA and the BQ25890 input limit follows suit.
Fixes: b3dddff502c5 ("usb: typec: tipd: Move initial irq mask to tipd_data")
Cc: stable@vger.kernel.org
Signed-off-by: Vincent Cloutier <vincent@cloutier.co>
---
drivers/usb/typec/tipd/core.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 84ee5687bb27..83f2fec6e34e 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -2395,7 +2395,11 @@ static const struct tipd_data tps6598x_data = {
.irq_handler = tps6598x_interrupt,
.irq_mask1 = TPS_REG_INT_POWER_STATUS_UPDATE |
TPS_REG_INT_DATA_STATUS_UPDATE |
- TPS_REG_INT_PLUG_EVENT,
+ TPS_REG_INT_PLUG_EVENT |
+ TPS_REG_INT_PP_SWITCH_CHANGED |
+ TPS_REG_INT_NEW_CONTRACT_AS_CONSUMER |
+ TPS_REG_INT_HARD_RESET |
+ TPS_REG_INT_STATUS_UPDATE,
.tps_struct_size = sizeof(struct tps6598x),
.register_port = tps6598x_register_port,
.unregister_port = tps6598x_unregister_port,
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v10 5/6] power: supply: max77759: add charger driver
From: Sebastian Reichel @ 2026-04-01 23:17 UTC (permalink / raw)
To: amitsd
Cc: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Greg Kroah-Hartman, Jagan Sridharan, Mark Brown,
Matti Vaittinen, Andrew Morton, Heikki Krogerus, Peter Griffin,
Tudor Ambarus, Alim Akhtar, linux-kernel, devicetree, linux-usb,
linux-pm, linux-arm-kernel, linux-samsung-soc, RD Babiera,
Kyle Tso
In-Reply-To: <20260331-max77759-charger-v10-5-76f59233c369@google.com>
[-- Attachment #1: Type: text/plain, Size: 2106 bytes --]
Hi,
On Tue, Mar 31, 2026 at 11:22:20PM +0000, Amit Sunil Dhamne via B4 Relay wrote:
> +/* Charge Termination Voltage Limits (in mV) */
> +static const struct linear_range chg_cv_prm_ranges[] = {
> + LINEAR_RANGE(3800, 0x38, 0x39, 100),
> + LINEAR_RANGE(4000, 0x0, 0x32, 10),
> +};
Let me quote from include/linux/power_supply.h:
* All voltages, currents, charges, energies, time and temperatures in uV,
* µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise
* stated. It's driver's job to convert its raw values to units in which
* this class operates.
What makes you think that CONSTANT_CHARGE_VOLTAGE_MAX is
special?
[...]
> +static int max77759_charger_get_property(struct power_supply *psy,
> + enum power_supply_property psp,
> + union power_supply_propval *pval)
> +{
> + struct max77759_charger *chg = power_supply_get_drvdata(psy);
> + int ret;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_ONLINE:
> + ret = get_online(chg);
> + break;
> + case POWER_SUPPLY_PROP_PRESENT:
> + ret = charger_input_valid(chg);
> + break;
> + case POWER_SUPPLY_PROP_STATUS:
> + ret = get_status(chg);
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_TYPE:
> + ret = get_charge_type(chg);
> + break;
> + case POWER_SUPPLY_PROP_HEALTH:
> + ret = get_health(chg);
> + break;
> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
> + ret = get_fast_charge_current(chg);
> + break;
> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
> + ret = get_float_voltage(chg);
> + break;
> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
> + ret = get_input_current_limit(chg);
> + break;
> + default:
> + ret = -EINVAL;
> + }
> +
> + pval->intval = ret;
> + return ret < 0 ? ret : 0;
As people like to use existing drivers as reference this definitely
needs a comment, that none of the properties used by this driver
support negative values. This is not a general thing as e.g. the
CHARGE current may be negative depending on the battery being
charged or discharged (OTG mode).
Greetings,
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2 0/4] usb: dwc3: xilinx: Add Versal2 MMI USB 3.2 controller support
From: Thinh Nguyen @ 2026-04-01 23:04 UTC (permalink / raw)
To: Radhey Shyam Pandey
Cc: gregkh@linuxfoundation.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, michal.simek@amd.com, Thinh Nguyen,
p.zabel@pengutronix.de, linux-usb@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, git@amd.com
In-Reply-To: <20260330190304.1841593-1-radhey.shyam.pandey@amd.com>
On Tue, Mar 31, 2026, Radhey Shyam Pandey wrote:
> This series introduces support for the Multi-Media Integrated (MMI) USB
> 3.2 Dual-Role Device (DRD) controller on Xilinx Versal2 platforms.
>
> The controller supports SSP(10-Gbps), SuperSpeed, high-speed, full-speed
> and low-speed operation modes.
>
> USB2 and USB3 PHY support Physical connectivity via the Type-C
> connectivity. DWC3 wrapper IP IO space is in SLCR so reg is made
> optional.
>
> The driver is required for the clock, reset and platform specific
> initialization (coherency/TX_DEEMPH etc). In this initial version typec
> reversibility is not implemented and it is assumed that USB3 PHY TCA mux
> programming is done by MMI configuration data object (CDOs) and TI PD
> controller is configured using external tiva programmer on VEK385
> evaluation board.
>
> Changes for v2:
> - DT binding: fix MHz spacing (SI convention), reorder description
> before $ref in xlnx,usb-syscon, restore zynqmp-dwc3 example and add
> versal2-mmi-dwc3 example, fix node name for no-reg case, use 1/1
> address/size configuration and lowercase hex in syscon offsets.
> - Split config struct refactoring (device_get_match_data,dwc3_xlnx_config)
> into a separate preparatory patch.
> - Fix error message capitalization to lowercase per kernel convention.
> - Rename property snps,lcsr_tx_deemph to snps,lcsr-tx-deemph (hyphens).
> - Fix double space in comment and missing blank line in core.h.
> - Use platform data instead of of_device_is_compatible() check for
> deemphasis support.
>
> Link: https://urldefense.com/v3/__https://lore.kernel.org/all/20251119193036.2666877-1-radhey.shyam.pandey@amd.com/__;!!A4F2R9G_pg!YSeyY-bpQrMLqswAc1cWND5CSHvGFygPGMEMpR9amrRMnRFjYrFZktzbLzEzVZcQmOW34IUAfwRKHwy7B8p_ciUorWGJsA$
>
> Radhey Shyam Pandey (4):
> dt-bindings: usb: dwc3-xilinx: Add MMI USB support on Versal Gen2
> platform
> usb: dwc3: xilinx: Introduce dwc3_xlnx_config for per-platform data
> usb: dwc3: xilinx: Add Versal2 MMI USB 3.2 controller support
> usb: dwc3: xilinx: Add support to program MMI USB TX deemphasis
>
> .../devicetree/bindings/usb/dwc3-xilinx.yaml | 70 ++++++++++++++-
> drivers/usb/dwc3/core.c | 17 ++++
> drivers/usb/dwc3/core.h | 8 ++
> drivers/usb/dwc3/dwc3-xilinx.c | 89 +++++++++++++++----
> 4 files changed, 166 insertions(+), 18 deletions(-)
>
>
> base-commit: 46b513250491a7bfc97d98791dbe6a10bcc8129d
> --
> 2.43.0
>
Hi Radhey,
Do you have plans to convert dwc3-xilinx to using the new flatten model?
The change you have here fits better for the new glue model.
BR,
Thinh
^ permalink raw reply
* Re: [PATCH] usb: dwc3: imx8mp: fix memory leak on probe failure path
From: Thinh Nguyen @ 2026-04-01 22:55 UTC (permalink / raw)
To: Xiaolei Wang
Cc: Thinh Nguyen, gregkh@linuxfoundation.org, Frank.Li@nxp.com,
s.hauer@pengutronix.de, kernel@pengutronix.de, festevam@gmail.com,
linux-usb@vger.kernel.org, imx@lists.linux.dev,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
In-Reply-To: <20260401134938.686748-1-xiaolei.wang@windriver.com>
On Wed, Apr 01, 2026, Xiaolei Wang wrote:
> When platform_get_drvdata() returns NULL and probe defers, the error
> path jumps to the 'depopulate' label, skipping put_device() for the
> reference acquired by of_find_device_by_node(). This extra reference
> prevents the child platform device from being freed when
> of_platform_depopulate() is called, resulting in memory leaks reported
> by kmemleak:
>
> unreferenced object 0xffff0000c92c1480 (size 64):
> comm "kworker/u16:2", pid 50, jiffies 4294895789
> backtrace (crc 49d507d0):
> kmemleak_alloc+0x34/0x40
> __kmalloc_noprof+0x430/0x670
> of_device_alloc+0xec/0x26c
> of_platform_device_create_pdata+0x60/0x1f0
> of_platform_bus_create+0x290/0x610
> of_platform_populate+0x74/0x118
> dwc3_imx8mp_probe+0x228/0x734
>
> Fixes: 86767625f525 ("usb: dwc3: imx8mp: disable auto suspend for host role")
> Signed-off-by: Xiaolei Wang <xiaolei.wang@windriver.com>
> ---
> drivers/usb/dwc3/dwc3-imx8mp.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c
> index b3d7252bd910..1cf96540b66e 100644
> --- a/drivers/usb/dwc3/dwc3-imx8mp.c
> +++ b/drivers/usb/dwc3/dwc3-imx8mp.c
> @@ -263,7 +263,7 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
> dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev);
> if (!dwc3) {
> err = dev_err_probe(dev, -EPROBE_DEFER, "failed to get dwc3 platform data\n");
> - goto depopulate;
> + goto put_dwc3;
> }
>
> dwc3->glue_ops = &dwc3_imx_glue_ops;
> --
> 2.43.0
>
Thanks for the catch.
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Thanks,
Thinh
^ permalink raw reply
* Re: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
From: Thinh Nguyen @ 2026-04-01 22:47 UTC (permalink / raw)
To: Mathias Nyman
Cc: Thinh Nguyen, linux-usb@vger.kernel.org,
stern@rowland.harvard.edu, michal.pecio@gmail.com,
oneukum@suse.com, niklas.neronin@linux.intel.com
In-Reply-To: <aa2f5418-5070-403a-8fcf-ed6169662e9e@linux.intel.com>
On Thu, Apr 02, 2026, Mathias Nyman wrote:
> On 4/2/26 01:08, Thinh Nguyen wrote:
> > On Mon, Mar 30, 2026, Mathias Nyman wrote:
> > >
> > > >
> > > > On a separate note, will you plan to implement the clear-halt for EPROTO
> > > > in xhci?
> > >
> > > I don't think this should be part of xhci driver. Decision to send control requests
> > > to the device should be done by core or class drivers.
> > >
> >
> > This not like STALL where it's standardized for the core or class driver
> > to know how to handle. The programming sequence for the errors that
> > resulted in EPROTO from xhci is specific to xhci. That is, the xhci
> > reset endpoint command will reset the bulk sequence, it's specific to
> > xhci. The xhci spec recommends to send a clear-halt for this scenario,
> > not the USB spec or any other class specific spec. So we should not
> > delegate this to the core or class driver to handle.
> >
> USB 2 Specification does mention handling halt conditions due to transmission
> erros _OR_ STALL handshake with clearing halt and resetting both host and device
> endpoint toggle.
>
> See USB 2
>
> 5.7.5 Interrupt Transfer Data Sequences
>
> "If a halt condition is detected on an interrupt pipe due to transmission errors or
> a STALL handshake being returned from the endpoint, all pending IRPs are retired.
> Removal of the halt condition is achieved via software intervention through a
> separate control pipe. This recovery will reset the data toggle bit to DATA0 for
> the endpoint on both the host and the device"
>
> 5.8.5 Bulk Transfer Data Sequences
>
> "If a halt condition is detected on a bulk pipe due to transmission errors or a
> STALL handshake being returned from the endpoint, all pending IRPs are retired.
> Removal of the halt condition is achieved via software intervention through a
> separate control pipe. This recovery will reset the data toggle bit to DATA0
> for the endpoint on both the host and the device"
>
Ah~ I stand corrected. My memory served me wrong. Thanks for pointing
this out.
BR,
Thinh
^ permalink raw reply
* Re: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
From: Mathias Nyman @ 2026-04-01 22:34 UTC (permalink / raw)
To: Thinh Nguyen
Cc: linux-usb@vger.kernel.org, stern@rowland.harvard.edu,
michal.pecio@gmail.com, oneukum@suse.com,
niklas.neronin@linux.intel.com
In-Reply-To: <20260401220816.ynyhgxr5yoeszoea@synopsys.com>
On 4/2/26 01:08, Thinh Nguyen wrote:
> On Mon, Mar 30, 2026, Mathias Nyman wrote:
>>
>>>
>>> On a separate note, will you plan to implement the clear-halt for EPROTO
>>> in xhci?
>>
>> I don't think this should be part of xhci driver. Decision to send control requests
>> to the device should be done by core or class drivers.
>>
>
> This not like STALL where it's standardized for the core or class driver
> to know how to handle. The programming sequence for the errors that
> resulted in EPROTO from xhci is specific to xhci. That is, the xhci
> reset endpoint command will reset the bulk sequence, it's specific to
> xhci. The xhci spec recommends to send a clear-halt for this scenario,
> not the USB spec or any other class specific spec. So we should not
> delegate this to the core or class driver to handle.
>
USB 2 Specification does mention handling halt conditions due to transmission
erros _OR_ STALL handshake with clearing halt and resetting both host and device
endpoint toggle.
See USB 2
5.7.5 Interrupt Transfer Data Sequences
"If a halt condition is detected on an interrupt pipe due to transmission errors or
a STALL handshake being returned from the endpoint, all pending IRPs are retired.
Removal of the halt condition is achieved via software intervention through a
separate control pipe. This recovery will reset the data toggle bit to DATA0 for
the endpoint on both the host and the device"
5.8.5 Bulk Transfer Data Sequences
"If a halt condition is detected on a bulk pipe due to transmission errors or a
STALL handshake being returned from the endpoint, all pending IRPs are retired.
Removal of the halt condition is achieved via software intervention through a
separate control pipe. This recovery will reset the data toggle bit to DATA0
for the endpoint on both the host and the device"
Thanks
Mathias
^ permalink raw reply
* Re: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
From: Thinh Nguyen @ 2026-04-01 22:08 UTC (permalink / raw)
To: Mathias Nyman
Cc: Thinh Nguyen, linux-usb@vger.kernel.org,
stern@rowland.harvard.edu, michal.pecio@gmail.com,
oneukum@suse.com, niklas.neronin@linux.intel.com
In-Reply-To: <2604e951-01e8-44d0-a11e-be63b0849c23@linux.intel.com>
On Mon, Mar 30, 2026, Mathias Nyman wrote:
>
> >
> > On a separate note, will you plan to implement the clear-halt for EPROTO
> > in xhci?
>
> I don't think this should be part of xhci driver. Decision to send control requests
> to the device should be done by core or class drivers.
>
This not like STALL where it's standardized for the core or class driver
to know how to handle. The programming sequence for the errors that
resulted in EPROTO from xhci is specific to xhci. That is, the xhci
reset endpoint command will reset the bulk sequence, it's specific to
xhci. The xhci spec recommends to send a clear-halt for this scenario,
not the USB spec or any other class specific spec. So we should not
delegate this to the core or class driver to handle.
BR,
Thinh
^ permalink raw reply
* Re: [RFC PATCH 1/2] xhci: prevent automatic endpoint restart after stall or error
From: Mathias Nyman @ 2026-04-01 22:08 UTC (permalink / raw)
To: stern@rowland.harvard.edu
Cc: Thinh Nguyen, linux-usb@vger.kernel.org, michal.pecio@gmail.com,
oneukum@suse.com, niklas.neronin@linux.intel.com
In-Reply-To: <86876c62-01d2-45da-81f3-7d4499ffa0ad@rowland.harvard.edu>
On 3/31/26 18:31, stern@rowland.harvard.edu wrote:
>
> How about this instead? We add a "halted" flag to the usb_host_endpoint
> structure, and the core will set this flag whenever a bulk or interrupt
> URB gets a status other than 0 (before putting the URB on the bh list).
> If an URB has one of these statuses, when its completion handler returns
> the core will unlink all the URBs queued to the same endpoint. Finally,
> the "halted" flag should be cleared after a completion handler returns
> if there are no more unlinked URBs still in the queue or URBs waiting on
> the bh list to be given back.
>
> The result of this is that any URB remaining in the queue when the flag
> is cleared must have been submitted by the class driver _after_ the
> failing URB's completion handler has run. We can assume the class
> driver knows what it's doing in this case.
>
> The endpoint queue shouldn't be restarted until the "halted" flag is
> cleared. Either right away, if there are any URBs in the queue, or not
> until the next URB is submitted. Doing this might require a new HCD
> callback. (It would also mean the kerneldoc for usb_unlink_urb() would
> need to be updated, because the endpoint might restart before all the
> completion handlers for the unlinked URBs have run.)
>
> What I'm trying to do here is come up with a single, consistent proposal
> for exactly when halted endpoint queues should restart. Maybe someone
> else has a better suggestion.
Sounds like a possible solution to me.
Just to clarify, core should unlink the remaining URBs queued to that endpoint
after setting the "halted" flag, but before URB completion is called.
"Halted" flag should be cleared after URB completion returns, and endpoint
should be restarted if there are any pending URBs.
This allows the class driver URB completion handler to re-queue the halted URB
without core unlinking it.
>
>>> Here's a troubling consequence for people to consider: Suppose an
>>> endpoint queue stops with -EPROTO or -EPIPE, and before the class driver
>>> gets around to calling usb_clear_halt(), it is unbound. What happens
>>> the next time a driver binds to the device and tries to use the
>>> endpoint?
>>
>> The disable/enable interface and set config calls during unbind/bind should,
>> if I remember correctly flush pending URBs and drop and re-add the endpoint,
>> clearing the xhci side halt and reset toggle.
>
> usb_probe_interface() doesn't do any of that stuff, other than a
> deferred switch to altsetting 0 if needed.
>
> usb_unbind_interface() does call usb_enable_interface() if the interface
> is already in altsetting 0, but the reset_ep argument is false so the
> endpoint state doesn't get affected. Should that be changed?
Looks like this needs more attention. Interface driver unbind/bind with
halted endpoint could be an issue. I don't have an answer right now.
Thanks
Mathias
^ permalink raw reply
* Re: correctly handling EPROTO
From: Michal Pecio @ 2026-04-01 21:50 UTC (permalink / raw)
To: Alan Stern; +Cc: Thinh Nguyen, Oliver Neukum, Bjørn Mork, USB list
In-Reply-To: <20260330143600.0594f0da.michal.pecio@gmail.com>
On Mon, 30 Mar 2026 14:36:00 +0200, Michal Pecio wrote:
> UVC allows both isoc and bulk transport. I have one bulk device and I
> found that if I randomly change urb->status to -EPROTO, the URB is not
> resubmitted but (on xHCI) the endpoint keeps going, until it stops after
> 5 errors (no URBs left). EHCI would presumably never restart at all.
I tried this again with real transaction errors (dodgy cable) on EHCI
with disabled XactErr retries and I see identical result. Failed URBs
are never submitted again (checked with usbmon) but the endpoint keeps
going as long as there are other URBs remaining.
[285740.194700] usb 4-4: new high-speed USB device number 3 using ehci-pci
[285740.243438] usb 4-4: New USB device found, idVendor=345f, idProduct=2130, bcdDevice=21.00
[285740.243448] usb 4-4: New USB device strings: Mfr=1, Product=2, SerialNumber=7
[285740.243452] usb 4-4: Product: USB3 Video
[285740.243455] usb 4-4: Manufacturer: UltraSemi
[285740.243457] usb 4-4: SerialNumber: 20210621
[285740.263078] uvcvideo 4-4:1.0: Found UVC 1.00 device USB3 Video (345f:2130)
[285745.210034] uvcvideo 4-4:1.0: Allocated 5 URB buffers of 30x512 bytes each
[285745.563613] ehci-pci 0000:08:00.2: devpath 4 ep3in 3strikes
[285745.563667] uvcvideo 4-4:1.1: Non-zero status (-71) in video completion handler.
[285745.830190] ehci-pci 0000:08:00.2: devpath 4 ep3in 3strikes
[285745.830233] uvcvideo 4-4:1.1: Non-zero status (-71) in video completion handler.
[285746.145090] ehci-pci 0000:08:00.2: devpath 4 ep3in 3strikes
[285746.145112] uvcvideo 4-4:1.1: Non-zero status (-71) in video completion handler.
[285746.500756] ehci-pci 0000:08:00.2: devpath 4 ep3in 3strikes
[285746.500778] uvcvideo 4-4:1.1: Non-zero status (-71) in video completion handler.
[285746.771547] ehci-pci 0000:08:00.2: devpath 4 ep3in 3strikes
[285746.771571] uvcvideo 4-4:1.1: Non-zero status (-71) in video completion handler.
# urb1
ffff8881a653ab40 3478085173 C Bi:4:003:3 0 15360 = 0c8d8a89 cd94dc59 d1941900 10801080 10801080 10801080 10801080 10801080
ffff8881a653ab40 3478085194 S Bi:4:003:3 -115 15360 <
# urb2
ffff8881be27dd80 3478085930 C Bi:4:003:3 0 15360 = 0c8d8a89 cd943acd d2941a00 10801080 10801080 10801080 10801080 10801080
ffff8881be27dd80 3478085955 S Bi:4:003:3 -115 15360 <
# urb3
ffff88814746f840 3478086931 C Bi:4:003:3 0 15360 = 0c8d8a89 cd94fe45 d4941b00 10801080 10801080 10801080 10801080 10801080
ffff88814746f840 3478086995 S Bi:4:003:3 -115 15360 <
# urb4
ffff8881617b3480 3478087544 C Bi:4:003:3 0 15360 = 0c8d8a89 cd94fab6 d5941c00 10801080 10801080 10801080 10801080 10801080
ffff8881617b3480 3478087563 S Bi:4:003:3 -115 15360 <
# urb1 errors out
ffff8881a653ab40 3478087703 C Bi:4:003:3 -71 0
# urb2
ffff8881be27dd80 3478088803 C Bi:4:003:3 0 15360 = 0c8d8a89 cd94f730 d7941d00 10801080 10801080 10801080 10801080 10801080
ffff8881be27dd80 3478088813 S Bi:4:003:3 -115 15360 <
# urb3
ffff88814746f840 3478089546 C Bi:4:003:3 0 15360 = 0c8d8a89 cd949cc4 d8941d00 10801080 10801080 10801080 10801080 10801080
ffff88814746f840 3478089554 S Bi:4:003:3 -115 15360 <
...
Not sure why it happens, maybe it's just the async giveback race,
but it worked like that reliably several times.
^ permalink raw reply
* [PATCHv2] thunderbolt: tunnel: simplify allocation
From: Rosen Penev @ 2026-04-01 21:47 UTC (permalink / raw)
To: linux-usb
Cc: Andreas Noever, Mika Westerberg, Yehezkel Bernat, Kees Cook,
Gustavo A. R. Silva, open list,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b
Use a flexible array member and kzalloc_flex to combine allocations.
Add __counted_by for extra runtime analysis. Move counting variable
assignment after allocation. kzalloc_flex with GCC >= 15 does this
automatically.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
v2: move kernel-doc and reword slightly.
drivers/thunderbolt/tunnel.c | 10 ++--------
drivers/thunderbolt/tunnel.h | 5 +++--
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 89676acf1290..f38f7753b6e4 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -180,19 +180,14 @@ static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths,
{
struct tb_tunnel *tunnel;
- tunnel = kzalloc_obj(*tunnel);
+ tunnel = kzalloc_flex(*tunnel, paths, npaths);
if (!tunnel)
return NULL;
- tunnel->paths = kzalloc_objs(tunnel->paths[0], npaths);
- if (!tunnel->paths) {
- kfree(tunnel);
- return NULL;
- }
+ tunnel->npaths = npaths;
INIT_LIST_HEAD(&tunnel->list);
tunnel->tb = tb;
- tunnel->npaths = npaths;
tunnel->type = type;
kref_init(&tunnel->kref);
@@ -219,7 +214,6 @@ static void tb_tunnel_destroy(struct kref *kref)
tb_path_free(tunnel->paths[i]);
}
- kfree(tunnel->paths);
kfree(tunnel);
}
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index 2c44fc8a10bc..4878763a82b3 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -37,7 +37,6 @@ enum tb_tunnel_state {
* @src_port: Source port of the tunnel
* @dst_port: Destination port of the tunnel. For discovered incomplete
* tunnels may be %NULL or null adapter port instead.
- * @paths: All paths required by the tunnel
* @npaths: Number of paths in @paths
* @pre_activate: Optional tunnel specific initialization called before
* activation. Can touch hardware.
@@ -69,13 +68,13 @@ enum tb_tunnel_state {
* @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read
* @callback: Optional callback called when DP tunnel is fully activated
* @callback_data: Optional data for @callback
+ * @paths: All paths required by the tunnel
*/
struct tb_tunnel {
struct kref kref;
struct tb *tb;
struct tb_port *src_port;
struct tb_port *dst_port;
- struct tb_path **paths;
size_t npaths;
int (*pre_activate)(struct tb_tunnel *tunnel);
int (*activate)(struct tb_tunnel *tunnel, bool activate);
@@ -107,6 +106,8 @@ struct tb_tunnel {
struct delayed_work dprx_work;
void (*callback)(struct tb_tunnel *tunnel, void *data);
void *callback_data;
+
+ struct tb_path *paths[] __counted_by(npaths);
};
struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down,
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox