1/*
2 * SPI bitbang implementation using generic gpio
3 *
4 * Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24#include <libwebsockets.h>
25
26int
27lws_bb_spi_init(const lws_spi_ops_t *octx)
28{
29	lws_bb_spi_t *ctx = (lws_bb_spi_t *)octx;
30	int n;
31
32	for (n = 0; n < LWS_SPI_BB_MAX_CH; n++) {
33		if (ctx->flags & (1 << n))
34			ctx->gpio->mode(ctx->ncs[n], LWSGGPIO_FL_WRITE);
35		if (ctx->flags & (1 << (n + 4)))
36			ctx->gpio->mode(ctx->ncmd[n], LWSGGPIO_FL_WRITE);
37	}
38
39	ctx->gpio->mode(ctx->clk, LWSGGPIO_FL_WRITE |
40				  ((octx->bus_mode & LWSSPIMODE_CPOL) ?
41					   0 : LWSGGPIO_FL_START_LOW));
42	ctx->gpio->mode(ctx->mosi, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_START_LOW);
43	ctx->gpio->mode(ctx->miso, LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
44
45	return 0;
46}
47
48/* if active, prepare DnC before this and call separately for Cmd / Data */
49
50static void
51lws_bb_spi_write(lws_bb_spi_t *ctx, const uint8_t *buf, size_t len)
52{
53	uint8_t u, inv = !!(ctx->bb_ops.bus_mode & LWSSPIMODE_CPOL);
54
55	while (len--) {
56		int n;
57
58		u = *buf++;
59
60		for (n = 0; n < 4; n++) {
61			ctx->gpio->set(ctx->clk, inv);
62			ctx->gpio->set(ctx->mosi, !!(u & 0x80));
63			ctx->gpio->set(ctx->clk, !inv);
64			ctx->gpio->set(ctx->clk, inv);
65			ctx->gpio->set(ctx->mosi, !!(u & 0x40));
66			ctx->gpio->set(ctx->clk, !inv);
67			u <<= 2;
68		}
69	}
70
71	ctx->gpio->set(ctx->clk, 0 ^ inv);
72}
73
74static void
75lws_bb_spi_read(lws_bb_spi_t *ctx, uint8_t *buf, size_t len)
76{
77	uint8_t u = 0;
78	uint8_t inv = !!(ctx->bb_ops.bus_mode & LWSSPIMODE_CPOL);
79
80	while (len--) {
81		int n;
82
83		for (n = 0; n < 8; n++) {
84			ctx->gpio->set(ctx->clk, inv);
85			u = (u << 1) | !!ctx->gpio->read(ctx->miso);
86			ctx->gpio->set(ctx->mosi, !!(u & 0x80));
87			ctx->gpio->set(ctx->clk, !inv);
88		}
89		*buf++ = u;
90	}
91
92	ctx->gpio->set(ctx->clk, 0 ^ inv);
93}
94
95int
96lws_bb_spi_queue(const lws_spi_ops_t *octx, const lws_spi_desc_t *desc)
97{
98	lws_bb_spi_t *ctx = (lws_bb_spi_t *)octx;
99	const uint8_t *src = desc->src;
100
101	/* clock to idle */
102	ctx->gpio->set(ctx->clk, 0 ^ !!(octx->bus_mode & LWSSPIMODE_CPOL));
103	/* enable nCS */
104	ctx->gpio->set(ctx->ncs[desc->channel], 0);
105
106	if (desc->count_cmd) {
107		ctx->gpio->set(ctx->ncmd[desc->channel], 0);
108		lws_bb_spi_write(ctx, src, desc->count_cmd);
109		ctx->gpio->set(ctx->ncmd[desc->channel], 1);
110
111		src += desc->count_cmd;
112	}
113
114	if (desc->count_write)
115		lws_bb_spi_write(ctx, desc->data, desc->count_write);
116
117	if (desc->count_read)
118		lws_bb_spi_read(ctx, desc->dest, desc->count_read);
119
120	/* disable nCS */
121	ctx->gpio->set(ctx->ncs[desc->channel], 1);
122
123	/* clock to idle */
124	ctx->gpio->set(ctx->clk, 0 ^ !!(octx->bus_mode & LWSSPIMODE_CPOL));
125
126	return 0;
127}
128