diff options
author | Noe Rubinstein <nrubinstein@proformatique.com> | 2010-12-17 14:42:39 +0100 |
---|---|---|
committer | Noe Rubinstein <nrubinstein@proformatique.com> | 2010-12-17 14:42:39 +0100 |
commit | cb620d8ecc82128738d8f2781dd57d4c9873c80e (patch) | |
tree | e7ab6d6f621dd4290079bad9721ece4de6a102b2 /xhfc/base.c | |
parent | 433efd139c08c4a5b7ca726c5313c17909b4ac3c (diff) |
move xhfc.c to base.c and rename module to xivoxhfc.c
Diffstat (limited to 'xhfc/base.c')
-rw-r--r-- | xhfc/base.c | 1202 |
1 files changed, 1202 insertions, 0 deletions
diff --git a/xhfc/base.c b/xhfc/base.c new file mode 100644 index 0000000..c2c9a02 --- /dev/null +++ b/xhfc/base.c @@ -0,0 +1,1202 @@ +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> + +#ifdef USE_GPIO +#include <gpio/gpio.h> +#endif + +#include "xhfc24sucd.h" +#include "xhfc.h" +#include "xhfc_leb.h" + +static const char xhfc_rev[] = "$Revision: 1.22 $"; +static int card_cnt = 0; + +MODULE_LICENSE("GPL"); + +uint debug = 0; +uint dbg_spanfilter = 0xFFFFFFFF; +int exit_after_reset = 0; + +uint ntte = 0x3; + +#ifdef USE_GPIO +static uint reset_gpio = 21; +#endif + +int softconf = 0; + +module_param(debug, uint, S_IRUGO | S_IWUSR); +module_param(dbg_spanfilter, uint, S_IRUGO | S_IWUSR); +module_param(softconf, bool, S_IRUGO); +module_param(ntte, uint, S_IRUGO); +module_param(exit_after_reset, bool, S_IRUGO); +#ifdef USE_GPIO +module_param(reset_gpio, uint, S_IRUGO); +#endif + +MODULE_PARM_DESC(debug, "Debug bitfield:\n" + "\t0: general\n" + //"\t2: verbose register access, only if DEBUG is defined\n" + "\t3: file operations\n" + "\t4: (not used, echo canceller)\n" + "\t5: ST state\n" + "\t6: HDLC\n" + "\t7: HDLC (verbose)\n" + "\t8: timing\n" + "\t9: alarms\n"); +MODULE_PARM_DESC(dbg_spanfilter, "bitfield, filter debug info by span."); +MODULE_PARM_DESC(ntte, "bitfield, configuration of the physical ports. ex: 0x3 = 0 NT, 1 NT, 2 TE, 3 TE."); +MODULE_PARM_DESC(softconf, "Configure the S/T port line interface in software."); +#ifdef USE_GPIO +MODULE_PARM_DESC(reset_gpio, "Reset the XHFC using this GPIO"); +#endif +MODULE_PARM_DESC(exit_after_reset, "Exit after hard reset"); + +#ifdef AUDIO +#define RX_HISTO_NB 16 +static int rx_histo[RX_HISTO_NB]; +#endif + +void xhfc_waitbusy(struct xhfc *xhfc) +{ + while (read_xhfc(xhfc, R_STATUS) & M_BUSY) + cpu_relax(); +} + +void xhfc_selfifo(struct xhfc *xhfc, int fifo, int receive) +{ + u8 r_fifo = 0x00; + SET_V_FIFO_NUM(r_fifo, fifo); + SET_V_FIFO_DIR(r_fifo, receive); + write_xhfc(xhfc, R_FIFO, r_fifo); + xhfc_waitbusy(xhfc); +} + +void xhfc_inc_f(struct xhfc *xhfc) +{ + write_xhfc(xhfc, A_INC_RES_FIFO, M_INC_F); + xhfc_waitbusy(xhfc); +} + +static inline void xhfc_resetfifo(struct xhfc *xhfc) +{ + write_xhfc(xhfc, A_INC_RES_FIFO, M_RES_FIFO | M_RES_FIFO_ERR); + xhfc_waitbusy(xhfc); +} + +int xhfc_reset(struct xhfc *xhfc) +{ + int timeout = 0x2000; + + /* software reset to enable R_FIFO_MD setting */ + write_xhfc(xhfc, R_CIRM, M_SRES); + udelay(5); + write_xhfc(xhfc, R_CIRM, 0); + + while ((read_xhfc(xhfc, R_STATUS) & (M_BUSY | M_PCM_INIT)) + && (timeout)) { + timeout--; + cpu_relax(); + } + + if (!(timeout)) { + printk(KERN_ERR + "%s %s: initialization sequence could not finish\n", + xhfc->pi->name, __func__); + return -EIO; + } + + return 0; +} + +void xhfc_config_st(struct xhfc *x, int port, int nt) +{ + u8 r_su_sel, a_su_ctrl0, a_su_ctrl1, a_su_ctrl2, a_su_wr_sta, a_su_clk_dly; + r_su_sel = a_su_ctrl0 = a_su_ctrl1 = a_su_ctrl2 = a_su_wr_sta = a_su_clk_dly = 0x00; + SET_V_SU_SEL(r_su_sel, port); + SET_V_SU_MD(a_su_ctrl0, !!nt); + SET_V_B1_TX_EN(a_su_ctrl0, 1); + SET_V_B2_TX_EN(a_su_ctrl0, 1); + SET_V_ST_E_IGNO(a_su_ctrl1, nt ? 1 : 0); + SET_V_G2_G3_EN(a_su_ctrl1, 0); /* Disallowed in order to be able to + * guarantee that T1 is handled + * correctly (xref6) */ + SET_V_B1_RX_EN(a_su_ctrl2, 1); + SET_V_B2_RX_EN(a_su_ctrl2, 1); + SET_V_SU_ACT(a_su_wr_sta, 3); /* activation */ + SET_V_SU_CLK_DLY(a_su_clk_dly, nt ? 0xC : 0xE); /* normal operation + * WARNING: if the + * interface is + * softconfigured, this + * should always be + * 0xC XXX */ + SET_V_ST_SMPL(a_su_clk_dly, nt ? 0x6 : 0x0); /* default value */ + + write_xhfc(x, R_SU_SEL, r_su_sel); + xhfc_waitbusy(x); + write_xhfc(x, A_SU_CTRL0, a_su_ctrl0); + write_xhfc(x, A_SU_CTRL1, a_su_ctrl1); + write_xhfc(x, A_SU_CTRL2, a_su_ctrl2); + write_xhfc(x, A_SU_CLK_DLY, a_su_clk_dly); + write_xhfc(x, A_SU_WR_STA, a_su_wr_sta); + xhfc_waitbusy(x); +} + +/* 2 MBit/s (C4IO is 4.096 MHz, 32 time slots): */ +#define V_PCM_DR_2M 0x00 + +/* 4 MBit/s (C4IO is 8.192 MHz, 64 time slots): */ +#define V_PCM_DR_4M 0x10 + +/* 8 MBit/s (C4IO is 16.384 MHz, 128 time slots): */ +#define V_PCM_DR_8M 0x20 + +/* configures PCM master mode +*/ +int xhfc_config_pcm(struct xhfc *xhfc, int master_or_slave) +{ + u8 r_pcm_md0 = 0; + + SET_V_F0_LEN(r_pcm_md0, 1); + + write_xhfc(xhfc, R_PCM_MD0, SET_V_PCM_IDX(r_pcm_md0, 0x9)); + /* use slow PCM clock adjust speed */ + write_xhfc(xhfc, R_PCM_MD1, M_PLL_ADJ | V_PCM_DR_2M); + /* NOTE: open drain on the PCM: add bit M_PCM_OD in R_PCM_MD1 + * We can't do that with the demo interface card for + * Megrez <-> XHFC EVB because Xavier did not managed to get + * SMD 1k R before soldering :P + */ + + if (master_or_slave == XHFC_PCM_MASTER) { + write_xhfc(xhfc, R_PCM_MD0, + SET_V_PCM_IDX(r_pcm_md0, 0xA)); + /* enable PCM bit clk for C2O pin */ + write_xhfc(xhfc, R_PCM_MD2, M_C2O_EN); + } else { + write_xhfc(xhfc, R_PCM_MD0, + SET_V_PCM_IDX(r_pcm_md0, 0xA)); + write_xhfc(xhfc, R_PCM_MD2, M_C2I_EN); + } + + /* this could maybe be done sooner, but right now I'm limiting + * the number of changes */ + write_xhfc(xhfc, R_PCM_MD0, SET_V_PCM_IDX(r_pcm_md0, 0x0) + | M_PCM_MD | M_C4_POL); + + return 0; +} + +#define debug_fz(fifo, prefix, buf) \ +do { \ +sprintf(buf, "%s(fifo %d): flen=%d, " \ + "zlen=%d", prefix, fifo, flen, \ + zlen); \ +} while (0) + +#define DEFINE_GET_LEN(F_or_Z) \ +static inline int \ +get_ ## F_or_Z ## len(struct xhfc* x) { \ +u8 _1, _2; \ +int len; \ + \ +_1 = read_xhfc(x, A_ ## F_or_Z ## 1); \ +_2 = read_xhfc(x, A_ ## F_or_Z ## 2); \ +len = _1 - _2; \ + \ +if (len < 0) \ + len += \ + (XHFC_ ## F_or_Z ## MAX - \ + XHFC_ ## F_or_Z ## MIN) + 1; \ + \ +return len; \ +} + +DEFINE_GET_LEN(F) +DEFINE_GET_LEN(Z) + +void xhfc_config_d_chan_on_fifo(struct xhfc *x, int fifo, int direction) +{ + u8 r_fifo, a_con_hdlc, a_channel, a_subch_cfg, a_inc_res_fifo; + r_fifo = a_con_hdlc = a_channel = a_subch_cfg = a_inc_res_fifo = 0x00; + + SET_V_FIFO_NUM(r_fifo, fifo); + + SET_V_IFF(a_con_hdlc, 1); /* inter frame fill with 1s */ + SET_V_HDLC_TRP(a_con_hdlc, 0); /* HDLC mode */ + SET_V_FIFO_IRQ(a_con_hdlc, 7); /* FIFO & IRQ enabled */ + SET_V_CH_FNUM(a_channel, fifo); /* chan number */ + SET_V_BIT_CNT(a_subch_cfg, 2); /* only two bits read or written */ + SET_V_RES_FIFO(a_inc_res_fifo, 1); /* reset FIFO */ + SET_V_RES_LOST(a_inc_res_fifo, 1); /* reset FIFO */ + SET_V_RES_FIFO_ERR(a_inc_res_fifo, 1); /* reset FIFO */ + + SET_V_DATA_FLOW(a_con_hdlc, 0); /* FIFO <-> ST/Up */ + + SET_V_FIFO_DIR(r_fifo, direction); + SET_V_CH_FDIR(a_channel, direction); + + write_xhfc(x, R_FIFO, r_fifo); + xhfc_waitbusy(x); + write_xhfc(x, A_CON_HDLC, a_con_hdlc); + write_xhfc(x, A_CHANNEL, a_channel); + write_xhfc(x, A_SUBCH_CFG, a_subch_cfg); + write_xhfc(x, A_INC_RES_FIFO, a_inc_res_fifo); + xhfc_waitbusy(x); +} + +void xhfc_config_b_chan_on_fifo(struct xhfc *x, int fifo, int slot, int direction) +{ + u8 r_fifo, a_con_hdlc, r_slot, a_sl_cfg; + r_fifo = a_con_hdlc = r_slot = a_sl_cfg = 0x00; + + SET_V_FIFO_DIR(r_fifo, direction); + SET_V_FIFO_NUM(r_fifo, fifo); + SET_V_REV(r_fifo, 1); + + SET_V_IFF(a_con_hdlc, 0); /* Ox7E as interframe fill -- not really used */ + SET_V_HDLC_TRP(a_con_hdlc, 1); /* transparent mode */ + SET_V_FIFO_IRQ(a_con_hdlc, 7); /* enable data transmission */ + SET_V_DATA_FLOW(a_con_hdlc, 6); /* '110': ST <-> PCM */ + + SET_V_SL_DIR(r_slot, direction); + SET_V_SL_NUM(r_slot, slot); + + SET_V_CH_SDIR(a_sl_cfg, direction); + SET_V_CH_SNUM(a_sl_cfg, fifo); + SET_V_ROUT(a_sl_cfg, 3); /* '11': receive data from STIO1, output to STIO2 */ + + write_xhfc(x, R_FIFO, r_fifo); + xhfc_waitbusy(x); + write_xhfc(x, A_CON_HDLC, a_con_hdlc); + write_xhfc(x, R_SLOT, r_slot); + write_xhfc(x, A_SL_CFG, a_sl_cfg); +} + +void xhfc_config_data_flow(struct xhfc* x) +{ + int i; + for (i = 0; i < SPANS_PER_CHIP; i++) { + /* HFC chan, PCM slot, direction */ + xhfc_config_b_chan_on_fifo(x, i * 4 + 0, i * 2 + 0, TRANSMIT); + xhfc_config_b_chan_on_fifo(x, i * 4 + 0, i * 2 + 0, RECEIVE); + xhfc_config_b_chan_on_fifo(x, i * 4 + 1, i * 2 + 1, TRANSMIT); + xhfc_config_b_chan_on_fifo(x, i * 4 + 1, i * 2 + 1, RECEIVE); + + xhfc_config_d_chan_on_fifo(x, i * 4 + 2, TRANSMIT); + xhfc_config_d_chan_on_fifo(x, i * 4 + 2, RECEIVE); + } +} + +/** +* hdlc_signal_complete() - Signal dahdi that we have a complete frame. +* +* @xhfc_span: The span which received the frame. +* @stat: The frame status byte from the XHFC controller. +* +*/ +static void hdlc_signal_complete(struct xhfc_span *xhfc_span, u8 stat) +{ + if (stat != 0x00) { + if (DBG_HDLC && DBG_SPAN(xhfc_span)) { + printk(KERN_INFO DRIVER_NAME "(port %d) STAT=0x%02x indicates " + "frame problem: %s\n", portno(xhfc_span), + stat, + (0xff == stat) ? "HDLC Abort" : "Bad FCS"); + } + + dahdi_hdlc_abort(xhfc_span->sigchan, (0xff == stat) ? + DAHDI_EVENT_ABORT : DAHDI_EVENT_BADFCS); + } else { + if (DBG_HDLC && DBG_SPAN(xhfc_span)) { + printk(KERN_INFO DRIVER_NAME + "(port %d) Frame " /*"%d" */ "is good!\n", + portno(xhfc_span) + /*, xhfc_span->frames_in */ ); + } + dahdi_hdlc_finish(xhfc_span->sigchan); + } +} + +/* + * Inner loop for D-channel receive function. Retrieves HDLC data from the + * hardware. If the hardware indicates that the frame is complete, we check + * the HDLC engine's STAT byte and update DAHDI as needed. + * + * Returns the number of HDLC frames left in the FIFO. */ +static int hdlc_rx_frame(struct xhfc_span *xhfc_span) +{ + char debugbuf[MAX(256, HDLC_BUF_LEN * 3 + 1)]; + int dbglen; + + int fifo, i, j, k, zleft; + int zlen, flen; + unsigned char buf[HDLC_BUF_LEN]; + struct xhfc *x = xhfc_span->xhfc; + + fifo = dchan_fifo(xhfc_span); + + if (DBG_VERBOSE_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME ": %s(fifo %d): start\n", + __func__, fifo); + + xhfc_selfifo(x, fifo, RECEIVE); + + flen = get_Flen(x); + zlen = get_Zlen(x); + + debug_fz(fifo, __func__, debugbuf); + + if (!flen && !zlen) { + if (DBG_VERBOSE_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME + ": %s, nothing to receive (%ld)\n", + debugbuf, xhfc_span->non_rx_cnt); + xhfc_span->non_rx_cnt++; + return 0; + } + xhfc_span->non_rx_cnt = 0; + + if (DBG_VERBOSE_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME ": %s\n", debugbuf); + + /* if we have at least one complete frame, increment zleft to include + * status byte */ + zleft = zlen; + if (flen) + zleft++; + + do { + j = zleft > HDLC_BUF_LEN ? HDLC_BUF_LEN : zleft; + + for (i = 0; i < j; i++) + buf[i] = read_xhfc(x, A_FIFO_DATA); + + /* don't send STAT byte to DAHDI */ + k = j; + if (xhfc_span->sigchan) { + if ((j != HDLC_BUF_LEN) && flen) + k--; + if (k) + dahdi_hdlc_putbuf(xhfc_span->sigchan, buf, k); + } + + zleft -= j; + + if (DBG_VERBOSE_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME + ": transmitted %d bytes to dahdi, " + "zleft=%d\n", k, zleft); + + if (DBG_HDLC && DBG_SPAN(xhfc_span)) { + dbglen = 0; + for (i = 0; i < j; i++) + dbglen += sprintf(debugbuf+dbglen, "%02x ", buf[i]); + debugbuf[dbglen] = '\0'; + + printk(KERN_INFO DRIVER_NAME ": %s(fifo %d): " + "zlen=%d, zleft=%d: %s\n", __func__, + fifo, zlen, zleft, debugbuf); + } + } while (zleft > 0); + + /* Frame received, increment F2 */ + if (flen) + xhfc_inc_f(x); + + /* If this channel is not configured with a signalling span we don't + * need to notify the rest of dahdi about this frame. */ + if (!xhfc_span->sigchan) { + if (DBG_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME + ": %s(fifo %d): flen %d, early end\n", + __func__, fifo, flen); + return flen; + } + + if (flen) + hdlc_signal_complete(xhfc_span, buf[i - 1]); + + if (DBG_VERBOSE_HDLC && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME ": %s(fifo %d): flen=%d end\n", + __func__, fifo, flen); + + return flen; +} + +static uint frames_out; +/* +* Takes one blob of data from DAHDI and shoots it out to the hardware. The +* blob may or may not be a complete HDLC frame. If it isn't, the rest will be +* pulled during the next timer interrupt. +* Returns nonzero if there is still data to send in the current HDLC frame. +*/ +static int hdlc_tx_frame(struct xhfc_span *xhfc_span) +{ + struct xhfc *x = xhfc_span->xhfc; + struct dahdi_span *dahdi_span = &xhfc_span->span; + int res, i, fifo; + int zlen; + int flen = -1; + unsigned char buf[HDLC_BUF_LEN]; + unsigned int size = ARRAY_SIZE(buf); + char debugbuf[MAX(256, HDLC_BUF_LEN * 3 + 1)]; + int dbglen; + + /* ignoring TE red alarms: if TE and we are in alarm, restart the + * S/T state machine... */ + if (!xhfc_span->nt && (xhfc_span->span.alarms != 0)) { + activate_request(xhfc_span); + } + + fifo = dchan_fifo(xhfc_span); + res = dahdi_hdlc_getbuf(xhfc_span->sigchan, buf, &size); + + xhfc_selfifo(x, fifo, TRANSMIT); + + zlen = get_Zlen(x); + debug_fz(fifo, __func__, debugbuf); + + /* TODO: check zlen, etc. */ + if ((XHFC_ZMAX - zlen) < size) { + static int arg; + printk(KERN_INFO DRIVER_NAME ": arg (%d), zlen (%d) < what we " + "grabbed from DAHDI (%d)!\n", ++arg, zlen, size); + size = zlen; + dahdi_hdlc_abort(xhfc_span->sigchan, DAHDI_EVENT_OVERRUN); + } + + if (size > 0) { + + for (i = 0; i < size; i++) + write_xhfc(x, A_FIFO_DATA, buf[i]); + /* + * If we got a full frame from DAHDI, increment F and + * decrement our HDLC pending counter. */ + if (res != 0) { + frames_out++; + xhfc_inc_f(x); + atomic_dec(&xhfc_span->hdlc_pending); + } + } + + if (DBG_HDLC && DBG_SPAN(xhfc_span)) { + printk(KERN_INFO DRIVER_NAME ": %s\n", debugbuf); + + dbglen = 0; + for (i = 0; i < size; i++) + dbglen += sprintf(debugbuf+dbglen, "%02x ", buf[i]); + debugbuf[dbglen] = '\0'; + + printk(KERN_INFO DRIVER_NAME + ": hdlc_tx_frame(span %d): DAHDI gave %d " + "bytes for FIFO %d (res = %d): %s\n", + dahdi_span->spanno, + size, fifo, res, debugbuf); + + if (size && res != 0) + printk(KERN_INFO DRIVER_NAME + ": Transmitted frame %d on span %d\n", + frames_out - 1, dahdi_span->spanno); + } + + return !res; +} + +/* DAHDI calls this when it has data it wants to send to the HDLC controller */ +void xhfc_hdlc_hard_xmit(struct dahdi_chan *chan) +{ + struct dahdi_span *dahdi_span = chan->span; + struct xhfc_span *xhfc_span = + container_of(dahdi_span, struct xhfc_span, span); + + if ((DBG_FOPS || DBG_HDLC) && DBG_SPAN(xhfc_span)) + printk(KERN_INFO DRIVER_NAME ": %s on chan %s (%i/%i), " + "span=%i (sigchan=%p, chan=%p): %d+1 frames\n", + __func__, chan->name, chan->channo, + chan->chanpos, dahdi_span->spanno, + xhfc_span->sigchan, chan, + atomic_read(&xhfc_span->hdlc_pending)); + + /* Increment the hdlc_pending counter and trigger hdlc_tx_frame */ + if (xhfc_span->sigchan == chan) + atomic_inc(&xhfc_span->hdlc_pending); +} + +/***************************************************************************** + * Interrupt handler, does all sort of stuff, some of which should prolly be * + * scheduled in a tasklet instead * + *****************************************************************************/ + +#define fifo_test(reg, fifonum, dir) (reg[fifonum/4] & (1 << (((fifonum % 4) * 2) + dir))) + +irqreturn_t xhfc_interrupt(int irq, void *dev_id, struct pt_regs* ptregs) +{ + + struct xhfc_pi *pi = dev_id; + struct xhfc *xhfc; + + int i; + u8 r_su_irq; + u8 misc_irq; + u8 fifo_irq[SPANS_PER_CHIP]; + u8 fifo_fill[SPANS_PER_CHIP]; + + (void) ptregs; + + xhfc = &pi->xhfc; + + /* IRQs for other HW with shared IRQ or spurious + * XXX for XiVO OpenHardware we should not share IRQ! */ + if (!(read_xhfc(xhfc, R_IRQ_OVIEW))) + return IRQ_NONE; + + /* reset IRQ source */ + misc_irq = read_xhfc(xhfc, R_MISC_IRQ); + + if (xhfc->running) { + /* check for Timer IRQ */ + if (misc_irq & M_TI_IRQMSK) + xhfc->ticks++; + + for(i = 0; i < SPANS_PER_CHIP; i++) { + fifo_irq[i] = read_xhfc(xhfc, R_FIFO_BL0_IRQ + i); + fifo_fill[i] = read_xhfc(xhfc, R_FILL_BL0 + i); + } + + for (i = 0; i < SPANS_PER_CHIP; i++) { + struct xhfc_span* s = &xhfc->spans[i]; + + if ((s->span.flags & DAHDI_FLAG_RUNNING) && s->sigchan && + s->span.alarms == DAHDI_ALARM_NONE) { + + /* No need to loop, makes no sense to receive + * more than one HDLC frame per second. + */ + if ( fifo_test(fifo_irq, dchan_fifo(s), RECEIVE) || + fifo_test(fifo_fill, dchan_fifo(s), RECEIVE)) + + hdlc_rx_frame(s); + + if (atomic_read(&s->hdlc_pending)) + hdlc_tx_frame(s); +#ifdef AUDIO + /* Put in readchunk chunk _previously_ retrieved + * from icp soft. + * We can't update it at icp level right now, + * because it has been written by trippy monkeys + * and it would probably be a very bad idea to + * try to call it from hardirq context. + * (look at ixOsalMutexLock ... :/ ) + */ + xivo_tdm_receive(pi->tdm_port, i*2 + 0, + s->chans[0]->readchunk); + xivo_tdm_receive(pi->tdm_port, i*2 + 1, + s->chans[1]->readchunk); + + dahdi_receive(&s->span); + dahdi_transmit(&s->span); + dahdi_ec_span(&s->span); + + xivo_tdm_transmit(pi->tdm_port, i*2 + 0, + s->chans[0]->writechunk); + xivo_tdm_transmit(pi->tdm_port, i*2 + 1, + s->chans[1]->writechunk); +#endif + } + } + +#ifdef AUDIO + xivo_tdm_tick(pi->tdm_port); +#endif + + r_su_irq = read_xhfc(xhfc, R_SU_IRQ); + + for (i = 0; i < SPANS_PER_CHIP; i++) { + handle_st_timers(&xhfc->spans[i]); + if (r_su_irq & (1 << i) || 2 == xhfc->ticks /* bootstrap XXX WTF*/) + handle_state_change(&xhfc->spans[i]); + } + } + + return IRQ_HANDLED; +} + + +/*****************************************************/ +/* disable all interrupts by disabling M_GLOB_IRQ_EN */ +/*****************************************************/ +static void disable_interrupts(struct xhfc * xhfc) +{ + printk(KERN_INFO "%s %s\n", xhfc->name, __func__); + + write_xhfc(xhfc, R_IRQ_CTRL, 0); + read_xhfc(xhfc, R_CHIP_ID); + mb(); + + set_mb(xhfc->running, 0); +} + + +/******************************************/ +/* start interrupt and set interrupt mask */ +/******************************************/ +static void enable_interrupts(struct xhfc * xhfc) +{ + u8 r_irq_ctrl = 0; + printk(KERN_INFO "%s %s\n", xhfc->name, __func__); + + set_mb(xhfc->running, 1); + + /* timer interrupt every 1 ms */ + write_xhfc(xhfc, R_TI_WD, 0x02); + write_xhfc(xhfc, R_MISC_IRQMSK, M_TI_IRQMSK); + + /* clear all pending interrupts bits */ + read_xhfc(xhfc, R_MISC_IRQ); + read_xhfc(xhfc, R_SU_IRQ); + read_xhfc(xhfc, R_FIFO_BL0_IRQ); + read_xhfc(xhfc, R_FIFO_BL1_IRQ); + read_xhfc(xhfc, R_FIFO_BL2_IRQ); + read_xhfc(xhfc, R_FIFO_BL3_IRQ); + + /* enable global interrupts */ + SET_V_GLOB_IRQ_EN(r_irq_ctrl, 1); + SET_V_FIFO_IRQ_EN(r_irq_ctrl, 1); + write_xhfc(xhfc, R_IRQ_CTRL, r_irq_ctrl); +} + + +/***********************************/ +/* initialise the XHFC ISDN Chip */ +/* return 0 on success. */ +/***********************************/ +int xhfc_collect_chip_id(struct xhfc * xhfc) +{ + u8 chip_id = read_xhfc(xhfc, R_CHIP_ID); + switch (chip_id) { + case CHIP_ID_1SU: + case CHIP_ID_2SU: + case CHIP_ID_2S4U: + case CHIP_ID_4SU: + printk(KERN_INFO "%s ChipID: 0x%x\n", + xhfc->name, chip_id); + return 0; + } + + printk(KERN_ERR "%s %s: unkown Chip ID 0x%x\n", + xhfc->name, __func__, chip_id); + return -EIO; +} + +void xhfc_hard_reset(void) +{ +#ifdef USE_GPIO + gpio_set_direction(reset_gpio, GPIO_OUTPUT); + gpio_set_to_gpio(reset_gpio); + gpio_set_level(reset_gpio, GPIO_LOW); + msleep(1); + gpio_set_level(reset_gpio, GPIO_HIGH); +#endif +} + +#if 0 +void configure_port(int port, int nt, int lineterm) +{ + gpio_set_direction(lineterm_gpio, GPIO_OUTPUT); + gpio_set_to_gpio(lineterm_gpio); + gpio_set_direction(ntte_gpio, GPIO_OUTPUT); + gpio_set_to_gpio(ntte_gpio); + gpio_set_level(lineterm_gpio, lineterm); + gpio_set_level(ntte_gpio, nt); +} +#endif + +void xhfc_init_and_configure(struct xhfc* x) +{ + u8 r_fifo_thres = 0; + /* In DAHDI's driver's this part is "stage 1" */ + xhfc_reset(x); + + /* Make sure interrupts disabled */ + disable_interrupts(x); + + /* "stage 2" */ + xhfc_config_pcm(x, XHFC_PCM_MASTER); + + /* Now we need to setup the flow controller and stuff */ + xhfc_config_data_flow(x); + + SET_V_THRES_RX(r_fifo_thres, 2); + SET_V_THRES_TX(r_fifo_thres, 2); + write_xhfc(x, R_FIFO_THRES, r_fifo_thres); +} + +static void xhfc_span_set_ntte(struct xhfc_span* s, int ntte) +{ + s->nt = !!ntte; + s->span.spantype = ntte ? "NT" : "TE"; +} + +int xhfc_spanconfig(struct dahdi_span *span, struct dahdi_lineconfig *lc) +{ + struct xhfc_span *xhfc_span; + struct xhfc *xhfc; + int term; + + xhfc_span = container_of(span, struct xhfc_span, span); + xhfc = xhfc_span->xhfc; + + xhfc_span_set_ntte(xhfc_span, lc->lineconfig & DAHDI_CONFIG_NTTE); /* xref2 */ + + term = (lc->lineconfig & DAHDI_CONFIG_TERM) ? 1 : 0; + + printk(KERN_INFO DRIVER_NAME ": Configuring port %d span %d in %s mode" + " with termination resistance %s\n", + portno(xhfc_span), span->spanno, + xhfc_span->nt ? "NT" : "TE", + term ? "ENABLED" : "DISABLED"); + + /* TODO conf hardware interface w/ GPIO */ + xhfc_config_st(xhfc, xhfc_span->port, xhfc_span->nt); + + printk(KERN_INFO DRIVER_NAME ": WARNING: This driver can't configure " + "the ports yet, expect problems if port %d isn't " + "configured in %s mode\n", + portno(xhfc_span), xhfc_span->nt ? "NT" : "TE"); + + if (lc->sync < 0) { + printk(KERN_INFO DRIVER_NAME + ": Span %d has invalid sync priority (%d), " + "removing from sync source list\n", + span->spanno, lc->sync); + lc->sync = 0; + } + + if (xhfc_span->nt && lc->sync) { + printk(KERN_INFO DRIVER_NAME + ": NT Spans cannot be timing sources. " + "Span %d requested to be timing source of " + "priority %d. Changing priority to 0\n", + span->offset, lc->sync); + lc->sync = 0; + } + + start_state_machine(xhfc_span); + + return 0; +} + +int xhfc_chanconfig(struct dahdi_chan *chan, int sigtype) +{ + int alreadyrunning; + struct xhfc_span *xhfc_span = container_of(chan->span, struct xhfc_span, span); + int res; + + alreadyrunning = xhfc_span->span.flags & DAHDI_FLAG_RUNNING; + + if (DBG_FOPS) + printk(KERN_INFO DRIVER_NAME + ": %s channel %d (%s) sigtype %08x\n", + alreadyrunning ? "Reconfigured" : "Configured", + chan->channo, chan->name, sigtype); + + switch (sigtype) { + case DAHDI_SIG_HARDHDLC: + /* xref1 */ + if (DBG_FOPS) + printk(KERN_INFO DRIVER_NAME + ": Configuring hardware HDLC on %s\n", + chan->name); + xhfc_span->sigchan = chan; + atomic_set(&xhfc_span->hdlc_pending, 0); + res = 0; + break; + case DAHDI_SIG_HDLCFCS: + printk(KERN_INFO DRIVER_NAME ": HDLCFCS not supported\n"); + res = -ENOSYS; + break; + case DAHDI_SIG_HDLCNET: + printk(KERN_INFO DRIVER_NAME ": HDLCNET not supported\n"); + res = -ENOSYS; + break; + case DAHDI_SIG_HDLCRAW: + printk(KERN_INFO DRIVER_NAME ": HDLCRAW not supported\n"); + res = -ENOSYS; + break; + default: + res = 0; + break; + }; + + return res; +} + +static int xhfc_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data) +{ + return -ENOTTY; +} + +static int xhfc_span_startup(struct dahdi_span* s) +{ + struct xhfc_span *xhfc_span = container_of(s, struct xhfc_span, span); + activate_request(xhfc_span); + return 0; +} + +static int xhfc_span_shutdown(struct dahdi_span* s) +{ + struct xhfc_span *xhfc_span = container_of(s, struct xhfc_span, span); + deactivate_request(xhfc_span); + return 0; +} + +static const struct dahdi_span_ops xhfc_span_ops = { + .owner = THIS_MODULE, + .spanconfig = xhfc_spanconfig, + .chanconfig = xhfc_chanconfig, + .startup = xhfc_span_startup, + .shutdown = xhfc_span_shutdown, + .ioctl = xhfc_ioctl, + .hdlc_hard_xmit = xhfc_hdlc_hard_xmit, +}; + +/************************************************************************ + * Responsible for initializing the xhfc_span and dahdi_span structures * + ************************************************************************/ +void init_spans(struct xhfc* x) +{ + int i, j; + struct xhfc_span *xhfc_span; + struct dahdi_span *dahdi_span; + struct dahdi_chan *chan; + +/* for each span on the card */ + for (i = 0; i < SPANS_PER_CHIP; i++) { + + xhfc_span = &x->spans[i]; + dahdi_span = &xhfc_span->span; + + xhfc_span->xhfc = x; + xhfc_span->port = i; + + xhfc_span->sigchan = NULL; /* conf'd in chanconfig (xref1) */ + xhfc_span_set_ntte(xhfc_span, ntte & (1 << i)); /* reconf'd in spanconfig (xref2) */ + + /* All the timer stuff is done in the timer and state + * functions, not here. */ + + dahdi_span->ops = &xhfc_span_ops; + dahdi_span->chans = xhfc_span->chans; + dahdi_span->irq = x->pi->pci_dev->irq; + dahdi_span->offset = i; + dahdi_span->channels = CHANS_PER_SPAN; + dahdi_span->flags = 0; + dahdi_span->deflaw = DAHDI_LAW_ALAW; + dahdi_span->linecompat = + DAHDI_CONFIG_TERM | DAHDI_CONFIG_NTTE | + DAHDI_CONFIG_AMI | DAHDI_CONFIG_CCS ; + + sprintf(dahdi_span->name, "XHFC/%d", i+1); + sprintf(dahdi_span->desc, "XHFC port %d", i+1); + dahdi_span->manufacturer = "Avencall"; + dahdi_copy_string(dahdi_span->devicetype, "XHFC-4SU", + sizeof(dahdi_span->devicetype)); + sprintf(dahdi_span->location, "PCI Bus %02d Slot %02d", + x->pi->pci_dev->bus->number, + PCI_SLOT(x->pi->pci_dev->devfn) + 1); + + dahdi_span->alarms = DAHDI_ALARM_RED; + + + init_waitqueue_head(&dahdi_span->maintq); /* kinda everyone does this */ + + /* now initialize each channel in the span */ + for (j=0; j < CHANS_PER_SPAN; j++) { + xhfc_span->chans[j] = &xhfc_span->_chans[j]; + chan = xhfc_span->chans[j]; + chan->pvt = x; + + sprintf(chan->name, "XHFC/%d/%d", i + 1, j + 1); + + if (j == 2) { + chan->sigcap = DAHDI_SIG_HARDHDLC; + } else { + chan->sigcap = DAHDI_SIG_CLEAR | DAHDI_SIG_DACS; + } + chan->chanpos = j + 1; + chan->writechunk = (void *)(xhfc_span->writechunk + j * DAHDI_CHUNKSIZE); + chan->readchunk = (void *)(xhfc_span->readchunk + j * DAHDI_CHUNKSIZE); + } + } +} + +void release_card_irq(struct xhfc_pi * pi) +{ + disable_interrupts(&pi->xhfc); + free_irq(pi->irq, pi); +} + +static const struct pci_device_id tlp_leb_pci_tbl[] = { + { 0x8086, 0x503d, PCI_ANY_ID, PCI_ANY_ID, }, /* Intel Tolapai LEB controler */ + { } +}; +MODULE_DEVICE_TABLE(pci, tlp_leb_pci_tbl); + +static struct pci_driver xhfc_driver = { + .name = DRIVER_NAME, + .id_table = tlp_leb_pci_tbl, + .probe = xhfc_init_one, + .remove = __devexit_p(xhfc_remove_one), +// .shutdown = xhfc_shutdown, +}; + +/* pci.txt: called from process context */ +int __devinit xhfc_init_one(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int span; + struct xhfc_pi * pi; + unsigned long base, size; + int rc = -ENOMEM; + + printk(KERN_DEBUG "entering xhfc_init_one\n"); + + + /**************** + * Memory alloc * + ****************/ + + pi = kzalloc(sizeof(struct xhfc_pi), GFP_KERNEL); + if (pi == NULL) + goto err_alloc; + + sprintf(pi->name, "%s", DRIVER_NAME); /* ? */ + + printk(KERN_INFO "%s %s: adapter '%s' found on PCI bus %02x dev %02x\n", + pi->name, __func__, pi->name, + pdev->bus->number, pdev->devfn); + + + /************ + * PCI init * + ************/ + + rc = pci_enable_device(pdev); + + if (rc) + goto err_enable_device; + + printk(KERN_DEBUG "pci_enable_device succeeded\n"); + + rc = pci_request_regions(pdev, DRIVER_NAME); + if (rc) + goto err_request_regions; + + rc = -ENOMEM; + + base = pci_resource_start(pdev, LEB_CSRBAR); + size = pci_resource_len(pdev, LEB_CSRBAR); + pi->regs = ioremap(base, size); + if (!pi->regs) + goto err_ioremap_csrbar; + + base = pci_resource_start(pdev, LEB_MMBAR); + size = pci_resource_len(pdev, LEB_MMBAR); + pi->cs_n0 = ioremap(base, size); + if (!pi->cs_n0) + goto err_ioremap_mmbar; + + pci_set_drvdata(pdev, pi); + + + /************* + * Hard init * + *************/ + + xhfc_hard_reset(); + + if (exit_after_reset) { + rc = -EINVAL; + goto exit_after_reset; + } + + leb_init(pi); + + +#ifdef AUDIO + /************* + * TDM alloc * + *************/ + + pi->tdm_port = xivo_tdm_get_port(XIVO_XHFC_TDM_PORT); + if (!pi->tdm_port) { + rc = -EBUSY; + goto err_tdm_get_port; + } +#endif + + /******************** + * XHFC struct init * + ********************/ + + pi->pci_dev = pdev; + pi->xhfc.pi = pi; + + /* only adress one xhfc per PI */ + pi->xhfc.name = pi->name; + + /* init interrupt engine */ + rc = pi->irq = acpi_register_gsi( + IRQ_TLP_GPIO_24, + ACPI_LEVEL_SENSITIVE, + ACPI_ACTIVE_LOW); + if (rc < 0) + goto err_acpi_register; + + + /******** + * test * + ********/ + + rc = xhfc_collect_chip_id(&pi->xhfc); + if (rc) + goto err_collect_chip_id; + + + /*************** + * OK let's go * + ***************/ + + card_cnt++; + + if ((rc = request_irq(pi->irq, &xhfc_interrupt, 0, "XHFC", pi))) { + printk(KERN_WARNING "%s %s: couldn't get interrupt %d\n", + pi->name, __func__, pi->irq); + goto err_request_irq; + } + + init_spans(&pi->xhfc); + + xhfc_init_and_configure(&pi->xhfc); +#ifdef AUDIO + /* TDM started on the XHFC side, XHFC is MASTER */ + /* Now it's possible to start the TDM bus on the EP80579 side, as SLAVE: */ + if ((rc = xivo_tdm_config_port(pi->tdm_port, XHFC_MEGREZ_PROTO_XIVO_CONFIG)) < 0) { + printk(KERN_ERR "%s %s: xivo_tdm_config_port failed (err=%d)\n", pi->name, __func__, rc); + goto err_tdm_config_port; + } +#endif + + for (span = 0; span < SPANS_PER_CHIP; span++) + if ((rc = dahdi_register(&pi->xhfc.spans[span].span, /*prefmaster*/ 1)) < 0) { + printk(KERN_WARNING "%s %s: couldn't register spans\n", pi->name, __func__); + goto err_in_dahdi_register; + } + + enable_interrupts(&pi->xhfc); + +#ifdef AUDIO + xivo_tdm_start_chans(pi->tdm_port, + /* timeslots 0->7 */ 0xFF, + NULL); +#endif + + return 0; + +err_in_dahdi_register: + for (span--; span >= 0; span--) + dahdi_unregister(&pi->xhfc.spans[span].span); +#ifdef AUDIO +err_tdm_config_port: +#endif + free_irq(pi->irq, pi); +err_request_irq: +err_collect_chip_id: + //acpi_unregister_gsi(IRQ_TLP_GPIO_30); /* symbol doesn't exist??? */ +err_acpi_register: +#ifdef AUDIO + xivo_tdm_put_port(pi->tdm_port); +err_tdm_get_port: +#endif +exit_after_reset: + iounmap(pi->cs_n0); +err_ioremap_mmbar: + iounmap(pi->regs); +err_ioremap_csrbar: + pci_release_regions(pdev); +err_request_regions: + pci_disable_device(pdev); +err_enable_device: + kfree(pi); +err_alloc: + return rc; +} + +void __devexit xhfc_remove_one(struct pci_dev *pdev) +{ + int i; + struct xhfc_pi *pi; + + pi = pci_get_drvdata(pdev); + + if (DBG) + printk(KERN_INFO "%s %s: removing card\n", + pi->name, __func__); + + for (i = 0; i < SPANS_PER_CHIP; i++) + dahdi_unregister(&pi->xhfc.spans[i].span); + + release_card_irq(pi); + + card_cnt--; + + iounmap(pi->regs); + pi->regs = NULL; + iounmap(pi->cs_n0); + pi->cs_n0 = NULL; + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + kfree(pi); + + printk(KERN_DEBUG "%s: bye-bye\n", __func__); +} + +/***************/ +/* Module init */ +/***************/ +static int __init xhfc_init(void) +{ + int err; + + if (DBG) + printk(KERN_INFO DRIVER_NAME " driver Rev. %s\n", xhfc_rev); + + err = dahdi_pci_module(&xhfc_driver); + if (err < 0) + goto out; + + /* XXX fail with error if no cards detected */ + + return 0; + + out: + return (err); +} + +static void __exit xhfc_cleanup(void) +{ + pci_unregister_driver(&xhfc_driver); + if (DBG) + printk(KERN_INFO "%s: driver removed\n", __func__); +} + +module_init(xhfc_init); +module_exit(xhfc_cleanup); + |