Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b483fe26 authored by Yan He's avatar Yan He
Browse files

msm: pcie: improve link recovery mechanism



The PCIe wakeup IRQ could be fired before the linkdown IRQ is fired
when PCIe link goes down, and also could happen during system
suspend/resume process. Thus, update the link recovery mechanism to
handle those cases.

CRs-fixed: 664003
Change-Id: I6bd6249c4c685b1df885226fb5aae551df4bddb9
Signed-off-by: default avatarYan He <yanhe@codeaurora.org>
parent d3e85426
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ enum msm_pcie_event {
	MSM_PCIE_EVENT_INVALID = 0,
	MSM_PCIE_EVENT_LINKDOWN = 0x1,
	MSM_PCIE_EVENT_LINKUP = 0x2,
	MSM_PCIE_EVENT_WAKEUP = 0x4,
};

enum msm_pcie_trigger {
+13 −5
Original line number Diff line number Diff line
@@ -1671,7 +1671,6 @@ static int __init pcie_init(void)
		msm_pcie_dev[i].cfg_access = true;
		mutex_init(&msm_pcie_dev[i].setup_lock);
		mutex_init(&msm_pcie_dev[i].recovery_lock);
		mutex_init(&msm_pcie_dev[i].linkdown_lock);
	}

	ret = platform_driver_register(&msm_pcie_driver);
@@ -1827,10 +1826,12 @@ void msm_pcie_fixup_resume(struct pci_dev *dev)
		return;
	}

	mutex_lock(&pcie_dev->recovery_lock);
	ret = msm_pcie_pm_resume(dev, NULL, NULL, 0);
	if (ret)
		pr_err("PCIe: RC%d got failure in fixup resume:%d.\n",
			pcie_dev->rc_idx, ret);
	mutex_unlock(&pcie_dev->recovery_lock);
}
DECLARE_PCI_FIXUP_RESUME(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP,
				 msm_pcie_fixup_resume);
@@ -1852,10 +1853,12 @@ void msm_pcie_fixup_resume_early(struct pci_dev *dev)
		return;
	}

	mutex_lock(&pcie_dev->recovery_lock);
	ret = msm_pcie_pm_resume(dev, NULL, NULL, 0);
	if (ret)
		pr_err("PCIe: RC%d got failure in resume:%d.\n",
			pcie_dev->rc_idx, ret);
	mutex_unlock(&pcie_dev->recovery_lock);
}
DECLARE_PCI_FIXUP_RESUME_EARLY(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP,
				 msm_pcie_fixup_resume_early);
@@ -1912,10 +1915,10 @@ int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, void *user,
				rc_idx, msm_pcie_dev[rc_idx].link_status);
			break;
		}
		if (!(options & MSM_PCIE_CONFIG_LINKDOWN))
		if (!(options & MSM_PCIE_CONFIG_LINKDOWN)) {
			msm_pcie_dev[rc_idx].user_suspend = true;

			mutex_lock(&msm_pcie_dev[rc_idx].recovery_lock);
		}
		ret = msm_pcie_pm_suspend(dev, user, data, options);
		if (ret) {
			pr_err(
@@ -1923,6 +1926,7 @@ int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, void *user,
				rc_idx);
			msm_pcie_dev[rc_idx].user_suspend = false;
		}
		if (!(options & MSM_PCIE_CONFIG_LINKDOWN))
			mutex_unlock(&msm_pcie_dev[rc_idx].recovery_lock);
		break;
	case MSM_PCIE_RESUME:
@@ -1934,12 +1938,16 @@ int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, void *user,
				rc_idx, msm_pcie_dev[rc_idx].link_status);
			break;
		}
		if (!(options & MSM_PCIE_CONFIG_LINKDOWN))
			mutex_lock(&msm_pcie_dev[rc_idx].recovery_lock);
		ret = msm_pcie_pm_resume(dev, user, data, options);
		if (ret)
			pr_err("PCIe: RC%d: user failed to resume the link.\n",
				rc_idx);
		else
			msm_pcie_dev[rc_idx].user_suspend = false;
		if (!(options & MSM_PCIE_CONFIG_LINKDOWN))
			mutex_unlock(&msm_pcie_dev[rc_idx].recovery_lock);
		break;
	default:
		pr_err("PCIe: RC%d: unsupported pm operation:%d.\n",
+0 −1
Original line number Diff line number Diff line
@@ -201,7 +201,6 @@ struct msm_pcie_dev_t {
	int                          handling_linkdown;
	bool                         recovery_pending;
	struct mutex                 recovery_lock;
	struct mutex                 linkdown_lock;
	ulong                        linkdown_counter;
	bool                         suspending;
	ulong                        wake_counter;
+107 −67
Original line number Diff line number Diff line
@@ -75,6 +75,62 @@ static int msm_pcie_recover_link(struct msm_pcie_dev_t *dev)
	return ret;
}

static void msm_pcie_notify_linkdown(struct msm_pcie_dev_t *dev)
{
	if (dev->event_reg && dev->event_reg->callback &&
		(dev->event_reg->events & MSM_PCIE_EVENT_LINKDOWN)) {
		struct msm_pcie_notify *notify = &dev->event_reg->notify;
		notify->event = MSM_PCIE_EVENT_LINKDOWN;
		notify->user = dev->event_reg->user;
		PCIE_DBG("PCIe: Linkdown callback for RC%d\n", dev->rc_idx);
		dev->event_reg->callback(notify);

		if (dev->event_reg->options & MSM_PCIE_CONFIG_NO_RECOVERY) {
			dev->user_suspend = true;
			PCIE_DBG(
				"PCIe: Client of RC%d will recover the link later.\n",
				dev->rc_idx);
			return;
		}

		if (dev->link_status == MSM_PCIE_LINK_DISABLED) {
			PCIE_DBG(
				"PCIe: Client of RC%d does not enable link in callback; so disable the link\n",
				dev->rc_idx);
			dev->recovery_pending = true;
			msm_pcie_disable(dev,
				PM_EXPT | PM_PIPE_CLK | PM_CLK | PM_VREG);
		} else {
			dev->recovery_pending = false;
			PCIE_DBG(
				"PCIe: Client of RC%d has enabled link in callback; so recover config space\n",
				dev->rc_idx);
			PCIE_DBG("PCIe: Recover RC%d\n", dev->rc_idx);
			msm_pcie_cfg_recover(dev, true);
			PCIE_DBG("PCIe: Recover EP of RC%d\n", dev->rc_idx);
			msm_pcie_cfg_recover(dev, false);
			dev->shadow_en = true;

			if ((dev->link_status == MSM_PCIE_LINK_ENABLED) &&
				dev->event_reg && dev->event_reg->callback &&
				(dev->event_reg->events &
					MSM_PCIE_EVENT_LINKUP)) {
				struct msm_pcie_notify *notify =
						&dev->event_reg->notify;
				notify->event = MSM_PCIE_EVENT_LINKUP;
				notify->user = dev->event_reg->user;
				PCIE_DBG("PCIe: Linkup callback for RC%d\n",
						dev->rc_idx);
				dev->event_reg->callback(notify);
			}
		}
	} else {
		pr_err(
			"PCIe: Client driver does not have registration and this linkdown of RC%d should never happen.\n",
			dev->rc_idx);
	}
}

static void handle_wake_func(struct work_struct *work)
{
	int ret;
@@ -83,15 +139,15 @@ static void handle_wake_func(struct work_struct *work)

	PCIE_DBG("PCIe: Wake work for RC%d\n", dev->rc_idx);

	mutex_lock(&dev->recovery_lock);

	if (!dev->enumerated) {
		mutex_lock(&dev->recovery_lock);
		ret = msm_pcie_enumerate(dev->rc_idx);
		mutex_unlock(&dev->recovery_lock);
		if (ret) {
			pr_err(
				"PCIe: failed to enable RC%d upon wake request from the device.\n",
				dev->rc_idx);
			goto out;
			return;
		}

		if ((dev->link_status == MSM_PCIE_LINK_ENABLED) &&
@@ -121,6 +177,7 @@ static void handle_wake_func(struct work_struct *work)
				"PCIe: Linkdown handling for RC%d is not finished after max waiting time.\n",
				dev->rc_idx);

		mutex_lock(&dev->recovery_lock);
		if (dev->link_status == MSM_PCIE_LINK_ENABLED) {
			PCIE_DBG(
				"PCIe: The link status of RC%d is up. Check if it is really up.\n",
@@ -133,18 +190,16 @@ static void handle_wake_func(struct work_struct *work)
				goto out;
			} else {
				dev->link_status = MSM_PCIE_LINK_DISABLED;
				dev->shadow_en = false;
				/* assert PERST */
				gpio_set_value(
					dev->gpio[MSM_PCIE_GPIO_PERST].num,
					dev->gpio[MSM_PCIE_GPIO_PERST].on);
				pr_err(
					"PCIe: The link of RC%d is actually down; start recovering link.\n",
					dev->rc_idx);
				msm_pcie_disable(dev, PM_EXPT | PM_PIPE_CLK |
							PM_CLK | PM_VREG);
				ret = msm_pcie_recover_link(dev);
				if (ret) {
					pr_err(
						"PCIe:failed to recover link for RC%d after receive wake IRQ.\n",
					"PCIe: The link of RC%d is actually down; notify the client.\n",
					dev->rc_idx);
					goto out;
				}

				msm_pcie_notify_linkdown(dev);
			}
		} else {
			PCIE_DBG("PCIe: The link status of RC%d is down.\n",
@@ -168,9 +223,40 @@ static void handle_wake_func(struct work_struct *work)
						dev->rc_idx, retries);
					retries = 1;
				}
			} else if (dev->user_suspend) {
				PCIE_DBG(
					"PCIe: wake IRQ for RC%d for a user-suspended link.\n",
					dev->rc_idx);
				if (dev->event_reg &&
					dev->event_reg->callback &&
					(dev->event_reg->events &
					MSM_PCIE_EVENT_WAKEUP)) {
					struct msm_pcie_notify *nfy =
						&dev->event_reg->notify;
					nfy->event = MSM_PCIE_EVENT_WAKEUP;
					nfy->user = dev->event_reg->user;
					PCIE_DBG(
						"PCIe: wakeup callback for RC%d\n",
						dev->rc_idx);
					dev->event_reg->callback(nfy);
					if (dev->link_status ==
						MSM_PCIE_LINK_ENABLED)
						PCIE_DBG(
							"PCIe: link is enabled after wakeup callback for RC%d\n",
							dev->rc_idx);
					else
						PCIE_DBG(
							"PCIe: link is NOT enabled after wakeup callback for RC%d\n",
							dev->rc_idx);
				} else {
					pr_err(
						"PCIe: client of RC%d does not register callback for wake IRQ for a user-suspended link.\n",
						dev->rc_idx);
				}
				goto out;
			} else {
				PCIE_DBG(
					"PCIe: No pending recovery for RC%d; so ignore wake IRQ.\n",
					"PCIe: No pending recovery or user-issued suspend for RC%d; so ignore wake IRQ.\n",
					dev->rc_idx);
				goto out;
			}
@@ -212,66 +298,20 @@ static void handle_linkdown_func(struct work_struct *work)

	PCIE_DBG("PCIe: Linkdown work for RC%d\n", dev->rc_idx);

	mutex_lock(&dev->linkdown_lock);

	if (dev->event_reg && dev->event_reg->callback &&
		(dev->event_reg->events & MSM_PCIE_EVENT_LINKDOWN)) {
		struct msm_pcie_notify *notify = &dev->event_reg->notify;
		notify->event = MSM_PCIE_EVENT_LINKDOWN;
		notify->user = dev->event_reg->user;
		PCIE_DBG("PCIe: Linkdown callback for RC%d\n", dev->rc_idx);
		dev->event_reg->callback(notify);

		if (dev->event_reg->options & MSM_PCIE_CONFIG_NO_RECOVERY) {
			dev->user_suspend = true;
			PCIE_DBG(
				"PCIe: Client of RC%d will recover the link later.\n",
				dev->rc_idx);
			goto out;
		}
	mutex_lock(&dev->recovery_lock);

		if (dev->link_status == MSM_PCIE_LINK_DISABLED) {
	if (msm_pcie_confirm_linkup(dev))
		PCIE_DBG(
				"PCIe: Client of RC%d does not enable link in callback; so disable the link\n",
			"PCIe: The link status of RC%d is up now, indicating recovery has been done.\n",
			dev->rc_idx);
			dev->recovery_pending = true;
			msm_pcie_disable(dev,
				PM_EXPT | PM_PIPE_CLK | PM_CLK | PM_VREG);
		} else {
			PCIE_DBG(
				"PCIe: Client of RC%d has enabled link in callback; so recover config space\n",
				dev->rc_idx);
			PCIE_DBG("PCIe: Recover RC%d\n", dev->rc_idx);
			msm_pcie_cfg_recover(dev, true);
			PCIE_DBG("PCIe: Recover EP of RC%d\n", dev->rc_idx);
			msm_pcie_cfg_recover(dev, false);
			dev->shadow_en = true;

			if ((dev->link_status == MSM_PCIE_LINK_ENABLED) &&
				dev->event_reg && dev->event_reg->callback &&
				(dev->event_reg->events &
					MSM_PCIE_EVENT_LINKUP)) {
				struct msm_pcie_notify *notify =
						&dev->event_reg->notify;
				notify->event = MSM_PCIE_EVENT_LINKUP;
				notify->user = dev->event_reg->user;
				PCIE_DBG("PCIe: Linkup callback for RC%d\n",
						dev->rc_idx);
				dev->event_reg->callback(notify);
			}
		}
	} else {
		pr_err(
			"PCIe: Client driver does not have registration and this linkdown of RC%d should never happen.\n",
			dev->rc_idx);
	}
	else
		msm_pcie_notify_linkdown(dev);

out:
	dev->handling_linkdown--;
	if (dev->handling_linkdown < 0)
		pr_err("PCIe:handling_linkdown for RC%d is %d\n",
			dev->rc_idx, dev->handling_linkdown);
	mutex_unlock(&dev->linkdown_lock);
	mutex_unlock(&dev->recovery_lock);
}

static irqreturn_t handle_linkdown_irq(int irq, void *data)