a6c9b88c66
Mostly linux/errno.h was included more than once. Signed-off-by: Shaun Ruffell <sruffell@digium.com> git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9557 a0bf4364-ded3-4de4-8d8a-66a801d63aff
2744 lines
73 KiB
C
2744 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 "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,
|
|
.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;
|
|
case DAHDI_VMWI:
|
|
LINE_DBG(SIGNAL, xpd, pos, "PRI: VMWI\n");
|
|
return -ENOTTY;
|
|
case DAHDI_VMWI_CONFIG:
|
|
LINE_DBG(SIGNAL, xpd, pos, "PRI: VMWI_CONFIG\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);
|