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@236 | 22 | #include <l4/devices/byteorder.h> |
paul@199 | 23 | #include <l4/devices/spi-gpio.h> |
paul@236 | 24 | #include <stdlib.h> /* NOTE: required by calloc/free */ |
paul@199 | 25 | #include <time.h> |
paul@199 | 26 | |
paul@199 | 27 | |
paul@199 | 28 | |
paul@199 | 29 | Spi_gpio::Spi_gpio(Hw::Gpio_chip *clock_device, int clock_pin, |
paul@199 | 30 | Hw::Gpio_chip *data_device, int data_pin, |
paul@199 | 31 | Hw::Gpio_chip *enable_device, int enable_pin, |
paul@222 | 32 | Hw::Gpio_chip *control_device, int control_pin) |
paul@199 | 33 | : _clock_device(clock_device), |
paul@199 | 34 | _clock_pin(clock_pin), |
paul@199 | 35 | _data_device(data_device), |
paul@199 | 36 | _data_pin(data_pin), |
paul@199 | 37 | _enable_device(enable_device), |
paul@199 | 38 | _enable_pin(enable_pin), |
paul@222 | 39 | _control_device(control_device), |
paul@222 | 40 | _control_pin(control_pin) |
paul@199 | 41 | { |
paul@199 | 42 | _clock_device->setup(_clock_pin, Hw::Gpio_chip::Output, 1); |
paul@199 | 43 | _data_device->setup(_data_pin, Hw::Gpio_chip::Output, 0); |
paul@199 | 44 | _enable_device->setup(_enable_pin, Hw::Gpio_chip::Output, 1); |
paul@232 | 45 | _control_device->setup(_control_pin, Hw::Gpio_chip::Output, 0); |
paul@199 | 46 | } |
paul@199 | 47 | |
paul@222 | 48 | Spi_gpio::Spi_gpio(uint64_t frequency, |
paul@222 | 49 | Hw::Gpio_chip *clock_device, int clock_pin, |
paul@222 | 50 | Hw::Gpio_chip *data_device, int data_pin, |
paul@222 | 51 | Hw::Gpio_chip *enable_device, int enable_pin, |
paul@222 | 52 | Hw::Gpio_chip *control_device, int control_pin) |
paul@222 | 53 | : Spi_gpio(clock_device, clock_pin, data_device, data_pin, enable_device, |
paul@222 | 54 | enable_pin, control_device, control_pin) |
paul@222 | 55 | { |
paul@222 | 56 | _frequency = frequency; |
paul@222 | 57 | } |
paul@222 | 58 | |
paul@221 | 59 | /* Send a byte sequence. */ |
paul@199 | 60 | |
paul@222 | 61 | uint32_t Spi_gpio::send(uint32_t bytes, const uint8_t data[]) |
paul@222 | 62 | { |
paul@236 | 63 | return send_dc(bytes, data, NULL, 8, false); |
paul@222 | 64 | } |
paul@222 | 65 | |
paul@236 | 66 | /* Send a byte sequence with accompanying data/command indicators. Bytes are |
paul@236 | 67 | grouped into character units, with each unit holding a value with the given |
paul@236 | 68 | byte ordering. */ |
paul@222 | 69 | |
paul@222 | 70 | uint32_t Spi_gpio::send_dc(uint32_t bytes, const uint8_t data[], |
paul@236 | 71 | const int dc[], uint8_t char_size, bool big_endian) |
paul@199 | 72 | { |
paul@199 | 73 | struct timespec ts; |
paul@236 | 74 | uint32_t offset, char_unit; |
paul@236 | 75 | uint8_t char_unit_size = ((char_size ? char_size - 1 : 0) / 8) + 1; |
paul@199 | 76 | |
paul@199 | 77 | if (_frequency) |
paul@199 | 78 | { |
paul@199 | 79 | ts.tv_sec = 0; |
paul@199 | 80 | ts.tv_nsec = 1000000000 / _frequency; |
paul@199 | 81 | } |
paul@199 | 82 | |
paul@199 | 83 | /* Initialise pin levels. */ |
paul@199 | 84 | |
paul@199 | 85 | _enable_device->set(_enable_pin, 1); |
paul@307 | 86 | _clock_device->set(_clock_pin, 0); |
paul@199 | 87 | _data_device->set(_data_pin, 0); |
paul@199 | 88 | |
paul@236 | 89 | if ((_control_device != NULL) && (_control_pin >= 0) && (dc != NULL)) |
paul@236 | 90 | _control_device->set(_control_pin, dc[0] ? 1 : 0); |
paul@236 | 91 | |
paul@199 | 92 | /* Enter the transmission state. */ |
paul@199 | 93 | |
paul@199 | 94 | _enable_device->set(_enable_pin, 0); |
paul@199 | 95 | |
paul@199 | 96 | /* Clock data using the clock and data outputs. */ |
paul@199 | 97 | |
paul@236 | 98 | for (offset = 0, char_unit = 0; offset < bytes; offset += char_unit_size, char_unit++) |
paul@199 | 99 | { |
paul@236 | 100 | uint32_t mask, value = get_stored_value(&data[offset], char_unit_size, big_endian); |
paul@236 | 101 | |
paul@236 | 102 | /* Set the control level, if appropriate. */ |
paul@199 | 103 | |
paul@236 | 104 | if ((_control_device != NULL) && (_control_pin >= 0) && (dc != NULL)) |
paul@236 | 105 | _control_device->set(_control_pin, dc[char_unit] ? 1 : 0); |
paul@236 | 106 | |
paul@236 | 107 | mask = 1 << (char_size - 1); |
paul@236 | 108 | |
paul@236 | 109 | for (uint8_t bit = 0; bit < char_size; bit++) |
paul@199 | 110 | { |
paul@199 | 111 | /* NOTE: Data presented on falling clock level and sampled on rising clock |
paul@307 | 112 | level. This is SPI mode 0 given that the enable level is driven |
paul@307 | 113 | low immediately before the first bit is presented. */ |
paul@199 | 114 | |
paul@199 | 115 | _clock_device->set(_clock_pin, 0); |
paul@236 | 116 | _data_device->set(_data_pin, value & mask ? 1 : 0); |
paul@222 | 117 | |
paul@199 | 118 | if (_frequency) |
paul@199 | 119 | nanosleep(&ts, NULL); |
paul@199 | 120 | |
paul@199 | 121 | _clock_device->set(_clock_pin, 1); |
paul@199 | 122 | |
paul@199 | 123 | if (_frequency) |
paul@199 | 124 | nanosleep(&ts, NULL); |
paul@199 | 125 | |
paul@199 | 126 | mask >>= 1; |
paul@199 | 127 | } |
paul@199 | 128 | } |
paul@199 | 129 | |
paul@199 | 130 | _enable_device->set(_enable_pin, 1); |
paul@307 | 131 | _clock_device->set(_clock_pin, 0); |
paul@222 | 132 | |
paul@222 | 133 | return bytes; |
paul@222 | 134 | } |
paul@222 | 135 | |
paul@236 | 136 | /* Send a number of units, each holding a value limited to the given character |
paul@236 | 137 | size, with any bit immediately above the character portion of the unit |
paul@236 | 138 | indicating the data/command level. */ |
paul@222 | 139 | |
paul@222 | 140 | uint32_t Spi_gpio::send_units(uint32_t bytes, const uint8_t data[], |
paul@236 | 141 | uint8_t unit_size, uint8_t char_size, |
paul@236 | 142 | bool big_endian) |
paul@222 | 143 | { |
paul@236 | 144 | uint32_t char_units = bytes / unit_size; |
paul@236 | 145 | uint8_t char_unit_size = ((char_size ? char_size - 1 : 0) / 8) + 1; |
paul@232 | 146 | |
paul@236 | 147 | /* Obtain the data/command values for each unit. */ |
paul@232 | 148 | /* Obtain the actual character bytes to be sent. */ |
paul@232 | 149 | |
paul@236 | 150 | /* NOTE: Work around stack allocation limitations in L4Re. We would want to |
paul@236 | 151 | use a static allocation as follows: |
paul@236 | 152 | |
paul@236 | 153 | int dc[char_units]; |
paul@236 | 154 | uint8_t char_data[char_unit_size * char_units]; |
paul@236 | 155 | */ |
paul@236 | 156 | |
paul@236 | 157 | int *dc = (int *) calloc(char_units, sizeof(int)); |
paul@236 | 158 | uint8_t *char_data = (uint8_t *) calloc(char_units, char_unit_size); |
paul@222 | 159 | |
paul@222 | 160 | /* Traverse the byte sequence, extracting data/command bits for each unit. */ |
paul@222 | 161 | |
paul@236 | 162 | for (uint32_t offset = 0, char_unit = 0, char_offset = 0; char_unit < char_units; |
paul@236 | 163 | offset += unit_size, char_unit++, char_offset += char_unit_size) |
paul@222 | 164 | { |
paul@232 | 165 | /* Obtain the unit value. */ |
paul@232 | 166 | |
paul@236 | 167 | uint32_t value = get_stored_value(&data[offset], unit_size, big_endian); |
paul@232 | 168 | |
paul@222 | 169 | /* The unit size must be greater than the character size for data/command |
paul@222 | 170 | bits to be present. */ |
paul@222 | 171 | |
paul@236 | 172 | if (unit_size * 8 > char_size) |
paul@236 | 173 | dc[char_unit] = value & (1 << char_size) ? 1 : 0; |
paul@222 | 174 | |
paul@232 | 175 | /* Obtain the actual character data from the input data, discarding the most |
paul@232 | 176 | significant bytes beyond the character and masking out any remaining |
paul@232 | 177 | superfluous bits, thus obtaining a whole number of bytes. */ |
paul@222 | 178 | |
paul@232 | 179 | value = value & ((1 << char_size) - 1); |
paul@222 | 180 | |
paul@236 | 181 | set_stored_value(value, &char_data[char_offset], char_unit_size, big_endian); |
paul@222 | 182 | } |
paul@222 | 183 | |
paul@236 | 184 | /* Discard the data/command values if they will not be used. */ |
paul@236 | 185 | |
paul@236 | 186 | uint32_t result = send_dc(char_units * char_unit_size, char_data, |
paul@236 | 187 | (unit_size * 8 <= char_size) ? NULL : dc, |
paul@236 | 188 | char_size, big_endian); |
paul@236 | 189 | |
paul@236 | 190 | /* NOTE: Working around allocation limitations with dc... */ |
paul@236 | 191 | |
paul@236 | 192 | free(dc); |
paul@236 | 193 | free(char_data); |
paul@236 | 194 | return result; |
paul@236 | 195 | } |
paul@236 | 196 | |
paul@236 | 197 | /* Perform a transfer, ignoring any DMA-specific parameters. */ |
paul@236 | 198 | |
paul@236 | 199 | uint32_t Spi_gpio::transfer(l4_addr_t vaddr, |
paul@236 | 200 | l4re_dma_space_dma_addr_t paddr, |
paul@236 | 201 | uint32_t count, uint8_t unit_size, |
paul@236 | 202 | uint8_t char_size, |
paul@236 | 203 | l4_addr_t desc_vaddr, |
paul@236 | 204 | l4re_dma_space_dma_addr_t desc_paddr) |
paul@236 | 205 | { |
paul@236 | 206 | (void) paddr; (void) desc_vaddr; (void) desc_paddr; |
paul@236 | 207 | |
paul@236 | 208 | /* Assume little endian data is being transferred, being the native data |
paul@236 | 209 | representation. */ |
paul@236 | 210 | |
paul@236 | 211 | return send_units(count, (const uint8_t *) vaddr, unit_size, char_size, |
paul@236 | 212 | false); |
paul@199 | 213 | } |
paul@199 | 214 | |
paul@199 | 215 | |
paul@199 | 216 | |
paul@199 | 217 | /* C language interface. */ |
paul@199 | 218 | |
paul@222 | 219 | void *spi_gpio_get_channel(uint64_t frequency, |
paul@222 | 220 | void *clock_chip, int clock_pin, |
paul@199 | 221 | void *data_chip, int data_pin, |
paul@199 | 222 | void *enable_chip, int enable_pin, |
paul@222 | 223 | void *control_chip, int control_pin) |
paul@199 | 224 | { |
paul@222 | 225 | return (void *) new Spi_gpio(frequency, |
paul@222 | 226 | reinterpret_cast<Hw::Gpio_chip *>(clock_chip), clock_pin, |
paul@199 | 227 | reinterpret_cast<Hw::Gpio_chip *>(data_chip), data_pin, |
paul@199 | 228 | reinterpret_cast<Hw::Gpio_chip *>(enable_chip), enable_pin, |
paul@222 | 229 | reinterpret_cast<Hw::Gpio_chip *>(control_chip), control_pin); |
paul@222 | 230 | } |
paul@222 | 231 | |
paul@222 | 232 | uint32_t spi_gpio_send(void *channel, uint32_t bytes, const uint8_t data[]) |
paul@222 | 233 | { |
paul@222 | 234 | return static_cast<Spi_gpio *>(channel)->send(bytes, data); |
paul@199 | 235 | } |
paul@199 | 236 | |
paul@222 | 237 | uint32_t spi_gpio_send_dc(void *channel, uint32_t bytes, const uint8_t data[], |
paul@236 | 238 | const int dc[], uint8_t char_size, int big_endian) |
paul@199 | 239 | { |
paul@236 | 240 | return static_cast<Spi_gpio *>(channel)->send_dc(bytes, data, dc, char_size, |
paul@236 | 241 | big_endian); |
paul@199 | 242 | } |
paul@222 | 243 | |
paul@222 | 244 | uint32_t spi_gpio_send_units(void *channel, uint32_t bytes, const uint8_t data[], |
paul@236 | 245 | uint8_t unit_size, uint8_t char_size, int big_endian) |
paul@222 | 246 | { |
paul@236 | 247 | return static_cast<Spi_gpio *>(channel)->send_units(bytes, data, unit_size, |
paul@236 | 248 | char_size, big_endian); |
paul@222 | 249 | } |
paul@236 | 250 | |
paul@236 | 251 | uint32_t spi_gpio_transfer(void *channel, l4_addr_t vaddr, |
paul@236 | 252 | l4re_dma_space_dma_addr_t paddr, |
paul@236 | 253 | uint32_t count, uint8_t unit_size, |
paul@236 | 254 | uint8_t char_size) |
paul@236 | 255 | { |
paul@236 | 256 | return static_cast<Spi_gpio *>(channel)->transfer(vaddr, paddr, count, |
paul@236 | 257 | unit_size, char_size); |
paul@236 | 258 | } |
paul@236 | 259 | |
paul@236 | 260 | uint32_t spi_gpio_transfer_descriptor(void *channel, l4_addr_t vaddr, |
paul@236 | 261 | l4re_dma_space_dma_addr_t paddr, |
paul@236 | 262 | uint32_t count, uint8_t unit_size, |
paul@236 | 263 | uint8_t char_size, l4_addr_t desc_vaddr, |
paul@236 | 264 | l4re_dma_space_dma_addr_t desc_paddr) |
paul@236 | 265 | { |
paul@236 | 266 | return static_cast<Spi_gpio *>(channel)->transfer(vaddr, paddr, count, |
paul@236 | 267 | unit_size, char_size, desc_vaddr, desc_paddr); |
paul@236 | 268 | } |