//driver_i2cdmx.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2011-2015 * * 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 "roard.h" #ifdef ROAR_HAVE_DRIVER_I2CDMX #include #include // hard disable SPI mode. #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV #undef ROAR_HAVE_H_LINUX_SPI_SPIDEV #endif #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV #include #include #define TRANSFER_DELAY 50 #define TRANSFER_SPEED 100000 #define TRANSFER_BPW 8 #define TRANSFER_CHANS 24 #define TRANSFER_SIZE (TRANSFER_CHANS+6) #define SPI_DEVICE "file://dev/spidev0.0" #endif #define DEFAULT_DEVICE "/dev/i2c-1" #define DEV_TYPE 0x01 /* RI2C_DEV_BRIDGE */ #define DEV_SUBTYPE 0x01 /* RI2C_SUBTYPE_BRIDGE_CONVERTER */ #define DEVSTATUS_READY 0x01 /* RI2C_STATUS0_DEVICE_READY */ #define CAPS0_DMX512 0x10 /* RI2C_CAPS0_BRIDGE_DMX512 */ #define MIN_ADDR 0x20 #define MAX_ADDR 0x70 #define MAX_BUSSES 8 #define ADDR_IFVERSION 0 #define ADDR_DEVSTATUS 1 #define ADDR_COMMAND 2 #define ADDR_DEVERROR 3 #define ADDR_BANK 4 #define ADDR_DATA 5 #define ADDR_VENDOR (ADDR_BANK+2) #define ADDR_TYPE (ADDR_BANK+3) #define ADDR_SUBTYPE (ADDR_BANK+4) #define ADDR_PVENDOR (ADDR_BANK+10) #define ADDR_PTYPE (ADDR_BANK+11) #define ADDR_PSUBTYPE (ADDR_BANK+12) #define ADDR_CAPS0 (ADDR_BANK+13) #define COMMAND_DEVINFO 0x00 #define COMMAND_DMX 0x3f struct driver_i2cdmx { struct roar_vio_calls vio; struct roar_vio_calls spi; int have_spi; uint8_t slave; size_t startaddr; size_t len; #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV char txtransfer[TRANSFER_SIZE]; char rxtransfer[TRANSFER_SIZE]; #endif }; #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV static int __spi_transfer(struct driver_i2cdmx * self, size_t offset_in, size_t offset_out, size_t len) { struct spi_ioc_transfer transfer_buffer = { .tx_buf = (unsigned long) self->txtransfer, .rx_buf = (unsigned long) self->rxtransfer, .len = 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}; self->txtransfer[0] = 1; // command self->txtransfer[1] = len; self->txtransfer[2] = (offset_in & 0xFF00) >> 8; self->txtransfer[3] = offset_in & 0x00FF; self->txtransfer[4] = (offset_in & 0xFF00) >> 8; self->txtransfer[5] = offset_in & 0x00FF; if ( roar_vio_ctl(&(self->spi), ROAR_VIO_CTL_SYSIO_IOCTL, &ctl) == -1 ) return -1; return 0; } #endif static inline int __i2c_set_slave(struct driver_i2cdmx * self) { struct roar_vio_sysio_ioctl ctl = {.cmd = I2C_SLAVE, .argp = (void*)(int)self->slave}; return roar_vio_ctl(&(self->vio), ROAR_VIO_CTL_SYSIO_IOCTL, &ctl); } static inline int16_t __i2c_read(struct driver_i2cdmx * self, size_t off) { union i2c_smbus_data data = {.byte = 0}; struct i2c_smbus_ioctl_data args = {.read_write = I2C_SMBUS_READ, .command = off, .size = I2C_SMBUS_BYTE_DATA, .data = &data}; struct roar_vio_sysio_ioctl ctl = {.cmd = I2C_SMBUS, .argp = &args}; int ret = roar_vio_ctl(&(self->vio), ROAR_VIO_CTL_SYSIO_IOCTL, &ctl); if ( ret == -1 ) return (int16_t)-1; return (int16_t)(uint16_t)(uint8_t)data.byte; } static inline int __i2c_write(struct driver_i2cdmx * self, size_t off, const uint8_t value) { union i2c_smbus_data data = {.byte = value}; struct i2c_smbus_ioctl_data args = {.read_write = I2C_SMBUS_WRITE, .command = off, .size = I2C_SMBUS_BYTE_DATA, .data = &data}; struct roar_vio_sysio_ioctl ctl = {.cmd = I2C_SMBUS, .argp = &args}; return roar_vio_ctl(&(self->vio), ROAR_VIO_CTL_SYSIO_IOCTL, &ctl); } static inline int __i2c_write_block(struct driver_i2cdmx * self, size_t off, const uint8_t * value) { union i2c_smbus_data data; struct i2c_smbus_ioctl_data args = {.read_write = I2C_SMBUS_WRITE, .command = off, .size = I2C_SMBUS_I2C_BLOCK_BROKEN, .data = &data}; struct roar_vio_sysio_ioctl ctl = {.cmd = I2C_SMBUS, .argp = &args}; size_t i; for (i = 0; i < sizeof(data.block); i++) data.block[i] = 0xAF; data.block[0] = I2C_SMBUS_BLOCK_MAX; for (i = 0; i < I2C_SMBUS_BLOCK_MAX; i++) data.block[i+1] = value[i]; return roar_vio_ctl(&(self->vio), ROAR_VIO_CTL_SYSIO_IOCTL, &ctl); } static inline int __i2c_command(struct driver_i2cdmx * self, uint8_t command) { int16_t ret; if ( __i2c_write(self, ADDR_COMMAND, command) == -1 ) return -1; ret = __i2c_read(self, ADDR_DEVERROR); if ( ret == (int16_t)-1 ) return -1; if ( ret == ROAR_ERROR_NONE ) return 0; roar_err_set(ret); return -1; } static inline int __i2c_start_dmx(struct driver_i2cdmx * self) { return __i2c_command(self, COMMAND_DMX); } static int __open_test_device_type(int16_t vendor, int16_t type, int16_t subtype) { if ( vendor == -1 || type == -1 || subtype == -1 ) return -1; if ( vendor != ROAR_VID_ROARAUDIO || type != DEV_TYPE || subtype != DEV_SUBTYPE ) { roar_err_set(ROAR_ERROR_TYPEMM); return -1; } return 0; } static int __open_test_device(struct driver_i2cdmx * self) { int16_t ret; int16_t vendor, type, subtype; #define __check_response(resp,test,error) if ( (resp) == (int16_t)-1 ) return -1; if ( !(test) ) { roar_err_set((error)); return -1; } // test for device interface version ret = __i2c_read(self, ADDR_IFVERSION); __check_response(ret, ret == 0, ROAR_ERROR_NSVERSION); // test for device overall status ret = __i2c_read(self, ADDR_DEVSTATUS); __check_response(ret, ret & DEVSTATUS_READY, ROAR_ERROR_BADSTATE); // Request device infos if ( __i2c_command(self, COMMAND_DEVINFO) == -1 ) return -1; // Check device infos // first check device type, then parent device type: vendor = __i2c_read(self, ADDR_VENDOR); type = __i2c_read(self, ADDR_TYPE); subtype = __i2c_read(self, ADDR_SUBTYPE); if ( __open_test_device_type(vendor, type, subtype) == -1 ) { vendor = __i2c_read(self, ADDR_PVENDOR); type = __i2c_read(self, ADDR_PTYPE); subtype = __i2c_read(self, ADDR_PSUBTYPE); if ( __open_test_device_type(vendor, type, subtype) == -1 ) return -1; } // check for DMX512 support: ret = __i2c_read(self, ADDR_CAPS0); __check_response(ret, ret & CAPS0_DMX512, ROAR_ERROR_TYPEMM); return 0; } static int __open_scan_devices(struct driver_i2cdmx * self) { uint8_t i; for (i = MIN_ADDR; i < MAX_ADDR; i++) { self->slave = i; if ( __i2c_set_slave(self) == -1 ) continue; if ( __open_test_device(self) == -1 ) continue; return 0; } self->slave = 0; roar_err_set(ROAR_ERROR_NOENT); return -1; } static int __open_device_and_slave(struct driver_i2cdmx * self, int autoconf, const char * device) { char filename[40]; char * p; int need_autoconf = 0; int ret; int err; // test if the device exist. if ( roar_vio_open_dstr_simple(&(self->vio), device, ROAR_VIOF_READWRITE) == 0 ) { need_autoconf = 1; } else { // the device doesn't exist. We guss it is in form $device/$slaveid. strncpy(filename, device, sizeof(filename)); p = strrchr(filename, '/'); if ( p == NULL ) { // it doesn't seem to be in the given form so it seems it was just a device name of an missingd evice. roar_err_set(ROAR_ERROR_NOENT); return -1; } *p = 0; p++; // now we have the device name in filename, and the slave ID in p. self->slave = atoi(p); // little test to protect the user from doing dumb things. if ( self->slave == 0 ) { roar_err_set(ROAR_ERROR_INVAL); return -1; } if ( roar_vio_open_dstr_simple(&(self->vio), filename, ROAR_VIOF_READWRITE) == -1 ) return -1; } // ok, the bus is now open. Let's open the device: if ( need_autoconf ) { ret = __open_scan_devices(self); } else { ret = __i2c_set_slave(self); if ( ret == 0 ) ret = __open_test_device(self); } if ( ret == -1 ) { err = roar_error; roar_vio_close(&(self->vio)); roar_err_set(err); } return ret; } static int __open_scan_busses(struct driver_i2cdmx * self) { char filename[20]; int i; int ret; for (i = 0; i < MAX_BUSSES; i++) { snprintf(filename, sizeof(filename), "/dev/i2c-%i", i); ret = __open_device_and_slave(self, 1, filename); if ( ret == 0 ) return 0; } roar_err_set(ROAR_ERROR_NOENT); return -1; } static int __i2c_write_channel(struct driver_i2cdmx * self, size_t channel, uint8_t value) { size_t bank, offset; bank = channel/32; offset = bank*32; if ( __i2c_write(self, ADDR_BANK, bank) == -1 ) return -1; return __i2c_write(self, ADDR_DATA+channel-offset, value); } static int __i2c_write_channel_block(struct driver_i2cdmx * self, size_t channel, const uint8_t * value) { size_t bank, offset; bank = channel/32; offset = bank*32; if ( __i2c_write(self, ADDR_BANK, bank) == -1 ) return -1; return __i2c_write_block(self, ADDR_DATA+channel-offset, value); } static ssize_t __vio_read (struct roar_vio_calls * vio, void *buf, size_t count) { struct driver_i2cdmx * self; if ( vio == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } self = vio->inst; if ( count != 512 ) { roar_err_set(ROAR_ERROR_INVAL); return -1; } if ( __i2c_start_dmx(self) == -1 ) return -1; memset(buf, 0, count); // optimize this... #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV memcpy(buf, self->rxtransfer+6, sizeof(self->rxtransfer)-6); #endif return 0; } static ssize_t __vio_write (struct roar_vio_calls * vio, void *buf, size_t count) { struct driver_i2cdmx * self; size_t i; // size_t endaddr; size_t todo; if ( vio == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } self = vio->inst; if ( count != 512 ) { roar_err_set(ROAR_ERROR_INVAL); return -1; } if ( __i2c_start_dmx(self) == -1 ) return -1; /* for (i = self->startaddr, endaddr = self->startaddr + self->len; i < endaddr; i++) if ( __i2c_write_channel(self, i, ((uint8_t*)buf)[i]) == -1 ) return -1; */ i = self->startaddr; todo = self->len; while (todo) { if ( todo >= I2C_SMBUS_BLOCK_MAX ) { if ( __i2c_write_channel_block(self, i, &(((const uint8_t*)buf)[i])) == -1 ) return -1; i += I2C_SMBUS_BLOCK_MAX; todo -= I2C_SMBUS_BLOCK_MAX; } else { if ( __i2c_write_channel(self, i, ((uint8_t*)buf)[i]) == -1 ) return -1; i++; todo--; } } #ifdef ROAR_HAVE_H_LINUX_SPI_SPIDEV memcpy(self->txtransfer+6, buf, sizeof(self->txtransfer)-6); __spi_transfer(self, 0, 0, TRANSFER_CHANS); #endif return count; } static int __vio_ctl (struct roar_vio_calls * vio, roar_vio_ctl_t cmd, void * data) { struct driver_i2cdmx * self; if ( vio == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } self = vio->inst; switch (cmd) { case ROAR_VIO_CTL_SET_SSTREAM: if ( ROAR_STREAM(data)->dir != ROAR_DIR_LIGHT_OUT && ROAR_STREAM(data)->dir != ROAR_DIR_LIGHT_IN ) { ROAR_STREAM(data)->dir = ROAR_DIR_LIGHT_OUT; } ROAR_STREAM_SERVER(data)->codec_orgi = ROAR_CODEC_DMX512; break; default: roar_err_set(ROAR_ERROR_BADRQC); return -1; } return 0; } static int __vio_close (struct roar_vio_calls * vio) { struct driver_i2cdmx * self = vio->inst; roar_vio_close(&(self->vio)); if ( self->have_spi ) roar_vio_close(&(self->spi)); roar_mm_free(self); return 0; } int driver_i2cdmx_open_vio (struct roar_vio_calls * inst, char * device, struct roar_audio_info * info, int fh, struct roar_stream_server * sstream) { struct driver_i2cdmx * self; int autoconf = 1; int ret; if ( fh != -1 ) { roar_err_set(ROAR_ERROR_NOSYS); return -1; } self = roar_mm_malloc(sizeof(struct driver_i2cdmx)); if ( self == NULL ) { return -1; } memset(self, 0, sizeof(*self)); self->have_spi = 0; self->startaddr = 0; self->len = info->channels; info->bits = 8; info->codec = ROAR_CODEC_DMX512; if ( device == NULL && !autoconf ) { device = DEFAULT_DEVICE; } if ( device == NULL ) { ret = __open_scan_busses(self); } else { ret = __open_device_and_slave(self, autoconf, device); } if ( ret == -1 ) { roar_mm_free_noerror(self); return -1; } #if defined(ROAR_HAVE_H_LINUX_SPI_SPIDEV) && defined(SPI_DEVICE) if ( roar_vio_open_dstr_simple(&(self->spi), SPI_DEVICE, ROAR_VIOF_READWRITE) == 0 ) self->have_spi = 1; #endif roar_vio_clear_calls(inst); inst->inst = self; inst->read = __vio_read; inst->write = __vio_write; inst->close = __vio_close; inst->ctl = __vio_ctl; return 0; } #endif