/*----------------------------------------
프로그램 설명:
라즈베리파이와 FPGA간의 interface를 위한 디바이스 드라이버 프로그램
----------------------------------------*/
/* FPGA LED Ioremap Control
FILE : fpga_fpga_itf_driver.c*/
/*----------------------------------------
사용되는 각종 헤더 파일의 정의
----------------------------------------*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <asm-generic/bitsperlong.h>
/*----------------------------------------
nWE: Write Enable (active low)
nOE: Output Enable (active low)
nCS: Chip Select (active low)
----------------------------------------*/
#define CTRL_nWE 0
#define CTRL_nOE 1
#define CTRL_nCS 2
/*----------------------------------------
라즈베리 파이와 FPGA 간 Memory Mapping이 아닌 GPIO를 이용한 DATA 통신을 이용한다.
FPGA_Interface_driver에서 GPIO를 이용하여 address 12bit 와 data 8bit를 타이밍에 따라 FPGA로 보낸다. address 12bit와 data 8bit 로 해당하는 device에 write/read 한다.
Linux/gpio.h 헤더파일 안을 보면 gpio 구조체는 다음과 같이 정의되어 있다.
-> gpio 구조체
다음과 같이 정의 되어 있으므로 GPIOF_OUT_INIT_LOW 는 0이다.
-> Raspberry pi 27pin maps for FPGA interface
----------------------------------------*/
/*----------------------------------------
gpio 구조체 타입의 배열 iom_fpga_address에 Raspberry pi Address pin 에 해당하는 pin번호,
GPIOF_OUT_INIT_LOW (0), label 값을 gpio 구조체로 묶어 선언한다.
----------------------------------------*/
static struct gpio iom_fpga_address[] = { // A1 ~ A11, A0=LOW
/*{ 10, GPIOF_OUT_INIT_LOW, "ADDRESS 00" },*/ { 11, GPIOF_OUT_INIT_LOW, "ADDRESS 01" },
{ 12, GPIOF_OUT_INIT_LOW, "ADDRESS 02" }, { 13, GPIOF_OUT_INIT_LOW, "ADDRESS 03" },
{ 14, GPIOF_OUT_INIT_LOW, "ADDRESS 04" }, { 15, GPIOF_OUT_INIT_LOW, "ADDRESS 05" },
{ 16, GPIOF_OUT_INIT_LOW, "ADDRESS 06" }, { 17, GPIOF_OUT_INIT_LOW, "ADDRESS 07" },
{ 18, GPIOF_OUT_INIT_LOW, "ADDRESS 08" }, { 19, GPIOF_OUT_INIT_LOW, "ADDRESS 09" },
{ 20, GPIOF_OUT_INIT_LOW, "ADDRESS 10" }, { 21, GPIOF_OUT_INIT_LOW, "ADDRESS 11" },
};
/*----------------------------------------
gpio 구조체 타입의 배열 iom_fpga_data에 Raspberry pi Data pin 에 해당하는 pin번호,
GPIOF_OUT_INIT_LOW (0), label 값을 gpio 구조체로 묶어 선언한다.
----------------------------------------*/
static struct gpio iom_fpga_data[] = {
{ 2, GPIOF_OUT_INIT_LOW, "DATA 0" }, { 3, GPIOF_OUT_INIT_LOW, "DATA 1" },
{ 4, GPIOF_OUT_INIT_LOW, "DATA 2" }, { 5, GPIOF_OUT_INIT_LOW, "DATA 3" },
{ 6, GPIOF_OUT_INIT_LOW, "DATA 4" }, { 7, GPIOF_OUT_INIT_LOW, "DATA 5" },
{ 8, GPIOF_OUT_INIT_LOW, "DATA 6" }, { 9, GPIOF_OUT_INIT_LOW, "DATA 7" },
};
/*----------------------------------------
gpio 구조체 타입의 배열 iom_fpga_control에 Raspberry pi Control pin 에 해당하는 pin번호,
GPIOF_OUT_INIT_LOW (0), label 값을 gpio 구조체로 묶어 선언한다.
nWE: Write Enable (active low)
nOE: Output Enable (active low)
nCS: Chip Select (active low)
----------------------------------------*/
static struct gpio iom_fpga_control[] = {
{ 22, GPIOF_OUT_INIT_LOW, "nWE" },
{ 23, GPIOF_OUT_INIT_LOW, "nOE" },
{ 25, GPIOF_OUT_INIT_LOW, "nCS" },
};
/*----------------------------------------
Raspberry pi의 Address 핀, Data 핀, Control 핀을 default로 설정해주는 함수이다. (초기설정)
Iom_fpga_itf_open( ) 함수 끝에서 실행 된다.
----------------------------------------*/
static void iom_fpga_itf_set_default(void)
{
int i = 0;
// A0 핀은 항상 LOW
gpio_set_value(10, 0); // A0: always set to LOW
// Address 핀이 저장되어있는 gpio타입 구조체 배열 iom_fpga_address의 원소들의 값을
모두 0 (LOW) 으로 설정
for (i=0; i<ARRAY_SIZE(iom_fpga_address); i++) {
gpio_set_value(iom_fpga_address[i].gpio, 0);
}
// Data 핀이 저장되어있는 gpio타입 구조체 배열 iom_fpga_data의 원소들의 값을
모두 0 (LOW) 으로 설정
for (i=0; i<ARRAY_SIZE(iom_fpga_data); i++) {
gpio_set_value(iom_fpga_data[i].gpio, 0);
}
// Control 핀이 저장되어있는 gpio타입 구조체 배열 iom_fpga_control의 원소들의 값을
모두 1 (HIGH) 으로 설정
for (i=0; i<ARRAY_SIZE(iom_fpga_control); i++) {
gpio_set_value(iom_fpga_control[i].gpio, 1);
}
}
/*----------------------------------------
FPGA를 처음 구동시킬 때 이 함수를 호출
----------------------------------------*/
static int iom_fpga_itf_open(void)
{
int ret = 0;
// iom_fpga_address 배열에서 에러가 있는 Address 핀 번호를 출력 및 해당 핀 번호 return
ret = gpio_request_array(iom_fpga_address, ARRAY_SIZE(iom_fpga_address));
if (ret) {
printk(KERN_ERR "Unable to request address GPIOs: %d\n", ret);
return ret;
}
// iom_fpga_data 배열에서 에러가 있는 Data 핀 번호를 출력 및 해당 핀 번호 return
ret = gpio_request_array(iom_fpga_data, ARRAY_SIZE(iom_fpga_data));
if (ret) {
printk(KERN_ERR "Unable to request data GPIOs: %d\n", ret);
return ret;
}
// iom_fpga_control 배열에서 에러가 있는 control 핀 번호를 출력 및 해당 핀 번호 return
ret = gpio_request_array(iom_fpga_control, ARRAY_SIZE(iom_fpga_control));
if (ret) {
printk(KERN_ERR "Unable to request control GPIOs: %d\n", ret);
return ret;
}
iom_fpga_itf_set_default(); // 모든 핀이 정상 상태이면 핀 초기 설정을 해주는 함수 호출
return ret;
}
/*----------------------------------------
FPGA를 사용하지 않을 때, 이 함수를 호출
----------------------------------------*/
static int iom_fpga_itf_release(void)
{
iom_fpga_itf_set_default(); // 핀 초기 설정 함수 호출
// Address, Data, Control 핀이 저장된 구조체 배열 등록 해제
gpio_free_array(iom_fpga_address, ARRAY_SIZE(iom_fpga_address));
gpio_free_array(iom_fpga_data, ARRAY_SIZE(iom_fpga_data));
gpio_free_array(iom_fpga_control, ARRAY_SIZE(iom_fpga_control));
return 0;
}
/*----------------------------------------
데이터를 쓰기 위해 GPIO를 타이밍 제어 하는 함수
RPi3에서 FPGA에 Data를 Write 할 때 동작 순서는 다음과 같다.
1. Address와 Data에 해당하는 GPIO pin에 주소와 데이터 신호를 인가한다.
2. nCS에 해당하는 GPIO pin에 Low를 인가한다.
3. nWE에 해당하는 GPIO pin에 Low를 5us 정도 인가한다.
-> nWE 신호가 Low로 인가되어 있는 동안 FPGA에서 주소와 데이터를 읽어 간다.
따라서 Address와 Data는 nCS가 LOW로 되어있는 동안 유지되어야 한다.
----------------------------------------*/
ssize_t iom_fpga_itf_write(unsigned int addr, unsigned char value)
{
size_t length = 1;
int i = 0;
// 어느 주소에 어떤 데이터를 쓰는지 출력
printk("FPGA WRITE: address = 0x%x, data = 0x%x \n", addr, value);
/* 아래 for문이 작동하는 방식
*/
for (i=0; i<ARRAY_SIZE(iom_fpga_address); i++) {
// 데이터를 쓸 FPGA 주소의 2진수 값을 GPIO 주소 핀에 출력
gpio_set_value(iom_fpga_address[i].gpio, (addr >> i) & 0x1);
}
for (i=0; i<ARRAY_SIZE(iom_fpga_data); i++) {
// FPGA에 쓸 데이터의 2진수 값을 GPIO 데이터 핀에 출력
gpio_set_value(iom_fpga_data[i].gpio, (value >> i) & 0x1);
}
// write를 위해 Control 핀 nCS, nWE 핀에 Low 인가
// nWE 가 Low 로 인가되어 있는 동안 FPGA에서 주소와 데이터를 읽어 감
gpio_set_value(iom_fpga_control[CTRL_nCS].gpio, 0); udelay(1);
gpio_set_value(iom_fpga_control[CTRL_nWE].gpio, 0); udelay(5);
//printk("CS:%d, ", gpio_get_value(iom_fpga_control[CTRL_nCS].gpio));
//printk("WE:%d, ", gpio_get_value(iom_fpga_control[CTRL_nWE].gpio));
//printk("\n");
// Control 핀 High 로 초기화
gpio_set_value(iom_fpga_control[CTRL_nWE].gpio, 1);
gpio_set_value(iom_fpga_control[CTRL_nCS].gpio, 1);
/*
// Debugging...
for (i=0; i<ARRAY_SIZE(iom_fpga_address); i++) {
printk("Address(%d):%d, ", i, gpio_get_value(iom_fpga_address[i].gpio));
}
printk("\n");
for (i=0; i<ARRAY_SIZE(iom_fpga_data); i++) {
printk("Data(%d):%d, ", i, gpio_get_value(iom_fpga_data[i].gpio));
}
printk("\n");
printk("CS:%d, ", gpio_get_value(iom_fpga_control[CTRL_nCS].gpio));
printk("WE:%d, ", gpio_get_value(iom_fpga_control[CTRL_nWE].gpio));
printk("\n");
*/
return length; // 해당 IO 함수의 성공여부를 리턴
}
EXPORT_SYMBOL(iom_fpga_itf_write); // 커널 심볼 테이블 등록 함수 : 커널의 다른 드라이버가 사용함
/*----------------------------------------
데이터를 읽어오기 위해 GPIO를 타이밍 제어하는 함수
RPi3에서 FPGA에 Data를 Read 할 때 동작 순서는 다음과 같다.
1. Address와 Data에 해당하는 GPIO pin에 FPGA Device의 주소를 인가한다.
2. nCS에 해당하는 GPIO pin에 LOW를 인가한다.
3. nOE에 해당하는 GPIO pin에 LOW를 인가한다.
-> nOE 신호가 Low로 인가되어 있는 동안 데이터를 읽어 온다.
따라서 Address는 nCS가 Low로 인가되어 있는 동안 유지되어야 한다.
----------------------------------------*/
unsigned char iom_fpga_itf_read(unsigned int addr)
{
unsigned char value = 0;
int i = 0;
// 데이터를 쓸 FPGA 주소의 2진수 값을 GPIO 주소 핀에 출력
for (i=0; i<ARRAY_SIZE(iom_fpga_address); i++) {
gpio_set_value(iom_fpga_address[i].gpio, (addr >> i) & 0x1);
}
// Read를 위해 Control 핀 nCS, nOE 핀에 Low 인가
// nOE 신호가 Low로 인가되어 있는 동안 데이터를 읽어 옴
gpio_set_value(iom_fpga_control[CTRL_nCS].gpio, 0); udelay(1);
gpio_set_value(iom_fpga_control[CTRL_nOE].gpio, 0); udelay(1);
// nOE 신호가 Low로 인가되어 있는 동안 데이터를 읽어 value에 저장
for (i=0; i<ARRAY_SIZE(iom_fpga_data); i++) {
value += gpio_get_value(iom_fpga_data[i].gpio) << i;
}
// Control 핀 High 로 초기화
gpio_set_value(iom_fpga_control[CTRL_nCS].gpio, 1);
gpio_set_value(iom_fpga_control[CTRL_nOE].gpio, 1);
// 해당 주소에서 읽은 데이터 출력
printk("FPGA READ: address = 0x%x, data = 0x%x \n", addr, value);
return value; // 해당 주소에서 읽은 데이터 반환
}
EXPORT_SYMBOL(iom_fpga_itf_read); // 커널 심볼 테이블 등록 함수 : 커널의 다른 드라이버가 사용함
// module_init 함수에 의해 한번 호출되는 함수, iom_fpga_itf_open( ) 함수를 실행
int __init iom_fpga_itf_init(void)
{
printk("init module: %s\n", __func__);
iom_fpga_itf_open();
return 0;
}
// module_exit 함수에 의해 한번 호출되는 함수, iom_fpga_itf_release( ) 함수를 실행
void __exit iom_fpga_itf_exit(void)
{
printk("exit module: %s\n", __func__);
iom_fpga_itf_release();
}
/*----------------------------------------
module_init 매크로는 커널에게 모듈이 로딩되었을 때 호출되어야 하는 함수를 알려주는 역할을 한다.
그리고 모듈이 하게 되는 모든 일들이 바로 이 초기화 함수가 호출하는 함수에 의해서 처리가 되게
된다.
---------------------------------------*/
module_init(iom_fpga_itf_init);
/*----------------------------------------
module_exit 매크로는 커널에게 모듈을 언로드 될때 호출되어야 하는 함수를 알려주는 역할을 한다.
---------------------------------------*/
module_exit(iom_fpga_itf_exit);
/*----------------------------------------
MODULE_LICENSE 매크로는 커널에게 모듈이 어떤 라이선스 하에서 커널을 이용하게 되는지를 알려주는
역할을 한다. 사용하는 라이선스에 따라서 사용할 수 있는 심볼(함수 혹은 변수 등)에 제한이 생기게 된다.
---------------------------------------*/
MODULE_LICENSE("GPL");
'Embedded System > Linux' 카테고리의 다른 글
[Embedded System - Linux] 리눅스 명령어 (0) | 2021.09.21 |
---|---|
[Embedded System - Linux] 리눅스의 종류 (0) | 2021.09.21 |