162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Cadence USBSS and USBSSP DRD Driver.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2018-2019 Cadence.
662306a36Sopenharmony_ci * Copyright (C) 2017-2018 NXP
762306a36Sopenharmony_ci * Copyright (C) 2019 Texas Instruments
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Author: Peter Chen <peter.chen@nxp.com>
1062306a36Sopenharmony_ci *         Pawel Laszczak <pawell@cadence.com>
1162306a36Sopenharmony_ci *         Roger Quadros <rogerq@ti.com>
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/of.h>
1862306a36Sopenharmony_ci#include <linux/platform_device.h>
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/io.h>
2162306a36Sopenharmony_ci#include <linux/pm_runtime.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "core.h"
2462306a36Sopenharmony_ci#include "host-export.h"
2562306a36Sopenharmony_ci#include "drd.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int cdns_idle_init(struct cdns *cdns);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic int cdns_role_start(struct cdns *cdns, enum usb_role role)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	int ret;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (WARN_ON(role > USB_ROLE_DEVICE))
3462306a36Sopenharmony_ci		return 0;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	mutex_lock(&cdns->mutex);
3762306a36Sopenharmony_ci	cdns->role = role;
3862306a36Sopenharmony_ci	mutex_unlock(&cdns->mutex);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (!cdns->roles[role])
4162306a36Sopenharmony_ci		return -ENXIO;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (cdns->roles[role]->state == CDNS_ROLE_STATE_ACTIVE)
4462306a36Sopenharmony_ci		return 0;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	mutex_lock(&cdns->mutex);
4762306a36Sopenharmony_ci	ret = cdns->roles[role]->start(cdns);
4862306a36Sopenharmony_ci	if (!ret)
4962306a36Sopenharmony_ci		cdns->roles[role]->state = CDNS_ROLE_STATE_ACTIVE;
5062306a36Sopenharmony_ci	mutex_unlock(&cdns->mutex);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	return ret;
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic void cdns_role_stop(struct cdns *cdns)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	enum usb_role role = cdns->role;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (WARN_ON(role > USB_ROLE_DEVICE))
6062306a36Sopenharmony_ci		return;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (cdns->roles[role]->state == CDNS_ROLE_STATE_INACTIVE)
6362306a36Sopenharmony_ci		return;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	mutex_lock(&cdns->mutex);
6662306a36Sopenharmony_ci	cdns->roles[role]->stop(cdns);
6762306a36Sopenharmony_ci	cdns->roles[role]->state = CDNS_ROLE_STATE_INACTIVE;
6862306a36Sopenharmony_ci	mutex_unlock(&cdns->mutex);
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistatic void cdns_exit_roles(struct cdns *cdns)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	cdns_role_stop(cdns);
7462306a36Sopenharmony_ci	cdns_drd_exit(cdns);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/**
7862306a36Sopenharmony_ci * cdns_core_init_role - initialize role of operation
7962306a36Sopenharmony_ci * @cdns: Pointer to cdns structure
8062306a36Sopenharmony_ci *
8162306a36Sopenharmony_ci * Returns 0 on success otherwise negative errno
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_cistatic int cdns_core_init_role(struct cdns *cdns)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct device *dev = cdns->dev;
8662306a36Sopenharmony_ci	enum usb_dr_mode best_dr_mode;
8762306a36Sopenharmony_ci	enum usb_dr_mode dr_mode;
8862306a36Sopenharmony_ci	int ret;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	dr_mode = usb_get_dr_mode(dev);
9162306a36Sopenharmony_ci	cdns->role = USB_ROLE_NONE;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	/*
9462306a36Sopenharmony_ci	 * If driver can't read mode by means of usb_get_dr_mode function then
9562306a36Sopenharmony_ci	 * chooses mode according with Kernel configuration. This setting
9662306a36Sopenharmony_ci	 * can be restricted later depending on strap pin configuration.
9762306a36Sopenharmony_ci	 */
9862306a36Sopenharmony_ci	if (dr_mode == USB_DR_MODE_UNKNOWN) {
9962306a36Sopenharmony_ci		if (cdns->version == CDNSP_CONTROLLER_V2) {
10062306a36Sopenharmony_ci			if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) &&
10162306a36Sopenharmony_ci			    IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
10262306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_OTG;
10362306a36Sopenharmony_ci			else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST))
10462306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_HOST;
10562306a36Sopenharmony_ci			else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
10662306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_PERIPHERAL;
10762306a36Sopenharmony_ci		} else {
10862306a36Sopenharmony_ci			if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
10962306a36Sopenharmony_ci			    IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
11062306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_OTG;
11162306a36Sopenharmony_ci			else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
11262306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_HOST;
11362306a36Sopenharmony_ci			else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
11462306a36Sopenharmony_ci				dr_mode = USB_DR_MODE_PERIPHERAL;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * At this point cdns->dr_mode contains strap configuration.
12062306a36Sopenharmony_ci	 * Driver try update this setting considering kernel configuration
12162306a36Sopenharmony_ci	 */
12262306a36Sopenharmony_ci	best_dr_mode = cdns->dr_mode;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	ret = cdns_idle_init(cdns);
12562306a36Sopenharmony_ci	if (ret)
12662306a36Sopenharmony_ci		return ret;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (dr_mode == USB_DR_MODE_OTG) {
12962306a36Sopenharmony_ci		best_dr_mode = cdns->dr_mode;
13062306a36Sopenharmony_ci	} else if (cdns->dr_mode == USB_DR_MODE_OTG) {
13162306a36Sopenharmony_ci		best_dr_mode = dr_mode;
13262306a36Sopenharmony_ci	} else if (cdns->dr_mode != dr_mode) {
13362306a36Sopenharmony_ci		dev_err(dev, "Incorrect DRD configuration\n");
13462306a36Sopenharmony_ci		return -EINVAL;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	dr_mode = best_dr_mode;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
14062306a36Sopenharmony_ci		if ((cdns->version == CDNSP_CONTROLLER_V2 &&
14162306a36Sopenharmony_ci		     IS_ENABLED(CONFIG_USB_CDNSP_HOST)) ||
14262306a36Sopenharmony_ci		    (cdns->version < CDNSP_CONTROLLER_V2 &&
14362306a36Sopenharmony_ci		     IS_ENABLED(CONFIG_USB_CDNS3_HOST)))
14462306a36Sopenharmony_ci			ret = cdns_host_init(cdns);
14562306a36Sopenharmony_ci		else
14662306a36Sopenharmony_ci			ret = -ENXIO;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci		if (ret) {
14962306a36Sopenharmony_ci			dev_err(dev, "Host initialization failed with %d\n",
15062306a36Sopenharmony_ci				ret);
15162306a36Sopenharmony_ci			goto err;
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
15662306a36Sopenharmony_ci		if (cdns->gadget_init)
15762306a36Sopenharmony_ci			ret = cdns->gadget_init(cdns);
15862306a36Sopenharmony_ci		else
15962306a36Sopenharmony_ci			ret = -ENXIO;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		if (ret) {
16262306a36Sopenharmony_ci			dev_err(dev, "Device initialization failed with %d\n",
16362306a36Sopenharmony_ci				ret);
16462306a36Sopenharmony_ci			goto err;
16562306a36Sopenharmony_ci		}
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	cdns->dr_mode = dr_mode;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	ret = cdns_drd_update_mode(cdns);
17162306a36Sopenharmony_ci	if (ret)
17262306a36Sopenharmony_ci		goto err;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* Initialize idle role to start with */
17562306a36Sopenharmony_ci	ret = cdns_role_start(cdns, USB_ROLE_NONE);
17662306a36Sopenharmony_ci	if (ret)
17762306a36Sopenharmony_ci		goto err;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	switch (cdns->dr_mode) {
18062306a36Sopenharmony_ci	case USB_DR_MODE_OTG:
18162306a36Sopenharmony_ci		ret = cdns_hw_role_switch(cdns);
18262306a36Sopenharmony_ci		if (ret)
18362306a36Sopenharmony_ci			goto err;
18462306a36Sopenharmony_ci		break;
18562306a36Sopenharmony_ci	case USB_DR_MODE_PERIPHERAL:
18662306a36Sopenharmony_ci		ret = cdns_role_start(cdns, USB_ROLE_DEVICE);
18762306a36Sopenharmony_ci		if (ret)
18862306a36Sopenharmony_ci			goto err;
18962306a36Sopenharmony_ci		break;
19062306a36Sopenharmony_ci	case USB_DR_MODE_HOST:
19162306a36Sopenharmony_ci		ret = cdns_role_start(cdns, USB_ROLE_HOST);
19262306a36Sopenharmony_ci		if (ret)
19362306a36Sopenharmony_ci			goto err;
19462306a36Sopenharmony_ci		break;
19562306a36Sopenharmony_ci	default:
19662306a36Sopenharmony_ci		ret = -EINVAL;
19762306a36Sopenharmony_ci		goto err;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_cierr:
20262306a36Sopenharmony_ci	cdns_exit_roles(cdns);
20362306a36Sopenharmony_ci	return ret;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci/**
20762306a36Sopenharmony_ci * cdns_hw_role_state_machine  - role switch state machine based on hw events.
20862306a36Sopenharmony_ci * @cdns: Pointer to controller structure.
20962306a36Sopenharmony_ci *
21062306a36Sopenharmony_ci * Returns next role to be entered based on hw events.
21162306a36Sopenharmony_ci */
21262306a36Sopenharmony_cistatic enum usb_role cdns_hw_role_state_machine(struct cdns *cdns)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	enum usb_role role = USB_ROLE_NONE;
21562306a36Sopenharmony_ci	int id, vbus;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (cdns->dr_mode != USB_DR_MODE_OTG) {
21862306a36Sopenharmony_ci		if (cdns_is_host(cdns))
21962306a36Sopenharmony_ci			role = USB_ROLE_HOST;
22062306a36Sopenharmony_ci		if (cdns_is_device(cdns))
22162306a36Sopenharmony_ci			role = USB_ROLE_DEVICE;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		return role;
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	id = cdns_get_id(cdns);
22762306a36Sopenharmony_ci	vbus = cdns_get_vbus(cdns);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	/*
23062306a36Sopenharmony_ci	 * Role change state machine
23162306a36Sopenharmony_ci	 * Inputs: ID, VBUS
23262306a36Sopenharmony_ci	 * Previous state: cdns->role
23362306a36Sopenharmony_ci	 * Next state: role
23462306a36Sopenharmony_ci	 */
23562306a36Sopenharmony_ci	role = cdns->role;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	switch (role) {
23862306a36Sopenharmony_ci	case USB_ROLE_NONE:
23962306a36Sopenharmony_ci		/*
24062306a36Sopenharmony_ci		 * Driver treats USB_ROLE_NONE synonymous to IDLE state from
24162306a36Sopenharmony_ci		 * controller specification.
24262306a36Sopenharmony_ci		 */
24362306a36Sopenharmony_ci		if (!id)
24462306a36Sopenharmony_ci			role = USB_ROLE_HOST;
24562306a36Sopenharmony_ci		else if (vbus)
24662306a36Sopenharmony_ci			role = USB_ROLE_DEVICE;
24762306a36Sopenharmony_ci		break;
24862306a36Sopenharmony_ci	case USB_ROLE_HOST: /* from HOST, we can only change to NONE */
24962306a36Sopenharmony_ci		if (id)
25062306a36Sopenharmony_ci			role = USB_ROLE_NONE;
25162306a36Sopenharmony_ci		break;
25262306a36Sopenharmony_ci	case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/
25362306a36Sopenharmony_ci		if (!vbus)
25462306a36Sopenharmony_ci			role = USB_ROLE_NONE;
25562306a36Sopenharmony_ci		break;
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return role;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic int cdns_idle_role_start(struct cdns *cdns)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	return 0;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic void cdns_idle_role_stop(struct cdns *cdns)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	/* Program Lane swap and bring PHY out of RESET */
27162306a36Sopenharmony_ci	phy_reset(cdns->usb3_phy);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic int cdns_idle_init(struct cdns *cdns)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct cdns_role_driver *rdrv;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
27962306a36Sopenharmony_ci	if (!rdrv)
28062306a36Sopenharmony_ci		return -ENOMEM;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	rdrv->start = cdns_idle_role_start;
28362306a36Sopenharmony_ci	rdrv->stop = cdns_idle_role_stop;
28462306a36Sopenharmony_ci	rdrv->state = CDNS_ROLE_STATE_INACTIVE;
28562306a36Sopenharmony_ci	rdrv->suspend = NULL;
28662306a36Sopenharmony_ci	rdrv->resume = NULL;
28762306a36Sopenharmony_ci	rdrv->name = "idle";
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	cdns->roles[USB_ROLE_NONE] = rdrv;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return 0;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci/**
29562306a36Sopenharmony_ci * cdns_hw_role_switch - switch roles based on HW state
29662306a36Sopenharmony_ci * @cdns: controller
29762306a36Sopenharmony_ci */
29862306a36Sopenharmony_ciint cdns_hw_role_switch(struct cdns *cdns)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	enum usb_role real_role, current_role;
30162306a36Sopenharmony_ci	int ret = 0;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	/* Depends on role switch class */
30462306a36Sopenharmony_ci	if (cdns->role_sw)
30562306a36Sopenharmony_ci		return 0;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	pm_runtime_get_sync(cdns->dev);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	current_role = cdns->role;
31062306a36Sopenharmony_ci	real_role = cdns_hw_role_state_machine(cdns);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* Do nothing if nothing changed */
31362306a36Sopenharmony_ci	if (current_role == real_role)
31462306a36Sopenharmony_ci		goto exit;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	cdns_role_stop(cdns);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	ret = cdns_role_start(cdns, real_role);
32162306a36Sopenharmony_ci	if (ret) {
32262306a36Sopenharmony_ci		/* Back to current role */
32362306a36Sopenharmony_ci		dev_err(cdns->dev, "set %d has failed, back to %d\n",
32462306a36Sopenharmony_ci			real_role, current_role);
32562306a36Sopenharmony_ci		ret = cdns_role_start(cdns, current_role);
32662306a36Sopenharmony_ci		if (ret)
32762306a36Sopenharmony_ci			dev_err(cdns->dev, "back to %d failed too\n",
32862306a36Sopenharmony_ci				current_role);
32962306a36Sopenharmony_ci	}
33062306a36Sopenharmony_ciexit:
33162306a36Sopenharmony_ci	pm_runtime_put_sync(cdns->dev);
33262306a36Sopenharmony_ci	return ret;
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci/**
33662306a36Sopenharmony_ci * cdns_role_get - get current role of controller.
33762306a36Sopenharmony_ci *
33862306a36Sopenharmony_ci * @sw: pointer to USB role switch structure
33962306a36Sopenharmony_ci *
34062306a36Sopenharmony_ci * Returns role
34162306a36Sopenharmony_ci */
34262306a36Sopenharmony_cistatic enum usb_role cdns_role_get(struct usb_role_switch *sw)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct cdns *cdns = usb_role_switch_get_drvdata(sw);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci	return cdns->role;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci/**
35062306a36Sopenharmony_ci * cdns_role_set - set current role of controller.
35162306a36Sopenharmony_ci *
35262306a36Sopenharmony_ci * @sw: pointer to USB role switch structure
35362306a36Sopenharmony_ci * @role: the previous role
35462306a36Sopenharmony_ci * Handles below events:
35562306a36Sopenharmony_ci * - Role switch for dual-role devices
35662306a36Sopenharmony_ci * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices
35762306a36Sopenharmony_ci */
35862306a36Sopenharmony_cistatic int cdns_role_set(struct usb_role_switch *sw, enum usb_role role)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct cdns *cdns = usb_role_switch_get_drvdata(sw);
36162306a36Sopenharmony_ci	int ret = 0;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	pm_runtime_get_sync(cdns->dev);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	if (cdns->role == role)
36662306a36Sopenharmony_ci		goto pm_put;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	if (cdns->dr_mode == USB_DR_MODE_HOST) {
36962306a36Sopenharmony_ci		switch (role) {
37062306a36Sopenharmony_ci		case USB_ROLE_NONE:
37162306a36Sopenharmony_ci		case USB_ROLE_HOST:
37262306a36Sopenharmony_ci			break;
37362306a36Sopenharmony_ci		default:
37462306a36Sopenharmony_ci			goto pm_put;
37562306a36Sopenharmony_ci		}
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) {
37962306a36Sopenharmony_ci		switch (role) {
38062306a36Sopenharmony_ci		case USB_ROLE_NONE:
38162306a36Sopenharmony_ci		case USB_ROLE_DEVICE:
38262306a36Sopenharmony_ci			break;
38362306a36Sopenharmony_ci		default:
38462306a36Sopenharmony_ci			goto pm_put;
38562306a36Sopenharmony_ci		}
38662306a36Sopenharmony_ci	}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	cdns_role_stop(cdns);
38962306a36Sopenharmony_ci	ret = cdns_role_start(cdns, role);
39062306a36Sopenharmony_ci	if (ret)
39162306a36Sopenharmony_ci		dev_err(cdns->dev, "set role %d has failed\n", role);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cipm_put:
39462306a36Sopenharmony_ci	pm_runtime_put_sync(cdns->dev);
39562306a36Sopenharmony_ci	return ret;
39662306a36Sopenharmony_ci}
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci/**
39962306a36Sopenharmony_ci * cdns_wakeup_irq - interrupt handler for wakeup events
40062306a36Sopenharmony_ci * @irq: irq number for cdns3/cdnsp core device
40162306a36Sopenharmony_ci * @data: structure of cdns
40262306a36Sopenharmony_ci *
40362306a36Sopenharmony_ci * Returns IRQ_HANDLED or IRQ_NONE
40462306a36Sopenharmony_ci */
40562306a36Sopenharmony_cistatic irqreturn_t cdns_wakeup_irq(int irq, void *data)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	struct cdns *cdns = data;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (cdns->in_lpm) {
41062306a36Sopenharmony_ci		disable_irq_nosync(irq);
41162306a36Sopenharmony_ci		cdns->wakeup_pending = true;
41262306a36Sopenharmony_ci		if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev)
41362306a36Sopenharmony_ci			pm_request_resume(&cdns->host_dev->dev);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci		return IRQ_HANDLED;
41662306a36Sopenharmony_ci	}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	return IRQ_NONE;
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci/**
42262306a36Sopenharmony_ci * cdns_init - probe for cdns3/cdnsp core device
42362306a36Sopenharmony_ci * @cdns: Pointer to cdns structure.
42462306a36Sopenharmony_ci *
42562306a36Sopenharmony_ci * Returns 0 on success otherwise negative errno
42662306a36Sopenharmony_ci */
42762306a36Sopenharmony_ciint cdns_init(struct cdns *cdns)
42862306a36Sopenharmony_ci{
42962306a36Sopenharmony_ci	struct device *dev = cdns->dev;
43062306a36Sopenharmony_ci	int ret;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
43362306a36Sopenharmony_ci	if (ret) {
43462306a36Sopenharmony_ci		dev_err(dev, "error setting dma mask: %d\n", ret);
43562306a36Sopenharmony_ci		return ret;
43662306a36Sopenharmony_ci	}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	mutex_init(&cdns->mutex);
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (device_property_read_bool(dev, "usb-role-switch")) {
44162306a36Sopenharmony_ci		struct usb_role_switch_desc sw_desc = { };
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci		sw_desc.set = cdns_role_set;
44462306a36Sopenharmony_ci		sw_desc.get = cdns_role_get;
44562306a36Sopenharmony_ci		sw_desc.allow_userspace_control = true;
44662306a36Sopenharmony_ci		sw_desc.driver_data = cdns;
44762306a36Sopenharmony_ci		sw_desc.fwnode = dev->fwnode;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci		cdns->role_sw = usb_role_switch_register(dev, &sw_desc);
45062306a36Sopenharmony_ci		if (IS_ERR(cdns->role_sw)) {
45162306a36Sopenharmony_ci			dev_warn(dev, "Unable to register Role Switch\n");
45262306a36Sopenharmony_ci			return PTR_ERR(cdns->role_sw);
45362306a36Sopenharmony_ci		}
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (cdns->wakeup_irq) {
45762306a36Sopenharmony_ci		ret = devm_request_irq(cdns->dev, cdns->wakeup_irq,
45862306a36Sopenharmony_ci						cdns_wakeup_irq,
45962306a36Sopenharmony_ci						IRQF_SHARED,
46062306a36Sopenharmony_ci						dev_name(cdns->dev), cdns);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci		if (ret) {
46362306a36Sopenharmony_ci			dev_err(cdns->dev, "couldn't register wakeup irq handler\n");
46462306a36Sopenharmony_ci			goto role_switch_unregister;
46562306a36Sopenharmony_ci		}
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	ret = cdns_drd_init(cdns);
46962306a36Sopenharmony_ci	if (ret)
47062306a36Sopenharmony_ci		goto init_failed;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	ret = cdns_core_init_role(cdns);
47362306a36Sopenharmony_ci	if (ret)
47462306a36Sopenharmony_ci		goto init_failed;
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	spin_lock_init(&cdns->lock);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	return 0;
48162306a36Sopenharmony_ciinit_failed:
48262306a36Sopenharmony_ci	cdns_drd_exit(cdns);
48362306a36Sopenharmony_cirole_switch_unregister:
48462306a36Sopenharmony_ci	if (cdns->role_sw)
48562306a36Sopenharmony_ci		usb_role_switch_unregister(cdns->role_sw);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	return ret;
48862306a36Sopenharmony_ci}
48962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cdns_init);
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci/**
49262306a36Sopenharmony_ci * cdns_remove - unbind drd driver and clean up
49362306a36Sopenharmony_ci * @cdns: Pointer to cdns structure.
49462306a36Sopenharmony_ci *
49562306a36Sopenharmony_ci * Returns 0 on success otherwise negative errno
49662306a36Sopenharmony_ci */
49762306a36Sopenharmony_ciint cdns_remove(struct cdns *cdns)
49862306a36Sopenharmony_ci{
49962306a36Sopenharmony_ci	cdns_exit_roles(cdns);
50062306a36Sopenharmony_ci	usb_role_switch_unregister(cdns->role_sw);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	return 0;
50362306a36Sopenharmony_ci}
50462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cdns_remove);
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
50762306a36Sopenharmony_ciint cdns_suspend(struct cdns *cdns)
50862306a36Sopenharmony_ci{
50962306a36Sopenharmony_ci	struct device *dev = cdns->dev;
51062306a36Sopenharmony_ci	unsigned long flags;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	if (pm_runtime_status_suspended(dev))
51362306a36Sopenharmony_ci		pm_runtime_resume(dev);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	if (cdns->roles[cdns->role]->suspend) {
51662306a36Sopenharmony_ci		spin_lock_irqsave(&cdns->lock, flags);
51762306a36Sopenharmony_ci		cdns->roles[cdns->role]->suspend(cdns, false);
51862306a36Sopenharmony_ci		spin_unlock_irqrestore(&cdns->lock, flags);
51962306a36Sopenharmony_ci	}
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	return 0;
52262306a36Sopenharmony_ci}
52362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cdns_suspend);
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ciint cdns_resume(struct cdns *cdns)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	enum usb_role real_role;
52862306a36Sopenharmony_ci	bool role_changed = false;
52962306a36Sopenharmony_ci	int ret = 0;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (cdns_power_is_lost(cdns)) {
53262306a36Sopenharmony_ci		if (cdns->role_sw) {
53362306a36Sopenharmony_ci			cdns->role = cdns_role_get(cdns->role_sw);
53462306a36Sopenharmony_ci		} else {
53562306a36Sopenharmony_ci			real_role = cdns_hw_role_state_machine(cdns);
53662306a36Sopenharmony_ci			if (real_role != cdns->role) {
53762306a36Sopenharmony_ci				ret = cdns_hw_role_switch(cdns);
53862306a36Sopenharmony_ci				if (ret)
53962306a36Sopenharmony_ci					return ret;
54062306a36Sopenharmony_ci				role_changed = true;
54162306a36Sopenharmony_ci			}
54262306a36Sopenharmony_ci		}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci		if (!role_changed) {
54562306a36Sopenharmony_ci			if (cdns->role == USB_ROLE_HOST)
54662306a36Sopenharmony_ci				ret = cdns_drd_host_on(cdns);
54762306a36Sopenharmony_ci			else if (cdns->role == USB_ROLE_DEVICE)
54862306a36Sopenharmony_ci				ret = cdns_drd_gadget_on(cdns);
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci			if (ret)
55162306a36Sopenharmony_ci				return ret;
55262306a36Sopenharmony_ci		}
55362306a36Sopenharmony_ci	}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	if (cdns->roles[cdns->role]->resume)
55662306a36Sopenharmony_ci		cdns->roles[cdns->role]->resume(cdns, cdns_power_is_lost(cdns));
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	return 0;
55962306a36Sopenharmony_ci}
56062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cdns_resume);
56162306a36Sopenharmony_ci
56262306a36Sopenharmony_civoid cdns_set_active(struct cdns *cdns, u8 set_active)
56362306a36Sopenharmony_ci{
56462306a36Sopenharmony_ci	struct device *dev = cdns->dev;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	if (set_active) {
56762306a36Sopenharmony_ci		pm_runtime_disable(dev);
56862306a36Sopenharmony_ci		pm_runtime_set_active(dev);
56962306a36Sopenharmony_ci		pm_runtime_enable(dev);
57062306a36Sopenharmony_ci	}
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	return;
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cdns_set_active);
57562306a36Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ciMODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
57862306a36Sopenharmony_ciMODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
57962306a36Sopenharmony_ciMODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
58062306a36Sopenharmony_ciMODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver");
58162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
582