+static void rockchip_usb2phy_otg_sm_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port,
+ otg_sm_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ static unsigned int cable;
+ unsigned long delay;
+ bool sch_work;
+
+ if (rport->utmi_avalid)
+ rport->vbus_attached =
+ property_enabled(rphy, &rport->port_cfg->utmi_avalid);
+ else
+ rport->vbus_attached =
+ property_enabled(rphy, &rport->port_cfg->utmi_bvalid);
+
+ sch_work = false;
+ delay = OTG_SCHEDULE_DELAY;
+
+ dev_dbg(&rport->phy->dev, "%s otg sm work\n",
+ usb_otg_state_string(rport->state));
+
+ switch (rport->state) {
+ case OTG_STATE_UNDEFINED:
+ rport->state = OTG_STATE_B_IDLE;
+ if (!rport->vbus_attached)
+ rockchip_usb2phy_power_off(rport->phy);
+ /* fall through */
+ case OTG_STATE_B_IDLE:
+ if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) > 0 ||
+ extcon_get_cable_state_(rphy->edev,
+ EXTCON_USB_VBUS_EN) > 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host connect\n");
+ rport->state = OTG_STATE_A_HOST;
+ rockchip_usb2phy_power_on(rport->phy);
+ return;
+ } else if (rport->vbus_attached) {
+ dev_dbg(&rport->phy->dev, "vbus_attach\n");
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ schedule_delayed_work(&rport->chg_work, 0);
+ return;
+ case USB_CHG_STATE_DETECTED:
+ switch (rphy->chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ dev_dbg(&rport->phy->dev,
+ "sdp cable is connecetd\n");
+ wake_lock(&rport->wakelock);
+ cable = EXTCON_CHG_USB_SDP;
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ rport->perip_connected = true;
+ sch_work = true;
+ break;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ dev_dbg(&rport->phy->dev,
+ "dcp cable is connecetd\n");
+ cable = EXTCON_CHG_USB_DCP;
+ rockchip_usb2phy_power_off(rport->phy);
+ sch_work = true;
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ dev_dbg(&rport->phy->dev,
+ "cdp cable is connecetd\n");
+ wake_lock(&rport->wakelock);
+ cable = EXTCON_CHG_USB_CDP;
+ rockchip_usb2phy_power_on(rport->phy);
+ rport->state = OTG_STATE_B_PERIPHERAL;
+ rport->perip_connected = true;
+ sch_work = true;
+ break;
+ case POWER_SUPPLY_TYPE_USB_FLOATING:
+ dev_dbg(&rport->phy->dev,
+ "floating cable is connecetd\n");
+ cable = EXTCON_CHG_USB_DCP;
+ rockchip_usb2phy_power_off(rport->phy);
+ sch_work = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (!rport->vbus_attached) {
+ dev_dbg(&rport->phy->dev, "usb disconnect\n");
+ rphy->chg_state = USB_CHG_STATE_UNDEFINED;
+ rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ rport->state = OTG_STATE_B_IDLE;
+ rport->perip_connected = false;
+ delay = 0;
+ rockchip_usb2phy_power_off(rport->phy);
+ wake_unlock(&rport->wakelock);
+ } else {
+ sch_work = true;
+ }
+ break;
+ case OTG_STATE_A_HOST:
+ if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) == 0) {
+ dev_dbg(&rport->phy->dev, "usb otg host disconnect\n");
+ rport->state = OTG_STATE_B_IDLE;
+ rockchip_usb2phy_power_off(rport->phy);
+ }
+ return;
+ default:
+ return;
+ }
+
+ if (extcon_get_state(rphy->edev, cable) != rport->vbus_attached)
+ extcon_set_cable_state_(rphy->edev,
+ cable, rport->vbus_attached);
+
+ if (rphy->edev_self &&
+ (extcon_get_state(rphy->edev, EXTCON_USB) !=
+ rport->perip_connected))
+ extcon_set_cable_state_(rphy->edev,
+ EXTCON_USB,
+ rport->perip_connected);
+
+ if (sch_work)
+ schedule_delayed_work(&rport->otg_sm_work, delay);
+}
+
+static const char *chg_to_string(enum power_supply_type chg_type)
+{
+ switch (chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ return "USB_SDP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ return "USB_DCP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ return "USB_CDP_CHARGER";
+ case POWER_SUPPLY_TYPE_USB_FLOATING:
+ return "USB_FLOATING_CHARGER";
+ default:
+ return "INVALID_CHARGER";
+ }
+}
+
+static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en);
+ property_enable(rphy, &rphy->phy_cfg->chg_det.idp_src_en, en);
+}
+
+static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy, &rphy->phy_cfg->chg_det.vdp_src_en, en);
+ property_enable(rphy, &rphy->phy_cfg->chg_det.idm_sink_en, en);
+}
+
+static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy,
+ bool en)
+{
+ property_enable(rphy, &rphy->phy_cfg->chg_det.vdm_src_en, en);
+ property_enable(rphy, &rphy->phy_cfg->chg_det.idp_sink_en, en);
+}
+
+#define CHG_DCD_POLL_TIME (100 * HZ / 1000)
+#define CHG_DCD_MAX_RETRIES 6
+#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000)
+#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000)
+static void rockchip_chg_detect_work(struct work_struct *work)
+{
+ struct rockchip_usb2phy_port *rport =
+ container_of(work, struct rockchip_usb2phy_port, chg_work.work);
+ struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
+ bool is_dcd, tmout, vout;
+ unsigned long delay;
+
+ dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",
+ rphy->chg_state);
+ switch (rphy->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ if (!rport->suspended)
+ rockchip_usb2phy_power_off(rport->phy);
+ /* put the controller in non-driving mode */
+ property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, false);
+ /* Start DCD processing stage 1 */
+ rockchip_chg_enable_dcd(rphy, true);
+ rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
+ rphy->dcd_retries = 0;
+ rphy->primary_retries = 0;
+ delay = CHG_DCD_POLL_TIME;
+ break;
+ case USB_CHG_STATE_WAIT_FOR_DCD:
+ /* get data contact detection status */
+ is_dcd = property_enabled(rphy, &rphy->phy_cfg->chg_det.dp_det);
+ tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES;
+ /* stage 2 */
+ if (is_dcd || tmout) {
+ /* stage 4 */
+ /* Turn off DCD circuitry */
+ rockchip_chg_enable_dcd(rphy, false);
+ /* Voltage Source on DP, Probe on DM */
+ rockchip_chg_enable_primary_det(rphy, true);
+ delay = CHG_PRIMARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_DCD_DONE;
+ } else {
+ /* stage 3 */
+ delay = CHG_DCD_POLL_TIME;
+ }
+ break;
+ case USB_CHG_STATE_DCD_DONE:
+ vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.cp_det);
+ rockchip_chg_enable_primary_det(rphy, false);
+ if (vout) {
+ /* Voltage Source on DM, Probe on DP */
+ rockchip_chg_enable_secondary_det(rphy, true);
+ delay = CHG_SECONDARY_DET_TIME;
+ rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE;
+ } else {
+ if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) {
+ /* floating charger found */
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_FLOATING;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ } else {
+ if (rphy->primary_retries < 2) {
+ /* Turn off DCD circuitry */
+ rockchip_chg_enable_dcd(rphy, false);
+ /* Voltage Source on DP, Probe on DM */
+ rockchip_chg_enable_primary_det(rphy,
+ true);
+ delay = CHG_PRIMARY_DET_TIME;
+ rphy->chg_state =
+ USB_CHG_STATE_DCD_DONE;
+ rphy->primary_retries++;
+ /* break USB_CHG_STATE_DCD_DONE */
+ break;
+ }
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB;
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ }
+ }
+ break;
+ case USB_CHG_STATE_PRIMARY_DONE:
+ vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.dcp_det);
+ /* Turn off voltage source */
+ rockchip_chg_enable_secondary_det(rphy, false);
+ if (vout)
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ else
+ rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP;
+ /* fall through */
+ case USB_CHG_STATE_SECONDARY_DONE:
+ rphy->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ /* fall through */
+ case USB_CHG_STATE_DETECTED:
+ /* put the controller in normal mode */
+ property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, true);
+ rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work);
+ dev_info(&rport->phy->dev, "charger = %s\n",
+ chg_to_string(rphy->chg_type));
+ return;
+ default:
+ return;
+ }
+
+ schedule_delayed_work(&rport->chg_work, delay);
+}
+