summaryrefslogtreecommitdiff
path: root/drivers/soundwire/intel_bus_common.c
blob: f180e3bea9897c9a41b335d8123d897ef9716de5 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
// Copyright(c) 2015-2023 Intel Corporation. All rights reserved.

#include <linux/acpi.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include "cadence_master.h"
#include "bus.h"
#include "intel.h"

int intel_start_bus(struct sdw_intel *sdw)
{
	struct device *dev = sdw->cdns.dev;
	struct sdw_cdns *cdns = &sdw->cdns;
	struct sdw_bus *bus = &cdns->bus;
	int ret;

	ret = sdw_cdns_enable_interrupt(cdns, true);
	if (ret < 0) {
		dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
		return ret;
	}

	/*
	 * follow recommended programming flows to avoid timeouts when
	 * gsync is enabled
	 */
	if (bus->multi_link)
		sdw_intel_sync_arm(sdw);

	ret = sdw_cdns_init(cdns);
	if (ret < 0) {
		dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret);
		goto err_interrupt;
	}

	ret = sdw_cdns_exit_reset(cdns);
	if (ret < 0) {
		dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret);
		goto err_interrupt;
	}

	if (bus->multi_link) {
		ret = sdw_intel_sync_go(sdw);
		if (ret < 0) {
			dev_err(dev, "%s: sync go failed: %d\n", __func__, ret);
			goto err_interrupt;
		}
	}
	sdw_cdns_check_self_clearing_bits(cdns, __func__,
					  true, INTEL_MASTER_RESET_ITERATIONS);

	return 0;

err_interrupt:
	sdw_cdns_enable_interrupt(cdns, false);
	return ret;
}

int intel_start_bus_after_reset(struct sdw_intel *sdw)
{
	struct device *dev = sdw->cdns.dev;
	struct sdw_cdns *cdns = &sdw->cdns;
	struct sdw_bus *bus = &cdns->bus;
	bool clock_stop0;
	int status;
	int ret;

	/*
	 * An exception condition occurs for the CLK_STOP_BUS_RESET
	 * case if one or more masters remain active. In this condition,
	 * all the masters are powered on for they are in the same power
	 * domain. Master can preserve its context for clock stop0, so
	 * there is no need to clear slave status and reset bus.
	 */
	clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);

	if (!clock_stop0) {

		/*
		 * make sure all Slaves are tagged as UNATTACHED and
		 * provide reason for reinitialization
		 */

		status = SDW_UNATTACH_REQUEST_MASTER_RESET;
		sdw_clear_slave_status(bus, status);

		ret = sdw_cdns_enable_interrupt(cdns, true);
		if (ret < 0) {
			dev_err(dev, "cannot enable interrupts during resume\n");
			return ret;
		}

		/*
		 * follow recommended programming flows to avoid
		 * timeouts when gsync is enabled
		 */
		if (bus->multi_link)
			sdw_intel_sync_arm(sdw);

		/*
		 * Re-initialize the IP since it was powered-off
		 */
		sdw_cdns_init(&sdw->cdns);

	} else {
		ret = sdw_cdns_enable_interrupt(cdns, true);
		if (ret < 0) {
			dev_err(dev, "cannot enable interrupts during resume\n");
			return ret;
		}
	}

	ret = sdw_cdns_clock_restart(cdns, !clock_stop0);
	if (ret < 0) {
		dev_err(dev, "unable to restart clock during resume\n");
		goto err_interrupt;
	}

	if (!clock_stop0) {
		ret = sdw_cdns_exit_reset(cdns);
		if (ret < 0) {
			dev_err(dev, "unable to exit bus reset sequence during resume\n");
			goto err_interrupt;
		}

		if (bus->multi_link) {
			ret = sdw_intel_sync_go(sdw);
			if (ret < 0) {
				dev_err(sdw->cdns.dev, "sync go failed during resume\n");
				goto err_interrupt;
			}
		}
	}
	sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);

	return 0;

err_interrupt:
	sdw_cdns_enable_interrupt(cdns, false);
	return ret;
}

void intel_check_clock_stop(struct sdw_intel *sdw)
{
	struct device *dev = sdw->cdns.dev;
	bool clock_stop0;

	clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
	if (!clock_stop0)
		dev_err(dev, "%s: invalid configuration, clock was not stopped\n", __func__);
}

int intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
{
	struct device *dev = sdw->cdns.dev;
	struct sdw_cdns *cdns = &sdw->cdns;
	int ret;

	ret = sdw_cdns_enable_interrupt(cdns, true);
	if (ret < 0) {
		dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
		return ret;
	}

	ret = sdw_cdns_clock_restart(cdns, false);
	if (ret < 0) {
		dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret);
		sdw_cdns_enable_interrupt(cdns, false);
		return ret;
	}

	sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime no_quirks",
					  true, INTEL_MASTER_RESET_ITERATIONS);

	return 0;
}

int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
{
	struct device *dev = sdw->cdns.dev;
	struct sdw_cdns *cdns = &sdw->cdns;
	bool wake_enable = false;
	int ret;

	if (clock_stop) {
		ret = sdw_cdns_clock_stop(cdns, true);
		if (ret < 0)
			dev_err(dev, "%s: cannot stop clock: %d\n", __func__, ret);
		else
			wake_enable = true;
	}

	ret = sdw_cdns_enable_interrupt(cdns, false);
	if (ret < 0) {
		dev_err(dev, "%s: cannot disable interrupts: %d\n", __func__, ret);
		return ret;
	}

	ret = sdw_intel_link_power_down(sdw);
	if (ret) {
		dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret);
		return ret;
	}

	sdw_intel_shim_wake(sdw, wake_enable);

	return 0;
}

/*
 * bank switch routines
 */

int intel_pre_bank_switch(struct sdw_intel *sdw)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	struct sdw_bus *bus = &cdns->bus;

	/* Write to register only for multi-link */
	if (!bus->multi_link)
		return 0;

	sdw_intel_sync_arm(sdw);

	return 0;
}

int intel_post_bank_switch(struct sdw_intel *sdw)
{
	struct sdw_cdns *cdns = &sdw->cdns;
	struct sdw_bus *bus = &cdns->bus;
	int ret = 0;

	/* Write to register only for multi-link */
	if (!bus->multi_link)
		return 0;

	mutex_lock(sdw->link_res->shim_lock);

	/*
	 * post_bank_switch() ops is called from the bus in loop for
	 * all the Masters in the steam with the expectation that
	 * we trigger the bankswitch for the only first Master in the list
	 * and do nothing for the other Masters
	 *
	 * So, set the SYNCGO bit only if CMDSYNC bit is set for any Master.
	 */
	if (sdw_intel_sync_check_cmdsync_unlocked(sdw))
		ret = sdw_intel_sync_go_unlocked(sdw);

	mutex_unlock(sdw->link_res->shim_lock);

	if (ret < 0)
		dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret);

	return ret;
}