summaryrefslogtreecommitdiff
path: root/drivers/modem/m6718_spi/util.c
blob: 9026f4427dda49540ee3cc3baea1cf382d0ba9ac (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
/*
 * 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:
 *   utility functions.
 */
#include <linux/gpio.h>
#include <linux/modem/m6718_spi/modem_driver.h>
#include "modem_util.h"

#define MODEM_COMMS_TMO_MS  (5000) /* 0 == no timeout */
#define SLAVE_STABLE_TMO_MS (1000)

#define DRIVER_NAME "ipcspi" /* name used when reserving gpio pins */


bool ipc_util_channel_is_loopback(u8 channel)
{
	return channel == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK0 ||
		channel == MODEM_M6718_SPI_CHN_MASTER_LOOPBACK1;
}

u32 ipc_util_make_l2_header(u8 channel, u32 len)
{
	return ((channel & 0xf) << 28) | (len & 0x000fffff);
}

u8 ipc_util_get_l2_channel(u32 hdr)
{
	return hdr >> 28;
}

u32 ipc_util_get_l2_length(u32 hdr)
{
	return hdr & 0x000fffff;
}

u32 ipc_util_make_l1_header(u8 cmd, u8 counter, u32 len)
{
	return (cmd << 28) |
		((counter & 0x000000ff) << 20) |
		(len & 0x000fffff);
}

u8 ipc_util_get_l1_cmd(u32 hdr)
{
	return hdr >> 28;
}

u8 ipc_util_get_l1_counter(u32 hdr)
{
	return (hdr >> 20) & 0x000000ff;
}

u32 ipc_util_get_l1_length(u32 hdr)
{
	return hdr & 0x000fffff;
}

u8 ipc_util_get_l1_bootresp_ver(u32 bootresp)
{
	return bootresp & 0x000000ff;
}

int ipc_util_ss_level_active(struct ipc_link_context *context)
{
	return context->link->gpio.ss_active == 0 ? 0 : 1;
}

int ipc_util_ss_level_inactive(struct ipc_link_context *context)
{
	return !ipc_util_ss_level_active(context);
}

int ipc_util_int_level_active(struct ipc_link_context *context)
{
	return context->link->gpio.int_active == 0 ? 0 : 1;
}

int ipc_util_int_level_inactive(struct ipc_link_context *context)
{
	return !ipc_util_int_level_active(context);
}

void ipc_util_deactivate_ss(struct ipc_link_context *context)
{
	gpio_set_value(context->link->gpio.ss_pin,
		ipc_util_ss_level_inactive(context));

	dev_dbg(&context->sdev->dev,
		"link %d: deactivated SS\n", context->link->id);
}

void ipc_util_activate_ss(struct ipc_link_context *context)
{
	gpio_set_value(context->link->gpio.ss_pin,
		ipc_util_ss_level_active(context));

	dev_dbg(&context->sdev->dev,
		"link %d: activated SS\n", context->link->id);
}

void ipc_util_activate_ss_with_tmo(struct ipc_link_context *context)
{
	gpio_set_value(context->link->gpio.ss_pin,
		ipc_util_ss_level_active(context));

#if MODEM_COMMS_TMO_MS == 0
	dev_dbg(&context->sdev->dev,
		 "link %d: activated SS (timeout is disabled)\n",
		 context->link->id);
#else
	context->comms_timer.expires = jiffies +
		((MODEM_COMMS_TMO_MS * HZ) / 1000);
	add_timer(&context->comms_timer);

	dev_dbg(&context->sdev->dev,
		"link %d: activated SS with timeout\n", context->link->id);
#endif
}

bool ipc_util_int_is_active(struct ipc_link_context *context)
{
	return gpio_get_value(context->link->gpio.int_pin) ==
		ipc_util_int_level_active(context);
}

bool ipc_util_link_is_idle(struct ipc_link_context *context)
{
	if (context->state == NULL)
		return false;

	switch (context->state->id) {
	case IPC_SM_IDL:
		return true;
	default:
		return false;
	}
}

void ipc_util_start_slave_stable_timer(struct ipc_link_context *context)
{
	context->slave_stable_timer.expires =
		jiffies + ((SLAVE_STABLE_TMO_MS * HZ) / 1000);
	add_timer(&context->slave_stable_timer);
}

void ipc_util_spi_message_prepare(struct ipc_link_context *link_context,
	void *tx_buf, void *rx_buf, int len)
{
	struct spi_transfer *tfr = &link_context->spi_transfer;
	struct spi_message *msg = &link_context->spi_message;

	tfr->tx_buf = tx_buf;
	tfr->rx_buf = rx_buf;
	tfr->len    = len;
	msg->context = link_context;
}

void ipc_util_spi_message_init(struct ipc_link_context *link_context,
	void (*complete)(void *))
{
	struct spi_message *msg = &link_context->spi_message;
	struct spi_transfer *tfr = &link_context->spi_transfer;

	tfr->bits_per_word = 16;

	/* common init of transfer - use default from board device */
	tfr->cs_change = 0;
	tfr->speed_hz = 0;
	tfr->delay_usecs = 0;

	/* common init of message */
	spi_message_init(msg);
	msg->spi = link_context->sdev;
	msg->complete = complete;
	spi_message_add_tail(tfr, msg);
}

bool ipc_util_link_gpio_request(struct ipc_link_context *context,
	irqreturn_t (*irqhnd)(int, void*))
{
	struct spi_device *sdev = context->sdev;
	struct modem_m6718_spi_link_platform_data *link = context->link;
	unsigned long irqflags;

	if (gpio_request(link->gpio.ss_pin, DRIVER_NAME) < 0) {
		dev_err(&sdev->dev,
			"link %d error: failed to get gpio %d for SS pin\n",
			link->id,
			link->gpio.ss_pin);
		return false;
	}
	if (gpio_request(link->gpio.int_pin, DRIVER_NAME) < 0) {
		dev_err(&sdev->dev,
			"link %d error: failed to get gpio %d for INT pin\n",
			link->id,
			link->gpio.int_pin);
		return false;
	}

	if (ipc_util_int_level_active(context) == 1)
		irqflags = IRQF_TRIGGER_RISING;
	else
		irqflags = IRQF_TRIGGER_FALLING;

	if (request_irq(GPIO_TO_IRQ(link->gpio.int_pin),
			irqhnd,
			irqflags,
			DRIVER_NAME,
			context) < 0) {
		dev_err(&sdev->dev,
			"link %d error: could not get irq %d\n",
			link->id, GPIO_TO_IRQ(link->gpio.int_pin));
		return false;
	}
	return true;
}

bool ipc_util_link_gpio_config(struct ipc_link_context *context)
{
	struct spi_device *sdev = context->sdev;
	struct modem_m6718_spi_link_platform_data *link = context->link;

	if (atomic_read(&context->gpio_configured) == 1)
		return true;

	dev_dbg(&sdev->dev, "link %d: configuring GPIO\n", link->id);

	ipc_util_deactivate_ss(context);
	gpio_direction_input(link->gpio.int_pin);
	if (enable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin)) < 0) {
		dev_err(&sdev->dev,
			"link %d error: failed to enable wake on INT\n",
			link->id);
		return false;
	}

	atomic_set(&context->state_int, gpio_get_value(link->gpio.int_pin));
	atomic_set(&context->gpio_configured, 1);
	return true;
}

bool ipc_util_link_gpio_unconfig(struct ipc_link_context *context)
{
	struct spi_device *sdev = context->sdev;
	struct modem_m6718_spi_link_platform_data *link = context->link;

	if (atomic_read(&context->gpio_configured) == 0)
		return true;

	dev_dbg(&sdev->dev, "link %d: un-configuring GPIO\n", link->id);

	/* SS: output anyway, just make sure it is low */
	gpio_set_value(link->gpio.ss_pin, 0);

	/* INT: disable system-wake, reconfigure as output-low */
	disable_irq_wake(GPIO_TO_IRQ(link->gpio.int_pin));
	gpio_direction_output(link->gpio.int_pin, 0);
	atomic_set(&context->gpio_configured, 0);
	return true;
}

bool ipc_util_link_is_suspended(struct ipc_link_context *context)
{
	return atomic_read(&context->suspended) == 1;
}

void ipc_util_suspend_link(struct ipc_link_context *context)
{
	atomic_set(&context->suspended, 1);
}

void ipc_util_resume_link(struct ipc_link_context *context)
{
	atomic_set(&context->suspended, 0);
}