#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>

#include "can.h"

#define CAN200PCI_DEVICE_ID				0x9030
#define CAN200PCI_VENDOR_ID				0x10b5
#define CAN200PCI_SUB_DEVICE_ID			0x0301
#define CAN200PCI_SUB_VENDOR_ID			0xe1c5

#define CAN200PCIE_DEVICE_ID			0x9056
#define CAN200PCIE_VENDOR_ID			0x10b5
#define CAN200PCIE_SUB_DEVICE_ID		0x0302
#define CAN200PCIE_SUB_VENDOR_ID		0xe1c5

#define CAN200_104PCI_DEVICE_ID			0x9030
#define CAN200_104PCI_VENDOR_ID			0xE1C5
#define CAN200_104PCI_SUB_DEVICE_ID		0x0303
#define CAN200_104PCI_SUB_VENDOR_ID		0xe1c5

//---------------------------------------------------------------------------
static int can200pci_read_vpd(struct CAN_info_t *info, struct pci_dev *dev);

//-----------------------------------------------------------------------------
int find_can200pci(struct can_t *can, int max_count)
{
	/* Поиск устройств CAN-200PCI */
	int res;
	int found;
	struct pci_dev *dev;
	void *ports;

	/* Ищем устройства с DeviceID == 0x9030 и VendorID == 0x10b5 */
	found = 0;
	dev = NULL;
	while (found < max_count)
	{
		dev = pci_get_device(CAN200PCI_VENDOR_ID, CAN200PCI_DEVICE_ID, dev);
		if (NULL == dev)
		{
			break;
		}
		if ((CAN200PCI_SUB_VENDOR_ID == dev->subsystem_vendor) && (CAN200PCI_SUB_DEVICE_ID == dev->subsystem_device))
		{
			/* Разрешаем устройство */
			res = pci_enable_device(dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error pci_enable_device.\n");
				return -1;
			}
			(can + found)->pci_enable_device = 1;

			/* CAN200PCI */
			(can + found)->dev = dev;
			/* Номер прерывания */
			(can + found)->wIrql = dev->irq;
			/* Тип платы */
			(can + found)->type = CAN200PCI;

			/* Информация о плате */
			res = can200pci_read_vpd(&(can + found)->info, dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error can200pci_read_vpd.\n");
				return -1;
			}

			/* Порты ввода-вывода двух каналов */
			(can + found)->wPorts[0] = pci_resource_start(dev, 2);
			(can + found)->wSizeRegion[0] = pci_resource_end(dev, 2) - pci_resource_start(dev, 2);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[0], (can + found)->wSizeRegion[0], "CAN-200PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[0] = 0;
				return -1;
			}

			(can + found)->wPorts[1] = pci_resource_start(dev, 3);
			(can + found)->wSizeRegion[1] = pci_resource_end(dev, 3) - pci_resource_start(dev, 3);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[1], (can + found)->wSizeRegion[1], "CAN-200PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[1] = 0;
				return -1;
			}
			
			found = found + 1;
		}
	}

	return found;
}

//-----------------------------------------------------------------------------
int find_can200pcie(struct can_t *can, int max_count)
{
	/* Поиск устройств CAN-200PCIe */
	int             res;
	int             found;
	struct pci_dev *dev;
	void           *ports;
	unsigned long   port;
	unsigned long   count;

	/* Ищем устройства с DeviceID == 0x9056 и VendorID == 0x10b5 */
	found = 0;
	dev = NULL;
	while (found < max_count)
	{
		dev = pci_get_device(CAN200PCIE_VENDOR_ID, CAN200PCIE_DEVICE_ID, dev);
		if (NULL == dev)
		{
			break;
		}
		if ((CAN200PCIE_SUB_VENDOR_ID == dev->subsystem_vendor) && (CAN200PCIE_SUB_DEVICE_ID == dev->subsystem_device))
		{
			/* Информация о плате */
			res = can200pci_read_vpd(&(can + found)->info, dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error can200pci_read_vpd.\n");
				return -1;
			}

			/* Разрешаем устройство */
			res = pci_enable_device(dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error pci_enable_device.\n");
				return -1;
			}
			(can + found)->pci_enable_device = 1;

			/* CAN200PCIe */
			(can + found)->dev = dev;
			/* Номер прерывания */
			(can + found)->wIrql = dev->irq;
			/* Тип платы */
			(can + found)->type = CAN200PCIE;

			/* Порты ввода-вывода двух каналов */
			(can + found)->wPorts[0] = pci_resource_start(dev, 2);
			(can + found)->wSizeRegion[0] = pci_resource_end(dev, 2) - pci_resource_start(dev, 2);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[0], (can + found)->wSizeRegion[0], "CAN-200PCIE");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[0] = 0;
				return -1;
			}

			(can + found)->wPorts[1] = pci_resource_start(dev, 3);
			(can + found)->wSizeRegion[1] = pci_resource_end(dev, 3) - pci_resource_start(dev, 3);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[1], (can + found)->wSizeRegion[1], "CAN-200PCIE");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[1] = 0;
				return -1;
			}
			/* Разрешаем прерывания (регистр PCI + 0x68) */
			port = pci_resource_start(dev, 1);
			count = pci_resource_end(dev, 1) - pci_resource_start(dev, 1);
			ports = request_region(port, count, "CAN200PCIE");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				return -1;
			}
			outl(inl(port + 0x68) | 0x900, port + 0x68);
			release_region(port, count);
			found = found + 1;
		}
	}

	return found;
}

//-----------------------------------------------------------------------------
int find_can200_104pci(struct can_t *can, int max_count)
{
	/* Поиск устройств CAN-200-104PCI */
	int res;
	int found;
	struct pci_dev *dev;
	void *ports;

	/* Ищем устройства с DeviceID == CAN200_104PCI_DEVICE_ID и VendorID == CAN200_104PCI_VENDOR_ID */
	found = 0;
	dev = NULL;
	while (found < max_count)
	{
		dev = pci_get_device(CAN200_104PCI_VENDOR_ID, CAN200_104PCI_DEVICE_ID, dev);
		if (NULL == dev)
		{
			break;
		}
		if ((CAN200_104PCI_SUB_VENDOR_ID == dev->subsystem_vendor) && (CAN200_104PCI_SUB_DEVICE_ID == dev->subsystem_device))
		{
			/* Разрешаем устройство */
			res = pci_enable_device(dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error pci_enable_device.\n");
				return -1;
			}
			(can + found)->pci_enable_device = 1;

			/* CAN-200-104PCI */
			(can + found)->dev = dev;
			/* Номер прерывания */
			(can + found)->wIrql = dev->irq;
			/* Тип платы */
			(can + found)->type = CAN200_104PCI;

			/* Информация о плате */
			res = can200pci_read_vpd(&(can + found)->info, dev);
			if (0 != res)
			{
				printk(KERN_ALERT "Error can200pci_read_vpd.\n");
				return -1;
			}

			/* Порты ввода-вывода 4-х каналов */
			(can + found)->wPorts[0] = pci_resource_start(dev, 2);
			(can + found)->wSizeRegion[0] = pci_resource_end(dev, 2) - pci_resource_start(dev, 2);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[0], (can + found)->wSizeRegion[0], "CAN-200-104PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[0] = 0;
				return -1;
			}

			(can + found)->wPorts[1] = pci_resource_start(dev, 3);
			(can + found)->wSizeRegion[1] = pci_resource_end(dev, 3) - pci_resource_start(dev, 3);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[1], (can + found)->wSizeRegion[1], "CAN-200-104PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[1] = 0;
				return -1;
			}

			(can + found)->wPorts[2] = pci_resource_start(dev, 4);
			(can + found)->wSizeRegion[2] = pci_resource_end(dev, 4) - pci_resource_start(dev, 4);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[2], (can + found)->wSizeRegion[2], "CAN-200-104PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[2] = 0;
				return -1;
			}

			(can + found)->wPorts[3] = pci_resource_start(dev, 5);
			(can + found)->wSizeRegion[3] = pci_resource_end(dev, 5) - pci_resource_start(dev, 5);
			/* Регистрируем используемые нами порты ввода-вывода */
			ports = request_region((can + found)->wPorts[3], (can + found)->wSizeRegion[2], "CAN-200-104PCI");
			if (NULL == ports)
			{
				printk(KERN_ALERT "Ports request region error.\n");
				(can + found)->wPorts[3] = 0;
				return -1;
			}

			found = found + 1;
		}
	}

	return found;
}

//---------------------------------------------------------------------------
static int can200pci_read_vpd(struct CAN_info_t *info, struct pci_dev *dev)
{
	int            res;
	int            i;
	unsigned short reg;
	unsigned short wAddr;
	unsigned int   dwWait;
	unsigned int   data[128];
	unsigned int   cb;
	unsigned char *ptr;
	
	memset(data, 0, 128 * sizeof(unsigned int));

	i = 0;
	/* Читаем имя и серийный номер платы */
	for (wAddr = 0x88; wAddr < 0x200; wAddr += 4)
	{
		dwWait = 0;
		/* Записываем адрес */
		res = pci_write_config_word(dev, 0x4e, wAddr);
		if (0 != res)
		{
			/* Ошибка записи адреса */
			printk(KERN_ALERT "PCI bus write error (%x).\n", res);
			return -1;
		}
		/* Ждем окончания чтения */
		do
		{
			dwWait = dwWait + 1;
			res = pci_read_config_word(dev, 0x4e, &reg);
			if (0 != res)
			{
				/* Ошибка чтения готовности */
				printk(KERN_ALERT "PCI bus read error.\n");
				return -1;
			}
			if (0 == dwWait % 500)
			{
				/* Повтор выдачи адреса */
				res = pci_write_config_word(dev, 0x4e, wAddr);
				if (0 != res)
				{
					/* Ошибка записи адреса */
					printk(KERN_ALERT "PCI bus write error.\n");
					return -1;
				}
			}
		} while (0 == (reg & 0x8000));
		/* Читаем данные */
		res = pci_read_config_dword(dev, 0x50, &data[i]);
		if (0 != res)
		{
			/* Ошибка чтения готовности */
			printk(KERN_ALERT "PCI bus read error.\n");
			return -1;
		}
		i = i + 1;
	}
	/* Длина имени */
	cb = (data[0] >> 8) & 0xff;
	ptr = (unsigned char *)&data[0];
	/* Имя платы */
	memcpy(info->szName, ptr + 3, cb);
	info->szName[cb] = 0;
	/* Серийный номер платы */
	cb = 0;
	for (i = 0; i < 80; i++)
	{
		if (0x4e53 == *(unsigned short *)(ptr + i))
		{
			cb = *(ptr + i + 2);
			memcpy(info->szSN, ptr + i + 3, cb);
		}
	}
	info->szSN[cb] = 0;

	return 0;
}

//-----------------------------------------------------------------------------
void can_get_info(struct CAN_info_t *info, int dev, int channel)
{
	/* Получение информации о плате */
	memcpy(info, &can[dev].info, sizeof(struct CAN_info_t));
	info->wPorts = can[dev].wPorts[channel];
	info->wIRQ = can[dev].wIrql;
}
