34b9c77c9a
In commit (d56c0d45f0e27 "proc: decouple proc from VFS with "struct proc_ops"") [1], proc_create_data no longer takes a file_operations structure, but instead takes a struct proc_ops in order to conserve memory in the kernel. This change is necessary for DAHDI to work with kernels >= 5.6 [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d56c0d45f0e27f814e87a1676b6bd Signed-off-by: Shaun Ruffell <sruffell@sruffell.net>
2405 lines
66 KiB
C
2405 lines
66 KiB
C
/*
|
|
* Written by Oron Peled <oron@actcom.co.il>
|
|
* Copyright (C) 2004-2006, Xorcom
|
|
*
|
|
* 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 <linux/seq_file.h>
|
|
#include "xpd.h"
|
|
#include "xproto.h"
|
|
#include "xpp_dahdi.h"
|
|
#include "card_fxs.h"
|
|
#include "dahdi_debug.h"
|
|
#include "xbus-core.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
/* must be before dahdi_debug.h */
|
|
static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
|
|
static DEF_PARM_BOOL(reversepolarity, 0, 0644, "Reverse Line Polarity");
|
|
static DEF_PARM_BOOL(dtmf_detection, 1, 0644, "Do DTMF detection in hardware");
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
static DEF_PARM(uint, poll_digital_inputs, 1000, 0644, "Poll Digital Inputs");
|
|
#endif
|
|
static DEF_PARM(uint, poll_chan_linefeed, 30000, 0644, "Poll Channel Linefeed");
|
|
|
|
static DEF_PARM_BOOL(vmwi_ioctl, 1, 0644,
|
|
"Asterisk support VMWI notification via ioctl");
|
|
static DEF_PARM_BOOL(ring_trapez, 0, 0664, "Use trapezoid ring type");
|
|
static DEF_PARM_BOOL(lower_ringing_noise, 0, 0664,
|
|
"Lower ringing noise (may loose CallerID)");
|
|
|
|
/* Signaling is opposite (fxo signalling for fxs card) */
|
|
#if 1
|
|
#define FXS_DEFAULT_SIGCAP \
|
|
(DAHDI_SIG_FXOKS | DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS)
|
|
#else
|
|
#define FXS_DEFAULT_SIGCAP \
|
|
(DAHDI_SIG_SF | DAHDI_SIG_EM)
|
|
#endif
|
|
|
|
#define VMWI_TYPE(priv, pos, type) \
|
|
((priv)->vmwisetting[pos].vmwi_type & DAHDI_VMWI_ ## type)
|
|
#define VMWI_NEON(priv, pos) VMWI_TYPE(priv, pos, HVAC)
|
|
|
|
#define LINES_DIGI_OUT 2
|
|
#define LINES_DIGI_INP 4
|
|
|
|
enum fxs_leds {
|
|
LED_GREEN,
|
|
LED_RED,
|
|
OUTPUT_RELAY,
|
|
};
|
|
|
|
#define NUM_LEDS 2
|
|
|
|
/* Shortcuts */
|
|
#define SLIC_WRITE 1
|
|
#define SLIC_READ 0
|
|
#define SLIC_DIRECT_REQUEST(xbus, xpd, port, writing, reg, dL) \
|
|
xpp_register_request((xbus), (xpd), (port), \
|
|
(writing), (reg), 0, 0, (dL), 0, 0, 0, 0)
|
|
#define SLIC_INDIRECT_REQUEST(xbus, xpd, port, writing, reg, dL, dH) \
|
|
xpp_register_request((xbus), (xpd), (port), \
|
|
(writing), 0x1E, 1, (reg), (dL), 1, (dH), 0, 0)
|
|
#define EXP_REQUEST(xbus, xpd, writing, reg, dL, dH) \
|
|
xpp_register_request((xbus), (xpd), 0, \
|
|
(writing), (reg), 1, 0, (dL), 1, (dH), 0, 1)
|
|
#define RAM_REQUEST(xbus, xpd, port, writing, addr, data) \
|
|
xpp_ram_request((xbus), (xpd), (port), \
|
|
(writing), (__u8)(addr), (__u8)((addr) >> 8), (__u8)(data), (__u8)((data) >> 8), (__u8)((data) >> 16), (__u8)((data) >> 24), 0)
|
|
|
|
#define VALID_PORT(port) \
|
|
(((port) >= 0 && (port) <= 7) || (port) == PORT_BROADCAST)
|
|
|
|
#define REG_TYPE1_DIGITAL_IOCTRL 0x06 /* LED and RELAY control */
|
|
|
|
/* Values of SLIC linefeed control register (0x40) */
|
|
enum fxs_state {
|
|
FXS_LINE_OPEN = 0x00, /* Open */
|
|
FXS_LINE_ACTIVE = 0x01, /* Forward active */
|
|
FXS_LINE_OHTRANS = 0x02, /* Forward on-hook transmission */
|
|
FXS_LINE_TIPOPEN = 0x03, /* TIP open */
|
|
FXS_LINE_RING = 0x04, /* Ringing */
|
|
FXS_LINE_REV_ACTIVE = 0x05, /* Reverse active */
|
|
FXS_LINE_REV_OHTRANS = 0x06, /* Reverse on-hook transmission */
|
|
FXS_LINE_RING_OPEN = 0x07 /* RING open */
|
|
};
|
|
|
|
enum neon_state {
|
|
END_NEON = 0,
|
|
INIT_NEON = 1,
|
|
};
|
|
|
|
#define FXS_LINE_POL_ACTIVE \
|
|
((reversepolarity) ? FXS_LINE_REV_ACTIVE : FXS_LINE_ACTIVE)
|
|
#define FXS_LINE_POL_OHTRANS \
|
|
((reversepolarity) ? FXS_LINE_REV_OHTRANS : FXS_LINE_OHTRANS)
|
|
|
|
/* FXS type 1 registers */
|
|
#define REG_TYPE1_RINGCON 0x22 /* 34 - Ringing Oscillator Control */
|
|
|
|
/*
|
|
* DTMF detection
|
|
*/
|
|
#define REG_TYPE1_DTMF_DECODE 0x18 /* 24 - DTMF Decode Status */
|
|
#define REG_TYPE1_BATTERY 0x42 /* 66 - Battery Feed Control */
|
|
#define REG_TYPE1_BATTERY_BATSL BIT(1) /* Battery Feed Select */
|
|
|
|
/* 68 - Loop Closure/Ring Trip Detect Status */
|
|
#define REG_TYPE1_LOOPCLOSURE 0x44
|
|
#define REG_TYPE1_LOOPCLOSURE_ZERO 0xF8 /* Loop Closure zero bits. */
|
|
#define REG_TYPE1_LOOPCLOSURE_LCR BIT(0) /* Loop Closure Detect Indicator. */
|
|
|
|
/* FXS type 6 registers */
|
|
#define REG_TYPE6_RINGCON 0x26 /* 38 - Ringing Oscillator Control */
|
|
|
|
/* 34 - Loop Closure/Ring Trip Detect Status */
|
|
#define REG_TYPE6_LCRRTP 0x22
|
|
#define REG_TYPE6_LCRRTP_ZERO 0xF0 /* Loop Closure zero bits. */
|
|
#define REG_TYPE6_LCRRTP_LCR BIT(1) /* Loop Closure Detect Indicator. */
|
|
|
|
#define REG_TYPE6_TONEN 0x3E /* 62 - Hardware DTMF detection */
|
|
#define REG_TYPE6_TONEN_DTMF_DIS BIT(2) /* DTMF Disable */
|
|
#define REG_TYPE6_LINEFEED 0x1E /* 30 - Linefeed */
|
|
#define REG_TYPE6_TONDTMF 0x3C /* 60 - DTMF Decode Status */
|
|
#define REG_TYPE6_EXP_GPIOA 0x12 /* I/O Expander GPIOA */
|
|
#define REG_TYPE6_EXP_GPIOB 0x13 /* I/O Expander GPIOB */
|
|
#define REG_TYPE6_ENHANCE 0x2F /* 47 - Enhance */
|
|
#define REG_TYPE6_USERSTAT 0x42 /* 66 - Userstat */
|
|
#define REG_TYPE6_DIAG1 0x47 /* 71 - Diag1 */
|
|
#define RAM_TYPE6_SLOPE_VLIM 634
|
|
#define SLOPE_VLIM_DFLT 0x1E655196L
|
|
#define SLOPE_VLIM_MWI 0x8000000L
|
|
#define RAM_TYPE6_VBATH_EXPECT 767
|
|
#define VBATH_EXPECT_DFLT 0x2B10A20L
|
|
#define VBATH_EXPECT_MWI 0x6147AB2L
|
|
|
|
/*---------------- FXS Protocol Commands ----------------------------------*/
|
|
|
|
static bool fxs_packet_is_valid(xpacket_t *pack);
|
|
static void fxs_packet_dump(const char *msg, xpacket_t *pack);
|
|
#ifdef CONFIG_PROC_FS
|
|
#ifdef DAHDI_HAVE_PROC_OPS
|
|
static const struct proc_ops proc_fxs_info_ops;
|
|
#else
|
|
static const struct file_operations proc_fxs_info_ops;
|
|
#endif
|
|
#ifdef WITH_METERING
|
|
#ifdef DAHDI_HAVE_PROC_OPS
|
|
static const struct proc_ops proc_xpd_metering_ops;
|
|
#else
|
|
static const struct file_operations proc_xpd_metering_ops;
|
|
#endif
|
|
#endif
|
|
#endif
|
|
static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos);
|
|
|
|
#define PROC_FXS_INFO_FNAME "fxs_info"
|
|
#ifdef WITH_METERING
|
|
#define PROC_METERING_FNAME "metering_gen"
|
|
#endif
|
|
|
|
struct FXS_priv_data {
|
|
#ifdef WITH_METERING
|
|
struct proc_dir_entry *meteringfile;
|
|
#endif
|
|
struct proc_dir_entry *fxs_info;
|
|
xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */
|
|
xpp_line_t ledcontrol[NUM_LEDS]; /* 0 - OFF, 1 - ON */
|
|
xpp_line_t search_fsk_pattern;
|
|
xpp_line_t found_fsk_pattern;
|
|
xpp_line_t update_offhook_state;
|
|
xpp_line_t want_dtmf_events; /* what dahdi want */
|
|
xpp_line_t want_dtmf_mute; /* what dahdi want */
|
|
xpp_line_t prev_key_down; /* DTMF down sets the bit */
|
|
xpp_line_t neon_blinking;
|
|
xpp_line_t neonstate;
|
|
xpp_line_t vbat_h; /* High voltage */
|
|
ktime_t prev_key_time[CHANNELS_PERXPD];
|
|
int led_counter[NUM_LEDS][CHANNELS_PERXPD];
|
|
int overheat_reset_counter[CHANNELS_PERXPD];
|
|
int ohttimer[CHANNELS_PERXPD];
|
|
#define OHT_TIMER 6000 /* How long after RING to retain OHT */
|
|
/* IDLE changing hook state */
|
|
enum fxs_state idletxhookstate[CHANNELS_PERXPD];
|
|
enum fxs_state lasttxhook[CHANNELS_PERXPD];
|
|
enum fxs_state polledhook[CHANNELS_PERXPD];
|
|
struct dahdi_vmwi_info vmwisetting[CHANNELS_PERXPD];
|
|
};
|
|
|
|
/*
|
|
* LED counter values:
|
|
* n>1 : BLINK every n'th tick
|
|
*/
|
|
#define LED_COUNTER(priv, pos, color) ((priv)->led_counter[color][pos])
|
|
#define IS_BLINKING(priv, pos, color) (LED_COUNTER(priv, pos, color) > 0)
|
|
#define MARK_BLINK(priv, pos, color, t) \
|
|
((priv)->led_counter[color][pos] = (t))
|
|
#define MARK_OFF(priv, pos, color) \
|
|
do { \
|
|
BIT_CLR((priv)->ledcontrol[color], (pos)); \
|
|
MARK_BLINK((priv), (pos), (color), 0); \
|
|
} while (0)
|
|
#define MARK_ON(priv, pos, color) \
|
|
do { \
|
|
BIT_SET((priv)->ledcontrol[color], (pos)); \
|
|
MARK_BLINK((priv), (pos), (color), 0); \
|
|
} while (0)
|
|
|
|
#define LED_BLINK_RING (1000/8) /* in ticks */
|
|
|
|
/*---------------- FXS: Static functions ----------------------------------*/
|
|
static int set_vm_led_mode(xbus_t *xbus, xpd_t *xpd, int pos,
|
|
unsigned int msg_waiting);
|
|
|
|
static int do_chan_power(xbus_t *xbus, xpd_t *xpd, lineno_t chan, bool on)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
unsigned long *p;
|
|
int was;
|
|
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xpd);
|
|
if (XPD_HW(xpd).type == 6) {
|
|
LINE_DBG(SIGNAL, xpd, chan, "is ignored in Si32260\n");
|
|
return 0;
|
|
}
|
|
priv = xpd->priv;
|
|
p = (unsigned long *)&priv->vbat_h;
|
|
if (on)
|
|
was = test_and_set_bit(chan, p) != 0;
|
|
else
|
|
was = test_and_clear_bit(chan, p) != 0;
|
|
if (was == on) {
|
|
LINE_DBG(SIGNAL, xpd, chan,
|
|
"%s (same, ignored)\n", (on) ? "up" : "down");
|
|
return 0;
|
|
}
|
|
LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "up" : "down");
|
|
return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, REG_TYPE1_BATTERY,
|
|
(on) ? (int)REG_TYPE1_BATTERY_BATSL : 0x00);
|
|
}
|
|
|
|
static int linefeed_control(xbus_t *xbus, xpd_t *xpd, lineno_t chan,
|
|
enum fxs_state value)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
bool want_vbat_h;
|
|
|
|
priv = xpd->priv;
|
|
/*
|
|
* Should we drop vbat_h only during actuall ring?
|
|
* - It would lower the noise caused to other channels by
|
|
* group ringing
|
|
* - But it may also stop CallerID from passing through the SLIC
|
|
*/
|
|
want_vbat_h = value == FXS_LINE_RING;
|
|
if (lower_ringing_noise || want_vbat_h)
|
|
do_chan_power(xbus, xpd, chan, want_vbat_h);
|
|
LINE_DBG(SIGNAL, xpd, chan, "value=0x%02X\n", value);
|
|
priv->lasttxhook[chan] = value;
|
|
if (XPD_HW(xpd).type == 6) {
|
|
int ret;
|
|
|
|
/* Make sure NEON state is off for */
|
|
if (value == FXS_LINE_POL_OHTRANS && IS_SET(priv->neon_blinking, chan))
|
|
set_vm_led_mode(xpd->xbus, xpd, chan, 0);
|
|
ret = SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, REG_TYPE6_LINEFEED, value);
|
|
if (value == FXS_LINE_POL_ACTIVE && PHONEDEV(xpd).msg_waiting[chan])
|
|
set_vm_led_mode(xpd->xbus, xpd, chan, PHONEDEV(xpd).msg_waiting[chan]);
|
|
return ret;
|
|
} else {
|
|
return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, 0x40, value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void vmwi_search(xpd_t *xpd, lineno_t pos, bool on)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
BUG_ON(!xpd);
|
|
if (VMWI_NEON(priv, pos) && on) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "START\n");
|
|
BIT_SET(priv->search_fsk_pattern, pos);
|
|
} else {
|
|
LINE_DBG(SIGNAL, xpd, pos, "STOP\n");
|
|
BIT_CLR(priv->search_fsk_pattern, pos);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* LED and RELAY control is done via SLIC register 0x06:
|
|
* 7 6 5 4 3 2 1 0
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
* | M2 | M1 | M3 | C2 | O1 | O3 | C1 | C3 |
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*
|
|
* Cn - Control bit (control one digital line)
|
|
* On - Output bit (program a digital line for output)
|
|
* Mn - Mask bit (only the matching output control bit is affected)
|
|
*
|
|
* C3 - OUTPUT RELAY (0 - OFF, 1 - ON)
|
|
* C1 - GREEN LED (0 - OFF, 1 - ON)
|
|
* O3 - Output RELAY (this line is output)
|
|
* O1 - Output GREEN (this line is output)
|
|
* C2 - RED LED (0 - OFF, 1 - ON)
|
|
* M3 - Mask RELAY. (1 - C3 effect the OUTPUT RELAY)
|
|
* M2 - Mask RED. (1 - C2 effect the RED LED)
|
|
* M1 - Mask GREEN. (1 - C1 effect the GREEN LED)
|
|
*
|
|
* The OUTPUT RELAY (actually a relay out) is connected to line 0 and 4 only.
|
|
*/
|
|
|
|
// GREEN RED OUTPUT RELAY
|
|
static const int led_register_mask[] = { BIT(7), BIT(6), BIT(5) };
|
|
static const int led_register_vals[] = { BIT(4), BIT(1), BIT(0) };
|
|
|
|
/*
|
|
* pos can be:
|
|
* - A line number
|
|
* - ALL_LINES. This is not valid anymore since 8-Jan-2007.
|
|
*/
|
|
static int do_led(xpd_t *xpd, lineno_t chan, __u8 which, bool on)
|
|
{
|
|
int ret = 0;
|
|
struct FXS_priv_data *priv;
|
|
int value;
|
|
xbus_t *xbus;
|
|
|
|
BUG_ON(!xpd);
|
|
BUG_ON(chan == ALL_LINES);
|
|
xbus = xpd->xbus;
|
|
priv = xpd->priv;
|
|
which = which % NUM_LEDS;
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, chan)
|
|
|| IS_SET(PHONEDEV(xpd).digital_inputs, chan))
|
|
goto out;
|
|
if (chan == PORT_BROADCAST) {
|
|
priv->ledstate[which] = (on) ? ~0 : 0;
|
|
} else {
|
|
if (on)
|
|
BIT_SET(priv->ledstate[which], chan);
|
|
else
|
|
BIT_CLR(priv->ledstate[which], chan);
|
|
}
|
|
LINE_DBG(LEDS, xpd, chan, "LED: (type=%d) which=%d -- %s\n", XPD_HW(xpd).type, which,
|
|
(on) ? "on" : "off");
|
|
if (XPD_HW(xpd).type == 6) {
|
|
int mask = 1 << chan;
|
|
value = (on) << chan;
|
|
XPD_DBG(LEDS, xpd, "LED(%d): 0x%0X (mask: 0x%0X)\n", chan,
|
|
value, mask);
|
|
if (which == LED_GREEN) { /* other leds ignored */
|
|
ret = EXP_REQUEST(xbus, xpd, SLIC_WRITE,
|
|
REG_TYPE6_EXP_GPIOA, value, mask);
|
|
}
|
|
} else {
|
|
value = BIT(2) | BIT(3);
|
|
value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[which]);
|
|
if (on)
|
|
value |= led_register_vals[which];
|
|
ret = SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE,
|
|
REG_TYPE1_DIGITAL_IOCTRL, value);
|
|
}
|
|
return 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static inline void set_mwi_led(xpd_t *xpd, int pos, int on)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
|
|
if (XPD_HW(xpd).type != 6)
|
|
return;
|
|
if (on) {
|
|
if (! IS_SET(priv->neonstate, pos)) {
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_ENHANCE, 0x00);
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_USERSTAT, 0x04);
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_DIAG1, 0x0F);
|
|
BIT_SET(priv->neonstate, pos);
|
|
}
|
|
} else {
|
|
if (IS_SET(priv->neonstate, pos)) {
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_DIAG1, 0x00);
|
|
BIT_CLR(priv->neonstate, pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void blink_mwi(xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
unsigned int timer_count;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
timer_count = xpd->timer_count;
|
|
for_each_line(xpd, i) {
|
|
unsigned int msgs = PHONEDEV(xpd).msg_waiting[i];
|
|
/* LED duty cycle: 300ms on, 700ms off */
|
|
unsigned int in_range = (timer_count % 1000) >= 0 && (timer_count % 1000) <= 300;
|
|
|
|
if (!IS_OFFHOOK(xpd, i) && msgs && in_range &&
|
|
IS_SET(priv->neon_blinking,i) && priv->ohttimer[i] == 0)
|
|
set_mwi_led(xpd, i, 1);
|
|
else
|
|
set_mwi_led(xpd, i, 0);
|
|
}
|
|
}
|
|
|
|
static void handle_fxs_leds(xpd_t *xpd)
|
|
{
|
|
int i;
|
|
const enum fxs_leds colors[] = { LED_GREEN, LED_RED };
|
|
enum fxs_leds color;
|
|
unsigned int timer_count;
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
timer_count = xpd->timer_count;
|
|
for (color = 0; color < ARRAY_SIZE(colors); color++) {
|
|
for_each_line(xpd, i) {
|
|
if (IS_SET
|
|
(PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).
|
|
digital_inputs, i))
|
|
continue;
|
|
/* Blinking? */
|
|
if ((xpd->blink_mode & BIT(i)) || IS_BLINKING(priv, i, color)) {
|
|
int mod_value = LED_COUNTER(priv, i, color);
|
|
|
|
if (!mod_value)
|
|
/* safety value */
|
|
mod_value = DEFAULT_LED_PERIOD;
|
|
/* led state is toggled */
|
|
if ((timer_count % mod_value) == 0) {
|
|
LINE_DBG(LEDS, xpd, i, "ledstate=%s\n",
|
|
(IS_SET
|
|
(priv->ledstate[color],
|
|
i)) ? "ON" : "OFF");
|
|
if (!IS_SET(priv->ledstate[color], i))
|
|
do_led(xpd, i, color, 1);
|
|
else
|
|
do_led(xpd, i, color, 0);
|
|
}
|
|
} else
|
|
if (IS_SET
|
|
(priv->ledcontrol[color] & ~priv->
|
|
ledstate[color], i)) {
|
|
do_led(xpd, i, color, 1);
|
|
} else
|
|
if (IS_SET
|
|
(~priv->ledcontrol[color] & priv->
|
|
ledstate[color], i)) {
|
|
do_led(xpd, i, color, 0);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static void restore_leds(xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
priv = xpd->priv;
|
|
for_each_line(xpd, i) {
|
|
if (IS_OFFHOOK(xpd, i))
|
|
MARK_ON(priv, i, LED_GREEN);
|
|
else
|
|
MARK_OFF(priv, i, LED_GREEN);
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_METERING
|
|
static int metering_gen(xpd_t *xpd, lineno_t chan, bool on)
|
|
{
|
|
__u8 value = (on) ? 0x94 : 0x00;
|
|
|
|
if (XPD_HW(xpd).type == 6) {
|
|
XBUS_NOTICE("Metering not supported with FXS type 6");
|
|
return 0;
|
|
}
|
|
LINE_DBG(SIGNAL, xpd, chan, "METERING Generate: %s\n",
|
|
(on) ? "ON" : "OFF");
|
|
return SLIC_DIRECT_REQUEST(xpd->xbus, xpd, chan, SLIC_WRITE, 0x23,
|
|
value);
|
|
}
|
|
#endif
|
|
|
|
/*---------------- FXS: Methods -------------------------------------------*/
|
|
|
|
static void fxs_proc_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
#ifdef CONFIG_PROC_FS
|
|
#ifdef WITH_METERING
|
|
if (priv->meteringfile) {
|
|
XPD_DBG(PROC, xpd, "Removing xpd metering tone file\n");
|
|
remove_proc_entry(PROC_METERING_FNAME, xpd->proc_xpd_dir);
|
|
priv->meteringfile = NULL;
|
|
}
|
|
#endif
|
|
if (priv->fxs_info) {
|
|
XPD_DBG(PROC, xpd, "Removing xpd FXS_INFO file\n");
|
|
remove_proc_entry(PROC_FXS_INFO_FNAME, xpd->proc_xpd_dir);
|
|
priv->fxs_info = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int fxs_proc_create(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
XPD_DBG(PROC, xpd, "Creating FXS_INFO file\n");
|
|
priv->fxs_info = proc_create_data(PROC_FXS_INFO_FNAME, 0444,
|
|
xpd->proc_xpd_dir,
|
|
&proc_fxs_info_ops, xpd);
|
|
if (!priv->fxs_info) {
|
|
XPD_ERR(xpd, "Failed to create proc file '%s'\n",
|
|
PROC_FXS_INFO_FNAME);
|
|
fxs_proc_remove(xbus, xpd);
|
|
return -EINVAL;
|
|
}
|
|
SET_PROC_DIRENTRY_OWNER(priv->fxs_info);
|
|
#ifdef WITH_METERING
|
|
XPD_DBG(PROC, xpd, "Creating Metering tone file\n");
|
|
priv->meteringfile = proc_create_data(PROC_METERING_FNAME, 0200,
|
|
xpd->proc_xpd_dir,
|
|
&proc_xpd_metering_ops, xpd);
|
|
if (!priv->meteringfile) {
|
|
XPD_ERR(xpd, "Failed to create proc file '%s'\n",
|
|
PROC_METERING_FNAME);
|
|
fxs_proc_remove(xbus, xpd);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static xpd_t *FXS_card_new(xbus_t *xbus, int unit, int subunit,
|
|
const xproto_table_t *proto_table,
|
|
const struct unit_descriptor *unit_descriptor,
|
|
bool to_phone)
|
|
{
|
|
xpd_t *xpd = NULL;
|
|
int channels;
|
|
int subunit_ports;
|
|
int regular_channels;
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
int d_inputs = 0;
|
|
int d_outputs = 0;
|
|
|
|
if (!to_phone) {
|
|
XBUS_NOTICE(xbus,
|
|
"XPD=%d%d: try to instanciate FXS with reverse direction\n",
|
|
unit, subunit);
|
|
return NULL;
|
|
}
|
|
subunit_ports = unit_descriptor->numchips * unit_descriptor->ports_per_chip;
|
|
if (unit_descriptor->subtype == 2)
|
|
regular_channels = min(6, subunit_ports);
|
|
else
|
|
regular_channels = min(8, subunit_ports);
|
|
channels = regular_channels;
|
|
/* Calculate digital inputs/outputs */
|
|
if (unit == 0 && unit_descriptor->subtype != 4 && unit_descriptor->numchips != 4) {
|
|
channels += 6; /* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */
|
|
d_inputs = LINES_DIGI_INP;
|
|
d_outputs = LINES_DIGI_OUT;
|
|
}
|
|
xpd =
|
|
xpd_alloc(xbus, unit, subunit,
|
|
sizeof(struct FXS_priv_data), proto_table, unit_descriptor, channels);
|
|
if (!xpd)
|
|
return NULL;
|
|
/* Initialize digital inputs/outputs */
|
|
if (d_inputs) {
|
|
XBUS_DBG(GENERAL, xbus, "Initialize %d digital inputs\n",
|
|
d_inputs);
|
|
PHONEDEV(xpd).digital_inputs =
|
|
BITMASK(d_inputs) << (regular_channels + d_outputs);
|
|
} else
|
|
XBUS_DBG(GENERAL, xbus, "No digital inputs\n");
|
|
if (d_outputs) {
|
|
XBUS_DBG(GENERAL, xbus, "Initialize %d digital outputs\n",
|
|
d_outputs);
|
|
PHONEDEV(xpd).digital_outputs =
|
|
BITMASK(d_outputs) << regular_channels;
|
|
} else
|
|
XBUS_DBG(GENERAL, xbus, "No digital outputs\n");
|
|
PHONEDEV(xpd).direction = TO_PHONE;
|
|
xpd->type_name = "FXS";
|
|
if (fxs_proc_create(xbus, xpd) < 0)
|
|
goto err;
|
|
priv = xpd->priv;
|
|
for_each_line(xpd, i) {
|
|
priv->idletxhookstate[i] = FXS_LINE_POL_ACTIVE;
|
|
}
|
|
return xpd;
|
|
err:
|
|
xpd_free(xpd);
|
|
return NULL;
|
|
}
|
|
|
|
static int FXS_card_init(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
/*
|
|
* Setup ring timers
|
|
*/
|
|
/* Software controled ringing (for CID) */
|
|
/* Ringing Oscilator Control */
|
|
if (XPD_HW(xpd).type == 6) {
|
|
ret = SLIC_DIRECT_REQUEST(xbus, xpd, PORT_BROADCAST, SLIC_WRITE,
|
|
REG_TYPE6_RINGCON, 0x00);
|
|
} else {
|
|
ret = SLIC_DIRECT_REQUEST(xbus, xpd, PORT_BROADCAST, SLIC_WRITE,
|
|
REG_TYPE1_RINGCON, 0x00);
|
|
}
|
|
if (ret < 0)
|
|
goto err;
|
|
for_each_line(xpd, i) {
|
|
if (XPD_HW(xpd).type == 6)
|
|
/* An arbitrary value that is not FXS_LINE_OPEN */
|
|
priv->polledhook[i] = FXS_LINE_ACTIVE;
|
|
linefeed_control(xbus, xpd, i, FXS_LINE_POL_ACTIVE);
|
|
}
|
|
XPD_DBG(GENERAL, xpd, "done\n");
|
|
for_each_line(xpd, i) {
|
|
do_led(xpd, i, LED_GREEN, 0);
|
|
do_led(xpd, i, LED_RED, 0);
|
|
}
|
|
for_each_line(xpd, i) {
|
|
do_led(xpd, i, LED_GREEN, 1);
|
|
msleep(50);
|
|
}
|
|
for_each_line(xpd, i) {
|
|
do_led(xpd, i, LED_GREEN, 0);
|
|
msleep(50);
|
|
}
|
|
restore_leds(xpd);
|
|
CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
|
|
/*
|
|
* We should query our offhook state long enough time after we
|
|
* set the linefeed_control()
|
|
* So we do this after the LEDs
|
|
*/
|
|
for_each_line(xpd, i) {
|
|
if (IS_SET
|
|
(PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).
|
|
digital_inputs, i))
|
|
continue;
|
|
if (XPD_HW(xpd).type == 6) {
|
|
SLIC_DIRECT_REQUEST(xbus, xpd, i, SLIC_READ, REG_TYPE6_LCRRTP,
|
|
0);
|
|
} else {
|
|
SLIC_DIRECT_REQUEST(xbus, xpd, i, SLIC_READ, REG_TYPE1_LOOPCLOSURE,
|
|
0);
|
|
}
|
|
}
|
|
return 0;
|
|
err:
|
|
fxs_proc_remove(xbus, xpd);
|
|
XPD_ERR(xpd, "Failed initializing registers (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static int FXS_card_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(GENERAL, xpd, "\n");
|
|
fxs_proc_remove(xbus, xpd);
|
|
return 0;
|
|
}
|
|
|
|
static int FXS_card_dahdi_preregistration(xpd_t *xpd, bool on)
|
|
{
|
|
xbus_t *xbus;
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
|
|
PHONEDEV(xpd).span.spantype = SPANTYPE_ANALOG_FXS;
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
|
|
|
|
XPD_DBG(GENERAL, xpd, "setting FXS channel %d\n", i);
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, i)) {
|
|
snprintf(cur_chan->name, MAX_CHANNAME,
|
|
"XPP_OUT/%02d/%1d%1d/%d", xbus->num,
|
|
xpd->addr.unit, xpd->addr.subunit, i);
|
|
} else if (IS_SET(PHONEDEV(xpd).digital_inputs, i)) {
|
|
snprintf(cur_chan->name, MAX_CHANNAME,
|
|
"XPP_IN/%02d/%1d%1d/%d", xbus->num,
|
|
xpd->addr.unit, xpd->addr.subunit, i);
|
|
} else {
|
|
snprintf(cur_chan->name, MAX_CHANNAME,
|
|
"XPP_FXS/%02d/%1d%1d/%d", xbus->num,
|
|
xpd->addr.unit, xpd->addr.subunit, i);
|
|
}
|
|
cur_chan->chanpos = i + 1;
|
|
cur_chan->pvt = xpd;
|
|
cur_chan->sigcap = FXS_DEFAULT_SIGCAP;
|
|
if (!vmwi_ioctl) {
|
|
/* Old asterisk, assume default VMWI type */
|
|
priv->vmwisetting[i].vmwi_type = DAHDI_VMWI_HVAC;
|
|
}
|
|
}
|
|
for_each_line(xpd, i) {
|
|
MARK_ON(priv, i, LED_GREEN);
|
|
msleep(4);
|
|
MARK_ON(priv, i, LED_RED);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int FXS_card_dahdi_postregistration(xpd_t *xpd, bool on)
|
|
{
|
|
xbus_t *xbus;
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
|
|
for_each_line(xpd, i) {
|
|
MARK_OFF(priv, i, LED_GREEN);
|
|
msleep(2);
|
|
MARK_OFF(priv, i, LED_RED);
|
|
msleep(2);
|
|
}
|
|
restore_leds(xpd);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Called with XPD spinlocked
|
|
*/
|
|
static void __do_mute_dtmf(xpd_t *xpd, int pos, bool muteit)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
priv = xpd->priv;
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s\n", (muteit) ? "MUTE" : "UNMUTE");
|
|
if (muteit)
|
|
BIT_SET(PHONEDEV(xpd).mute_dtmf, pos);
|
|
else
|
|
BIT_CLR(PHONEDEV(xpd).mute_dtmf, pos);
|
|
/* already spinlocked */
|
|
CALL_PHONE_METHOD(card_pcm_recompute, xpd, priv->search_fsk_pattern);
|
|
}
|
|
|
|
struct ring_reg_param {
|
|
int is_indirect;
|
|
int regno;
|
|
uint8_t h_val;
|
|
uint8_t l_val;
|
|
};
|
|
|
|
enum ring_types {
|
|
RING_TYPE_NEON = 0,
|
|
RING_TYPE_TRAPEZ,
|
|
RING_TYPE_NORMAL,
|
|
};
|
|
|
|
struct byte_pair {
|
|
uint8_t h_val;
|
|
uint8_t l_val;
|
|
};
|
|
|
|
struct ring_reg_params {
|
|
const int is_indirect;
|
|
const int regno;
|
|
struct byte_pair values[1 + RING_TYPE_NORMAL - RING_TYPE_NEON];
|
|
};
|
|
|
|
#define REG_ENTRY(di, reg, vh1, vl1, vh2, vl2, vh3, vl3) \
|
|
{ (di), (reg), .values = { \
|
|
[RING_TYPE_NEON] = { .h_val = (vh1), .l_val = (vl1) }, \
|
|
[RING_TYPE_TRAPEZ] = { .h_val = (vh2), .l_val = (vl2) }, \
|
|
[RING_TYPE_NORMAL] = { .h_val = (vh3), .l_val = (vl3) }, \
|
|
}, \
|
|
}
|
|
|
|
static struct ring_reg_params ring_parameters[] = {
|
|
/* INDIR REG NEON TRAPEZ NORMAL */
|
|
REG_ENTRY(1, 0x16, 0xE8, 0x03, 0xC8, 0x00, 0x00, 0x00),
|
|
REG_ENTRY(1, 0x15, 0xEF, 0x7B, 0xAB, 0x5E, 0x77, 0x01),
|
|
REG_ENTRY(1, 0x14, 0x9F, 0x00, 0x8C, 0x01, 0xFD, 0x7E),
|
|
|
|
REG_ENTRY(0, 0x22, 0x00, 0x19, 0x00, 0x01, 0x00, 0x00),
|
|
|
|
REG_ENTRY(0, 0x30, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00),
|
|
REG_ENTRY(0, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00),
|
|
REG_ENTRY(0, 0x32, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00),
|
|
REG_ENTRY(0, 0x33, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00),
|
|
|
|
REG_ENTRY(1, 0x1D, 0x00, 0x46, 0x00, 0x36, 0x00, 0x36),
|
|
};
|
|
|
|
static void set_neon_state(xbus_t *xbus, xpd_t *xpd, int pos,
|
|
enum neon_state ns)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
LINE_DBG(SIGNAL, xpd, pos, "set NEON -> %d\n", ns);
|
|
priv = xpd->priv;
|
|
if (ns == INIT_NEON)
|
|
BIT_SET(priv->neon_blinking, pos);
|
|
else
|
|
BIT_CLR(priv->neon_blinking, pos);
|
|
if (XPD_HW(xpd).type == 6) {
|
|
switch (ns) {
|
|
case INIT_NEON:
|
|
RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_VBATH_EXPECT, VBATH_EXPECT_MWI << 3);
|
|
//RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_SLOPE_VLIM, SLOPE_VLIM_MWI << 3);
|
|
break;
|
|
default:
|
|
LINE_DBG(REGS, xpd, pos, "0x%04X: R 0x\n", RAM_TYPE6_SLOPE_VLIM);
|
|
set_mwi_led(xpd, pos, 0); /* Cannot have NEON LED during OHT (type == 6) */
|
|
SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_USERSTAT, 0x00);
|
|
SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, REG_TYPE6_ENHANCE, 0x10);
|
|
RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_VBATH_EXPECT, VBATH_EXPECT_DFLT << 3);
|
|
RAM_REQUEST(xbus, xpd, pos, SLIC_WRITE, RAM_TYPE6_SLOPE_VLIM, SLOPE_VLIM_DFLT << 3);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
static int send_ring_parameters(xbus_t *xbus, xpd_t *xpd, int pos,
|
|
enum ring_types rtype)
|
|
{
|
|
const struct ring_reg_params *p;
|
|
const struct byte_pair *v;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
if (XPD_HW(xpd).type == 6)
|
|
return 0;
|
|
if (rtype < RING_TYPE_NEON || rtype > RING_TYPE_NORMAL)
|
|
return -EINVAL;
|
|
for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
|
|
p = &ring_parameters[i];
|
|
v = &(p->values[rtype]);
|
|
if (p->is_indirect) {
|
|
LINE_DBG(REGS, xpd, pos,
|
|
"[%d] 0x%02X: I 0x%02X 0x%02X\n",
|
|
i, p->regno, v->h_val, v->l_val);
|
|
ret = SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE,
|
|
p->regno, v->h_val, v->l_val);
|
|
if (ret < 0) {
|
|
LINE_ERR(xpd, pos,
|
|
"Failed: 0x%02X: I 0x%02X, 0x%02X\n",
|
|
p->regno, v->h_val, v->l_val);
|
|
break;
|
|
}
|
|
} else {
|
|
LINE_DBG(REGS, xpd, pos, "[%d] 0x%02X: D 0x%02X\n",
|
|
i, p->regno, v->l_val);
|
|
ret = SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE,
|
|
p->regno, v->l_val);
|
|
if (ret < 0) {
|
|
LINE_ERR(xpd, pos,
|
|
"Failed: 0x%02X: D 0x%02X\n",
|
|
p->regno, v->l_val);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int set_vm_led_mode(xbus_t *xbus, xpd_t *xpd, int pos,
|
|
unsigned int msg_waiting)
|
|
{
|
|
int ret = 0;
|
|
struct FXS_priv_data *priv;
|
|
BUG_ON(!xbus);
|
|
BUG_ON(!xpd);
|
|
|
|
priv = xpd->priv;
|
|
if (VMWI_NEON(priv, pos) && msg_waiting) {
|
|
/* A write to register 0x40 will now turn on/off the VM led */
|
|
LINE_DBG(SIGNAL, xpd, pos, "NEON\n");
|
|
set_neon_state(xbus, xpd, pos, INIT_NEON);
|
|
ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_NEON);
|
|
} else if (ring_trapez) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "RINGER: Trapez ring\n");
|
|
set_neon_state(xbus, xpd, pos, END_NEON);
|
|
ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_TRAPEZ);
|
|
} else {
|
|
/* A write to register 0x40 will now turn on/off the ringer */
|
|
LINE_DBG(SIGNAL, xpd, pos, "RINGER\n");
|
|
set_neon_state(xbus, xpd, pos, END_NEON);
|
|
ret = send_ring_parameters(xbus, xpd, pos, RING_TYPE_NORMAL);
|
|
}
|
|
return (ret ? -EPROTO : 0);
|
|
}
|
|
|
|
static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
unsigned int msgs;
|
|
|
|
BUG_ON(!xpd);
|
|
if (IS_SET
|
|
(PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).digital_inputs, pos))
|
|
return;
|
|
priv = xpd->priv;
|
|
msgs = PHONEDEV(xpd).msg_waiting[pos];
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s\n", (msgs) ? "ON" : "OFF");
|
|
set_vm_led_mode(xbus, xpd, pos, msgs);
|
|
if (XPD_HW(xpd).type == 1) {
|
|
do_chan_power(xbus, xpd, pos, msgs > 0);
|
|
linefeed_control(xbus, xpd, pos,
|
|
(msgs > 0) ? FXS_LINE_RING : priv->idletxhookstate[pos]);
|
|
}
|
|
}
|
|
|
|
static int relay_out(xpd_t *xpd, int pos, bool on)
|
|
{
|
|
int ret = 0;
|
|
int value = 0;
|
|
int which = pos;
|
|
|
|
BUG_ON(!xpd);
|
|
/* map logical position to output port number (0/1) */
|
|
which -= (XPD_HW(xpd).subtype == 2) ? 6 : 8;
|
|
LINE_DBG(SIGNAL, xpd, pos, "which=%d -- %s\n", which,
|
|
(on) ? "on" : "off");
|
|
if (XPD_HW(xpd).type == 6) {
|
|
int relay_values_type6[] = { 0x01, 0x40 };
|
|
which = which % ARRAY_SIZE(relay_values_type6);
|
|
if (on)
|
|
value |= relay_values_type6[which];
|
|
ret = EXP_REQUEST(xpd->xbus, xpd, SLIC_WRITE,
|
|
REG_TYPE6_EXP_GPIOB, value, relay_values_type6[which]);
|
|
} else {
|
|
int relay_channels_type1[] = { 0, 4 };
|
|
which = which % ARRAY_SIZE(relay_channels_type1);
|
|
value = BIT(2) | BIT(3);
|
|
value |=
|
|
((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[OUTPUT_RELAY]);
|
|
if (on)
|
|
value |= led_register_vals[OUTPUT_RELAY];
|
|
ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, relay_channels_type1[which],
|
|
SLIC_WRITE, REG_TYPE1_DIGITAL_IOCTRL, value);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int send_ring(xpd_t *xpd, lineno_t chan, bool on)
|
|
{
|
|
int ret = 0;
|
|
xbus_t *xbus;
|
|
struct FXS_priv_data *priv;
|
|
enum fxs_state value = (on) ? FXS_LINE_RING : FXS_LINE_POL_ACTIVE;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "on" : "off");
|
|
priv = xpd->priv;
|
|
set_vm_led_mode(xbus, xpd, chan, 0);
|
|
do_chan_power(xbus, xpd, chan, on); /* Power up (for ring) */
|
|
ret = linefeed_control(xbus, xpd, chan, value);
|
|
if (on) {
|
|
MARK_BLINK(priv, chan, LED_GREEN, LED_BLINK_RING);
|
|
} else {
|
|
if (IS_BLINKING(priv, chan, LED_GREEN))
|
|
MARK_BLINK(priv, chan, LED_GREEN, 0);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int FXS_card_hooksig(xpd_t *xpd, int pos, enum dahdi_txsig txsig)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
int ret = 0;
|
|
struct dahdi_chan *chan = NULL;
|
|
enum fxs_state txhook;
|
|
unsigned long flags;
|
|
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig));
|
|
priv = xpd->priv;
|
|
BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
|
|
if (IS_SET(PHONEDEV(xpd).digital_inputs, pos)) {
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"Ignoring signal sent to digital input line\n");
|
|
return 0;
|
|
}
|
|
if (SPAN_REGISTERED(xpd))
|
|
chan = XPD_CHAN(xpd, pos);
|
|
switch (txsig) {
|
|
case DAHDI_TXSIG_ONHOOK:
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
PHONEDEV(xpd).ringing[pos] = 0;
|
|
oht_pcm(xpd, pos, 0);
|
|
vmwi_search(xpd, pos, 0);
|
|
BIT_CLR(priv->want_dtmf_events, pos);
|
|
BIT_CLR(priv->want_dtmf_mute, pos);
|
|
__do_mute_dtmf(xpd, pos, 0);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s -> digital output OFF\n",
|
|
txsig2str(txsig));
|
|
ret = relay_out(xpd, pos, 0);
|
|
return ret;
|
|
}
|
|
if (priv->lasttxhook[pos] == FXS_LINE_OPEN) {
|
|
/*
|
|
* Restore state after KEWL hangup.
|
|
*/
|
|
LINE_DBG(SIGNAL, xpd, pos, "KEWL STOP\n");
|
|
linefeed_control(xpd->xbus, xpd, pos,
|
|
FXS_LINE_POL_ACTIVE);
|
|
if (IS_OFFHOOK(xpd, pos))
|
|
MARK_ON(priv, pos, LED_GREEN);
|
|
}
|
|
ret = send_ring(xpd, pos, 0); // RING off
|
|
if (!IS_OFFHOOK(xpd, pos))
|
|
start_stop_vm_led(xpd->xbus, xpd, pos);
|
|
txhook = priv->lasttxhook[pos];
|
|
if (chan) {
|
|
switch (chan->sig) {
|
|
case DAHDI_SIG_EM:
|
|
case DAHDI_SIG_FXOKS:
|
|
case DAHDI_SIG_FXOLS:
|
|
txhook = priv->idletxhookstate[pos];
|
|
break;
|
|
case DAHDI_SIG_FXOGS:
|
|
txhook = FXS_LINE_TIPOPEN;
|
|
break;
|
|
}
|
|
}
|
|
ret = linefeed_control(xpd->xbus, xpd, pos, txhook);
|
|
break;
|
|
case DAHDI_TXSIG_OFFHOOK:
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
|
|
LINE_NOTICE(xpd, pos,
|
|
"%s -> Is digital output. Ignored\n",
|
|
txsig2str(txsig));
|
|
return -EINVAL;
|
|
}
|
|
txhook = priv->lasttxhook[pos];
|
|
if (PHONEDEV(xpd).ringing[pos]) {
|
|
oht_pcm(xpd, pos, 1);
|
|
txhook = FXS_LINE_OHTRANS;
|
|
}
|
|
PHONEDEV(xpd).ringing[pos] = 0;
|
|
if (chan) {
|
|
switch (chan->sig) {
|
|
case DAHDI_SIG_EM:
|
|
txhook = FXS_LINE_POL_ACTIVE;
|
|
break;
|
|
default:
|
|
txhook = priv->idletxhookstate[pos];
|
|
break;
|
|
}
|
|
}
|
|
ret = linefeed_control(xpd->xbus, xpd, pos, txhook);
|
|
break;
|
|
case DAHDI_TXSIG_START:
|
|
PHONEDEV(xpd).ringing[pos] = 1;
|
|
oht_pcm(xpd, pos, 0);
|
|
vmwi_search(xpd, pos, 0);
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s -> digital output ON\n",
|
|
txsig2str(txsig));
|
|
ret = relay_out(xpd, pos, 1);
|
|
return ret;
|
|
}
|
|
ret = send_ring(xpd, pos, 1); // RING on
|
|
break;
|
|
case DAHDI_TXSIG_KEWL:
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, pos)) {
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"%s -> Is digital output. Ignored\n",
|
|
txsig2str(txsig));
|
|
return -EINVAL;
|
|
}
|
|
linefeed_control(xpd->xbus, xpd, pos, FXS_LINE_OPEN);
|
|
MARK_OFF(priv, pos, LED_GREEN);
|
|
break;
|
|
default:
|
|
XPD_NOTICE(xpd, "%s: Can't set tx state to %s (%d)\n", __func__,
|
|
txsig2str(txsig), txsig);
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int set_vmwi(xpd_t *xpd, int pos, unsigned long arg)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
struct dahdi_vmwi_info vmwisetting;
|
|
const int vmwi_flags =
|
|
DAHDI_VMWI_LREV | DAHDI_VMWI_HVDC | DAHDI_VMWI_HVAC;
|
|
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if (copy_from_user
|
|
(&vmwisetting, (__user void *)arg, sizeof(vmwisetting)))
|
|
return -EFAULT;
|
|
if ((vmwisetting.vmwi_type & ~vmwi_flags) != 0) {
|
|
LINE_NOTICE(xpd, pos, "Bad DAHDI_VMWI_CONFIG: 0x%X\n",
|
|
vmwisetting.vmwi_type);
|
|
return -EINVAL;
|
|
}
|
|
LINE_DBG(SIGNAL, xpd, pos, "DAHDI_VMWI_CONFIG: 0x%X\n",
|
|
vmwisetting.vmwi_type);
|
|
if (VMWI_TYPE(priv, pos, LREV)) {
|
|
LINE_NOTICE(xpd, pos,
|
|
"%s: VMWI(lrev) is not implemented yet. Ignored.\n",
|
|
__func__);
|
|
}
|
|
if (VMWI_TYPE(priv, pos, HVDC)) {
|
|
LINE_NOTICE(xpd, pos,
|
|
"%s: VMWI(hvdc) is not implemented yet. Ignored.\n",
|
|
__func__);
|
|
}
|
|
if (VMWI_TYPE(priv, pos, HVAC))
|
|
; /* VMWI_NEON */
|
|
if (priv->vmwisetting[pos].vmwi_type == 0)
|
|
; /* Disable VMWI */
|
|
priv->vmwisetting[pos] = vmwisetting;
|
|
set_vm_led_mode(xpd->xbus, xpd, pos, PHONEDEV(xpd).msg_waiting[pos]);
|
|
return 0;
|
|
}
|
|
|
|
static int hardware_dtmf_control(xpd_t *xpd, int pos, bool on)
|
|
{
|
|
int ret = 0;
|
|
|
|
LINE_DBG(SIGNAL, xpd, pos, "%s: %s\n", __func__, (on) ? "on" : "off");
|
|
if (XPD_HW(xpd).type == 6) {
|
|
int value = (on) ? 0xE0 : REG_TYPE6_TONEN_DTMF_DIS;
|
|
ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE,
|
|
REG_TYPE6_TONEN, value);
|
|
} else {
|
|
ret = SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_WRITE, 0x17, on);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Private ioctl()
|
|
* We don't need it now, since we detect vmwi via FSK patterns
|
|
*/
|
|
static int FXS_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
xbus_t *xbus;
|
|
int val;
|
|
unsigned long flags;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
if (!XBUS_IS(xbus, READY))
|
|
return -ENODEV;
|
|
if (pos < 0 || pos >= PHONEDEV(xpd).channels) {
|
|
XPD_NOTICE(xpd, "Bad channel number %d in %s(), cmd=%u\n", pos,
|
|
__func__, cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case DAHDI_ONHOOKTRANSFER:
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
LINE_DBG(SIGNAL, xpd, pos, "DAHDI_ONHOOKTRANSFER (%d millis)\n",
|
|
val);
|
|
if (IS_SET
|
|
(PHONEDEV(xpd).digital_inputs | PHONEDEV(xpd).
|
|
digital_outputs, pos))
|
|
return 0; /* Nothing to do */
|
|
oht_pcm(xpd, pos, 1); /* Get ready of VMWI FSK tones */
|
|
if (priv->lasttxhook[pos] == FXS_LINE_POL_ACTIVE
|
|
|| IS_SET(priv->neon_blinking, pos)) {
|
|
priv->ohttimer[pos] = val;
|
|
priv->idletxhookstate[pos] = FXS_LINE_POL_OHTRANS;
|
|
vmwi_search(xpd, pos, 1);
|
|
CALL_PHONE_METHOD(card_pcm_recompute, xpd,
|
|
priv->search_fsk_pattern);
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"Start OHT_TIMER. wanted_pcm_mask=0x%X\n",
|
|
PHONEDEV(xpd).wanted_pcm_mask);
|
|
}
|
|
if (VMWI_NEON(priv, pos) && !IS_OFFHOOK(xpd, pos))
|
|
start_stop_vm_led(xbus, xpd, pos);
|
|
return 0;
|
|
case DAHDI_TONEDETECT:
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"DAHDI_TONEDETECT: %s %s (dtmf_detection=%s)\n",
|
|
(val & DAHDI_TONEDETECT_ON) ? "ON" : "OFF",
|
|
(val & DAHDI_TONEDETECT_MUTE) ? "MUTE" : "NO-MUTE",
|
|
(dtmf_detection ? "YES" : "NO"));
|
|
if (!dtmf_detection) {
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
if (IS_SET(priv->want_dtmf_events, pos)) {
|
|
/*
|
|
* Detection mode changed:
|
|
* Disable DTMF interrupts
|
|
*/
|
|
}
|
|
hardware_dtmf_control(xpd, pos, 0);
|
|
BIT_CLR(priv->want_dtmf_events, pos);
|
|
BIT_CLR(priv->want_dtmf_mute, pos);
|
|
__do_mute_dtmf(xpd, pos, 0);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return -ENOTTY;
|
|
}
|
|
/*
|
|
* During natively bridged calls, Asterisk
|
|
* will request one of the sides to stop sending
|
|
* dtmf events. Check the requested state.
|
|
*/
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
if (val & DAHDI_TONEDETECT_ON) {
|
|
if (!IS_SET(priv->want_dtmf_events, pos)) {
|
|
/*
|
|
* Detection mode changed:
|
|
* Enable DTMF interrupts
|
|
*/
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"DAHDI_TONEDETECT: "
|
|
"Enable Hardware DTMF\n");
|
|
hardware_dtmf_control(xpd, pos, 1);
|
|
}
|
|
BIT_SET(priv->want_dtmf_events, pos);
|
|
} else {
|
|
if (IS_SET(priv->want_dtmf_events, pos)) {
|
|
/*
|
|
* Detection mode changed:
|
|
* Disable DTMF interrupts
|
|
*/
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"DAHDI_TONEDETECT: "
|
|
"Disable Hardware DTMF\n");
|
|
hardware_dtmf_control(xpd, pos, 0);
|
|
}
|
|
BIT_CLR(priv->want_dtmf_events, pos);
|
|
}
|
|
if (val & DAHDI_TONEDETECT_MUTE) {
|
|
BIT_SET(priv->want_dtmf_mute, pos);
|
|
} else {
|
|
BIT_CLR(priv->want_dtmf_mute, pos);
|
|
__do_mute_dtmf(xpd, pos, 0);
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return 0;
|
|
case DAHDI_SETPOLARITY:
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
/*
|
|
* Asterisk may send us this if chan_dahdi config
|
|
* has "hanguponpolarityswitch=yes" to notify
|
|
* that the other side has hanged up.
|
|
*
|
|
* This has no effect on normal phone (but we may
|
|
* be connected to another FXO equipment).
|
|
* note that this chan_dahdi settings has different
|
|
* meaning for FXO, where it signals polarity
|
|
* reversal *detection* logic.
|
|
*
|
|
* It seems that sometimes we get this from
|
|
* asterisk in wrong state (e.g: while ringing).
|
|
* In these cases, silently ignore it.
|
|
*/
|
|
if (priv->lasttxhook[pos] == FXS_LINE_RING
|
|
|| priv->lasttxhook[pos] == FXS_LINE_OPEN) {
|
|
LINE_DBG(SIGNAL, xpd, pos,
|
|
"DAHDI_SETPOLARITY: %s Cannot change "
|
|
"when lasttxhook=0x%X\n",
|
|
(val) ? "ON" : "OFF", priv->lasttxhook[pos]);
|
|
return -EINVAL;
|
|
}
|
|
LINE_DBG(SIGNAL, xpd, pos, "DAHDI_SETPOLARITY: %s\n",
|
|
(val) ? "ON" : "OFF");
|
|
if ((val && !reversepolarity) || (!val && reversepolarity))
|
|
priv->lasttxhook[pos] |= FXS_LINE_RING;
|
|
else
|
|
priv->lasttxhook[pos] &= ~FXS_LINE_RING;
|
|
linefeed_control(xbus, xpd, pos, priv->lasttxhook[pos]);
|
|
return 0;
|
|
case DAHDI_VMWI_CONFIG:
|
|
if (set_vmwi(xpd, pos, arg) < 0)
|
|
return -EINVAL;
|
|
return 0;
|
|
case DAHDI_VMWI: /* message-waiting led control */
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
if (!vmwi_ioctl) {
|
|
static bool notified;
|
|
|
|
if (!notified) {
|
|
notified = true;
|
|
LINE_NOTICE(xpd, pos,
|
|
"Got DAHDI_VMWI notification "
|
|
"but vmwi_ioctl parameter is off. "
|
|
"Ignoring.\n");
|
|
}
|
|
return 0;
|
|
}
|
|
/* Digital inputs/outputs don't have VM leds */
|
|
if (IS_SET
|
|
(PHONEDEV(xpd).digital_inputs | PHONEDEV(xpd).
|
|
digital_outputs, pos))
|
|
return 0;
|
|
PHONEDEV(xpd).msg_waiting[pos] = val;
|
|
LINE_DBG(SIGNAL, xpd, pos, "DAHDI_VMWI: %s\n",
|
|
(val) ? "yes" : "no");
|
|
return 0;
|
|
default:
|
|
report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
|
|
}
|
|
return -ENOTTY;
|
|
}
|
|
|
|
static int FXS_card_open(xpd_t *xpd, lineno_t chan)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
if (IS_OFFHOOK(xpd, chan))
|
|
LINE_NOTICE(xpd, chan, "Already offhook during open. OK.\n");
|
|
else
|
|
LINE_DBG(SIGNAL, xpd, chan, "is onhook\n");
|
|
/*
|
|
* Delegate updating dahdi to FXS_card_tick():
|
|
* The problem is that dahdi_hooksig() is spinlocking the channel and
|
|
* we are called by dahdi with the spinlock already held on the
|
|
* same channel.
|
|
*/
|
|
BIT_SET(priv->update_offhook_state, chan);
|
|
return 0;
|
|
}
|
|
|
|
static int FXS_card_close(xpd_t *xpd, lineno_t chan)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
LINE_DBG(GENERAL, xpd, chan, "\n");
|
|
priv = xpd->priv;
|
|
priv->idletxhookstate[chan] = FXS_LINE_POL_ACTIVE;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
/*
|
|
* INPUT polling is done via SLIC register 0x06 (same as LEDS):
|
|
* 7 6 5 4 3 2 1 0
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
* | I1 | I3 | | | I2 | I4 | | |
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*
|
|
*/
|
|
static int input_ports_type1[] = {
|
|
/* slic = input_port */
|
|
[0] = -1,
|
|
[1] = -1,
|
|
[2] = 2,
|
|
[3] = 3,
|
|
[4] = -1,
|
|
[5] = -1,
|
|
[6] = 0,
|
|
[7] = 1,
|
|
};
|
|
|
|
static void poll_inputs(xpd_t *xpd)
|
|
{
|
|
int i;
|
|
|
|
BUG_ON(xpd->xbus_idx != 0); // Only unit #0 has digital inputs
|
|
if (XPD_HW(xpd).type == 6) {
|
|
EXP_REQUEST(xpd->xbus, xpd, SLIC_READ,
|
|
REG_TYPE6_EXP_GPIOB, 0, 0);
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(input_ports_type1); i++) {
|
|
int pos = input_ports_type1[i];
|
|
if (pos >= 0) {
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, i, SLIC_READ, 0x06, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void poll_linefeed(xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
if (XPD_HW(xpd).type != 6)
|
|
return;
|
|
if (xpd->xpd_state != XPD_STATE_READY)
|
|
return;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
BUG_ON(!xpd->xbus);
|
|
|
|
XPD_DBG(GENERAL, xpd, "periodic poll");
|
|
for_each_line(xpd, i) {
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, i)
|
|
|| IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
continue;
|
|
if (priv->polledhook[i] == FXS_LINE_OPEN &&
|
|
priv->lasttxhook[i] != FXS_LINE_OPEN) {
|
|
LINE_NOTICE(xpd, i, "Overheat detected, resetting.");
|
|
priv->overheat_reset_counter[i]++;
|
|
linefeed_control(xpd->xbus, xpd, i,
|
|
priv->lasttxhook[i]);
|
|
}
|
|
SLIC_DIRECT_REQUEST(xpd->xbus, xpd, i, SLIC_READ,
|
|
REG_TYPE6_LINEFEED, 0);
|
|
}
|
|
}
|
|
|
|
static void handle_linefeed(xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
for_each_line(xpd, i) {
|
|
if (priv->lasttxhook[i] == FXS_LINE_RING
|
|
&& !IS_SET(priv->neon_blinking, i)) {
|
|
/* RINGing, prepare for OHT */
|
|
priv->ohttimer[i] = OHT_TIMER;
|
|
priv->idletxhookstate[i] = FXS_LINE_POL_OHTRANS;
|
|
} else {
|
|
if (priv->ohttimer[i]) {
|
|
priv->ohttimer[i]--;
|
|
if (!priv->ohttimer[i]) {
|
|
LINE_DBG(SIGNAL, xpd, i,
|
|
"ohttimer expired\n");
|
|
priv->idletxhookstate[i] =
|
|
FXS_LINE_POL_ACTIVE;
|
|
oht_pcm(xpd, i, 0);
|
|
vmwi_search(xpd, i, 0);
|
|
if (priv->lasttxhook[i] ==
|
|
FXS_LINE_POL_OHTRANS) {
|
|
/* Apply the change if appropriate */
|
|
linefeed_control(xpd->xbus, xpd,
|
|
i,
|
|
FXS_LINE_POL_ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Optimized memcmp() like function. Only test for equality (true/false).
|
|
* This optimization reduced the detect_vmwi() runtime by a factor of 3.
|
|
*/
|
|
static inline bool mem_equal(const char a[], const char b[], size_t len)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
if (a[i] != b[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Detect Voice Mail Waiting Indication
|
|
*/
|
|
static void detect_vmwi(xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
xbus_t *xbus;
|
|
static const __u8 FSK_COMMON_PATTERN[] =
|
|
{ 0xA8, 0x49, 0x22, 0x3B, 0x9F, 0xFF, 0x1F, 0xBB };
|
|
static const __u8 FSK_ON_PATTERN[] =
|
|
{ 0xA2, 0x2C, 0x1F, 0x2C, 0xBB, 0xA1, 0xA5, 0xFF };
|
|
static const __u8 FSK_OFF_PATTERN[] =
|
|
{ 0xA2, 0x2C, 0x28, 0xA5, 0xB1, 0x21, 0x49, 0x9F };
|
|
int i;
|
|
xpp_line_t ignore_mask;
|
|
|
|
BUG_ON(!xpd);
|
|
xbus = xpd->xbus;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
ignore_mask =
|
|
PHONEDEV(xpd).offhook_state |
|
|
~(PHONEDEV(xpd).oht_pcm_pass) |
|
|
~(priv->search_fsk_pattern) |
|
|
PHONEDEV(xpd).digital_inputs |
|
|
PHONEDEV(xpd).digital_outputs;
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
__u8 *writechunk = chan->writechunk;
|
|
|
|
if (IS_SET(ignore_mask, i))
|
|
continue;
|
|
#if 0
|
|
if (writechunk[0] != 0x7F && writechunk[0] != 0) {
|
|
int j;
|
|
|
|
LINE_DBG(GENERAL, xpd, i, "MSG:");
|
|
for (j = 0; j < DAHDI_CHUNKSIZE; j++) {
|
|
if (debug)
|
|
printk(" %02X", writechunk[j]);
|
|
}
|
|
if (debug)
|
|
printk("\n");
|
|
}
|
|
#endif
|
|
if (unlikely
|
|
(mem_equal
|
|
(writechunk, FSK_COMMON_PATTERN, DAHDI_CHUNKSIZE))) {
|
|
LINE_DBG(SIGNAL, xpd, i,
|
|
"Found common FSK pattern. "
|
|
"Start looking for ON/OFF patterns.\n");
|
|
BIT_SET(priv->found_fsk_pattern, i);
|
|
} else if (unlikely(IS_SET(priv->found_fsk_pattern, i))) {
|
|
BIT_CLR(priv->found_fsk_pattern, i);
|
|
oht_pcm(xpd, i, 0);
|
|
if (unlikely
|
|
(mem_equal
|
|
(writechunk, FSK_ON_PATTERN, DAHDI_CHUNKSIZE))) {
|
|
LINE_DBG(SIGNAL, xpd, i, "MSG WAITING ON\n");
|
|
PHONEDEV(xpd).msg_waiting[i] = 1;
|
|
start_stop_vm_led(xbus, xpd, i);
|
|
} else
|
|
if (unlikely
|
|
(mem_equal
|
|
(writechunk, FSK_OFF_PATTERN,
|
|
DAHDI_CHUNKSIZE))) {
|
|
LINE_DBG(SIGNAL, xpd, i, "MSG WAITING OFF\n");
|
|
PHONEDEV(xpd).msg_waiting[i] = 0;
|
|
start_stop_vm_led(xbus, xpd, i);
|
|
} else {
|
|
int j;
|
|
|
|
LINE_NOTICE(xpd, i, "MSG WAITING Unexpected:");
|
|
for (j = 0; j < DAHDI_CHUNKSIZE; j++)
|
|
printk(" %02X", writechunk[j]);
|
|
printk("\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int FXS_card_tick(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
struct FXS_priv_data *priv;
|
|
|
|
BUG_ON(!xpd);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
if (poll_digital_inputs && PHONEDEV(xpd).digital_inputs) {
|
|
if ((xpd->timer_count % poll_digital_inputs) == 0)
|
|
poll_inputs(xpd);
|
|
}
|
|
#endif
|
|
if ((xpd->timer_count % poll_chan_linefeed) == 0)
|
|
poll_linefeed(xpd);
|
|
handle_fxs_leds(xpd);
|
|
handle_linefeed(xpd);
|
|
if (XPD_HW(xpd).type == 6)
|
|
blink_mwi(xpd);
|
|
/*
|
|
* Hack alert (FIXME):
|
|
* Asterisk did FXS_card_open() and we wanted to report
|
|
* offhook state. However, the channel is spinlocked by dahdi
|
|
* so we marked it in the priv->update_offhook_state mask and
|
|
* now we take care of notification to dahdi and Asterisk
|
|
*/
|
|
if (priv->update_offhook_state) {
|
|
enum dahdi_rxsig rxsig;
|
|
int i;
|
|
|
|
for_each_line(xpd, i) {
|
|
if (!IS_SET(priv->update_offhook_state, i))
|
|
continue;
|
|
rxsig =
|
|
IS_OFFHOOK(xpd,
|
|
i) ? DAHDI_RXSIG_OFFHOOK :
|
|
DAHDI_RXSIG_ONHOOK;
|
|
/* Notify after open() */
|
|
notify_rxsig(xpd, i, rxsig);
|
|
BIT_CLR(priv->update_offhook_state, i);
|
|
}
|
|
}
|
|
if (SPAN_REGISTERED(xpd)) {
|
|
if (!vmwi_ioctl && priv->search_fsk_pattern)
|
|
detect_vmwi(xpd); /* Detect via FSK modulation */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- FXS: HOST COMMANDS -------------------------------------*/
|
|
|
|
/*---------------- FXS: Astribank Reply Handlers --------------------------*/
|
|
|
|
/*
|
|
* Should be called with spinlocked XPD
|
|
*/
|
|
static void process_hookstate(xpd_t *xpd, xpp_line_t offhook,
|
|
xpp_line_t change_mask)
|
|
{
|
|
xbus_t *xbus;
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
|
|
xbus = xpd->xbus;
|
|
priv = xpd->priv;
|
|
XPD_DBG(SIGNAL, xpd, "offhook=0x%X change_mask=0x%X\n", offhook,
|
|
change_mask);
|
|
for_each_line(xpd, i) {
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, i)
|
|
|| IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
continue;
|
|
if (IS_SET(change_mask, i)) {
|
|
PHONEDEV(xpd).ringing[i] = 0; /* No more ringing... */
|
|
#ifdef WITH_METERING
|
|
metering_gen(xpd, i, 0); /* Stop metering... */
|
|
#endif
|
|
MARK_BLINK(priv, i, LED_GREEN, 0);
|
|
/*
|
|
* Reset our previous DTMF memories...
|
|
*/
|
|
BIT_CLR(priv->prev_key_down, i);
|
|
priv->prev_key_time[i] = ktime_set(0L, 0UL);
|
|
if (IS_SET(offhook, i)) {
|
|
LINE_DBG(SIGNAL, xpd, i, "OFFHOOK\n");
|
|
MARK_ON(priv, i, LED_GREEN);
|
|
hookstate_changed(xpd, i, 1);
|
|
} else {
|
|
LINE_DBG(SIGNAL, xpd, i, "ONHOOK\n");
|
|
MARK_OFF(priv, i, LED_GREEN);
|
|
hookstate_changed(xpd, i, 0);
|
|
}
|
|
/*
|
|
* Must switch to low power. In high power, an ONHOOK
|
|
* won't be detected.
|
|
*/
|
|
do_chan_power(xbus, xpd, i, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
HANDLER_DEF(FXS, SIG_CHANGED)
|
|
{
|
|
xpp_line_t sig_status =
|
|
RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_status);
|
|
xpp_line_t sig_toggles =
|
|
RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_toggles);
|
|
unsigned long flags;
|
|
|
|
BUG_ON(!xpd);
|
|
BUG_ON(PHONEDEV(xpd).direction != TO_PHONE);
|
|
XPD_DBG(SIGNAL, xpd, "(PHONE) sig_toggles=0x%04X sig_status=0x%04X\n",
|
|
sig_toggles, sig_status);
|
|
#if 0
|
|
Is this needed ? for_each_line(xpd, i) {
|
|
// Power down (prevent overheating!!!)
|
|
if (IS_SET(sig_toggles, i))
|
|
do_chan_power(xpd->xbus, xpd, BIT(i), 0);
|
|
}
|
|
#endif
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
process_hookstate(xpd, sig_status, sig_toggles);
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
static inline void notify_digital_input(xpd_t *xpd, int input_port, int offhook)
|
|
{
|
|
int channo = PHONEDEV(xpd).channels - LINES_DIGI_INP + input_port;
|
|
|
|
/* Stop ringing. No leds for digital inputs. */
|
|
PHONEDEV(xpd).ringing[channo] = 0;
|
|
if (offhook && !IS_OFFHOOK(xpd, channo)) {
|
|
LINE_DBG(SIGNAL, xpd, channo, "OFFHOOK\n");
|
|
hookstate_changed(xpd, channo, 1);
|
|
} else if (!offhook && IS_OFFHOOK(xpd, channo)) {
|
|
LINE_DBG(SIGNAL, xpd, channo, "ONHOOK\n");
|
|
hookstate_changed(xpd, channo, 0);
|
|
}
|
|
}
|
|
|
|
static void process_digital_inputs(xpd_t *xpd, const reg_cmd_t *info)
|
|
{
|
|
bool offhook;
|
|
/* Sanity check */
|
|
if (!PHONEDEV(xpd).digital_inputs) {
|
|
XPD_NOTICE(xpd, "%s called without digital inputs. Ignored\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
if (XPD_HW(xpd).type == 6) {
|
|
static int input_values_type6[] = { 0x80, 0x20, 0x08, 0x02 }; /* I/O Expander values of input relays */
|
|
int i;
|
|
|
|
/* Map I/O Expander GPIO into line number */
|
|
for (i = 0; i < ARRAY_SIZE(input_values_type6); i++) {
|
|
int chanmask = input_values_type6[i];
|
|
|
|
offhook = (REG_FIELD(info, data_low) & chanmask) == 0;
|
|
notify_digital_input(xpd, i, offhook);
|
|
}
|
|
} else {
|
|
int channo = info->h.portnum;
|
|
int input_port;
|
|
offhook = (REG_FIELD(info, data_low) & 0x1) == 0;
|
|
if (channo < 0 || channo >= ARRAY_SIZE(input_ports_type1)) {
|
|
XPD_ERR(xpd, "%s: got bad portnum=%d\n", __func__, channo);
|
|
return;
|
|
}
|
|
input_port = input_ports_type1[channo];
|
|
if (input_port < 0) {
|
|
XPD_ERR(xpd, "%s: portnum=%d is not input port\n", __func__, channo);
|
|
return;
|
|
}
|
|
notify_digital_input(xpd, input_port, offhook);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static const char dtmf_digits[] = {
|
|
'D', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#',
|
|
'A', 'B', 'C'
|
|
};
|
|
|
|
/*
|
|
* This function is called with spinlocked XPD
|
|
*/
|
|
static void process_dtmf(xpd_t *xpd, uint portnum, __u8 val)
|
|
{
|
|
__u8 digit;
|
|
bool key_down = val & 0x10;
|
|
bool want_mute;
|
|
bool want_event;
|
|
struct FXS_priv_data *priv;
|
|
ktime_t now;
|
|
s64 msec = 0;
|
|
struct timespec64 ts;
|
|
|
|
if (!dtmf_detection)
|
|
return;
|
|
if (!SPAN_REGISTERED(xpd))
|
|
return;
|
|
priv = xpd->priv;
|
|
val &= 0xF;
|
|
digit = dtmf_digits[val];
|
|
want_mute = IS_SET(priv->want_dtmf_mute, portnum);
|
|
want_event = IS_SET(priv->want_dtmf_events, portnum);
|
|
if (!IS_SET(priv->prev_key_down, portnum) && !key_down)
|
|
LINE_NOTICE(xpd, portnum, "DTMF: duplicate UP (%c)\n", digit);
|
|
if (key_down)
|
|
BIT_SET(priv->prev_key_down, portnum);
|
|
else
|
|
BIT_CLR(priv->prev_key_down, portnum);
|
|
now = ktime_get();
|
|
if (!dahdi_ktime_equal(priv->prev_key_time[portnum], ktime_set(0, 0)))
|
|
msec = ktime_ms_delta(now, priv->prev_key_time[portnum]);
|
|
priv->prev_key_time[portnum] = now;
|
|
ts = ktime_to_timespec64(now);
|
|
LINE_DBG(SIGNAL, xpd, portnum,
|
|
"[%lld.%06ld] DTMF digit %-4s '%c' (val=%d, want_mute=%s want_event=%s, delta=%lld msec)\n",
|
|
(s64)ts.tv_sec, ts.tv_nsec * NSEC_PER_USEC,
|
|
(key_down) ? "DOWN" : "UP", digit, val,
|
|
(want_mute) ? "yes" : "no", (want_event) ? "yes" : "no", msec);
|
|
/*
|
|
* FIXME: we currently don't use the want_dtmf_mute until
|
|
* we are sure about the logic in Asterisk native bridging.
|
|
* Meanwhile, simply mute it on button press.
|
|
*/
|
|
if (key_down && want_mute)
|
|
__do_mute_dtmf(xpd, portnum, 1);
|
|
else
|
|
__do_mute_dtmf(xpd, portnum, 0);
|
|
if (want_event) {
|
|
int event =
|
|
(key_down) ? DAHDI_EVENT_DTMFDOWN : DAHDI_EVENT_DTMFUP;
|
|
|
|
dahdi_qevent_lock(XPD_CHAN(xpd, portnum), event | digit);
|
|
}
|
|
}
|
|
|
|
static int FXS_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
|
|
{
|
|
unsigned long flags;
|
|
struct FXS_priv_data *priv;
|
|
__u8 regnum = 0;
|
|
bool indirect = 0;
|
|
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
if (info->h.bytes == REG_CMD_SIZE(REG)) {
|
|
if ((XPD_HW(xpd).type == 1) && (REG_FIELD(info, regnum) == 0x1E))
|
|
indirect = 1;
|
|
regnum = (indirect) ? REG_FIELD(info, subreg) : REG_FIELD(info, regnum);
|
|
XPD_DBG(REGS, xpd, "%s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
|
|
(indirect) ? "I" : "D", regnum, REG_FIELD(info, data_low),
|
|
REG_FIELD(info, data_high));
|
|
}
|
|
if (info->h.bytes == REG_CMD_SIZE(RAM)) {
|
|
uint addr;
|
|
unsigned long val;
|
|
XPD_DBG(REGS, xpd, "port=%d, addr_low=0x%X, addr_high=0x%X, data_0=0x%X data_1=0x%X data_2=0x%X data_3=0x%X\n",
|
|
info->h.portnum,
|
|
REG_FIELD_RAM(info, addr_low),
|
|
REG_FIELD_RAM(info, addr_high),
|
|
REG_FIELD_RAM(info, data_0),
|
|
REG_FIELD_RAM(info, data_1),
|
|
REG_FIELD_RAM(info, data_2),
|
|
REG_FIELD_RAM(info, data_3));
|
|
addr = (REG_FIELD_RAM(info, addr_high) << 8) | REG_FIELD_RAM(info, addr_low);
|
|
val = (REG_FIELD_RAM(info, data_3) << 24) |
|
|
(REG_FIELD_RAM(info, data_2) << 16) |
|
|
(REG_FIELD_RAM(info, data_1) << 8) |
|
|
REG_FIELD_RAM(info, data_0);
|
|
} else if ((XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_DTMF_DECODE) ||
|
|
(XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_TONDTMF)) {
|
|
__u8 val = REG_FIELD(info, data_low);
|
|
|
|
process_dtmf(xpd, info->h.portnum, val);
|
|
} else if ((XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_LINEFEED)) {
|
|
__u8 val = REG_FIELD(info, data_low);
|
|
|
|
LINE_DBG(SIGNAL, xpd, info->h.portnum,
|
|
"REG_TYPE6_LINEFEED: dataL=0x%X \n", val);
|
|
priv->polledhook[info->h.portnum] = val;
|
|
}
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
/*
|
|
* Process digital inputs polling results
|
|
*/
|
|
else if ( (XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_DIGITAL_IOCTRL) ||
|
|
(XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_EXP_GPIOB &&
|
|
REG_FIELD(info, do_expander)))
|
|
process_digital_inputs(xpd, info);
|
|
#endif
|
|
else if (XPD_HW(xpd).type == 1 && !indirect && regnum == REG_TYPE1_LOOPCLOSURE) { /* OFFHOOK ? */
|
|
__u8 val = REG_FIELD(info, data_low);
|
|
xpp_line_t mask = BIT(info->h.portnum);
|
|
xpp_line_t offhook;
|
|
|
|
/*
|
|
* Validate reply. Non-existing/disabled ports
|
|
* will reply with 0xFF. Ignore these.
|
|
*/
|
|
if ((val & REG_TYPE1_LOOPCLOSURE_ZERO) == 0) {
|
|
offhook = (val & REG_TYPE1_LOOPCLOSURE_LCR) ? mask : 0;
|
|
LINE_DBG(SIGNAL, xpd, info->h.portnum,
|
|
"REG_TYPE1_LOOPCLOSURE: dataL=0x%X "
|
|
"(offhook=0x%X mask=0x%X)\n",
|
|
val, offhook, mask);
|
|
process_hookstate(xpd, offhook, mask);
|
|
}
|
|
} else if (XPD_HW(xpd).type == 6 && !indirect && regnum == REG_TYPE6_LCRRTP) { /* OFFHOOK ? */
|
|
__u8 val = REG_FIELD(info, data_low);
|
|
xpp_line_t mask = BIT(info->h.portnum);
|
|
xpp_line_t offhook;
|
|
|
|
/*
|
|
* Validate reply. Non-existing/disabled ports
|
|
* will reply with 0xFF. Ignore these.
|
|
*/
|
|
if ((val & REG_TYPE6_LCRRTP_ZERO) == 0) {
|
|
offhook = (val & REG_TYPE6_LCRRTP_LCR) ? mask : 0;
|
|
LINE_DBG(SIGNAL, xpd, info->h.portnum,
|
|
"REG_TYPE6_LCRRTP: dataL=0x%X "
|
|
"(offhook=0x%X mask=0x%X)\n",
|
|
val, offhook, mask);
|
|
process_hookstate(xpd, offhook, mask);
|
|
}
|
|
} else {
|
|
#if 0
|
|
XPD_NOTICE(xpd,
|
|
"Spurious register reply(ignored): "
|
|
"%s reg_num=0x%X, dataL=0x%X dataH=0x%X\n",
|
|
(indirect) ? "I" : "D",
|
|
regnum, REG_FIELD(info, data_low),
|
|
REG_FIELD(info, data_high));
|
|
#endif
|
|
}
|
|
/*
|
|
* 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;
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int FXS_card_state(xpd_t *xpd, bool on)
|
|
{
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off");
|
|
return 0;
|
|
}
|
|
|
|
static const struct xops fxs_xops = {
|
|
.card_new = FXS_card_new,
|
|
.card_init = FXS_card_init,
|
|
.card_remove = FXS_card_remove,
|
|
.card_tick = FXS_card_tick,
|
|
.card_register_reply = FXS_card_register_reply,
|
|
};
|
|
|
|
static const struct phoneops fxs_phoneops = {
|
|
.card_dahdi_preregistration = FXS_card_dahdi_preregistration,
|
|
.card_dahdi_postregistration = FXS_card_dahdi_postregistration,
|
|
.card_hooksig = FXS_card_hooksig,
|
|
.card_pcm_recompute = generic_card_pcm_recompute,
|
|
.card_pcm_fromspan = generic_card_pcm_fromspan,
|
|
.card_pcm_tospan = generic_card_pcm_tospan,
|
|
.card_timing_priority = generic_timing_priority,
|
|
.echocancel_timeslot = generic_echocancel_timeslot,
|
|
.echocancel_setmask = generic_echocancel_setmask,
|
|
.card_open = FXS_card_open,
|
|
.card_close = FXS_card_close,
|
|
.card_ioctl = FXS_card_ioctl,
|
|
.card_state = FXS_card_state,
|
|
};
|
|
|
|
static xproto_table_t PROTO_TABLE(FXS) = {
|
|
.owner = THIS_MODULE,
|
|
.entries = {
|
|
/* Prototable Card Opcode */
|
|
XENTRY( FXS, FXS, SIG_CHANGED ),
|
|
},
|
|
.name = "FXS", /* protocol name */
|
|
.ports_per_subunit = 8,
|
|
.type = XPD_TYPE_FXS,
|
|
.xops = &fxs_xops,
|
|
.phoneops = &fxs_phoneops,
|
|
.packet_is_valid = fxs_packet_is_valid,
|
|
.packet_dump = fxs_packet_dump,
|
|
};
|
|
|
|
static bool fxs_packet_is_valid(xpacket_t *pack)
|
|
{
|
|
const xproto_entry_t *xe;
|
|
|
|
// DBG(GENERAL, "\n");
|
|
xe = xproto_card_entry(&PROTO_TABLE(FXS), XPACKET_OP(pack));
|
|
return xe != NULL;
|
|
}
|
|
|
|
static void fxs_packet_dump(const char *msg, xpacket_t *pack)
|
|
{
|
|
DBG(GENERAL, "%s\n", msg);
|
|
}
|
|
|
|
/*------------------------- SLIC Handling --------------------------*/
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static int proc_fxs_info_show(struct seq_file *sfile, void *not_used)
|
|
{
|
|
unsigned long flags;
|
|
xpd_t *xpd = sfile->private;
|
|
struct FXS_priv_data *priv;
|
|
int i;
|
|
int led;
|
|
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
seq_printf(sfile, "%-12s", "Channel:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d", i);
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "");
|
|
for_each_line(xpd, i) {
|
|
char *chan_type;
|
|
|
|
if (IS_SET(PHONEDEV(xpd).digital_outputs, i))
|
|
chan_type = "out";
|
|
else if (IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
chan_type = "in";
|
|
else
|
|
chan_type = "";
|
|
seq_printf(sfile, "%4s", chan_type);
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "idletxhook:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d", priv->idletxhookstate[i]);
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "lasttxhook:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d", priv->lasttxhook[i]);
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "ohttimer:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d", priv->ohttimer[i]);
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "neon_blink:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d",
|
|
IS_SET(priv->neon_blinking, i));
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "search_fsk:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d",
|
|
IS_SET(priv->search_fsk_pattern, i));
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "vbat_h:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d",
|
|
test_bit(i, (unsigned long *)&priv->vbat_h));
|
|
}
|
|
seq_printf(sfile, "\n");
|
|
for (led = 0; led < NUM_LEDS; led++) {
|
|
seq_printf(sfile, "\nLED #%d\t%-12s: ",
|
|
led, "ledstate");
|
|
for_each_line(xpd, i) {
|
|
if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
|
|
&& !IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
seq_printf(sfile, "%d ",
|
|
IS_SET(priv->ledstate[led], i));
|
|
}
|
|
seq_printf(sfile, "\nLED #%d\t%-12s: ",
|
|
led, "ledcontrol");
|
|
for_each_line(xpd, i) {
|
|
if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
|
|
&& !IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
seq_printf(sfile, "%d ",
|
|
IS_SET(priv->ledcontrol[led], i));
|
|
}
|
|
seq_printf(sfile, "\nLED #%d\t%-12s: ",
|
|
led, "led_counter");
|
|
for_each_line(xpd, i) {
|
|
if (!IS_SET(PHONEDEV(xpd).digital_outputs, i)
|
|
&& !IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
seq_printf(sfile, "%d ",
|
|
LED_COUNTER(priv, i, led));
|
|
}
|
|
}
|
|
seq_printf(sfile, "\n%-12s", "overheats:");
|
|
for_each_line(xpd, i) {
|
|
seq_printf(sfile, "%4d", priv->overheat_reset_counter[i]);
|
|
}
|
|
seq_printf(sfile, "\n");
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int proc_fxs_info_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, proc_fxs_info_show, PDE_DATA(inode));
|
|
}
|
|
|
|
#ifdef DAHDI_HAVE_PROC_OPS
|
|
static const struct proc_ops proc_fxs_info_ops = {
|
|
.proc_open = proc_fxs_info_open,
|
|
.proc_read = seq_read,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_release = single_release,
|
|
};
|
|
#else
|
|
static const struct file_operations proc_fxs_info_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = proc_fxs_info_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
#endif
|
|
|
|
#ifdef WITH_METERING
|
|
static ssize_t proc_xpd_metering_write(struct file *file,
|
|
const char __user *buffer, size_t count, loff_t *offset)
|
|
{
|
|
xpd_t *xpd = file->private_data;
|
|
char buf[MAX_PROC_WRITE];
|
|
lineno_t chan;
|
|
int num;
|
|
int ret;
|
|
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
if (count >= MAX_PROC_WRITE - 1) {
|
|
XPD_ERR(xpd, "Metering string too long (%zu)\n", count);
|
|
return -EINVAL;
|
|
}
|
|
if (copy_from_user(&buf, buffer, count))
|
|
return -EFAULT;
|
|
buf[count] = '\0';
|
|
ret = sscanf(buf, "%d", &num);
|
|
if (ret != 1) {
|
|
XPD_ERR(xpd, "Metering value should be number. Got '%s'\n",
|
|
buf);
|
|
return -EINVAL;
|
|
}
|
|
chan = num;
|
|
if (chan != PORT_BROADCAST && chan > xpd->channels) {
|
|
XPD_ERR(xpd, "Metering tone: bad channel number %d\n", chan);
|
|
return -EINVAL;
|
|
}
|
|
if ((ret = metering_gen(xpd, chan, 1)) < 0) {
|
|
XPD_ERR(xpd, "Failed sending metering tone\n");
|
|
return ret;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int proc_xpd_metering_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = PDE_DATA(inode);
|
|
}
|
|
|
|
#ifdef DAHDI_HAVE_PROC_OPS
|
|
static const struct proc_ops proc_xpd_metering_ops = {
|
|
.proc_open = proc_xpd_metering_open,
|
|
.proc_write = proc_xpd_metering_write,
|
|
.proc_release = single_release,
|
|
};
|
|
#else
|
|
static const struct file_operations proc_xpd_metering_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = proc_xpd_metering_open,
|
|
.write = proc_xpd_metering_write,
|
|
.release = single_release,
|
|
};
|
|
#endif /* DAHDI_HAVE_PROC_OPS */
|
|
#endif
|
|
#endif
|
|
|
|
static DEVICE_ATTR_READER(fxs_ring_registers_show, dev, buf)
|
|
{
|
|
xpd_t *xpd;
|
|
struct FXS_priv_data *priv;
|
|
unsigned long flags;
|
|
const struct ring_reg_params *p;
|
|
const struct byte_pair *v;
|
|
enum ring_types rtype;
|
|
int len = 0;
|
|
int i;
|
|
|
|
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, "# Reg#: D/I\tNEON \tTRAPEZ \tNORMAL \n");
|
|
for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
|
|
p = &ring_parameters[i];
|
|
len += sprintf(buf + len, "[%d] 0x%02X: %c",
|
|
i, p->regno, (p->is_indirect) ? 'I' : 'D');
|
|
for (rtype = RING_TYPE_NEON; rtype <= RING_TYPE_NORMAL; rtype++) {
|
|
v = &(p->values[rtype]);
|
|
if (p->is_indirect)
|
|
len += sprintf(buf + len, "\t0x%02X 0x%02X",
|
|
v->h_val, v->l_val);
|
|
else
|
|
len += sprintf(buf + len, "\t0x%02X ----",
|
|
v->l_val);
|
|
}
|
|
len += sprintf(buf + len, "\n");
|
|
}
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_WRITER(fxs_ring_registers_store, dev, buf, count)
|
|
{
|
|
xpd_t *xpd;
|
|
struct FXS_priv_data *priv;
|
|
unsigned long flags;
|
|
char rtype_name[MAX_PROC_WRITE];
|
|
enum ring_types rtype;
|
|
struct ring_reg_params *params;
|
|
struct byte_pair *v;
|
|
int regno;
|
|
int h_val;
|
|
int l_val;
|
|
int ret;
|
|
int i;
|
|
|
|
BUG_ON(!dev);
|
|
xpd = dev_to_xpd(dev);
|
|
if (!xpd)
|
|
return -ENODEV;
|
|
priv = xpd->priv;
|
|
BUG_ON(!priv);
|
|
ret = sscanf(buf, "%10s %X %X %X\n",
|
|
rtype_name, ®no, &h_val, &l_val);
|
|
if (ret < 3 || ret > 4) {
|
|
XPD_ERR(xpd, "Bad input: '%s'\n", buf);
|
|
XPD_ERR(xpd, "# Correct input\n");
|
|
XPD_ERR(xpd, "{NEON|TRAPEZ|NORMAL} <regno> <byte> [<byte>]\n");
|
|
goto invalid_input;
|
|
}
|
|
if (strcasecmp("NEON", rtype_name) == 0)
|
|
rtype = RING_TYPE_NEON;
|
|
else if (strcasecmp("TRAPEZ", rtype_name) == 0)
|
|
rtype = RING_TYPE_TRAPEZ;
|
|
else if (strcasecmp("NORMAL", rtype_name) == 0)
|
|
rtype = RING_TYPE_NORMAL;
|
|
else {
|
|
XPD_ERR(xpd, "Unknown ring type '%s' (NEON/TRAPEZ/NORMAL)\n",
|
|
rtype_name);
|
|
goto invalid_input;
|
|
}
|
|
params = NULL;
|
|
for (i = 0; i < ARRAY_SIZE(ring_parameters); i++) {
|
|
if (ring_parameters[i].regno == regno) {
|
|
params = &ring_parameters[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!params) {
|
|
XPD_ERR(xpd, "Bad register 0x%X\n", regno);
|
|
goto invalid_input;
|
|
}
|
|
if (params->is_indirect) {
|
|
if (ret != 4) {
|
|
XPD_ERR(xpd,
|
|
"Missing low-byte (0x%X is indirect register)\n",
|
|
regno);
|
|
goto invalid_input;
|
|
}
|
|
XPD_DBG(SIGNAL, xpd, "%s Indirect 0x%X <=== 0x%X 0x%X\n",
|
|
rtype_name, regno, h_val, l_val);
|
|
} else {
|
|
if (ret != 3) {
|
|
XPD_ERR(xpd,
|
|
"Should give exactly one value (0x%X is direct register)\n",
|
|
regno);
|
|
goto invalid_input;
|
|
}
|
|
l_val = h_val;
|
|
h_val = 0;
|
|
XPD_DBG(SIGNAL, xpd, "%s Direct 0x%X <=== 0x%X\n",
|
|
rtype_name, regno, h_val);
|
|
}
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
v = &(params->values[rtype]);
|
|
v->h_val = h_val;
|
|
v->l_val = l_val;
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return count;
|
|
invalid_input:
|
|
return -EINVAL;
|
|
}
|
|
|
|
static DEVICE_ATTR(fxs_ring_registers, S_IRUGO | S_IWUSR,
|
|
fxs_ring_registers_show,
|
|
fxs_ring_registers_store);
|
|
|
|
static int fxs_xpd_probe(struct device *dev)
|
|
{
|
|
xpd_t *xpd;
|
|
int ret;
|
|
|
|
xpd = dev_to_xpd(dev);
|
|
/* Is it our device? */
|
|
if (xpd->xpd_type != XPD_TYPE_FXS) {
|
|
XPD_ERR(xpd, "drop suggestion for %s (%d)\n", dev_name(dev),
|
|
xpd->xpd_type);
|
|
return -EINVAL;
|
|
}
|
|
XPD_DBG(DEVICES, xpd, "SYSFS\n");
|
|
ret = device_create_file(dev, &dev_attr_fxs_ring_registers);
|
|
if (ret) {
|
|
XPD_ERR(xpd, "%s: device_create_file(fxs_ring_registers) failed: %d\n",
|
|
__func__, ret);
|
|
goto fail_fxs_ring_registers;
|
|
}
|
|
return 0;
|
|
fail_fxs_ring_registers:
|
|
return ret;
|
|
}
|
|
|
|
static int fxs_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_fxs_ring_registers);
|
|
return 0;
|
|
}
|
|
|
|
static struct xpd_driver fxs_driver = {
|
|
.xpd_type = XPD_TYPE_FXS,
|
|
.driver = {
|
|
.name = "fxs",
|
|
.owner = THIS_MODULE,
|
|
.probe = fxs_xpd_probe,
|
|
.remove = fxs_xpd_remove}
|
|
};
|
|
|
|
static int __init card_fxs_startup(void)
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = xpd_driver_register(&fxs_driver.driver)) < 0)
|
|
return ret;
|
|
|
|
#ifdef POLL_DIGITAL_INPUTS
|
|
INFO("FEATURE: with DIGITAL INPUTS support (polled every %d msec)\n",
|
|
poll_digital_inputs);
|
|
#else
|
|
INFO("FEATURE: without DIGITAL INPUTS support\n");
|
|
#endif
|
|
INFO("FEATURE: DAHDI_VMWI (HVAC only)\n");
|
|
#ifdef WITH_METERING
|
|
INFO("FEATURE: WITH METERING Generation\n");
|
|
#else
|
|
INFO("FEATURE: NO METERING Generation\n");
|
|
#endif
|
|
xproto_register(&PROTO_TABLE(FXS));
|
|
return 0;
|
|
}
|
|
|
|
static void __exit card_fxs_cleanup(void)
|
|
{
|
|
xproto_unregister(&PROTO_TABLE(FXS));
|
|
xpd_driver_unregister(&fxs_driver.driver);
|
|
}
|
|
|
|
MODULE_DESCRIPTION("XPP FXS Card Driver");
|
|
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_XPD(XPD_TYPE_FXS);
|
|
|
|
module_init(card_fxs_startup);
|
|
module_exit(card_fxs_cleanup);
|