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; }