summaryrefslogtreecommitdiff
path: root/cmd/sbc_harness/hw/rp2040_hwspi.c
blob: 6742c60a4ee542a8a81cd7855481ad6c074621a0 (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
/* hw/rp2040_hwspi.c - `implements_spi` implementation for the RP2040's
 *                     ARM Primecell SSP (PL022) (implementation file)
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-Licence-Identifier: AGPL-3.0-or-later
 */

#include <assert.h>

#include <hardware/spi.h>  /* pico-sdk:hardware_spi */
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio5 */

#include <libmisc/vcall.h>

#include "hw/rp2040_hwspi.h"

static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt);

struct spi_vtable rp2040_hwspi_vtable = {
	.readwritev = rp2040_hwspi_readwritev,
};

void _rp2040_hwspi_init(struct rp2040_hwspi *self,
                        enum rp2040_hwspi_instance inst_num,
                        enum spi_mode mode,
                        uint baudrate_hz,
                        uint pin_miso,
                        uint pin_mosi,
                        uint pin_clk,
                        uint pin_cs) {
	/* Be not weary: This is but 12 lines of actual code; and many
	 * lines of comments and assert()s.  */
	spi_inst_t *inst;

	assert(self);
	assert(baudrate_hz);
	assert(pin_miso != pin_mosi);
	assert(pin_miso != pin_clk);
	assert(pin_miso != pin_cs);
	assert(pin_mosi != pin_clk);
	assert(pin_mosi != pin_cs);
	assert(pin_clk != pin_cs);

	/* I know we called this "hwspi", but we're actually going to
	 * disconnect the CS pin from the PL022 SSP and manually drive
	 * it from software.  This is because the PL022 has a maximum
	 * of 16-bit frames, while we need to be able to do *at least*
	 * 32-bit frames (and ideally, much larger).  By managing it
	 * ourselves, we can just keep CS pulled low extra-long,
	 * making the frame extra-long.  */

	/* Regarding the constraints on pin assignments: see the
	 * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions".  */
	switch (inst_num) {
	case RP2040_HWSPI_0:
		inst = spi0;
		assert(pin_miso ==  0 || pin_miso ==  4 || pin_miso == 16 || pin_miso == 20);
		/*assert(pin_cs   ==  1 || pin_cs   ==  5 || pin_cs   == 17 || pin_cs   == 21);*/
		assert(pin_clk  ==  2 || pin_clk  ==  6 || pin_clk  == 18 || pin_clk  == 22);
		assert(pin_mosi ==  3 || pin_mosi ==  7 || pin_mosi == 19 || pin_mosi == 23);
		break;
	case RP2040_HWSPI_1:
		inst = spi1;
		assert(pin_miso ==  8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28);
		/*assert(pin_cs   ==  9 || pin_cs   == 13 || pin_cs   == 25 || pin_cs   == 29);*/
		assert(pin_clk  == 10 || pin_clk  == 14 || pin_clk  == 26);
		assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27);
		break;
	default:
		assert(false);
	}

	spi_init(inst, baudrate_hz);
	spi_set_format(inst, 8,
	               (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0,
	               (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0,
	               SPI_MSB_FIRST);

	/* Connect the pins to the PL022; set them each to "function
	 * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3
	 * "GPIO Functions").
	 *
	 * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1",
	 * since on the RP2040 all of the "function 1" functions are
	 * some part of SPI.)  */
	gpio_set_function(pin_clk,  GPIO_FUNC_SPI);
	gpio_set_function(pin_mosi, GPIO_FUNC_SPI);
	gpio_set_function(pin_miso, GPIO_FUNC_SPI);

	/* Initialize the CS pin for software control.  */
	gpio_init(pin_cs);
	gpio_set_dir(pin_cs, GPIO_OUT);
	gpio_put(pin_cs, 1);

	/* Return.  */
	self->vtable = &rp2040_hwspi_vtable;
	self->inst = inst;
	self->pin_cs = pin_cs;
}

static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) {
	struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self);
	assert(self);
	spi_inst_t *inst = self->inst;

	assert(inst);
	assert(iov);
	assert(iovcnt);

	gpio_put(self->pin_cs, 0);
	/* TODO: Replace blocking reads+writes with DMA.  */
	for (int i = 0; i < iovcnt; i++) {
		if (iov[i].iov_write_src && iov[i].iov_read_dst)
			spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len);
		else if (iov[i].iov_write_src)
			spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len);
		else if (iov[i].iov_read_dst)
			spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len);
		else
			assert(false);
	}
	gpio_put(self->pin_cs, 1);
}