diff options
Diffstat (limited to 'drivers/usb/host/shost')
25 files changed, 9591 insertions, 0 deletions
diff --git a/drivers/usb/host/shost/Makefile b/drivers/usb/host/shost/Makefile new file mode 100644 index 0000000..c891a18 --- /dev/null +++ b/drivers/usb/host/shost/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for USB OTG Host Controller Drivers +# + +obj-$(CONFIG_USB_S3C_OTG_HOST) += shost_hcd.o + +shost_hcd-objs += shost_driver.o +shost_hcd-objs += shost_scheduler.o +shost_hcd-objs += shost_transferchecker.o diff --git a/drivers/usb/host/shost/shost.h b/drivers/usb/host/shost/shost.h new file mode 100644 index 0000000..6774667 --- /dev/null +++ b/drivers/usb/host/shost/shost.h @@ -0,0 +1,56 @@ +#ifndef _SHOST_H +#define _SHOST_H + + +#include <linux/usb.h> +#include <linux/errno.h> +#include <linux/wakelock.h> +#include <plat/s5p-otghost.h> + + +#include "shost_debug.h" +#include "shost_list.h" + +#include "shost_const.h" +#include "shost_errorcode.h" +#include "shost_regs.h" +#include "shost_struct.h" + +#include "shost_kal.h" +#include "shost_mem.h" +#include "shost_oci.h" + +#include "shost_scheduler.h" +#include "shost_transfer.h" + +/* #ifdef CONFIG_MACH_C1 GB */ +#if 0 + #include <plat/regs-otg.h> + #define OTG_CLOCK S5P_CLKGATE_IP_FSYS + #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL + #define OTG_PHYPWR S3C_USBOTG_PHYPWR + #define OTG_PHYCLK S3C_USBOTG_PHYCLK + #define OTG_RSTCON S3C_USBOTG_RSTCON + #define S3C_VA_HSOTG S3C_VA_OTG +#else /* ICS */ + #include <mach/regs-usb-phy.h> + #define OTG_CLOCK EXYNOS4_CLKGATE_IP_FSYS + #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL + #define OTG_PHYPWR EXYNOS4_PHYPWR + #define OTG_PHYCLK EXYNOS4_PHYCLK + #define OTG_RSTCON EXYNOS4_RSTCON + #define IRQ_OTG IRQ_USB_HSOTG + #define S3C_VA_OTG S3C_VA_HSOTG +#endif + +/* transferchecker-common.c + * called by isr + */ +void do_transfer_checker(struct sec_otghost *otghost); +void otg_print_registers(void); + +#ifdef CONFIG_USB_HOST_NOTIFY +#undef CONFIG_USB_HOST_NOTIFY +#endif + +#endif diff --git a/drivers/usb/host/shost/shost_const.h b/drivers/usb/host/shost/shost_const.h new file mode 100644 index 0000000..8ba1494 --- /dev/null +++ b/drivers/usb/host/shost/shost_const.h @@ -0,0 +1,140 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-const.h + * [Description] : The Header file defines constants + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created s3c-otg-common-const.h file and defines some constants. + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _CONST_TYPE_DEF_H_ +#define _CONST_TYPE_DEF_H_ + +/** + * @def OTG_PORT_NUMBER + * + * @brief write~ description + * + * describe in detail + */ +#define OTG_PORT_NUMBER 0 + +/* Defines Stages of Control Transfer */ +#define SETUP_STAGE 1 +#define DATA_STAGE 2 +#define STATUS_STAGE 3 +#define COMPLETE_STAGE 4 + +/* Defines Direction of Endpoint */ +#define EP_IN 1 +#define EP_OUT 0 + +/* Define speed of USB Device */ +#define LOW_SPEED_OTG 2 +#define FULL_SPEED_OTG 1 +#define HIGH_SPEED_OTG 0 +#define SUPER_SPEED_OTG 3 + +/* Define multiple count of packet in periodic transfer. */ +#define MULTI_COUNT_ZERO 0 +#define MULTI_COUNT_ONE 1 +#define MULTI_COUNT_TWO 2 + +/* Define USB Transfer Types. */ +#define CONTROL_TRANSFER 0 +#define ISOCH_TRANSFER 1 +#define BULK_TRANSFER 2 +#define INT_TRANSFER 3 + +#define BULK_TIMEOUT 300 + +/* Defines PID */ +#define DATA0 0 +#define DATA1 2 +#define DATA2 1 +#define MDATA 3 +#define SETUP 3 + +/* Defines USB Transfer Request Size on USB2.0 */ +#define USB_20_STAND_DEV_REQUEST_SIZE 8 +/* Define Max Channel Number */ +#define MAX_CH_NUMBER 16 +/* Define Channel Number */ +#define CH_0 0 +#define CH_1 1 +#define CH_2 2 +#define CH_3 3 +#define CH_4 4 +#define CH_5 5 +#define CH_6 6 +#define CH_7 7 +#define CH_8 8 +#define CH_9 9 +#define CH_10 10 +#define CH_11 11 +#define CH_12 12 +#define CH_13 13 +#define CH_14 14 +#define CH_15 15 +#define CH_NONE 20 + +/* define the Constant for result of processing the USB Transfer. */ +#define RE_TRANSMIT 1 +#define RE_SCHEDULE 2 +#define DE_ALLOCATE 3 +#define NO_ACTION 4 + +/* define the threshold value to retransmit USB Transfer */ +#define RETRANSMIT_THRESHOLD 2 + +/* define the maximum size of data to be tranferred through channel. */ +#define MAX_CH_TRANSFER_SIZE 65536 /* 65535 */ + +/* define Max Frame Number which Synopsys OTG suppports. */ +#define MAX_FRAME_NUMBER 0x3FFF +/* Channel Interrupt Status */ +#define CH_STATUS_DataTglErr (0x1<<10) +#define CH_STATUS_FrmOvrun (0x1<<9) +#define CH_STATUS_BblErr (0x1<<8) +#define CH_STATUS_XactErr (0x1<<7) +#define CH_STATUS_NYET (0x1<<6) +#define CH_STATUS_ACK (0x1<<5) +#define CH_STATUS_NAK (0x1<<4) +#define CH_STATUS_STALL (0x1<<3) +#define CH_STATUS_AHBErr (0x1<<2) +#define CH_STATUS_ChHltd (0x1<<1) +#define CH_STATUS_XferCompl (0x1<<0) +#define CH_STATUS_ALL 0x7FF + +/* Define USB Transfer Flag.. */ +#define USB_TRANS_FLAG_NOT_SHORT URB_SHORT_NOT_OK +#define USB_TRANS_FLAG_ISO_ASYNCH URB_ISO_ASAP + +#define HFNUM_MAX_FRNUM 0x3FFF +#define SCHEDULE_SLOT 10 + + +#endif + + diff --git a/drivers/usb/host/shost/shost_debug.h b/drivers/usb/host/shost/shost_debug.h new file mode 100644 index 0000000..dd67b98 --- /dev/null +++ b/drivers/usb/host/shost/shost_debug.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-debug.c + * @brief It provides debug functions for display message \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SHOST_DEBUG_H +#define _SHOST_DEBUG_H + +#define OTG_DEBUG + +#ifdef OTG_DEBUG + +#define OTG_DBG_OTGHCDI_DRIVER 1 +#define OTG_DBG_OTGHCDI_HCD 0 +#define OTG_DBG_OTGHCDI_KAL 0 +#define OTG_DBG_OTGHCDI_LIST 0 +#define OTG_DBG_OTGHCDI_MEM 0 +#define OTG_DBG_OTGHCDI_IRQ 0 + +#define OTG_DBG_TRANSFER 0 +#define OTG_DBG_SCHEDULE 0 +#define OTG_DBG_SCHEDULE_ED 0 +#define OTG_DBG_OCI 0 +#define OTG_DBG_DONETRASF 0 +#define OTG_DBG_ISR 0 +#define OTG_DBG_ROOTHUB 0 + + +#include <linux/kernel.h> /* for printk */ + +#define otg_err(is_active, msg, args...) \ + do { \ + if ((is_active) == true) {\ + pr_err("OTG_ERR %s(%d): " msg, \ + __func__ , __LINE__, ##args); \ + } \ + } while (0) + +#define otg_dbg(is_active, msg, args...) \ + do { \ + if ((is_active) == true) { \ + pr_info("OTG %s(%d): " msg, \ + __func__, __LINE__, ##args); \ + } \ + } while (0) + +#else /* OTG_DEBUG */ + +#define otg_err(is_active, msg...) do {} while (0) +#define otg_dbg(is_active, msg...) do {} while (0) + +#endif + + +#endif /* SHOST_DEBUG_H */ diff --git a/drivers/usb/host/shost/shost_driver.c b/drivers/usb/host/shost/shost_driver.c new file mode 100644 index 0000000..f10136c --- /dev/null +++ b/drivers/usb/host/shost/shost_driver.c @@ -0,0 +1,450 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-driver.c + * @brief It provides functions related with module for OTGHCD driver. + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) + * : Modifying for successful rmmod & disconnecting + * @see None + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> /* for SA_SHIRQ */ +#include <mach/map.h> /* address for smdk */ +#include <linux/dma-mapping.h> /* dma_alloc_coherent */ +#include <linux/ioport.h> /* request_mem_request ... */ +#include <asm/irq.h> /* for IRQ_OTG */ +#include <linux/clk.h> + + +#include "shost.h" + + +static inline struct sec_otghost *hcd_to_sec_otghost(struct usb_hcd *hcd) +{ + return (struct sec_otghost *)(hcd->hcd_priv); +} +static inline struct usb_hcd *sec_otghost_to_hcd(struct sec_otghost *otghost) +{ + return container_of((void *) otghost, struct usb_hcd, hcd_priv); +} + + +#include "shost_oci.c" +#include "shost_transfer.c" +#include "shost_roothub.c" +#include "shost_hcd.c" + +volatile u8 *g_pUDCBase; +struct usb_hcd *g_pUsbHcd; + +static const char gHcdName[] = "EMSP_OTG_HCD"; +static struct platform_device *g_pdev; + + +static void otg_power_work(struct work_struct *work) +{ + struct sec_otghost *otghost = container_of(work, + struct sec_otghost, work); + struct sec_otghost_data *hdata = otghost->otg_data; + + if (hdata && hdata->set_pwr_cb) { + pr_info("otg power off - don't turn off the power\n"); + hdata->set_pwr_cb(0); +#ifdef CONFIG_USB_HOST_NOTIFY + if (hdata->host_notify_cb) + hdata->host_notify_cb(NOTIFY_HOST_OVERCURRENT); +#endif + } else { + otg_err(true, "invalid otghost data\n"); + } +} + +static int s5pc110_mapping_resources(struct platform_device *pdev) +{ + g_pUsbHcd->rsrc_start = pdev->resource[0].start; + g_pUsbHcd->rsrc_len = pdev->resource[0].end - + pdev->resource[0].start + 1; + + if (!request_mem_region(g_pUsbHcd->rsrc_start, + g_pUsbHcd->rsrc_len, gHcdName)) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to request_mem_region\n"); + return -EBUSY; + } + + pr_info("otg rsrc_start %llu, ren %llu\n", + g_pUsbHcd->rsrc_start, + g_pUsbHcd->rsrc_len); + + pr_info("otg regs : %p\n", S3C_VA_HSOTG); + + /* Physical address => Virtual address */ + g_pUsbHcd->regs = S3C_VA_HSOTG; + g_pUDCBase = (u8 *)g_pUsbHcd->regs; + + return 0; +} + +static void s5pc110_prevent_suspend(struct usb_device *rhdev) +{ + dev_info(&rhdev->dev, "otg host do not enter suspend.\n"); + pm_runtime_disable(&rhdev->dev); +} + +static int s5pc110_start_otg(u32 regs) +{ + int ret_val = 0; + u32 reg_val = 0; + struct platform_device *pdev = g_pdev; + struct sec_otghost *otghost = NULL; + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + + pr_info("%s + regs=0x%x\n", __func__, regs); + + /* 1. hcd */ + g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev, + "s3cotg");/*pdev->dev.bus_id*/ + if (g_pUsbHcd == NULL) { + otg_err(1, "failed to usb_create_hcd\n"); + return -ENOMEM; + } + g_pUsbHcd->self.otg_port = 1; + + /* sec_otghost */ + otghost = hcd_to_sec_otghost(g_pUsbHcd); + if (otghost == NULL) { + otg_err(true, "failed to get otghost hcd\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + otghost->otg_data = otg_data; + + /* 2. wake lock */ + wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg"); + wake_lock(&otghost->wake_lock); + + /* base address */ + if (!regs) { + pr_info("otg mapping hcd resource\n"); + ret_val = s5pc110_mapping_resources(pdev); + if (ret_val) + goto err_out_create_hcd; + } else + g_pUDCBase = (u8 *)regs; + + pr_info("otg g_pUDCBase 0x%p\n", g_pUDCBase); + + /* 3. workqueue */ + INIT_WORK(&otghost->work, otg_power_work); + otghost->wq = create_singlethread_workqueue("sec_otghostd"); + + /* 4. phy */ + ret_val = otg_hcd_init_modules(otghost); + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to otg_hcd_init_modules\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + + /** + * Attempt to ensure this device is really a s5pc110 USB-OTG Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX". + */ + reg_val = read_reg_32(0x40); + pr_info("otg reg 0x40 = %x\n", reg_val); + if ((reg_val & 0xFFFFF000) != 0x4F542000) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Bad value for SNPSID: 0x%x\n", reg_val); + ret_val = -EINVAL; + goto err_out_create_hcd_init; + } + +#ifdef CONFIG_USB_SEC_WHITELIST + if (otg_data->sec_whlist_table_num) + g_pUsbHcd->sec_whlist_table_num = + otg_data->sec_whlist_table_num; +#endif + + /* 5. hcd + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls s5pc110_otghcd_start method. + */ + ret_val = usb_add_hcd(g_pUsbHcd, + pdev->resource[1].start, IRQF_DISABLED); + if (ret_val < 0) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Failed to add hcd driver\n"); + goto err_out_create_hcd_init; + } + + s5pc110_prevent_suspend(g_pUsbHcd->self.root_hub); + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "OTG HCD Initialized HCD, bus=%s, usbbus=%d\n", + "C110 OTG Controller", g_pUsbHcd->self.busnum); + + /* otg_print_registers(); */ + pr_info("%s -\n", __func__); + + return USB_ERR_SUCCESS; + +err_out_create_hcd_init: + otg_hcd_deinit_modules(otghost); + if (!regs) + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + +err_out_create_hcd: + usb_put_hcd(g_pUsbHcd); + + return ret_val; +} + +static int s5pc110_stop_otg(void) +{ + struct sec_otghost *otghost = NULL; + struct sec_otghost_data *otgdata = NULL; + + pr_info("%s +\n", __func__); + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_stop_otg\n"); + + otghost = hcd_to_sec_otghost(g_pUsbHcd); + + otg_hcd_deinit_modules(otghost); + + /* 5. hcd */ + usb_remove_hcd(g_pUsbHcd); +#if 1 + if (g_pUDCBase == S3C_VA_HSOTG) { + pr_info("otg release_mem_region\n"); + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + } +#endif + /* 4. phy */ + otgdata = otghost->otg_data; + if (otgdata && otgdata->phy_exit && otgdata->pdev) { + pr_info("otg phy_off\n"); + otgdata->phy_exit(0); + clk_disable(otgdata->clk); + } + + /* 3. workqueue */ + destroy_workqueue(otghost->wq); + + /* 2. wake lock */ + wake_unlock(&otghost->wake_lock); + wake_lock_destroy(&otghost->wake_lock); + + /* 1. hcd */ + usb_put_hcd(g_pUsbHcd); + + pr_info("%s -\n", __func__); + + return 0; +} + +/** + * static int s5pc110_otg_drv_probe (struct platform_device *pdev) + * + * @brief probe function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If fail + * @remark + * it allocates resources of it and call other modules' init function. + * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions + */ + +static int s5pc110_otg_drv_probe(struct platform_device *pdev) +{ + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + g_pdev = pdev; + + otg_data->clk = clk_get(&pdev->dev, "usbotg"); + + if (IS_ERR(otg_data->clk)) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Failed to get clock\n"); + return PTR_RET(otg_data->clk); + } + + pr_info("otg host_probe start %p\n", s5pc110_start_otg); + otg_data->start = s5pc110_start_otg; + otg_data->stop = s5pc110_stop_otg; + otg_data->pdev = pdev; + + return 0; +} + + +/** + * static int s5pc110_otg_drv_remove (struct platform_device *dev) + * + * @brief remove function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If fail + * @remark + * This function is called when the otg device unregistered with the + * s5pc110_otg_driver. This happens, for example, when the rmmod command is + * executed. The device may or may not be electrically present. If it is + * present, the driver stops device processing. Any resources used on behalf + * of this device are freed. + */ + +static int s5pc110_otg_drv_remove(struct platform_device *pdev) +{ + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + + clk_put(otg_data->clk); + return USB_ERR_SUCCESS; +} + +/** + * @struct s5pc110_otg_driver + * + * @brief + * This structure defines the methods to be called by a bus driver + * during the lifecycle of a device on that bus. Both drivers and + * devices are registered with a bus driver. The bus driver matches + * devices to drivers based on information in the device and driver + * structures. + * + * The probe function is called when the bus driver matches a device + * to this driver. The remove function is called when a device is + * unregistered with the bus driver. + */ +struct platform_driver s5pc110_otg_driver = { + .probe = s5pc110_otg_drv_probe, + .remove = s5pc110_otg_drv_remove, +/* .shutdown = usb_hcd_platform_shutdown, */ + .driver = { + .name = "s3c_otghcd", + .owner = THIS_MODULE, + }, +}; + +/** + * static int __init s5pc110_otg_module_init(void) + * + * @brief module_init function + * + * @return it returns result of platform_driver_register + * @remark + * This function is called when the s5pc110_otg_driver is installed with the + * insmod command. It registers the s5pc110_otg_driver structure with the + * appropriate bus driver. This will cause the s5pc110_otg_driver_probe function + * to be called. In addition, the bus driver will automatically expose + * attributes defined for the device and driver in the special sysfs file + * system. + */ +static int __init s5pc110_otg_module_init(void) +{ + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_init\n"); + + ret_val = platform_driver_register(&s5pc110_otg_driver); + if (ret_val < 0) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "platform_driver_register\n"); + } + return ret_val; +} + +/** + * static void __exit s5pc110_otg_module_exit(void) + * + * @brief module_exit function + * + * @remark + * This function is called when the driver is removed from the kernel + * with the rmmod command. The driver unregisters itself with its bus + * driver. + */ +static void __exit s5pc110_otg_module_exit(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_exit\n"); + platform_driver_unregister(&s5pc110_otg_driver); +} + +/* for debug */ +void otg_print_registers(void) +{ + /* USB PHY Control Registers */ + + pr_info("otg clock = %s\n", + (readl(OTG_CLOCK) & (1<<13)) ? "ON" : "OFF"); + pr_info("otg USB_CONTROL = 0x%x.\n", readl(OTG_PHY_CONTROL)); + pr_info("otg UPHYPWR = 0x%x.\n", readl(OTG_PHYPWR)); + pr_info("otg UPHYCLK = 0x%x.\n", readl(OTG_PHYCLK)); + pr_info("otg URSTCON = 0x%x.\n", readl(OTG_RSTCON)); + + /* OTG LINK Core registers (Core Global Registers) */ + pr_info("otg GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL)); + pr_info("otg GOTGINT = 0x%x.\n", read_reg_32(GOTGINT)); + pr_info("otg GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG)); + pr_info("otg GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG)); + pr_info("otg GINTSTS = 0x%x.\n", read_reg_32(GINTSTS)); + pr_info("otg GINTMSK = 0x%x.\n", read_reg_32(GINTMSK)); + + /* Host Mode Registers */ + pr_info("otg HCFG = 0x%x.\n", read_reg_32(HCFG)); + pr_info("otg HPRT = 0x%x.\n", read_reg_32(HPRT)); + pr_info("otg HFIR = 0x%x.\n", read_reg_32(HFIR)); + + /* Synopsys ID */ + pr_info("otg GSNPSID = 0x%x.\n", read_reg_32(GSNPSID)); + + /* HWCFG */ + pr_info("otg GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1)); + pr_info("otg GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2)); + pr_info("otg GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3)); + pr_info("otg GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4)); + + /* PCGCCTL */ + pr_info("otg PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL)); +} + +late_initcall(s5pc110_otg_module_init); +module_exit(s5pc110_otg_module_exit); + +MODULE_DESCRIPTION("OTG USB HOST controller driver"); +MODULE_AUTHOR("SAMSUNG / System LSI / EMSP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/shost/shost_errorcode.h b/drivers/usb/host/shost/shost_errorcode.h new file mode 100644 index 0000000..f52b606 --- /dev/null +++ b/drivers/usb/host/shost/shost_errorcode.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-errorcode.h + * [Description] : The Header file defines Error Codes + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - add HCD error code + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _ERRORCODE_H +#define _ERRORCODE_H + + +/* General USB Error Code.*/ +#define USB_ERR_SUCCESS 0 +#define USB_ERR_FAIL (-1) + +#define USB_ERR_NO 1 + +#define USB_ERR_NO_ENTITY (-2) + +/* S3CTransfer Error Code */ +#define USB_ERR_NODEV (-ENODEV) +#define USB_ERR_NOMEM (-ENOMEM) +#define USB_ERR_NOSPACE (-ENOSPC) +#define USB_ERR_NOIO (-EIO) + +/* OTG-HCD error code */ +#define USB_ERR_NOELEMENT (-ENOENT) +#define USB_ERR_ESHUTDOWN (-ESHUTDOWN) /* unplug */ +#define USB_ERR_DEQUEUED (-ECONNRESET) /* unlink */ + + +/* S3CScheduler Error Code */ +#define USB_ERR_ALREADY_EXIST (-1) +#define USB_ERR_NO_RESOURCE (-2) +#define USB_ERR_NO_CHANNEL (-3) +#define USB_ERR_NO_BANDWIDTH (-4) +#define USB_ERR_ALL_RESROUCE (-5) + +/************************************************ + *Defines the USB Error Status Code of USB Transfer. + ************************************************/ + +#define USB_ERR_STATUS_COMPLETE 0 +#define USB_ERR_STATUS_INPROGRESS (-EINPROGRESS) +#define USB_ERR_STATUS_CRC (-EILSEQ) +#define USB_ERR_STATUS_XACTERR (-EPROTO) +#define USB_ERR_STATUS_STALL (-EPIPE) +#define USB_ERR_STATUS_BBLERR (-EOVERFLOW) +#define USB_ERR_STATUS_AHBERR (-EIO) +#define USB_ERR_STATUS_FRMOVRUN_OUT (-ENOSR) +#define USB_ERR_STATUS_FRMOVRUN_IN (-ECOMM) +#define USB_ERR_STATUS_SHORTREAD (-EREMOTEIO) + + +#endif + diff --git a/drivers/usb/host/shost/shost_hcd.c b/drivers/usb/host/shost/shost_hcd.c new file mode 100644 index 0000000..f06b237 --- /dev/null +++ b/drivers/usb/host/shost/shost_hcd.c @@ -0,0 +1,912 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-hcd.c + * @brief implementation of structure hc_drive + * @version + * -# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) + * : Modifying for successful rmmod & disconnecting + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +/*Internal function of isr */ +static void process_port_intr(struct usb_hcd *hcd) +{ + hprt_t hprt; /* by ss1, clear_hprt; */ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ISR, "Port Interrupt() : HPRT = 0x%x\n", hprt.d32); + + if (hprt.b.prtconndet) { + otg_dbg(true, "detect connection"); + + otghost->port_flag.b.port_connect_status_change = 1; + + if (hprt.b.prtconnsts) + otghost->port_flag.b.port_connect_status = 1; + + /* wake_lock(&otghost->wake_lock); */ + } + + + if (hprt.b.prtenchng) { + otg_dbg(true, "port enable/disable changed\n"); + otghost->port_flag.b.port_enable_change = 1; + } + + if (hprt.b.prtovrcurrchng) { + otg_dbg(true, "over current condition is changed\n"); + + if (hprt.b.prtovrcurract) { + otg_dbg(true, "port_over_current_change = 1\n"); + otghost->port_flag.b.port_over_current_change = 1; + + } else { + otghost->port_flag.b.port_over_current_change = 0; + } + /* defer otg power control into a kernel thread */ + queue_work(otghost->wq, &otghost->work); + } + + hprt.b.prtena = 0; /* prtena를 writeclear시키면 안됨. */ + /* hprt.b.prtpwr = 0; */ + hprt.b.prtrst = 0; + hprt.b.prtconnsts = 0; + + write_reg_32(HPRT, hprt.d32); +} + +/** + * void otg_handle_interrupt(void) + * + * @brief Main interrupt processing routine + * + * @param None + * + * @return None + * + * @remark + * + */ + +static inline void otg_handle_interrupt(struct usb_hcd *hcd) +{ + gintsts_t clearIntr = {.d32 = 0}; + gintsts_t gintsts = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + gintsts.d32 = read_reg_32(GINTSTS) & read_reg_32(GINTMSK); + + otg_dbg(OTG_DBG_ISR, "otg_handle_interrupt - GINTSTS=0x%8x\n", + gintsts.d32); + + if (gintsts.b.wkupintr) { + otg_dbg(true, "Wakeup Interrupt\n"); + clearIntr.b.wkupintr = 1; + } + + if (gintsts.b.disconnect) { + otg_dbg(true, "Disconnect Interrupt\n"); + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + clearIntr.b.disconnect = 1; + /* + wake_unlock(&otghost->wake_lock); + */ + } + + if (gintsts.b.conidstschng) { + otg_dbg(OTG_DBG_ISR, "Connect ID Status Change Interrupt\n"); + clearIntr.b.conidstschng = 1; + oci_init_mode(); + } + + if (gintsts.b.hcintr) { + /* Mask Channel Interrupt to prevent generating interrupt */ + otg_dbg(OTG_DBG_ISR, "Channel Interrupt\n"); + + if (!otghost->ch_halt) + do_transfer_checker(otghost); + } + + if (gintsts.b.portintr) { + /* Read Only */ + otg_dbg(true, "Port Interrupt\n"); + process_port_intr(hcd); + } + + + if (gintsts.b.otgintr) { + /* Read Only */ + otg_dbg(OTG_DBG_ISR, "OTG Interrupt\n"); + } + + if (gintsts.b.sofintr) { + /* otg_dbg(OTG_DBG_ISR, "SOF Interrupt\n"); */ + do_schedule(otghost); + clearIntr.b.sofintr = 1; + } + + if (gintsts.b.modemismatch) { + otg_dbg(OTG_DBG_ISR, "Mode Mismatch Interrupt\n"); + clearIntr.b.modemismatch = 1; + } + update_reg_32(GINTSTS, clearIntr.d32); +} + + + +/** + * otg_hcd_init_modules(struct sec_otghost *otghost) + * + * @brief call other modules' init functions + * + * @return PASS : If success + * FAIL : If fail + */ +static int otg_hcd_init_modules(struct sec_otghost *otghost) +{ + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_init_modules\n"); + + spin_lock_init(&otghost->lock); + + init_transfer(); + init_scheduler(); + oci_init(otghost); + + return USB_ERR_SUCCESS; +}; + +/** + * void otg_hcd_deinit_modules(struct sec_otghost *otghost) + * + * @brief call other modules' de-init functions + * + * @return PASS : If success + * FAIL : If fail + */ +static void otg_hcd_deinit_modules(struct sec_otghost *otghost) +{ + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_deinit_modules\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + deinit_transfer(otghost); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); +} + +/** + * irqreturn_t (*s5pc110_otghcd_irq) (struct usb_hcd *hcd) + * + * @brief interrupt handler of otg irq + * + * @param [in] hcd : pointer of usb_hcd + * + * @return IRQ_HANDLED + */ +static irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_IRQ, "s5pc110_otghcd_irq\n"); + + spin_lock(&otghost->lock); + otg_handle_interrupt(hcd); + spin_unlock(&otghost->lock); + + return IRQ_HANDLED; +} + +/** + * int s5pc110_otghcd_start(struct usb_hcd *hcd) + * + * @brief initialize and start otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_start(struct usb_hcd *usb_hcd_p) +{ + struct usb_bus *usb_bus_p; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start\n"); + + usb_bus_p = hcd_to_bus(usb_hcd_p); + + /* Initialize and connect root hub if one is not already attached */ + if (usb_bus_p->root_hub) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "OTG HCD Has Root Hub\n"); + + /* Inform the HUB driver to resume. */ + otg_usbcore_resume_roothub(); + } else { + otg_err(OTG_DBG_OTGHCDI_HCD, + "OTG HCD Does Not Have Root Hub\n"); + return USB_ERR_FAIL; + } + +/* #ifdef CONFIG_MACH_C1 */ +#if 0 + usb_hcd_p->poll_rh = 1; +#else + set_bit(HCD_FLAG_POLL_RH, &usb_hcd_p->flags); +#endif + usb_hcd_p->uses_new_polling = 1; + usb_hcd_p->has_tt = 1; + + /* init bus state before enable irq */ + usb_hcd_p->state = HC_STATE_RUNNING; + + oci_start(); /* enable irq */ + + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_stop(struct usb_hcd *hcd) + * + * @brief deinitialize and stop otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + */ +static void s5pc110_otghcd_stop(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_stop\n"); + + otg_hcd_deinit_modules(otghost); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + oci_stop(); + + root_hub_feature(hcd, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); +} + +/** + * void s5pc110_otghcd_shutdown(struct usb_hcd *hcd) + * + * @brief shutdown otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + */ +static void s5pc110_otghcd_shutdown(struct usb_hcd *usb_hcd_p) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_p); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_shutdown\n"); + otg_hcd_deinit_modules(otghost); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + oci_stop(); + root_hub_feature(usb_hcd_p, 0, ClearPortFeature, + USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + free_irq(IRQ_OTG, usb_hcd_p); + usb_hcd_p->state = HC_STATE_HALT; + otg_usbcore_hc_died(); +} + + +/** + * int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) + * + * @brief get currnet frame number + * + * @param [in] hcd : pointer of usb_hcd + * + * @return ret : frame number + */ +static int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_get_frame_number\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret = oci_get_frame_num(); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return ret; +} + +int compare_ed(struct sec_otghost *otghost, void *hcpriv, struct urb *urb) +{ + struct ed *ped = NULL; + int ret = 0; + u32 update = 0; + + if (!hcpriv) + return ret; + + ped = (struct ed *)hcpriv; + + if (ped->ed_desc.device_addr != usb_pipedevice(urb->pipe)) { + ped->ed_desc.device_addr = usb_pipedevice(urb->pipe); + update |= 1 << 1; + } + + if (ped->ed_desc.endpoint_num != usb_pipeendpoint(urb->pipe)) { + ped->ed_desc.endpoint_num = usb_pipeendpoint(urb->pipe); + update |= 1 << 2; + } + + if (ped->ed_desc.is_ep_in != (usb_pipein(urb->pipe) ? 1 : 0)) { + ped->ed_desc.is_ep_in = usb_pipein(urb->pipe) ? 1 : 0; + update |= 1 << 3; + } + + if (ped->ed_desc.max_packet_size != + usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe)))) { + update |= 1 << 4; + } + + if (update) + otg_dbg(1, "update ed %d (0x%x)\n", update, update); + + return ret; +} + +/** + * int s5pc110_otghcd_urb_enqueue() + * + * @brief enqueue a urb to otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + * [in] urb : pointer of urb + * [in] mem_flags : type of gfp_t + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + int ret_val = 0; + u32 trans_flag = 0; + u32 return_td_addr = 0; + u8 dev_speed, ed_type = 0, additional_multi_count; + u16 max_packet_size; + + u8 dev_addr = 0; + u8 ep_num = 0; + bool f_is_ep_in = true; + u8 interval = 0; + u32 sched_frame = 0; + u8 hub_addr = 0; + u8 hub_port = 0; + bool f_is_do_split = false; + + struct ed *target_ed = NULL; + struct isoch_packet_desc *new_isoch_packet_desc = NULL; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + unsigned long spin_lock_flag = 0; + + if (!otghost && !otghost->port_flag.b.port_connect_status) { + otg_err(1, "Error : %s is zero\n", otghost ? + "Port status" : "otghost"); + return USB_ERR_NOIO; + } + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "enqueue\n"); + if (compare_ed(otghost, urb->ep->hcpriv, urb)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "compare ed error\n"); + pr_info("otg compare ed error\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + /* check ep has ed_t or not */ + if (!(urb->ep->hcpriv)) { + /* for getting dev_speed */ + switch (urb->dev->speed) { + case USB_SPEED_HIGH: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "HS_OTG\n"); + dev_speed = HIGH_SPEED_OTG; + break; + + case USB_SPEED_FULL: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "FS_OTG\n"); + dev_speed = FULL_SPEED_OTG; + break; + + case USB_SPEED_LOW: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "LS_OTG\n"); + dev_speed = LOW_SPEED_OTG; + break; + + default: + otg_err(OTG_DBG_OTGHCDI_HCD, + "unKnown Device Speed\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + /* for getting ed_type */ + switch (usb_pipetype(urb->pipe)) { + case PIPE_BULK: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "bulk transfer\n"); + ed_type = BULK_TRANSFER; + break; + + case PIPE_INTERRUPT: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "interrupt transfer\n"); + ed_type = INT_TRANSFER; + break; + + case PIPE_CONTROL: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "control transfer\n"); + ed_type = CONTROL_TRANSFER; + break; + + case PIPE_ISOCHRONOUS: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "isochronous transfer\n"); + ed_type = ISOCH_TRANSFER; + break; + default: + otg_err(OTG_DBG_OTGHCDI_HCD, "unKnown ep type\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + max_packet_size = usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe))); + + additional_multi_count = ((max_packet_size) >> 11) & 0x03; + dev_addr = usb_pipedevice(urb->pipe); + ep_num = usb_pipeendpoint(urb->pipe); + + f_is_ep_in = usb_pipein(urb->pipe) ? true : false; + interval = (u8)(urb->interval); + sched_frame = (u8)(urb->start_frame); + + /* check */ + if (urb->dev->tt == NULL) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "urb->dev->tt == NULL\n"); + hub_port = 0; /* u8 hub_port */ + hub_addr = 0; /* u8 hub_addr */ + + } else { + hub_port = (u8)(urb->dev->ttport); + if (urb->dev->tt->hub) { + if (((dev_speed == FULL_SPEED_OTG) || + (dev_speed == LOW_SPEED_OTG)) && + (urb->dev->tt) && + (urb->dev->tt->hub->devnum != 1)) { + f_is_do_split = true; + } + hub_addr = (u8)(urb->dev->tt->hub->devnum); + } + + if (urb->dev->tt->multi) + hub_addr = 0x80; + } + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "dev_spped =%d, hub_port=%d, hub_addr=%d\n", + dev_speed, hub_port, hub_addr); + + ret_val = create_ed(&target_ed); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to create_ed()\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return ret_val; + } + + ret_val = init_ed(target_ed, + dev_addr, + ep_num, + f_is_ep_in, + dev_speed, + ed_type, + max_packet_size, + additional_multi_count, + interval, + sched_frame, + hub_addr, + hub_port, + f_is_do_split, + (void *)urb->ep); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to init_ed() :err = %d\n", (int)ret_val); + otg_mem_free(target_ed); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + urb->ep->hcpriv = (void *)(target_ed); + + } else { /* if (!(ep->hcpriv)) */ + + dev_addr = usb_pipedevice(urb->pipe); + if (((struct ed *)(urb->ep->hcpriv))->ed_desc.device_addr + != dev_addr) + ((struct ed *)urb->ep->hcpriv)->ed_desc.device_addr + = dev_addr; + } + + target_ed = (struct ed *)urb->ep->hcpriv; + + if (urb->transfer_flags & URB_SHORT_NOT_OK) + trans_flag += USB_TRANS_FLAG_NOT_SHORT; + + if (urb->transfer_flags & URB_ISO_ASAP) + trans_flag += USB_TRANS_FLAG_ISO_ASYNCH; + + if (ed_type == ISOCH_TRANSFER) { + otg_err(OTG_DBG_OTGHCDI_HCD, "ISO not yet supported\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + if (!HC_IS_RUNNING(hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return -USB_ERR_NODEV; + } + + /* in case of unlink-during-submit */ + if (urb->status != -EINPROGRESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "urb->status is -EINPROGRESS\n"); + urb->hcpriv = NULL; + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + usb_hcd_giveback_urb(hcd, urb, urb->status); + return USB_ERR_SUCCESS; + } + + ret_val = issue_transfer(otghost, target_ed, (void *)NULL, (void *)NULL, + trans_flag, + (usb_pipetype(urb->pipe) == PIPE_CONTROL) ? true : false, + (u32)urb->setup_packet, (u32)urb->setup_dma, + (u32)urb->transfer_buffer, (u32)urb->transfer_dma, + (u32)urb->transfer_buffer_length, + (u32)urb->start_frame, (u32)urb->number_of_packets, + new_isoch_packet_desc, (void *)urb, &return_td_addr); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to issue_transfer()\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + urb->hcpriv = (void *)return_td_addr; + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_urb_dequeue(struct usb_hcd *_hcd, struct urb *_urb ) + * + * @brief dequeue a urb to otg + * + * @param [in] _hcd : pointer of usb_hcd + * [in] _urb : pointer of urb + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_urb_dequeue( + struct usb_hcd *_hcd, struct urb *_urb, int status) +{ + int ret_val = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + unsigned long spin_lock_flag = 0; + struct td *cancel_td = (struct td *)_urb->hcpriv; + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue\n"); */ + + if (cancel_td == NULL) { + otg_err(OTG_DBG_OTGHCDI_HCD, "cancel_td is NULL\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue status = %d\n", status); + + ret_val = usb_hcd_check_unlink_urb(_hcd, _urb, status); + if ((ret_val) && (ret_val != -EIDRM)) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "ret_val = %d\n", ret_val); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return ret_val; + } + + if (!HC_IS_RUNNING(_hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + otg_usbcore_giveback(cancel_td); + return USB_ERR_SUCCESS; + } + + ret_val = cancel_transfer(otghost, cancel_td->parent_ed_p, cancel_td); + if (ret_val != USB_ERR_DEQUEUED && ret_val != USB_ERR_NOELEMENT) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to cancel_transfer()\n"); +/* otg_usbcore_giveback(cancel_td); */ + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + +/* otg_usbcore_giveback(cancel_td); */ + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_endpoint_disable( + * struct usb_hcd *hcd, + * struct usb_host_endpoint *ep) + * + * @brief disable a endpoint + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + */ +static void s5pc110_otghcd_endpoint_disable( + struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_endpoint_disable\n"); + + if (!((struct ed *)ep->hcpriv)) + return; + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = delete_ed(otghost, (struct ed *)ep->hcpriv); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to delete_ed()\n"); + return; + } + + /* ep->hcpriv = NULL; delete_ed coveres it */ +} + +/** + * int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) + * + * @brief get status of root hub + * + * @param [in] _hcd : pointer of usb_hcd + * [inout] _buf : pointer of buffer for write a status data + * + * @return ret_val : return port status + */ +static int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_status_data\n"); */ + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(_hcd->state)) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "_hcd->state is NOT HC_IS_RUNNING\n"); + return 0; + } + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = get_otg_port_status(_hcd, OTG_PORT_NUMBER, _buf); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_hub_control() + * + * @brief control root hub + * + * @param [in] hcd : pointer of usb_hcd + * [in] typeReq : type of control request + * [in] value : value + * [in] index : index + * [in] buf_p : pointer of urb + * [in] length : type of gfp_t + * + * @return ret_val : return root_hub_feature + */ +static int +s5pc110_otghcd_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 value, + u16 index, + char *buf_p, + u16 length) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_control\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + ret_val = root_hub_feature(hcd, OTG_PORT_NUMBER, + typeReq, value, (void *)buf_p); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to root_hub_feature()\n"); + return ret_val; + } + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) + * + * @brief suspend otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS + */ +static int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_suspend\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + bus_suspend(); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) + * + * @brief resume otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS + */ +static int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_resume\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + if (bus_resume(otghost) != USB_ERR_SUCCESS) + return USB_ERR_FAIL; + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) + * + * @brief reset otg port + * + * @param [in] hcd : pointer of usb_hcd + * [in] port : number of port + * + * @return USB_ERR_SUCCESS : If success + * ret_val : If call fail + */ +static int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start_port_reset\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = reset_and_enable_port(hcd, OTG_PORT_NUMBER); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to reset_and_enable_port()\n"); + return ret_val; + } + return USB_ERR_SUCCESS; +} + + +/** + * @struct hc_driver s5pc110_otg_hc_driver + * + * @brief implementation of hc_driver for OTG HCD + * + * describe in detail + */ +static const struct hc_driver s5pc110_otg_hc_driver = { + .description = "EMSP_OTGHCD", + .product_desc = "S3C OTGHCD", + .hcd_priv_size = sizeof(struct sec_otghost), + + .irq = s5pc110_otghcd_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /** basic lifecycle operations */ + /* .reset = */ + .start = s5pc110_otghcd_start, + /* .suspend = */ + /* .resume = */ + .stop = s5pc110_otghcd_stop, + .shutdown = s5pc110_otghcd_shutdown, + + /** managing i/o requests and associated device resources */ + .urb_enqueue = s5pc110_otghcd_urb_enqueue, + .urb_dequeue = s5pc110_otghcd_urb_dequeue, + .endpoint_disable = s5pc110_otghcd_endpoint_disable, + + /** scheduling support */ + .get_frame_number = s5pc110_otghcd_get_frame_number, + + /** root hub support */ + .hub_status_data = s5pc110_otghcd_hub_status_data, + .hub_control = s5pc110_otghcd_hub_control, + /* .hub_irq_enable = */ + .bus_suspend = s5pc110_otghcd_bus_suspend, + .bus_resume = s5pc110_otghcd_bus_resume, + .start_port_reset = s5pc110_otghcd_start_port_reset, +}; + + diff --git a/drivers/usb/host/shost/shost_kal.h b/drivers/usb/host/shost/shost_kal.h new file mode 100644 index 0000000..1a213a1 --- /dev/null +++ b/drivers/usb/host/shost/shost_kal.h @@ -0,0 +1,361 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-kal.h + * @brief header of s3c-otg-hcdi-kal \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SHOST_KAL_H_ +#define _SHOST_KAL_H_ + +#include <linux/io.h> /* for readl, writel */ +#include <linux/usb/ch9.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <mach/map.h> +#include <plat/regs-otg.h> + +extern volatile u8 *g_pUDCBase; +extern struct usb_hcd *g_pUsbHcd; + +#define ctrlr_base_reg_addr(offset) \ + ((volatile unsigned int *)((g_pUDCBase) + (offset))) +/** + * otg_kal_make_ep_null + * + * @brief make ep->hcpriv NULL + * + * @param [in] pdelete_ed : pointer of ed + * + * @return void \n + */ +static inline void +otg_kal_make_ep_null(struct ed *pdelete_ed) +{ + ((struct usb_host_endpoint *)(pdelete_ed->ed_private))->hcpriv = NULL; +} + +/** + * otg_kal_is_ep_null + * + * @brief check ep->hcpriv is NULL or not + * + * @param [in] pdelete_ed : pointer of ed + * + * @return bool \n + */ +static inline bool +otg_kal_is_ep_null(struct ed *pdelete_ed) +{ + if (((struct usb_host_endpoint *) + (pdelete_ed->ed_private))->hcpriv == NULL) + return true; + else + return false; +} + + +/** + * int otg_usbcore_get_calc_bustime() + * + * @brief get bus time of usbcore + * + * @param [in] speed : usb speed + * [in] is_input : input or not + * [in] is_isoch : isochronous or not + * [in] byte_count : bytes + * + * @return bus time of usbcore \n + */ +static inline int +otg_usbcore_get_calc_bustime(u8 speed, bool is_input, + bool is_isoch, unsigned int byte_count) +{ + unsigned int convert_speed = 0; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_get_calc_bustime\n"); +/* enum usb_device_speed { + USB_SPEED_UNKNOWN = 0, + USB_SPEED_LOW, USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_VARIABLE, };*/ + switch (speed) { + case HIGH_SPEED_OTG: + convert_speed = USB_SPEED_HIGH; break; + case FULL_SPEED_OTG: + convert_speed = USB_SPEED_FULL; break; + case LOW_SPEED_OTG: + convert_speed = USB_SPEED_LOW; break; + default: + convert_speed = USB_SPEED_UNKNOWN; break; + } + return usb_calc_bus_time(convert_speed, is_input, + (unsigned int)is_isoch, byte_count); +} + + +/** + * void otg_usbcore_giveback(struct td td_p) + * + * @brief give-back a td as urb + * + * @param [in] td_p : pointer of struct td to give back + * + * @return void \n + */ +static inline void +otg_usbcore_giveback(struct td *td_p) +{ + struct urb *urb_p = NULL; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_giveback\n"); + + if (td_p->td_private == NULL) { + otg_err(OTG_DBG_OTGHCDI_KAL, + "td_p->td_private == NULL\n"); + return; + } + + urb_p = (struct urb *)td_p->td_private; + + urb_p->actual_length = (int)(td_p->transferred_szie); + urb_p->status = (int)(td_p->error_code); + urb_p->error_count = (int)(td_p->err_cnt); + urb_p->hcpriv = NULL; + + usb_hcd_giveback_urb(g_pUsbHcd, urb_p, urb_p->status); +} + +/** + * void otg_usbcore_hc_died(void) + * + * @brief inform usbcore of hc die + * + * @return void \n + */ +static inline void +otg_usbcore_hc_died(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_hc_died\n"); + usb_hc_died(g_pUsbHcd); +} + +/** + * void otg_usbcore_poll_rh_status(void) + * + * @brief invoke usbcore's usb_hcd_poll_rh_status + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_poll_rh_status(void) +{ + usb_hcd_poll_rh_status(g_pUsbHcd); +} + +/** + * void otg_usbcore_resume_roothub(void) + * + * @brief invoke usbcore's usb_hcd_resume_root_hub + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_resume_roothub(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_resume_roothub\n"); + usb_hcd_resume_root_hub(g_pUsbHcd); +}; + +/** + * int otg_usbcore_inc_usb_bandwidth(u32 band_width) + * + * @brief increase bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be increased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_inc_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_usb_bandwidth\n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated += band_width; + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_des_usb_bandwidth(u32 uiBandwidth) + * + * @brief decrease bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be decreased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_des_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_usb_bandwidth\n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated -= band_width; + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) + * + * @brief increase count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_periodic_transfer_cnt\n"); + + switch (transfer_type) { + case INT_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs++; + break; + case ISOCH_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs++; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) + * + * @brief decrease count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_periodic_transfer_cnt\n"); + + switch (transfer_type) { + case INT_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs--; + break; + case ISOCH_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs--; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * u32 read_reg_32(u32 offset) + * + * @brief Reads the content of a register. + * + * @param [in] offset : offset of address of register to read. + * + * @return contents of the register. \n + * @remark call readl() + */ +static inline u32 read_reg_32(u32 offset) +{ + volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset); + + return *reg_addr_p; + /* return readl(reg_addr_p); */ +}; + +/** + * void write_reg_32( u32 offset, const u32 value) + * + * @brief Writes a register with a 32 bit value. + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to write + * + * @remark call writel() + */ +static inline void write_reg_32(u32 offset, const u32 value) +{ + volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset); + + *reg_addr_p = value; + /* writel( value, reg_addr_p ); */ +}; + +/** + * void update_reg_32(u32 offset, u32 value) + * + * @brief logic or operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to or + * + */ +static inline void update_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) | value)); +} + +/** + * void clear_reg_32(u32 offset, u32 value) + * + * @brief logic not operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to not + * + */ +static inline void clear_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) & ~value)); +} + +#endif /* _S3C_OTG_HCDI_KAL_H_ */ + diff --git a/drivers/usb/host/shost/shost_list.h b/drivers/usb/host/shost/shost_list.h new file mode 100644 index 0000000..7283fb7 --- /dev/null +++ b/drivers/usb/host/shost/shost_list.h @@ -0,0 +1,188 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-list.h + * @brief list functions for otg + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _S3C_OTG_HCDI_LIST_H_ +#define _S3C_OTG_HCDI_LIST_H_ + +#include <linux/list.h> + +typedef struct list_head otg_list_head; + +#define otg_list_get_node(ptr, type, member) container_of(ptr, type, member) + + +#define otg_list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + + +/** + * void otg_list_push_next(otg_list_head *n, otg_list_head *list) + * + * @brief push a list node into the next of head + * + * @param [in] n : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ + +static inline +void otg_list_push_next(otg_list_head *n, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_next\n"); + list_add(n, list); +} + +/** + * void otg_list_push_prev(otg_list_head *n, otg_list_head *list) + * + * @brief push a list node into the previous of head + * + * @param [in] n : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_push_prev(otg_list_head *n, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_prev\n"); + list_add_tail(n, list); +} + +/** + * void otg_list_pop(otg_list_head *list_entity_p) + * + * @brief pop a list node + * + * @param [in] n : node to be poped + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_pop(otg_list_head *list_entity_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_pop\n"); + + if (list_entity_p->prev && list_entity_p->next) + list_del(list_entity_p); + else + pr_info("usb: otg_list_pop error\n"); +} + +/** + * void otg_list_move_next(otg_list_head *node_p, otg_list_head *list) + * + * @brief move a list to next of head + * + * @param [in] n : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_next(otg_list_head *node_p, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_next\n"); + list_move(node_p, list); +} + +/** + * void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list) + * + * @brief move a list to previous of head + * + * @param [in] n : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_prev\n"); + list_move_tail(node_p, list); +} + +/** + * bool otg_list_empty(otg_list_head *list) + * + * @brief check a list empty or not + * + * @param [in] list : node to check + * + * @return true : empty list \n + * false : not empty list + */ +static inline +bool otg_list_empty(otg_list_head *list) +{ + /* otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_empty\n"); */ + if (list_empty(list)) + return true; + return false; +} + +/** + * void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) + * + * @brief merge two list + * + * @param [in] list_p : a head + * @param [in] head_p : target list head + * + * @return void \n + */ +static inline +void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_merge\n"); + list_splice(list_p, head_p); +} + +/** + * void otg_list_init(otg_list_head *list_p) + * + * @brief initialize a list + * + * @param [in] list_p : node to be initialized + * + * @return void \n + */ +static inline +void otg_list_init(otg_list_head *list_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_init\n"); + list_p->next = list_p; + list_p->prev = list_p; +} + +#endif /* _S3C_OTG_HCDI_LIST_H_ */ + diff --git a/drivers/usb/host/shost/shost_mem.h b/drivers/usb/host/shost/shost_mem.h new file mode 100644 index 0000000..ecbbed2 --- /dev/null +++ b/drivers/usb/host/shost/shost_mem.h @@ -0,0 +1,155 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-memory.h + * @brief header of s3c-otg-hcdi-memory \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _S3C_OTG_HCDI_MEMORY_H_ +#define _S3C_OTG_HCDI_MEMORY_H_ + + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> + +/** + * @enum otg_mem_alloc_flag + * + * @brief enumeration for flag of memory allocation + */ +enum otg_mem_type { + USB_MEM_SYNC, + USB_MEM_ASYNC, + USB_MEM_DMA +}; + +/** + * int otg_mem_alloc(void ** addr_pp, u16 byte_size, u8 ubType); + * + * @brief allocating momory specified + * + * @param [inout] addr_pp : address to be assigned + * [in] byte_size : size of memory + * [in] type : enum otg_mem_type + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_alloc(void **addr_pp, u16 byte_size, + enum otg_mem_type type) +{ + gfp_t flags; + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_alloc\n"); + + switch (type) { + case USB_MEM_SYNC: + flags = GFP_KERNEL; + break; + case USB_MEM_ASYNC: + flags = GFP_ATOMIC; + break; + case USB_MEM_DMA: + flags = GFP_DMA; + break; + default: + otg_err(OTG_DBG_OTGHCDI_MEM, + "not proper enum otg_mem_type\n"); + return USB_ERR_FAIL; + } + + *addr_pp = kmalloc((size_t)byte_size, flags); + + if (*addr_pp == 0) { + otg_err(OTG_DBG_OTGHCDI_MEM, "kmalloc failed\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * int otg_mem_copy(void *to_addr_p, void *from_addr_p, u16 byte_size); + * + * @brief memory copy + * + * @param [in] to_addr_p : target address + * [in] from_addr_p : source address + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_copy(void *to_addr_p, + void *from_addr_p, u16 byte_size) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_copy\n"); + + memcpy(to_addr_p, from_addr_p, (size_t)byte_size); + + return USB_ERR_SUCCESS; +} + +/** + * int otg_mem_free(void * addr_p); + * + * @brief de-allocating memory + * + * @param [in] addr_p : target address to be de-allocated + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_free(void *addr_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_free\n"); + kfree(addr_p); + return USB_ERR_SUCCESS; +} + + +/** + * int otg_mem_set(void *addr_p, char value, u16 byte_size) + * + * @brief writing a value to memory + * + * @param [in] addr_p : target address + * [in] value : value to be written + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_set(void *addr_p, char value, u16 byte_size) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_set\n"); + memset(addr_p, value, (size_t)byte_size); + return USB_ERR_SUCCESS; +} + + +#endif /* _S3C_OTG_HCDI_MEMORY_H_ */ diff --git a/drivers/usb/host/shost/shost_oci.c b/drivers/usb/host/shost/shost_oci.c new file mode 100644 index 0000000..4f47055 --- /dev/null +++ b/drivers/usb/host/shost/shost_oci.c @@ -0,0 +1,809 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : OCI.c + * [Description] : The file implement the external + * and internal functions of OCI + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/12 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and Implement functions of OCI + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +static bool ch_enable[16]; + +/* OCI Internal Functions */ + +static void oci_set_global_interrupt(bool set) +{ + gahbcfg_t ahbcfg; + + otg_dbg(OTG_DBG_OCI, "oci_set_global_interrupt\n"); + + ahbcfg.d32 = 0; + ahbcfg.b.glblintrmsk = 1; + + if (set) + update_reg_32(GAHBCFG, ahbcfg.d32); + else + clear_reg_32(GAHBCFG, ahbcfg.d32); +} + +static int oci_channel_alloc(u8 *ch_num) +{ + hcchar_t hcchar = {.d32 = 0}; + u8 ch; + + for (ch = 0 ; ch < 16 ; ch++) { + if (ch_enable[ch] == true) { + + hcchar.d32 = read_reg_32(HCCHAR(ch)); + + if (hcchar.b.chdis == 0) { + *ch_num = ch; + ch_enable[ch] = false; + return USB_ERR_SUCCESS; + } + } + } + + return USB_ERR_FAIL; +} + + + +static void oci_flush_tx_fifo(u32 num) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_tx_fifo\n"); + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + write_reg_32(GRSTCTL, greset.d32); + + /* wait for flush to end */ + while (greset.b.txfflsh == 1) { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + }; + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +static void oci_flush_rx_fifo(void) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_rx_fifo\n"); + + greset.b.rxfflsh = 1; + write_reg_32(GRSTCTL, greset.d32); + + do { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + } while (greset.b.rxfflsh == 1); + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +static void oci_config_flush_fifo(u32 mode) +{ + ghwcfg2_t hwcfg2 = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_config_flush_fifo\n"); + + hwcfg2.d32 = read_reg_32(GHWCFG2); + + /* Configure data FIFO sizes */ + if (hwcfg2.b.dynamic_fifo) { + /* Rx FIFO */ + write_reg_32(GRXFSIZ, 0x0000010D); + + /* Non-periodic Tx FIFO */ + write_reg_32(GNPTXFSIZ, 0x0080010D); + + if (mode == OTG_HOST_MODE) { + /* For Periodic transactions, */ + /* program HPTXFSIZ */ + } + } + + /* Flush the FIFOs */ + oci_flush_tx_fifo(0); + + oci_flush_rx_fifo(); +} + +static int oci_core_reset(void) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_core_reset\n"); + + /* Wait for AHB master IDLE state. */ + do { + greset.d32 = read_reg_32(GRSTCTL); + mdelay(50); + + if (++count > 100) { + otg_dbg(OTG_DBG_OCI, "AHB status is not IDLE\n"); + return USB_ERR_FAIL; + } + + } while (greset.b.ahbidle != 1); + + /* Core Soft Reset */ + greset.b.csftrst = 1; + write_reg_32(GRSTCTL, greset.d32); + + /* Wait for 3 PHY Clocks*/ + mdelay(50); + return USB_ERR_SUCCESS; +} + +/** + * int oci_core_init(void) + * + * @brief process core initialize as s5pc110 otg spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_core_init(void) +{ + gahbcfg_t ahbcfg = {.d32 = 0}; + gusbcfg_t usbcfg = {.d32 = 0}; + ghwcfg2_t hwcfg2 = {.d32 = 0}; + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_core_init\n"); + + usbcfg.d32 = read_reg_32(GUSBCFG); + otg_dbg(OTG_DBG_OCI, "before - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + usbcfg.d32, read_reg_32(GOTGCTL)); + + /* PHY parameters */ + usbcfg.b.physel = 0; + usbcfg.b.phyif = 1; /* 16 bit */ + usbcfg.b.ulpi_utmi_sel = 0; /* UTMI */ + /* usbcfg.b.ddrsel = 1; */ /* DDR */ + usbcfg.b.usbtrdtim = 5; /* 16 bit UTMI */ + usbcfg.b.toutcal = 7; + usbcfg.b.forcehstmode = 1; + write_reg_32(GUSBCFG, usbcfg.d32); + + otg_dbg(OTG_DBG_OCI, + "after - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + read_reg_32(GUSBCFG), + read_reg_32(GOTGCTL)); + + /* Reset after setting the PHY parameters */ + if (oci_core_reset() == USB_ERR_SUCCESS) { + /* Program the GAHBCFG Register.*/ + hwcfg2.d32 = read_reg_32(GHWCFG2); + + switch (hwcfg2.b.architecture) { + case HWCFG2_ARCH_SLAVE_ONLY: + otg_dbg(OTG_DBG_OCI, "Slave Only Mode\n"); + ahbcfg.b.nptxfemplvl = 0; + ahbcfg.b.ptxfemplvl = 0; + break; + + case HWCFG2_ARCH_EXT_DMA: + otg_dbg(OTG_DBG_OCI, "External DMA Mode - TBD!\n"); + break; + + case HWCFG2_ARCH_INT_DMA: + otg_dbg(OTG_DBG_OCI, "Internal DMA Setting\n"); + ahbcfg.b.dmaenable = true; + ahbcfg.b.hburstlen = INT_DMA_MODE_INCR4; + break; + + default: + otg_dbg(OTG_DBG_OCI, "ERR> hwcfg2\n "); + break; + } + write_reg_32(GAHBCFG, ahbcfg.d32); + + /* Program the GUSBCFG register.*/ + switch (hwcfg2.b.op_mode) { + case MODE_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_HNP_SRP_CAPABLE\n"); + usbcfg.b.hnpcap = 1; + usbcfg.b.srpcap = 1; + + otg_dbg(OTG_DBG_OCI, + "OTG_DBG_OCI : use HNP and SRP\n"); + break; + + case MODE_SRP_ONLY_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_ONLY_CAPABLE\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_HNP_SRP_CAPABLE\n"); + usbcfg.b.hnpcap = 0; + break; + + case MODE_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_DEVICE\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_DEVICE\n"); + usbcfg.b.srpcap = 0; + break; + + case MODE_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_HOST\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_HOST\n"); + usbcfg.b.srpcap = 0; + break; + default: + otg_err(OTG_DBG_OCI, "ERR> hwcfg2\n"); + break; + } + write_reg_32(GUSBCFG, usbcfg.d32); + + /* Program the GINTMSK register.*/ + gintmsk.b.modemismatch = 1; + gintmsk.b.sofintr = 1; + /*gintmsk.b.otgintr = 1; */ + gintmsk.b.conidstschng = 1; + /*gintmsk.b.wkupintr = 1;*/ + gintmsk.b.disconnect = 1; + /*gintmsk.b.usbsuspend = 1;*/ + /*gintmsk.b.sessreqintr = 1;*/ + /*gintmsk.b.portintr = 1;*/ + /*gintmsk.b.hcintr = 1; */ + write_reg_32(GINTMSK, gintmsk.d32); + + return USB_ERR_SUCCESS; + + } else { + otg_err(OTG_DBG_OCI, "Core Reset FAIL\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_host_init(void) + * + * @brief Process host initialize as s5pc110 spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ + +static int oci_host_init(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + hcfg_t hcfg = {.d32 = 0}; +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + hprt_t hprt; + hprt.d32 = read_reg_32(HPRT); +#endif + + otg_dbg(OTG_DBG_OCI, "oci_host_init\n"); + + gintmsk.b.portintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + + hcfg.b.fslspclksel = HCFG_30_60_MHZ; + update_reg_32(HCFG, hcfg.d32); + +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + /* turn on vbus */ + if (!hprt.b.prtpwr) { + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + + otg_dbg(true, "turn on Vbus\n"); + } +#endif + + oci_config_flush_fifo(OTG_HOST_MODE); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_channel_init( u8 ch_num, struct stransfer *transfer) + * + * @brief Process channel initialize to prepare starting transfer + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_channel_init(u8 ch_num, struct stransfer *transfer) +{ + u32 intr_enable = 0; + gintmsk_t gintmsk = {.d32 = 0}; + hcchar_t hcchar = {.d32 = 0}; + hctsiz_t hctsiz = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_channel_init\n"); + + /* Clear channel information */ + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL);/*write clear*/ + write_reg_32(HCDMA(ch_num), 0); + + /* enable host channel interrupt in GINTSTS */ + gintmsk.b.hcintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + /* Enable the top level host channel interrupt in HAINT */ + intr_enable = (1 << ch_num); + update_reg_32(HAINTMSK, intr_enable); + /* unmask the down level host channel interrupt in HCINT */ + write_reg_32(HCINTMSK(ch_num), transfer->hc_reg.hc_int_msk.d32); + + /* Program the HCSIZn register with the endpoint characteristics for */ + hctsiz.b.xfersize = transfer->buf_size; + hctsiz.b.pktcnt = transfer->packet_cnt; + + /* Program the HCCHARn register with the endpoint characteristics for */ + hcchar.b.mps = transfer->ed_desc_p->max_packet_size; + hcchar.b.epnum = transfer->ed_desc_p->endpoint_num; + hcchar.b.epdir = transfer->ed_desc_p->is_ep_in; + hcchar.b.lspddev = (transfer->ed_desc_p->dev_speed == LOW_SPEED_OTG); + hcchar.b.eptype = transfer->ed_desc_p->endpoint_type; + hcchar.b.multicnt = transfer->ed_desc_p->mc; + hcchar.b.devaddr = transfer->ed_desc_p->device_addr; + + if (transfer->ed_desc_p->endpoint_type == INT_TRANSFER || + transfer->ed_desc_p->endpoint_type == ISOCH_TRANSFER) { + u32 uiFrameNum = 0; + uiFrameNum = oci_get_frame_num(); + + hcchar.b.oddfrm = (uiFrameNum % 2) ? 1 : 0; + + /* + * if transfer type is periodic transfer, + * must support sof interrupt + */ + + /* + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + */ + } + + if (transfer->ed_desc_p->endpoint_type == CONTROL_TRANSFER) { + + struct td *td_p; + td_p = (struct td *)transfer->parent_td; + + switch (td_p->standard_dev_req_info.conrol_transfer_stage) { + + case SETUP_STAGE: + hctsiz.b.pid = transfer->ed_status_p-> + control_data_toggle.setup; + hcchar.b.epdir = EP_OUT; + break; + + case DATA_STAGE: + hctsiz.b.pid = + transfer->ed_status_p->control_data_toggle.data; + hcchar.b.epdir = transfer->ed_desc_p->is_ep_in; + break; + + case STATUS_STAGE: + hctsiz.b.pid = transfer->ed_status_p-> + control_data_toggle.status; + + if (td_p->standard_dev_req_info.is_data_stage) { + hcchar.b.epdir = + ~(transfer->ed_desc_p->is_ep_in); + } else + hcchar.b.epdir = EP_IN; + break; + default: + break; + } + + } else + hctsiz.b.pid = transfer->ed_status_p->data_toggle; + + otg_dbg(OTG_DBG_OCI, "eptype %d, stage %d, dir %d\n", + transfer->ed_desc_p->endpoint_type, + ((struct td *)transfer->parent_td)-> + standard_dev_req_info.conrol_transfer_stage, + hcchar.b.epdir); + + hctsiz.b.dopng = transfer->ed_status_p->is_ping_enable; + write_reg_32(HCTSIZ(ch_num), hctsiz.d32); + transfer->ed_status_p->is_ping_enable = false; + + /* Write DMA Address */ + write_reg_32(HCDMA(ch_num), transfer->phy_addr); + + /* Wrote HCCHAR Register */ + write_reg_32(HCCHAR(ch_num), hcchar.d32); + + return USB_ERR_SUCCESS; +} + +static int oci_dev_init(void) +{ + otg_dbg(OTG_DBG_OCI, "oci_dev_init - do nothing.\n"); + /* return USB_ERR_FAIL; */ + return USB_ERR_SUCCESS; +} + +static int oci_init_mode(void) +{ + gintsts_t gintsts; + gintsts.d32 = read_reg_32(GINTSTS); + + otg_dbg(OTG_DBG_OCI, + "GINSTS = 0x%x,GINMSK = 0x%x.\n", + (unsigned int)gintsts.d32, + (unsigned int)read_reg_32(GINTMSK)); + + if (gintsts.b.curmode == OTG_HOST_MODE) { + otg_dbg(OTG_DBG_OCI, "HOST Mode\n"); + + if (oci_host_init() == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_host_init() Fail\n"); + return USB_ERR_FAIL; + } + + } else { /* Device Mode */ + otg_dbg(OTG_DBG_OCI, "DEVICE Mode\n"); + + if (oci_dev_init() == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + + } else { + otg_err(OTG_DBG_OCI, "oci_dev_init() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_SUCCESS; +} + + + +/** + * int oci_start(void) + * + * @brief start to operate oci module by calling oci_core_init function + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_start(void) +{ + otg_dbg(OTG_DBG_OCI, "oci_start\n"); + + if (oci_core_init() == USB_ERR_SUCCESS) { + mdelay(50); + + if (oci_init_mode() == USB_ERR_SUCCESS) { + oci_set_global_interrupt(true); + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_init_mode() Fail\n"); + return USB_ERR_FAIL; + } + + } else { + otg_dbg(OTG_DBG_OCI, "oci_core_init() Fail\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_stop(void) + * + * @brief stop to opearte otg core + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_stop(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_stop\n"); + + /* sometimes, port interrupt occured after removed + * otg host driver. so, we have to mask port interrupt. */ + write_reg_32(GINTMSK, gintmsk.d32); + + oci_set_global_interrupt(false); + + return USB_ERR_SUCCESS; +} + + +static int oci_sys_init(struct sec_otghost *otghost) +{ + struct sec_otghost_data *pdata = otghost->otg_data; + + if (pdata && pdata->phy_init && pdata->pdev) { + pr_info("otg phy_init\n"); + clk_enable(pdata->clk); + pdata->phy_init(0); + + writel(0, OTG_PHYPWR); + writel(0x87, OTG_PHYCLK); + } else + pr_info("otg phy_init failed\n"); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_init(struct sec_otghost *otghost) + * + * @brief Initialize oci module. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_init(struct sec_otghost *otghost) +{ + otg_mem_set((void *)ch_enable, true, sizeof(bool)*16); + otghost->ch_halt = false; + + if (oci_sys_init(otghost) == USB_ERR_SUCCESS) { + if (oci_core_reset() == USB_ERR_SUCCESS) { + + oci_set_global_interrupt(false); + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_core_reset() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_FAIL; +} + +/*********************************************************************** + * API + */ + +/* also called by ischeduler.c */ +int oci_channel_dealloc(u8 ch_num) +{ + if (ch_num < 16 && ch_enable[ch_num] == false) { + ch_enable[ch_num] = true; + + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL); + write_reg_32(HCDMA(ch_num), 0); + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + +/** + * oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer) + * + * @brief start transfer by using transfer information to receive from scheduler + * + * @param [IN] *transfer - information about transfer to write register by calling oci_channel_init function + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ + +u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer) +{ + hcchar_t hcchar = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_start_transfer\n"); + + if (transfer->alloc_chnum == CH_NONE) { + + if (oci_channel_alloc(&(transfer->alloc_chnum)) + != USB_ERR_SUCCESS) { + + otg_dbg(OTG_DBG_OCI, + "Fail - Channel Allocation Error\n"); + return CH_NONE; + } + } + + oci_channel_init(transfer->alloc_chnum, transfer); + + hcchar.b.chen = 1; + update_reg_32(HCCHAR(transfer->alloc_chnum), hcchar.d32); + + return transfer->alloc_chnum; +} + +/** + * int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) + * + * @brief stop to transfer even if transfering + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) +{ + hcchar_t hcchar = {.d32 = 0}; + hcintmsk_t hcintmsk = {.d32 = 0}; + int count = 0, max_error_count = 10000; + + otg_dbg(OTG_DBG_OCI, + "step1: oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + if (ch_num > 16) + return USB_ERR_FAIL; + + otghost->ch_halt = true; + + hcintmsk.b.chhltd = 1; + update_reg_32(HCINTMSK(ch_num), hcintmsk.d32); + + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + update_reg_32(HCCHAR(ch_num), hcchar.d32); + + /* wait for Channel Disabled Interrupt */ + do { + hcchar.d32 = read_reg_32(HCCHAR(ch_num)); + + if (count > max_error_count) { + otg_dbg(OTG_DBG_OCI, + "Warning!! oci_stop_transfer()" + "ChDis is not cleared! ch=%d, hcchar=0x%x\n", + ch_num, hcchar.d32); + break; + } + count++; + + } while (hcchar.b.chdis); + + oci_channel_dealloc(ch_num); + + clear_reg_32(HAINTMSK, ch_num); + write_reg_32(HCINT(ch_num), INT_ALL); + clear_reg_32(HCINTMSK(ch_num), INT_ALL); + + otghost->ch_halt = false; + otg_dbg(OTG_DBG_OCI, + "step2 : oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + return USB_ERR_SUCCESS; +} + +/** + * u32 oci_get_frame_num(void) + * + * @brief Get current frame number by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u32 oci_get_frame_num(void) +{ + hfnum_t hfnum; + hfnum.d32 = read_reg_32(HFNUM); + + otg_dbg(OTG_DBG_OCI, "oci_get_frame_num=%d\n", hfnum.b.frnum); + + return hfnum.b.frnum; +} + +/** + * u16 oci_get_frame_interval(void) + * + * @brief Get current frame interval by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u16 oci_get_frame_interval(void) +{ + hfir_t hfir; + hfir.d32 = read_reg_32(HFIR); + return hfir.b.frint; +} + +void oci_set_frame_interval(u16 interval) +{ + hfir_t hfir = {.d32 = 0}; + hfir.b.frint = interval; + write_reg_32(HFIR, hfir.d32); +} + diff --git a/drivers/usb/host/shost/shost_oci.h b/drivers/usb/host/shost/shost_oci.h new file mode 100644 index 0000000..8fa6bbb --- /dev/null +++ b/drivers/usb/host/shost/shost_oci.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] :s3c-otg-oci.h + * [Description] : The Header file defines the external + * and internal functions of OCI. + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2008/06/18 + * [Revision History] + * (1) 2008/06/25 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Added some functions and data structure of OCI + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _OCI_H_ +#define _OCI_H_ + + +#include <mach/map.h> /* virtual address*/ + +extern void otg_host_phy_init(void); +#include <mach/regs-clock.h> + +int oci_channel_dealloc(u8 ch_num); + +u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *st_t); +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num); + +u32 oci_get_frame_num(void); +u16 oci_get_frame_interval(void); +void oci_set_frame_interval(u16 intervl); + + +#endif /* _OCI_H_ */ diff --git a/drivers/usb/host/shost/shost_readyq.c b/drivers/usb/host/shost/shost_readyq.c new file mode 100644 index 0000000..9e41da5 --- /dev/null +++ b/drivers/usb/host/shost/shost_readyq.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : TransferReadyQ.c + * [Description] : The source file implements the internal + * functions of TransferReadyQ. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/04 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of TransferReadyQ. + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ + +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +static struct trans_ready_q periodic_trans_ready_q; +static struct trans_ready_q nonperiodic_trans_ready_q; + +/******************************************************************************/ +/*! + * @name void init_transfer_ready_q(void) + * + * @brief this function initiates PeriodicTransferReadyQ + * and NonPeriodicTransferReadyQ. + * + * + * @param void + * + * @return void. + */ +/******************************************************************************/ +static void init_transfer_ready_q(void) +{ + otg_dbg(OTG_DBG_SCHEDULE, "start init_transfer_ready_q\n"); + + otg_list_init(&periodic_trans_ready_q.entity_list); + periodic_trans_ready_q.is_periodic = true; + periodic_trans_ready_q.entity_num = 0; + periodic_trans_ready_q.total_alloc_chnum = 0; + periodic_trans_ready_q.total_perio_bus_bandwidth = 0; + + otg_list_init(&nonperiodic_trans_ready_q.entity_list); + nonperiodic_trans_ready_q.is_periodic = false; + nonperiodic_trans_ready_q.entity_num = 0; + nonperiodic_trans_ready_q.total_alloc_chnum = 0; + nonperiodic_trans_ready_q.total_perio_bus_bandwidth = 0; +} + + +/******************************************************************************/ +/*! + * @name int insert_ed_to_ready_q(struct ed *insert_ed, + * bool is_first) + * + * @brief this function inserts ed_t * to TransferReadyQ. + * + * + * @param [IN] insert_ed + * = indicates the ed_t to be inserted to TransferReadyQ. + * [IN] is_first + * = indicates whether the insert_ed is inserted + * as first entry of TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to insert the insert_ed to TransferReadyQ. + * USB_ERR_FAILl -if fails to insert the insert_ed to TransferReadyQ. + */ +/******************************************************************************/ +static int insert_ed_to_ready_q(struct ed *insert_ed, bool is_first) +{ + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d, %d\n", + insert_ed->ed_id, insert_ed->num_td, is_first); + + if (insert_ed->ed_desc.endpoint_type == BULK_TRANSFER || + insert_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) { + + if (is_first) { + otg_list_push_next(&insert_ed->readyq_list, + &nonperiodic_trans_ready_q.entity_list); + } else { + otg_list_push_prev(&insert_ed->readyq_list, + &nonperiodic_trans_ready_q.entity_list); + } + nonperiodic_trans_ready_q.entity_num++; + + } else { + if (is_first) { + otg_list_push_next(&insert_ed->readyq_list, + &periodic_trans_ready_q.entity_list); + } else { + otg_list_push_prev(&insert_ed->readyq_list, + &periodic_trans_ready_q.entity_list); + } + periodic_trans_ready_q.entity_num++; + } + return USB_ERR_SUCCESS; +} + + +static u32 get_periodic_ready_q_entity_num(void) +{ + return periodic_trans_ready_q.entity_num; +} + +/******************************************************************************/ +/*! + * @name int remove_ed_from_ready_q(ed_t *remove_ed) + * + * @brief this function removes ed_t * from TransferReadyQ. + * + * + * @param [IN] remove_ed + * = indicate the ed_t to be removed from TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAILl -if fails to remove the remove_ed from TransferReadyQ. + */ +/******************************************************************************/ +static int remove_ed_from_ready_q(struct ed *remove_ed) +{ + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d\n", + remove_ed->ed_id, remove_ed->num_td); + + otg_list_pop(&remove_ed->readyq_list); + + if (remove_ed->ed_desc.endpoint_type == BULK_TRANSFER || + remove_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) { + + nonperiodic_trans_ready_q.entity_num--; + + } else + periodic_trans_ready_q.entity_num--; + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int get_ed_from_ready_q(bool is_periodic, + * struct td **get_ed) + * + * @brief this function returns the first entity of TransferReadyQ. + * if there are some ed_t on TransferReadyQ, + * this function pops first ed_t from TransferReadyQ. + * So, the TransferReadyQ don's has the poped ed_t. + * + * @param [IN] is_periodic + * = indicate whether Periodic or not + * [OUT] get_ed + * = indicate the double pointer to store the address of first entity + * on TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to get frist ed_t from TransferReadyQ. + * USB_ERR_NO_ENTITY -if fails to get frist ed_t from TransferReadyQ + * because there is no entity on TransferReadyQ. + */ +/******************************************************************************/ + +static int get_ed_from_ready_q(struct ed **get_ed, bool is_periodic) +{ + otg_list_head *qlist = NULL; + + /* periodic transfer : control and bulk */ + if (is_periodic) { + + if (periodic_trans_ready_q.entity_num == 0) + return USB_ERR_NO_ENTITY; + + qlist = periodic_trans_ready_q.entity_list.next; + + if (!otg_list_empty(&periodic_trans_ready_q.entity_list)) { + + *get_ed = otg_list_get_node(qlist, + struct ed, readyq_list); + + if (qlist->prev == LIST_POISON2 || + qlist->next == LIST_POISON1) { + + printk(KERN_ERR "shost scheduler: get_ed_from_ready_q error\n"); + periodic_trans_ready_q.entity_num = 0; + + } else { + otg_list_pop(qlist); + periodic_trans_ready_q.entity_num--; + } + return USB_ERR_SUCCESS; + + } else + return USB_ERR_NO_ENTITY; + + /* non-periodic transfer : interrupt and ischo */ + } else { + + if (nonperiodic_trans_ready_q.entity_num == 0) + return USB_ERR_NO_ENTITY; + + qlist = nonperiodic_trans_ready_q.entity_list.next; + + if (!otg_list_empty(&nonperiodic_trans_ready_q.entity_list)) { + + *get_ed = otg_list_get_node(qlist, + struct ed, readyq_list); + + if (qlist->prev == LIST_POISON2 || + qlist->next == LIST_POISON1) { + + printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n"); + nonperiodic_trans_ready_q.entity_num = 0; + + } else { + otg_list_pop(qlist); + nonperiodic_trans_ready_q.entity_num--; + } + return USB_ERR_SUCCESS; + } else + return USB_ERR_NO_ENTITY; + } +} + + diff --git a/drivers/usb/host/shost/shost_regs.h b/drivers/usb/host/shost/shost_regs.h new file mode 100644 index 0000000..d560567 --- /dev/null +++ b/drivers/usb/host/shost/shost_regs.h @@ -0,0 +1,747 @@ +/**************************************************************************** +* (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved +* +* [File Name] : s3c-otg-common-regdef.h +* [Description] : +* +* [Author] : Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* [Department] : System LSI Division/Embedded Software Center +* [Created Date]: 2007/12/15 +* [Revision History] +* (1) 2007/12/15 by Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* - Created +* +****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _OTG_REG_DEF_H +#define _OTG_REG_DEF_H + + +#define GOTGCTL 0x000 /* OTG Control & Status */ +#define GOTGINT 0x004 /* OTG Interrupt */ +#define GAHBCFG 0x008 /* Core AHB Configuration */ +#define GUSBCFG 0x00C /* Core USB Configuration */ +#define GRSTCTL 0x010 /* Core Reset */ +#define GINTSTS 0x014 /* Core Interrupt */ +#define GINTMSK 0x018 /* Core Interrupt Mask */ +#define GRXSTSR 0x01C /* Receive Status Debug Read/Status Read */ +#define GRXSTSP 0x020 /* Receive Status Debug Pop/Status Pop */ +#define GRXFSIZ 0x024 /* Receive FIFO Size */ +#define GNPTXFSIZ 0x028 /* Non-Periodic Transmit FIFO Size */ +#define GNPTXSTS 0x02C /* Non-Periodic Transmit FIFO/Queue Status */ +#define GPVNDCTL 0x034 /* PHY Vendor Control */ +#define GGPIO 0x038 /* General Purpose I/O */ +#define GUID 0x03C /* User ID */ +#define GSNPSID 0x040 /* Synopsys ID */ +#define GHWCFG1 0x044 /* User HW Config1 */ +#define GHWCFG2 0x048 /* User HW Config2 */ +#define GHWCFG3 0x04C /* User HW Config3 */ +#define GHWCFG4 0x050 /* User HW Config4 */ + +#define HPTXFSIZ 0x100 /* Host Periodic Transmit FIFO Size */ +#define DPTXFSIZ1 0x104 /* Device Periodic Transmit FIFO-1 Size */ +#define DPTXFSIZ2 0x108 /* Device Periodic Transmit FIFO-2 Size */ +#define DPTXFSIZ3 0x10C /* Device Periodic Transmit FIFO-3 Size */ +#define DPTXFSIZ4 0x110 /* Device Periodic Transmit FIFO-4 Size */ +#define DPTXFSIZ5 0x114 /* Device Periodic Transmit FIFO-5 Size */ +#define DPTXFSIZ6 0x118 /* Device Periodic Transmit FIFO-6 Size */ +#define DPTXFSIZ7 0x11C /* Device Periodic Transmit FIFO-7 Size */ +#define DPTXFSIZ8 0x120 /* Device Periodic Transmit FIFO-8 Size */ +#define DPTXFSIZ9 0x124 /* Device Periodic Transmit FIFO-9 Size */ +#define DPTXFSIZ10 0x128 /* Device Periodic Transmit FIFO-10 Size */ +#define DPTXFSIZ11 0x12C /* Device Periodic Transmit FIFO-11 Size */ +#define DPTXFSIZ12 0x130 /* Device Periodic Transmit FIFO-12 Size */ +#define DPTXFSIZ13 0x134 /* Device Periodic Transmit FIFO-13 Size */ +#define DPTXFSIZ14 0x138 /* Device Periodic Transmit FIFO-14 Size */ +#define DPTXFSIZ15 0x13C /* Device Periodic Transmit FIFO-15 Size */ + +/********************************************************************* + * Host Mode Registers + *********************************************************************/ +/* Host Global Registers */ + +/* Channel specific registers */ +#define HCCHAR_ADDR (0x500) +#define HCCHAR(n) (0x500 + ((n)*0x20)) +#define HCSPLT(n) (0x504 + ((n)*0x20)) +#define HCINT(n) (0x508 + ((n)*0x20)) +#define HCINTMSK(n) (0x50C + ((n)*0x20)) +#define HCTSIZ(n) (0x510 + ((n)*0x20)) +#define HCDMA(n) (0x514 + ((n)*0x20)) + +#define HCFG 0x400 /* Host Configuration */ +#define HFIR 0x404 /* Host Frame Interval */ +#define HFNUM 0x408 /* Host Frame Number/Frame Time Remaining */ +#define HPTXSTS 0x410 /* Host Periodic Transmit FIFO/Queue Status */ +#define HAINT 0x414 /* Host All Channels Interrupt */ +#define HAINTMSK 0x418 /* Host All Channels Interrupt Mask */ + +/* Host Port Control & Status Registers */ +#define HPRT 0x440 /* Host Port Control & Status */ + +#define EP_FIFO(n) (0x1000 + ((n)*0x1000)) + +#define PCGCCTL 0x0E00 + +#define BASE_REGISTER_OFFSET 0x0 +#define REGISTER_SET_SIZE 0x200 + +/* Power Reg Bits */ +#define USB_RESET 0x8 +#define MCU_RESUME 0x4 +#define SUSPEND_MODE 0x2 +#define SUSPEND_MODE_ENABLE_CTRL 0x1 + +/* EP0 CSR */ +#define EP0_OUT_PACKET_RDY 0x1 +#define EP0_IN_PACKET_RDY 0x2 +#define EP0_SENT_STALL 0x4 +#define DATA_END 0x8 +#define SETUP_END 0x10 +#define EP0_SEND_STALL 0x20 +#define SERVICED_OUT_PKY_RDY 0x40 +#define SERVICED_SETUP_END 0x80 + +/* IN_CSR1_REG Bit definitions */ +#define IN_PACKET_READY 0x1 +#define UNDER_RUN 0x4 /* Iso Mode Only */ +#define FLUSH_IN_FIFO 0x8 +#define IN_SEND_STALL 0x10 +#define IN_SENT_STALL 0x20 +#define IN_CLR_DATA_TOGGLE 0x40 + +/* OUT_CSR1_REG Bit definitions */ +#define OUT_PACKET_READY 0x1 +#define FLUSH_OUT_FIFO 0x10 +#define OUT_SEND_STALL 0x20 +#define OUT_SENT_STALL 0x40 +#define OUT_CLR_DATA_TOGGLE 0x80 + +/* IN_CSR2_REG Bit definitions */ +#define IN_DMA_INT_DISABLE 0x10 +#define SET_MODE_IN 0x20 + +#define EPTYPE (0x3<<18) +#define SET_TYPE_CONTROL (0x0<<18) +#define SET_TYPE_ISO (0x1<<18) +#define SET_TYPE_BULK (0x2<<18) +#define SET_TYPE_INTERRUPT (0x3<<18) + +#define AUTO_MODE 0x80 + +/* OUT_CSR2_REG Bit definitions */ +#define AUTO_CLR 0x40 +#define OUT_DMA_INT_DISABLE 0x20 + +/* Can be used for Interrupt and Interrupt Enable Reg - common bit def */ +#define EP0_IN_INT (0x1<<0) +#define EP1_IN_INT (0x1<<1) +#define EP2_IN_INT (0x1<<2) +#define EP3_IN_INT (0x1<<3) +#define EP4_IN_INT (0x1<<4) +#define EP5_IN_INT (0x1<<5) +#define EP6_IN_INT (0x1<<6) +#define EP7_IN_INT (0x1<<7) +#define EP8_IN_INT (0x1<<8) +#define EP9_IN_INT (0x1<<9) +#define EP10_IN_INT (0x1<<10) +#define EP11_IN_INT (0x1<<11) +#define EP12_IN_INT (0x1<<12) +#define EP13_IN_INT (0x1<<13) +#define EP14_IN_INT (0x1<<14) +#define EP15_IN_INT (0x1<<15) +#define EP0_OUT_INT (0x1<<16) +#define EP1_OUT_INT (0x1<<17) +#define EP2_OUT_INT (0x1<<18) +#define EP3_OUT_INT (0x1<<19) +#define EP4_OUT_INT (0x1<<20) +#define EP5_OUT_INT (0x1<<21) +#define EP6_OUT_INT (0x1<<22) +#define EP7_OUT_INT (0x1<<23) +#define EP8_OUT_INT (0x1<<24) +#define EP9_OUT_INT (0x1<<25) +#define EP10_OUT_INT (0x1<<26) +#define EP11_OUT_INT (0x1<<27) +#define EP12_OUT_INT (0x1<<28) +#define EP13_OUT_INT (0x1<<29) +#define EP14_OUT_INT (0x1<<30) +#define EP15_OUT_INT (0x1<<31) + +/* GOTGINT */ +#define SesEndDet (0x1<<2) + +/* GRSTCTL */ +#define TxFFlsh (0x1<<5) +#define RxFFlsh (0x1<<4) +#define INTknQFlsh (0x1<<3) +#define FrmCntrRst (0x1<<2) +#define HSftRst (0x1<<1) +#define CSftRst (0x1<<0) + +#define CLEAR_ALL_EP_INTRS 0xffffffff + +/* Bits to write to EP_INT_EN_REG - Use CLEAR */ +#define EP_INTERRUPT_DISABLE_ALL 0x0 + +/* DMA control register bit definitions */ +#define RUN_OB 0x80 +#define STATE 0x70 +#define DEMAND_MODE 0x8 +#define OUT_DMA_RUN 0x4 +#define IN_DMA_RUN 0x2 +#define DMA_MODE_EN 0x1 + + +#define REAL_PHYSICAL_ADDR_EP0_FIFO (0x520001c0) /*Endpoint 0 FIFO */ +#define REAL_PHYSICAL_ADDR_EP1_FIFO (0x520001c4) /*Endpoint 1 FIFO */ +#define REAL_PHYSICAL_ADDR_EP2_FIFO (0x520001c8) /*Endpoint 2 FIFO */ +#define REAL_PHYSICAL_ADDR_EP3_FIFO (0x520001cc) /*Endpoint 3 FIFO */ +#define REAL_PHYSICAL_ADDR_EP4_FIFO (0x520001d0) /*Endpoint 4 FIFO */ + +/* GAHBCFG */ +#define MODE_DMA (1<<5) +#define MODE_SLAVE (0<<5) +#define BURST_SINGLE (0<<1) +#define BURST_INCR (1<<1) +#define BURST_INCR4 (3<<1) +#define BURST_INCR8 (5<<1) +#define BURST_INCR16 (7<<1) +#define GBL_INT_MASK (0<<0) +#define GBL_INT_UNMASK (1<<0) + + +/********************************************************************* + * Global CSR + *********************************************************************/ + +/* GAHBCFG + * OTG AHB Configuration Register + * p1359 + * d201 + */ +typedef union _gahbcfg_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned glblintrmsk:1; +#define GAHBCFG_GLBINT_ENABLE 1 + unsigned hburstlen:4; +#define INT_DMA_MODE_SINGLE 0 +#define INT_DMA_MODE_INCR 1 +#define INT_DMA_MODE_INCR4 3 +#define INT_DMA_MODE_INCR8 5 +#define INT_DMA_MODE_INCR16 7 + unsigned dmaenable:1; +#define GAHBCFG_DMAENABLE 1 + unsigned reserved:1; + unsigned nptxfemplvl:1; + unsigned ptxfemplvl:1; +#define GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_31:22; + } b; +} gahbcfg_t; + + +/* GUSBCFG + * OTG USB Configuration Register + * p1360 + * d202 + */ +typedef union _gusbcfg_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned toutcal:3; + unsigned phyif:1; + unsigned ulpi_utmi_sel:1; + unsigned fsintf:1; + unsigned physel:1; + unsigned ddrsel:1; + unsigned srpcap:1; + unsigned hnpcap:1; + unsigned usbtrdtim:4; + unsigned nptxfrwnden:1; + unsigned phylpwrclksel:1; + unsigned reserved:13; + unsigned forcehstmode:1; + unsigned reserved2:2; + } b; +} gusbcfg_t; + + +/* GRSTCTL + * Core Reset Register + * p1362 + * d208 + */ +typedef union grstctl_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned csftrst:1; + unsigned hsftrst:1; + unsigned hstfrm:1; + unsigned intknqflsh:1; + unsigned rxfflsh:1; + unsigned txfflsh:1; + unsigned txfnum:5; + unsigned reserved11_29:19; + unsigned dmareq:1; + unsigned ahbidle:1; + } b; +} grstctl_t; + + +/* GINTSTS + * Core Interrupt Register + * p1364 + * d212 + */ +typedef union _gintsts_t { + /** raw register data */ + u32 d32; +#define SOF_INTR_MASK 0x0008 + /** register bits */ + struct { +#define HOST_MODE 1 +#define DEVICE_MODE 0 + unsigned curmode:1; +#define OTG_HOST_MODE 1 +#define OTG_DEVICE_MODE 0 + + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned reserved8:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned intokenrx:1; + unsigned epmismatch:1; + unsigned inepint:1; + unsigned outepintr:1; + unsigned incompisoin:1; + unsigned incompisoout:1; + unsigned reserved22_23:2; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned reserved27:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintsts_t; + + +/* GINTMSK + * Core Interrupt Mask Register + * p1369 + * d217 + */ +typedef union _gintmsk_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned reserved0:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned reserved8:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned reserved16:1; + unsigned epmismatch:1; + unsigned inepintr:1; + unsigned outepintr:1; + unsigned incompisoin:1; + unsigned incompisoout:1; + unsigned reserved22_23:2; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned reserved27:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintmsk_t; + + +/* GRXSTSR/GRXSTSP + * Host Mode Receive Status Debug Read/Status Read and Pop Registers + * p1370 + * d219 + */ +typedef union _grxstsr_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned chnum:4; + unsigned bcnt:11; + unsigned dpid:2; + unsigned pktsts:4; + unsigned Reserved:11; + } b; +} grxstsr_t; + + +/* User HW Config2 Register + * d230 + */ +typedef union _ghwcfg2_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + /* GHWCFG2 */ + unsigned op_mode:3; +#define MODE_HNP_SRP_CAPABLE 0 +#define MODE_SRP_ONLY_CAPABLE 1 +#define MODE_NO_HNP_SRP_CAPABLE 2 +#define MODE_SRP_CAPABLE_DEVICE 3 +#define MODE_NO_SRP_CAPABLE_DEVICE 4 +#define MODE_SRP_CAPABLE_HOST 5 +#define MODE_NO_SRP_CAPABLE_HOST 6 + + unsigned architecture:2; +#define HWCFG2_ARCH_SLAVE_ONLY 0x00 +#define HWCFG2_ARCH_EXT_DMA 0x01 +#define HWCFG2_ARCH_INT_DMA 0x02 + + unsigned point2point:1; + unsigned hs_phy_type:2; + unsigned fs_phy_type:2; + unsigned num_dev_ep:4; + unsigned num_host_chan:4; + unsigned perio_ep_supported:1; + unsigned dynamic_fifo:1; + unsigned rx_status_q_depth:2; + unsigned nonperio_tx_q_depth:2; + unsigned host_perio_tx_q_depth:2; + unsigned dev_token_q_depth:5; + unsigned reserved31:1; + } b; +} ghwcfg2_t; + + +/********************************************************************* + * Host Mode Registers + *********************************************************************/ + +/* Host Configuration Register + * d247 + */ +typedef union _hcfg_t { + /** raw register data */ + u32 d32; + + /** register bits */ + struct { + /** FS/LS Phy Clock Select */ + unsigned fslspclksel:2; +#define HCFG_30_60_MHZ 0 +#define HCFG_48_MHZ 1 +#define HCFG_6_MHZ 2 + + /** FS/LS Only Support */ + unsigned fslssupp:1; + unsigned reserved3_31:29; + } b; +} hcfg_t; + + +/* Host Frame Interval Register + * d247 + */ +typedef union _hfir_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned frint:16; + unsigned Reserved:16; + } b; +} hfir_t; + +/* Host Frame Number/Frame Time Remaining Register + * d248 + */ +typedef union _hfnum_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned frnum:16; +#define HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + } b; +} hfnum_t; + + +/* Host Channel Interrupt Mask Register + d257 + */ +typedef union _hcintmsk_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned xfercompl:1; + unsigned chhltd:1; + unsigned ahberr:1; + unsigned stall:1; + unsigned nak:1; + unsigned ack:1; + unsigned nyet:1; + unsigned xacterr:1; + unsigned bblerr:1; + unsigned frmovrun:1; + unsigned datatglerr:1; + unsigned reserved:21; + } b; +} hcintmsk_t; + + +/* Host Channel Interrupt Register + d254 + */ +typedef union _hcintn_t { + u32 d32; + struct { + u32 xfercompl:1; + u32 chhltd:1; + u32 abherr:1; + u32 stall:1; + u32 nak:1; + u32 ack:1; + u32 nyet:1; + u32 xacterr:1; + u32 bblerr:1; + u32 frmovrun:1; + u32 datatglerr:1; + u32 reserved:21; + } b; +} hcintn_t; + + +/* Host All Channels Interrupt Register + d250 + */ + +#define MAX_COUNT 10000 +#define INT_ALL 0xffffffff + +typedef union _haint_t { + u32 d32; + struct { + u32 channel_intr_0:1; + u32 channel_intr_1:1; + u32 channel_intr_2:1; + u32 channel_intr_3:1; + u32 channel_intr_4:1; + u32 channel_intr_5:1; + u32 channel_intr_6:1; + u32 channel_intr_7:1; + u32 channel_intr_8:1; + u32 channel_intr_9:1; + u32 channel_intr_10:1; + u32 channel_intr_11:1; + u32 channel_intr_12:1; + u32 channel_intr_13:1; + u32 channel_intr_14:1; + u32 channel_intr_15:1; + u32 reserved1:16; + } b; +} haint_t; + + +/* Host Port Control and Status Register + * d250 + */ +typedef union _hprt_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned prtconnsts:1; + unsigned prtconndet:1; + unsigned prtena:1; + unsigned prtenchng:1; + unsigned prtovrcurract:1; + unsigned prtovrcurrchng:1; + unsigned prtres:1; + unsigned prtsusp:1; + unsigned prtrst:1; + unsigned reserved9:1; + unsigned prtlnsts:2; + unsigned prtpwr:1; + unsigned prttstctl:4; + unsigned prtspd:2; +#define HPRT0_PRTSPD_HIGH_SPEED 0 +#define HPRT0_PRTSPD_FULL_SPEED 1 +#define HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + } b; +} hprt_t; + + +/* Port status for the HC */ +#define HCD_DRIVE_RESET 0x0001 +#define HCD_SEND_SETUP 0x0002 + +#define HC_MAX_PKT_COUNT 511 +#define HC_MAX_TRANSFER_SIZE 65535 +#define MAXP_SIZE_64BYTE 64 +#define MAXP_SIZE_512BYTE 512 +#define MAXP_SIZE_1024BYTE 1024 + +/* Host Channel-n Charracteristics Register + * d253 + */ +typedef union _hcchar_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + /* Maximum packet size in bytes */ + unsigned mps:11; + + /* Endpoint number */ + unsigned epnum:4; + + /* 0: OUT, 1: IN */ + unsigned epdir:1; +#define HCDIR_OUT 0 +#define HCDIR_IN 1 + + unsigned reserved:1; + + /* 0: Full/high speed device, 1: Low speed device */ + unsigned lspddev:1; + + /* 0: Control, 1: Isoc, 2: Bulk, 3: Intr */ + unsigned eptype:2; +#define OTG_EP_TYPE_CONTROL 0 +#define OTG_EP_TYPE_ISOC 1 +#define OTG_EP_TYPE_BULK 2 +#define OTG_EP_TYPE_INTR 3 + + /* Packets per frame for periodic transfers. 0 is reserved. */ + unsigned multicnt:2; + + /* Device address */ + unsigned devaddr:7; + + /* Frame to transmit periodic transaction. */ + /* 0: even, 1: odd */ + unsigned oddfrm:1; + + /* Channel disable */ + unsigned chdis:1; + + /* Channel enable */ + unsigned chen:1; + } b; +} hcchar_t; + +/* Host Channel-n Transfer Size Register + * d257 + */ +typedef union _hctsiz_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + /* Total transfer size in bytes */ + unsigned xfersize:19; + + /* Data packets to transfer */ + unsigned pktcnt:10; + + /* Packet ID for next data packet */ + /* 0: DATA0 */ + /* 1: DATA2 */ + /* 2: DATA1 */ + /* 3: MDATA (non-Control), SETUP (Control) */ + unsigned pid:2; +#define HCTSIZ_DATA0 0 +#define HCTSIZ_DATA1 2 +#define HCTSIZ_DATA2 1 +#define HCTSIZ_MDATA 3 +#define HCTSIZ_SETUP 3 + + /* Do PING protocol when 1 */ + unsigned dopng:1; + } b; +} hctsiz_t; + + +/* The Power and Clock Gating Control Register + d292 + */ +typedef union _pcgcctl_t { + + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned stoppclk:1; + unsigned gatehclk:1; + unsigned pwrclmp:1; + unsigned rstpdwnmodule:1; + unsigned physuspended:1; + unsigned Reserved5_31:27; + } b; +} pcgcctl_t; + + +#endif + + diff --git a/drivers/usb/host/shost/shost_roothub.c b/drivers/usb/host/shost/shost_roothub.c new file mode 100644 index 0000000..3e2541d --- /dev/null +++ b/drivers/usb/host/shost/shost_roothub.c @@ -0,0 +1,579 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : RootHub.c + * [Description] : The file implement the external + * and internal functions of RootHub + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and implements functions of RootHub + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +/** + * void bus_suspend(void) + * + * @brief Make suspend status when this platform support PM Mode + * + * @param None + * + * @return None + * + * @remark + * + */ +static void bus_suspend(void) +{ + hprt_t hprt; + pcgcctl_t pcgcctl; + + otg_dbg(OTG_DBG_ROOTHUB, " bus_suspend\n"); + + hprt.d32 = 0; + pcgcctl.d32 = 0; + + hprt.b.prtsusp = 1; + update_reg_32(HPRT, hprt.d32); + + pcgcctl.b.pwrclmp = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); + + pcgcctl.b.stoppclk = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); +} + +/** + * int bus_resume(struct sec_otghost *otghost) + * + * @brief Make resume status when this platform support PM Mode + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static int bus_resume(struct sec_otghost *otghost) +{ + /* + hprt_t hprt; + pcgcctl_t pcgcctl; + hprt.d32 = 0; + pcgcctl.d32 = 0; + + pcgcctl.b.stoppclk = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.pwrclmp = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + hprt.b.prtres = 1; + update_reg_32(HPRT, hprt.d32); + mdelay(20); + + clear_reg_32(HPRT, hprt.d32); + */ + otg_dbg(OTG_DBG_ROOTHUB, "bus_resume()......\n"); + otg_dbg(OTG_DBG_ROOTHUB, "wait for 50 ms...\n"); + + mdelay(50); + + if (oci_init(otghost) == USB_ERR_SUCCESS) { + if (oci_start() == USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_ROOTHUB, "OTG Init Success\n"); + return USB_ERR_SUCCESS; + } + } + + return USB_ERR_FAIL; +} + +static void setPortPower(bool on) +{ +#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS + hprt_t hprt = {.d32 = 0}; + + if (on) { + hprt.d32 = read_reg_32(HPRT); + + if (!hprt.b.prtpwr) { + /*hprt.d32 = 0;*/ + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + } + } else { + hprt.b.prtpwr = 1; + clear_reg_32(HPRT, hprt.d32); + } +#else + otg_dbg(true, "turn %s Vbus\n", on ? "on" : "off"); +#endif +} + +/** + * int get_otg_port_status(const u8 port, char* status) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [OUT] status : buffer to store bitmap information + * + * @returnUSB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static inline int get_otg_port_status( + struct usb_hcd *hcd, const u8 port, char *status) +{ + + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + /* return root_hub_feature(port, GetPortStatus, NULL, status); */ +#if 0 + /* for debug */ + hprt_t hprt; + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + "HPRT:spd=%d,pwr=%d,lnsts=%d,rst=%d,susp=%d,res=%d," + "ovrcurract=%d,ena=%d,connsts=%d\n", + hprt.b.prtspd, + hprt.b.prtpwr, + hprt.b.prtlnsts, + hprt.b.prtrst, + hprt.b.prtsusp, + hprt.b.prtres, + hprt.b.prtovrcurract, + hprt.b.prtena, + hprt.b.prtconnsts); + +#endif + status[port] = 0; + status[port] |= (otghost->port_flag.b.port_connect_status_change || + otghost->port_flag.b.port_reset_change || + otghost->port_flag.b.port_enable_change || + otghost->port_flag.b.port_suspend_change || + otghost->port_flag.b.port_over_current_change) << 1; + +#if 0 + /* for debug */ + otg_dbg(OTG_DBG_ROOTHUB, + "connect:%d,reset:%d,enable:%d,suspend:%d,over_current:%d\n", + otghost->port_flag.b.port_connect_status_change, + otghost->port_flag.b.port_reset_change, + otghost->port_flag.b.port_enable_change, + otghost->port_flag.b.port_suspend_change, + otghost->port_flag.b.port_over_current_change); +#endif + + if (status[port]) { + otg_dbg(OTG_DBG_ROOTHUB, + " Root port status changed\n"); + otg_dbg(OTG_DBG_ROOTHUB, + " port_connect_status_change: %d\n", + otghost->port_flag.b.port_connect_status_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_reset_change: %d\n", + otghost->port_flag.b.port_reset_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_enable_change: %d\n", + otghost->port_flag.b.port_enable_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_suspend_change: %d\n", + otghost->port_flag.b.port_suspend_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_over_current_change: %d\n", + otghost->port_flag.b.port_over_current_change); + } + + return (status[port] != 0); +} + +/** + * int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) + * + * @brief Reset port and make enable status the specific port + * + * @param [IN] port : port number + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) +{ + hprt_t hprt; + u32 count = 0; + u32 max_error_count = 10000; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + " reset_and_enable_port\n"); + + if (hprt.b.prtconnsts == 0) { + otg_dbg(OTG_DBG_ROOTHUB, + "No Attached Device, HPRT = 0x%x\n", hprt.d32); + + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + + return USB_ERR_FAIL; + } + + if (!hprt.b.prtena) { + hprt.b.prtrst = 1; /* drive reset */ + write_reg_32(HPRT, hprt.d32); + + mdelay(60); + hprt.b.prtrst = 0; + write_reg_32(HPRT, hprt.d32); + + do { + hprt.d32 = read_reg_32(HPRT); + + if (count > max_error_count) { + otg_dbg(OTG_DBG_ROOTHUB, + "Port Reset Fail : HPRT : 0x%x\n", hprt.d32); + return USB_ERR_FAIL; + } + count++; + + } while (!hprt.b.prtena); + + } + return USB_ERR_SUCCESS; +} + +/** + * int root_hub_feature( + * struct usb_hcd *hcd, + * const u8 port, + * const u16 type_req, + * const u16 feature, + * void* buf) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [IN] type_req : request type of hub feature as usb 2.0 spec + * [IN] feature : hub feature as usb 2.0 spec + * [OUT] status : buffer to store results + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static inline int root_hub_feature( + struct usb_hcd *hcd, + const u8 port, + const u16 type_req, + const u16 feature, + void *buf) +{ + int retval = USB_ERR_SUCCESS; + struct hub_descriptor *desc = NULL; + u32 port_status = 0; + hprt_t hprt = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_ROOTHUB, " root_hub_feature\n"); + + switch (type_req) { + case ClearHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearHubFeature\n"); + switch (feature) { + case C_HUB_LOCAL_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_LOCAL_POWER\n"); + break; + case C_HUB_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_OVER_CURRENT\n"); + /* Nothing required here */ + break; + default: + retval = USB_ERR_FAIL; + } + break; + + case ClearPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearPortFeature\n"); + switch (feature) { + case USB_PORT_FEAT_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_ENABLE\n"); + hprt.b.prtena = 1; + update_reg_32(HPRT, hprt.d32); + break; + + case USB_PORT_FEAT_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_resume(otghost); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(false); + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_INDICATOR\n"); + /* Port inidicator not supported */ + break; + + case USB_PORT_FEAT_C_CONNECTION: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_CONNECTION\n"); + /* Clears drivers internal connect status change flag */ + otghost->port_flag.b.port_connect_status_change = 0; + break; + + case USB_PORT_FEAT_C_RESET: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_RESET\n"); + /* Clears the driver's internal Port Reset Change flag*/ + otghost->port_flag.b.port_reset_change = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_ENABLE\n"); + /* Clears Port Enable/Disable Change flag */ + otghost->port_flag.b.port_enable_change = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_SUSPEND\n"); + /* Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete */ + otghost->port_flag.b.port_suspend_change = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature - USB_PORT_FEAT_C_OVER_CURRENT\n"); + otghost->port_flag.b.port_over_current_change = 0; + break; + + default: + retval = USB_ERR_FAIL; + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature - FAIL\n"); + } + break; + + case GetHubDescriptor: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubDescriptor\n"); + desc = (struct hub_descriptor *)buf; + desc->desc_length = 9; + desc->desc_type = 0x29; + desc->port_number = 1; + desc->hub_characteristics = 0x08; + desc->power_on_to_power_good = 1; + desc->hub_control_current = 0; + desc->DeviceRemovable[0] = 0; + desc->DeviceRemovable[1] = 0xff; + break; + + case GetHubStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubStatus\n"); + otg_mem_set(buf, 0, 4); + break; + + case GetPortStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetPortStatus\n"); + + if (otghost->port_flag.b.port_connect_status_change) { + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_CONNECTION\n"); + } + + if (otghost->port_flag.b.port_enable_change) { + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_ENABLE\n"); + } + + if (otghost->port_flag.b.port_suspend_change) { + port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_SUSPEND\n"); + } + + if (otghost->port_flag.b.port_reset_change) { + port_status |= (1 << USB_PORT_FEAT_C_RESET); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_RESET\n"); + } + + if (otghost->port_flag.b.port_over_current_change) { + port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_OVER_CURRENT\n"); + } + + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - disconnected\n"); + + *((__le32 *)buf) = cpu_to_le32(port_status); + /*break;*/ + } + + hprt.d32 = read_reg_32(HPRT); + + if (hprt.b.prtconnsts) + port_status |= (1 << USB_PORT_FEAT_CONNECTION); + + if (hprt.b.prtena) + port_status |= (1 << USB_PORT_FEAT_ENABLE); + + if (hprt.b.prtsusp) + port_status |= (1 << USB_PORT_FEAT_SUSPEND); + + if (hprt.b.prtovrcurract) + port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); + + if (hprt.b.prtrst) + port_status |= (1 << USB_PORT_FEAT_RESET); + + if (hprt.b.prtpwr) + port_status |= (1 << USB_PORT_FEAT_POWER); + + if (hprt.b.prtspd == 0) { + port_status |= USB_PORT_STAT_HIGH_SPEED; + } else { + if (hprt.b.prtspd == 2) + port_status |= USB_PORT_STAT_LOW_SPEED; + } + + if (hprt.b.prttstctl) + port_status |= (1 << USB_PORT_FEAT_TEST); + + *((__le32 *)buf) = cpu_to_le32(port_status); + + otg_dbg(OTG_DBG_ROOTHUB, "port_status=0x%x.\n", port_status); + break; + + case SetHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetHubFeature\n"); + /* No HUB features supported */ + break; + + case SetPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetPortFeature\n"); + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case SetPortFeature - disconnected\n"); + + /*break;*/ + } + + switch (feature) { + case USB_PORT_FEAT_SUSPEND: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_suspend(); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(true); + break; + + case USB_PORT_FEAT_RESET: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_RESET\n"); + retval = reset_and_enable_port(hcd, port); + /* + if(retval == USB_ERR_SUCCESS) + wake_lock(&otghost->wake_lock); + */ + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_INDICATOR\n"); + break; + + default: + otg_dbg(true, "case SetPortFeature -USB_ERR_FAIL\n"); + retval = USB_ERR_FAIL; + break; + } + break; + + default: + retval = USB_ERR_FAIL; + otg_err(true, "root_hub_feature() Function Error\n"); + break; + } + + if (retval != USB_ERR_SUCCESS) + retval = USB_ERR_FAIL; + + return retval; +} + diff --git a/drivers/usb/host/shost/shost_scheduler.c b/drivers/usb/host/shost/shost_scheduler.c new file mode 100644 index 0000000..1739298 --- /dev/null +++ b/drivers/usb/host/shost/shost_scheduler.c @@ -0,0 +1,420 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal + * functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/2/10 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +****************************************************************************/ + +#include "shost.h" + +#include "shost_readyq.c" +#include "shost_schedulerlib.c" + +void init_scheduler(void) +{ + /*init_scheduling();*/ + init_transfer_ready_q(); +} + +/******************************************************************************/ +/*! + * @name int reserve_used_resource_for_periodic(u32 usb_time) + * + * @brief this function reserves the necessary resource of USB Transfer + * for Periodic Transfer. + * So, this function firstly checks there ares some available USB Time + * and Channel resource for USB Transfer. + * if there exists necessary resources for Periodic Transfer, + * then reserves the resource. + * + * @param [IN] usb_time + * - indicates the USB Time for the USB Transfer. + * + * @return USB_ERR_SUCCESS - if success to insert pInsertED to S3CScheduler. + * USB_ERR_NO_BANDWIDTH - if fail to reserve the USB Bandwidth. + * USB_ERR_NO_CHANNEL - if fail to reserve the Channel. + */ +/******************************************************************************/ + +int reserve_used_resource_for_periodic(u32 usb_time, + u8 dev_speed, u8 trans_type) +{ + if (inc_perio_bus_time(usb_time, dev_speed) == USB_ERR_SUCCESS) { + + if (inc_perio_chnum() == USB_ERR_SUCCESS) { + + otg_usbcore_inc_usb_bandwidth(usb_time); + otg_usbcore_inc_periodic_transfer_cnt(trans_type); + + return USB_ERR_SUCCESS; + + } else { + dec_perio_bus_time(usb_time); + return USB_ERR_NO_CHANNEL; + } + + } else + return USB_ERR_NO_BANDWIDTH; +} + +/******************************************************************************/ +/*! + * @name int free_usb_resource_for_periodic(ed_t *pFreeED) + * + * @brief this function frees the resources to be allocated + * to pFreeED at S3CScheduler. + * that is, this functions only releases the resources + * to be allocated by S3C6400Scheduler. + * + * @param [IN] pFreeED + * - indicates ed_t to have the information of the resource to be released. + * + * @return USB_ERR_SUCCESS - if success to free the USB Resource. + * USB_ERR_FAIL - if fail to free the USB Resrouce. + */ +/******************************************************************************/ +int free_usb_resource_for_periodic( + u32 free_usb_time, u8 free_chnum, u8 trans_type) +{ + if (dec_perio_bus_time(free_usb_time) == USB_ERR_SUCCESS) { + + if (dec_perio_chnum() == USB_ERR_SUCCESS) { + + if (free_chnum != CH_NONE) { + oci_channel_dealloc(free_chnum); + set_transferring_td_array(free_chnum, 0); + } + + otg_usbcore_des_usb_bandwidth(free_usb_time); + otg_usbcore_des_periodic_transfer_cnt(trans_type); + + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int remove_ed_from_scheduler(ed_t *remove_ed) + * + * @brief this function just remove the remove_ed from TransferReadyQ. + * So if you want to stop the USB Tranfer of remove_ed + * or release the releated resources. + * you should call another functions of S3CScheduler. + * + * @param [IN] remove_ed + * - indicates ed_t to be removed from TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if success to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAIL -if fail to remove the remove_ed from TransferReadyQ. + */ +/******************************************************************************/ +int remove_ed_from_scheduler(struct ed *remove_ed) +{ + if (remove_ed->ed_status.is_in_transfer_ready_q) { + remove_ed_from_ready_q(remove_ed); + remove_ed->ed_status.is_in_transfer_ready_q = false; + + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int cancel_to_transfer_td(struct sec_otghost *otghost, + * struct td *cancel_td) + * + * @brief this function stop to execute the USB Transfer of cancel_td and + * release the Channel Resources to be allocated the cancel_td , + * if the Transfer Type of cancel_td is NonPeriodic Transfer. + * this function don't release any usb resources(Channel, USB Bandwidth) + * for Periodic Transfer. + * if you want to release some usb resources for a periodic Transfer, + * you should call the free_usb_resource_for_periodic() + * + * @param [IN] cancel_td = indicates the struct td to be canceled. + * + * @return + * USB_ERR_SUCCESS-if success to cancel the USB Transfer of cancel_td. + * USB_ERR_FAIL -if fail to cancel the USB Transfer of cancel_td. + */ +/******************************************************************************/ +int cancel_to_transfer_td(struct sec_otghost *otghost, struct td *cancel_td) +{ + if (cancel_td->is_transfer_done) + return USB_ERR_FAIL; + + if (cancel_td->is_transferring) { + int err; + err = oci_stop_transfer(otghost, cancel_td->cur_stransfer. + alloc_chnum); + + if (err == USB_ERR_SUCCESS) { + + set_transferring_td_array(cancel_td->cur_stransfer. + alloc_chnum, 0); + + cancel_td->cur_stransfer.alloc_chnum = CH_NONE; + cancel_td->is_transferring = false; + cancel_td->parent_ed_p->ed_status.is_in_transferring = + false; + cancel_td->parent_ed_p->ed_status.in_transferring_td = + 0; + cancel_td->parent_ed_p->is_need_to_insert_scheduler = + true; + + if (cancel_td->cur_stransfer.ed_desc_p-> + endpoint_type == BULK_TRANSFER || + cancel_td->cur_stransfer.ed_desc_p-> + endpoint_type == CONTROL_TRANSFER) { + + dec_nonperio_chnum(); + } + return err; + } else + return err; + } else + return USB_ERR_FAIL; + + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td) + * + * @brief this function retransmits the retrasmit_td immediately. + * So, the Channel of pRetransmitted is reused for retransmittion. + * + * @param [IN] retrasmit_td + * - indicates the pointer ot the struct td to be retransmitted. + * + * @return USB_ERR_SUCCESS - if success to retransmit the retrasmit_td. + * USB_ERR_FAIL - if fail to retransmit the retrasmit_td. + */ +/******************************************************************************/ +int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td) +{ + u32 td_addr = 0; + + if (get_transferring_td_array(retrasmit_td->cur_stransfer. + alloc_chnum, &td_addr) == USB_ERR_SUCCESS) { + + if (td_addr == (u32)retrasmit_td) { + + if (oci_start_transfer(otghost, + &retrasmit_td->cur_stransfer) == + retrasmit_td->cur_stransfer.alloc_chnum) { + + retrasmit_td->is_transferring = true; + retrasmit_td->parent_ed_p->ed_status. + in_transferring_td = (u32)retrasmit_td; + retrasmit_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = false; + retrasmit_td->parent_ed_p->ed_status. + is_in_transferring = true; + } + + } else + return USB_ERR_FAIL; + } else + return USB_ERR_FAIL; + + return USB_ERR_SUCCESS; + +} + +/******************************************************************************/ +/*! + * @name int reschedule(struct td *reschedule_td) + * + * @brief this function re-schedules the reschedule_td. + * So, the Channel of pRescheuleTD is released + * and reschedule_td is inserted to TransferReadyQ. + * + * @param [IN] reschedule_td + * - indicates the pointer ot the struct td to be rescheduled. + * + * @return USB_ERR_SUCCESS -if success to re-schedule the reschedule_td. + * USB_ERR_FAIL -if fail to re-schedule the reschedule_td. + */ +/******************************************************************************/ +int reschedule(struct td *reschedule_td) +{ + u32 td_addr; + + if (get_transferring_td_array(reschedule_td->cur_stransfer. + alloc_chnum, &td_addr) == USB_ERR_SUCCESS) { + if ((u32)reschedule_td == td_addr) { + set_transferring_td_array(reschedule_td->cur_stransfer. + alloc_chnum, 0); + oci_channel_dealloc(reschedule_td->cur_stransfer. + alloc_chnum); + + reschedule_td->cur_stransfer.alloc_chnum = CH_NONE; + reschedule_td->parent_ed_p-> + is_need_to_insert_scheduler = true; + reschedule_td->parent_ed_p->ed_status. + in_transferring_td = 0; + + if (reschedule_td->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER || + reschedule_td->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER) { + /* Increase the available Channel */ + dec_nonperio_chnum(); + + } + + insert_ed_to_ready_q(reschedule_td->parent_ed_p, false); + reschedule_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = true; + + } + /* this case is not support.... + else { + } + */ + } + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int deallocate(struct td *deallocate_td) + * + * @brief this function frees resources to be allocated deallocate_td + * by S3CScheduler. + * this function just free the resource by S3CScheduler. + * that is, Channel Resource. + * if there are another struct td at ed_t, + * deallocate() insert the ed_t to TransferReadyQ. + * + * @param [IN] deallocate_td + * - indicates the pointer ot the struct td to be deallocated. + * + * @return + * USB_ERR_SUCCESS -if success to dealloate the resources for the deallocate_td. + * USB_ERR_FAIL -if fail to dealloate the resources for the deallocate_td. + */ +/******************************************************************************/ +int deallocate(struct td *deallocate_td) +{ + u32 td_addr; + + if (get_transferring_td_array(deallocate_td->cur_stransfer.alloc_chnum, + &td_addr) == USB_ERR_SUCCESS) { + if ((u32)deallocate_td == td_addr) { + + set_transferring_td_array(deallocate_td-> + cur_stransfer.alloc_chnum, 0); + + oci_channel_dealloc(deallocate_td->cur_stransfer. + alloc_chnum); + + deallocate_td->cur_stransfer.alloc_chnum = CH_NONE; + + if (deallocate_td->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER || + deallocate_td->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER) { + /* Increase the available Channel */ + dec_nonperio_chnum(); + } + + deallocate_td->parent_ed_p->is_need_to_insert_scheduler + = true; + + if (deallocate_td->parent_ed_p->num_td) { + /* insert ed_t to TransferReadyQ. */ + insert_ed_to_ready_q(deallocate_td->parent_ed_p, + false); + deallocate_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = true; + deallocate_td->parent_ed_p-> + is_need_to_insert_scheduler = false; + } + return USB_ERR_SUCCESS; + + } else + return USB_ERR_FAIL; + } else + return USB_ERR_FAIL; + +} + +/* TBD.... */ +/* transferchecker-common.c + hcd.c */ +void do_schedule(struct sec_otghost *otghost) +{ + if (get_avail_chnum()) { + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + } +} + + +/******************************************************************************/ +/*! + * @name int get_td_info(u8 chnum, + * unsigned int *td_addr_p) + * + * @brief this function returns the pointer of + * struct td at TransferringTDArray[chnum] + * + * @param [IN] chnum + * - indicates the index of TransferringTDArray + * to include the address of struct td which we gets + * [OUT] td_addr_p + * - indicate pointer to store the address of struct td. + * + * @return + * USB_ERR_SUCCESS -if success to get the address of struct td. + * USB_ERR_FAIL-if fail to get the address of struct td. + */ +/******************************************************************************/ +/* transferchecker-common.c */ +int get_td_info(u8 chnum, unsigned int *td_addr_p) +{ + u32 td_addr; + + if (get_transferring_td_array(chnum, &td_addr) == USB_ERR_SUCCESS) { + *td_addr_p = td_addr; + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + + diff --git a/drivers/usb/host/shost/shost_scheduler.h b/drivers/usb/host/shost/shost_scheduler.h new file mode 100644 index 0000000..fed5035 --- /dev/null +++ b/drivers/usb/host/shost/shost_scheduler.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.h + * [Description] : The Header file defines the external + * and internal functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SCHEDULER_H +#define _SCHEDULER_H + +/* hcd.c */ +extern void init_scheduler(void); + +extern int reserve_used_resource_for_periodic(u32 usb_time, + u8 dev_speed, u8 trans_type); +extern int free_usb_resource_for_periodic(u32 free_usb_time, + u8 free_chnum, u8 trans_type); +extern int remove_ed_from_scheduler(struct ed *remove_ed); +extern int cancel_to_transfer_td(struct sec_otghost *otghost, + struct td *cancel_td); +extern int retransmit(struct sec_otghost *otghost, struct td *retransmit_td); +extern int reschedule(struct td *resched_td); +extern int deallocate(struct td *dealloc_td); +extern void do_schedule(struct sec_otghost *otghost); +extern int get_td_info(u8 chnum, unsigned int *td_addr); + +/* transfer-common.c */ +int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed); + +#endif + diff --git a/drivers/usb/host/shost/shost_schedulerlib.c b/drivers/usb/host/shost/shost_schedulerlib.c new file mode 100644 index 0000000..de17401 --- /dev/null +++ b/drivers/usb/host/shost/shost_schedulerlib.c @@ -0,0 +1,426 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal + * functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +/* the max periodic bus time is 80%*125us on High Speed Mode */ +static const u32 perio_highbustime_threshold = 100; + +/* the max periodic bus time is 90%*1000us(1ms) on Full/Low Speed Mode. */ +static const u32 perio_fullbustime_threshold = 900; + +static const u8 perio_chnum_threshold = 14; +/* static const u8 total_chnum_threshold = 16; */ +static u8 total_chnum_threshold = 16; + +static u32 perio_used_bustime; +static u8 perio_used_chnum; +static u8 nonperio_used_chnum; +static u8 total_used_chnum; +static u32 transferring_td_array[16]; + + +static int inc_perio_bus_time(u32 bus_time, u8 dev_speed) +{ + switch (dev_speed) { + + case HIGH_SPEED_OTG: + if ((bus_time + perio_used_bustime) <= + perio_highbustime_threshold) { + + perio_used_bustime += bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; + + case LOW_SPEED_OTG: + case FULL_SPEED_OTG: + if ((bus_time + perio_used_bustime) <= + perio_fullbustime_threshold) { + + perio_used_bustime += bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; + + case SUPER_SPEED_OTG: + break; + default: + break; + } + return USB_ERR_FAIL; +} + +static int dec_perio_bus_time(u32 bus_time) +{ + if (perio_used_bustime >= bus_time) { + perio_used_bustime -= bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +static int inc_perio_chnum(void) +{ + if (perio_used_chnum < perio_chnum_threshold) { + if (total_used_chnum < total_chnum_threshold) { + perio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static u8 get_avail_chnum(void) +{ + return total_chnum_threshold - total_used_chnum; +} + +static int dec_perio_chnum(void) +{ + if (perio_used_chnum > 0) { + if (total_used_chnum > 0) { + perio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int inc_non_perio_chnum(void) +{ + if (nonperio_used_chnum < total_chnum_threshold) { + if (total_used_chnum < total_chnum_threshold) { + nonperio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int dec_nonperio_chnum(void) +{ + if (nonperio_used_chnum > 0) { + if (total_used_chnum > 0) { + nonperio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int get_transferring_td_array(u8 chnum, unsigned int *td_addr) +{ + if (transferring_td_array[chnum] != 0) { + *td_addr = transferring_td_array[chnum]; + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +static int set_transferring_td_array(u8 chnum, u32 td_addr) +{ + if (td_addr == 0) { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } + + if (transferring_td_array[chnum] == 0) { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int do_periodic_schedule(struct sec_otghost *otghost) + * + * @brief this function schedules PeriodicTransferReadyQ. + * this function checks whether PeriodicTransferReadyQ has some ed_t. + * if there are some ed_t on PeriodicTransferReadyQ + * , this function request to start USB Trasnfer to S3C6400OCI. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +static void do_periodic_schedule(struct sec_otghost *otghost) +{ + struct ed *scheduling_ed = NULL; + int err_sched = USB_ERR_SUCCESS; + u32 sched_cnt = 0; + + otg_list_head *td_list_entry; + struct td *td; + u32 cur_frame_num = 0; + u8 alloc_ch; + + otg_dbg(OTG_DBG_SCHEDULE, "***** Start periodicSchedul *****\n"); + + sched_cnt = get_periodic_ready_q_entity_num(); + + while (sched_cnt) { + /* in periodic transfser, + the channel resource was already reserved. + So, we don't need this routine... */ + +start_sched_perio_transfer: + if (!sched_cnt) + goto end_sched_perio_transfer; + + otg_dbg(OTG_DBG_SCHEDULE, "sched_cnt : %d\n", sched_cnt); + + err_sched = get_ed_from_ready_q(&scheduling_ed, true); + + if (err_sched != USB_ERR_SUCCESS) { + /* there is no ED on PeriodicTransferQ. + So we finish scheduling.*/ + otg_dbg(OTG_DBG_SCHEDULE, "no ed\n"); + goto end_sched_perio_transfer; + } + + cur_frame_num = 0; + + otg_dbg(OTG_DBG_SCHEDULE, + "the %d ed to be scheduled\n", (int)scheduling_ed); + + sched_cnt--; + td_list_entry = scheduling_ed->td_list_entry.next; + + if (td_list_entry == &scheduling_ed->td_list_entry) { + /* scheduling_ed has no struct td. + so we schedules another ed on + PeriodicTransferReadyQ. */ + goto start_sched_perio_transfer; + } + + if (scheduling_ed->ed_status.is_in_transferring) { + /* scheduling_ed is already Scheduled. + so we schedules another ed on + PeriodicTransferReadyQ. */ + goto start_sched_perio_transfer; + } + + cur_frame_num = oci_get_frame_num(); + + if (((cur_frame_num - scheduling_ed->ed_desc.sched_frame) & + HFNUM_MAX_FRNUM) > (HFNUM_MAX_FRNUM >> 1)) { + + insert_ed_to_ready_q(scheduling_ed, false); + goto start_sched_perio_transfer; + } + + td = otg_list_get_node(td_list_entry, + struct td, td_list_entry); + + if ((td->is_transferring) || (td->is_transfer_done)) { + /* the selected struct td was already transferring + or completed to transfer. + we should decide how to control this case.*/ + goto end_sched_perio_transfer; + } + + otg_dbg(OTG_DBG_SCHEDULE, + "the struct td to be scheduled :%d", (int)td); + + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + + if (alloc_ch < total_chnum_threshold) { + + td->cur_stransfer.alloc_chnum = alloc_ch; + transferring_td_array[alloc_ch] = (u32)td; + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td = (u32)td; + + td->is_transferring = true; + + } else { + /* we should insert the ed_t to TransferReadyQ, + because the USB Transfer of current ed is failed.*/ + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + scheduling_ed->ed_status.in_transferring_td = 0; + + insert_ed_to_ready_q(scheduling_ed, true); + + scheduling_ed->is_need_to_insert_scheduler = false; + goto end_sched_perio_transfer; + } + + } + +end_sched_perio_transfer: + + return; +} + + +/******************************************************************************/ +/*! + * @name int do_nonperiodic_schedule(struct sec_otghost *otghost) + * + * @brief this function start to schedule thie NonPeriodicTransferReadyQ. + * + * this function checks whether NonPeriodicTransferReadyQ has some ed. + * if there are some ed on NonPeriodicTransferReadyQ, + * this function request to start USB Trasnfer to S3C6400OCI. + * + * @param void + * @return void + */ +/******************************************************************************/ +static void do_nonperiodic_schedule(struct sec_otghost *otghost) +{ + struct ed *scheduling_ed; + int err_sched; + + otg_list_head *td_list_entry; + struct td *td; + u8 alloc_ch; + + if (total_used_chnum >= total_chnum_threshold) { + pr_info("shost nonperiodic: used_chnum >= total_chnum\n"); + return; + } + + while (1) { + +start_nonperiodic: + /* check there is available channel resource + for Non-Periodic Transfer. */ + if (total_used_chnum == total_chnum_threshold) { + pr_info("shost nonperiodic: used_chnum == total_chnum\n"); + goto end_nonperiodic; + } + + err_sched = get_ed_from_ready_q(&scheduling_ed, false); + + if (err_sched != USB_ERR_SUCCESS) { + /* pr_info("shost: non: nothing to schedule\n"); */ + goto end_nonperiodic; + } + + td_list_entry = scheduling_ed->td_list_entry.next; + + if (otg_list_empty(&scheduling_ed->td_list_entry)) { + /* scheduling_ed has no td. + so we schedules another ed + on PeriodicTransferReadyQ.*/ + goto start_nonperiodic; + } + + if (scheduling_ed->ed_status.is_in_transferring) { + /* scheduling_ed is already Scheduled. + so we schedules another ed + on PeriodicTransferReadyQ.*/ + goto start_nonperiodic; + } + + td = otg_list_get_node(td_list_entry, struct td, td_list_entry); + + if ((td->is_transferring) || (td->is_transfer_done)) { + pr_info("shost nonperiodic: td is busy\n"); + goto end_nonperiodic; + } + + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + + if (alloc_ch < total_chnum_threshold) { + + td->cur_stransfer.alloc_chnum = alloc_ch; + transferring_td_array[alloc_ch] = (u32)td; + + inc_non_perio_chnum(); + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td = (u32)td; + td->is_transferring = true; + + } else { + /* we must insert the ed to queue, + because the USB Transfer of the ed is failed.*/ + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.in_transferring_td = 0; + insert_ed_to_ready_q(scheduling_ed, true); + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + + goto end_nonperiodic; + } + } /* while */ + +end_nonperiodic: + return; +} + +/******************************************************************************/ +/*! + * @name int insert_ed_to_scheduler(struct sec_otghost *otghost, + * ed_t *insert_ed) + * + * @brief this function transfers the insert_ed to S3C6400Scheduler, + * and after that, the insert_ed is inserted to TransferReadyQ + * and scheduled by Scheduler. + * + * @param [IN] insert_ed + * = indicates pointer of ed_t to be inserted to TransferReadyQ. + * + * @return + * USB_ERR_ALREADY_EXIST - if the insert_ed is already existed. + * USB_ERR_SUCCESS - if success to insert insert_ed to S3CScheduler. + */ +/******************************************************************************/ +int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed) +{ + if (!insert_ed->is_need_to_insert_scheduler) + return USB_ERR_ALREADY_EXIST; + + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td %d\n", + insert_ed->ed_id, insert_ed->num_td); + + insert_ed_to_ready_q(insert_ed, false); + insert_ed->is_need_to_insert_scheduler = false; + insert_ed->ed_status.is_in_transfer_ready_q = true; + + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + + return USB_ERR_SUCCESS; +} + diff --git a/drivers/usb/host/shost/shost_struct.h b/drivers/usb/host/shost/shost_struct.h new file mode 100644 index 0000000..638c6f2 --- /dev/null +++ b/drivers/usb/host/shost/shost_struct.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-datastruct.h + * [Description] : The Header file defines Data Structures + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines Data Structure to be managed by Transfer. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - modifying ED structure + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _DATA_STRUCT_DEF_H +#define _DATA_STRUCT_DEF_H + + +struct isoch_packet_desc { + /* start address of buffer is buffer address + uiOffsert.*/ + u32 isoch_packiet_start_addr; + u32 buf_size; + u32 transferred_szie; + u32 isoch_status; +}; + +struct standard_dev_req_info { + bool is_data_stage; + u8 conrol_transfer_stage; + u32 vir_standard_dev_req_addr; + u32 phy_standard_dev_req_addr; +}; + +struct control_data_toggle { + u8 setup; + u8 data; + u8 status; +}; + +struct ed_status { + struct control_data_toggle control_data_toggle; + u8 data_toggle; + bool is_ping_enable; + bool is_in_transfer_ready_q; + bool is_in_transferring; + u32 in_transferring_td; + bool is_alloc_resource_for_ed; +}; + +struct ed_desc { + u8 device_addr; + u8 endpoint_num; + bool is_ep_in; + u8 dev_speed; + u8 endpoint_type; + u16 max_packet_size; + u8 mc; + u8 interval; + u32 sched_frame; + u32 used_bus_time; + u8 hub_addr; + u8 hub_port; + bool is_do_split; +}; + +struct hc_reg { + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + +}; + +struct stransfer { + u32 stransfer_id; + u32 parent_td; + struct ed_desc *ed_desc_p; + struct ed_status *ed_status_p; + u32 vir_addr; + u32 phy_addr; + u32 buf_size; + u32 packet_cnt; + u8 alloc_chnum; + struct hc_reg hc_reg; +}; + +struct ed { + u32 ed_id; + bool is_halted; + bool is_need_to_insert_scheduler; + struct ed_desc ed_desc; + struct ed_status ed_status; + otg_list_head ed_list_entry; + otg_list_head td_list_entry; + otg_list_head readyq_list; + u32 num_td; + void *ed_private; +}; + +struct td { + u32 td_id; + struct ed *parent_ed_p; + void *call_back_func_p; + void *call_back_func_param_p; + bool is_transferring; + bool is_transfer_done; + u32 transferred_szie; + bool is_standard_dev_req; + + struct standard_dev_req_info standard_dev_req_info; + u32 vir_buf_addr; + u32 phy_buf_addr; + u32 buf_size; + u32 transfer_flag; + + struct stransfer cur_stransfer; + u32 error_code; + u32 err_cnt; + otg_list_head td_list_entry; + + /* Isochronous Transfer Specific */ + u32 isoch_packet_num; + struct isoch_packet_desc *isoch_packet_desc_p; + u32 isoch_packet_index; + u32 isoch_packet_position; + u32 sched_frame; + u32 interval; + u32 used_total_bus_time; + + /* the private data can be used by S3C6400Interface. */ + void *td_private; +}; + +struct trans_ready_q { + bool is_periodic; + otg_list_head entity_list; + u32 entity_num; + + /* In case of Periodic Transfer */ + u32 total_perio_bus_bandwidth; + u8 total_alloc_chnum; +}; + +struct hc_info { + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + hcchar_t hc_char; + hctsiz_t hc_size; +}; + +#ifndef USB_MAXCHILDREN + #define USB_MAXCHILDREN (31) +#endif + +/* TODO: use usb_hub_descriptor + * <linux/usb/ch11.h> + */ +struct hub_descriptor { + u8 desc_length; + u8 desc_type; + u8 port_number; + u16 hub_characteristics; + u8 power_on_to_power_good; + u8 hub_control_current; + + /* add 1 bit for hub status change; round to bytes */ + u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8]; + u8 port_pwr_ctrl_mask[(USB_MAXCHILDREN + 1 + 7) / 8]; +} __packed; + +#endif diff --git a/drivers/usb/host/shost/shost_transfer.c b/drivers/usb/host/shost/shost_transfer.c new file mode 100644 index 0000000..122ce65 --- /dev/null +++ b/drivers/usb/host/shost/shost_transfer.c @@ -0,0 +1,1025 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Commons3c-otg-transfer-transfer.h + * [Description] : This source file implements the functions + * to be defined at CommonTransfer Module. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements some functions of CommonTransfer. + * (2) 2008/07/15 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - Optimizing for performance + * (3) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - Modifying for successful rmmod & disconnecting + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include "shost_transfer.h" + +/* the header pointer to indicate the ED_list + * to manage the struct ed to be created and initiated. + */ +static otg_list_head ed_list_head; +static u32 ref_periodic_transfer; + +/** + * void enable_sof(void) + * + * @brief Generate SOF Interrupt. + * + * @param None + * + * @return None + * + * @remark + * + */ +static void enable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); +} + +/** + * void disable_sof(void) + * + * @brief Stop to generage SOF interrupt + * + * @param None + * + * @return None + * + * @remark + * + */ +static void disable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + clear_reg_32(GINTMSK, gintmsk.d32); +} + + + +/******************************************************************************/ +/*! + * @name int cancel_transfer(struct sec_otghost *otghost, + * struct ed *parent_ed, + * struct td *cancel_td) + * + * @brief this function cancels to transfer USB Transfer of cancel_td. + * this function firstly check whether this cancel_td + * is transferring or not. if the cancel_td is transferring, the this + * function requests to cancel the USB Transfer + * to S3CScheduler. if the parent_ed is for Periodic Transfer, and + * there is not any struct td at parent_ed, then this function requests + * to release some usb resources for the struct ed to S3CScheduler. + * finally this function deletes the cancel_td. + * + * @param [IN] pUpdateTD = indicates the pointer ot the struct td + * to have STransfer to be updated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ +/******************************************************************************/ +static int cancel_transfer(struct sec_otghost *otghost, + struct ed *parent_ed, struct td *cancel_td) +{ + otg_list_head *tmp_list_p, *tmp_list2_p; + int err = USB_ERR_DEQUEUED; + bool cond_found = false; + + if (parent_ed == NULL || cancel_td == NULL) { + otg_err(1, "%s is null.\n", parent_ed ? + "cancel_td" : "parent_ed"); + return USB_ERR_NOELEMENT; + } + + otg_list_for_each_safe(tmp_list_p, tmp_list2_p, + &parent_ed->td_list_entry) { + + if (&cancel_td->td_list_entry == tmp_list_p) { + cond_found = true; + break; + } + } + + if (cond_found != true) { + otg_dbg(OTG_DBG_TRANSFER, "cond_found != true\n"); + cancel_td->error_code = USB_ERR_NOELEMENT; + otg_usbcore_giveback(cancel_td); + return cancel_td->error_code; + } + + + if (cancel_td->is_transferring) { + if (!parent_ed->ed_status.is_in_transfer_ready_q) { + err = cancel_to_transfer_td(otghost, cancel_td); + + parent_ed->ed_status.in_transferring_td = 0; + + if (err != USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_TRANSFER, + "cancel_to_transfer_td\n"); + cancel_td->error_code = err; + otg_usbcore_giveback(cancel_td); + goto ErrorStatus; + } + + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + } + + } else { + + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + + if (parent_ed->num_td == 0) { + remove_ed_from_scheduler(parent_ed); + parent_ed->is_need_to_insert_scheduler = true; + } + } + + if (parent_ed->num_td) { + parent_ed->is_need_to_insert_scheduler = true; + insert_ed_to_scheduler(otghost, parent_ed); + + } else { + + if (parent_ed->ed_desc.endpoint_type == INT_TRANSFER || + parent_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + + /* Release channel and usb bus resource for + * this struct ed. but, not release memory for this ed. + */ + free_usb_resource_for_periodic( + parent_ed->ed_desc.used_bus_time, + cancel_td->cur_stransfer.alloc_chnum, + cancel_td->parent_ed_p->ed_desc.endpoint_type); + + parent_ed->ed_status.is_alloc_resource_for_ed = false; + } + } + /* the caller of this functions should call + otg_usbcore_giveback(cancel_td); */ + cancel_td->error_code = USB_ERR_DEQUEUED; + otg_usbcore_giveback(cancel_td); + + /* TODO: recursive call occured. FIX */ + delete_td(otghost, cancel_td); + +ErrorStatus: + + return err; +} + + + + +/******************************************************************************/ +/*! + * @name int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed) + * + * @brief this function cancels all Transfer which parent_ed manages. + * + * @param [IN] parent_ed = indicates the pointer ot the struct ed + * to manage TD_ts to be canceled. + * + * @return + * USB_ERR_SUCCESS - if success to cancel all TD_ts of pParentsED. + * USB_ERR_FAIL - if fail to cancel all TD_ts of pParentsED. + */ +/******************************************************************************/ +static int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed) +{ + otg_list_head *cancel_td_list_entry; + struct td *cancel_td; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "cancel_all_td\n"); + do { + cancel_td_list_entry = parent_ed->td_list_entry.next; + + cancel_td = otg_list_get_node(cancel_td_list_entry, + struct td, td_list_entry); + + cancel_transfer(otghost, parent_ed, cancel_td); + + } while (parent_ed->num_td); + + return USB_ERR_SUCCESS; +} + + + + +/******************************************************************************/ +/*! + * @name int delete_ed(struct ed *delete_ed) + * + * @brief this function delete the delete_ed. + * if there is some available TD_ts on delete_ed, + * then this function also deletes these struct td + * + * @param [IN] delete_ed = indicates the address of struct ed to be deleted. + * + * @return USB_ERR_SUCCESS -if successes to delete the struct ed. + * USB_ERR_FAILl -if fails to delete the ed. + */ +/******************************************************************************/ +static int delete_ed(struct sec_otghost *otghost, struct ed *delete_ed) +{ + otg_kal_make_ep_null(delete_ed); + + if (delete_ed->num_td) { + cancel_all_td(otghost, delete_ed); + /** + * need to giveback of td's urb with considering life-cycle of + * TD, ED, urb->hcpriv, td->private, ep->hcpriv, td->parentED + * (commented by ss1.yang) + */ + } + + otg_list_pop(&delete_ed->ed_list_entry); + + if (delete_ed->ed_desc.endpoint_type == INT_TRANSFER || + delete_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + ref_periodic_transfer--; + } + + if (ref_periodic_transfer == 0) + disable_sof(); + + otg_mem_free(delete_ed); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name void init_transfer(void) + * + * @brief this function initiates the S3CTranfer module. + * that is, this functions initiates + * the ED_list_head OTG List which manages the all ed to be existed. + * + * @param void + * @return void + */ +/******************************************************************************/ + +static void init_transfer(void) +{ + otg_dbg(OTG_DBG_TRANSFER, "start to init_transfer\n"); + otg_list_init(&ed_list_head); + ref_periodic_transfer = 0; +} + + +/******************************************************************************/ +/*! + * @name void DeInitTransfer(void) + * + * @brief this function Deinitiates the S3CTranfer module. + * this functions check which there are + * some ed on ED_list_head. if some ed exists, + * deinit_transfer() deletes the ed. + * + * @param void + * @return void + */ +/******************************************************************************/ +static void deinit_transfer(struct sec_otghost *otghost) +{ + otg_list_head *ed_list_member; + struct ed *delete_ed_p; + + while (otg_list_empty(&ed_list_head) != true) { + + ed_list_member = ed_list_head.next; + + /* otg_list_pop(ed_list_member); */ + + delete_ed_p = otg_list_get_node(ed_list_member, + struct ed, ed_list_entry); + + delete_ed(otghost, delete_ed_p); + } +} + +/******************************************************************************/ +/*! + * @name int delete_td(struct sec_otghost *otghost, struct td *delete_td) + * + * @brief this function frees memory resource for the delete_td. + * and if delete_td is transferring USB Transfer, then + * this function request to cancel the USB Transfer to scheduler. + * + * + * @param [OUT] new_td_p = returns the address of the new struct td. + * + * @return USB_ERR_SUCCESS -if successes to create the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/******************************************************************************/ +int delete_td(struct sec_otghost *otghost, struct td *delete_td) +{ + if (delete_td->is_transferring) { + /* at this case, we should cancel the USB Transfer. */ + cancel_to_transfer_td(otghost, delete_td); + } + + otg_mem_free(delete_td); + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int create_ed(struct ed **new_ed) + * + * @brief this function creates a new ed and returns the ed to Caller + * + * @param [OUT] new_ed = returns the address of the new ed . + * + * @return USB_ERR_SUCCESS -if successes to create the new ed. + * USB_ERR_FAILl -if fails to create to new ed. + */ +/******************************************************************************/ +static int create_ed(struct ed **new_ed) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_ed, + (u16)sizeof(struct ed), USB_MEM_ASYNC); + otg_mem_set(*new_ed, 0, sizeof(struct ed)); + return err_code; +} + + +/******************************************************************************/ +/*! + * @name int init_ed(struct ed *init_ed, + * u8 dev_addr, + * u8 ep_num, + * bool f_is_ep_in, + * u8 dev_speed, + * u8 ep_type, + * u32 max_packet_size, + * u8 multi_count, + * u8 interval, + * u32 sched_frame, + * u8 hub_addr, + * u8 hub_port, + * bool f_is_do_split) + * + * @brief this function initiates the init_ed by using the another parameters. + * + * @param [OUT] init_ed = returns the struct ed to be initiated. + * [IN] dev_addr = inidcates the address of USB Device. + * [IN] ep_num = inidcates the number of the specific endpoint on USB Device. + * [IN] f_is_ep_in = inidcates whether the endpoint is IN or not + * [IN] dev_speed = inidcates the speed of USB Device. + * [IN] max_packet_size = inidcates the maximum packet size of a specific + * endpoint on USB Device. + * [IN] multi_count = if the endpoint supports periodic transfer + * , this indicates the multiple packet to be transferred on a uframe + * [IN] interval= if the endpoint support periodic transfer, this indicates + * the polling rate. + * [IN] sched_frame= if the endpoint supports periodic transfer, this indicates + * the start frame number. + * [IN] hub_addr= indicate the address of hub which the USB device attachs to. + * [IN] hub_port= inidcates the port number of the hub which the USB + * device attachs to. + * [IN] f_is_do_split= inidcates whether this tranfer is + * split transaction or not. + * + * @return USB_ERR_SUCCESS -if successes to initiate the ed. + * USB_ERR_FAILl -if fails to initiate the ed. + * USB_ERR_NOSPACE -if fails to initiate the ed + * because there is no USB Resource for this init_ed. + */ +/******************************************************************************/ +static int init_ed(struct ed *init_ed, + u8 dev_addr, + u8 ep_num, + bool f_is_ep_in, + u8 dev_speed, + u8 ep_type, + u16 max_packet_size, + u8 multi_count, + u8 interval, + u32 sched_frame, + u8 hub_addr, + u8 hub_port, + bool f_is_do_split, + void *ep) +{ + init_ed->is_halted = false; + init_ed->is_need_to_insert_scheduler = true; + init_ed->ed_id = (u32)init_ed; + init_ed->num_td = 0; + init_ed->ed_private = ep; + + otg_list_init(&init_ed->td_list_entry); + + /* start to initiate struct ed_desc.... */ + init_ed->ed_desc.is_do_split = f_is_do_split; + init_ed->ed_desc.is_ep_in = f_is_ep_in; + init_ed->ed_desc.dev_speed = dev_speed; + init_ed->ed_desc.hub_addr = hub_addr; + init_ed->ed_desc.hub_port = hub_port; + init_ed->ed_desc.mc = multi_count; + init_ed->ed_desc.device_addr = dev_addr; + init_ed->ed_desc.endpoint_num = ep_num; + init_ed->ed_desc.endpoint_type = ep_type; + init_ed->ed_desc.max_packet_size = max_packet_size; + init_ed->ed_desc.sched_frame = sched_frame; + + if (init_ed->ed_desc.endpoint_type == INT_TRANSFER) { + + if (init_ed->ed_desc.dev_speed == LOW_SPEED_OTG || + init_ed->ed_desc.dev_speed == FULL_SPEED_OTG) { + + init_ed->ed_desc.interval = interval; + + } else if (init_ed->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + u8 count = 0; + u8 cal_interval = 1; + + for (count = 0; + count < (init_ed->ed_desc.interval-1); count++) + cal_interval *= 2; + + init_ed->ed_desc.interval = cal_interval; + + } else { + otg_dbg(OTG_DBG_TRANSFER, + "Super-Speed is not supported\n"); + } + + init_ed->ed_desc.sched_frame = + (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + + if (init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + u8 count = 0; + u8 cal_interval = 1; + + for (count = 0; count < (init_ed->ed_desc.interval-1); count++) + cal_interval *= 2; + + init_ed->ed_desc.interval = cal_interval; + init_ed->ed_desc.sched_frame = + (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + + /* start to initiate struct ed_status.... */ + + /* initiates PID */ + switch (ep_type) { + case BULK_TRANSFER: + case INT_TRANSFER: + init_ed->ed_status.data_toggle = DATA0; + break; + + case CONTROL_TRANSFER: + init_ed->ed_status.control_data_toggle.setup = SETUP; + init_ed->ed_status.control_data_toggle.data = DATA1; + init_ed->ed_status.control_data_toggle.status = DATA1; + break; + + case ISOCH_TRANSFER: + if (f_is_ep_in) { + switch (multi_count) { + case MULTI_COUNT_ZERO: + init_ed->ed_status.data_toggle = DATA0; + break; + case MULTI_COUNT_ONE: + init_ed->ed_status.data_toggle = DATA1; + break; + case MULTI_COUNT_TWO: + init_ed->ed_status.data_toggle = DATA2; + break; + default: + break; + } + + } else { + switch (multi_count) { + case MULTI_COUNT_ZERO: + init_ed->ed_status.data_toggle = DATA0; + break; + case MULTI_COUNT_ONE: + init_ed->ed_status.data_toggle = MDATA; + break; + case MULTI_COUNT_TWO: + init_ed->ed_status.data_toggle = MDATA; + break; + default: + break; + } + } + break; + default: + break; + } + + if (init_ed->ed_desc.endpoint_type == INT_TRANSFER || + init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + + u32 usb_time = 0, byte_count = 0; + + /* calculates the bytes to be transferred + at one (uframe)frame.*/ + byte_count = (init_ed->ed_desc.mc+1) * + init_ed->ed_desc.max_packet_size; + + usb_time = (u32)otg_usbcore_get_calc_bustime( + init_ed->ed_desc.dev_speed, + init_ed->ed_desc.is_ep_in, + (init_ed->ed_desc.endpoint_type == + ISOCH_TRANSFER ? true : false), byte_count); + + usb_time /= 1000; /* convert nanosec unit to usec unit */ + + if (reserve_used_resource_for_periodic(usb_time, + init_ed->ed_desc.dev_speed, + init_ed->ed_desc.endpoint_type) + != USB_ERR_SUCCESS) { + return USB_ERR_NOSPACE; + } + + init_ed->ed_status.is_alloc_resource_for_ed = true; + init_ed->ed_desc.used_bus_time = usb_time; + init_ed->ed_desc.mc = multi_count+1; + } + + init_ed->ed_status.is_in_transfer_ready_q = false; + init_ed->ed_status.is_in_transferring = false; + init_ed->ed_status.is_ping_enable = false; + init_ed->ed_status.in_transferring_td = 0; + + /* push the ed to ED_list. */ + otg_list_push_prev(&init_ed->ed_list_entry, &ed_list_head); + + if (ref_periodic_transfer) + enable_sof(); + + return USB_ERR_SUCCESS; +} + + +/*****************************************************************************/ +/*! + * @name int create_td(struct td **new_td) + * + * @brief this function creates a new struct td and returns the struct td to Caller + * + * + * @param [OUT] new_td = returns the address of the new struct td . + * + * @return USB_ERR_SUCCESS -if successes to create the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/*****************************************************************************/ +static int create_td(struct td **new_td) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_td, + (u16)sizeof(struct td), USB_MEM_ASYNC); + otg_mem_set(*new_td, 0, sizeof(struct td)); + return err_code; +} + + +/*****************************************************************************/ +/*! + * @name int init_perio_stransfer( bool f_is_isoch_transfer, + * struct td *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer for Periodic + * Transfer and inserts this init_td_p to init_td_p->parent_ed_p. + * + * @param [IN] f_is_isoch_transfer = indicates whether this transfer + * is Isochronous or not. + * [IN] parent_td = indicates the address of struct td to be initiated. + * + * @return USB_ERR_SUCCESS -if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL -if fail to update the STranfer of pUpdateTD. + */ +/*****************************************************************************/ +static int init_perio_stransfer(bool f_is_isoch_transfer, + struct td *parent_td) +{ + parent_td->cur_stransfer.ed_desc_p = + &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = + &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = + (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = + (u32)&parent_td->cur_stransfer; + + otg_mem_set(&parent_td->cur_stransfer.hc_reg, 0, sizeof(struct hc_reg)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if (f_is_isoch_transfer) { + /* initiates the STransfer usinb the IsochPacketDesc[0]. */ + parent_td->cur_stransfer.buf_size = + parent_td->isoch_packet_desc_p[0].buf_size; + + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->isoch_packet_desc_p[0]. + isoch_packiet_start_addr; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->isoch_packet_desc_p[0]. + isoch_packiet_start_addr; + + } else { + parent_td->cur_stransfer.buf_size = + (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) ? + MAX_CH_TRANSFER_SIZE : parent_td->buf_size; + + parent_td->cur_stransfer.phy_addr = parent_td->phy_buf_addr; + parent_td->cur_stransfer.vir_addr = parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + + +/*****************************************************************************/ +/*! + * @name int init_nonperio_stransfer(bool f_is_standard_dev_req, + * struct td *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer + * for NonPeriodic Transfer and inserts this init_td_p + * to init_td_p->parent_ed_p. + * + * @param [IN] f_is_standard_dev_req- indicates whether this is Control or not. + * [IN] parent_td- indicates the address of struct td to be initiated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ +/******************************************************************************/ +static int init_nonperio_stransfer(bool f_is_standard_dev_req, + struct td *parent_td) +{ + parent_td->cur_stransfer.ed_desc_p = + &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = + &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = + (u32)&parent_td->cur_stransfer; + + otg_mem_set(&(parent_td->cur_stransfer.hc_reg), + 0, sizeof(struct hc_reg)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if (f_is_standard_dev_req) { + parent_td->cur_stransfer.buf_size = + USB_20_STAND_DEV_REQUEST_SIZE; + parent_td->cur_stransfer.phy_addr = + parent_td->standard_dev_req_info. + phy_standard_dev_req_addr; + parent_td->cur_stransfer.vir_addr = + parent_td->standard_dev_req_info. + vir_standard_dev_req_addr; + } else { + parent_td->cur_stransfer.buf_size + = (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size; + + parent_td->cur_stransfer.phy_addr + = parent_td->phy_buf_addr; + parent_td->cur_stransfer.vir_addr + = parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int init_td(struct td *init_td, + * struct ed *parent_ed, + * void *call_back_fun, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 phy_setup, + * u32 vir_setup, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 isoch_start_frame, + * isoch_packet_desc_t *isoch_packet_desc, + * u32 isoch_packet_num, + * void *td_priv) + * + * @brief this function initiates the init_td by using another parameter. + * + * + * @param [IN] init_td- indicate the struct td to be initiated. + * [IN] parent_ed- indicate the ed to manage this init_td + * [IN] call_back_func- indicate the call-back function of application. + * [IN] call_back_param- indicate the parameter of the call-back function. + * [IN] transfer_flag- indicate the transfer flag. + * [IN] f_is_standard_dev_req- indicates the issue transfer request is Request + * [IN] phy_setup- the physical address of buffer to store the Request. + * [IN] vir_setup- the virtual address of buffer to store the Request. + * [IN] vir_buf_addr- the virtual address of buffer to store the data + * to be transferred or received. + * [IN] phy_buf_addr- the physical address of buffer to store the data + * to be transferred or received. + * [IN] buf_size- indicates the buffer size. + * [IN] isoch_start_frame- if this usb transfer is isochronous transfer + * , this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_desc- if the usb transfer is isochronous transfer + * , this indicates the structure to describe the isochronous transfer. + * [IN] isoch_packet_num- if the usb transfer is isochronous transfer + * , this indicates the number of packet to consist of the usb transfer. + * [IN] td_priv- indicate the private data to be delivered from usb core. + * td_priv stores the urb of linux. + * + * @return USB_ERR_SUCCESS -if successes to initiate the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/******************************************************************************/ +static int init_td(struct td *init_td, + struct ed *parent_ed, + void *call_back_fun, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 phy_setup, + u32 vir_setup, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 isoch_start_frame, + struct isoch_packet_desc *isoch_packet_desc, + u32 isoch_packet_num, + void *td_priv) +{ + if (f_is_standard_dev_req) { + + if ((phy_buf_addr > 0) && (buf_size > 0)) + init_td->standard_dev_req_info.is_data_stage = true; + else + init_td->standard_dev_req_info.is_data_stage = false; + + init_td->standard_dev_req_info.conrol_transfer_stage + = SETUP_STAGE; + init_td->standard_dev_req_info.phy_standard_dev_req_addr + = phy_setup; + init_td->standard_dev_req_info.vir_standard_dev_req_addr + = vir_setup; + } + + init_td->call_back_func_p = call_back_fun; + init_td->call_back_func_param_p = call_back_param; + init_td->error_code = USB_ERR_SUCCESS; + init_td->is_standard_dev_req = f_is_standard_dev_req; + init_td->is_transfer_done = false; + init_td->is_transferring = false; + init_td->td_private = td_priv; + init_td->err_cnt = 0; + init_td->parent_ed_p = parent_ed; + init_td->phy_buf_addr = phy_buf_addr; + init_td->vir_buf_addr = vir_buf_addr; + init_td->buf_size = buf_size; + init_td->isoch_packet_desc_p = isoch_packet_desc; + init_td->isoch_packet_num = isoch_packet_num; + init_td->isoch_packet_index = 0; + init_td->isoch_packet_position = 0; + init_td->sched_frame = isoch_start_frame; + init_td->used_total_bus_time = parent_ed->ed_desc.used_bus_time; + init_td->td_id = (u32)init_td; + init_td->transfer_flag = transfer_flag; + init_td->transferred_szie = 0; + + switch (parent_ed->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + init_nonperio_stransfer(true, init_td); + break; + + case BULK_TRANSFER: + init_nonperio_stransfer(false, init_td); + break; + + case INT_TRANSFER: + init_perio_stransfer(false, init_td); + break; + + case ISOCH_TRANSFER: + init_perio_stransfer(true, init_td); + break; + + default: + return USB_ERR_FAIL; + } + + /* insert the struct td to parent_ed->td_list_entry. */ + otg_list_push_prev(&init_td->td_list_entry, &parent_ed->td_list_entry); + parent_ed->num_td++; + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int issue_transfer(struct sec_otghost *otghost, + * struct ed *parent_ed, + * void *call_back_func, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 setup_vir_addr, + * u32 setup_phy_addr, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 start_frame, + * u32 isoch_packet_num, + * isoch_packet_desc_t *isoch_packet_desc, + * void *td_priv, + * unsigned int *return_td_addr) + * + * @brief this function start USB Transfer + * + * + * @param [IN] parent_ed - indicate the ed to manage this issue transfer. + * [IN] call_back_func - indicate the call-back function of application. + * [IN] call_back_param - indicate the parameter of the call-back function. + * [IN] transfer_flag - indicate the transfer flag. + * [IN] f_is_standard_dev_req - indicates the issue transfer request + * is USB Standard Request + * [IN] setup_vir_addr - the virtual address of buffer to store the Request. + * [IN] setup_phy_addr - the physical address of buffer to store the Request. + * [IN] vir_buf_addr - the virtual address of buffer to store the data + * to be transferred or received. + * [IN] phy_buf_addr - the physical address of buffer to store the data + * to be transferred or received. + * [IN] buf_siz- indicates the buffer size. + * [IN] start_frame - if this usb transfer is isochronous transfer, + * this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_num - if the usb transfer is isochronous transfer, + * this indicates the number of packet to consist of the usb transfer. + * [IN] isoch_packet_desc - if the usb transfer is isochronous transfer + * this indicates the structure to describe the isochronous transfer. + * [IN] td_priv - indicate the private data to be delivered from usb core. + * td_priv stores the urb of linux. + * [OUT] return_td_addr - indicates the variable address + * to store the new struct td for this transfer + * + * @return USB_ERR_SUCCESS - if successes to initiate the new struct td. + * USB_ERR_FAILl - if fails to create to new struct td. + */ +/******************************************************************************/ +static int issue_transfer(struct sec_otghost *otghost, + struct ed *parent_ed, + void *call_back_func, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 setup_vir_addr, + u32 setup_phy_addr, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 start_frame, + u32 isoch_packet_num, + struct isoch_packet_desc *isoch_packet_desc, + void *td_priv, + unsigned int *return_td_addr) +{ + struct td *new_td_p = NULL; + int err = USB_ERR_SUCCESS; + + if (create_td(&new_td_p) == USB_ERR_SUCCESS) { + err = init_td(new_td_p, + parent_ed, + call_back_func, + call_back_param, + transfer_flag, + f_is_standard_dev_req, + setup_phy_addr, + setup_vir_addr, + vir_buf_addr, + phy_buf_addr, + buf_size, + start_frame, + isoch_packet_desc, + isoch_packet_num, + td_priv); + + if (err != USB_ERR_SUCCESS) + return USB_ERR_NOMEM; + + if (parent_ed->is_need_to_insert_scheduler) + insert_ed_to_scheduler(otghost, parent_ed); + + *return_td_addr = (u32)new_td_p; + + return USB_ERR_SUCCESS; + } else + return USB_ERR_NOMEM; +} + + +/* TODO: not used. removed */ +#if 0 +static int create_isoch_packet_desc(isoch_packet_desc_t **new_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_alloc((void **)new_isoch_packet_desc, + (u16)sizeof(isoch_packet_desc_t)*isoch_packet_num, + USB_MEM_SYNC); +} + +static int delete_isoch_packet_desc(isoch_packet_desc_t *del_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_free(del_isoch_packet_desc); +} + +/******************************************************************************/ +/*! + * @name + * void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc, + * u32 isoch_packet_start_addr, + * u32 isoch_packet_size, + * u32 index) + * + * @brief this function initiates the isoch_packet_desc_t[index]. + * + * @param + * [OUT] init_isoch_packet_desc = indicates the pointer of + * IsochPackDesc_t to be initiated. + * [IN] isoch_packet_start_addr = indicates the start address of the buffer + * to be used at USB Isochronous Transfer. + * [IN] isoch_packet_size = indicates the size of Isochronous packet. + * [IN] index = indicates the index to be mapped with this. + * + * @return void + */ +/******************************************************************************/ +static void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc, + u32 isoch_packet_start_addr, + u32 isoch_packet_size, + u32 index) +{ + init_isoch_packet_desc[index].buf_size = isoch_packet_size; + init_isoch_packet_desc[index].isoch_packiet_start_addr + = isoch_packet_start_addr; + init_isoch_packet_desc[index].isoch_status = 0; + init_isoch_packet_desc[index].transferred_szie = 0; +} +#endif + + diff --git a/drivers/usb/host/shost/shost_transfer.h b/drivers/usb/host/shost/shost_transfer.h new file mode 100644 index 0000000..397fdae --- /dev/null +++ b/drivers/usb/host/shost/shost_transfer.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-transfer-transfer.h + * [Description] : The Header file defines the external + * and internal functions of Transfer. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Transfer + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _TRANSFER_H +#define _TRANSFER_H + +/* + transfer-common.c + transferchecker_common.c + */ +int delete_td(struct sec_otghost *otghost, struct td * delete_td); + +static inline u32 calc_packet_cnt(u32 data_size, u16 max_packet_size) +{ + if (data_size != 0) { + return (data_size % max_packet_size == 0) ? + data_size/max_packet_size : + data_size/max_packet_size + 1; + } + return 1; +} +#endif + diff --git a/drivers/usb/host/shost/shost_transferchecker.c b/drivers/usb/host/shost/shost_transferchecker.c new file mode 100644 index 0000000..855164c --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker.c @@ -0,0 +1,503 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : CommonTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of CommonTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/01/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of CommonTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include "shost.h" + +/** + * void mask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Mask specific channel interrupt + * + * @param [IN] chnum : channel number for masking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void mask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + clear_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * void unmask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Unmask specific channel interrupt + * + * @param [IN] chnum : channel number for unmasking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void unmask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + update_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * int get_ch_info(struct hc_info * hc_reg, u8 ch_num) + * + * @brief Get current channel information about specific channel + * + * @param [OUT] hc_reg : structure to write channel inforamtion value + * [IN] ch_num : channel number for unmasking + * + * @return None + * + * @remark + * + */ +static int get_ch_info(struct hc_info *hc_reg, u8 ch_num) +{ + if (hc_reg != NULL) { + hc_reg->hc_int_msk.d32 = read_reg_32(HCINTMSK(ch_num)); + hc_reg->hc_int.d32 = read_reg_32(HCINT(ch_num)); + hc_reg->dma_addr = read_reg_32(HCDMA(ch_num)); + hc_reg->hc_char.d32 = read_reg_32(HCCHAR(ch_num)); + hc_reg->hc_size.d32 = read_reg_32(HCTSIZ(ch_num)); + + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +/** + * void get_intr_ch(u32* haint, u32* haintmsk) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [OUT] haint : HAINT register value + * [OUT] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void get_intr_ch(u32 *haint, u32 *haintmsk) +{ + *haint = read_reg_32(HAINT); + *haintmsk = read_reg_32(HAINTMSK); +} + +/** + * void clear_ch_intr(u8 ch_num, u32 clear_bit) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [IN] haint : HAINT register value + * [IN] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void clear_ch_intr(u8 ch_num, u32 clear_bit) +{ + update_reg_32(HCINT(ch_num), clear_bit); +} + + +static int +release_trans_resource(struct sec_otghost *otghost, + struct td *done_td) +{ + /* remove the pDeallocateTD from parent_ed_p. */ + otg_list_pop(&done_td->td_list_entry); + done_td->parent_ed_p->num_td--; + + /* Call deallocate to release the channel + and bandwidth resource of S3CScheduler. */ + deallocate(done_td); + delete_td(otghost, done_td); + return USB_ERR_SUCCESS; +} + +static u32 calc_transferred_size(bool f_is_complete, + struct td *td, struct hc_info *hc_info) +{ + if (f_is_complete) { + if (td->parent_ed_p->ed_desc.is_ep_in) { + return td->cur_stransfer.buf_size - + hc_info->hc_size.b.xfersize; + + } else + return td->cur_stransfer.buf_size; + + } else { + return (td->cur_stransfer.packet_cnt - + hc_info->hc_size.b.pktcnt)*td-> + parent_ed_p->ed_desc.max_packet_size; + } +} + +static void update_frame_number(struct td *pResultTD) +{ + u32 cur_frame_num = 0; + + if (pResultTD->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER || + pResultTD->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER) { + return; + } + + pResultTD->parent_ed_p->ed_desc.sched_frame += + pResultTD->parent_ed_p->ed_desc.interval; + pResultTD->parent_ed_p->ed_desc.sched_frame &= + HFNUM_MAX_FRNUM; + + cur_frame_num = oci_get_frame_num(); + + if (((cur_frame_num - pResultTD->parent_ed_p->ed_desc.sched_frame) + &HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1)) { + pResultTD->parent_ed_p->ed_desc.sched_frame = cur_frame_num; + } +} + +static void update_data_toggle(struct td *td, u8 toggle) +{ + switch (td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + if (td->standard_dev_req_info.conrol_transfer_stage + == DATA_STAGE) { + td->parent_ed_p->ed_status. + control_data_toggle.data = toggle; + } + break; + case BULK_TRANSFER: + case INT_TRANSFER: + td->parent_ed_p->ed_status.data_toggle = toggle; + break; + + case ISOCH_TRANSFER: + break; + default: + break; + } +} + +/******************************************************************************/ +/*! + * @name void update_perio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by oci. the STransfer of parent_td is for Periodic Transfer. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/******************************************************************************/ +static void update_perio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + case INT_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE : parent_td->buf_size; + break; + + case ISOCH_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.buf_size = + (parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position) > + MAX_CH_TRANSFER_SIZE + ? MAX_CH_TRANSFER_SIZE + : parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position; + + break; + + default: + break; + } +} + +/****************************************************************************/ +/*! + * @name void update_nonperio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by S3COCI. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/****************************************************************************/ +static void update_nonperio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + + case BULK_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td->transferred_szie) + > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + break; + + case CONTROL_TRANSFER: + if (parent_td->standard_dev_req_info. + conrol_transfer_stage == SETUP_STAGE) { + /* but, this case will not be occured...... */ + parent_td->cur_stransfer.phy_addr = + parent_td->standard_dev_req_info. + phy_standard_dev_req_addr; + parent_td->cur_stransfer.vir_addr = + parent_td->standard_dev_req_info. + vir_standard_dev_req_addr; + parent_td->cur_stransfer.buf_size = 8; + + } else if (parent_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td-> + transferred_szie) > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + + } else { + parent_td->cur_stransfer.phy_addr = 0; + parent_td->cur_stransfer.vir_addr = 0; + parent_td->cur_stransfer.buf_size = 0; + } + break; + default: + break; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + +} + +#include "shost_transferchecker_control.c" +#include "shost_transferchecker_interrupt.c" +#include "shost_transferchecker_bulk.c" + +/******************************************************************************/ +/*! + * @name void do_transfer_checker(struct sec_otghost *otghost) + * + * @brief this function processes the result of USB Transfer. + * So, do_transfer_checker fistly + * check which channel occurs OTG Interrupt and gets the status + * information of the channel. + * do_transfer_checker requests the information of td to scheduler. + * To process the interrupt of the channel, do_transfer_checker + * calls the sub-modules of + * S3CDoneTransferChecker, for example, + * ControlTransferChecker, BulkTransferChecker. + * according to the process result of the channel interrupt, + * do_transfer_checker decides + * the USB Transfer will be done or retransmitted. + * + * + * @param void + * + * @return void + */ +/*****************************************************************************/ +void do_transfer_checker(struct sec_otghost *otghost) +__releases(&otghost->lock) +__acquires(&otghost->lock) +{ + u32 hc_intr = 0; + u32 hc_intr_msk = 0; + u8 do_try_cnt = 0; + + struct hc_info ch_info; + struct td *done_td = {0}; + + u32 td_addr = 0; + u8 proc_result = 0; + + otg_mem_set((void *)&ch_info, 0, sizeof(struct hc_info)); + + /* Get value of HAINT... */ + get_intr_ch(&hc_intr, &hc_intr_msk); + +start_do_transfer_checker: + + while (do_try_cnt < MAX_CH_NUMBER) { + /* checks the channel number to be masked or not. */ + if (!(hc_intr & hc_intr_msk & (1 << do_try_cnt))) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the address of the struct td + to have the channel to be interrupted. */ + if (!(get_td_info(do_try_cnt, &td_addr))) { + + done_td = (struct td *)td_addr; + + if (do_try_cnt != done_td->cur_stransfer.alloc_chnum) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + } else { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the informationof channel to be interrupted. */ + get_ch_info(&ch_info, do_try_cnt); + + switch (done_td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + proc_result = + process_control_transfer(done_td, &ch_info); + break; + case BULK_TRANSFER: + proc_result = process_bulk_transfer(done_td, &ch_info); + break; + case INT_TRANSFER: + proc_result = process_intr_transfer(done_td, &ch_info); + break; + case ISOCH_TRANSFER: + /* proc_result = + ProcessIsochTransfer(done_td, &ch_info); */ + break; + default: + break; + } + + if ((proc_result == RE_TRANSMIT) || + (proc_result == RE_SCHEDULE)) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->is_transfer_done = false; + done_td->is_transferring = false; + + if (done_td->parent_ed_p->ed_desc.endpoint_type + == CONTROL_TRANSFER || + done_td->parent_ed_p->ed_desc.endpoint_type + == BULK_TRANSFER) { + + update_nonperio_stransfer(done_td); + + } else + update_perio_stransfer(done_td); + + if (proc_result == RE_TRANSMIT) + retransmit(otghost, done_td); + else + reschedule(done_td); + } + + else if (proc_result == DE_ALLOCATE) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->parent_ed_p->ed_status. + in_transferring_td = 0; + done_td->is_transfer_done = true; + done_td->is_transferring = false; + + spin_unlock(&otghost->lock); + otg_usbcore_giveback(done_td); + spin_lock(&otghost->lock); + release_trans_resource(otghost, done_td); + + } else { /* NO_ACTION.... */ + done_td->parent_ed_p->ed_status. + is_in_transferring = true; + done_td->parent_ed_p->ed_status. + in_transferring_td = (u32)done_td; + done_td->is_transfer_done = false; + done_td->is_transferring = true; + } + do_try_cnt++; + } + /* Complete to process the Channel Interrupt. + So. we now start to scheduler of S3CScheduler. */ + do_schedule(otghost); +} + + diff --git a/drivers/usb/host/shost/shost_transferchecker_bulk.c b/drivers/usb/host/shost/shost_transferchecker_bulk.c new file mode 100644 index 0000000..13c6b0c --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_bulk.c @@ -0,0 +1,671 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : BulkTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of BulkTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/13 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of BulkTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the xfercompl event. + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status.BulkDataTgl. + * 5. calculates the tranferred size on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_xfercompl_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + result_td->transferred_szie += calc_transferred_size(true, + result_td, hc_reg_data); + + if (result_td->transferred_szie == result_td->buf_size) { + /* at IN Transfer, short transfer is accepted. */ + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hc_reg_data->hc_size.b.xfersize) { + + if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT) + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + else + result_td->error_code = USB_ERR_STATUS_COMPLETE; + + ret_val = DE_ALLOCATE; + + } else + ret_val = RE_SCHEDULE; + } + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + if (hc_reg_data->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with theAHB Errorl event. + * this function stop the channel to be executed + * TBD.... + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +/* TODO: not used. removed */ +#if 0 +static u8 process_ahb_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + /* we just calculate the size of the transferred data on Data Stage + of Bulk Transfer. */ + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with theStall event. + * when Stall is occured at Bulk Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + /* this channel is stalled, So we don't process another interrupts.*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + result_td->transferred_szie += calc_transferred_size(false, + result_td, hc_reg_data); + + update_data_toggle(result_td, DATA0); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the nak event. + * nak is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle at OUT Transaction, + * this function check whether the speed of USB Device + * is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +static u8 process_nak_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + /* at OUT Transfer, we must re-transmit.*/ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.dev_speed == + HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return RE_TRANSMIT; + } + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT + * and ed_status.is_ping_enable. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_ack_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return NO_ACTION; +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the nyet event. + * nyet is only occured at OUT Transaction. + * If nyet is occured at OUT Transaction, + * this function processes this interrupt as following. + * + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * 7. return RE_SCHEDULE to retransmit. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nyet_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + /* Error State.... */ + return NO_ACTION; + } + + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return RE_TRANSMIT; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the xacterr event. + * xacterr is occured at OUT/IN Transaction + * and we should retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. if the speed of USB Device is High-Speed, sets the ping protocol. + * 7. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +static u8 process_xacterr_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK+CH_STATUS_DataTglErr); + + ret_val = RE_TRANSMIT; + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + } + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the Babble event + * babble error is occured when the buffer to receive data + * to be transmit is overflow. + * So, babble error can be just occured at IN Transaction. + * when Babble Error is occured, we should stop the USB Transfer, + * and return the fact to Application. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_bblerr_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt..*/ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + return DE_ALLOCATE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_bulk( struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the datatglerr + * the datatglerr event is occured at IN Transfer, + * and the channel is not halted. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_datatgl_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * @brief this function processes Channel Halt event. + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + if (hc_reg_data->hc_int.b.xfercompl) + return process_xfercompl_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.stall) + return process_stall_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.bblerr) + return process_bblerr_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.xacterr) + return process_xacterr_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.nak) + return process_nak_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.nyet) + return process_nyet_on_bulk(result_td, hc_reg_data); + + else { + /* occur error state...*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt..*/ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if (result_td->err_cnt == 3) { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name u8 process_bulk_transfer(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function processes the result of the Bulk Transfer. + * firstly, this function checks the result of the Bulk Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td whose channel is interruped. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +static u8 process_bulk_transfer(struct td *result_td, + struct hc_info *hc_reg_data) +{ + hcintn_t hc_intr_info; + u8 ret = 0; + + /* we just deal with the interrupts to be unmasked.*/ + hc_intr_info.d32 = hc_reg_data->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hc_intr_info.b.chhltd) + ret = process_chhltd_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.ack) + ret = process_ack_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.nak) + ret = process_nak_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.datatglerr) + ret = process_datatgl_on_bulk(result_td, hc_reg_data); + + } else { + + if (hc_intr_info.b.chhltd) + ret = process_chhltd_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.ack) + ret = process_ack_on_bulk(result_td, hc_reg_data); + } + + return ret; +} + diff --git a/drivers/usb/host/shost/shost_transferchecker_control.c b/drivers/usb/host/shost/shost_transferchecker_control.c new file mode 100644 index 0000000..8d0cfbc --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_control.c @@ -0,0 +1,777 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : ControlTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of ControlTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of ControlTransferChecker + * (2) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Completed to implement ControlTransferChecker.c v1.0 + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xfercompl event + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * by using clear_ch_intr() of S3CIsr. + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status. + * control_data_tgl. + * 5. calculates the tranferred size + * by calling calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_xfercompl_on_control(struct td *result_td, struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + switch (result_td->standard_dev_req_info.conrol_transfer_stage) { + + case SETUP_STAGE: + if (result_td->standard_dev_req_info.is_data_stage) + result_td->standard_dev_req_info. + conrol_transfer_stage = DATA_STAGE; + else + result_td->standard_dev_req_info. + conrol_transfer_stage = STATUS_STAGE; + + ret_val = RE_TRANSMIT; + + break; + + case DATA_STAGE: + + result_td->transferred_szie += + calc_transferred_size(true, result_td, hcinfo); + + /* at IN Transfer, short transfer is accepted. */ + if (result_td->transferred_szie == result_td->buf_size) { + result_td->standard_dev_req_info. + conrol_transfer_stage = STATUS_STAGE; + result_td->error_code = USB_ERR_STATUS_COMPLETE; + + } else { + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hcinfo->hc_size.b.xfersize) { + + if (result_td->transfer_flag & + USB_TRANS_FLAG_NOT_SHORT) { + + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + result_td->standard_dev_req_info. + conrol_transfer_stage = + STATUS_STAGE; + + } else { + result_td->error_code = + USB_ERR_STATUS_COMPLETE; + result_td->standard_dev_req_info. + conrol_transfer_stage = + STATUS_STAGE; + } + + } else { + /* the Data Stage is not completed. + So we need to continue Data Stage. */ + result_td->standard_dev_req_info. + conrol_transfer_stage = DATA_STAGE; + update_data_toggle(result_td, + hcinfo->hc_size.b.pid); + } + } + + if (hcinfo->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + ret_val = RE_TRANSMIT; + break; + + case STATUS_STAGE: + result_td->standard_dev_req_info. + conrol_transfer_stage = COMPLETE_STAGE; + + if (hcinfo->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + + ret_val = DE_ALLOCATE; + break; + + default: + break; + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theAHB Errorl event + * this function stop the channel to be executed + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ + +/* TODO: not used. remove */ +#if 0 +static u8 process_ahb_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theStall event + * but USB2.0 Spec don't permit the Stall + * on Setup Stage of Control Transfer. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the nak event + * nak is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of + * USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nak_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + } + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + + return RE_SCHEDULE; +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_ack_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the nyet event + * nyet is occured at OUT Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nyet is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nyet_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return RE_SCHEDULE; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xacterr event + * xacterr is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * if Timeout/CRC error/false EOP is occured, then xacterr is occured. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. if the speed of USB Device is High-Speed, sets the ping protocol. + * 7. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the Error Counter is less than RETRANSMIT_THRESHOLD + * DE_ALLOCATE -if the Error Counter is equal to RETRANSMIT_THRESHOLD + */ +/******************************************************************************/ +static u8 process_xacterr_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr); + + ret_val = RE_TRANSMIT; + + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_control(truct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the Babble event + * babble error can be just occured at IN Transaction. + * So if the direction of transfer is + * OUT, this function return Error Code. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + * NO_ACTION -if the direction is OUT + */ +/******************************************************************************/ +static u8 process_bblerr_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the datatglerr event + * the datatglerr event is occured at IN Transfer. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_datatgl_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_control(truct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function processes Channel Halt event + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.bblerr) + return process_bblerr_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nyet) + return process_nyet_on_control(result_td, hcinfo); + + else { + + /* Occure Error State.....*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + result_td->err_cnt++; + + if (result_td->err_cnt == 3) { + + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + return RE_TRANSMIT; + } + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name u8 process_control_transfer(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function processes the result of the Control Transfer. + * firstly, this function checks the result the Control Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] ubChNum + * -indicates the number of the channel to be interrupted. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_control_transfer(struct td *result_td, + struct hc_info *hcinfo) +{ + hcintn_t hcintr_info; + u8 ret_val = 0; + + /* we just deal with the interrupts to be unmasked. */ + hcintr_info.d32 = hcinfo->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hcintr_info.b.chhltd) + ret_val = process_chhltd_on_control(result_td, hcinfo); + + else if (hcintr_info.b.ack) + ret_val = process_ack_on_control(result_td, hcinfo); + + else if (hcintr_info.b.nak) + ret_val = process_nak_on_control(result_td, hcinfo); + + else if (hcintr_info.b.datatglerr) + ret_val = process_datatgl_on_control(result_td, hcinfo); + + } else { + + if (hcintr_info.b.chhltd) + ret_val = process_chhltd_on_control(result_td, hcinfo); + + else if (hcintr_info.b.ack) + ret_val = process_ack_on_control(result_td, hcinfo); + } + + return ret_val; +} + diff --git a/drivers/usb/host/shost/shost_transferchecker_interrupt.c b/drivers/usb/host/shost/shost_transferchecker_interrupt.c new file mode 100644 index 0000000..fa85721 --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_interrupt.c @@ -0,0 +1,617 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : IntTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of IntTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/19 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of IntTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_intr( struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xfercompl event + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * by using clear_ch_intr() of S3CIsr. + * 2. masks ack/nak(?)/datatglerr(?) bit of HCINTMSK + * 3. Resets the err_cnt of result_td. + * 4. updates the result_td->parent_ed_p->ed_status. + * IntDataTgl. + * 5. calculates the tranferred size by calling + * calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE -if USB Transfer is completed. + * RE_TRANSMIT -if need to retransmit the result_td. + */ +/******************************************************************************/ +static u8 process_xfercompl_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(true, result_td, hcinfo); + + if (result_td->transferred_szie == result_td->buf_size) { + /* at IN Transfer, short transfer is accepted. */ + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hcinfo->hc_size.b.xfersize) { + + if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT) + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + else + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + ret_val = RE_TRANSMIT; + } + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theAHB Errorl event + * this function stop the channel to be executed + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +/* TODO: not used. removed */ +#if 0 +static u8 process_ahb_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the Stall event + * when Stall is occured at Int Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, DATA0); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the nak event + * nak is occured at OUT/IN Transaction of Interrupt Transfer. + * we can't use ping protocol on Interrupt Transfer. + * and Syonopsys OTG IP occures + * chhltd interrupt on nak of IN/OUT Transaction. + * So we should retransmit the transfer on IN Transfer. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. calculates frame number to retransmit this Interrupt Transfer. + * + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of + * USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +static u8 process_nak_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_SCHEDULE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT + * and ed_status.is_ping_enable. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_ack_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + + return NO_ACTION; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the xacterr event + * xacterr is occured at OUT/IN Transaction and we should + * retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. update the Data Toggle. + * 7. update the frame number to start retransmitting + * the Interrupt Transfer. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +static u8 process_xacterr_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr); + ret_val = RE_SCHEDULE; + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + if (ret_val == RE_SCHEDULE) + update_frame_number(result_td); + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the Babble event + * babble error is occured when the USB device continues to send packets + * althrough EOP is occured. So Babble error is only occured + * at IN Transfer. + * when Babble Error is occured, we should stop the USB Transfer, + * and return the fact to Application. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_bblerr_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_intr( struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the datatglerr event + * the datatglerr event is occured at IN Transfer, + * and the channel is not halted. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_datatgl_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_SCHEDULE; +} + +static u8 process_frmovrrun_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_TRANSMIT; +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_intr(struct td *result_td, + * struct hc_info *HCRegData) + * + * + * @brief this function processes Channel Halt event + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.bblerr) + return process_bblerr_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.datatglerr) + return process_datatgl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.frmovrun) + return process_frmovrrun_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_intr(result_td, hcinfo); + + else { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_DataTglErr); + return RE_TRANSMIT; + } + + } else { + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.frmovrun) + return process_frmovrrun_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_intr(result_td, hcinfo); + + else { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + + if (result_td->err_cnt == 3) { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + } +} + + + +/******************************************************************************/ +/*! + * @name u8 process_intr_transfer(struct td *result_td, + * struct hc_info *HCRegData) + * + * @brief this function processes the result of the Interrupt Transfer. + * firstly, this function checks the result of the Interrupt Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td whose channel is interruped. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +static u8 process_intr_transfer(struct td *result_td, + struct hc_info *hcinfo) +{ + hcintn_t hc_intr_info; + u8 ret_val = 0; + + hc_intr_info.d32 = hcinfo->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + + if (hc_intr_info.b.chhltd) + ret_val = process_chhltd_on_intr(result_td, hcinfo); + + else if (hc_intr_info.b.ack) + ret_val = process_ack_on_intr(result_td, hcinfo); + + } else { + if (hc_intr_info.b.chhltd) + ret_val = process_chhltd_on_intr(result_td, hcinfo); + + else if (hc_intr_info.b.ack) + ret_val = process_ack_on_intr(result_td, hcinfo); + } + + return ret_val; +} + + |