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@199 | 43 | } |
paul@199 | 44 | |
paul@222 | 45 | Spi_gpio::Spi_gpio(uint64_t frequency, |
paul@222 | 46 | Hw::Gpio_chip *clock_device, int clock_pin, |
paul@222 | 47 | Hw::Gpio_chip *data_device, int data_pin, |
paul@222 | 48 | Hw::Gpio_chip *enable_device, int enable_pin, |
paul@222 | 49 | Hw::Gpio_chip *control_device, int control_pin) |
paul@222 | 50 | : Spi_gpio(clock_device, clock_pin, data_device, data_pin, enable_device, |
paul@222 | 51 | enable_pin, control_device, control_pin) |
paul@222 | 52 | { |
paul@222 | 53 | _frequency = frequency; |
paul@222 | 54 | } |
paul@222 | 55 | |
paul@221 | 56 | /* Send a byte sequence. */ |
paul@199 | 57 | |
paul@222 | 58 | uint32_t Spi_gpio::send(uint32_t bytes, const uint8_t data[]) |
paul@222 | 59 | { |
paul@222 | 60 | return send_dc(bytes, data, NULL); |
paul@222 | 61 | } |
paul@222 | 62 | |
paul@222 | 63 | /* Send a byte sequence with accompanying data/command indicators. */ |
paul@222 | 64 | |
paul@222 | 65 | uint32_t Spi_gpio::send_dc(uint32_t bytes, const uint8_t data[], |
paul@222 | 66 | const int dc[]) |
paul@199 | 67 | { |
paul@199 | 68 | struct timespec ts; |
paul@199 | 69 | uint8_t mask; |
paul@222 | 70 | int bit; |
paul@222 | 71 | uint32_t byte; |
paul@199 | 72 | |
paul@199 | 73 | if (_frequency) |
paul@199 | 74 | { |
paul@199 | 75 | ts.tv_sec = 0; |
paul@199 | 76 | ts.tv_nsec = 1000000000 / _frequency; |
paul@199 | 77 | } |
paul@199 | 78 | |
paul@199 | 79 | /* Initialise pin levels. */ |
paul@199 | 80 | |
paul@199 | 81 | _enable_device->set(_enable_pin, 1); |
paul@199 | 82 | _clock_device->set(_clock_pin, 1); |
paul@199 | 83 | _data_device->set(_data_pin, 0); |
paul@199 | 84 | |
paul@199 | 85 | /* Enter the transmission state. */ |
paul@199 | 86 | |
paul@199 | 87 | _enable_device->set(_enable_pin, 0); |
paul@199 | 88 | |
paul@199 | 89 | /* Clock data using the clock and data outputs. */ |
paul@199 | 90 | |
paul@199 | 91 | for (byte = 0; byte < bytes; byte++) |
paul@199 | 92 | { |
paul@199 | 93 | mask = 0x80; |
paul@199 | 94 | |
paul@199 | 95 | for (bit = 0; bit < 8; bit++) |
paul@199 | 96 | { |
paul@199 | 97 | /* NOTE: Data presented on falling clock level and sampled on rising clock |
paul@199 | 98 | level. This is SPI mode 3, or 0 given that the enable level is |
paul@199 | 99 | driven low immediately before the first bit is presented. */ |
paul@199 | 100 | |
paul@199 | 101 | _clock_device->set(_clock_pin, 0); |
paul@199 | 102 | _data_device->set(_data_pin, data[byte] & mask ? 1 : 0); |
paul@199 | 103 | |
paul@222 | 104 | if ((_control_device != NULL) && (dc != NULL)) |
paul@222 | 105 | _control_device->set(_control_pin, dc[byte] ? 1 : 0); |
paul@222 | 106 | |
paul@199 | 107 | if (_frequency) |
paul@199 | 108 | nanosleep(&ts, NULL); |
paul@199 | 109 | |
paul@199 | 110 | _clock_device->set(_clock_pin, 1); |
paul@199 | 111 | |
paul@199 | 112 | if (_frequency) |
paul@199 | 113 | nanosleep(&ts, NULL); |
paul@199 | 114 | |
paul@199 | 115 | mask >>= 1; |
paul@199 | 116 | } |
paul@199 | 117 | } |
paul@199 | 118 | |
paul@199 | 119 | _enable_device->set(_enable_pin, 1); |
paul@222 | 120 | |
paul@222 | 121 | return bytes; |
paul@222 | 122 | } |
paul@222 | 123 | |
paul@222 | 124 | /* Send a number of units, each holding a value in a big endian arrangement |
paul@222 | 125 | limited to the given character size, with any bit immediately above the |
paul@222 | 126 | character portion of the unit indicating the data/command level. */ |
paul@222 | 127 | |
paul@222 | 128 | uint32_t Spi_gpio::send_units(uint32_t bytes, const uint8_t data[], |
paul@222 | 129 | uint8_t unit_size, uint8_t char_size) |
paul@222 | 130 | { |
paul@222 | 131 | uint32_t count = bytes / unit_size; |
paul@222 | 132 | int dc[count]; |
paul@222 | 133 | |
paul@222 | 134 | /* Traverse the byte sequence, extracting data/command bits for each unit. */ |
paul@222 | 135 | |
paul@222 | 136 | for (uint32_t offset = 0, unit = 0; offset < bytes; offset += unit_size, unit++) |
paul@222 | 137 | { |
paul@222 | 138 | /* The unit size must be greater than the character size for data/command |
paul@222 | 139 | bits to be present. */ |
paul@222 | 140 | |
paul@222 | 141 | if (unit_size * 8 <= char_size) |
paul@222 | 142 | dc[unit] = 0; |
paul@222 | 143 | else |
paul@222 | 144 | { |
paul@222 | 145 | /* Obtain the unit value. */ |
paul@222 | 146 | |
paul@222 | 147 | uint32_t value = 0; |
paul@222 | 148 | |
paul@222 | 149 | for (uint8_t byte = 0; byte < unit_size; byte++) |
paul@222 | 150 | value = (value << 8) | data[offset + byte]; |
paul@222 | 151 | |
paul@222 | 152 | /* Extract the data/command level. */ |
paul@222 | 153 | |
paul@222 | 154 | dc[unit] = value & (1 << char_size) ? 1 : 0; |
paul@222 | 155 | } |
paul@222 | 156 | } |
paul@222 | 157 | |
paul@222 | 158 | return send_dc(bytes, data, dc); |
paul@199 | 159 | } |
paul@199 | 160 | |
paul@199 | 161 | |
paul@199 | 162 | |
paul@199 | 163 | /* C language interface. */ |
paul@199 | 164 | |
paul@222 | 165 | void *spi_gpio_get_channel(uint64_t frequency, |
paul@222 | 166 | void *clock_chip, int clock_pin, |
paul@199 | 167 | void *data_chip, int data_pin, |
paul@199 | 168 | void *enable_chip, int enable_pin, |
paul@222 | 169 | void *control_chip, int control_pin) |
paul@199 | 170 | { |
paul@222 | 171 | return (void *) new Spi_gpio(frequency, |
paul@222 | 172 | reinterpret_cast<Hw::Gpio_chip *>(clock_chip), clock_pin, |
paul@199 | 173 | reinterpret_cast<Hw::Gpio_chip *>(data_chip), data_pin, |
paul@199 | 174 | reinterpret_cast<Hw::Gpio_chip *>(enable_chip), enable_pin, |
paul@222 | 175 | reinterpret_cast<Hw::Gpio_chip *>(control_chip), control_pin); |
paul@222 | 176 | } |
paul@222 | 177 | |
paul@222 | 178 | uint32_t spi_gpio_send(void *channel, uint32_t bytes, const uint8_t data[]) |
paul@222 | 179 | { |
paul@222 | 180 | return static_cast<Spi_gpio *>(channel)->send(bytes, data); |
paul@199 | 181 | } |
paul@199 | 182 | |
paul@222 | 183 | uint32_t spi_gpio_send_dc(void *channel, uint32_t bytes, const uint8_t data[], |
paul@222 | 184 | const int dc[]) |
paul@199 | 185 | { |
paul@222 | 186 | return static_cast<Spi_gpio *>(channel)->send_dc(bytes, data, dc); |
paul@199 | 187 | } |
paul@222 | 188 | |
paul@222 | 189 | uint32_t spi_gpio_send_units(void *channel, uint32_t bytes, const uint8_t data[], |
paul@222 | 190 | uint8_t unit_size, uint8_t char_size) |
paul@222 | 191 | { |
paul@222 | 192 | return static_cast<Spi_gpio *>(channel)->send_units(bytes, data, unit_size, char_size); |
paul@222 | 193 | } |