diff --git a/include/edma.h b/include/edma.h index 08a24c6..fe7643c 100644 --- a/include/edma.h +++ b/include/edma.h @@ -5,8 +5,9 @@ extern "C" { #endif int edma_set_irq(int vect_id); -int edma_attach_tcc_irq(int tccNum, void (*func)(int tccNum)); +int edma_attach_tcc_irq(int tccNum, void (*func)(int tccNum, void *arg), void *arg); int edma_dettach_tcc_irq(int tccNum); +int edma_copy(void *to, const void *from, int len); #ifdef __cplusplus } diff --git a/include/spi.h b/include/spi.h index fedbe5f..ec3226e 100644 --- a/include/spi.h +++ b/include/spi.h @@ -16,7 +16,8 @@ int spi_open(int dev_id, int mode, int freq, int bits); int spi_write(int dev_id, const void *buf, int len); int spi_read(int dev_id, void *buf, int len); -int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *args), void *args); +int spi_edma_write(int dev_id, const void *buf, int len, void (*on_complete)(void *args), void *args); +int spi_edma_read(int dev_id, void *buf, int len, void (*on_complete)(void *args), void *args); void spi_close(int dev_id); diff --git a/platforms/dsk/platform_dsk.c b/platforms/dsk/platform_dsk.c index 018a98f..bebfd69 100644 --- a/platforms/dsk/platform_dsk.c +++ b/platforms/dsk/platform_dsk.c @@ -73,6 +73,9 @@ void platform_pre_init() void platform_post_init() { CACHE_setL2Mode(CACHE_32KCACHE); + + edma_set_irq(8); + led_off(0); led_off(1); led_off(2); diff --git a/platforms/dsk/tlv320aic23.c b/platforms/dsk/tlv320aic23.c index ffb14f2..43a3606 100644 --- a/platforms/dsk/tlv320aic23.c +++ b/platforms/dsk/tlv320aic23.c @@ -26,8 +26,8 @@ void aic23_init() Uint16 aic23[] = { 0x0, 0x17, 0x1, 0x17, - 0x2, 0xff, - 0x3, 0xf9, + 0x2, 0xfb, + 0x3, 0xfb, 0x4, 0x133, 0x5, 0x06, 0x6, 0x0, diff --git a/src/bfsk/tone.c b/src/bfsk/tone.c index 91bb984..2dfedd0 100644 --- a/src/bfsk/tone.c +++ b/src/bfsk/tone.c @@ -160,7 +160,7 @@ void tone_generate(simpleaudio *sa_out, float tone_freq, size_t nsamples_dur) } - assert ( simpleaudio_write(sa_out, buf, nsamples_dur * sa_out->channels) > 0 ); + assert (simpleaudio_write(sa_out, buf, nsamples_dur) > 0 ); free(buf); } diff --git a/src/console.c b/src/console.c index ceac2da..46f85e3 100644 --- a/src/console.c +++ b/src/console.c @@ -8,7 +8,7 @@ simpleaudio *sa = NULL; int console_init() { - tone_init(4096, 1.0); + tone_init(4096, 0.4); sa = simpleaudio_open_stream(SA_BACKEND_SYSDEFAULT, "mcbsp:2", SA_STREAM_PLAYBACK, diff --git a/src/edma.c b/src/edma.c index bed1c70..0420aba 100644 --- a/src/edma.c +++ b/src/edma.c @@ -1,15 +1,81 @@ +#include +#include #include "platform_internal.h" +struct edma_descr { + int in_use; + void (*handler)(int tcc, void *arg); + void *data; +}; + +#define EDMA_CHANNEL_MAX (_EDMA_CHA_CNT) +static struct edma_descr *__edma_descr[EDMA_CHANNEL_MAX]; + +void edma_lowlevel_init() +{ + memset(__edma_descr, 0, sizeof __edma_descr); +} + +void edma_lowlevel_close() +{ + int i; + struct edma_descr *edptr; + + irq_dettach(IRQ_EVT_EDMAINT, EDMA_intDispatcher, NULL); + for (i = 0; i < EDMA_CHANNEL_MAX; ++i) { + edptr = __edma_descr[i]; + if (edptr) { + if (edptr->in_use) + EDMA_intDisable(i); + free(edptr); + __edma_descr[i] = NULL; + } + } +} + int edma_set_irq(int vect_id) { return irq_attach(vect_id, IRQ_EVT_EDMAINT, EDMA_intDispatcher, NULL); } -int edma_attach_tcc_irq(int tccNum, void (*func)(int tccNum)) +static void __edma_int_handler(int tccNum) { - EDMA_reset(IRQ_EVT_EDMAINT); + struct edma_descr *edptr = __edma_descr[tccNum]; + + EDMA_intClear(tccNum); + if (edptr && edptr->in_use && edptr->handler) { + edptr->handler(tccNum, edptr->data); + } +} + +int edma_attach_tcc_irq(int tccNum, void (*func)(int tccNum, void *arg), void *arg) +{ + struct edma_descr *edptr; + + if (tccNum < 0 || tccNum >= EDMA_CHANNEL_MAX) { + return -1; + } + + edptr = __edma_descr[tccNum]; + if (!edptr) { + edptr = calloc(1, sizeof *edptr); + if (edptr == NULL) { + return -2; + } + __edma_descr[tccNum] = edptr; + } + + if (edptr->in_use) { + return -2; + } + + edptr->handler = func; + edptr->data = arg; + edptr->in_use = 1; + EDMA_intDisable(tccNum); - EDMA_intHook(tccNum, func); + EDMA_intAlloc(tccNum); + EDMA_intHook(tccNum, __edma_int_handler); EDMA_intClear(tccNum); EDMA_intEnable(tccNum); return 0; @@ -17,8 +83,99 @@ int edma_attach_tcc_irq(int tccNum, void (*func)(int tccNum)) int edma_dettach_tcc_irq(int tccNum) { + struct edma_descr *edptr; + + if (tccNum < 0 || tccNum >= EDMA_CHANNEL_MAX) { + return -1; + } + edptr = __edma_descr[tccNum]; + if (edptr) + edptr->in_use = 0; + EDMA_intDisable(tccNum); + EDMA_intFree(tccNum); EDMA_intHook(tccNum, EDMA_intDefaultHandler); return 0; } +static void __copy_done(int tccNum, void *arg) +{ + int *done = arg; + + *done = 1; +} + +int edma_copy(void *to, const void *from, int len) +{ + Uint32 esize; + EDMA_Config edma_conf; + int slen = len; + int tcc; + EDMA_Handle handle; + volatile int done = 0; + + if ((len & 0x3) == 0) { + esize = EDMA_OPT_ESIZE_32BIT; + len /= 4; + } else { + if ((len & 0x1) == 0) { + esize = EDMA_OPT_ESIZE_16BIT; + len /= 2; + } else { + esize = EDMA_OPT_ESIZE_8BIT; + } + } + + handle = EDMA_open(EDMA_CHA_ANY, EDMA_OPEN_RESET); + if (handle == EDMA_HINV) { + return -1; + } + + tcc = EDMA_intAlloc(-1); + if (tcc < 0) { + EDMA_close(handle); + return -2; + } + + edma_conf.opt = EDMA_OPT_RMK( + EDMA_OPT_PRI_MEDIUM, + esize, + EDMA_OPT_2DS_NO, + EDMA_OPT_SUM_INC, + EDMA_OPT_2DD_NO, + EDMA_OPT_DUM_INC, + EDMA_OPT_TCINT_YES, + EDMA_OPT_TCC_OF(tcc), + EDMA_OPT_TCCM_OF(tcc >> 4), + EDMA_OPT_ATCINT_NO, + EDMA_OPT_ATCC_OF(0), + EDMA_OPT_PDTS_DEFAULT, + EDMA_OPT_PDTS_DEFAULT, + EDMA_OPT_LINK_NO, + EDMA_OPT_FS_NO); + + edma_conf.src = EDMA_SRC_RMK((Uint32)from); + edma_conf.cnt = EDMA_CNT_RMK(0, len); + edma_conf.dst = EDMA_DST_RMK((Uint32)(to)); + edma_conf.idx = EDMA_IDX_RMK(0, 0); + edma_conf.rld = EDMA_RLD_RMK(0, 0); + + edma_attach_tcc_irq(tcc, __copy_done, (void *)&done); + + EDMA_config(handle, &edma_conf); +// EDMA_enableChannel(handle); + + EDMA_setChannel(handle); + + while (done == 0) + ; + + EDMA_disableChannel(handle); + + edma_dettach_tcc_irq(tcc); + + EDMA_close(handle); + + return (slen); +} + diff --git a/src/platform.c b/src/platform.c index 76af497..953bda1 100644 --- a/src/platform.c +++ b/src/platform.c @@ -8,8 +8,10 @@ int platform_init() spi_lowlevel_init(); timer_lowlevel_init(); - /* clear edma PaRam */ EDMA_clearPram(0x00000000); + + edma_lowlevel_init(); + /* clear edma PaRam */ platform_pre_init(); IRQ_setVecs(vectors); @@ -18,12 +20,20 @@ int platform_init() IRQ_nmiEnable(); IRQ_globalEnable(); + EDMA_reset(IRQ_EVT_EDMAINT); platform_post_init(); return 0; } +void platform_close() +{ + IRQ_globalDisable(); + + edma_lowlevel_close(); +} + Uint8 read_reg8(Uint32 addr) { return *(volatile Uint8 *)(addr); diff --git a/src/platform_internal.h b/src/platform_internal.h index a0ec127..5d016ac 100644 --- a/src/platform_internal.h +++ b/src/platform_internal.h @@ -10,8 +10,12 @@ void tick_close(); void irq_lowlevel_init(); void spi_lowlevel_init(); void spi_lowlevel_init(); +void edma_lowlevel_init(); void timer_lowlevel_init(); int timer_event_id(int dev_id); + +void edma_lowlevel_close(); + #endif diff --git a/src/spi.c b/src/spi.c index f19808c..3888f1b 100644 --- a/src/spi.c +++ b/src/spi.c @@ -1,63 +1,70 @@ +/* vim: set fdm=marker sw=4 ts=4 nu: */ #include #include #include "platform_internal.h" #include "spi.h" -#define TXBUF_SIZE (1024) -#define RXBUF_SIZE (1024) - typedef struct __spi_handle *spi_t; +/* {{{: struct __spi_handle {...} */ struct __spi_handle { MCBSP_Handle spi; int bits; + int is_i2s; /* tx */ EDMA_Handle xh; void (*xnotify)(void *args); void *xargs; - char xbuf[TXBUF_SIZE]; /* rx */ EDMA_Handle rh; void (*rnotify)(void *args); void *rargs; - char rbuf[RXBUF_SIZE]; }; static struct __spi_handle __spi_devs[_MCBSP_PORT_CNT]; +/* }}} */ void spi_lowlevel_init() { memset(__spi_devs, 0, sizeof __spi_devs); } -static spi_t __find_spi_with_xtcc(int tccNum) +/* {{{: interrupt handler */ +static void __tx_notify(int tccNum, void *arg) { - int i; - spi_t spi; - for (i = 0; i < _MCBSP_PORT_CNT; ++i) { - spi = &__spi_devs[i]; - if (MCBSP_getXmtEventId(spi->spi) == tccNum) { - return spi; + spi_t spi = arg; + void (*handler)(void *); + + if (spi) { + EDMA_disableChannel(spi->xh); + handler = spi->xnotify; + if (handler) { + spi->xnotify = NULL; + handler(spi->xargs); + edma_dettach_tcc_irq(tccNum); } } - return NULL; } -static void __tx_notify(int tccNum) +static void __rx_notify(int tccNum, void *arg) { - spi_t spi; + spi_t spi = arg; + void (*handler)(void *); - EDMA_intClear(tccNum); - - spi = __find_spi_with_xtcc(tccNum); if (spi) { - EDMA_disableChannel(spi->xh); - if (spi->xnotify) - spi->xnotify(spi->xargs); + EDMA_disableChannel(spi->rh); + handler = spi->rnotify; + if (handler) { + spi->rnotify = NULL; + handler(spi->rargs); + edma_dettach_tcc_irq(tccNum); + } } } +/* }}} */ +/* {{{: i2s_slave_open(...) */ static Uint32 __parse_to_dlen1(int bits) { Uint32 dlen1; @@ -104,6 +111,8 @@ int i2s_slave_open(int dev_id, int bits) return -1; /* busy ? */ } + spi->is_i2s = 1; + if ((int)(dlen1 = __parse_to_dlen1(bits)) < 0) { return -3; } @@ -146,7 +155,7 @@ int i2s_slave_open(int dev_id, int bits) MCBSP_RCR_RCOMPAND_MSB, // Transmit companding mode(XCOMPAND) MCBSP_RCR_RFIG_YES, // Transmit frame ignore(XFIG) MCBSP_RCR_RDATDLY_1BIT, // Transmit data delay(XDATDLY) - MCBSP_RCR_RFRLEN1_OF(0), // Transmit frame length + MCBSP_RCR_RFRLEN1_OF(1), // Transmit frame length // in phase 1(XFRLEN1) dlen1, // Transmit element length // in phase 1(XWDLEN1) @@ -163,7 +172,7 @@ int i2s_slave_open(int dev_id, int bits) MCBSP_XCR_XCOMPAND_MSB, // Transmit companding mode(XCOMPAND) MCBSP_XCR_XFIG_NO, // Transmit frame ignore(XFIG) MCBSP_XCR_XDATDLY_1BIT, // Transmit data delay(XDATDLY) - MCBSP_XCR_XFRLEN1_OF(0), // Transmit frame length + MCBSP_XCR_XFRLEN1_OF(1), // Transmit frame length // in phase 1(XFRLEN1) dlen1, // Transmit element length // in phase 1(XWDLEN1) @@ -206,7 +215,9 @@ int i2s_slave_open(int dev_id, int bits) MCBSP_SRGR_DEFAULT_DELAY); return 0; } +/* }}} */ +/* {{{: spi_open(int dev_id, int mode, int freq, int bits) */ int spi_open(int dev_id, int mode, int freq, int bits) { MCBSP_Config mbconf; @@ -224,6 +235,9 @@ int spi_open(int dev_id, int mode, int freq, int bits) if (spi->spi) { return -1; /* busy ? */ } + + spi->is_i2s = 0; + if ((int)(dlen1 = __parse_to_dlen1(bits)) < 0) { return -3; } @@ -355,45 +369,9 @@ int spi_open(int dev_id, int mode, int freq, int bits) spi->spi = handle; return 0; } +/* }}} */ -static int __write_u8(spi_t spi, const Uint8 *buf, int len) -{ - int i; - for (i = 0; i < len; ++i) { - while (!MCBSP_xrdy(spi->spi)) - ; - MCBSP_write(spi->spi, buf[i]); - } - return (len); -} - -static int __write_u16(spi_t spi, const Uint16 *buf, int len) -{ - int i; - - len >>= 1; - - for (i = 0; i < len; ++i) { - while (!MCBSP_xrdy(spi->spi)) - ; - MCBSP_write(spi->spi, buf[i]); - } - return (len); -} - -static int __write_u32(spi_t spi, const Uint32 *buf, int len) -{ - int i; - - len >>= 2; - for (i = 0; i < len; ++i) { - while (!MCBSP_xrdy(spi->spi)) - ; - MCBSP_write(spi->spi, buf[i]); - } - return (len); -} - +/* {{{: int spi_write(int dev_id, const void *buf, int len) */ static spi_t __get_active_device(int dev_id) { spi_t spi; @@ -406,11 +384,12 @@ static spi_t __get_active_device(int dev_id) return spi->spi ? spi : NULL; } -int spi_write(int dev_id, const void *buf, int len) +int spi_write(int dev_id, const void *buf, int _len) { const Uint8 *u8; const Uint16 *u16; const Uint32 *u32; + int i, len = _len; spi_t spi; if ((spi = __get_active_device(dev_id)) == NULL) { @@ -422,65 +401,62 @@ int spi_write(int dev_id, const void *buf, int len) switch (spi->bits) { case 8: u8 = buf; - __write_u8(spi, u8, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_xrdy(spi->spi)) + ; + MCBSP_write(spi->spi, u8[i]); + } break; case 12: case 16: + len >>= 1; u16 = buf; - __write_u16(spi, u16, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_xrdy(spi->spi)) + ; + MCBSP_write(spi->spi, u16[i]); + } break; case 20: case 24: case 32: + len >>= 2; u32 = buf; - __write_u32(spi, u32, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_xrdy(spi->spi)) + ; + MCBSP_write(spi->spi, u32[i]); + } break; default: return (-1); } - return (len); + return (_len); } +/* }}} */ -int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *args), void *args) +static int __do_spi_edma_write(spi_t spi, int bits, const void *buf, int len, void (*notify)(void *args), void *args) { - spi_t spi; Uint32 esize; - EDMA_Config edma_conf; - int slen, xevent; + static EDMA_Config edma_conf; + int frmlen = 0, xevent, slen = len; - if ((spi = __get_active_device(dev_id)) == NULL) { - return -2; - } - - spi = &__spi_devs[dev_id]; - if (len > TXBUF_SIZE) { - len = TXBUF_SIZE; - } - - slen = len; - memcpy(spi->xbuf, buf, len); - - switch (spi->bits) { + switch (bits) { case 8: esize = EDMA_OPT_ESIZE_8BIT; break; - case 12: case 16: esize = EDMA_OPT_ESIZE_16BIT; - len /= 2; + len >>= 1; break; - case 20: - case 24: case 32: esize = EDMA_OPT_ESIZE_32BIT; - len /= 4; + len >>= 2; break; default: return (-2); } - CACHE_wbL2(spi->xbuf, len, CACHE_WAIT); - xevent = MCBSP_getXmtEventId(spi->spi); edma_conf.opt = EDMA_OPT_RMK( @@ -492,7 +468,7 @@ int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *ar EDMA_OPT_DUM_NONE, EDMA_OPT_TCINT_YES, EDMA_OPT_TCC_OF(xevent), - EDMA_OPT_TCCM_OF(xevent >> 4), + EDMA_OPT_TCCM_OF((xevent >> 4)), EDMA_OPT_ATCINT_NO, EDMA_OPT_ATCC_OF(0), EDMA_OPT_PDTS_DEFAULT, @@ -500,11 +476,22 @@ int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *ar EDMA_OPT_LINK_NO, EDMA_OPT_FS_NO ); - edma_conf.src = EDMA_SRC_RMK((Uint32)(spi->xbuf)); - edma_conf.cnt = EDMA_CNT_RMK(0, len); + + if (len > 65535) { + frmlen = len / 65535 - 1; + + if (frmlen > 65535) + frmlen = 65535; + + len = 65535; + slen = (frmlen + 1) * 65535 * (bits / 8); + } + + edma_conf.src = EDMA_SRC_RMK((Uint32)(buf)); + edma_conf.cnt = EDMA_CNT_RMK(frmlen, len); edma_conf.dst = EDMA_DST_OF(MCBSP_getXmtAddr(spi->spi)); edma_conf.idx = EDMA_IDX_RMK(0, 0); - edma_conf.rld = EDMA_RLD_RMK(0, 0); + edma_conf.rld = EDMA_RLD_RMK(len, 0); spi->xnotify = notify; spi->xargs = args; @@ -512,58 +499,38 @@ int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *ar if (spi->xh == NULL) spi->xh = EDMA_open(xevent, 0); - edma_attach_tcc_irq(xevent, __tx_notify); - EDMA_config(spi->xh, &edma_conf); + edma_attach_tcc_irq(xevent, __tx_notify, spi); + EDMA_enableChannel(spi->xh); return (slen); } -static int __read_u8(spi_t spi, Uint8 *buf, int len) -{ - int i; - for (i = 0; i < len; ++i) { - while (!MCBSP_rrdy(spi->spi)) - ; - buf[i] = MCBSP_read(spi->spi); - } - return (len); -} - -static int __read_u16(spi_t spi, Uint16 *buf, int len) +int spi_edma_write(int dev_id, const void *buf, int len, void (*notify)(void *args), void *args) { - int i; + spi_t spi; + int bits; - len >>= 1; - for (i = 0; i < len; ++i) { - while (!MCBSP_rrdy(spi->spi)) - ; - buf[i] = MCBSP_read(spi->spi); + if ((spi = __get_active_device(dev_id)) == NULL) { + return -2; } - return (len); -} -static int __read_u32(spi_t spi, Uint32 *buf, int len) -{ - int i; + spi = &__spi_devs[dev_id]; - len >>= 2; - for (i = 0; i < len; ++i) { - while (!MCBSP_rrdy(spi->spi)) - ; - buf[i] = MCBSP_read(spi->spi); - } - return (len); + bits = spi->bits; /* i2s: 2 channels ? */ + return __do_spi_edma_write(spi, bits, buf, len, notify, args); } -int spi_read(int dev_id, void *buf, int len) +/* {{{: int spi_read(int dev_id, void *buf, int _len) */ +int spi_read(int dev_id, void *buf, int _len) { Uint8 *u8; Uint16 *u16; Uint32 *u32; spi_t spi; + int i, len = _len; if ((spi = __get_active_device(dev_id)) == NULL) { return -1; @@ -572,25 +539,113 @@ int spi_read(int dev_id, void *buf, int len) switch (spi->bits) { case 8: u8 = buf; - __read_u8(spi, u8, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_rrdy(spi->spi)) + ; + u8[i] = MCBSP_read(spi->spi); + } break; case 12: case 16: + len >>= 1; u16 = buf; - __read_u16(spi, u16, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_rrdy(spi->spi)) + ; + u16[i] = MCBSP_read(spi->spi); + } break; case 20: case 24: case 32: + len >>= 2; u32 = buf; - __read_u32(spi, u32, len); + for (i = 0; i < len; ++i) { + while (!MCBSP_rrdy(spi->spi)) + ; + u32[i] = MCBSP_read(spi->spi); + } break; default: return (-1); } - return (len); + return (_len); +} +/* }}} */ + +int spi_edma_read(int dev_id, void *buf, int len, void (*on_complete)(void *args), void *args) +{ + spi_t spi; + Uint32 esize; + EDMA_Config edma_conf; + int xevent, slen = len; + + if ((spi = __get_active_device(dev_id)) == NULL) { + return -2; + } + + spi = &__spi_devs[dev_id]; + + switch (spi->bits) { + case 8: + esize = EDMA_OPT_ESIZE_8BIT; + break; + case 12: + case 16: + esize = EDMA_OPT_ESIZE_16BIT; + len /= 2; + break; + case 20: + case 24: + case 32: + esize = EDMA_OPT_ESIZE_32BIT; + len /= 4; + break; + default: + return (-2); + } + + xevent = MCBSP_getRcvEventId(spi->spi); + + edma_conf.opt = EDMA_OPT_RMK( + EDMA_OPT_PRI_MEDIUM, + esize, + EDMA_OPT_2DS_NO, + EDMA_OPT_SUM_NONE, + EDMA_OPT_2DD_NO, + EDMA_OPT_DUM_INC, + EDMA_OPT_TCINT_YES, + EDMA_OPT_TCC_OF(xevent), + EDMA_OPT_TCCM_OF(xevent >> 4), + EDMA_OPT_ATCINT_NO, + EDMA_OPT_ATCC_OF(0), + EDMA_OPT_PDTS_DEFAULT, + EDMA_OPT_PDTS_DEFAULT, + EDMA_OPT_LINK_NO, + EDMA_OPT_FS_NO); + + edma_conf.src = EDMA_SRC_OF(MCBSP_getRcvAddr(spi->spi)); + edma_conf.cnt = EDMA_CNT_RMK(0, len); + edma_conf.dst = EDMA_DST_RMK((Uint32)(buf)); + edma_conf.idx = EDMA_IDX_RMK(0, 0); + edma_conf.rld = EDMA_RLD_RMK(0, 0); + + spi->rnotify = on_complete; + spi->rargs = args; + + if (spi->rh == NULL) + spi->rh = EDMA_open(xevent, 0); + + edma_attach_tcc_irq(xevent, __rx_notify, spi); + + EDMA_config(spi->rh, &edma_conf); + + EDMA_enableChannel(spi->rh); + + return (slen); } +/* {{{ void spi_close(int dev_id) */ void spi_close(int dev_id) { spi_t spi; @@ -603,4 +658,5 @@ void spi_close(int dev_id) spi->spi = NULL; spi->bits = 0; } +/* }}} */