summaryrefslogtreecommitdiff
path: root/drivers/bus/mhi/ep/sm.c
blob: e3865b85399d115d4122c7e112212b7c7c02bc5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2022 Linaro Ltd.
 * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
 */

#include <linux/errno.h>
#include <linux/mhi_ep.h>
#include "internal.h"

bool __must_check mhi_ep_check_mhi_state(struct mhi_ep_cntrl *mhi_cntrl,
					 enum mhi_state cur_mhi_state,
					 enum mhi_state mhi_state)
{
	if (mhi_state == MHI_STATE_SYS_ERR)
		return true;    /* Allowed in any state */

	if (mhi_state == MHI_STATE_READY)
		return cur_mhi_state == MHI_STATE_RESET;

	if (mhi_state == MHI_STATE_M0)
		return cur_mhi_state == MHI_STATE_M3 || cur_mhi_state == MHI_STATE_READY;

	if (mhi_state == MHI_STATE_M3)
		return cur_mhi_state == MHI_STATE_M0;

	return false;
}

int mhi_ep_set_mhi_state(struct mhi_ep_cntrl *mhi_cntrl, enum mhi_state mhi_state)
{
	struct device *dev = &mhi_cntrl->mhi_dev->dev;

	if (!mhi_ep_check_mhi_state(mhi_cntrl, mhi_cntrl->mhi_state, mhi_state)) {
		dev_err(dev, "MHI state change to %s from %s is not allowed!\n",
			mhi_state_str(mhi_state),
			mhi_state_str(mhi_cntrl->mhi_state));
		return -EACCES;
	}

	/* TODO: Add support for M1 and M2 states */
	if (mhi_state == MHI_STATE_M1 || mhi_state == MHI_STATE_M2) {
		dev_err(dev, "MHI state (%s) not supported\n", mhi_state_str(mhi_state));
		return -EOPNOTSUPP;
	}

	mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK, mhi_state);
	mhi_cntrl->mhi_state = mhi_state;

	if (mhi_state == MHI_STATE_READY)
		mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK, 1);

	if (mhi_state == MHI_STATE_SYS_ERR)
		mhi_ep_mmio_masked_write(mhi_cntrl, EP_MHISTATUS, MHISTATUS_SYSERR_MASK, 1);

	return 0;
}

int mhi_ep_set_m0_state(struct mhi_ep_cntrl *mhi_cntrl)
{
	struct device *dev = &mhi_cntrl->mhi_dev->dev;
	enum mhi_state old_state;
	int ret;

	spin_lock_bh(&mhi_cntrl->state_lock);
	old_state = mhi_cntrl->mhi_state;

	ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M0);
	spin_unlock_bh(&mhi_cntrl->state_lock);

	if (ret) {
		mhi_ep_handle_syserr(mhi_cntrl);
		return ret;
	}

	/* Signal host that the device moved to M0 */
	ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M0);
	if (ret) {
		dev_err(dev, "Failed sending M0 state change event\n");
		return ret;
	}

	if (old_state == MHI_STATE_READY) {
		/* Send AMSS EE event to host */
		ret = mhi_ep_send_ee_event(mhi_cntrl, MHI_EE_AMSS);
		if (ret) {
			dev_err(dev, "Failed sending AMSS EE event\n");
			return ret;
		}
	}

	return 0;
}

int mhi_ep_set_m3_state(struct mhi_ep_cntrl *mhi_cntrl)
{
	struct device *dev = &mhi_cntrl->mhi_dev->dev;
	int ret;

	spin_lock_bh(&mhi_cntrl->state_lock);
	ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_M3);
	spin_unlock_bh(&mhi_cntrl->state_lock);

	if (ret) {
		mhi_ep_handle_syserr(mhi_cntrl);
		return ret;
	}

	/* Signal host that the device moved to M3 */
	ret = mhi_ep_send_state_change_event(mhi_cntrl, MHI_STATE_M3);
	if (ret) {
		dev_err(dev, "Failed sending M3 state change event\n");
		return ret;
	}

	return 0;
}

int mhi_ep_set_ready_state(struct mhi_ep_cntrl *mhi_cntrl)
{
	struct device *dev = &mhi_cntrl->mhi_dev->dev;
	enum mhi_state mhi_state;
	int ret, is_ready;

	spin_lock_bh(&mhi_cntrl->state_lock);
	/* Ensure that the MHISTATUS is set to RESET by host */
	mhi_state = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_MHISTATE_MASK);
	is_ready = mhi_ep_mmio_masked_read(mhi_cntrl, EP_MHISTATUS, MHISTATUS_READY_MASK);

	if (mhi_state != MHI_STATE_RESET || is_ready) {
		dev_err(dev, "READY state transition failed. MHI host not in RESET state\n");
		spin_unlock_bh(&mhi_cntrl->state_lock);
		return -EIO;
	}

	ret = mhi_ep_set_mhi_state(mhi_cntrl, MHI_STATE_READY);
	spin_unlock_bh(&mhi_cntrl->state_lock);

	if (ret)
		mhi_ep_handle_syserr(mhi_cntrl);

	return ret;
}