| /* Copyright (c) 2018, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include "main.h" |
| #include "bus.h" |
| #include "debug.h" |
| #include "usb.h" |
| |
| int cnss_usb_dev_powerup(struct cnss_usb_data *usb_priv) |
| { |
| int ret = 0; |
| |
| if (!usb_priv) { |
| cnss_pr_err("usb_priv is NULL\n"); |
| return -ENODEV; |
| } |
| return ret; |
| } |
| |
| int cnss_usb_wlan_register_driver(struct cnss_usb_wlan_driver *driver_ops) |
| { |
| int ret = 0; |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| struct cnss_usb_data *usb_priv; |
| |
| if (!plat_priv) { |
| cnss_pr_err("plat_priv is NULL\n"); |
| return -ENODEV; |
| } |
| |
| usb_priv = plat_priv->bus_priv; |
| if (!usb_priv) { |
| cnss_pr_err("usb_priv is NULL\n"); |
| return -ENODEV; |
| } |
| |
| if (usb_priv->driver_ops) { |
| cnss_pr_err("Driver has already registered\n"); |
| return -EEXIST; |
| } |
| |
| ret = cnss_driver_event_post(plat_priv, |
| CNSS_DRIVER_EVENT_REGISTER_DRIVER, |
| CNSS_EVENT_SYNC_UNINTERRUPTIBLE, |
| driver_ops); |
| return ret; |
| } |
| EXPORT_SYMBOL(cnss_usb_wlan_register_driver); |
| |
| void cnss_usb_wlan_unregister_driver(struct cnss_usb_wlan_driver *driver_ops) |
| { |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| |
| if (!plat_priv) { |
| cnss_pr_err("plat_priv is NULL\n"); |
| return; |
| } |
| |
| cnss_driver_event_post(plat_priv, |
| CNSS_DRIVER_EVENT_UNREGISTER_DRIVER, |
| CNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL); |
| } |
| EXPORT_SYMBOL(cnss_usb_wlan_unregister_driver); |
| |
| int cnss_usb_register_driver_hdlr(struct cnss_usb_data *usb_priv, |
| void *data) |
| { |
| int ret = 0; |
| struct cnss_plat_data *plat_priv = usb_priv->plat_priv; |
| |
| set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); |
| usb_priv->driver_ops = data; |
| |
| ret = cnss_bus_call_driver_probe(plat_priv); |
| |
| return ret; |
| } |
| |
| int cnss_usb_unregister_driver_hdlr(struct cnss_usb_data *usb_priv) |
| { |
| struct cnss_plat_data *plat_priv = usb_priv->plat_priv; |
| |
| set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state); |
| cnss_usb_dev_shutdown(usb_priv); |
| usb_priv->driver_ops = NULL; |
| |
| return 0; |
| } |
| |
| int cnss_usb_dev_shutdown(struct cnss_usb_data *usb_priv) |
| { |
| int ret = 0; |
| |
| if (!usb_priv) { |
| cnss_pr_err("usb_priv is NULL\n"); |
| return -ENODEV; |
| } |
| |
| switch (usb_priv->device_id) { |
| case QCN7605_COMPOSITE_DEVICE_ID: |
| case QCN7605_STANDALONE_DEVICE_ID: |
| break; |
| default: |
| cnss_pr_err("Unknown device_id found: 0x%x\n", |
| usb_priv->device_id); |
| ret = -ENODEV; |
| } |
| return ret; |
| } |
| |
| int cnss_usb_call_driver_probe(struct cnss_usb_data *usb_priv) |
| { |
| int ret = 0; |
| struct cnss_plat_data *plat_priv = usb_priv->plat_priv; |
| |
| if (!usb_priv->driver_ops) { |
| cnss_pr_err("driver_ops is NULL\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) && |
| test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) { |
| ret = usb_priv->driver_ops->reinit(usb_priv->usb_intf, |
| usb_priv->usb_device_id); |
| if (ret) { |
| cnss_pr_err("Failed to reinit host driver, err = %d\n", |
| ret); |
| goto out; |
| } |
| clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); |
| } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) { |
| ret = usb_priv->driver_ops->probe(usb_priv->usb_intf, |
| usb_priv->usb_device_id); |
| if (ret) { |
| cnss_pr_err("Failed to probe host driver, err = %d\n", |
| ret); |
| goto out; |
| } |
| clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state); |
| clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state); |
| set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); |
| } |
| |
| return 0; |
| |
| out: |
| return ret; |
| } |
| |
| int cnss_usb_call_driver_remove(struct cnss_usb_data *usb_priv) |
| { |
| struct cnss_plat_data *plat_priv = usb_priv->plat_priv; |
| |
| if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state) || |
| test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state) || |
| test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) { |
| cnss_pr_dbg("Skip driver remove\n"); |
| return 0; |
| } |
| |
| if (!usb_priv->driver_ops) { |
| cnss_pr_err("driver_ops is NULL\n"); |
| return -EINVAL; |
| } |
| |
| if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) && |
| test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) { |
| usb_priv->driver_ops->shutdown(usb_priv->usb_intf); |
| } else if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) { |
| usb_priv->driver_ops->remove(usb_priv->usb_intf); |
| clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state); |
| } |
| |
| return 0; |
| } |
| |
| static struct usb_driver cnss_usb_driver; |
| #define QCN7605_WLAN_STANDALONE_INTERFACE_NUM 0x0000 |
| #define QCN7605_WLAN_COMPOSITE_INTERFACE_NUM 0x0002 |
| |
| static int cnss_usb_probe(struct usb_interface *interface, |
| const struct usb_device_id *id) |
| { |
| int ret = 0; |
| struct cnss_usb_data *usb_priv; |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| struct usb_device *usb_dev; |
| unsigned short bcd_device; |
| |
| cnss_pr_dbg("USB probe, vendor ID: 0x%x, product ID: 0x%x\n", |
| id->idVendor, id->idProduct); |
| |
| usb_dev = interface_to_usbdev(interface); |
| usb_priv = devm_kzalloc(&usb_dev->dev, sizeof(*usb_priv), |
| GFP_KERNEL); |
| if (!usb_priv) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| bcd_device = le16_to_cpu(usb_dev->descriptor.bcdDevice); |
| usb_priv->plat_priv = plat_priv; |
| usb_priv->usb_intf = interface; |
| usb_priv->usb_device_id = id; |
| usb_priv->device_id = id->idProduct; |
| usb_priv->target_version = bcd_device; |
| cnss_set_usb_priv(interface, usb_priv); |
| plat_priv->device_id = usb_priv->device_id; |
| plat_priv->bus_priv = usb_priv; |
| |
| /*increment the ref count of usb dev structure*/ |
| usb_get_dev(usb_dev); |
| |
| ret = cnss_register_subsys(plat_priv); |
| if (ret) |
| goto reset_ctx; |
| |
| ret = cnss_register_ramdump(plat_priv); |
| if (ret) |
| goto unregister_subsys; |
| |
| switch (usb_priv->device_id) { |
| case QCN7605_COMPOSITE_DEVICE_ID: |
| case QCN7605_STANDALONE_DEVICE_ID: |
| break; |
| default: |
| cnss_pr_err("Unknown USB device found: 0x%x\n", |
| usb_priv->device_id); |
| ret = -ENODEV; |
| goto unregister_ramdump; |
| } |
| |
| return 0; |
| |
| unregister_ramdump: |
| cnss_unregister_ramdump(plat_priv); |
| unregister_subsys: |
| cnss_unregister_subsys(plat_priv); |
| reset_ctx: |
| plat_priv->bus_priv = NULL; |
| devm_kfree(&usb_dev->dev, usb_priv); |
| out: |
| return ret; |
| } |
| |
| static void cnss_usb_remove(struct usb_interface *interface) |
| { |
| struct usb_device *usb_dev; |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| struct cnss_usb_data *usb_priv = plat_priv->bus_priv; |
| |
| usb_priv->plat_priv = NULL; |
| plat_priv->bus_priv = NULL; |
| usb_dev = interface_to_usbdev(interface); |
| usb_put_dev(usb_dev); |
| devm_kfree(&usb_dev->dev, usb_priv); |
| } |
| |
| static int cnss_usb_suspend(struct usb_interface *interface, pm_message_t state) |
| { |
| int ret = 0; |
| struct cnss_usb_data *usb_priv; |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| |
| usb_priv = plat_priv->bus_priv; |
| if (!usb_priv->driver_ops) { |
| cnss_pr_err("driver_ops is NULL\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| ret = usb_priv->driver_ops->suspend(usb_priv->usb_intf, |
| state); |
| out: |
| return ret; |
| } |
| |
| static int cnss_usb_resume(struct usb_interface *interface) |
| { |
| int ret = 0; |
| struct cnss_usb_data *usb_priv; |
| struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL); |
| |
| usb_priv = plat_priv->bus_priv; |
| if (!usb_priv->driver_ops) { |
| cnss_pr_err("driver_ops is NULL\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| ret = usb_priv->driver_ops->resume(usb_priv->usb_intf); |
| |
| out: |
| return ret; |
| } |
| |
| static int cnss_usb_reset_resume(struct usb_interface *interface) |
| { |
| return 0; |
| } |
| |
| static struct usb_device_id cnss_usb_id_table[] = { |
| { USB_DEVICE_INTERFACE_NUMBER(QCN7605_USB_VENDOR_ID, |
| QCN7605_COMPOSITE_PRODUCT_ID, |
| QCN7605_WLAN_COMPOSITE_INTERFACE_NUM) }, |
| { USB_DEVICE_INTERFACE_NUMBER(QCN7605_USB_VENDOR_ID, |
| QCN7605_STANDALONE_PRODUCT_ID, |
| QCN7605_WLAN_STANDALONE_INTERFACE_NUM) }, |
| {} /* Terminating entry */ |
| }; |
| |
| static struct usb_driver cnss_usb_driver = { |
| .name = "cnss_usb", |
| .id_table = cnss_usb_id_table, |
| .probe = cnss_usb_probe, |
| .disconnect = cnss_usb_remove, |
| .suspend = cnss_usb_suspend, |
| .resume = cnss_usb_resume, |
| .reset_resume = cnss_usb_reset_resume, |
| .supports_autosuspend = true, |
| }; |
| |
| int cnss_usb_init(struct cnss_plat_data *plat_priv) |
| { |
| int ret = 0; |
| |
| ret = usb_register(&cnss_usb_driver); |
| if (ret) { |
| cnss_pr_err("Failed to register to Linux USB framework, err = %d\n", |
| ret); |
| goto out; |
| } |
| |
| return 0; |
| out: |
| return ret; |
| } |
| |
| void cnss_usb_deinit(struct cnss_plat_data *plat_priv) |
| { |
| usb_deregister(&cnss_usb_driver); |
| } |