I2C Subsystem -1- (Basic)

 

I2C(Inter-Integrated Circuit)

I2C는 필립스에서 개발한 직렬 컴퓨터 버스이며 여러 개의 저속의 주변 기기를 연결하기 위해 사용된다. 시리얼 통신에 사용되는 UART 또는 SPI들보다 더 단순하여 심플한 회로 구성이 가능하다.

 

I2C 통신에 사용되는 2 개의 라인은 다음과 같은 기능을 수행한다.

  • SDA (Serial DAta)
    • 데이터의 직렬 전송에 사용된다.
  • SCL (Serial CLock)
    • 디바이스간 신호 동기화에 사용되는 클럭이다.

 

다음 그림은 I2C 버스에 연결되어 있는 디바이스들을 보여준다.

 

i2c 버스에 연결되는 디바이스 종류들은 수백~수천 가지로 매우 다양하다. 다음 그림은 몇 가지 종류의 디바이스들을 i2c 버스에 연결하여 사용할 때의 모습을 보여준다.

 

I2C Specification History

i2c 스펙은 다음과 같이 발전을 해왔다. 아래 노란 박스는 새 버전이 나올 때 마다 변경된 주요 항목이다.

  • 필립스에서 처음 I2C가 개발되었을 때 다음과 같은 specification으로 동작하였다.
    • 가장 널리 사용되는 스탠다드 모드가 100Khz로 사용되고, 저속 모드로 운영시에는 10Khz의 속도로 동작한다.
    • 7비트 주소를 사용하여 총 128개의 주소를 사용할 수 있지만 그 중 16개는 reserve되어 있고, 나머지 112개를 사용할 수 있다. 따라서 하나의 I2C 버스에는 최대 112개의 디바이스가 연결될 수 있다.
  • 그 후 추가 버전들은 속도가 증가되었고, 주소가 10비트로 확장되었다.
    • 속도 증가
      • Fast 모드에서 400Khz, High 모드에서 3.4Mhz로 속도가 증가됨을 알 수 있다.
    • 주소 확대
      • 10비트 모드가 사용되면서 최대 1008개의 디바이스를 연결할 수 있게 되었다.

  • (1) 3.4Mhz High-speed-mode를 사용하기 위해서는 SDAH SLCH를 사용하는 디바이스를 사용하고 arbitration 및 클럭 동기화를 사용하지 않아야 한다.
  • (2) 5Mhz Ultra Fast-speed-mode를 위해서는 USDA, USCL 라인이 추가되어야하고 Master → Slave 단방향만 지원된다.

 

멀티 마스터 지원

I2C는 다음 그림과 같이 1개의 버스에 2개 이상의 컨트롤러가 경쟁할 수 있다. 이 때 두 컨트롤러는 버스 Master 역할을 하기 위해 경쟁하게된다.

  • Multi-master
    • 2개 이상의 마스터가 메시지 손상 없이 동시에 버스를 제어하는 경우.
  • Arbitration
    • 2개 이상의 마스터가 버스를 동시에 통제할 때 오직 하나만이 허용되며 선택된 마스터의 메시지는 손상되지 않는다.
  • Synchronization
    • 두 개 이상의 디바이스의 클럭들을 동기화

 

I2C-Mux

다음과 같이 i2c 스위치 및 i2c 멀티플레서 두 종류로 나뉜다. 각각의 기능은 다음과 같다.

  • i2c 스위치
    • 1:N으로 구성 시 하위 채널 각각에 대해 select  및 deselect 할 수 있다. 즉 1개 이상을 select할 수 있다.
  • i2c 멀티플렉서
    • 1:N 또는 N:1로 구성 시 하위 채널 하나를 select 및 deselect 할 수 있다.  select 시 한 번에 하나의 채널만 select할 수 있다.

 

 

제품에 따라 다음과 같은 기능이 지원될 수도 있다.

  • HW reset 핀(option)
    • i2c 스위치에서 외부 HW reset(option) 핀이 지원되는 경우 버스 리커버리를 수행할 수 있다.
    • i2c 스위치 및 i2c 멀티플렉서 둘 다 내부에서 POR(Power On Reset) 기능을 지원한다.
  • 인터럽트(option)
    • 인터럽트를 받아 mux하여 전송할 수 있다.
  • HW addr 설정 핀
    • i2c-mux 디바이스를 하나의 버스에 여러 개를 사용할 경우 중복되지 않게 주소 하위 비트 몇 개(3~4)를 설정할 수 있는 핀이 패키지 외부로 제공된다.

 

다음 그림은 외부 reset, 인터럽트, HW 주소 설정핀 등이 지원되는 모습을 보여준다.

 

아래 그림은 2개의 i2c 컨트롤러와 그 중 하나에 연결된 4채널 i2c 멀티플렉서를 보여준다.

 

I2C 통신 프로토콜

2 개의 라인만을 사용하는 I2C 버스에서 I2C 디바이스들이 사용하는 통신 프로토콜을 알아보기로 하자.

 

Feature 리스트

다음 표와 같이 I2C 통신 프로토콜에 사용되는 Feature들이다. 디바이스들은 싱글 마스터, 멀티 마스터 또는 슬래이브 등의 각 구성(Configuration)에 따라 사용되는 Feature들이 다르다.

 

주요 Feature

i2c 버스에 어떠한 통신도 없는 경우 SDA 및 SCL 라인은 high 상태에 있다. 마스터나 디바이스가 통신을 시도할 때 전류가 흐르며 low 상태로 바뀐다.

 

Start & Stop condition

다음 그림과 같이 데이터를 송수신하기 위한 시작과 끝 조건을 알아본다.

  • Start condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 low 시그널로 변경될 때 Start condition으로 인식한다.
    • 표기법: S
  • Stop condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 high 시그널로 변경될 때 Stop condition으로 인식한다.
    • 표기법: P

 

i2c 데이터 통신 중에는 클럭이 high 시그널일 때에는 데이터(SDA) 시그널이 변경되면 안된다. 이러한 규칙으로 인해 클럭이 high인 상태에서는 데이터를 안정적으로 읽는 것을 보장한다.

 

그런데 아래 그림과 같이 클럭(SCL)이 high 상태일 때 데이터(SDA)가 변경되는 특수한 상태가 있다. 이러한 경우 데이터(SDA)가 변화한 방향에 따라 low 상태로 변경되면 Start condition이고 high 상태로 변경되면 Stop condition이라 한다.

 

1 바이트 전송 및 Acknowledge 처리

Start condition이 발생한 후 Stop condition이 나오기 전까지 디바이스들 간에 연속으로 바이트 단위의 통신을 할 수 있다. 1 바이트를 수신할 때마다 수신측 디바이스는 전송측을 위해 응답(Acknowledge) 비트를 전송해야 한다.

  • 전송측 디바이스는 1 바이트를 MSB부터 전송한다.
  • 수신측 디바이스는 1 바이트 수신 완료 후 1 비트의 Ack(low)를 전송한다.
  • 표기법: A 또는 ACK

 

아래 그림을 보면 클럭(SCL)을 마스터가 주기적으로 보내고 있음을 알 수 있다. 데이터(SDA)는 전송 디바이스측에서 8비트를 보낸 후 9번째에 high 상태를 유지하고 있다. 그리고 수신 디바이스측에서 9번째 클럭에 해당하는 위치가 오기전에 ACK(low) 상태를 유지하는 것을 알 수 있다.

Synchronization

하나의 i2c 버스에서 두 개의 마스터가 동작할 때 각각의 클럭을 and 하여 동기화 시키는 것으로 하나의 클럭을 만들어낸다.

 

Arbtration

하나의 i2c 버스가 free 상태일 때 두 개의 마스터가 동시에 버스를 차지하려고 각각 Start condition을 보낸 경우이다. 그 이후로 운좋게(?) 서로 데이터를 동일하게 보내다가 어느 시점에서 각자의 전송 비트가 달라지게 되는데 그 시점에서 데이터(SDA)를 high 상태로 바꾼 마스터가 arbtration의 패자가 된다. 즉 데이터(SDA)를 low 상태로 물고 있는 마스터가 승자이다.

 

아래 그림은 두 개의 마스터가 거의 동시에 start condtion을 보냈고, 첫 번째 마스터가 데이터를 high로 변경하였음에도 SDA 라인은 low 상태를 유지하고 있다. 이 때 첫 번째 마스터가 자신이 arbitration의 패자가 되었음을 인식한다.

 

Clock Streach (Clock low extending)

i2c 마스터가 규칙적으로 clock을 발생하여야 하고 이에 대해 i2c 디바이스는 클럭에 맞춰 데이터를 송/수신해야한다. 그러나 요청을 받은 후에 클럭 타임을 초과하는 처리 시간이 필요한 경우 slave 디바이스가 데이터를 준비하기 위해 클럭을 강제로 low로 설정해버린다. 그런 후 데이터가 준비되어 응답을 할 수 있으면 다시 클럭을 풀어준다. 그런 경우 마스터가 계속 하여 클럭을 진행할 수 있다.

  • 예) 주소 0x45를 갖는 EEPROM 디바이스에 offset 08번에 해당하는 데이터를 요청하였다. eeprom 장치에서 데이터를 가져오는 시간이 많이 걸릴 때가 있으므로 이러한 경우 잠시 클럭을 low로 묶어놓고 데이터가 다 준비된 경우 이를 응답하는 형식으로 처리를 하는 것을 clock streaching이라 한다.
  • ACK를 수신하면 인터럽트가 발생하고 이 인터럽트의 처리가 완료되기 전까지 클럭(SCL) 시그널은 low 상태로 고정되어 대기한다.
  • clock streach 기능을 사용하면 adapter가 I2C_FUNC_SLAVE 기능이 지원되어야 한다.
  • 클럭을 최대 100us까지 지연시킬 수 있다.
  • 두 가지 지연 방법이 있다.
    • Periodic clock stretching by slave
      • 주기적으로 매 번 클럭을 늘려 smbus 통신 속도를 10Khz로 줄일 수 있다.
    • Random clock stretching by master or slave
      • ISR 루틴에서 데이터 준비를 위해 지연을 시키는 경우이다.

 

다음 그림은 특정 디바이스로부터 무조건 1 바이트 값을 읽어오는 시나리오에서 디바이스가 clock streaching을 사용하는 유무에 대해 비교하였다.

  • 보통 주소를 전송하고 요청 offset 및 size를 요청하는데 이렇게 하면 길어지므로 아래 그림에서는 간단히 주소와 데이터를 수신하는 것으로 보여준다.
  • slave 디바이스가 scl을 low로 강제함으로써 마스터 디바이스가 계속하여 클럭을 발생시키지 못하게 홀드시킨다.

 

7-bit slave addresss (슬레이브 디바이스 선택 후 데이터 전송 또는 수신)

먼저 상대 디바이스를 선택한 후 데이터를 송/수신하는데 방법은 다음과 같다.

  • 7bit 주소를 전송하는 것으로 디바이스를 선택한다. 그 후 전송측을 결정하는 R/W 비트를 전송한다.
    • R/W 비트
      • 1=마스터가 수신
      • 0=마스터가 송신
  • 송수신 비트가 0인 경우 마스터가 슬레이브에 8 비트 데이터를 전송한다.
  • 송수신 비트가 1인 경우 슬레이브가 마스터에 8비트 데이터를 수신한다.
  • 그 후 수신측에서 응답(Ack) 1 비트를 전송한다.
  • 표기법:  Addr + R/W

 

특별 주소(Special Addresses)

다음의 16개 주소들은 다음과 같이 특별한 목적으로 사용된다.

  • R/W 비트 상태여부와 관계 없이 Address만을 보면 16개의 주소가 특별 주소로 사용된다.

  • smbus의 경우 다음 주소의 디바이스 용도는 다음과 같다.
    • 0001-000 (0x08)
      • SMBus host
      • SMBus를 이용할 때 host controller의 address이다.
      • 디바이스는 이 주소로 Host Notify 이벤트를 보낼 수 있다.
    • 0001-001 (0x09)
      • Smart Battery Charger
    • 0001-010
      • Smart Battery Selector
      • Smart Battery System Manager
    • 0001-011
      • Smart Battery
    • 0001-100
      • SMBus
    • 0001-100 (0x0c)
      • ARA(Alert Response Address)
      • SMBus alert 응답 주소
    • 0101-000 (0x28)
      • ACCESS.bus host
    • 0101-100 (0x2c)
      • LCD Contrast Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0101-101 (0x2d)
      • CCFL Backlight Driver로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0110-111 (0x37)
      • ACCESS.bus 디폴트 주소
    • 1000-0XX (0x40 ~ 0x43)
      • PCMCIA Socket Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1000-100 (0x44)
      • VGA Graphics Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1001-0XX (0x48 ~ 0x4b)
      • 제한없이 사용할 수 있는 주소
    • 1100-001 (0x61)
      • SMBus 디바이스의 디폴트 주소

 

참고로 ACCESS.bus는 2-wire를 사용하여 외부 장치들을 연결하여 나갈 수 있도록 설계한 시도이다.

  • 전원까지 4개의 pin을 사용한다.
  • RS-232C 인터페이스를 대체할 목적이다.

 

10-bit addressing

10비트 주소를 사용하는 경우 다음과 같이 2 바이트를 사용하여 디바이스의 주소를 지정할 수 있다.

  • 첫 번째 바이트
    • 10비트 중 2비트 주소
  • 두 번째 바이트
    • 10비트 중 8비트 주소

 

 

General Call address

General call 주소가 사용되면 디바이스에 몇 가지 특별한 요청을 할 수 있다. 이 기능은 아래 Start BYTE와 같이 대부분의 시스템에서 잘 사용되지 않는 기능이다.  이 기능은 주로 i2c 디바이스들 중 EEPROM등에서 프로그래밍등이 요구될 때 사용되는 특수 기능이다.

  • 첫 번째 바이트 구성을 General call(0x0)로 사용한다.
  • 두 번째 바이트에 기능 코드 또는 마스터 주소를 부여할 수 있다.
    • LSB가 0일 때 다음과 같은 기능 코드가 있다.
      • 0x04: Write programmable part of slave address by hardware
      • 0x06: Reset and write programmable part of slave address by hardware
    • LSB가 1인 경우 마스터 주소를 사용하는 경우 Hardware Generall Call을 사용할 수 있다.

 

 

Start BYTE

Srart BYTE feature는 0x00 데이터와 dummy NACK(1)을 전송한다. 두 개 이상의 호스트 컨트롤러가 버스 Master를 경쟁하고 있는 경우 stop condition을 detect한 경우 두 호스트 컨트롤러 중 하나가 버스 마스터가 된다. 특별한 경우 어떤 호스트 컨트롤러가 계속하여 버스를 점유하여 명령(reset, EEPROM 기록 등)을 처리해야 하는 경우 stop condition을 사용하지 않아야 한다. stop condition을 하는 경우 버스를 빼앗길 염려가 있다. 이러한 경우에 stop condition 대신 START BYTE를 보내고 다시 start condition을 사용하는 방식으로 반복하여 계속 명령을 보낼 수 있다. 잘 사용되지 않는 기능이지만 드믈게 특정 시스템에서 사용된다.

 

Device ID

디바이스에 대한 제조사 id와 파트 id 및 리비전 정보를 알아올 수 있다.

 

디바이스를 알아오는 경우 다음과 같은 제조사 id를 읽어낼 수 있다.

 

i2c 전송 포맷

i2c 전송 포맷은 다음과 같이 3가지 방법이 있다.

  • 마스터 송신
    • 슬레이브로 데이터를 송신 중에 방향이 바뀌지 않는다.
  • 마스터 수신
    • 마스터가 주소 송신 후 슬레이브로 부터 데이터를 수신한다. 역시 방향이 바뀌지 않는다.
  • Combined 포맷
    • 마스터와 슬레이브의 복합 송수신이 가능하다.

 

마스터 송신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 N 바이트를  전송하는 모습이다.

  • 슬레이브가 바이트를 받을 때 마다 ACK를 보내는데 더 이상 받을 수 없는 상황인 경우 NACK를 보낸다.

 

마스터 수신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 1 바이트를 수신하는 모습이다.

  • 마지막 바이트를 수신한 후 마스터가 NACK를 보내야한다.

 

Combination 전송

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 여러 개의 메시지를 송신 또는 수신하는 모습이다.

  • 마스터가 버스를 계속 점유한 채로 전송 및 수신들의 combination을 완료해야 하는 경우 사용한다.
  • 메시지의 전송이 끝난 후 반복 시작하고자 할 때 중간에 repeated start condition 을 전송한다.
    • 표기법: Sr

 

 

SMBus(System Management Bus)

필립스의 i2c를 기반으로 동작하도록 인텔과 듀라셀이 1994년 정의한 버스이다. 특징은 다음과 같다.

  • 주로 PC 마더보드에 연결된 전력 제어 장치(power on/off) 및 온도 센서등이 smbus에 연결되어 사용된다.
  • 참고: System Management Bus (SMBus) Specification Version 2.0 August 3, 2000 | smbus.org – 다운로드 pdf

 

SMBus가 I2C와의 다른 점

타이밍
  • Speed
    • smbus
      • 10~100Khz를 사용한다.
        • 실제 대부분의 구현은 50~100Khz를 사용한다.
      • PMBus로 발전되면서 400Khz 속도도 사용할 수 있다.
    • i2c
      • 최저 속도는 100Khz이다.
  • 클럭(scl) 라인 low 타임아웃
    • smbus
      • 타임아웃을 설정할 수 있다.
      • Slave 디바이스의 경우 최대 25ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
      • Master 디바이스의 경우 최대 10ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
    • i2c
      • 타임아웃을 설정할 수 없다.
  • 데이터(sda) 라인 홀드 시간
    • smbus
      • 최대 300ns까지 홀드 시킬 수 있다.
    • i2c
      • 홀드할 수 없다.
  • Power-On-Reset 후 디바이스 준비 시간
    • smbus
      • 최대 0.5초까지 대기해야 한다.
ACK 및 NACK
  • addr 전송 후 ACK 용도
    • smbus
      • 탈부착 smbus 디바이스가 버스에 연결되었는지 여부를 detect 하는 비트 용도로 항상 ACK를 요구한다.
      • 슬레이브 디바이스는 자신의 주소(Addr) 비트를 인식하면 ACK를 회신한다.
      • S | Addr + R/W | ACK | …
    • i2c
      • ACK가 없어도 된다.
  • NACK 의미
    • smbus
      • N잘못된 명령이나 데이터의 수신을 의미한다.
    • i2c
      • 더이상 데이터를 전송하지 않는다는 것을 의미한다.
Protocol
  • 탈부착 디바이스 연결
    • smbus
      • 통신 중 smbus 디바이스가 smbus에 연결될 수 있다.
    • i2c
      • 통신 중 i2c 디바이스가 i2c 버스에 연결될 수 없다.
  • ARP(Address Resolution Protocol)
    • smbus
      • ARP를 지원한다.
    • i2c
      • ARP를 지원하지 않는다.
  • PEC(Packet Error Checking)
    • smbus
      • PEC를 지원한다.
      • 에러 체킹을 위해 CRC-8 규격으로 1 바이트를 추가 사용한다.
    • i2c
      • PEC를 지원하지 않는다.
Alert
  • smbus에 연결된 디바이스들이 alert라는 옵션 신호를 사용하여 host에 전달할 수 있게 하였다.
전기 규격
  • 전기적인 특성이 I2C와 일부 다르다.

 

SMBus protocol

SMBus 프로토콜에 사용되는 명령들을 알아본다. 참고로 그림에서 사용하는 기호는 다음과 같다.

  • S
    • Start condition
  • P
    • Stop condition
  • Sr
    • Repeted Start condition
  • Addr
    • 7비트 주소
  • R/W
    • 1비트 (R=1, W=0)
  • A
    • Ack (0)
  • PEC
    • 8비트 Packet Error Checking

 

Quick, Send & Receive 명령

Quick

슬레이브 디바이스로 R/W 1 비트를 전달할 수 있고, Ack 1비트를 수신하여 detect 여부를 확인할 수 있다.

  • R/W 비트를 수신하여 장치의 on/off를 제어하거나, low-power 스탠바이 모드를 enable/disable하는데 사용할 수 있다.
  • 데이터 바이트의 송신 또는 수신은 없다.

 

Send byte

슬레이브 디바이스에 1 바이트를 송신한다. Write Byte와 다른 점은 command를 전송하지 않는다는 점이다.

  • pure smbus의 ACK는 디바이스에서의 처리 결과를 호스트에 반환하는 기능을 가진다.
  • pure i2c-bus의 ACK는 디바이스가 항상 1을 응답해야한다.

 

Receive byte

슬레이브 디바이스로부터 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Write & Read 명령

Write byte

슬레이브 디바이스에 1 바이트 명령 + 1 바이트를 송신한다.

 

Write word

슬레이브 디바이스에 1 바이트 명령 + 워드(2 바이트)를 송신한다.

  • smbus에서 워드는 2바이트이다.

 

Read byte

슬레이브 디바이스에 1 바이트 명령을 송신하고 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Read word

슬레이브 디바이스에 1 바이트 명령을 송신하고 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block 명령

Block write

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스에 길이 + 블럭(최대 32바이트) 데이터를 송신한다.

 

Block read

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이 + 블럭(최대 32바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Process Call 명령

Process call

슬레이브 디바이스에 명령 + 워드(2 바이트)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block Process Call

슬레이브 디바이스에 명령 + 길이와 블럭 데이터(32 바이트까지)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이와 블럭(최대 32 바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

이벤트

Host notify

슬레이브 디바이스가 호스트에 워드 데이터를 전달하고자 할 때 사용한다. 호스트는 워드 데이터를 수신하여 호스트에서 슬레이브를 담당하는 드라이버에 이벤트를 전달한다.

  • pure smbus에서 host notify는 반드시 구현되어야 한다.

 

smbalert#

슬레이브 디바이스가 smbalert# 인터럽트를 호스트에 발생시킨다. 발생 시킨 디바이스를 확인하기 위해 아래와 같이 ARA(Alert Response Address)로부터 디바이스 주소를 알아올 수 있다.

  • 만일 HW 구성 시 별도의 smbalert# 라인이 호스트에 연결되어 구성되지 않은 경우 호스트가 alert를 주기적으로 폴링하여 알아내는 방법도 사용할 수 있다.
  • pure smbus에서 alert는 option 사항이다.
  • alert callback을 위해 i2c_setup_smbus_alert() 함수를 사용하여 callback용 함수를 등록하여 사용한다.

 

I2C 서브시스템

I2C 디바이스와 드라이버를 등록하면 I2C 서브시스템은 유저 스페이스에 다양한 인터페이스를 제공한다. (i2c-N은 i2c adapter로 N은 특별히 지정하지 않으면 0번부터 시작된다.)

  • i2c-core가 크게 두 가지를 관리한다.
    • i2c adapter 디바이스
      • /sys/devices/…/<i2c 호스트 디바이스>/i2c-N
        • i2c 어댑터 디바이스 인터페이스
      • /sys/class/i2c-adapter
        • i2c-adapter 클래스 디바이스 인터페이스
      • /sys/bus/i2c/devices/i2c-N
        • 버스에 등록된 i2c 어댑터 디바이스 인터페이스
    • i2c client 디바이스 및 드라이버
      • /sys/devices/…/<i2c 호스트 디바이스>/i2c-N/<client device>
        • i2c 클라이언트 디바이스 인터페이스
        • <client device>의 기본 포맷은 <버스번호>-<4자리의 addr 숫자>
          • 예) 0x3f 주소를 가진 디바이스가 1번 i2c 버스에 연결된 경우 -> 1-003f
      • /sys/bus/i2c/devices/<client device>
  • i2c-dev는 i2c 클라이언트 디바이스를 위해 2 개의 인터페이스를 제공한다.
    • /dev/i2c-N
      • 캐릭터 디바이스 인터페이스
      • i2cdetect, i2cdump, i2cset, i2cget 등의 i2c-tools를 사용하려면 i2c 캐릭터디바이스가 동작해야 한다.
    • /sys/class/i2c-dev/i2c-N
      • i2c-dev 클래스 디바이스 인터페이스
      • i2c adapter에 대한 character 디바이스를 사용할 때 생성된다.(CONFIG_I2C_CHARDEV)

 

SoC 외부에 위치한 i2c 호스트 컨트롤러

임베디드 시스템에서 i2c 호스트 컨트롤러들은 보통 SoC 내부에 위치하여 플랫폼 디바이스로 곧바로 등록할 수 있다. 그러나 이를 사용하지 못하는 경우에는 속도가 빠른 버스에 연결하여 사용할 수 있다. 일반적인 PC에는 i2c 호스트 컨트롤러가 내장되지 않았다. 이러한 PC에는 pci 버스와 usb 버스가 연결되어 있는데 pci 버스 또는 usb 버스에 i2c 호스트 컨트롤러를 연결하여 사용하는 경우가 많다. 다음 그림은 usb 버스에 i2c 버스가 연결된 경우를 보여준다. 좌측 그림이 H/W 연결 구성이고 우측 그림이 이에 대한 각 버스 서브 시스템과 core 및 드라이버 위치를 보여준다.

  • 예) PCI 버스  —–>  USB 버스  —–>  I2C 버스

 

i2c sysfs

다음 그림은 i2c adapater 디바이스 및 드라이버가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 2 개의 i2c adapter가 플랫폼 디바이스로 등록되어있고, 2 개의 드라이버 역시 바인딩 되어 동작중인 상태이다.

 

다음 그림은 여러 개의 i2c client 디바이스 및 드라이버가 추가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 여러 가지 종류의 디바이스 및 드라이버가 등록되어 있는 모습을 보여준다.

 

i2c 호스트 컨트롤러(Adapter) 및 i2 디바이스(Client) 등록

 

i2c 디바이스는 버스 마스터 기능 유무에 따라 i2c 호스트 컨트롤러와 i2c 클라이언트의 역할을 한다.

  • i2c 호스트 컨트롤러(included i2c adapter)는 상위 버스와 i2c 버스간의 브릿지 기능을 수행한다.
  • i2c 클라이언트는 i2c 버스에 연결되는 consumer 디바이스이다.

 

i2c 버스는 pci나 usb 버스와 다르게 i2c 버스를 스캔하여 디바이스를 찾는 기능이 없다. 따라서 사전에 연결하고자 하는 디바이스의 주소를 미리 등록해두고 이 주소를 사용하여 디바이스가 있는지 HW detect를 시도할 수 있다.

 

다음 그림은 상위 버스에 i2c 호스트 컨트롤러(included i2c adapter)를 등록시키고 i2c 클라이언트 디바이스가 등록된 모습을 보여준다.

  • i2c 클라이언트 디바이스
    • 하드웨어 센서 모니터링 디바이스인 lm75를 예를 들었다.

 

i2c 호스트 컨트롤러 등록

i2c 호스트 컨트롤러 디바이스와 드라이버를 등록하기 위해 다음과 같은 API들을 사용한다.

예) 플랫폼 버스에 등록

  • 디바이스
    • platform_device_register() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_platform_device_create_pdata()
  • 드라이버
    • platform_register_driver() 함수를 사용하여 등록시킨다.
    • 드라이버 내부 probe 함수에서 i2c_add_adapter() 함수를 사용하여 i2c 버스를 제어할 i2c_adapter 구조체를 등록시킨다.

 

다음 그림은 broadcom 사의 i2c 호스트 컨트롤러 드라이버가 등록되는 주요 코드 과정을 보여준다.

 

i2c 클라이언트 등록

  • 디바이스 부분
    • i2c_new_device() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_i2c_register_device()
    • 유저 스페이스에서 sysfs를 사용하여 등록하는 방법도 있다.
      • 예) echo <device> 0x71 > /sys/bus/i2c/devices/i2c-0/new_device
  • 드라이버 부분
    • i2c_add_driver() 또는 i2c_register_driver() 함수를 사용하여 등록시킨다.

 

다음 그림은 wm8750 디바이스가 i2c 클라이언트 디바이스 및 드라이버로 등록되는 과정에서의 주요 코드 부분을 보여준다.

  • i2c 노드에 포함된 wm8750@1a 노드가 파싱되어 i2c_client 구조체에 포함된 후 i2c 버스에 디바이스로 이미 포함된 상태이다.
  • i2c_driver 구조체가 wm8750@1a 디바이스에 대응하는

 

유저 스페이스 바인딩

드라이버를 i2c 디바이스와 attach하는 방법

  • 기존(old) 방법
    • modprobe <driver> probe=1,0×71
    • modprobe <driver> force=1,0×71
    • modprobe <driver> force_<device>=1,0×71
  • 새로운(new) 방법
    • echo <device> 0x71 > /sys/bus/i2c/devices/i2c-0/new_device

 

드라이버를 i2c 디바이스와 attach하지 못하게 하는 방법

  • 기존(old) 방법
    • modprobe <driver> ignore=1,0×71
  • 새로운(new) 방법
    • 드라이버를 dummy 디바이스와 연결해두는 방법을 사용한다.
      • echo dummy 0x2f > /sys/bus/i2c/devices/i2c-1/new_device
      • modprobe <driver>

 

I2C Tools

i2c 툴을 설치하기 위해서는 다음과 같이 i2c-tools를 설치한다.

  • sudo apt-get install i2c-tools

 

i2cdetect

명령어 사용법

$ i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.

 

i2c 버스 및 i2c Adapter명 확인

x86 서버

다음과 같이 pc server는 두 개의 i2c adapter가 서로 다름을 알 수 있다.

$ i2cdetect -l
i2c-0   unknown         AST i2c bit bus                         N/A
i2c-1   unknown         SMBus I801 adapter at 0580              N/A

 

broadcom ns2(Cortex-A57)
$ i2cdetect -l
i2c-0 i2c Broadcom iProc I2C adapter I2C adapter
i2c-1 i2c Broadcom iProc I2C adapter I2C adapter

 

rpi3(Cortex-A53)
$ i2cdetect -l
i2c-1 i2c 3f804000.i2c I2C adapter

 

i2c 버스별 Functionalities 확인

x86 서버

0번 버스는 i2c  전송이 가능하고 smbus 전송도 에뮬레이션 처리가 가능하다. 반면에 1번 버스는 smbus 전송만 가능하다.

$ i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call yes
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ sudo i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C no
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call no
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

broadcom ns2(Cortex-A57)
$ i2cdetect -F 0
Functionalities implemented by /dev/i2c/0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ i2cdetect -F 0
  (상동)

 

rpi3(Cortex-A53)
$ i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

i2c 버스별 디바이스 스캔

x86 서버
$ sudo i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

$ sudo i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

broadcom ns2(Cortex-A57)
  • HW 스캔된 디바이스는 숫자로 표기되고, UU로 표기된 디바이스는 이미 바인딩(dummy 포함)된 디바이스이다.
$ i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: UU -- UU -- -- -- -- --

# i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

rpi3(Cortex-A53)
$ i2cdetect -y -r 0
Error: Could not open file `/dev/i2c-0' or `/dev/i2c/0': No such file or directory

$ i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

i2cdump

명령어 사용법
$ i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-o] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)

 

데이터 dump

1번 버스의 0x4f 디바이스 덤프

$ i2cdump -f -y 1 0x4f
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
10: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............
…
e0: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
f0: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............

 

i2cget

명령어 사용법
$ i2cget 
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC

 

1 바이트 읽기

0번 버스의 0x4f 디바이스에서 offset 0x0b에 해당하는 바이트를 읽어온다.

$ i2cget -f -y 0 0x4c 0x0b
0x0f

 

i2cset

명령어 사용법
$ i2cset
Usage: i2cset [-f] [-y] [-m MASK] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC

 

1바이트 쓰기

1번 버스의 0x72 디바이스 0 offset에 0x01을 기록한다. (i2c 스위치에 0번 채널을 선택하게 한다.)

$ i2cset -f -y 1 0x72 0 0x01

 

i2c trace

i2c를 trace하기 위해 ftrace를 사용해본다. ftrace를 사용하기 위해 다음의 커널 옵션이 사용된다.

  • CONFIG_FTRACE
  • CONFIG_ENABLE_DEFAULT_TRACERS

 

먼저 tracefs를 /sys/kernel/tracing 디렉토리에 마운트한다.

$ mount -t tracefs nodev /sys/kernel/tracing

 

마운트한  후 해당 디렉토리를 살펴보면 다음과 같다.

# cd /sys/kernel/tracing/
# ls -la
total 0
drwx------    6 root     root             0 Jan  1 09:00 .
drwxr-xr-x    8 root     root             0 Jan  1 09:31 ..
-r--r--r--    1 root     root             0 Jan  1 09:00 README
-r--r--r--    1 root     root             0 Jan  1 09:00 available_events
-r--r--r--    1 root     root             0 Jan  1 09:00 available_tracers
-rw-r--r--    1 root     root             0 Jan  1 09:00 buffer_size_kb
-r--r--r--    1 root     root             0 Jan  1 09:00 buffer_total_size_kb
-rw-r--r--    1 root     root             0 Jan  1 09:00 current_tracer
drwxr-xr-x   45 root     root             0 Jan  1 09:00 events
--w-------    1 root     root             0 Jan  1 09:00 free_buffer
drwxr-xr-x    2 root     root             0 Jan  1 09:00 instances
drwxr-xr-x    2 root     root             0 Jan  1 09:00 options
drwxr-xr-x    6 root     root             0 Jan  1 09:00 per_cpu
-r--r--r--    1 root     root             0 Jan  1 09:00 printk_formats
-r--r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines
-rw-r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines_size
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event_pid
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_clock
--w--w----    1 root     root             0 Jan  1 09:00 trace_marker
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_options
-r--r--r--    1 root     root             0 Jan  1 09:00 trace_pipe
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_cpumask
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_on
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_thresh

 

사용할 수 있는 i2c 이벤트를 확인해본다.

$ cat available_events | grep i2c
i2c:smbus_result
i2c:smbus_reply
i2c:smbus_read
i2c:smbus_write
i2c:i2c_result
i2c:i2c_reply
i2c:i2c_read
i2c:i2c_write

 

다음 명령을 통해 i2c 이벤트 전체를 trace 하도록 enable 한다.

$ echo 1 > events/i2c/enable

 

만일 i2c-adapter(i2c-bus 호스트 컨트롤러)가 여러 개 준비되어 있는 경우 그 중 하나만 선택하려면 다음과 같은 명령을 사용할 수 있다.

$ echo adapter_nr==0 > events/i2c/filter

 

다음은 0x72 주소를 사용하는 i2c 스위치 디바이스에 0x80 데이터를 기록하여 총 8개 채널 중 마지막 채널을 연결한다.

  • trace에 0 값을 기록하는 경우 기존 trace 로그를 삭제한다.
$ echo 0 > trace; i2cset -f -y 0 0x72 0x80; cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 4/4   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cset-168   [000] ....  2027.045809: smbus_write: i2c-0 a=072 f=0000 c=80 BYTE l=0 []
          i2cset-168   [000] ....  2027.045814: i2c_write: i2c-0 #0 a=072 f=0000 l=1 [80]
          i2cset-168   [000] ....  2027.046032: i2c_result: i2c-0 n=1 ret=1
          i2cset-168   [000] ....  2027.046033: smbus_result: i2c-0 a=072 f=0000 c=80 BYTE wr res=0

 

기록된 값을 확인해본다.

$ echo 0 > trace; i2cget -f -y 0 0x72; cat trace
0x80
# tracer: nop
#
# entries-in-buffer/entries-written: 6/6   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cget-176   [000] ....  2326.033639: smbus_read: i2c-0 a=072 f=0000 c=0 BYTE
          i2cget-176   [000] ....  2326.033641: i2c_read: i2c-0 #0 a=072 f=0001 l=1
          i2cget-176   [000] ....  2326.033856: i2c_reply: i2c-0 #0 a=072 f=0001 l=1 [80]
          i2cget-176   [000] ....  2326.033857: i2c_result: i2c-0 n=1 ret=1
          i2cget-176   [000] ....  2326.033857: smbus_reply: i2c-0 a=072 f=0000 c=0 BYTE l=1 [80]
          i2cget-176   [000] ....  2326.033858: smbus_result: i2c-0 a=072 f=0000 c=0 BYTE rd res=0

 

위의 방법과 유사하지만 tracefs를 마운트하지 않고 /sys/kernel/debug/tracing 디렉토리에서 동일한 동작을 할 수도 있다.

 

U-Boot에서 I2C 제어

명령어 헬프
u-boot> i2c
i2c - I2C sub-system

Usage:
i2c crc32 chip address[.0, .1, .2] count - compute CRC32 checksum
i2c dev [dev] - show or set current I2C bus
i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device
i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device
i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)
i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)
i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)
i2c probe [address] - test for and show device(s) on the I2C bus
i2c read chip address[.0, .1, .2] length memaddress - read to memory
i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory
          to I2C; the -s option selects bulk write in a single transaction
i2c reset - re-init the I2C Controller
i2c speed [speed] - show or set I2C bus speed

 

버스 선택 및 probe
u-boot> i2c dev 0
Setting bus to 0
u-boot> i2c probe
Valid chip addresses: 00 4C 68 70 72

 

u-boot> i2c dev 1
Setting bus to 1
u-boot> i2c probe
Valid chip addresses: 4F

 

데이터 덤프
u-boot> i2c md 0x4f 0
0000: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "............…

u-boot> i2c md 0x4f 2 1
0002: ff

 

데이터 기록(0x4f 디바이스 2 offset에 0xf7 기록)
u-boot> i2c mw 0x4f 2 f7

 

참고

 

 

18 thoughts to “I2C Subsystem -1- (Basic)”

  1. 정말 감사합니다 !! 디바이스 드라이버 관련해서 이해가 되지 않는 부분들이 너무 많았었는데, 정리가… 존경스럽습니다. 이런 고급 정보들을 쉽게 알려주셔서 감사합니다!!

  2. 잘봤습니다. 한가지 궁금한 사항이 있는데, 혹시 i2c 드라이버상에서 SDA값을 사용자가 컨트롤 할 수 있나요?

  3. 안녕하세요? 기본적으로 i2c는 바이트 단위로 TX FIFO 레지스터에 값을 쓰는 것으로 전송하므로 비트 단위의 조작은 불가능합니다. 다만 특별한 하드웨어 환경에서 충분히 가능하게 만들 수 있는 방법이 두 가지가 있습니다.
    첫 번째, scl 및 sda에 해당하는 2개의 gpio 핀을 사용하여 i2c를 에뮬레이션 하여 사용하는 방법이 있습니다. (soft serial 처럼 soft i2c가 되겠네요.)
    두 번째 SoC에서 gpio와 i2c가 같은 핀을 사용하는 경우 i2c로 사용하다가 장애 시 pinctrl 시스템의 도움을 받아 gpio 모드로 변경한 후 리커버리를 목적으로 gpio api로 sda 및 scl을 에뮬레이션 합니다. (또한 이렇게 직접 scl 및 sda를 제어할 수 있도록 i2c에는 bit 알고리즘이 준비되어 있습니다.)

    어려운 방법이지만 위와 같이 gpio를 이용할 수 있으면 직접 sda 및 scl을 제어할 수 있습니다.
    도움이 되시길 바랍니다.

    1. 아니면 제가 NXP I2C관련 문서를 보면서 생각해 본건데, SLAVE단이 TRANSMITTER MODE에 갖히게 되면 SDA도 LOW로 갖히게 된다는데… 이걸 가능하게 하려면 I2C 커널 드라이브단에서 무엇을 건드려야 되나요? 계속 여기 저기 건드려 봐도 잘 모르겠네요…

      1. 그래서 sda가 low에 갖히는 해결하기 위해서 i2c-bus recovery 를 해주는 것으로 알고 있는데…

  4. i2c 버스 리커버리는 대부분의 i2c 드라이버에 구현되어 있지 않습니다. i2c 버스가 물려서 장애 시에 버스 초기화만을 제공하는 일부 드라이버도 있고, gpio를 사용하여 full로 sda 및 scl을 컨트롤하는 방법도 있습니다. 만일 gpio를 사용할 수 있으면 i2c-imx.c 드라이버 파일을 참고하셔서 nxp 드라이버를 수정하시기 바랍니다.
    struct i2c_bus_recovery_info 구조체에 구현 후크 함수를 연결하면 되는데, 간단하게 각 후크를 설명하자면
    (*recover_bus) 후크는 버스를 리커버리하기 위한 구현이 있어야 합니다. (gpio가 없는 시스템에서는 i2c 레지스터를 사용하여 초기화 등…)
    (*get_scl) 후크는 scl 값을 읽어오는 구현으로 주로 scl에 해당하는 gpio 값을 읽어옵니다.
    (*set_scl) 후크는 scl 값을 설정하는 구현으로 주로 scl에 해당하는 gpio 값을 설정합니다.
    (*get_sda) 후크는 sda 값을 읽어오는 구현으로 주로 sda에 해당하는 gpio 값을 읽어옵니다.
    (*prepare_recovery)) 후크는 리커버리 동작을 하기 전에 수행해야 하는 구현으로, 주로 pinctrl을 사용하여 gpio 모드로 전환합니다.
    (*unprepare_recovery)) 후크는 리커버리 동작을 완료한 후에 수행하는 구현으로, 주로 pinctrl을 사용하여 원래 i2c 모드로 전환합니다.

    개발 시에는 usb 로직 아날라이저 등을 사용하면 pc에서 sda 및 scl의 각 파형들을 분석할 수 있습니다.

  5. 문c 블로그 – I2C Subsystem -3- (Transfer) 글에서 gpio를 사용하는 경우는 그림과 설명을 해놓았으므로 참고하시기 바랍니다.

    추가로 아주 중요한 디바이스를 안정적으로 컨트롤 해야 하는 상황일 때에는
    gpio를 사용한 i2c 버스 리커버리 외에 별도의 gpio 핀을 사용하여 연결된 i2c 디바이스들을 리셋할 수 있도록 구현하는 경우도 있습니다. (리셋을 위한 hw 변경 필요)

    1. 안녕하세요. 추가적으로 궁금한 사항이 있는데요…
      제가 가지고 있는 i2c 소스 코드중에서 i2c-dev.c를 보면, i2cdev_ioctl_rdrw라는 함수가 있습니다.
      그런데 문제는요… 해당 함수로 입력되는 매개변수 2가지 중에서 arg값에 들어가는 특정 2 종류의 값들이 일련의 수행 순서에서
      꼬이게 되면 sda가 low로 갖히게 되더라고요…
      결론으로, 질문은 i2c-dev.c에 있는 i2cdev_ioctl_rdrw의 매개변수 arg는 무슨 역할을 하는 건가요? (솔직히… i2cdev_ioctl_rdrw 얘도 무슨 역할을 하는지… 대충 알고 있어서 이해가 잘 안되네요…)

  6. 안녕하세요? 오늘 이상하게 스팸 댓글 수백개가 공격해왔는데, 조원님 댓글마저도 자동 분류되어 휩쓸린것을 우연히 보고 옮겨왔습니다. ^^;
    궁금하신 내용 답변 드립니다.
    i2c-dev.c 파일은 커널이 아닌 유저 application을 위해 i2c 기능에 대응하는 캐릭터 디바이스 인터페이스를 제공합니다.
    유저 application에서 /dev/i2c-N 파일을 open한 후 I2C_RDWR command로 ioctl 호출합니다. 이 때 i2c_msg 구조체를 arg 인자로 커널로 전달하여 i2c_transfer() 함수를 이용하여 i2c 전송이 이루어집니다.
    참고로 i2c_msg 구조체는 송신과 수신 데이터가 담깁니다.

    데이터를 보낼 때 i2c 디바이스가 스마트하지 못한 경우가 대부분입니다. 따라서 정확한 데이터를 보내지 못한 경우 i2c 디바이스가 stuck 되는 경우가 발생합니다.
    이러한 경우 위의 리커버리 루틴으로 해결할 수 있습니다. 만일 i2c 디바이스가 i2c 버스 리커버리 루틴에도 동작하지 않는 최악의 경우는 i2c 디바이스를 gpio로 리셋하여 해결해야 합니다.

    1. 아.. 그랬었나요? 어쩐지 어제 안 올라 가더라구요…ㅠ;;
      답변 정말 감사드립니다. 이제는 어느정도 이해가 되는거 같아요.

      그런데, 설명해 주신 것에서 조금 보태서 궁금한 것이 있는데요…
      1) 우선 “i2c 기능에 대응하는 캐릭터 디바이스 인터페이스”라고 하셨는데, 이게 그러면 i2c sda 파형에서 나타나는 slave 주소나 read/write 신호 등을 나타내면서 양 디바이스간에 인터페이스 역할을 해준다.. 모 이런 말인가요?
      2) 그리고, “I2C_RDWR command로 ioctl호출”이라고 하셨는데요. 이것은 rdwr 명령어로 i2cdev_ioctl_rdrw()함수를 호출한다는 의미인가요?
      3) 마지막으로,

      struct i2c_msg {
      __u16 addr; /* slave address */
      __u16 flags;
      #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
      #define I2C_M_RD 0x0001 /* read data, from slave to master */
      #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
      #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
      #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
      #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
      #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
      #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
      __u16 len; /* msg length */
      __u8 *buf; /* pointer to msg data */
      };

      위 구조체가 i2c_msg 인데, 이 구조체가 arg로 전달되잖아요? 그런데, 앞 서 제가 말씀드린 것 처럼 순서가 섞였더니 low stuck이 됬다는 것은 i2c를 이용한 데이터 송신/수신이 섞였다고 이해하면 되나요?
      아… 제가 너무 두리뭉실하게 물어보고 또 애매하게 답하니.. 죄송스럽네요…
      다시 한번 답변은 정말 도움됬고, 감사합니다.

  7. 네. 번호 순서대로 답변드리겠습니다.

    1) i2c 드라이버는 커널에서 동작합니다. 커널 API를 사용하여 i2c 데이터를 송수신할 수 있습니다. 커널이 아닌 유저 application에서 커널용 i2c 호스트 드라이버를 이용할 때 가장 쉽게 사용할 수 있는 방법이 i2c-dev라고 하는 캐릭터 디바이스(/dev/i2c-N)를 사용하는 방법입니다. 샘플은 인터넷에 여러 개가 있고, 그 중 몇 개를 소개합니다.
    http://forum.falinux.com/zbxe/index.php?document_srl=504163&mid=ezboard
    http://wittgens.pe.kr/blog/blog_wittgens/4620
    https://m.blog.naver.com/PostView.nhn?blogId=khsrdc&logNo=110165550647&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F
    위와 같이 캐릭터 디바이스를 사용하여 slave 주소를 지정하고 i2c 호스트 컨트롤러를 통해 ioctl 또는 read/write 등을 수행할 수 있습니다.

    2) 번 항목은 위의 유저 application 샘플에서 I2C_RDWR 명령을 사용하여 combined format으로 쓰고, 읽고를 할 수 있습니다. 아래는 이에 대한 원문이고, 인터넷을 통해 샘플 소스들을 몇 개 보시면 이해하실 겁니다. (유저에서 I2C_RDWR 명령을 사용하면 커널 쪽 i2cdev_ioctl_rdrw() 함수가 호출됩니다.)
    https://www.kernel.org/doc/Documentation/i2c/dev-interface

    3) low stuck이 되는 이유는 디바이스가 원하는 포맷을 사용하지 않고 없는 곳에 읽거나 기록하려고 할 때, 또는 읽고 쓰고 순서를 잘못했을 때 즉 장치가 사용하는 프로토콜을 사용하지 않아 발생할 수 있습니다. 그 외에도 여러 가지 원인이 있습니다만 처음 프로그래밍할 때 발생하는 문제들은 대부분 읽고 쓰는 순서 및 규칙이 잘못되었을 때 발생합니다. i2c 데이터를 송수신하는 방법이 여러 가지가 있고, i2c 디바이스 종류마다 쓰거나 읽을 데이터가 모두 다르므로 해당 디바이스에 대한 규칙(프로토콜)을 지켜야 합니다.만일 read만 허용하는 장비에서 I2C_RDWR 같은 것을 사용하면 디바이스가 stuck이 될 수도 있습니다.

    참고로 전문적인 분석이 필요한 경우 도움이 되는 방법은 다음과 같습니다.
    – HW에 송/수신 되는 비트들을 분석하려면 커널 트레이스를 이용하는 방법이 있습니다. (커널 옵션도 바꿔야 하고 커널 컴파일도 해야하므로 복잡합니다)
    – 간단히 USB 로직 아날라이저 장비를 사용하는 방법이 있습니다. (중국제는 불과 수천원이더군요. https://www.youtube.com/watch?v=rR5cEFRO9_s)

    열심히 해결책을 찾기 바랍니다.

    1. 이런 애매한 질문에 이렇게 성실히 답해 주셔서 정말 감사합니다.
      이해하는데, 많은 도움이 되었습니다. 감사합니다.

    1. 안녕하세요. 문영일님… 또 왔습니다. 궁금한게 있어서요… (정말 커널을 모르는 사람이다 라고 생각해 주세요.)
      low stuck이 발생하고 recovery 함수로 다시 심폐소생술 하듯이 빠져나오잖아요? 그런데, 궁금한게 SDA LOW로 되고 나서
      어떻게 recovery 함수로 넘어가나요?

  8. 각 i2c host adapter에 구현된 i2c 전송 함수에서 전송에 문제가 있을 때 i2c 리커버리 커널 API인 i2c_recover_bus() 함수를 호출하도록 구현합니다.

    예) drivers/i2c/busses/i2c-imx.c
    static int i2c_imx_xfer(struct i2c_adapter *adapter,
    struct i2c_msg *msgs, int num)
    {
    . unsigned int i, temp;
    . int result;
    . bool is_lastmsg = false;
    . struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
    .
    . dev_dbg(&i2c_imx->adapter.dev, “< %s>\n”, __func__);
    .
    . /* Start I2C transfer */
    . result = i2c_imx_start(i2c_imx);
    . if (result) {
    . if (i2c_imx->adapter.bus_recovery_info) {
    . i2c_recover_bus(&i2c_imx->adapter);
    . result = i2c_imx_start(i2c_imx);
    . }
    . }

    1. 제가 가지고 있는 i2c-imx.c에서는
      /* Start I2C transfer */
      result = i2c_imx_start(i2c_imx);
      if (result)
      goto fail0;

      정도만 정의되어 있는데, 이럴 경우 i2c_bus_recovery()함수로
      못 뛰지 않나요? 지금 보면 fail()함수로 가게 되어 있는데…

      그리고, 명절 잘 보네세요. ^^

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다