static void bcmpmu_otg_xceiv_vbus_a_invalid_handler(struct work_struct *work)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data =
	    container_of(work, struct bcmpmu_otg_xceiv_data,
			 bcm_otg_vbus_a_invalid_work);

	dev_info(xceiv_data->dev, "A session invalid\n");

	if (!bcm_hsotgctrl_get_clk_count())
		bcm_hsotgctrl_en_clock(true);

	/* Inform the core of session invalid level  */
	bcm_hsotgctrl_phy_set_vbus_stat(false);

	if (xceiv_data->otg_enabled) {
		/* Stop Vbus discharge */
		bcmpmu_usb_set(xceiv_data->bcmpmu, BCMPMU_USB_CTRL_DISCHRG_VBUS,
			       0);

		if (bcmpmu_otg_xceiv_check_id_gnd(xceiv_data)) {
			/* Use n-1 method for ADP rise time comparison */
			bcmpmu_usb_set(xceiv_data->bcmpmu,
				       BCMPMU_USB_CTRL_SET_ADP_COMP_METHOD, 1);
			if (xceiv_data->otg_xceiver.otg_vbus_off)
				schedule_delayed_work(&xceiv_data->
					bcm_otg_delayed_adp_work,
					msecs_to_jiffies
					  (T_NO_ADP_DELAY_MIN_IN_MS));
			else
				bcm_otg_do_adp_probe(xceiv_data);
		} else if (!bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data)) {
			if (xceiv_data->otg_xceiver.otg_srp_reqd) {
				/* Start Session End SRP timer */
				xceiv_data->otg_xceiver.sess_end_srp_timer.
				    expires =
				    jiffies +
				    msecs_to_jiffies
				    (T_SESS_END_SRP_START_IN_MS);
				add_timer(&xceiv_data->otg_xceiver.
					  sess_end_srp_timer);
			} else
				bcm_otg_do_adp_sense(xceiv_data);
		}
	} else {
		bool id_default_host = false;

		id_default_host =
			bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
			bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

		if (!id_default_host) {
			atomic_notifier_call_chain(&xceiv_data->otg_xceiver.
						   xceiver.notifier,
						   USB_EVENT_NONE, NULL);
		}
	}
}
static void bcmpmu_otg_xceiv_chg_detect_handler(struct work_struct *work)
{
    struct bcmpmu_otg_xceiv_data *xceiv_data =
        container_of(work, struct bcmpmu_otg_xceiv_data,
                     bcm_otg_chg_detect_work);

    dev_info(xceiv_data->dev, "Charger detect event\n");

    /* Read and save USB charger type */
    bcmpmu_usb_get(xceiv_data->bcmpmu,
                   BCMPMU_USB_CTRL_GET_USB_TYPE,
                   (void *)&xceiv_data->usb_charger_type);

    if (xceiv_data->otg_enabled) {
        /* Core is already up so just set the Vbus status */
        bcm_hsotgctrl_phy_set_vbus_stat(true);

        /* Vbus is up so allow the core to connect */
        bcm_hsotgctrl_phy_set_non_driving(false);
    } else {
        bool id_default_host = false;

        id_default_host =
            bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
            bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

        if (!id_default_host && xceiv_data->otg_xceiver.xceiver.gadget)
            atomic_notifier_call_chain(&xceiv_data->otg_xceiver.
                                       xceiver.notifier,
                                       USB_EVENT_VBUS, NULL);
    }
}
static int bcmpmu_otg_xceiv_set_host(struct otg_transceiver *otg,
                                     struct usb_bus *host)
{
    struct bcmpmu_otg_xceiv_data *xceiv_data = dev_get_drvdata(otg->dev);
    int status = 0;

    dev_dbg(xceiv_data->dev, "Setting Host\n");
    otg->host = host;

    if (host) {
        if (xceiv_data->otg_enabled) {
            /* Wake lock forever in OTG build */
            wake_lock(&xceiv_data->otg_xceiver.xceiver_wake_lock);
            /* Do calibration probe */
            bcm_otg_do_adp_calibration_probe(xceiv_data);
        }

        if (bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
                bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data)) {
            bcm_hsotgctrl_phy_set_id_stat(false);
            bcm_hsotgctrl_phy_set_non_driving(false);
        } else
            bcm_hsotgctrl_phy_set_id_stat(true);
    }
    return status;
}
static int bcmpmu_otg_xceiv_start(struct otg_transceiver *otg)
{
    struct bcmpmu_otg_xceiv_data *xceiv_data = dev_get_drvdata(otg->dev);
    bool id_default_host = false;

    if (xceiv_data) {

        if (!xceiv_data->otg_enabled) {
            if (xceiv_data->bcm_hsotg_regulator &&
                    !xceiv_data->regulator_enabled) {
                regulator_enable(xceiv_data->
                                 bcm_hsotg_regulator);
                xceiv_data->regulator_enabled = true;
                /* Give 2ms to ramp up USBLDO */
                mdelay(USBLDO_RAMP_UP_DELAY_IN_MS);
            }
        }


        id_default_host =
            bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
            bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

        /* Initialize OTG core and PHY */
        bcm_hsotgctrl_phy_init(!id_default_host);

        bcmpmu_otg_xceiv_set_def_state(xceiv_data,
                                       id_default_host);

    } else
        return -EINVAL;

    return 0;
}
static void bcmpmu_otg_xceiv_id_change_handler(struct work_struct *work)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data =
	    container_of(work, struct bcmpmu_otg_xceiv_data,
			 bcm_otg_id_status_change_work);
	bool id_gnd = false;
	bool id_rid_a = false;
	bool id_rid_c = false;

	dev_info(xceiv_data->dev, "ID change detected\n");
	id_gnd = bcmpmu_otg_xceiv_check_id_gnd(xceiv_data);
	id_rid_a = bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);
	id_rid_c = bcmpmu_otg_xceiv_check_id_rid_c(xceiv_data);

	bcm_hsotgctrl_phy_set_id_stat(!(id_gnd || id_rid_a));

	/* If ID is gnd, we need to turn on Vbus within 200ms
	 * If ID is RID_A/B/C/FLOAT then we should not turn it on
	 */
	bcmpmu_otg_xceiv_set_vbus(&xceiv_data->otg_xceiver.
		    xceiver, id_gnd ? true : false);

	if (!id_rid_c)
		msleep(HOST_TO_PERIPHERAL_DELAY_MS);

	if (id_gnd || id_rid_a || id_rid_c) {
		bcm_hsotgctrl_phy_deinit();
		xceiv_data->otg_xceiver.xceiver.state = OTG_STATE_UNDEFINED;
		atomic_notifier_call_chain(&xceiv_data->otg_xceiver.xceiver.
					   notifier, USB_EVENT_ID, NULL);
	}
}
static int bcmpmu_otg_xceiv_set_vbus(struct otg_transceiver *otg, bool enabled)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data = dev_get_drvdata(otg->dev);
	int stat;

	/* The order of these operations has temporarily been
	 * swapped due to overcurrent issue caused by slow I2C
	 * operations. I2C operations take >200ms to complete */
	bcm_hsotgctrl_phy_set_vbus_stat(enabled);

	if (enabled && bcmpmu_otg_xceiv_check_id_gnd(xceiv_data)) {
		dev_info(xceiv_data->dev, "Turning on VBUS\n");
		xceiv_data->vbus_enabled = true;
		stat =
		    xceiv_data->bcmpmu->usb_set(xceiv_data->bcmpmu,
						BCMPMU_USB_CTRL_VBUS_ON_OFF, 1);
	} else {
		dev_info(xceiv_data->dev, "Turning off VBUS\n");
		xceiv_data->vbus_enabled = false;
		stat =
		    xceiv_data->bcmpmu->usb_set(xceiv_data->bcmpmu,
						BCMPMU_USB_CTRL_VBUS_ON_OFF, 0);
	}

	if (stat < 0)
		dev_warn(xceiv_data->dev, "Failed to set VBUS\n");

	return stat;
}
static void bcmpmu_otg_xceiv_id_change_handler(struct work_struct *work)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data =
	    container_of(work, struct bcmpmu_otg_xceiv_data,
			 bcm_otg_id_status_change_work);
	unsigned int new_id;
	bool id_gnd = false;
	bool id_rid_a = false;
	bool id_rid_c = false;

	dev_info(xceiv_data->dev, "ID change detected\n");

	bcmpmu_usb_get(xceiv_data->bcmpmu,
		BCMPMU_USB_CTRL_GET_ID_VALUE,
		&new_id);

	if (xceiv_data->prev_otg_id != new_id) {
		id_gnd = bcmpmu_otg_xceiv_check_id_gnd(xceiv_data);
		id_rid_a = bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);
		id_rid_c = bcmpmu_otg_xceiv_check_id_rid_c(xceiv_data);

		bcm_hsotgctrl_phy_set_id_stat(!(id_gnd || id_rid_a));

		if (id_gnd) {
			/* If ID is gnd, we need to turn on
			 * Vbus within 200ms
			 */
			bcmpmu_otg_xceiv_set_vbus(&xceiv_data->otg_xceiver.
				    xceiver, true);
		}

		if (!id_rid_c)
			msleep(HOST_TO_PERIPHERAL_DELAY_MS);

		if (id_gnd || id_rid_a || id_rid_c) {
			bcm_hsotgctrl_phy_deinit();
			xceiv_data->otg_xceiver.xceiver.state =
				OTG_STATE_UNDEFINED;
			atomic_notifier_call_chain(&xceiv_data->
				otg_xceiver.xceiver.notifier,
				USB_EVENT_ID, NULL);
		}
	}

	/* Update local ID copy */
	xceiv_data->prev_otg_id = new_id;

}
static int bcmpmu_otg_xceiv_set_peripheral(struct otg_transceiver *otg,
					   struct usb_gadget *gadget)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data = dev_get_drvdata(otg->dev);
	int status = 0;
	bool id_default_host = false;

	dev_dbg(xceiv_data->dev, "Setting Peripheral\n");
	otg->gadget = gadget;

	id_default_host = bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
		  bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

	if (!id_default_host) {
		if (xceiv_data->otg_enabled &&
			(bcmpmu_otg_xceiv_check_id_rid_b(xceiv_data) ==
			    false)) { /* No SRP if RID_B */
			/* REVISIT. Shutdown uses sequence for lowest power
			 * and does not meet timing so don't do that in OTG mode
			 * for now. Just do SRP for ADP startup */
			bcmpmu_otg_xceiv_do_srp(xceiv_data);
		} else {
			int data;
			bcmpmu_usb_get(xceiv_data->bcmpmu,
				       BCMPMU_USB_CTRL_GET_USB_TYPE, &data);
			if ((data != PMU_USB_TYPE_SDP)
			    && (data != PMU_USB_TYPE_CDP)) {
				/* Shutdown the core */
				atomic_notifier_call_chain(&xceiv_data->
							   otg_xceiver.xceiver.
							   notifier,
							   USB_EVENT_NONE,
							   NULL);
			}
		}

	} else {
		bcm_hsotgctrl_phy_set_id_stat(false);
		/* Come up connected  */
		bcm_hsotgctrl_phy_set_non_driving(false);
	}

	return status;
}
static void bcmpmu_otg_xceiv_id_change_handler(struct work_struct *work)
{
    struct bcmpmu_otg_xceiv_data *xceiv_data =
        container_of(work, struct bcmpmu_otg_xceiv_data,
                     bcm_otg_id_status_change_work);
    unsigned int new_id;

    bool id_gnd = false;
    bool id_rid_a = false;
    bool id_rid_c = false;

    dev_info(xceiv_data->dev, "ID change detected\n");

    bcmpmu_usb_get(xceiv_data->bcmpmu,
                   BCMPMU_USB_CTRL_GET_ID_VALUE,
                   &new_id);

    if (xceiv_data->otg_enabled) {
        /* Stop any stale ADP probe/sense attempts */
        bcm_otg_do_adp_probe(xceiv_data, false);
        bcm_otg_do_adp_sense(xceiv_data, false);

        /* Use n and n-1 comparison method */
        bcmpmu_usb_set(xceiv_data->bcmpmu,
                       BCMPMU_USB_CTRL_SET_ADP_COMP_METHOD, 1);
    }

    if ((xceiv_data->prev_otg_id != new_id) ||
            xceiv_data->otg_enabled) {
        id_gnd = bcmpmu_otg_xceiv_check_id_gnd(xceiv_data);
        id_rid_a = bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);
        id_rid_c = bcmpmu_otg_xceiv_check_id_rid_c(xceiv_data);

        bcm_hsotgctrl_phy_set_id_stat(!(id_gnd || id_rid_a));

        atomic_notifier_call_chain(&xceiv_data->otg_xceiver.
                                   xceiver.notifier, USB_EVENT_ID, NULL);
    }

    /* Update local ID copy */
    xceiv_data->prev_otg_id = new_id;

}
static void bcmpmu_otg_xceiv_chg_detect_handler(struct work_struct *work)
{
	struct bcmpmu_otg_xceiv_data *xceiv_data =
	    container_of(work, struct bcmpmu_otg_xceiv_data,
			 bcm_otg_chg_detect_work);

	dev_info(xceiv_data->dev, "Charger detect event\n");

	if (xceiv_data->otg_enabled) {
		/* Core is already up so just set the Vbus status */
		bcm_hsotgctrl_phy_set_vbus_stat(true);
	} else {
		bool id_default_host = false;

		id_default_host =
			bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
			bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

		if (!id_default_host && xceiv_data->otg_xceiver.xceiver.gadget)
			atomic_notifier_call_chain(&xceiv_data->otg_xceiver.
						   xceiver.notifier,
						   USB_EVENT_VBUS, NULL);
	}
}
static int __devinit bcmpmu_otg_xceiv_probe(struct platform_device *pdev)
{
    int error = 0;
    struct bcmpmu_otg_xceiv_data *xceiv_data;
    struct bcmpmu *bcmpmu = pdev->dev.platform_data;

    dev_info(&pdev->dev, "Probing started...\n");

    xceiv_data = kzalloc(sizeof(*xceiv_data), GFP_KERNEL);
    if (!xceiv_data) {
        dev_warn(&pdev->dev, "Memory allocation failed\n");
        return -ENOMEM;
    }

    /* REVISIT: Currently there isn't a way to obtain
     * regulator string associated with USB. Hardcode for now
     */
    xceiv_data->bcm_hsotg_regulator =
        regulator_get(NULL, "usbldo_uc");

    if (IS_ERR(xceiv_data->bcm_hsotg_regulator)) {
        dev_warn(&pdev->dev, "Failed to get regulator handle\n");
        kfree(xceiv_data);
        return -ENODEV;
    }

    /* Enable USB LDO */
    regulator_enable(xceiv_data->bcm_hsotg_regulator);
    xceiv_data->regulator_enabled = true;
    /* Give 2ms to ramp up USBLDO */
    mdelay(USBLDO_RAMP_UP_DELAY_IN_MS);
    xceiv_data->dev = &pdev->dev;
    xceiv_data->bcmpmu = bcmpmu;
    xceiv_data->otg_xceiver.xceiver.dev = xceiv_data->dev;
    xceiv_data->otg_xceiver.xceiver.label = "bcmpmu_otg_xceiv";
    xceiv_data->host = false;
    xceiv_data->vbus_enabled = false;

    /* Create a work queue for OTG work items */
    xceiv_data->bcm_otg_work_queue = create_workqueue("bcm_otg_events");
    if (xceiv_data->bcm_otg_work_queue == NULL) {
        dev_warn(&pdev->dev,
                 "BCM OTG events work queue creation failed\n");
        bcmpmu_otg_free_regulator(xceiv_data);
        kfree(xceiv_data);
        return -ENOMEM;
    }

    /* Create one work item per deferrable function */
    INIT_WORK(&xceiv_data->bcm_otg_vbus_invalid_work,
              bcmpmu_otg_xceiv_vbus_invalid_handler);
    INIT_WORK(&xceiv_data->bcm_otg_vbus_valid_work,
              bcmpmu_otg_xceiv_vbus_valid_handler);
    INIT_WORK(&xceiv_data->bcm_otg_vbus_a_invalid_work,
              bcmpmu_otg_xceiv_vbus_a_invalid_handler);
    INIT_WORK(&xceiv_data->bcm_otg_vbus_a_valid_work,
              bcmpmu_otg_xceiv_vbus_a_valid_handler);
    INIT_WORK(&xceiv_data->bcm_otg_id_status_change_work,
              bcmpmu_otg_xceiv_id_change_handler);
    INIT_WORK(&xceiv_data->bcm_otg_chg_detect_work,
              bcmpmu_otg_xceiv_chg_detect_handler);
    INIT_WORK(&xceiv_data->bcm_otg_sess_end_srp_work,
              bcmpmu_otg_xceiv_sess_end_srp_handler);
    INIT_DELAYED_WORK(&xceiv_data->bcm_otg_delayed_adp_work,
                      bcmpmu_otg_xceiv_delayed_adp_handler);

    /* Initial value for previous OTG ID value.
     * 0 means unsupported
     */
    xceiv_data->prev_otg_id = 0;

    /* Charger type not known yet */
    xceiv_data->usb_charger_type = PMU_USB_TYPE_NONE;

    xceiv_data->otg_xceiver.xceiver.state = OTG_STATE_UNDEFINED;
    xceiv_data->otg_xceiver.xceiver.set_vbus =
        bcmpmu_otg_xceiv_set_vbus;
    xceiv_data->otg_xceiver.xceiver.set_peripheral =
        bcmpmu_otg_xceiv_set_peripheral;
    xceiv_data->otg_xceiver.xceiver.set_host =
        bcmpmu_otg_xceiv_set_host;
    xceiv_data->otg_xceiver.xceiver.shutdown =
        bcmpmu_otg_xceiv_shutdown;
    xceiv_data->otg_xceiver.xceiver.init =
        bcmpmu_otg_xceiv_start;

    xceiv_data->otg_xceiver.xceiver.set_power =
        bcmpmu_otg_xceiv_set_vbus_power;

    xceiv_data->otg_xceiver.xceiver.set_delayed_adp =
        bcmpmu_otg_xceiv_set_delayed_adp;
    xceiv_data->otg_xceiver.xceiver.set_srp_reqd =
        bcmpmu_otg_xceiv_set_srp_reqd_handler;
    xceiv_data->otg_xceiver.xceiver.pullup_on =
        bcmpmu_otg_xceiv_pullup_on;
    xceiv_data->otg_xceiver.xceiver.set_otg_enable =
        bcmpmu_otg_xceiv_set_otg_enable;
    xceiv_data->otg_xceiver.xceiver.set_suspend =
        bcmpmu_otg_xceiv_set_suspend;

    ATOMIC_INIT_NOTIFIER_HEAD(&xceiv_data->otg_xceiver.xceiver.notifier);

    xceiv_data->bcm_otg_vbus_validity_notifier.notifier_call =
        bcmpmu_otg_xceiv_vbus_notif_handler;
    bcmpmu_add_notifier(BCMPMU_USB_EVENT_VBUS_VALID,
                        &xceiv_data->bcm_otg_vbus_validity_notifier);

    xceiv_data->bcm_otg_vbus_invalidity_notifier.notifier_call =
        bcmpmu_otg_xceiv_vbus_invalid_notif_handler;
    bcmpmu_add_notifier(BCMPMU_USB_EVENT_VBUS_INVALID,
                        &xceiv_data->bcm_otg_vbus_invalidity_notifier);

    bcmpmu_add_notifier(BCMPMU_USB_EVENT_SESSION_INVALID,
                        &xceiv_data->bcm_otg_vbus_validity_notifier);

    xceiv_data->bcm_otg_id_chg_notifier.notifier_call =
        bcmpmu_otg_xceiv_id_chg_notif_handler;
    bcmpmu_add_notifier(BCMPMU_USB_EVENT_ID_CHANGE,
                        &xceiv_data->bcm_otg_id_chg_notifier);

    xceiv_data->bcm_otg_chg_detection_notifier.notifier_call =
        bcmpmu_otg_xceiv_chg_detection_notif_handler;
    bcmpmu_add_notifier(BCMPMU_USB_EVENT_USB_DETECTION,
                        &xceiv_data->bcm_otg_chg_detection_notifier);


    wake_lock_init(&xceiv_data->otg_xceiver.xceiver_wake_lock, WAKE_LOCK_SUSPEND, "otg_xcvr_wakelock");

#ifdef CONFIG_USB_OTG
    init_timer(&xceiv_data->otg_xceiver.srp_failure_timer);
    xceiv_data->otg_xceiver.srp_failure_timer.data = (unsigned long)xceiv_data;
    xceiv_data->otg_xceiver.srp_failure_timer.function = bcmpmu_otg_xceiv_srp_failure_handler;

    init_timer(&xceiv_data->otg_xceiver.sess_end_srp_timer);
    xceiv_data->otg_xceiver.sess_end_srp_timer.data = (unsigned long)xceiv_data;
    xceiv_data->otg_xceiver.sess_end_srp_timer.function = bcmpmu_otg_xceiv_sess_end_srp_timer_handler;

    error = bcm_otg_adp_init(xceiv_data);
    if (error)
        goto error_attr_host;
#endif

    otg_set_transceiver(&xceiv_data->otg_xceiver.xceiver);
    local_otg_xceiver = &xceiv_data->otg_xceiver.xceiver;

    platform_set_drvdata(pdev, xceiv_data);

    error = device_create_file(&pdev->dev, &dev_attr_host);
    if (error) {
        dev_warn(&pdev->dev, "Failed to create HOST file\n");
        goto error_attr_host;;
    }

    error = device_create_file(&pdev->dev, &dev_attr_vbus);
    if (error) {
        dev_warn(&pdev->dev, "Failed to create VBUS file\n");
        goto error_attr_vbus;
    }

    error = device_create_file(&pdev->dev, &dev_attr_wake);
    if (error) {
        dev_warn(&pdev->dev, "Failed to create WAKE file\n");
        goto error_attr_wake;
    }

    /* Save original ID value */
    bcmpmu_usb_get(xceiv_data->bcmpmu,
                   BCMPMU_USB_CTRL_GET_ID_VALUE,
                   &xceiv_data->prev_otg_id);

    /* Check if we should default to A-device */
    xceiv_data->otg_xceiver.xceiver.default_a =
        bcmpmu_otg_xceiv_check_id_gnd(xceiv_data) ||
        bcmpmu_otg_xceiv_check_id_rid_a(xceiv_data);

    bcmpmu_otg_xceiv_set_def_state(xceiv_data,
                                   xceiv_data->otg_xceiver.xceiver.default_a);

    pm_runtime_set_active(&pdev->dev);
    pm_runtime_enable(&pdev->dev);

    dev_info(&pdev->dev, "Probing successful\n");

    return 0;

error_attr_wake:
    device_remove_file(xceiv_data->dev, &dev_attr_vbus);

error_attr_vbus:
    device_remove_file(xceiv_data->dev, &dev_attr_host);

error_attr_host:
    wake_lock_destroy(&xceiv_data->otg_xceiver.xceiver_wake_lock);
    destroy_workqueue(xceiv_data->bcm_otg_work_queue);
    bcmpmu_otg_free_regulator(xceiv_data);
    kfree(xceiv_data);
    return error;
}