summaryrefslogtreecommitdiff
path: root/xpp/card_fxo.c
diff options
context:
space:
mode:
authortzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-05-03 23:06:02 +0000
committertzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-05-03 23:06:02 +0000
commit2dd60aaf18e98b0e9d3c06bd9dce5f1128fa55ad (patch)
tree1a1cd28888f191e6ce83bcbbe539124e2529c90b /xpp/card_fxo.c
parent8c4db4e3acd9a7626e709af0494055487b589719 (diff)
xpp driver release 1.1.0 (first part of commit)
* FPGA firmware now loaded from PC (for newer models) * Driver for the FXO module * Moved most userspace files to the subdirectory utils (see also next commit) * Explicit license for firmware files * Optionally avoid auto-registration * Initializations parameters to chips given from userspace * And did I mention bugfixes? git-svn-id: http://svn.digium.com/svn/zaptel/trunk@1021 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'xpp/card_fxo.c')
-rw-r--r--xpp/card_fxo.c886
1 files changed, 886 insertions, 0 deletions
diff --git a/xpp/card_fxo.c b/xpp/card_fxo.c
new file mode 100644
index 0000000..024e112
--- /dev/null
+++ b/xpp/card_fxo.c
@@ -0,0 +1,886 @@
+/*
+ * 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 <version.h> /* 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 */
+
+enum fxo_leds {
+ LED_GREEN,
+};
+
+#define NUM_LEDS 1
+#define DELAY_UNTIL_DIALTONE 3000
+
+/*---------------- FXO Protocol Commands ----------------------------------*/
+
+/* 0x0F */ DECLARE_CMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on);
+/* 0x0F */ DECLARE_CMD(FXO, CHAN_CID, xpp_line_t lines);
+/* 0x0F */ DECLARE_CMD(FXO, RING, int pos, bool on);
+/* 0x0F */ DECLARE_CMD(FXO, SETHOOK, int pos, bool offhook);
+/* 0x0F */ DECLARE_CMD(FXO, RELAY_OUT, byte which, bool on);
+/* 0x0F */ DECLARE_CMD(FXO, DAA_INIT);
+/* 0x0F */ DECLARE_CMD(FXO, DAA_QUERY, int pos, byte reg_num);
+
+static bool fxo_packet_is_valid(xpacket_t *pack);
+static void fxo_packet_dump(xpacket_t *pack);
+static int proc_fxo_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_4_19.inc"
+#else
+#include "init_data_4_20.inc"
+#endif
+};
+#undef S_
+
+#define PROC_DAA_FNAME "slics"
+#define PROC_FXO_INFO_FNAME "fxo_info"
+
+struct FXO_priv_data {
+ struct proc_dir_entry *xpd_slic;
+ struct proc_dir_entry *fxo_info;
+ slic_reply_t requested_reply;
+ slic_reply_t last_reply;
+ xpp_line_t battery;
+ xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */
+ int blinking[NUM_LEDS][CHANNELS_PERXPD];
+};
+
+/*---------------- FXO: Static functions ----------------------------------*/
+
+#define IS_BLINKING(priv,pos,color) ((priv)->blinking[color][pos] != 0)
+#define DO_BLINK(priv,pos,color,val) ((priv)->blinking[color][pos] = (val))
+
+/*
+ * LED control is done via DAA register 0x20
+ */
+static int do_led(xpd_t *xpd, lineno_t pos, byte which, bool on)
+{
+ unsigned long flags;
+ int ret = 0;
+ xpacket_t *pack;
+ slic_cmd_t *sc;
+ int len;
+ struct FXO_priv_data *priv;
+ xpp_line_t lines;
+ xbus_t *xbus;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+ 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");
+ XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id);
+ sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, lines, 0x20, on);
+ // DBG("LED pack: line=%d %s\n", i, (on)?"on":"off");
+ pack->datalen = len;
+ packet_send(xbus, pack);
+out:
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ return ret;
+}
+
+static void handle_fxo_leds(xpd_t *xpd)
+{
+ int i;
+ unsigned long flags;
+ const enum fxo_leds color = LED_GREEN;
+ unsigned int timer_count;
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+ priv = xpd->priv;
+ timer_count = xpd->timer_count;
+ 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)) {
+ // led state is toggled
+ if((timer_count % LED_BLINK_PERIOD) == 0) {
+ DBG("%s/%s/%d: led_state=%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);
+ }
+ }
+ }
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+static void do_sethook(xpd_t *xpd, int pos, bool offhook)
+{
+ unsigned long flags;
+
+ BUG_ON(!xpd);
+ BUG_ON(xpd->direction == TO_PHONE); // We can SETHOOK state only on PSTN
+ spin_lock_irqsave(&xpd->lock, flags);
+ xpd->ringing[pos] = 0; // No more rings
+ CALL_XMETHOD(SETHOOK, xpd->xbus, xpd, pos, offhook);
+ if(offhook) {
+ BIT_SET(xpd->hookstate, pos);
+ } else {
+ BIT_CLR(xpd->hookstate, pos);
+ xpd->delay_until_dialtone[pos] = 0;
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if(offhook)
+ wake_up_interruptible(&xpd->txstateq[pos]);
+}
+
+/*---------------- FXO: Methods -------------------------------------------*/
+
+static xpd_t *FXO_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);
+
+ xpd = xpd_alloc(sizeof(struct FXO_priv_data), xbus, xpd_num, proto_table, channels, revision);
+ if(!xpd)
+ return NULL;
+ xpd->direction = TO_PSTN;
+ xpd->revision = revision;
+ return xpd;
+}
+
+static void clean_proc(xbus_t *xbus, xpd_t *xpd)
+{
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+ DBG("%s/%s\n", xbus->busname, xpd->xpdname);
+#ifdef CONFIG_PROC_FS
+ if(priv->xpd_slic) {
+ DBG("Removing xpd DAA file %s/%s\n", xbus->busname, xpd->xpdname);
+ remove_proc_entry(PROC_DAA_FNAME, xpd->proc_xpd_dir);
+ }
+ if(priv->fxo_info) {
+ DBG("Removing xpd FXO_INFO file %s/%s\n", xbus->busname, xpd->xpdname);
+ remove_proc_entry(PROC_FXO_INFO_FNAME, xpd->proc_xpd_dir);
+ }
+#endif
+}
+
+static int FXO_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+ struct FXO_priv_data *priv;
+ int ret = 0;
+
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+#ifdef CONFIG_PROC_FS
+ DBG("Creating FXO_INFO file for %s/%s\n", xbus->busname, xpd->xpdname);
+ priv->fxo_info = create_proc_read_entry(PROC_FXO_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxo_info_read, xpd);
+ if(!priv->fxo_info) {
+ ERR("Failed to create proc '%s' for %s/%s\n", PROC_FXO_INFO_FNAME, xbus->busname, xpd->xpdname);
+ ret = -ENOENT;
+ goto out;
+ }
+ DBG("Creating DAAs file for %s/%s\n", xbus->busname, xpd->xpdname);
+ priv->xpd_slic = create_proc_entry(PROC_DAA_FNAME, 0644, xpd->proc_xpd_dir);
+ if(!priv->xpd_slic) {
+ ERR("Failed to create proc file for DAAs of %s/%s\n", xbus->busname, xpd->xpdname);
+ 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(FXO, DAA_INIT, xbus, xpd);
+#else
+ ret = run_initialize_registers(xpd);
+ if(ret < 0)
+ goto out;
+#endif
+ if(xpd->direction == TO_PSTN) {
+ int i;
+
+ // Hanghup all lines
+ for_each_enabled_line(xpd, i) {
+ init_waitqueue_head(&xpd->txstateq[i]);
+ do_sethook(xpd, i, 0);
+ }
+ }
+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 FXO_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+ struct FXO_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 FXO_card_zaptel_registration(xpd_t *xpd, bool on)
+{
+ xbus_t *xbus;
+ struct FXO_priv_data *priv;
+ int i;
+
+ BUG_ON(!xpd);
+ xbus = xpd->xbus;
+ BUG_ON(!xbus);
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on);
+ if(on) {
+ for_each_line(xpd, i) {
+ do_led(xpd, i, LED_GREEN, LED_ON);
+ mdelay(50);
+ }
+ for_each_line(xpd, i) {
+ do_led(xpd, i, LED_GREEN, LED_OFF);
+ mdelay(50);
+ }
+ } else {
+ for_each_line(xpd, i) {
+ do_led(xpd, i, LED_GREEN, LED_ON);
+ mdelay(100);
+ do_led(xpd, i, LED_GREEN, LED_OFF);
+ }
+ }
+ return 0;
+}
+
+int FXO_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:
+ do_sethook(xpd, pos, 0);
+ break;
+ case ZT_START:
+ DBG("%s/%s/%d: fall through ZT_OFFHOOK\n", xbus->busname, xpd->xpdname, pos);
+ xpd->delay_until_dialtone[pos] = DELAY_UNTIL_DIALTONE;
+ // Fall through
+ case ZT_OFFHOOK:
+ do_sethook(xpd, pos, 1);
+ wait_event_interruptible(xpd->txstateq[pos], xpd->delay_until_dialtone[pos] <= 0);
+ 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]);
+ break;
+ case ZT_RINGOFF:
+ WARN("No code yet\n");
+ break;
+ }
+ return ret;
+}
+
+static void poll_battery(xbus_t *xbus, xpd_t *xpd)
+{
+ int i;
+
+ for_each_enabled_line(xpd, i) {
+ CALL_PROTO(FXO, DAA_QUERY, xbus, xpd, i, DAA_VBAT_REGISTER);
+ }
+}
+
+
+static int FXO_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+ static int rate_limit = 0;
+ struct FXO_priv_data *priv;
+
+ BUG_ON(!xpd);
+ priv = xpd->priv;
+ BUG_ON(!priv);
+ rate_limit++;
+ if((rate_limit % 100) == 0) {
+ poll_battery(xbus, xpd);
+ }
+ handle_fxo_leds(xpd);
+ return 0;
+}
+
+/*---------------- FXO: HOST COMMANDS -------------------------------------*/
+
+/* 0x0F */ HOSTCMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on)
+{
+ int ret = 0;
+ 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");
+ if(on) {
+ for_each_enabled_line(xpd, i) {
+ do_led(xpd, i, LED_GREEN, LED_ON);
+ mdelay(20);
+ }
+ for_each_enabled_line(xpd, i) {
+ do_led(xpd, i, LED_GREEN, LED_OFF);
+ mdelay(20);
+ }
+ }
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(FXO, CHAN_CID, xpp_line_t lines)
+{
+ int ret = 0;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ lines &= xpd->enabled_chans; // Ignore disabled channels
+ if(!lines) {
+ return 0;
+ }
+ DBG("Channel CID: 0x%04X\n", lines);
+ return ret;
+}
+
+
+/* 0x0F */ HOSTCMD(FXO, RING, int pos, bool on)
+{
+ int ret = 0;
+ xpacket_t *pack;
+ slic_cmd_t *sc;
+ xpp_line_t mask = (1 << pos);
+ int len;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ mask &= xpd->enabled_chans; // Ignore disabled channels
+ if(!mask) {
+ return 0;
+ }
+ DBG("%s/%s/%d %s\n", xpd->xbus->busname, xpd->xpdname, pos, (on) ? "on" : "off");
+ XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id);
+ sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, mask, 0x40, (on)?0x04:0x01);
+ pack->datalen = len;
+
+ packet_send(xbus, pack);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(FXO, SETHOOK, int pos, bool offhook)
+{
+ int ret = 0;
+ xpacket_t *pack;
+ slic_cmd_t *sc;
+ int len;
+ unsigned long flags;
+ bool value;
+
+ BUG_ON(!xbus);
+ BUG_ON(!xpd);
+ value = (offhook) ? 0x01 : 0x00;
+ // value |= BIT(3); /* Bit 3 is for CID */
+ DBG("%s/%s/%d: SETHOOK: value=0x%02X %s\n", xbus->busname, xpd->xpdname, pos, value, (offhook)?"OFFHOOK":"ONHOOK");
+ spin_lock_irqsave(&xpd->lock, flags);
+ if(!IS_SET(xpd->enabled_chans, pos))
+ goto out;
+ XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id);
+ sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd);
+ len = slic_cmd_direct_write(sc, BIT(pos), 0x05, value);
+ pack->datalen = len;
+ packet_send(xbus, pack);
+ do_led(xpd, pos, LED_GREEN, (offhook)?LED_ON:LED_OFF);
+out:
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(FXO, RELAY_OUT, byte which, bool on)
+{
+ return -ENOSYS;
+}
+
+/* 0x0F */ HOSTCMD(FXO, DAA_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 DAA\n");
+ for(i = 0; i < ARRAY_SIZE(slic_init_data); i++) {
+ source = &slic_init_data[i];
+ XPACKET_NEW(pack, xbus, FXO, DAA_INIT, xpd->id);
+ RPACKET_FIELD(pack, FXO, DAA_INIT, lines) = source->lines;
+
+ slic = &RPACKET_FIELD(pack, FXO, DAA_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("DAA", pack, print_dbg);
+ packet_send(xbus, pack);
+ mdelay(1); // FIXME: check with Dima
+ }
+ return ret;
+}
+
+/* 0x0F */ HOSTCMD(FXO, DAA_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, FXO, DAA_WRITE, xpd->id);
+ sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd);
+ len = slic_cmd_direct_read(sc, BIT(pos), reg_num);
+
+ pack->datalen = len;
+
+ packet_send(xbus, pack);
+ return ret;
+}
+
+/*---------------- FXO: Astribank Reply Handlers --------------------------*/
+
+HANDLER_DEF(FXO, SIG_CHANGED)
+{
+ xpp_line_t sig_status = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_status);
+ xpp_line_t sig_toggles = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_toggles);
+ unsigned long flags;
+ int i;
+
+ if(!xpd) {
+ NOTICE("%s: received %s for non-existing xpd: %d\n",
+ __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr));
+ return -EPROTO;
+ }
+ DBG("%s/%s: (PSTN) sig_toggles=0x%04X sig_status=0x%04X\n", xpd->xbus->busname, xpd->xpdname, sig_toggles, sig_status);
+ spin_lock_irqsave(&xpd->lock, flags);
+ for_each_line(xpd, i) {
+ if(IS_SET(sig_status, i)) {
+ xpd->ringing[i] = 1;
+ } else {
+ xpd->ringing[i] = 0;
+ }
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ return 0;
+}
+
+HANDLER_DEF(FXO, DAA_REPLY)
+{
+ slic_reply_t *info = &RPACKET_FIELD(pack, FXO, DAA_REPLY, info);
+ xpp_line_t lines = RPACKET_FIELD(pack, FXO, DAA_REPLY, lines);
+ unsigned long flags;
+ struct FXO_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(!info->indirect && info->reg_num == DAA_VBAT_REGISTER) {
+ xpp_line_t last_batt_on = priv->battery;
+ xpp_line_t changed_lines;
+ int i;
+
+ if(abs(info->data_low) < BAT_THRESHOLD) {
+ priv->battery &= ~lines;
+ // DBG("BATTERY OFF (%04X) = %d\n", lines, info->data_low);
+ } else {
+ priv->battery |= lines;
+ // DBG("BATTERY ON (%04X) = %d\n", lines, info->data_low);
+ }
+ changed_lines = last_batt_on ^ priv->battery;
+ for_each_line(xpd, i) {
+ if(!IS_SET(changed_lines, i) || IS_SET(xpd->hookstate, i))
+ continue;
+#if 0
+ /* FIXME: We don't want to affect the whole span */
+ if(IS_SET(priv->battery, i))
+ update_xpd_status(xpd, ZT_ALARM_NONE);
+ else
+ update_xpd_status(xpd, ZT_ALARM_RED);
+#endif
+ }
+ }
+#if 0
+ DBG("DAA_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
+
+ /* 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(FXO) = {
+ .owner = THIS_MODULE,
+ .entries = {
+ /* Card Opcode */
+ XENTRY( FXO, SIG_CHANGED ),
+ XENTRY( FXO, DAA_REPLY ),
+ },
+ .name = "FXO",
+ .type = XPD_TYPE_FXO,
+ .xops = {
+ .card_new = FXO_card_new,
+ .card_init = FXO_card_init,
+ .card_remove = FXO_card_remove,
+ .card_zaptel_registration = FXO_card_zaptel_registration,
+ .card_sethook = FXO_card_sethook,
+ .card_tick = FXO_card_tick,
+
+ .RING = XPROTO_CALLER(FXO, RING),
+ .SETHOOK = XPROTO_CALLER(FXO, SETHOOK),
+ .RELAY_OUT = XPROTO_CALLER(FXO, RELAY_OUT),
+ .CHAN_ENABLE = XPROTO_CALLER(FXO, CHAN_ENABLE),
+ .CHAN_CID = XPROTO_CALLER(FXO, CHAN_CID),
+
+ .SYNC_SOURCE = XPROTO_CALLER(GLOBAL, SYNC_SOURCE),
+ .PCM_WRITE = XPROTO_CALLER(GLOBAL, PCM_WRITE),
+ },
+ .packet_is_valid = fxo_packet_is_valid,
+ .packet_dump = fxo_packet_dump,
+};
+
+static bool fxo_packet_is_valid(xpacket_t *pack)
+{
+ const xproto_entry_t *xe;
+
+ //DBG("\n");
+ xe = xproto_card_entry(&PROTO_TABLE(FXO), pack->content.opcode);
+ return xe != NULL;
+}
+
+static void fxo_packet_dump(xpacket_t *pack)
+{
+ DBG("\n");
+}
+
+/*------------------------- DAA Handling --------------------------*/
+
+static int proc_fxo_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 FXO_priv_data *priv;
+ int i;
+
+ 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\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_GREEN], 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_GREEN));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "battery");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", IS_SET(priv->battery, i));
+ }
+ 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 FXO_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, "DAA_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
+ * |
+ * DAA #
+ */
+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, &reg_type, &reg_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 FXO_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_DAA", &sc);
+ XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id);
+ RPACKET_FIELD(pack, FXO, DAA_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_fxo_startup(void)
+{
+ INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION);
+ xproto_register(&PROTO_TABLE(FXO));
+ return 0;
+}
+
+void __exit card_fxo_cleanup(void)
+{
+ xproto_unregister(&PROTO_TABLE(FXO));
+}
+
+MODULE_DESCRIPTION("XPP FXO Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(ZAPTEL_VERSION);
+MODULE_ALIAS_XPD(XPD_TYPE_FXO);
+
+module_init(card_fxo_startup);
+module_exit(card_fxo_cleanup);