/*
 * Stuff used by all variants of the driver
 *
 * Copyright (c) 2001 by Stefan Eilers,
 *                       Hansjoerg Lipp <hjlipp@web.de>,
 *                       Tilman Schmidt <tilman@imap.cc>.
 *
 * =====================================================================
 *	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.
 * =====================================================================
 */

#include "gigaset.h"
#include <linux/ctype.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

/* Version Information */
#define DRIVER_AUTHOR "Hansjoerg Lipp <hjlipp@web.de>, Tilman Schmidt <tilman@imap.cc>, Stefan Eilers"
#define DRIVER_DESC "Driver for Gigaset 307x"

/* Module parameters */
int gigaset_debuglevel = DEBUG_DEFAULT;
EXPORT_SYMBOL_GPL(gigaset_debuglevel);
module_param_named(debug, gigaset_debuglevel, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(debug, "debug level");

/* driver state flags */
#define VALID_MINOR	0x01
#define VALID_ID	0x02
#define ASSIGNED	0x04

void gigaset_dbg_buffer(enum debuglevel level, const unsigned char *msg,
			size_t len, const unsigned char *buf)
{
	unsigned char outbuf[80];
	unsigned char c;
	size_t space = sizeof outbuf - 1;
	unsigned char *out = outbuf;
	size_t numin = len;

	while (numin--) {
		c = *buf++;
		if (c == '~' || c == '^' || c == '\\') {
			if (!space--)
				break;
			*out++ = '\\';
		}
		if (c & 0x80) {
			if (!space--)
				break;
			*out++ = '~';
			c ^= 0x80;
		}
		if (c < 0x20 || c == 0x7f) {
			if (!space--)
				break;
			*out++ = '^';
			c ^= 0x40;
		}
		if (!space--)
			break;
		*out++ = c;
	}
	*out = 0;

	gig_dbg(level, "%s (%u bytes): %s", msg, (unsigned) len, outbuf);
}
EXPORT_SYMBOL_GPL(gigaset_dbg_buffer);

static int setflags(struct cardstate *cs, unsigned flags, unsigned delay)
{
	int r;

	r = cs->ops->set_modem_ctrl(cs, cs->control_state, flags);
	cs->control_state = flags;
	if (r < 0)
		return r;

	if (delay) {
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(delay * HZ / 1000);
	}

	return 0;
}

int gigaset_enterconfigmode(struct cardstate *cs)
{
	int i, r;

	cs->control_state = TIOCM_RTS; //FIXME

	r = setflags(cs, TIOCM_DTR, 200);
	if (r < 0)
		goto error;
	r = setflags(cs, 0, 200);
	if (r < 0)
		goto error;
	for (i = 0; i < 5; ++i) {
		r = setflags(cs, TIOCM_RTS, 100);
		if (r < 0)
			goto error;
		r = setflags(cs, 0, 100);
		if (r < 0)
			goto error;
	}
	r = setflags(cs, TIOCM_RTS|TIOCM_DTR, 800);
	if (r < 0)
		goto error;

	return 0;

error:
	dev_err(cs->dev, "error %d on setuartbits\n", -r);
	cs->control_state = TIOCM_RTS|TIOCM_DTR; // FIXME is this a good value?
	cs->ops->set_modem_ctrl(cs, 0, TIOCM_RTS|TIOCM_DTR);

	return -1; //r
}

static int test_timeout(struct at_state_t *at_state)
{
	if (!at_state->timer_expires)
		return 0;

	if (--at_state->timer_expires) {
		gig_dbg(DEBUG_MCMD, "decreased timer of %p to %lu",
			at_state, at_state->timer_expires);
		return 0;
	}

	if (!gigaset_add_event(at_state->cs, at_state, EV_TIMEOUT, NULL,
			       at_state->timer_index, NULL)) {
		//FIXME what should we do?
	}

	return 1;
}

static void timer_tick(unsigned long data)
{
	struct cardstate *cs = (struct cardstate *) data;
	unsigned long flags;
	unsigned channel;
	struct at_state_t *at_state;
	int timeout = 0;

	spin_lock_irqsave(&cs->lock, flags);

	for (channel = 0; channel < cs->channels; ++channel)
		if (test_timeout(&cs->bcs[channel].at_state))
			timeout = 1;

	if (test_timeout(&cs->at_state))
		timeout = 1;

	list_for_each_entry(at_state, &cs->temp_at_states, list)
		if (test_timeout(at_state))
			timeout = 1;

	if (cs->running) {
		mod_timer(&cs->timer, jiffies + msecs_to_jiffies(GIG_TICK));
		if (timeout) {
			gig_dbg(DEBUG_CMD, "scheduling timeout");
			tasklet_schedule(&cs->event_tasklet);
		}
	}

	spin_unlock_irqrestore(&cs->lock, flags);
}

int gigaset_get_channel(struct bc_state *bcs)
{
	unsigned long flags;

	spin_lock_irqsave(&bcs->cs->lock, flags);
	if (bcs->use_count) {
		gig_dbg(DEBUG_ANY, "could not allocate channel %d",
			bcs->channel);
		spin_unlock_irqrestore(&bcs->cs->lock, flags);
		return 0;
	}
	++bcs->use_count;
	bcs->busy = 1;
	gig_dbg(DEBUG_ANY, "allocated channel %d", bcs->channel);
	spin_unlock_irqrestore(&bcs->cs->lock, flags);
	return 1;
}

void gigaset_free_channel(struct bc_state *bcs)
{
	unsigned long flags;

	spin_lock_irqsave(&bcs->cs->lock, flags);
	if (!bcs->busy) {
		gig_dbg(DEBUG_ANY, "could not free channel %d", bcs->channel);
		spin_unlock_irqrestore(&bcs->cs->lock, flags);
		return;
	}
	--bcs->use_count;
	bcs->busy = 0;
	gig_dbg(DEBUG_ANY, "freed channel %d", bcs->channel);
	spin_unlock_irqrestore(&bcs->cs->lock, flags);
}

int gigaset_get_channels(struct cardstate *cs)
{
	unsigned long flags;
	int i;

	spin_lock_irqsave(&cs->lock, flags);
	for (i = 0; i < cs->channels; ++i)
		if (cs->bcs[i].use_count) {
			spin_unlock_irqrestore(&cs->lock, flags);
			gig_dbg(DEBUG_ANY, "could not allocate all channels");
			return 0;
		}
	for (i = 0; i < cs->channels; ++i)
		++cs->bcs[i].use_count;
	spin_unlock_irqrestore(&cs->lock, flags);

	gig_dbg(DEBUG_ANY, "allocated all channels");

	return 1;
}

void gigaset_free_channels(struct cardstate *cs)
{
	unsigned long flags;
	int i;

	gig_dbg(DEBUG_ANY, "unblocking all channels");
	spin_lock_irqsave(&cs->lock, flags);
	for (i = 0; i < cs->channels; ++i)
		--cs->bcs[i].use_count;
	spin_unlock_irqrestore(&cs->lock, flags);
}

void gigaset_block_channels(struct cardstate *cs)
{
	unsigned long flags;
	int i;

	gig_dbg(DEBUG_ANY, "blocking all channels");
	spin_lock_irqsave(&cs->lock, flags);
	for (i = 0; i < cs->channels; ++i)
		++cs->bcs[i].use_count;
	spin_unlock_irqrestore(&cs->lock, flags);
}

static void clear_events(struct cardstate *cs)
{
	struct event_t *ev;
	unsigned head, tail;
	unsigned long flags;

	spin_lock_irqsave(&cs->ev_lock, flags);

	head = cs->ev_head;
	tail = cs->ev_tail;

	while (tail != head) {
		ev = cs->events + head;
		kfree(ev->ptr);
		head = (head + 1) % MAX_EVENTS;
	}

	cs->ev_head = tail;

	spin_unlock_irqrestore(&cs->ev_lock, flags);
}

struct event_t *gigaset_add_event(struct cardstate *cs,
				  struct at_state_t *at_state, int type,
				  void *ptr, int parameter, void *arg)
{
	unsigned long flags;
	unsigned next, tail;
	struct event_t *event = NULL;

	spin_lock_irqsave(&cs->ev_lock, flags);

	tail = cs->ev_tail;
	next = (tail + 1) % MAX_EVENTS;
	if (unlikely(next == cs->ev_head))
		err("event queue full");
	else {
		event = cs->events + tail;
		event->type = type;
		event->at_state = at_state;
		event->cid = -1;
		event->ptr = ptr;
		event->arg = arg;
		event->parameter = parameter;
		cs->ev_tail = next;
	}

	spin_unlock_irqrestore(&cs->ev_lock, flags);

	return event;
}
EXPORT_SYMBOL_GPL(gigaset_add_event);

static void free_strings(struct at_state_t *at_state)
{
	int i;

	for (i = 0; i < STR_NUM; ++i) {
		kfree(at_state->str_var[i]);
		at_state->str_var[i] = NULL;
	}
}

static void clear_at_state(struct at_state_t *at_state)
{
	free_strings(at_state);
}

static void dealloc_at_states(struct cardstate *cs)
{
	struct at_state_t *cur, *next;

	list_for_each_entry_safe(cur, next, &cs->temp_at_states, list) {
		list_del(&cur->list);
		free_strings(cur);
		kfree(cur);
	}
}

static void gigaset_freebcs(struct bc_state *bcs)
{
	int i;

	gig_dbg(DEBUG_INIT, "freeing bcs[%d]->hw", bcs->channel);
	if (!bcs->cs->ops->freebcshw(bcs)) {
		gig_dbg(DEBUG_INIT, "failed");
	}

	gig_dbg(DEBUG_INIT, "clearing bcs[%d]->at_state", bcs->channel);
	clear_at_state(&bcs->at_state);
	gig_dbg(DEBUG_INIT, "freeing bcs[%d]->skb", bcs->channel);

	if (bcs->skb)
		dev_kfree_skb(bcs->skb);
	for (i = 0; i < AT_NUM; ++i) {
		kfree(bcs->commands[i]);
		bcs->commands[i] = NULL;
	}
}

static struct cardstate *alloc_cs(struct gigaset_driver *drv)
{
	unsigned long flags;
	unsigned i;
	struct cardstate *ret = NULL;

	spin_lock_irqsave(&drv->lock, flags);
	for (i = 0; i < drv->minors; ++i) {
		if (!(drv->flags[i] & VALID_MINOR)) {
			if (try_module_get(drv->owner)) {
				drv->flags[i] = VALID_MINOR;
				ret = drv->cs + i;
			}
			break;
		}
	}
	spin_unlock_irqrestore(&drv->lock, flags);
	return ret;
}

static void free_cs(struct cardstate *cs)
{
	unsigned long flags;
	struct gigaset_driver *drv = cs->driver;
	spin_lock_irqsave(&drv->lock, flags);
	if (drv->flags[cs->minor_index] & VALID_MINOR)
		module_put(drv->owner);
	drv->flags[cs->minor_index] = 0;
	spin_unlock_irqrestore(&drv->lock, flags);
}

static void make_valid(struct cardstate *cs, unsigned mask)
{
	unsigned long flags;
	struct gigaset_driver *drv = cs->driver;
	spin_lock_irqsave(&drv->lock, flags);
	drv->flags[cs->minor_index] |= mask;
	spin_unlock_irqrestore(&drv->lock, flags);
}

static void make_invalid(struct cardstate *cs, unsigned mask)
{
	unsigned long flags;
	struct gigaset_driver *drv = cs->driver;
	spin_lock_irqsave(&drv->lock, flags);
	drv->flags[cs->minor_index] &= ~mask;
	spin_unlock_irqrestore(&drv->lock, flags);
}

void gigaset_freecs(struct cardstate *cs)
{
	int i;
	unsigned long flags;

	if (!cs)
		return;

	mutex_lock(&cs->mutex);

	if (!cs->bcs)
		goto f_cs;
	if (!cs->inbuf)
		goto f_bcs;

	spin_lock_irqsave(&cs->lock, flags);
	cs->running = 0;
	spin_unlock_irqrestore(&cs->lock, flags); /* event handler and timer are
						     not rescheduled below */

	tasklet_kill(&cs->event_tasklet);
	del_timer_sync(&cs->timer);

	switch (cs->cs_init) {
	default:
		/* clear device sysfs */
		gigaset_free_dev_sysfs(cs);

		gigaset_if_free(cs);

		gig_dbg(DEBUG_INIT, "clearing hw");
		cs->ops->freecshw(cs);

		//FIXME cmdbuf

		/* fall through */
	case 2: /* error in initcshw */
		/* Deregister from LL */
		make_invalid(cs, VALID_ID);
		gig_dbg(DEBUG_INIT, "clearing iif");
		gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD);

		/* fall through */
	case 1: /* error when regestering to LL */
		gig_dbg(DEBUG_INIT, "clearing at_state");
		clear_at_state(&cs->at_state);
		dealloc_at_states(cs);

		/* fall through */
	case 0: /* error in one call to initbcs */
		for (i = 0; i < cs->channels; ++i) {
			gig_dbg(DEBUG_INIT, "clearing bcs[%d]", i);
			gigaset_freebcs(cs->bcs + i);
		}

		clear_events(cs);
		gig_dbg(DEBUG_INIT, "freeing inbuf");
		kfree(cs->inbuf);
	}
f_bcs:	gig_dbg(DEBUG_INIT, "freeing bcs[]");
	kfree(cs->bcs);
f_cs:	gig_dbg(DEBUG_INIT, "freeing cs");
	mutex_unlock(&cs->mutex);
	free_cs(cs);
}
EXPORT_SYMBOL_GPL(gigaset_freecs);

void gigaset_at_init(struct at_state_t *at_state, struct bc_state *bcs,
		     struct cardstate *cs, int cid)
{
	int i;

	INIT_LIST_HEAD(&at_state->list);
	at_state->waiting = 0;
	at_state->getstring = 0;
	at_state->pending_commands = 0;
	at_state->timer_expires = 0;
	at_state->timer_active = 0;
	at_state->timer_index = 0;
	at_state->seq_index = 0;
	at_state->ConState = 0;
	for (i = 0; i < STR_NUM; ++i)
		at_state->str_var[i] = NULL;
	at_state->int_var[VAR_ZDLE] = 0;
	at_state->int_var[VAR_ZCTP] = -1;
	at_state->int_var[VAR_ZSAU] = ZSAU_NULL;
	at_state->cs = cs;
	at_state->bcs = bcs;
	at_state->cid = cid;
	if (!cid)
		at_state->replystruct = cs->tabnocid;
	else
		at_state->replystruct = cs->tabcid;
}


static void gigaset_inbuf_init(struct inbuf_t *inbuf, struct bc_state *bcs,
			       struct cardstate *cs, int inputstate)
/* inbuf->read must be allocated before! */
{
	atomic_set(&inbuf->head, 0);
	atomic_set(&inbuf->tail, 0);
	inbuf->cs = cs;
	inbuf->bcs = bcs; /*base driver: NULL*/
	inbuf->rcvbuf = NULL; //FIXME
	inbuf->inputstate = inputstate;
}

/* append received bytes to inbuf */
int gigaset_fill_inbuf(struct inbuf_t *inbuf, const unsigned char *src,
		       unsigned numbytes)
{
	unsigned n, head, tail, bytesleft;

	gig_dbg(DEBUG_INTR, "received %u bytes", numbytes);

	if (!numbytes)
		return 0;

	bytesleft = numbytes;
	tail = atomic_read(&inbuf->tail);
	head = atomic_read(&inbuf->head);
	gig_dbg(DEBUG_INTR, "buffer state: %u -> %u", head, tail);

	while (bytesleft) {
		if (head > tail)
			n = head - 1 - tail;
		else if (head == 0)
			n = (RBUFSIZE-1) - tail;
		else
			n = RBUFSIZE - tail;
		if (!n) {
			dev_err(inbuf->cs->dev,
				"buffer overflow (%u bytes lost)\n",
				bytesleft);
			break;
		}
		if (n > bytesleft)
			n = bytesleft;
		memcpy(inbuf->data + tail, src, n);
		bytesleft -= n;
		tail = (tail + n) % RBUFSIZE;
		src += n;
	}
	gig_dbg(DEBUG_INTR, "setting tail to %u", tail);
	atomic_set(&inbuf->tail, tail);
	return numbytes != bytesleft;
}
EXPORT_SYMBOL_GPL(gigaset_fill_inbuf);

/* Initialize the b-channel structure */
static struct bc_state *gigaset_initbcs(struct bc_state *bcs,
					struct cardstate *cs, int channel)
{
	int i;

	bcs->tx_skb = NULL; //FIXME -> hw part

	skb_queue_head_init(&bcs->squeue);

	bcs->corrupted = 0;
	bcs->trans_down = 0;
	bcs->trans_up = 0;

	gig_dbg(DEBUG_INIT, "setting up bcs[%d]->at_state", channel);
	gigaset_at_init(&bcs->at_state, bcs, cs, -1);

	bcs->rcvbytes = 0;

#ifdef CONFIG_GIGASET_DEBUG
	bcs->emptycount = 0;
#endif

	gig_dbg(DEBUG_INIT, "allocating bcs[%d]->skb", channel);
	bcs->fcs = PPP_INITFCS;
	bcs->inputstate = 0;
	if (cs->ignoreframes) {
		bcs->inputstate |= INS_skip_frame;
		bcs->skb = NULL;
	} else if ((bcs->skb = dev_alloc_skb(SBUFSIZE + HW_HDR_LEN)) != NULL)
		skb_reserve(bcs->skb, HW_HDR_LEN);
	else {
		warn("could not allocate skb");
		bcs->inputstate |= INS_skip_frame;
	}

	bcs->channel = channel;
	bcs->cs = cs;

	bcs->chstate = 0;
	bcs->use_count = 1;
	bcs->busy = 0;
	bcs->ignore = cs->ignoreframes;

	for (i = 0; i < AT_NUM; ++i)
		bcs->commands[i] = NULL;

	gig_dbg(DEBUG_INIT, "  setting up bcs[%d]->hw", channel);
	if (cs->ops->initbcshw(bcs))
		return bcs;

	gig_dbg(DEBUG_INIT, "  failed");

	gig_dbg(DEBUG_INIT, "  freeing bcs[%d]->skb", channel);
	if (bcs->skb)
		dev_kfree_skb(bcs->skb);

	return NULL;
}

/* gigaset_initcs
 * Allocate and initialize cardstate structure for Gigaset driver
 * Calls hardware dependent gigaset_initcshw() function
 * Calls B channel initialization function gigaset_initbcs() for each B channel
 * parameters:
 *	drv		hardware driver the device belongs to
 *	channels	number of B channels supported by device
 *	onechannel	!=0: B channel data and AT commands share one
 *			     communication channel
 *			==0: B channels have separate communication channels
 *	ignoreframes	number of frames to ignore after setting up B channel
 *	cidmode		!=0: start in CallID mode
 *	modulename	name of driver module (used for I4L registration)
 * return value:
 *	pointer to cardstate structure
 */
struct cardstate *gigaset_initcs(struct gigaset_driver *drv, int channels,
				 int onechannel, int ignoreframes,
				 int cidmode, const char *modulename)
{
	struct cardstate *cs = NULL;
	unsigned long flags;
	int i;

	gig_dbg(DEBUG_INIT, "allocating cs");
	if (!(cs = alloc_cs(drv))) {
		err("maximum number of devices exceeded");
		return NULL;
	}
	mutex_init(&cs->mutex);

	gig_dbg(DEBUG_INIT, "allocating bcs[0..%d]", channels - 1);
	cs->bcs = kmalloc(channels * sizeof(struct bc_state), GFP_KERNEL);
	if (!cs->bcs) {
		err("out of memory");
		goto error;
	}
	gig_dbg(DEBUG_INIT, "allocating inbuf");
	cs->inbuf = kmalloc(sizeof(struct inbuf_t), GFP_KERNEL);
	if (!cs->inbuf) {
		err("out of memory");
		goto error;
	}

	cs->cs_init = 0;
	cs->channels = channels;
	cs->onechannel = onechannel;
	cs->ignoreframes = ignoreframes;
	INIT_LIST_HEAD(&cs->temp_at_states);
	cs->running = 0;
	init_timer(&cs->timer); /* clear next & prev */
	spin_lock_init(&cs->ev_lock);
	cs->ev_tail = 0;
	cs->ev_head = 0;

	tasklet_init(&cs->event_tasklet, &gigaset_handle_event,
		     (unsigned long) cs);
	atomic_set(&cs->commands_pending, 0);
	cs->cur_at_seq = 0;
	cs->gotfwver = -1;
	cs->open_count = 0;
	cs->dev = NULL;
	cs->tty = NULL;
	cs->tty_dev = NULL;
	cs->cidmode = cidmode != 0;

	//if(onechannel) { //FIXME
		cs->tabnocid = gigaset_tab_nocid_m10x;
		cs->tabcid = gigaset_tab_cid_m10x;
	//} else {
	//	cs->tabnocid = gigaset_tab_nocid;
	//	cs->tabcid = gigaset_tab_cid;
	//}

	init_waitqueue_head(&cs->waitqueue);
	cs->waiting = 0;

	atomic_set(&cs->mode, M_UNKNOWN);
	atomic_set(&cs->mstate, MS_UNINITIALIZED);

	for (i = 0; i < channels; ++i) {
		gig_dbg(DEBUG_INIT, "setting up bcs[%d].read", i);
		if (!gigaset_initbcs(cs->bcs + i, cs, i)) {
			err("could not allocate channel %d data", i);
			goto error;
		}
	}

	++cs->cs_init;

	gig_dbg(DEBUG_INIT, "setting up at_state");
	spin_lock_init(&cs->lock);
	gigaset_at_init(&cs->at_state, NULL, cs, 0);
	cs->dle = 0;
	cs->cbytes = 0;

	gig_dbg(DEBUG_INIT, "setting up inbuf");
	if (onechannel) {			//FIXME distinction necessary?
		gigaset_inbuf_init(cs->inbuf, cs->bcs, cs, INS_command);
	} else
		gigaset_inbuf_init(cs->inbuf, NULL,    cs, INS_command);

	cs->connected = 0;
	cs->isdn_up = 0;

	gig_dbg(DEBUG_INIT, "setting up cmdbuf");
	cs->cmdbuf = cs->lastcmdbuf = NULL;
	spin_lock_init(&cs->cmdlock);
	cs->curlen = 0;
	cs->cmdbytes = 0;

	gig_dbg(DEBUG_INIT, "setting up iif");
	if (!gigaset_register_to_LL(cs, modulename)) {
		err("register_isdn failed");
		goto error;
	}

	make_valid(cs, VALID_ID);
	++cs->cs_init;
	gig_dbg(DEBUG_INIT, "setting up hw");
	if (!cs->ops->initcshw(cs)) {
		err("could not allocate device specific data");
		goto error;
	}

	++cs->cs_init;

	/* set up character device */
	gigaset_if_init(cs);

	/* set up device sysfs */
	gigaset_init_dev_sysfs(cs);

	spin_lock_irqsave(&cs->lock, flags);
	cs->running = 1;
	spin_unlock_irqrestore(&cs->lock, flags);
	setup_timer(&cs->timer, timer_tick, (unsigned long) cs);
	cs->timer.expires = jiffies + msecs_to_jiffies(GIG_TICK);
	/* FIXME: can jiffies increase too much until the timer is added?
	 * Same problem(?) with mod_timer() in timer_tick(). */
	add_timer(&cs->timer);

	gig_dbg(DEBUG_INIT, "cs initialized");
	return cs;

error:
	gig_dbg(DEBUG_INIT, "failed");
	gigaset_freecs(cs);
	return NULL;
}
EXPORT_SYMBOL_GPL(gigaset_initcs);

/* ReInitialize the b-channel structure on hangup */
void gigaset_bcs_reinit(struct bc_state *bcs)
{
	struct sk_buff *skb;
	struct cardstate *cs = bcs->cs;
	unsigned long flags;

	while ((skb = skb_dequeue(&bcs->squeue)) != NULL)
		dev_kfree_skb(skb);

	spin_lock_irqsave(&cs->lock, flags);
	clear_at_state(&bcs->at_state);
	bcs->at_state.ConState = 0;
	bcs->at_state.timer_active = 0;
	bcs->at_state.timer_expires = 0;
	bcs->at_state.cid = -1;			/* No CID defined */
	spin_unlock_irqrestore(&cs->lock, flags);

	bcs->inputstate = 0;

#ifdef CONFIG_GIGASET_DEBUG
	bcs->emptycount = 0;
#endif

	bcs->fcs = PPP_INITFCS;
	bcs->chstate = 0;

	bcs->ignore = cs->ignoreframes;
	if (bcs->ignore)
		bcs->inputstate |= INS_skip_frame;


	cs->ops->reinitbcshw(bcs);
}

static void cleanup_cs(struct cardstate *cs)
{
	struct cmdbuf_t *cb, *tcb;
	int i;
	unsigned long flags;

	spin_lock_irqsave(&cs->lock, flags);

	atomic_set(&cs->mode, M_UNKNOWN);
	atomic_set(&cs->mstate, MS_UNINITIALIZED);

	clear_at_state(&cs->at_state);
	dealloc_at_states(cs);
	free_strings(&cs->at_state);
	gigaset_at_init(&cs->at_state, NULL, cs, 0);

	kfree(cs->inbuf->rcvbuf);
	cs->inbuf->rcvbuf = NULL;
	cs->inbuf->inputstate = INS_command;
	atomic_set(&cs->inbuf->head, 0);
	atomic_set(&cs->inbuf->tail, 0);

	cb = cs->cmdbuf;
	while (cb) {
		tcb = cb;
		cb = cb->next;
		kfree(tcb);
	}
	cs->cmdbuf = cs->lastcmdbuf = NULL;
	cs->curlen = 0;
	cs->cmdbytes = 0;
	cs->gotfwver = -1;
	cs->dle = 0;
	cs->cur_at_seq = 0;
	atomic_set(&cs->commands_pending, 0);
	cs->cbytes = 0;

	spin_unlock_irqrestore(&cs->lock, flags);

	for (i = 0; i < cs->channels; ++i) {
		gigaset_freebcs(cs->bcs + i);
		if (!gigaset_initbcs(cs->bcs + i, cs, i))
			break;			//FIXME error handling
	}

	if (cs->waiting) {
		cs->cmd_result = -ENODEV;
		cs->waiting = 0;
		wake_up_interruptible(&cs->waitqueue);
	}
}


int gigaset_start(struct cardstate *cs)
{
	unsigned long flags;

	if (mutex_lock_interruptible(&cs->mutex))
		return 0;

	spin_lock_irqsave(&cs->lock, flags);
	cs->connected = 1;
	spin_unlock_irqrestore(&cs->lock, flags);

	if (atomic_read(&cs->mstate) != MS_LOCKED) {
		cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR|TIOCM_RTS);
		cs->ops->baud_rate(cs, B115200);
		cs->ops->set_line_ctrl(cs, CS8);
		cs->control_state = TIOCM_DTR|TIOCM_RTS;
	} else {
		//FIXME use some saved values?
	}

	cs->waiting = 1;

	if (!gigaset_add_event(cs, &cs->at_state, EV_START, NULL, 0, NULL)) {
		cs->waiting = 0;
		//FIXME what should we do?
		goto error;
	}

	gig_dbg(DEBUG_CMD, "scheduling START");
	gigaset_schedule_event(cs);

	wait_event(cs->waitqueue, !cs->waiting);

	mutex_unlock(&cs->mutex);
	return 1;

error:
	mutex_unlock(&cs->mutex);
	return 0;
}
EXPORT_SYMBOL_GPL(gigaset_start);

void gigaset_shutdown(struct cardstate *cs)
{
	mutex_lock(&cs->mutex);

	cs->waiting = 1;

	if (!gigaset_add_event(cs, &cs->at_state, EV_SHUTDOWN, NULL, 0, NULL)) {
		//FIXME what should we do?
		goto exit;
	}

	gig_dbg(DEBUG_CMD, "scheduling SHUTDOWN");
	gigaset_schedule_event(cs);

	wait_event(cs->waitqueue, !cs->waiting);

	cleanup_cs(cs);

exit:
	mutex_unlock(&cs->mutex);
}
EXPORT_SYMBOL_GPL(gigaset_shutdown);

void gigaset_stop(struct cardstate *cs)
{
	mutex_lock(&cs->mutex);

	cs->waiting = 1;

	if (!gigaset_add_event(cs, &cs->at_state, EV_STOP, NULL, 0, NULL)) {
		//FIXME what should we do?
		goto exit;
	}

	gig_dbg(DEBUG_CMD, "scheduling STOP");
	gigaset_schedule_event(cs);

	wait_event(cs->waitqueue, !cs->waiting);

	cleanup_cs(cs);

exit:
	mutex_unlock(&cs->mutex);
}
EXPORT_SYMBOL_GPL(gigaset_stop);

static LIST_HEAD(drivers);
static DEFINE_SPINLOCK(driver_lock);

struct cardstate *gigaset_get_cs_by_id(int id)
{
	unsigned long flags;
	struct cardstate *ret = NULL;
	struct cardstate *cs;
	struct gigaset_driver *drv;
	unsigned i;

	spin_lock_irqsave(&driver_lock, flags);
	list_for_each_entry(drv, &drivers, list) {
		spin_lock(&drv->lock);
		for (i = 0; i < drv->minors; ++i) {
			if (drv->flags[i] & VALID_ID) {
				cs = drv->cs + i;
				if (cs->myid == id)
					ret = cs;
			}
			if (ret)
				break;
		}
		spin_unlock(&drv->lock);
		if (ret)
			break;
	}
	spin_unlock_irqrestore(&driver_lock, flags);
	return ret;
}

void gigaset_debugdrivers(void)
{
	unsigned long flags;
	static struct cardstate *cs;
	struct gigaset_driver *drv;
	unsigned i;

	spin_lock_irqsave(&driver_lock, flags);
	list_for_each_entry(drv, &drivers, list) {
		gig_dbg(DEBUG_DRIVER, "driver %p", drv);
		spin_lock(&drv->lock);
		for (i = 0; i < drv->minors; ++i) {
			gig_dbg(DEBUG_DRIVER, "  index %u", i);
			gig_dbg(DEBUG_DRIVER, "    flags 0x%02x",
				drv->flags[i]);
			cs = drv->cs + i;
			gig_dbg(DEBUG_DRIVER, "    cardstate %p", cs);
			gig_dbg(DEBUG_DRIVER, "    minor_index %u",
				cs->minor_index);
			gig_dbg(DEBUG_DRIVER, "    driver %p", cs->driver);
			gig_dbg(DEBUG_DRIVER, "    i4l id %d", cs->myid);
		}
		spin_unlock(&drv->lock);
	}
	spin_unlock_irqrestore(&driver_lock, flags);
}

static struct cardstate *gigaset_get_cs_by_minor(unsigned minor)
{
	unsigned long flags;
	struct cardstate *ret = NULL;
	struct gigaset_driver *drv;
	unsigned index;

	spin_lock_irqsave(&driver_lock, flags);
	list_for_each_entry(drv, &drivers, list) {
		if (minor < drv->minor || minor >= drv->minor + drv->minors)
			continue;
		index = minor - drv->minor;
		spin_lock(&drv->lock);
		if (drv->flags[index] & VALID_MINOR)
			ret = drv->cs + index;
		spin_unlock(&drv->lock);
		if (ret)
			break;
	}
	spin_unlock_irqrestore(&driver_lock, flags);
	return ret;
}

struct cardstate *gigaset_get_cs_by_tty(struct tty_struct *tty)
{
	if (tty->index < 0 || tty->index >= tty->driver->num)
		return NULL;
	return gigaset_get_cs_by_minor(tty->index + tty->driver->minor_start);
}

void gigaset_freedriver(struct gigaset_driver *drv)
{
	unsigned long flags;

	spin_lock_irqsave(&driver_lock, flags);
	list_del(&drv->list);
	spin_unlock_irqrestore(&driver_lock, flags);

	gigaset_if_freedriver(drv);

	kfree(drv->cs);
	kfree(drv->flags);
	kfree(drv);
}
EXPORT_SYMBOL_GPL(gigaset_freedriver);

/* gigaset_initdriver
 * Allocate and initialize gigaset_driver structure. Initialize interface.
 * parameters:
 *	minor		First minor number
 *	minors		Number of minors this driver can handle
 *	procname	Name of the driver
 *	devname		Name of the device files (prefix without minor number)
 * return value:
 *	Pointer to the gigaset_driver structure on success, NULL on failure.
 */
struct gigaset_driver *gigaset_initdriver(unsigned minor, unsigned minors,
					  const char *procname,
					  const char *devname,
					  const struct gigaset_ops *ops,
					  struct module *owner)
{
	struct gigaset_driver *drv;
	unsigned long flags;
	unsigned i;

	drv = kmalloc(sizeof *drv, GFP_KERNEL);
	if (!drv)
		return NULL;

	drv->have_tty = 0;
	drv->minor = minor;
	drv->minors = minors;
	spin_lock_init(&drv->lock);
	drv->blocked = 0;
	drv->ops = ops;
	drv->owner = owner;
	INIT_LIST_HEAD(&drv->list);

	drv->cs = kmalloc(minors * sizeof *drv->cs, GFP_KERNEL);
	if (!drv->cs)
		goto error;

	drv->flags = kmalloc(minors * sizeof *drv->flags, GFP_KERNEL);
	if (!drv->flags)
		goto error;

	for (i = 0; i < minors; ++i) {
		drv->flags[i] = 0;
		drv->cs[i].driver = drv;
		drv->cs[i].ops = drv->ops;
		drv->cs[i].minor_index = i;
	}

	gigaset_if_initdriver(drv, procname, devname);

	spin_lock_irqsave(&driver_lock, flags);
	list_add(&drv->list, &drivers);
	spin_unlock_irqrestore(&driver_lock, flags);

	return drv;

error:
	kfree(drv->cs);
	kfree(drv);
	return NULL;
}
EXPORT_SYMBOL_GPL(gigaset_initdriver);

/* For drivers without fixed assignment device<->cardstate (usb) */
struct cardstate *gigaset_getunassignedcs(struct gigaset_driver *drv)
{
	unsigned long flags;
	struct cardstate *cs = NULL;
	unsigned i;

	spin_lock_irqsave(&drv->lock, flags);
	if (drv->blocked)
		goto exit;
	for (i = 0; i < drv->minors; ++i) {
		if ((drv->flags[i] & VALID_MINOR) &&
		    !(drv->flags[i] & ASSIGNED)) {
			drv->flags[i] |= ASSIGNED;
			cs = drv->cs + i;
			break;
		}
	}
exit:
	spin_unlock_irqrestore(&drv->lock, flags);
	return cs;
}
EXPORT_SYMBOL_GPL(gigaset_getunassignedcs);

void gigaset_unassign(struct cardstate *cs)
{
	unsigned long flags;
	unsigned *minor_flags;
	struct gigaset_driver *drv;

	if (!cs)
		return;
	drv = cs->driver;
	spin_lock_irqsave(&drv->lock, flags);
	minor_flags = drv->flags + cs->minor_index;
	if (*minor_flags & VALID_MINOR)
		*minor_flags &= ~ASSIGNED;
	spin_unlock_irqrestore(&drv->lock, flags);
}
EXPORT_SYMBOL_GPL(gigaset_unassign);

void gigaset_blockdriver(struct gigaset_driver *drv)
{
	unsigned long flags;
	spin_lock_irqsave(&drv->lock, flags);
	drv->blocked = 1;
	spin_unlock_irqrestore(&drv->lock, flags);
}
EXPORT_SYMBOL_GPL(gigaset_blockdriver);

static int __init gigaset_init_module(void)
{
	/* in accordance with the principle of least astonishment,
	 * setting the 'debug' parameter to 1 activates a sensible
	 * set of default debug levels
	 */
	if (gigaset_debuglevel == 1)
		gigaset_debuglevel = DEBUG_DEFAULT;

	info(DRIVER_AUTHOR);
	info(DRIVER_DESC);
	return 0;
}

static void __exit gigaset_exit_module(void)
{
}

module_init(gigaset_init_module);
module_exit(gigaset_exit_module);

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_LICENSE("GPL");
