/*!
 * zasevb_modexit() - This is used as module exit, and as cleanup if modinit fails.
 */
static void zasevb_modexit (void)
{
        struct otg_instance *otg = ocd_instance->otg;
        //struct pcd_instance *pcd = (struct pcd_instance *)otg->pcd;
        //struct usbd_bus_instance *bus= pcd->bus;

        TRACE_MSG0(ZAS, "Modules exit!");

        if (otg) otg_exit(otg);

        mxc_procfs_exit();

        /* Disable GPT
         */
        #if defined(CONFIG_OTG_GPTR)
        mxc_gptcr_mod_exit();
        #endif /* defined(CONFIG_OTG_GPTR) */

        #if defined(CONFIG_OTG_HRT)
        mxc_hrt_mod_exit();
        #endif /* defined(CONFIG_OTG_GPTR) */

        #ifdef OTG_USE_I2C
        TRACE_MSG0(ZAS, "0. I2C");
        i2c_mod_exit(otg);
        #endif

        #if !defined(CONFIG_USB_HOST)
        if (pcd_ops.mod_exit) pcd_ops.mod_exit(otg);
        REMOVE_pcd_instance = otg_set_pcd_ops(otg, NULL);
        #else /* !defined(CONFIG_USB_HOST) */
        printk(KERN_INFO"%s: PCD DRIVER N/A\n", __FUNCTION__);
        #endif /* !defined(CONFIG_USB_HOST) */


        #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE)
        if (hcd_ops.mod_exit) hcd_ops.mod_exit(otg);
        hcd_instance = otg_set_hcd_ops(otg, NULL);
        //HCD = otg_trace_invalidate_tag(HCD);
        #else /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */
        printk(KERN_INFO"%s: HCD DRIVER N/A\n", __FUNCTION__);
        #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */


        if (tcd_ops.mod_exit) tcd_ops.mod_exit(otg);
        printk(KERN_INFO"%s: set_tcd_ops\n", __FUNCTION__);
        REMOVE_tcd_instance = otg_set_tcd_ops(otg, NULL);
        //REMOVE_TCD = otg_trace_invalidate_tag(REMOVE_TCD);

        if (ocd_ops.mod_exit) ocd_ops.mod_exit(otg);
        ocd_instance = otg_set_ocd_ops(otg, NULL);


        ZAS = otg_trace_invalidate_tag(ZAS);


        otg_destroy(otg);
}
/* pcd_ocd_modexit - module exit or init failure cleanup
 *
 * Specifically for each driver:
 *
 * 	call ops.mod_exit
 * 	reset instance address and  ops table address in state machine to NULL
 * 	invalidate tag
 */
static void pcd_ocd_modexit (void)
{
        struct otg_instance *otg = ocd_instance->otg;
        printk(KERN_INFO"%s\n", __FUNCTION__);
        if (otg)
                otg_exit(otg);

        if (pcd_ops.mod_exit) pcd_ops.mod_exit();
        pcd_instance = otg_set_pcd_ops(NULL);
        PCD = otg_trace_invalidate_tag(PCD);

        if (ocd_ops.mod_exit) ocd_ops.mod_exit();
        ocd_instance = otg_set_ocd_ops(NULL);
        OCD = otg_trace_invalidate_tag(OCD);
}
/* pcd_ocd_modinit - linux module initialization
 *
 * This needs to initialize the ocd, pcd and tcd drivers.  
 *
 * Specifically for each driver:
 *
 * 	obtain tag
 * 	pass ops table address to state machine and get instance address
 * 	call ops.mod_init
 *
 * Note that we automatically provide a default tcd_init if
 * none is set.
 */
static int pcd_ocd_modinit (void)
{
        printk(KERN_INFO"%s\n", __FUNCTION__);

        #if !defined(OTG_C99)
        pcd_global_init();
        #endif /* !defined(OTG_C99) */

        UNLESS(pcd_ops.pcd_init_func) pcd_ops.pcd_init_func = pcd_init_func;
        PCD = otg_trace_obtain_tag();
        THROW_UNLESS(pcd_instance = otg_set_pcd_ops(&pcd_ops), error);
        THROW_IF((pcd_ops.mod_init) ? pcd_ops.mod_init() : 0, error);

        OCD = otg_trace_obtain_tag();
        THROW_UNLESS(ocd_instance = otg_set_ocd_ops(&ocd_ops), error);
        THROW_IF((ocd_ops.mod_init) ? ocd_ops.mod_init() : 0, error);

        CATCH(error) {
                pcd_ocd_modexit();
                return -EINVAL;
        }
        return 0;
}
/*!
 * zasevb_modinit() - linux module initialization
 *
 * This needs to initialize the hcd, pcd and tcd drivers. This includes tcd and possibly hcd
 * for some architectures.
 *
 */
static int zasevb_modinit (void)
{
        struct otg_instance *otg = NULL;

        #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15)
        struct clk *clk = clk_get(NULL, "usb_clk");
        clk_enable(clk);
        clk_put(clk);
        #endif

        THROW_UNLESS((otg = otg_create()), error);

        mxc_pcd_ops_init();
        #if !defined(OTG_C99)
        pcd_global_init();
        fs_ocd_global_init();
        zasevb_tcd_global_init();
        fs_pcd_global_init();
        #endif /* !defined(OTG_C99) */

        ZAS = otg_trace_obtain_tag(otg, "zas");

        mxc_procfs_init();

        TRACE_MSG0(ZAS, "1. ZAS");

        #if 0
        /* ZAS EVB Platform setup
         */
        TRACE_MSG4(ZAS, "BCTRL Version: %04x Status: %04x 1: %04x 2: %04x",
                        readw(PBC_BASE_ADDRESS ),
                        readw(PBC_BASE_ADDRESS + PBC_BSTAT),
                        readw(PBC_BASE_ADDRESS + PBC_BCTRL1_SET),
                        readw(PBC_BASE_ADDRESS + PBC_BCTRL2_SET));

        #endif

        /* ZAS EVB Clock setup
         */

#if defined(CONFIG_ARCH_ARGONPLUS) || defined(CONFIG_ARCH_ARGONLV)
#define ZASEVB_MULTIPLIER       12
#define ZASEVB_DIVISOR          775             // ~10.
#else
#define ZASEVB_MULTIPLIER       12
#define ZASEVB_DIVISOR 155
#endif

        TRACE_MSG0(ZAS, "2. Setup GPT");

        THROW_UNLESS(ocd_instance = otg_set_ocd_ops(otg, &ocd_ops), error);
        REMOVE_OCD = ocd_instance->TAG;
        // XXX THROW_IF((ocd_ops.mod_init ? ocd_ops.mod_init() : 0), error);

        #if defined(CONFIG_OTG_GPTR)
        mxc_gptcr_mod_init(ZASEVB_DIVISOR, ZASEVB_MULTIPLIER);
        #endif /* defined(CONFIG_OTG_GPTR) */

        #if defined(CONFIG_OTG_HRT)
        mxc_hrt_mod_init(otg, ZASEVB_DIVISOR, ZASEVB_MULTIPLIER);
        #endif /* defined(CONFIG_OTG_GPTR) */


        #if !defined(CONFIG_USB_HOST)
        TRACE_MSG0(ZAS, "3. PCD");
        THROW_UNLESS(REMOVE_pcd_instance = otg_set_pcd_ops(otg, &pcd_ops), error);
        REMOVE_PCD = REMOVE_pcd_instance->TAG;
        // XXX THROW_IF((pcd_ops.mod_init ? pcd_ops.mod_init() : 0), error);
        #else /* !defined(CONFIG_USB_HOST) */
        printk(KERN_INFO"%s: PCD DRIVER N/A\n", __FUNCTION__);
        #endif /* !defined(CONFIG_USB_HOST) */


        TRACE_MSG0(ZAS, "4. TCD");
        THROW_UNLESS(REMOVE_tcd_instance = otg_set_tcd_ops(otg, &tcd_ops), error);
        REMOVE_TCD = REMOVE_tcd_instance->TAG;
        // XXX THROW_IF((tcd_ops.mod_init ? tcd_ops.mod_init() : 0), error);
#ifdef OTG_USE_I2C
        TRACE_MSG0(ZAS, "0. I2C");
        i2c_mod_init(otg);
#endif


        #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE)
        TRACE_MSG0(ZAS, "5. Host");
        THROW_UNLESS(hcd_instance = otg_set_hcd_ops(otg, &hcd_ops), error);
        HCD = hcd_instance->TAG;
        // XXX THROW_IF((hcd_ops.mod_init) ? hcd_ops.mod_init() : 0, error);
        #else /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */
        printk(KERN_INFO"%s: HCD DRIVER N/A\n", __FUNCTION__);
        #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */



        TRACE_MSG0(ZAS, "6. Init & check");
        THROW_IF((ocd_ops.mod_init ? ocd_ops.mod_init(otg) : 0), error);

        #if !defined(CONFIG_USB_HOST)
        THROW_IF((pcd_ops.mod_init ? pcd_ops.mod_init(otg) : 0), error);
        #endif /* !defined(CONFIG_USB_HOST) */

        THROW_IF((tcd_ops.mod_init ? tcd_ops.mod_init(otg) : 0), error);

        #if defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE)
        THROW_IF((hcd_ops.mod_init) ? hcd_ops.mod_init(otg) : 0, error);
        #endif /* defined(CONFIG_OTG_USB_HOST) || defined(CONFIG_OTG_USB_PERIPHERAL_OR_HOST)|| defined(CONFIG_OTG_DEVICE) */

        THROW_UNLESS(ocd_instance && (otg = ocd_instance->otg), error);


        TRACE_MSG0(ZAS, "7. otg_init");
        if (MODPARM(serial_number_str) && strlen(MODPARM(serial_number_str))) {

                TRACE_MSG1(ZAS, "serial_number_str: %s", MODPARM(serial_number_str));
                otg_serial_number (otg, MODPARM(serial_number_str));
        }
        otg_init(otg);

        return 0;

        CATCH(error) {
                //zasevb_modexit();
                return -EINVAL;
        }

        return 0;
}