/*
 * main.c - модуль ядра CAN-200
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <asm/atomic.h>

#include "can.h"
#include "can_open.h"
#include "can_release.h"
#include "can_ioctl.h"
#include "can_write.h"
#include "can_irq.h"
#include "sja1000.h"
#include "can_read.h"
#include "can_poll.h"

struct can_t can[CAN_MAX_DEVICE]; /* Массив для хранения информации о платах */
int can_count = 0; /* Суммарное число плат */
int can_major = 0; /* Старший номер устройства */
struct class *can_class; /* Класс устройства для udev */

int can_major_register = 0; /* Признак успешной регистрации старшего номера */
int can_class_create = 0; /* Признак успешного создания класса */

struct file_operations can_fops =
{
	open:		can_open,
	release:	can_release,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
    unlocked_ioctl:	can_ioctl,
#else
    ioctl:  	can_ioctl,
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) */
    write:		can_write,
	read:		can_read,
	poll:		can_poll,
	owner:		THIS_MODULE
};

int porta0, portb0, irq0;  /* Базовые адреса и номер прерывания для плат ISA */
int porta1, portb1, irq1;
int porta2, portb2, irq2;
int porta3, portb3, irq3;
int porta4, portb4, irq4;
int porta5, portb5, irq5;
int porta6, portb6, irq6;
int porta7, portb7, irq7;
int porta8, portb8, irq8;
int porta9, portb9, irq9;
int hardware_repetition;   /* Режим выдачи пакета с аппаратным повтором */ 
int tx_counter;            /* Количество повторов для выдачи пакета */ 

/* Параметры настройки модуля (для плат не поддерживающих pnp) */
module_param(porta0, int, 0);
module_param(portb0, int, 0);
module_param(irq0, int, 0);
MODULE_PARM_DESC(porta0, "Base port channel 1 device 0");
MODULE_PARM_DESC(portb0, "Base port channel 2 device 0");
MODULE_PARM_DESC(irq0, "Interrupt device 0");

module_param(porta1, int, 0);
module_param(portb1, int, 0);
module_param(irq1, int, 0);
MODULE_PARM_DESC(porta1, "Base port channel 1 device 1");
MODULE_PARM_DESC(portb1, "Base port channel 2 device 1");
MODULE_PARM_DESC(irq1, "Interrupt device 1");

module_param(porta2, int, 0);
module_param(portb2, int, 0);
module_param(irq2, int, 0);
MODULE_PARM_DESC(porta2, "Base port channel 1 device 2");
MODULE_PARM_DESC(portb2, "Base port channel 2 device 2");
MODULE_PARM_DESC(irq2, "Interrupt device 2");

module_param(porta3, int, 0);
module_param(portb3, int, 0);
module_param(irq3, int, 0);
MODULE_PARM_DESC(porta3, "Base port channel 1 device 3");
MODULE_PARM_DESC(portb3, "Base port channel 2 device 3");
MODULE_PARM_DESC(irq3, "Interrupt device 3");

module_param(porta4, int, 0);
module_param(portb4, int, 0);
module_param(irq4, int, 0);
MODULE_PARM_DESC(porta4, "Base port channel 1 device 4");
MODULE_PARM_DESC(portb4, "Base port channel 2 device 4");
MODULE_PARM_DESC(irq4, "Interrupt device 4");

module_param(porta5, int, 0);
module_param(portb5, int, 0);
module_param(irq5, int, 0);
MODULE_PARM_DESC(porta5, "Base port channel 1 device 5");
MODULE_PARM_DESC(portb5, "Base port channel 2 device 5");
MODULE_PARM_DESC(irq5, "Interrupt device 5");

module_param(porta6, int, 0);
module_param(portb6, int, 0);
module_param(irq6, int, 0);
MODULE_PARM_DESC(porta6, "Base port channel 1 device 6");
MODULE_PARM_DESC(portb6, "Base port channel 2 device 6");
MODULE_PARM_DESC(irq6, "Interrupt device 6");

module_param(porta7, int, 0);
module_param(portb7, int, 0);
module_param(irq7, int, 0);
MODULE_PARM_DESC(porta7, "Base port channel 1 device 7");
MODULE_PARM_DESC(portb7, "Base port channel 2 device 7");
MODULE_PARM_DESC(irq7, "Interrupt device 7");

module_param(porta8, int, 0);
module_param(portb8, int, 0);
module_param(irq8, int, 0);
MODULE_PARM_DESC(porta8, "Base port channel 1 device 8");
MODULE_PARM_DESC(portb8, "Base port channel 2 device 8");
MODULE_PARM_DESC(irq8, "Interrupt device 8");

module_param(porta9, int, 0);
module_param(portb9, int, 0);
module_param(irq9, int, 0);
MODULE_PARM_DESC(porta9, "Base port channel 1 device 9");
MODULE_PARM_DESC(portb9, "Base port channel 2 device 9");
MODULE_PARM_DESC(irq9, "Interrupt device 9");

module_param(hardware_repetition, int, 0);
MODULE_PARM_DESC(hardware_repetition, "Hardware retransmission mode");
module_param(tx_counter, int, 0);
MODULE_PARM_DESC(tx_counter, "Retransmission counter");

MODULE_DESCRIPTION("Driver for Elcus (http://www.elcus.ru) CAN-200 boards");

//-----------------------------------------------------------------------------
static void can_cleanup(void);
/* Освобождаем все выделенные драйвером ресурсы */

//-----------------------------------------------------------------------------
static int test_base_port(int port);
/* Проверка на допустимость базового порта платы */

/*
 * can_init - функция инициализации, вызывается при загрузке модуля.
 * В случае успешной загрузки модуля возврашает значение нуль,
 * и ненулевое значение в противном случае.
 */
//-----------------------------------------------------------------------------
static int can_init(void)
{
	int                    i, j;
	int                    res;
	union CAN_interrupt_t  interrupt;
	void                  *ports;
	int                    porta[CAN_MAX_DEVICE], portb[CAN_MAX_DEVICE], irq[CAN_MAX_DEVICE];
	int                    count;
	struct p_filter_t      p_filter;

	can_major = 0;
	/* Проверка входных параметров */
	if ((tx_counter < 1) || (tx_counter > 256))
	{
		tx_counter = TX_COUNTER;
	}
	if (0 != hardware_repetition)
	{
		/* Режим выдачи пакета с аппаратным повтором */
		hardware_repetition = CAN200_ENABLE_HARDWARE_REPETITION;
	}
	else
	{
		/* Режим выдачи пакета с программным повтором */
		hardware_repetition = CAN200_DISABLE_HARDWARE_REPETITION;
	}

	/* Ищем платы CAN200PCI */
	count = find_can200pci(can, CAN_MAX_DEVICE);
	if (count < 0)
	{
		printk(KERN_ALERT "Error find devices.\n");
		can_cleanup();
		return -1;
	}
	can_count = count;

	/* Ищем платы CAN200PCIe */
	count = find_can200pcie(can + can_count, CAN_MAX_DEVICE - can_count);
	if (count < 0)
	{
		printk(KERN_ALERT "Error find devices.\n");
		can_cleanup();
		return -1;
	}
	can_count += count;

	/* Ищем платы CAN-200-PCI104 */
	count = find_can200_104pci(can + can_count, CAN_MAX_DEVICE - can_count);
	if (count < 0)
	{
		printk(KERN_ALERT "Error find devices.\n");
		can_cleanup();
		return -1;
	}
	can_count += count;

	porta[0] = porta0; portb[0] = portb0; irq[0] = irq0;
	porta[1] = porta1; portb[1] = portb1; irq[1] = irq1;
	porta[2] = porta2; portb[2] = portb2; irq[2] = irq2;
	porta[3] = porta3; portb[3] = portb3; irq[3] = irq3;
	porta[4] = porta4; portb[4] = portb4; irq[4] = irq4;
	porta[5] = porta5; portb[5] = portb5; irq[5] = irq5;
	porta[6] = porta6; portb[6] = portb6; irq[6] = irq6;
	porta[7] = porta7; portb[7] = portb7; irq[7] = irq7;
	porta[8] = porta8; portb[8] = portb8; irq[8] = irq8;
	porta[9] = porta9; portb[9] = portb9; irq[9] = irq9;

	for (i = 0; i < CAN_MAX_DEVICE; i++)
	{
		if ((test_base_port(porta[i]) == 0) && (test_base_port(portb[i]) == 0) && (can_count < CAN_MAX_DEVICE))
		{
			can[can_count].wPorts[0] = porta[i];
			ports = request_region(can[can_count].wPorts[0], 0x20, "CAN200");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				can[can_count].wPorts[0] = 0;
				can_cleanup();
				return -1;
			}
			can[can_count].wPorts[1] = portb[i];
			ports = request_region(can[can_count].wPorts[1], 0x20, "CAN200");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				can[can_count].wPorts[1] = 0;
				can_cleanup();
				return -1;
			}
			can[can_count].wIrql = irq[i];
			can[can_count].type = CAN200PC;
			sprintf(can[can_count].info.szName, "CAN-200PC/MP/PC104");
			can[can_count].info.szSN[0] = 0;
			can_count = can_count + 1;
		}
	}

	if (0 == can_count)
	{
		printk(KERN_ALERT "Devices not found.\n");
		can_cleanup();
		return -1;
	}

    printk(KERN_INFO "Driver for CAN-200PC/MP/PC104/PCI/PCIe/104PCI device. Version %d.%d.%d.\n", VERSION, SUBVERSION, SUBSUBVERSION);
    printk(KERN_INFO "Copyleft (c) 2009-2017 Elcus Corporation.\n");
    /* Информация о найденных платах */
	for (i = 0; i < can_count; i++)
	{
		switch (can[i].type)
		{
		case CAN200PCI:
			{
				printk(KERN_INFO "CAN-200PCI device %d.\n", i);
				break;
			}
		case CAN200PCIE:
			{
				printk(KERN_INFO "CAN-200PCIe device %d.\n", i);
				break;
			}
		case CAN200_104PCI:
			{
				printk(KERN_INFO "CAN-200-104PCI device %d.\n", i);
				break;
			}
		default:
			{
				printk(KERN_INFO "CAN-200PC/MP/PC104 device %d.\n", i);
				break;
			}
		}
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
			printk(KERN_INFO "wPorts%d: 0x%x.\n", j + 1, can[i].wPorts[j]);
		}
        printk(KERN_INFO "wIrql: %d.\n", can[i].wIrql);
        if ((CAN200PCI == can[i].type) || (CAN200PCIE == can[i].type) || (CAN200_104PCI == can[i].type))
        {
            printk(KERN_INFO "Serial number: %s.\n", can[i].info.szSN);
        }
	}

	/* Регистрируем префикс в пространстве имен */
	res = register_chrdev(can_major, "can200", &can_fops);
	if (res < 0)
	{
		printk(KERN_ALERT "Error register major number.\n");
		can_cleanup();
		return -1;
	}
	if (0 == can_major)
	{
		/* Старший номер не задан */
		can_major = res;
	}
	can_major_register = 1;

	/* Создаем класс устройства */
#if LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0)
	can_class = class_create(THIS_MODULE, "can200");
#else
	can_class = class_create("can200");
#endif
	if (IS_ERR(can_class))
	{
		/* Ошибка создания класса */
		printk(KERN_ALERT "Error create class.\n");
		can_cleanup();
		return -1;
	}
	can_class_create = 1;

	/* Инициализация */
	for (i = 0; i < can_count; i++)
	{
		can[i].tx_counter = tx_counter;
		can[i].hardware_repetition = hardware_repetition;
		spin_lock_init(&can[i].irq_lock);
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
			can[i].bus_mode[j] = BUS_ON;
			can[i].stop_trans[j] = 0;
			can[i].stopping[j] = 0;
			init_waitqueue_head(&can[i].rx_queue[j]);
			init_waitqueue_head(&can[i].tx_queue[j]);
			can[i].tx_fifo[j] = NULL;
			can[i].rx_fifo[j] = NULL;
			spin_lock_init(&can[i].tx_fifo_lock[j]);
			spin_lock_init(&can[i].rx_fifo_lock[j]);
			mutex_init(&can[i].ioctl_mutex[j]);
		}
	}

	/* Регистрируем обработчики прерываний */
	for (i = 0; i < can_count; i++)
	{
		res = request_irq(can[i].wIrql, can_irq, IRQF_SHARED, "CAN200", (void *)&can[i]);
		if (0 != res)
		{
			printk(KERN_ALERT "Error register interrupt.\n");
			can_cleanup();
			return -1;
		}
		can[i].irq_register = 1;
	}

	interrupt.byte = 0;
	interrupt.bit.ri = 1;
	interrupt.bit.ti = 1;
	interrupt.bit.doi = 1;
	interrupt.bit.ei = 1;

	p_filter.mode = 1;
	p_filter.mask[0] = 0xff;
	p_filter.mask[1] = 0xff;
	p_filter.mask[2] = 0xff;
	p_filter.mask[3] = 0xff;
	p_filter.filter[0] = 0xff;
	p_filter.filter[1] = 0xff;
	p_filter.filter[2] = 0xff;
	p_filter.filter[3] = 0xff;

	/* Разрешаем прерывания от плат, устанавливаем режим работы и скорость */
	for (i = 0; i < can_count; i++)
	{
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
			/* Сбрасываем канал */
			reset_request(can[i].wPorts[j], ResetMode);
			set_work_mode(can[i].wPorts[j], PeliCAN);
			set_CAN_speed(can[i].wPorts[j], CAN_SPEED_1000);
			/* Фильтр */
			p_set_filter(can[i].wPorts[j], &p_filter);
			/* Разрешаем прием/выдачу */
			set_driver_mode(can[i].wPorts[j]);
			set_interrupt(can[i].wPorts[j], interrupt.byte); /* Разрешаем прерывания */
			/* Читаем прерывания для сброса */
			get_interrupt(can[i].wPorts[j]);
			/* Канал остается в состоянии сброса */
		}
	}

	/* Создаем файлы /dev/can200_n */
	for (i = 0; i < can_count; i++)
	{
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
			device_create(can_class, NULL, MKDEV(can_major, (CAN_NUM_CHANNEL * i + j)), NULL, "can200_%d", CAN_NUM_CHANNEL * i + j);
#else
			device_create(can_class, NULL, MKDEV(can_major, (CAN_NUM_CHANNEL * i + j)), "can200_%d", CAN_NUM_CHANNEL * i + j);
#endif
			can[i].device_create[j] = 1;
		}
	}

	return 0;
}

/*
 * can_exit - функция завершения, вызывается при выгрузке модуля.
 */
//-----------------------------------------------------------------------------
static void can_exit(void)
{
	can_cleanup();
	return;
}

//-----------------------------------------------------------------------------
static void can_cleanup(void)
{
	/* Освобождаем все выделенные драйвером ресурсы */
	int i, j;

	for (i = 0; i < can_count; i++)
	{
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
			set_interrupt(can[i].wPorts[j], 0); /* Запрещаем прерывания */
			/* Читаем прерывания для сброса */
			get_interrupt(can[i].wPorts[j]);
		}
	}

	for (i = 0; i < can_count; i++)
	{
		for (j = 0; j < can200_num_ports(&can[i]); j++)
		{
			if (0 != can[i].wPorts[j])
			{
				if (CAN200PCI == can[i].type || CAN200PCIE == can[i].type || CAN200_104PCI == can[i].type)
				{
					release_region(can[i].wPorts[j], can[i].wSizeRegion[j]);
				}
				else
				{
					release_region(can[i].wPorts[j], 0x20);
				}
			}
			if (0 != can[i].device_create[j])
			{
				device_destroy(can_class, MKDEV(can_major, (2 * i + j)));
			}
		}
		if (0 != can[i].irq_register)
		{
			free_irq(can[i].wIrql, (void *)&can[i]);
		}
		if (0 != can[i].pci_enable_device)
		{
			pci_disable_device(can[i].dev);
		}
	}
	if (0 != can_major_register)
	{
		unregister_chrdev(can_major, "can200");
	}
	if (0 != can_class_create)
	{
		class_destroy(can_class);
	}
}

//-----------------------------------------------------------------------------
static int test_base_port(int port)
{
	/* Проверка на допустимость базового порта платы */
	if ((port == 0x100) || (port == 0x120) ||
		(port == 0x140) || (port == 0x160) ||
		(port == 0x180) || (port == 0x1a0) ||
		(port == 0x1c0) || (port == 0x1e0))
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

//------------------------------------------------------------------------------
int can200_num_ports(struct can_t *can)
{
	switch (can->type)
	{
	default:
	case CAN200PCI:
	case CAN200PCIE:
		{
			return 2;
		}
	case CAN200_104PCI:
		{
			return 4;
		}
	}
}

module_init(can_init);
module_exit(can_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhelezkoff Vladimir");

