18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * otg.c - ChipIdea USB IP core OTG driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Freescale Semiconductor, Inc.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Peter Chen
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci/*
118c2ecf20Sopenharmony_ci * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
128c2ecf20Sopenharmony_ci * are also included.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/usb/otg.h>
168c2ecf20Sopenharmony_ci#include <linux/usb/gadget.h>
178c2ecf20Sopenharmony_ci#include <linux/usb/chipidea.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include "ci.h"
208c2ecf20Sopenharmony_ci#include "bits.h"
218c2ecf20Sopenharmony_ci#include "otg.h"
228c2ecf20Sopenharmony_ci#include "otg_fsm.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/**
258c2ecf20Sopenharmony_ci * hw_read_otgsc returns otgsc register bits value.
268c2ecf20Sopenharmony_ci * @ci: the controller
278c2ecf20Sopenharmony_ci * @mask: bitfield mask
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ciu32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	struct ci_hdrc_cable *cable;
328c2ecf20Sopenharmony_ci	u32 val = hw_read(ci, OP_OTGSC, mask);
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	/*
358c2ecf20Sopenharmony_ci	 * If using extcon framework for VBUS and/or ID signal
368c2ecf20Sopenharmony_ci	 * detection overwrite OTGSC register value
378c2ecf20Sopenharmony_ci	 */
388c2ecf20Sopenharmony_ci	cable = &ci->platdata->vbus_extcon;
398c2ecf20Sopenharmony_ci	if (!IS_ERR(cable->edev) || ci->role_switch) {
408c2ecf20Sopenharmony_ci		if (cable->changed)
418c2ecf20Sopenharmony_ci			val |= OTGSC_BSVIS;
428c2ecf20Sopenharmony_ci		else
438c2ecf20Sopenharmony_ci			val &= ~OTGSC_BSVIS;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci		if (cable->connected)
468c2ecf20Sopenharmony_ci			val |= OTGSC_BSV;
478c2ecf20Sopenharmony_ci		else
488c2ecf20Sopenharmony_ci			val &= ~OTGSC_BSV;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci		if (cable->enabled)
518c2ecf20Sopenharmony_ci			val |= OTGSC_BSVIE;
528c2ecf20Sopenharmony_ci		else
538c2ecf20Sopenharmony_ci			val &= ~OTGSC_BSVIE;
548c2ecf20Sopenharmony_ci	}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	cable = &ci->platdata->id_extcon;
578c2ecf20Sopenharmony_ci	if (!IS_ERR(cable->edev) || ci->role_switch) {
588c2ecf20Sopenharmony_ci		if (cable->changed)
598c2ecf20Sopenharmony_ci			val |= OTGSC_IDIS;
608c2ecf20Sopenharmony_ci		else
618c2ecf20Sopenharmony_ci			val &= ~OTGSC_IDIS;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci		if (cable->connected)
648c2ecf20Sopenharmony_ci			val &= ~OTGSC_ID; /* host */
658c2ecf20Sopenharmony_ci		else
668c2ecf20Sopenharmony_ci			val |= OTGSC_ID; /* device */
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci		if (cable->enabled)
698c2ecf20Sopenharmony_ci			val |= OTGSC_IDIE;
708c2ecf20Sopenharmony_ci		else
718c2ecf20Sopenharmony_ci			val &= ~OTGSC_IDIE;
728c2ecf20Sopenharmony_ci	}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	return val & mask;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/**
788c2ecf20Sopenharmony_ci * hw_write_otgsc updates target bits of OTGSC register.
798c2ecf20Sopenharmony_ci * @ci: the controller
808c2ecf20Sopenharmony_ci * @mask: bitfield mask
818c2ecf20Sopenharmony_ci * @data: to be written
828c2ecf20Sopenharmony_ci */
838c2ecf20Sopenharmony_civoid hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	struct ci_hdrc_cable *cable;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	cable = &ci->platdata->vbus_extcon;
888c2ecf20Sopenharmony_ci	if (!IS_ERR(cable->edev) || ci->role_switch) {
898c2ecf20Sopenharmony_ci		if (data & mask & OTGSC_BSVIS)
908c2ecf20Sopenharmony_ci			cable->changed = false;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci		/* Don't enable vbus interrupt if using external notifier */
938c2ecf20Sopenharmony_ci		if (data & mask & OTGSC_BSVIE) {
948c2ecf20Sopenharmony_ci			cable->enabled = true;
958c2ecf20Sopenharmony_ci			data &= ~OTGSC_BSVIE;
968c2ecf20Sopenharmony_ci		} else if (mask & OTGSC_BSVIE) {
978c2ecf20Sopenharmony_ci			cable->enabled = false;
988c2ecf20Sopenharmony_ci		}
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	cable = &ci->platdata->id_extcon;
1028c2ecf20Sopenharmony_ci	if (!IS_ERR(cable->edev) || ci->role_switch) {
1038c2ecf20Sopenharmony_ci		if (data & mask & OTGSC_IDIS)
1048c2ecf20Sopenharmony_ci			cable->changed = false;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci		/* Don't enable id interrupt if using external notifier */
1078c2ecf20Sopenharmony_ci		if (data & mask & OTGSC_IDIE) {
1088c2ecf20Sopenharmony_ci			cable->enabled = true;
1098c2ecf20Sopenharmony_ci			data &= ~OTGSC_IDIE;
1108c2ecf20Sopenharmony_ci		} else if (mask & OTGSC_IDIE) {
1118c2ecf20Sopenharmony_ci			cable->enabled = false;
1128c2ecf20Sopenharmony_ci		}
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/**
1198c2ecf20Sopenharmony_ci * ci_otg_role - pick role based on ID pin state
1208c2ecf20Sopenharmony_ci * @ci: the controller
1218c2ecf20Sopenharmony_ci */
1228c2ecf20Sopenharmony_cienum ci_role ci_otg_role(struct ci_hdrc *ci)
1238c2ecf20Sopenharmony_ci{
1248c2ecf20Sopenharmony_ci	enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
1258c2ecf20Sopenharmony_ci		? CI_ROLE_GADGET
1268c2ecf20Sopenharmony_ci		: CI_ROLE_HOST;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	return role;
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_civoid ci_handle_vbus_change(struct ci_hdrc *ci)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	if (!ci->is_otg)
1348c2ecf20Sopenharmony_ci		return;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
1378c2ecf20Sopenharmony_ci		usb_gadget_vbus_connect(&ci->gadget);
1388c2ecf20Sopenharmony_ci	else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active)
1398c2ecf20Sopenharmony_ci		usb_gadget_vbus_disconnect(&ci->gadget);
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci/**
1438c2ecf20Sopenharmony_ci * When we switch to device mode, the vbus value should be lower
1448c2ecf20Sopenharmony_ci * than OTGSC_BSV before connecting to host.
1458c2ecf20Sopenharmony_ci *
1468c2ecf20Sopenharmony_ci * @ci: the controller
1478c2ecf20Sopenharmony_ci *
1488c2ecf20Sopenharmony_ci * This function returns an error code if timeout
1498c2ecf20Sopenharmony_ci */
1508c2ecf20Sopenharmony_cistatic int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	unsigned long elapse = jiffies + msecs_to_jiffies(5000);
1538c2ecf20Sopenharmony_ci	u32 mask = OTGSC_BSV;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	while (hw_read_otgsc(ci, mask)) {
1568c2ecf20Sopenharmony_ci		if (time_after(jiffies, elapse)) {
1578c2ecf20Sopenharmony_ci			dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n",
1588c2ecf20Sopenharmony_ci					mask);
1598c2ecf20Sopenharmony_ci			return -ETIMEDOUT;
1608c2ecf20Sopenharmony_ci		}
1618c2ecf20Sopenharmony_ci		msleep(20);
1628c2ecf20Sopenharmony_ci	}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	return 0;
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic void ci_handle_id_switch(struct ci_hdrc *ci)
1688c2ecf20Sopenharmony_ci{
1698c2ecf20Sopenharmony_ci	enum ci_role role;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	mutex_lock(&ci->mutex);
1728c2ecf20Sopenharmony_ci	role = ci_otg_role(ci);
1738c2ecf20Sopenharmony_ci	if (role != ci->role) {
1748c2ecf20Sopenharmony_ci		dev_dbg(ci->dev, "switching from %s to %s\n",
1758c2ecf20Sopenharmony_ci			ci_role(ci)->name, ci->roles[role]->name);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci		if (ci->vbus_active && ci->role == CI_ROLE_GADGET)
1788c2ecf20Sopenharmony_ci			/*
1798c2ecf20Sopenharmony_ci			 * vbus disconnect event is lost due to role
1808c2ecf20Sopenharmony_ci			 * switch occurs during system suspend.
1818c2ecf20Sopenharmony_ci			 */
1828c2ecf20Sopenharmony_ci			usb_gadget_vbus_disconnect(&ci->gadget);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci		ci_role_stop(ci);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci		if (role == CI_ROLE_GADGET &&
1878c2ecf20Sopenharmony_ci				IS_ERR(ci->platdata->vbus_extcon.edev))
1888c2ecf20Sopenharmony_ci			/*
1898c2ecf20Sopenharmony_ci			 * Wait vbus lower than OTGSC_BSV before connecting
1908c2ecf20Sopenharmony_ci			 * to host. If connecting status is from an external
1918c2ecf20Sopenharmony_ci			 * connector instead of register, we don't need to
1928c2ecf20Sopenharmony_ci			 * care vbus on the board, since it will not affect
1938c2ecf20Sopenharmony_ci			 * external connector status.
1948c2ecf20Sopenharmony_ci			 */
1958c2ecf20Sopenharmony_ci			hw_wait_vbus_lower_bsv(ci);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		ci_role_start(ci, role);
1988c2ecf20Sopenharmony_ci		/* vbus change may have already occurred */
1998c2ecf20Sopenharmony_ci		if (role == CI_ROLE_GADGET)
2008c2ecf20Sopenharmony_ci			ci_handle_vbus_change(ci);
2018c2ecf20Sopenharmony_ci	}
2028c2ecf20Sopenharmony_ci	mutex_unlock(&ci->mutex);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci/**
2058c2ecf20Sopenharmony_ci * ci_otg_work - perform otg (vbus/id) event handle
2068c2ecf20Sopenharmony_ci * @work: work struct
2078c2ecf20Sopenharmony_ci */
2088c2ecf20Sopenharmony_cistatic void ci_otg_work(struct work_struct *work)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
2138c2ecf20Sopenharmony_ci		enable_irq(ci->irq);
2148c2ecf20Sopenharmony_ci		return;
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	pm_runtime_get_sync(ci->dev);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	if (ci->id_event) {
2208c2ecf20Sopenharmony_ci		ci->id_event = false;
2218c2ecf20Sopenharmony_ci		ci_handle_id_switch(ci);
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	if (ci->b_sess_valid_event) {
2258c2ecf20Sopenharmony_ci		ci->b_sess_valid_event = false;
2268c2ecf20Sopenharmony_ci		ci_handle_vbus_change(ci);
2278c2ecf20Sopenharmony_ci	}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	pm_runtime_put_sync(ci->dev);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	enable_irq(ci->irq);
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci/**
2368c2ecf20Sopenharmony_ci * ci_hdrc_otg_init - initialize otg struct
2378c2ecf20Sopenharmony_ci * @ci: the controller
2388c2ecf20Sopenharmony_ci */
2398c2ecf20Sopenharmony_ciint ci_hdrc_otg_init(struct ci_hdrc *ci)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	INIT_WORK(&ci->work, ci_otg_work);
2428c2ecf20Sopenharmony_ci	ci->wq = create_freezable_workqueue("ci_otg");
2438c2ecf20Sopenharmony_ci	if (!ci->wq) {
2448c2ecf20Sopenharmony_ci		dev_err(ci->dev, "can't create workqueue\n");
2458c2ecf20Sopenharmony_ci		return -ENODEV;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	if (ci_otg_is_fsm_mode(ci))
2498c2ecf20Sopenharmony_ci		return ci_hdrc_otg_fsm_init(ci);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	return 0;
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci/**
2558c2ecf20Sopenharmony_ci * ci_hdrc_otg_destroy - destroy otg struct
2568c2ecf20Sopenharmony_ci * @ci: the controller
2578c2ecf20Sopenharmony_ci */
2588c2ecf20Sopenharmony_civoid ci_hdrc_otg_destroy(struct ci_hdrc *ci)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	if (ci->wq) {
2618c2ecf20Sopenharmony_ci		flush_workqueue(ci->wq);
2628c2ecf20Sopenharmony_ci		destroy_workqueue(ci->wq);
2638c2ecf20Sopenharmony_ci	}
2648c2ecf20Sopenharmony_ci	/* Disable all OTG irq and clear status */
2658c2ecf20Sopenharmony_ci	hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
2668c2ecf20Sopenharmony_ci						OTGSC_INT_STATUS_BITS);
2678c2ecf20Sopenharmony_ci	if (ci_otg_is_fsm_mode(ci))
2688c2ecf20Sopenharmony_ci		ci_hdrc_otg_fsm_remove(ci);
2698c2ecf20Sopenharmony_ci}
270