#include "sja1000.h"

//-----------------------------------------------------------------------------
void hard_reset(int port)
{
	/* Аппаратный сброс канала платы CAN-200 */
	outb(1, port + 0x1e);
}

//-----------------------------------------------------------------------------
unsigned char test_reset_request(int port)
{
	/* Получение режима работы/сброса канала платы CAN */
	unsigned char tmp;

	tmp = inb(port);
	if( (tmp & 0x01) == 1 )
	{
		return ResetMode;
	}
	else
	{
		return NormalMode;
	}
}

//-----------------------------------------------------------------------------
void reset_request(int port, unsigned char mode)
{
	/* Установка режима работы/сброса канала платы CAN */
	unsigned char tmp;

	tmp = test_reset_request(port);
	if( tmp != mode )
	{
		/* Переходим в другое состояние */
		tmp = inb(port) & 0xfe;
		outb(tmp | (mode & 0x01), port);
		/* Ждем перехода в режим */
		while( test_reset_request(port) != mode );
	}
}

//-----------------------------------------------------------------------------
void set_interrupt(int port, unsigned char interrupt)
{
	/* Установка прерываний канала платы CAN */
	unsigned char mode;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode == BasicCAN )
	{
		interrupt = interrupt & 0x0f;
	}

	if( mode == BasicCAN )
	{
		outb((interrupt << 1) & 0x1e, port);
	}
	else
	{
		outb(interrupt, port + 0x04);
	}
}

//-----------------------------------------------------------------------------
unsigned char get_work_mode(int port)
{
	/* Получение режима работы канала платы CAN */
	unsigned char mode;

	mode = inb(port + 0x1f);
	if( (mode & 0x80) == 0 )
	{
		return BasicCAN;
	}
	else
	{
		return PeliCAN;
	}
}

//-----------------------------------------------------------------------------
void set_work_mode(int port, unsigned char mode)
{
	/* Установка режима работы канала платы CAN */
	unsigned char tmp;

	/* Устанавливаем заданный режим работы канала */
	tmp = inb(port + 0x1f) & 0x7f;
	outb(tmp | (mode << 7), port + 0x1f);
}

//-----------------------------------------------------------------------------
void set_CAN_speed(int port, unsigned int speed)
{
	/* Установка скорости работы канала платы */
    switch (speed & 0xffff)
	{
	case CAN_SPEED_1000:
		{
			outb(0x40, port + 0x06);
			outb(0x23, port + 0x07);
			break;
		}
	case CAN_SPEED_800:
		{
			outb(0x40, port + 0x06);
			outb(0x25, port + 0x07);
			break;
		}
	case CAN_SPEED_500:
		{
			outb(0x40, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
	case CAN_SPEED_250:
		{
			outb(0x41, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
	case CAN_SPEED_125:
		{
			outb(0x43, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
	case CAN_SPEED_50:
		{
			outb(0x49, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
	case CAN_SPEED_20:
		{
			outb(0x58, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
	case CAN_SPEED_10:
		{
			outb(0x71, port + 0x06);
			outb(0x2b, port + 0x07);
			break;
		}
    default:
        {
            if (IS_CAN_SPEED_USER_DEFINED(speed))
            {
                outb(CAN_SPEED_GET_BTR0(speed), port + 0x06);
                outb(CAN_SPEED_GET_BTR1(speed), port + 0x07);
            }
            break;
        }
	}
}

//-----------------------------------------------------------------------------
void set_driver_mode(int port)
{
	/* Настройка выходного формирователя CAN-контроллера */
	outb(0x1b, port + 0x08);
}

//-----------------------------------------------------------------------------
unsigned char get_interrupt(int port)
{
	/* Получение статуса прерывания канала платы CAN */
	unsigned char mask, mode;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode == BasicCAN )
	{
		mask = inb(port) & 0x1e;
		mask = mask >> 1;
	}
	else
	{
		mask = inb(port + 0x04);
	}

	return (inb(port + 0x03) & mask);
}

//-----------------------------------------------------------------------------
void set_command(int port, unsigned char command)
{
	/* Запись в регистр команд канала платы CAN */
	outb(command, port + 0x01);
	return;
}

//-----------------------------------------------------------------------------
void read_message(int port, CAN_data *packet)
{
	/* Чтение принятого кадра */
	int i;
	unsigned char tmp, mode;

	/* В режиме BasicCAN можно выдавать только стандартные кадры */
	mode = get_work_mode(port);
	if( mode == BasicCAN )
	{
		/* Прием стандартного кадра */
		packet->type = BasicCAN;
		tmp = inb(port + 0x15);
		packet->id = (inb(port + 0x14) << 3) | (tmp >> 5);
		packet->rtr = (tmp >> 4) & 0x01;
		packet->dlc = tmp & 0x0f;
		for(i = 0; i < packet->dlc && i < 8; i++)
		{
			packet->data[i] = inb(port + 0x16 + i);
		}
	}
	else
	{
		/* Расширенный режим */
		tmp = inb(port + 0x10);
		if( (tmp & 0x80) == 0 )
		{
			/* Стандартный кадр */
			packet->type = BasicCAN;
			packet->rtr = (tmp >> 6) & 0x01;
			packet->dlc = tmp & 0x0f;
			packet->id = (inb(port + 0x11) << 3) | ((inb(port + 0x12) >> 5) & 0x07);
			for(i = 0; i < packet->dlc && i < 8; i++)
			{
				packet->data[i] = inb(port + 0x13 + i);
			}
		}
		else
		{
			/* Расширенный кадр */
			packet->type = PeliCAN;
			packet->rtr = (tmp >> 6) & 0x01;
			packet->dlc = tmp & 0x0f;
			packet->id = inb(port + 0x11) << 21;
			packet->id += inb(port + 0x12) << 13;
			packet->id += inb(port + 0x13) << 5;
			packet->id += inb(port + 0x14) >> 3;
			for(i = 0; i < packet->dlc && i < 8; i++)
			{
				packet->data[i] = inb(port + 0x15 + i);
			}
		}
	}
	return;
}

//-----------------------------------------------------------------------------
int write_message(int port, CAN_data *packet, int repetition)
{
	/* Выдача одного кадра по шине CAN */
	int i;
	unsigned char tmp, mode;
	union CAN_status_t status;

	/* В режиме BasicCAN можно выдавать только стандартные кадры */
	mode = get_work_mode(port);
	if( (mode == BasicCAN) && (packet->type == PeliCAN) )
	{
        return -1;
	}

	/* Проверяем на доступность буфер передачи */
	status.byte = get_status(port);
	if( status.bit.tbs == 0 )
	{
        return -2;
	}

	if( mode == BasicCAN )
	{
        /* Канал в режиме BasicCAN */
		tmp = (unsigned char)(packet->id >> 3);
		outb(tmp, port + 0x0a);
        tmp = (unsigned char)((packet->id & 0x07) << 5) | ((packet->rtr & 0x01) << 4) | (packet->dlc & 0x0f);
		outb(tmp, port + 0x0b);
        for(i = 0; i < packet->dlc; i++)
		{
			outb(packet->data[i], port + 0x0c + i);
        }
	}
	else
	{
        /* Канал в режиме PeliCAN */
		if( packet->type == BasicCAN )
		{
			/* Для стандартного кадра */
			tmp = ((packet->rtr & 0x01) << 6) | (packet->dlc & 0x0f);
			outb(tmp, port + 0x10);
			outb((unsigned char)(packet->id >> 3), port + 0x11);
			outb((unsigned char)((packet->id & 0x07) << 5), port + 0x12);
			for(i = 0; i < packet->dlc; i++)
			{
				outb(packet->data[i], port + 0x13 + i);
			}
		}
		else
		{
			/* Для расширенного кадра */
			tmp = 0x80 | ((packet->rtr & 0x01) << 6) | (packet->dlc & 0x0f);
			outb(tmp, port + 0x10);
			outb((unsigned char)(packet->id >> 21), port + 0x11);
			outb((unsigned char)(packet->id >> 13), port + 0x12);
			outb((unsigned char)(packet->id >> 5), port + 0x13);
			outb((unsigned char)(packet->id << 3), port + 0x14);
			for(i = 0; i < packet->dlc; i++)
			{
				outb(packet->data[i], port + 0x15 + i);
			}
		}
	}

	/* Команда на выдачу кадра */
	if (0 == repetition)
	{
        outb(0x01, port + 0x01); /* Выдача */
		while (1)
		{
			status.byte = get_status(port);
			if (1 == status.bit.tcs || 1 == status.bit.tbs)
			{
                /* Возврат */
				return 0;
			}
			else if (1 == status.bit.ts)
			{
                outb(0x02, port + 0x01); /* Однократная выдача */
				return 0;
			}
		}
	}
	else
	{
		outb(0x01, port + 0x01); /* Выдача с автоматическим повтором */
	}

	return 0;
}

//-----------------------------------------------------------------------------
unsigned int get_CAN_speed(int port)
{
	/* Получение скорости работы канала платы */
    unsigned char btr0;
    unsigned char btr1;

    btr0 = inb(port + 0x06);
    btr1 = inb(port + 0x07);
    switch (btr0)
	{
	case 0x40:
		{
            switch (btr1)
			{
			case 0x23:
				{
					return CAN_SPEED_1000;
				}
			case 0x25:
				{
					return CAN_SPEED_800;
				}
			case 0x2b:
				{
					return CAN_SPEED_500;
				}
			default:
				{
                    return CAN_SPEED_USER_DEFINED(btr0, btr1);
				}
			}
		}
	case 0x41:
		{
            if (0x2b == btr1)
			{
				return CAN_SPEED_250;
			}
			else
			{
                return CAN_SPEED_USER_DEFINED(btr0, btr1);
			}
		}
	case 0x43:
		{
            if (0x2b == btr1)
			{
				return CAN_SPEED_125;
			}
			else
			{
                return CAN_SPEED_USER_DEFINED(btr0, btr1);
			}
		}
	case 0x49:
		{
            if (0x2b == btr1)
			{
				return CAN_SPEED_50;
			}
			else
			{
                return CAN_SPEED_USER_DEFINED(btr0, btr1);
			}
		}
	case 0x58:
		{
            if (0x2b == btr1)
			{
				return CAN_SPEED_20;
			}
			else
			{
                return CAN_SPEED_USER_DEFINED(btr0, btr1);
			}
		}
	case 0x71:
		{
            if (0x2b == btr1)
			{
				return CAN_SPEED_10;
			}
			else
			{
                return CAN_SPEED_USER_DEFINED(btr0, btr1);
			}
		}
	}
    return CAN_SPEED_USER_DEFINED(btr0, btr1);
}

//-----------------------------------------------------------------------------
unsigned char get_status(int port)
{
	/* Получение статуса канала платы CAN */
	return inb(port + 0x02);
}

//-----------------------------------------------------------------------------
int b_set_filter(int port, struct b_filter_t *b_filter)
{
	/* Установка фильтра режима BasicCAN канала платы CAN */
	unsigned char mode;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode != BasicCAN )
	{
		return -1;
	}

	outb(b_filter->filter, port + 0x04);
	outb(b_filter->mask, port + 0x05);

	return 0;
}

//-----------------------------------------------------------------------------
int b_get_filter(int port, struct b_filter_t *b_filter)
{
	/* Получение фильтра режима BasicCAN канала платы CAN */
	unsigned char mode;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode != BasicCAN )
	{
		return -1;
	}

	b_filter->filter = inb(port + 0x04);
	b_filter->mask = inb(port + 0x05);

	return 0;
}

//-----------------------------------------------------------------------------
int p_set_filter(int port, struct p_filter_t *p_filter)
{
	/* Установка фильтра режима PeliCAN канала платы CAN */
	unsigned char mode, tmp;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	/* Задаем фильтр */
	tmp = inb(port);
	tmp = (tmp & 0xf7) | ((p_filter->mode & 0x01) << 3);
	outb(tmp, port);
	outb(p_filter->filter[0], port + 0x10);
	outb(p_filter->filter[1], port + 0x11);
	outb(p_filter->filter[2], port + 0x12);
	outb(p_filter->filter[3], port + 0x13);
	outb(p_filter->mask[0], port + 0x14);
	outb(p_filter->mask[1], port + 0x15);
	outb(p_filter->mask[2], port + 0x16);
	outb(p_filter->mask[3], port + 0x17);

	return 0;
}

//-----------------------------------------------------------------------------
int p_get_filter(int port, struct p_filter_t *p_filter)
{
	/* Получение фильтра режима PeliCAN канала платы CAN */
	unsigned char mode, tmp;

	/* Определяем режим работы платы */
	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	/* Задаем фильтр */
	tmp = inb(port);
	p_filter->mode = (tmp >> 3) & 0x01;
	p_filter->filter[0] = inb(port + 0x10);
	p_filter->filter[1] = inb(port + 0x11);
	p_filter->filter[2] = inb(port + 0x12);
	p_filter->filter[3] = inb(port + 0x13);
	p_filter->mask[0] = inb(port + 0x14);
	p_filter->mask[1] = inb(port + 0x15);
	p_filter->mask[2] = inb(port + 0x16);
	p_filter->mask[3] = inb(port + 0x17);

	return 0;
}

//-----------------------------------------------------------------------------
int get_rx_error_counter(int port, unsigned char *counter)
{
	/* Получение числа ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	*counter = inb(port + 0x0e);
	return 0;
}

//-----------------------------------------------------------------------------
int get_tx_error_counter(int port, unsigned char *counter)
{
	/* Получение числа ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	*counter = inb(port + 0x0f);
	return 0;
}

//-----------------------------------------------------------------------------
int get_error_warning_limit(int port, unsigned char *counter)
{
	/* Получение верхнего предела счетчиков ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	*counter = inb(port + 0x0d);
	return 0;
}

//-----------------------------------------------------------------------------
int set_rx_error_counter(int port, unsigned char counter)
{
	/* Установка числа ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	outb(counter, port + 0x0e);

	return 0;
}

//-----------------------------------------------------------------------------
int set_tx_error_counter(int port, unsigned char counter)
{
	/* Установка числа ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	outb(counter, port + 0x0f);

	return 0;
}

//-----------------------------------------------------------------------------
int set_error_warning_limit(int port, unsigned char counter)
{
	/* Установка верхнего предела счетчиков ошибок при обмене по шине CAN (режим PeliCAN) */
	unsigned char mode;

	mode = get_work_mode(port);
	if( mode != PeliCAN )
	{
		return -1;
	}

	outb(counter, port + 0x0d);

	return 0;
}

//-----------------------------------------------------------------------------
int get_error_code(int port, unsigned char *code)
{
	/* Получение кода ошибки канала платы CAN */
	if (get_work_mode(port) != PeliCAN)
	{
		*code = 0;
		return -1;
	}
	*code = inb(port + 0x0c);
	return 0;
}

//-----------------------------------------------------------------------------
int get_lost_capture(int port, unsigned char *data)
{
	/* Получение номера бита на котором канала платы CAN проиграл арбитраж */
	if (get_work_mode(port) != PeliCAN)
	{
		*data = 0;
		return -1;
	}
	*data = inb(port + 0x0b);
	return 0;
}

//-----------------------------------------------------------------------------
int get_rx_message_counter(int port, unsigned char *data)
{
	/* Получение количество пакетов в буфере FIFO */
	if (get_work_mode(port) != PeliCAN)
	{
		*data = 0;
		return -1;
	}
	*data = inb(port + 0x1d);
	return 0;
}
