티스토리 뷰

Study/Linux

[Linux] GPIO Descriptor Consumer Interface

생각많은 소심남 2015. 10. 12. 15:43

* 이 글은 Linux Documentation에 있는 내용을 번역하고 개인 의견을 첨부한 것이다.


linux내에서 GPIO framework를 사용하려면 여러가지 방법이 있긴 하지만, 가장 쉬운 방법 중 하나가 기존에 구현되어 있는 interface를 활용하는 것이다. GPIO도 이에 맞는 interface가 구현되어 있으며, 이를 활용하기 위해서는 다음과 같은 header파일이 include되어야 한다.

#include <linux/gpio/consumer.h>

(정의부 : drivers/gpio/gpiolib.c)

여기에 정의되어 있는 함수들은 기본적으로 gpiod_ 라는 접두어가 붙는데 여기에 담긴 의미는 descriptor-based GPIO interface 라는 뜻이다. 이렇게 interface로 구현되어 있기 때문에 어떤 필요에 의해서 새로운 함수를 구현하고자 할때는 이 접두어를 사용하면 안된다.


GPIO 다루기

- Direction 설정

: 이전 포스트에서 잠시 언급했던 것과 같이 GPIO에선 입력과 출력 기능을 사용할 수 있고, 경우에 따라서는 bidirectional하게 사용할 수 있다. 물론 이를 활용하기 위해서는 가장 먼저 해야 할 일이 direction을 설정하는 것이다. 해당 인터페이스에서 방향설정에 관하여 제공하는 함수는 두 개다.

int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)

이 함수의 return 값은 성공했을 때 0이고, 실패했을 경우 음수형태의 errno가 반환된다. 다른 함수 호출때도 많이 사용되는 기법이긴 하지만 반드시 이부분을 통해서 error checking이 이뤄져야 하는데, 이게 안되는 경우에는 해당 입출력이 어떤 error에 의해서 오류가 발생했는지, 또는 혹시 모를 misconfiguration에 의해서 값이 잘 못 나오는 경우를 감지할 수 없다. output 방향 설정과 같은 경우에 두번째 인자로 들어가는 값은 초기 상태값을 지정해주는 값인데, 이 값을 활용하면 system이 시작할 때 발생할 수 있는 glitch 현상을 막을 수 있다. 참고로 glitch라고 하는 건 analog 회로 내에서 발생한 noise signal로 인해서 잘 못 발생할 수 있는 digital output signal을 말한다. 

 이밖에도 현재 GPIO의 방향이 어떻게 설정되어 있는지는 다음 함수를 통해서 알 수 있다.

int gpiod_get_direction(const struct gpio_desc *desc)

이 함수의 반환값으로는 GPIOF_DIR_IN (1<<0) 이나 GPIOF_DIR_OUT(0 << 0)을 받을 수 있다. 이렇게 방향을 설정할 때 유의해야 할 점은 GPIO 방향 설정에 있어서 default 값이 없다는 것이다. 즉, 방향 설정을 하지 않고 사용하는 것은 illegal한 방법이며, 예측할 수 없는 결과를 얻을 수 있다.


- Spinlock-Safe 환경에서의 GPIO 값 접근

대부분의 GPIO 컨트롤러는 memory read/write 명령을 수행할 수 있는데, 이때는 sleep과 같은 delay state가 필요하지 않고, (non-threaded )IRQ handler를 통해서 외부의 간섭을 받지 않고 수행할 수 있다. 역시 인터페이스내에서도 해당 기능을 지원하는 함수는 다음과 같다.

int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);

get_value에 대한 반환값은 boolean의 형태로 나오는데, 이때 0이면 low, 0이 아닌 값을 high로 정의한다. 이렇게 출력핀으로부터 읽은 값 자체가 함수의 반환값이 되는데, gpio가 어떤 환경으로 설정되어 있는지(open-drain/open-source ...)에 따라서 출력값이 달라질 수 있기 때문에 항상 고정되어 있지 않다.

 이런 get/set function은 에러를 반환하지 않는다. 앞에서 말한 것처럼 이 함수들은 gpio와 직접적으로 연결되어 있어서 해당 핀의 입출력값을 그대로 보여주기 때문이다. 만약 에러가 발생하였을 경우는 get/set function이 아닌, 환경설정시 사용한 gpiod_direction_*()함수에서 걸러졌어야 한다. 물론 지금까지 언급한 환경에서는 다른 방식으로 정의되어 있을 수도 있으며, 이 때는 값들을 그대로 활용할 수 없다. 또한 sleeping 없이는 GPIO에 접근할 수 없는 형태도 있기도 하다.


- Sleep이 필요한 환경에서의 GPIO 접근

 GPIO controller에 따라서는 I2C나 SPI같이 message 기반의 bus를 이용해서 접근하는 방식도 있다. message로 전달되기 때문에 해당 GPIO에서 값을 읽거나 쓰기 위해서는 그 명령어를 전송하거나 response를 받는 시간만큼 기다려야 한다. 이 때는 sleep과 같은 delay instruction이 필요하고, 이런건 IRQ handler 내에서는 처리할 수 없다.

 보통 이런 형식의 GPIO를 지원하는 platform은 sleep을 지원하는 형식의 다음의 함수를 사용한다.

int gpiod_cansleep(const struct gpio_desc *desc)

그리고 다른 형식의 GPIO handling과 구분하기 위해서 0이 아닌 다른 값을 반환한다. 역시 이런 형식의 GPIO를 access하는데 있어서도 앞의 함수와는 조금 다른 함수를 사용한다.

int gpiod_get_value_cansleep(const struct gpio_desc *desc)
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)

threaded IRQ handler와 같이 sleep이 필요한 GPIO 접근의 경우는 앞에서 언급한 함수와는 다른, _cansleep()이라는 접미사가 붙은 함수를 사용해야 한다. 이름만 다를 뿐이지 실제 사용하는 용도나 반환값은 똑같다.


- Active-low State와 실제 GPIO value

 Device driver와 같이 GPIO 값의 logical state를 따지는 경우는 device와 GPIO line 사이에서 일어나는 일에 대해선 신경쓰지 않는다. 하지만 경우에 따라서는 실제로 GPIO line을 통해서 어떤 값이 들어왔는지를 확인하고 설정할 필요가 있는데 이를 위해서는 GPIO에 설정되어 있는 active-low property를 해제하고 실제 값을 읽는 함수를 사용해야 한다. 해당 인터페이스에서는 

int gpiod_get_raw_value(const struct gpio_desc *desc)
void gpiod_set_raw_value(struct gpio_desc *desc, int value)
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc)
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value)
int gpiod_direction_output_raw(struct gpio_desc *desc, int value)

를 통해서 raw value를 읽거나 설정할 수 있고, 역시 sleep이 필요한 환경에서도 비슷한 함수를 제공한다. 물론 이를 위해서는 현재 gpio 설정이 active low인지를 알 수 있어야 하는데, 이에 대한 query function도 동시에 제공된다.

int gpiod_is_active_low(const struct gpio_desc *desc)

이런 함수들이 제공되기는 하지만, 실제로 device driver내에서는 이런 physical 한 값 자체에 대해서 신경쓸 필요가 없어야 한다.


- Active low property

 앞에서 언급한 것처럼 driver 단에서는 physical level의 값에 대해서 신경쓸 필요가 없어야 하고, 이때문에 gpio에 값을 쓰는 함수들 (gpiod_set_value_xxx() 나 gpiod_set_array_value_xxx())은 항상 logical value로만 동작하게 되어 있다. 이런 함수들은 기본적으로 active-low property를 가정하고 동작한다. 즉, GPIO를 통해서 어떤 값이 전달될때, 해당 pin이 active-low로 설정되어 있는지를 확인하고, 만약 그렇게 되어 있는 경우, 전달 받은 값을 재처리해서 원하는 곳에 전달하게 된다.

 잘보면 set 해주는 함수는 value 인자를 받는데, 이 값을 active-low property와 맞춰서 active("1"), inactive("0")을 전달한다. 표로 정리하면 다음과 같이 된다.

Function (example)               active-low proporty  physical line
gpiod_set_raw_value(desc, 0);        don't care           low
gpiod_set_raw_value(desc, 1);        don't care           high
gpiod_set_value(desc, 0);       default (active-high)     low
gpiod_set_value(desc, 1);       default (active-high)     high
gpiod_set_value(desc, 0);             active-low          high
gpiod_set_value(desc, 1);             active-low          low

위에서도 언급한 내용이지만 드라이버단에서는 physical value에 대해서 신경쓸 필요가 없어야 한다.


- 여러 개의 GPIO 출력을 하나의 function call을 통해서 정하는 방법

기본적으로 board에는 여러개의 묶음 단위의 GPIO가 제공되는데, 경우에 따라서 여러개의 핀에 동시에 입력값을 줘야 하는 경우 다음 함수들을 사용한다.

void gpiod_set_array_value(unsigned int array_size,
				   struct gpio_desc **desc_array,
				   int *value_array)
void gpiod_set_raw_array_value(unsigned int array_size,
			       struct gpio_desc **desc_array,
			       int *value_array)
void gpiod_set_array_value_cansleep(unsigned int array_size,
				    struct gpio_desc **desc_array,
				    int *value_array)
void gpiod_set_raw_array_value_cansleep(unsigned int array_size,
					struct gpio_desc **desc_array,
					int *value_array)

칩 드라이버에서 지원하는 경우, 위의 함수를 사용하게 되면 같은 bank나 chip에 속해있는 GPIO에 한해서는 단일 함수를 여러번 쓰는 것보다 좋은 성능을 얻을 수 있다.

 이때의 인자로는 세 개를 받는데 각각에 대한 설명은 다음과 같다.

 * array_size : array elements의 갯수

 * desc_array : GPIO descriptor 배열

 * value_array : GPIO로 할당될 값들에 대한 배열 포인터

여기서 descriptor 배열은 gpiod_get_array() 함수를 통해서 그 시작 주소를 얻을 수 있는데, 이 값이 할당하고자 하는 GPIO의 descriptor와 맞을 경우, 그대로 사용할 수 있다. 그래서 보통 이런 형식으로 많이 사용된다.

struct gpio_descs *my_gpio_descs = gpiod_get_array(...);
gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc, my_gpio_values);


- IRQ에 map되어있는 GPIO

GPIO는 IRQ로도 사용할 수 있는데 다음 함수를 사용하면 gpio가 어떤 IRQ 번호로 mapping되어 있는지를 알 수 있다.

int gpiod_to_irq(const struct gpio_desc *desc)

정상적으로 연결되어 있는 경우 연결된 IRQ 번호를 반환하고, 아닌 경우는 음수 형태의 errno 코드를 반환한다. 혹은 gpiod_direction_input을 통해서 입력 설정이 안되어 있다던가 gpiod_to_irq()로는 받을 수 없는 IRQ 번호를 사용한 경우에도 에러를 반환한다. 여기서 정상적으로 넘어온 값은 request_irq()나 free_irq()로 값이 전달되는데, 여기서 부터는 board마다 정의된 initialization code에 의해서 특정 IRQ resource안에 저장되게 된다. 


- 기존 GPIO subsystem간의 interaction 

 descriptor 기반의 interface가 나오기 이전에 integer 기반의 interface를 통해서 GPIO를 처리하는 subsystem들도 존재한다. 이와의 호환성을 위해서 다음 두개의 변환 함수를 제공한다.

int desc_to_gpio(const struct gpio_desc *desc)
struct gpio_desc *gpio_to_desc(unsigned gpio)

desc_to_gpio()에 의해서 반환된 GPIO 번호는 GPIO descriptor가 free되지 않는 한, 언제든지 자유롭게 사용할 수 있다. 반대의 case인 gpio_to_desc()의 반환값인 GPIO descriptor를 사용하고자 할때는 반드시 이전에 사용되고 있던 GPIO number가 반환되어야 한다.

 당연한 이야기이겠지만 다른 API에서 사용하고 있는 GPIO를 현재 API에서 free하는 경우는 금지되어 있고, error로 나타나게 된다.

댓글