//driver_i2cdmx.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2011-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 "roard.h" #ifdef ROAR_HAVE_DRIVER_I2CDMX #include #include #define DEFAULT_DEVICE "/dev/i2c-1" #define DEV_TYPE 0x00 /* FIXME */ #define DEV_SUBTYPE 0x00 /* FIXME */ #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 COMMAND_DEVINFO 0x00 #define COMMAND_DMX 0x3f struct driver_i2cdmx { struct roar_vio_calls vio; uint8_t slave; size_t startaddr; size_t len; }; 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_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_IFVERSION); __check_response(ret, ret & 0x01, ROAR_ERROR_NSVERSION); // 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) == 0 ) return 0; vendor = __i2c_read(self, ADDR_PVENDOR); type = __i2c_read(self, ADDR_PTYPE); subtype = __i2c_read(self, ADDR_PSUBTYPE); return __open_test_device_type(vendor, type, subtype); } 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. roar_mm_strlcat(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 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 ( __i2c_start_dmx(self) == -1 ) return -1; roar_err_set(ROAR_ERROR_NOSYS); return -1; } 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; 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; 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)); 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->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; } 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