/*
   forward-irq.h - driver for SoftLab-NSK Forward boards

   Copyright (C) 2017 - 2024 SoftLab-NSK <forward@softlab.tv>

   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#ifndef FORWARD_IRQ_H
#define FORWARD_IRQ_H

struct forward_dev;

#define FORWARD_IRQ_LISTENER_WAIT (1 << 0)
#define FORWARD_IRQ_LISTENER_CALLBACK (1 << 1)
#define FORWARD_IRQ_LISTENER_WORKQUEUE (1 << 2)

#define forward_irq_flags_and(dst, src1, src2) \
	bitmap_and((dst).irq, (src1).irq, (src2).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_or(dst, src1, src2) \
	bitmap_or((dst).irq, (src1).irq, (src2).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_set(dst, flags, mask) \
	bitmap_or((dst).irq, (flags).irq, (mask).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_set_one(dst, nr) bitmap_set((dst).irq, (nr), 1)
#define forward_irq_flags_clear(dst, flags, mask) \
	bitmap_andnot((dst).irq, (flags).irq, (mask).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_clear_all(dst) bitmap_zero((dst).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_set_all(dst) bitmap_fill((dst).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_is_zero(flags) bitmap_empty((flags).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_pack(flags, src) \
	bitmap_from_arr32((flags).irq, (src), FORWARD_IRQ_FLAG_BITS)
#define forward_irq_flags_unpack(dst, flags) \
	bitmap_to_arr32((dst), (flags).irq, FORWARD_IRQ_FLAG_BITS)
#define forward_irq_has_irq(dst, nr) test_bit((nr), (dst).irq)
static inline bool forward_irq_has_any_irq(forward_irq_t flags, forward_irq_t mask)
{
	forward_irq_t masked;
	forward_irq_flags_and(masked, flags, mask);
	return !forward_irq_flags_is_zero(masked);
}
static inline unsigned long forward_irq_extract_data(forward_irq_t flags, unsigned long start,
						     unsigned long nbits)
{
	size_t index = BIT_WORD(start);
	unsigned long offset = start % BITS_PER_LONG;
	unsigned long space = BITS_PER_LONG - offset;
	unsigned long value_low, value_high;

	if (unlikely(!nbits))
		return 0;
	if (space >= nbits)
		return (flags.irq[index] >> offset) & GENMASK(nbits - 1, 0);
	value_low = flags.irq[index] & BITMAP_FIRST_WORD_MASK(start);
	value_high = flags.irq[index + 1] & BITMAP_LAST_WORD_MASK(start + nbits);
	return (value_low >> offset) | (value_high << space);
}

#define FORWARD_IRQ_LISTENER_WAIT (1 << 0)
#define FORWARD_IRQ_LISTENER_CALLBACK (1 << 1)
#define FORWARD_IRQ_LISTENER_WORKQUEUE (1 << 2)

struct forward_irq_listener {
	struct list_head list;
	int type;
	forward_irq_t mask;
	forward_irq_t flags;
	u64 time;
	spinlock_t slock;
	wait_queue_head_t wait;
	struct work_struct work;
	int work_queue;
	int priority;
	void *private;
	void (*func)(void *, const forward_irq_t *, u64);
	void (*wq_func)(void *, const forward_irq_t *, u64);
};

void forward_irq_listener_init(struct forward_irq_listener *listener);
int forward_irq_listener_add(struct forward_dev *dev, struct forward_irq_listener *listener);
struct forward_irq_listener *forward_irq_listener_find(struct forward_dev *dev, void *private);
int forward_irq_listener_remove(struct forward_dev *dev, struct forward_irq_listener *listener);
forward_irq_t forward_irq_listener_wait(struct forward_irq_listener *listener, bool clear,
					int timeout);
forward_irq_t forward_irq_listener_wait_kthread(struct forward_irq_listener *listener, bool clear,
						int timeout);
void forward_irq_listener_clear(struct forward_irq_listener *listener);

int forward_irq_probe(struct forward_dev *dev);
void forward_irq_remove(struct forward_dev *dev);

#endif
