Line data Source code
1 : /*
2 : * Copyright (C) 2015 Intel Corporation. All rights reserved.
3 : *
4 : * This file is free software: you can redistribute it and/or modify it
5 : * under the terms of the GNU General Public License as published by the
6 : * Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This file is distributed in the hope that it will be useful, but
10 : * WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 : * See the GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License along
15 : * with this program. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 : #include "SPIDevice.h"
18 :
19 : #include <assert.h>
20 : #include <errno.h>
21 : #include <fcntl.h>
22 : #include <linux/spi/spidev.h>
23 : #include <stdio.h>
24 : #include <sys/ioctl.h>
25 : #include <sys/stat.h>
26 : #include <sys/types.h>
27 : #include <unistd.h>
28 : #include <vector>
29 :
30 : #include <AP_HAL/AP_HAL.h>
31 : #include <AP_HAL/utility/OwnPtr.h>
32 :
33 : #include "GPIO.h"
34 : #include "PollerThread.h"
35 : #include "Scheduler.h"
36 : #include "Semaphores.h"
37 : #include "Thread.h"
38 : #include "Util.h"
39 :
40 : #define DEBUG 0
41 :
42 : extern const AP_HAL::HAL& hal;
43 :
44 : namespace Linux {
45 :
46 : #define MHZ (1000U*1000U)
47 : #define KHZ (1000U)
48 : #define SPI_CS_KERNEL -1
49 :
50 : #ifdef HAL_SPI_DEVICE_LIST
51 : SPIDesc SPIDeviceManager::_device[] = {
52 : HAL_SPI_DEVICE_LIST
53 : };
54 : #else
55 : // empty device table
56 : SPIDesc SPIDeviceManager::_device[] = {
57 : SPIDesc("**dummy**", 0, 0, SPI_MODE_3, 0, 0, 0 * MHZ, 0 * MHZ),
58 : };
59 : #define LINUX_SPI_DEVICE_NUM_DEVICES 1
60 : #endif
61 :
62 : #ifndef LINUX_SPI_DEVICE_NUM_DEVICES
63 : #define LINUX_SPI_DEVICE_NUM_DEVICES ARRAY_SIZE(SPIDeviceManager::_device)
64 : #endif
65 :
66 : #define MAX_SUBDEVS 6
67 :
68 : const uint8_t SPIDeviceManager::_n_device_desc = LINUX_SPI_DEVICE_NUM_DEVICES;
69 :
70 :
71 : /* Private struct to maintain for each bus */
72 : class SPIBus : public TimerPollable::WrapperCb {
73 : public:
74 : SPIBus(uint16_t bus_);
75 : ~SPIBus();
76 :
77 : /*
78 : * TimerPollable::WrapperCb methods to take
79 : * and release semaphore while calling the callback
80 : */
81 : void start_cb() override;
82 : void end_cb() override;
83 :
84 : void open(uint16_t subdev);
85 :
86 : PollerThread thread;
87 : Semaphore sem;
88 : int fd[MAX_SUBDEVS];
89 : uint16_t bus;
90 : int16_t last_mode = -1;
91 : uint8_t ref;
92 : };
93 :
94 0 : SPIBus::SPIBus(uint16_t bus_)
95 0 : : bus(bus_)
96 : {
97 0 : memset(fd, -1, sizeof(fd));
98 0 : }
99 :
100 0 : SPIBus::~SPIBus()
101 : {
102 0 : for (unsigned i = 0; i < MAX_SUBDEVS; i++) {
103 0 : ::close(fd[i]);
104 : }
105 0 : }
106 :
107 0 : void SPIBus::start_cb()
108 : {
109 0 : sem.take_blocking();
110 0 : }
111 :
112 0 : void SPIBus::end_cb()
113 : {
114 0 : sem.give();
115 0 : }
116 :
117 :
118 0 : void SPIBus::open(uint16_t subdev)
119 : {
120 : /* Already open by another device */
121 0 : if (fd[subdev] >= 0) {
122 0 : return;
123 : }
124 :
125 : char path[32];
126 0 : snprintf(path, sizeof(path), "/dev/spidev%u.%u", bus, subdev);
127 0 : fd[subdev] = ::open(path, O_RDWR | O_CLOEXEC);
128 0 : if (fd[subdev] < 0) {
129 0 : AP_HAL::panic("SPI: unable to open SPI bus %s: %s",
130 0 : path, strerror(errno));
131 : }
132 : }
133 :
134 :
135 0 : SPIDevice::SPIDevice(SPIBus &bus, SPIDesc &device_desc)
136 0 : : _bus(bus)
137 0 : , _desc(device_desc)
138 : {
139 0 : set_device_bus(_bus.bus);
140 0 : set_device_address(_desc.subdev);
141 0 : _speed = _desc.highspeed;
142 :
143 0 : if (_desc.cs_pin != SPI_CS_KERNEL) {
144 0 : _cs = hal.gpio->channel(_desc.cs_pin);
145 0 : if (!_cs) {
146 0 : AP_HAL::panic("Unable to instantiate cs pin");
147 : }
148 :
149 0 : _cs->mode(HAL_GPIO_OUTPUT);
150 :
151 : // do not hold the SPI bus initially
152 0 : _cs_release();
153 : }
154 0 : }
155 :
156 0 : SPIDevice::~SPIDevice()
157 : {
158 : // Unregister itself from the SPIDeviceManager
159 0 : SPIDeviceManager::from(hal.spi)->_unregister(_bus);
160 0 : }
161 :
162 0 : bool SPIDevice::set_speed(AP_HAL::Device::Speed speed)
163 : {
164 0 : switch (speed) {
165 0 : case AP_HAL::Device::SPEED_HIGH:
166 0 : _speed = _desc.highspeed;
167 0 : break;
168 0 : case AP_HAL::Device::SPEED_LOW:
169 0 : _speed = _desc.lowspeed;
170 0 : break;
171 : }
172 :
173 0 : return true;
174 : }
175 :
176 0 : bool SPIDevice::transfer(const uint8_t *send, uint32_t send_len,
177 : uint8_t *recv, uint32_t recv_len)
178 : {
179 0 : struct spi_ioc_transfer msgs[2] = { };
180 0 : unsigned nmsgs = 0;
181 0 : int fd = _bus.fd[_desc.subdev];
182 :
183 0 : if (send && send_len != 0) {
184 0 : msgs[nmsgs].tx_buf = (uint64_t) send;
185 0 : msgs[nmsgs].rx_buf = 0;
186 0 : msgs[nmsgs].len = send_len;
187 0 : msgs[nmsgs].speed_hz = _speed;
188 0 : msgs[nmsgs].delay_usecs = 0;
189 0 : msgs[nmsgs].bits_per_word = _desc.bits_per_word;
190 0 : msgs[nmsgs].cs_change = 0;
191 0 : nmsgs++;
192 : }
193 :
194 0 : if (recv && recv_len != 0) {
195 0 : msgs[nmsgs].tx_buf = 0;
196 0 : msgs[nmsgs].rx_buf = (uint64_t) recv;
197 0 : msgs[nmsgs].len = recv_len;
198 0 : msgs[nmsgs].speed_hz = _speed;
199 0 : msgs[nmsgs].delay_usecs = 0;
200 0 : msgs[nmsgs].bits_per_word = _desc.bits_per_word;
201 0 : msgs[nmsgs].cs_change = 0;
202 0 : nmsgs++;
203 : }
204 :
205 0 : if (!nmsgs) {
206 0 : return false;
207 : }
208 :
209 : #if DEBUG
210 : if (_desc.mode == _bus.last_mode) {
211 : /*
212 : the mode in the kernel is not tied to the file descriptor,
213 : so there is a chance some other process has changed it since
214 : we last used the bus. We want to report when this happens so
215 : the user has a chance of figuring out when there is
216 : conflicted use of the SPI bus. Unfortunately this costs us
217 : an extra syscall per transfer.
218 : */
219 : uint8_t current_mode;
220 : if (ioctl(fd, SPI_IOC_RD_MODE, ¤t_mode) < 0) {
221 : hal.console->printf("SPIDevice: error on getting mode fd=%d (%s)\n",
222 : fd, strerror(errno));
223 : _bus.last_mode = -1;
224 : } else if (current_mode != _bus.last_mode) {
225 : hal.console->printf("SPIDevice: bus mode conflict fd=%d mode=%u/%u\n",
226 : fd, (unsigned)_bus.last_mode, (unsigned)current_mode);
227 : _bus.last_mode = -1;
228 : }
229 : }
230 : #endif
231 :
232 : int r;
233 0 : if (_desc.mode != _bus.last_mode) {
234 0 : r = ioctl(fd, SPI_IOC_WR_MODE, &_desc.mode);
235 0 : if (r < 0) {
236 0 : hal.console->printf("SPIDevice: error on setting mode fd=%d (%s)\n",
237 0 : fd, strerror(errno));
238 0 : return false;
239 : }
240 0 : _bus.last_mode = _desc.mode;
241 : }
242 :
243 0 : _cs_assert();
244 0 : r = ioctl(fd, SPI_IOC_MESSAGE(nmsgs), &msgs);
245 0 : _cs_release();
246 :
247 0 : if (r == -1) {
248 0 : hal.console->printf("SPIDevice: error transferring data fd=%d (%s)\n",
249 0 : fd, strerror(errno));
250 0 : return false;
251 : }
252 :
253 0 : return true;
254 : }
255 :
256 0 : bool SPIDevice::transfer_fullduplex(const uint8_t *send, uint8_t *recv,
257 : uint32_t len)
258 : {
259 0 : struct spi_ioc_transfer msgs[1] = { };
260 0 : int fd = _bus.fd[_desc.subdev];
261 :
262 0 : if (!send || !recv || len == 0) {
263 0 : return false;
264 : }
265 :
266 0 : msgs[0].tx_buf = (uint64_t) send;
267 0 : msgs[0].rx_buf = (uint64_t) recv;
268 0 : msgs[0].len = len;
269 0 : msgs[0].speed_hz = _speed;
270 0 : msgs[0].delay_usecs = 0;
271 0 : msgs[0].bits_per_word = _desc.bits_per_word;
272 0 : msgs[0].cs_change = 0;
273 :
274 0 : int r = ioctl(fd, SPI_IOC_WR_MODE, &_desc.mode);
275 0 : if (r < 0) {
276 0 : hal.console->printf("SPIDevice: error on setting mode fd=%d (%s)\n",
277 0 : fd, strerror(errno));
278 0 : return false;
279 : }
280 :
281 0 : _cs_assert();
282 0 : r = ioctl(fd, SPI_IOC_MESSAGE(1), &msgs);
283 0 : _cs_release();
284 :
285 0 : if (r == -1) {
286 0 : hal.console->printf("SPIDevice: error transferring data fd=%d (%s)\n",
287 0 : fd, strerror(errno));
288 0 : return false;
289 : }
290 :
291 0 : return true;
292 : }
293 :
294 0 : bool SPIDevice::transfer_fullduplex(uint8_t *send_recv, uint32_t len)
295 : {
296 0 : return transfer_fullduplex(send_recv, send_recv, len);
297 : }
298 :
299 0 : void SPIDevice::_cs_assert()
300 : {
301 0 : if (_desc.cs_pin == SPI_CS_KERNEL) {
302 0 : return;
303 : }
304 :
305 0 : _cs->write(0);
306 : }
307 :
308 0 : void SPIDevice::_cs_release()
309 : {
310 0 : if (_desc.cs_pin == SPI_CS_KERNEL) {
311 0 : return;
312 : }
313 :
314 0 : _cs->write(1);
315 : }
316 :
317 0 : AP_HAL::Semaphore *SPIDevice::get_semaphore()
318 : {
319 0 : return &_bus.sem;
320 : }
321 :
322 0 : AP_HAL::Device::PeriodicHandle SPIDevice::register_periodic_callback(
323 : uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)
324 : {
325 0 : TimerPollable *p = _bus.thread.add_timer(cb, &_bus, period_usec);
326 0 : if (!p) {
327 0 : AP_HAL::panic("Could not create periodic callback");
328 : }
329 :
330 0 : if (!_bus.thread.is_started()) {
331 : char name[16];
332 0 : snprintf(name, sizeof(name), "ap-spi-%u", _bus.bus);
333 :
334 0 : _bus.thread.set_stack_size(AP_LINUX_SENSORS_STACK_SIZE);
335 0 : _bus.thread.start(name, AP_LINUX_SENSORS_SCHED_POLICY,
336 : AP_LINUX_SENSORS_SCHED_PRIO);
337 : }
338 :
339 0 : return static_cast<AP_HAL::Device::PeriodicHandle>(p);
340 : }
341 :
342 0 : bool SPIDevice::adjust_periodic_callback(
343 : AP_HAL::Device::PeriodicHandle h, uint32_t period_usec)
344 : {
345 0 : return _bus.thread.adjust_timer(static_cast<TimerPollable*>(h), period_usec);
346 : }
347 :
348 :
349 : AP_HAL::SPIDevice *
350 0 : SPIDeviceManager::get_device_ptr(const char *name)
351 : {
352 0 : SPIDesc *desc = nullptr;
353 :
354 : /* Find the bus description in the table */
355 0 : for (uint8_t i = 0; i < _n_device_desc; i++) {
356 0 : if (!strcmp(_device[i].name, name)) {
357 0 : desc = &_device[i];
358 0 : break;
359 : }
360 : }
361 :
362 0 : if (!desc) {
363 0 : return nullptr;
364 : }
365 :
366 : /* Find if bus already exists */
367 0 : for (uint8_t i = 0, n = _buses.size(); i < n; i++) {
368 0 : if (_buses[i]->bus == desc->bus) {
369 0 : return _create_device(*_buses[i], *desc);
370 : }
371 : }
372 :
373 : /* Bus not found for this device, create a new one */
374 0 : AP_HAL::OwnPtr<SPIBus> b{new SPIBus(desc->bus)};
375 0 : if (!b) {
376 0 : return nullptr;
377 : }
378 :
379 0 : auto dev = _create_device(*b, *desc);
380 0 : if (!dev) {
381 0 : return nullptr;
382 : }
383 :
384 0 : _buses.push_back(b.leak());
385 :
386 0 : return dev;
387 0 : }
388 :
389 0 : uint8_t SPIDeviceManager::get_count()
390 : {
391 0 : return _n_device_desc;
392 : }
393 :
394 0 : const char* SPIDeviceManager::get_device_name(uint8_t idx)
395 : {
396 0 : return _device[idx].name;
397 : }
398 :
399 : /* Create a new device increasing the bus reference */
400 : AP_HAL::SPIDevice *
401 0 : SPIDeviceManager::_create_device(SPIBus &b, SPIDesc &desc) const
402 : {
403 : // Ensure bus is open
404 0 : b.open(desc.subdev);
405 :
406 0 : auto *dev = NEW_NOTHROW SPIDevice(b, desc);
407 0 : if (!dev) {
408 0 : return nullptr;
409 : }
410 :
411 0 : b.ref++;
412 :
413 0 : return dev;
414 : }
415 :
416 0 : void SPIDeviceManager::_unregister(SPIBus &b)
417 : {
418 0 : if (b.ref == 0 || --b.ref > 0) {
419 0 : return;
420 : }
421 :
422 0 : for (auto it = _buses.begin(); it != _buses.end(); it++) {
423 0 : if ((*it)->bus == b.bus) {
424 0 : _buses.erase(it);
425 0 : delete &b;
426 0 : break;
427 : }
428 : }
429 : }
430 :
431 0 : void SPIDeviceManager::teardown()
432 : {
433 0 : for (auto it = _buses.begin(); it != _buses.end(); it++) {
434 : /* Try to stop thread - it may not even be started yet */
435 0 : (*it)->thread.stop();
436 : }
437 :
438 0 : for (auto it = _buses.begin(); it != _buses.end(); it++) {
439 : /* Try to join thread - failing is normal if thread was not started */
440 0 : (*it)->thread.join();
441 : }
442 0 : }
443 :
444 : }
|