/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2004-2006, Xorcom
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#ifndef	XBUS_CORE_H
#define	XBUS_CORE_H

#include <linux/wait.h>
#include <linux/interrupt.h>	/* for tasklets */
#include "xpd.h"
#include "xframe_queue.h"
#include "xbus-pcm.h"

#define	MAX_BUSES		32
#define	XFRAME_DATASIZE		512

/* forward declarations */
struct xbus_workqueue;
#ifdef XPP_DEBUGFS
struct debugfs_data;
#endif

#ifdef	__KERNEL__

struct xbus_ops {
	int (*xframe_send_pcm)(xbus_t *xbus, xframe_t *xframe);
	int (*xframe_send_cmd)(xbus_t *xbus, xframe_t *xframe);
	xframe_t *(*alloc_xframe)(xbus_t *xbus, gfp_t gfp_flags);
	void (*free_xframe)(xbus_t *xbus, xframe_t *xframe);
};

/*
 * XBUS statistics counters
 */
enum {
	XBUS_N_UNITS,
	XBUS_N_TX_XFRAME_PCM,
	XBUS_N_RX_XFRAME_PCM,
	XBUS_N_TX_PACK_PCM,
	XBUS_N_RX_PACK_PCM,
	XBUS_N_TX_BYTES,
	XBUS_N_RX_BYTES,
	XBUS_N_TX_PCM_FRAG,
	XBUS_N_RX_CMD,
	XBUS_N_TX_CMD,
};

#define	XBUS_COUNTER(xbus, counter)	((xbus)->counters[XBUS_N_ ## counter])

#define	C_(x)	[ XBUS_N_ ## x ] = { #x }

/* yucky, make an instance so we can size it... */
static struct xbus_counters {
	char	*name;
} xbus_counters[] = {
	C_(UNITS),
	C_(TX_XFRAME_PCM),
	C_(RX_XFRAME_PCM),
	C_(TX_PACK_PCM),
	C_(RX_PACK_PCM),
	C_(TX_BYTES),
	C_(RX_BYTES),
	C_(TX_PCM_FRAG),
	C_(RX_CMD),
	C_(TX_CMD),
};

#undef C_

#define	XBUS_COUNTER_MAX	ARRAY_SIZE(xbus_counters)

enum xbus_state {
	XBUS_STATE_START,
	XBUS_STATE_IDLE,
	XBUS_STATE_SENT_REQUEST,
	XBUS_STATE_RECVD_DESC,
	XBUS_STATE_READY,
	XBUS_STATE_DEACTIVATING,
	XBUS_STATE_DEACTIVATED,
	XBUS_STATE_FAIL,
};

const char *xbus_statename(enum xbus_state st);

struct xbus_transport {
	struct xbus_ops		*ops;
	void			*priv;
	struct device		*transport_device;
	ushort			max_send_size;
	enum xbus_state		xbus_state;
	unsigned long		transport_flags;
	spinlock_t		state_lock;
	atomic_t		transport_refcount;
	wait_queue_head_t	transport_unused;
	spinlock_t		lock;
};

#define	MAX_SEND_SIZE(xbus)	((xbus)->transport.max_send_size)
#define	XBUS_STATE(xbus)	((xbus)->transport.xbus_state)
#define	XBUS_IS(xbus, st)	(XBUS_STATE(xbus) == XBUS_STATE_ ## st)
#define	TRANSPORT_EXIST(xbus)	((xbus)->transport.ops != NULL)

#define	XBUS_FLAG_CONNECTED	0
#define	XBUS_FLAGS(xbus, flg)	test_bit(XBUS_FLAG_ ## flg, &((xbus)->transport.transport_flags))

struct xbus_ops *transportops_get(xbus_t *xbus);
void transportops_put(xbus_t *xbus);

/*
 * Encapsulate all poll related data of a single xbus.
 */
struct xbus_workqueue {
	xbus_t			*xbus;
	struct workqueue_struct	*wq;
	struct work_struct	xpds_init_work;
	bool			xpds_init_done;
	struct list_head	card_list;
	int			num_units;
	int			num_units_initialized;
	wait_queue_head_t	wait_for_xpd_initialization;
	struct proc_dir_entry	*proc_xbus_waitfor_xpds;
	spinlock_t		worker_lock;
};

/*
 * Allocate/Free an xframe from pools of empty xframes.
 * Calls to {get,put}_xframe are wrapped in
 * the macros bellow, so we take/return it
 * to the correct pool.
 */
xframe_t *get_xframe(struct xframe_queue *q);
void put_xframe(struct xframe_queue *q, xframe_t *xframe);

#define	ALLOC_SEND_XFRAME(xbus)	get_xframe(&(xbus)->send_pool)
#define	ALLOC_RECV_XFRAME(xbus)	get_xframe(&(xbus)->receive_pool)
#define	FREE_SEND_XFRAME(xbus, xframe)	put_xframe(&(xbus)->send_pool, (xframe))
#define	FREE_RECV_XFRAME(xbus, xframe)	put_xframe(&(xbus)->receive_pool, (xframe))

xbus_t		*xbus_num(uint num);
xbus_t		*get_xbus(const char *msg, xbus_t *xbus);
void		put_xbus(const char *msg, xbus_t *xbus);
int		refcount_xbus(xbus_t *xbus);

/*
 * An xbus is a transport layer for Xorcom Protocol commands
 */
struct xbus {
	char			busname[XBUS_NAMELEN];	/* set by xbus_new() */

	/* low-level bus drivers set these 2 fields */
	char			connector[XBUS_DESCLEN];
	char			label[LABEL_SIZE];
	byte			revision;		/* Protocol revision */
	struct xbus_transport	transport;

	int			num;
	struct xpd		*xpds[MAX_XPDS];

	int			command_tick_counter;
	int			usec_nosend;		/* Firmware flow control */
	struct xframe_queue	command_queue;
	wait_queue_head_t	command_queue_empty;

	struct xframe_queue	send_pool;		/* empty xframes for send */
	struct xframe_queue	receive_pool;		/* empty xframes for receive */

	/* tasklet processing */
	struct xframe_queue	receive_queue;
	struct tasklet_struct	receive_tasklet;
	int			cpu_rcv_intr[NR_CPUS];
	int			cpu_rcv_tasklet[NR_CPUS];

	bool			self_ticking;
	enum sync_mode		sync_mode;
	struct timer_list	command_timer;
	unsigned int		xbus_frag_count;
	struct xframe_queue	pcm_tospan;

	struct xpp_ticker	ticker;		/* for tick rate */
	struct xpp_drift	drift;		/* for tick offset */

	atomic_t		pcm_rx_counter;
	unsigned int		global_counter;

	/* Device-Model */
	struct device		astribank;
#define	dev_to_xbus(dev)	container_of(dev, struct xbus, astribank)

	spinlock_t		lock;

	/* PCM metrics */
	struct timeval		last_tx_sync;
	struct timeval		last_rx_sync;
	unsigned long		max_tx_sync;
	unsigned long		min_tx_sync;
	unsigned long		max_rx_sync;
	unsigned long		min_rx_sync;
	unsigned long		max_rx_process;		/* packet processing time (usec) */
#ifdef	SAMPLE_TICKS
#define	SAMPLE_SIZE	1000
	int			sample_ticks[SAMPLE_SIZE];
	bool			sample_running;
	int			sample_pos;
#endif

	struct xbus_workqueue	*worker;
	struct semaphore	in_worker;

	/*
	 * Sync adjustment
	 */
	int			sync_adjustment;
	int			sync_adjustment_offset;
	long			pll_updated_at;

	atomic_t		num_xpds;

#ifdef	XPP_DEBUGFS
	struct dentry		*debugfs_dir;
	struct dentry		*debugfs_file;
	struct debugfs_data	*debugfs_data;
#endif
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry	*proc_xbus_dir;
	struct proc_dir_entry	*proc_xbus_summary;
#ifdef	PROTOCOL_DEBUG
	struct proc_dir_entry	*proc_xbus_command;
#endif
#endif

	/* statistics */
	int		counters[XBUS_COUNTER_MAX];
};
#endif

#define	XFRAME_MAGIC	123456L

struct xframe {
	unsigned long		xframe_magic;
	struct list_head	frame_list;
	atomic_t		frame_len;
	xbus_t			*xbus;
	struct timeval		tv_created;
	struct timeval		tv_queued;
	struct timeval		tv_submitted;
	struct timeval		tv_received;
	/* filled by transport layer */
	size_t			frame_maxlen;
	byte			*packets;	/* max XFRAME_DATASIZE */
	byte			*first_free;
	int			usec_towait;	/* prevent overflowing AB */
	void			*priv;
};

void xframe_init(xbus_t *xbus, xframe_t *xframe, void *buf, size_t maxsize, void *priv);

#define XFRAME_LEN(frame)	atomic_read(&(frame)->frame_len)

int xbus_core_init(void);		/* Initializer */
void xbus_core_shutdown(void);		/* Terminator */

#ifdef XPP_DEBUGFS
/* Debugfs handling */
int xbus_log(xbus_t *xbus, xpd_t *xpd, int direction, const void *buf, unsigned long len);
#endif

/* Frame handling */
void dump_xframe(const char msg[], const xbus_t *xbus, const xframe_t *xframe, int debug);
int send_cmd_frame(xbus_t *xbus, xframe_t *xframe);

/*
 * Return pointer to next packet slot in the frame
 * or NULL if the frame is full.
 */
xpacket_t *xframe_next_packet(xframe_t *xframe, int len);

/* XBUS handling */

/*
 * Map: unit+subunit <--> index in xbus->xpds[]
 */
#define	XPD_IDX(unit,subunit)	((unit) * MAX_SUBUNIT + (subunit))
#define	XBUS_UNIT(idx)		((idx) / MAX_SUBUNIT)
#define	XBUS_SUBUNIT(idx)	((idx) % MAX_SUBUNIT)

xpd_t	*xpd_of(const xbus_t *xbus, int xpd_num);
xpd_t	*xpd_byaddr(const xbus_t *xbus, uint unit, uint subunit);
bool	xbus_setstate(xbus_t *xbus, enum xbus_state newstate);
bool	xbus_setflags(xbus_t *xbus, int flagbit, bool on);
xbus_t	*xbus_new(struct xbus_ops *ops, ushort max_send_size, struct device *transport_device, void *priv);
void	xbus_free(xbus_t *xbus);
int	xbus_connect(xbus_t *xbus);
int	xbus_activate(xbus_t *xbus);
void	xbus_deactivate(xbus_t *xbus);
void	xbus_disconnect(xbus_t *xbus);
void	xbus_receive_xframe(xbus_t *xbus, xframe_t *xframe);
int	xbus_process_worker(xbus_t *xbus);
int	waitfor_xpds(xbus_t *xbus, char *buf);

int	xbus_xpd_bind(xbus_t *xbus, xpd_t *xpd, int unit, int subunit);
int	xbus_xpd_unbind(xbus_t *xbus, xpd_t *xpd);

/* sysfs */
int	xpd_device_register(xbus_t *xbus, xpd_t *xpd);
void	xpd_device_unregister(xpd_t *xpd);

int	xpp_driver_init(void);
void	xpp_driver_exit(void);
int	xbus_sysfs_create(xbus_t *xbus);
void	xbus_sysfs_remove(xbus_t *xbus);

#ifdef	OLD_HOTPLUG_SUPPORT_269
/* Copy from new kernels lib/kobject_uevent.c */
enum kobject_action {
	KOBJ_ADD,
	KOBJ_REMOVE,
	KOBJ_CHANGE,
	KOBJ_MOUNT,
	KOBJ_UMOUNT,
	KOBJ_OFFLINE,
	KOBJ_ONLINE,
};
#endif

void	astribank_uevent_send(xbus_t *xbus, enum kobject_action act);

#endif	/* XBUS_CORE_H */

