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@199 | 30 | uint32_t frequency) |
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@199 | 37 | _frequency(frequency) |
paul@199 | 38 | { |
paul@199 | 39 | _clock_device->setup(_clock_pin, Hw::Gpio_chip::Output, 1); |
paul@199 | 40 | _data_device->setup(_data_pin, Hw::Gpio_chip::Output, 0); |
paul@199 | 41 | _enable_device->setup(_enable_pin, Hw::Gpio_chip::Output, 1); |
paul@199 | 42 | } |
paul@199 | 43 | |
paul@199 | 44 | /* Send a SPI command. */ |
paul@199 | 45 | |
paul@207 | 46 | void Spi_gpio::send(int bytes, const uint8_t data[]) |
paul@199 | 47 | { |
paul@199 | 48 | struct timespec ts; |
paul@199 | 49 | uint8_t mask; |
paul@199 | 50 | int bit, byte; |
paul@199 | 51 | |
paul@199 | 52 | if (_frequency) |
paul@199 | 53 | { |
paul@199 | 54 | ts.tv_sec = 0; |
paul@199 | 55 | ts.tv_nsec = 1000000000 / _frequency; |
paul@199 | 56 | } |
paul@199 | 57 | |
paul@199 | 58 | /* Initialise pin levels. */ |
paul@199 | 59 | |
paul@199 | 60 | _enable_device->set(_enable_pin, 1); |
paul@199 | 61 | _clock_device->set(_clock_pin, 1); |
paul@199 | 62 | _data_device->set(_data_pin, 0); |
paul@199 | 63 | |
paul@199 | 64 | /* Enter the transmission state. */ |
paul@199 | 65 | |
paul@199 | 66 | _enable_device->set(_enable_pin, 0); |
paul@199 | 67 | |
paul@199 | 68 | /* Clock data using the clock and data outputs. */ |
paul@199 | 69 | |
paul@199 | 70 | for (byte = 0; byte < bytes; byte++) |
paul@199 | 71 | { |
paul@199 | 72 | mask = 0x80; |
paul@199 | 73 | |
paul@199 | 74 | for (bit = 0; bit < 8; bit++) |
paul@199 | 75 | { |
paul@199 | 76 | /* NOTE: Data presented on falling clock level and sampled on rising clock |
paul@199 | 77 | level. This is SPI mode 3, or 0 given that the enable level is |
paul@199 | 78 | driven low immediately before the first bit is presented. */ |
paul@199 | 79 | |
paul@199 | 80 | _clock_device->set(_clock_pin, 0); |
paul@199 | 81 | _data_device->set(_data_pin, data[byte] & mask ? 1 : 0); |
paul@199 | 82 | |
paul@199 | 83 | if (_frequency) |
paul@199 | 84 | nanosleep(&ts, NULL); |
paul@199 | 85 | |
paul@199 | 86 | _clock_device->set(_clock_pin, 1); |
paul@199 | 87 | |
paul@199 | 88 | if (_frequency) |
paul@199 | 89 | nanosleep(&ts, NULL); |
paul@199 | 90 | |
paul@199 | 91 | mask >>= 1; |
paul@199 | 92 | } |
paul@199 | 93 | } |
paul@199 | 94 | |
paul@199 | 95 | _enable_device->set(_enable_pin, 1); |
paul@199 | 96 | } |
paul@199 | 97 | |
paul@199 | 98 | |
paul@199 | 99 | |
paul@199 | 100 | /* C language interface. */ |
paul@199 | 101 | |
paul@199 | 102 | void *spi_gpio_get_channel(void *clock_chip, int clock_pin, |
paul@199 | 103 | void *data_chip, int data_pin, |
paul@199 | 104 | void *enable_chip, int enable_pin, |
paul@199 | 105 | uint32_t frequency) |
paul@199 | 106 | { |
paul@199 | 107 | return (void *) new Spi_gpio(reinterpret_cast<Hw::Gpio_chip *>(clock_chip), clock_pin, |
paul@199 | 108 | reinterpret_cast<Hw::Gpio_chip *>(data_chip), data_pin, |
paul@199 | 109 | reinterpret_cast<Hw::Gpio_chip *>(enable_chip), enable_pin, |
paul@199 | 110 | frequency); |
paul@199 | 111 | } |
paul@199 | 112 | |
paul@207 | 113 | void spi_gpio_send(void *channel, int bytes, const uint8_t data[]) |
paul@199 | 114 | { |
paul@199 | 115 | static_cast<Spi_gpio *>(channel)->send(bytes, data); |
paul@199 | 116 | } |