rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
tle6240.cpp
Go to the documentation of this file.
1/*
2 * tle6240.c
3 *
4 * TLE6240GP Smart 16-Channel Low-Side Switch
5 *
6 * All 16 channels can be controlled via the serial interface (SPI).
7 * In addition to the serial control it is possible to control channel 1 to 4
8 * and 9 to 12 direct in parallel with a separate input pin.
9 *
10 * Looks like 3.3v SI and SCLK are NOT possible (H above 0.7Vs required, that's 3.5v for 5.0Vs)
11 * 5 MHz SPI
12 * Update: looks like possible:
13 * DS page 3: "Compatible with 3 V Microcontrollers"
14 * DS page 12: "Input High Voltage 2.0 V min"
15 *
16 * @date Dec 29, 2018
17 * @author Andrey Belomutskiy, (c) 2012-2020
18 *
19 * @date Mar 06, 2019
20 * @author Andrey Gusakov, (c) 2019
21 */
22
23#include "pch.h"
24
25#include "gpio/gpio_ext.h"
26#include "gpio/tle6240.h"
27
28#if defined(BOARD_TLE6240_COUNT) && (BOARD_TLE6240_COUNT > 0)
29
30/*
31 * TODO list:
32 * - add irq support with fallback to polling mode (now polling mode only)
33 * - handle low-active inputs (set with PRG pin). Now driver assume high-active
34 * - add way to export native pin data of direct driven outputs. To avoid
35 * call to tle6240_writePad that will finally call native gpio set/clear fn.
36 * In this case direct drive gpios should not be occupied by markUsed in init?
37 * - fill deinit function with some code?
38 * - support emergency shutdown using reset pin
39 * - convert diagnostic to some enum
40 * - use DMA (currently there is issue (?) with SPI+DMA on STM32F7xx)
41 */
42
43/*==========================================================================*/
44/* Driver local definitions. */
45/*==========================================================================*/
46
47#define DRIVER_NAME "tle6240"
48
49static bool drv_task_ready = false;
50
57
58/* set 0000b for channes == 0..7 and 1111b for channels 8..15 */
59#define CMD_CHIP(ch) ((ch < 8) ? 0x00 : 0x0f)
60/* Full Diagnoscit, data byte ignored */
61#define CMD_FULL_DIAG(ch) (((0x00 | CMD_CHIP(ch)) << 8) | 0x00)
62/* Get state of 8 paralled inputs and 1-bit Diagnostic, data byte ignored */
63#define CMD_IO_SHORTDIAG(ch) (((0xc0 | CMD_CHIP(ch)) << 8) | 0x00)
64/* Echo function test of SPI, SI will be connected to SO on next access */
65#define CMD_ECHO(ch) (((0xA0 | CMD_CHIP(ch)) << 8) | 0x00)
66/* in data ORed, Full diagnostic output on next access */
67#define CMD_OR_DIAG(ch, data) (((0x30 | CMD_CHIP(ch)) << 8) | (data & 0xff))
68/* in data ANDed, Full diagnostic output on next access */
69#define CMD_AND_DIAG(ch, data) (((0xf0 | CMD_CHIP(ch)) << 8) | (data & 0xff))
70
71/*==========================================================================*/
72/* Driver exported variables. */
73/*==========================================================================*/
74
75/*==========================================================================*/
76/* Driver local variables and types. */
77/*==========================================================================*/
78
79/* OS */
80SEMAPHORE_DECL(tle6240_wake, 10 /* or BOARD_TLE6240_COUNT ? */);
81static THD_WORKING_AREA(tle6240_thread_1_wa, 256);
82
83/* Driver */
84struct Tle6240 : public GpioChip {
85 int init() override;
86
87 int writePad(size_t pin, int value) override;
88 brain_pin_diag_e getDiag(size_t pin) override;
89
90
91 // internal functions
92 int spi_rw(uint16_t tx, uint16_t *rx);
93 int update_output_and_diag();
94 int chip_init();
95
96
97 const tle6240_config *cfg;
98 /* cached output state - state last send to chip */
99 uint16_t o_state_cached;
100 /* state to be sended to chip */
101 uint16_t o_state;
102 /* direct driven output mask */
103 uint16_t o_direct_mask;
104 /* full diagnostic status */
105 uint16_t diag[2];
106 /* diagnostic for ch 8..15 was requsted by last access
107 * can skip one transaction next time */
108 bool diag_8_reguested;
109
110 tle6240_drv_state drv_state;
111};
112
113static Tle6240 chips[BOARD_TLE6240_COUNT];
114
115static const char* tle6240_pin_names[TLE6240_OUTPUTS] = {
116 "tle6240.OUT1", "tle6240.OUT2", "tle6240.OUT3", "tle6240.OUT4",
117 "tle6240.OUT5", "tle6240.OUT6", "tle6240.OUT7", "tle6240.OUT8",
118 "tle6240.OUT9", "tle6240.OUT10", "tle6240.OUT11", "tle6240.OUT12",
119 "tle6240.OUT13", "tle6240.OUT14", "tle6240.OUT15", "tle6240.OUT16",
120};
121
122/*==========================================================================*/
123/* Driver local functions. */
124/*==========================================================================*/
125
126/**
127 * @brief TLE6240 send and receive routine.
128 * @details Sends and receives 16 bits. CS asserted before and released
129 * after transaction.
130 */
131
132int Tle6240::spi_rw(uint16_t tx, uint16_t *rx)
133{
134 uint16_t rxb;
135 SPIDriver *spi = cfg->spi_bus;
136
137 /* Acquire ownership of the bus. */
138 spiAcquireBus(spi);
139 /* Setup transfer parameters. */
140 spiStart(spi, &cfg->spi_config);
141 /* Slave Select assertion. */
142 spiSelect(spi);
143 /* Atomic transfer operations. */
144 rxb = spiPolledExchange(spi, tx);
145 /* Slave Select de-assertion. */
146 spiUnselect(spi);
147 /* Ownership release. */
148 spiReleaseBus(spi);
149
150 if (rx)
151 *rx = rxb;
152
153 /* no errors for now */
154 return 0;
155}
156
157/**
158 * @brief TLE6240 send output registers data.
159 * @details Sends ORed data to register, also receive 2-bit diagnostic.
160 */
161
162int Tle6240::update_output_and_diag()
163{
164 int ret;
165 uint16_t out_data;
166
167 /* atomic */
168 /* set value only for non-direct driven pins */
169 out_data = o_state & (~o_direct_mask);
170 if (diag_8_reguested) {
171 /* diagnostic for OUT8..15 was requested on prev access */
172 ret = spi_rw(CMD_OR_DIAG(0, (out_data >> 0) & 0xff), &diag[1]);
173 ret |= spi_rw(CMD_OR_DIAG(8, (out_data >> 8) & 0xff), &diag[0]);
174 } else {
175 ret = spi_rw(CMD_OR_DIAG(0, (out_data >> 0) & 0xff), NULL);
176 ret |= spi_rw(CMD_OR_DIAG(8, (out_data >> 8) & 0xff), &diag[0]);
177 /* send same one more time to receive OUT8..15 diagnostic */
178 ret |= spi_rw(CMD_OR_DIAG(8, (out_data >> 8) & 0xff), &diag[1]);
179 }
180
181 diag_8_reguested = false;
182 if (ret == 0) {
183 /* atomic */
184 o_state_cached = out_data;
185 diag_8_reguested = true;
186 }
187
188 return ret;
189}
190
191/**
192 * @brief TLE6240 chip init.
193 * @details Checks communication. Mark all used pins.
194 * Checks direct io signals integrity using test cmd.
195 * Reads initial diagnostic state.
196 */
197
198int Tle6240::chip_init()
199{
200 int n;
201 int ret;
202 uint16_t rx;
203
204 /* mark pins used */
205 //ret = gpio_pin_markUsed(cfg->spi_config.ssport, cfg->spi_config.sspad, DRIVER_NAME " CS");
206 ret = 0;
207 if (cfg->reset.port != NULL)
208 ret |= gpio_pin_markUsed(cfg->reset.port, cfg->reset.pad, DRIVER_NAME " RST");
209 for (n = 0; n < TLE6240_DIRECT_OUTPUTS; n++)
210 if (cfg->direct_io[n].port)
211 ret |= gpio_pin_markUsed(cfg->direct_io[n].port, cfg->direct_io[n].pad, DRIVER_NAME " DIRECT IO");
212
213 if (ret) {
214 ret = -1;
215 goto err_gpios;
216 }
217
218 /* release reset */
219 if (cfg->reset.port) {
220 palClearPort(cfg->reset.port,
221 PAL_PORT_BIT(cfg->reset.pad));
222 chThdSleepMilliseconds(1);
223 palSetPort(cfg->reset.port,
224 PAL_PORT_BIT(cfg->reset.pad));
225 chThdSleepMilliseconds(10);
226 }
227
228 /* check SPI communication */
229 /* 0. set echo mode, chip number - don't care */
230 ret = spi_rw(CMD_ECHO(0), nullptr);
231 /* 1. check loopback */
232 ret |= spi_rw(0x5555, &rx);
233 if (ret || (rx != 0x5555)) {
234 //print(DRIVER_NAME " spi loopback test failed\n");
235 ret = -2;
236 goto err_gpios;
237 }
238
239 /* check direct io communication */
240 /* 0. set all direct out to 0 */
241 for (n = 0; n < TLE6240_DIRECT_OUTPUTS; n++) {
242 int i = (n < 4) ? n : (n + 4);
243 if (o_direct_mask & (1 << i)) {
244 palClearPort(cfg->direct_io[n].port,
245 PAL_PORT_BIT(cfg->direct_io[n].pad));
246 }
247 }
248 /* 1. disable IN0..7 outputs first (ADNed with 0x00)
249 * also will get full diag on next access */
250 ret = spi_rw(CMD_AND_DIAG(0, 0x00), NULL);
251 /* 2. get diag for OUT0..7 and send disable OUT8..15 */
252 ret |= spi_rw(CMD_AND_DIAG(8, 0x00), &diag[0]);
253 /* 3. get diag for OUT8..15 and readback input status */
254 ret |= spi_rw(CMD_IO_SHORTDIAG(0), &diag[1]);
255 /* 4. send dummy short diag command and get 8 bit of input data and
256 * 8 bit of short diag */
257 ret |= spi_rw(CMD_IO_SHORTDIAG(0), &rx);
258 rx = ((rx >> 4) & 0x0f00) | ((rx >> 8) & 0x000f);
259 if (ret || (rx & o_direct_mask)) {
260 //print(DRIVER_NAME " direct io test #1 failed (invalid io mask %04x)\n", (rx & chip->o_direct_mask));
261 ret = -3;
262 goto err_gpios;
263 }
264
265 /* 5. set all direct io to 1 */
266 for (n = 0; n < TLE6240_DIRECT_OUTPUTS; n++) {
267 int i = (n < 4) ? n : (n + 4);
268 if (o_direct_mask & (1 << i)) {
269 palSetPort(cfg->direct_io[n].port,
270 PAL_PORT_BIT(cfg->direct_io[n].pad));
271 }
272 }
273 /* 6. read chort diagnostic again */
274 ret |= spi_rw(CMD_IO_SHORTDIAG(0), &rx);
275 rx = ((rx >> 4) & 0x0f00) | ((rx >> 8) & 0x000f);
276 rx &= o_direct_mask;
277 if (ret || (rx != o_direct_mask)) {
278 //print(DRIVER_NAME " direct io test #2 failed (invalid io mask %04x)\n", (rx ^ (~chip->o_direct_mask)));
279 ret = -4;
280 goto err_gpios;
281 }
282
283 /* 7. set all all pins to OR mode, and upload pin states */
284 ret = update_output_and_diag();
285 if (ret) {
286 //print(DRIVER_NAME " final setup error\n");
287 ret = -5;
288 goto err_gpios;
289 }
290
291 return 0;
292
293err_gpios:
294 /* unmark pins */
295 //gpio_pin_markUnused(cfg->spi_config.ssport, cfg->spi_config.sspad);
296 if (cfg->reset.port != NULL)
297 gpio_pin_markUnused(cfg->reset.port, cfg->reset.pad);
298 for (n = 0; n < TLE6240_DIRECT_OUTPUTS; n++)
299 if (cfg->direct_io[n].port)
300 gpio_pin_markUnused(cfg->direct_io[n].port, cfg->direct_io[n].pad);
301
302 return ret;
303}
304
305/**
306 * @brief TLE6240 chip driver wakeup.
307 * @details Wake up driver. Will cause output register and
308 * diagnostic update.
309 */
310
312{
313 /* Entering a reentrant critical zone.*/
314 chibios_rt::CriticalSectionLocker csl;
315
316 chSemSignalI(&tle6240_wake);
317 if (!port_is_isr_context()) {
318 /**
319 * chSemSignalI above requires rescheduling
320 * interrupt handlers have implicit rescheduling
321 */
322 chSchRescheduleS();
323 }
324
325 return 0;
326}
327
328/*==========================================================================*/
329/* Driver thread. */
330/*==========================================================================*/
331
332static THD_FUNCTION(tle6240_driver_thread, p)
333{
334 int i;
335 msg_t msg;
336
337 (void)p;
338
339 chRegSetThreadName(DRIVER_NAME);
340
341 while(1) {
342 msg = chSemWaitTimeout(&tle6240_wake, TIME_MS2I(TLE6240_POLL_INTERVAL_MS));
343
344 /* should we care about msg == MSG_TIMEOUT? */
345 (void)msg;
346
347 for (i = 0; i < BOARD_TLE6240_COUNT; i++) {
348 int ret;
349 Tle6240& chip = chips[i];
350
351 if (!chip.cfg ||
352 (chip.drv_state == TLE6240_DISABLED) ||
353 (chip.drv_state == TLE6240_FAILED))
354 continue;
355
356 ret = chip.update_output_and_diag();
357 if (ret) {
358 /* set state to TLE6240_FAILED? */
359 }
360 }
361 }
362}
363
364/*==========================================================================*/
365/* Driver interrupt handlers. */
366/*==========================================================================*/
367
368/* TODO: add IRQ support */
369
370/*==========================================================================*/
371/* Driver exported functions. */
372/*==========================================================================*/
373
374int Tle6240::writePad(unsigned int pin, int value)
375{
376 if (pin >= TLE6240_OUTPUTS)
377 return -1;
378
379 {
380 chibios_rt::CriticalSectionLocker csl;
381
382 if (value)
383 o_state |= (1 << pin);
384 else
385 o_state &= ~(1 << pin);
386 }
387
388 /* direct driven? */
389 if (o_direct_mask & (1 << pin)) {
390 int n = (pin < 8) ? pin : (pin - 4);
391
392 /* TODO: ensure that TLE6240 configured in active high mode */
393 if (value)
394 palSetPort(cfg->direct_io[n].port,
395 PAL_PORT_BIT(cfg->direct_io[n].pad));
396 else
397 palClearPort(cfg->direct_io[n].port,
398 PAL_PORT_BIT(cfg->direct_io[n].pad));
399 } else {
401 }
402
403 return 0;
404}
405
406brain_pin_diag_e Tle6240::getDiag(size_t pin)
407{
408 int val;
409 int diagVal;
410
411 if (pin >= TLE6240_OUTPUTS)
412 return PIN_UNKNOWN;
413
414 val = (diag[(pin > 7) ? 1 : 0] >> ((pin % 8) * 2)) & 0x03;
415 if (val == 0x3)
416 diagVal = PIN_OK;
417 else if (val == 0x2)
418 /* Overload, shorted load or overtemperature */
419 diagVal = PIN_OVERLOAD | PIN_DRIVER_OVERTEMP;
420 else if (val == 0x1)
421 diagVal = PIN_OPEN;
422 else if (val == 0x0)
423 diagVal = PIN_SHORT_TO_GND;
424
425 return static_cast<brain_pin_diag_e>(diagVal);
426}
427
428int Tle6240::init()
429{
430 int ret = chip_init();
431 if (ret)
432 return ret;
433
434 drv_state = TLE6240_READY;
435
436 if (!drv_task_ready) {
437 chThdCreateStatic(tle6240_thread_1_wa, sizeof(tle6240_thread_1_wa),
438 PRIO_GPIOCHIP, tle6240_driver_thread, nullptr);
439 drv_task_ready = true;
440 }
441
442 return 0;
443}
444
445/**
446 * @brief TLE6240 driver add.
447 * @details Checks for valid config
448 */
449
450int tle6240_add(brain_pin_e base, unsigned int index, const tle6240_config *cfg)
451{
452 int i;
453 int ret;
454 Tle6240 *chip;
455
456 /* no config or no such chip */
457 if ((!cfg) || (!cfg->spi_bus) || (index >= BOARD_TLE6240_COUNT))
458 return -1;
459
460 /* check for valid cs.
461 * TODO: remove this check? CS can be driven by SPI */
462 //if (cfg->spi_config.ssport == NULL)
463 // return -1;
464
465 chip = &chips[index];
466
467 /* already initted? */
468 if (chip->cfg != NULL)
469 return -1;
470
471 chip->cfg = cfg;
472 chip->o_state = 0;
473 chip->o_state_cached = 0;
474 chip->o_direct_mask = 0;
475 chip->drv_state = TLE6240_WAIT_INIT;
476 for (i = 0; i < TLE6240_DIRECT_OUTPUTS; i++) {
477 if (cfg->direct_io[i].port != 0)
478 chip->o_direct_mask |= (1 << ((i < 4) ? i : (i + 4)));
479 }
480
481 /* register, return gpio chip base */
482 ret = gpiochip_register(base, DRIVER_NAME, *chip, TLE6240_OUTPUTS);
483 if (ret < 0)
484 return ret;
485
486 /* set default pin names, board init code can rewrite */
488
489 return ret;
490}
491
492#else /* BOARD_TLE6240_COUNT > 0 */
493
494int tle6240_add(brain_pin_e base, unsigned int index, const tle6240_config *cfg)
495{
496 (void)base; (void)index; (void)cfg;
497
498 return -1;
499}
500
501#endif /* BOARD_TLE6240_COUNT */
int gpiochip_register(brain_pin_e base, const char *name, GpioChip &gpioChip, size_t size)
Register gpiochip.
Definition core.cpp:186
int gpiochips_setPinNames(brain_pin_e base, const char **names)
Set pins names for registered gpiochip.
Definition core.cpp:266
void gpio_pin_markUnused(ioportid_t port, ioportmask_t pin)
bool gpio_pin_markUsed(ioportid_t port, ioportmask_t pin, const char *msg)
brain_pin_diag_e
brain_pin_e pin
Definition stm32_adc.cpp:15
virtual brain_pin_diag_e getDiag(size_t)
Definition gpio_ext.h:31
virtual int writePad(size_t, int)
Definition gpio_ext.h:28
virtual int init()=0
SPIDriver * spi_bus
Definition tle6240.h:23
struct tle6240_config::@39 direct_io[TLE6240_DIRECT_OUTPUTS]
ioportid_t port
Definition tle6240.h:27
int tle6240_add(brain_pin_e base, unsigned int index, const tle6240_config *cfg)
TLE6240 driver add.
Definition tle6240.cpp:450
static bool drv_task_ready
Definition tle6240.cpp:49
SEMAPHORE_DECL(tle6240_wake, 10)
static THD_WORKING_AREA(tle6240_thread_1_wa, 256)
static int tle6240_wake_driver()
TLE6240 chip driver wakeup.
Definition tle6240.cpp:311
static Tle6240 chips[BOARD_TLE6240_COUNT]
Definition tle6240.cpp:113
tle6240_drv_state
Definition tle6240.cpp:51
@ TLE6240_WAIT_INIT
Definition tle6240.cpp:53
@ TLE6240_DISABLED
Definition tle6240.cpp:52
@ TLE6240_FAILED
Definition tle6240.cpp:55
@ TLE6240_READY
Definition tle6240.cpp:54
static const char * tle6240_pin_names[TLE6240_OUTPUTS]
Definition tle6240.cpp:115
static THD_FUNCTION(tle6240_driver_thread, p)
Definition tle6240.cpp:332