diff options
author | Christian König <chrissi@zweiundvierzig.(none)> | 2010-05-30 20:14:42 +0200 |
---|---|---|
committer | Christian König <chrissi@zweiundvierzig.(none)> | 2010-05-30 20:14:42 +0200 |
commit | 88c7f9c427279ef8ab395e47f17b14e31e3f17b9 (patch) | |
tree | 107cf0c8750f8c206924df0e6ef5a6a959c5386b | |
parent | cd0e30d31f8adf852a3ca579f88cc3467ef289bc (diff) |
Initial alsa plugin
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | alsa/Makefile | 19 | ||||
-rw-r--r-- | alsa/libasound_module_ctl_cec.c | 276 | ||||
-rw-r--r-- | cmd/cec.c | 24 | ||||
-rw-r--r-- | lib/cec.h | 11 | ||||
-rw-r--r-- | lib/device.c | 12 | ||||
-rw-r--r-- | lib/msp430.c | 79 |
7 files changed, 376 insertions, 46 deletions
@@ -4,3 +4,4 @@ cmd/cec msp430/cec.a43 msp430/cec.elf msp430/cec.lst +alsa/libasound_module_ctl_cec.so diff --git a/alsa/Makefile b/alsa/Makefile new file mode 100644 index 0000000..21e03b1 --- /dev/null +++ b/alsa/Makefile @@ -0,0 +1,19 @@ +NAME = libasound_module_ctl_cec.so +OBJECTS = libasound_module_ctl_cec.o +HEADERS = ../lib/cec.h + +CFLAGS = -fPIC -DPIC -Wall -g -I ../lib -L ../lib +LDFLAGS = -shared -lcec -lasound -Wl,-soname -Wl,${NAME} + +.PHONY: all install clean + +all: ${NAME} + +${NAME}: ${OBJECTS} + $(CC) ${CFLAGS} ${OBJECTS} ${LDFLAGS} -o ${NAME} + +install: ${NAME} + sudo install ${NAME} /usr/lib/alsa-lib/ + +clean: + rm -f ${NAME} ${OBJECTS} diff --git a/alsa/libasound_module_ctl_cec.c b/alsa/libasound_module_ctl_cec.c new file mode 100644 index 0000000..33e2c1b --- /dev/null +++ b/alsa/libasound_module_ctl_cec.c @@ -0,0 +1,276 @@ +/* + * Copyright + * + * Copyright (C) 2009-2010 Christian König (deathsimple@vodafone.de) + * + * License + * + * This program is free software; you can redistribute and/or modify + * program under the terms of GNU General Public license either version 3 + * of the License, or (at your option) any later version. + * + */ + +#include <alsa/asoundlib.h> +#include <alsa/control_external.h> + +#include <cec.h> + +#define TIMEOUT 16 + +#define VOL_NAME "Volume" +#define VOL_INDEX 0 +#define MUTE_NAME "Mute" +#define MUTE_INDEX 1 + +typedef struct snd_ctl_cec { + snd_ctl_ext_t ext; + + uint8_t addr; + struct CEC_Device device; + + int pending_update; + + uint8_t volume:7; + uint8_t mute:1; + +} snd_ctl_cec_t; + +static void handle_audio_status(struct CEC_Device* device, struct CEC_Packet* packet) +{ + snd_ctl_cec_t *ctl = device->private_data; + assert(ctl); + + ctl->volume = packet->audio_volume_status; + ctl->mute = packet->audio_mute_status; + + ctl->pending_update = 0; +} + +static void update_audio_status(snd_ctl_cec_t* ctl) +{ + int timeout=0; + if(CEC_TX_Give_Audio_Status(&ctl->device, ctl->addr) == CEC_TX_Success) { + ctl->pending_update = 1; + while(ctl->pending_update && (timeout++ < TIMEOUT)) { + CEC_Receive(&ctl->device); + } + } +} + +static int cec_elem_count(snd_ctl_ext_t * ext) +{ + return 2; +} + +static int cec_elem_list(snd_ctl_ext_t * ext, unsigned int offset, snd_ctl_elem_id_t * id) +{ + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); + switch(offset) { + case VOL_INDEX: + snd_ctl_elem_id_set_name(id, VOL_NAME); + break; + + case MUTE_INDEX: + snd_ctl_elem_id_set_name(id, MUTE_NAME); + break; + } + return 0; +} + +static snd_ctl_ext_key_t cec_find_elem(snd_ctl_ext_t * ext, const snd_ctl_elem_id_t * id) +{ + const char *name; + unsigned int numid; + + numid = snd_ctl_elem_id_get_numid(id); + if (numid > 0 && numid <= 2) + return numid - 1; + + name = snd_ctl_elem_id_get_name(id); + + if (strcmp(name, VOL_NAME) == 0) + return 0; + if (strcmp(name, MUTE_NAME) == 0) + return 1; + + return SND_CTL_EXT_KEY_NOT_FOUND; +} + +static int cec_get_attribute(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, int *type, unsigned int *acc, unsigned int *count) +{ + switch(key) { + case VOL_INDEX: + *type = SND_CTL_ELEM_TYPE_INTEGER; + break; + + case MUTE_INDEX: + *type = SND_CTL_ELEM_TYPE_BOOLEAN; + break; + } + + *acc = SND_CTL_EXT_ACCESS_READWRITE; + *count = 1; + return 0; +} + +static int cec_get_integer_info(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *imin, long *imax, long *istep) +{ + *istep = 1; + *imin = 0; + *imax = 100; + + return 0; +} + +static int cec_read_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *value) +{ + snd_ctl_cec_t *ctl = ext->private_data; + assert(ctl); + + update_audio_status(ctl); + + switch(key) { + case VOL_INDEX: + *value = ctl->volume; + break; + + case MUTE_INDEX: + *value = ctl->mute; + break; + } + return 0; +} + +static int cec_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *value) +{ + snd_ctl_cec_t *ctl = ext->private_data; + assert(ctl); + + update_audio_status(ctl); + + switch(key) { + case VOL_INDEX: + if(*value < ctl->volume) { + while(*value < ctl->volume) { + CEC_TX_User_Control_Pressed(&ctl->device, ctl->addr, CEC_UI_Volume_Down); + CEC_TX_User_Control_Released(&ctl->device, ctl->addr); + update_audio_status(ctl); + } + + } else if(*value > ctl->volume) { + while(*value > ctl->volume) { + CEC_TX_User_Control_Pressed(&ctl->device, ctl->addr, CEC_UI_Volume_Up); + CEC_TX_User_Control_Released(&ctl->device, ctl->addr); + update_audio_status(ctl); + } + } + + break; + + case MUTE_INDEX: + if(*value == ctl->mute) return 0; + + CEC_TX_User_Control_Pressed(&ctl->device, ctl->addr, CEC_UI_Mute); + update_audio_status(ctl); + CEC_TX_User_Control_Released(&ctl->device, ctl->addr); + break; + } + + return 0; +} + +static void cec_close(snd_ctl_ext_t * ext) +{ + snd_ctl_cec_t *ctl = ext->private_data; + assert(ctl); + + free(ctl); +} + +static const snd_ctl_ext_callback_t cec_ext_callback = { + .elem_count = cec_elem_count, + .elem_list = cec_elem_list, + .find_elem = cec_find_elem, + .get_attribute = cec_get_attribute, + .get_integer_info = cec_get_integer_info, + .read_integer = cec_read_integer, + .write_integer = cec_write_integer, + .close = cec_close, +}; + +SND_CTL_PLUGIN_DEFINE_FUNC(cec) +{ + snd_config_iterator_t i, next; + const char* hardware; + long addr; + snd_ctl_cec_t *ctl; + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + + if (snd_config_get_id(n, &id) < 0) continue; + + if (strcmp(id, "comment") == 0 || + strcmp(id, "type") == 0 || + strcmp(id, "hint") == 0) + continue; + + if (strcmp(id, "msp430") == 0) { + if (snd_config_get_string(n, &hardware) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + + if (strcmp(id, "addr") == 0) { + if (snd_config_get_integer(n, &addr) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + + SNDERR("Unknown field %s", id); + return -EINVAL; + } + + ctl = calloc(1, sizeof(*ctl)); + if (!ctl) return -ENOMEM; + + ctl->addr = addr; + + CEC_Init_Device(&ctl->device); + ctl->device.hardware = MSP430_Open_Hardware(hardware); + if(!ctl->device.hardware) { + free(ctl); + return -EIO; + } + ctl->device.func_handler[CEC_Report_Audio_Status] = handle_audio_status; + ctl->device.private_data = ctl; + + CEC_Alloc_Addr(&ctl->device, CEC_Playback_Device); + + ctl->ext.version = SND_CTL_EXT_VERSION; + ctl->ext.card_idx = 0; + strncpy(ctl->ext.id, "cec", sizeof(ctl->ext.id) - 1); + strncpy(ctl->ext.driver, "CEC plugin", sizeof(ctl->ext.driver) - 1); + strncpy(ctl->ext.name, "CEC", sizeof(ctl->ext.name) - 1); + strncpy(ctl->ext.longname, "CEC", sizeof(ctl->ext.longname) - 1); + strncpy(ctl->ext.mixername, "CEC", sizeof(ctl->ext.mixername) - 1); + + ctl->ext.poll_fd = -1; + ctl->ext.callback = &cec_ext_callback; + ctl->ext.private_data = ctl; + + int err = snd_ctl_ext_create(&ctl->ext, name, mode); + + if(err) free(ctl); + else *handlep = ctl->ext.handle; + + return err; +} + +SND_CTL_PLUGIN_SYMBOL(cec); @@ -43,15 +43,27 @@ void debug(enum CEC_Debug reason, struct CEC_Device* device, struct CEC_Packet* { switch(reason) { case CEC_RX_Packet: - fprintf(stderr, "received "); + fprintf(stderr, "received "); break; - case CEC_TX_Sucess: - fprintf(stderr, "succeeded "); + case CEC_TX_Success: + fprintf(stderr, "succeeded "); break; - case CEC_TX_Failure: - fprintf(stderr, "failed "); + case CEC_TX_ERROR: + fprintf(stderr, "io error "); + break; + + case CEC_TX_TIMEOUT: + fprintf(stderr, "timeout "); + break; + + case CEC_TX_NACK: + fprintf(stderr, "no ack "); + break; + + case CEC_TX_ARL: + fprintf(stderr, "arbitation "); break; } CEC_Dump_Packet(stderr, packet); @@ -222,7 +234,7 @@ int main(int argc, const char* argv[]) } else if (!strncmp("-scan", argv[i], 6)) { for(addr=0x0; addr<=0xF; addr++) { - if(CEC_TX_Ping(&device, addr) && addr != 0xF) { + if(CEC_TX_Ping(&device, addr) == 0 && addr != 0xF) { CEC_TX_Give_Physical_Address(&device, addr); } } @@ -16,6 +16,7 @@ #include <stdint.h> #include <stdio.h> +#include <errno.h> #pragma pack(push,1) @@ -530,9 +531,13 @@ extern void MSP430_Close_Hardware(struct CEC_Hardware* hardware); enum CEC_Debug { - CEC_RX_Packet, - CEC_TX_Sucess, - CEC_TX_Failure + CEC_TX_Success = 0, + CEC_RX_Packet = 1, + + CEC_TX_ERROR = -EIO, + CEC_TX_TIMEOUT = -ETIMEDOUT, + CEC_TX_NACK = -EBUSY, + CEC_TX_ARL = -EAGAIN }; struct CEC_Device diff --git a/lib/device.c b/lib/device.c index bf421f5..5d11c77 100644 --- a/lib/device.c +++ b/lib/device.c @@ -68,15 +68,13 @@ int CEC_Transmit(struct CEC_Device* device, struct CEC_Packet* packet) if(!device || !device->hardware) return 0; if(!device->hardware->func_transmit) return 0; - int retransmit, result = 0; + int retransmit, result; for(retransmit=0; retransmit<5; retransmit++) { - if(device->hardware->func_transmit(device, packet)) { - result = 1; - break; - } + result = device->hardware->func_transmit(device, packet); + if(result != CEC_TX_NACK && result != CEC_TX_ARL) break; } if(device->func_debug) { - device->func_debug(result ? CEC_TX_Sucess : CEC_TX_Failure, device, packet); + device->func_debug(result, device, packet); } return result; } @@ -105,7 +103,7 @@ int CEC_Alloc_Addr(struct CEC_Device* device, enum CEC_Device_Type device_type) for(addr=0; addr<0xF; addr++) { if(!(allowed & (1 << addr))) continue; device->logical_address = addr; - if(!CEC_TX_Ping(device, addr)) break; + if(CEC_TX_Ping(device, addr) == CEC_TX_NACK) break; } device->logical_address = addr; diff --git a/lib/msp430.c b/lib/msp430.c index b74d3ec..58f31f5 100644 --- a/lib/msp430.c +++ b/lib/msp430.c @@ -15,6 +15,7 @@ #include <string.h> #include <stdlib.h> #include <fcntl.h> +#include <errno.h> #include <linux/i2c-dev.h> #ifndef I2C_M_RD @@ -28,9 +29,8 @@ #define I2C_ADDR 0x60 #define RX_DELAY 10000 -#define TX_BASE_DELAY 4500 #define TX_BIT_DELAY 2400 -#define TX_RETRY 10 +#define TX_RETRY 128 #define EDID_BLOCK_SEL 0x30 #define EDID_BYTE_SEL 0x50 @@ -40,6 +40,8 @@ struct MSP430_Hardware struct CEC_Hardware cec; int i2c; + uint8_t* tx_result; + int index; uint8_t buffer[BUFFER_SIZE]; }; @@ -62,65 +64,80 @@ static struct CEC_Packet* Receive(struct CEC_Device* device) { struct MSP430_Hardware* hw = (struct MSP430_Hardware*)device->hardware; + /* try to refill buffer */ if(Buffer_Empty(hw)) { usleep(RX_DELAY); if(Fill_Buffer(hw) < 0) return NULL; } - while(!Buffer_Empty(hw) && hw->buffer[hw->index] > CEC_Bytes_Max) - hw->index++; - - if(!Buffer_Empty(hw)) { - hw->buffer[hw->index] >>= 3; + /* still empty? than thats it */ + if(Buffer_Empty(hw)) return NULL; - void* packet = &(hw->buffer[hw->index]); - hw->index += hw->buffer[hw->index]; + /* special result code received? */ + if(hw->buffer[hw->index] > CEC_Bytes_Max) { + if(hw->tx_result) { + *hw->tx_result = hw->buffer[hw->index]; + hw->tx_result = NULL; + } hw->index++; - if(hw->index > BUFFER_SIZE) return NULL; - return (struct CEC_Packet*)packet; + return NULL; } - return NULL; + + /* normal packet received */ + + /* change the packet length from bits to bytes */ + hw->buffer[hw->index] >>= 3; + + void* packet = &(hw->buffer[hw->index]); + hw->index += hw->buffer[hw->index]; + hw->index++; + if(hw->index > BUFFER_SIZE) return NULL; + return (struct CEC_Packet*)packet; } static int Transmit(struct CEC_Device* device, struct CEC_Packet* packet) { struct MSP430_Hardware* hw = (struct MSP430_Hardware*)device->hardware; + int retry; do { CEC_Receive(device); - CEC_Receive(device); } while(!Buffer_Empty(hw)); if(write(hw->i2c, ((void*)packet)+1, packet->length) != packet->length) return 0; + usleep(packet->length * TX_BIT_DELAY * 10); - int retry; for(retry=0; retry<TX_RETRY; retry++) { - usleep(TX_BASE_DELAY + packet->length * TX_BIT_DELAY * 10); + if(hw->tx_result) { + CEC_Receive(device); + } else { + uint8_t result = 0; - int result = Fill_Buffer(hw); - if(result < 0) continue; + hw->tx_result = &result; + CEC_Receive(device); + hw->tx_result = NULL; - result = -1; + switch(result) { + case CEC_Transmitted: + return 0; - int i; - for(i=0;i<BUFFER_SIZE;i++) { - if(hw->buffer[i] == CEC_Transmitted) - result = 1; - else if(hw->buffer[i] > CEC_Bytes_Max) - result = 0; - else if(hw->buffer[i] == 0) - break; + case CEC_Timeout: + case CEC_OOR: + return -EIO; - i += hw->buffer[i]; + case CEC_NACK: + return -EBUSY; + + case CEC_ARL: + return -EAGAIN; + } } - if(result != -1) return result; - usleep(RX_DELAY); } - return 0; + return -ETIMEDOUT; } static uint8_t Read_DDC(struct MSP430_Hardware* hw, uint16_t addr) @@ -220,6 +237,8 @@ struct CEC_Hardware* MSP430_Open_Hardware(const char* device) if(ioctl(result->i2c, I2C_SLAVE, I2C_ADDR) < 0) goto error2; + result->tx_result = NULL; + result->index = BUFFER_SIZE; return &(result->cec); |