paul@199 | 1 | /* |
paul@199 | 2 | * Perform SPI communication using GPIO operations. |
paul@199 | 3 | * |
paul@199 | 4 | * Copyright (C) 2018, 2020, 2023 Paul Boddie <paul@boddie.org.uk> |
paul@199 | 5 | * |
paul@199 | 6 | * This program is free software; you can redistribute it and/or |
paul@199 | 7 | * modify it under the terms of the GNU General Public License as |
paul@199 | 8 | * published by the Free Software Foundation; either version 2 of |
paul@199 | 9 | * the License, or (at your option) any later version. |
paul@199 | 10 | * |
paul@199 | 11 | * This program is distributed in the hope that it will be useful, |
paul@199 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@199 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@199 | 14 | * GNU General Public License for more details. |
paul@199 | 15 | * |
paul@199 | 16 | * You should have received a copy of the GNU General Public License |
paul@199 | 17 | * along with this program; if not, write to the Free Software |
paul@199 | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
paul@199 | 19 | * Boston, MA 02110-1301, USA |
paul@199 | 20 | */ |
paul@199 | 21 | |
paul@199 | 22 | #include <l4/devices/spi-gpio.h> |
paul@199 | 23 | #include <time.h> |
paul@199 | 24 | |
paul@199 | 25 | |
paul@199 | 26 | |
paul@199 | 27 | Spi_gpio::Spi_gpio(Hw::Gpio_chip *clock_device, int clock_pin, |
paul@199 | 28 | Hw::Gpio_chip *data_device, int data_pin, |
paul@199 | 29 | Hw::Gpio_chip *enable_device, int enable_pin, |
paul@222 | 30 | Hw::Gpio_chip *control_device, int control_pin) |
paul@199 | 31 | : _clock_device(clock_device), |
paul@199 | 32 | _clock_pin(clock_pin), |
paul@199 | 33 | _data_device(data_device), |
paul@199 | 34 | _data_pin(data_pin), |
paul@199 | 35 | _enable_device(enable_device), |
paul@199 | 36 | _enable_pin(enable_pin), |
paul@222 | 37 | _control_device(control_device), |
paul@222 | 38 | _control_pin(control_pin) |
paul@199 | 39 | { |
paul@199 | 40 | _clock_device->setup(_clock_pin, Hw::Gpio_chip::Output, 1); |
paul@199 | 41 | _data_device->setup(_data_pin, Hw::Gpio_chip::Output, 0); |
paul@199 | 42 | _enable_device->setup(_enable_pin, Hw::Gpio_chip::Output, 1); |
paul@232 | 43 | _control_device->setup(_control_pin, Hw::Gpio_chip::Output, 0); |
paul@199 | 44 | } |
paul@199 | 45 | |
paul@222 | 46 | Spi_gpio::Spi_gpio(uint64_t frequency, |
paul@222 | 47 | Hw::Gpio_chip *clock_device, int clock_pin, |
paul@222 | 48 | Hw::Gpio_chip *data_device, int data_pin, |
paul@222 | 49 | Hw::Gpio_chip *enable_device, int enable_pin, |
paul@222 | 50 | Hw::Gpio_chip *control_device, int control_pin) |
paul@222 | 51 | : Spi_gpio(clock_device, clock_pin, data_device, data_pin, enable_device, |
paul@222 | 52 | enable_pin, control_device, control_pin) |
paul@222 | 53 | { |
paul@222 | 54 | _frequency = frequency; |
paul@222 | 55 | } |
paul@222 | 56 | |
paul@221 | 57 | /* Send a byte sequence. */ |
paul@199 | 58 | |
paul@222 | 59 | uint32_t Spi_gpio::send(uint32_t bytes, const uint8_t data[]) |
paul@222 | 60 | { |
paul@222 | 61 | return send_dc(bytes, data, NULL); |
paul@222 | 62 | } |
paul@222 | 63 | |
paul@222 | 64 | /* Send a byte sequence with accompanying data/command indicators. */ |
paul@222 | 65 | |
paul@222 | 66 | uint32_t Spi_gpio::send_dc(uint32_t bytes, const uint8_t data[], |
paul@222 | 67 | const int dc[]) |
paul@199 | 68 | { |
paul@199 | 69 | struct timespec ts; |
paul@199 | 70 | uint8_t mask; |
paul@222 | 71 | int bit; |
paul@222 | 72 | uint32_t byte; |
paul@199 | 73 | |
paul@199 | 74 | if (_frequency) |
paul@199 | 75 | { |
paul@199 | 76 | ts.tv_sec = 0; |
paul@199 | 77 | ts.tv_nsec = 1000000000 / _frequency; |
paul@199 | 78 | } |
paul@199 | 79 | |
paul@199 | 80 | /* Initialise pin levels. */ |
paul@199 | 81 | |
paul@199 | 82 | _enable_device->set(_enable_pin, 1); |
paul@199 | 83 | _clock_device->set(_clock_pin, 1); |
paul@199 | 84 | _data_device->set(_data_pin, 0); |
paul@199 | 85 | |
paul@199 | 86 | /* Enter the transmission state. */ |
paul@199 | 87 | |
paul@199 | 88 | _enable_device->set(_enable_pin, 0); |
paul@199 | 89 | |
paul@199 | 90 | /* Clock data using the clock and data outputs. */ |
paul@199 | 91 | |
paul@199 | 92 | for (byte = 0; byte < bytes; byte++) |
paul@199 | 93 | { |
paul@199 | 94 | mask = 0x80; |
paul@199 | 95 | |
paul@199 | 96 | for (bit = 0; bit < 8; bit++) |
paul@199 | 97 | { |
paul@199 | 98 | /* NOTE: Data presented on falling clock level and sampled on rising clock |
paul@199 | 99 | level. This is SPI mode 3, or 0 given that the enable level is |
paul@199 | 100 | driven low immediately before the first bit is presented. */ |
paul@199 | 101 | |
paul@199 | 102 | _clock_device->set(_clock_pin, 0); |
paul@199 | 103 | _data_device->set(_data_pin, data[byte] & mask ? 1 : 0); |
paul@199 | 104 | |
paul@232 | 105 | if ((_control_device != NULL) && (_control_pin >= 0) && (dc != NULL)) |
paul@222 | 106 | _control_device->set(_control_pin, dc[byte] ? 1 : 0); |
paul@222 | 107 | |
paul@199 | 108 | if (_frequency) |
paul@199 | 109 | nanosleep(&ts, NULL); |
paul@199 | 110 | |
paul@199 | 111 | _clock_device->set(_clock_pin, 1); |
paul@199 | 112 | |
paul@199 | 113 | if (_frequency) |
paul@199 | 114 | nanosleep(&ts, NULL); |
paul@199 | 115 | |
paul@199 | 116 | mask >>= 1; |
paul@199 | 117 | } |
paul@199 | 118 | } |
paul@199 | 119 | |
paul@199 | 120 | _enable_device->set(_enable_pin, 1); |
paul@222 | 121 | |
paul@222 | 122 | return bytes; |
paul@222 | 123 | } |
paul@222 | 124 | |
paul@222 | 125 | /* Send a number of units, each holding a value in a big endian arrangement |
paul@222 | 126 | limited to the given character size, with any bit immediately above the |
paul@222 | 127 | character portion of the unit indicating the data/command level. */ |
paul@222 | 128 | |
paul@222 | 129 | uint32_t Spi_gpio::send_units(uint32_t bytes, const uint8_t data[], |
paul@222 | 130 | uint8_t unit_size, uint8_t char_size) |
paul@222 | 131 | { |
paul@232 | 132 | /* Obtain the data/command values for each unit. */ |
paul@232 | 133 | |
paul@232 | 134 | uint32_t chars = bytes / unit_size; |
paul@232 | 135 | int dc[chars]; |
paul@232 | 136 | |
paul@232 | 137 | /* Obtain the actual character bytes to be sent. */ |
paul@232 | 138 | |
paul@232 | 139 | int char_unit_size = ((char_size ? char_size - 1 : 0) / 8) + 1; |
paul@232 | 140 | uint8_t char_data[char_unit_size * chars]; |
paul@222 | 141 | |
paul@222 | 142 | /* Traverse the byte sequence, extracting data/command bits for each unit. */ |
paul@222 | 143 | |
paul@232 | 144 | for (uint32_t offset = 0, unit = 0, char_offset = 0; unit < chars; |
paul@232 | 145 | offset += unit_size, unit++, char_offset += char_unit_size) |
paul@222 | 146 | { |
paul@232 | 147 | /* Obtain the unit value. */ |
paul@232 | 148 | |
paul@232 | 149 | uint32_t value = 0; |
paul@232 | 150 | |
paul@232 | 151 | for (uint8_t byte = 0; byte < unit_size; byte++) |
paul@232 | 152 | value = (value << 8) | data[offset + byte]; |
paul@232 | 153 | |
paul@222 | 154 | /* The unit size must be greater than the character size for data/command |
paul@222 | 155 | bits to be present. */ |
paul@222 | 156 | |
paul@222 | 157 | if (unit_size * 8 <= char_size) |
paul@222 | 158 | dc[unit] = 0; |
paul@232 | 159 | |
paul@232 | 160 | /* Extract the data/command level. */ |
paul@232 | 161 | |
paul@222 | 162 | else |
paul@232 | 163 | dc[unit] = value & (1 << char_size) ? 1 : 0; |
paul@222 | 164 | |
paul@232 | 165 | /* Obtain the actual character data from the input data, discarding the most |
paul@232 | 166 | significant bytes beyond the character and masking out any remaining |
paul@232 | 167 | superfluous bits, thus obtaining a whole number of bytes. */ |
paul@222 | 168 | |
paul@232 | 169 | value = value & ((1 << char_size) - 1); |
paul@222 | 170 | |
paul@232 | 171 | for (uint8_t byte = char_unit_size; byte != 0; byte--) |
paul@232 | 172 | { |
paul@232 | 173 | char_data[char_offset + byte - 1] = value & 0xff; |
paul@232 | 174 | value >>= 8; |
paul@222 | 175 | } |
paul@222 | 176 | } |
paul@222 | 177 | |
paul@232 | 178 | return send_dc(chars, char_data, dc); |
paul@199 | 179 | } |
paul@199 | 180 | |
paul@199 | 181 | |
paul@199 | 182 | |
paul@199 | 183 | /* C language interface. */ |
paul@199 | 184 | |
paul@222 | 185 | void *spi_gpio_get_channel(uint64_t frequency, |
paul@222 | 186 | void *clock_chip, int clock_pin, |
paul@199 | 187 | void *data_chip, int data_pin, |
paul@199 | 188 | void *enable_chip, int enable_pin, |
paul@222 | 189 | void *control_chip, int control_pin) |
paul@199 | 190 | { |
paul@222 | 191 | return (void *) new Spi_gpio(frequency, |
paul@222 | 192 | reinterpret_cast<Hw::Gpio_chip *>(clock_chip), clock_pin, |
paul@199 | 193 | reinterpret_cast<Hw::Gpio_chip *>(data_chip), data_pin, |
paul@199 | 194 | reinterpret_cast<Hw::Gpio_chip *>(enable_chip), enable_pin, |
paul@222 | 195 | reinterpret_cast<Hw::Gpio_chip *>(control_chip), control_pin); |
paul@222 | 196 | } |
paul@222 | 197 | |
paul@222 | 198 | uint32_t spi_gpio_send(void *channel, uint32_t bytes, const uint8_t data[]) |
paul@222 | 199 | { |
paul@222 | 200 | return static_cast<Spi_gpio *>(channel)->send(bytes, data); |
paul@199 | 201 | } |
paul@199 | 202 | |
paul@222 | 203 | uint32_t spi_gpio_send_dc(void *channel, uint32_t bytes, const uint8_t data[], |
paul@222 | 204 | const int dc[]) |
paul@199 | 205 | { |
paul@222 | 206 | return static_cast<Spi_gpio *>(channel)->send_dc(bytes, data, dc); |
paul@199 | 207 | } |
paul@222 | 208 | |
paul@222 | 209 | uint32_t spi_gpio_send_units(void *channel, uint32_t bytes, const uint8_t data[], |
paul@222 | 210 | uint8_t unit_size, uint8_t char_size) |
paul@222 | 211 | { |
paul@222 | 212 | return static_cast<Spi_gpio *>(channel)->send_units(bytes, data, unit_size, char_size); |
paul@222 | 213 | } |