/* BRI support on XiVO IPBX OpenHardware with Cologne Chip's XHFC * * Copyright (C) 2010,2012 Avencall * Authors: * Noe Rubinstein * Guillaume Knispel * * 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, 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, see . */ /* BUGBUG: pnp vs. pci driver registration / unregistration could be racy * (if the bind / unbind mechanism in /sys is used by user or another reason * results in a .remove method to be called) * * TODO: resource management strategy has been designed under influence, * clean it * * TODO: hardware modif to ensure terminations are not activated * while the xhfc is in reset. * * TODO: we might want to remove the ntte module param? */ #include #include #include #include #include #include #include #ifdef USE_GPIO #include #endif #include "xhfc24sucd.h" #include "xhfc.h" #include "xhfc_leb.h" static const char xhfc_rev[] = "42"; // XXX static int card_cnt = 0; MODULE_LICENSE("GPL"); #ifdef USE_GPIO #define GPIO_FROM_PLATFORM_DESC (-2) #define GPIO_NONE (-1) static int reset_gpio = GPIO_FROM_PLATFORM_DESC; module_param(reset_gpio, int, S_IRUGO); MODULE_PARM_DESC(reset_gpio, "Reset the XHFC using this GPIO" "(override ACPI platform description)"); #endif static struct xhfc_pi *g_pi; uint debug = 0; uint dbg_spanfilter = 0xFFFFFFFF; static int exit_after_reset = 0; static uint ntte = 0x3; // XXX we might want to remove that module_param(debug, uint, S_IRUGO | S_IWUSR); module_param(dbg_spanfilter, uint, S_IRUGO | S_IWUSR); module_param(ntte, uint, S_IRUGO); module_param(exit_after_reset, bool, S_IRUGO); MODULE_PARM_DESC(debug, "Debug bitfield:\n" "\t0: general\n" //"\t2: verbose register access, only if DEBUG is defined\n" "\t3: file operations\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(exit_after_reset, "Exit after hard reset"); static void xhfc_waitbusy(struct xhfc *xhfc) { // XXX add timeout while (read_xhfc(xhfc, R_STATUS) & M_BUSY) cpu_relax(); } static 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); } static 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); } static 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", DRIVER_NAME, __func__); return -EIO; } return 0; } static 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, 0xC); /* OxE for TE not softconfigured */ 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 */ static 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) static 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); } static 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); } static 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); } } static uint frames_in; /** * 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) { frames_in++; if (stat != 0x00) { if (DBG_HDLC && DBG_SPAN(xhfc_span)) { printk(KERN_NOTICE DRIVER_NAME "(port %d): STAT=0x%02x " "indicates frame %d problem: %s\n", portno(xhfc_span), stat, frames_in, (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), 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, 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) { printk(KERN_WARNING DRIVER_NAME ": %s, " "nothing to receive (should not happen!)\n", debugbuf); return 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; 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 (%d frames left; res = %d): " "%s\n", dahdi_span->spanno, size, fifo, atomic_read(&xhfc_span->hdlc_pending), 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 */ static 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); /* If there is an alarm, try to activate layer1 before sending out * anything (xref7) */ if (xhfc_span->span.alarms != 0) activate_request(xhfc_span); } /***************************************************************************** * 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))) static irqreturn_t xhfc_interrupt(int irq, void *dev_id) { 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]; xhfc = &pi->xhfc; /* Not for us / spurious interrupt. * (NOTE: for XiVO OpenHardware we don't 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 there is an alarm, wait for the port to go up before transmission * (xref7) */ 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 ms. */ int irq = fifo_test(fifo_irq, dchan_fifo(s), RECEIVE); int fill = fifo_test(fifo_fill, dchan_fifo(s), RECEIVE); if (irq || fill) hdlc_rx_frame(s); if (atomic_read(&s->hdlc_pending)) hdlc_tx_frame(s); } } 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)) handle_state_change(&xhfc->spans[i]); } } return IRQ_HANDLED; } static int dbg_rxtx; module_param(dbg_rxtx, int, 0664); MODULE_PARM_DESC(dbg_rxtx, "number of calls to xhfc_rxtx to trace"); #ifdef AUDIO /* hardirq */ static void xhfc_rxtx(void *data) { struct xhfc *xhfc = data; int i; for (i = 0; i < SPANS_PER_CHIP; i++) { struct xhfc_span* s = &xhfc->spans[i]; if (dbg_rxtx) printk(KERN_ERR "%d -- R0:%p W0:%p R1:%p W1:%p\n", i, s->_chans[0].readchunk, s->_chans[0].writechunk, s->_chans[1].readchunk, s->_chans[1].writechunk); if ((s->span.flags & DAHDI_FLAG_RUNNING) && s->sigchan) { // XXX s->sigchan? dahdi_receive(&s->span); if (s->span.alarms == DAHDI_ALARM_NONE) dahdi_transmit(&s->span); dahdi_ec_span(&s->span); } if (dbg_rxtx) printk(KERN_ERR "%d -- R0:%p W0:%p R1:%p W1:%p\n", i, s->_chans[0].readchunk, s->_chans[0].writechunk, s->_chans[1].readchunk, s->_chans[1].writechunk); } if (dbg_rxtx) --dbg_rxtx; } #endif /*****************************************************/ /* disable all interrupts by disabling M_GLOB_IRQ_EN */ /*****************************************************/ static void disable_interrupts(struct xhfc * xhfc) { if (DBG) printk(KERN_INFO "%s %s\n", DRIVER_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; if (DBG) printk(KERN_INFO "%s %s\n", DRIVER_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. */ /***********************************/ static int xhfc_collect_chip_id(struct xhfc * xhfc) { u8 chip_id = read_xhfc(xhfc, R_CHIP_ID); switch (chip_id) { case CHIP_ID_4SU: printk(KERN_INFO "%s ChipID: 0x%x\n", DRIVER_NAME, chip_id); return 0; case CHIP_ID_1SU: case CHIP_ID_2SU: case CHIP_ID_2S4U: printk(KERN_ERR "%s %s: unsupported device XHFC-%s\n", DRIVER_NAME, __func__, chip_id == CHIP_ID_1SU ? "1SU" : chip_id == CHIP_ID_2SU ? "2SU" : "2S4U"); return -EIO; } printk(KERN_ERR "%s %s: unkown Chip ID 0x%x\n", DRIVER_NAME, __func__, chip_id); return -EIO; } static void xhfc_hard_reset(void) { #ifdef USE_GPIO if (reset_gpio == GPIO_NONE) { printk(KERN_NOTICE DRIVER_NAME ": No hard reset performed " "(no GPIO configured)\n"); return; } 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 } static inline int lebcs1_bit_nt(int port) { return (port * 2) + 1; } /* on the leb, cs1 */ static inline int lebcs1_bit_term(int port) { return (port * 2); } static inline u8 byte_replace_bit(u8 byte, int nr, bool value) { return (byte & ~(1 << nr)) | (value << nr); } static void configure_ntte(struct xhfc_span *xhfc_span, int nt) { struct xhfc *xhfc = xhfc_span->xhfc; u8 soft_conf = xhfc->pi->soft_conf_byte; if (DBG_REGS) printk(KERN_INFO "NTTE %d %d before %02x\n", xhfc_span->port, nt, soft_conf); soft_conf = byte_replace_bit(soft_conf, lebcs1_bit_nt(xhfc_span->port), !nt); xhfc->pi->soft_conf_byte = soft_conf; write_xhfc_soft_conf(xhfc, soft_conf); if (DBG_REGS) printk(KERN_INFO "NTTE %d %d after %02x\n", xhfc_span->port, nt, soft_conf); } static void configure_term(struct xhfc_span *xhfc_span, int lineterm) { struct xhfc *xhfc = xhfc_span->xhfc; u8 soft_conf = xhfc->pi->soft_conf_byte; if (DBG_REGS) printk(KERN_INFO "TERM %d %d before %02x\n", xhfc_span->port, lineterm, soft_conf); soft_conf = byte_replace_bit(xhfc->pi->soft_conf_byte, lebcs1_bit_term(xhfc_span->port), !!lineterm); xhfc->pi->soft_conf_byte = soft_conf; write_xhfc_soft_conf(xhfc, xhfc->pi->soft_conf_byte); if (DBG_REGS) printk(KERN_INFO "TERM %d %d after %02x\n", xhfc_span->port, lineterm, soft_conf); } static void __devinit xhfc_preset_all_te_noterm(struct xhfc *xhfc) { xhfc->pi->soft_conf_byte = 0; // te: 0 / noterm: 0 write_xhfc_soft_conf(xhfc, 0); } static 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); disable_interrupts(x); /* "stage 2" */ xhfc_config_pcm(x, XHFC_PCM_MASTER); 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"; } static int xhfc_spanconfig(struct file *file, 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; /* xref2 */ xhfc_span_set_ntte(xhfc_span, lc->lineconfig & DAHDI_CONFIG_NTTE); 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"); xhfc_config_st(xhfc, xhfc_span->port, xhfc_span->nt); configure_ntte(xhfc_span, xhfc_span->nt); configure_term(xhfc_span, term); 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; } static int xhfc_chanconfig(struct file *file, 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 file *file, 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 * ************************************************************************/ static void init_spans(struct xhfc* x) { int i, j; struct xhfc_span *xhfc_span; struct dahdi_span *dahdi_span; struct dahdi_chan *chan; 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->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, "XIVO_XHFC/%d", i+1); sprintf(dahdi_span->desc, "XHFC port %d", i+1); // BUGBUG: free all that: dahdi_span->alarms = DAHDI_ALARM_RED; /* 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, "XIVO_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 void xhfc_shutdown(struct pci_dev *pdev) { (void) pdev; #ifdef AUDIO xivo_tdm_shutdown(); #endif } /* pci.txt: called from process context */ static 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; #ifdef AUDIO int ts; u8 **readchunk_ptrs[XIVO_TDM_TS_NUM] = { 0 }; u8 **writechunk_ptrs[XIVO_TDM_TS_NUM] = { 0 }; #endif u8 __iomem *cs_n2; pi = g_pi; printk(KERN_INFO "%s %s: LEB PI found on PCI bus %02x dev %02x\n", DRIVER_NAME, __func__, pdev->bus->number, pdev->devfn); /************ * PCI init * ************/ rc = pci_enable_device(pdev); if (rc) goto err_enable_device; if (DBG) 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; /* we have no LEB CS configured to 32MB, so CS are every 16MB * see [Intel 320066] 42.4.1.1 Chip Select Address Allocation */ pi->cs_n1 = pi->cs_n0 + 16 * 1024 * 1024; cs_n2 = pi->cs_n0 + 32 * 1024 * 1024; /* WARNING: don't use cs_n0 / cs_n1 before leb_init() */ pci_set_drvdata(pdev, pi); /************* * Hard init * *************/ leb_init(pi); // XXX maybe we should disable the CS on exit? xhfc_preset_all_te_noterm(&pi->xhfc); xhfc_hard_reset(); if (exit_after_reset) { rc = -EINVAL; goto exit_after_reset; } #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; /******** * 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", DRIVER_NAME, __func__, pi->irq); goto err_request_irq; } pi->xhfc.ddev = dahdi_create_device(); pi->xhfc.ddev->manufacturer = "Avencall"; pi->xhfc.ddev->devicetype = kasprintf(GFP_KERNEL, "xhfc-4su"); pi->xhfc.ddev->location = kasprintf(GFP_KERNEL, "PCI Bus %02d Slot %02d", pi->pci_dev->bus->number, PCI_SLOT(pi->pci_dev->devfn) + 1); 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, cs_n2)) < 0) { printk(KERN_ERR "%s %s: xivo_tdm_config_port failed (err=%d)\n", DRIVER_NAME, __func__, rc); goto err_tdm_config_port; } #endif for (span = 0; span < SPANS_PER_CHIP; span++) list_add_tail(&pi->xhfc.spans[span].span.device_node, &pi->xhfc.ddev->spans); if ((rc = dahdi_register_device(pi->xhfc.ddev, &pdev->dev))) { printk(KERN_WARNING "%s %s: couldn't register spans\n", DRIVER_NAME, __func__); goto err_in_dahdi_register; } enable_interrupts(&pi->xhfc); #ifdef AUDIO ts = 0; for (span = 0; span < SPANS_PER_CHIP; span++) { readchunk_ptrs[ts] = &pi->xhfc.spans[span].chans[0]->readchunk; writechunk_ptrs[ts] = &pi->xhfc.spans[span].chans[0]->writechunk; ts++; readchunk_ptrs[ts] = &pi->xhfc.spans[span].chans[1]->readchunk; writechunk_ptrs[ts] = &pi->xhfc.spans[span].chans[1]->writechunk; ts++; } xivo_tdm_start_chans(pi->tdm_port, readchunk_ptrs, writechunk_ptrs, xhfc_rxtx, &pi->xhfc); #endif return 0; err_in_dahdi_register: dahdi_unregister_device(pi->xhfc.ddev); #ifdef AUDIO err_tdm_config_port: #endif free_irq(pi->irq, pi); err_request_irq: err_collect_chip_id: #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); return rc; } static void __devexit xhfc_remove_one(struct pci_dev *pdev) { struct xhfc_pi *pi; pi = pci_get_drvdata(pdev); if (DBG) printk(KERN_INFO "%s %s: removing card\n", DRIVER_NAME, __func__); dahdi_unregister_device(pi->xhfc.ddev); 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); if (DBG) printk(KERN_DEBUG "%s: bye-bye\n", __func__); } static const struct pci_device_id tlp_leb_pci_tbl[] = { /* Intel Tolapai LEB controler: */ { 0x8086, 0x503d, PCI_ANY_ID, PCI_ANY_ID, }, { 0 } }; // this PCI "device" conceptually is an internal interface of our // XHFC device. // It is a generic simple parallel bus provided by the EP80579 SoC. // So don't export it. // 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, }; /* Note about possible evolution: to allow retro compatible expansion, * you'll have to introduce a new structure with e.g. the dynamical * platform descriptor length in a header. */ struct xhfc_platform_desc { u8 reset_gpio; // 255 => no gpio for reset } __packed; /* no const: acpi API are not const clean */ static struct acpi_vendor_uuid xhfc_platform_desc_uuid = { .subtype = 0x42, .data = { 0x34, 0x89, 0xfa, 0xc1, 0xd1, 0xcc, 0xb7, 0x4b, 0xbf, 0xa0, 0x0d, 0x93, 0x8b, 0xf3, 0x8e, 0xd5 }, }; static int __devinit xhfc_acpi_get_platform_desc(acpi_handle handle, struct xhfc_platform_desc *desc) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_resource *resource; struct acpi_resource_vendor_typed *vendor; int payload_length; if (!handle) { if (DBG) printk(KERN_DEBUG DRIVER_NAME ": %s: xhfc acpi handle missing\n", __func__); return -ENXIO; } status = acpi_get_vendor_resource(handle, METHOD_NAME__CRS, &xhfc_platform_desc_uuid, &buffer); if (ACPI_FAILURE(status)) { if (status == AE_NOT_EXIST) { if (DBG) printk(KERN_DEBUG DRIVER_NAME ": %s: xhfc platform descriptor " "not found\n", __func__); return -ENOENT; } printk(KERN_ERR DRIVER_NAME ": %s: acpi_get_vendor_resource() error %s\n", __func__, acpi_format_exception(status)); return -EFAULT; } resource = buffer.pointer; vendor = &resource->data.vendor_typed; payload_length = vendor->byte_length - sizeof (struct acpi_vendor_uuid); /* NOTE: modify that simple test if you modify xhfc_platform_desc in * a retrocompatible fashion. */ if (payload_length < sizeof (struct xhfc_platform_desc)) { printk(KERN_ERR DRIVER_NAME ": %s: invalid xhfc platform descriptor detected\n", __func__); kfree(buffer.pointer); return -EBADMSG; } memcpy(desc, vendor->byte_data, sizeof (struct xhfc_platform_desc)); kfree(buffer.pointer); return 0; } /** * xhfc_pnp_get_platform_desc() * @dev: the pnp device * @desc: out param to return the platform desc * * Preconditions: neither @dev nor @desc shall be NULL. * * Returns >= 0 if the platform_desc could be retrieved. * Returns -ENOENT if no descriptor has been found. * Returns an other error code in cases where the caller should * back off. (The caller can also chose to back off on -ENOENT.) */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33) static int __devinit xhfc_pnp_get_platform_desc(struct pnp_dev *dev, struct xhfc_platform_desc *desc) { acpi_handle handle = DEVICE_ACPI_HANDLE(&(dev->dev)); return xhfc_acpi_get_platform_desc(handle, desc); } #else # error not implemented #endif static int __devinit xhfc_pd_get_reset_gpio(const struct xhfc_platform_desc *desc) { if (desc->reset_gpio == 255) return -1; else return desc->reset_gpio; } static int __devinit xhfc_pnp_init_one(struct pnp_dev *dev, const struct pnp_device_id *dev_id) { static int xhfc_pnp_num = 0; struct xhfc_pi *pi; resource_size_t irq; int err; if (xhfc_pnp_num >= 1) { printk(KERN_DEBUG DRIVER_NAME ": ignoring spurious additional pnp device %s " "(dev_id=%s)\n", pnp_dev_name(dev), dev_id->id); return -1; } printk(KERN_DEBUG DRIVER_NAME ": xhfc_pnp_init_one: pnp device %s (id=%s)\n", pnp_dev_name(dev), dev_id->id); if (reset_gpio == GPIO_FROM_PLATFORM_DESC) { struct xhfc_platform_desc xhfc_pd; err = xhfc_pnp_get_platform_desc(dev, &xhfc_pd); if (err < 0) { printk(KERN_ERR DRIVER_NAME ": %s: xhfc_pnp_get_platform_desc() error %d\n", __func__, err); return err; } else reset_gpio = xhfc_pd_get_reset_gpio(&xhfc_pd); if (reset_gpio < 0 && reset_gpio != GPIO_NONE) { printk(KERN_ERR DRIVER_NAME ": %s: unknown reset gpio\n", __func__); return -EIO; } } if (DBG) { printk(KERN_DEBUG DRIVER_NAME ": reset gpio = %d\n", reset_gpio); } irq = pnp_irq(dev, 0); if (irq == (resource_size_t)-1) { printk(KERN_ERR DRIVER_NAME ": failed to get irq for pnp device %s (dev_id=%s)\n", pnp_dev_name(dev), dev_id->id); return -1; } /* External resources validated, all subsequent failures * in this function are internal */ xhfc_pnp_num++; pi = kzalloc(sizeof(struct xhfc_pi), GFP_KERNEL); if (pi == NULL) { printk(KERN_ERR DRIVER_NAME ": allocation failed\n"); goto err_alloc; } pi->xhfc.pi = pi; pi->irq = irq; pi->pnp_dev = dev; g_pi = pi; err = pci_register_driver(&xhfc_driver); if (err < 0) { printk(KERN_ERR DRIVER_NAME ": LEB PCI driver registration failed\n"); goto err_pci_reg; } pnp_set_drvdata(dev, pi); return 0; err_pci_reg: kfree(pi); err_alloc: return -1; } static void __devexit xhfc_pnp_remove_one(struct pnp_dev *dev) { pci_unregister_driver(&xhfc_driver); } static const struct pnp_device_id xivo_xhfc_pnp_dev_table[] = { { "AEN0001", 0 }, { "", 0 } }; MODULE_DEVICE_TABLE(pnp, xivo_xhfc_pnp_dev_table); static struct pnp_driver xhfc_pnp_driver = { .name = DRIVER_NAME, .id_table = xivo_xhfc_pnp_dev_table, .probe = xhfc_pnp_init_one, .remove = __devexit_p(xhfc_pnp_remove_one), }; /***************/ /* Module init */ /***************/ static int __init xhfc_init(void) { if (DBG) printk(KERN_INFO DRIVER_NAME " driver Rev. %s\n", xhfc_rev); /* XXX fail with error if no cards detected * (by checking a real device counter, not just success of * driver registration) */ return pnp_register_driver(&xhfc_pnp_driver); } static void __exit xhfc_cleanup(void) { pnp_unregister_driver(&xhfc_pnp_driver); if (DBG) printk(KERN_INFO "%s: driver removed\n", __func__); } module_init(xhfc_init); module_exit(xhfc_cleanup);