summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian König <chrissi@zweiundvierzig.(none)>2010-05-30 20:14:42 +0200
committerChristian König <chrissi@zweiundvierzig.(none)>2010-05-30 20:14:42 +0200
commit88c7f9c427279ef8ab395e47f17b14e31e3f17b9 (patch)
tree107cf0c8750f8c206924df0e6ef5a6a959c5386b
parentcd0e30d31f8adf852a3ca579f88cc3467ef289bc (diff)
Initial alsa plugin
-rw-r--r--.gitignore1
-rw-r--r--alsa/Makefile19
-rw-r--r--alsa/libasound_module_ctl_cec.c276
-rw-r--r--cmd/cec.c24
-rw-r--r--lib/cec.h11
-rw-r--r--lib/device.c12
-rw-r--r--lib/msp430.c79
7 files changed, 376 insertions, 46 deletions
diff --git a/.gitignore b/.gitignore
index 9a40d92..c5e5bf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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);
diff --git a/cmd/cec.c b/cmd/cec.c
index 215a53a..4ad8b1b 100644
--- a/cmd/cec.c
+++ b/cmd/cec.c
@@ -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);
}
}
diff --git a/lib/cec.h b/lib/cec.h
index acefd07..41b8a32 100644
--- a/lib/cec.h
+++ b/lib/cec.h
@@ -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);