/* * 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 . */ #include #include #include #include #include #include #include #include #include #ifdef AUDIO #include #define XIVO_VE890_TDM_PORT 1 #endif #include "vp_api.h" #include "EVB_Le71HR8921G_rev_E_2M048.h" #define VP_TICK_MS 5 /* matches with device profile configuration */ #include "tolapai_spi.h" #define DRV_NAME "xivovp" #define TRACES_VANISH_DEFAULT 2000 #define NB_LINES 2 #define TICK_JIFFIES(n) DIV_ROUND_UP(n * HZ, 1000) #define VP_TICK_JIFFIES TICK_JIFFIES(VP_TICK_MS) #define DEBUG_SELECT_RUNNING (VP_DBG_ERROR | VP_DBG_WARNING | VP_DBG_INFO) #define FXS_TIMESLOT 1 #define FXO_TIMESLOT 3 #ifdef AUDIO #define TS_MASK ((1u << FXS_TIMESLOT) | (1u << FXO_TIMESLOT)) #endif #define REVERSE_POLARITY(line) (!( \ (!reverse_polarity) ^ \ (!(line)->reverse_polarity && \ (line)->vmwi_info.vmwi_type & \ DAHDI_VMWI_LREV) ^ \ (!(line)->message_waiting))) #define POL(line, sig) (REVERSE_POLARITY(line) ? sig ## _POLREV : sig) static uint init_dbg = VP_DBG_ALL; static int traces_vanish = TRACES_VANISH_DEFAULT; static int alawoverride = 1; /* It's named like that in every DAHDI driver except I prefer it to be 1 so the name doesn't make much sense. */ static int reverse_polarity = false; static int wait_port0 = true; static uint reset_gpio = 25; module_param(init_dbg, uint, 0444); module_param(reset_gpio, uint, 0444); module_param(traces_vanish, int, 0444); module_param(wait_port0, int, 0444); module_param(alawoverride, int, 0600); module_param(reverse_polarity, int, 0600); MODULE_PARM_DESC(init_dbg, "initial VP_API debug mask (default: " __stringify(VP_DBG_ALL) ")"); MODULE_PARM_DESC(traces_vanish, "number of " __stringify(VP_TICK_MS) "ms ticks after device init " "complete event at which point VP-API II Lite traces will be reduced " "to only ERROR/WARNING/INFO (default: " __stringify(TRACES_VANISH_DEFAULT) ")"); enum xivovp_line_type { FXS_LINE = 0, FXO_LINE = 1 }; struct xivovp_line { enum xivovp_line_type type; bool ready; Vp890LineObjectType line_obj; VpLineCtxType vp_ctx; struct dahdi_chan chan; bool txsig_pending; enum dahdi_txsig txsig; spinlock_t lock; struct dahdi_vmwi_info vmwi_info; bool reverse_polarity; int message_waiting; bool previous_polarity; }; static struct xivovp { struct dahdi_span span; struct dahdi_device *ddev; struct timer_list vp_tick_timer; struct xivovp_line line[NB_LINES]; struct dahdi_chan *chans[NB_LINES]; Vp890DeviceObjectType ve890_dev_obj; VpDevCtxType dev_ctx; #ifdef AUDIO struct xivo_tdm_port *tdm_port; #endif } xivovp; #ifdef AUDIO static void xivovp_txrx(void *data) { (void) data; dahdi_ec_span(&xivovp.span); dahdi_receive(&xivovp.span); dahdi_transmit(&xivovp.span); } #endif void reset_ve890(void) { gpio_set_direction(reset_gpio, GPIO_OUTPUT); gpio_set_to_gpio(reset_gpio); gpio_set_level(reset_gpio, GPIO_HIGH); msleep(1); gpio_set_level(reset_gpio, GPIO_LOW); msleep(1); gpio_set_level(reset_gpio, GPIO_HIGH); } /* Returns the xivovp_line which line is the same as the VpLineCtxType */ static struct xivovp_line* xivovp_line_from_ctx(VpLineCtxType* line_ctx) { return container_of(line_ctx->pLineObj, struct xivovp_line, line_obj); } static void vp_set_debug_select_running(void) { int i; uint32 debug_select; for (i = 0; i < NB_LINES; i++) { debug_select = DEBUG_SELECT_RUNNING; VpSetOption(&xivovp.line[i].vp_ctx, NULL, VP_OPTION_ID_DEBUG_SELECT, &debug_select); } debug_select = DEBUG_SELECT_RUNNING; VpSetOption(NULL, &xivovp.dev_ctx, VP_OPTION_ID_DEBUG_SELECT, &debug_select); debug_select = DEBUG_SELECT_RUNNING; VpSetOption(NULL, NULL, VP_OPTION_ID_DEBUG_SELECT, &debug_select); } static int xivovp_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig) { struct xivovp_line* line = chan->pvt; unsigned long flags; spin_lock_irqsave(&line->lock, flags); line->txsig = txsig; line->txsig_pending = true; spin_unlock_irqrestore(&line->lock, flags); return 0; } #define HOOKSIG_TRACE(line, txsig, vpsig) \ printk(KERN_DEBUG DRV_NAME "(chan %d): " \ "transmitting %s on %s, setting to %s%s.\n", \ chanpos, txsig, \ (line->type == FXO_LINE ? "FXO" : "FXS"), vpsig,\ (REVERSE_POLARITY(line) && \ line->type == FXS_LINE ? \ "_POLREV" : "")); /* * hooksig transmits sig from DAHDI to VP. * sig from VP is handled in vp_tick and transmitted to DAHDI with * dahdi_hooksig */ static void xivovp_hooksig_pvt(struct xivovp_line* line, enum dahdi_txsig txsig) { int chanpos = line->chan.chanpos; if (line->type == FXO_LINE) { switch (txsig) { case DAHDI_TXSIG_START: case DAHDI_TXSIG_OFFHOOK: HOOKSIG_TRACE(line, (txsig == DAHDI_TXSIG_START ? "START" : "OFFHOOK"), "TALK"); VpSetLineState(&line->vp_ctx, VP_LINE_FXO_TALK); break; case DAHDI_TXSIG_ONHOOK: HOOKSIG_TRACE(line, "ONHOOK", "TALK"); VpSetLineState(&line->vp_ctx, VP_LINE_FXO_OHT); break; default: printk(KERN_DEBUG DRV_NAME "(chan %d): " "unsupported tx state for FXO: %d\n", chanpos, txsig); } } else /* FXS */ { switch (txsig) { case DAHDI_TXSIG_START: HOOKSIG_TRACE(line, "START", "RINGING"); VpSetLineState(&line->vp_ctx, POL(line, VP_LINE_RINGING)); break; case DAHDI_TXSIG_OFFHOOK: HOOKSIG_TRACE(line, "OFFHOOK", "TALK"); VpSetLineState(&line->vp_ctx, POL(line, VP_LINE_TALK)); break; case DAHDI_TXSIG_ONHOOK: HOOKSIG_TRACE(line, "ONHOOK", "OHT"); VpSetLineState(&line->vp_ctx, POL(line, VP_LINE_OHT)); break; case DAHDI_TXSIG_KEWL: printk(KERN_DEBUG DRV_NAME "(chan %d): " "transmitting remote disconnect (KEWL)" "on FXS\n", chanpos); VpSetLineState(&line->vp_ctx, VP_LINE_DISCONNECT); break; default: printk(KERN_DEBUG DRV_NAME "(chan %d): " "unsupported tx state for FXS: %d\n", chanpos, txsig); } } } static void xivovp_handle_txsig(void) { static int said; int i; enum dahdi_txsig txsig; struct xivovp_line *line; unsigned long flags; bool pol_change; for (i = 0; i < NB_LINES; i++) { line = &xivovp.line[i]; pol_change = line->type == FXS_LINE && line->previous_polarity != REVERSE_POLARITY(line); //XXX shouldn't do that while line is ringing? if (line->ready && (line->txsig_pending || pol_change)) { spin_lock_irqsave(&xivovp.line[i].lock, flags); txsig = xivovp.line[i].txsig; xivovp.line[i].txsig_pending = false; line->previous_polarity = REVERSE_POLARITY(line); spin_unlock_irqrestore(&xivovp.line[i].lock, flags); xivovp_hooksig_pvt(&xivovp.line[i], txsig); } else if (!said) { said = 1; printk(KERN_WARNING DRV_NAME ": " "txsig pending but calibration " "not done yet! Waiting...\n"); } } } static bool count_and_stop_traces = FALSE; static void vp_post_init(VpEventType *event) { VpStatusType vpst; VpOptionTimeslotType fxs_timeslot = { .tx = FXS_TIMESLOT, .rx = FXS_TIMESLOT, }; VpOptionTimeslotType fxo_timeslot = { .tx = FXO_TIMESLOT, .rx = FXO_TIMESLOT, }; VpOptionEventMaskType event_mask = { .faults = 0, .signaling = 0, .response = 0, .test = 0, .process = 0, .fxo = 0, }; printk(KERN_INFO DRV_NAME ": vp dev init complete\n"); vpst = VpSetOption(NULL, event->pDevCtx, VP_OPTION_ID_EVENT_MASK, &event_mask); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpSetOption VP_OPTION_ID_EVENT_MASK " "returned %d\n", (int)vpst); } /* FXS */ vpst = VpSetOption(&xivovp.line[FXS_LINE].vp_ctx, NULL, VP_OPTION_ID_TIMESLOT, &fxs_timeslot); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpSetOption FXS VP_OPTION_ID_TIMESLOT " "returned %d\n", (int)vpst); } vpst = VpSetLineState(&xivovp.line[FXS_LINE].vp_ctx, VP_LINE_OHT); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpSetLineState returned %d\n", (int)vpst); } vpst = VpCalLine(&xivovp.line[FXS_LINE].vp_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpCalLine returned %d\n", (int)vpst); } /* FXO */ vpst = VpSetOption(&xivovp.line[FXO_LINE].vp_ctx, NULL, VP_OPTION_ID_TIMESLOT, &fxo_timeslot); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpSetOption FXO VP_OPTION_ID_TIMESLOT " "returned %d\n", (int)vpst); } vpst = VpSetLineState(&xivovp.line[FXO_LINE].vp_ctx, VP_LINE_FXO_OHT); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpSetLineState returned %d\n", (int)vpst); } xivovp.line[FXO_LINE].ready = true; /* No VpCalLine for FXO */ count_and_stop_traces = TRUE; } static void event_calibration_complete(VpEventType *event) { printk(KERN_INFO DRV_NAME ": calibration succeeded\n"); xivovp_line_from_ctx(event->pLineCtx)->ready = true; vp_set_debug_select_running(); } static void event_calibration_fail(VpEventType *event) { (void) event; printk(KERN_WARNING DRV_NAME ": " "calibration failed (returned VP_EVID_CAL_BUSY)\n"); vp_set_debug_select_running(); } static void event_hook_off(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received hook off event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_OFFHOOK); } static void event_hook_on(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received hook on event %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_ONHOOK); } static void event_fxo_hook_off(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": %s on %s\n", event->eventId == VP_LINE_EVID_LIU ? "line in use" : event->eventId == VP_LINE_EVID_FEED_DIS ? "voltage no longer detected across the loop" : event->eventId == VP_LINE_EVID_DISCONNECT ? "loop current from FXS no longer detected" : "???", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_OFFHOOK); } static void event_fxo_hook_on(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": %s on %s\n", event->eventId == VP_LINE_EVID_LNIU ? "line not in use" : event->eventId == VP_LINE_EVID_FEED_EN ? "FXS has applied voltage accross the loop" : event->eventId == VP_LINE_EVID_RECONNECT ? "FXS has started driving loop current" : "???", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_ONHOOK); } static void event_ring_on(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received ring on event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_RING); } static void event_ring_off(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received ring off event on %s\n", line->chan.name); dahdi_hooksig(&line->chan, DAHDI_RXSIG_ONHOOK); } static void event_polarity_reversal(VpEventType *event) { struct xivovp_line *line = xivovp_line_from_ctx(event->pLineCtx); printk(KERN_INFO DRV_NAME ": received polarity reversal event on %s\n", line->chan.name); line->reverse_polarity = !line->reverse_polarity; dahdi_qevent_lock(&line->chan, DAHDI_EVENT_POLARITY); } #define UNSUPPORTED(ev) \ case ev: \ printk(KERN_WARNING DRV_NAME \ ": reveived unsupported event " #ev "\n"); \ break; static void vp_tick(const unsigned long data) { static VpEventType event; static VpResultsType result; bool ev_pending; VpStatusType vpst; (void) data; vpst = VpApiTick(&xivovp.dev_ctx, &ev_pending); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpApiTick returned %d\n", (int)vpst); return; } while (ev_pending) { ev_pending = VpGetEvent(&xivovp.dev_ctx, &event); if (event.status != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpGetEvent returned " "event.status == %d\n", (int)event.status); return; } if (ev_pending) { printk(KERN_DEBUG "vp evt: %d %d\n", (int)event.eventCategory, (int)event.eventId); switch ((int)event.eventCategory) { case VP_EVCAT_RESPONSE: switch (event.eventId) { case VP_DEV_EVID_DEV_INIT_CMP: vp_post_init(&event); break; case VP_EVID_CAL_CMP: event_calibration_complete(&event); break; case VP_EVID_CAL_BUSY: event_calibration_fail(&event); break; UNSUPPORTED(VP_LINE_EVID_RD_OPTION); UNSUPPORTED(VP_LINE_EVID_GAIN_CMP); UNSUPPORTED(VP_LINE_EVID_LINE_INIT_CMP); UNSUPPORTED(VP_DEV_EVID_IO_ACCESS_CMP); } break; case VP_EVCAT_FAULT: switch(event.eventId) { UNSUPPORTED(VP_DEV_EVID_BAT_FLT); UNSUPPORTED(VP_DEV_EVID_CLK_FLT); UNSUPPORTED(VP_LINE_EVID_THERM_FLT); UNSUPPORTED(VP_LINE_EVID_DC_FLT); UNSUPPORTED(VP_LINE_EVID_RES_LEAK_FLT); } break; case VP_EVCAT_SIGNALING: switch (event.eventId) { case VP_LINE_EVID_HOOK_OFF: event_hook_off(&event); break; case VP_LINE_EVID_HOOK_ON: event_hook_on(&event); break; UNSUPPORTED(VP_LINE_EVID_GKEY_DET); UNSUPPORTED(VP_LINE_EVID_GKEY_REL); UNSUPPORTED(VP_LINE_EVID_FLASH); UNSUPPORTED(VP_LINE_EVID_STARTPULSE); UNSUPPORTED(VP_LINE_EVID_DTMF_DIG); UNSUPPORTED(VP_LINE_EVID_PULSE_DIG); } break; case VP_EVCAT_TEST: break; case VP_EVCAT_PROCESS: break; case VP_EVCAT_FXO: switch (event.eventId) { case VP_LINE_EVID_RING_ON: event_ring_on(&event); break; case VP_LINE_EVID_RING_OFF: event_ring_off(&event); break; case VP_LINE_EVID_POLREV: event_polarity_reversal(&event); break; case VP_LINE_EVID_LIU: case VP_LINE_EVID_FEED_DIS: case VP_LINE_EVID_DISCONNECT: event_fxo_hook_off(&event); break; case VP_LINE_EVID_LNIU: case VP_LINE_EVID_FEED_EN: case VP_LINE_EVID_RECONNECT: event_fxo_hook_on(&event); break; UNSUPPORTED(VP_LINE_EVID_POH); UNSUPPORTED(VP_LINE_EVID_PNOH); } break; } if (event.hasResults) { printk(KERN_INFO "VpGetResults\n"); vpst = VpGetResults(&event, &result); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpGetResults returned " "%d\n", (int) vpst); return; // TOTHINK } } } } xivovp_handle_txsig(); if (count_and_stop_traces) { if (traces_vanish >= 0 && --traces_vanish < 0) vp_set_debug_select_running(); } mod_timer(&xivovp.vp_tick_timer, jiffies + VP_TICK_JIFFIES); } static int xivovp_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long data) { struct xivovp_line* line = chan->pvt; int x; switch (cmd) { case DAHDI_VMWI_CONFIG: if (line->type != FXS_LINE) return -EINVAL; if (copy_from_user(&line->vmwi_info, (__user void *) data, sizeof(line->vmwi_info))) return -EFAULT; if (!(line->vmwi_info.vmwi_type & DAHDI_VMWI_LREV)) printk(KERN_WARNING DRV_NAME "(chan %d): " "Only lrev VMWI is supported\n", chan->chanpos); break; case DAHDI_VMWI: if (line->type != FXS_LINE) return -EINVAL; if (get_user(x, (__user int *) data)) return -EFAULT; line->message_waiting = x; break; case DAHDI_SETPOLARITY: if (line->type != FXS_LINE) return -EINVAL; if (get_user(x, (__user int *) data)) return -EFAULT; line->reverse_polarity = !!x; break; default: return -ENOTTY; } return 0; } static const struct dahdi_span_ops xivovp_span_ops = { .owner = THIS_MODULE, .hooksig = xivovp_hooksig, .ioctl = xivovp_ioctl, }; static int span_init(void) { int i; struct xivovp_line* l; dahdi_copy_string(xivovp.span.name, "XivoVP", sizeof(xivovp.span.name)); dahdi_copy_string(xivovp.span.desc, "FXO/FXS driver for Avencall's XiVO IPBX OpenHardware " "platform", sizeof(xivovp.span.desc)); xivovp.ddev = dahdi_create_device(); // BUGBUG xivovp.ddev->manufacturer = "Avencall"; xivovp.ddev->devicetype = kasprintf(GFP_KERNEL, "Ve890"); xivovp.span.ops = &xivovp_span_ops; xivovp.span.channels = NB_LINES; xivovp.span.chans = xivovp.chans; xivovp.span.deflaw = alawoverride ? DAHDI_LAW_ALAW : DAHDI_LAW_MULAW; xivovp.span.flags = DAHDI_FLAG_RBS; for (i = 0; i < NB_LINES; i++) { l = &xivovp.line[i]; xivovp.chans[i] = &l->chan; l->chan.pvt = l; l->chan.chanpos = i + 1; if (l->type == FXO_LINE) { /* /!\ FXO uses FXS sig and vice-versa */ sprintf(l->chan.name, "XIVO/FXO"); l->chan.sigcap = DAHDI_SIG_FXSLS | DAHDI_SIG_FXSGS | DAHDI_SIG_FXSKS; } else if (l->type == FXS_LINE) { sprintf(l->chan.name, "XIVO/FXS"); l->chan.sigcap = DAHDI_SIG_FXOLS | DAHDI_SIG_FXOGS | DAHDI_SIG_FXOKS; } else { printk(KERN_ERR DRV_NAME ": " "WTF?? Unsupported line type %d\n", l->type); return -EINVAL; } spin_lock_init(&l->lock); } list_add_tail(&xivovp.span.device_node, &xivovp.ddev->spans); if (dahdi_register_device(xivovp.ddev, &the_spi_pci_dev->dev)) { printk(KERN_WARNING DRV_NAME ": couldn't register span.\n"); return -EINVAL; } return 0; } static int xivovp_init(void) { init_timer(&xivovp.vp_tick_timer); xivovp.vp_tick_timer.function = vp_tick; xivovp.vp_tick_timer.data = 42; xivovp.line[FXS_LINE].type = FXS_LINE; xivovp.line[FXO_LINE].type = FXO_LINE; #ifdef AUDIO xivovp.tdm_port = xivo_tdm_get_port(XIVO_VE890_TDM_PORT); if (!xivovp.tdm_port) { printk(KERN_ERR "%s: could not get Ve890 TDM port\n", __func__); return -EIO; } #endif return 0; } static void xivovp_cleanup(void) { #ifdef AUDIO if (xivovp.tdm_port) { xivo_tdm_put_port(xivovp.tdm_port); xivovp.tdm_port = NULL; } #endif } static int vp_init(void) { int rc; VpStatusType vpst; uint32 debug_select; debug_select = init_dbg; VpSetOption(NULL, NULL, VP_OPTION_ID_DEBUG_SELECT, &debug_select); debug_select = init_dbg; VpSetOption(NULL, NULL, VP_OPTION_ID_GLOBAL_DEBUG_SELECT, &debug_select); vpst = VpMakeDeviceObject( VP_DEV_890_SERIES, /* deviceId */ 0, &xivovp.dev_ctx, (void*)&xivovp.ve890_dev_obj); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpMakeDeviceObject failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_device_object; } vpst = VpMakeLineObject( VP_TERM_FXS_GENERIC, FXS_LINE, &xivovp.line[FXS_LINE].vp_ctx, (void*)&xivovp.line[FXS_LINE].line_obj, &xivovp.dev_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpMakeLineObject (FXS) failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_line_object; } vpst = VpMakeLineObject( VP_TERM_FXO_GENERIC, FXO_LINE, &xivovp.line[FXO_LINE].vp_ctx, (void*)&xivovp.line[FXO_LINE].line_obj, &xivovp.dev_ctx); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": " "VpMakeLineObject (FXO) failed (%d)\n", vpst); rc = -EIO; goto err_vp_make_line_object; } else { printk(KERN_INFO DRV_NAME ": VpMakeLineObject (FXO) done.\n"); } vpst = VpInitDevice( &xivovp.dev_ctx, DEV_PROFILE_Buck_Boost, AC_FXS_RF50_FR, DC_FXS_DEF, RING_FR, AC_FXO_LC_FR, FXO_DIALING_FR); if (vpst != VP_STATUS_SUCCESS) { printk(KERN_ERR DRV_NAME ": VpInitDevice failed (%d)\n", vpst); rc = -EIO; goto err_vp_init_device; } return 0; /* nothing */ err_vp_init_device: /* nothing */ err_vp_make_line_object: /* nothing */ err_vp_make_device_object: return rc; } #ifdef AUDIO /* process context */ static void xivovp_port0_configured_cb(void *data); /* process context */ static void xivovp_port0_started_cb(void *data); #endif static int test_evb_ve890_init(void) { int rc; printk(KERN_INFO DRV_NAME ": entering %s\n", __func__); reset_ve890(); rc = tlp_spidev_init(); if (rc < 0) { printk(KERN_ERR DRV_NAME ": tlp_spidev_init failed (%d)\n", rc); goto err_spidev_init; } rc = xivovp_init(); if (rc < 0) goto err_xivovp_init; rc = span_init(); if (rc < 0) { printk(KERN_ERR DRV_NAME ": ve890_dahdi_init failed\n"); goto err_dahdi_init; } #ifdef AUDIO if(wait_port0) /* UGLY EVIL HACK -1 */ xivo_tdm_register_port0_configured( xivovp.tdm_port, xivovp_port0_configured_cb, NULL); else { xivovp_port0_configured_cb(NULL); xivovp_port0_started_cb(NULL); } return 0; #else rc = vp_init(); if (rc < 0) goto err_vp_init; mod_timer(&xivovp.vp_tick_timer, jiffies + VP_TICK_JIFFIES); return 0; err_vp_init: dahdi_unregister_device(xivovp.ddev); #endif err_dahdi_init: xivovp_cleanup(); err_xivovp_init: tlp_spidev_exit(); err_spidev_init: return rc; } #ifdef AUDIO static void xivovp_port0_configured_cb(void *data) { int rc; printk(KERN_ERR "%s START\n", __func__); if ((rc = xivo_tdm_config_port(xivovp.tdm_port, LE89316_MEGREZ_PROTO_XIVO_CONFIG, NULL)) < 0) { printk(KERN_CRIT "%s: xivo_tdm_config_port returned %d\n", __func__, rc); msleep(999); goto err_config_port; } rc = vp_init(); if (rc < 0) goto err_vp_init; /* UGLY EVIL HACK */ xivo_tdm_register_port0_started( xivovp.tdm_port, xivovp_port0_started_cb, NULL); mod_timer(&xivovp.vp_tick_timer, jiffies + VP_TICK_JIFFIES); printk(KERN_ERR "%s END\n", __func__); return; err_vp_init: dahdi_unregister_device(xivovp.ddev); err_config_port: xivovp_cleanup(); tlp_spidev_exit(); } #endif #ifdef AUDIO /* process context */ static void xivovp_port0_started_cb(void *data) { u8 **readchunk_ptrs[XIVO_TDM_TS_NUM] = { 0 }; u8 **writechunk_ptrs[XIVO_TDM_TS_NUM] = { 0 }; printk(KERN_ERR "%s START\n", __func__); readchunk_ptrs[FXS_TIMESLOT] = &xivovp.chans[FXS_LINE]->readchunk; writechunk_ptrs[FXS_TIMESLOT] = &xivovp.chans[FXS_LINE]->writechunk; readchunk_ptrs[FXO_TIMESLOT] = &xivovp.chans[FXO_LINE]->readchunk; writechunk_ptrs[FXO_TIMESLOT] = &xivovp.chans[FXO_LINE]->writechunk; /* XXX */ (void) xivo_tdm_start_chans( xivovp.tdm_port, readchunk_ptrs, writechunk_ptrs, xivovp_txrx, NULL); printk(KERN_ERR "%s END\n", __func__); } #endif static void test_evb_ve890_exit(void) { printk(KERN_INFO DRV_NAME ": %s\n", __func__); dahdi_unregister_device(xivovp.ddev); del_timer_sync(&xivovp.vp_tick_timer); /* XXX : TODO update upstream Linux doc of del_timer_sync */ xivovp_cleanup(); tlp_spidev_exit(); } module_init(test_evb_ve890_init); module_exit(test_evb_ve890_exit); MODULE_DESCRIPTION("Test driver for EVB Ve890"); MODULE_AUTHOR("Guillaume Knispel"); MODULE_LICENSE("GPL"); MODULE_VERSION("48");