1 /* 2 * Export JZ4740 GPIO pins as a SPI server. 3 * 4 * (c) 2018 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/devices/gpio-jz4740.h> 23 24 #include <l4/cxx/ipc_server> 25 #include <l4/re/env> 26 #include <l4/re/util/object_registry> 27 28 #include <errno.h> 29 #include <stdint.h> 30 #include <stdlib.h> 31 32 #include "spi-ops.h" 33 #include "memory.h" 34 35 /* Virtual addresses for the GPIO register block. */ 36 37 static l4_addr_t gpio_virt_base = 0, gpio_virt_base_end = 0; 38 39 40 41 static int setup_memory(void) 42 { 43 if (get_memory("jz4740-gpio", &gpio_virt_base, &gpio_virt_base_end)) 44 return 1; 45 46 return 0; 47 } 48 49 50 51 /* SPI peripheral device. */ 52 53 class Spi_device_server : public L4::Server_object_t<L4::Kobject> 54 { 55 Gpio_jz4740_chip *_clock_device = 0, *_data_device = 0, *_enable_device = 0; 56 int _clock_pin, _data_pin, _enable_pin; 57 58 public: 59 /* Associate the device with a particular memory region. */ 60 61 explicit Spi_device_server(Gpio_jz4740_chip *clock_device, 62 Gpio_jz4740_chip *data_device, 63 Gpio_jz4740_chip *enable_device, 64 int clock_pin, int data_pin, int enable_pin) 65 : _clock_device(clock_device), 66 _data_device(data_device), 67 _enable_device(enable_device), 68 _clock_pin(clock_pin), _data_pin(data_pin), _enable_pin(enable_pin) 69 { 70 } 71 72 /* Dispatch incoming requests. */ 73 74 int dispatch(l4_umword_t obj, L4::Ipc::Iostream &ios) 75 { 76 l4_msgtag_t tag; 77 int bits, data; 78 79 (void) obj; 80 ios >> tag; 81 82 switch (tag.label()) 83 { 84 case Spi_op_send: 85 ios >> bits; 86 ios >> data; 87 send(bits, data); 88 return L4_EOK; 89 90 default: 91 return -L4_EBADPROTO; 92 } 93 } 94 95 /* Send a SPI command. */ 96 97 void send(int bits, int data) 98 { 99 uint32_t mask = 1 << (bits - 1); 100 int bit; 101 102 /* Initialise pin levels. */ 103 104 _enable_device->set(_enable_pin, 1); 105 _clock_device->set(_clock_pin, 1); 106 _data_device->set(_data_pin, 0); 107 108 /* Enter the transmission state. */ 109 110 _enable_device->set(_enable_pin, 0); 111 112 /* Clock data using the clock and data outputs. */ 113 114 for (bit = 0; bit < bits; bit++) 115 { 116 _clock_device->set(_clock_pin, 0); 117 _data_device->set(_data_pin, data & mask ? 1 : 0); 118 _clock_device->set(_clock_pin, 1); 119 mask >>= 1; 120 } 121 122 _enable_device->set(_enable_pin, 1); 123 } 124 }; 125 126 static L4Re::Util::Registry_server<> server; 127 128 129 130 /* 131 Parse a string of the form "<port><pin>" where <port> is a single character 132 whose position in the ports string indicates the port number, and where <pin> is 133 either a base 10 number, a base 8 number preceded by "0", or a base 16 number 134 preceded by "0x" or "0X", defined by the strtol library function. 135 */ 136 137 static int parse_pin(const char *s, const char *ports, int *port, int *pin) 138 { 139 int i = 0; 140 141 if (!s || !(*s)) return 0; 142 143 /* Parse prefix in character range. */ 144 145 while (*ports) 146 { 147 if (s[0] == *ports) 148 { 149 *port = i; 150 151 /* Parse pin number. */ 152 153 *pin = (int) strtol(s+1, NULL, 0); 154 return !errno; 155 } 156 ports++; i++; 157 } 158 159 return 0; 160 } 161 162 /* Arguments: <SPI clock pin> <SPI data pin> <SPI enable pin> */ 163 164 int main(int argc, char *argv[]) 165 { 166 int clock_port, clock_pin, data_port, data_pin, enable_port, enable_pin; 167 168 if (argc < 4) return 1; 169 170 /* Interpret the pin details. */ 171 172 if (!parse_pin(argv[1], "ABCD", &clock_port, &clock_pin)) return 1; 173 if (!parse_pin(argv[2], "ABCD", &data_port, &data_pin)) return 1; 174 if (!parse_pin(argv[3], "ABCD", &enable_port, &enable_pin)) return 1; 175 176 /* Obtain access to peripheral memory. */ 177 178 if (setup_memory()) return 1; 179 180 /* Configure the clock pin. */ 181 182 Gpio_jz4740_chip gpio_port_clock(gpio_virt_base + clock_port * 0x100, 183 gpio_virt_base + (clock_port + 1) * 0x100, 32); 184 185 gpio_port_clock.setup(clock_pin, Hw::Gpio_chip::Output, 0); 186 187 /* Configure the data pin. */ 188 189 Gpio_jz4740_chip gpio_port_data(gpio_virt_base + data_port * 0x100, 190 gpio_virt_base + (data_port + 1) * 0x100, 32); 191 192 gpio_port_data.setup(data_pin, Hw::Gpio_chip::Output, 0); 193 194 /* Configure the enable pin. */ 195 196 Gpio_jz4740_chip gpio_port_enable(gpio_virt_base + enable_port * 0x100, 197 gpio_virt_base + (enable_port + 1) * 0x100, 32); 198 199 gpio_port_enable.setup(enable_pin, Hw::Gpio_chip::Output, 0); 200 201 /* Initialise and register a new server object. */ 202 203 Spi_device_server server_obj(&gpio_port_clock, &gpio_port_data, &gpio_port_enable, 204 clock_pin, data_pin, enable_pin); 205 206 server.registry()->register_obj(&server_obj, "spi"); 207 208 /* Enter the IPC server loop. */ 209 210 server.loop(); 211 return 0; 212 }