diff options
-rw-r--r-- | drivers/dahdi/firmware/Makefile | 15 | ||||
-rw-r--r-- | drivers/dahdi/voicebus/Kbuild | 2 | ||||
-rw-r--r-- | drivers/dahdi/voicebus/vpmoct.c | 649 | ||||
-rw-r--r-- | drivers/dahdi/voicebus/vpmoct.h | 110 | ||||
-rw-r--r-- | drivers/dahdi/wctdm24xxp/base.c | 309 | ||||
-rw-r--r-- | drivers/dahdi/wctdm24xxp/wctdm24xxp.h | 11 | ||||
-rw-r--r-- | drivers/dahdi/wcte12xp/base.c | 276 | ||||
-rw-r--r-- | drivers/dahdi/wcte12xp/wcte12xp.h | 2 |
8 files changed, 1292 insertions, 82 deletions
diff --git a/drivers/dahdi/firmware/Makefile b/drivers/dahdi/firmware/Makefile index e7ec5a8..6d5d053 100644 --- a/drivers/dahdi/firmware/Makefile +++ b/drivers/dahdi/firmware/Makefile @@ -27,10 +27,11 @@ OCT6114_128_VERSION:=1.05.01 TC400M_VERSION:=MR6.12 VPMADT032_VERSION:=1.25.0 HX8_VERSION:=2.06 +VPMOCT032_VERSION:=1.8.0 FIRMWARE_URL:=http://downloads.digium.com/pub/telephony/firmware/releases -ALL_FIRMWARE=FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-TC400M FIRMWARE-HX8 +ALL_FIRMWARE=FIRMWARE-OCT6114-064 FIRMWARE-OCT6114-128 FIRMWARE-TC400M FIRMWARE-HX8 FIRMWARE-VPMOCT032 # Firmware files should use the naming convention: dahdi-fw-<base name>-<sub name>-<version> or dahdi-fw-<base name>-<version> # First example: dahdi-fw-oct6114-064-1.05.01 @@ -43,6 +44,7 @@ FIRMWARE:=$(ALL_FIRMWARE:FIRMWARE-OCT6114-064=dahdi-fw-oct6114-064-$(OCT6114_064 FIRMWARE:=$(FIRMWARE:FIRMWARE-OCT6114-128=dahdi-fw-oct6114-128-$(OCT6114_128_VERSION).tar.gz) FIRMWARE:=$(FIRMWARE:FIRMWARE-TC400M=dahdi-fw-tc400m-$(TC400M_VERSION).tar.gz) FIRMWARE:=$(FIRMWARE:FIRMWARE-HX8=dahdi-fw-hx8-$(HX8_VERSION).tar.gz) +FIRMWARE:=$(FIRMWARE:FIRMWARE-VPMOCT032=dahdi-fw-vpmoct032-$(VPMOCT032_VERSION).tar.gz) FWLOADERS:=dahdi-fwload-vpmadt032-$(VPMADT032_VERSION).tar.gz @@ -143,6 +145,17 @@ ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-hx8-$(HX8_ else @echo "Firmware dahdi-fw-hx8.bin is already installed with required version $(HX8_VERSION)" endif +ifeq ($(shell if ( [ -f $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-vpmoct032-$(VPMOCT032_VERSION) ] ) && ( [ -f $(DESTDIR)/lib/firmware/.dahdi-fw-vpmoct032-$(VPMOCT032_VERSION) ] ); then echo "no"; else echo "yes"; fi),yes) + @echo "Installing dahdi-fw-vpmoct032.bin to hotplug firmware directories" + @install -m 644 dahdi-fw-vpmoct032.bin $(DESTDIR)/usr/lib/hotplug/firmware + @rm -rf $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-vpmoct032-* + @touch $(DESTDIR)/usr/lib/hotplug/firmware/.dahdi-fw-vpmoct032-$(VPMOCT032_VERSION) + @install -m 644 dahdi-fw-vpmoct032.bin $(DESTDIR)/lib/firmware + @rm -rf $(DESTDIR)/lib/firmware/.dahdi-fw-vpmoct032-* + @touch $(DESTDIR)/lib/firmware/.dahdi-fw-vpmoct032-$(VPMOCT032_VERSION) +else + @echo "Firmware dahdi-fw-vpmoct032.bin is already installed with required version $(VPMOCT032_VERSION)" +endif # Uninstall any installed dahdi firmware images from hotplug firmware directories hotplug-uninstall: diff --git a/drivers/dahdi/voicebus/Kbuild b/drivers/dahdi/voicebus/Kbuild index 6316174..b8b23ff 100644 --- a/drivers/dahdi/voicebus/Kbuild +++ b/drivers/dahdi/voicebus/Kbuild @@ -1,5 +1,5 @@ obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_VOICEBUS) += dahdi_voicebus.o -dahdi_voicebus-objs := voicebus.o GpakCust.o GpakApi.o voicebus_net.o +dahdi_voicebus-objs := voicebus.o GpakCust.o GpakApi.o voicebus_net.o vpmoct.o EXTRA_CFLAGS := -I$(src)/.. -Wno-undef diff --git a/drivers/dahdi/voicebus/vpmoct.c b/drivers/dahdi/voicebus/vpmoct.c new file mode 100644 index 0000000..26db816 --- /dev/null +++ b/drivers/dahdi/voicebus/vpmoct.c @@ -0,0 +1,649 @@ +/* + * VPMOCT Driver. + * + * Written by Russ Meyerriecks <rmeyerriecks@digium.com> + * + * Copyright (C) 2010-2011 Digium, Inc. + * + * All rights reserved. + * + */ + +/* + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2 as published by the + * Free Software Foundation. See the LICENSE file included with + * this program for more details. + */ + +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/crc32.h> + +#include "voicebus/vpmoct.h" +#include "linux/firmware.h" + +struct vpmoct_header { + u8 header[6]; + __le32 chksum; + u8 pad[20]; + u8 major; + u8 minor; +} __packed; + +static int _vpmoct_read(struct vpmoct *vpm, u8 address, + void *data, size_t size, + u8 *new_command, u8 *new_address) +{ + struct vpmoct_cmd *cmd; + unsigned long flags; + + if (unlikely(size >= ARRAY_SIZE(cmd->data))) { + memset(data, -1, size); + return -1; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + dev_info(vpm->dev, "Unable to allocate memory for vpmoct_cmd\n"); + return 0; + } + + init_completion(&cmd->complete); + + cmd->command = 0x60 + size; + cmd->address = address; + cmd->chunksize = size; + + spin_lock_irqsave(&vpm->list_lock, flags); + list_add_tail(&cmd->node, &vpm->pending_list); + spin_unlock_irqrestore(&vpm->list_lock, flags); + + /* Wait for receiveprep to process our result */ + if (!wait_for_completion_timeout(&cmd->complete, HZ/5)) { + spin_lock_irqsave(&vpm->list_lock, flags); + list_del(&cmd->node); + spin_unlock_irqrestore(&vpm->list_lock, flags); + kfree(cmd); + dev_err(vpm->dev, "vpmoct_read_byte cmd timed out :O(\n"); + return 0; + } + + memcpy(data, &cmd->data[0], size); + + if (new_command) + *new_command = cmd->command; + if (new_address) + *new_address = cmd->address; + + kfree(cmd); + return 0; +} + +static u8 vpmoct_read_byte(struct vpmoct *vpm, u8 address) +{ + u8 val; + _vpmoct_read(vpm, address, &val, sizeof(val), NULL, NULL); + return val; +} + +static u32 vpmoct_read_dword(struct vpmoct *vpm, u8 address) +{ + __le32 val; + _vpmoct_read(vpm, address, &val, sizeof(val), NULL, NULL); + return le32_to_cpu(val); +} + +static void vpmoct_write_byte(struct vpmoct *vpm, u8 address, u8 data) +{ + struct vpmoct_cmd *cmd; + unsigned long flags; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + dev_info(vpm->dev, "Unable to allocate memory for vpmoct_cmd\n"); + return; + } + + cmd->command = 0x21; + cmd->address = address; + cmd->data[0] = data; + cmd->chunksize = 1; + + spin_lock_irqsave(&vpm->list_lock, flags); + list_add_tail(&cmd->node, &vpm->pending_list); + spin_unlock_irqrestore(&vpm->list_lock, flags); +} + +static void vpmoct_write_dword(struct vpmoct *vpm, u8 address, u32 data) +{ + struct vpmoct_cmd *cmd; + unsigned long flags; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + dev_info(vpm->dev, "Unable to allocate memory for vpmoct_cmd\n"); + return; + } + + cmd->command = 0x20 + sizeof(data); + cmd->address = address; + *(__le32 *)(&cmd->data[0]) = cpu_to_le32(data); + cmd->chunksize = sizeof(data); + + spin_lock_irqsave(&vpm->list_lock, flags); + list_add_tail(&cmd->node, &vpm->pending_list); + spin_unlock_irqrestore(&vpm->list_lock, flags); +} + +static void vpmoct_write_chunk(struct vpmoct *vpm, u8 address, + const u8 *data, u8 chunksize) +{ + struct vpmoct_cmd *cmd; + unsigned long flags; + + if (unlikely(chunksize > ARRAY_SIZE(cmd->data))) + return; + + cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); + if (unlikely(!cmd)) { + dev_info(vpm->dev, "Unable to allocate memory for vpmoct_cmd\n"); + return; + } + + cmd->command = 0x20 + chunksize; + cmd->address = address; + cmd->chunksize = chunksize; + + memcpy(cmd->data, data, chunksize); + + spin_lock_irqsave(&vpm->list_lock, flags); + list_add_tail(&cmd->node, &vpm->pending_list); + spin_unlock_irqrestore(&vpm->list_lock, flags); +} + +static u8 vpmoct_resync(struct vpmoct *vpm) +{ + unsigned long time; + u8 status = 0xff; + u8 address; + u8 command; + + /* Poll the status register until it returns valid values + * This is because we have to wait on the bootloader to do + * its thing. + * Timeout after 3 seconds + */ + time = jiffies + 3*HZ; + while (time_after(time, jiffies) && (0xff == status)) { + status = _vpmoct_read(vpm, VPMOCT_BOOT_STATUS, &status, + sizeof(status), &command, &address); + + /* Throw out invalid statuses */ + if ((0x55 != command) || (0xaa != address)) + status = 0xff; + } + + if ((status != 0xff) && status) + dev_info(vpm->dev, "Resync with status %x\n", status); + + return status; +} + +static inline short vpmoct_erase_flash(struct vpmoct *vpm) +{ + short res; + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, VPMOCT_BOOT_FLASH_ERASE); + res = vpmoct_resync(vpm); + if (res) + dev_info(vpm->dev, "Unable to erase flash\n"); + return res; +} + +static inline short +vpmoct_send_firmware_header(struct vpmoct *vpm, const struct firmware *fw) +{ + unsigned short i; + short res; + + /* Send the encrypted firmware header */ + for (i = 0; i < VPMOCT_FIRM_HEADER_LEN; i++) { + vpmoct_write_byte(vpm, VPMOCT_BOOT_RAM+i, + fw->data[i + sizeof(struct vpmoct_header)]); + } + /* Decrypt header */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, VPMOCT_BOOT_DECRYPT); + res = vpmoct_resync(vpm); + if (res) + dev_info(vpm->dev, "Unable to send firmware header\n"); + return res; +} + +static inline short +vpmoct_send_firmware_body(struct vpmoct *vpm, const struct firmware *fw) +{ + unsigned int i, ram_index, flash_index, flash_address; + const u8 *buf; + u8 chunksize; + + /* Load the body of the firmware */ + ram_index = 0; + flash_index = 0; + flash_address = 0; + for (i = VPMOCT_FIRM_HEADER_LEN*2; i < fw->size;) { + if (ram_index >= VPMOCT_BOOT_RAM_LEN) { + /* Tell bootloader to load ram buffer into buffer */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, + 0x10 + flash_index); + /* Assuming the memory load doesn't take longer than 1 + * eframe just insert a blank eframe before continuing + * the firmware load */ + vpmoct_read_byte(vpm, VPMOCT_BOOT_STATUS); + ram_index = 0; + flash_index++; + } + if (flash_index >= VPMOCT_FLASH_BUF_SECTIONS) { + /* Tell the bootloader the memory address for load */ + vpmoct_write_dword(vpm, VPMOCT_BOOT_ADDRESS1, + flash_address); + /* Tell the bootloader to load from flash buffer */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, + VPMOCT_BOOT_FLASH_COPY); + if (vpmoct_resync(vpm)) + goto error; + flash_index = 0; + flash_address = i-VPMOCT_FIRM_HEADER_LEN*2; + } + /* Try to buffer for batch writes if possible */ + chunksize = VPMOCT_BOOT_RAM_LEN - ram_index; + if (chunksize > VPMOCT_MAX_CHUNK) + chunksize = VPMOCT_MAX_CHUNK; + + buf = &fw->data[i]; + vpmoct_write_chunk(vpm, VPMOCT_BOOT_RAM+ram_index, + buf, chunksize); + ram_index += chunksize; + i += chunksize; + } + + /* Flush remaining ram buffer to flash buffer */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, + VPMOCT_BOOT_FLASHLOAD + flash_index); + if (vpmoct_resync(vpm)) + goto error; + /* Tell boot loader the memory address to flash load */ + vpmoct_write_dword(vpm, VPMOCT_BOOT_ADDRESS1, flash_address); + /* Tell the bootloader to load flash from flash buffer */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, VPMOCT_BOOT_FLASH_COPY); + if (vpmoct_resync(vpm)) + goto error; + + return 0; + +error: + dev_info(vpm->dev, "Unable to load firmware body\n"); + return -1; +} + +static inline short +vpmoct_check_firmware_crc(struct vpmoct *vpm, size_t size, u8 major, u8 minor) +{ + u8 status; + + /* Load firmware size */ + vpmoct_write_dword(vpm, VPMOCT_BOOT_RAM, size); + + /* Load firmware version */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_RAM+8, major); + vpmoct_write_byte(vpm, VPMOCT_BOOT_RAM+9, minor); + + /* Validate the firmware load */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, VPMOCT_BOOT_IMAGE_VALIDATE); + + status = vpmoct_resync(vpm); + if (status) { + dev_info(vpm->dev, + "vpmoct firmware CRC check failed: %x\n", status); + /* TODO: Try the load again */ + return -1; + } else { + dev_info(vpm->dev, "vpmoct firmware uploaded successfully\n"); + /* Switch to application code */ + vpmoct_write_dword(vpm, VPMOCT_BOOT_ADDRESS2, 0xDEADBEEF); + /* Soft reset the processor */ + vpmoct_write_byte(vpm, VPMOCT_BOOT_CMD, VPMOCT_BOOT_REBOOT); + return 0; + } +} + +static inline short vpmoct_switch_to_boot(struct vpmoct *vpm) +{ + vpmoct_write_dword(vpm, 0x74, 0x00009876); + vpmoct_write_byte(vpm, 0x71, 0x02); + if (vpmoct_resync(vpm)) { + dev_info(vpm->dev, "Failed to switch to bootloader\n"); + return -1; + } + vpm->mode = VPMOCT_MODE_BOOTLOADER; + return 0; +} + +static bool is_valid_vpmoct_firmware(const struct firmware *fw) +{ + const struct vpmoct_header *header = + (const struct vpmoct_header *)fw->data; + u32 crc = crc32(~0, &fw->data[10], fw->size - 10) ^ ~0; + return (!memcmp("DIGIUM", header->header, sizeof(header->header)) && + (le32_to_cpu(header->chksum) == crc)); +} + +static void vpmoct_set_defaults(struct vpmoct *vpm) +{ + vpmoct_write_dword(vpm, 0x40, 0); + vpmoct_write_dword(vpm, 0x30, 0); +} + +/** + * vpmoct_load_flash - Check the current flash version and possibly load. + * @vpm: The VPMOCT032 module to check / load. + * + * Returns 0 on success, otherwise an error message. + * + * Must be called in process context. + * + */ +static int vpmoct_load_flash(struct vpmoct *vpm) +{ + int firm; + const struct firmware *fw; + const struct vpmoct_header *header; + char serial[VPMOCT_SERIAL_SIZE+1]; + const char *const FIRMWARE_NAME = "dahdi-fw-vpmoct032.bin"; + int i; + + /* Load the firmware */ + firm = request_firmware(&fw, FIRMWARE_NAME, vpm->dev); + if (firm) { + dev_info(vpm->dev, "vpmoct: Failed to load firmware from"\ + " userspace!, %d\n", firm); + return -ENOMEM; + } + + if (!is_valid_vpmoct_firmware(fw)) { + dev_warn(vpm->dev, + "%s is invalid. Please reinstall.\n", FIRMWARE_NAME); + release_firmware(fw); + return -EINVAL; + } + + header = (const struct vpmoct_header *)fw->data; + + if (vpm->mode == VPMOCT_MODE_APPLICATION) { + /* Check the running application firmware + * for the proper version */ + vpm->major = vpmoct_read_byte(vpm, VPMOCT_MAJOR); + vpm->minor = vpmoct_read_byte(vpm, VPMOCT_MINOR); + for (i = 0; i < VPMOCT_SERIAL_SIZE; i++) + serial[i] = vpmoct_read_byte(vpm, VPMOCT_SERIAL+i); + serial[VPMOCT_SERIAL_SIZE] = '\0'; + + dev_info(vpm->dev, "vpmoct: Detected firmware v%d.%d\n", + vpm->major, vpm->minor); + dev_info(vpm->dev, "vpmoct: Serial %s\n", serial); + + if (vpm->minor == header->minor && + vpm->major == header->major) { + /* Proper version is running */ + release_firmware(fw); + vpmoct_set_defaults(vpm); + return 0; + } else { + + /* Incorrect version of application code is + * loaded. Reset to bootloader mode */ + if (vpmoct_switch_to_boot(vpm)) + goto error; + } + } + + dev_info(vpm->dev, "vpmoct: Uploading firmware, v%d.%d. This can "\ + "take up to 1 minute\n", + header->major, header->minor); + if (vpmoct_erase_flash(vpm)) + goto error; + if (vpmoct_send_firmware_header(vpm, fw)) + goto error; + if (vpmoct_send_firmware_body(vpm, fw)) + goto error; + if (vpmoct_check_firmware_crc(vpm, fw->size-VPMOCT_FIRM_HEADER_LEN*2, + header->major, header->minor)) + goto error; + release_firmware(fw); + vpmoct_set_defaults(vpm); + return 0; + +error: + dev_info(vpm->dev, "Unable to load firmware\n"); + release_firmware(fw); + return -1; +} + +struct vpmoct *vpmoct_alloc(void) +{ + struct vpmoct *vpm; + + vpm = kzalloc(sizeof(*vpm), GFP_KERNEL); + if (!vpm) + return NULL; + + spin_lock_init(&vpm->list_lock); + INIT_LIST_HEAD(&vpm->pending_list); + INIT_LIST_HEAD(&vpm->active_list); + mutex_init(&vpm->mutex); + return vpm; +} +EXPORT_SYMBOL(vpmoct_alloc); + +void vpmoct_free(struct vpmoct *vpm) +{ + kfree(vpm); +} +EXPORT_SYMBOL(vpmoct_free); + +/** + * vpmoct_init - Check for / initialize VPMOCT032 module. + * @vpm: struct vpmoct allocated with vpmoct_alloc + * + * Returns 0 on success or an error code. + * + * Must be called in process context. + */ +int vpmoct_init(struct vpmoct *vpm) +{ + unsigned int i; + char identifier[10]; + + if (vpmoct_resync(vpm)) + return -ENODEV; + + /* Probe for vpmoct ident string */ + for (i = 0; i < ARRAY_SIZE(identifier); i++) + identifier[i] = vpmoct_read_byte(vpm, VPMOCT_IDENT+i); + + if (!memcmp(identifier, "bootloader", sizeof(identifier))) { + /* vpmoct is in bootloader mode */ + dev_info(vpm->dev, "Detected vpmoct bootloader, attempting "\ + "to load firmware\n"); + vpm->mode = VPMOCT_MODE_BOOTLOADER; + return vpmoct_load_flash(vpm); + } else if (!memcmp(identifier, "VPMOCT032\0", sizeof(identifier))) { + /* vpmoct is in application mode */ + vpm->mode = VPMOCT_MODE_APPLICATION; + return vpmoct_load_flash(vpm); + } else { + /* No vpmoct is installed */ + return -ENODEV; + } +} +EXPORT_SYMBOL(vpmoct_init); + +static void +vpmoct_set_companding(struct vpmoct *vpm, int channo, int companding) +{ + u32 new_companding; + bool do_update = false; + + mutex_lock(&vpm->mutex); + new_companding = (DAHDI_LAW_MULAW == companding) ? + (vpm->companding & ~(1 << channo)) : + (vpm->companding | (1 << channo)); + if (vpm->companding != new_companding) { + vpm->companding = new_companding; + if (!vpm->companding_update_active) { + do_update = true; + vpm->companding_update_active = 1; + } + } + mutex_unlock(&vpm->mutex); + + while (do_update) { + u32 update; + + vpmoct_write_dword(vpm, 0x40, new_companding); + update = vpmoct_read_dword(vpm, 0x40); + + WARN_ON(new_companding != update); + + mutex_lock(&vpm->mutex); + if (vpm->companding != new_companding) { + new_companding = vpm->companding; + } else { + vpm->companding_update_active = 0; + do_update = false; + } + mutex_unlock(&vpm->mutex); + } +} + +/** + * vpmoct_echo_update - Enable / Disable the VPMOCT032 echocan state + * @vpm: The echocan to operate on. + * @channo: Which echocan timeslot to enable / disable. + * @echo_on: Whether we're turning the echocan on or off. + * + * When this function returns, the echocan is scheduled to be enabled or + * disabled at some point in the near future. + * + * Must be called in process context. + * + */ +static void vpmoct_echo_update(struct vpmoct *vpm, int channo, bool echo_on) +{ + u32 echo; + unsigned long timeout; + bool do_update = false; + + mutex_lock(&vpm->mutex); + echo = (echo_on) ? (vpm->echo | (1 << channo)) : + (vpm->echo & ~(1 << channo)); + if (vpm->echo != echo) { + vpm->echo = echo; + if (!vpm->echo_update_active) { + do_update = true; + vpm->echo_update_active = 1; + } + } + mutex_unlock(&vpm->mutex); + + timeout = jiffies + 2*HZ; + while (do_update) { + u32 new; + + vpmoct_write_dword(vpm, 0x30, echo); + new = vpmoct_read_dword(vpm, 0x10); + + mutex_lock(&vpm->mutex); + if (((vpm->echo != echo) || (new != echo)) && + time_before(jiffies, timeout)) { + echo = vpm->echo; + } else { + vpm->echo_update_active = 0; + do_update = false; + } + mutex_unlock(&vpm->mutex); + } + + if (!time_before(jiffies, timeout)) + dev_warn(vpm->dev, "vpmoct: Updating echo state timed out.\n"); +} + +int vpmoct_echocan_create(struct vpmoct *vpm, int channo, int companding) +{ + vpmoct_set_companding(vpm, channo, companding); + vpmoct_echo_update(vpm, channo, true); + return 0; +} +EXPORT_SYMBOL(vpmoct_echocan_create); + +void vpmoct_echocan_free(struct vpmoct *vpm, int channo) +{ + vpmoct_echo_update(vpm, channo, false); +} +EXPORT_SYMBOL(vpmoct_echocan_free); + +/* Enable a vpm debugging mode where the pre-echo-canceled audio + * stream is physically output on timeslot 24. + */ +int vpmoct_preecho_enable(struct vpmoct *vpm, const int channo) +{ + int ret; + mutex_lock(&vpm->mutex); + if (!vpm->preecho_enabled) { + vpm->preecho_enabled = 1; + vpm->preecho_timeslot = channo; + + vpmoct_write_dword(vpm, 0x74, channo); + + /* Begin pre-echo stream on timeslot 24 */ + vpmoct_write_byte(vpm, 0x71, 0x0a); + ret = 0; + } else { + ret = -EBUSY; + } + mutex_unlock(&vpm->mutex); + + return ret; +} +EXPORT_SYMBOL(vpmoct_preecho_enable); + +int vpmoct_preecho_disable(struct vpmoct *vpm, const int channo) +{ + int ret; + + mutex_lock(&vpm->mutex); + if (!vpm->preecho_enabled) { + ret = 0; + } else if (channo == vpm->preecho_timeslot) { + vpm->preecho_enabled = 0; + + /* Disable pre-echo stream by loading in a non-existing + * channel number */ + vpmoct_write_byte(vpm, 0x74, 0xff); + + /* Stop pre-echo stream on timeslot 24 */ + vpmoct_write_byte(vpm, 0x71, 0x0a); + ret = 0; + } else { + ret = -EINVAL; + } + mutex_unlock(&vpm->mutex); + + return ret; +} +EXPORT_SYMBOL(vpmoct_preecho_disable); diff --git a/drivers/dahdi/voicebus/vpmoct.h b/drivers/dahdi/voicebus/vpmoct.h new file mode 100644 index 0000000..486a80d --- /dev/null +++ b/drivers/dahdi/voicebus/vpmoct.h @@ -0,0 +1,110 @@ +/* + * VPMOCT Driver. + * + * Written by Russ Meyerriecks <rmeyerriecks@digium.com> + * + * Copyright (C) 2010-2011 Digium, Inc. + * + * All rights reserved. + * + */ + +/* + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2 as published by the + * Free Software Foundation. See the LICENSE file included with + * this program for more details. + */ + +#ifndef _VPMOCT_H +#define _VPMOCT_H + +#include <linux/list.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include "dahdi/kernel.h" + +#include <stdbool.h> + +#define VPMOCT_FIRM_HEADER_LEN 32 +#define VPMOCT_BOOT_RAM_LEN 128 +#define VPMOCT_FLASH_BUF_SECTIONS 4 +#define VPMOCT_MAX_CHUNK 7 + +/* Bootloader commands */ +#define VPMOCT_BOOT_FLASH_ERASE 0x01 +#define VPMOCT_BOOT_FLASH_COPY 0x02 +#define VPMOCT_BOOT_IMAGE_VALIDATE 0x06 +#define VPMOCT_BOOT_REBOOT 0x07 +#define VPMOCT_BOOT_DECRYPT 0x08 +#define VPMOCT_BOOT_FLASHLOAD 0x10 + +/* Dual use registers */ +#define VPMOCT_IDENT 0x00 +#define VPMOCT_MAJOR 0x0a +#define VPMOCT_MINOR 0x0b +#define VPMOCT_SERIAL 0x90 +#define VPMOCT_SERIAL_SIZE 32 + +/* Bootloader registers */ +#define VPMOCT_BOOT_ERROR 0x0c +#define VPMOCT_BOOT_STATUS 0x10 +#define VPMOCT_BOOT_CMD 0x11 +#define VPMOCT_BOOT_LEN 0x14 +#define VPMOCT_BOOT_ADDRESS1 0x18 +#define VPMOCT_BOOT_ADDRESS2 0x1c +#define VPMOCT_BOOT_RAM 0x20 + +#define VPMOCT_MODE_BOOTLOADER 0 +#define VPMOCT_MODE_APPLICATION 1 + +struct vpmoct { + struct list_head pending_list; + struct list_head active_list; + spinlock_t list_lock; + struct mutex mutex; + unsigned short int mode; + struct device *dev; + u32 companding; + u32 echo; + unsigned int preecho_enabled:1; + unsigned int echo_update_active:1; + unsigned int companding_update_active:1; + u8 preecho_timeslot; + u8 preecho_buf[8]; + u8 major; + u8 minor; +}; + +struct vpmoct_cmd { + struct list_head node; + u8 address; + u8 data[VPMOCT_MAX_CHUNK]; + u8 command; + u8 chunksize; + u8 txident; + struct completion complete; +}; + +static inline bool is_vpmoct_cmd_read(const struct vpmoct_cmd *cmd) +{ + return (0x60 == (cmd->command & 0xf0)); +} + +struct vpmoct *vpmoct_alloc(void); +void vpmoct_free(struct vpmoct *vpm); +int vpmoct_init(struct vpmoct *vpm); +int vpmoct_echocan_create(struct vpmoct *vpm, + int channo, + int companding); +void vpmoct_echocan_free(struct vpmoct *vpm, + int channo); +int vpmoct_preecho_enable(struct vpmoct *vpm, int channo); +int vpmoct_preecho_disable(struct vpmoct *vpm, int channo); +#endif diff --git a/drivers/dahdi/wctdm24xxp/base.c b/drivers/dahdi/wctdm24xxp/base.c index 5ff0253..73bcae6 100644 --- a/drivers/dahdi/wctdm24xxp/base.c +++ b/drivers/dahdi/wctdm24xxp/base.c @@ -7,7 +7,7 @@ * Support for Hx8 by Andrew Kohlsmith <akohlsmith@mixdown.ca> and Matthew * Fredrickson <creslin@digium.com> * - * Copyright (C) 2005 - 2010 Digium, Inc. + * Copyright (C) 2005 - 2011 Digium, Inc. * All rights reserved. * * Sections for QRV cards written by Jim Dixon <jim@lambdatel.com> @@ -165,6 +165,7 @@ static alpha indirect_regs[] = /* names of HWEC modules */ static const char *vpmadt032_name = "VPMADT032"; +static const char *vpmoct_name = "VPMOCT032"; /* Undefine to enable Power alarm / Transistor debug -- note: do not enable for normal operation! */ @@ -308,6 +309,12 @@ static inline __attribute_const__ int VPM_CMD_BYTE(int timeslot, int bit) return ((((timeslot) & 0x3) * 3 + (bit)) * 7) + ((timeslot) >> 2); } +static inline bool is_initialized(struct wctdm *wc) +{ + WARN_ON(wc->initialized < 0); + return (wc->initialized == 0); +} + static void setchanconfig_from_state(struct vpmadt032 *vpm, int channel, GpakChannelConfig_t *chanconfig) @@ -435,7 +442,8 @@ static int config_vpmadt032(struct vpmadt032 *vpm, struct wctdm *wc) dev_info(&wc->vb.pdev->dev, "Configured McBSP ports successfully\n"); } - if ((res = gpakPingDsp(vpm->dspid, &vpm->version))) { + res = gpakPingDsp(vpm->dspid, &vpm->version); + if (res) { dev_notice(&wc->vb.pdev->dev, "Error pinging DSP (%d)\n", res); return -1; } @@ -490,6 +498,52 @@ static inline bool is_good_frame(const u8 *sframe) return a != b; } +static inline void cmd_dequeue_vpmoct(struct wctdm *wc, u8 *eframe) +{ + struct vpmoct *vpm = wc->vpmoct; + struct vpmoct_cmd *cmd; + u8 i; + + /* Pop a command off pending list */ + spin_lock(&vpm->list_lock); + if (list_empty(&vpm->pending_list)) { + spin_unlock(&vpm->list_lock); + return; + } + + cmd = list_entry(vpm->pending_list.next, struct vpmoct_cmd, node); + if (is_vpmoct_cmd_read(cmd)) + list_move_tail(&cmd->node, &vpm->active_list); + else + list_del_init(&cmd->node); + + /* Skip audio (24 bytes) and ignore first 6 timeslots */ + eframe += 30; + + /* Save ident so we can match the return eframe */ + cmd->txident = wc->txident; + + /* We have four timeslots to work with for a regular spi packet */ + /* TODO: Create debug flag for this in dev */ + + /* The vpmoct requires a "sync" spi command as the first three bytes + * of an eframe */ + eframe[7*0] = 0x12; + eframe[7*1] = 0x34; + eframe[7*2] = 0x56; + eframe[7*3] = cmd->command; + eframe[7*4] = cmd->address; + eframe[7*5] = cmd->data[0]; + for (i = 1; i < cmd->chunksize; i++) + eframe[(7*5)+7*i] = cmd->data[i]; + + /* Clean up fire-and-forget messages from memory */ + if (list_empty(&cmd->node)) + kfree(cmd); + + spin_unlock(&vpm->list_lock); +} + static inline void cmd_dequeue_vpmadt032(struct wctdm *wc, u8 *eframe) { struct vpmadt032_cmd *curcmd = NULL; @@ -709,6 +763,40 @@ static void _cmd_dequeue(struct wctdm *wc, u8 *eframe, int card, int pos) } } +static inline void cmd_decipher_vpmoct(struct wctdm *wc, const u8 *eframe) +{ + struct vpmoct *vpm = wc->vpmoct; + struct vpmoct_cmd *cmd; + int i; + + /* Skip audio and first 6 timeslots */ + eframe += 30; + + spin_lock(&vpm->list_lock); + /* No command to handle, just exit */ + if (list_empty(&vpm->active_list)) { + spin_unlock(&vpm->list_lock); + return; + } + + cmd = list_entry(vpm->active_list.next, struct vpmoct_cmd, node); + if (wc->rxident == cmd->txident) + list_del_init(&cmd->node); + else + cmd = NULL; + spin_unlock(&vpm->list_lock); + + if (!cmd) + return; + + /* Store result, Ignoring the first "sync spi command" bytes */ + cmd->command = eframe[7*3]; + cmd->address = eframe[7*4]; + for (i = 0; i < cmd->chunksize; ++i) + cmd->data[i] = eframe[7*(5+i)]; + complete(&cmd->complete); +} + static inline void cmd_decipher_vpmadt032(struct wctdm *wc, const u8 *eframe) { struct vpmadt032 *const vpm = wc->vpmadt032; @@ -923,7 +1011,7 @@ static inline void wctdm_transmitprep(struct wctdm *wc, unsigned char *sframe) unsigned char *eframe = sframe; /* Calculate Transmission */ - if (likely(wc->initialized)) { + if (likely(is_initialized(wc))) { for (x = 0; x < MAX_SPANS; x++) { if (wc->spans[x]) { s = &wc->spans[x]->span; @@ -953,11 +1041,10 @@ static inline void wctdm_transmitprep(struct wctdm *wc, unsigned char *sframe) _cmd_dequeue(wc, eframe, y, x); } - if (wc->vpmadt032) { - cmd_dequeue_vpmadt032(wc, eframe); - } else if (wc->vpmadt032) { + if (wc->vpmadt032) cmd_dequeue_vpmadt032(wc, eframe); - } + else if (wc->vpmoct) + cmd_dequeue_vpmoct(wc, eframe); if (x < DAHDI_CHUNKSIZE - 1) { eframe[EFRAME_SIZE] = wc->ctlreg; @@ -1171,6 +1258,22 @@ static void extract_tdm_data(struct wctdm *wc, const u8 *sframe) chanchunk[6] = sframe[3 + i + (EFRAME_SIZE + EFRAME_GAP)*6]; chanchunk[7] = sframe[3 + i + (EFRAME_SIZE + EFRAME_GAP)*7]; } + + /* Pre-echo with the vpmoct overwrites the 24th timeslot with the + * specified channel's pre-echo audio stream. This data is ignored + * on all but the 24xx card, so we store it in a temporary buffer. + */ + if (wc->vpmoct && wc->vpmoct->preecho_enabled) { + chanchunk = &wc->vpmoct->preecho_buf[0]; + chanchunk[0] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*0]; + chanchunk[1] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*1]; + chanchunk[2] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*2]; + chanchunk[3] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*3]; + chanchunk[4] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*4]; + chanchunk[5] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*5]; + chanchunk[6] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*6]; + chanchunk[7] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*7]; + } } static inline void wctdm_receiveprep(struct wctdm *wc, const u8 *sframe) @@ -1184,10 +1287,11 @@ static inline void wctdm_receiveprep(struct wctdm *wc, const u8 *sframe) if (unlikely(!is_good_frame(sframe))) return; - if (likely(wc->initialized)) + spin_lock_irqsave(&wc->reglock, flags); + + if (likely(is_initialized(wc))) extract_tdm_data(wc, sframe); - spin_lock_irqsave(&wc->reglock, flags); for (x = 0; x < DAHDI_CHUNKSIZE; x++) { if (x < DAHDI_CHUNKSIZE - 1) { expected = wc->rxident + 1; @@ -1203,13 +1307,15 @@ static inline void wctdm_receiveprep(struct wctdm *wc, const u8 *sframe) if (wc->vpmadt032) cmd_decipher_vpmadt032(wc, eframe); + else if (wc->vpmoct) + cmd_decipher_vpmoct(wc, eframe); eframe += (EFRAME_SIZE + EFRAME_GAP); } spin_unlock_irqrestore(&wc->reglock, flags); /* XXX We're wasting 8 taps. We should get closer :( */ - if (likely(wc->initialized)) { + if (likely(is_initialized(wc))) { for (x = 0; x < wc->avchannels; x++) { struct wctdm_chan *const wchan = wc->chans[x]; struct dahdi_chan *const c = &wchan->chan; @@ -1219,7 +1325,16 @@ static inline void wctdm_receiveprep(struct wctdm *wc, const u8 *sframe) ARRAY_SIZE(buffer)); dahdi_ec_chunk(c, c->readchunk, buffer); #else - dahdi_ec_chunk(c, c->readchunk, c->writechunk); + if ((wc->vpmoct) && + (wchan->timeslot == wc->vpmoct->preecho_timeslot) && + (wc->vpmoct->preecho_enabled)) { + __dahdi_ec_chunk(c, c->readchunk, + wc->vpmoct->preecho_buf, + c->writechunk); + } else { + __dahdi_ec_chunk(c, c->readchunk, c->readchunk, + c->writechunk); + } #endif } @@ -2014,6 +2129,8 @@ static const char *wctdm_echocan_name(const struct dahdi_chan *chan) struct wctdm *wc = chan->pvt; if (wc->vpmadt032) return vpmadt032_name; + else if (wc->vpmoct) + return vpmoct_name; return NULL; } @@ -2033,21 +2150,31 @@ static int wctdm_echocan_create(struct dahdi_chan *chan, if (!vpmsupport) return -ENODEV; #endif - if (!wc->vpmadt032) + if (wc->vpmadt032) { + ops = &vpm_ec_ops; + features = &vpm_ec_features; + + *ec = &wchan->ec; + (*ec)->ops = ops; + (*ec)->features = *features; + + comp = (DAHDI_LAW_ALAW == chan->span->deflaw) ? + ADT_COMP_ALAW : ADT_COMP_ULAW; + + return vpmadt032_echocan_create(wc->vpmadt032, wchan->timeslot, + comp, ecp, p); + } else if (wc->vpmoct) { + ops = &vpm_ec_ops; + features = &vpm_ec_features; + + *ec = &wchan->ec; + (*ec)->ops = ops; + (*ec)->features = *features; + return vpmoct_echocan_create(wc->vpmoct, wchan->timeslot, + chan->span->deflaw); + } else { return -ENODEV; - - ops = &vpm_ec_ops; - features = &vpm_ec_features; - - *ec = &wchan->ec; - (*ec)->ops = ops; - (*ec)->features = *features; - - comp = (DAHDI_LAW_ALAW == chan->span->deflaw) ? - ADT_COMP_ALAW : ADT_COMP_ULAW; - - return vpmadt032_echocan_create(wc->vpmadt032, wchan->timeslot, - comp, ecp, p); + } } static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec) @@ -2058,6 +2185,9 @@ static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec if (wc->vpmadt032) { memset(ec, 0, sizeof(*ec)); vpmadt032_echocan_free(wc->vpmadt032, wchan->timeslot, ec); + } else if (wc->vpmoct) { + memset(ec, 0, sizeof(*ec)); + vpmoct_echocan_free(wc->vpmoct, wchan->timeslot); } } @@ -2135,7 +2265,7 @@ static inline void wctdm_isr_misc(struct wctdm *wc) { int x; - if (unlikely(!wc->initialized)) + if (unlikely(!is_initialized(wc))) return; for (x = 0; x < wc->mods_per_board; x++) { @@ -3920,9 +4050,9 @@ static int wctdm_dacs(struct dahdi_chan *dst, struct dahdi_chan *src) * Check if the board has finished any setup and is ready to start processing * calls. */ -int wctdm_wait_for_ready(const struct wctdm *wc) +int wctdm_wait_for_ready(struct wctdm *wc) { - while (!wc->initialized) { + while (!is_initialized(wc)) { if (fatal_signal_pending(current)) return -EIO; msleep_interruptible(250); @@ -3930,6 +4060,28 @@ int wctdm_wait_for_ready(const struct wctdm *wc) return 0; } +static int wctdm_enable_hw_preechocan(struct dahdi_chan *chan) +{ + struct wctdm *wc = chan->pvt; + struct wctdm_chan *wchan = container_of(chan, struct wctdm_chan, chan); + + if (!wc->vpmoct) + return 0; + + return vpmoct_preecho_enable(wc->vpmoct, wchan->timeslot); +} + +static void wctdm_disable_hw_preechocan(struct dahdi_chan *chan) +{ + struct wctdm *wc = chan->pvt; + struct wctdm_chan *wchan = container_of(chan, struct wctdm_chan, chan); + + if (!wc->vpmoct) + return; + + vpmoct_preecho_disable(wc->vpmoct, wchan->timeslot); +} + /** * wctdm_chanconfig - Called when the channels are being configured. * @@ -3944,7 +4096,7 @@ wctdm_chanconfig(struct file *file, struct dahdi_chan *chan, int sigtype) { struct wctdm *wc = chan->pvt; - if ((file->f_flags & O_NONBLOCK) && !wc->initialized) + if ((file->f_flags & O_NONBLOCK) && !is_initialized(wc)) return -EAGAIN; return wctdm_wait_for_ready(wc); @@ -3960,6 +4112,8 @@ static const struct dahdi_span_ops wctdm24xxp_analog_span_ops = { .chanconfig = wctdm_chanconfig, .dacs = wctdm_dacs, #ifdef VPM_SUPPORT + .enable_hw_preechocan = wctdm_enable_hw_preechocan, + .disable_hw_preechocan = wctdm_disable_hw_preechocan, .echocan_create = wctdm_echocan_create, .echocan_name = wctdm_echocan_name, #endif @@ -3976,6 +4130,8 @@ static const struct dahdi_span_ops wctdm24xxp_digital_span_ops = { .chanconfig = b400m_chanconfig, .dacs = wctdm_dacs, #ifdef VPM_SUPPORT + .enable_hw_preechocan = wctdm_enable_hw_preechocan, + .disable_hw_preechocan = wctdm_disable_hw_preechocan, .echocan_create = wctdm_echocan_create, .echocan_name = wctdm_echocan_name, #endif @@ -4222,14 +4378,44 @@ static void wctdm_initialize_vpm(struct wctdm *wc) { int res = 0; - if (!vpmsupport) { - dev_notice(&wc->vb.pdev->dev, "VPM: Support Disabled\n"); - return; - } + if (!vpmsupport) + goto cleanup; res = wctdm_initialize_vpmadt032(wc); - if (!res) + if (!res) { wc->ctlreg |= 0x10; + return; + } else { + struct vpmoct *vpm; + unsigned long flags; + + vpm = vpmoct_alloc(); + if (!vpm) { + dev_info(&wc->vb.pdev->dev, + "Unable to allocate memory for struct vpmoct\n"); + goto cleanup; + } + + vpm->dev = &wc->vb.pdev->dev; + + spin_lock_irqsave(&wc->reglock, flags); + wc->vpmoct = vpm; + spin_unlock_irqrestore(&wc->reglock, flags); + + if (!vpmoct_init(vpm)) { + wc->ctlreg |= 0x10; + return; + } else { + spin_lock_irqsave(&wc->reglock, flags); + wc->vpmoct = NULL; + spin_unlock_irqrestore(&wc->reglock, flags); + vpmoct_free(vpm); + goto cleanup; + } + } + +cleanup: + dev_info(&wc->vb.pdev->dev, "VPM: Support Disabled\n"); } static void wctdm_identify_modules(struct wctdm *wc) @@ -4904,6 +5090,45 @@ static ssize_t vpm_firmware_version_show(struct device *dev, static DEVICE_ATTR(vpm_firmware_version, 0400, vpm_firmware_version_show, NULL); +static ssize_t +enable_vpm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long flags; + struct wctdm *wc = dev_get_drvdata(dev); + unsigned int enable_vpm; + spin_lock_irqsave(&wc->reglock, flags); + enable_vpm = (wc->ctlreg & 0x10) != 0; + spin_unlock_irqrestore(&wc->reglock, flags); + return sprintf(buf, "%d\n", enable_vpm); +} + +static ssize_t +enable_vpm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long flags; + struct wctdm *wc = dev_get_drvdata(dev); + unsigned int enable_vpm; + if (count < 2) + return -EINVAL; + + if (('0' == buf[0]) || (0 == buf[0])) + enable_vpm = 0; + else + enable_vpm = 1; + + spin_lock_irqsave(&wc->reglock, flags); + if (enable_vpm) + wc->ctlreg |= 0x10; + else + wc->ctlreg &= ~0x10; + spin_unlock_irqrestore(&wc->reglock, flags); + return count; +} + +static DEVICE_ATTR(enable_vpm, 0644, + enable_vpm_show, enable_vpm_store); static void create_sysfs_files(struct wctdm *wc) { @@ -4921,11 +5146,21 @@ static void create_sysfs_files(struct wctdm *wc) dev_info(&wc->vb.pdev->dev, "Failed to create device attributes.\n"); } + + ret = device_create_file(&wc->vb.pdev->dev, + &dev_attr_enable_vpm); + if (ret) { + dev_info(&wc->vb.pdev->dev, + "Failed to create device attributes.\n"); + } } static void remove_sysfs_files(struct wctdm *wc) { device_remove_file(&wc->vb.pdev->dev, + &dev_attr_enable_vpm); + + device_remove_file(&wc->vb.pdev->dev, &dev_attr_vpm_firmware_version); device_remove_file(&wc->vb.pdev->dev, @@ -4979,6 +5214,8 @@ __wctdm_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) if (!wc) return -ENOMEM; + wc->initialized = 1; + down(&ifacelock); /* \todo this is a candidate for removal... */ for (pos = 0; pos < WC_MAX_IFACES; ++pos) { @@ -5223,7 +5460,7 @@ __wctdm_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) } } - wc->initialized = 1; + wc->initialized--; dev_info(&wc->vb.pdev->dev, "Found a %s: %s (%d BRI spans, %d analog %s)\n", @@ -5275,7 +5512,7 @@ static void wctdm_release(struct wctdm *wc) { int i; - if (wc->initialized) { + if (is_initialized(wc)) { for (i = 0; i < MAX_SPANS; i++) { if (wc->spans[i]) dahdi_unregister(&wc->spans[i]->span); diff --git a/drivers/dahdi/wctdm24xxp/wctdm24xxp.h b/drivers/dahdi/wctdm24xxp/wctdm24xxp.h index db3d07a..0f497aa 100644 --- a/drivers/dahdi/wctdm24xxp/wctdm24xxp.h +++ b/drivers/dahdi/wctdm24xxp/wctdm24xxp.h @@ -99,6 +99,8 @@ #include "voicebus/GpakCust.h" #endif +#include "voicebus/vpmoct.h" + struct calregs { unsigned char vals[NUM_CAL_REGS]; }; @@ -140,6 +142,7 @@ struct wctdm_chan { struct dahdi_chan chan; struct dahdi_echocan_state ec; int timeslot; + unsigned int hwpreec_enabled:1; }; struct fxo { @@ -256,7 +259,7 @@ struct wctdm { struct wctdm_module mods[NUM_MODULES]; struct vpmadt032 *vpmadt032; - + struct vpmoct *vpmoct; struct voicebus vb; struct wctdm_span *aspan; /* pointer to the spans[] holding the analog span */ struct wctdm_span *spans[MAX_SPANS]; @@ -270,8 +273,8 @@ struct wctdm { struct semaphore syncsem; int oldsync; - int initialized; /* =1 when the entire card is ready to go */ - unsigned long checkflag; /* Internal state flags and task bits */ + int initialized; /* 0 when the entire card is ready to go */ + unsigned long checkflag; /* Internal state flags and task bits */ int companding; }; @@ -282,7 +285,7 @@ int wctdm_getreg(struct wctdm *wc, struct wctdm_module *const mod, int addr); int wctdm_setreg(struct wctdm *wc, struct wctdm_module *const mod, int addr, int val); -int wctdm_wait_for_ready(const struct wctdm *wc); +int wctdm_wait_for_ready(struct wctdm *wc); extern struct semaphore ifacelock; extern struct wctdm *ifaces[WC_MAX_IFACES]; diff --git a/drivers/dahdi/wcte12xp/base.c b/drivers/dahdi/wcte12xp/base.c index a524482..28afdf6 100644 --- a/drivers/dahdi/wcte12xp/base.c +++ b/drivers/dahdi/wcte12xp/base.c @@ -8,7 +8,7 @@ * Matthew Fredrickson <creslin@digium.com> * William Meadows <wmeadows@digium.com> * - * Copyright (C) 2007-2010, Digium, Inc. + * Copyright (C) 2007-2011, Digium, Inc. * * All rights reserved. * @@ -45,6 +45,7 @@ #include "wct4xxp/wct4xxp.h" /* For certain definitions */ #include "voicebus/voicebus.h" +#include "voicebus/vpmoct.h" #include "wcte12xp.h" #include "voicebus/GpakCust.h" @@ -95,6 +96,7 @@ static const struct t1_desc te121 = {"Wildcard TE121"}; /* names of HWEC modules */ static const char *vpmadt032_name = "VPMADT032"; +static const char *vpmoct_name = "VPMOCT032"; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) static kmem_cache_t *cmd_cache; @@ -144,6 +146,60 @@ static void _resend_cmds(struct t1 *wc) vpmadt032_resend(wc->vpmadt032); } +static inline void cmd_dequeue_vpmoct(struct t1 *wc, u8 *eframe) +{ + struct vpmoct *vpm = wc->vpmoct; + struct vpmoct_cmd *cmd; + u8 i; + + /* Pop a command off pending list */ + spin_lock(&vpm->list_lock); + if (list_empty(&vpm->pending_list)) { + spin_unlock(&vpm->list_lock); + return; + } + cmd = list_entry(vpm->pending_list.next, struct vpmoct_cmd, node); + /* Push the command onto active list, if it's a syncronous cmd */ + if (is_vpmoct_cmd_read(cmd)) + list_move_tail(&cmd->node, &vpm->active_list); + else + list_del_init(&cmd->node); + + /* Skip audio */ + eframe += 66; + + /* Save ident so we can match the return eframe */ + cmd->txident = wc->txident; + /* We have four timeslots to work with for a regular spi packet */ + /* TODO: Create debug flag for this in dev */ + eframe[CMD_BYTE(0, 0, 1)] = 0x12; + eframe[CMD_BYTE(0, 1, 1)] = 0x34; + eframe[CMD_BYTE(0, 2, 1)] = 0x56; + eframe[CMD_BYTE(1, 0, 1)] = cmd->command; + eframe[CMD_BYTE(1, 1, 1)] = cmd->address; + eframe[CMD_BYTE(1, 2, 1)] = cmd->data[0]; + for (i = 1; i < cmd->chunksize; i++) { + /* Every time slot is filled with chunk data + * ignoring command/address/data structure */ + eframe[CMD_BYTE(1, 2, 1) + 2*i] = cmd->data[i]; + } + + /* Clean up fire-and-forget messages from memory */ + if (list_empty(&cmd->node)) + kfree(cmd); + + spin_unlock(&vpm->list_lock); +#if 0 + dev_info(&wc->vb.pdev->dev, "Wrote: "); + for (i = 0; i < 7; i++) { + dev_info(&wc->vb.pdev->dev, "|%x %x %x|", + eframe[CMD_BYTE(i, 0, 1)], + eframe[CMD_BYTE(i, 1, 1)], + eframe[CMD_BYTE(i, 2, 1)]); + } +#endif +} + static void cmd_dequeue(struct t1 *wc, unsigned char *eframe, int frame_num, int slot) { struct command *curcmd=NULL; @@ -213,6 +269,50 @@ static inline void cmd_decipher(struct t1 *wc, const u8 *eframe) } } +inline void cmd_decipher_vpmoct(struct t1 *wc, const u8 *eframe) +{ + int i; + struct vpmoct *vpm = wc->vpmoct; + struct vpmoct_cmd *cmd; + + /* Skip audio and first 6 timeslots */ + eframe += 66; + + spin_lock(&vpm->list_lock); + /* No command to handle, just exit */ + if (list_empty(&vpm->active_list)) { + spin_unlock(&vpm->list_lock); + return; + } + + cmd = list_entry(vpm->active_list.next, struct vpmoct_cmd, node); + if (wc->rxident == cmd->txident) + list_del_init(&cmd->node); + else + cmd = NULL; + spin_unlock(&vpm->list_lock); + + if (!cmd) + return; + +#if 0 + /* Store result */ + dev_info(&wc->vb.pdev->dev, "Read: "); + for (i = 0; i < 7; i++) { + dev_info(&wc->vb.pdev->dev, "|%x %x %x|", + eframe[CMD_BYTE(i, 0, 1)], + eframe[CMD_BYTE(i, 1, 1)], + eframe[CMD_BYTE(i, 2, 1)]); + } + dev_info(&wc->vb.pdev->dev, "\n"); +#endif + cmd->command = eframe[CMD_BYTE(1, 0, 1)]; + cmd->address = eframe[CMD_BYTE(1, 1, 1)]; + for (i = 0; i < cmd->chunksize; ++i) + cmd->data[i] = eframe[CMD_BYTE(1, 2, 1) + 2*i]; + complete(&cmd->complete); +} + inline void cmd_decipher_vpmadt032(struct t1 *wc, const u8 *eframe) { struct vpmadt032 *vpm = wc->vpmadt032; @@ -952,16 +1052,21 @@ static int t1xxp_startup(struct file *file, struct dahdi_span *span) return 0; } +static inline bool is_initialized(struct t1 *wc) +{ + WARN_ON(wc->initialized < 0); + return (wc->initialized == 0); +} + static int t1xxp_chanconfig(struct file *file, struct dahdi_chan *chan, int sigtype) { struct t1 *wc = chan->pvt; - if (file->f_flags & O_NONBLOCK) { - if (!test_bit(READY, &wc->bit_flags)) + if (file->f_flags & O_NONBLOCK && !is_initialized(wc)) { return -EAGAIN; } else { - while (!test_bit(READY, &wc->bit_flags)) { + while (!is_initialized(wc)) { if (fatal_signal_pending(current)) return -EIO; msleep_interruptible(250); @@ -1301,6 +1406,8 @@ static const char *t1xxp_echocan_name(const struct dahdi_chan *chan) struct t1 *wc = chan->pvt; if (wc->vpmadt032) return vpmadt032_name; + else if (wc->vpmoct) + return vpmoct_name; return NULL; } @@ -1312,28 +1419,39 @@ static int t1xxp_echocan_create(struct dahdi_chan *chan, struct t1 *wc = chan->pvt; enum adt_companding comp; - if (!vpmsupport || !wc->vpmadt032 || - !test_bit(VPM150M_ACTIVE, &wc->ctlreg)) + if (!vpmsupport || !test_bit(VPM150M_ACTIVE, &wc->ctlreg)) return -ENODEV; - *ec = wc->ec[chan->chanpos - 1]; - (*ec)->ops = &vpm150m_ec_ops; - (*ec)->features = vpm150m_ec_features; - - comp = (DAHDI_LAW_ALAW == chan->span->deflaw) ? - ADT_COMP_ALAW : ADT_COMP_ULAW; - - return vpmadt032_echocan_create(wc->vpmadt032, chan->chanpos - 1, - comp, ecp, p); + if (wc->vpmadt032) { + *ec = wc->ec[chan->chanpos - 1]; + (*ec)->ops = &vpm150m_ec_ops; + (*ec)->features = vpm150m_ec_features; + + comp = (DAHDI_LAW_ALAW == chan->span->deflaw) ? + ADT_COMP_ALAW : ADT_COMP_ULAW; + + return vpmadt032_echocan_create(wc->vpmadt032, chan->chanpos-1, + comp, ecp, p); + } else if (wc->vpmoct) { + /* Hookup legacy callbacks */ + *ec = wc->ec[chan->chanpos - 1]; + (*ec)->ops = &vpm150m_ec_ops; + (*ec)->features = vpm150m_ec_features; + + return vpmoct_echocan_create(wc->vpmoct, chan->chanpos-1, + chan->span->deflaw); + } else { + return -ENODEV; + } } static void echocan_free(struct dahdi_chan *chan, struct dahdi_echocan_state *ec) { struct t1 *wc = chan->pvt; - if (!wc->vpmadt032) - return; - - vpmadt032_echocan_free(wc->vpmadt032, chan->chanpos - 1, ec); + if (wc->vpmadt032) + vpmadt032_echocan_free(wc->vpmadt032, chan->chanpos - 1, ec); + else if (wc->vpmoct) + vpmoct_echocan_free(wc->vpmoct, chan->chanpos - 1); } static void @@ -1421,7 +1539,7 @@ static void vpm_load_func(struct work_struct *work) set_bit(0, &wc->ctlreg); } - set_bit(READY, &wc->bit_flags); + wc->initialized--; kfree(w); } @@ -1488,7 +1606,7 @@ static int check_and_load_vpm(struct t1 *wc) res = vpmadt032_test(vpm, &wc->vb); if (-ENODEV == res) { - struct vpmadt032 *vpm = wc->vpmadt032; + struct vpmoct *vpmoct; /* There does not appear to be a VPMADT032 installed. */ clear_bit(VPM150M_ACTIVE, &wc->ctlreg); @@ -1496,6 +1614,30 @@ static int check_and_load_vpm(struct t1 *wc) wc->vpmadt032 = NULL; spin_unlock_irqrestore(&wc->reglock, flags); vpmadt032_free(vpm); + + /* Check for vpmoct */ + vpmoct = vpmoct_alloc(); + if (!vpmoct) + return -ENOMEM; + + vpmoct->dev = &wc->vb.pdev->dev; + + spin_lock_irqsave(&wc->reglock, flags); + wc->vpmoct = vpmoct; + spin_unlock_irqrestore(&wc->reglock, flags); + + res = vpmoct_init(wc->vpmoct); + if (res) { + dev_info(&wc->vb.pdev->dev, + "Unable to initialize vpmoct module\n"); + spin_lock_irqsave(&wc->reglock, flags); + wc->vpmoct = NULL; + spin_unlock_irqrestore(&wc->reglock, flags); + vpmoct_free(vpmoct); + } else { + set_bit(VPM150M_ACTIVE, &wc->ctlreg); + } + return res; } @@ -1561,10 +1703,10 @@ t1xxp_spanconfig(struct file *file, struct dahdi_span *span, int i; if (file->f_flags & O_NONBLOCK) { - if (!test_bit(READY, &wc->bit_flags)) + if (!is_initialized(wc)) return -EAGAIN; } else { - while (!test_bit(READY, &wc->bit_flags)) { + while (!is_initialized(wc)) { if (fatal_signal_pending(current)) return -EIO; msleep_interruptible(250); @@ -1591,6 +1733,26 @@ t1xxp_spanconfig(struct file *file, struct dahdi_span *span, return 0; } +static int t1xxp_enable_hw_preechocan(struct dahdi_chan *chan) +{ + struct t1 *wc = chan->pvt; + + if (!wc->vpmoct) + return 0; + + return vpmoct_preecho_enable(wc->vpmoct, chan->chanpos - 1); +} + +static void t1xxp_disable_hw_preechocan(struct dahdi_chan *chan) +{ + struct t1 *wc = chan->pvt; + + if (!wc->vpmoct) + return; + + vpmoct_preecho_disable(wc->vpmoct, chan->chanpos - 1); +} + static const struct dahdi_span_ops t1_span_ops = { .owner = THIS_MODULE, .spanconfig = t1xxp_spanconfig, @@ -1600,6 +1762,8 @@ static const struct dahdi_span_ops t1_span_ops = { .maint = t1xxp_maint, .ioctl = t1xxp_ioctl, #ifdef VPM_SUPPORT + .enable_hw_preechocan = t1xxp_enable_hw_preechocan, + .disable_hw_preechocan = t1xxp_disable_hw_preechocan, .echocan_create = t1xxp_echocan_create, .echocan_name = t1xxp_echocan_name, #endif @@ -1987,8 +2151,11 @@ static inline void t1_transmitprep(struct t1 *wc, u8 *sframe) cmd_dequeue(wc, eframe, x, y); #ifdef VPM_SUPPORT - if (wc->vpmadt032) + if (wc->vpmadt032) { cmd_dequeue_vpmadt032(wc, eframe); + } else if (wc->vpmoct) { + cmd_dequeue_vpmoct(wc, eframe); + } #endif if (x < DAHDI_CHUNKSIZE - 1) { @@ -2032,6 +2199,21 @@ static void extract_tdm_data(struct t1 *wc, const u8 *const sframe) chanchunk[6] = sframe[(i+1)*2 + (EFRAME_SIZE + EFRAME_GAP)*6]; chanchunk[7] = sframe[(i+1)*2 + (EFRAME_SIZE + EFRAME_GAP)*7]; } + + /* Pre-echo with the vpmoct overwrites the 24th timeslot with the + * specified channel's pre-echo audio stream. This timeslot is unused + * by the te12xp */ + if (wc->vpmoct && wc->vpmoct->preecho_enabled) { + chanchunk = &wc->vpmoct->preecho_buf[0]; + chanchunk[0] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*0]; + chanchunk[1] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*1]; + chanchunk[2] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*2]; + chanchunk[3] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*3]; + chanchunk[4] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*4]; + chanchunk[5] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*5]; + chanchunk[6] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*6]; + chanchunk[7] = sframe[23 + (EFRAME_SIZE + EFRAME_GAP)*7]; + } } static inline void t1_receiveprep(struct t1 *wc, const u8* sframe) @@ -2066,6 +2248,8 @@ static inline void t1_receiveprep(struct t1 *wc, const u8* sframe) #ifdef VPM_SUPPORT if (wc->vpmadt032) cmd_decipher_vpmadt032(wc, eframe); + else if (wc->vpmoct) + cmd_decipher_vpmoct(wc, eframe); #endif eframe += (EFRAME_SIZE + EFRAME_GAP); } @@ -2074,21 +2258,29 @@ static inline void t1_receiveprep(struct t1 *wc, const u8* sframe) /* echo cancel */ if (likely(test_bit(INITIALIZED, &wc->bit_flags))) { -#ifdef CONFIG_VOICEBUS_ECREFERENCE - unsigned char buffer[DAHDI_CHUNKSIZE]; for (x = 0; x < wc->span.channels; x++) { + struct dahdi_chan *const c = wc->chans[x]; +#ifdef CONFIG_VOICEBUS_ECREFERENCE + unsigned char buffer[DAHDI_CHUNKSIZE]; __dahdi_fifo_get(wc->ec_reference[x], buffer, ARRAY_SIZE(buffer)); - _dahdi_ec_chunk(wc->chans[x], wc->chans[x]->readchunk, + _dahdi_ec_chunk(c, c->readchunk, buffer); - } #else - for (x = 0; x < wc->span.channels; x++) { - _dahdi_ec_chunk(wc->chans[x], wc->chans[x]->readchunk, wc->ec_chunk2[x]); - memcpy(wc->ec_chunk2[x], wc->ec_chunk1[x], - DAHDI_CHUNKSIZE); - memcpy(wc->ec_chunk1[x], wc->chans[x]->writechunk, - DAHDI_CHUNKSIZE); + if ((wc->vpmoct) && + (c->chanpos-1 == wc->vpmoct->preecho_timeslot) && + (wc->vpmoct->preecho_enabled)) { + __dahdi_ec_chunk(c, c->readchunk, + wc->vpmoct->preecho_buf, + c->writechunk); + } else { + _dahdi_ec_chunk(c, c->readchunk, + wc->ec_chunk2[x]); + memcpy(wc->ec_chunk2[x], wc->ec_chunk1[x], + DAHDI_CHUNKSIZE); + memcpy(wc->ec_chunk1[x], c->writechunk, + DAHDI_CHUNKSIZE); + } } #endif _dahdi_receive(&wc->span); @@ -2148,7 +2340,7 @@ static void vpm_check_func(struct work_struct *work) /* If there is a failed VPM module, do not block dahdi_cfg * indefinitely. */ if (++wc->vpm_check_count > MAX_CHECKS) { - set_bit(READY, &wc->bit_flags); + wc->initialized--; wc->vpm_check = MAX_JIFFY_OFFSET; t1_info(wc, "Disabling VPMADT032 Checking.\n"); return; @@ -2203,7 +2395,7 @@ static void vpm_check_func(struct work_struct *work) set_bit(VPM150M_ACTIVE, &wc->ctlreg); t1_info(wc, "VPMADT032 is reenabled.\n"); wc->vpm_check = jiffies + HZ*5; - set_bit(READY, &wc->bit_flags); + wc->initialized--; return; } @@ -2352,6 +2544,8 @@ static int __devinit te12xp_init_one(struct pci_dev *pdev, const struct pci_devi if (!wc) return -ENOMEM; + wc->initialized = 1; + ifaces[index] = wc; wc->ledstate = -1; @@ -2465,10 +2659,11 @@ static int __devinit te12xp_init_one(struct pci_dev *pdev, const struct pci_devi t1_info(wc, "Found a %s\n", wc->variety); voicebus_unlock_latency(&wc->vb); - /* If there is VPMADT032 module attached to this device, it will - * signal ready after the channels are configured and ready for use. */ - if (!wc->vpmadt032) - set_bit(READY, &wc->bit_flags); + /* If there is VPMADT032 or VPMOCT032 module attached to this device, + * it will signal ready after the channels are configured and ready + * for use. */ + if (!wc->vpmadt032 && !wc->vpmoct) + wc->initialized--; return 0; } @@ -2503,6 +2698,7 @@ static void __devexit te12xp_remove_one(struct pci_dev *pdev) clear_bit(VPM150M_ACTIVE, &vpm->control); vpmadt032_free(vpm); } + #endif t1_info(wc, "Freed a Wildcard TE12xP.\n"); diff --git a/drivers/dahdi/wcte12xp/wcte12xp.h b/drivers/dahdi/wcte12xp/wcte12xp.h index 66f3323..a927e07 100644 --- a/drivers/dahdi/wcte12xp/wcte12xp.h +++ b/drivers/dahdi/wcte12xp/wcte12xp.h @@ -129,6 +129,7 @@ struct t1 { struct voicebus vb; atomic_t txints; struct vpmadt032 *vpmadt032; + struct vpmoct *vpmoct; unsigned long vpm_check; struct work_struct vpm_check_work; @@ -138,6 +139,7 @@ struct t1 { struct timer_list timer; struct work_struct timer_work; struct workqueue_struct *wq; + bool initialized; /* 0 when entire card is ready to go */ }; #define t1_info(t1, format, arg...) \ |