diff options
Diffstat (limited to 'drivers/usb/misc')
28 files changed, 2922 insertions, 761 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 99b15b77dfd5..0b56b773dbdf 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -147,6 +147,7 @@ config USB_APPLEDISPLAY config USB_QCOM_EUD tristate "QCOM Embedded USB Debugger(EUD) Driver" depends on ARCH_QCOM || COMPILE_TEST + select QCOM_SCM select USB_ROLE_SWITCH help This module enables support for Qualcomm Technologies, Inc. @@ -165,6 +166,34 @@ config APPLE_MFI_FASTCHARGE It is safe to say M here. +config USB_LJCA + tristate "Intel La Jolla Cove Adapter support" + select AUXILIARY_BUS + depends on USB && ACPI + help + This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO + Master Adapter (LJCA). Additional drivers such as I2C_LJCA, + GPIO_LJCA and SPI_LJCA must be enabled in order to use the + functionality of the device. + + This driver can also be built as a module. If so, the module + will be called usb-ljca. + +config USB_USBIO + tristate "Intel USBIO Bridge support" + depends on USB && ACPI + depends on X86 || COMPILE_TEST + select AUXILIARY_BUS + help + This adds support for Intel USBIO drivers. + This enables the USBIO bridge driver module in charge to talk + to the USB device. Additional drivers such as GPIO_USBIO and + I2C_USBIO must be enabled in order to use the device's full + functionality. + + This driver can also be built as a module. If so, the module + will be called usbio. + source "drivers/usb/misc/sisusbvga/Kconfig" config USB_LD @@ -218,8 +247,8 @@ config USB_EHSET_TEST_FIXTURE VID/PID pairs. This driver then initiates a corresponding test mode on the downstream port to which the test fixture is attached. - See <http://www.usb.org/developers/onthego/EHSET_v1.01.pdf> for more - information. + See <https://www.usb.org/sites/default/files/EHSET_v1.01%281%29.pdf> + for more information. config USB_ISIGHTFW tristate "iSight firmware loading support" @@ -303,18 +332,30 @@ config BRCM_USB_PINMAP signals, which are typically on dedicated pins on the chip, to any gpio. -config USB_ONBOARD_HUB - tristate "Onboard USB hub support" +config USB_ONBOARD_DEV + tristate "Onboard USB device support" depends on OF help - Say Y here if you want to support discrete onboard USB hubs that - don't require an additional control bus for initialization, but - need some non-trivial form of initialization, such as enabling a - power regulator. An example for such a hub is the Realtek - RTS5411. + Say Y here if you want to support discrete onboard USB devices + that don't require an additional control bus for initialization, + but need some non-trivial form of initialization, such as + enabling a power regulator. An example for such device is the + Realtek RTS5411 hub. This driver can be used as a module but its state (module vs builtin) must match the state of the USB subsystem. Enabling this config will enable the driver and it will automatically match the state of the USB subsystem. If this driver is a - module it will be called onboard_usb_hub. + module it will be called onboard_usb_dev. + +config USB_ONBOARD_DEV_USB5744 + bool "Onboard USB Microchip usb5744 hub with SMBus support" + depends on (USB_ONBOARD_DEV && I2C=y) || (USB_ONBOARD_DEV=m && I2C=m) + help + Say Y here if you want to support onboard USB Microchip usb5744 + hub that requires SMBus initialization. + + This options enables usb5744 i2c default initialization sequence + during hub start-up configuration stage. It is must to enable this + option on AMD Kria KR260 Robotics Starter Kit as this hub is + connected to USB-SD converter which mounts the root filesystem. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 1992cc284d8a..494ab0377f35 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_USB_EMI26) += emi26.o obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_EZUSB_FX2) += ezusb.o obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o +obj-$(CONFIG_USB_LJCA) += usb-ljca.o +obj-$(CONFIG_USB_USBIO) += usbio.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o @@ -32,4 +34,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o -obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o +obj-$(CONFIG_USB_ONBOARD_DEV) += onboard_usb_dev.o diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c index ac8695195c13..47b38dcc2992 100644 --- a/drivers/usb/misc/apple-mfi-fastcharge.c +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -44,6 +44,7 @@ MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); struct mfi_device { struct usb_device *udev; struct power_supply *battery; + struct power_supply_desc battery_desc; int charge_type; }; @@ -133,7 +134,6 @@ static int apple_mfi_fc_set_property(struct power_supply *psy, ret = -EINVAL; } - pm_runtime_mark_last_busy(&mfi->udev->dev); pm_runtime_put_autosuspend(&mfi->udev->dev); return ret; @@ -178,6 +178,7 @@ static int mfi_fc_probe(struct usb_device *udev) { struct power_supply_config battery_cfg = {}; struct mfi_device *mfi = NULL; + char *battery_name; int err; if (!mfi_fc_match(udev)) @@ -187,23 +188,38 @@ static int mfi_fc_probe(struct usb_device *udev) if (!mfi) return -ENOMEM; + battery_name = kasprintf(GFP_KERNEL, "apple_mfi_fastcharge_%d-%d", + udev->bus->busnum, udev->devnum); + if (!battery_name) { + err = -ENOMEM; + goto err_free_mfi; + } + + mfi->battery_desc = apple_mfi_fc_desc; + mfi->battery_desc.name = battery_name; + battery_cfg.drv_data = mfi; mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; mfi->battery = power_supply_register(&udev->dev, - &apple_mfi_fc_desc, + &mfi->battery_desc, &battery_cfg); if (IS_ERR(mfi->battery)) { dev_err(&udev->dev, "Can't register battery\n"); err = PTR_ERR(mfi->battery); - kfree(mfi); - return err; + goto err_free_name; } mfi->udev = usb_get_dev(udev); dev_set_drvdata(&udev->dev, mfi); return 0; + +err_free_name: + kfree(battery_name); +err_free_mfi: + kfree(mfi); + return err; } static void mfi_fc_disconnect(struct usb_device *udev) @@ -213,6 +229,7 @@ static void mfi_fc_disconnect(struct usb_device *udev) mfi = dev_get_drvdata(&udev->dev); if (mfi->battery) power_supply_unregister(mfi->battery); + kfree(mfi->battery_desc.name); dev_set_drvdata(&udev->dev, NULL); usb_put_dev(mfi->udev); kfree(mfi); diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c index c8098e9b432e..62b5a30edc42 100644 --- a/drivers/usb/misc/appledisplay.c +++ b/drivers/usb/misc/appledisplay.c @@ -107,7 +107,12 @@ static void appledisplay_complete(struct urb *urb) case ACD_BTN_BRIGHT_UP: case ACD_BTN_BRIGHT_DOWN: pdata->button_pressed = 1; - schedule_delayed_work(&pdata->work, 0); + /* + * there is a window during which no device + * is registered + */ + if (pdata->bd ) + schedule_delayed_work(&pdata->work, 0); break; case ACD_BTN_NONE: default: @@ -202,6 +207,7 @@ static int appledisplay_probe(struct usb_interface *iface, const struct usb_device_id *id) { struct backlight_properties props; + struct backlight_device *backlight; struct appledisplay *pdata; struct usb_device *udev = interface_to_usbdev(iface); struct usb_endpoint_descriptor *endpoint; @@ -272,13 +278,14 @@ static int appledisplay_probe(struct usb_interface *iface, memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; props.max_brightness = 0xff; - pdata->bd = backlight_device_register(bl_name, NULL, pdata, + backlight = backlight_device_register(bl_name, NULL, pdata, &appledisplay_bl_data, &props); - if (IS_ERR(pdata->bd)) { + if (IS_ERR(backlight)) { dev_err(&iface->dev, "Backlight registration failed\n"); - retval = PTR_ERR(pdata->bd); + retval = PTR_ERR(backlight); goto error; } + pdata->bd = backlight; /* Try to get brightness */ brightness = appledisplay_bl_get_brightness(pdata->bd); diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c index 2b2019c19cde..1ce885e4184c 100644 --- a/drivers/usb/misc/brcmstb-usb-pinmap.c +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -335,6 +335,7 @@ static const struct of_device_id brcmstb_usb_pinmap_of_match[] = { { .compatible = "brcm,usb-pinmap" }, { }, }; +MODULE_DEVICE_TABLE(of, brcmstb_usb_pinmap_of_match); static struct platform_driver brcmstb_usb_pinmap_driver = { .driver = { diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c index 6fb5140e29b9..45cff32656c6 100644 --- a/drivers/usb/misc/chaoskey.c +++ b/drivers/usb/misc/chaoskey.c @@ -27,6 +27,8 @@ static struct usb_class_driver chaoskey_class; static int chaoskey_rng_read(struct hwrng *rng, void *data, size_t max, bool wait); +static DEFINE_MUTEX(chaoskey_list_lock); + #define usb_dbg(usb_if, format, arg...) \ dev_dbg(&(usb_if)->dev, format, ## arg) @@ -233,6 +235,7 @@ static void chaoskey_disconnect(struct usb_interface *interface) usb_deregister_dev(interface, &chaoskey_class); usb_set_intfdata(interface, NULL); + mutex_lock(&chaoskey_list_lock); mutex_lock(&dev->lock); dev->present = false; @@ -244,6 +247,7 @@ static void chaoskey_disconnect(struct usb_interface *interface) } else mutex_unlock(&dev->lock); + mutex_unlock(&chaoskey_list_lock); usb_dbg(interface, "disconnect done"); } @@ -251,6 +255,7 @@ static int chaoskey_open(struct inode *inode, struct file *file) { struct chaoskey *dev; struct usb_interface *interface; + int rv = 0; /* get the interface from minor number and driver information */ interface = usb_find_interface(&chaoskey_driver, iminor(inode)); @@ -266,18 +271,23 @@ static int chaoskey_open(struct inode *inode, struct file *file) } file->private_data = dev; + mutex_lock(&chaoskey_list_lock); mutex_lock(&dev->lock); - ++dev->open; + if (dev->present) + ++dev->open; + else + rv = -ENODEV; mutex_unlock(&dev->lock); + mutex_unlock(&chaoskey_list_lock); - usb_dbg(interface, "open success"); - return 0; + return rv; } static int chaoskey_release(struct inode *inode, struct file *file) { struct chaoskey *dev = file->private_data; struct usb_interface *interface; + int rv = 0; if (dev == NULL) return -ENODEV; @@ -286,14 +296,15 @@ static int chaoskey_release(struct inode *inode, struct file *file) usb_dbg(interface, "release"); + mutex_lock(&chaoskey_list_lock); mutex_lock(&dev->lock); usb_dbg(interface, "open count at release is %d", dev->open); if (dev->open <= 0) { usb_dbg(interface, "invalid open count (%d)", dev->open); - mutex_unlock(&dev->lock); - return -ENODEV; + rv = -ENODEV; + goto bail; } --dev->open; @@ -302,13 +313,15 @@ static int chaoskey_release(struct inode *inode, struct file *file) if (dev->open == 0) { mutex_unlock(&dev->lock); chaoskey_free(dev); - } else - mutex_unlock(&dev->lock); - } else - mutex_unlock(&dev->lock); - + goto destruction; + } + } +bail: + mutex_unlock(&dev->lock); +destruction: + mutex_unlock(&chaoskey_list_lock); usb_dbg(interface, "release success"); - return 0; + return rv; } static void chaos_read_callback(struct urb *urb) @@ -431,9 +444,19 @@ static ssize_t chaoskey_read(struct file *file, goto bail; mutex_unlock(&dev->rng_lock); - result = mutex_lock_interruptible(&dev->lock); - if (result) - goto bail; + if (file->f_flags & O_NONBLOCK) { + result = mutex_trylock(&dev->lock); + if (result == 0) { + result = -EAGAIN; + goto bail; + } else { + result = 0; + } + } else { + result = mutex_lock_interruptible(&dev->lock); + if (result) + goto bail; + } if (dev->valid == dev->used) { result = _chaoskey_fill(dev); if (result < 0) { diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c index 14faec51d7a5..75f5a740cba3 100644 --- a/drivers/usb/misc/cypress_cy7c63.c +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -88,6 +88,9 @@ static int vendor_command(struct cypress *dev, unsigned char request, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, address, data, iobuf, CYPRESS_MAX_REQSIZE, USB_CTRL_GET_TIMEOUT); + /* we must not process garbage */ + if (retval < 2) + goto err_buf; /* store returned data (more READs to be added) */ switch (request) { @@ -107,6 +110,7 @@ static int vendor_command(struct cypress *dev, unsigned char request, break; } +err_buf: kfree(iobuf); error: return retval; @@ -203,7 +207,7 @@ ATTRIBUTE_GROUPS(cypress); static int cypress_probe(struct usb_interface *interface, const struct usb_device_id *id) { - struct cypress *dev = NULL; + struct cypress *dev; int retval = -ENOMEM; /* allocate memory for our device state and initialize it */ diff --git a/drivers/usb/misc/cytherm.c b/drivers/usb/misc/cytherm.c index 3e3802aaefa3..875016dd073c 100644 --- a/drivers/usb/misc/cytherm.c +++ b/drivers/usb/misc/cytherm.c @@ -304,20 +304,20 @@ static int cytherm_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev = interface_to_usbdev(interface); - struct usb_cytherm *dev = NULL; + struct usb_cytherm *dev; int retval = -ENOMEM; - dev = kzalloc (sizeof(struct usb_cytherm), GFP_KERNEL); + dev = kzalloc(sizeof(struct usb_cytherm), GFP_KERNEL); if (!dev) goto error_mem; dev->udev = usb_get_dev(udev); - usb_set_intfdata (interface, dev); + usb_set_intfdata(interface, dev); dev->brightness = 0xFF; - dev_info (&interface->dev, + dev_info(&interface->dev, "Cypress thermometer device now attached\n"); return 0; @@ -329,10 +329,10 @@ static void cytherm_disconnect(struct usb_interface *interface) { struct usb_cytherm *dev; - dev = usb_get_intfdata (interface); + dev = usb_get_intfdata(interface); /* first remove the files, then NULL the pointer */ - usb_set_intfdata (interface, NULL); + usb_set_intfdata(interface, NULL); usb_put_dev(dev->udev); diff --git a/drivers/usb/misc/ezusb.c b/drivers/usb/misc/ezusb.c index 78aaee56c2b7..7ab4856aba0e 100644 --- a/drivers/usb/misc/ezusb.c +++ b/drivers/usb/misc/ezusb.c @@ -148,4 +148,5 @@ int ezusb_fx2_ihex_firmware_download(struct usb_device *dev, EXPORT_SYMBOL_GPL(ezusb_fx2_ihex_firmware_download); #endif +MODULE_DESCRIPTION("EZUSB device support"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 1e3df27bab58..365c10069345 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -277,28 +277,45 @@ static ssize_t iowarrior_read(struct file *file, char __user *buffer, struct iowarrior *dev; int read_idx; int offset; + int retval; dev = file->private_data; + if (file->f_flags & O_NONBLOCK) { + retval = mutex_trylock(&dev->mutex); + if (!retval) + return -EAGAIN; + } else { + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return -ERESTARTSYS; + } + /* verify that the device wasn't unplugged */ - if (!dev || !dev->present) - return -ENODEV; + if (!dev->present) { + retval = -ENODEV; + goto exit; + } dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", dev->minor, count); /* read count must be packet size (+ time stamp) */ if ((count != dev->report_size) - && (count != (dev->report_size + 1))) - return -EINVAL; + && (count != (dev->report_size + 1))) { + retval = -EINVAL; + goto exit; + } /* repeat until no buffer overrun in callback handler occur */ do { atomic_set(&dev->overflow_flag, 0); if ((read_idx = read_index(dev)) == -1) { /* queue empty */ - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } else { //next line will return when there is either new data, or the device is unplugged int r = wait_event_interruptible(dev->read_wait, @@ -309,28 +326,37 @@ static ssize_t iowarrior_read(struct file *file, char __user *buffer, -1)); if (r) { //we were interrupted by a signal - return -ERESTART; + retval = -ERESTART; + goto exit; } if (!dev->present) { //The device was unplugged - return -ENODEV; + retval = -ENODEV; + goto exit; } if (read_idx == -1) { // Can this happen ??? - return 0; + retval = 0; + goto exit; } } } offset = read_idx * (dev->report_size + 1); if (copy_to_user(buffer, dev->read_queue + offset, count)) { - return -EFAULT; + retval = -EFAULT; + goto exit; } } while (atomic_read(&dev->overflow_flag)); read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx; atomic_set(&dev->read_idx, read_idx); + mutex_unlock(&dev->mutex); return count; + +exit: + mutex_unlock(&dev->mutex); + return retval; } /* @@ -501,7 +527,6 @@ static long iowarrior_ioctl(struct file *file, unsigned int cmd, dev->minor, cmd, arg); retval = 0; - io_res = 0; switch (cmd) { case IOW_WRITE: if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 || @@ -886,7 +911,6 @@ error: static void iowarrior_disconnect(struct usb_interface *interface) { struct iowarrior *dev = usb_get_intfdata(interface); - int minor = dev->minor; usb_deregister_dev(interface, &iowarrior_class); @@ -910,9 +934,6 @@ static void iowarrior_disconnect(struct usb_interface *interface) mutex_unlock(&dev->mutex); iowarrior_delete(dev); } - - dev_info(&interface->dev, "I/O-Warror #%d now disconnected\n", - minor - IOWARRIOR_MINOR_BASE); } /* usb specific object needed to register this driver with the usb subsystem */ diff --git a/drivers/usb/misc/isight_firmware.c b/drivers/usb/misc/isight_firmware.c index 4d30095d6ad2..2d9af74a62c1 100644 --- a/drivers/usb/misc/isight_firmware.c +++ b/drivers/usb/misc/isight_firmware.c @@ -127,5 +127,6 @@ static struct usb_driver isight_firmware_driver = { module_usb_driver(isight_firmware_driver); +MODULE_DESCRIPTION("iSight firmware loading support"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c index 7cbef74dfc9a..f392d6f84df9 100644 --- a/drivers/usb/misc/ldusb.c +++ b/drivers/usb/misc/ldusb.c @@ -627,7 +627,6 @@ static const struct file_operations ld_usb_fops = { .open = ld_usb_open, .release = ld_usb_release, .poll = ld_usb_poll, - .llseek = no_llseek, }; /* diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c new file mode 100644 index 000000000000..41360a7591e5 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB devices + * + * Copyright (c) 2022, Google LLC + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/sysfs.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/onboard_dev.h> +#include <linux/workqueue.h> + +#include "onboard_usb_dev.h" + +/* USB5744 register offset and mask */ +#define USB5744_CMD_ATTACH 0xAA +#define USB5744_CMD_ATTACH_LSB 0x56 +#define USB5744_CMD_CREG_ACCESS 0x99 +#define USB5744_CMD_CREG_ACCESS_LSB 0x37 +#define USB5744_CREG_MEM_ADDR 0x00 +#define USB5744_CREG_MEM_RD_ADDR 0x04 +#define USB5744_CREG_WRITE 0x00 +#define USB5744_CREG_READ 0x01 +#define USB5744_CREG_RUNTIMEFLAGS2 0x411D +#define USB5744_CREG_BYPASS_UDC_SUSPEND BIT(3) + +static void onboard_dev_attach_usb_driver(struct work_struct *work); + +static struct usb_device_driver onboard_dev_usbdev_driver; +static DECLARE_WORK(attach_usb_driver_work, onboard_dev_attach_usb_driver); + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_dev { + struct regulator_bulk_data supplies[MAX_SUPPLIES]; + struct device *dev; + const struct onboard_dev_pdata *pdata; + struct gpio_desc *reset_gpio; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; + struct clk *clk; +}; + +static int onboard_dev_get_regulators(struct onboard_dev *onboard_dev) +{ + const char * const *supply_names = onboard_dev->pdata->supply_names; + unsigned int num_supplies = onboard_dev->pdata->num_supplies; + struct device *dev = onboard_dev->dev; + unsigned int i; + int err; + + if (num_supplies > MAX_SUPPLIES) + return dev_err_probe(dev, -EINVAL, "max %d supplies supported!\n", + MAX_SUPPLIES); + + for (i = 0; i < num_supplies; i++) + onboard_dev->supplies[i].supply = supply_names[i]; + + err = devm_regulator_bulk_get(dev, num_supplies, onboard_dev->supplies); + if (err) + dev_err(dev, "Failed to get regulator supplies: %pe\n", + ERR_PTR(err)); + + return err; +} + +static int onboard_dev_power_on(struct onboard_dev *onboard_dev) +{ + int err; + + err = clk_prepare_enable(onboard_dev->clk); + if (err) { + dev_err(onboard_dev->dev, "failed to enable clock: %pe\n", + ERR_PTR(err)); + return err; + } + + err = regulator_bulk_enable(onboard_dev->pdata->num_supplies, + onboard_dev->supplies); + if (err) { + dev_err(onboard_dev->dev, "failed to enable supplies: %pe\n", + ERR_PTR(err)); + goto disable_clk; + } + + fsleep(onboard_dev->pdata->reset_us); + gpiod_set_value_cansleep(onboard_dev->reset_gpio, 0); + fsleep(onboard_dev->pdata->power_on_delay_us); + + onboard_dev->is_powered_on = true; + + return 0; + +disable_clk: + clk_disable_unprepare(onboard_dev->clk); + return err; +} + +static int onboard_dev_power_off(struct onboard_dev *onboard_dev) +{ + int err; + + gpiod_set_value_cansleep(onboard_dev->reset_gpio, 1); + + err = regulator_bulk_disable(onboard_dev->pdata->num_supplies, + onboard_dev->supplies); + if (err) { + dev_err(onboard_dev->dev, "failed to disable supplies: %pe\n", + ERR_PTR(err)); + return err; + } + + clk_disable_unprepare(onboard_dev->clk); + + onboard_dev->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_dev_suspend(struct device *dev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (onboard_dev->always_powered_in_suspend) + return 0; + + mutex_lock(&onboard_dev->lock); + + list_for_each_entry(node, &onboard_dev->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&onboard_dev->lock); + + if (!power_off) + return 0; + + return onboard_dev_power_off(onboard_dev); +} + +static int __maybe_unused onboard_dev_resume(struct device *dev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + if (onboard_dev->is_powered_on) + return 0; + + return onboard_dev_power_on(onboard_dev); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, + size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_dev_add_usbdev(struct onboard_dev *onboard_dev, + struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&onboard_dev->lock); + + if (onboard_dev->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &onboard_dev->udev_list); + + mutex_unlock(&onboard_dev->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&onboard_dev->dev->kobj, &udev->dev.kobj, + link_name)); + + return 0; + +error: + mutex_unlock(&onboard_dev->lock); + + return err; +} + +static void onboard_dev_remove_usbdev(struct onboard_dev *onboard_dev, + const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&onboard_dev->dev->kobj, link_name); + + mutex_lock(&onboard_dev->lock); + + list_for_each_entry(node, &onboard_dev->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&onboard_dev->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", onboard_dev->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + onboard_dev->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_dev_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; + +static umode_t onboard_dev_attrs_are_visible(struct kobject *kobj, + struct attribute *attr, + int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct onboard_dev *onboard_dev = dev_get_drvdata(dev); + + if (attr == &dev_attr_always_powered_in_suspend.attr && + !onboard_dev->pdata->is_hub) + return 0; + + return attr->mode; +} + +static const struct attribute_group onboard_dev_group = { + .is_visible = onboard_dev_attrs_are_visible, + .attrs = onboard_dev_attrs, +}; +__ATTRIBUTE_GROUPS(onboard_dev); + + +static void onboard_dev_attach_usb_driver(struct work_struct *work) +{ + int err; + + err = driver_attach(&onboard_dev_usbdev_driver.driver); + if (err) + pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err)); +} + +#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) +static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data) +{ + struct i2c_msg msg[2]; + u8 rd_buf[3]; + int ret; + + u8 wr_buf[7] = {0, USB5744_CREG_MEM_ADDR, 4, + USB5744_CREG_READ, 1, + addr >> 8 & 0xff, + addr & 0xff}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + msg[0].len = 3; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + wr_buf[0] = 0; + wr_buf[1] = USB5744_CREG_MEM_RD_ADDR; + msg[0].len = 2; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 2; + msg[1].buf = rd_buf; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + *data = rd_buf[1]; + + return 0; +} + +static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, u8 data) +{ + struct i2c_msg msg[2]; + int ret; + + u8 wr_buf[8] = {0, USB5744_CREG_MEM_ADDR, 5, + USB5744_CREG_WRITE, 1, + addr >> 8 & 0xff, + addr & 0xff, + data}; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = sizeof(wr_buf); + msg[0].buf = wr_buf; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + msg[0].len = 3; + wr_buf[0] = USB5744_CMD_CREG_ACCESS; + wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB; + wr_buf[2] = 0; + + ret = i2c_transfer(client->adapter, msg, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int onboard_dev_5744_i2c_init(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + u8 reg; + + /* + * Set BYPASS_UDC_SUSPEND bit to ensure MCU is always enabled + * and ready to respond to SMBus runtime commands. + * The command writes 5 bytes to memory and single data byte in + * configuration register. + */ + ret = onboard_dev_5744_i2c_read_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, ®); + if (ret) + return dev_err_probe(dev, ret, "CREG_RUNTIMEFLAGS2 read failed\n"); + + reg |= USB5744_CREG_BYPASS_UDC_SUSPEND; + ret = onboard_dev_5744_i2c_write_byte(client, + USB5744_CREG_RUNTIMEFLAGS2, reg); + if (ret) + return dev_err_probe(dev, ret, "BYPASS_UDC_SUSPEND bit configuration failed\n"); + + /* Send SMBus command to boot hub. */ + ret = i2c_smbus_write_word_data(client, USB5744_CMD_ATTACH, + USB5744_CMD_ATTACH_LSB); + if (ret < 0) + return dev_err_probe(dev, ret, "USB Attach with SMBus command failed\n"); + + return ret; +} +#else +static int onboard_dev_5744_i2c_init(struct i2c_client *client) +{ + return -ENODEV; +} +#endif + +static int onboard_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_dev *onboard_dev; + struct device_node *i2c_node; + int err; + + onboard_dev = devm_kzalloc(dev, sizeof(*onboard_dev), GFP_KERNEL); + if (!onboard_dev) + return -ENOMEM; + + onboard_dev->pdata = device_get_match_data(dev); + if (!onboard_dev->pdata) + return -EINVAL; + + if (!onboard_dev->pdata->is_hub) + onboard_dev->always_powered_in_suspend = true; + + onboard_dev->dev = dev; + + err = onboard_dev_get_regulators(onboard_dev); + if (err) + return err; + + onboard_dev->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(onboard_dev->clk)) + return dev_err_probe(dev, PTR_ERR(onboard_dev->clk), + "failed to get clock\n"); + + onboard_dev->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(onboard_dev->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(onboard_dev->reset_gpio), + "failed to get reset GPIO\n"); + + mutex_init(&onboard_dev->lock); + INIT_LIST_HEAD(&onboard_dev->udev_list); + + dev_set_drvdata(dev, onboard_dev); + + err = onboard_dev_power_on(onboard_dev); + if (err) + return err; + + i2c_node = of_parse_phandle(pdev->dev.of_node, "i2c-bus", 0); + if (i2c_node) { + struct i2c_client *client = NULL; + +#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744) + client = of_find_i2c_device_by_node(i2c_node); +#endif + of_node_put(i2c_node); + + if (!client) { + err = -EPROBE_DEFER; + goto err_power_off; + } + + if (of_device_is_compatible(pdev->dev.of_node, "usb424,2744") || + of_device_is_compatible(pdev->dev.of_node, "usb424,5744")) { + err = onboard_dev_5744_i2c_init(client); + onboard_dev->always_powered_in_suspend = true; + } + + put_device(&client->dev); + if (err < 0) + goto err_power_off; + } + + /* + * The USB driver might have been detached from the USB devices by + * onboard_dev_remove() (e.g. through an 'unbind' by userspace), + * make sure to re-attach it if needed. + * + * This needs to be done deferred to avoid self-deadlocks on systems + * with nested onboard hubs. + */ + schedule_work(&attach_usb_driver_work); + + return 0; + +err_power_off: + onboard_dev_power_off(onboard_dev); + return err; +} + +static void onboard_dev_remove(struct platform_device *pdev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + onboard_dev->going_away = true; + + mutex_lock(&onboard_dev->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&onboard_dev->udev_list)) { + node = list_first_entry(&onboard_dev->udev_list, + struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_dev_remove_usbdev(), + * which acquires onboard_dev->lock. We must release the lock + * first. + */ + get_device(&udev->dev); + mutex_unlock(&onboard_dev->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&onboard_dev->lock); + } + + mutex_unlock(&onboard_dev->lock); + + onboard_dev_power_off(onboard_dev); +} + +MODULE_DEVICE_TABLE(of, onboard_dev_match); + +static const struct dev_pm_ops __maybe_unused onboard_dev_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_dev_suspend, onboard_dev_resume) +}; + +static struct platform_driver onboard_dev_driver = { + .probe = onboard_dev_probe, + .remove = onboard_dev_remove, + + .driver = { + .name = "onboard-usb-dev", + .of_match_table = onboard_dev_match, + .pm = pm_ptr(&onboard_dev_pm_ops), + .dev_groups = onboard_dev_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_BISON 0x5986 +#define VENDOR_ID_CYPRESS 0x04b4 +#define VENDOR_ID_GENESYS 0x05e3 +#define VENDOR_ID_MICROCHIP 0x0424 +#define VENDOR_ID_PARADE 0x1da0 +#define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TI 0x0451 +#define VENDOR_ID_VIA 0x2109 +#define VENDOR_ID_XMOS 0x20B1 + +/* + * Returns the onboard_dev platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_dev *_find_onboard_dev(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_dev *onboard_dev; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "peer-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for peer hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + onboard_dev = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata indicates that the platform driver finished + * probing. This handles the case where (conceivably) we could be + * running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!onboard_dev) + return ERR_PTR(-EPROBE_DEFER); + + return onboard_dev; +} + +static bool onboard_dev_usbdev_match(struct usb_device *udev) +{ + /* Onboard devices using this driver must have a device tree node */ + return !!udev->dev.of_node; +} + +static int onboard_dev_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_dev *onboard_dev; + int err; + + onboard_dev = _find_onboard_dev(dev); + if (IS_ERR(onboard_dev)) + return PTR_ERR(onboard_dev); + + dev_set_drvdata(dev, onboard_dev); + + err = onboard_dev_add_usbdev(onboard_dev, udev); + if (err) + return err; + + return 0; +} + +static void onboard_dev_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_dev *onboard_dev = dev_get_drvdata(&udev->dev); + + onboard_dev_remove_usbdev(onboard_dev, udev); +} + +static const struct usb_device_id onboard_dev_id_table[] = { + { USB_DEVICE(VENDOR_ID_BISON, 0x1198) }, /* Bison Electronics Inc. Integrated Camera */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6500) }, /* CYUSB330x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6502) }, /* CYUSB330x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6503) }, /* CYUSB33{0,1}x 2.0 HUB, Vendor Mode */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB331x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB331x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6507) }, /* CYUSB332x 2.0 HUB, Vendor Mode */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6508) }, /* CYUSB332x 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x650a) }, /* CYUSB332x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6570) }, /* CY7C6563x 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x5511) }, /* PS5511 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_PARADE, 0x55a1) }, /* PS5511 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 HUB */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0179) }, /* RTL8188ETV 2.4GHz WiFi */ + { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8440) }, /* TI USB8044 3.0 HUB */ + { USB_DEVICE(VENDOR_ID_TI, 0x8442) }, /* TI USB8044 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 HUB */ + { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_XMOS, 0x0013) }, /* XMOS XVF3500 Voice Processor */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_dev_id_table); + +static struct usb_device_driver onboard_dev_usbdev_driver = { + .name = "onboard-usb-dev", + .match = onboard_dev_usbdev_match, + .probe = onboard_dev_usbdev_probe, + .disconnect = onboard_dev_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_dev_id_table, +}; + +static int __init onboard_dev_init(void) +{ + int ret; + + ret = usb_register_device_driver(&onboard_dev_usbdev_driver, THIS_MODULE); + if (ret) + return ret; + + ret = platform_driver_register(&onboard_dev_driver); + if (ret) + usb_deregister_device_driver(&onboard_dev_usbdev_driver); + + return ret; +} +module_init(onboard_dev_init); + +static void __exit onboard_dev_exit(void) +{ + usb_deregister_device_driver(&onboard_dev_usbdev_driver); + platform_driver_unregister(&onboard_dev_driver); + + cancel_work_sync(&attach_usb_driver_work); +} +module_exit(onboard_dev_exit); + +MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); +MODULE_DESCRIPTION("Driver for discrete onboard USB devices"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h new file mode 100644 index 000000000000..c1462be5526d --- /dev/null +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2022, Google LLC + */ + +#ifndef _USB_MISC_ONBOARD_USB_DEV_H +#define _USB_MISC_ONBOARD_USB_DEV_H + +#define MAX_SUPPLIES 2 + +struct onboard_dev_pdata { + unsigned long reset_us; /* reset pulse width in us */ + unsigned long power_on_delay_us; /* power on delay in us */ + unsigned int num_supplies; /* number of supplies */ + const char * const supply_names[MAX_SUPPLIES]; + bool is_hub; +}; + +static const struct onboard_dev_pdata microchip_usb424_data = { + .reset_us = 1, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata microchip_usb2514_data = { + .reset_us = 1, + .num_supplies = 2, + .supply_names = { "vdd", "vdda" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata microchip_usb5744_data = { + .reset_us = 0, + .power_on_delay_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd", "vdd2" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata parade_ps5511_data = { + .reset_us = 500, + .num_supplies = 2, + .supply_names = { "vddd11", "vdd33"}, + .is_hub = true, +}; + +static const struct onboard_dev_pdata realtek_rts5411_data = { + .reset_us = 0, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata realtek_rtl8188etv_data = { + .reset_us = 0, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = false, +}; + +static const struct onboard_dev_pdata ti_tusb8020b_data = { + .reset_us = 3000, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata ti_tusb8041_data = { + .reset_us = 3000, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata bison_intcamera_data = { + .reset_us = 1000, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = false, +}; + +static const struct onboard_dev_pdata cypress_hx3_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd", "vdd2" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata cypress_hx2vl_data = { + .reset_us = 1, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata genesys_gl850g_data = { + .reset_us = 3, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata genesys_gl852g_data = { + .reset_us = 50, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata vialab_vl817_data = { + .reset_us = 10, + .num_supplies = 1, + .supply_names = { "vdd" }, + .is_hub = true, +}; + +static const struct onboard_dev_pdata xmos_xvf3500_data = { + .reset_us = 1, + .num_supplies = 2, + .supply_names = { "vdd", "vddio" }, + .is_hub = false, +}; + +static const struct of_device_id onboard_dev_match[] = { + { .compatible = "usb424,2412", .data = µchip_usb424_data, }, + { .compatible = "usb424,2514", .data = µchip_usb2514_data, }, + { .compatible = "usb424,2517", .data = µchip_usb424_data, }, + { .compatible = "usb424,2744", .data = µchip_usb5744_data, }, + { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, + { .compatible = "usb451,8025", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8440", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8442", .data = &ti_tusb8041_data, }, + { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6570", .data = &cypress_hx2vl_data, }, + { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, + { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, }, + { .compatible = "usbbda,179", .data = &realtek_rtl8188etv_data, }, + { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb1da0,5511", .data = ¶de_ps5511_data, }, + { .compatible = "usb1da0,55a1", .data = ¶de_ps5511_data, }, + { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, + { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, + { .compatible = "usb20b1,0013", .data = &xmos_xvf3500_data, }, + { .compatible = "usb5986,1198", .data = &bison_intcamera_data, }, + {} +}; + +#endif /* _USB_MISC_ONBOARD_USB_DEV_H */ diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_dev_pdevs.c index ed22a18f4ab7..722504752ce3 100644 --- a/drivers/usb/misc/onboard_usb_hub_pdevs.c +++ b/drivers/usb/misc/onboard_usb_dev_pdevs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * API for creating and destroying USB onboard hub platform devices + * API for creating and destroying USB onboard platform devices * * Copyright (c) 2022, Google LLC */ @@ -15,29 +15,30 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/of.h> -#include <linux/usb/onboard_hub.h> +#include <linux/usb/onboard_dev.h> -#include "onboard_usb_hub.h" +#include "onboard_usb_dev.h" struct pdev_list_entry { struct platform_device *pdev; struct list_head node; }; -static bool of_is_onboard_usb_hub(const struct device_node *np) +static bool of_is_onboard_usb_dev(struct device_node *np) { - return !!of_match_node(onboard_hub_match, np); + return !!of_match_node(onboard_dev_match, np); } /** - * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs - * @parent_hub : parent hub to scan for connected onboard hubs - * @pdev_list : list of onboard hub platform devices owned by the parent hub + * onboard_dev_create_pdevs -- create platform devices for onboard USB devices + * @parent_hub : parent hub to scan for connected onboard devices + * @pdev_list : list of onboard platform devices owned by the parent hub * - * Creates a platform device for each supported onboard hub that is connected to - * the given parent hub. The platform device is in charge of initializing the - * hub (enable regulators, take the hub out of reset, ...) and can optionally - * control whether the hub remains powered during system suspend or not. + * Creates a platform device for each supported onboard device that is connected + * to the given parent hub. The platform device is in charge of initializing the + * device (enable regulators, take the device out of reset, ...). For onboard + * hubs, it can optionally control whether the device remains powered during + * system suspend or not. * * To keep track of the platform devices they are added to a list that is owned * by the parent hub. @@ -50,9 +51,9 @@ static bool of_is_onboard_usb_hub(const struct device_node *np) * node. That means the root hubs of the primary and secondary HCD share the * same device tree node (the HCD node). As a result this function can be called * twice with the same DT node for root hubs. We only want to create a single - * platform device for each physical onboard hub, hence for root hubs the loop - * is only executed for the root hub of the primary HCD. Since the function - * scans through all child nodes it still creates pdevs for onboard hubs + * platform device for each physical onboard device, hence for root hubs the + * loop is only executed for the root hub of the primary HCD. Since the function + * scans through all child nodes it still creates pdevs for onboard devices * connected to the root hub of the secondary HCD if needed. * * Further there must be only one platform device for onboard hubs with a peer @@ -63,7 +64,7 @@ static bool of_is_onboard_usb_hub(const struct device_node *np) * the function processes the nodes of both peers. A platform device is only * created if the peer hub doesn't have one already. */ -void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +void onboard_dev_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) { int i; struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus); @@ -82,7 +83,7 @@ void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *p if (!np) continue; - if (!of_is_onboard_usb_hub(np)) + if (!of_is_onboard_usb_dev(np)) goto node_put; npc = of_parse_phandle(np, "peer-hub", 0); @@ -104,7 +105,7 @@ void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *p pdev = of_platform_device_create(np, NULL, &parent_hub->dev); if (!pdev) { dev_err(&parent_hub->dev, - "failed to create platform device for onboard hub '%pOF'\n", np); + "failed to create platform device for onboard dev '%pOF'\n", np); goto node_put; } @@ -121,16 +122,16 @@ node_put: of_node_put(np); } } -EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); +EXPORT_SYMBOL_GPL(onboard_dev_create_pdevs); /** - * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices - * @pdev_list : list of onboard hub platform devices + * onboard_dev_destroy_pdevs -- free resources of onboard platform devices + * @pdev_list : list of onboard platform devices * * Destroys the platform devices in the given list and frees the memory associated * with the list entry. */ -void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +void onboard_dev_destroy_pdevs(struct list_head *pdev_list) { struct pdev_list_entry *pdle, *tmp; @@ -140,4 +141,4 @@ void onboard_hub_destroy_pdevs(struct list_head *pdev_list) kfree(pdle); } } -EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); +EXPORT_SYMBOL_GPL(onboard_dev_destroy_pdevs); diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c deleted file mode 100644 index 83f14ca1d38e..000000000000 --- a/drivers/usb/misc/onboard_usb_hub.c +++ /dev/null @@ -1,462 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for onboard USB hubs - * - * Copyright (c) 2022, Google LLC - */ - -#include <linux/device.h> -#include <linux/export.h> -#include <linux/gpio/consumer.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/list.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/of.h> -#include <linux/of_platform.h> -#include <linux/platform_device.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <linux/suspend.h> -#include <linux/sysfs.h> -#include <linux/usb.h> -#include <linux/usb/hcd.h> -#include <linux/usb/onboard_hub.h> -#include <linux/workqueue.h> - -#include "onboard_usb_hub.h" - -static void onboard_hub_attach_usb_driver(struct work_struct *work); - -static struct usb_device_driver onboard_hub_usbdev_driver; -static DECLARE_WORK(attach_usb_driver_work, onboard_hub_attach_usb_driver); - -/************************** Platform driver **************************/ - -struct usbdev_node { - struct usb_device *udev; - struct list_head list; -}; - -struct onboard_hub { - struct regulator *vdd; - struct device *dev; - const struct onboard_hub_pdata *pdata; - struct gpio_desc *reset_gpio; - bool always_powered_in_suspend; - bool is_powered_on; - bool going_away; - struct list_head udev_list; - struct mutex lock; -}; - -static int onboard_hub_power_on(struct onboard_hub *hub) -{ - int err; - - err = regulator_enable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to enable regulator: %d\n", err); - return err; - } - - fsleep(hub->pdata->reset_us); - gpiod_set_value_cansleep(hub->reset_gpio, 0); - - hub->is_powered_on = true; - - return 0; -} - -static int onboard_hub_power_off(struct onboard_hub *hub) -{ - int err; - - gpiod_set_value_cansleep(hub->reset_gpio, 1); - - err = regulator_disable(hub->vdd); - if (err) { - dev_err(hub->dev, "failed to disable regulator: %d\n", err); - return err; - } - - hub->is_powered_on = false; - - return 0; -} - -static int __maybe_unused onboard_hub_suspend(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - struct usbdev_node *node; - bool power_off = true; - - if (hub->always_powered_in_suspend) - return 0; - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (!device_may_wakeup(node->udev->bus->controller)) - continue; - - if (usb_wakeup_enabled_descendants(node->udev)) { - power_off = false; - break; - } - } - - mutex_unlock(&hub->lock); - - if (!power_off) - return 0; - - return onboard_hub_power_off(hub); -} - -static int __maybe_unused onboard_hub_resume(struct device *dev) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - - if (hub->is_powered_on) - return 0; - - return onboard_hub_power_on(hub); -} - -static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) -{ - snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); -} - -static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) -{ - struct usbdev_node *node; - char link_name[64]; - int err; - - mutex_lock(&hub->lock); - - if (hub->going_away) { - err = -EINVAL; - goto error; - } - - node = kzalloc(sizeof(*node), GFP_KERNEL); - if (!node) { - err = -ENOMEM; - goto error; - } - - node->udev = udev; - - list_add(&node->list, &hub->udev_list); - - mutex_unlock(&hub->lock); - - get_udev_link_name(udev, link_name, sizeof(link_name)); - WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); - - return 0; - -error: - mutex_unlock(&hub->lock); - - return err; -} - -static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) -{ - struct usbdev_node *node; - char link_name[64]; - - get_udev_link_name(udev, link_name, sizeof(link_name)); - sysfs_remove_link(&hub->dev->kobj, link_name); - - mutex_lock(&hub->lock); - - list_for_each_entry(node, &hub->udev_list, list) { - if (node->udev == udev) { - list_del(&node->list); - kfree(node); - break; - } - } - - mutex_unlock(&hub->lock); -} - -static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - const struct onboard_hub *hub = dev_get_drvdata(dev); - - return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); -} - -static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct onboard_hub *hub = dev_get_drvdata(dev); - bool val; - int ret; - - ret = kstrtobool(buf, &val); - if (ret < 0) - return ret; - - hub->always_powered_in_suspend = val; - - return count; -} -static DEVICE_ATTR_RW(always_powered_in_suspend); - -static struct attribute *onboard_hub_attrs[] = { - &dev_attr_always_powered_in_suspend.attr, - NULL, -}; -ATTRIBUTE_GROUPS(onboard_hub); - -static void onboard_hub_attach_usb_driver(struct work_struct *work) -{ - int err; - - err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); - if (err) - pr_err("Failed to attach USB driver: %d\n", err); -} - -static int onboard_hub_probe(struct platform_device *pdev) -{ - const struct of_device_id *of_id; - struct device *dev = &pdev->dev; - struct onboard_hub *hub; - int err; - - hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - - of_id = of_match_device(onboard_hub_match, &pdev->dev); - if (!of_id) - return -ENODEV; - - hub->pdata = of_id->data; - if (!hub->pdata) - return -EINVAL; - - hub->vdd = devm_regulator_get(dev, "vdd"); - if (IS_ERR(hub->vdd)) - return PTR_ERR(hub->vdd); - - hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_HIGH); - if (IS_ERR(hub->reset_gpio)) - return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); - - hub->dev = dev; - mutex_init(&hub->lock); - INIT_LIST_HEAD(&hub->udev_list); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_power_on(hub); - if (err) - return err; - - /* - * The USB driver might have been detached from the USB devices by - * onboard_hub_remove() (e.g. through an 'unbind' by userspace), - * make sure to re-attach it if needed. - * - * This needs to be done deferred to avoid self-deadlocks on systems - * with nested onboard hubs. - */ - schedule_work(&attach_usb_driver_work); - - return 0; -} - -static void onboard_hub_remove(struct platform_device *pdev) -{ - struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); - struct usbdev_node *node; - struct usb_device *udev; - - hub->going_away = true; - - mutex_lock(&hub->lock); - - /* unbind the USB devices to avoid dangling references to this device */ - while (!list_empty(&hub->udev_list)) { - node = list_first_entry(&hub->udev_list, struct usbdev_node, list); - udev = node->udev; - - /* - * Unbinding the driver will call onboard_hub_remove_usbdev(), - * which acquires hub->lock. We must release the lock first. - */ - get_device(&udev->dev); - mutex_unlock(&hub->lock); - device_release_driver(&udev->dev); - put_device(&udev->dev); - mutex_lock(&hub->lock); - } - - mutex_unlock(&hub->lock); - - onboard_hub_power_off(hub); -} - -MODULE_DEVICE_TABLE(of, onboard_hub_match); - -static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { - SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) -}; - -static struct platform_driver onboard_hub_driver = { - .probe = onboard_hub_probe, - .remove_new = onboard_hub_remove, - - .driver = { - .name = "onboard-usb-hub", - .of_match_table = onboard_hub_match, - .pm = pm_ptr(&onboard_hub_pm_ops), - .dev_groups = onboard_hub_groups, - }, -}; - -/************************** USB driver **************************/ - -#define VENDOR_ID_GENESYS 0x05e3 -#define VENDOR_ID_MICROCHIP 0x0424 -#define VENDOR_ID_REALTEK 0x0bda -#define VENDOR_ID_TI 0x0451 -#define VENDOR_ID_VIA 0x2109 - -/* - * Returns the onboard_hub platform device that is associated with the USB - * device passed as parameter. - */ -static struct onboard_hub *_find_onboard_hub(struct device *dev) -{ - struct platform_device *pdev; - struct device_node *np; - struct onboard_hub *hub; - - pdev = of_find_device_by_node(dev->of_node); - if (!pdev) { - np = of_parse_phandle(dev->of_node, "peer-hub", 0); - if (!np) { - dev_err(dev, "failed to find device node for peer hub\n"); - return ERR_PTR(-EINVAL); - } - - pdev = of_find_device_by_node(np); - of_node_put(np); - - if (!pdev) - return ERR_PTR(-ENODEV); - } - - hub = dev_get_drvdata(&pdev->dev); - put_device(&pdev->dev); - - /* - * The presence of drvdata ('hub') indicates that the platform driver - * finished probing. This handles the case where (conceivably) we could - * be running at the exact same time as the platform driver's probe. If - * we detect the race we request probe deferral and we'll come back and - * try again. - */ - if (!hub) - return ERR_PTR(-EPROBE_DEFER); - - return hub; -} - -static int onboard_hub_usbdev_probe(struct usb_device *udev) -{ - struct device *dev = &udev->dev; - struct onboard_hub *hub; - int err; - - /* ignore supported hubs without device tree node */ - if (!dev->of_node) - return -ENODEV; - - hub = _find_onboard_hub(dev); - if (IS_ERR(hub)) - return PTR_ERR(hub); - - dev_set_drvdata(dev, hub); - - err = onboard_hub_add_usbdev(hub, udev); - if (err) - return err; - - return 0; -} - -static void onboard_hub_usbdev_disconnect(struct usb_device *udev) -{ - struct onboard_hub *hub = dev_get_drvdata(&udev->dev); - - onboard_hub_remove_usbdev(hub, udev); -} - -static const struct usb_device_id onboard_hub_id_table[] = { - { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */ - { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */ - { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ - { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ - { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ - { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ - { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ - { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ - { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 */ - {} -}; -MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); - -static struct usb_device_driver onboard_hub_usbdev_driver = { - .name = "onboard-usb-hub", - .probe = onboard_hub_usbdev_probe, - .disconnect = onboard_hub_usbdev_disconnect, - .generic_subclass = 1, - .supports_autosuspend = 1, - .id_table = onboard_hub_id_table, -}; - -static int __init onboard_hub_init(void) -{ - int ret; - - ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); - if (ret) - return ret; - - ret = platform_driver_register(&onboard_hub_driver); - if (ret) - usb_deregister_device_driver(&onboard_hub_usbdev_driver); - - return ret; -} -module_init(onboard_hub_init); - -static void __exit onboard_hub_exit(void) -{ - usb_deregister_device_driver(&onboard_hub_usbdev_driver); - platform_driver_unregister(&onboard_hub_driver); - - cancel_work_sync(&attach_usb_driver_work); -} -module_exit(onboard_hub_exit); - -MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); -MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h deleted file mode 100644 index aca5f50eb0da..000000000000 --- a/drivers/usb/misc/onboard_usb_hub.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright (c) 2022, Google LLC - */ - -#ifndef _USB_MISC_ONBOARD_USB_HUB_H -#define _USB_MISC_ONBOARD_USB_HUB_H - -struct onboard_hub_pdata { - unsigned long reset_us; /* reset pulse width in us */ -}; - -static const struct onboard_hub_pdata microchip_usb424_data = { - .reset_us = 1, -}; - -static const struct onboard_hub_pdata realtek_rts5411_data = { - .reset_us = 0, -}; - -static const struct onboard_hub_pdata ti_tusb8041_data = { - .reset_us = 3000, -}; - -static const struct onboard_hub_pdata genesys_gl850g_data = { - .reset_us = 3, -}; - -static const struct onboard_hub_pdata genesys_gl852g_data = { - .reset_us = 50, -}; - -static const struct onboard_hub_pdata vialab_vl817_data = { - .reset_us = 10, -}; - -static const struct of_device_id onboard_hub_match[] = { - { .compatible = "usb424,2514", .data = µchip_usb424_data, }, - { .compatible = "usb424,2517", .data = µchip_usb424_data, }, - { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, - { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, - { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, - { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, - { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, - { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, - { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, - { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, - { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, - { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, - {} -}; - -#endif /* _USB_MISC_ONBOARD_USB_HUB_H */ diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c index 7f371ea1248c..926419ca560f 100644 --- a/drivers/usb/misc/qcom_eud.c +++ b/drivers/usb/misc/qcom_eud.c @@ -15,6 +15,7 @@ #include <linux/slab.h> #include <linux/sysfs.h> #include <linux/usb/role.h> +#include <linux/firmware/qcom/qcom_scm.h> #define EUD_REG_INT1_EN_MASK 0x0024 #define EUD_REG_INT_STATUS_1 0x0044 @@ -34,7 +35,7 @@ struct eud_chip { struct device *dev; struct usb_role_switch *role_sw; void __iomem *base; - void __iomem *mode_mgr; + phys_addr_t mode_mgr; unsigned int int_status; int irq; bool enabled; @@ -43,18 +44,29 @@ struct eud_chip { static int enable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1); + if (ret) + return ret; + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, priv->base + EUD_REG_INT1_EN_MASK); - writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); } -static void disable_eud(struct eud_chip *priv) +static int disable_eud(struct eud_chip *priv) { + int ret; + + ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0); + if (ret) + return ret; + writel(0, priv->base + EUD_REG_CSR_EUD_EN); - writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); + return 0; } static ssize_t enable_show(struct device *dev, @@ -82,11 +94,12 @@ static ssize_t enable_store(struct device *dev, chip->enabled = enable; else disable_eud(chip); + } else { - disable_eud(chip); + ret = disable_eud(chip); } - return count; + return ret < 0 ? ret : count; } static DEVICE_ATTR_RW(enable); @@ -178,6 +191,7 @@ static void eud_role_switch_release(void *data) static int eud_probe(struct platform_device *pdev) { struct eud_chip *chip; + struct resource *res; int ret; chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); @@ -193,18 +207,21 @@ static int eud_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); if (ret) - return dev_err_probe(chip->dev, ret, - "failed to add role switch release action\n"); + return ret; chip->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(chip->base)) return PTR_ERR(chip->base); - chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(chip->mode_mgr)) - return PTR_ERR(chip->mode_mgr); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + chip->mode_mgr = res->start; chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return chip->irq; + ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); if (ret) @@ -229,14 +246,14 @@ static void eud_remove(struct platform_device *pdev) } static const struct of_device_id eud_dt_match[] = { - { .compatible = "qcom,sc7280-eud" }, + { .compatible = "qcom,eud" }, { } }; MODULE_DEVICE_TABLE(of, eud_dt_match); static struct platform_driver eud_driver = { .probe = eud_probe, - .remove_new = eud_remove, + .remove = eud_remove, .driver = { .name = "qcom_eud", .dev_groups = eud_groups, diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c new file mode 100644 index 000000000000..9e65bb9577ea --- /dev/null +++ b/drivers/usb/misc/usb-ljca.c @@ -0,0 +1,909 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel La Jolla Cove Adapter USB driver + * + * Copyright (c) 2023, Intel Corporation. + */ + +#include <linux/acpi.h> +#include <linux/auxiliary_bus.h> +#include <linux/dev_printk.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/usb.h> +#include <linux/usb/ljca.h> + +#include <linux/unaligned.h> + +/* command flags */ +#define LJCA_ACK_FLAG BIT(0) +#define LJCA_RESP_FLAG BIT(1) +#define LJCA_CMPL_FLAG BIT(2) + +#define LJCA_MAX_PACKET_SIZE 64u +#define LJCA_MAX_PAYLOAD_SIZE \ + (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg)) + +#define LJCA_WRITE_TIMEOUT_MS 200 +#define LJCA_WRITE_ACK_TIMEOUT_MS 500 +#define LJCA_ENUM_CLIENT_TIMEOUT_MS 20 + +/* ljca client type */ +enum ljca_client_type { + LJCA_CLIENT_MNG = 1, + LJCA_CLIENT_GPIO = 3, + LJCA_CLIENT_I2C = 4, + LJCA_CLIENT_SPI = 5, +}; + +/* MNG client commands */ +enum ljca_mng_cmd { + LJCA_MNG_RESET = 2, + LJCA_MNG_ENUM_GPIO = 4, + LJCA_MNG_ENUM_I2C = 5, + LJCA_MNG_ENUM_SPI = 8, +}; + +/* ljca client acpi _ADR */ +enum ljca_client_acpi_adr { + LJCA_GPIO_ACPI_ADR, + LJCA_I2C1_ACPI_ADR, + LJCA_I2C2_ACPI_ADR, + LJCA_SPI1_ACPI_ADR, + LJCA_SPI2_ACPI_ADR, + LJCA_CLIENT_ACPI_ADR_MAX, +}; + +/* ljca cmd message structure */ +struct ljca_msg { + u8 type; + u8 cmd; + u8 flags; + u8 len; + u8 data[] __counted_by(len); +} __packed; + +struct ljca_i2c_ctr_info { + u8 id; + u8 capacity; + u8 intr_pin; +} __packed; + +struct ljca_i2c_descriptor { + u8 num; + struct ljca_i2c_ctr_info info[] __counted_by(num); +} __packed; + +struct ljca_spi_ctr_info { + u8 id; + u8 capacity; + u8 intr_pin; +} __packed; + +struct ljca_spi_descriptor { + u8 num; + struct ljca_spi_ctr_info info[] __counted_by(num); +} __packed; + +struct ljca_bank_descriptor { + u8 bank_id; + u8 pin_num; + + /* 1 bit for each gpio, 1 means valid */ + __le32 valid_pins; +} __packed; + +struct ljca_gpio_descriptor { + u8 pins_per_bank; + u8 bank_num; + struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num); +} __packed; + +/** + * struct ljca_adapter - represent a ljca adapter + * + * @intf: the usb interface for this ljca adapter + * @usb_dev: the usb device for this ljca adapter + * @dev: the specific device info of the usb interface + * @rx_pipe: bulk in pipe for receive data from firmware + * @tx_pipe: bulk out pipe for send data to firmware + * @rx_urb: urb used for the bulk in pipe + * @rx_buf: buffer used to receive command response and event + * @rx_len: length of rx buffer + * @ex_buf: external buffer to save command response + * @ex_buf_len: length of external buffer + * @actual_length: actual length of data copied to external buffer + * @tx_buf: buffer used to download command to firmware + * @tx_buf_len: length of tx buffer + * @lock: spinlock to protect tx_buf and ex_buf + * @cmd_completion: completion object as the command receives ack + * @mutex: mutex to avoid command download concurrently + * @client_list: client device list + * @disconnect: usb disconnect ongoing or not + * @reset_id: used to reset firmware + */ +struct ljca_adapter { + struct usb_interface *intf; + struct usb_device *usb_dev; + struct device *dev; + + unsigned int rx_pipe; + unsigned int tx_pipe; + + struct urb *rx_urb; + void *rx_buf; + unsigned int rx_len; + + u8 *ex_buf; + u8 ex_buf_len; + u8 actual_length; + + void *tx_buf; + u8 tx_buf_len; + + spinlock_t lock; + + struct completion cmd_completion; + struct mutex mutex; + + struct list_head client_list; + + bool disconnect; + + u32 reset_id; +}; + +struct ljca_match_ids_walk_data { + const struct acpi_device_id *ids; + const char *uid; + struct acpi_device *adev; +}; + +/* + * ACPI hardware IDs for LJCA client devices. + * + * [1] Some BIOS implementations use these IDs for denoting LJCA client devices + * even though the IDs have been allocated for USBIO. This isn't a problem + * as the usb-ljca driver is probed based on the USB device's vendor and + * product IDs and its client drivers are probed based on auxiliary device + * names, not these ACPI _HIDs. List of such systems: + * + * Dell Precision 5490 + */ +static const struct acpi_device_id ljca_gpio_hids[] = { + { "INTC100B" }, /* RPL LJCA GPIO */ + { "INTC1074" }, /* CVF LJCA GPIO */ + { "INTC1096" }, /* ADL LJCA GPIO */ + { "INTC10B5" }, /* LNL LJCA GPIO */ + { "INTC10D1" }, /* MTL (CVF VSC) USBIO GPIO [1] */ + {}, +}; + +static const struct acpi_device_id ljca_i2c_hids[] = { + { "INTC100C" }, /* RPL LJCA I2C */ + { "INTC1075" }, /* CVF LJCA I2C */ + { "INTC1097" }, /* ADL LJCA I2C */ + { "INTC10D2" }, /* MTL (CVF VSC) USBIO I2C [1] */ + {}, +}; + +static const struct acpi_device_id ljca_spi_hids[] = { + { "INTC100D" }, /* RPL LJCA SPI */ + { "INTC1091" }, /* TGL/ADL LJCA SPI */ + { "INTC1098" }, /* ADL LJCA SPI */ + { "INTC10D3" }, /* MTL (CVF VSC) USBIO SPI [1] */ + {}, +}; + +static void ljca_handle_event(struct ljca_adapter *adap, + struct ljca_msg *header) +{ + struct ljca_client *client; + + list_for_each_entry(client, &adap->client_list, link) { + /* + * Currently only GPIO register event callback, but + * firmware message structure should include id when + * multiple same type clients register event callback. + */ + if (client->type == header->type) { + unsigned long flags; + + spin_lock_irqsave(&client->event_cb_lock, flags); + client->event_cb(client->context, header->cmd, + header->data, header->len); + spin_unlock_irqrestore(&client->event_cb_lock, flags); + + break; + } + } +} + +/* process command ack and received data if available */ +static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header) +{ + struct ljca_msg *tx_header = adap->tx_buf; + u8 ibuf_len, actual_len = 0; + unsigned long flags; + u8 *ibuf; + + spin_lock_irqsave(&adap->lock, flags); + + if (tx_header->type != header->type || tx_header->cmd != header->cmd) { + spin_unlock_irqrestore(&adap->lock, flags); + dev_err(adap->dev, "cmd ack mismatch error\n"); + return; + } + + ibuf_len = adap->ex_buf_len; + ibuf = adap->ex_buf; + + if (ibuf && ibuf_len) { + actual_len = min(header->len, ibuf_len); + + /* copy received data to external buffer */ + memcpy(ibuf, header->data, actual_len); + } + /* update copied data length */ + adap->actual_length = actual_len; + + spin_unlock_irqrestore(&adap->lock, flags); + + complete(&adap->cmd_completion); +} + +static void ljca_recv(struct urb *urb) +{ + struct ljca_msg *header = urb->transfer_buffer; + struct ljca_adapter *adap = urb->context; + int ret; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ENOENT: + /* + * directly complete the possible ongoing transfer + * during disconnect + */ + if (adap->disconnect) + complete(&adap->cmd_completion); + return; + case -ECONNRESET: + case -ESHUTDOWN: + case -EPIPE: + /* rx urb is terminated */ + dev_dbg(adap->dev, "rx urb terminated with status: %d\n", + urb->status); + return; + default: + dev_dbg(adap->dev, "rx urb error: %d\n", urb->status); + goto resubmit; + } + + if (header->len + sizeof(*header) != urb->actual_length) + goto resubmit; + + if (header->flags & LJCA_ACK_FLAG) + ljca_handle_cmd_ack(adap, header); + else + ljca_handle_event(adap, header); + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret && ret != -EPERM) + dev_err(adap->dev, "resubmit rx urb error %d\n", ret); +} + +static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd, + const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len, + bool ack, unsigned long timeout) +{ + unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len; + struct ljca_msg *header = adap->tx_buf; + unsigned int transferred; + unsigned long flags; + int ret; + + if (adap->disconnect) + return -ENODEV; + + if (msg_len > adap->tx_buf_len) + return -EINVAL; + + mutex_lock(&adap->mutex); + + spin_lock_irqsave(&adap->lock, flags); + + header->type = type; + header->cmd = cmd; + header->len = obuf_len; + if (obuf) + memcpy(header->data, obuf, obuf_len); + + header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0); + + adap->ex_buf = ibuf; + adap->ex_buf_len = ibuf_len; + adap->actual_length = 0; + + spin_unlock_irqrestore(&adap->lock, flags); + + reinit_completion(&adap->cmd_completion); + + ret = usb_autopm_get_interface(adap->intf); + if (ret < 0) + goto out; + + ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header, + msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS); + if (ret < 0) + goto out_put; + if (transferred != msg_len) { + ret = -EIO; + goto out_put; + } + + if (ack) { + ret = wait_for_completion_timeout(&adap->cmd_completion, + timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto out_put; + } + } + ret = adap->actual_length; + +out_put: + usb_autopm_put_interface(adap->intf); + +out: + spin_lock_irqsave(&adap->lock, flags); + adap->ex_buf = NULL; + adap->ex_buf_len = 0; + + memset(header, 0, sizeof(*header)); + spin_unlock_irqrestore(&adap->lock, flags); + + mutex_unlock(&adap->mutex); + + return ret; +} + +int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf, + u8 obuf_len, u8 *ibuf, u8 ibuf_len) +{ + return ljca_send(client->adapter, client->type, cmd, + obuf, obuf_len, ibuf, ibuf_len, true, + LJCA_WRITE_ACK_TIMEOUT_MS); +} +EXPORT_SYMBOL_NS_GPL(ljca_transfer, "LJCA"); + +int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf, + u8 obuf_len) +{ + return ljca_send(client->adapter, client->type, cmd, obuf, + obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS); +} +EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, "LJCA"); + +int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb, + void *context) +{ + unsigned long flags; + + if (!event_cb) + return -EINVAL; + + spin_lock_irqsave(&client->event_cb_lock, flags); + + if (client->event_cb) { + spin_unlock_irqrestore(&client->event_cb_lock, flags); + return -EALREADY; + } + + client->event_cb = event_cb; + client->context = context; + + spin_unlock_irqrestore(&client->event_cb_lock, flags); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, "LJCA"); + +void ljca_unregister_event_cb(struct ljca_client *client) +{ + unsigned long flags; + + spin_lock_irqsave(&client->event_cb_lock, flags); + + client->event_cb = NULL; + client->context = NULL; + + spin_unlock_irqrestore(&client->event_cb_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, "LJCA"); + +static int ljca_match_device_ids(struct acpi_device *adev, void *data) +{ + struct ljca_match_ids_walk_data *wd = data; + const char *uid = acpi_device_uid(adev); + + if (acpi_match_device_ids(adev, wd->ids)) + return 0; + + if (!wd->uid) + goto match; + + if (!uid) + /* + * Some DSDTs have only one ACPI companion for the two I2C + * controllers and they don't set a UID at all (e.g. Dell + * Latitude 9420). On these platforms only the first I2C + * controller is used, so if a HID match has no UID we use + * "0" as the UID and assign ACPI companion to the first + * I2C controller. + */ + uid = "0"; + else + uid = strchr(uid, wd->uid[0]); + + if (!uid || strcmp(uid, wd->uid)) + return 0; + +match: + wd->adev = adev; + + return 1; +} + +/* bind auxiliary device to acpi device */ +static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap, + struct auxiliary_device *auxdev, + u64 adr, u8 id) +{ + struct ljca_match_ids_walk_data wd = { 0 }; + struct device *dev = adap->dev; + struct acpi_device *parent; + char uid[4]; + + parent = ACPI_COMPANION(dev); + if (!parent) + return; + + /* + * Currently LJCA hw doesn't use _ADR instead the shipped + * platforms use _HID to distinguish children devices. + */ + switch (adr) { + case LJCA_GPIO_ACPI_ADR: + wd.ids = ljca_gpio_hids; + break; + case LJCA_I2C1_ACPI_ADR: + case LJCA_I2C2_ACPI_ADR: + snprintf(uid, sizeof(uid), "%d", id); + wd.uid = uid; + wd.ids = ljca_i2c_hids; + break; + case LJCA_SPI1_ACPI_ADR: + case LJCA_SPI2_ACPI_ADR: + wd.ids = ljca_spi_hids; + break; + default: + dev_warn(dev, "unsupported _ADR\n"); + return; + } + + acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); + if (wd.adev) { + ACPI_COMPANION_SET(&auxdev->dev, wd.adev); + return; + } + + parent = ACPI_COMPANION(dev->parent->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(&auxdev->dev, wd.adev); +} + +static void ljca_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + + kfree(auxdev->dev.platform_data); +} + +static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id, + char *name, void *data, u64 adr) +{ + struct auxiliary_device *auxdev; + struct ljca_client *client; + int ret; + + client = kzalloc(sizeof *client, GFP_KERNEL); + if (!client) { + kfree(data); + return -ENOMEM; + } + + client->type = type; + client->id = id; + client->adapter = adap; + spin_lock_init(&client->event_cb_lock); + + auxdev = &client->auxdev; + auxdev->name = name; + auxdev->id = id; + + auxdev->dev.parent = adap->dev; + auxdev->dev.platform_data = data; + auxdev->dev.release = ljca_auxdev_release; + + ret = auxiliary_device_init(auxdev); + if (ret) { + kfree(data); + goto err_free; + } + + ljca_auxdev_acpi_bind(adap, auxdev, adr, id); + + ret = auxiliary_device_add(auxdev); + if (ret) + goto err_uninit; + + list_add_tail(&client->link, &adap->client_list); + + return 0; + +err_uninit: + auxiliary_device_uninit(auxdev); + +err_free: + kfree(client); + + return ret; +} + +static int ljca_enumerate_gpio(struct ljca_adapter *adap) +{ + u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)]; + struct ljca_gpio_descriptor *desc; + struct ljca_gpio_info *gpio_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + int ret, gpio_num; + unsigned int i; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* check firmware response */ + desc = (struct ljca_gpio_descriptor *)buf; + if (ret != struct_size(desc, bank_desc, desc->bank_num)) + return -EINVAL; + + gpio_num = desc->pins_per_bank * desc->bank_num; + if (gpio_num > LJCA_MAX_GPIO_NUM) + return -EINVAL; + + /* construct platform data */ + gpio_info = kzalloc(sizeof *gpio_info, GFP_KERNEL); + if (!gpio_info) + return -ENOMEM; + gpio_info->num = gpio_num; + + for (i = 0; i < desc->bank_num; i++) + valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins); + bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num); + + return ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio", + gpio_info, LJCA_GPIO_ACPI_ADR); +} + +static int ljca_enumerate_i2c(struct ljca_adapter *adap) +{ + struct ljca_i2c_descriptor *desc; + struct ljca_i2c_info *i2c_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + unsigned int i; + int ret; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* check firmware response */ + desc = (struct ljca_i2c_descriptor *)buf; + if (ret != struct_size(desc, info, desc->num)) + return -EINVAL; + + for (i = 0; i < desc->num; i++) { + /* construct platform data */ + i2c_info = kzalloc(sizeof *i2c_info, GFP_KERNEL); + if (!i2c_info) + return -ENOMEM; + + i2c_info->id = desc->info[i].id; + i2c_info->capacity = desc->info[i].capacity; + i2c_info->intr_pin = desc->info[i].intr_pin; + + ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i, + "ljca-i2c", i2c_info, + LJCA_I2C1_ACPI_ADR + i); + if (ret) + return ret; + } + + return 0; +} + +static int ljca_enumerate_spi(struct ljca_adapter *adap) +{ + struct ljca_spi_descriptor *desc; + struct ljca_spi_info *spi_info; + u8 buf[LJCA_MAX_PAYLOAD_SIZE]; + unsigned int i; + int ret; + + /* Not all LJCA chips implement SPI, a timeout reading the descriptors is normal */ + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf, + sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS); + if (ret < 0) + return (ret == -ETIMEDOUT) ? 0 : ret; + + /* check firmware response */ + desc = (struct ljca_spi_descriptor *)buf; + if (ret != struct_size(desc, info, desc->num)) + return -EINVAL; + + for (i = 0; i < desc->num; i++) { + /* construct platform data */ + spi_info = kzalloc(sizeof *spi_info, GFP_KERNEL); + if (!spi_info) + return -ENOMEM; + + spi_info->id = desc->info[i].id; + spi_info->capacity = desc->info[i].capacity; + + ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i, + "ljca-spi", spi_info, + LJCA_SPI1_ACPI_ADR + i); + if (ret) + return ret; + } + + return 0; +} + +static int ljca_reset_handshake(struct ljca_adapter *adap) +{ + __le32 reset_id = cpu_to_le32(adap->reset_id); + __le32 reset_id_ret = 0; + int ret; + + adap->reset_id++; + + ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id, + sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32), + true, LJCA_WRITE_ACK_TIMEOUT_MS); + if (ret < 0) + return ret; + + if (reset_id_ret != reset_id) + return -EINVAL; + + return 0; +} + +static int ljca_enumerate_clients(struct ljca_adapter *adap) +{ + struct ljca_client *client, *next; + int ret; + + ret = ljca_reset_handshake(adap); + if (ret) + goto err_kill; + + ret = ljca_enumerate_gpio(adap); + if (ret) { + dev_err(adap->dev, "enumerate GPIO error\n"); + goto err_kill; + } + + ret = ljca_enumerate_i2c(adap); + if (ret) { + dev_err(adap->dev, "enumerate I2C error\n"); + goto err_kill; + } + + ret = ljca_enumerate_spi(adap); + if (ret) { + dev_err(adap->dev, "enumerate SPI error\n"); + goto err_kill; + } + + return 0; + +err_kill: + adap->disconnect = true; + + usb_kill_urb(adap->rx_urb); + + list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + + list_del_init(&client->link); + kfree(client); + } + + return ret; +} + +static int ljca_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + struct usb_host_interface *alt = interface->cur_altsetting; + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &interface->dev; + struct ljca_adapter *adap; + int ret; + + adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL); + if (!adap) + return -ENOMEM; + + /* separate tx buffer allocation for alignment */ + adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL); + if (!adap->tx_buf) + return -ENOMEM; + adap->tx_buf_len = LJCA_MAX_PACKET_SIZE; + + mutex_init(&adap->mutex); + spin_lock_init(&adap->lock); + init_completion(&adap->cmd_completion); + INIT_LIST_HEAD(&adap->client_list); + + adap->intf = usb_get_intf(interface); + adap->usb_dev = usb_dev; + adap->dev = dev; + + /* + * find the first bulk in and out endpoints. + * ignore any others. + */ + ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); + if (ret) { + dev_err(dev, "bulk endpoints not found\n"); + goto err_put; + } + adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in)); + adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out)); + + /* setup rx buffer */ + adap->rx_len = usb_endpoint_maxp(ep_in); + adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL); + if (!adap->rx_buf) { + ret = -ENOMEM; + goto err_put; + } + + /* alloc rx urb */ + adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!adap->rx_urb) { + ret = -ENOMEM; + goto err_put; + } + usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe, + adap->rx_buf, adap->rx_len, ljca_recv, adap); + + usb_set_intfdata(interface, adap); + + /* submit rx urb before enumerate clients */ + ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL); + if (ret) { + dev_err(dev, "submit rx urb failed: %d\n", ret); + goto err_free; + } + + ret = ljca_enumerate_clients(adap); + if (ret) + goto err_free; + + /* + * This works around problems with ov2740 initialization on some + * Lenovo platforms. The autosuspend delay, has to be smaller than + * the delay after setting the reset_gpio line in ov2740_resume(). + * Otherwise the sensor randomly fails to initialize. + */ + pm_runtime_set_autosuspend_delay(&usb_dev->dev, 10); + + usb_enable_autosuspend(usb_dev); + + return 0; + +err_free: + usb_free_urb(adap->rx_urb); + +err_put: + usb_put_intf(adap->intf); + + mutex_destroy(&adap->mutex); + + return ret; +} + +static void ljca_disconnect(struct usb_interface *interface) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + struct ljca_client *client, *next; + + adap->disconnect = true; + + usb_kill_urb(adap->rx_urb); + + list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + + list_del_init(&client->link); + kfree(client); + } + + usb_free_urb(adap->rx_urb); + + usb_put_intf(adap->intf); + + mutex_destroy(&adap->mutex); +} + +static int ljca_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + + usb_kill_urb(adap->rx_urb); + + return 0; +} + +static int ljca_resume(struct usb_interface *interface) +{ + struct ljca_adapter *adap = usb_get_intfdata(interface); + + return usb_submit_urb(adap->rx_urb, GFP_KERNEL); +} + +static const struct usb_device_id ljca_table[] = { + { USB_DEVICE(0x8086, 0x0b63) }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(usb, ljca_table); + +static struct usb_driver ljca_driver = { + .name = "ljca", + .id_table = ljca_table, + .probe = ljca_probe, + .disconnect = ljca_disconnect, + .suspend = ljca_suspend, + .resume = ljca_resume, + .supports_autosuspend = 1, +}; +module_usb_driver(ljca_driver); + +MODULE_AUTHOR("Wentong Wu"); +MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>"); +MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>"); +MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c index e4edb486b69e..7c0778631bea 100644 --- a/drivers/usb/misc/usb251xb.c +++ b/drivers/usb/misc/usb251xb.c @@ -16,7 +16,8 @@ #include <linux/i2c.h> #include <linux/module.h> #include <linux/nls.h> -#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -242,15 +243,19 @@ static int usb251xb_check_dev_children(struct device *dev, void *child) static int usb251x_check_gpio_chip(struct usb251xb *hub) { struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); - struct i2c_adapter *adap = hub->i2c->adapter; + struct i2c_adapter *adap; int ret; + if (!hub->i2c) + return 0; + if (!hub->gpio_reset) return 0; if (!gc) return -EINVAL; + adap = hub->i2c->adapter; ret = usb251xb_check_dev_children(&adap->dev, gc->parent); if (ret) { dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); @@ -271,7 +276,8 @@ static void usb251xb_reset(struct usb251xb *hub) if (!hub->gpio_reset) return; - i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); gpiod_set_value_cansleep(hub->gpio_reset, 1); usleep_range(1, 10); /* >=1us RESET_N asserted */ @@ -280,7 +286,8 @@ static void usb251xb_reset(struct usb251xb *hub) /* wait for hub recovery/stabilization */ usleep_range(500, 750); /* >=500us after RESET_N deasserted */ - i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + if (hub->i2c) + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); } static int usb251xb_connect(struct usb251xb *hub) @@ -289,6 +296,12 @@ static int usb251xb_connect(struct usb251xb *hub) int err, i; char i2c_wb[USB251XB_I2C_REG_SZ]; + if (!hub->i2c) { + usb251xb_reset(hub); + dev_info(dev, "hub is put in default configuration.\n"); + return 0; + } + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); if (hub->skip_config) { @@ -382,11 +395,9 @@ static void usb251xb_get_ports_field(struct usb251xb *hub, bool ds_only, u8 *fld) { struct device *dev = hub->dev; - struct property *prop; - const __be32 *p; u32 port; - of_property_for_each_u32(dev->of_node, prop_name, prop, p, port) { + of_property_for_each_u32(dev->of_node, prop_name, port) { if ((port >= ds_only ? 1 : 0) && (port <= port_cnt)) *fld |= BIT(port); else @@ -638,10 +649,8 @@ static int usb251xb_probe(struct usb251xb *hub) if (np && usb_data) { err = usb251xb_get_ofdata(hub, usb_data); - if (err) { - dev_err(dev, "failed to get ofdata: %d\n", err); - return err; - } + if (err) + return dev_err_probe(dev, err, "failed to get ofdata\n"); } /* @@ -702,18 +711,13 @@ static int usb251xb_i2c_probe(struct i2c_client *i2c) return usb251xb_probe(hub); } -static int __maybe_unused usb251xb_suspend(struct device *dev) +static int usb251xb_suspend(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); - return regulator_disable(hub->vdd); } -static int __maybe_unused usb251xb_resume(struct device *dev) +static int usb251xb_resume(struct usb251xb *hub) { - struct i2c_client *client = to_i2c_client(dev); - struct usb251xb *hub = i2c_get_clientdata(client); int err; err = regulator_enable(hub->vdd); @@ -723,18 +727,34 @@ static int __maybe_unused usb251xb_resume(struct device *dev) return usb251xb_connect(hub); } -static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); +static int usb251xb_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_suspend(hub); +} + +static int usb251xb_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return usb251xb_resume(hub); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume); static const struct i2c_device_id usb251xb_id[] = { - { "usb2422", 0 }, - { "usb2512b", 0 }, - { "usb2512bi", 0 }, - { "usb2513b", 0 }, - { "usb2513bi", 0 }, - { "usb2514b", 0 }, - { "usb2514bi", 0 }, - { "usb2517", 0 }, - { "usb2517i", 0 }, + { "usb2422" }, + { "usb2512b" }, + { "usb2512bi" }, + { "usb2513b" }, + { "usb2513bi" }, + { "usb2514b" }, + { "usb2514bi" }, + { "usb2517" }, + { "usb2517i" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, usb251xb_id); @@ -743,13 +763,71 @@ static struct i2c_driver usb251xb_i2c_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = usb251xb_of_match, - .pm = &usb251xb_pm_ops, + .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops), }, .probe = usb251xb_i2c_probe, .id_table = usb251xb_id, }; -module_i2c_driver(usb251xb_i2c_driver); +static int usb251xb_plat_probe(struct platform_device *pdev) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + platform_set_drvdata(pdev, hub); + hub->dev = &pdev->dev; + + return usb251xb_probe(hub); +} + +static int usb251xb_plat_suspend(struct device *dev) +{ + return usb251xb_suspend(dev_get_drvdata(dev)); +} + +static int usb251xb_plat_resume(struct device *dev) +{ + return usb251xb_resume(dev_get_drvdata(dev)); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume); + +static struct platform_driver usb251xb_plat_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops), + }, + .probe = usb251xb_plat_probe, +}; + +static int __init usb251xb_init(void) +{ + int err; + + err = i2c_add_driver(&usb251xb_i2c_driver); + if (err) + return err; + + err = platform_driver_register(&usb251xb_plat_driver); + if (err) { + i2c_del_driver(&usb251xb_i2c_driver); + return err; + } + + return 0; +} +module_init(usb251xb_init); + +static void __exit usb251xb_exit(void) +{ + platform_driver_unregister(&usb251xb_plat_driver); + i2c_del_driver(&usb251xb_i2c_driver); +} +module_exit(usb251xb_exit); MODULE_AUTHOR("Richard Leitner <richard.leitner@skidata.com>"); MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver"); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index 72765077932c..322e59381b78 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -390,7 +390,7 @@ static SIMPLE_DEV_PM_OPS(usb3503_platform_pm_ops, usb3503_platform_suspend, usb3503_platform_resume); static const struct i2c_device_id usb3503_id[] = { - { USB3503_I2C_NAME, 0 }, + { USB3503_I2C_NAME }, { } }; MODULE_DEVICE_TABLE(i2c, usb3503_id); @@ -423,7 +423,7 @@ static struct platform_driver usb3503_platform_driver = { .pm = pm_ptr(&usb3503_platform_pm_ops), }, .probe = usb3503_platform_probe, - .remove_new = usb3503_platform_remove, + .remove = usb3503_platform_remove, }; static int __init usb3503_init(void) diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c index 065e269ba4e3..c9a2fb3518ae 100644 --- a/drivers/usb/misc/usb4604.c +++ b/drivers/usb/misc/usb4604.c @@ -135,7 +135,7 @@ static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend, usb4604_i2c_resume); static const struct i2c_device_id usb4604_id[] = { - { "usb4604", 0 }, + { "usb4604" }, { } }; MODULE_DEVICE_TABLE(i2c, usb4604_id); diff --git a/drivers/usb/misc/usb_u132.h b/drivers/usb/misc/usb_u132.h deleted file mode 100644 index 1584efbbd704..000000000000 --- a/drivers/usb/misc/usb_u132.h +++ /dev/null @@ -1,97 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* -* Common Header File for the Elan Digital Systems U132 adapter -* this file should be included by both the "ftdi-u132" and -* the "u132-hcd" modules. -* -* Copyright(C) 2006 Elan Digital Systems Limited -*(http://www.elandigitalsystems.com) -* -* Author and Maintainer - Tony Olech - Elan Digital Systems -*(tony.olech@elandigitalsystems.com) -* -* The driver was written by Tony Olech(tony.olech@elandigitalsystems.com) -* based on various USB client drivers in the 2.6.15 linux kernel -* with constant reference to the 3rd Edition of Linux Device Drivers -* published by O'Reilly -* -* The U132 adapter is a USB to CardBus adapter specifically designed -* for PC cards that contain an OHCI host controller. Typical PC cards -* are the Orange Mobile 3G Option GlobeTrotter Fusion card. -* -* The U132 adapter will *NOT *work with PC cards that do not contain -* an OHCI controller. A simple way to test whether a PC card has an -* OHCI controller as an interface is to insert the PC card directly -* into a laptop(or desktop) with a CardBus slot and if "lspci" shows -* a new USB controller and "lsusb -v" shows a new OHCI Host Controller -* then there is a good chance that the U132 adapter will support the -* PC card.(you also need the specific client driver for the PC card) -* -* Please inform the Author and Maintainer about any PC cards that -* contain OHCI Host Controller and work when directly connected to -* an embedded CardBus slot but do not work when they are connected -* via an ELAN U132 adapter. -* -* The driver consists of two modules, the "ftdi-u132" module is -* a USB client driver that interfaces to the FTDI chip within -* the U132 adapter manufactured by Elan Digital Systems, and the -* "u132-hcd" module is a USB host controller driver that talks -* to the OHCI controller within CardBus card that are inserted -* in the U132 adapter. -* -* The "ftdi-u132" module should be loaded automatically by the -* hot plug system when the U132 adapter is plugged in. The module -* initialises the adapter which mostly consists of synchronising -* the FTDI chip, before continuously polling the adapter to detect -* PC card insertions. As soon as a PC card containing a recognised -* OHCI controller is seen the "ftdi-u132" module explicitly requests -* the kernel to load the "u132-hcd" module. -* -* The "ftdi-u132" module provides the interface to the inserted -* PC card and the "u132-hcd" module uses the API to send and receive -* data. The API features call-backs, so that part of the "u132-hcd" -* module code will run in the context of one of the kernel threads -* of the "ftdi-u132" module. -* -*/ -int ftdi_elan_switch_on_diagnostics(int number); -void ftdi_elan_gone_away(struct platform_device *pdev); -void start_usb_lock_device_tracing(void); -struct u132_platform_data { - u16 vendor; - u16 device; - u8 potpg; - void (*port_power) (struct device *dev, int is_on); - void (*reset) (struct device *dev); -}; -int usb_ftdi_elan_edset_single(struct platform_device *pdev, u8 ed_number, - void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, - void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, - int toggle_bits, int error_count, int condition_code, int repeat_number, - int halted, int skipped, int actual, int non_null)); -int usb_ftdi_elan_edset_output(struct platform_device *pdev, u8 ed_number, - void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, - void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, - int toggle_bits, int error_count, int condition_code, int repeat_number, - int halted, int skipped, int actual, int non_null)); -int usb_ftdi_elan_edset_empty(struct platform_device *pdev, u8 ed_number, - void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, - void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, - int toggle_bits, int error_count, int condition_code, int repeat_number, - int halted, int skipped, int actual, int non_null)); -int usb_ftdi_elan_edset_input(struct platform_device *pdev, u8 ed_number, - void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, - void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, - int toggle_bits, int error_count, int condition_code, int repeat_number, - int halted, int skipped, int actual, int non_null)); -int usb_ftdi_elan_edset_setup(struct platform_device *pdev, u8 ed_number, - void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits, - void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, - int toggle_bits, int error_count, int condition_code, int repeat_number, - int halted, int skipped, int actual, int non_null)); -int usb_ftdi_elan_edset_flush(struct platform_device *pdev, u8 ed_number, - void *endp); -int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, int mem_offset, - u8 width, u32 *data); -int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, int mem_offset, - u8 width, u32 data); diff --git a/drivers/usb/misc/usbio.c b/drivers/usb/misc/usbio.c new file mode 100644 index 000000000000..37644dddf157 --- /dev/null +++ b/drivers/usb/misc/usbio.c @@ -0,0 +1,749 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel USBIO Bridge driver + * + * Copyright (c) 2025 Intel Corporation. + * Copyright (c) 2025 Red Hat, Inc. + */ + +#include <linux/acpi.h> +#include <linux/auxiliary_bus.h> +#include <linux/byteorder/generic.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/lockdep.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/usb.h> +#include <linux/usb/usbio.h> + +/************************************* + * USBIO Bridge Protocol Definitions * + *************************************/ + +/* USBIO Control Commands */ +#define USBIO_CTRLCMD_PROTVER 0 +#define USBIO_CTRLCMD_FWVER 1 +#define USBIO_CTRLCMD_HS 2 +#define USBIO_CTRLCMD_ENUMGPIO 16 +#define USBIO_CTRLCMD_ENUMI2C 17 + +/* USBIO Packet Flags */ +#define USBIO_PKTFLAG_ACK BIT(0) +#define USBIO_PKTFLAG_RSP BIT(1) +#define USBIO_PKTFLAG_CMP BIT(2) +#define USBIO_PKTFLAG_ERR BIT(3) + +#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK) + +#define USBIO_CTRLXFER_TIMEOUT 0 +#define USBIO_BULKXFER_TIMEOUT 100 + +struct usbio_protver { + u8 ver; +} __packed; + +struct usbio_fwver { + u8 major; + u8 minor; + __le16 patch; + __le16 build; +} __packed; + +/*********************************** + * USBIO Bridge Device Definitions * + ***********************************/ + +/** + * struct usbio_device - the usb device exposing IOs + * + * @dev: the device in the usb interface + * @udev: the detected usb device + * @intf: the usb interface + * @quirks: quirks + * @ctrl_mutex: protects ctrl_buf + * @ctrl_pipe: the control transfer pipe + * @ctrlbuf_len: the size of the control transfer pipe + * @ctrlbuf: the buffer used for control transfers + * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted + * @tx_pipe: the bulk out pipe + * @txbuf_len: the size of the bulk out pipe + * @txbuf: the buffer used for bulk out transfers + * @rx_pipe: the bulk in pipe + * @rxbuf_len: the size of the bulk in pipe + * @rxdat_len: the data length at rx buffer + * @rxbuf: the buffer used for bulk in transfers + * @urb: the urb to read bulk pipe + * @done: completion object as request is done + * @cli_list: device's client list + * @nr_gpio_banks: Number of GPIO banks + * @gpios: GPIO bank descriptors + * @nr_gpio_banks: Number of I2C busses + * @gpios: I2C bank descriptors + */ +struct usbio_device { + struct device *dev; + struct usb_device *udev; + struct usb_interface *intf; + unsigned long quirks; + + struct mutex ctrl_mutex; + unsigned int ctrl_pipe; + u16 ctrlbuf_len; + void *ctrlbuf; + + struct mutex bulk_mutex; + unsigned int tx_pipe; + u16 txbuf_len; + void *txbuf; + + unsigned int rx_pipe; + u16 rxbuf_len; + u16 rxdat_len; + void *rxbuf; + struct urb *urb; + + struct completion done; + + struct list_head cli_list; + + unsigned int nr_gpio_banks; + struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS]; + + unsigned int nr_i2c_buses; + struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES]; +}; + +/** + * struct usbio_client - represents a usbio client + * + * @auxdev: auxiliary device object + * @mutex: protects @bridge + * @bridge: usbio bridge who service the client + * @link: usbio bridge clients list member + */ +struct usbio_client { + struct auxiliary_device auxdev; + struct mutex mutex; + struct usbio_device *bridge; + struct list_head link; +}; + +#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev) + +static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + struct usbio_ctrl_packet *cpkt; + unsigned int pipe; + u16 cpkt_len; + int ret; + + lockdep_assert_held(&usbio->ctrl_mutex); + + if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) || + (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt)))) + return -EMSGSIZE; + + /* Prepare Control Packet Header */ + cpkt = usbio->ctrlbuf; + cpkt->header.type = type; + cpkt->header.cmd = cmd; + if (type == USBIO_PKTTYPE_CTRL || ibuf_len) + cpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + cpkt->header.flags = USBIO_PKTFLAG_CMP; + cpkt->len = obuf_len; + + /* Copy the data */ + memcpy(cpkt->data, obuf, obuf_len); + + pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + obuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret != cpkt_len) { + dev_err(usbio->dev, "USB control out failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK)) + return 0; + + pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe); + cpkt_len = sizeof(*cpkt) + ibuf_len; + ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0, + cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT); + dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret, + (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); + + if (ret < sizeof(*cpkt)) { + dev_err(usbio->dev, "USB control in failed: %d\n", ret); + return (ret < 0) ? ret : -EPROTO; + } + + if (cpkt->header.type != type || cpkt->header.cmd != cmd || + !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n", + cpkt->header.type, cpkt->header.cmd, cpkt->header.flags); + return -EPROTO; + } + + if (cpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < cpkt->len) + return -ENOSPC; + + memcpy(ibuf, cpkt->data, cpkt->len); + + return cpkt->len; +} + +int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return -ENODEV; /* Disconnected */ + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + return ret; + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len); + + mutex_unlock(&usbio->ctrl_mutex); + usb_autopm_put_interface(usbio->intf); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO"); + +static void usbio_bulk_recv(struct urb *urb) +{ + struct usbio_bulk_packet *bpkt = urb->transfer_buffer; + struct usbio_device *usbio = urb->context; + + if (!urb->status) { + if (bpkt->header.flags & USBIO_PKTFLAG_RSP) { + usbio->rxdat_len = urb->actual_length; + complete(&usbio->done); + } + } else if (urb->status != -ENOENT) { + dev_err(usbio->dev, "Bulk in error %d\n", urb->status); + } + + usb_submit_urb(usbio->urb, GFP_ATOMIC); +} + +int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last, + const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + struct usbio_bulk_packet *bpkt; + int ret, act = 0; + u16 bpkt_len; + + lockdep_assert_held(&client->mutex); + lockdep_assert_held(&usbio->bulk_mutex); + + if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) || + (ibuf_len > (usbio->txbuf_len - sizeof(*bpkt)))) + return -EMSGSIZE; + + if (ibuf_len) + reinit_completion(&usbio->done); + + /* If no data to send, skip to read */ + if (!obuf_len) + goto read; + + /* Prepare Bulk Packet Header */ + bpkt = usbio->txbuf; + bpkt->header.type = type; + bpkt->header.cmd = cmd; + if (!last) + bpkt->header.flags = 0; + else if (ibuf_len) + bpkt->header.flags = USBIO_PKTFLAGS_REQRESP; + else + bpkt->header.flags = USBIO_PKTFLAG_CMP; + bpkt->len = cpu_to_le16(obuf_len); + + /* Copy the data */ + memcpy(bpkt->data, obuf, obuf_len); + + bpkt_len = sizeof(*bpkt) + obuf_len; + ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act, + USBIO_BULKXFER_TIMEOUT); + dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data); + + if (ret || act != bpkt_len) { + dev_err(usbio->dev, "Bulk out failed: %d\n", ret); + return ret ?: -EPROTO; + } + + if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK)) + return obuf_len; + +read: + ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT); + if (ret <= 0) { + dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret); + return ret ?: -ETIMEDOUT; + } + + act = usbio->rxdat_len; + bpkt = usbio->rxbuf; + bpkt_len = le16_to_cpu(bpkt->len); + dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act, + (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data); + + /* + * Unsupported bulk commands get only an usbio_packet_header with + * the error flag set as reply. Return -EPIPE for this case. + */ + if (act == sizeof(struct usbio_packet_header) && + (bpkt->header.flags & USBIO_PKTFLAG_ERR)) + return -EPIPE; + + if (act < sizeof(*bpkt)) { + dev_err(usbio->dev, "Bulk in short read: %d\n", act); + return -EPROTO; + } + + if (bpkt->header.type != type || bpkt->header.cmd != cmd || + !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) { + dev_err(usbio->dev, + "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n", + bpkt->header.type, bpkt->header.cmd, bpkt->header.flags); + return -EPROTO; + } + + if (bpkt->header.flags & USBIO_PKTFLAG_ERR) + return -EREMOTEIO; + + if (ibuf_len < bpkt_len) + return -ENOSPC; + + memcpy(ibuf, bpkt->data, bpkt_len); + + return bpkt_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO"); + +int usbio_acquire(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + int ret; + + mutex_lock(&client->mutex); + + usbio = client->bridge; + if (!usbio) { + ret = -ENODEV; /* Disconnected */ + goto err_unlock; + } + + ret = usb_autopm_get_interface(usbio->intf); + if (ret) + goto err_unlock; + + mutex_lock(&usbio->bulk_mutex); + + /* Leave client locked until release to avoid abba deadlock issues */ + return 0; + +err_unlock: + mutex_unlock(&client->mutex); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO"); + +void usbio_release(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio = client->bridge; + + lockdep_assert_held(&client->mutex); + + mutex_unlock(&usbio->bulk_mutex); + usb_autopm_put_interface(usbio->intf); + mutex_unlock(&client->mutex); +} +EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO"); + +void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return; /* Disconnected */ + + *txbuf_len = usbio->txbuf_len; + *rxbuf_len = usbio->rxbuf_len; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO"); + +unsigned long usbio_get_quirks(struct auxiliary_device *adev) +{ + struct usbio_client *client = adev_to_client(adev); + struct usbio_device *usbio; + + guard(mutex)(&client->mutex); + + usbio = client->bridge; + if (!usbio) + return 0; /* Disconnected */ + + return usbio->quirks; +} +EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO"); + +static void usbio_auxdev_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + struct usbio_client *client = adev_to_client(adev); + + mutex_destroy(&client->mutex); + kfree(client); +} + +static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data) +{ + struct usbio_client *client; + struct auxiliary_device *adev; + int ret; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + mutex_init(&client->mutex); + client->bridge = usbio; + adev = &client->auxdev; + adev->name = name; + adev->id = id; + + adev->dev.parent = usbio->dev; + adev->dev.platform_data = data; + adev->dev.release = usbio_auxdev_release; + + ret = auxiliary_device_init(adev); + if (ret) { + usbio_auxdev_release(&adev->dev); + return ret; + } + + ret = auxiliary_device_add(adev); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + list_add_tail(&client->link, &usbio->cli_list); + + return 0; +} + +static int usbio_enum_gpios(struct usbio_device *usbio) +{ + struct usbio_gpio_bank_desc *gpio = usbio->gpios; + + dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks); + + for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++) + dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n", + gpio[i].id, gpio[i].pins, gpio[i].bmap); + + usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio); + + return 0; +} + +static int usbio_enum_i2cs(struct usbio_device *usbio) +{ + struct usbio_i2c_bus_desc *i2c = usbio->i2cs; + + dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses); + + for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) { + dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps); + usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]); + } + + return 0; +} + +static int usbio_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + usb_kill_urb(usbio->urb); + + return 0; +} + +static int usbio_resume(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + + return usb_submit_urb(usbio->urb, GFP_KERNEL); +} + +static void usbio_disconnect(struct usb_interface *intf) +{ + struct usbio_device *usbio = usb_get_intfdata(intf); + struct usbio_client *client; + + /* Wakeup any clients waiting for a reply */ + usbio->rxdat_len = 0; + complete(&usbio->done); + + /* Let clients know the bridge is gone */ + list_for_each_entry(client, &usbio->cli_list, link) { + mutex_lock(&client->mutex); + client->bridge = NULL; + mutex_unlock(&client->mutex); + } + + /* From here on clients will no longer touch struct usbio_device */ + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + list_for_each_entry_reverse(client, &usbio->cli_list, link) { + auxiliary_device_delete(&client->auxdev); + auxiliary_device_uninit(&client->auxdev); + } +} + +static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *ep_in, *ep_out; + struct device *dev = &intf->dev; + struct usbio_protver protver; + struct usbio_device *usbio; + struct usbio_fwver fwver; + int ret; + + usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL); + if (!usbio) + return -ENOMEM; + + ret = devm_mutex_init(dev, &usbio->ctrl_mutex); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &usbio->bulk_mutex); + if (ret) + return ret; + + usbio->dev = dev; + usbio->udev = udev; + usbio->intf = intf; + usbio->quirks = id ? id->driver_info : 0; + init_completion(&usbio->done); + INIT_LIST_HEAD(&usbio->cli_list); + usb_set_intfdata(intf, usbio); + + usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc); + usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe); + usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL); + if (!usbio->ctrlbuf) + return -ENOMEM; + + /* Find the first bulk-in and bulk-out endpoints */ + ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out, + NULL, NULL); + if (ret) { + dev_err(dev, "Cannot find bulk endpoints: %d\n", ret); + return ret; + } + + usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->txbuf_len = 63; + else + usbio->txbuf_len = usb_endpoint_maxp(ep_out); + + usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL); + if (!usbio->txbuf) + return -ENOMEM; + + usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in)); + + if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63) + usbio->rxbuf_len = 63; + else + usbio->rxbuf_len = usb_endpoint_maxp(ep_in); + + usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL); + if (!usbio->rxbuf) + return -ENOMEM; + + usbio->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usbio->urb) + return -ENOMEM; + + usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf, + usbio->rxbuf_len, usbio_bulk_recv, usbio); + ret = usb_submit_urb(usbio->urb, GFP_KERNEL); + if (ret) + return dev_err_probe(dev, ret, "Submitting usb urb\n"); + + mutex_lock(&usbio->ctrl_mutex); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0, + &protver, sizeof(protver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0, + &fwver, sizeof(fwver)); + if (ret < 0) + goto err_unlock; + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0, + usbio->gpios, sizeof(usbio->gpios)); + if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc); + + ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0, + usbio->i2cs, sizeof(usbio->i2cs)); + if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) { + ret = (ret < 0) ? ret : -EPROTO; + goto err_unlock; + } + usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc); + + mutex_unlock(&usbio->ctrl_mutex); + + dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n", + protver.ver, fwver.major, fwver.minor, + le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build)); + + usbio_enum_gpios(usbio); + usbio_enum_i2cs(usbio); + + return 0; + +err_unlock: + mutex_unlock(&usbio->ctrl_mutex); + usb_kill_urb(usbio->urb); + usb_free_urb(usbio->urb); + + return ret; +} + +static const struct usb_device_id usbio_table[] = { + { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */ + .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 }, + { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */ + .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 | + USBIO_QUIRK_I2C_ALLOW_400KHZ }, + { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */ + { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */ + .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN }, + { } +}; +MODULE_DEVICE_TABLE(usb, usbio_table); + +static struct usb_driver usbio_driver = { + .name = "usbio-bridge", + .probe = usbio_probe, + .disconnect = usbio_disconnect, + .suspend = usbio_suspend, + .resume = usbio_resume, + .id_table = usbio_table, + .supports_autosuspend = 1, +}; +module_usb_driver(usbio_driver); + +struct usbio_match_ids_walk_data { + struct acpi_device *adev; + const struct acpi_device_id *hids; + unsigned int id; +}; + +static int usbio_match_device_ids(struct acpi_device *adev, void *data) +{ + struct usbio_match_ids_walk_data *wd = data; + unsigned int id = 0; + char *uid; + + if (acpi_match_device_ids(adev, wd->hids)) + return 0; + + uid = acpi_device_uid(adev); + if (uid) { + for (int i = 0; i < strlen(uid); i++) { + if (!kstrtouint(&uid[i], 10, &id)) + break; + } + } + + if (!uid || wd->id == id) { + wd->adev = adev; + return 1; + } + + return 0; +} + +void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids) +{ + struct device *dev = &adev->dev; + struct acpi_device *parent; + struct usbio_match_ids_walk_data wd = { + .adev = NULL, + .hids = hids, + .id = adev->id, + }; + + parent = ACPI_COMPANION(dev->parent); + if (!parent) + return; + + acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd); + if (wd.adev) + ACPI_COMPANION_SET(dev, wd.adev); +} +EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO"); + +MODULE_DESCRIPTION("Intel USBIO Bridge driver"); +MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c index c3114d9bd128..546deff754ba 100644 --- a/drivers/usb/misc/usbsevseg.c +++ b/drivers/usb/misc/usbsevseg.c @@ -305,7 +305,7 @@ static int sevseg_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev = interface_to_usbdev(interface); - struct usb_sevsegdev *mydev = NULL; + struct usb_sevsegdev *mydev; int rc = -ENOMEM; mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL); diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index ac0d75ac2d2f..5c92c8d8e283 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -592,7 +592,7 @@ struct sg_timeout { static void sg_timeout(struct timer_list *t) { - struct sg_timeout *timeout = from_timer(timeout, t, timer); + struct sg_timeout *timeout = timer_container_of(timeout, t, timer); usb_sg_cancel(timeout->req); } @@ -626,11 +626,11 @@ static int perform_sglist( mod_timer(&timeout.timer, jiffies + msecs_to_jiffies(SIMPLE_IO_TIMEOUT)); usb_sg_wait(req); - if (!del_timer_sync(&timeout.timer)) + if (!timer_delete_sync(&timeout.timer)) retval = -ETIMEDOUT; else retval = req->status; - destroy_timer_on_stack(&timeout.timer); + timer_destroy_on_stack(&timeout.timer); /* FIXME check resulting data pattern */ @@ -705,7 +705,7 @@ static int is_good_config(struct usbtest_dev *tdev, int len) { struct usb_config_descriptor *config; - if (len < sizeof(*config)) + if (len < (int)sizeof(*config)) return 0; config = (struct usb_config_descriptor *) tdev->buf; @@ -2021,7 +2021,8 @@ static struct urb *iso_alloc_urb( for (i = 0; i < packets; i++) { /* here, only the last packet will be short */ - urb->iso_frame_desc[i].length = min((unsigned) bytes, maxp); + urb->iso_frame_desc[i].length = min_t(unsigned int, + bytes, maxp); bytes -= urb->iso_frame_desc[i].length; urb->iso_frame_desc[i].offset = maxp * i; diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index b00d92db5dfd..b26c1d382d59 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -677,7 +677,7 @@ static int uss720_probe(struct usb_interface *intf, struct parport_uss720_private *priv; struct parport *pp; unsigned char reg; - int i; + int ret; dev_dbg(&intf->dev, "probe: vendor id 0x%x, device id 0x%x\n", le16_to_cpu(usbdev->descriptor.idVendor), @@ -688,12 +688,12 @@ static int uss720_probe(struct usb_interface *intf, usb_put_dev(usbdev); return -ENODEV; } - i = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); - dev_dbg(&intf->dev, "set interface result %d\n", i); + ret = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); + dev_dbg(&intf->dev, "set interface result %d\n", ret); interface = intf->cur_altsetting; - if (interface->desc.bNumEndpoints < 3) { + if (interface->desc.bNumEndpoints < 2) { usb_put_dev(usbdev); return -ENODEV; } @@ -719,18 +719,27 @@ static int uss720_probe(struct usb_interface *intf, priv->pp = pp; pp->private_data = priv; - pp->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_TRISTATE | PARPORT_MODE_EPP | PARPORT_MODE_ECP | PARPORT_MODE_COMPAT; + pp->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_TRISTATE | PARPORT_MODE_EPP | PARPORT_MODE_COMPAT; + if (interface->desc.bNumEndpoints >= 3) + pp->modes |= PARPORT_MODE_ECP; + pp->dev = &usbdev->dev; /* set the USS720 control register to manual mode, no ECP compression, enable all ints */ set_1284_register(pp, 7, 0x00, GFP_KERNEL); set_1284_register(pp, 6, 0x30, GFP_KERNEL); /* PS/2 mode */ set_1284_register(pp, 2, 0x0c, GFP_KERNEL); - /* debugging */ - get_1284_register(pp, 0, ®, GFP_KERNEL); + + /* The Belkin F5U002 Rev 2 P80453-B USB parallel port adapter shares the + * device ID 050d:0002 with some other device that works with this + * driver, but it itself does not. Detect and handle the bad cable + * here. */ + ret = get_1284_register(pp, 0, ®, GFP_KERNEL); dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg); + if (ret < 0) + return ret; - i = usb_find_last_int_in_endpoint(interface, &epd); - if (!i) { + ret = usb_find_last_int_in_endpoint(interface, &epd); + if (!ret) { dev_dbg(&intf->dev, "epaddr %d interval %d\n", epd->bEndpointAddress, epd->bInterval); } @@ -766,14 +775,15 @@ static void uss720_disconnect(struct usb_interface *intf) /* table of cables that work through this driver */ static const struct usb_device_id uss720_table[] = { - { USB_DEVICE(0x047e, 0x1001) }, - { USB_DEVICE(0x04b8, 0x0002) }, - { USB_DEVICE(0x04b8, 0x0003) }, + { USB_DEVICE(0x047e, 0x1001) }, /* Infowave 901-0030 */ + { USB_DEVICE(0x04b8, 0x0002) }, /* Epson CAEUL0002 ISD-103 */ + { USB_DEVICE(0x04b8, 0x0003) }, /* Epson ISD-101 */ { USB_DEVICE(0x050d, 0x0002) }, - { USB_DEVICE(0x050d, 0x1202) }, + { USB_DEVICE(0x050d, 0x1202) }, /* Belkin F5U120-PC */ { USB_DEVICE(0x0557, 0x2001) }, - { USB_DEVICE(0x05ab, 0x0002) }, - { USB_DEVICE(0x06c6, 0x0100) }, + { USB_DEVICE(0x05ab, 0x0002) }, /* Belkin F5U002 ISD-101 */ + { USB_DEVICE(0x05ab, 0x1001) }, /* Belkin F5U002 P80453-A */ + { USB_DEVICE(0x06c6, 0x0100) }, /* Infowave ISD-103 */ { USB_DEVICE(0x0729, 0x1284) }, { USB_DEVICE(0x1293, 0x0002) }, { } /* Terminating entry */ diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c index c640f98d20c5..70dff0db5354 100644 --- a/drivers/usb/misc/yurex.c +++ b/drivers/usb/misc/yurex.c @@ -400,7 +400,7 @@ static ssize_t yurex_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct usb_yurex *dev; - int len = 0; + int len; char in_buffer[20]; unsigned long flags; @@ -441,7 +441,10 @@ static ssize_t yurex_write(struct file *file, const char __user *user_buffer, if (count == 0) goto error; - mutex_lock(&dev->io_mutex); + retval = mutex_lock_interruptible(&dev->io_mutex); + if (retval < 0) + return -EINTR; + if (dev->disconnected) { /* already disconnected */ mutex_unlock(&dev->io_mutex); retval = -ENODEV; @@ -507,8 +510,11 @@ static ssize_t yurex_write(struct file *file, const char __user *user_buffer, __func__, retval); goto error; } - if (set && timeout) + if (set && timeout) { + spin_lock_irq(&dev->lock); dev->bbu = c2; + spin_unlock_irq(&dev->lock); + } return timeout ? count : -EIO; error: @@ -527,4 +533,5 @@ static const struct file_operations yurex_fops = { module_usb_driver(yurex_driver); +MODULE_DESCRIPTION("USB YUREX driver support"); MODULE_LICENSE("GPL"); |
