xref: /openbmc/linux/net/nfc/nci/spi.c (revision ce55c22ec8b223a90ff3e084d842f73cfba35588)
1a61127c2SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28a00a61bSFrederic Danis /*
38a00a61bSFrederic Danis  * Copyright (C) 2013  Intel Corporation. All rights reserved.
48a00a61bSFrederic Danis  */
58a00a61bSFrederic Danis 
68a00a61bSFrederic Danis #define pr_fmt(fmt) "nci_spi: %s: " fmt, __func__
78a00a61bSFrederic Danis 
8fcd9d046SVincent Cuissard #include <linux/module.h>
9fcd9d046SVincent Cuissard 
108a00a61bSFrederic Danis #include <linux/export.h>
118a00a61bSFrederic Danis #include <linux/spi/spi.h>
12ee9596d4SFrederic Danis #include <linux/crc-ccitt.h>
138a00a61bSFrederic Danis #include <net/nfc/nci_core.h>
148a00a61bSFrederic Danis 
15391d8a2dSFrederic Danis #define NCI_SPI_ACK_SHIFT		6
16391d8a2dSFrederic Danis #define NCI_SPI_MSB_PAYLOAD_MASK	0x3F
178a00a61bSFrederic Danis 
18ee9596d4SFrederic Danis #define NCI_SPI_SEND_TIMEOUT	(NCI_CMD_TIMEOUT > NCI_DATA_TIMEOUT ? \
19ee9596d4SFrederic Danis 					NCI_CMD_TIMEOUT : NCI_DATA_TIMEOUT)
20ee9596d4SFrederic Danis 
21ee9596d4SFrederic Danis #define NCI_SPI_DIRECT_WRITE	0x01
22ee9596d4SFrederic Danis #define NCI_SPI_DIRECT_READ	0x02
23ee9596d4SFrederic Danis 
24ee9596d4SFrederic Danis #define ACKNOWLEDGE_NONE	0
25ee9596d4SFrederic Danis #define ACKNOWLEDGE_ACK		1
26ee9596d4SFrederic Danis #define ACKNOWLEDGE_NACK	2
27ee9596d4SFrederic Danis 
28ee9596d4SFrederic Danis #define CRC_INIT		0xFFFF
29ee9596d4SFrederic Danis 
__nci_spi_send(struct nci_spi * nspi,const struct sk_buff * skb,int cs_change)30ddecf555SKrzysztof Kozlowski static int __nci_spi_send(struct nci_spi *nspi, const struct sk_buff *skb,
312bed2785SEric Lapuyade 			  int cs_change)
32ee9596d4SFrederic Danis {
33ee9596d4SFrederic Danis 	struct spi_message m;
34ee9596d4SFrederic Danis 	struct spi_transfer t;
35ee9596d4SFrederic Danis 
36a4ada6caSEric Lapuyade 	memset(&t, 0, sizeof(struct spi_transfer));
372bed2785SEric Lapuyade 	/* a NULL skb means we just want the SPI chip select line to raise */
382bed2785SEric Lapuyade 	if (skb) {
39ee9596d4SFrederic Danis 		t.tx_buf = skb->data;
40ee9596d4SFrederic Danis 		t.len = skb->len;
412bed2785SEric Lapuyade 	} else {
422bed2785SEric Lapuyade 		/* still set tx_buf non NULL to make the driver happy */
432bed2785SEric Lapuyade 		t.tx_buf = &t;
442bed2785SEric Lapuyade 		t.len = 0;
452bed2785SEric Lapuyade 	}
462bed2785SEric Lapuyade 	t.cs_change = cs_change;
47099ffd7eSAlexandru Ardelean 	t.delay.value = nspi->xfer_udelay;
48099ffd7eSAlexandru Ardelean 	t.delay.unit = SPI_DELAY_UNIT_USECS;
492bd83245SVincent Cuissard 	t.speed_hz = nspi->xfer_speed_hz;
50ee9596d4SFrederic Danis 
51ee9596d4SFrederic Danis 	spi_message_init(&m);
52ee9596d4SFrederic Danis 	spi_message_add_tail(&t, &m);
53ee9596d4SFrederic Danis 
54fa544fffSEric Lapuyade 	return spi_sync(nspi->spi, &m);
55ee9596d4SFrederic Danis }
56ee9596d4SFrederic Danis 
nci_spi_send(struct nci_spi * nspi,struct completion * write_handshake_completion,struct sk_buff * skb)572bed2785SEric Lapuyade int nci_spi_send(struct nci_spi *nspi,
582bed2785SEric Lapuyade 		 struct completion *write_handshake_completion,
592bed2785SEric Lapuyade 		 struct sk_buff *skb)
608a00a61bSFrederic Danis {
61ee9596d4SFrederic Danis 	unsigned int payload_len = skb->len;
62ee9596d4SFrederic Danis 	unsigned char *hdr;
63ee9596d4SFrederic Danis 	int ret;
64ee9596d4SFrederic Danis 	long completion_rc;
65ee9596d4SFrederic Danis 
66ee9596d4SFrederic Danis 	/* add the NCI SPI header to the start of the buffer */
67ee9596d4SFrederic Danis 	hdr = skb_push(skb, NCI_SPI_HDR_LEN);
68ee9596d4SFrederic Danis 	hdr[0] = NCI_SPI_DIRECT_WRITE;
69fa544fffSEric Lapuyade 	hdr[1] = nspi->acknowledge_mode;
70ee9596d4SFrederic Danis 	hdr[2] = payload_len >> 8;
71ee9596d4SFrederic Danis 	hdr[3] = payload_len & 0xFF;
72ee9596d4SFrederic Danis 
73fa544fffSEric Lapuyade 	if (nspi->acknowledge_mode == NCI_SPI_CRC_ENABLED) {
74ee9596d4SFrederic Danis 		u16 crc;
75ee9596d4SFrederic Danis 
76ee9596d4SFrederic Danis 		crc = crc_ccitt(CRC_INIT, skb->data, skb->len);
77634fef61SJohannes Berg 		skb_put_u8(skb, crc >> 8);
78634fef61SJohannes Berg 		skb_put_u8(skb, crc & 0xFF);
79ee9596d4SFrederic Danis 	}
80ee9596d4SFrederic Danis 
812bed2785SEric Lapuyade 	if (write_handshake_completion)	{
822bed2785SEric Lapuyade 		/* Trick SPI driver to raise chip select */
832bed2785SEric Lapuyade 		ret = __nci_spi_send(nspi, NULL, 1);
842bed2785SEric Lapuyade 		if (ret)
852bed2785SEric Lapuyade 			goto done;
86ee9596d4SFrederic Danis 
872bed2785SEric Lapuyade 		/* wait for NFC chip hardware handshake to complete */
882bed2785SEric Lapuyade 		if (wait_for_completion_timeout(write_handshake_completion,
892bed2785SEric Lapuyade 						msecs_to_jiffies(1000)) == 0) {
902bed2785SEric Lapuyade 			ret = -ETIME;
912bed2785SEric Lapuyade 			goto done;
922bed2785SEric Lapuyade 		}
932bed2785SEric Lapuyade 	}
94ee9596d4SFrederic Danis 
952bed2785SEric Lapuyade 	ret = __nci_spi_send(nspi, skb, 0);
96fa544fffSEric Lapuyade 	if (ret != 0 || nspi->acknowledge_mode == NCI_SPI_CRC_DISABLED)
97ee9596d4SFrederic Danis 		goto done;
98ee9596d4SFrederic Danis 
999bec44bfSAxel Lin 	reinit_completion(&nspi->req_completion);
100d5937511SEric Lapuyade 	completion_rc =	wait_for_completion_interruptible_timeout(
101fa544fffSEric Lapuyade 							&nspi->req_completion,
102ee9596d4SFrederic Danis 							NCI_SPI_SEND_TIMEOUT);
103ee9596d4SFrederic Danis 
104fa544fffSEric Lapuyade 	if (completion_rc <= 0 || nspi->req_result == ACKNOWLEDGE_NACK)
105ee9596d4SFrederic Danis 		ret = -EIO;
106ee9596d4SFrederic Danis 
107ee9596d4SFrederic Danis done:
1082bed2785SEric Lapuyade 	kfree_skb(skb);
1092bed2785SEric Lapuyade 
110ee9596d4SFrederic Danis 	return ret;
1118a00a61bSFrederic Danis }
112fa544fffSEric Lapuyade EXPORT_SYMBOL_GPL(nci_spi_send);
1138a00a61bSFrederic Danis 
1148a00a61bSFrederic Danis /* ---- Interface to NCI SPI drivers ---- */
1158a00a61bSFrederic Danis 
1168a00a61bSFrederic Danis /**
117fa544fffSEric Lapuyade  * nci_spi_allocate_spi - allocate a new nci spi
1188a00a61bSFrederic Danis  *
1198a00a61bSFrederic Danis  * @spi: SPI device
120fa544fffSEric Lapuyade  * @acknowledge_mode: Acknowledge mode used by the NFC device
1218a00a61bSFrederic Danis  * @delay: delay between transactions in us
122fa544fffSEric Lapuyade  * @ndev: nci dev to send incoming nci frames to
1238a00a61bSFrederic Danis  */
nci_spi_allocate_spi(struct spi_device * spi,u8 acknowledge_mode,unsigned int delay,struct nci_dev * ndev)124fa544fffSEric Lapuyade struct nci_spi *nci_spi_allocate_spi(struct spi_device *spi,
125fa544fffSEric Lapuyade 				     u8 acknowledge_mode, unsigned int delay,
126fa544fffSEric Lapuyade 				     struct nci_dev *ndev)
1278a00a61bSFrederic Danis {
128fa544fffSEric Lapuyade 	struct nci_spi *nspi;
1298a00a61bSFrederic Danis 
130fa544fffSEric Lapuyade 	nspi = devm_kzalloc(&spi->dev, sizeof(struct nci_spi), GFP_KERNEL);
131fa544fffSEric Lapuyade 	if (!nspi)
1328a00a61bSFrederic Danis 		return NULL;
1338a00a61bSFrederic Danis 
134fa544fffSEric Lapuyade 	nspi->acknowledge_mode = acknowledge_mode;
135fa544fffSEric Lapuyade 	nspi->xfer_udelay = delay;
1362bd83245SVincent Cuissard 	/* Use controller max SPI speed by default */
1372bd83245SVincent Cuissard 	nspi->xfer_speed_hz = 0;
138645d5087SEric Lapuyade 	nspi->spi = spi;
139fa544fffSEric Lapuyade 	nspi->ndev = ndev;
1409bec44bfSAxel Lin 	init_completion(&nspi->req_completion);
1418a00a61bSFrederic Danis 
142fa544fffSEric Lapuyade 	return nspi;
1438a00a61bSFrederic Danis }
144fa544fffSEric Lapuyade EXPORT_SYMBOL_GPL(nci_spi_allocate_spi);
1458a00a61bSFrederic Danis 
send_acknowledge(struct nci_spi * nspi,u8 acknowledge)146fa544fffSEric Lapuyade static int send_acknowledge(struct nci_spi *nspi, u8 acknowledge)
147391d8a2dSFrederic Danis {
148391d8a2dSFrederic Danis 	struct sk_buff *skb;
149391d8a2dSFrederic Danis 	unsigned char *hdr;
150391d8a2dSFrederic Danis 	u16 crc;
151391d8a2dSFrederic Danis 	int ret;
152391d8a2dSFrederic Danis 
153fa544fffSEric Lapuyade 	skb = nci_skb_alloc(nspi->ndev, 0, GFP_KERNEL);
154*7937609cSKrzysztof Kozlowski 	if (!skb)
155*7937609cSKrzysztof Kozlowski 		return -ENOMEM;
156391d8a2dSFrederic Danis 
157391d8a2dSFrederic Danis 	/* add the NCI SPI header to the start of the buffer */
158391d8a2dSFrederic Danis 	hdr = skb_push(skb, NCI_SPI_HDR_LEN);
159391d8a2dSFrederic Danis 	hdr[0] = NCI_SPI_DIRECT_WRITE;
160391d8a2dSFrederic Danis 	hdr[1] = NCI_SPI_CRC_ENABLED;
161391d8a2dSFrederic Danis 	hdr[2] = acknowledge << NCI_SPI_ACK_SHIFT;
162391d8a2dSFrederic Danis 	hdr[3] = 0;
163391d8a2dSFrederic Danis 
164391d8a2dSFrederic Danis 	crc = crc_ccitt(CRC_INIT, skb->data, skb->len);
165634fef61SJohannes Berg 	skb_put_u8(skb, crc >> 8);
166634fef61SJohannes Berg 	skb_put_u8(skb, crc & 0xFF);
167391d8a2dSFrederic Danis 
1682bed2785SEric Lapuyade 	ret = __nci_spi_send(nspi, skb, 0);
169391d8a2dSFrederic Danis 
170391d8a2dSFrederic Danis 	kfree_skb(skb);
171391d8a2dSFrederic Danis 
172391d8a2dSFrederic Danis 	return ret;
173391d8a2dSFrederic Danis }
174391d8a2dSFrederic Danis 
__nci_spi_read(struct nci_spi * nspi)17522d4aae5SEric Lapuyade static struct sk_buff *__nci_spi_read(struct nci_spi *nspi)
176391d8a2dSFrederic Danis {
177391d8a2dSFrederic Danis 	struct sk_buff *skb;
178391d8a2dSFrederic Danis 	struct spi_message m;
179391d8a2dSFrederic Danis 	unsigned char req[2], resp_hdr[2];
180391d8a2dSFrederic Danis 	struct spi_transfer tx, rx;
181391d8a2dSFrederic Danis 	unsigned short rx_len = 0;
182391d8a2dSFrederic Danis 	int ret;
183391d8a2dSFrederic Danis 
184391d8a2dSFrederic Danis 	spi_message_init(&m);
185a4ada6caSEric Lapuyade 
186a4ada6caSEric Lapuyade 	memset(&tx, 0, sizeof(struct spi_transfer));
187391d8a2dSFrederic Danis 	req[0] = NCI_SPI_DIRECT_READ;
188fa544fffSEric Lapuyade 	req[1] = nspi->acknowledge_mode;
189391d8a2dSFrederic Danis 	tx.tx_buf = req;
190391d8a2dSFrederic Danis 	tx.len = 2;
191391d8a2dSFrederic Danis 	tx.cs_change = 0;
1922bd83245SVincent Cuissard 	tx.speed_hz = nspi->xfer_speed_hz;
193391d8a2dSFrederic Danis 	spi_message_add_tail(&tx, &m);
194a4ada6caSEric Lapuyade 
195a4ada6caSEric Lapuyade 	memset(&rx, 0, sizeof(struct spi_transfer));
196391d8a2dSFrederic Danis 	rx.rx_buf = resp_hdr;
197391d8a2dSFrederic Danis 	rx.len = 2;
198391d8a2dSFrederic Danis 	rx.cs_change = 1;
1992bd83245SVincent Cuissard 	rx.speed_hz = nspi->xfer_speed_hz;
200391d8a2dSFrederic Danis 	spi_message_add_tail(&rx, &m);
201a4ada6caSEric Lapuyade 
202fa544fffSEric Lapuyade 	ret = spi_sync(nspi->spi, &m);
203391d8a2dSFrederic Danis 	if (ret)
204391d8a2dSFrederic Danis 		return NULL;
205391d8a2dSFrederic Danis 
206fa544fffSEric Lapuyade 	if (nspi->acknowledge_mode == NCI_SPI_CRC_ENABLED)
207391d8a2dSFrederic Danis 		rx_len = ((resp_hdr[0] & NCI_SPI_MSB_PAYLOAD_MASK) << 8) +
208391d8a2dSFrederic Danis 				resp_hdr[1] + NCI_SPI_CRC_LEN;
209391d8a2dSFrederic Danis 	else
210391d8a2dSFrederic Danis 		rx_len = (resp_hdr[0] << 8) | resp_hdr[1];
211391d8a2dSFrederic Danis 
212fa544fffSEric Lapuyade 	skb = nci_skb_alloc(nspi->ndev, rx_len, GFP_KERNEL);
213391d8a2dSFrederic Danis 	if (!skb)
214391d8a2dSFrederic Danis 		return NULL;
215391d8a2dSFrederic Danis 
216391d8a2dSFrederic Danis 	spi_message_init(&m);
217a4ada6caSEric Lapuyade 
218a4ada6caSEric Lapuyade 	memset(&rx, 0, sizeof(struct spi_transfer));
219391d8a2dSFrederic Danis 	rx.rx_buf = skb_put(skb, rx_len);
220391d8a2dSFrederic Danis 	rx.len = rx_len;
221391d8a2dSFrederic Danis 	rx.cs_change = 0;
222099ffd7eSAlexandru Ardelean 	rx.delay.value = nspi->xfer_udelay;
223099ffd7eSAlexandru Ardelean 	rx.delay.unit = SPI_DELAY_UNIT_USECS;
2242bd83245SVincent Cuissard 	rx.speed_hz = nspi->xfer_speed_hz;
225391d8a2dSFrederic Danis 	spi_message_add_tail(&rx, &m);
226a4ada6caSEric Lapuyade 
227fa544fffSEric Lapuyade 	ret = spi_sync(nspi->spi, &m);
228391d8a2dSFrederic Danis 	if (ret)
229391d8a2dSFrederic Danis 		goto receive_error;
230391d8a2dSFrederic Danis 
231fa544fffSEric Lapuyade 	if (nspi->acknowledge_mode == NCI_SPI_CRC_ENABLED) {
232d58ff351SJohannes Berg 		*(u8 *)skb_push(skb, 1) = resp_hdr[1];
233d58ff351SJohannes Berg 		*(u8 *)skb_push(skb, 1) = resp_hdr[0];
234391d8a2dSFrederic Danis 	}
235391d8a2dSFrederic Danis 
236391d8a2dSFrederic Danis 	return skb;
237391d8a2dSFrederic Danis 
238391d8a2dSFrederic Danis receive_error:
239391d8a2dSFrederic Danis 	kfree_skb(skb);
240391d8a2dSFrederic Danis 
241391d8a2dSFrederic Danis 	return NULL;
242391d8a2dSFrederic Danis }
243391d8a2dSFrederic Danis 
nci_spi_check_crc(struct sk_buff * skb)244391d8a2dSFrederic Danis static int nci_spi_check_crc(struct sk_buff *skb)
245391d8a2dSFrederic Danis {
246391d8a2dSFrederic Danis 	u16 crc_data = (skb->data[skb->len - 2] << 8) |
247391d8a2dSFrederic Danis 			skb->data[skb->len - 1];
248391d8a2dSFrederic Danis 	int ret;
249391d8a2dSFrederic Danis 
250391d8a2dSFrederic Danis 	ret = (crc_ccitt(CRC_INIT, skb->data, skb->len - NCI_SPI_CRC_LEN)
251391d8a2dSFrederic Danis 			== crc_data);
252391d8a2dSFrederic Danis 
253391d8a2dSFrederic Danis 	skb_trim(skb, skb->len - NCI_SPI_CRC_LEN);
254391d8a2dSFrederic Danis 
255391d8a2dSFrederic Danis 	return ret;
256391d8a2dSFrederic Danis }
257391d8a2dSFrederic Danis 
nci_spi_get_ack(struct sk_buff * skb)258391d8a2dSFrederic Danis static u8 nci_spi_get_ack(struct sk_buff *skb)
259391d8a2dSFrederic Danis {
260391d8a2dSFrederic Danis 	u8 ret;
261391d8a2dSFrederic Danis 
262391d8a2dSFrederic Danis 	ret = skb->data[0] >> NCI_SPI_ACK_SHIFT;
263391d8a2dSFrederic Danis 
264391d8a2dSFrederic Danis 	/* Remove NFCC part of the header: ACK, NACK and MSB payload len */
265391d8a2dSFrederic Danis 	skb_pull(skb, 2);
266391d8a2dSFrederic Danis 
267391d8a2dSFrederic Danis 	return ret;
268391d8a2dSFrederic Danis }
269391d8a2dSFrederic Danis 
270391d8a2dSFrederic Danis /**
27122d4aae5SEric Lapuyade  * nci_spi_read - read frame from NCI SPI drivers
272391d8a2dSFrederic Danis  *
273fa544fffSEric Lapuyade  * @nspi: The nci spi
274391d8a2dSFrederic Danis  * Context: can sleep
275391d8a2dSFrederic Danis  *
276391d8a2dSFrederic Danis  * This call may only be used from a context that may sleep.  The sleep
277391d8a2dSFrederic Danis  * is non-interruptible, and has no timeout.
278391d8a2dSFrederic Danis  *
27922d4aae5SEric Lapuyade  * It returns an allocated skb containing the frame on success, or NULL.
280391d8a2dSFrederic Danis  */
nci_spi_read(struct nci_spi * nspi)28122d4aae5SEric Lapuyade struct sk_buff *nci_spi_read(struct nci_spi *nspi)
282391d8a2dSFrederic Danis {
283391d8a2dSFrederic Danis 	struct sk_buff *skb;
284391d8a2dSFrederic Danis 
285391d8a2dSFrederic Danis 	/* Retrieve frame from SPI */
28622d4aae5SEric Lapuyade 	skb = __nci_spi_read(nspi);
28722d4aae5SEric Lapuyade 	if (!skb)
288391d8a2dSFrederic Danis 		goto done;
289391d8a2dSFrederic Danis 
290fa544fffSEric Lapuyade 	if (nspi->acknowledge_mode == NCI_SPI_CRC_ENABLED) {
291391d8a2dSFrederic Danis 		if (!nci_spi_check_crc(skb)) {
292fa544fffSEric Lapuyade 			send_acknowledge(nspi, ACKNOWLEDGE_NACK);
293391d8a2dSFrederic Danis 			goto done;
294391d8a2dSFrederic Danis 		}
295391d8a2dSFrederic Danis 
296391d8a2dSFrederic Danis 		/* In case of acknowledged mode: if ACK or NACK received,
297391d8a2dSFrederic Danis 		 * unblock completion of latest frame sent.
298391d8a2dSFrederic Danis 		 */
299fa544fffSEric Lapuyade 		nspi->req_result = nci_spi_get_ack(skb);
300fa544fffSEric Lapuyade 		if (nspi->req_result)
301fa544fffSEric Lapuyade 			complete(&nspi->req_completion);
302391d8a2dSFrederic Danis 	}
303391d8a2dSFrederic Danis 
304391d8a2dSFrederic Danis 	/* If there is no payload (ACK/NACK only frame),
305391d8a2dSFrederic Danis 	 * free the socket buffer
306391d8a2dSFrederic Danis 	 */
30722d4aae5SEric Lapuyade 	if (!skb->len) {
308391d8a2dSFrederic Danis 		kfree_skb(skb);
30922d4aae5SEric Lapuyade 		skb = NULL;
310391d8a2dSFrederic Danis 		goto done;
311391d8a2dSFrederic Danis 	}
312391d8a2dSFrederic Danis 
313fa544fffSEric Lapuyade 	if (nspi->acknowledge_mode == NCI_SPI_CRC_ENABLED)
314fa544fffSEric Lapuyade 		send_acknowledge(nspi, ACKNOWLEDGE_ACK);
315391d8a2dSFrederic Danis 
316391d8a2dSFrederic Danis done:
317391d8a2dSFrederic Danis 
31822d4aae5SEric Lapuyade 	return skb;
319391d8a2dSFrederic Danis }
32022d4aae5SEric Lapuyade EXPORT_SYMBOL_GPL(nci_spi_read);
321fcd9d046SVincent Cuissard 
322fcd9d046SVincent Cuissard MODULE_LICENSE("GPL");
323