/* * Astfin Team - Zarlink Echo Cancellation Module * * * Copyright (C) 2008, Astfin / uCpbx Ltd. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program 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 program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include /* printk() */ #include /* kmalloc() */ #include /* everything... */ #include /* error codes */ #include /* size_t */ #include #include /* O_ACCMODE */ #include /* cli(), *_flags */ #include /* copy_from/to_user */ #include /* for gpio_xxx(...) */ #include /* for udelay(...) */ #include "ec_zlxxxxx.h" #include "getopt.h" #ifdef LINUX26 #include #endif MODULE_DESCRIPTION("Zarlink LEC Module"); MODULE_AUTHOR("Astfin/uCpbx"); #ifdef MODULE_LICENSE MODULE_LICENSE("GPL"); #endif EXPORT_SYMBOL(zl38065_echocan); EXPORT_SYMBOL(echocan_tail_length); EXPORT_SYMBOL(echocan_setEnc); EXPORT_SYMBOL(echocan_setGains); EXPORT_SYMBOL(echocan_set_uProfile); #define EC_MODULE_NAME "zarlink_LEC_chip: " #define DEBUG(args...) printk(KERN_DEBUG EC_MODULE_NAME args); #define ERROR(args...) printk(KERN_ERR EC_MODULE_NAME args); #define INFO(args...) printk(KERN_INFO EC_MODULE_NAME args); /* Global variables of the driver */ /* Major number */ const int EchoCanceler_major = 240; //?????? const char *cECdrvName = "ec_module"; /* Proc FS specific */ #define PROCFS_NAME "lec_zarlink" static struct proc_dir_entry *EchoCanceller_Proc; int EchoCanceller_proc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data); int EchoCanceller_proc_write(struct file *file, const char *buf, unsigned long count, void *data); //**************************************************************************** //Debug mede static int debug = 0; //By default DEBUG is off/DEBUG //* Our variables related to echo canceler and set to defaults. static EC_STAT ec_stat = EC_NA; //* 16 is for ZL38065. 8 is for ZL50235. It will be set at init while checking //* the type of EC and will further indicate this type. static int cfgNumChanGroups = 0; static int cfgNumChan = 0; //Default encoding should be set to A-Law static int encoding = 2; //Double tail should be set to no by default (PR1-Appliance) static int doubletail = 0; //**************************************************************************** /* Declaration of EchoCanceller functions */ int EchoCanceler_open(struct inode *inode, struct file *filp); int EchoCanceler_release(struct inode *inode, struct file *filp); //ssize_t EchoCanceler_read(struct file *filp, char *buf, size_t count, loff_t *f_pos); //ssize_t EchoCanceler_write(struct file *filp, char *buf, size_t count, loff_t *f_pos); void EchoCanceler_exit(void); int EchoCanceler_init(void); int EchoCanceler_ioctl( struct inode *inodep, struct file *filep, unsigned int cmd, unsigned long arg); /* Structure that declares the usual file */ /* access functions */ struct file_operations EchoCanceler_fops = { //* gcc & C99 style initialization - more portability ? // .read EchoCanceler_read, // .write EchoCanceler_write, .open = EchoCanceler_open, .release = EchoCanceler_release, .ioctl = EchoCanceler_ioctl }; typedef unsigned char zl_reg_type; zl_reg_type get_reg(int channel, int reg) { int chB = channel%2; int ch_group = channel/2; int group_offs = ch_group * EC_GROUP_OFFSET; zl_reg_type regv; int adrs = reg + group_offs + chB*ECB_OFFSET; regv = readb( (regA_t)(ECREGADDR(adrs)) ); return regv; } enum ec_status_row_type{ EC_ST_ROW_CHANNEL=0, EC_ST_ROW_BYPASS, EC_ST_ROW_ACTIVE, EC_ST_ROW_NB, EC_ST_ROW_TD, EC_ST_ROW_TDG, EC_ST_ROW_DTDET, EC_ST_ROW_RPEAK, EC_ST_ROW_SPEAK, COUNT_OF_STATUS_ROW }; #define TEST_FLAG(reg, flag) (reg & flag) int strlcat_status_table(char *ptable, size_t len_of_table) { char *first_col[COUNT_OF_STATUS_ROW] = { "Channels ", "Bypass the EC ", "Active (Rin>LP threshold)", "Narrow-band signal on Rin", "2100Hz tone ", "2100Hz tone bypass the EC", "Double-talk ", "Rin Peak % ", "Sin Peak % ", }; int i; u16 signal_level; int ch; char temp[20]; zl_reg_type flags_status_reg[] = { EC_ACTIVE, EC_NB, EC_TD, EC_TDG, EC_DTDet }; zl_reg_type regv; for (i = EC_ST_ROW_CHANNEL; i < COUNT_OF_STATUS_ROW; i++){ strlcat( ptable, first_col[i], len_of_table); switch(i){ case EC_ST_ROW_CHANNEL: for (ch = 0; ch < cfgNumChan; ch++ ) { snprintf( temp, sizeof(temp), " %02u", ch+1); strlcat( ptable, temp, len_of_table); } strlcat( ptable, "\n", len_of_table); break; case EC_ST_ROW_BYPASS: for (ch = 0; ch < cfgNumChan; ch++ ) { regv = get_reg( ch, EC_CONTROL_REG_1 ); if( TEST_FLAG(regv, EC_Bypass) ){ strlcat( ptable, " *", len_of_table); }else{ strlcat( ptable, " -", len_of_table); } } strlcat( ptable, "\n", len_of_table); break; case EC_ST_ROW_ACTIVE: case EC_ST_ROW_NB: case EC_ST_ROW_TD: case EC_ST_ROW_TDG: case EC_ST_ROW_DTDET: for (ch = 0; ch < cfgNumChan; ch++ ) { regv = get_reg( ch, EC_CONTROL_REG_1 ); if( TEST_FLAG(regv, EC_Bypass) ){ strlcat( ptable, " ?", len_of_table); }else{ regv = get_reg( ch, EC_STATUS_REG ); if( TEST_FLAG(regv, flags_status_reg[i-EC_ST_ROW_ACTIVE]) ){ strlcat( ptable, " *", len_of_table); }else{ strlcat( ptable, " -", len_of_table); } } } strlcat( ptable, "\n", len_of_table); break; case EC_ST_ROW_RPEAK: case EC_ST_ROW_SPEAK: for (ch = 0; ch < cfgNumChan; ch++ ) { regv = get_reg( ch, EC_CONTROL_REG_1 ); if( TEST_FLAG(regv, EC_Bypass) ){ strlcat( ptable, " ?", len_of_table); }else{ if( i == EC_ST_ROW_RPEAK ){ signal_level = get_reg( ch, EC_RIN_PEAK_DETECT_REG_L ); signal_level |= (u16)get_reg( ch, EC_RIN_PEAK_DETECT_REG_H )<<8; }else{ signal_level = get_reg( ch, EC_SIN_PEAK_DETECT_REG_L ); signal_level |= (u16)get_reg( ch, EC_SIN_PEAK_DETECT_REG_H )<<8; } snprintf( temp, sizeof(temp), " %02u", ((signal_level>>6)*100)/255 ); strlcat( ptable, temp, len_of_table); } } strlcat( ptable, "\n", len_of_table); break; } } strlcat( ptable, " Legend: (*)Active flag (-)Not active flag (?)Unknown value\n", len_of_table); return strlen(ptable); } #define COUNT_OF_CTRL_ROW 6 int strlcat_ctrl_reg_table(char *ptable, size_t len_of_table) { char *first_col[COUNT_OF_CTRL_ROW] = { "Channels ", "CONTROL_REG1 ", "CONTROL_REG2 ", "CONTROL_REG3 ", "CONTROL_REG4 ", "STATUS " }; int i; int ch; char temp[20]; int reg_table[] = { EC_CONTROL_REG_1, EC_CONTROL_REG_2, EC_CONTROL_REG_3, EC_CONTROL_REG_4, EC_STATUS_REG }; zl_reg_type regv; for (i = 0; i < COUNT_OF_CTRL_ROW; i++){ strlcat( ptable, first_col[i], len_of_table); switch(i){ case 0: for (ch = 0; ch < cfgNumChan; ch++ ) { snprintf( temp, sizeof(temp), " %02u", ch+1); strlcat( ptable, temp, len_of_table); } strlcat( ptable, "\n", len_of_table); break; case 1: case 2: case 3: case 4: case 5: for (ch = 0; ch < cfgNumChan; ch++ ) { regv = get_reg( ch, reg_table[i-1] ); snprintf( temp, sizeof(temp), " %02X", regv); strlcat( ptable, temp, len_of_table); } strlcat( ptable, "\n", len_of_table); break; } } return strlen(ptable); } int EchoCanceller_proc_read(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int len, res; len = sprintf(buf, "Number of available channels at 64 ms..: %d\n" "Encoding is set to.....................: %s\n" "Maximum tail length....................: %d ms\n", cfgNumChanGroups * 2, encoding == 2 ? "a-law":"u-law", #ifndef CHANNELS_8 doubletail ? 128:64 #else 64 #endif ); res = strlcat_status_table(buf, count); if( res == 0 ){ strlcat( buf, "ERROR:Can not read status", count); } if( debug ){ res = strlcat_ctrl_reg_table(buf, count); if( res == 0 ){ strlcat( buf, "ERROR:Can not read control register", count); } } *eof=1; len = strlen( buf ); return len; } static const struct ncp_option opts[] = { { "channel", OPT_INT, 'c' }, { "tail", OPT_INT, 't' }, { "help", OPT_NOPARAM, 'h' }, { NULL, 0, 0 } }; int parse_parameters(char *pbuf, unsigned long count){ int optval; char *optarg; unsigned long optint; int ret, i; int write_ec_tail = false; int channel = 0; int tail = 0; while ((optval = ncp_getopt("ec_module", &pbuf, opts, NULL, &optarg, &optint)) != 0) { ret = optval; if (ret < 0) goto err; switch (optval) { case 'c': DEBUG("channel: %lu\n", optint); channel = optint; break; case 't': DEBUG("tail: %lu\n", optint); tail = optint; write_ec_tail = true; break; case 'h': INFO("Example:\n"); INFO("\techo \"channel=4,tail=32\" > /proc/"PROCFS_NAME"\n"); INFO("Options:\n"); for(i=0; opts[i].name != NULL; i++){ INFO("\t%s %s\n", opts[i].name, (opts[i].has_arg & OPT_INT) ? "(integer argument)" : ""); } break; } } if( write_ec_tail ){ echocan_tail_length(channel-1, tail); } return 0; err: return ret; } #define PROCFS_MAX_SIZE 500 int EchoCanceller_proc_write(struct file *file, const char *user_buf, unsigned long count, void *data){ char write_buf[PROCFS_MAX_SIZE]; unsigned long write_buf_size = count; char *pstr; if (write_buf_size > PROCFS_MAX_SIZE ) { write_buf_size = PROCFS_MAX_SIZE; } /* write data to the buffer */ if ( copy_from_user(write_buf, user_buf, write_buf_size) ) { return -EFAULT; } pstr = memchr(write_buf, '\n', write_buf_size); if (pstr) *pstr = 0; if(debug){ DEBUG("Proc write: \"%s\"\n", write_buf); DEBUG("write_buf_size: %lu\n", write_buf_size); } parse_parameters( write_buf, write_buf_size ); return write_buf_size; } void EchoCanceler_softreset(void){ int i; //* Make a sw reset for (i = 0; i < cfgNumChanGroups; i++ ) { writeb(EC_PWUP, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); } //* Here should wait 250us udelay(250/*us*/); //* Next disable all. for (i = 0; i < cfgNumChanGroups; i++ ) { writeb(0, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); } //* And here should wait 500us for PLL to relock. udelay(500/*us*/); //* Next disable interrupts and enable Rout & Sout. writeb(EC_ODE | EC_MIRQ | EC_MTDBI | EC_MTDAI | EC_Law_A | EC_PWUP | EC_Format, ECREGADDR(EC_MAIN_CTRL_REG_0)); for (i = 1; i < cfgNumChanGroups; i++ ) { writeb(EC_MTDBI | EC_MTDAI | EC_Law_A | EC_PWUP | EC_Format, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); } //* Wait another 250us for ECA & ECB to execute their init procedures. udelay(250/*us*/); echocan_setEnc(encoding); } int EchoCanceler_open(struct inode *inode, struct file *filp) { //* Check first if device is not busy. if (ec_stat == EC_BUSY) { ERROR("Device is busy !!\n"); return FAILURE; } if (ec_stat == EC_NA) { ERROR("Device is not available !!\n"); return FAILURE; } ec_stat = EC_BUSY; /* Success */ return SUCCESS; } int EchoCanceler_release(struct inode *inode, struct file *filp) { //*** Probably put in powerdown here... ec_stat = EC_FREE; /* Success */ return 0; } //* Loading module (# insmod xxx.ko) int EchoCanceler_init(void) { int result; int gpioRetCode; ec_stat = EC_NA; cfgNumChanGroups = 0; /* At this point (after POR) all ECs are powered down, so it seems no special reset or power down procedure neccessary. And also Sout & Rout are in hihg impedance. */ //* Request GPIOs for the EC driver. gpioRetCode = gpio_request(GPIO_EC_PRESENT, cECdrvName); if (gpioRetCode != 0) { ERROR("unsuccessful GPIO request.\n"); return gpioRetCode; } gpioRetCode = gpio_request(GPIO_EC_32, cECdrvName); if (gpioRetCode != 0) { ERROR("unsuccessful GPIO request.\n"); return gpioRetCode; } gpioRetCode = gpio_direction_input(GPIO_EC_PRESENT); if (gpioRetCode != 0) { ERROR("unsuccessful GPIO request.\n"); return gpioRetCode; } gpioRetCode = gpio_direction_input(GPIO_EC_32); if (gpioRetCode != 0) { ERROR("unsuccessful GPIO request.\n"); return gpioRetCode; } //* Check hw configuration for EC. result = gpio_get_value(GPIO_EC_PRESENT); if (result) { ERROR("not present.\n"); return -1; //* Above stat set to EC_NA & Num of Groups set to 0, DPN not loading the driver if hardware doesn't exist !! } ec_stat = EC_FREE; //result = gpio_get_value(GPIO_EC_32); DPN we have many deployed e/c modules not programmed properly so we don't use PG2 #ifdef CHANNELS_4 cfgNumChanGroups = EC4_NUMOFGROUPS; INFO("ZL50233 chip detected.\n"); #elif CHANNELS_8 cfgNumChanGroups = EC8_NUMOFGROUPS; INFO("ZL50234 chip detected.\n"); #elif CHANNELS_16 cfgNumChanGroups = EC16_NUMOFGROUPS; INFO("ZL50235 chip detected.\n"); #elif CHANNELS_32 cfgNumChanGroups = EC32_NUMOFGROUPS; INFO("ZL38065/ZL50232 chip detected.\n"); #endif cfgNumChan = cfgNumChanGroups * 2; /* Registering device */ result = register_chrdev(EchoCanceler_major, "EchoCanceler", &EchoCanceler_fops); if (result < 0) { ERROR("cannot obtain major number %d\n", EchoCanceler_major); return result; } /* create the /proc file */ EchoCanceller_Proc = create_proc_entry(PROCFS_NAME, S_IFREG | S_IRUGO, NULL); if (EchoCanceller_Proc == NULL) { remove_proc_entry(PROCFS_NAME, &proc_root); printk("Error: Could not initialize /proc/%s\n", PROCFS_NAME); return -ENOMEM; } EchoCanceller_Proc->read_proc = EchoCanceller_proc_read; EchoCanceller_Proc->write_proc = EchoCanceller_proc_write; EchoCanceller_Proc->owner = THIS_MODULE; EchoCanceller_Proc->mode = S_IFREG | S_IRUGO; EchoCanceller_Proc->uid = 0; EchoCanceller_Proc->gid = 0; INFO("Inserting EchoCanceler module. %d channels\n",2*cfgNumChanGroups); udelay(500); EchoCanceler_softreset(); if(debug) DEBUG("Initial encoding set to %d\n",encoding); return 0; } //* Unloading module (# rmmod xxx) void EchoCanceler_exit(void) { /* Removing the proc entry */ remove_proc_entry(PROCFS_NAME, &proc_root); /* Freeing the major number */ unregister_chrdev(EchoCanceler_major, "EchoCanceler"); INFO("Removing EchoCanceler module\n"); } int EchoCanceler_ioctl( struct inode *inodep, struct file *filep, unsigned int cmd, unsigned long arg) { switch(cmd) { case ECCMD_NOP: //* Just to have one... break; default: ERROR("invalid ioctl command 0x%X\n", cmd); return FAILURE; break; } return SUCCESS; } //**************************************************************************** //* Functions to export. // Case 64ms - echo celceller alocation for the TDM (here 0, 1 ... are groups) // | | | | | // echo0A echo0B echo1A echo1B .... // // Case 128ms - echo celceller alocation for the TDM (here 0, 1 ... are groups) // | | | | | // echo0A+echo0B HiZ echo1A+echo1B HiZ // // // ECRETCODE_t echocan_tail_length(int channel, int tail_ms) { unsigned short regv; //* Byte ? unsigned int offs; int chGroup = channel/2; int chAB = channel%2; spinlock_t lock = SPIN_LOCK_UNLOCKED; unsigned long flags; if (debug) DEBUG("Echo canceller at channel %d, tail set to %d [ms]\n", channel, tail_ms); if (channel < 0 || channel >= 2*cfgNumChanGroups) { ERROR("invalid channel number %d\n", channel); return FAILURE; } switch(tail_ms) { case 0: //* If we would like to bypass spin_lock_irqsave(&lock, flags); offs = chGroup * EC_GROUP_OFFSET; regv = readb( (regA_t)(ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)) ); writeb(regv & (~EC_ExtDl), //* Clear ExtDelay in echo A ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)); regv = readb( (regA_t)(ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)) ); //udelay(1); //dpn: In order to correctly write to Control Register 1 and 2 of ECB, writeb(regv | EC_Bypass, //* Set Bypass of the appropriate echo ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)); spin_unlock_irqrestore(&lock, flags); break; case 64: //* If normal delay is required. spin_lock_irqsave(&lock, flags); offs = chGroup * EC_GROUP_OFFSET; regv = readb( (regA_t)(ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)) ); writeb(regv & (~EC_ExtDl), //* Clear ExtDelay in echo A ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)); regv = readb( (regA_t)(ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)) ); writeb(regv & (~EC_Bypass), //* Clear Bypass of the appropriate echo ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)); udelay(1); //dpn: In order to correctly write to Control Register 1 and 2 of ECB, //it is necessary to write the data twice to the register, one immediately //after another. The two writes must be separated by at least 350 ns and //no more than 20 us. writeb(regv & (~EC_Bypass), //* Clear Bypass of the appropriate echo ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)); spin_unlock_irqrestore(&lock, flags); break; #ifndef CHANNELS_8 if (doubletail) { case 128: //* If extended delay is required init both A & B. spin_lock_irqsave(&lock, flags); offs = chGroup * EC_GROUP_OFFSET; regv = readb( (regA_t)(ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)) ); regv = regv | EC_ExtDl; //Set ExtDelay in echo A regv = regv & (~EC_Bypass); //Clear bypass of A echo writeb(regv, ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)); regv = readb( (regA_t)(ECREGADDR(offs + ECB_OFFSET + EC_CONTROL_REG_1)) ); writeb(regv & (~EC_Bypass), ECREGADDR(offs + ECB_OFFSET + EC_CONTROL_REG_1)); //Clear bypass of echo B udelay(1); //dpn: In order to correctly write to Control Register 1 and 2 of ECB, //it is necessary to write the data twice to the register, one immediately //after another. The two writes must be separated by at least 350 ns and //no more than 20 us. writeb(regv & (~EC_Bypass), ECREGADDR(offs + ECB_OFFSET + EC_CONTROL_REG_1)); //Clear bypass of echo B spin_unlock_irqrestore(&lock, flags); break; } #endif default: ERROR("invalid tail(ms) set %d.\nWe will run in bypass mode\n", tail_ms); //* If we would like to bypass spin_lock_irqsave(&lock, flags); offs = chGroup * EC_GROUP_OFFSET; regv = readb( (regA_t)(ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)) ); writeb(regv & (~EC_ExtDl), //* Clear ExtDelay in echo A ECREGADDR(offs + ECA_OFFSET + EC_CONTROL_REG_1)); regv = readb( (regA_t)(ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)) ); //udelay(1); //dpn: In order to correctly write to Control Register 1 and 2 of ECB, writeb(regv | EC_Bypass, //* Set Bypass of the appropriate echo ECREGADDR(offs + chAB * ECB_OFFSET + EC_CONTROL_REG_1)); spin_unlock_irqrestore(&lock, flags); break; } return SUCCESS; } ECRETCODE_t zl38065_echocan(int channel, int tail_ms) { return echocan_tail_length(channel, tail_ms); } ECRETCODE_t echocan_setEnc(ECLAW_t code) { unsigned short regv; //* Byte ? int i; switch(code) { case LINEAR: for (i = 0; i < cfgNumChanGroups; i++ ) { regv = readb( (regA_t)(ECREGADDR(EC_MAIN_CTRL_REG_0 + i)) ); regv = regv & (~EC_Format); //* Clear bits. writeb(regv, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); //* Write value. } if (debug) DEBUG("Encoding updated to LINEAR.\n"); break; case ULAW: for (i = 0; i < cfgNumChanGroups; i++ ) { regv = readb( (regA_t)(ECREGADDR(EC_MAIN_CTRL_REG_0 + i)) ); regv = regv & (~EC_Law_A); //* Clear bits. regv = regv | (EC_Format | EC_Law_u); //* Set appropriate. writeb(regv, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); //* Write value. } if (debug) DEBUG("Encoding updated to u-Law.\n"); break; case ALAW: for (i = 0; i < cfgNumChanGroups; i++ ) { regv = readb( (regA_t)(ECREGADDR(EC_MAIN_CTRL_REG_0 + i)) ); regv = regv | (EC_Format | EC_Law_A); //* Set appropriate. writeb(regv, ECREGADDR(EC_MAIN_CTRL_REG_0 + i)); //* Write value. } if (debug) DEBUG("Encoding updated to a-Law.\n"); break; default: ERROR("invalid encoding %d set.\n", code); return FAILURE; } return SUCCESS; } ECRETCODE_t echocan_setGains(int channel, int gainRin, int gainRout, int gainSin, int gainSout) { //* Since PAD bit in EC_CONTROL_REG_1 is left cleared here is control to the gains. unsigned short regv = 0; if (channel < 0 || channel >= 2*cfgNumChanGroups) { ERROR("invalid channel number %d\n", channel); return FAILURE; } switch(gainRin) { case 9: regv += EC_Rin_p9dB; break; case 6: regv += EC_Rin_p6dB; break; case 3: regv += EC_Rin_p3dB; break; case 0: regv += EC_Rin_0dB; break; case -3: regv += EC_Rin_m3dB; break; case -6: regv += EC_Rin_m6dB; break; case -9: regv += EC_Rin_m9dB; break; case -12: regv += EC_Rin_m12dB; break; default: ERROR("invalid Rin gain %d set.\n", gainRin); return FAILURE; } switch(gainRout) { case 9: regv += EC_Rout_p9dB; break; case 6: regv += EC_Rout_p6dB; break; case 3: regv += EC_Rout_p3dB; break; case 0: regv += EC_Rout_0dB; break; case -3: regv += EC_Rout_m3dB; break; case -6: regv += EC_Rout_m6dB; break; case -9: regv += EC_Rout_m9dB; break; case -12: regv += EC_Rout_m12dB; break; default: ERROR("invalid Rout gain %d set.\n", gainRout); return FAILURE; } writeb( regv, ECREGADDR(channel*ECB_OFFSET + EC_GAIN_PAD_CONTROL_H) ); regv = 0; switch(gainSin) { case 9: regv += EC_Sin_p9dB; break; case 6: regv += EC_Sin_p6dB; break; case 3: regv += EC_Sin_p3dB; break; case 0: regv += EC_Sin_0dB; break; case -3: regv += EC_Sin_m3dB; break; case -6: regv += EC_Sin_m6dB; break; case -9: regv += EC_Sin_m9dB; break; case -12: regv += EC_Sin_m12dB; break; default: ERROR("invalid Sin gain %d set.\n", gainSin); return FAILURE; } switch(gainSout) { case 9: regv += EC_Sout_p9dB; break; case 6: regv += EC_Sout_p6dB; break; case 3: regv += EC_Sout_p3dB; break; case 0: regv += EC_Sout_0dB; break; case -3: regv += EC_Sout_m3dB; break; case -6: regv += EC_Sout_m6dB; break; case -9: regv += EC_Sout_m9dB; break; case -12: regv += EC_Sout_m12dB; break; default: ERROR("invalid Sout gain %d set.\n", gainSout); return FAILURE; } writeb( regv, ECREGADDR(channel*ECB_OFFSET + EC_GAIN_PAD_CONTROL_L) ); return SUCCESS; } ECRETCODE_t echocan_set_uProfile(int channel, USHORT_t iFlatDelay, USHORT_t iDecayStepSize, USHORT_t iDecayStepNum) { if (channel < 0 || channel >= 2*cfgNumChanGroups) { ERROR("invalid channel number %d.\n", channel); return FAILURE; } if (iFlatDelay > 7) { ERROR("invalid value for Flat Delay %d.\n", iFlatDelay); return FAILURE; } writeb( iFlatDelay, ECREGADDR(channel*ECB_OFFSET + EC_FLAT_DELAY_REG) ); writeb( iDecayStepSize, ECREGADDR(channel*ECB_OFFSET + EC_DECAY_STEP_SIZE_REG) ); writeb( iDecayStepNum, ECREGADDR(channel*ECB_OFFSET + EC_DECAY_STEP_NUMBER_REG) ); return SUCCESS; } ECRETCODE_t echocan_setNoiseControl(int channel) { //* x return SUCCESS; } //We can have fix encoding since we need to reinit zaptel to change between t1 and e1 anyway module_param(encoding, int, 0600); module_param(doubletail, int, 0600); module_param(debug, int, 0600); module_init(EchoCanceler_init); module_exit(EchoCanceler_exit);