summaryrefslogtreecommitdiff
path: root/drivers/modem/m6718_spi/protocol.c
blob: 38e9190e397f6821a6d5c2165134bcda71969249 (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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
 * Copyright (C) ST-Ericsson SA 2010,2011
 *
 * Author: Chris Blair <chris.blair@stericsson.com> for ST-Ericsson
 *
 * License terms: GNU General Public License (GPL) version 2
 *
 * U9500 <-> M6718 IPC protocol implementation using SPI.
 */
#include <linux/modem/m6718_spi/modem_driver.h>
#include "modem_protocol.h"
#include "modem_private.h"
#include "modem_util.h"
#include "modem_queue.h"
#include "modem_debug.h"
#include "modem_netlink.h"

#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
#include <linux/workqueue.h>
#include "modem_state.h"

#define MODEM_STATE_REGISTER_TMO_MS  (500)
#endif

#ifdef WORKAROUND_DUPLICATED_IRQ
#include <linux/amba/pl022.h>
#endif

struct l2mux_channel {
	u8 open:1;
	u8 link:7;
};

/* valid open L2 mux channels */
static const struct l2mux_channel channels[255] = {
	[MODEM_M6718_SPI_CHN_ISI] = {
		.open = true,
		.link = IPC_LINK_COMMON
	},
	[MODEM_M6718_SPI_CHN_AUDIO] = {
		.open = true,
		.link = IPC_LINK_AUDIO
	},
	[MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0] = {
		.open = true,
		.link = IPC_LINK_COMMON
	},
	[MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK0] = {
		.open = true,
		.link = IPC_LINK_COMMON
	},
	[MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1] = {
		.open = true,
		.link = IPC_LINK_AUDIO
	},
	[MODEM_M6718_SPI_CHN_SLAVE_LOOPBACK1] = {
		.open = true,
		.link = IPC_LINK_AUDIO
	}
};

#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
static void modem_state_reg_wq(struct work_struct *work);
static DECLARE_DELAYED_WORK(modem_state_reg_work, modem_state_reg_wq);
#endif

/* the spi driver context */
struct ipc_l1_context l1_context = {
#ifdef CONFIG_DEBUG_FS
	.msr_disable = false,
#endif
	.init_done = false
};

bool modem_protocol_channel_is_open(u8 channel)
{
	return channels[channel].open;
}

void modem_comms_timeout(unsigned long data)
{
	ipc_sm_kick(IPC_SM_RUN_COMMS_TMO, (struct ipc_link_context *)data);
}

void slave_stable_timeout(unsigned long data)
{
	ipc_sm_kick(IPC_SM_RUN_STABLE_TMO, (struct ipc_link_context *)data);
}

/**
 * modem_protocol_init() - initialise the IPC protocol
 *
 * Initialises the IPC protocol in preparation for use. After this is called
 * the protocol is ready to be probed for each link to be supported.
 */
void modem_protocol_init(void)
{
	pr_info("M6718 IPC protocol initialising version %02x\n",
		IPC_DRIVER_VERSION);

	atomic_set(&l1_context.boot_sync_done, 0);
	ipc_dbg_debugfs_init();
	ipc_dbg_throughput_init();
	l1_context.init_done = true;
	ipc_dbg_measure_throughput(0);
#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
	schedule_delayed_work(&modem_state_reg_work, 0);
#endif
}

/**
 * modem_m6718_spi_send() - send a frame using the IPC protocol
 * @modem_spi_dev: pointer to modem driver information structure
 * @channel:       L2 channel to send on
 * @len:           length of data to send
 * @data:          pointer to buffer containing data
 *
 * Check that the requested channel is supported and open, queue a frame
 * containing the data on the appropriate link and ensure the state machine
 * is running to start the transfer.
 */
int modem_m6718_spi_send(struct modem_spi_dev *modem_spi_dev, u8 channel,
	u32 len, void *data)
{
	int err;
	struct ipc_link_context *context;

	if (!channels[channel].open) {
		dev_err(modem_spi_dev->dev,
			"error: invalid channel (%d), discarding frame\n",
			channel);
		return -EINVAL;
	}

	context = &l1_context.device_context[channels[channel].link];
	if (context->state == NULL || context->state->id == IPC_SM_HALT) {
		static unsigned long linkfail_warn_time;
		if (printk_timed_ratelimit(&linkfail_warn_time, 60 * 1000))
			dev_err(modem_spi_dev->dev,
				"error: link %d for ch %d is not available, "
				"discarding frames\n",
				channels[channel].link, channel);
		return -ENODEV;
	}

	err = ipc_queue_push_frame(context, channel, len, data);
	if (err < 0)
		return err;

	if (ipc_util_link_is_idle(context)) {
		dev_dbg(modem_spi_dev->dev,
			"link %d is idle, kicking\n", channels[channel].link);
		ipc_sm_kick(IPC_SM_RUN_TX_REQ, context);
	} else {
		dev_dbg(modem_spi_dev->dev,
			"link %d is already running\n", channels[channel].link);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(modem_m6718_spi_send);

/**
 * modem_m6718_spi_is_boot_done() - check if boot handshake with modem is done
 */
bool modem_m6718_spi_is_boot_done(void)
{
	return atomic_read(&l1_context.boot_sync_done);
}
EXPORT_SYMBOL_GPL(modem_m6718_spi_is_boot_done);

/**
 * modem_protocol_is_busy() - check if the protocol is currently active
 * @sdev: pointer to spi_device for link to check
 *
 * Checks each of the IPC links to see if they are inactive: this means they
 * can be in either IDLE or INIT states. If any of the links are not idle then
 * true is returned to indicate that the protocol is busy.
 */
bool modem_protocol_is_busy(struct spi_device *sdev)
{
	int i;

	for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
		switch (l1_context.device_context[i].state->id) {
		case IPC_SM_IDL:
		case IPC_SM_INIT:
		case IPC_SM_WAIT_SLAVE_STABLE:
			/* not busy; continue checking */
			break;
		default:
			dev_info(&sdev->dev, "link %d is busy\n", i);
			return true;
		}
	return false;
}

int modem_protocol_suspend(struct spi_device *sdev)
{
	struct modem_m6718_spi_link_platform_data *link =
		sdev->dev.platform_data;
	struct ipc_link_context *context;
	int link_id;

	if (link == NULL) {
		/* platform data missing in board config? */
		dev_err(&sdev->dev, "error: no platform data for link!\n");
		return -ENODEV;
	}

	link_id = link->id;
	context = &l1_context.device_context[link_id];

	if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
		dev_err(&sdev->dev,
			"link %d error: too many links! (max %d)\n",
			link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
		return -ENODEV;
	}

	ipc_util_suspend_link(context);
	return 0;
}

int modem_protocol_resume(struct spi_device *sdev)
{
	struct modem_m6718_spi_link_platform_data *link =
		sdev->dev.platform_data;
	struct ipc_link_context *context;
	int link_id;

	if (link == NULL) {
		/* platform data missing in board config? */
		dev_err(&sdev->dev, "error: no platform data for link!\n");
		return -ENODEV;
	}

	link_id = link->id;
	context = &l1_context.device_context[link_id];

	if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
		dev_err(&sdev->dev,
			"link %d error: too many links! (max %d)\n",
			link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
		return -ENODEV;
	}

	ipc_util_resume_link(context);

	/*
	 * If the resume event was an interrupt from the slave then the event
	 * is pending and we need to service it now.
	 */
	if (ipc_util_int_is_active(context)) {
		dev_dbg(&sdev->dev,
			"link %d: slave-ready is pending after resume\n",
			link_id);
		ipc_sm_kick(IPC_SM_RUN_SLAVE_IRQ, context);
	}
	return 0;
}

static void spi_tfr_complete(void *context)
{
	ipc_sm_kick(IPC_SM_RUN_TFR_COMPLETE,
		(struct ipc_link_context *)context);
}

static irqreturn_t slave_ready_irq(int irq, void *dev)
{
	struct ipc_link_context *context = (struct ipc_link_context *)dev;
	struct modem_m6718_spi_link_platform_data *link = context->link;
	struct spi_device *sdev = context->sdev;

	if (irq != GPIO_TO_IRQ(link->gpio.int_pin)) {
		dev_err(&sdev->dev,
			"link %d error: spurious slave irq!", link->id);
		return IRQ_NONE;
	}

#ifdef WORKAROUND_DUPLICATED_IRQ
	if (pl022_tfr_in_progress(sdev)) {
		dev_warn(&sdev->dev,
			"link %d warning: slave irq while transfer "
			"is active! discarding event\n", link->id);
		return IRQ_HANDLED;
	}
#endif
	ipc_sm_kick(IPC_SM_RUN_SLAVE_IRQ, context);
	return IRQ_HANDLED;
}

#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_MODEM_STATE
static int modem_state_callback(unsigned long unused)
{
	int modem_state = modem_state_get_state();
	struct ipc_link_context *contexts = l1_context.device_context;
	u8 i;

	pr_info("M6718 IPC protocol modemstate reports modem is %s\n",
		modem_state_to_str(modem_state));

	switch (modem_state) {
	case MODEM_STATE_ON:
		/*
		 * Modem is on, ensure each link is configured and trigger
		 * a state change on link0 to begin handshake.
		 */
		for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
			ipc_util_link_gpio_config(&contexts[i]);
		ipc_sm_kick(IPC_SM_RUN_INIT, &contexts[0]);
		break;
	case MODEM_STATE_OFF:
	case MODEM_STATE_RESET:
	case MODEM_STATE_CRASH:
		/* force all links to reset */
		for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
			ipc_sm_kick(IPC_SM_RUN_RESET, &contexts[i]);
		break;
	default:
		break;
	}
	return 0;
}

static void modem_state_reg_wq(struct work_struct *work)
{
	if (modem_state_register_callback(modem_state_callback, 0) == -EAGAIN) {
		pr_info("M6718 IPC protocol failed to register with "
			"modemstate, will retry\n");
		schedule_delayed_work(&modem_state_reg_work,
			(MODEM_STATE_REGISTER_TMO_MS * HZ) / 1000);
	} else {
		pr_info("M6718 IPC protocol registered with modemstate\n");
	}
}
#endif

int modem_protocol_probe(struct spi_device *sdev)
{
	struct modem_m6718_spi_link_platform_data *link =
		sdev->dev.platform_data;
	struct ipc_link_context *context;
	int link_id;

	if (link == NULL) {
		/* platform data missing in board config? */
		dev_err(&sdev->dev, "error: no platform data for link!\n");
		return -ENODEV;
	}

	link_id = link->id;
	context = &l1_context.device_context[link_id];

	if (link_id >= IPC_NBR_SUPPORTED_SPI_LINKS) {
		dev_err(&sdev->dev,
			"link %d error: too many links! (max %d)\n",
			link->id, IPC_NBR_SUPPORTED_SPI_LINKS);
		return -ENODEV;
	}

	dev_info(&sdev->dev,
		"link %d: registering SPI link bus:%d cs:%d\n",
		link->id, sdev->master->bus_num, sdev->chip_select);

	/* update spi device with correct word size for our device */
	sdev->bits_per_word = 16;
	spi_setup(sdev);

	/* init link context */
	context->link = link;
	context->sdev = sdev;
	ipc_util_resume_link(context);
	atomic_set(&context->gpio_configured, 0);
	atomic_set(&context->state_int,
		ipc_util_int_level_inactive(context));
	spin_lock_init(&context->sm_lock);
	context->state = ipc_sm_init_state(context);
	ipc_util_spi_message_init(context, spi_tfr_complete);
	init_timer(&context->comms_timer);
	context->comms_timer.function = modem_comms_timeout;
	context->comms_timer.data = (unsigned long)context;
	init_timer(&context->slave_stable_timer);
	context->slave_stable_timer.function = slave_stable_timeout;
	context->slave_stable_timer.data = (unsigned long)context;

	if (!ipc_util_link_gpio_request(context, slave_ready_irq))
		return -ENODEV;
	if (!ipc_util_link_gpio_config(context))
		return -ENODEV;

#ifdef CONFIG_MODEM_M6718_SPI_ENABLE_FEATURE_VERIFY_FRAMES
	context->last_frame = NULL;
#endif

	ipc_queue_init(context);
	ipc_dbg_debugfs_link_init(context);
	ipc_dbg_throughput_link_init(context);
	ipc_create_netlink_socket(context);

	/*
	 * For link0 (the handshake link) we force a state transition now so
	 * that it prepares for boot sync.
	 */
	if (link->id == 0)
		ipc_sm_kick(IPC_SM_RUN_INIT, context);

	/*
	 * unlikely but possible: for links other than 0, check if handshake is
	 * already complete by the time this link is probed - if so we force a
	 * state transition since the one issued by the handshake exit actions
	 * will have been ignored.
	 */
	if (link->id > 0 && atomic_read(&l1_context.boot_sync_done)) {
		dev_dbg(&sdev->dev,
			"link %d: boot sync is done, kicking state machine\n",
			link->id);
		ipc_sm_kick(IPC_SM_RUN_INIT, context);
	}
	return 0;
}

void modem_protocol_exit(void)
{
	int i;

	pr_info("M6718 IPC protocol exit\n");
	for (i = 0; i < IPC_NBR_SUPPORTED_SPI_LINKS; i++)
		ipc_util_link_gpio_unconfig(&l1_context.device_context[i]);
}