/* * Written by Oron Peled * 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 #include #include #include #include /* For zaptel version */ #include "xpd.h" #include "xproto.h" #include "xpp_zap.h" #include "card_fxo.h" #include "zap_debug.h" static const char rcsid[] = "$Id$"; DEF_PARM(int, print_dbg, 0, "Print DBG statements"); /* must be before zap_debug.h */ #define LINES_REGULAR 8 #define LINES_DIGI_OUT 2 #define LINES_DIGI_INP 4 #define MASK_BITS(b) ((1U << (b)) - 1) #define MASK_DIGI_OUT (MASK_BITS(LINES_DIGI_OUT) << LINES_REGULAR) #define MASK_DIGI_INP (MASK_BITS(LINES_DIGI_INP) << (LINES_REGULAR + LINES_DIGI_OUT)) enum fxs_leds { LED_GREEN, LED_RED, OUTPUT_RELAY, }; #define NUM_LEDS 2 /*---------------- FXS Protocol Commands ----------------------------------*/ static /* 0x0F */ DECLARE_CMD(FXS, CHAN_ENABLE, xpp_line_t lines, bool on); static /* 0x0F */ DECLARE_CMD(FXS, CHAN_CID, xpp_line_t lines); static /* 0x0F */ DECLARE_CMD(FXS, RING, int pos, bool on); static /* 0x0F */ DECLARE_CMD(FXS, SETHOOK, int pos, bool offhook); static /* 0x0F */ DECLARE_CMD(FXS, RELAY_OUT, byte which, bool on); static /* 0x0F */ DECLARE_CMD(FXS, SLIC_INIT); static /* 0x0F */ DECLARE_CMD(FXS, SLIC_QUERY, int pos, byte reg_num); static bool fxs_packet_is_valid(xpacket_t *pack); static void fxs_packet_dump(xpacket_t *pack); static int proc_fxs_info_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data); #define S_(s,l,...) \ { \ .lines = s, \ { \ .len = l, \ .data = { __VA_ARGS__ }, \ } \ } struct slic_init_data { xpp_line_t lines; slic_data_t slic_data; } slic_init_data[] = { #ifdef OLD_CARD #include "init_data_3_19.inc" #else #include "init_data_3_20.inc" #endif }; #undef S_ #define PROC_SLIC_FNAME "slics" #define PROC_FXS_INFO_FNAME "fxs_info" struct FXS_priv_data { struct proc_dir_entry *xpd_slic; struct proc_dir_entry *fxs_info; slic_reply_t requested_reply; slic_reply_t last_reply; xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */ xpp_line_t ledcontrol[NUM_LEDS]; /* 0 - OFF, 1 - ON */ int blinking[NUM_LEDS][CHANNELS_PERXPD]; }; /*---------------- FXS: Static functions ----------------------------------*/ static int do_chan_power(xbus_t *xbus, xpd_t *xpd, xpp_line_t lines, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; BUG_ON(!xbus); BUG_ON(!xpd); lines &= xpd->enabled_chans; // Ignore disabled channels if(!lines) { return 0; } DBG("%s/%s: 0x%04X %s\n", xbus->busname, xpd->xpdname, lines, (on) ? "up" : "down"); XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); if(on) { // Power up len = slic_cmd_direct_write(sc, lines, 0x42, 0x06); } else { // Power down len = slic_cmd_direct_write(sc, lines, 0x42, 0x00); } pack->datalen = len; packet_send(xbus, pack); return ret; } #define IS_BLINKING(priv,pos,color) ((priv)->blinking[color][pos] != 0) #define DO_BLINK(priv,pos,color,val) ((priv)->blinking[color][pos] = (val)) #define DO_LED(priv,pos,color,val) ((val)?BIT_SET((priv)->ledcontrol[color],(pos)):BIT_CLR((priv)->ledcontrol[color],(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 */ static int do_led(xpd_t *xpd, lineno_t pos, byte which, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; int value; struct FXS_priv_data *priv; xpp_line_t lines; xbus_t *xbus; BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; which = which % NUM_LEDS; if(IS_SET(xpd->digital_outputs, pos) || IS_SET(xpd->digital_inputs, pos)) goto out; if(pos == ALL_LINES) { lines = ~0; priv->ledstate[which] = (on) ? ~0 : 0; } else { lines = BIT(pos); if(on) { BIT_SET(priv->ledstate[which], pos); } else { BIT_CLR(priv->ledstate[which], pos); } } if(!(lines & xpd->enabled_chans)) // Ignore disabled channels goto out; DBG("%s/%s: LED: lines=0x%04X which=%d -- %s\n", xbus->busname, xpd->xpdname, lines, which, (on) ? "on" : "off"); value = BIT(2) | BIT(3); value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[which]); if(on) value |= led_register_vals[which]; XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, lines, 0x06, value); pack->datalen = len; packet_send(xbus, pack); out: return ret; } static void handle_fxs_leds(xpd_t *xpd) { int i; unsigned long flags; const enum fxs_leds colors[] = { LED_GREEN, LED_RED }; int color; unsigned int timer_count; struct FXS_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; spin_lock_irqsave(&xpd->lock, flags); timer_count = xpd->timer_count; for(color = 0; color < ARRAY_SIZE(colors); color++) { for_each_enabled_line(xpd, i) { if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) continue; if(IS_BLINKING(priv, i, color)) { // Blinking // led state is toggled if((timer_count % LED_BLINK_PERIOD) == 0) { DBG("%s/%s/%d ledstate=%s\n", xpd->xbus->busname, xpd->xpdname, i, (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], i) && !IS_SET(priv->ledstate[color], i)) { do_led(xpd, i, color, 1); } else if(!IS_SET(priv->ledcontrol[color], i) && IS_SET(priv->ledstate[color], i)) { do_led(xpd, i, color, 0); } } } spin_unlock_irqrestore(&xpd->lock, flags); } /*---------------- FXS: Methods -------------------------------------------*/ static xpd_t *FXS_card_new(xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, byte revision) { xpd_t *xpd = NULL; int channels = min(8, CHANNELS_PERXPD); if(xpd_num == 0) channels += 6; /* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */ xpd = xpd_alloc(sizeof(struct FXS_priv_data), xbus, xpd_num, proto_table, channels, revision); if(!xpd) return NULL; if(xpd_num == 0) { DBG("First XPD on %s detected. Initialize digital outputs/inputs\n", xbus->busname); xpd->digital_outputs = MASK_DIGI_OUT; xpd->digital_inputs = MASK_DIGI_INP; } xpd->direction = TO_PHONE; xpd->revision = revision; return xpd; } static void clean_proc(xbus_t *xbus, xpd_t *xpd) { struct FXS_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; #ifdef CONFIG_PROC_FS if(priv->xpd_slic) { DBG("Removing xpd SLIC file %s/%s\n", xbus->busname, xpd->xpdname); remove_proc_entry(PROC_SLIC_FNAME, xpd->proc_xpd_dir); } if(priv->fxs_info) { DBG("Removing xpd FXS_INFO file %s/%s\n", xbus->busname, xpd->xpdname); remove_proc_entry(PROC_FXS_INFO_FNAME, xpd->proc_xpd_dir); } #endif } static int FXS_card_init(xbus_t *xbus, xpd_t *xpd) { struct FXS_priv_data *priv; int ret = 0; BUG_ON(!xpd); priv = xpd->priv; #ifdef CONFIG_PROC_FS DBG("Creating FXS_INFO file for %s/%s\n", xbus->busname, xpd->xpdname); priv->fxs_info = create_proc_read_entry(PROC_FXS_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxs_info_read, xpd); if(!priv->fxs_info) { ERR("Failed to create proc '%s' for %s/%s\n", PROC_FXS_INFO_FNAME, xbus->busname, xpd->xpdname); ret = -ENOENT; goto out; } DBG("Creating SLICs file for %s/%s\n", xbus->busname, xpd->xpdname); priv->xpd_slic = create_proc_entry(PROC_SLIC_FNAME, 0644, xpd->proc_xpd_dir); if(!priv->xpd_slic) { ERR("Failed to create proc file for SLICs of %s/%s\n", xbus->busname, xpd->xpdname); ret = -ENOENT; goto out; } priv->xpd_slic->write_proc = proc_xpd_slic_write; priv->xpd_slic->read_proc = proc_xpd_slic_read; priv->xpd_slic->data = xpd; #endif #ifdef HARD_CODED_INIT CALL_PROTO(FXS, SLIC_INIT, xbus, xpd); #else ret = run_initialize_registers(xpd); #endif out: if(ret < 0) { clean_proc(xbus, xpd); ERR("%s/%s: Failed initializing registers (%d)\n", xbus->busname, xpd->xpdname, ret); } else { DBG("done: %s/%s\n", xbus->busname, xpd->xpdname); } return ret; } static int FXS_card_remove(xbus_t *xbus, xpd_t *xpd) { struct FXS_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; DBG("%s/%s\n", xbus->busname, xpd->xpdname); clean_proc(xbus, xpd); return 0; } static int FXS_card_zaptel_registration(xpd_t *xpd, bool on) { xbus_t *xbus; struct FXS_priv_data *priv; unsigned long flags; int i; BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; BUG_ON(!xbus); DBG("%s/%s: %s\n", xbus->busname, xpd->xpdname, (on)?"on":"off"); if(on) { spin_lock_irqsave(&xpd->lock, flags); do_led(xpd, ALL_LINES, LED_GREEN, LED_OFF); spin_unlock_irqrestore(&xpd->lock, flags); for_each_enabled_line(xpd, i) { DO_LED(priv,i,LED_GREEN,LED_ON); mdelay(50); } for_each_enabled_line(xpd, i) { DO_LED(priv,i,LED_GREEN,LED_OFF); mdelay(50); } } else { spin_lock_irqsave(&xpd->lock, flags); do_led(xpd, ALL_LINES, LED_RED, LED_OFF); spin_unlock_irqrestore(&xpd->lock, flags); for_each_enabled_line(xpd, i) { DO_LED(priv,i,LED_RED,LED_ON); mdelay(50); } for_each_enabled_line(xpd, i) { DO_LED(priv,i,LED_RED,LED_OFF); mdelay(50); } } return 0; } int FXS_card_sethook(xbus_t *xbus, xpd_t *xpd, int pos, int hookstate) { int ret = 0; DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, pos, hookstate2str(hookstate)); switch(hookstate) { /* On-hook, off-hook: The PBX is playing a phone on an FXO line. * Can be ignored for an FXS line */ case ZT_ONHOOK: if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to ONHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos); ret = -EINVAL; break; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("%s/%s/%d: digital output OFF\n", xbus->busname, xpd->xpdname, pos); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0); break; } xpd->ringing[pos] = 0; DBG("%s/%s/%d: stop ringing\n", xbus->busname, xpd->xpdname, pos); #if 1 // FIXME: Not needed -- verify ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off #endif if(ret) { DBG("ZT_ONHOOK(stop ring) Failed: ret=0x%02X\n", ret); break; } break; case ZT_START: DBG("%s/%s/%d: fall through ZT_OFFHOOK\n", xbus->busname, xpd->xpdname, pos); // Fall through case ZT_OFFHOOK: DBG("%s/%s/%d: ignoring (PHONE)\n", xbus->busname, xpd->xpdname, pos); break; case ZT_WINK: WARN("No code yet\n"); break; case ZT_FLASH: WARN("No code yet\n"); break; case ZT_RING: DBG("%s/%s/%d: ringing[%d]=%d\n", xbus->busname, xpd->xpdname, pos, pos, xpd->ringing[pos]); if(IS_SET(xpd->digital_inputs, pos)) { NOTICE("%s: Trying to RING a digital input channel %d. Ignoring\n", __FUNCTION__, pos); return -EINVAL; } if(IS_SET(xpd->digital_outputs, pos)) { DBG("%s/%s/%d: digital output ON\n", xbus->busname, xpd->xpdname, pos); ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1); return ret; } xpd->ringing[pos] = 1; ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on if(ret) { DBG("ZT_RING Failed: ret=0x%02X\n", ret); } break; case ZT_RINGOFF: WARN("No code yet\n"); break; } return ret; } /* * 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_channels[] = { 6, 7, 2, 3 }; // Slic numbers of input relays static void poll_inputs(xbus_t *xbus, xpd_t *xpd) { int i; for(i = 0; i < ARRAY_SIZE(input_channels); i++) { int pos = input_channels[i]; CALL_PROTO(FXS, SLIC_QUERY, xbus, xpd, pos, 0x06); } } static int FXS_card_tick(xbus_t *xbus, xpd_t *xpd) { static int rate_limit = 0; struct FXS_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; BUG_ON(!priv); if((rate_limit++ % 1000) == 0) { poll_inputs(xbus, xpd); } handle_fxs_leds(xpd); return 0; } /*---------------- FXS: HOST COMMANDS -------------------------------------*/ static /* 0x0F */ HOSTCMD(FXS, CHAN_ENABLE, xpp_line_t lines, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; enum fxs_state value = (on) ? 0x01 : 0x00; unsigned long flags; int i; BUG_ON(!xbus); BUG_ON(!xpd); lines &= xpd->enabled_chans; // Ignore disabled channels if(!lines) { return 0; } DBG("Channel Activation: 0x%4X %s\n", lines, (on) ? "on" : "off"); XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, lines, 0x40, value); pack->datalen = len; for_each_line(xpd, i) xpd->lasttxhook[i] = value; packet_send(xbus, pack); spin_lock_irqsave(&xpd->lock, flags); if(on) { do_led(xpd, ALL_LINES, LED_GREEN, LED_ON); } else { do_led(xpd, ALL_LINES, LED_GREEN, LED_OFF); } spin_unlock_irqrestore(&xpd->lock, flags); return ret; } static /* 0x0F */ HOSTCMD(FXS, CHAN_CID, xpp_line_t lines) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int i; BUG_ON(!xbus); BUG_ON(!xpd); lines &= xpd->enabled_chans; // Ignore disabled channels if(!lines) { return 0; } DBG("Channel CID: 0x%04X\n", lines); XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); pack->datalen = slic_cmd_direct_write(sc, lines, 0x40, FXS_LINE_CID); packet_send(xbus, pack); for_each_line(xpd, i) xpd->lasttxhook[i] = FXS_LINE_CID; return ret; } static /* 0x0F */ HOSTCMD(FXS, RING, int pos, bool on) { int ret = 0; struct FXS_priv_data *priv; xpacket_t *pack; slic_cmd_t *sc; xpp_line_t mask = (1 << pos); int len; enum fxs_state value = (on) ? 0x04 : 0x01; BUG_ON(!xbus); BUG_ON(!xpd); priv = xpd->priv; mask &= xpd->enabled_chans; // Ignore disabled channels if(!mask) { return 0; } DBG("%s/%s/%d %s\n", xbus->busname, xpd->xpdname, pos, (on) ? "on" : "off"); do_chan_power(xbus, xpd, BIT(pos), on); // Power up (for ring) XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, mask, 0x40, value); xpd->lasttxhook[pos] = value; pack->datalen = len; packet_send(xbus, pack); if(on) { DO_BLINK(priv,pos,LED_GREEN,LED_BLINK); } else { if(IS_BLINKING(priv, pos, LED_GREEN)) DO_BLINK(priv,pos,LED_GREEN,0); } return ret; } static /* 0x0F */ HOSTCMD(FXS, SETHOOK, int pos, bool offhook) { BUG(); // Should never be called return 0; } static /* 0x0F */ HOSTCMD(FXS, RELAY_OUT, byte which, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; int value; xpp_line_t lines; int relay_channels[] = { 0, 4 }; BUG_ON(!xbus); BUG_ON(!xpd); DBG("RELAY_OUT: which=%d -- %s\n", which, (on) ? "on" : "off"); which = which % ARRAY_SIZE(relay_channels); lines = BIT(relay_channels[which]); 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]; XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, lines, 0x06, value); DBG("RELAY_OUT pack: line=%d value=0x%04X\n", lines, value); pack->datalen = len; packet_send(xbus, pack); return ret; } static /* 0x0F */ HOSTCMD(FXS, SLIC_INIT) { int ret = 0; xpacket_t *pack; slic_data_t *slic; struct slic_init_data *source; int i; BUG_ON(!xbus); BUG_ON(!xpd); DBG("INITIALIZING SLIC\n"); for(i = 0; i < ARRAY_SIZE(slic_init_data); i++) { source = &slic_init_data[i]; XPACKET_NEW(pack, xbus, FXS, SLIC_INIT, xpd->id); RPACKET_FIELD(pack, FXS, SLIC_INIT, lines) = source->lines; slic = &RPACKET_FIELD(pack, FXS, SLIC_INIT, slic_data); slic->len = source->slic_data.len; memcpy(slic->data, source->slic_data.data, source->slic_data.len); pack->datalen = sizeof(xpp_line_t) + slic->len + 1; // dump_packet("SLIC", pack, print_dbg); packet_send(xbus, pack); mdelay(1); // FIXME: check with Dima } return ret; } static /* 0x0F */ HOSTCMD(FXS, SLIC_QUERY, int pos, byte reg_num) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; BUG_ON(!xbus); BUG_ON(!xpd); // DBG("\n"); XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd); len = slic_cmd_direct_read(sc, BIT(pos), reg_num); pack->datalen = len; packet_send(xbus, pack); return ret; } /*---------------- FXS: Astribank Reply Handlers --------------------------*/ 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); struct FXS_priv_data *priv; int i; BUG_ON(!xpd); BUG_ON(xpd->direction != TO_PHONE); priv = xpd->priv; DBG("%s/%s: (PHONE) sig_toggles=0x%04X sig_status=0x%04X\n", xbus->busname, xpd->xpdname, sig_toggles, sig_status); if(!SPAN_REGISTERED(xpd)) { NOTICE("%s: %s/%s is not registered. Skipping.\n", __FUNCTION__, xbus->busname, xpd->xpdname); return -ENODEV; } #if 0 Is this needed? for_each_enabled_line(xpd, i) { if(IS_SET(sig_toggles, i)) do_chan_power(xpd->xbus, xpd, BIT(i), 0); // Power down (prevent overheating!!!) } #endif for_each_line(xpd, i) { if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) continue; if(IS_SET(sig_toggles, i)) { struct zt_chan *chan = &xpd->span.chans[i]; xpd->ringing[i] = 0; // No more ringing... DO_BLINK(priv,i,LED_GREEN,0); if(IS_SET(sig_status, i)) { DBG("OFFHOOK: channo=%d\n", chan->channo); DO_LED(priv,i,LED_GREEN,LED_ON); BIT_SET(xpd->hookstate, i); zt_hooksig(chan, ZT_RXSIG_OFFHOOK); } else { DBG("ONHOOK channo=%d\n", chan->channo); DO_LED(priv,i,LED_GREEN,LED_OFF); BIT_CLR(xpd->hookstate, i); zt_hooksig(chan, ZT_RXSIG_ONHOOK); } } } return 0; } HANDLER_DEF(FXS, SLIC_REPLY) { slic_reply_t *info = &RPACKET_FIELD(pack, FXS, SLIC_REPLY, info); xpp_line_t lines = RPACKET_FIELD(pack, FXS, SLIC_REPLY, lines); unsigned long flags; struct FXS_priv_data *priv; if(!xpd) { NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr)); return -EPROTO; } spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); #if 0 DBG("SLIC_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n", xpd->id, (info->indirect)?"I":"D", info->reg_num, info->data_low, info->data_high); #endif if(xpd->id == 0 && info->indirect == 0 && info->reg_num == 0x06) { /* Digital Inputs Poll Result */ int i; bool offhook = (info->data_low & 0x1) == 0; /* Map SLIC number into line number */ for(i = 0; i < ARRAY_SIZE(input_channels); i++) { int channo = input_channels[i]; int newchanno; struct zt_chan *chan; if(IS_SET(lines, channo)) { newchanno = LINES_REGULAR + LINES_DIGI_OUT + i; BIT_CLR(lines, channo); BIT_SET(lines, newchanno); chan = &xpd->span.chans[newchanno]; xpd->ringing[newchanno] = 0; // Stop ringing. No leds for digital inputs. if(offhook && !IS_SET(xpd->hookstate, newchanno)) { // OFFHOOK DBG("OFFHOOK: channo=%d\n", chan->channo); BIT_SET(xpd->hookstate, newchanno); zt_hooksig(chan, ZT_RXSIG_OFFHOOK); } else if(!offhook && IS_SET(xpd->hookstate, newchanno)) { // ONHOOK DBG("ONHOOK channo=%d\n", chan->channo); BIT_CLR(xpd->hookstate, newchanno); zt_hooksig(chan, ZT_RXSIG_ONHOOK); } } } } /* Update /proc info only if reply relate to the last slic read request */ if(priv->requested_reply.indirect == info->indirect && priv->requested_reply.reg_num == info->reg_num) { priv->last_reply = *info; } spin_unlock_irqrestore(&xpd->lock, flags); return 0; } xproto_table_t PROTO_TABLE(FXS) = { .owner = THIS_MODULE, .entries = { /* Card Opcode */ XENTRY( FXS, SIG_CHANGED ), XENTRY( FXS, SLIC_REPLY ), }, .name = "FXS", .type = XPD_TYPE_FXS, .xops = { .card_new = FXS_card_new, .card_init = FXS_card_init, .card_remove = FXS_card_remove, .card_zaptel_registration = FXS_card_zaptel_registration, .card_sethook = FXS_card_sethook, .card_tick = FXS_card_tick, .RING = XPROTO_CALLER(FXS, RING), .SETHOOK = XPROTO_CALLER(FXS, SETHOOK), .RELAY_OUT = XPROTO_CALLER(FXS, RELAY_OUT), .CHAN_ENABLE = XPROTO_CALLER(FXS, CHAN_ENABLE), .CHAN_CID = XPROTO_CALLER(FXS, CHAN_CID), .SYNC_SOURCE = XPROTO_CALLER(GLOBAL, SYNC_SOURCE), .PCM_WRITE = XPROTO_CALLER(GLOBAL, PCM_WRITE), }, .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("\n"); xe = xproto_card_entry(&PROTO_TABLE(FXS), pack->content.opcode); return xe != NULL; } static void fxs_packet_dump(xpacket_t *pack) { DBG("\n"); } /*------------------------- SLIC Handling --------------------------*/ static int proc_fxs_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 FXS_priv_data *priv; int i; int led; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); len += sprintf(page + len, "\t%-17s: ", "Channel"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%d ", i % 10); } len += sprintf(page + len, "\n"); for(led = 0; led < NUM_LEDS; led++) { len += sprintf(page + len, "LED #%d", led); len += sprintf(page + len, "\n\t%-17s: ", "ledstate"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%d ", IS_SET(priv->ledstate[led], i)); } len += sprintf(page + len, "\n\t%-17s: ", "ledcontrol"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%d ", IS_SET(priv->ledcontrol[led], i)); } len += sprintf(page + len, "\n\t%-17s: ", "blinking"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%d ", IS_BLINKING(priv,i,led)); } len += sprintf(page + len, "\n"); } 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; } static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xpd_t *xpd = data; slic_reply_t *info; struct FXS_priv_data *priv; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); info = &priv->last_reply; len += sprintf(page + len, "# Writing bad data into this file may damage your hardware!\n"); len += sprintf(page + len, "# Consult firmware docs first\n"); len += sprintf(page + len, "SLIC_REPLY: %s reg_num=0x%X, dataH=0x%X dataL=0x%X\n", (info->indirect)?"I":"D", info->reg_num, info->data_high, info->data_low); 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; } /* * Direct/Indirect * v * FF FF FF FF WD 06 1 * ^---------^ ^ Reg * | Write/Read * | * SLIC # */ static int parse_slic_cmd(const char *buf, slic_cmd_t *sc, slic_reply_t *requested_reply) { char op; /* [W]rite, [R]ead */ char reg_type; /* [D]irect, [I]ndirect */ int s1, s2, s3, s4; int reg_num; int data_low, data_high; xpp_line_t lines; int ret; ret = sscanf(buf, "%x %x %x %x %c%c %x %x %x", &s1, &s2, &s3, &s4, &op, ®_type, ®_num, &data_high, &data_low); lines = (s4 << 24) | (s3 << 16) | (s2 << 8) | (s1); switch(op) { case 'R': if(reg_type == 'D' && ret == 7) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); ret = slic_cmd_direct_read(sc, lines, reg_num); if(requested_reply) { requested_reply->indirect = 0; requested_reply->reg_num = reg_num; } } else if(reg_type == 'I' && ret == 7) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); ret = slic_cmd_indirect_read(sc, lines, reg_num); if(requested_reply) { requested_reply->indirect = 1; requested_reply->reg_num = reg_num; } } else { NOTICE("%s: Bad read input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); goto err; } break; case 'W': if(reg_type == 'D' && ret == 8) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high); ret = slic_cmd_direct_write(sc, lines, reg_num, data_high); } else if(reg_type == 'I' && ret == 9) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high, data_low); ret = slic_cmd_indirect_write(sc, lines, reg_num, data_low, data_high); } else { NOTICE("%s: Bad write input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); goto err; } break; default: NOTICE("%s: Bad input: ret=%d buf='%s' op=%c\n", __FUNCTION__, ret, buf, op); goto err; } return ret; err: return -EINVAL; } static int process_slic_cmdline(xpd_t *xpd, char *cmdline) { xbus_t *xbus; struct FXS_priv_data *priv; slic_cmd_t sc; xpacket_t *pack; char *p; int len = strlen(cmdline); BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; if((p = strchr(cmdline, '#')) != NULL) /* Truncate comments */ *p = '\0'; if((p = strchr(cmdline, ';')) != NULL) /* Truncate comments */ *p = '\0'; for(p = cmdline; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */ ; if(*p == '\0') return 0; len = parse_slic_cmd(p, &sc, &priv->requested_reply); if(len < 0) return len; sc.lines &= xpd->enabled_chans; // Ignore disabled channels if(!sc.lines) { NOTICE("%s: no enabled channels are marked. Skip.\n", __FUNCTION__); return 0; } dump_slic_cmd("WRITE_SLIC", &sc); XPACKET_NEW(pack, xbus, FXS, SLIC_WRITE, xpd->id); RPACKET_FIELD(pack, FXS, SLIC_WRITE, slic_cmd) = sc; pack->datalen = len; packet_send(xbus, pack); return 0; } static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data) { xpd_t *xpd = data; const int LINE_LEN = 500; char buf[LINE_LEN]; char *p; int i; int ret; BUG_ON(!xpd); for(i = 0; i < count; /* noop */) { for(p = buf; p < buf + LINE_LEN; p++) { /* read a line */ if(i >= count) break; if(get_user(*p, buffer + i)) return -EFAULT; i++; if(*p == '\n' || *p == '\r') /* whatever */ break; } if(p >= buf + LINE_LEN) return -E2BIG; *p = '\0'; ret = process_slic_cmdline(xpd, buf); if(ret < 0) return ret; mdelay(1); } return count; } int __init card_fxs_startup(void) { INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION); xproto_register(&PROTO_TABLE(FXS)); return 0; } void __exit card_fxs_cleanup(void) { xproto_unregister(&PROTO_TABLE(FXS)); } MODULE_DESCRIPTION("XPP FXS Card Driver"); MODULE_AUTHOR("Oron Peled "); MODULE_LICENSE("GPL"); MODULE_VERSION(ZAPTEL_VERSION); MODULE_ALIAS_XPD(XPD_TYPE_FXS); module_init(card_fxs_startup); module_exit(card_fxs_cleanup);