//piface.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2013 * * This file is part of roard a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * RoarAudio is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV #include #include #define NUM_PORTS (8+8+1) #define TRANSFER_LEN 3 #define TRANSFER_DELAY 5 #define TRANSFER_SPEED 1000000 #define TRANSFER_BPW 8 #define SPI_WRITE_CMD 0x40 #define SPI_READ_CMD 0x41 #define IODIRA 0x00 // I/O direction A #define IODIRB 0x01 // I/O direction B #define IOCON 0x0A // I/O config #define GPIOA 0x12 // port A #define GPIOB 0x13 // port B #define GPPUA 0x0C // port A pullups #define GPPUB 0x0D // port B pullups #define OUTPUT_PORT GPIOA #define INPUT_PORT GPIOB struct state { int inited; int bus; int device; struct roar_vio_calls vio_store; struct roar_vio_calls * vio; unsigned char buffer_output; unsigned char buffer_input; struct roar_service_gpio_port ports[NUM_PORTS]; }; static struct state * state = NULL; static struct state state_init = { .inited = 0, .bus = 0, .device = 0, .vio = NULL, .buffer_output = 0x00, .buffer_input = 0x00, .ports = { // inputs: {.id = 0, .name = "gpin0", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 1, .name = "gpin1", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 2, .name = "gpin2", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 3, .name = "gpin3", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 4, .name = "gpin4", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 5, .name = "gpin5", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 6, .name = "gpin6", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 7, .name = "gpin7", .mode = ROAR_SERVICE_GPIO_FINPUT|ROAR_SERVICE_GPIO_FPULLUP, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, // outputs: {.id = 8, .name = "gpout0", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 9, .name = "gpout1", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 10, .name = "gpout2", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 11, .name = "gpout3", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 12, .name = "gpout4", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 13, .name = "gpout5", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 14, .name = "gpout6", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, {.id = 15, .name = "gpout7", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TBOOL, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 1, .frange_min = 0, .frange_max = 1}, // special: {.id = 16, .name = "bar0", .mode = ROAR_SERVICE_GPIO_FOUTPUT, .unit = NULL, .type = ROAR_SERVICE_GPIO_TINT, .state = ROAR_SERVICE_GPIO_SUNINITED, .irange_min = 0, .irange_max = 8, .frange_min = 0, .frange_max = 8} } }; static int piface__transfer(char cmd, char port, char value) { unsigned char txbuffer[3] = {cmd, port, value}; unsigned char rxbuffer[3]; struct spi_ioc_transfer transfer_buffer = { .tx_buf = (unsigned long) txbuffer, .rx_buf = (unsigned long) rxbuffer, .len = TRANSFER_LEN, .delay_usecs = TRANSFER_DELAY, .speed_hz = TRANSFER_SPEED, .bits_per_word = TRANSFER_BPW, }; struct roar_vio_sysio_ioctl ctl = {.cmd = SPI_IOC_MESSAGE(1), .argp = &transfer_buffer}; if ( roar_vio_ctl(state->vio, ROAR_VIO_CTL_SYSIO_IOCTL, &ctl) == -1 ) return -1; return (unsigned int)rxbuffer[2]; } static int piface__init(void) { char path[80]; if ( state->inited ) { roar_err_set(ROAR_ERROR_ALREADY); return -1; } snprintf(path, sizeof(path), "file://dev/spidev%i.%i", state->bus, state->device); state->vio = &(state->vio_store); if ( roar_vio_open_dstr_simple(state->vio, path, ROAR_VIOF_READWRITE) == -1 ) return -1; piface__transfer(SPI_WRITE_CMD, IOCON, 8); // enable hardware addressing piface__transfer(SPI_WRITE_CMD, GPIOA, 0x00); // turn on port A piface__transfer(SPI_WRITE_CMD, IODIRA, 0); // set port A as an output piface__transfer(SPI_WRITE_CMD, IODIRB, 0xFF); // set port B as an input piface__transfer(SPI_WRITE_CMD, GPPUB, 0xFF); // turn on port B pullups piface__transfer(SPI_WRITE_CMD, OUTPUT_PORT, 0x00); // turn of all outputs. state->inited = 1; return 0; } static int piface__free(void) { if ( !state->inited ) { roar_err_set(ROAR_ERROR_BADSTATE); return -1; } roar_vio_close(state->vio); state->inited = 0; return 0; } static void piface__write_output(unsigned char val) { piface__transfer(SPI_WRITE_CMD, OUTPUT_PORT, state->buffer_output = val); } static unsigned char piface__read_output(void) { return state->buffer_output = piface__transfer(SPI_READ_CMD, OUTPUT_PORT, 0xFF); } static unsigned char piface__read_input(void) { return state->buffer_input = piface__transfer(SPI_READ_CMD, INPUT_PORT, 0xFF) ^ 0xFF; } static inline int __portval_to_bool(const struct roar_service_gpio_port * port, unsigned int val) { int bit = 1 << (port->id & 0x07); return val & bit ? 1 : 0; } static inline unsigned char __render_bar(int bits) { unsigned char ret = 0; int i; // this is a generic version. As long as this is not called too often it should work fine. for (i = 0; i < bits; i++) { ret <<= 1; ret |= 1; } return ret; } // get list of gpio IDs. // buffer is passed as ids, buffer size (in elements) is passed as len. // returns the number of elements stored in ids or -1 on error. static ssize_t __list(int * ids, size_t len) { size_t i; if ( ids == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } for (i = 0; i < len && i < NUM_PORTS; i++) ids[i] = i; return i; } // get the number of gpios. See also comments above on what. static ssize_t __num(enum roar_service_num what) { (void)what; return NUM_PORTS; // 8 inputs, 8 outputs and a a virtual bar } // get a gpio by ID. The object returned is a copy and must not be motified or freed. static int __get(int id, struct roar_service_gpio_port * port) { if ( id < 0 || id >= NUM_PORTS ) { roar_err_set(ROAR_ERROR_RANGE); return -1; } if ( port == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } *port = state->ports[id]; return 0; } // Sets up ports. // the controler should set mode and state of port to given target mode and state. // If id is given as -1 this is about the controler. mode MUST NOT contain any // flags beside ROAR_SERVICE_GPIO_FCACHED. If ROAR_SERVICE_GPIO_FCACHED // is set it is applied all ports. static int __setup(int id, uint_least32_t mode, enum roar_service_gpio_state tstate) { size_t i; if ( id < -1 || id >= NUM_PORTS ) { roar_err_set(ROAR_ERROR_RANGE); return -1; } if ( id == -1 ) { if ( mode != 0 && mode != ROAR_SERVICE_GPIO_FCACHED ) { roar_err_set(ROAR_ERROR_INVAL); return -1; } switch (tstate) { case ROAR_SERVICE_GPIO_SREADY: if ( state->inited ) return 0; if ( piface__init() == -1 ) return -1; for (i = 0; i < NUM_PORTS; i++) { state->ports[i].mode |= ROAR_SERVICE_GPIO_FCACHED; state->ports[i].mode -= ROAR_SERVICE_GPIO_FCACHED; state->ports[i].mode |= mode; } return 0; case ROAR_SERVICE_GPIO_SUNINITED: if ( !state->inited ) return 0; return piface__free(); default: roar_err_set(ROAR_ERROR_INVAL); return -1; break; } } roar_err_set(ROAR_ERROR_NOSYS); return -1; } // get value of port (input or output) as int or float. static int __get_int(int id) { const struct roar_service_gpio_port * port; unsigned char val; if ( !state->inited ) { roar_err_set(ROAR_ERROR_BADSTATE); return -1; } if ( id < 0 || id >= NUM_PORTS ) { roar_err_set(ROAR_ERROR_RANGE); return -1; } port = &(state->ports[id]); if ( port->type != ROAR_SERVICE_GPIO_TBOOL ) { roar_err_set(ROAR_ERROR_NOTSUP); return -1; } if ( port->mode & ROAR_SERVICE_GPIO_FINPUT ) { val = piface__read_input(); } else { val = piface__read_output(); } return __portval_to_bool(port, val); } static double __get_float(int id) { return __get_int(id); } // set value of output (as int or float). static int __set_int(int id, int val) { const struct roar_service_gpio_port * port; unsigned char buffer; int bit; if ( !state->inited ) { roar_err_set(ROAR_ERROR_BADSTATE); return -1; } if ( id < 0 || id >= NUM_PORTS ) { roar_err_set(ROAR_ERROR_RANGE); return -1; } port = &(state->ports[id]); if ( !(port->mode & ROAR_SERVICE_GPIO_FOUTPUT) ) { roar_err_set(ROAR_ERROR_NOTSUP); return -1; } if ( val < port->irange_min || val > port->irange_max ) { roar_err_set(ROAR_ERROR_RANGE); return -1; } switch (port->type) { case ROAR_SERVICE_GPIO_TBOOL: bit = 1 << (id & 0x07); buffer = state->buffer_output; buffer |= bit; if ( !val ) buffer -= bit; piface__write_output(buffer); return 0; break; case ROAR_SERVICE_GPIO_TINT: piface__write_output(__render_bar(val)); return 0; break; default: roar_err_set(ROAR_ERROR_NOTSUP); return -1; break; } } static int __set_float(int id, double val) { return __set_int(id, val); } static struct roar_service_gpio api = { .list = __list, .num = __num, .get = __get, .setup = __setup, .get_int = __get_int, .get_float = __get_float, .set_int = __set_int, .set_float = __set_float, .convert_int = NULL, .convert_float = NULL }; ROAR_DL_PLUGIN_REG_SERVICES_GET_API(get_api, api) static const struct roar_dl_service service[1] = { { .appname = NULL, .appabi = NULL, .servicename = ROAR_SERVICE_GPIO_NAME, .serviceabi = ROAR_SERVICE_GPIO_ABI, .description = "Interface to PiFace", .flags = ROAR_DL_SERVICE_FLAGS_NONE, .userdata = NULL, .get_api = get_api } }; ROAR_DL_PLUGIN_REG_SERVICES(service); ROAR_DL_PLUGIN_START(piface) { ROAR_DL_PLUGIN_META_PRODUCT_NIV("piface", ROAR_VID_ROARAUDIO, ROAR_VNAME_ROARAUDIO); ROAR_DL_PLUGIN_META_VERSION(ROAR_VERSION_STRING); ROAR_DL_PLUGIN_META_LICENSE_TAG(GPLv3_0); ROAR_DL_PLUGIN_META_CONTACT_FLNE("Philipp", "Schafft", "ph3-der-loewe", "lion@lion.leolix.org"); ROAR_DL_PLUGIN_META_DESC("This plugin provides a simple interface to the PiFace board."); ROAR_DL_PLUGIN_REG_FNFUNC(ROAR_DL_FN_SERVICE); ROAR_DL_PLUGIN_REG_GLOBAL_DATA(state, state_init); } ROAR_DL_PLUGIN_END #endif //ll