bf3fe05dfb
This needs some more testing before it's on by default. If the card is otherwise functioning, these messages may be confusing to the user. If the card is not functioning, the driver can be reloaded with debug to check for this condition. Signed-off-by: Shaun Ruffell <sruffell@digium.com> git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9205 a0bf4364-ded3-4de4-8d8a-66a801d63aff
2740 lines
73 KiB
C
2740 lines
73 KiB
C
/*
|
|
* Written by Oron Peled <oron@actcom.co.il>
|
|
* Copyright (C) 2004-2006, Xorcom
|
|
*
|
|
* Parts derived from Cologne demo driver for the chip.
|
|
*
|
|
* 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 <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/delay.h>
|
|
#include "xpd.h"
|
|
#include "xproto.h"
|
|
#include "xpp_dahdi.h"
|
|
#include "card_pri.h"
|
|
#include "dahdi_debug.h"
|
|
#include "xpd.h"
|
|
#include "xbus-core.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
static DEF_PARM(int, debug, 0, 0644, "Print DBG statements"); /* must be before dahdi_debug.h */
|
|
static DEF_PARM(uint, poll_interval, 500, 0644, "Poll channel state interval in milliseconds (0 - disable)");
|
|
|
|
#define PRI_LINES_BITMASK BITMASK(31)
|
|
#define PRI_SIGCAP ( \
|
|
DAHDI_SIG_EM | \
|
|
DAHDI_SIG_CLEAR | \
|
|
DAHDI_SIG_FXSLS | \
|
|
DAHDI_SIG_FXSGS | \
|
|
DAHDI_SIG_FXSKS | \
|
|
DAHDI_SIG_HARDHDLC | \
|
|
DAHDI_SIG_MTP2 | \
|
|
DAHDI_SIG_FXOLS | \
|
|
DAHDI_SIG_FXOGS | \
|
|
DAHDI_SIG_FXOKS | \
|
|
DAHDI_SIG_CAS | \
|
|
DAHDI_SIG_EM_E1 | \
|
|
DAHDI_SIG_DACS_RBS \
|
|
)
|
|
|
|
static bool is_sigtype_dchan(int sigtype)
|
|
{
|
|
if((sigtype & DAHDI_SIG_HDLCRAW) == DAHDI_SIG_HDLCRAW)
|
|
return 1;
|
|
if((sigtype & DAHDI_SIG_HDLCFCS) == DAHDI_SIG_HDLCFCS)
|
|
return 1;
|
|
if((sigtype & DAHDI_SIG_HARDHDLC) == DAHDI_SIG_HARDHDLC)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_SLAVES 4 /* we have MUX of 4 clocks */
|
|
|
|
#define PRI_PORT(xpd) ((xpd)->addr.subunit)
|
|
#define CHAN_PER_REGS(p) (((p)->is_esf) ? 2 : 4)
|
|
|
|
/*---------------- PRI Protocol Commands ----------------------------------*/
|
|
|
|
static bool pri_packet_is_valid(xpacket_t *pack);
|
|
static void pri_packet_dump(const char *msg, xpacket_t *pack);
|
|
#ifdef OLD_PROC
|
|
static int proc_pri_info_read(char *page, char **start, off_t off, int count, int *eof, void *data);
|
|
static int proc_pri_info_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
|
|
#endif
|
|
static int pri_startup(struct dahdi_span *span);
|
|
static int pri_shutdown(struct dahdi_span *span);
|
|
static int pri_rbsbits(struct dahdi_chan *chan, int bits);
|
|
static int pri_lineconfig(xpd_t *xpd, int lineconfig);
|
|
static void send_idlebits(xpd_t *xpd, bool saveold);
|
|
|
|
#define PROC_REGISTER_FNAME "slics"
|
|
#ifdef OLD_PROC
|
|
#define PROC_PRI_INFO_FNAME "pri_info"
|
|
#endif
|
|
|
|
enum pri_protocol {
|
|
PRI_PROTO_0 = 0,
|
|
PRI_PROTO_E1 = 1,
|
|
PRI_PROTO_T1 = 2,
|
|
PRI_PROTO_J1 = 3
|
|
};
|
|
|
|
static const char *pri_protocol_name(enum pri_protocol pri_protocol)
|
|
{
|
|
static const char *protocol_names[] = {
|
|
[PRI_PROTO_0] = "??", /* unknown */
|
|
[PRI_PROTO_E1] = "E1",
|
|
[PRI_PROTO_T1] = "T1",
|
|
[PRI_PROTO_J1] = "J1"
|
|
};
|
|
return protocol_names[pri_protocol];
|
|
}
|
|
|
|
static int pri_num_channels(enum pri_protocol pri_protocol)
|
|
{
|
|
static int num_channels[] = {
|
|
[PRI_PROTO_0] = 0,
|
|
[PRI_PROTO_E1] = 31,
|
|
[PRI_PROTO_T1] = 24,
|
|
[PRI_PROTO_J1] = 0
|
|
};
|
|
return num_channels[pri_protocol];
|
|
}
|
|
|
|
static const char *type_name(enum pri_protocol pri_protocol)
|
|
{
|
|
static const char *names[4] = {
|
|
[PRI_PROTO_0] = "PRI-Unknown",
|
|
[PRI_PROTO_E1] = "E1",
|
|
[PRI_PROTO_T1] = "T1",
|
|
[PRI_PROTO_J1] = "J1"
|
|
};
|
|
|
|
return names[pri_protocol];
|
|
}
|
|
|
|
static int pri_linecompat(enum pri_protocol pri_protocol)
|
|
{
|
|
static const int linecompat[] = {
|
|
[PRI_PROTO_0] = 0,
|
|
[PRI_PROTO_E1] =
|
|
/* coding */
|
|
DAHDI_CONFIG_CCS |
|
|
DAHDI_CONFIG_CRC4 |
|
|
/* framing */
|
|
DAHDI_CONFIG_AMI | DAHDI_CONFIG_HDB3,
|
|
[PRI_PROTO_T1] =
|
|
/* coding */
|
|
DAHDI_CONFIG_D4 |
|
|
DAHDI_CONFIG_ESF |
|
|
/* framing */
|
|
DAHDI_CONFIG_AMI | DAHDI_CONFIG_B8ZS,
|
|
[PRI_PROTO_J1] = 0
|
|
};
|
|
|
|
DBG(GENERAL, "pri_linecompat: pri_protocol=%d\n", pri_protocol);
|
|
return linecompat[pri_protocol];
|
|
}
|
|
|
|
#define PRI_DCHAN_IDX(priv) ((priv)->dchan_num - 1)
|
|
|
|
enum pri_led_state {
|
|
PRI_LED_OFF = 0x0,
|
|
PRI_LED_ON = 0x1,
|
|
/*
|
|
* We blink by software from driver, so that
|
|
* if the driver malfunction that blink would stop.
|
|
*/
|
|
// PRI_LED_BLINK_SLOW = 0x2, /* 1/2 a second blink cycle */
|
|
// PRI_LED_BLINK_FAST = 0x3 /* 1/4 a second blink cycle */
|
|
};
|
|
|
|
enum pri_led_selectors {
|
|
BOTTOM_RED_LED = 0,
|
|
BOTTOM_GREEN_LED = 1,
|
|
TOP_RED_LED = 2,
|
|
TOP_GREEN_LED = 3,
|
|
};
|
|
|
|
#define NUM_LEDS 4
|
|
|
|
struct pri_leds {
|
|
byte state:2; /* enum pri_led_state */
|
|
byte led_sel:2; /* enum pri_led_selectors */
|
|
byte reserved:4;
|
|
};
|
|
|
|
#define REG_CCB1_T 0x2F /* Clear Channel Register 1 */
|
|
|
|
#define REG_FRS0 0x4C /* Framer Receive Status Register 0 */
|
|
#define REG_FRS0_T1_FSR BIT(0) /* T1 - Frame Search Restart Flag */
|
|
#define REG_FRS0_LMFA BIT(1) /* Loss of Multiframe Alignment */
|
|
#define REG_FRS0_E1_NMF BIT(2) /* E1 - No Multiframe Alignment Found */
|
|
#define REG_FRS0_RRA BIT(4) /* Receive Remote Alarm: T1-YELLOW-Alarm */
|
|
#define REG_FRS0_LFA BIT(5) /* Loss of Frame Alignment */
|
|
#define REG_FRS0_AIS BIT(6) /* Alarm Indication Signal: T1-BLUE-Alarm */
|
|
#define REG_FRS0_LOS BIT(7) /* Los Of Signal: T1-RED-Alarm */
|
|
|
|
#define REG_FRS1 0x4D /* Framer Receive Status Register 1 */
|
|
|
|
#define REG_LIM0 0x36
|
|
#define REG_LIM0_MAS BIT(0) /* Master Mode, DCO-R circuitry is frequency synchronized to the clock supplied by SYNC */
|
|
#define REG_LIM0_RTRS BIT(5) /*
|
|
* Receive Termination Resistance Selection:
|
|
* integrated resistor to create 75 Ohm termination (100 || 300 = 75)
|
|
* 0 = 100 Ohm
|
|
* 1 = 75 Ohm
|
|
*/
|
|
#define REG_LIM0_LL BIT(1) /* LL (Local Loopback) */
|
|
|
|
#define REG_FMR0 0x1C
|
|
#define REG_FMR0_E_RC0 BIT(4) /* Receive Code - LSB */
|
|
#define REG_FMR0_E_RC1 BIT(5) /* Receive Code - MSB */
|
|
#define REG_FMR0_E_XC0 BIT(6) /* Transmit Code - LSB */
|
|
#define REG_FMR0_E_XC1 BIT(7) /* Transmit Code - MSB */
|
|
|
|
#define REG_FMR1 0x1D
|
|
#define REG_FMR1_XAIS BIT(0) /* Transmit AIS toward transmit end */
|
|
#define REG_FMR1_SSD0 BIT(1)
|
|
#define REG_FMR1_ECM BIT(2)
|
|
#define REG_FMR1_T_CRC BIT(3) /* Enable CRC6 */
|
|
#define REG_FMR1_E_XFS BIT(3) /* Transmit Framing Select */
|
|
#define REG_FMR1_PMOD BIT(4) /* E1 = 0, T1/J1 = 1 */
|
|
#define REG_FMR1_EDL BIT(5)
|
|
#define REG_FMR1_AFR BIT(6)
|
|
|
|
#define REG_FMR2 0x1E
|
|
#define REG_FMR2_E_ALMF BIT(0) /* Automatic Loss of Multiframe */
|
|
#define REG_FMR2_T_EXZE BIT(0) /* Excessive Zeros Detection Enable */
|
|
#define REG_FMR2_E_AXRA BIT(1) /* Automatic Transmit Remote Alarm */
|
|
#define REG_FMR2_T_AXRA BIT(1) /* Automatic Transmit Remote Alarm */
|
|
#define REG_FMR2_E_PLB BIT(2) /* Payload Loop-Back */
|
|
#define REG_FMR2_E_RFS0 BIT(6) /* Receive Framing Select - LSB */
|
|
#define REG_FMR2_E_RFS1 BIT(7) /* Receive Framing Select - MSB */
|
|
#define REG_FMR2_T_SSP BIT(5) /* Select Synchronization/Resynchronization Procedure */
|
|
#define REG_FMR2_T_MCSP BIT(6) /* Multiple Candidates Synchronization Procedure */
|
|
#define REG_FMR2_T_AFRS BIT(7) /* Automatic Force Resynchronization */
|
|
|
|
#define REG_FMR3 0x31
|
|
#define REG_FMR3_EXTIW BIT(0) /* Extended CRC4 to Non-CRC4 Interworking */
|
|
|
|
#define REG_FMR4 0x20
|
|
#define REG_FMR4_FM0 BIT(0)
|
|
#define REG_FMR4_FM1 BIT(1)
|
|
#define REG_FMR4_AUTO BIT(2)
|
|
#define REG_FMR4_SSC0 BIT(3)
|
|
#define REG_FMR4_SSC1 BIT(4)
|
|
#define REG_FMR4_XRA BIT(5) /* Transmit Remote Alarm (Yellow Alarm) */
|
|
#define REG_FMR4_TM BIT(6)
|
|
#define REG_FMR4_AIS3 BIT(7)
|
|
|
|
#define REG_XSW_E 0x20
|
|
#define REG_XSW_E_XY4 BIT(0)
|
|
#define REG_XSW_E_XY3 BIT(1)
|
|
#define REG_XSW_E_XY2 BIT(2)
|
|
#define REG_XSW_E_XY1 BIT(3)
|
|
#define REG_XSW_E_XY0 BIT(4)
|
|
#define REG_XSW_E_XRA BIT(5) /* Transmit Remote Alarm (Yellow Alarm) */
|
|
#define REG_XSW_E_XTM BIT(6)
|
|
#define REG_XSW_E_XSIS BIT(7)
|
|
|
|
#define REG_XSP_E 0x21
|
|
#define REG_FMR5_T 0x21
|
|
#define REG_XSP_E_XSIF BIT(2) /* Transmit Spare Bit For International Use (FAS Word) */
|
|
#define REG_FMR5_T_XTM BIT(2) /* Transmit Transparent Mode */
|
|
#define REG_XSP_E_AXS BIT(3) /* Automatic Transmission of Submultiframe Status */
|
|
#define REG_XSP_E_EBP BIT(4) /* E-Bit Polarity, Si-bit position of every outgoing CRC multiframe */
|
|
#define REG_XSP_E_CASEN BIT(6) /* CAS: Channel Associated Signaling Enable */
|
|
#define REG_FMR5_T_EIBR BIT(6) /* CAS: Enable Internal Bit Robbing Access */
|
|
|
|
#define REG_XC0_T 0x22 /* Transmit Control 0 */
|
|
#define REG_XC0_BRIF BIT(5) /* Bit Robbing Idle Function */
|
|
|
|
#define REG_CMDR_E 0x02 /* Command Register */
|
|
#define REG_CMDR_RRES BIT(6) /* Receiver reset */
|
|
#define REG_CMDR_XRES BIT(4) /* Transmitter reset */
|
|
|
|
#define REG_RC0 0x24
|
|
#define REG_RC0_SJR BIT(7) /* T1 = 0, J1 = 1 */
|
|
|
|
#define REG_CMR1 0x44
|
|
#define REG_CMR1_DRSS (BIT(7) | BIT(6))
|
|
#define REG_CMR1_RS (BIT(5) | BIT(4))
|
|
#define REG_CMR1_STF BIT(2)
|
|
|
|
#define REG_RS1_E 0x70 /* Receive CAS Register 1 */
|
|
#define REG_RS2_E 0x71 /* Receive CAS Register 2 */
|
|
#define REG_RS3_E 0x72 /* Receive CAS Register 3 */
|
|
#define REG_RS4_E 0x73 /* Receive CAS Register 4 */
|
|
#define REG_RS5_E 0x74 /* Receive CAS Register 5 */
|
|
#define REG_RS6_E 0x75 /* Receive CAS Register 6 */
|
|
#define REG_RS7_E 0x76 /* Receive CAS Register 7 */
|
|
#define REG_RS8_E 0x77 /* Receive CAS Register 8 */
|
|
#define REG_RS9_E 0x78 /* Receive CAS Register 9 */
|
|
#define REG_RS10_E 0x79 /* Receive CAS Register 10 */
|
|
#define REG_RS11_E 0x7A /* Receive CAS Register 11 */
|
|
#define REG_RS12_E 0x7B /* Receive CAS Register 12 */
|
|
#define REG_RS13_E 0x7C /* Receive CAS Register 13 */
|
|
#define REG_RS14_E 0x7D /* Receive CAS Register 14 */
|
|
#define REG_RS15_E 0x7E /* Receive CAS Register 15 */
|
|
#define REG_RS16_E 0x7F /* Receive CAS Register 16 */
|
|
|
|
#define REG_PC2 0x81 /* Port Configuration 2 */
|
|
#define REG_PC3 0x82 /* Port Configuration 3 */
|
|
#define REG_PC4 0x83 /* Port Configuration 4 */
|
|
|
|
#define REG_XPM2 0x28 /* Transmit Pulse Mask 2 */
|
|
|
|
#define VAL_PC_SYPR 0x00 /* Synchronous Pulse Receive (Input, low active) */
|
|
#define VAL_PC_GPI 0x90 /* General purpose input */
|
|
#define VAL_PC_GPOH 0x0A /* General Purpose Output, high level */
|
|
#define VAL_PC_GPOL 0x0B /* General Purpose Output, low level */
|
|
|
|
#define NUM_CAS_RS_E (REG_RS16_E - REG_RS2_E + 1)
|
|
/* and of those, the ones used in T1: */
|
|
#define NUM_CAS_RS_T (REG_RS12_E - REG_RS1_E + 1)
|
|
|
|
struct PRI_priv_data {
|
|
bool clock_source;
|
|
#ifdef OLD_PROC
|
|
struct proc_dir_entry *pri_info;
|
|
#endif
|
|
enum pri_protocol pri_protocol;
|
|
xpp_line_t rbslines;
|
|
int deflaw;
|
|
unsigned int dchan_num;
|
|
bool initialized;
|
|
int is_cas;
|
|
|
|
unsigned int chanconfig_dchan;
|
|
#define NO_DCHAN (0)
|
|
#define DCHAN(p) ((p)->chanconfig_dchan)
|
|
#define VALID_DCHAN(p) (DCHAN(p) != NO_DCHAN)
|
|
#define SET_DCHAN(p,d) do { DCHAN(p) = (d); } while(0);
|
|
|
|
byte cas_rs_e[NUM_CAS_RS_E];
|
|
byte cas_ts_e[NUM_CAS_RS_E];
|
|
uint cas_replies;
|
|
bool is_esf;
|
|
bool local_loopback;
|
|
uint poll_noreplies;
|
|
uint layer1_replies;
|
|
byte reg_frs0;
|
|
byte reg_frs1;
|
|
bool layer1_up;
|
|
int alarms;
|
|
byte dchan_tx_sample;
|
|
byte dchan_rx_sample;
|
|
uint dchan_tx_counter;
|
|
uint dchan_rx_counter;
|
|
bool dchan_alive;
|
|
uint dchan_alive_ticks;
|
|
enum pri_led_state ledstate[NUM_LEDS];
|
|
};
|
|
|
|
static xproto_table_t PROTO_TABLE(PRI);
|
|
|
|
DEF_RPACKET_DATA(PRI, SET_LED, /* Set one of the LED's */
|
|
struct pri_leds pri_leds;
|
|
);
|
|
|
|
|
|
static /* 0x33 */ DECLARE_CMD(PRI, SET_LED, enum pri_led_selectors led_sel, enum pri_led_state to_led_state);
|
|
|
|
#define DO_LED(xpd, which, tostate) \
|
|
CALL_PROTO(PRI, SET_LED, (xpd)->xbus, (xpd), (which), (tostate))
|
|
|
|
/*---------------- PRI: Methods -------------------------------------------*/
|
|
|
|
static int query_subunit(xpd_t *xpd, byte regnum)
|
|
{
|
|
XPD_DBG(REGS, xpd, "(%d%d): REG=0x%02X\n",
|
|
xpd->addr.unit, xpd->addr.subunit,
|
|
regnum);
|
|
return xpp_register_request(
|
|
xpd->xbus, xpd,
|
|
PRI_PORT(xpd), /* portno */
|
|
0, /* writing */
|
|
regnum,
|
|
0, /* do_subreg */
|
|
0, /* subreg */
|
|
0, /* data_L */
|
|
0, /* do_datah */
|
|
0, /* data_H */
|
|
0 /* should_reply */
|
|
);
|
|
}
|
|
|
|
|
|
static int write_subunit(xpd_t *xpd, byte regnum, byte val)
|
|
{
|
|
XPD_DBG(REGS, xpd, "(%d%d): REG=0x%02X dataL=0x%02X\n",
|
|
xpd->addr.unit, xpd->addr.subunit,
|
|
regnum, val);
|
|
return xpp_register_request(
|
|
xpd->xbus, xpd,
|
|
PRI_PORT(xpd), /* portno */
|
|
1, /* writing */
|
|
regnum,
|
|
0, /* do_subreg */
|
|
0, /* subreg */
|
|
val, /* data_L */
|
|
0, /* do_datah */
|
|
0, /* data_H */
|
|
0 /* should_reply */
|
|
);
|
|
}
|
|
|
|
static int pri_write_reg(xpd_t *xpd, int regnum, byte val)
|
|
{
|
|
XPD_DBG(REGS, xpd, "(%d%d): REG=0x%02X dataL=0x%02X\n",
|
|
xpd->addr.unit, xpd->addr.subunit,
|
|
regnum, val);
|
|
return xpp_register_request(
|
|
xpd->xbus, xpd,
|
|
0, /* portno=0 */
|
|
1, /* writing */
|
|
regnum,
|
|
0, /* do_subreg */
|
|
0, /* subreg */
|
|
val, /* data_L */
|
|
0, /* do_datah */
|
|
0, /* data_H */
|
|
0 /* should_reply */
|
|
);
|
|
}
|
|
|
|
static int cas_regbase(xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
switch (priv->pri_protocol) {
|
|
case PRI_PROTO_E1:
|
|
return REG_RS2_E;
|
|
case PRI_PROTO_T1:
|
|
/* fall-through */
|
|
case PRI_PROTO_J1:
|
|
return REG_RS1_E;
|
|
case PRI_PROTO_0:
|
|
/* fall-through */
|
|
;
|
|
}
|
|
BUG();
|
|
return 0;
|
|
}
|
|
|
|
static int cas_numregs(xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
switch (priv->pri_protocol) {
|
|
case PRI_PROTO_E1:
|
|
return NUM_CAS_RS_E;
|
|
case PRI_PROTO_T1:
|
|
/* fall-through */
|
|
case PRI_PROTO_J1:
|
|
return NUM_CAS_RS_T;
|
|
case PRI_PROTO_0:
|
|
/* fall-through */
|
|
;
|
|
}
|
|
BUG();
|
|
return 0;
|
|
}
|
|
|
|
static int write_cas_reg(xpd_t *xpd, int rsnum, byte val)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int regbase = cas_regbase(xpd);
|
|
int num_cas_rs = cas_numregs(xpd);
|
|
int regnum;
|
|
bool is_d4 = 0;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if ((priv->pri_protocol == PRI_PROTO_T1) && !priv->is_esf) {
|
|
/* same data should be copied to RS7..12 in D4 only */
|
|
is_d4 = 1;
|
|
}
|
|
if (rsnum < 0 || rsnum >= num_cas_rs) {
|
|
XPD_ERR(xpd, "RBS(TX): rsnum=%d\n", rsnum);
|
|
BUG();
|
|
}
|
|
regnum = regbase + rsnum;
|
|
priv->cas_ts_e[rsnum] = val;
|
|
XPD_DBG(SIGNAL, xpd, "RBS(TX): reg=0x%X val=0x%02X\n", regnum, val);
|
|
write_subunit(xpd, regbase + rsnum, val);
|
|
if (is_d4) {
|
|
/* same data should be copied to RS7..12 in D4 only */
|
|
regnum = REG_RS7_E + rsnum;
|
|
XPD_DBG(SIGNAL, xpd, "RBS(TX): reg=0x%X val=0x%02X\n",
|
|
regnum, val);
|
|
write_subunit(xpd, regnum, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef OLD_PROC
|
|
static void pri_proc_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
XPD_DBG(PROC, xpd, "\n");
|
|
#ifdef CONFIG_PROC_FS
|
|
if(priv->pri_info) {
|
|
XPD_DBG(PROC, xpd, "Removing xpd PRI_INFO file\n");
|
|
remove_proc_entry(PROC_PRI_INFO_FNAME, xpd->proc_xpd_dir);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef OLD_PROC
|
|
static int pri_proc_create(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
XPD_DBG(PROC, xpd, "\n");
|
|
#ifdef CONFIG_PROC_FS
|
|
XPD_DBG(PROC, xpd, "Creating PRI_INFO file\n");
|
|
priv->pri_info = create_proc_entry(PROC_PRI_INFO_FNAME, 0644, xpd->proc_xpd_dir);
|
|
if(!priv->pri_info) {
|
|
XPD_ERR(xpd, "Failed to create proc '%s'\n", PROC_PRI_INFO_FNAME);
|
|
goto err;
|
|
}
|
|
SET_PROC_DIRENTRY_OWNER(priv->pri_info);
|
|
priv->pri_info->write_proc = proc_pri_info_write;
|
|
priv->pri_info->read_proc = proc_pri_info_read;
|
|
priv->pri_info->data = xpd;
|
|
#endif
|
|
return 0;
|
|
err:
|
|
pri_proc_remove(xbus, xpd);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
static bool valid_pri_modes(const xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if(
|
|
priv->pri_protocol != PRI_PROTO_E1 &&
|
|
priv->pri_protocol != PRI_PROTO_T1 &&
|
|
priv->pri_protocol != PRI_PROTO_J1)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void PRI_card_pcm_recompute(xbus_t *xbus, xpd_t *xpd,
|
|
xpp_line_t pcm_mask)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int i;
|
|
int line_count = 0;
|
|
unsigned long flags;
|
|
uint pcm_len;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
spin_lock_irqsave(&xpd->lock_recompute_pcm, flags);
|
|
//XPD_DBG(SIGNAL, xpd, "pcm_mask=0x%X\n", pcm_mask);
|
|
/* Add/remove all the trivial cases */
|
|
pcm_mask |= xpd->offhook_state;
|
|
for_each_line(xpd, i)
|
|
if (IS_SET(pcm_mask, i))
|
|
line_count++;
|
|
if(priv->is_cas) {
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
/* CAS: Don't send PCM to D-Channel */
|
|
line_count--;
|
|
pcm_mask &= ~BIT(PRI_DCHAN_IDX(priv));
|
|
}
|
|
}
|
|
/*
|
|
* FIXME: Workaround a bug in sync code of the Astribank.
|
|
* Send dummy PCM for sync.
|
|
*/
|
|
if (xpd->addr.unit == 0 && pcm_mask == 0) {
|
|
pcm_mask = BIT(0);
|
|
line_count = 1;
|
|
}
|
|
pcm_len = (line_count)
|
|
? RPACKET_HEADERSIZE + sizeof(xpp_line_t) +
|
|
line_count * DAHDI_CHUNKSIZE
|
|
: 0L;
|
|
update_wanted_pcm_mask(xpd, pcm_mask, pcm_len);
|
|
spin_unlock_irqrestore(&xpd->lock_recompute_pcm, flags);
|
|
}
|
|
|
|
/*
|
|
* Set E1/T1/J1
|
|
* May only be called on unregistered xpd's
|
|
* (the span and channel description are set according to this)
|
|
*/
|
|
static int set_pri_proto(xpd_t *xpd, enum pri_protocol set_proto)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int deflaw;
|
|
unsigned int dchan_num;
|
|
int default_lineconfig = 0;
|
|
int ret;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
XPD_NOTICE(xpd, "Registered as span %d. Cannot do setup pri protocol (%s)\n",
|
|
xpd->span.spanno, __FUNCTION__);
|
|
return -EBUSY;
|
|
}
|
|
if(priv->pri_protocol != PRI_PROTO_0) {
|
|
if(priv->pri_protocol == set_proto) {
|
|
XPD_NOTICE(xpd, "Already in protocol %s. Ignored\n", pri_protocol_name(set_proto));
|
|
return 0;
|
|
} else {
|
|
XPD_INFO(xpd, "Switching from %s to %s\n",
|
|
pri_protocol_name(priv->pri_protocol),
|
|
pri_protocol_name(set_proto));
|
|
}
|
|
}
|
|
switch(set_proto) {
|
|
case PRI_PROTO_E1:
|
|
deflaw = DAHDI_LAW_ALAW;
|
|
dchan_num = 16;
|
|
default_lineconfig = DAHDI_CONFIG_CCS | DAHDI_CONFIG_CRC4 | DAHDI_CONFIG_HDB3;
|
|
break;
|
|
case PRI_PROTO_T1:
|
|
deflaw = DAHDI_LAW_MULAW;
|
|
dchan_num = 24;
|
|
default_lineconfig = DAHDI_CONFIG_ESF | DAHDI_CONFIG_B8ZS;
|
|
break;
|
|
case PRI_PROTO_J1:
|
|
/*
|
|
* Check all assumptions
|
|
*/
|
|
deflaw = DAHDI_LAW_MULAW;
|
|
dchan_num = 24;
|
|
default_lineconfig = 0; /* FIXME: J1??? */
|
|
XPD_NOTICE(xpd, "J1 is not supported yet\n");
|
|
return -ENOSYS;
|
|
default:
|
|
XPD_ERR(xpd, "%s: Unknown pri protocol = %d\n",
|
|
__FUNCTION__, set_proto);
|
|
return -EINVAL;
|
|
}
|
|
priv->pri_protocol = set_proto;
|
|
priv->is_cas = -1;
|
|
xpd->channels = pri_num_channels(set_proto);
|
|
xpd->offhook_state = BITMASK(xpd->channels);
|
|
CALL_XMETHOD(card_pcm_recompute, xpd->xbus, xpd, 0);
|
|
priv->deflaw = deflaw;
|
|
priv->dchan_num = dchan_num;
|
|
priv->local_loopback = 0;
|
|
xpd->type_name = type_name(priv->pri_protocol);
|
|
XPD_DBG(GENERAL, xpd, "%s, channels=%d, dchan_num=%d, deflaw=%d\n",
|
|
pri_protocol_name(set_proto),
|
|
xpd->channels,
|
|
priv->dchan_num,
|
|
priv->deflaw
|
|
);
|
|
/*
|
|
* Must set default now, so layer1 polling (Register REG_FRS0) would
|
|
* give reliable results.
|
|
*/
|
|
ret = pri_lineconfig(xpd, default_lineconfig);
|
|
if(ret) {
|
|
XPD_NOTICE(xpd, "Failed setting PRI default line config\n");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void dahdi_update_syncsrc(xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
xpd_t *subxpd;
|
|
int best_spanno = 0;
|
|
int i;
|
|
|
|
if(!SPAN_REGISTERED(xpd))
|
|
return;
|
|
for(i = 0; i < MAX_SLAVES; i++) {
|
|
subxpd = xpd_byaddr(xpd->xbus, xpd->addr.unit, i);
|
|
if(!subxpd)
|
|
continue;
|
|
priv = subxpd->priv;
|
|
if(priv->clock_source && priv->alarms == 0) {
|
|
if(best_spanno)
|
|
XPD_ERR(xpd, "Duplicate XPD's with clock_source=1\n");
|
|
best_spanno = subxpd->span.spanno;
|
|
}
|
|
}
|
|
for(i = 0; i < MAX_SLAVES; i++) {
|
|
subxpd = xpd_byaddr(xpd->xbus, xpd->addr.unit, i);
|
|
if(!subxpd)
|
|
continue;
|
|
if(subxpd->span.syncsrc == best_spanno)
|
|
XPD_DBG(SYNC, xpd, "Setting SyncSource to span %d\n", best_spanno);
|
|
else
|
|
XPD_DBG(SYNC, xpd, "Slaving to span %d\n", best_spanno);
|
|
subxpd->span.syncsrc = best_spanno;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called from:
|
|
* - set_master_mode() --
|
|
* As a result of dahdi_cfg
|
|
* - layer1_state() --
|
|
* As a result of an alarm.
|
|
*/
|
|
static void set_clocking(xpd_t *xpd)
|
|
{
|
|
xbus_t *xbus;
|
|
xpd_t *best_xpd = NULL;
|
|
int best_subunit = -1; /* invalid */
|
|
unsigned int best_subunit_prio = INT_MAX;
|
|
int i;
|
|
|
|
xbus = xpd->xbus;
|
|
/* Find subunit with best timing priority */
|
|
for(i = 0; i < MAX_SLAVES; i++) {
|
|
struct PRI_priv_data *priv;
|
|
xpd_t *subxpd;
|
|
|
|
subxpd = xpd_byaddr(xbus, xpd->addr.unit, i);
|
|
if(!subxpd)
|
|
continue;
|
|
priv = subxpd->priv;
|
|
if(priv->alarms != 0)
|
|
continue;
|
|
if(subxpd->timing_priority > 0 && subxpd->timing_priority < best_subunit_prio) {
|
|
best_xpd = subxpd;
|
|
best_subunit = i;
|
|
best_subunit_prio = subxpd->timing_priority;
|
|
}
|
|
}
|
|
/* Now set it */
|
|
if(best_xpd && ((struct PRI_priv_data *)(best_xpd->priv))->clock_source == 0) {
|
|
byte reg_pc_init[] = { VAL_PC_GPI, VAL_PC_GPI, VAL_PC_GPI };
|
|
|
|
for(i = 0; i < ARRAY_SIZE(reg_pc_init); i++) {
|
|
byte reg_pc = reg_pc_init[i];
|
|
|
|
reg_pc |= (best_subunit & (1 << i)) ? VAL_PC_GPOH : VAL_PC_GPOL;
|
|
XPD_DBG(SYNC, best_xpd,
|
|
"ClockSource Set: PC%d=0x%02X\n", 2+i, reg_pc);
|
|
pri_write_reg(xpd, REG_PC2 + i, reg_pc);
|
|
}
|
|
((struct PRI_priv_data *)(best_xpd->priv))->clock_source = 1;
|
|
}
|
|
/* clear old clock sources */
|
|
for(i = 0; i < MAX_SLAVES; i++) {
|
|
struct PRI_priv_data *priv;
|
|
xpd_t *subxpd;
|
|
|
|
subxpd = xpd_byaddr(xbus, xpd->addr.unit, i);
|
|
if(subxpd && subxpd != best_xpd) {
|
|
XPD_DBG(SYNC, subxpd, "Clearing clock source\n");
|
|
priv = subxpd->priv;
|
|
priv->clock_source = 0;
|
|
}
|
|
}
|
|
dahdi_update_syncsrc(xpd);
|
|
}
|
|
|
|
static void set_reg_lim0(const char *msg, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
bool is_master_mode;
|
|
bool localloop;
|
|
byte lim0 = 0;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
is_master_mode = xpd->timing_priority == 0;
|
|
localloop = priv->local_loopback;
|
|
lim0 |= (localloop) ? REG_LIM0_LL : 0;
|
|
if(is_master_mode)
|
|
lim0 |= REG_LIM0_MAS;
|
|
else
|
|
lim0 &= ~REG_LIM0_MAS;
|
|
XPD_DBG(SIGNAL, xpd, "%s(%s): %s, %s\n", __FUNCTION__, msg,
|
|
(is_master_mode) ? "MASTER" : "SLAVE",
|
|
(localloop) ? "LOCALLOOP" : "NO_LOCALLOOP");
|
|
write_subunit(xpd, REG_LIM0 , lim0);
|
|
}
|
|
|
|
/*
|
|
* Normally set by the timing parameter in /etc/dahdi/system.conf
|
|
* If this is called by dahdi_cfg, than it's too late to change
|
|
* dahdi sync priority (we are already registered)
|
|
*
|
|
* Also called from set_localloop()
|
|
*/
|
|
static int set_master_mode(const char *msg, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(SIGNAL, xpd, "\n");
|
|
priv = xpd->priv;
|
|
set_reg_lim0(__FUNCTION__, xpd);
|
|
set_clocking(xpd);
|
|
return 0;
|
|
}
|
|
|
|
static int set_localloop(xpd_t *xpd, bool localloop)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
XPD_NOTICE(xpd, "Registered as span %d. Cannot do %s\n",
|
|
xpd->span.spanno, __FUNCTION__);
|
|
return -EBUSY;
|
|
}
|
|
priv->local_loopback = localloop;
|
|
XPD_DBG(SIGNAL, xpd, "%s: %s\n", __FUNCTION__, (localloop) ? "LOCALLOOP" : "NO");
|
|
set_master_mode(__FUNCTION__, xpd);
|
|
return 0;
|
|
}
|
|
|
|
#define VALID_CONFIG(bit,flg,str) [bit] = { .flags = flg, .name = str }
|
|
|
|
static const struct {
|
|
const char *name;
|
|
const int flags;
|
|
} valid_spanconfigs[sizeof(unsigned int)*8] = {
|
|
/* These apply to T1 */
|
|
VALID_CONFIG(4, DAHDI_CONFIG_D4, "D4"),
|
|
VALID_CONFIG(5, DAHDI_CONFIG_ESF, "ESF"),
|
|
VALID_CONFIG(6, DAHDI_CONFIG_AMI, "AMI"),
|
|
VALID_CONFIG(7, DAHDI_CONFIG_B8ZS, "B8ZS"),
|
|
/* These apply to E1 */
|
|
VALID_CONFIG(8, DAHDI_CONFIG_CCS, "CCS"),
|
|
VALID_CONFIG(9, DAHDI_CONFIG_HDB3, "HDB3"),
|
|
VALID_CONFIG(10, DAHDI_CONFIG_CRC4, "CRC4"),
|
|
};
|
|
|
|
/*
|
|
* Mark the lines as CLEAR or RBS signalling.
|
|
* With T1, we need to mark the CLEAR lines on the REG_CCB1_T registers
|
|
* Should be called only when we are registered to DAHDI
|
|
* The channo parameter:
|
|
* channo == 0: set lines for the whole span
|
|
* channo != 0: only set modified lines
|
|
*/
|
|
static void set_rbslines(xpd_t *xpd, int channo)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
xpp_line_t new_rbslines = 0;
|
|
xpp_line_t modified_lines;
|
|
int i;
|
|
|
|
priv = xpd->priv;
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
|
|
if(chan->flags & DAHDI_FLAG_CLEAR)
|
|
BIT_CLR(new_rbslines, i);
|
|
else
|
|
BIT_SET(new_rbslines, i);
|
|
}
|
|
new_rbslines &= BITMASK(xpd->channels);
|
|
modified_lines = priv->rbslines ^ new_rbslines;
|
|
XPD_DBG(DEVICES, xpd, "RBSLINES-%d(%s): 0x%X\n",
|
|
channo, pri_protocol_name(priv->pri_protocol), new_rbslines);
|
|
if((priv->pri_protocol == PRI_PROTO_T1) || (priv->pri_protocol == PRI_PROTO_J1)) {
|
|
byte clear_lines = 0; /* Mark clear lines */
|
|
bool reg_changed = 0;
|
|
|
|
for_each_line(xpd, i) {
|
|
int bytenum = i / 8;
|
|
int bitnum = i % 8;
|
|
|
|
if(!IS_SET(new_rbslines, i)) {
|
|
BIT_SET(clear_lines, (7 - bitnum));
|
|
}
|
|
if(IS_SET(modified_lines, i))
|
|
reg_changed = 1;
|
|
if(bitnum == 7) {
|
|
if(channo == 0 || reg_changed) {
|
|
bytenum += REG_CCB1_T;
|
|
XPD_DBG(DEVICES, xpd, "RBS(%s): modified=0x%X rbslines=0x%X reg=0x%X clear_lines=0x%X\n",
|
|
pri_protocol_name(priv->pri_protocol),
|
|
modified_lines, new_rbslines, bytenum, clear_lines);
|
|
write_subunit(xpd, bytenum, clear_lines);
|
|
}
|
|
clear_lines = 0;
|
|
reg_changed = 0;
|
|
}
|
|
}
|
|
}
|
|
priv->rbslines = new_rbslines;
|
|
}
|
|
|
|
static int set_mode_cas(xpd_t *xpd, bool want_cas)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
XPD_INFO(xpd, "Setting TDM to %s\n", (want_cas) ? "CAS" : "PRI");
|
|
if(want_cas) {
|
|
priv->is_cas = 1;
|
|
priv->dchan_alive = 0;
|
|
} else {
|
|
priv->is_cas = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pri_lineconfig(xpd_t *xpd, int lineconfig)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
const char *framingstr = "";
|
|
const char *codingstr = "";
|
|
const char *crcstr = "";
|
|
#ifdef JAPANEZE_SUPPORT
|
|
byte rc0 = 0; /* FIXME: PCM offsets */
|
|
#endif
|
|
byte fmr0 = 0;
|
|
byte fmr1 = REG_FMR1_ECM;
|
|
byte fmr2 = 0;
|
|
byte fmr3 = 0; /* write only for CRC4 */
|
|
byte fmr4 = 0;
|
|
byte cmdr = REG_CMDR_RRES | REG_CMDR_XRES;
|
|
byte xsp = 0;
|
|
unsigned int bad_bits;
|
|
bool force_cas = 0;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
/*
|
|
* validate
|
|
*/
|
|
bad_bits = lineconfig & pri_linecompat(priv->pri_protocol);
|
|
bad_bits = bad_bits ^ lineconfig;
|
|
for(i = 0; i < ARRAY_SIZE(valid_spanconfigs); i++) {
|
|
unsigned int flags = valid_spanconfigs[i].flags;
|
|
|
|
if(bad_bits & BIT(i)) {
|
|
if(flags) {
|
|
XPD_ERR(xpd,
|
|
"Bad config item '%s' for %s. Ignore\n",
|
|
valid_spanconfigs[i].name,
|
|
pri_protocol_name(priv->pri_protocol));
|
|
} else {
|
|
/* we got real garbage */
|
|
XPD_ERR(xpd,
|
|
"Unknown config item 0x%lX for %s. Ignore\n",
|
|
BIT(i),
|
|
pri_protocol_name(priv->pri_protocol));
|
|
}
|
|
}
|
|
if(flags && flags != BIT(i)) {
|
|
ERR("%s: BUG: i=%d flags=0x%X\n",
|
|
__FUNCTION__, i, flags);
|
|
// BUG();
|
|
}
|
|
}
|
|
if(bad_bits)
|
|
goto bad_lineconfig;
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
fmr1 |= REG_FMR1_AFR;
|
|
fmr2 = REG_FMR2_E_AXRA | REG_FMR2_E_ALMF; /* 0x03 */
|
|
fmr4 = 0x9F; /* E1.XSW: All spare bits = 1*/
|
|
xsp |= REG_XSP_E_EBP | REG_XSP_E_AXS | REG_XSP_E_XSIF;
|
|
} else if(priv->pri_protocol == PRI_PROTO_T1) {
|
|
fmr1 |= REG_FMR1_PMOD | REG_FMR1_T_CRC;
|
|
fmr2 = REG_FMR2_T_SSP | REG_FMR2_T_AXRA; /* 0x22 */
|
|
fmr4 = 0x0C;
|
|
xsp &= ~REG_FMR5_T_XTM;
|
|
force_cas = 1; /* T1 - Chip always in CAS mode */
|
|
} else if(priv->pri_protocol == PRI_PROTO_J1) {
|
|
fmr1 |= REG_FMR1_PMOD;
|
|
fmr4 = 0x1C;
|
|
xsp &= ~REG_FMR5_T_XTM;
|
|
force_cas = 1; /* T1 - Chip always in CAS mode */
|
|
XPD_ERR(xpd, "J1 unsupported yet\n");
|
|
return -ENOSYS;
|
|
}
|
|
if(priv->local_loopback)
|
|
fmr2 |= REG_FMR2_E_PLB;
|
|
/* framing first */
|
|
if (lineconfig & DAHDI_CONFIG_B8ZS) {
|
|
framingstr = "B8ZS";
|
|
fmr0 = REG_FMR0_E_XC1 | REG_FMR0_E_XC0 | REG_FMR0_E_RC1 | REG_FMR0_E_RC0;
|
|
} else if (lineconfig & DAHDI_CONFIG_AMI) {
|
|
framingstr = "AMI";
|
|
fmr0 = REG_FMR0_E_XC1 | REG_FMR0_E_RC1;
|
|
/*
|
|
* From Infineon Errata Sheet: PEF 22554, Version 3.1
|
|
* Problem: Incorrect CAS Receiption when
|
|
* using AMI receive line code
|
|
* Workaround: For E1,
|
|
* "...The receive line coding HDB3 is
|
|
* recommended instead."
|
|
* For T1,
|
|
* "...in T1 mode it is recommended to
|
|
* configure the Rx side to B8ZS coding"
|
|
* For both cases this is the same bit in FMR0
|
|
*/
|
|
if(priv->pri_protocol == PRI_PROTO_J1)
|
|
XPD_NOTICE(xpd, "J1 is not supported yet\n");
|
|
else
|
|
fmr0 |= REG_FMR0_E_RC0;
|
|
} else if (lineconfig & DAHDI_CONFIG_HDB3) {
|
|
framingstr = "HDB3";
|
|
fmr0 = REG_FMR0_E_XC1 | REG_FMR0_E_XC0 | REG_FMR0_E_RC1 | REG_FMR0_E_RC0;
|
|
} else {
|
|
XPD_NOTICE(xpd, "Bad lineconfig. Not (B8ZS|AMI|HDB3). Ignored.\n");
|
|
return -EINVAL;
|
|
}
|
|
/* then coding */
|
|
priv->is_esf = 0;
|
|
if (lineconfig & DAHDI_CONFIG_ESF) {
|
|
codingstr = "ESF";
|
|
fmr4 |= REG_FMR4_FM1;
|
|
fmr2 |= REG_FMR2_T_AXRA | REG_FMR2_T_MCSP | REG_FMR2_T_SSP;
|
|
priv->is_esf = 1;
|
|
} else if (lineconfig & DAHDI_CONFIG_D4) {
|
|
codingstr = "D4";
|
|
} else if (lineconfig & DAHDI_CONFIG_CCS) {
|
|
codingstr = "CCS";
|
|
set_mode_cas(xpd, 0); /* In E1 we know right from the span statement. */
|
|
} else {
|
|
codingstr = "CAS"; /* In E1 we know right from the span statement. */
|
|
force_cas = 1;
|
|
set_mode_cas(xpd, 1);
|
|
}
|
|
CALL_XMETHOD(card_pcm_recompute, xpd->xbus, xpd, 0);
|
|
/*
|
|
* E1's can enable CRC checking
|
|
* CRC4 is legal only for E1, and it is checked by pri_linecompat()
|
|
* in the beginning of the function.
|
|
*/
|
|
if (lineconfig & DAHDI_CONFIG_CRC4) {
|
|
crcstr = "CRC4";
|
|
fmr1 |= REG_FMR1_E_XFS;
|
|
fmr2 |= REG_FMR2_E_RFS1;
|
|
fmr3 |= REG_FMR3_EXTIW;
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "[%s] lineconfig=%s/%s/%s %s (0x%X)\n",
|
|
(priv->clock_source)?"MASTER":"SLAVE",
|
|
framingstr, codingstr, crcstr,
|
|
(lineconfig & DAHDI_CONFIG_NOTOPEN)?"YELLOW":"",
|
|
lineconfig);
|
|
set_reg_lim0(__FUNCTION__, xpd);
|
|
XPD_DBG(GENERAL, xpd, "%s: fmr1(0x%02X) = 0x%02X\n", __FUNCTION__, REG_FMR1, fmr1);
|
|
write_subunit(xpd, REG_FMR1, fmr1);
|
|
XPD_DBG(GENERAL, xpd, "%s: fmr2(0x%02X) = 0x%02X\n", __FUNCTION__, REG_FMR2, fmr2);
|
|
write_subunit(xpd, REG_FMR2, fmr2);
|
|
XPD_DBG(GENERAL, xpd, "%s: fmr0(0x%02X) = 0x%02X\n", __FUNCTION__, REG_FMR0, fmr0);
|
|
write_subunit(xpd, REG_FMR0, fmr0);
|
|
XPD_DBG(GENERAL, xpd, "%s: fmr4(0x%02X) = 0x%02X\n", __FUNCTION__, REG_FMR4, fmr4);
|
|
write_subunit(xpd, REG_FMR4, fmr4);
|
|
if(fmr3) {
|
|
XPD_DBG(GENERAL, xpd, "%s: fmr3(0x%02X) = 0x%02X\n", __FUNCTION__, REG_FMR3, fmr3);
|
|
write_subunit(xpd, REG_FMR3, fmr3);
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "%s: cmdr(0x%02X) = 0x%02X\n", __FUNCTION__, REG_CMDR_E, cmdr);
|
|
write_subunit(xpd, REG_CMDR_E, cmdr);
|
|
#ifdef JAPANEZE_SUPPORT
|
|
if(rc0) {
|
|
XPD_DBG(GENERAL, xpd, "%s: rc0(0x%02X) = 0x%02X\n", __FUNCTION__, REG_RC0, rc0);
|
|
write_subunit(xpd, REG_RC0, rc0);
|
|
}
|
|
#endif
|
|
if(force_cas) {
|
|
xsp |= REG_XSP_E_CASEN; /* Same as REG_FMR5_T_EIBR for T1 */
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "%s: xsp(0x%02X) = 0x%02X\n", __FUNCTION__, REG_XSP_E, xsp);
|
|
write_subunit(xpd, REG_XSP_E, xsp);
|
|
return 0;
|
|
bad_lineconfig:
|
|
XPD_ERR(xpd, "Bad lineconfig. Abort\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Called only for 'span' keyword in /etc/dahdi/system.conf
|
|
*/
|
|
|
|
static int pri_spanconfig(struct dahdi_span *span, struct dahdi_lineconfig *lc)
|
|
{
|
|
xpd_t *xpd = container_of(span, struct xpd, span);
|
|
struct PRI_priv_data *priv;
|
|
int ret;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if(lc->span != xpd->span.spanno) {
|
|
XPD_ERR(xpd, "I am span %d but got spanconfig for span %d\n",
|
|
xpd->span.spanno, lc->span);
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* FIXME: lc->name is unused by dahdi_cfg and dahdi...
|
|
* We currently ignore it also.
|
|
*/
|
|
XPD_DBG(GENERAL, xpd, "[%s] lbo=%d lineconfig=0x%X sync=%d\n",
|
|
(priv->clock_source)?"MASTER":"SLAVE", lc->lbo, lc->lineconfig, lc->sync);
|
|
ret = pri_lineconfig(xpd, lc->lineconfig);
|
|
if(!ret) {
|
|
span->lineconfig = lc->lineconfig;
|
|
xpd->timing_priority = lc->sync;
|
|
set_master_mode("spanconfig", xpd);
|
|
elect_syncer("PRI-master_mode");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Set signalling type (if appropriate)
|
|
* Called from dahdi with spinlock held on chan. Must not call back
|
|
* dahdi functions.
|
|
*/
|
|
static int pri_chanconfig(struct dahdi_chan *chan, int sigtype)
|
|
{
|
|
xpd_t *xpd = container_of(chan->span, struct xpd, span);
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
DBG(GENERAL, "channel %d (%s) -> %s\n", chan->channo, chan->name, sig2str(sigtype));
|
|
/*
|
|
* Some bookkeeping to check if we have DChan defined or not
|
|
* FIXME: actually use this to prevent duplicate DChan definitions
|
|
* and prevent DChan definitions with CAS.
|
|
*/
|
|
if(is_sigtype_dchan(sigtype)) {
|
|
if(VALID_DCHAN(priv) && DCHAN(priv) != chan->channo) {
|
|
ERR("channel %d (%s) marked DChan but also channel %d.\n",
|
|
chan->channo, chan->name, DCHAN(priv));
|
|
return -EINVAL;
|
|
} else {
|
|
XPD_DBG(GENERAL, xpd, "channel %d (%s) marked as DChan\n", chan->channo, chan->name);
|
|
SET_DCHAN(priv, chan->channo);
|
|
/* In T1, we don't know before-hand */
|
|
if(priv->pri_protocol != PRI_PROTO_E1 && priv->is_cas != 0)
|
|
set_mode_cas(xpd, 0);
|
|
}
|
|
} else {
|
|
if(chan->channo == 1) {
|
|
XPD_DBG(GENERAL, xpd, "channel %d (%s) marked a not DChan\n", chan->channo, chan->name);
|
|
SET_DCHAN(priv, NO_DCHAN);
|
|
}
|
|
/* In T1, we don't know before-hand */
|
|
if(priv->pri_protocol != PRI_PROTO_E1 && priv->is_cas != 1)
|
|
set_mode_cas(xpd, 1);
|
|
}
|
|
if(xpd->span.flags & DAHDI_FLAG_RUNNING) {
|
|
XPD_DBG(DEVICES, xpd, "Span is RUNNING. Updating rbslines.\n");
|
|
set_rbslines(xpd, chan->channo);
|
|
}
|
|
// FIXME: sanity checks:
|
|
// - should be supported (within the sigcap)
|
|
// - should not replace fxs <->fxo ??? (covered by previous?)
|
|
return 0;
|
|
}
|
|
|
|
static xpd_t *PRI_card_new(xbus_t *xbus, int unit, int subunit, const xproto_table_t *proto_table,
|
|
byte subtype, int subunits, int subunit_ports, bool to_phone)
|
|
{
|
|
xpd_t *xpd = NULL;
|
|
struct PRI_priv_data *priv;
|
|
int channels = min(31, CHANNELS_PERXPD); /* worst case */
|
|
|
|
if(subunit_ports != 1) {
|
|
XBUS_ERR(xbus, "Bad subunit_ports=%d\n", subunit_ports);
|
|
return NULL;
|
|
}
|
|
XBUS_DBG(GENERAL, xbus, "\n");
|
|
xpd = xpd_alloc(xbus, unit, subunit, subtype, subunits, sizeof(struct PRI_priv_data), proto_table, channels);
|
|
if(!xpd)
|
|
return NULL;
|
|
priv = xpd->priv;
|
|
priv->pri_protocol = PRI_PROTO_0; /* Default, changes in set_pri_proto() */
|
|
priv->deflaw = DAHDI_LAW_DEFAULT; /* Default, changes in set_pri_proto() */
|
|
xpd->type_name = type_name(priv->pri_protocol);
|
|
#ifdef OLD_PROC
|
|
if(pri_proc_create(xbus, xpd) < 0) {
|
|
xpd_free(xpd);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
xbus->sync_mode_default = SYNC_MODE_AB;
|
|
return xpd;
|
|
}
|
|
|
|
static int PRI_card_init(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int ret = 0;
|
|
xproto_table_t *proto_table;
|
|
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(GENERAL, xpd, "\n");
|
|
xpd->type = XPD_TYPE_PRI;
|
|
proto_table = &PROTO_TABLE(PRI);
|
|
priv = xpd->priv;
|
|
if(priv->pri_protocol == PRI_PROTO_0) {
|
|
/*
|
|
* init_card_* script didn't set pri protocol
|
|
* Let's have a default E1
|
|
*/
|
|
ret = set_pri_proto(xpd, PRI_PROTO_E1);
|
|
if(ret < 0)
|
|
goto err;
|
|
}
|
|
SET_DCHAN(priv, NO_DCHAN);
|
|
/*
|
|
* initialization script should have set correct
|
|
* operating modes.
|
|
*/
|
|
if(!valid_pri_modes(xpd)) {
|
|
XPD_NOTICE(xpd, "PRI protocol not set\n");
|
|
goto err;
|
|
}
|
|
xpd->type_name = type_name(priv->pri_protocol);
|
|
xpd->direction = TO_PSTN;
|
|
XPD_DBG(DEVICES, xpd, "%s\n", xpd->type_name);
|
|
xpd->timing_priority = 1; /* High priority SLAVE */
|
|
set_master_mode(__FUNCTION__, xpd);
|
|
for(ret = 0; ret < NUM_LEDS; ret++) {
|
|
DO_LED(xpd, ret, PRI_LED_ON);
|
|
msleep(20);
|
|
DO_LED(xpd, ret, PRI_LED_OFF);
|
|
}
|
|
priv->initialized = 1;
|
|
return 0;
|
|
err:
|
|
#ifdef OLD_PROC
|
|
pri_proc_remove(xbus, xpd);
|
|
#endif
|
|
XPD_ERR(xpd, "Failed initializing registers (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static int PRI_card_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
XPD_DBG(GENERAL, xpd, "\n");
|
|
#ifdef OLD_PROC
|
|
pri_proc_remove(xbus, xpd);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DAHDI_AUDIO_NOTIFY
|
|
static int pri_audio_notify(struct dahdi_chan *chan, int on)
|
|
{
|
|
xpd_t *xpd = chan->pvt;
|
|
int pos = chan->chanpos - 1;
|
|
|
|
BUG_ON(!xpd);
|
|
LINE_DBG(SIGNAL, xpd, pos, "PRI-AUDIO: %s\n", (on) ? "on" : "off");
|
|
mark_offhook(xpd, pos, on);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dahdi_span_ops PRI_span_ops = {
|
|
.owner = THIS_MODULE,
|
|
.spanconfig = pri_spanconfig,
|
|
.chanconfig = pri_chanconfig,
|
|
.startup = pri_startup,
|
|
.shutdown = pri_shutdown,
|
|
.rbsbits = pri_rbsbits,
|
|
.open = xpp_open,
|
|
.close = xpp_close,
|
|
.hooksig = xpp_hooksig, /* Only with RBS bits */
|
|
.ioctl = xpp_ioctl,
|
|
.maint = xpp_maint,
|
|
#ifdef DAHDI_SYNC_TICK
|
|
.sync_tick = dahdi_sync_tick,
|
|
#endif
|
|
#ifdef CONFIG_DAHDI_WATCHDOG
|
|
.watchdog = xpp_watchdog,
|
|
#endif
|
|
|
|
#ifdef DAHDI_AUDIO_NOTIFY
|
|
.audio_notify = pri_audio_notify,
|
|
#endif
|
|
};
|
|
|
|
static int PRI_card_dahdi_preregistration(xpd_t *xpd, bool on)
|
|
{
|
|
xbus_t *xbus;
|
|
struct PRI_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
priv = xpd->priv;
|
|
BUG_ON(!xbus);
|
|
XPD_DBG(GENERAL, xpd, "%s (proto=%s, channels=%d, deflaw=%d)\n",
|
|
(on)?"on":"off",
|
|
pri_protocol_name(priv->pri_protocol),
|
|
xpd->channels,
|
|
priv->deflaw);
|
|
if(!on) {
|
|
/* Nothing to do yet */
|
|
return 0;
|
|
}
|
|
xpd->span.spantype = pri_protocol_name(priv->pri_protocol);
|
|
xpd->span.linecompat = pri_linecompat(priv->pri_protocol);
|
|
xpd->span.deflaw = priv->deflaw;
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
|
|
bool is_dchan = i == PRI_DCHAN_IDX(priv);
|
|
|
|
XPD_DBG(GENERAL, xpd, "setting PRI channel %d (%s)\n", i,
|
|
(is_dchan)?"DCHAN":"CLEAR");
|
|
snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%02d/%1d%1d/%d",
|
|
xpd->type_name, xbus->num, xpd->addr.unit, xpd->addr.subunit, i);
|
|
cur_chan->chanpos = i + 1;
|
|
cur_chan->pvt = xpd;
|
|
cur_chan->sigcap = PRI_SIGCAP;
|
|
if(is_dchan && !priv->is_cas) { /* D-CHAN */
|
|
//FIXME: cur_chan->flags |= DAHDI_FLAG_PRIDCHAN;
|
|
cur_chan->flags &= ~DAHDI_FLAG_HDLC;
|
|
}
|
|
}
|
|
xpd->offhook_state = xpd->wanted_pcm_mask;
|
|
xpd->span.ops = &PRI_span_ops;
|
|
return 0;
|
|
}
|
|
|
|
static int PRI_card_dahdi_postregistration(xpd_t *xpd, bool on)
|
|
{
|
|
xbus_t *xbus;
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
priv = xpd->priv;
|
|
BUG_ON(!xbus);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off");
|
|
dahdi_update_syncsrc(xpd);
|
|
return(0);
|
|
}
|
|
|
|
static void dchan_state(xpd_t *xpd, bool up)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(priv->is_cas) {
|
|
return;
|
|
}
|
|
if(priv->dchan_alive == up)
|
|
return;
|
|
if(!priv->layer1_up) /* No layer1, kill dchan */
|
|
up = 0;
|
|
if(up) {
|
|
XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel RUNNING\n");
|
|
priv->dchan_alive = 1;
|
|
} else {
|
|
int d = PRI_DCHAN_IDX(priv);
|
|
|
|
if(SPAN_REGISTERED(xpd) && d >= 0 && d < xpd->channels) {
|
|
byte *pcm;
|
|
|
|
pcm = (byte *)XPD_CHAN(xpd, d)->readchunk;
|
|
pcm[0] = 0x00;
|
|
pcm = (byte *)XPD_CHAN(xpd, d)->writechunk;
|
|
pcm[0] = 0x00;
|
|
}
|
|
XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel STOPPED\n");
|
|
priv->dchan_rx_counter = priv->dchan_tx_counter = 0;
|
|
priv->dchan_alive = 0;
|
|
priv->dchan_alive_ticks = 0;
|
|
priv->dchan_rx_sample = priv->dchan_tx_sample = 0x00;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* LED managment is done by the driver now:
|
|
* - Turn constant ON RED/GREEN led to indicate MASTER/SLAVE port
|
|
* - Very fast "Double Blink" to indicate Layer1 alive (without D-Channel)
|
|
* - Constant blink (1/2 sec cycle) to indicate D-Channel alive.
|
|
*/
|
|
static void handle_leds(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
unsigned int timer_count;
|
|
int which_led;
|
|
int other_led;
|
|
enum pri_led_state ledstate;
|
|
int mod;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(xpd->timing_priority == 0) {
|
|
which_led = TOP_RED_LED;
|
|
other_led = BOTTOM_GREEN_LED;
|
|
} else {
|
|
which_led = BOTTOM_GREEN_LED;
|
|
other_led = TOP_RED_LED;
|
|
}
|
|
ledstate = priv->ledstate[which_led];
|
|
timer_count = xpd->timer_count;
|
|
if(xpd->blink_mode) {
|
|
if((timer_count % DEFAULT_LED_PERIOD) == 0) {
|
|
// led state is toggled
|
|
if(ledstate == PRI_LED_OFF) {
|
|
DO_LED(xpd, which_led, PRI_LED_ON);
|
|
DO_LED(xpd, other_led, PRI_LED_ON);
|
|
} else {
|
|
DO_LED(xpd, which_led, PRI_LED_OFF);
|
|
DO_LED(xpd, other_led, PRI_LED_OFF);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if(priv->ledstate[other_led] != PRI_LED_OFF)
|
|
DO_LED(xpd, other_led, PRI_LED_OFF);
|
|
if(priv->dchan_alive) {
|
|
mod = timer_count % 1000;
|
|
switch(mod) {
|
|
case 0:
|
|
DO_LED(xpd, which_led, PRI_LED_ON);
|
|
break;
|
|
case 500:
|
|
DO_LED(xpd, which_led, PRI_LED_OFF);
|
|
break;
|
|
}
|
|
} else if(priv->layer1_up) {
|
|
mod = timer_count % 1000;
|
|
switch(mod) {
|
|
case 0:
|
|
case 100:
|
|
DO_LED(xpd, which_led, PRI_LED_ON);
|
|
break;
|
|
case 50:
|
|
case 150:
|
|
DO_LED(xpd, which_led, PRI_LED_OFF);
|
|
break;
|
|
}
|
|
} else {
|
|
if(priv->ledstate[which_led] != PRI_LED_ON)
|
|
DO_LED(xpd, which_led, PRI_LED_ON);
|
|
}
|
|
}
|
|
|
|
static int PRI_card_tick(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(!priv->initialized || !xbus->self_ticking)
|
|
return 0;
|
|
/*
|
|
* Poll layer1 status (cascade subunits)
|
|
*/
|
|
if(poll_interval != 0 &&
|
|
((xpd->timer_count % poll_interval) == 0)) {
|
|
priv->poll_noreplies++;
|
|
query_subunit(xpd, REG_FRS0);
|
|
//query_subunit(xpd, REG_FRS1);
|
|
}
|
|
if(priv->dchan_tx_counter >= 1 && priv->dchan_rx_counter > 1) {
|
|
dchan_state(xpd, 1);
|
|
priv->dchan_alive_ticks++;
|
|
}
|
|
handle_leds(xbus, xpd);
|
|
return 0;
|
|
}
|
|
|
|
static int PRI_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct dahdi_chan *chan;
|
|
|
|
BUG_ON(!xpd);
|
|
if(!XBUS_IS(xpd->xbus, READY))
|
|
return -ENODEV;
|
|
chan = XPD_CHAN(xpd, pos);
|
|
switch (cmd) {
|
|
case DAHDI_TONEDETECT:
|
|
/*
|
|
* Asterisk call all span types with this (FXS specific)
|
|
* call. Silently ignore it.
|
|
*/
|
|
LINE_DBG(SIGNAL, xpd, pos, "PRI: TONEDETECT (%s)\n",
|
|
(chan->flags & DAHDI_FLAG_AUDIO) ?
|
|
"AUDIO" : "SILENCE");
|
|
return -ENOTTY;
|
|
case DAHDI_ONHOOKTRANSFER:
|
|
LINE_DBG(SIGNAL, xpd, pos, "PRI: ONHOOKTRANSFER\n");
|
|
return -ENOTTY;
|
|
default:
|
|
report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
|
|
return -ENOTTY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int PRI_card_close(xpd_t *xpd, lineno_t pos)
|
|
{
|
|
//struct dahdi_chan *chan = XPD_CHAN(xpd, pos);
|
|
dchan_state(xpd, 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Called only for 'span' keyword in /etc/dahdi/system.conf
|
|
*/
|
|
static int pri_startup(struct dahdi_span *span)
|
|
{
|
|
xpd_t *xpd = container_of(span, struct xpd, span);
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(!XBUS_IS(xpd->xbus, READY)) {
|
|
XPD_DBG(GENERAL, xpd, "Startup called by dahdi. No Hardware. Ignored\n");
|
|
return -ENODEV;
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "STARTUP\n");
|
|
// Turn on all channels
|
|
CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 1);
|
|
set_rbslines(xpd, 0);
|
|
write_subunit(xpd, REG_XPM2, 0x00);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Called only for 'span' keyword in /etc/dahdi/system.conf
|
|
*/
|
|
static int pri_shutdown(struct dahdi_span *span)
|
|
{
|
|
xpd_t *xpd = container_of(span, struct xpd, span);
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(!XBUS_IS(xpd->xbus, READY)) {
|
|
XPD_DBG(GENERAL, xpd, "Shutdown called by dahdi. No Hardware. Ignored\n");
|
|
return -ENODEV;
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "SHUTDOWN\n");
|
|
// Turn off all channels
|
|
CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int encode_rbsbits_e1(xpd_t *xpd, int pos, int bits)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
byte val;
|
|
int rsnum;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
BUG_ON(priv->pri_protocol != PRI_PROTO_E1);
|
|
if(pos == 15)
|
|
return 0; /* Don't write dchan in CAS */
|
|
if(pos < 0 || pos > 31) {
|
|
XPD_NOTICE(xpd, "%s: pos=%d out of range. Ignore\n", __FUNCTION__, pos);
|
|
return 0;
|
|
}
|
|
if(pos >= 16) {
|
|
/* Low nibble */
|
|
rsnum = pos - 16;
|
|
val = (priv->cas_ts_e[rsnum] & 0xF0) | (bits & 0x0F);
|
|
} else {
|
|
/* High nibble */
|
|
rsnum = pos;
|
|
val = (priv->cas_ts_e[rsnum] & 0x0F) | ((bits << 4) & 0xF0);
|
|
}
|
|
LINE_DBG(SIGNAL, xpd, pos, "RBS: TX: bits=0x%X\n", bits);
|
|
write_cas_reg(xpd, rsnum, val);
|
|
return 0;
|
|
}
|
|
|
|
static int encode_rbsbits_t1(xpd_t *xpd, int pos, int bits)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int rsnum;
|
|
int chan_per_reg;
|
|
int offset;
|
|
int width;
|
|
uint tx_bits = bits;
|
|
uint mask;
|
|
byte val;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
BUG_ON(priv->pri_protocol != PRI_PROTO_T1);
|
|
if(pos < 0 || pos >= xpd->channels) {
|
|
XPD_ERR(xpd, "%s: Bad pos=%d\n", __FUNCTION__, pos);
|
|
return 0;
|
|
}
|
|
chan_per_reg = CHAN_PER_REGS(priv);
|
|
width = 8 / chan_per_reg;
|
|
rsnum = pos / chan_per_reg;
|
|
offset = pos % chan_per_reg;
|
|
mask = BITMASK(width) << (chan_per_reg - offset - 1) * width;
|
|
if (!priv->is_esf)
|
|
tx_bits >>= 2;
|
|
tx_bits &= BITMASK(width);
|
|
tx_bits <<= (chan_per_reg - offset - 1) * width;
|
|
val = priv->cas_ts_e[rsnum];
|
|
val &= ~mask;
|
|
val |= tx_bits;
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"bits=0x%02X RS%02d(%s) offset=%d tx_bits=0x%02X\n",
|
|
bits, rsnum+1,
|
|
(priv->is_esf) ? "esf" : "d4",
|
|
offset, tx_bits);
|
|
write_cas_reg(xpd, rsnum , val);
|
|
priv->dchan_tx_counter++;
|
|
return 0;
|
|
}
|
|
|
|
static void send_idlebits(xpd_t *xpd, bool saveold)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
byte save_rs[NUM_CAS_RS_E];
|
|
int i;
|
|
|
|
if (!SPAN_REGISTERED(xpd))
|
|
return;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
XPD_DBG(SIGNAL, xpd, "saveold=%d\n", saveold);
|
|
if (saveold)
|
|
memcpy(save_rs, priv->cas_ts_e, sizeof(save_rs));
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
|
|
pri_rbsbits(chan, chan->idlebits);
|
|
}
|
|
if (saveold)
|
|
memcpy(priv->cas_ts_e, save_rs, sizeof(save_rs));
|
|
}
|
|
|
|
static void send_oldbits(xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
XPD_DBG(SIGNAL, xpd, "\n");
|
|
for (i = 0; i < cas_numregs(xpd); i++)
|
|
write_cas_reg(xpd, i , priv->cas_ts_e[i]);
|
|
}
|
|
|
|
static int pri_rbsbits(struct dahdi_chan *chan, int bits)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
int pos;
|
|
|
|
xpd = chan->pvt;
|
|
BUG_ON(!xpd);
|
|
pos = chan->chanpos - 1;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(!priv->layer1_up) {
|
|
XPD_DBG(SIGNAL, xpd, "RBS: TX: No layer1 yet. Keep going.\n");
|
|
}
|
|
if(!priv->is_cas) {
|
|
XPD_DBG(SIGNAL, xpd, "RBS: TX: not in CAS mode. Ignore.\n");
|
|
return 0;
|
|
}
|
|
if (chan->sig == DAHDI_SIG_NONE) {
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"RBS: TX: sigtyp=%s. , bits=0x%X. Ignore.\n",
|
|
sig2str(chan->sig), bits);
|
|
return 0;
|
|
}
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
if(encode_rbsbits_e1(xpd, pos, bits) < 0)
|
|
return -EINVAL;
|
|
} else if(priv->pri_protocol == PRI_PROTO_T1) {
|
|
if(encode_rbsbits_t1(xpd, pos, bits) < 0)
|
|
return -EINVAL;
|
|
} else {
|
|
XPD_NOTICE(xpd, "%s: protocol %s is not supported yet with CAS\n",
|
|
__FUNCTION__, pri_protocol_name(priv->pri_protocol));
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*! Copy PCM chunks from the buffers of the xpd to a new packet
|
|
* \param xbus xbus of source xpd.
|
|
* \param xpd source xpd.
|
|
* \param lines a bitmask of the active channels that need to be copied.
|
|
* \param pack packet to be filled.
|
|
*
|
|
* On PRI this function is should also shift the lines mask one bit, as
|
|
* channel 0 on the wire is an internal chip control channel. We only
|
|
* send 31 channels to the device, but they should be called 1-31 rather
|
|
* than 0-30 .
|
|
*/
|
|
static void PRI_card_pcm_fromspan(xbus_t *xbus, xpd_t *xpd, xpacket_t *pack)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
byte *pcm;
|
|
unsigned long flags;
|
|
int i;
|
|
xpp_line_t wanted_lines;
|
|
int physical_chan;
|
|
int physical_mask = 0;
|
|
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xpd);
|
|
BUG_ON(!pack);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
wanted_lines = xpd->wanted_pcm_mask;
|
|
physical_chan = 0;
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
/* In E1 - Only 0'th channel is unused */
|
|
if(i == 0) {
|
|
physical_chan++;
|
|
}
|
|
} else if(priv->pri_protocol == PRI_PROTO_T1) {
|
|
/* In T1 - Every 4'th channel is unused */
|
|
if((i % 3) == 0) {
|
|
physical_chan++;
|
|
}
|
|
}
|
|
if(IS_SET(wanted_lines, i)) {
|
|
physical_mask |= BIT(physical_chan);
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
#ifdef DEBUG_PCMTX
|
|
int channo = XPD_CHAN(xpd, i)->channo;
|
|
|
|
if(pcmtx >= 0 && pcmtx_chan == channo)
|
|
memset((u_char *)pcm, pcmtx, DAHDI_CHUNKSIZE);
|
|
else
|
|
#endif
|
|
memcpy((u_char *)pcm, chan->writechunk, DAHDI_CHUNKSIZE);
|
|
if(i == PRI_DCHAN_IDX(priv)) {
|
|
if(priv->dchan_tx_sample != chan->writechunk[0]) {
|
|
priv->dchan_tx_sample = chan->writechunk[0];
|
|
priv->dchan_tx_counter++;
|
|
} else if(chan->writechunk[0] == 0xFF)
|
|
dchan_state(xpd, 0);
|
|
else
|
|
chan->writechunk[0] = 0xFF; /* Clobber for next tick */
|
|
}
|
|
} else
|
|
memset((u_char *)pcm, DAHDI_XLAW(0, chan), DAHDI_CHUNKSIZE);
|
|
pcm += DAHDI_CHUNKSIZE;
|
|
}
|
|
physical_chan++;
|
|
}
|
|
RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = physical_mask;
|
|
XPD_COUNTER(xpd, PCM_WRITE)++;
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
}
|
|
|
|
/*! Copy PCM chunks from the packet we received to the xpd struct.
|
|
* \param xbus xbus of target xpd.
|
|
* \param xpd target xpd.
|
|
* \param pack Source packet.
|
|
*
|
|
* On PRI this function is should also shift the lines back mask one bit, as
|
|
* channel 0 on the wire is an internal chip control channel.
|
|
*
|
|
* \see PRI_card_pcm_fromspan
|
|
*/
|
|
static void PRI_card_pcm_tospan(xbus_t *xbus, xpd_t *xpd, xpacket_t *pack)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
byte *pcm;
|
|
xpp_line_t physical_mask;
|
|
unsigned long flags;
|
|
int i;
|
|
int logical_chan;
|
|
|
|
if(!SPAN_REGISTERED(xpd))
|
|
return;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm);
|
|
physical_mask = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
logical_chan = 0;
|
|
for (i = 0; i < CHANNELS_PERXPD; i++) {
|
|
volatile u_char *r;
|
|
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
/* In E1 - Only 0'th channel is unused */
|
|
if(i == 0)
|
|
continue;
|
|
} else if(priv->pri_protocol == PRI_PROTO_T1) {
|
|
/* In T1 - Every 4'th channel is unused */
|
|
if((i % 4) == 0)
|
|
continue;
|
|
}
|
|
if(logical_chan == PRI_DCHAN_IDX(priv) && !priv->is_cas) {
|
|
if(priv->dchan_rx_sample != pcm[0]) {
|
|
if(debug & DBG_PCM) {
|
|
XPD_INFO(xpd, "RX-D-Chan: prev=0x%X now=0x%X\n",
|
|
priv->dchan_rx_sample, pcm[0]);
|
|
dump_packet("RX-D-Chan", pack, 1);
|
|
}
|
|
priv->dchan_rx_sample = pcm[0];
|
|
priv->dchan_rx_counter++;
|
|
} else if(pcm[0] == 0xFF)
|
|
dchan_state(xpd, 0);
|
|
}
|
|
if(IS_SET(physical_mask, i)) {
|
|
r = XPD_CHAN(xpd, logical_chan)->readchunk;
|
|
// memset((u_char *)r, 0x5A, DAHDI_CHUNKSIZE); // DEBUG
|
|
memcpy((u_char *)r, pcm, DAHDI_CHUNKSIZE);
|
|
pcm += DAHDI_CHUNKSIZE;
|
|
}
|
|
logical_chan++;
|
|
}
|
|
XPD_COUNTER(xpd, PCM_READ)++;
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
}
|
|
|
|
int PRI_timing_priority(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if (priv->layer1_up)
|
|
return xpd->timing_priority;
|
|
XPD_DBG(SYNC, xpd, "No timing priority (no layer1)\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
/*---------------- PRI: HOST COMMANDS -------------------------------------*/
|
|
|
|
static /* 0x0F */ HOSTCMD(PRI, XPD_STATE, bool on)
|
|
{
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off");
|
|
return 0;
|
|
}
|
|
|
|
static /* 0x33 */ HOSTCMD(PRI, SET_LED, enum pri_led_selectors led_sel, enum pri_led_state to_led_state)
|
|
{
|
|
int ret = 0;
|
|
xframe_t *xframe;
|
|
xpacket_t *pack;
|
|
struct pri_leds *pri_leds;
|
|
struct PRI_priv_data *priv;
|
|
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
XPD_DBG(LEDS, xpd, "led_sel=%d to_state=%d\n", led_sel, to_led_state);
|
|
XFRAME_NEW_CMD(xframe, pack, xbus, PRI, SET_LED, xpd->xbus_idx);
|
|
pri_leds = &RPACKET_FIELD(pack, PRI, SET_LED, pri_leds);
|
|
pri_leds->state = to_led_state;
|
|
pri_leds->led_sel = led_sel;
|
|
pri_leds->reserved = 0;
|
|
XPACKET_LEN(pack) = RPACKET_SIZE(PRI, SET_LED);
|
|
ret = send_cmd_frame(xbus, xframe);
|
|
priv->ledstate[led_sel] = to_led_state;
|
|
return ret;
|
|
}
|
|
|
|
/*---------------- PRI: Astribank Reply Handlers --------------------------*/
|
|
static void layer1_state(xpd_t *xpd, byte data_low)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
int alarms = DAHDI_ALARM_NONE;
|
|
int layer1_up_prev;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
priv->poll_noreplies = 0;
|
|
if(data_low & REG_FRS0_LOS)
|
|
alarms |= DAHDI_ALARM_RED;
|
|
if(data_low & REG_FRS0_AIS)
|
|
alarms |= DAHDI_ALARM_BLUE;
|
|
if(data_low & REG_FRS0_RRA)
|
|
alarms |= DAHDI_ALARM_YELLOW;
|
|
layer1_up_prev = priv->layer1_up;
|
|
priv->layer1_up = alarms == DAHDI_ALARM_NONE;
|
|
#if 0
|
|
/*
|
|
* Some bad bits (e.g: LMFA and NMF have no alarm "colors"
|
|
* associated. However, layer1 is still not working if they are set.
|
|
* FIXME: These behave differently in E1/T1, so ignore them for while.
|
|
*/
|
|
if(data_low & (REG_FRS0_LMFA | REG_FRS0_E1_NMF))
|
|
priv->layer1_up = 0;
|
|
#endif
|
|
priv->alarms = alarms;
|
|
if(!priv->layer1_up) {
|
|
dchan_state(xpd, 0);
|
|
} else if (priv->is_cas && !layer1_up_prev) {
|
|
int regbase = cas_regbase(xpd);
|
|
int i;
|
|
|
|
XPD_DBG(SIGNAL , xpd,
|
|
"Returning From Alarm Refreshing Rx register data \n");
|
|
for (i = 0; i < cas_numregs(xpd); i++)
|
|
query_subunit(xpd, regbase + i);
|
|
}
|
|
|
|
if(SPAN_REGISTERED(xpd) && xpd->span.alarms != alarms) {
|
|
char str1[MAX_PROC_WRITE];
|
|
char str2[MAX_PROC_WRITE];
|
|
|
|
alarm2str(xpd->span.alarms, str1, sizeof(str1));
|
|
alarm2str(alarms, str2, sizeof(str2));
|
|
XPD_NOTICE(xpd, "Alarms: 0x%X (%s) => 0x%X (%s)\n",
|
|
xpd->span.alarms, str1,
|
|
alarms, str2);
|
|
if (priv->is_cas) {
|
|
if (alarms == DAHDI_ALARM_NONE)
|
|
send_oldbits(xpd);
|
|
else if (xpd->span.alarms == DAHDI_ALARM_NONE)
|
|
send_idlebits(xpd, 1);
|
|
}
|
|
xpd->span.alarms = alarms;
|
|
elect_syncer("LAYER1");
|
|
dahdi_alarm_notify(&xpd->span);
|
|
set_clocking(xpd);
|
|
}
|
|
priv->reg_frs0 = data_low;
|
|
priv->layer1_replies++;
|
|
XPD_DBG(REGS, xpd, "subunit=%d data_low=0x%02X\n", xpd->addr.subunit, data_low);
|
|
}
|
|
|
|
static int decode_cas_e1(xpd_t *xpd, byte regnum, byte data_low)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
uint pos = regnum - REG_RS2_E;
|
|
int rsnum = pos + 2;
|
|
int chan1 = pos;
|
|
int chan2 = pos + 16;
|
|
int val1 = (data_low >> 4) & 0xF;
|
|
int val2 = data_low & 0xF;
|
|
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv->is_cas);
|
|
BUG_ON(priv->pri_protocol != PRI_PROTO_E1);
|
|
XPD_DBG(SIGNAL, xpd, "RBS: RX: data_low=0x%02X\n", data_low);
|
|
if(pos < 0 || pos >= NUM_CAS_RS_E) {
|
|
XPD_ERR(xpd, "%s: got bad pos=%d [0-%d]\n", __FUNCTION__, pos, NUM_CAS_RS_E);
|
|
return -EINVAL;
|
|
}
|
|
if(chan1 < 0 || chan1 > xpd->channels) {
|
|
XPD_NOTICE(xpd, "%s: %s CAS: Bad chan1 number (%d)\n",
|
|
__FUNCTION__,
|
|
pri_protocol_name(priv->pri_protocol),
|
|
chan1);
|
|
return -EINVAL;
|
|
}
|
|
if(chan2 < 0 || chan2 > xpd->channels) {
|
|
XPD_NOTICE(xpd, "%s: %s CAS: Bad chan2 number (%d)\n",
|
|
__FUNCTION__,
|
|
pri_protocol_name(priv->pri_protocol),
|
|
chan2);
|
|
return -EINVAL;
|
|
}
|
|
XPD_DBG(SIGNAL, xpd, "RBS: RX: RS%02d (channel %2d, channel %2d): 0x%02X -> 0x%02X\n",
|
|
rsnum, chan1+1, chan2+1, priv->cas_rs_e[pos], data_low);
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
dahdi_rbsbits(XPD_CHAN(xpd, chan1), val1);
|
|
dahdi_rbsbits(XPD_CHAN(xpd, chan2), val2);
|
|
}
|
|
priv->dchan_rx_counter++;
|
|
priv->cas_rs_e[pos] = data_low;
|
|
return 0;
|
|
}
|
|
|
|
static int decode_cas_t1(xpd_t *xpd, byte regnum, byte data_low)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
uint rsnum;
|
|
uint chan_per_reg;
|
|
uint width;
|
|
int i;
|
|
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv->is_cas);
|
|
BUG_ON(priv->pri_protocol != PRI_PROTO_T1);
|
|
rsnum = regnum - REG_RS1_E;
|
|
if(rsnum < 0 || rsnum >= 12) {
|
|
XPD_ERR(xpd, "Bad rsnum=%d\n", rsnum);
|
|
return 0;
|
|
}
|
|
if(!priv->is_esf)
|
|
rsnum = rsnum % 6; /* 2 identical banks of 6 registers */
|
|
chan_per_reg = CHAN_PER_REGS(priv);
|
|
width = 8 / chan_per_reg;
|
|
XPD_DBG(SIGNAL, xpd,
|
|
"RBS: RX(%s,%d): RS%02d data_low=0x%02X\n",
|
|
(priv->is_esf) ? "esf" : "d4",
|
|
chan_per_reg,
|
|
rsnum+1, data_low);
|
|
for(i = 0; i < chan_per_reg; i++) {
|
|
uint rxsig = (data_low >> (i * width)) & BITMASK(width);
|
|
int pos;
|
|
struct dahdi_chan *chan;
|
|
|
|
if (!priv->is_esf)
|
|
rxsig <<= 2;
|
|
pos = rsnum * chan_per_reg + chan_per_reg - i - 1;
|
|
if(pos < 0 || pos >= xpd->channels) {
|
|
XPD_ERR(xpd, "%s: Bad pos=%d\n", __FUNCTION__, pos);
|
|
continue;
|
|
}
|
|
chan = XPD_CHAN(xpd, pos);
|
|
if(!chan) {
|
|
XPD_ERR(xpd, "%s: Null channel in pos=%d\n", __FUNCTION__, pos);
|
|
continue;
|
|
}
|
|
if(chan->rxsig != rxsig) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "i=%d rxsig=0x%02X\n", i, rxsig);
|
|
dahdi_rbsbits(chan, rxsig);
|
|
}
|
|
}
|
|
priv->cas_rs_e[rsnum] = data_low;
|
|
return 0;
|
|
}
|
|
|
|
static void process_cas_dchan(xpd_t *xpd, byte regnum, byte data_low)
|
|
{
|
|
struct PRI_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
if(!priv->is_cas) {
|
|
static int rate_limit;
|
|
|
|
if((rate_limit++ % 10003) == 0)
|
|
XPD_NOTICE(xpd, "RBS: RX: not in CAS mode. Ignore.\n");
|
|
return;
|
|
}
|
|
if(!priv->layer1_up) {
|
|
static int rate_limit;
|
|
|
|
if((rate_limit++ % 10003) == 0)
|
|
XPD_DBG(SIGNAL, xpd, "RBS: RX: No layer1.\n");
|
|
}
|
|
if(!SPAN_REGISTERED(xpd)) {
|
|
static int rate_limit;
|
|
|
|
if((rate_limit++ % 10003) == 0)
|
|
XPD_DBG(SIGNAL, xpd, "RBS: RX: Span not registered. Ignore.\n");
|
|
return;
|
|
}
|
|
if(priv->pri_protocol == PRI_PROTO_E1) {
|
|
if(regnum == REG_RS1_E)
|
|
return; /* Time slot 0: Ignored for E1 */
|
|
if(regnum < REG_RS2_E) {
|
|
/* Should not happen, but harmless. Ignore */
|
|
if (regnum == REG_RS1_E)
|
|
return;
|
|
|
|
XPD_NOTICE(xpd,
|
|
"%s: received register 0x%X in protocol %s. Ignore\n",
|
|
__FUNCTION__, regnum, pri_protocol_name(priv->pri_protocol));
|
|
return;
|
|
}
|
|
if(decode_cas_e1(xpd, regnum, data_low) < 0)
|
|
return;
|
|
} else if(priv->pri_protocol == PRI_PROTO_T1) {
|
|
if(regnum > REG_RS12_E) {
|
|
XPD_NOTICE(xpd,
|
|
"%s: received register 0x%X in protocol %s. Ignore\n",
|
|
__FUNCTION__, regnum, pri_protocol_name(priv->pri_protocol));
|
|
return;
|
|
}
|
|
if(decode_cas_t1(xpd, regnum, data_low) < 0)
|
|
return;
|
|
} else {
|
|
XPD_NOTICE(xpd, "%s: protocol %s is not supported yet with CAS\n",
|
|
__FUNCTION__, pri_protocol_name(priv->pri_protocol));
|
|
}
|
|
priv->cas_replies++;
|
|
}
|
|
|
|
static int PRI_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
|
|
{
|
|
unsigned long flags;
|
|
struct PRI_priv_data *priv;
|
|
struct xpd_addr addr;
|
|
xpd_t *orig_xpd;
|
|
byte regnum;
|
|
byte data_low;
|
|
|
|
/* Map UNIT + PORTNUM to XPD */
|
|
orig_xpd = xpd;
|
|
addr.unit = orig_xpd->addr.unit;
|
|
addr.subunit = info->portnum;
|
|
regnum = REG_FIELD(info, regnum);
|
|
data_low = REG_FIELD(info, data_low);
|
|
xpd = xpd_byaddr(xbus, addr.unit, addr.subunit);
|
|
if(!xpd) {
|
|
static int rate_limit;
|
|
|
|
if((rate_limit++ % 1003) < 5)
|
|
notify_bad_xpd(__FUNCTION__, xbus, addr , orig_xpd->xpdname);
|
|
return -EPROTO;
|
|
}
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if(info->is_multibyte) {
|
|
XPD_NOTICE(xpd, "Got Multibyte: %d bytes, eoframe: %d\n",
|
|
info->bytes, info->eoframe);
|
|
goto end;
|
|
}
|
|
if(regnum == REG_FRS0 && !REG_FIELD(info, do_subreg))
|
|
layer1_state(xpd, data_low);
|
|
else if(regnum == REG_FRS1 && !REG_FIELD(info, do_subreg))
|
|
priv->reg_frs1 = data_low;
|
|
if(priv->is_cas && !REG_FIELD(info, do_subreg)) {
|
|
if(regnum >= REG_RS1_E && regnum <= REG_RS16_E) {
|
|
process_cas_dchan(xpd, regnum, data_low);
|
|
}
|
|
}
|
|
/* Update /proc info only if reply relate to the last slic read request */
|
|
if(
|
|
REG_FIELD(&xpd->requested_reply, regnum) == REG_FIELD(info, regnum) &&
|
|
REG_FIELD(&xpd->requested_reply, do_subreg) == REG_FIELD(info, do_subreg) &&
|
|
REG_FIELD(&xpd->requested_reply, subreg) == REG_FIELD(info, subreg)) {
|
|
xpd->last_reply = *info;
|
|
}
|
|
|
|
end:
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static xproto_table_t PROTO_TABLE(PRI) = {
|
|
.owner = THIS_MODULE,
|
|
.entries = {
|
|
/* Table Card Opcode */
|
|
},
|
|
.name = "PRI", /* protocol name */
|
|
.ports_per_subunit = 1,
|
|
.type = XPD_TYPE_PRI,
|
|
.xops = {
|
|
.card_new = PRI_card_new,
|
|
.card_init = PRI_card_init,
|
|
.card_remove = PRI_card_remove,
|
|
.card_dahdi_preregistration = PRI_card_dahdi_preregistration,
|
|
.card_dahdi_postregistration = PRI_card_dahdi_postregistration,
|
|
.card_tick = PRI_card_tick,
|
|
.card_pcm_recompute = PRI_card_pcm_recompute,
|
|
.card_pcm_fromspan = PRI_card_pcm_fromspan,
|
|
.card_pcm_tospan = PRI_card_pcm_tospan,
|
|
.card_timing_priority = PRI_timing_priority,
|
|
.card_ioctl = PRI_card_ioctl,
|
|
.card_close = PRI_card_close,
|
|
.card_register_reply = PRI_card_register_reply,
|
|
|
|
.XPD_STATE = XPROTO_CALLER(PRI, XPD_STATE),
|
|
},
|
|
.packet_is_valid = pri_packet_is_valid,
|
|
.packet_dump = pri_packet_dump,
|
|
};
|
|
|
|
static bool pri_packet_is_valid(xpacket_t *pack)
|
|
{
|
|
const xproto_entry_t *xe = NULL;
|
|
// DBG(GENERAL, "\n");
|
|
xe = xproto_card_entry(&PROTO_TABLE(PRI), XPACKET_OP(pack));
|
|
return xe != NULL;
|
|
}
|
|
|
|
static void pri_packet_dump(const char *msg, xpacket_t *pack)
|
|
{
|
|
DBG(GENERAL, "%s\n", msg);
|
|
}
|
|
/*------------------------- REGISTER Handling --------------------------*/
|
|
#ifdef OLD_PROC
|
|
static int proc_pri_info_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
|
|
{
|
|
xpd_t *xpd = data;
|
|
struct PRI_priv_data *priv;
|
|
char buf[MAX_PROC_WRITE];
|
|
char *p;
|
|
char *tok;
|
|
int ret = 0;
|
|
bool got_localloop = 0;
|
|
bool got_nolocalloop = 0;
|
|
bool got_e1 = 0;
|
|
bool got_t1 = 0;
|
|
bool got_j1 = 0;
|
|
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
XPD_NOTICE(xpd, "%s: DEPRECATED: %s[%d] write to /proc interface instead of /sys\n",
|
|
__FUNCTION__, current->comm, current->tgid);
|
|
priv = xpd->priv;
|
|
if(count >= MAX_PROC_WRITE) { /* leave room for null */
|
|
XPD_ERR(xpd, "write too long (%ld)\n", count);
|
|
return -E2BIG;
|
|
}
|
|
if(copy_from_user(buf, buffer, count)) {
|
|
XPD_ERR(xpd, "Failed reading user data\n");
|
|
return -EFAULT;
|
|
}
|
|
buf[count] = '\0';
|
|
XPD_DBG(PROC, xpd, "PRI-SETUP: got %s\n", buf);
|
|
/*
|
|
* First parse. Act only of *everything* is good.
|
|
*/
|
|
p = buf;
|
|
while((tok = strsep(&p, " \t\v\n")) != NULL) {
|
|
if(*tok == '\0')
|
|
continue;
|
|
XPD_DBG(PROC, xpd, "Got token='%s'\n", tok);
|
|
if(strnicmp(tok, "LOCALLOOP", 8) == 0)
|
|
got_localloop = 1;
|
|
else if(strnicmp(tok, "NOLOCALLOOP", 8) == 0)
|
|
got_nolocalloop = 1;
|
|
else if(strnicmp(tok, "E1", 2) == 0)
|
|
got_e1 = 1;
|
|
else if(strnicmp(tok, "T1", 2) == 0)
|
|
got_t1 = 1;
|
|
else if(strnicmp(tok, "J1", 2) == 0) {
|
|
got_j1 = 1;
|
|
} else {
|
|
XPD_NOTICE(xpd, "PRI-SETUP: unknown keyword: '%s'\n", tok);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if(got_e1)
|
|
ret = set_pri_proto(xpd, PRI_PROTO_E1);
|
|
else if(got_t1)
|
|
ret = set_pri_proto(xpd, PRI_PROTO_T1);
|
|
else if(got_j1)
|
|
ret = set_pri_proto(xpd, PRI_PROTO_J1);
|
|
if(priv->pri_protocol == PRI_PROTO_0) {
|
|
XPD_ERR(xpd,
|
|
"Must set PRI protocol (E1/T1/J1) before setting other parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
if(got_localloop)
|
|
ret = set_localloop(xpd, 1);
|
|
if(got_nolocalloop)
|
|
ret = set_localloop(xpd, 0);
|
|
return (ret) ? ret : count;
|
|
}
|
|
|
|
|
|
static int proc_pri_info_read(char *page, char **start, off_t off, int count, int *eof, void *data)
|
|
{
|
|
int len = 0;
|
|
unsigned long flags;
|
|
xpd_t *xpd = data;
|
|
struct PRI_priv_data *priv;
|
|
int i;
|
|
|
|
DBG(PROC, "\n");
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
XPD_NOTICE(xpd, "%s: DEPRECATED: %s[%d] read from /proc interface instead of /sys\n",
|
|
__FUNCTION__, current->comm, current->tgid);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
len += sprintf(page + len, "PRI: %s %s%s (deflaw=%d, dchan=%d)\n",
|
|
(priv->clock_source) ? "MASTER" : "SLAVE",
|
|
pri_protocol_name(priv->pri_protocol),
|
|
(priv->local_loopback) ? " LOCALLOOP" : "",
|
|
priv->deflaw, priv->dchan_num);
|
|
len += sprintf(page + len, "%05d Layer1: ", priv->layer1_replies);
|
|
if(priv->poll_noreplies > 1)
|
|
len += sprintf(page + len, "No Replies [%d]\n",
|
|
priv->poll_noreplies);
|
|
else {
|
|
len += sprintf(page + len, "%s\n",
|
|
((priv->layer1_up) ? "UP" : "DOWN"));
|
|
len += sprintf(page + len,
|
|
"Framer Status: FRS0=0x%02X, FRS1=0x%02X ALARMS:",
|
|
priv->reg_frs0, priv->reg_frs1);
|
|
if(priv->reg_frs0 & REG_FRS0_LOS)
|
|
len += sprintf(page + len, " RED");
|
|
if(priv->reg_frs0 & REG_FRS0_AIS)
|
|
len += sprintf(page + len, " BLUE");
|
|
if(priv->reg_frs0 & REG_FRS0_RRA)
|
|
len += sprintf(page + len, " YELLOW");
|
|
len += sprintf(page + len, "\n");
|
|
}
|
|
if(priv->is_cas) {
|
|
len += sprintf(page + len,
|
|
"CAS: replies=%d\n", priv->cas_replies);
|
|
len += sprintf(page + len, " CAS-TS: ");
|
|
for(i = 0; i < NUM_CAS_RS_E; i++) {
|
|
len += sprintf(page + len, " %02X", priv->cas_ts_e[i]);
|
|
}
|
|
len += sprintf(page + len, "\n");
|
|
len += sprintf(page + len, " CAS-RS: ");
|
|
for(i = 0; i < NUM_CAS_RS_E; i++) {
|
|
len += sprintf(page + len, " %02X", priv->cas_rs_e[i]);
|
|
}
|
|
len += sprintf(page + len, "\n");
|
|
}
|
|
len += sprintf(page + len, "D-Channel: TX=[%5d] (0x%02X) RX=[%5d] (0x%02X) ",
|
|
priv->dchan_tx_counter, priv->dchan_tx_sample,
|
|
priv->dchan_rx_counter, priv->dchan_rx_sample);
|
|
if(priv->dchan_alive) {
|
|
len += sprintf(page + len, "(alive %d K-ticks)\n",
|
|
priv->dchan_alive_ticks/1000);
|
|
} else {
|
|
len += sprintf(page + len, "(dead)\n");
|
|
}
|
|
for(i = 0; i < NUM_LEDS; i++)
|
|
len += sprintf(page + len, "LED #%d: %d\n", i, priv->ledstate[i]);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
if (len <= off+count)
|
|
*eof = 1;
|
|
*start = page + off;
|
|
len -= off;
|
|
if (len > count)
|
|
len = count;
|
|
if (len < 0)
|
|
len = 0;
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
/*------------------------- sysfs stuff --------------------------------*/
|
|
static DEVICE_ATTR_READER(pri_protocol_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf, "%s\n", pri_protocol_name(priv->pri_protocol));
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(pri_protocol_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
enum pri_protocol new_protocol = PRI_PROTO_0;
|
|
int i;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", buf);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
if((i = strcspn(buf, " \r\n")) != 2) {
|
|
XPD_NOTICE(xpd,
|
|
"Protocol name '%s' has %d characters (should be 2). Ignored.\n",
|
|
buf, i);
|
|
return -EINVAL;
|
|
}
|
|
if(strnicmp(buf, "E1", 2) == 0)
|
|
new_protocol = PRI_PROTO_E1;
|
|
else if(strnicmp(buf, "T1", 2) == 0)
|
|
new_protocol = PRI_PROTO_T1;
|
|
else if(strnicmp(buf, "J1", 2) == 0)
|
|
new_protocol = PRI_PROTO_J1;
|
|
else {
|
|
XPD_NOTICE(xpd,
|
|
"Unknown PRI protocol '%s' (should be E1|T1|J1). Ignored.\n",
|
|
buf);
|
|
return -EINVAL;
|
|
}
|
|
ret = set_pri_proto(xpd, new_protocol);
|
|
return (ret < 0) ? ret : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_protocol, S_IRUGO | S_IWUSR, pri_protocol_show, pri_protocol_store);
|
|
|
|
static DEVICE_ATTR_READER(pri_localloop_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf, "%c\n",
|
|
(priv->local_loopback) ? 'Y' : 'N');
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(pri_localloop_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
bool ll = 0;
|
|
int i;
|
|
int ret;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", buf);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
if((i = strcspn(buf, " \r\n")) != 1) {
|
|
XPD_NOTICE(xpd,
|
|
"Value '%s' has %d characters (should be 1). Ignored.\n",
|
|
buf, i);
|
|
return -EINVAL;
|
|
}
|
|
if(strchr("1Yy", buf[0]) != NULL)
|
|
ll = 1;
|
|
else if(strchr("0Nn", buf[0]) != NULL)
|
|
ll = 0;
|
|
else {
|
|
XPD_NOTICE(xpd,
|
|
"Unknown value '%s' (should be [1Yy]|[0Nn]). Ignored.\n",
|
|
buf);
|
|
return -EINVAL;
|
|
}
|
|
ret = set_localloop(xpd, ll);
|
|
return (ret < 0) ? ret : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_localloop, S_IRUGO | S_IWUSR, pri_localloop_show, pri_localloop_store);
|
|
|
|
static DEVICE_ATTR_READER(pri_layer1_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
if(priv->poll_noreplies > 1)
|
|
len += sprintf(buf + len, "Unknown[%d]", priv->poll_noreplies);
|
|
else
|
|
len += sprintf(buf + len, "%-10s", ((priv->layer1_up) ? "UP" : "DOWN"));
|
|
len += sprintf(buf + len, "%d\n", priv->layer1_replies);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_layer1, S_IRUGO, pri_layer1_show, NULL);
|
|
|
|
static DEVICE_ATTR_READER(pri_alarms_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
const static struct {
|
|
byte bits;
|
|
const char *name;
|
|
} alarm_types[] = {
|
|
{ REG_FRS0_LOS, "RED" },
|
|
{ REG_FRS0_AIS, "BLUE" },
|
|
{ REG_FRS0_RRA, "YELLOW" },
|
|
};
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
if(priv->poll_noreplies > 1)
|
|
len += sprintf(buf + len, "Unknown[%d]", priv->poll_noreplies);
|
|
else {
|
|
int i;
|
|
|
|
for(i = 0; i < ARRAY_SIZE(alarm_types); i++) {
|
|
if(priv->reg_frs0 & alarm_types[i].bits)
|
|
len += sprintf(buf + len, "%s ", alarm_types[i].name);
|
|
}
|
|
}
|
|
len += sprintf(buf + len, "\n");
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_alarms, S_IRUGO, pri_alarms_show, NULL);
|
|
|
|
static DEVICE_ATTR_READER(pri_cas_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
if(priv->is_cas) {
|
|
int i;
|
|
|
|
len += sprintf(buf + len,
|
|
"CAS: replies=%d\n", priv->cas_replies);
|
|
len += sprintf(buf + len, " CAS-TS: ");
|
|
for(i = 0; i < NUM_CAS_RS_E; i++) {
|
|
len += sprintf(buf + len, " %02X", priv->cas_ts_e[i]);
|
|
}
|
|
len += sprintf(buf + len, "\n");
|
|
len += sprintf(buf + len, " CAS-RS: ");
|
|
for(i = 0; i < NUM_CAS_RS_E; i++) {
|
|
len += sprintf(buf + len, " %02X", priv->cas_rs_e[i]);
|
|
}
|
|
len += sprintf(buf + len, "\n");
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_cas, S_IRUGO, pri_cas_show, NULL);
|
|
|
|
static DEVICE_ATTR_READER(pri_dchan_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf + len, "D-Channel: TX=[%5d] (0x%02X) RX=[%5d] (0x%02X) ",
|
|
priv->dchan_tx_counter, priv->dchan_tx_sample,
|
|
priv->dchan_rx_counter, priv->dchan_rx_sample);
|
|
if(priv->dchan_alive) {
|
|
len += sprintf(buf + len, "(alive %d K-ticks)\n",
|
|
priv->dchan_alive_ticks/1000);
|
|
} else {
|
|
len += sprintf(buf + len, "(dead)\n");
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_dchan, S_IRUGO, pri_dchan_show, NULL);
|
|
|
|
static DEVICE_ATTR_READER(pri_clocking_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct PRI_priv_data *priv;
|
|
unsigned long flags;
|
|
int len = 0;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if(!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
len += sprintf(buf + len, "%s\n", (priv->clock_source) ? "MASTER" : "SLAVE");
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR(pri_clocking, S_IRUGO, pri_clocking_show, NULL);
|
|
|
|
|
|
static int pri_xpd_probe(struct device *dev)
|
|
{
|
|
xpd_t *xpd;
|
|
int ret = 0;
|
|
|
|
xpd = dev_to_xpd(dev);
|
|
/* Is it our device? */
|
|
if(xpd->type != XPD_TYPE_PRI) {
|
|
XPD_ERR(xpd, "drop suggestion for %s (%d)\n",
|
|
dev_name(dev), xpd->type);
|
|
return -EINVAL;
|
|
}
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
ret = device_create_file(dev, &dev_attr_pri_protocol);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_protocol) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_protocol;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_localloop);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_localloop) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_localloop;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_layer1);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_layer1) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_layer1;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_alarms);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_alarms) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_alarms;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_cas);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_cas) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_cas;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_dchan);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_dchan) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_dchan;
|
|
}
|
|
ret = device_create_file(dev, &dev_attr_pri_clocking);
|
|
if(ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(pri_clocking) failed: %d\n", __FUNCTION__, ret);
|
|
goto fail_pri_clocking;
|
|
}
|
|
return 0;
|
|
fail_pri_clocking:
|
|
device_remove_file(dev, &dev_attr_pri_dchan);
|
|
fail_pri_dchan:
|
|
device_remove_file(dev, &dev_attr_pri_cas);
|
|
fail_pri_cas:
|
|
device_remove_file(dev, &dev_attr_pri_alarms);
|
|
fail_pri_alarms:
|
|
device_remove_file(dev, &dev_attr_pri_layer1);
|
|
fail_pri_layer1:
|
|
device_remove_file(dev, &dev_attr_pri_localloop);
|
|
fail_pri_localloop:
|
|
device_remove_file(dev, &dev_attr_pri_protocol);
|
|
fail_pri_protocol:
|
|
return ret;
|
|
}
|
|
|
|
static int pri_xpd_remove(struct device *dev)
|
|
{
|
|
xpd_t *xpd;
|
|
|
|
xpd = dev_to_xpd(dev);
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
device_remove_file(dev, &dev_attr_pri_clocking);
|
|
device_remove_file(dev, &dev_attr_pri_dchan);
|
|
device_remove_file(dev, &dev_attr_pri_cas);
|
|
device_remove_file(dev, &dev_attr_pri_alarms);
|
|
device_remove_file(dev, &dev_attr_pri_layer1);
|
|
device_remove_file(dev, &dev_attr_pri_localloop);
|
|
device_remove_file(dev, &dev_attr_pri_protocol);
|
|
return 0;
|
|
}
|
|
|
|
static struct xpd_driver pri_driver = {
|
|
.type = XPD_TYPE_PRI,
|
|
.driver = {
|
|
.name = "pri",
|
|
#ifndef OLD_HOTPLUG_SUPPORT
|
|
.owner = THIS_MODULE,
|
|
#endif
|
|
.probe = pri_xpd_probe,
|
|
.remove = pri_xpd_remove
|
|
}
|
|
};
|
|
|
|
static int __init card_pri_startup(void)
|
|
{
|
|
int ret;
|
|
|
|
if((ret = xpd_driver_register(&pri_driver.driver)) < 0)
|
|
return ret;
|
|
INFO("revision %s\n", XPP_VERSION);
|
|
#ifdef DAHDI_AUDIO_NOTIFY
|
|
INFO("FEATURE: WITH DAHDI_AUDIO_NOTIFY\n");
|
|
#else
|
|
INFO("FEATURE: WITHOUT DAHDI_AUDIO_NOTIFY\n");
|
|
#endif
|
|
xproto_register(&PROTO_TABLE(PRI));
|
|
return 0;
|
|
}
|
|
|
|
static void __exit card_pri_cleanup(void)
|
|
{
|
|
DBG(GENERAL, "\n");
|
|
xproto_unregister(&PROTO_TABLE(PRI));
|
|
xpd_driver_unregister(&pri_driver.driver);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("XPP PRI Card Driver");
|
|
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(XPP_VERSION);
|
|
MODULE_ALIAS_XPD(XPD_TYPE_PRI);
|
|
|
|
module_init(card_pri_startup);
|
|
module_exit(card_pri_cleanup);
|