PCI Subsystem -1- (Basic)

최근 PC 및 Embedded 시스템에서 가장 많이 사용하는 고속 Bus인 PCI Express를 위주로 설명한다. 먼저 PCI Bus에 대해 간략히 알아보고 PCI Express를 더 자세히 비교하여 설명하기로 한다.

 

PCI

PCI(Peripheral Component Interconnect) 아키텍처는 주변 장치 연결을 위해 ISA(Industry Standard Architecture bus), EISA, MCA, VESA 등의 버스 표준을 대체하기 위해 설계되었다. 오늘날 PC에서 가장 널리 사용되는 버스 규격이다.

 

다음 그림은 버스 타입별로 비트 수, 클럭 및 핀 수를 비교하였다.

 

PCI는 다음과 같은 주요 목표로 설계되었다.

  • 더 빠른 전송 속도의 달성
    • 33Mhz 클럭부터 시작하여 66Mhz까지가 가장 많이 사용된다. (25Mhz 시스템도 있다.)
    • PCI/X에서는 66Mhz, 133Mhz, 266Mhz 등으로 발전되었다. (533Mhz 도 시도되었다.)
  • 플랫폼 독립성
    • 일부 플랫폼에 종속되지 않고 대부분의 플랫폼에서 사용된다.
    • 예) x86, IA-32, IA-64, Alpha, MIPS, PowerPC, ARM, …
  • 디바이스 추가/제거의 편이성
    • 버스에 장치가 쉽게 연결되고 해제될 수 있는 구조를 채택하였다.

 

PCI와 ISA(EISA)와의 연동 관계

32비트 PC가 처음 출현했을 때 사용하던 방식으로 ISA 호환을 위해 PCI 버스 하단에서 ISA 버스를 지원함을 알 수 있다.

 

ISA 슬롯

8비트 및 16비트 ISA 버스가 채택된 ISA 슬롯 모습을 알아본다.

  • 16비트 ISA 슬롯은 16비트 ISA 카드 및 8비트 ISA 카드 둘 다 지원한다.

 

PCI & PCI/X 슬롯

32비트 PCI 및 64비트 PCI 버스가 채택된 PCI 및 PCI/X 슬롯 모습을 알아본다.

  • 64비트 PCI/X 슬롯은 64비트 PCI/X 카드 및 32비트 PCI 카드 둘 다 지원한다.
  • PCI 및 PCI/X 슬롯이 지원하는 전압(Volatage)을 구분할 수 있도록 슬롯에 홀(Hole)의 위치를 바꿔 종류가 다른 카드가 꽂히지 않게 하였다.
  • 아래 예)
    • 5V PCI 슬롯 3개
    • 3.3V PCI/X 슬롯 2개

 

PCI 핀

1 ~ 수 개의 pci 슬롯을 통해 pci 버스를 공유한다.

  • PCI 버스는 4개의 legacy 인터럽트 라인을 지원한다. (INTA#, INTB#, INTC#, INTD#)

 

PCI 전송

다음은 버스 컨트롤러가 발생시키는 시그널 라인들에 대한 설명이다. (# 기호는 low active로 동작함을 의미한다.)

  • CLK
    • 33/66Mhz 기반 클럭(CLK) 시그널
    • 고정 주기로 클럭이 발생되며 rising edge 신호에 동기하도록 한다.
  • RST#
    • Reset
    • 1ms 이상의 low active 시그널을 사용하여 pci 버스에 연결된 컨트롤러와 모든 pci 디바이스를 HW 리셋시킨다.
    • 리셋이 해제된 후 5 사이클 이내에 각 디바이스들은 전송을 요청하면 안된다.
  • IDSEL
    • Initialization Device Select
    • 타겟 디바이스가 이 시그널을 읽어 configuration 레지스터 중 하나를 읽을 때 사용된다.

 

버스 마스터가 발생시키는 시그널 라인들에 대한 설명이다.

  • FRAME#
    • 현재 initiator(버스 마스터)로부터 버스 엑세스가 시작되고 계속됨을 알리기 위해 low active 시그널을 발생한다.
  • ADx
    • Address / Data
    • initiator가 시 분할 방법을 사용하여 32비트 주소 또는 데이터 비트 둘 중 하나를 출력한다.
  • C/BEx#
    • Command / Byte Enables
      • initiator가 트랜잭션 타입을 정의하며 4 비트 조합을 사용한다. (C/BE0 ~ C/BE3)
    • 명령 또는 바이트 enable
  • IRDY#
    • Initiator Ready for data
    • 현재 initiator(버스 마스터)가 AD# 및 C/BEx를 준비한 후 Ready되었음을 알리기 위해 low active 시그널을 발생한다.
    • high일 때에는 idle 상태이다.
    • 전송은 반드시 8 사이클 이내에 이루어져야 한다.
  • LOCK#
    •  Initiator가 atomic 트랜잭션을 처리하기 위해 현재 타겟 디바이스를 고정(lock)하도록 low active 시그널을 발생한다.

 

다음 파란색 시그널은 타겟 디바이스가 발생시키는 시그널 라인들에 대한 설명이다. (# 기호는 low active로 동작함을 의미한다.)

  • STOP#
    • 데이터 전송 중 타겟 디바이스가 전송을 중단해달라고 요청할 때 low active 시그널을 발생한다.
  • TRDY#
    • Target Ready
    • 타겟 디바이스가 응답하며, 준비되어 전송 가능한 상태에서 low active 신호를 시그널을 발생한다.
  • DEVSEL#
    • Device Select
    • 타겟 디바이스가 자신이 선택되었음을 initiator에게 알리기 위해 low active 시그널을 발생한다.
    • initiator가 6 사이클 이내에 이 시그널을 받지 못하면 취소된다.
  • INTA# ~ INTD#
    • 4개의 인터럽트 라인을 가지며 PCI 디바이스는 이 라인을 share하여 사용하며 인터럽트를 발생시킬 때 low active 시그널을 발생한다.

 

 

PCI Bus Arbiter

PCI Bus Arbiter의 구성은 다음과 같이 Centralized 또는 Daisy Chain 방식을 사용한다.

  • Centralized Arbiter
    • 별도의 회로이지만 보통 PCI host 또는 PCI 브리지에 통합되어있다.
    • 여러가지 선택 알고리즘
      • Fairness (default)
        • 가장 중요한 디바이스를 우선 선택한다.
        • 디바이스별 우선 순위에 의해 최대 Latency 레지스터를 설정한다. 그런 후 이 latency를 만족하도록 디바이스를 선택한다.
      •  FIFO
        • 가장 먼저 요청한 디바이스 우선 선택
      • RR
        • 라운드 로빈 방식에 의한 선택

  • Daisy Chain Arbiter
    • Arbiter에 가까운 슬롯에 위치한 디바이스가 먼저 선택된다.

 

다음 시그널은 PCI 버스 arbiter 회로가 수신또는 발생시키는 시그널 라인들에 대한 설명이다.

  • REQ#
    • 버스 사용 요청을 의미한다.
    • 버스를 사용하고자 하는 버스 마스터 디바이스가 PCI bus Arbiter에 요청하며 여러 버스 마스터 디바이스가 동시 요청한 경우 GNT#를 통해 하나의 디바이스를 선택한다.
    • 참고로 대부분의 PCI 디바이스들은 bus-master 기능을 갖는다. 이 들은 peer-to-peer 전송 및 DMA 전송을 위해 버스 요청을 할 때 다른 PCI 디바이스들과 버스를 차지하기 위해 경합(arbitration) 한다.
  • GNT#
    • PCI 버스 arbiter가 발생시키는 신호로 arbitration 상태에서 승리한 경우를 의미한다. 그런 경우 다음 시작 시 처리하게됨을 의미한다. (first request ownership)

 

PCI 전송 에러 체크

PCI 전송 에러를 감지하는 회로이다. 오래된 pc에서는 단순하게 처리하기 위해 전송 에러를 감지하면 NMI를 발생시킨 후 시스템을 정지시키기도 했다.

  • PAR
    • Initiator가 Parity 부호로 마스터 버스가 짝수 사이클의 주소 및 데이터에 대해 parity 부호를 전송한다.
  • PERR#
    • Parity Error
    • 타겟 디바이스가 짝수 사이클에 수신한 데이터와 그 다음 사이클에 받은 Parity 부호를 비교하여 문제가 발생하였을 때 PERR#에 low active 시그널을 발생시킨다.
  • SERR#
    • System Error
    • 위와 동일하지만 타겟 디바이스가 데이터가 아닌 타겟 주소에 대한 전송에 문제가 발생했을 때 SERR#에 low active 시그널을 발생시킨다.

 

 버스 접근 Latency

Bus Access Latency는 다음 3가지를 포함한다. 참고로 MLT(Master Latency Timer)는 configuration 레지스터 중 offset=14 바이트 위치에 있다.

  • Arbitration Latency
  • Bus Acquisition Latency
    • 최대 16 클럭 이내
  • Initiator and Target Latency
    • 마스터의 경우 최대 8클럭 이내
    • 타겟의 경우 최대 16클럭 이내

 

PCI Command – C/BE[3:0]#

4비트를 사용하여 명령 타입을 구분한다.

  • Special Cycle
    • AD[15:0]를 사용하여 메시지 전달
      • 0000h
        • Shutdown
      • 0001h
        • Halt

 

 

PCI History

PCI H/W는 다음과 같이 3가지 종류의 버스를 사용한다.

  • PCI
    • 병렬 32bit 버스
  •  PCI/X
    • 병렬 64bit 버스
  • PCIe
    • 직렬 버스

 

PCI

  • PCI 2.2 규격부터슬롯 크기를 줄인 miniPCI 규격이 추가로 발표되었다.

 

PCI/X

  • PCIe로 대체되어 오늘날 거의 사용되지 않는다.

 

 

PCI Address Space Map

PCI는 다음과 같이 3가지 주소 영역을 지원한다.

  • Memory Map
    • cpu가 사용하는 4G 영역의 메모리 주소 영역을 지원한다.
  • I/O Map
    • 64K 영역의 I/O 메모리 주소 영역을 지원한다.
  • PCI Configuration Space
    • 16MB 영역의 PCI configuration 영역을 지원한다.

 

Configuration Space

PCI Configuration

pci configuration은 다음과 같은 수의 256바이트 configuration을 access할 수 있다.

버스(256) * 디바이스(32) * 펑션(8)

 

Single Function & Multi Function Device

Single Function Device
  • 하나의 물리 디바이스내에 하나의 기능을 가진 pci 디바이스
Multi Function Device
  • 하나의 물리 디바이스내에 여러개의 function을 가진 pci 디바이스이다.
  • 물리적으로 하나의 패키지로 묶여 있지만 각각의 function들은 별도의 pci 디바이스이다.

 

Legacy Interrupts

INTA ~ INTD 인터럽트 라인

  • 싱글 펑션 디바이스의 경우 레거시 인터럽트를 사용하려면 INTA 라인만을 지정하여 사용할 수 있다.
  • 멀티 펑션 디바이스의 경우 각각의 펑션은 레거시 인터럽트로 INTA ~ INTD 까지 4개의 인터럽트 라인 중 하나를 지정할 수 있다.
    • 여러 개의 펑션이 하나의 인터럽트 라인을 공유하여 지정할 수도 있다.
    • 예) 최대 8개의 펑션을 가진 pci 디바이스가 7개의 펑션이 INTA를 지정하고, 마지막 1개의 펑션만 INTB를 지정한다.

 

Legacy Interrupt 라우팅

시스템에 여러 개의 PCI 슬롯을 지원하는 경우 각 슬롯의 INTA ~ INTD 라인들의 라우팅은 시스템 설계에 따라 다르다.

  • 독립된 인터럽트 라인
    • 각각의 PCI 슬롯에 사용된 INTA ~ INTD 까지의 인터럽트를 인터럽트 컨트롤러의 인터럽트 라인에 중복되지 않도록 독립적으로 연결한다.
      • 슬롯 1번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
      • 슬롯 2번 INTA, INTB, INTC, INTD -> 60, 61, 62, 63
  • 공유 인터럽트 라인
    • 모든 슬롯의 INTA를 하나로 묶어 인터럽트 컨트롤러의 인터럽트 라인에 연결한다. 나머지 INTB ~ INTD도 같은 방식으로 연결하는 방법으로 4개의 인터럽트 라인만을 공유하여 사용한다.
      • 슬롯 1번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
      • 슬롯 2번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
  • Mixed 인터럽트 라인
    • 위의 2 가지 방법을 섞어 구성한다.

 

MSI(Message Signaled Interrupt)

PCI 디바이스가 레거시 인터럽트 대신 MSI를 사용하면 다음과 같은 장점들이 있다.

  • 인터럽트 라인의 트레이스(라우팅)가 필요 없다.
  • 멀티 펑션 디바이스들이 같은 번호의 인터럽트 라인을 공유하여 사용할 필요없다.
  • 디바이스 드라이버들 간의 인터럽트 chain이 필요없어진다.

 

PCI Express

 

PCI Express 슬롯 (PCIe)

각각의 lane 수를 가지는 PCIe 슬롯 및 PCIe 카드를 알아본다.

  • PCI Express 버스는 x1, x2, x4, x8, x12, x16, x32 레인을 지원한다.
  • 표준 PCI Express 슬롯 형태로는 아래 그림과 같이 x1, x4, x8, x16 레인을 지원한다.

 

 

miniPCI 및 miniPCIe 슬롯

PCI 및 PCIe의 미니 사이즈로도 사용되고 있다. 이 폼팩터들은 점점 사용되지 않고 있으며 이들은 m.2 폼팩터로 사용 추세가 이동하고 있다.

miniPCIe 핀

 

M.2

  • m.2는 NGFF(Next Gen Form Factor)로 알려져 있으며 여러가지 버스들을 조합하여 제공한다.
    • PCIe 3.0
      • 기존 mPCIe 슬롯이 1개의 레인만을 지원한 것에 비해 최대 x4 레인을 지원한다.
        • x2 레인을 지원하는 슬롯은 m.2 B key (양방향 4GB)
        • x4 레인을 지원하는 슬롯은 m.2 M key (양방향 8GB)
    • SATA 3.0
      • 최대 6Gbps 속도
    • USB 3.0
      • 최대 5Gbps 속도
    • 그 외
      • USB 2.0, I2C, SMBus, DP x4, audio, UIM, SSIC, SDIO, UART, PCM, FMI
  • keying
      • 75개의 핀 포지션 중 key A ~ M 까지의 자리를 막아 구분한다.
  • 표준 사이즈
    •  폭(mm)
      • 12, 16, 22, 30
    • 길이(mm)
      • 16, 26, 30, 38, 42, 60, 80, 110
    • 두께(mm)
      • 상면을 사용한 싱글 사이드 또는 상/하면을 사용한 더블 사이드
        • 상면
          • 1.20, 1.35, 1.50
        • 하면
          • 0.7, 1.35, 1.50
    • 예) WWLL-HH-K-[K]
      • 2280 -> 폭은 22mm, 길이는 80mm
      • D1 -> Double (상면은 1.20mm, 하면은 1.35mm)
      • BM -> b key, m key

 

M.2 Keying

M.2 소켓(슬롯)을 통해 여러 가지 버스를 제공하는데 이의 제공 여부 구분을 정의한다.

  • M.2 소켓에서 Key ID에 맞는 핀들을 막았으므로 이 M.2 소켓에 연결할 수 있는 M.2 카드도 동일한 key ID를 보유해야 한다.
    • M.2 슬롯은 1개의 key ID를 가진다.
    • M.2 카드는 1~2 개의 key ID를 가지고 있다.
  • 가장 많이 사용하는 M.2 소켓의 key ID는 다음과 같다.
    • Key B
      • M.2 소켓에서 B 포지션에 해당하는 12~19번 핀 자리에 핀들이 연결되지 못하게 연결 구멍을 막는다.
        • B 포지션만 막는 제품은 많지 않고, 대부분 B+M을 사용한다. 이 경우 key M을 사용하는 카드도 허용되지만 x4 레인을 사용하지 못한다.
    • Key M
      • M.2 소켓에서 B 포지션에 해당하는 59~66번 핀 자리에 핀들이 연결되지 못하게 연결 구멍을 막는다.

M.2 카드에서 가장 많이 사용하는 key ID는 다음과 같다.

  • Key B+M
    • M.2 카드에서 B 포지션 및 M 포지션에 해당하는 핀 자리를 hole 상태로 비워둔다.
      • Key B 및 Key M을 사용하는 M.2 소켓에 연결할 수 있다.
  • Key M
    • M.2 카드에서 M 포지션에 해당하는 핀 자리를 hole 상태로 비워둔다.

 

아래 그림을 보고 key B+M과 key M 타입을 구분하는 방법을 알아두자.

  • 아래 카드 그림은 top면에서 볼때의 기준이다.

 

다음은 여러 가지 SSD 타입에 대해 설명하였다.

  • SATA 커넥터
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 SATA 커넥터에 연결된 SSD를 사용한다.
    • 예) 삼성 2.5인치 850 SATA SSD
  • mSATA 커넥터
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 mSATAQ 커넥터에 연결된 SSD를 사용한다.
    • 예) 삼성 850 EVO mSATA SSD
  • M.2 커넥터 – M.2 SATA
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 M.2 커넥터의 SATA3 버스에 연결된 SSD를 사용한다.
    • 예) 삼성 850 EVO M.2 2282 SSD
    • 참고로 USB 3.0/3.1 등의 외장형 SSD 케이스에 포함되는 경우 대역폭이 모자라므로 M.2 SATA 타입의 SSD를 사용한다.
  • M.2 커넥터 – M.2 PCIe AHCI
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 M.2 커넥터의 PCIe 버스에 연결된 SSD를 사용한다.
    • 현재는 이 방식을 거의 사용하지 않는다.
  • M.2 커넥터 – M.2 PCIe NVMe – 가장 빠름
    • NVME 프로토콜을 채용한 드라이버를 사용하여 m.2 커넥터의 PCIe 버스에 연결된 SSD를 사용한다.
    • 예) 삼성 970 EVO M.2 2282 SSD (x4 레인용)
    • 예) 킹스톤 A1000 M.2 2280 (x2 레인용)

 

위에서 구분한 내용을 아래 그림을 보고 실제 제품 형태와 비교해보자.

  • 주의할 것은M.2 key B+M 타입의 카드들은 외형만을 보고 SATA 버스를 사용하는 SSD인지 고속 PCIe 버스를 사용하는 SSD인지 구분할 수 없으므로 주의해야 한다.

 

PCIe 버전

  • 오늘날 가장 많이 사용되는 규격이다.
  • 최근 임베디드 시스템에서 사용하는 PCI 규격이다.

 

PCIe 속도 비교

 

PCI Express 특징

  • 저비용
    • 저 비용으로 대량 양산 가능한 솔루션이다.
  • 인터커넥트
    • 커넥터나 케이블링을 통해 칩-to-칩 또는 보드-to-보드 연결 구성이 가능하다.
  • 새로운 폼 팩터
    • 모바일 및 모듈라 폼팩터 및 카트리지 폼팩터를 지원하는 새로운 폼 팩터이다.
  • PCI 호환
    • 기존 PCI 드라이버를 수정 없이 동작시킬 수 있는 호환성을 제공한다.
  • 성능
    • low-overhead, low-latency 통신을 통해 밴드폭과 링크 효과를 최대화한다.
    • 최소한의 핀 수로 높은 밴드폭을 지원한다.
    • lane들을 통합한 scalable 성능을 낼 수 있다.
  • 향상된 기능
    • 서로 다른 데이터 타입과 오더링 룰을 제공한다.
    • 절전 관리
    • 미니 PCI Express
    • QoS
    • 기존 핫플러그 및 native 핫플러그 지원 및 비동기 removal 지원한다.
    • 모든 타입의 데이터에 대해 데이터 정합성(Data Integrity)을 제공한다.
    • 진보된 에러 리포팅 및 에러 핸들링을 통해 향상된 폴트 isolation 및 리커버리 솔루션을 제공한다.
    • 간편한 테스트 지원

 

PCIe 전송 H/W

PCI Express 링크

패킷을 주고(Transmit) 받을(Receive) 수 있는 하나의 lane으로 PCI Express 링크를 구성할 수 있다. 또한 2 개 이상의 lane을 묶어(aggregation) 하나의 링크로 만들면 통신 속도를 통합한 lane 만큼 높일 수 있다.

  • 1 개의 lane 부터 최대 32개의 lane을 하나 링크로 묶어 고속 통신이 가능하다.
    • x1, x2, x4, x8, x12, x16, x32 lane을 지원한다.
  • 1개의 lane에는 TX 페어와 RX 페어가 사용된다.
  • 1개의 링크는 두 개의 디바이스끼리만 통신할 수 있다.

 

PCI Express Fabric Topology

다음 그림에서 SoC가 Root Complex 가상 PCI Bridge를 통해 PCIe 버스를 3개 제공하는 것을 보여준다. 각각의 버스를 다음과 같이 사용하고 있다.

  • BUS 1
    • PCIe 버스를 브릿지를 통해 PCI 또는 PCIX 버스로 변환한다.
  • BUS 3
    • PCIe 버스에 PCI Express Switch를 연결하여 더 많은 PCIe 버스를 제공하기 위해 확장한다.
  • BUS 9
    • PCIe 버스에 하나의 디바이스만 곧장 연결하여 사용한다.

PCI 버스가 CPU와 연결되는 가장 상위는 Root Complex Device이고 버스를 확장하기 위해 PCI Switch를 사용할 수 있다.

 

1) Root Complex 디바이스

루트 컴플렉스(RC)는 CPU/메모리 서브시스템을 I/O 연결할 수 있는 I/O 계층 구조의 루트이다.

  • 루트 컴플렉스는 1개 이상의 PCI Express 포트를 가질 수 있다.
  • PCI Express 포트는 하나의 End Point 디바이스와 연결되거나, 브리지나 스위치를 통해 확장할 수 있다.
  • 구현 옵션에 따라 peer-to-peer 트랜잭션 전송이 가능하다.
  • 드물지만 디바이스별 페이로드 제한에 따라 하나의 패킷을 분할하여 전송하는 경우도 있다.

PCI Express 루트 컴플렉스 디바이스를 더 자세히 살펴보면 다수의 내부 PCI-PCI 브리지 구조로 구성된다.

  • 호스트 브릿지 디바이스
    • 호스트에 연결된다.
  • 루트 PCI Express 포트
    • 하나의 PCI Expresss이다.
    • 브리지의 세컨더리 버스에 해당하는 Configuration Space에 매핑된다.

 

 

2) Endpoint 디바이스

  • Legacy PCI Endpoint
    • Type 0 Configuration 헤더를 제공하는 Function이어야 한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 제공할 수도 있다.
    • I/O 요청을 발생시킬 수 있다.
    • Locked 요청을 할 수 없다.
    • MSI/MSI-X를 지원한다.
    • 4G 주소를 초과하는 메모리 트랜잭션을 할 수 없다.
      • 32비트 어드레싱 레지스터
  • PCI Express Endpoint
    • Type 0 Configuration Space를 제공하는 Function이어야한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 발생시키면 안된다.
    • Locked 요청을 지원하지 않는다.
    • MSI/MSI-X를 지원한다.
    • 4G 주소를 초과하는 메모리 트랜잭션이 가능하다.
      • 64비트 어드레싱 레지스터
      • 32비트 어드레싱도 가능하다.
  • Root Complex Integrated Endpoint
    • 루트 컴플렉스의 내부 로직에 구현된 루트 포트이다.
    • Type 0 Configuration Space를 제공하는 Function이어야한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 발생시키면 안된다.
    • Locked 요청을 지원하지 않는다.
    • MSI/MSI-X를 지원한다.
    • 32비트 BAR를 통한 메모리 리소스를 지원한다.
    • PCI Express Extended Capability를 구현하면 안된다.
    • 전원 관리를 구현하지 않는다.
    • 핫플러그를 지원하지 않는다. (당연하다.)
    • 루트 컴플렉스가 노출하는 계층에 나타나면 안된다.
    • 스위치에서도 나타나면 안된다.

3) Switch 디바이스

스위치는 다 수의 가상 PCI-to-PCI 브리지 디바이스들로 내부적으로 구성되어 있다.

  • 멀티캐스트를 제외한 트랜잭션을 라우팅 주소 기반으로 PCI 브리지 메커니즘을 사용하여 전달(forward)한다.
  • 다운스트림 포트 방향으로 Locked 요청을 지원한다.
  • 모든 활성화된 스위치 포트는 플로우 컨트롤을 지원할 수 있어야 한다.
  • 패킷을 작은 사이즈로 분리하는 것을 지원하지 않는다.
  • 인바운드된 포트 간에 중재는 라운드 로빈 또는 가중치 라운드 로빈을 구현할 수 있다.
  • Endpoint는 스위치 내부 버스의 가상 PCI-to-PCI 브릿지의 피어로 노출되면 안된다.
  • 하나의 업스트림 PCI Express 포트와 다수의 다운스트림 PCI Express 포트로 구성된다.

 

4) Root Complex Event Collector

  • 메모리 및 IO 리소스를 디코딩할 필요가 없다.
  • 다비이스/포트 타입 값으로 식별할 수 있다.
  • 베이스 클래스(0x08), 서브 클래스(0x07)과 프로그래밍 인터페이스(0x00) 값을 사용한다.
  • 싱글 로지컬 버스에 상주한다.
  • 옵션으로 제공된다.

 

5) PCI Express to PCI/PCI-X Bridge

  • PCI Express fabric과 PCI/PCI-X 계층간의 연결을 지원한다.

 

전송 계층

PCI 전송에 다음 3가지의 전송 계층을 사용한다.

  • Transaction Layer
  • Data Link Layer
  • Physical Layer

 

3 계층의 TLP(Transaction Layer Packet) 또는 2 계층의 DLLP(Data Link Layer Packet)을 전송하며 각 계층에서 취급하는 데이터는 다음과 같다.

 

TLP(Transaction Layer Packet)

  • Physical Layer
    • Framing
      • Start (1B)
      • End (1B)
  • Data Link Layer
    • Sequence Number (2B)
    • LCRC (1DW)
  •  Transaction Layer
    •  Header (3~4DW)
    • Data (0~1024DW)
    • ECRC (1DW)

 

DLLP(Data Link Layer Packet)

  • Physical Layer
    • Framing
      • Start (1B)
      • End (1B)
  • Data Link Layer
    • DLLP Type + Misc (1DW)
    • CRC (2B)

 

Transaction Layer

  • TLPs(Transaction Layer Packets)의 생성과 처리
    • 메모리 트랜잭션
      • Memory 주소 공간에 매핑
    • I/O 트랜잭션
      • I/O 주소 공간에 매핑
    • Configuration 트랜잭션
      • 디바이스 펑션 설정 및 셋업
    • 메시지 트랜잭션
      • 이벤트 시그널링에서 일반적인 메시징까지
  • 플로우 컨트롤
  • 가상 채널 관리
  • PCI/PCI-X 호환 오더링
  • 트래픽 클래스 differentiation 포함

 

TLPs(Transaction Layer Packets) Header 포맷

트랜잭션 레이어 패킷은 다음과 같은 구성을 사용한다. 전송 시 Byte 0 위치부터 시작한다.

  • TLP Prefixes (optional)
  • TLP Header
  • Data Payload
  • TLP Digest (optional)

 

 

TLP 헤더

다음 그림은 TLP 헤더와 패킷 포맷을 보여준다. 패킷 포맷에 따라 1~4바이트의 TLP 헤더를 갖는다.

  • 아래 그림에 이어 32비트 주소 또는 64비트 주소가 추가된 주소 라우팅을 표시한다.
    • 32비트 주소 라우팅의 경우 3 DW 헤더 길이를 갖는다.
    • 64비트 주소 라우팅의 경우 4 DW 헤더 길이를 갖는다.

  • Fmt
    • 패킷 포맷
  • Type
    • 패킷 타입
  • R
    • Reserved로 반드시 0 이어야 한다.
  • TC
    • 트래픽 클래스로 QoS 목적으로 사용한다.
      • 000: TC0로 가장 우선 순위가 느린 베스트 Effort 처리 방식이다.
      • 001 ~ 111: TC1 ~ TC7으로 스케줄러를 통해 처리 순서를 제어할 수 있다.
  • Attr
    • 속성
  • LN
    • 메모리 요청이 LN Read, LN Write 이거나,
    • 컴플리션이 LN Completion인 경우이다.
  • TH
    • TLP 처리 힌트를 의미한다.
  • TD
    • TLP 다이제스트를 의미한다.
  • EP
    • poisned TLP를 의미한다.
  • Attr
    • 오더링 타입을 표시하는 속성이다.
    • Attr[2:1]
      • 00: 디폴트 오더링
        • PCI Strongly 오더드 모델이다.
      • 01: 릴렉스드 오더링
        • PCI-X 릴렉스드 오더링 모델이다.
      • 10: ID 베이스 오더링
        • 리퀘스터와 컴플리터 ID 기반의 독립적인 오더링이다.
      • 11: 릴렉스드 오더링 + ID 베이스 오더링
    • Attr[0]
      • 0: 디폴트
        • 하드웨어가 수행하는 캐시 코히런시
      • 1: No Snoop
        • 하드웨어가 캐시 코히런시를 수행하지 않는다.
  • AT
    • 주소 타입으로 주소  변환이 필요한지 여부를 의미한다.
    • 00: 디폴트로 변환하지 않는다.
    • 01: 변환 요청
    • 10: 변환됨
    • 11: Reserved
  • Length
    • DW(4바이트, 더블 워드) 단위의 데이터 페이로드 길이
    • 0~1023까지 지정이 가능하다. 단 0의 경우 1024 DW를 의미한다.
    • 트랜스미터(Transmiter) 또는 리시버(Receiver) 디바이스 컨트롤 레지스터의 Max_Payload_Size 필드(DW 단위)를 초과하면 안된다.

 

패킷 포맷

 

TLP 타입

다음 표에서 트랜잭션 타입을 보여준다.

 

 

트랜잭션 처리 방향

 Non-Posted (응답 확인 필요)

  • Memory Reads.
    • MRd 요청: 디바이스가 Root Complex에 메모리 읽기 요청
    • CplD 응답: Root Complex에 연결된 시스템 메모리를 읽어 디바이스에 Completion 데이터 응답
  • Memory Locked Reads
    • MRdLk 요청: Root Complex가 락을 유지한채 디바이스에 메모리 읽기 요청
    • CplDLk 응답: 디바이스가 Completion 데이터 응답
  • IO 및 Configuration Writes
    • IOWr 요청: Root Complex가 디바이스에 IO 쓰기 요청
    • Cpl 응답: 디바이스가 데이터를 기록한 후 Completion 응답

Posted (응답 확인 없음)

  • Memory Writes
    • MWr 요청: Root Complex가 디바이스에 메모리 기록 요청한다. 그 후 응답은 받지 않는다.
  • Message Writes
    • Msg 요청: 트랜잭션 메시지 라우팅

 

트랜잭션 라우팅

다음과 같이 두 가지 타입의 라우팅이 지원된다.

  • 주소 라우팅
    • 64비트 주소 라우팅
    • 32비트 주소 라우팅
  • ID 라우팅
    • non-ARI ID 라우팅
    • ARI ID 라우팅

 

64/32 비트 주소 라우팅

 

  • PH
    • 00: 양방향 데이터
    • 01: 요청자
    • 10: 타겟
    • 11: 우선순위 포함한 타겟

 

ID 라우팅
  • non ARI ID 라우팅
    • Bus 번호, 디바이스 번호 및 펑션 번호 세 가지를 사용한다.
  • ARI ID 라우팅
    • Bus 번호 및 펑션 번호 두 가지를 사용한다.

 

아래 그림은 4 DW 길이의 TLP 헤더이다. 포맷이 0 x 0 인 경우 3 DW 길이의 TLP 헤더를 사용한다.

 

First/Last DW Byte Enables

TH 비트가 설정된 메모리 읽기 요청은 다음표와 같이 첫번째와 마지막 DW에 대한 바이트 enable을 표시한다.

 

Request (메모리, I/O, Configuration)

Request Memory

다음 그림은 메모리, I/O 및 Configuration 트랜잭션 데이터를 요청할 때 사용하는 TLP 헤더이다. (Memory 32비트는 생략)

 

Data Link Layer

  • 링크 트레이닝
    • 링크 폭
    • 링크 데이터 rate
    • Lane reversal (Lane 역 방향 순서 지원)
    • Polarity inversion (극성 반대 지원)
    • 멀티 레인에서 레인 to 레인 de‐skew
  • 전원 관리
    • 트랜잭션 게층에서 요청한 전원 상태를 수락하고 물리 계층으로 전달한다.
    • 활성/재설정/연결 해제/전원 관리 상태를 트랜잭션 계층으로 전달한다.
  • 데이터 프로텍션, 에러 체킹
    • CRC 생성
    • 에러 체킹
    • 재전송 메시리지대한 TLP 응답
    • 에러 리포팅 및 로깅을 위한 에러 인디케이션

 

Physical Layer

  • 인터페이스 초기화, 메인트넌스 제어 및 스테이터스 트래킹
    • 리셋/핫플러그 컨트롤
    • 전원 관리 인터커넥트
    • width & lane 매핑 협상
    • lane 극성 반전
  • 심볼 및 오더
    • 8b/10b 엔코딩 및 디코딩
    • 임베디드 클럭 튜닝 및 정렬
  • 심볼 전송 및 정렬
    • 전송 회로
    • 리셉션 회로
    • 수신 측 Elastic 버퍼
    • 수신 측 멀티 lane de-skew

 

MAC & PHY 인터페이스

데이터 전송 계층의 MAC과 물리 계층의 PHY간의 인터커넥트

  • MAC
    • 링크 관리
  • PHY
    • PCS(Physical Coding Sublayer)
      • 코드 변환을 수행한다.
    • PMA(Physical Medium Attachment)
      • 물리 매체 접속부로 Serialization/Deserialization 기능을 수행한다.
      • 수신부(Receiver)에는 클럭 및 데이터의 리커버리 기능도 포함된다.
    • PMD(Physical Medium Dependent Sublayer)
      • 물리 매체 전송부로 전송 매체에 최적화된 트랜시버 모듈(광 트랜시버 등)

 

PCS & PMA & PMD

물리 전송 계층의 3가지 서브 레이어를 각 기능별로 송/수신 흐름을 알아본다.

 

PCIe Gen1 & Gen2

 

PCIe Gen3 & Gen4 & Gen5

 

Encoding

다음과 같은 엔코딩을 사용하여 5개 이상의 비트가 연속되지 않게 제한시킨다. 이렇게 하므로써 출력이 커지는 것을 방지하여 EMI 방사(radiation)를 줄여준다.

  •  8b/10b
    • PCIe Gen 1과 Gen 2에서 사용한다.
    • 8비트 입력에 대해 2비트가 추가되어 10비트가 출력된다.
      • 25%의 추가 대역폭이 필요하다.
        • 예) 1Gbps -> 1.25Gbps
  • 128b/130b
    • PCIe Gen3 이후부터 사용한다.
    • 128비트 입력에 대해 2비트가 추가되어 130비트가 출력된다.
      • 소모되는 추가 대역폭이 1% 미만으로 거의 없다.

 

Configuration Topology

PCI Express Configuration 모델은 두 개의 Configuration Space access 매커니즘을 지원한다.

  • PCI compatible CAM(Configuration Access Mechanism)
    • 호환을 위해 PCI express 디바이스는 이 항목을 항상 제공한다.
  • PCI Express ECAM(Enhanced Configuration Access Mechanism)

 

PCI Express Configuration Space Layout

PCI Express 하나를 담당하는 Configuration Space를 알아본다.

  • PCI 호환 주소 영역
    • 0x00 ~ 0xff의 영역을 갖는다.
    • 그 중 헤더 영역은 0x00 ~ 0x3f이다.
  • PCI Express 확장 주소 영역
    • 0x100 ~ 0xfff의 영역을 갖는다.

 

다음 그림은 Root Complex 가상 PCI Bridge가 3개의 PCIe 버스를 제공하며 이에 대한 pci 주소 공간을 보여준다.

  • Root Complex에 해당하는 configuration space가 주어진다.
  • 그 아래에 각 가상 PCI Bridge에 해당하는 configuration space가 있음을 알 수 있다.

 

ECAM(Enhanced Configuration Access Mechanism)

다수의 PCI Express 버스를 관리하기 위해 아래와 같은 주소 체제로 나누어 사용한다.

  • 버스 번호
    • 1부터 최대 256개의 버스 번호를 가질 수 있다.
  • 디바이스 번호
    • 최대 32개의 디바이스 번호를 가질 수 있다.
  • 펑션 번호
    • 최대 8개의 펑션 번호를 가질 수 있다.
  • PCI Express Configuration Space
    • 0x0 ~ 0xfff까지의 영역을 담당한다.

 

Configuration Space Header

디바이스 타입에 따라 Configuration Space Header는 다음과 같이 2 가지 타입을 지원한다.

  • Type 0 header
    • PCI Express 디바이스
  • Type 1 header
    • Switch 및 Root Complex 가상 PCI Bridges

Type 0 헤더

 

Type 1 헤더

 

각 헤더에서 사용되는 공통 레지스터들

Vendor ID (0x00)
  • 2바이트 벤더 ID
  • 예) 인텔: 0x8086
Device ID (0x02)
  • 2바이트 디바이스 ID
  • 예) 고속 이더넷 장치: 0x0009
Command (0x04)
  • 2바이트 명령 레지스터

Status (0x06)
  • 2 바이트 상태 레지스터
  • RW1C (Read, Write 1 to Clear Status)
    • Read는 동일하되, 1 값을 기록 시에만 클리어한다. 0을 기록시에는 아무런 변화가 없다.

 

Revision ID (0x08)
  • 1 바이트 Revision ID
Class Code (0x09)
  • 3 바이트 클래스 코드로 장치 유형을 표시한다.
  • 예) SCSI: 0x0100
Cache Line Size (0x0c)
  • 1바이트 캐시 라인 사이즈
  • 호환 목적으로 사용되며 PCI express에서는 사용하지 않으므로 항상 0이다.
Primary Latency Timer (0x0d)
  • 1바이트 대표 Latency 타이머
  • PCI express에서는 사용하지 않으므로 항상 0이어야 한다.
Header Type
  • 1바이트 헤더 타입
BIST
  • 1바이트 BIST
Interrupt Line (0x3c)
  • 1 바이트 인터럽트 번호로 디바이스 드라이버에서 사용하는 정보이다.
Interrupt Pin (0x3d)
  • 1 바이트 실제 라우팅되는 legacy 인터럽트 라인 번호로 1~4까지 (INTA, INTB, INTC, INTD) 주어질 수 있다. 0인 경우 사용하지 않는다.

 

Type 0 레지스터들

BAR(Base Address Register) (0x10~0x24)
  • 6개의 BAR를 제공한다.
  • PCI I/O, PCI 메모리 공간의 유형과 크기, 위치를 지정하는데 사용된다.
Min_Gnt (0x3e)
  • 1 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.
Max_Lat (0x3f)
  • 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.

 

Type 1 레지스터들

BAR(Base Address Register) (0x10~0x24)
  • 2개의 BAR를 제공한다.
Primary Bus Number (0x18)
  • 1 바이트로 PCI Express에서는 사용되지 않는다.
Secondary Bus Number (0x19)
  • 1 바이트
Subordinate Bus Number (0x1a)
  • 1 바이트
Secondary Latency Timer (0x1b)
  • 1 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.
I/O Base (0x1c)
I/O Limit (0x1d)
Secondary Status Register (0x1e)
  • 2 바이트 두 번째 상태 레지스터

Prefetchable Memory Base
  • 전체 64비트로 PCI Bridge에서 사용된다.
Prefetchable Memory Limit
  • 전체 64비트로 PCI Bridge에서 사용된다.
Capability Pointer (0x34)
  • 1 바이트로 Capability가 있는 경우 첫 Capability offset이 기록되어 있다.
Expansion ROM Base Address (0x38)
  • 4 바이트
Bridge Control (0x3e)
  • 2 바이트로 PCI Bridge에서 사용된다.

 

참고

I2C Subsystem -4- (I2C-Mux)

 

I2C-Mux Core

다음 그림은 i2c 버스에 연결된 i2c 멀티플레서를 블럭 다이아그램으로 표현하였다.

 

i2c_mux_core 구조체

include/linux/i2c-mux.h

struct i2c_mux_core {                                                           
        struct i2c_adapter *parent;                                             
        struct device *dev;                                                     
        unsigned int mux_locked:1;                                              
        unsigned int arbitrator:1;                                              
        unsigned int gate:1;                                                    
                                                                                
        void *priv;                                                             
                                                                                
        int (*select)(struct i2c_mux_core *, u32 chan_id);                      
        int (*deselect)(struct i2c_mux_core *, u32 chan_id);                    
                                                                                
        int num_adapters;                                                       
        int max_adapters;                                                       
        struct i2c_adapter *adapter[0];                                         
};
  • *parent
    • 부모 adapter를 가리킨다.
  • *dev
    • 현 멀티플레서 디바이스를 가리킨다.
  • mux_locked
    • mux-lock 오퍼레이션을 선택사용하게 한다.
      • 1인 경우 i2c_mux_lock_ops를 사용한다.
        • I2C_LOCK_ROOT_ADAPTER 플래그를 사용한 경우에만 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
      • 0인 경우 &i2c_parent_lock_ops를 사용한다.
        • 항상 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
    • 다음 드라이버들이 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
      • drivers/i2c/muxes/i2c-mux-ltc4306.c
      • drivers/media/dvb-frontends/lgdt3306a.c
      • drivers/media/dvb-frontends/si2168.c
      • drivers/media/dvb-frontends/rtl2832.c
  • arbitrator
    • mux-arbitrator 지원하는 경우 1
    • i2c 멀티플렉서가 1:N 형태로 버스를 확장시키는데 반해 N:1 형태로 버스를 합치는 성격의 멀티플렉서이다.
      • 예) pca9541 칩: 2개의 i2c 버스를 1개의 i2c 버스로 합치는 기능을 제공한다.
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-arb 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/i2c/muxes/i2c-mux-pca9541.c
      • drivers/i2c/muxes/i2c-arb-gpio-challenge.c
  • gate
    • mux-gate 지원하는 경우 1
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-gate 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
  • *priv
    • 사용자 private 데이터가 있을 때 사용한다.
  • (*select)
    • i2c-mux에 있는 i2c 버스를 선택할 때 호출되는 후크이다.
  • (*deselect)
    • i2c-mux에 있는 i2c 버스의 사용이 완료한 후 호출되는 후크이다.
  • num_adapters
    • adpater 수
  • max_adapters
    • 최대 adapter 수
  • *adapter[0]
    • 최대 adapter 수 만큼의 adapter 포인터 배열이 할당될 위치이다.

 

i2c_mux_priv 구조체

drivers/i2c/muxes/i2c-mux.c

/* multiplexer per channel data */                                              
struct i2c_mux_priv {                                                           
        struct i2c_adapter adap;                                                
        struct i2c_algorithm algo;                                              
        struct i2c_mux_core *muxc;                                              
        u32 chan_id;                                                            
};
  • adap
    • i2c_adapter가 임베드되어 있다.
  • algo
    • i2c_algorithm이 임베드되어 있다.
  • *muxc
    • i2c mux를 가리킨다.
  • chan_id
    • 채널 번호

 

i2c-mux 할당

i2c_mux_alloc()

drivers/i2c/muxes/i2c-mux.c

struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,                  
                                   struct device *dev, int max_adapters,        
                                   int sizeof_priv, u32 flags,                  
                                   int (*select)(struct i2c_mux_core *, u32),   
                                   int (*deselect)(struct i2c_mux_core *, u32)) 
{                                                                               
        struct i2c_mux_core *muxc;                                              
                                                                                
        muxc = devm_kzalloc(dev, sizeof(*muxc)                                  
                            + max_adapters * sizeof(muxc->adapter[0])           
                            + sizeof_priv, GFP_KERNEL);                         
        if (!muxc)                                                              
                return NULL;                                                    
        if (sizeof_priv)                                                        
                muxc->priv = &muxc->adapter[max_adapters];                      
                                                                                
        muxc->parent = parent;                                                  
        muxc->dev = dev;                                                        
        if (flags & I2C_MUX_LOCKED)                                             
                muxc->mux_locked = true;                                        
        if (flags & I2C_MUX_ARBITRATOR)                                         
                muxc->arbitrator = true;                                        
        if (flags & I2C_MUX_GATE)                                               
                muxc->gate = true;                                              
        muxc->select = select;                                                  
        muxc->deselect = deselect;                                              
        muxc->max_adapters = max_adapters;                                      
                                                                                
        return muxc;                                                            
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_alloc);

i2c_mux_core를 할당한다.

  • 코드 라인 9~13에서 i2c_mux_core 구조체 + adapter 포인터 수 + private 데이터 크기 만큼의 공간을 할당한다.
  • 코드 라인 15~16에서 muxc의 priv 포인터가 할당한 private 데이터 공간을 가리키게한다.
  • 코드 라인 18~19에서 부모 i2c adapter와 디바이스를 지정한다.
  • 코드 라인 20~25에서 플래그에 해당하는 muxc 멤버(locked, arbtrator, gate) 변수를 설정한다.
  • 코드 라인 26~28에서 (*select) 및 (*deselect) 후크 함수를 지정하고, max_adapters 까지 설정한다.

 

다음 그림은 8개의 adapter를 위한 i2c_mux_core가 준비됨을 알 수 있다.

 

 

mux에 adapter 추가

i2c_mux_add_adapter()

drivers/i2c/muxes/i2c-mux.c -1/3-

int i2c_mux_add_adapter(struct i2c_mux_core *muxc,                              
                        u32 force_nr, u32 chan_id,                              
                        unsigned int class)                                     
{                                                                               
        struct i2c_adapter *parent = muxc->parent;                              
        struct i2c_mux_priv *priv;                                              
        char symlink_name[20];                                                  
        int ret;                                                                
                                                                                
        if (muxc->num_adapters >= muxc->max_adapters) {                         
                dev_err(muxc->dev, "No room for more i2c-mux adapters\n");      
                return -EINVAL;                                                 
        }                                                                       
                                                                                
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);                              
        if (!priv)                                                              
                return -ENOMEM;                                                 
                                                                                
        /* Set up private adapter data */                                       
        priv->muxc = muxc;                                                      
        priv->chan_id = chan_id;                                                
                                                                                
        /* Need to do algo dynamically because we don't know ahead              
         * of time what sort of physical adapter we'll be dealing with.         
         */                                                                     
        if (parent->algo->master_xfer) {                                        
                if (muxc->mux_locked)                                           
                        priv->algo.master_xfer = i2c_mux_master_xfer;           
                else                                                            
                        priv->algo.master_xfer = __i2c_mux_master_xfer;         
        }                                                                       
        if (parent->algo->smbus_xfer) {                                         
                if (muxc->mux_locked)                                           
                        priv->algo.smbus_xfer = i2c_mux_smbus_xfer;             
                else                                                            
                        priv->algo.smbus_xfer = __i2c_mux_smbus_xfer;           
        }                                                                       
        priv->algo.functionality = i2c_mux_functionality;                       
                                                                                
        /* Now fill out new adapter structure */                                
        snprintf(priv->adap.name, sizeof(priv->adap.name),                      
                 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);   
        priv->adap.owner = THIS_MODULE;                                         
        priv->adap.algo = &priv->algo;                                          
        priv->adap.algo_data = priv;                                            
        priv->adap.dev.parent = &parent->dev;                                   
        priv->adap.retries = parent->retries;                                   
        priv->adap.timeout = parent->timeout;                                   
        priv->adap.quirks = parent->quirks;                                     
        if (muxc->mux_locked)                                                   
                priv->adap.lock_ops = &i2c_mux_lock_ops;                        
        else                                                                    
                priv->adap.lock_ops = &i2c_parent_lock_ops;                     
                                                                                
        /* Sanity check on class */                                             
        if (i2c_mux_parent_classes(parent) & class)                             
                dev_err(&parent->dev,                                           
                        "Segment %d behind mux can't share classes with ancestors\n",
                        chan_id);                                               
        else                                                                    
                priv->adap.class = class;

adapter를 추가한 후 i2c_mux_core에 연결한다.

  • 코드 라인 10~13에서 이미 최대 adapter 수 만큼 등록한 경우 추가할 수 없다.
  • 코드 라인 15~21에서 i2c_mux_priv를 할당하고 muxc 및 chan_id를 지정한다.
  • 코드 라인 26~31에서 부모 i2c adapter의 i2c 전송 후크가 있는 경우 현재 생성할 adapter의 i2c 전송 후크에 select가 포함된 부모 adapter로의 i2c 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 32~38에서부모 i2c adapter의 smbus 전송 후크가 있는 경우 현재 생성할 adapter의 smbus 전송 후크에 select가 포함된 부모 adapter로의 smbus 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 39에서 부모 i2c adapter가 사용하는 functionality를 지정한다.
  • 코드 라인44~50에서 할당한 adapter의 멤버를 설정한다. retries, timeout 및 quirks는 부모 adapter가 사용하는 값을 그대로 적용한다.
  • 코드 라인 51~54에서 할당한 adapter에서 사용할 lock operation은 부모 adapter가 사용하는 lock 버스를 사용한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 57~62에서 adapter의 클래스를 지정한다. 만일 클래스와 부모 adapter의 클래스가 동일한 경우 에러 메시지를 출력한다.

 

drivers/i2c/muxes/i2c-mux.c -2/3-

.       /*                                                                      
         * Try to populate the mux adapter's of_node, expands to                
         * nothing if !CONFIG_OF.                                               
         */                                                                     
        if (muxc->dev->of_node) {                                               
                struct device_node *dev_node = muxc->dev->of_node;              
                struct device_node *mux_node, *child = NULL;                    
                u32 reg;                                                        
                                                                                
                if (muxc->arbitrator)                                           
                        mux_node = of_get_child_by_name(dev_node, "i2c-arb");   
                else if (muxc->gate)                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-gate");  
                else                                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-mux");   
                                                                                
                if (mux_node) {                                                 
                        /* A "reg" property indicates an old-style DT entry */  
                        if (!of_property_read_u32(mux_node, "reg", &reg)) {     
                                of_node_put(mux_node);                          
                                mux_node = NULL;                                
                        }                                                       
                }                                                               
                                                                                
                if (!mux_node)                                                  
                        mux_node = of_node_get(dev_node);                       
                else if (muxc->arbitrator || muxc->gate)                        
                        child = of_node_get(mux_node);                          
                                                                                
                if (!child) {                                                   
                        for_each_child_of_node(mux_node, child) {               
                                ret = of_property_read_u32(child, "reg", &reg); 
                                if (ret)                                        
                                        continue;                               
                                if (chan_id == reg)                             
                                        break;                                  
                        }                                                       
                }                                                               
                                                                                
                priv->adap.dev.of_node = child;                                 
                of_node_put(mux_node);                                          
        }
  • 코드 라인 5~15에서 디바이스 트리에 mux 디바이스 노드가 있는 경우 mux의 arbitratory, gate 유무에 따라 mux 디바이스의 서브 노드에서 mux 노드를 알아온다. 각각 mux 디바이스 노드의 서브 노드에 “i2c-arb”, “i2c-gate”, “i2c-mux” 이름으로 노드가 있으면 mux_node로 알아온다.
  • 코드 라인 17~23에서 mux_node에 “reg” 속성이 존재하면 old-style DT 이다. 이러한 경우 mux_node를 null로 설정한다.
    • arbitratory 및 gate 타입의 경우 i2c 버스가 1개이다. 따라서 adapter 번호가 지정될 필요 없다.
  • 코드 라인 25~26에서 mux_node가 null인 경우 디바이스 노드를 mux_node로 사용한다.
  • 코드 라인 27~28에서 arbitrator 또는 gate 타입의 경우 mux_node를 child에 대입한다.
  • 코드 라인 30~38에서 arbitrator, gate에 대한 노드가 없는 경우 mux 노드의 child 노드의 “reg” 속성 값을 읽어서 chan_id로 사용한다.
  • 코드 라인 40~41에서 adapter의 노드를 지정한다.

 

drivers/i2c/muxes/i2c-mux.c -3/3-

        /*                                                                      
         * Associate the mux channel with an ACPI node.                         
         */                                                                     
        if (has_acpi_companion(muxc->dev))                                      
                acpi_preset_companion(&priv->adap.dev,                          
                                      ACPI_COMPANION(muxc->dev),                
                                      chan_id);                                 
                                                                                
        if (force_nr) {                                                         
                priv->adap.nr = force_nr;                                       
                ret = i2c_add_numbered_adapter(&priv->adap);                    
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u as bus %u (error=%d)\n",
                                chan_id, force_nr, ret);                        
                        goto err_free_priv;                                     
                }                                                               
        } else {                                                                
                ret = i2c_add_adapter(&priv->adap);                             
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u (error=%d)\n",    
                                chan_id, ret);                                  
                        goto err_free_priv;                                     
                }                                                               
        }                                                                       
                                                                                
        WARN(sysfs_create_link(&priv->adap.dev.kobj, &muxc->dev->kobj,          
                               "mux_device"),                                   
             "can't create symlink to mux device\n");                           
                                                                                
        snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);    
        WARN(sysfs_create_link(&muxc->dev->kobj, &priv->adap.dev.kobj,          
                               symlink_name),                                   
             "can't create symlink for channel %u\n", chan_id);                 
        dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",                
                 i2c_adapter_id(&priv->adap));                                  
                                                                                
        muxc->adapter[muxc->num_adapters++] = &priv->adap;                      
        return 0;                                                               
                                                                                
err_free_priv:                                                                  
        kfree(priv);                                                            
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_add_adapter);
  • 코드 라인 4~7에서 acpi 노드와 chan_id를 연결한다.
  • 코드 라인 9~17에서 adapter 번호를 지정하여 adapter를 추가한다.
  • 코드 라인 18~26에서 adapter 번호를 자동 할당하여 adapter를 추가한다.
  • 코드 라인 28~31에서 adapter 디렉토리에 mux_device 심볼링크를 생성하여 mux 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1/mux_device ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070
  • 코드 라인 33~36에서 mux 디렉토리에 “channel-N” 라는 심볼링크를 생성하여 adapter 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/channle-0 ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1
  • 코드 라인 i2c_mux_core의 adapter 포인터가 생성된 i2c_mux_priv의 adapter를 가리키도록 한다.

 

다음 그림은 i2c_mux_add_adapter() 함수가 4번 호출되어 4개의 i2c 채널(i2c_mux_priv)이 생성되고 그 내부에 adapter와 algorithm이 있는 것을 확인할 수 있다.

 

다음 그림과 같이 i2c-mux의 경우 child 노드의 파란색 reg 속성 값이 adapter 번호가 된다. 즉 i2c 버스 번호가 된다. 그러나 적색으로된 i2c-arb 또는 i2c-gate 노드의 경우 i2c 버스가 하나이므로 reg 속성이 주어지지 않는 것을 알 수 있다.

 

채널 셀렉트 및 전송

전송 Core

다음 그림은 i2c_transfer() 함수를 사용 시 버스 락 처리 및 i2c-mux의 채널 선택과 전송을 수행하는 과정을 보여준다.

 

i2c-mux core 전송

__i2c_mux_master_xfer()

drivers/i2c/i2c-mux.c

static int __i2c_mux_master_xfer(struct i2c_adapter *adap,                      
                                 struct i2c_msg msgs[], int num)                
{                                                                               
        struct i2c_mux_priv *priv = adap->algo_data;                            
        struct i2c_mux_core *muxc = priv->muxc;                                 
        struct i2c_adapter *parent = muxc->parent;                              
        int ret;                                                                
                                                                                
        /* Switch to the right mux port and perform the transfer. */            
                                                                                
        ret = muxc->select(muxc, priv->chan_id);                                
        if (ret >= 0)                                                           
                ret = __i2c_transfer(parent, msgs, num);                        
        if (muxc->deselect)                                                     
                muxc->deselect(muxc, priv->chan_id);                            
                                                                                
        return ret;                                                             
}

adapter에 해당하는 채널을 선택한 채로 부모 adapter를 통해 메시지를 전송한다. 사용이 완료되면 deselect 하여야 한다.

 

i2c-mux 버스 락 core

i2c_parent_lock_bus()

drivers/i2c/i2c-mux.c

static void i2c_parent_lock_bus(struct i2c_adapter *adapter,                    
                                unsigned int flags)                             
{                                                                               
        struct i2c_mux_priv *priv = adapter->algo_data;                         
        struct i2c_adapter *parent = priv->muxc->parent;                        
                                                                                
        rt_mutex_lock(&parent->mux_lock);                                       
        i2c_lock_bus(parent, flags);                                            
}

mux 뮤텍스 락을 건채로 부모 adapter 버스 락을 건다.

 

i2c_lock_bus()

include/linux/i2c.h

/**                                                                             
 * i2c_lock_bus - Get exclusive access to an I2C bus segment                    
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static inline void                                                              
i2c_lock_bus(struct i2c_adapter *adapter, unsigned int flags)                   
{                                                                               
        adapter->lock_ops->lock_bus(adapter, flags);                            
}

adapter에 해당하는 버스 락 오퍼레이션을 수행한다.

 

채널 선택 (for 954x chip)

pca954x_select_chan()

drivers/i2c/muxes/i2c-mux-pca954x.c

static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan)             
{                                                                               
        struct pca954x *data = i2c_mux_priv(muxc);                              
        struct i2c_client *client = data->client;                               
        const struct chip_desc *chip = data->chip;                              
        u8 regval;                                                              
        int ret = 0;                                                            
                                                                                
        /* we make switches look like muxes, not sure how to be smarter */      
        if (chip->muxtype == pca954x_ismux)                                     
                regval = chan | chip->enable;                                   
        else                                                                    
                regval = 1 << chan;                                             
                                                                                
        /* Only select the channel if its different from the last channel */    
        if (data->last_chan != regval) {                                        
                ret = pca954x_reg_write(muxc->parent, client, regval);          
                data->last_chan = ret < 0 ? 0 : regval;                         
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c-mux의 요청받은 채널 번호에 해당하는 i2c 버스를 HW 선택한다.

  • 채널을 선택하기 위해서는 부모 i2c adapter를 통해 i2c-mux 장치에게 1바이트의 채널 선택 value를 전송해야 한다. 즉 일반적인 1 바이트의 i2c 메시지를 전송한다.
  • pca954x 시리즈는 i2c 멀티플렉서와 i2c 스위치 두 타입이 있다. 타입에 따라 전송하는 값은 다음과 같다.
    • i2c 멀티 플렉서
      • 채널 번호 + enable 비트
      • enable 비트는 다음과 같이 결정된다.
        • 2채널 멀티플렉서 -> bit1
        • 4채널 멀티플렉서 -> bit2
        • 8채널 멀티플렉서 -> bit3
    • i2c 스위치
      • 각 비트가 하나의 채널을 담당한다.
      • 하나의 채널만 선택하려면
        • 1 << 채널 번호

 

pca954x_reg_write()

drivers/i2c/muxes/i2c-mux-pca954x.c

/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()             
   for this as they will try to lock adapter a second time */                   
static int pca954x_reg_write(struct i2c_adapter *adap,                          
                             struct i2c_client *client, u8 val)                 
{                                                                               
        int ret = -ENODEV;                                                      
                                                                                
        if (adap->algo->master_xfer) {                                          
                struct i2c_msg msg;                                             
                char buf[1];                                                    
                                                                                
                msg.addr = client->addr;                                        
                msg.flags = 0;                                                  
                msg.len = 1;                                                    
                buf[0] = val;                                                   
                msg.buf = buf;                                                  
                ret = __i2c_transfer(adap, &msg, 1);                            
                                                                                
                if (ret >= 0 && ret != 1)                                       
                        ret = -EREMOTEIO;                                       
        } else {                                                                
                union i2c_smbus_data data;                                      
                ret = adap->algo->smbus_xfer(adap, client->addr,                
                                             client->flags,                     
                                             I2C_SMBUS_WRITE,                   
                                             val, I2C_SMBUS_BYTE, &data);       
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c 전송 후크가 있는 경우 i2c 버스에 인자로 요청한 1 바이트 값을 전송한다. i2c 전송 후크가 없는 경우 smbus에 인자로 요청한 1 바이트 값을 전송한다.

 

필립스 i2c 멀티플렉서/스위치 pca954x 시리즈

pca9541x 제품 종류

가장 많이 사용되는 필립스 반도체의 i2c-mux 제품들 중 954X 시리즈를 기능별로 구분할 수 있는 테이블이다.

 

i2c-스위치 pca9549 블록 다이어그램

  • 8개 채널을 각각 선택할 수 있다.
  • hw reset 핀이 지원된다.
  • a0~a2 핀을 사용하여 주소를 설정할 수 있다.

 

주소 설정

각 제품마다 하위 2~4개의 비트를 설정하여 주소를 설정할 수 있다.

  • 예) pca9545a의 경우 0x70 ~ 0x73까지 4개의 주소를 설정하여 사용할 수 있다.

 

채널 선택(select)

다음과 같이 채널을 선택한다.

  • 4채널 i2c 멀티 플렉서 pca9544a의 경우
    • 채널 선택: bit0 ~ bit1
    • enable bit: bit2
    • 예) 0x6 -> 채널 2 선택
  • 2채널 i2c 스위치 pca9543a의 경우
    • 채널 선택: bit0 ~ bit3
    • 예) 0x3 -> 채널 0과 채널 1이 동시 선택

 

다음 그림은 some device와 통신하기 위해 i2c-mux 칩인 pca9545a 디바이스에 해당하는 주소 0x72로 0x8 값을 전송하여 #3번 채널을 선택하는 모습을 보여준다.

 

참고

 

I2C Subsystem -3- (Transfer)

<kernel v4.14>

전송에 관여하는 다음 항목들에 대해 알아본다.

  • I2C Quirks
  • I2C Bus Recovery
  • I2C Bus Lock
  • I2C Transfer

 

I2C Quirks

Kernel v4.1-rc1에서 추가된 기능으로 특정 i2c 호스트 컨트롤러들에서 HW 전송 제한이 걸려 있는 경우 이에 대한 전송 제한 체크를 수행할 수 있도록 하였다.

 

I2C Quirks 체크

i2c_check_for_quirks()

drivers/i2c/base/i2c-core-base.c

static int i2c_check_for_quirks(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{                                                                               
        const struct i2c_adapter_quirks *q = adap->quirks;                      
        int max_num = q->max_num_msgs, i;                                       
        bool do_len_check = true;                                               
                                                                                
        if (q->flags & I2C_AQ_COMB) {                                           
                max_num = 2;                                                    
                                                                                
                /* special checks for combined messages */                      
                if (num == 2) {                                                 
                        if (q->flags & I2C_AQ_COMB_WRITE_FIRST && msgs[0].flags & I2C_M_RD)
                                return i2c_quirk_error(adap, &msgs[0], "1st comb msg must be write");
                                                                                
                        if (q->flags & I2C_AQ_COMB_READ_SECOND && !(msgs[1].flags & I2C_M_RD))
                                return i2c_quirk_error(adap, &msgs[1], "2nd comb msg must be read");
                                                                                
                        if (q->flags & I2C_AQ_COMB_SAME_ADDR && msgs[0].addr != msgs[1].addr)
                                return i2c_quirk_error(adap, &msgs[0], "comb msg only to same addr");
                                                                                
                        if (i2c_quirk_exceeded(msgs[0].len, q->max_comb_1st_msg_len))
                                return i2c_quirk_error(adap, &msgs[0], "msg too long");
                                                                                
                        if (i2c_quirk_exceeded(msgs[1].len, q->max_comb_2nd_msg_len))
                                return i2c_quirk_error(adap, &msgs[1], "msg too long");
                                                                                
                        do_len_check = false;                                   
                }                                                               
        }                                                                       
                                                                                
        if (i2c_quirk_exceeded(num, max_num))                                   
                return i2c_quirk_error(adap, &msgs[0], "too many messages");    
                                                                                
        for (i = 0; i < num; i++) {                                             
                u16 len = msgs[i].len;                                          
                                                                                
                if (msgs[i].flags & I2C_M_RD) {                                 
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_read_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                } else {                                                        
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_write_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}

송/수신할 메시지에 대해 메시지 수 및 길이 제한을 체크한다. 성공 시 0을 반환한다.

  • 코드 라인  7~29에서 I2C_AQ_COMB 플래그를 사용한 combined 메시지인 경우 2개의 메시지만을 송/수신할 수 있는데 특별히 다음과 같은 조건인 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 첫 번째 메시지가 read인 경우
    • 두 번째 메시지가 read가 아닌 경우
    • 첫 번째와 두 번째 메시지의 타겟 주소가 동일하지 않는 경우
    • 첫 번째 메시지의 길이가 quirks에서 지정한 첫 번째 메시지보다 큰 경우
    • 두 번째 메시지의 길이가 quirks에서 지정한 두 번째 메시지보다 큰 경우
  • 코드 라인 31~32에서 인자로 요청한 일반 메시지 수가 quirks에서 지정한 메시지 수 제한을 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 단 quirks에 메시지 수가 지정되지 않은 경우 제한 초과 체크하지 않는다.
    • #define i2c_quirk_exceeded(val, quirk)    ((quirk) && ((val) > (quirk)))
  • 코드 라인 34~44에서 모든 메시지 수 만큼 순회하며 read 및 write 메시지의 경우 quirks에서 제한한 길이를 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.

 

다음 그림은 quirks 체크를 수행하는 모습을 보여준다.

 

i2c_adapter_quirks 구조체

/**                                                                             
 * struct i2c_adapter_quirks - describe flaws of an i2c adapter                 
 * @flags: see I2C_AQ_* for possible flags and read below                       
 * @max_num_msgs: maximum number of messages per transfer                       
 * @max_write_len: maximum length of a write message                            
 * @max_read_len: maximum length of a read message                              
 * @max_comb_1st_msg_len: maximum length of the first msg in a combined message 
 * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
 *                                                                              
 * Note about combined messages: Some I2C controllers can only send one message 
 * per transfer, plus something called combined message or write-then-read.     
 * This is (usually) a small write message followed by a read message and       
 * barely enough to access register based devices like EEPROMs. There is a flag 
 * to support this mode. It implies max_num_msg = 2 and does the length checks  
 * with max_comb_*_len because combined message mode usually has its own        
 * limitations. Because of HW implementations, some controllers can actually do 
 * write-then-anything or other variants. To support that, write-then-read has  
 * been broken out into smaller bits like write-first and read-second which can 
 * be combined as needed.                                                       
 */
struct i2c_adapter_quirks {                                                     
        u64 flags;                                                              
        int max_num_msgs;                                                       
        u16 max_write_len;                                                      
        u16 max_read_len;                                                       
        u16 max_comb_1st_msg_len;                                               
        u16 max_comb_2nd_msg_len;                                               
};
  • flags
    • I2C_AQ_COMB(1)
      • 메시지의 전송을 2개로 제한하고, max_comb_1st_msg_len 및 max_comb_2nd_msg_len 길이 체크를 사용한다.
    • I2C_AQ_COMB_WRITE_FIRST(2)
      • 첫 번째 combined 메시지를 먼저 write 해야한다.
    • I2C_AQ_COMB_READ_SECOND(4)
      • 두 번째 combined 메시지를 read 해야 한다.
    • I2C_AQ_COMB_SAME_ADDR(8)
      • 두 개의 메시지의 타겟 주소가 반드시 동일해야 한다.
    • I2C_AQ_COMB_WRITE_THEN_READ(15)
      • 같은 디바이스에 주소로 한 번 쓴 후, 다시 한 번 읽는다.
      • 위의 4개 플래그를 동시에 사용하여 편리하게 사용할 수 있도록 하였다.
    • I2C_AQ_NO_CLK_STRETCH(16)
      • clock stretching을 지원하지 않는다.
  • max_num_msgs
    • 전송할 메시지의 최대 수
  • max_write_len
    • 최대 전송 길이
  • max_read_len
    • 최대 수신 길이
  • max_comb_1st_msg_len
    • 첫 번째 메시지 최대 길이
  • max_comb_2nd_msg_len
    • 두 번째 메시지 최대 길이

 

I2C Bus Recovery

I2C 버스 리커버리는 kernel v3.10-rc1에서 소개되었다.

 

Bus Clear

i2c 프로토콜 규격의 섹션 3.1.16에서 “Bus clear”라는 항목이 구현되었다.

  • 드물게 클록(SCL) 또는 데이터 라인(SDA)가 고정되어 움직이지 않는 경우가 있다. 이러한 경우 다음과 같이 처리한다.
    • A) 클록 (SCL)이 LOW로 고정된 경우
      • I2C 디바이스에 HW 리셋 신호를 사용하여 버스를 리셋한다.
      • 만일 I2C 디바이스에 HW 리셋 입력이 없으면 디바이스 전원을 껏다 켠다.
    • B) 데이터 라인 (SDA)이 LOW로 고정된 경우
      • 버스 마스터는 9 개의 클럭 펄스를 전송해야 한다.
      • 버스를 LOW로 유지 한 장치는 9 클럭 내에서 이에 반응하여 해제해야 한다.
      • 여전히 동작하지 않는 경우 A)와 같이 처리한다.

 

i2c 프로토콜 규격 – 섹션 3.1.16에서 “Bus clear” 원문
3.1.16 Bus clear

In the unlikely event where the clock (SCL) is stuck LOW, the preferential procedure is to reset the bus using the HW reset signal if your I2C devices have HW reset inputs. If the I2C devices do not have HW reset inputs, cycle power to the devices to activate the mandatory internal Power-On Reset (POR) circuit.

If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device that held the bus LOW should release it sometime within those nine clocks. If not, then
use the HW reset or cycle power to clear the bus.

 

i2c Bus Recovery 준비

i2c 버스를 리커버리하는 방법은 다음과 같은 방법들이 있다.

  • 커널에 미리 준비된 i2c_generic_scl_recovery() 함수 사용
  • 커널에 미리 준비된 i2c_generic_gpio_recovery() 함수 사용
  • custom 리커버리 함수를 사용하여 직접 HW 설정

 

i2c_init_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_init_recovery(struct i2c_adapter *adap)                         
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        char *err_str;                                                          
                                                                                
        if (!bri)                                                               
                return;                                                         
                                                                                
        if (!bri->recover_bus) {                                                
                err_str = "no recover_bus() found";                             
                goto err;                                                       
        }                                                                       
                                                                                
        /* Generic GPIO recovery */                                             
        if (bri->recover_bus == i2c_generic_gpio_recovery) {                    
                if (!gpio_is_valid(bri->scl_gpio)) {                            
                        err_str = "invalid SCL gpio";                           
                        goto err;                                               
                }                                                               
                                                                                
                if (gpio_is_valid(bri->sda_gpio))                               
                        bri->get_sda = get_sda_gpio_value;                      
                else                                                            
                        bri->get_sda = NULL;                                    
                                                                                
                bri->get_scl = get_scl_gpio_value;                              
                bri->set_scl = set_scl_gpio_value;                              
        } else if (bri->recover_bus == i2c_generic_scl_recovery) {              
                /* Generic SCL recovery */                                      
                if (!bri->set_scl || !bri->get_scl) {                           
                        err_str = "no {get|set}_scl() found";                   
                        goto err;                                               
                }                                                               
        }                                                                       
                                                                                
        return;                                                                 
 err:                                                                           
        dev_err(&adap->dev, "Not using recovery: %s\n", err_str);               
        adap->bus_recovery_info = NULL;                                         
}

i2c 버스 리커버리를 준비한다.

  • 코드 라인 9~12에서 리커버리용 후크가 설정되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 15~27에서 gpio를 사용한 리커버리인 경우 scl_gpio 및 sda_gpio 핀 번호 값이 이상 없는지 확인한다. 그런 후 get_scl, set_scl 및 get_sda의 디폴트 함수를 설정 한다.
  • 코드 라인 28~34에서 scl을 사용한 리커버리인 경우 set_scl 및 get_scl 후크가 지정되지 않은 경우 에러를 출력하고 함수를 빠져나간다.

 

다음 그림은 i2c_init_recovery() 함수에서 3가지 타입의 (*recover_bus) 후크에 대해 처리하는 과정을 보여준다.

  • A) SCL 리커버리: (*get_scl) 및 (*set_scl) 후크 함수 체크
  • B) gpio 리커버리: (*get_scl), (*set_scl) 및 (*get_sda) 후크에 디폴트 함수 지정 및 scl_gpio, sda_gpio 핀 번호 체크

 

i2c_bus_recovery_info 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_bus_recovery_info - I2C bus recovery information                  
 * @recover_bus: Recover routine. Either pass driver's recover_bus() routine, or
 *      i2c_generic_scl_recovery() or i2c_generic_gpio_recovery().              
 * @get_scl: This gets current value of SCL line. Mandatory for generic SCL     
 *      recovery. Used internally for generic GPIO recovery.                    
 * @set_scl: This sets/clears SCL line. Mandatory for generic SCL recovery. Used
 *      internally for generic GPIO recovery.                                   
 * @get_sda: This gets current value of SDA line. Optional for generic SCL      
 *      recovery. Used internally, if sda_gpio is a valid GPIO, for generic GPIO
 *      recovery.                                                               
 * @prepare_recovery: This will be called before starting recovery. Platform may
 *      configure padmux here for SDA/SCL line or something else they want.     
 * @unprepare_recovery: This will be called after completing recovery. Platform 
 *      may configure padmux here for SDA/SCL line or something else they want. 
 * @scl_gpio: gpio number of the SCL line. Only required for GPIO recovery.     
 * @sda_gpio: gpio number of the SDA line. Only required for GPIO recovery.     
 */
struct i2c_bus_recovery_info {                                                  
        int (*recover_bus)(struct i2c_adapter *);                               
                                                                                
        int (*get_scl)(struct i2c_adapter *);                                   
        void (*set_scl)(struct i2c_adapter *, int val);                         
        int (*get_sda)(struct i2c_adapter *);                                   
                                                                                
        void (*prepare_recovery)(struct i2c_adapter *);                         
        void (*unprepare_recovery)(struct i2c_adapter *);                       
                                                                                
        /* gpio recovery */                                                     
        int scl_gpio;                                                           
        int sda_gpio;                                                           
};
  • (*recover_bus)
    • 이 후크 함수를 호출하여 i2c 리커버리를 시도한다.
      • scl 또는 sda가 low 상태에서 벗어나지 못하면 이 함수를 호출하여 9번의 클럭을 만들면서 sda가 high 상태로 풀려나는지 체크한다.
  • (*get_scl)
    • 시리얼 클럭(scl) 상태를 알아온다. 1/0
  • (*set_scl)
    • 시리얼 클럭(scl) 상태를 설정한다. 1/0
  •  (*get_sda)
    • 시리얼 데이터(sda) 상태를 알아온다. 1/0
  • (*prepare_recovery)
    • 버스 리커버리를 수행하기 전에 먼저 처리할 일을 지정한다.
  • (*prepare_recovery)
    • 버스 리커버리를 완료한 후 처리할 일을 지정한다.
  • scl_gpio
    • gpio를 사용하여 시리얼 클럭(scl)을 만들 gpio  핀 번호
  • sda_gpio
    • gpio를 사용하여 시리얼 데이터(sda) 값을 읽을 핀 번호

 

i2c Bus Recovery 동작

다음 그림은 i2c 버스가 stuck 상태일 때 100Khz의 클럭 펄스를 9번 만들어 i2c 버스가 리커버리를 시도하는 모습을 보여준다.

  • 100Khz 클럭을 만들기 위해 매 사이클마다 low에서 5ms, high에서 5ms씩 delay해야 한다.

 

i2c_recover_bus()

drivers/i2c/base/i2c-core-base.c

int i2c_recover_bus(struct i2c_adapter *adap)                                   
{                                                                               
        if (!adap->bus_recovery_info)                                           
                return -EOPNOTSUPP;                                             
                                                                                
        dev_dbg(&adap->dev, "Trying i2c bus recovery\n");                       
        return adap->bus_recovery_info->recover_bus(adap);                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_recover_bus);

i2c 버스 리커버리를 시도한다.

 

i2c_generic_scl_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_scl_recovery(struct i2c_adapter *adap)                          
{                                                                               
        return i2c_generic_recovery(adap);                                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_scl_recovery);

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

 

i2c_generic_recovery()

drivers/i2c/base/i2c-core-base.c

/*                                                                              
 * We are generating clock pulses. ndelay() determines durating of clk pulses.  
 * We will generate clock with rate 100 KHz and so duration of both clock levels
 * is: delay in ns = (10^6 / 100) / 2                                           
 */                                                                             
#define RECOVERY_NDELAY         5000                                            
#define RECOVERY_CLK_CNT        9                                               
                                                                                
static int i2c_generic_recovery(struct i2c_adapter *adap)                       
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        int i = 0, val = 1, ret = 0;                                            
                                                                                
        if (bri->prepare_recovery)                                              
                bri->prepare_recovery(adap);                                    
                                                                                
        bri->set_scl(adap, val);                                                
        ndelay(RECOVERY_NDELAY);                                                
                                                                                
        /*                                                                      
         * By this time SCL is high, as we need to give 9 falling-rising edges  
         */                                                                     
        while (i++ < RECOVERY_CLK_CNT * 2) {                                    
                if (val) {                                                      
                        /* Break if SDA is high */                              
                        if (bri->get_sda && bri->get_sda(adap))                 
                                        break;                                  
                        /* SCL shouldn't be low here */                         
                        if (!bri->get_scl(adap)) {                              
                                dev_err(&adap->dev,                             
                                        "SCL is stuck low, exit recovery\n");   
                                ret = -EBUSY;                                   
                                break;                                          
                        }                                                       
                }                                                               
                                                                                
                val = !val;                                                     
                bri->set_scl(adap, val);                                        
                ndelay(RECOVERY_NDELAY);                                        
        }                                                                       
                                                                                
        if (bri->unprepare_recovery)                                            
                bri->unprepare_recovery(adap);                                  
                                                                                
        return ret;                                                             
}

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

  • 코드 라인 14~15에서 버스 리커버리를 시도하기 전에 먼저 수행해야 할 일을 처리한다.
  • 코드 라인 17~41에서 9번 루프를 돌며 scl을 high 상태로 설정하고, sda 상태를 읽어본다. sda 상태가 high가 되면 리커버리가 되었으므로 루프를 벗어나다. 그렇지 않은 경우 scl 상태가 변경이 잘 되었는지 확인해본다. high 상태가 아니면 scl이 고정되어 stuck이 걸렸으므로 -EBUSY 에러를 반환한다. SCL 상태가 잘 변경이 되는 경우에는 계속하여 scl을 low 및 high로 변경해가면서 루프를 돌며 리커버리를 시도한다.
  • 코드 라인 43~44에서 버스 리커버리를 완료한 후 수행해야 할 일을 처리한다.

 

i2c_generic_gpio_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_gpio_recovery(struct i2c_adapter *adap)                         
{                                                                               
        int ret;                                                                
                                                                                
        ret = i2c_get_gpios_for_recovery(adap);                                 
        if (ret)                                                                
                return ret;                                                     
                                                                                
        ret = i2c_generic_recovery(adap);                                       
        i2c_put_gpios_for_recovery(adap);                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_gpio_recovery);

gpio 모드로 변경하여 scl을 준비하고 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다. 처리한 후 다시 gpio 모드를 해제한다.

  • pinmux에 i2c 기능과 gpio 기능이 모두 있는 경우 i2c 기능으로 동작하다가 리커버리가 필요할 때 gpio 모드로 전환하여 리커버리를 수행할 수 있다.

 

다음 그림은 pinmux를 통해 i2c 펑션 모드로 사용하다가 i2c 버스 리커버리를 시도하는 모습을 보여준다.

  • gpio 펑션 모드로 전환 시켜 i2c 버스 리커버리를 하는 것을 알 수 있다.

 

i2c_get_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static int i2c_get_gpios_for_recovery(struct i2c_adapter *adap)                 
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        struct device *dev = &adap->dev;                                        
        int ret = 0;                                                            
                                                                                
        ret = gpio_request_one(bri->scl_gpio, GPIOF_OPEN_DRAIN |                
                        GPIOF_OUT_INIT_HIGH, "i2c-scl");                        
        if (ret) {                                                              
                dev_warn(dev, "Can't get SCL gpio: %d\n", bri->scl_gpio);       
                return ret;                                                     
        }                                                                       
                                                                                
        if (bri->get_sda) {                                                     
                if (gpio_request_one(bri->sda_gpio, GPIOF_IN, "i2c-sda")) {     
                        /* work without SDA polling */                          
                        dev_warn(dev, "Can't get SDA gpio: %d. Not using SDA polling\n",
                                        bri->sda_gpio);                         
                        bri->get_sda = NULL;                                    
                }                                                               
        }                                                                       
                                                                                
        return ret;                                                             
}

scl_gpio 번 핀을 scl로 사용하기 위해 open-drain 설정으로 초기 high 출력 상태로 요청한다. (*get_sda) 후크가 사용되는 경우 sda_gpio 번 핀을 sda로 input mode로 사용 요청한다.

 

i2c_put_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_put_gpios_for_recovery(struct i2c_adapter *adap)                
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
                                                                                
        if (bri->get_sda)                                                       
                gpio_free(bri->sda_gpio);                                       
                                                                                
        gpio_free(bri->scl_gpio);                                               
}

리커버리를 종료하였으므로 scl_gpio 및 sda_gpio 번 핀을 gpio 모드에서 해제한다.

 

gpio 리커버리에서 사용하는 세 함수들

get_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_scl_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->scl_gpio);               
}

scl_gpio 번 핀의 값을 알아온다.

 

set_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static void set_scl_gpio_value(struct i2c_adapter *adap, int val)               
{                                                                               
        gpio_set_value(adap->bus_recovery_info->scl_gpio, val);                 
}

scl_gpio 번 핀에 값을 출력한다.

 

get_sda_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_sda_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->sda_gpio);               
}

sda_gpio 번 핀의 값을 알아온다.

 

 

I2C 전송 및 버스 락

I2C 버스 락

i2c 호스트 컨트롤러를 통해 i2c 버스에 데이터를 송/수신할 때 먼저 버스를 점유하기 위해 i2c 버스 락을 걸고 사용한다. 이 때 대부분의 호스트 컨트롤러의 adapter 인터페이스는 lock_ops가 주어지지 않았을 때 디폴트로 adapter의 bus_lock 뮤텍스를 사용한다.

 

다음 그림은 i2c 전송 전에 디폴트 i2c 버스 락 오퍼레이션인 adapter의 bus_lock 뮤텍스를 사용하는 것을 보여준다.

  • i2c-mux 호스트 컨트롤러를 사용할 때 채널별로 있는 adapter 들의 lock 오퍼레이션은 별도로 지정하여 사용한다.

 

i2c_adapter_lock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_lock_bus - Get exclusive access to an I2C bus segment            
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static void i2c_adapter_lock_bus(struct i2c_adapter *adapter,                   
                                 unsigned int flags)                            
{                                                                               
        rt_mutex_lock(&adapter->bus_lock);                                      
}

adapter에 있는 버스 뮤텍스 락을 건다.

 

i2c_adapter_trylock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_trylock_bus - Try to get exclusive access to an I2C bus segment  
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER trylocks the root i2c adapter, I2C_LOCK_SEGMENT
 *      trylocks only this branch in the adapter tree                           
 */                                                                             
static int i2c_adapter_trylock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        return rt_mutex_trylock(&adapter->bus_lock);                            
}

adapter에 있는 버스 뮤텍스 락을 시도한다.

 

i2c_adapter_unlock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_unlock_bus - Release exclusive access to an I2C bus segment      
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER unlocks the root i2c adapter, I2C_LOCK_SEGMENT 
 *      unlocks only this branch in the adapter tree                            
 */                                                                             
static void i2c_adapter_unlock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        rt_mutex_unlock(&adapter->bus_lock);                                    
}

adapter에 있는 버스 뮤텍스 락을 해제한다.

 

I2C 전송 Core

i2c 전송은 i2c_algorithm에 구현되어 있는 i2c용 전송 후크를 이용한다. 만일 smbus 전송이 필요한 경우 smbus용 후크도 지정하여 사용할 수 있다.

 

다음 그림은 i2c 전송에 관여하는 lock operation과 알고리즘 및 전송 제한 값 및 i2c 전송 함수의 호출 단계를 보여준다.

 

i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_transfer - execute a single or combined I2C message                      
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Note that there is no requirement that each message be sent to               
 * the same slave address, although that is the most common model.              
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)       
{                                                                               
        int ret;                                                                
                                                                                
        /* REVISIT the fault reporting model here is weak:                      
         *                                                                      
         *  - When we get an error after receiving N bytes from a slave,        
         *    there is no way to report "N".                                    
         *                                                                      
         *  - When we get a NAK after transmitting N bytes to a slave,          
         *    there is no way to report "N" ... or to let the master            
         *    continue executing the rest of this combined message, if          
         *    that's the appropriate response.                                  
         *                                                                      
         *  - When for example "num" is two and we successfully complete        
         *    the first message but get an error part way through the           
         *    second, it's unclear whether that should be reported as           
         *    one (discarding status on the second message) or errno            
         *    (discarding status on the first one).                             
         */                                                                     
                                                                                
        if (adap->algo->master_xfer) {                                          
#ifdef DEBUG                                                                    
                for (ret = 0; ret < num; ret++) {                               
                        dev_dbg(&adap->dev,                                     
                                "master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",  
                                ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',  
                                msgs[ret].addr, msgs[ret].len,                  
                                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); 
                }                                                               
#endif                                                                          
                                                                                
                if (in_atomic() || irqs_disabled()) {                           
                        ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);          
                        if (!ret)                                               
                                /* I2C activity is ongoing. */                  
                                return -EAGAIN;                                 
                } else {                                                        
                        i2c_lock_bus(adap, I2C_LOCK_SEGMENT);                   
                }                                                               
                                                                                
                ret = __i2c_transfer(adap, msgs, num);                          
                i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);                         
                                                                                
                return ret;                                                     
        } else {                                                                
                dev_dbg(&adap->dev, "I2C level transfers not supported\n");     
                return -EOPNOTSUPP;                                             
        }                                                                       
}                                                                               
EXPORT_SYMBOL(i2c_transfer);

i2c 버스에 락을 건채 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 22에서 i2c 전송을 지원하는 후크 함수가 있는 경우이다.
    • pc의 smbus는 i2c 전송은 지원하지 않고, smbus 전송만 지원하는 경우가 있다.
  • 코드 라인 34~38에서 전송을 하기 전에 먼저 버스를 점유해야 한다. irq context이거나 preempt disable 상태인 경우 i2c bus 락을 시도한다. 이 때 실패하는 경우 -EAGAIN 에러를 반환한다.
  • 코드 라인 39~41에서 i2c 버스 락을 건다.
  • 코드 라인 43~44에서 요청한 메시지 수 만큼 전송을 한 후 i2c 버스 언락을 한다.

 

__i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * __i2c_transfer - unlocked flavor of i2c_transfer                             
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Adapter lock must be held when calling this function. No debug logging       
 * takes place. adap->algo->master_xfer existence isn't checked.                
 */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)     
{                                                                               
        unsigned long orig_jiffies;                                             
        int ret, try;                                                           
                                                                                
        if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))              
                return -EOPNOTSUPP;                                             
                                                                                
        /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets         
         * enabled.  This is an efficient way of keeping the for-loop from      
         * being executed when not needed.                                      
         */                                                                     
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < num; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_read(adap, &msgs[i], i);              
                        else                                                    
                                trace_i2c_write(adap, &msgs[i], i);             
        }                                                                       
                                                                                
        /* Retry automatically on arbitration loss */                           
        orig_jiffies = jiffies;                                                 
        for (ret = 0, try = 0; try <= adap->retries; try++) {                   
                ret = adap->algo->master_xfer(adap, msgs, num);                 
                if (ret != -EAGAIN)                                             
                        break;                                                  
                if (time_after(jiffies, orig_jiffies + adap->timeout))          
                        break;                                                  
        }                                                                       
                                                                                
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < ret; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_reply(adap, &msgs[i], i);             
                trace_i2c_result(adap, i, ret);                                 
        }                                                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL(__i2c_transfer);

i2c 버스에 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 6~7에서 quirks 전송 제한에 걸린 경우 -EOPNOTSUPP 에러를 반환한다.
  • 코드 라인 13~20에서 읽고 쓸 메시지에 대해 trace 출력을 한다.
  • 코드 라인 23~30에서 i2c 전송을 한다. 전송 후 -EAGAIN 에러인 경우 retries 수 이내에서 루프를 돌며 시도한다. 만일 제한 시간을 초과하는 경우 재시도를 하지 않는다.
  • 코드 라인 32~38에서 읽은 메시지가 있는 경우 이에 대한 trace 출력을 한다.

 

i2c 전송 (for bcm-i2c-iproc)

broadcom ns2에 내장된 i2c에 사용하는 bcm-i2c-iproc 드라이버에서 사용하는 전송함수을 예로 알아본다.

 

bcm_iproc_i2c_xfer()

drivers/i2c/busses/i2c-bcm-iproc.c

static int bcm_iproc_i2c_xfer(struct i2c_adapter *adapter,                      
                              struct i2c_msg msgs[], int num)                   
{                                                                               
        struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);        
        int ret, i;                                                             
                                                                                
        /* go through all messages */                                           
        for (i = 0; i < num; i++) {                                             
                ret = bcm_iproc_i2c_xfer_single_msg(iproc_i2c, &msgs[i]);       
                if (ret) {                                                      
                        dev_dbg(iproc_i2c->device, "xfer failed\n");            
                        return ret;                                             
                }                                                               
        }                                                                       
                                                                                
        return num;                                                             
}

요청한 메시지 수 만큼 메시지들을 전송한다. 성공 시 메시지 수가 반환된다. 실패의 경우 음수를 반환한다.

 

bcm_iproc_i2c_xfer_single_msg()

drivers/i2c/busses/i2c-bcm-iproc.c -1/2-

static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c,   
                                         struct i2c_msg *msg)                   
{                                                                               
        int ret, i;                                                             
        u8 addr;                                                                
        u32 val;                                                                
        unsigned int tx_bytes;                                                  
        unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MSEC);           
                                                                                
        /* check if bus is busy */                                              
        if (!!(readl(iproc_i2c->base + M_CMD_OFFSET) &                          
               BIT(M_CMD_START_BUSY_SHIFT))) {                                  
                dev_warn(iproc_i2c->device, "bus is busy\n");                   
                return -EBUSY;                                                  
        }                                                                       
                                                                                
        iproc_i2c->msg = msg;                                                   
                                                                                
        /* format and load slave address into the TX FIFO */                    
        addr = i2c_8bit_addr_from_msg(msg);                                     
        writel(addr, iproc_i2c->base + M_TX_OFFSET);                            
                                                                                
        /*                                                                      
         * For a write transaction, load data into the TX FIFO. Only allow      
         * loading up to TX FIFO size - 1 bytes of data since the first byte    
         * has been used up by the slave address                                
         */                                                                     
        tx_bytes = min_t(unsigned int, msg->len, M_TX_RX_FIFO_SIZE - 1);        
        if (!(msg->flags & I2C_M_RD)) {                                         
                for (i = 0; i < tx_bytes; i++) {                                
                        val = msg->buf[i];                                      
                                                                                
                        /* mark the last byte */                                
                        if (i == msg->len - 1)                                  
                                val |= 1 << M_TX_WR_STATUS_SHIFT;               
                                                                                
                        writel(val, iproc_i2c->base + M_TX_OFFSET);             
                }                                                               
                iproc_i2c->tx_bytes = tx_bytes;                                 
        }                                                                       
                                                                                
        /* mark as incomplete before starting the transaction */                
        reinit_completion(&iproc_i2c->done);                                    
        iproc_i2c->xfer_is_done = 0;                                            
                                                                                
        /*                                                                      
         * Enable the "start busy" interrupt, which will be triggered after the 
         * transaction is done, i.e., the internal start_busy bit, transitions  
         * from 1 to 0.                                                         
         */                                                                     
        val = BIT(IE_M_START_BUSY_SHIFT);                                       
                                                                                
        /*                                                                      
         * If TX data size is larger than the TX FIFO, need to enable TX        
         * underrun interrupt, which will be triggerred when the TX FIFO is     
         * empty. When that happens we can then pump more data into the FIFO    
         */                                                                     
        if (!(msg->flags & I2C_M_RD) &&                                         
            msg->len > iproc_i2c->tx_bytes)                                     
                val |= BIT(IE_M_TX_UNDERRUN_SHIFT);                             
                                                                                
        writel(val, iproc_i2c->base + IE_OFFSET);

1개의 메시지를 전송한다. 실패 시 음수를 반환한다.

  • 코드 라인 11~15에서 이미 명령이 시작되어 처리되고 있는 중이라 i2c 버스 Busy 상태이다. 이러한 경우 -EBUSY 에러를 반환한다.
  • 코드 라인 17~21에서 메시지에서 7비트 주소와 R/W 비트를 추가한 8비트 값을 tx 레지스터에 기록한다.
  • 코드 라인 28에서 전송할 메시지 길이를 최대 63바이트 범위이내로 제한한다.
  • 코드 라인 28~40에서 Read 메시지가 아니면 송신할 바이트 수 만큼 순회하며 tx 레지스터에 기록한다. 32비트 tx 레지스터에 1 바이트를 기록할 때 tx 레지스터의 msb를 항상 1로 설정한다.
    • 예) { 0x30, 0x40, 0x50, 0x60 } 을 tx 레지스터에 기록 시
      • -> { 0x8000_0030, 0x8000_0040, 0x8000_0050, 0x8000_0060 }
  • 코드 라인 43~44에서 전송이 완료되었음을 알린다.
  • 코드 라인 50~61에서 IE 레지스터에 값을 기록한다.
    • start_busy 비트와 송신 메시지인 경우 adapter의 tx FIFO 버퍼 크기보다 큰 경우 tx underrun 비트를 합친 값이다.

 

drivers/i2c/busses/i2c-bcm-iproc.c -2/2-

        /*                                                                      
         * Now we can activate the transfer. For a read operation, specify the  
         * number of bytes to read                                              
         */                                                                     
        val = BIT(M_CMD_START_BUSY_SHIFT);                                      
        if (msg->flags & I2C_M_RD) {                                            
                val |= (M_CMD_PROTOCOL_BLK_RD << M_CMD_PROTOCOL_SHIFT) |        
                       (msg->len << M_CMD_RD_CNT_SHIFT);                        
        } else {                                                                
                val |= (M_CMD_PROTOCOL_BLK_WR << M_CMD_PROTOCOL_SHIFT);         
        }                                                                       
        writel(val, iproc_i2c->base + M_CMD_OFFSET);                            
                                                                                
        time_left = wait_for_completion_timeout(&iproc_i2c->done, time_left);   
                                                                                
        /* disable all interrupts */                                            
        writel(0, iproc_i2c->base + IE_OFFSET);                                 
        /* read it back to flush the write */                                   
        readl(iproc_i2c->base + IE_OFFSET);                                     
                                                                                
        /* make sure the interrupt handler isn't running */                     
        synchronize_irq(iproc_i2c->irq);                                        
                                                                                
        if (!time_left && !iproc_i2c->xfer_is_done) {                           
                dev_err(iproc_i2c->device, "transaction timed out\n");          
                                                                                
                /* flush FIFOs */                                               
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return -ETIMEDOUT;                                              
        }                                                                       
                                                                                
        ret = bcm_iproc_i2c_check_status(iproc_i2c, msg);                       
        if (ret) {                                                              
                /* flush both TX/RX FIFOs */                                    
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return ret;                                                     
        }                                                                       
                                                                                
        /*                                                                      
         * For a read operation, we now need to load the data from FIFO         
         * into the memory buffer                                               
         */                                                                     
        if (msg->flags & I2C_M_RD) {                                            
                for (i = 0; i < msg->len; i++) {                                
                        msg->buf[i] = (readl(iproc_i2c->base + M_RX_OFFSET) >>  
                                      M_RX_DATA_SHIFT) & M_RX_DATA_MASK;        
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}
  • 코드 라인 5~12에서 M_CMD 레지스터에 적절한 값을 기록한다.
  • 코드 라인 14에서 남은 타임아웃 시간만큼 전송이 완료될 때 까지 기다린다.
  • 코드 라인 17~19에서 모든 인터럽트를 disable하고 write 플러쉬를 수행한다.
  • 코드 라인 22에서 인터럽트 핸들러가 처리 중인 경우 완료될 때 까지 대기한다.
  • 코드 라인 24~32에서 시간이 만료되었고 여전히 전송이 완료되지 않은 경우 RX, TX FIFO 버퍼를 flush한 후 -ETIMEOUT 에러를 반환한다.
  • 코드 라인 34~41에서 다시 i2c 버스 상태가 성공(0) 상태가 아닌 경우에도 RX, TX FIFO 버퍼를 flush한 후 에러를 반환한다.
  • 코드 라인 47~52에서 read 메시지 요청인 경우 M_RX 레지스터에서 요청한 메시지 길이 만큼 반복하여 읽어온다.
    • 수신한 데이터의 하위 8비트만 mask하여 바이트로 저장한다.

 

Broadcom ns2 I2C Register

다음 그림은 broadcom ns2 SoC 내부에 임베드된 i2c 호스 컨트롤러에서 사용되는 주요 레지스터들이다.

 

참고

 

I2C Subsystem -2- (Core)

<kernel v4.14>

I2C subsystem 준비

i2c_init()

i2c-core-base.c

static int __init i2c_init(void)
{
        int retval;

        retval = of_alias_get_highest_id("i2c");

        down_write(&__i2c_board_lock);
        if (retval >= __i2c_first_dynamic_bus_num)
                __i2c_first_dynamic_bus_num = retval + 1;
        up_write(&__i2c_board_lock);

        retval = bus_register(&i2c_bus_type);
        if (retval)
                return retval;

        is_registered = true;

#ifdef CONFIG_I2C_COMPAT
        i2c_adapter_compat_class = class_compat_register("i2c-adapter");
        if (!i2c_adapter_compat_class) {
                retval = -ENOMEM;
                goto bus_err;
        }
#endif
        retval = i2c_add_driver(&dummy_driver);
        if (retval)
                goto class_err;

        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
        if (IS_ENABLED(CONFIG_ACPI))
                WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

        return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
        class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
        is_registered = false;
        bus_unregister(&i2c_bus_type);
        return retval;
}
postcore_initcall(i2c_init);

i2c 버스를 사용할 수 있도록 준비한다.

  • 코드 라인 5에서 “i2c”로 등록된 alias 들 중 가장 큰 alias id를 알아온다.
  • 코드 라인 7~10에서 알아온 alias id가 __i2c_first_dynamic_bus_num보다 큰 경우 __i2c_first_dynamic_bus_num 값을 알아온 alias id+1 값으로 갱신한다. 즉 i2c 버스가 추가될 때 사용될 번호를 갱신한다.
  • 코드 라인 12~16에서 i2c 버스 타입을 등록한다.
  • 코드 라인 18~24에서 CONFIG_I2C_COMPAT 커널 옵션을 사용하는 경우 legacy 호환을 위해 i2c-adapter 클래스를 등록한다.
  • 코드 라인 25~27에서 i2c 버스에 일단 dummy_driver를 등록한다.
  • 코드 라인 29~30에서 디바이스 트리를 사용하는 커널인 경우 i2c bus에 등록/해제되는 디바이스 및 드라이버마다 of_i2c_notify() 함수를 호출하도록 notifier 블럭을 등록한다.
  • 코드 라인 31~32에서 ACPI를 사용하는 커널인 경우 i2c bus에 등록/해제되는 디바이스 및 드라이버마다 i2c_acpi_notify() 함수를 호출하도록 notifier 블럭을 등록한다.

 

i2c 코어 서브시스템

i2c 버스에 디바이스와 드라이버가 등록될 때 동작하는 i2c 코어를 먼저 설명하고 i2c 호스트 컨트롤러의 등록은 잠시 후에 다루기로 한다.

i2c 클라이언트 디바이스 및 드라이버 등록

i2c 클라이언트 디바이스 및 i2c 드라이버의 등록방법은 다음과 같다.

i2c 디바이스 등록
  • i2c_new_device() 함수를 사용하여 등록
    • 드라이버에서 지정한 주소 리스트를 사용하는 경우 HW detect하여 등록할 수 있다.
    • 커널의 특정 기능을 로드할 때(주로 i2c host controller) 이미 알고 있는 i2c 디바이스를 등록한다.
  • 디바이스 트리를 파싱하여 등록을 한다.
    • of_i2c_register_device()
  • ACPI 펌웨어를 통해 등록된다.
    • i2c_acpi_register_device()
  • user space 디바이스 인터페이스를 통해 i2c 디바이스를 등록한다.
    • echo <device-name> <i2c-addr> /sys/bus/i2c/i2c-N/new_device
      • 예) echo foo_lcd 0x3f > /sys/bus/i2c-0/new_device

 

i2c 드라이버 등록

다음 API를 통해 등록한다.

  • i2c_add_driver()
  • i2c_register_driver()
  • module_i2c_driver()

 

i2c 버스 타입

drivers/i2c/i2c-core-base.c

struct bus_type i2c_bus_type = {
        .name           = "i2c",
        .match          = i2c_device_match,
        .probe          = i2c_device_probe,
        .remove         = i2c_device_remove,
        .shutdown       = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type)

i2c 버스에 등록되는 디바이스 및 드라이버가 있을 때마다 i2c_device_match() 함수가 호출된다. 이의 결과가 성공(1)인 경우 i2c_device_probe() 함수가 호출된다.

 

i2c 디바이스 매치

i2c 버스에 디바이스나 드라이버가 등록될 때마다 버스에 등록되어 있는 모든 디바이스와 드라이버들이 서로 매치될 수 있는지 이 함수를 호출한다.

예를 들어 i2c 버스에 다음 디바이스 및 드라이버들이 등록되어 있다고 가정하자.

+ i2c 버스
      |
      +---- 디바이스 리스트
      |            +---------- A-device
      |            +---------- B-device
      +---- 드라이버 리스트
                   +---------- C-driver
                   +---------- D-driver
                   +---------- E-driver

 

위와 같은 경우 매치 여부를 테스트하기 위해 A디바이스를 C,D,E 드라이버와 비교해야 하고, B 디바이스도 역시 C, D, E 드라이버와 비교해야 한다. 즉 등록된 디바이스들과 드라이버들의 조합으로 디바이스 및 드라이버 인자를 가지고 i2c_device_match() 함수가 호출된다.

 

매치 테이블

다음과 같이 3가지 방식을 사용하여 아래 순서대로 매치 여부를 결정한다.

  • of_match_table
    • 드라이버에 있는 이 테이블에 등록된 명칭을 디바이스 트리의 노드명 또는 compatible 속성명과 비교한다.
  • ACPI
    • 드라이버명을 ACPI 펌웨어에 내장된 ACPI 테이블의 디바이스명과 비교한다.
  • id_table
    • 드라이버에 있는 이 테이블에 등록된 명칭을 디바이스명과 비교한다.

 

i2c_device_match()

drivers/i2c/i2c-core-base.c

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
        struct i2c_client       *client = i2c_verify_client(dev);
        struct i2c_driver       *driver;

        /* Attempt an OF style match */
        if (i2c_of_match_device(drv->of_match_table, client))
                return 1;

        /* Then ACPI style match */
        if (acpi_driver_match_device(dev, drv))
                return 1;

        driver = to_i2c_driver(drv);

        /* Finally an I2C match */
        if (i2c_match_id(driver->id_table, client))
                return 1;

        return 0;
}

디바이스와 드라이버가 매치되는 경우 성공(1)을 반환한다.

  • 코드 라인 7~8에서 드라이버의 of_match_table에 등록된 드라이버 이름과 디바이스 트리의 이름 또는 compatible 명이 같은 경우 성공(1)을 반환한다.
    • 예) drv->of_match_table에 등록된 “company,foo”와 디바이스 트리의 compatible=”company,foo”가 동일
  • 코드 라인 11~12에서 ACPI 테이블에서 읽어온 디바이스 명과 드라이버명이 동일한 경우 성공(1)을 반환한다.
  • 코드 라인 17~18에서 마지막으로 드라이버에 등록된 id_table의 id 이름과 디바이스 이름이 동일한 경우 성공(1)을 반환한다.

 

i2c_device_probe()

i2c 버스에 등록된 디바이스와 드라이버들 중 하나가 매치되는 경우 이 함수가 호출된다.

  • 물론 매치 여부와 관련 없이 매뉴얼 probe 하는 경우도 있다.

drivers/i2c/i2c-core-base.c

static int i2c_device_probe(struct device *dev)                                 
{                                                                               
        struct i2c_client       *client = i2c_verify_client(dev);               
        struct i2c_driver       *driver;                                        
        int status;                                                             
                                                                                
        if (!client)                                                            
                return 0;                                                       
                                                                                
        driver = to_i2c_driver(dev->driver);                                    
                                                                                
        if (!client->irq && !driver->disable_i2c_core_irq_mapping) {            
                int irq = -ENOENT;                                              
                                                                                
                if (client->flags & I2C_CLIENT_HOST_NOTIFY) {                   
                        dev_dbg(dev, "Using Host Notify IRQ\n");                
                        irq = i2c_smbus_host_notify_to_irq(client);             
                } else if (dev->of_node) {                                      
                        irq = of_irq_get_byname(dev->of_node, "irq");           
                        if (irq == -EINVAL || irq == -ENODATA)                  
                                irq = of_irq_get(dev->of_node, 0);              
                } else if (ACPI_COMPANION(dev)) {                               
                        irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);    
                }                                                               
                if (irq == -EPROBE_DEFER)                                       
                        return irq;                                             
                                                                                
                if (irq < 0)                                                    
                        irq = 0;                                                
                                                                                
                client->irq = irq;                                              
        }                                                                       
                                                                                
        /*                                                                      
         * An I2C ID table is not mandatory, if and only if, a suitable OF      
         * or ACPI ID table is supplied for the probing device.                 
         */                                                                     
        if (!driver->id_table &&                                                
            !i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&    
            !i2c_of_match_device(dev->driver->of_match_table, client))          
                return -ENODEV;                                                 
                                                                                
        if (client->flags & I2C_CLIENT_WAKE) {                                  
                int wakeirq = -ENOENT;                                          
                                                                                
                if (dev->of_node) {                                             
                        wakeirq = of_irq_get_byname(dev->of_node, "wakeup");    
                        if (wakeirq == -EPROBE_DEFER)                           
                                return wakeirq;                                 
                }                                                               
                                                                                
                device_init_wakeup(&client->dev, true);                         
                                                                                
                if (wakeirq > 0 && wakeirq != client->irq)                      
                        status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);   
                else if (client->irq > 0)                                       
                        status = dev_pm_set_wake_irq(dev, client->irq);         
                else                                                            
                        status = 0;                                             
                                                                                
                if (status)                                                     
                        dev_warn(&client->dev, "failed to set up wakeup irq\n");
        }

디바이스에 바인딩된 드라이버의 (*probe_new) 후크 함수를 호출한다. 후크 함수가 없는 경우 (*probe) 후크 함수를 호출한다. 이 과정에서 사용할 irq를 클라이언트 디바이스에 지정하고 클럭 설정을 한다. 그리고  디바이스에 절전 기능이 있는 경우 pm 도메인을 어태치한다.

  • 코드 라인 12~32에서 i2c 클라이언트 디바이스에 irq가 지정되지 않았고, i2c 코어 시스템에서 irq 매핑을 허용한 경우 다음과 같은 방법으로 irq를 알아온다. 알아오지 못한 경우 0으로 한다.
    • i2c 어댑터에 등록된 irq 도메인에서 client 디바이스의 i2c 주소에 배정된 irq를 알아와 매핑한다.
    • 디바이스 트리 노드의 “interrupt-names” 속성값에서 “irq” 문자열이 포함된 항목의 인덱스 번호에 해당하는 irq를 알아온다.
    • acpi 디바이스의 경우 지정된 irq를 알아온다.
  • 코드 라인 38~41에서 드라이버에 id_table이 지정되지 않은 경우 acpi 또는 디바이스 트리를 통해 배치된 디바이스가 없는 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 43~63에서 절전 기능이 있어 I2C_CLIENT_WAKE 플래그가 사용된 디바이스의 경우 wakeup 처리를 수행한다. 디바이스 트리에서 “wakeup” 속성으로 wakeirq가 지정된 경우 디바이스에 wakeup irq를 지정한다.

 

        dev_dbg(dev, "probe\n");                                                
                                                                                
        status = of_clk_set_defaults(dev->of_node, false);                      
        if (status < 0)                                                         
                goto err_clear_wakeup_irq;                                      
                                                                                
        status = dev_pm_domain_attach(&client->dev, true);                      
        if (status == -EPROBE_DEFER)                                            
                goto err_clear_wakeup_irq;                                      
                                                                                
        /*                                                                      
         * When there are no more users of probe(),                             
         * rename probe_new to probe.                                           
         */                                                                     
        if (driver->probe_new)                                                  
                status = driver->probe_new(client);                             
        else if (driver->probe)                                                 
                status = driver->probe(client,                                  
                                       i2c_match_id(driver->id_table, client)); 
        else                                                                    
                status = -EINVAL;                                               
                                                                                
        if (status)                                                             
                goto err_detach_pm_domain;                                      
                                                                                
        return 0;                                                               
                                                                                
err_detach_pm_domain:                                                           
        dev_pm_domain_detach(&client->dev, true);                               
err_clear_wakeup_irq:                                                           
        dev_pm_clear_wake_irq(&client->dev);                                    
        device_init_wakeup(&client->dev, false);                                
        return status;                                                          
}
  • 코드 라인 3~5에서 i2c 디바이스의 디바이스 트리 노드에서 클럭 설정이 있는 경우 이 값으로 클럭을 설정한다.
    • 클럭 값으로 “assigned-clock-rates” 속성값을 읽어온다. 그런 후 “assigned-clocks” phandle 노드가 가리키는 클럭에 읽어온 클럭 값으로 설정한다.
  • 코드 라인 7~9에서 절전을 위해 acpi pm 도메인을 디바이스에 어태치하거나 디바이스 트리의  “power-domains” phandle 노드가 가리키는 파워 도메인에서 지정된 pm 도메인을 디바이스에 어태치한다.
  • 코드 라인 15~24에서 드라이버에 (*probe_new) 후크 함수를 호출한다. 후크 함수가 없는 경우 (*probe) 후크 함수를 호출한다.

 

i2c 호스트 컨트롤러 드라이버 등록

 

버스별 드라이버 등록

i2c 호스트 컨트롤러가 어떠한 버스에 연결되는지에 따라 모듈의 드라이버 등록부분의 매크 함수가 바뀐다.

  • platform 버스
    • module_platform_driver() 또는 platform_driver_register()
  • pci 버스:
    • module_pci_driver() 또는 pci_register_driver()
  • usb 버스:
    • module_usb_driver() 또는 usb_register()
  • isa 버스:
    • module_isa_driver() 또는 isa_register_driver()
  • amba 버스
    • module_amba_driver() 또는 amba_driver_register()
  • parallel 포트 버스
    • parport_register_driver()

 

참고로 i2c 호스트 컨트롤러는 대부분이 플랫폼 버스에 연결되어 사용된다. 그리고 나머지는 pci 버스에 연결되어 사용된다. usb, isa 및 amba 버스에 연결되어 사용되는 i2c 컨트롤러 드라이버는 몇 개 없다.

usb 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-robotfuzz-osif.c
  • drivers/i2c/busses/i2c-tiny-usb.c
  • drivers/i2c/busses/i2c-diolan-u2c.c

 

isa 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-elektor.c
  • drivers/i2c/busses/i2c-pca-isa.c

 

amba 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-nomadik.c

 

parallel port 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-paraport.c

 

이제 버스별로 드라이버를 등록하는 과정을 살펴보자.

플랫폼 버스

i2c 호스트 컨트롤러를 플랫폼 버스에 붙이는 방법이다.

  • id_table을 사용하거나 of_match_table을 사용하여 디바이스 매치여부를 판단하게 할 수 있다.
  • 아래 소스는 id 테이블이 아닌 디바이스 트리 방식으로 매치 테이블을 구성한 예이다.
static const struct of_device_id foo_of_match[] = { 
        {.compatible = "company,foo-i2c", }, 
        {}, 
}; 
MODULE_DEVICE_TABLE(of, foo_of_match); 

static struct platform_driver foo_i2c_driver = {                          
        .driver = {                                                             
                .name = "foo-i2c",                                        
                .of_match_table = foo_i2c_of_match,                       
        },                                                                      
        .probe = foo_i2c_probe,                                           
        .remove = foo_i2c_remove,                                         
};                                                                              
module_platform_driver(foo_i2c_driver);

 

플랫폼 디바이스 정보들은 디바이스 트리를 사용하는 시스템에서는 대부분 디바이스 트리에서 플랫폼 리소스 정보와 드라이버 명이 지정된다. PC 서버와 같이 ACPI를 사용하는 시스템에서는 ACPI 펌웨어로부터 디바이스에 대한 정보를 알아올 수 있다. 다음은 디바이스 트리를 사용하여 플랫폼 디바이스가 등록된 샘플이다.

i2c0: i2c@66080000 {
        compatible = "company,foo-i2c";
        reg = <0x66080000 0x100>;
        #address-cells = <1>;
        #size-cells = <0>;
        interrupts = <GIC_SPI 394 IRQ_TYPE_NONE>;
        clock-frequency = <100000>;
};

 

pci 버스

i2c 호스트 컨트롤러를 pci 버스에 붙이는 방법이다.  pci 버스에서는 pci 벤더 id와 pci 디바이스 id를 사용하여 매치 테이블을 구성한다.

#define PCI_VENDOR_ID_FOO 0x1234
#define PCI_DEVICE_ID_FOO 0x5678

static const struct pci_device_id foo_ids[] = { 
        { PCI_DEVICE(PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_FOO) }, 
        { 0, } 
}; 
MODULE_DEVICE_TABLE (pci, hydra_ids);

static struct pci_driver foo_driver = {                                       
        .name           = "foo",                                        
        .id_table       = foo_ids,                                            
        .probe          = foo_probe,                                          
        .remove         = foo_remove,                                         
};                                                                              
module_pci_driver(hydra_driver);

 

usb  버스

i2c 호스트 컨트롤러를 usb 버스에 붙이는 방법이다. usb 버스에서는 벤더 id와 제품 id를 사용하여 매치 테이블을 구성한다.

static const struct usb_device_id foo_table[] = { 
        { USB_DEVICE(0x1234, 0x5678) },
        { }
};

static struct usb_driver foo_driver = {                                  
        .name = "foo-i2c",                                                    
        .probe = foo_probe,                                              
        .disconnect = foo_disconnect,                                    
        .id_table = foo_table,                                           
};                                                                                
module_usb_driver(foo_driver);

 

probe 후크

probe 함수가 호출되면 먼저 디바이스 트리를 통해 플랫폼 디바이스에 등록되어 있던 플랫폼 리소스 정보를 알아와서 아래 구조체에 등록하고 HW 설정등을 수행한다.

struct foo_i2c_dev {                                                      
        struct device *device;                                                  

        struct clk *clk;             /* ic2 클럭이 필요한 경우에만 */
        u32 bus_clk_rate;          

        void __iomem *base;          /* 매핑하여 사용할 i2c 레지스터 */                                            

        int irq;                     /* 인터럽트가 사용되는 경우에만 */                                             
                                                                                
        struct i2c_adapter adapter;  /* 등록할 i2c adapter */                                           
        unsigned int bus_speed;      /* 디바이스 트리에서 읽어온 버스 속도 */

        ...                                                 
};

 

플랫폼 드라이버에서의 probe 함수는 다음과 같은 형식으로 작성된다.

static int foo_probe(struct platform_device *pdev)                    
{                                                                               
        int irq, ret = 0;                                                       
        struct foo_i2c_dev *foo_i2c;                                    
        struct i2c_adapter *adap;                                               
        struct resource *res;                                                   
                                                                                
        foo_i2c = devm_kzalloc(&pdev->dev, sizeof(*foo_i2c),                
                                 GFP_KERNEL);                                   
        if (!foo_i2c)                                                         
                return -ENOMEM;                                                 
                                                                                
        platform_set_drvdata(pdev, foo_i2c);                                  
        foo_i2c->device = &pdev->dev;
                                                                                
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);                   
        foo_i2c->base = devm_ioremap_resource(foo_i2c->device, res);        
        if (IS_ERR(foo_i2c->base))                                            
                return PTR_ERR(foo_i2c->base);                                
                                                                                
        ret = foo_i2c_init(foo_i2c);   /* HW 초기화 설정이 필요한 경우 */
        if (ret)                                                                
                return ret;                                                     
                                                                                
        ret = foo_i2c_cfg_speed(foo_i2c);   /* 버스 속도 HW 설정 */                            
        if (ret)                                                                
                return ret;                                                     
                                                                                
        irq = platform_get_irq(pdev, 0);                                        
        if (irq <= 0) {                                                         
                dev_err(foo_i2c->device, "no irq resource\n");                
                return irq;                                                     
        }                                                                       
        foo_i2c->irq = irq;                                                   
                                                                                
        ret = devm_request_irq(foo_i2c->device, irq, foo_i2c_isr, 0,    
                               pdev->name, foo_i2c);                          
        if (ret < 0) {                                                          
                dev_err(foo_i2c->device, "unable to request irq %i\n", irq);  
                return ret;                                                     
        }                                                                       
                                                                                
        foo_i2c_enable(foo_i2c);      /* i2c enable HW 설정 */
                                                                                
        adap = &foo_i2c->adapter;                                             
        i2c_set_adapdata(adap, foo_i2c);                                      
        strlcpy(adap->name, "foo i2c adapter", sizeof(adap->name));  
        adap->algo = &foo_algo;                                           
        adap->quirks = &foo_quirks;                                   
        adap->dev.parent = &pdev->dev;                                          
        adap->dev.of_node = pdev->dev.of_node;                                  
                                                                                
        return i2c_add_adapter(adap);                                           
} 
  • 코드 라인 8~11에서 i2c 호스트 컨트롤러 드라이버 데이터를 담을 foo_i2c_dev 구조체를 할당받는다.
  • 코드 라인 13~14에서 플랫폼 디바이스에 위에서 할당한 i2c 호스트 컨트롤러 드라이버의 데이터를 지정하고, 반대로 드라이버가 플랫폼 디바이스도 지정하여 서로 바인드한다.
  • 코드 라인 16~19에서 플랫폼 디바이스에 저장된 리소스 정보 중 i2c 레지스터 주소가 담긴 정보를 가져와서 io 매핑한다. 매핑하여 사용할 수 있는 가상주소가 base 멤버에 저장되므로 이제 이 멤버를 통해 i2c 레지스터에 접근할 수 있게되었다.
  • 코드 라인 21~23에서 i2c HW 초기화 설정이 필요한 경우 관련 함수를 하나 만들어 호출한다. 보통 여기에서 i2c 호스트 컨트롤러를 리셋하고 송/수신 큐들에 대한 flush와 pending 인터럽트 등을 클리어하는 등 HW의 초기화가 필요한 모든 것들을 처리한다.
  • 코드 라인 25~27에서 i2c 버스 스피드 HW 설정을 한다.
    • 플랫폼 드라이버의 경우 i2c 버스 스피드를 설정할 때 디바이스 트리 또는 ACPI 테이블로부터 정보를 알아온다.
  • 코드 라인 29~41에서 다시 플랫폼 디바이스에 저장된 리소스 정보 중 이번에는 irq 번호를 가져와서 irq 요청을 해야 한다. irq 처리를 위한 핸들러도 같이 등록해준다. 또한 irq 번호에 해당하는 irq chip controller의 구현과 irq domain 및 irq descriptor가 모두 사전에 할당되어 구성되어 있어야한다.
    • 대부분의 경우 irq chip 컨트롤러에서 irq domain 및 irq descriptor등이 사전에 구성되어 있다.
  • 코드 라인 43에서 i2c enable을 하기 위한 HW 조작이 필요한 경우 관련 작업을 수행하는 함수를 작성하여 호출한다.
  • 코드 라인 45~53에서 adapter를 구성하여 등록한다. 이 adapter에는 다음과 같은 항목을 다룬다.
    • *algo
      • i2c 전송 알고리즘 오퍼레이션을 지정한다.
    • *algo_data
      • i2c 전송 알고리즘의 후크 함수들의 인자로 전달된 데이터이다. 일반적으로 i2c 디바이스 정보를 전달한다. (예: foo_i2c_dev)
      • *alog가 지정되지 않는 경우 scl과 sda 라인을 직접 비트 조작할 수 있는 오퍼레이션을 지정할 수 있다.
    • *quirks
      • 옵션으로 메시지 전송 제한을 할 수 있는 quirks 오퍼레이션을 지정한다.

 

i2c 전송 구현(algorithm)

i2c adapter에서 i2c 전송에 대한 구현은 i2c chip마다 모두 다르다. 가장 많이 사용하는 방법으로 i2c_algorithm 구조체에 i2c 및 smbus 프로토콜 전송 함수를 구현하여 등록하고 이를 adapter->algo에서 지정하면 된다.

 

i2c_algorithm 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_algorithm - represent I2C transfer method                         
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter       
 *   defined by the msgs array, with num messages available to transfer via     
 *   the adapter specified by adap.                                             
 * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this      
 *   is not present, then the bus layer will try and convert the SMBus calls    
 *   into I2C transfers instead.                                                
 * @functionality: Return the flags that this algorithm/adapter pair supports   
 *   from the I2C_FUNC_* flags.                                                 
 * @reg_slave: Register given client to I2C slave mode of this adapter          
 * @unreg_slave: Unregister given client from I2C slave mode of this adapter    
 *                                                                              
 * The following structs are for those who like to implement new bus drivers:   
 * i2c_algorithm is the interface to a class of hardware solutions which can    
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584 
 * to name two of the most common.                                              
 *                                                                              
 * The return codes from the @master_xfer field should indicate the type of     
 * error code that occurred during the transfer, as documented in the kernel    
 * Documentation file Documentation/i2c/fault-codes.                            
 */
struct i2c_algorithm {                                                          
        /* If an adapter algorithm can't do I2C-level access, set master_xfer   
           to NULL. If an adapter algorithm can do SMBus access, set            
           smbus_xfer. If set to NULL, the SMBus protocol is simulated          
           using common I2C messages */                                         
        /* master_xfer should return the number of messages successfully        
           processed, or a negative value on error */                           
        int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,      
                           int num);                                            
        int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,                  
                           unsigned short flags, char read_write,               
                           u8 command, int size, union i2c_smbus_data *data);   
                                                                                
        /* To determine what the adapter supports */                            
        u32 (*functionality) (struct i2c_adapter *);                            
                                                                                
#if IS_ENABLED(CONFIG_I2C_SLAVE)                                                
        int (*reg_slave)(struct i2c_client *client);                            
        int (*unreg_slave)(struct i2c_client *client);                          
#endif                                                                          
};
  • (*master_xfer)
    • 메시지 어레이에 대해 i2c 프로토콜로 전송을 구현해야 하는 후크 함수
    • i2c 프로토콜로 전송을 하지 않는 경우 null을 대입
  • (*smbus_xfer)
    • 메시지 어레이에 대해 smbus 프로토콜로 전송을 구현해야 하는 후크 함수
    • null인 경우 i2c 전송 프로토콜을 사용하여 smbus 전송 요청을 시뮬레이션한다.
    • (*master_xfer)와 (*smbus_xfer) 두 후크가 모두 구현되었고, 특별히 i2c 프로토콜 방식을 사용하라고 명시하지 않으면 (*smbus_xfer)를 사용한다.
  • (*functionality)
    • 이 adapter가 가지고 있는 기능을 비트 플래그로 표현
  • (*reg-slave)
    • clock streaching을 위해 slave 기능이 필요한 경우 사용되는 slave 등록 함수
    • eeprom에서 사용
  • (*unreg_slave)
    • slave 등록을 지원하는 경우 사용되는 slave 등록 해제 함수

 

내장 알고리즘 3가지

또 다른 방법으로는 다음과 같이 커널에 미리 준비된 3 가지 방법 알고리즘을 사용하는 방법도 있다.

  • bit 알고리즘 방식
    • i2c_algorithm 구조체는 bit 방식으로 임베드되어 있고 i2c_algo_bit_data 구조체만 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • i2c bus를 scl 및 sda 라인에 대해 비트 단위의 low-level로 직접 제어하기 위해 사용된다. 이러한 특징을 이용하여 gpio의 2핀을 scl 및 sda 라인으로 동작시켜 i2c 호스트 컨트롤러와 같은 동작을 하게 할 수 있다.
    • i2c adapter를 등록할 때 보통 i2c_add_adapter() 함수를 사용하지만, 이 방식에서는 i2c_bit_add_bus() 또는 i2c_bit_add_numbered_bus() 함수를 통해 내부에 임베드된 i2c_add_adapter() 함수를 호출하여 사용한다.
    • 이 방식을 사용하는 드라이버들
      • drivers/i2c/busses/i2c-gpio.c
        • 2개의 gpio 핀을 사용하여 i2c 호스트 컨트롤러로 동작시키는 방식으로 i2c_bit 알고리즘을 사용하는 대표적인 드라이버이다.
      • drivers/i2c/busses/i2c-versatile.c
        • 이 드라이버도 scl 및 sda 라인을 직접 제어할 수 있는 대표적인 드라이버이다.
      • drivers/i2c/busses/i2c-parport-light.c
      • drivers/i2c/busses/i2c-simtec.c
      • drivers/i2c/busses/i2c-via.c
      • drivers/i2c/busses/i2c-acorn.c
      • drivers/i2c/busses/i2c-parport.c
      • drivers/i2c/busses/i2c-hydra.c
  • pca 알고리즘 방식
    • i2c_algorithm 구조체는 pca 방식으로 임베드되어 있고 i2c_algo_pca_data 구조체를 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • isa 버스 및 platform 버스에 연결되어 사용하는 pca9564/9665 칩 전용 방식이다.
    • i2c adapter를 등록 시 i2c_pca_add_bus() 또는 i2c_pca_add_numbered_bus() 함수를 사용한다.
    • 이 방식을 사용하는 드라이버들
      • drivers/i2c/busses/i2c-pca-isa.c
      • drivers/i2c/busses/i2c-pca-platform.c
  • pcf 알고리즘 방식
    • i2c_algorithm 구조체는 pcf 방식으로 임베드되어 있고 i2c_algo_pcf_data 구조체를 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • isa 버스에 연결되어 사용하는 pcf8584 칩 전용 방식이다.
    • i2c adapter를 등록 시 i2c_pcf_add_bus() 또는 i2c_pcf_add_numbered_bus() 함수를 사용한다.
    • 이 방식을 사용하는 드라이버
      • drivers/i2c/busses/i2c-elektor.c

 

bit 알고리즘 구현

아래에 bit 알고리즘 방식으로 사용하는 구조체만 설명하기로 한다. gpio를 사용하여 i2c 호스트 컨트롤러를 구현해보고자 하는 경우 반드시 이해를 해야 한다.

i2c_algo_bit_data 구조체

include/linux/i2c-algo-bit.h

/* --- Defines for bit-adapters --------------------------------------- */
/*
 * This struct contains the hw-dependent functions of bit-style adapters to
 * manipulate the line states, and to init any hw-specific features. This is
 * only used if you have more than one hw-type of adapter running. 
 */
struct i2c_algo_bit_data {
        void *data;             /* private data for lowlevel routines */
        void (*setsda) (void *data, int state);
        void (*setscl) (void *data, int state);
        int  (*getsda) (void *data);
        int  (*getscl) (void *data); 
        int  (*pre_xfer)  (struct i2c_adapter *);
        void (*post_xfer) (struct i2c_adapter *);

        /* local settings */
        int udelay;             /* half clock cycle time in us,
                                   minimum 2 us for fast-mode I2C,
                                   minimum 5 us for standard-mode I2C and SMBus,
                                   maximum 50 us for SMBus */
        int timeout;            /* in jiffies */
};
  • *data
    • private data
  • (*setsda)
    • 시리얼 데이터(sda) 라인을 설정한다.
  • (*setscl)
    • 시리얼 클럭(scl) 라인을 설정한다.
  • (*getsda)
    • 시리얼 데이터(sda) 라인 상태를 알아온다.
  • (*getscl)
    • 시리얼 클럭(scl) 라인 상태를 알아온다.
  • (*pre_xfer)
  • (*post_xfer)
  • udelay
    • 클럭과 클럭 사이에 delay(us)를 할 때 필요한 시간으로 사이클의 절반 시간을 지정한다.
    • 400Khz 속도로 동작하는 fast-mode I2C의 경우 클럭 주기는 2.5us이다. 그 절반인 1.25가 필요하지만 올림 처리하여 2us가 필요하다.
    • 100Khz 속도로 동작하는 standard-mode I2C의 경우 클럭 주기는 10us이다. 따라서 최소한 그 절반인 5us가 필요하다.
    • 10Khz 저속 처리가 가능한 SMBus 방식의 I2C의 경우 클럭 주기는 100us이다. 최대 값으로 그 절반인 50us까지 허용된다.
  • timeout
    • 타임아웃(jiffies)을 지정한다.

 

2 개의 gpio 핀을 사용한 i2c 호스트 컨트롤러 구현 샘플

위의 구조체를 구현하여 동작시키는 gpio 드라이버를 작성하는 방법을 알아본다. (전체 소스를 만드는 것이 아니라 주요 부분만 설명하였다.)

struct i2c_gpio_platform_data {
        unsigned int    sda_pin;                 <- i2c sda 용도의 gpio 핀 번호
        unsigned int    scl_pin;                 <- i2c scl 용도의 gpio 핀 번호
        int             udelay;
        int             timeout;
        unsigned int    sda_is_open_drain:1;
        unsigned int    scl_is_open_drain:1;    
        unsigned int    scl_is_output_only:1;
};

위의 구조체에 들어갈 값들은 i2c 디바이스가 디바이스 트리를 통해 플랫폼 디바이스로 등록된 경우 아래와 같이 플랫폼 데이터로 전달받을 수 있다.

  • pdata = dev_get_platdata(&pdev->dev);

 

gpio를 사용하여 i2c 디바이스 트리를 만드는 경우의 디바이스 트리 노드 샘플

아래와 같이 gpio의 23번을 i2c sda 라인으로 사용하고, gpio 24번을 i2c scl 라인으로 사용하게 한다. 둘 다 gpio핀 상태를 open-drain으로 설정한다.

  • 서브 노드의 rv3029c2는 i2c 주소 0x56을 사용하는 i2c 클라이언트 디바이스이다. 여기에서는 설명하지 않는다.
i2c@0 {
        compatible = "i2c-gpio";
        gpios = <&pioA 23 0 /* sda */
                 &pioA 24 0 /* scl */>
        i2c-gpio,sda-open-drain;
        i2c-gpio,scl-open-drain;
        i2c-gpio,delay-us = <2>;        /* ~100 kHz */
        #address-cells = <1>;
        #size-cells = <0>;

        rv3029c2@56 {
                compatible = "rv3029c2";
                reg = <0x56>;
        };
};

 

만일 그렇지 않은 경우 직접 다음과 같은 방법으로 디바이스 트리 노드를 파싱하여 알아올 수 있다.

  • of_i2c_gpio_get_pins() 함수를 사용하여 다음 값들에 해당하는 gpio 핀 번호를 알아온다.
    • sda_pin
    • scl_pin
  • of_i2c_gpio_get_props() 함수를 사용하여 디바이스 트리 노드를 통해 다음 속성값들을 알아온다.
    • “i2c-gpio,delay-us”
    • “i2c-gpio,timeout-ms”
    • “i2c-gpio,sda-open-drain”
    • “i2c-gpio,scl-open-drain”
    • “i2c-gpio,scl-output-only”

 

그 후 bit 알고리즘 처리를 위한 후크 함수와 i2c_algo_bit_data 구조체를 채워 구현한다.

static void i2c_gpio_setsda_val(void *data, int state)
{
        struct i2c_gpio_platform_data *pdata = data;

        gpio_set_value(pdata->sda_pin, state);
}

static void i2c_gpio_setscl_val(void *data, int state)
{
        struct i2c_gpio_platform_data *pdata = data;

        gpio_set_value(pdata->scl_pin, state);
}

static int i2c_gpio_getsda(void *data)
{
        struct i2c_gpio_platform_data *pdata = data;

        return gpio_get_value(pdata->sda_pin);
}

static int i2c_gpio_getscl(void *data)
{
        struct i2c_gpio_platform_data *pdata = data;

        return gpio_get_value(pdata->scl_pin);
}

static struct i2c_algo_bit_data {

         .data = &pdata
         .setsda = i2c_gpio_setsda_val,
         .setscl = i2c_gpio_setscl_val,
         .getsda = i2c_gpio_getsda,
         .getscl = i2c_gpio_getscl,
         .udelay = 5,                 /* 디바이스 트리에서 읽은 값(us) */
         .timeout = 100,              /* 디바이스 트리에서 읽은 값(ms) */
} foo_bit;

 

위의 정보들을 모아 i2c_adapter 구조체를 만든 후 다음 api를 통해 adapter를 등록하는 것으로 호스트 컨트롤러 드라이버가 완성되다.

  • i2c_bit_add_numbered_bus(adap);

 

I2C Adapter

i2c 호스트 컨트롤러 드라이버가 상위 버스(플랫폼 버스나 pci 버스 등)에 등록된 후 그 아래에 위치한 i2c 버스를 control하는 칩과 연동하여 사용하기 위해 i2c adapter 디바이스를 구현하여 사용한다.

 

I2C Adapter 포지션

다음 그림은 pci 버스 아래에 i2c bus가 연결되어 있는 모습이다. i2c adapter는 상위 버스와 i2c 버스 중간에 위치한다.

 

include/linux/i2c.h

i2c_adapter 구조체

/*                                                                              
 * i2c_adapter is the structure used to identify a physical i2c bus along       
 * with the access algorithms necessary to access it.                           
 */                                                                             
struct i2c_adapter {                                                            
        struct module *owner;                                                   
        unsigned int class;               /* classes to allow probing for */    
        const struct i2c_algorithm *algo; /* the algorithm to access the bus */ 
        void *algo_data;                                                        
                                                                                
        /* data fields that are valid for all devices   */                      
        const struct i2c_lock_operations *lock_ops;                             
        struct rt_mutex bus_lock;                                               
        struct rt_mutex mux_lock;                                               
                                                                                
        int timeout;                    /* in jiffies */                        
        int retries;                                                            
        struct device dev;              /* the adapter device */                
                                                                                
        int nr;                                                                 
        char name[48];                                                          
        struct completion dev_released;                                         
                                                                                
        struct mutex userspace_clients_lock;                                    
        struct list_head userspace_clients;                                     
                                                                                
        struct i2c_bus_recovery_info *bus_recovery_info;                        
        const struct i2c_adapter_quirks *quirks;                                
                                                                                
        struct irq_domain *host_notify_domain;                                  
};
  • *owner
    • 모듈을 가리킨다.
  • class
    • 플래그 같이 사용한다.
      • I2C_CLASS_HWMON(0x01)
        • hwmon 클래스를 사용하는 디바이스들(lm 센서들)
      • I2C_CLASS_DDC(0x08)
        • 그래픽 아탑터위의 DDC 버스
      • I2C_CLASS_SPD(0x80)
        • 메모리 모듈
      • I2C_CLASS_DEPRECATED
        • 더 이상 클래스 타입을 지원하지 않는다.
      • I2C_CLIENT_END
        • lists의 터미네이션을 위한 내부 번호
  • *algo
    • i2c 버스에 접근할 알고리즘
  • *algo_data
    • 알고리즘 데이터
  • *lock_ops
    • i2c 버스에 접근하기 위해 사용되는 lock operation 후크
    • 지정하지 않는 경우 default i2c lock operation을 사용한다.
  • bus_lock
    • 버스에 대한 뮤텍스 락
  • mux_lock
    • i2c-mux에 대한 뮤텍스 락
  • timeout
    • 타임아웃(단위: jiffies)
  • retries
    • 반복 횟수
  • dev
    • adapter 디바이스
  • nr
    • adapter 번호
    • -1: auto
  • name
    • adapter 이름
  • dev_released
  • userspace_clients_lock
    • 유저스페이스 클라이언트 뮤텍스 락
  • userspace_clients
    • 유저 스페이스 클라이언트 리스트
  • *bus_recovery_info
    • i2c 버스 리커버리를 지원하는 경우 사용한다.
  • *quirks
    • i2c 전송 제한을 지원하는 경우 사용한다.
  • *host_notify_domain
    • smbus를 사용 시 irq 를 호스트에 전달하기 위한 irq domain이다.

 

Adapter 디바이스 등록

다음 그림은 adapter 디바이스를 등록하고 해당 i2c 버스에서 검출되는 i2c 디바이스들을 등록하는 과정을 보여준다.

 

i2c_add_adapter()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_add_adapter - declare i2c adapter, use dynamic bus number                
 * @adapter: the adapter to add                                                 
 * Context: can sleep                                                           
 *                                                                              
 * This routine is used to declare an I2C adapter when its bus number           
 * doesn't matter or when its bus number is specified by an dt alias.           
 * Examples of bases when the bus number doesn't matter: I2C adapters           
 * dynamically added by USB links or PCI plugin cards.                          
 *                                                                              
 * When this returns zero, a new bus number was allocated and stored            
 * in adap->nr, and the specified adapter became available for clients.         
 * Otherwise, a negative errno value is returned.                               
 */
int i2c_add_adapter(struct i2c_adapter *adapter)                                
{                                                                               
        struct device *dev = &adapter->dev;                                     
        int id;                                                                 
                                                                                
        if (dev->of_node) {                                                     
                id = of_alias_get_id(dev->of_node, "i2c");                      
                if (id >= 0) {                                                  
                        adapter->nr = id;                                       
                        return __i2c_add_numbered_adapter(adapter);             
                }                                                               
        }                                                                       
                                                                                
        mutex_lock(&core_lock);                                                 
        id = idr_alloc(&i2c_adapter_idr, adapter,                               
                       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);             
        mutex_unlock(&core_lock);                                               
        if (WARN(id < 0, "couldn't get idr"))                                   
                return id;                                                      
                                                                                
        adapter->nr = id;                                                       
                                                                                
        return i2c_register_adapter(adapter);                                   
}                                                                               
EXPORT_SYMBOL(i2c_add_adapter);

i2c adapter 디바이스를 등록하고 해당 i2c 버스의 일부 디바이스들을 HW 디텍트 시도하여 등록한다. adapter id는 디바이스 트리에서 지정할 수도 있다. 결정된 i2c adapter id는 adapter->nr에 저장된다.

  • 코드 라인 6~12에서 디바이스 트리에서 “i2c” alias 노드가 있고 버스 id가 지정된 경우 그 번호로 adapter를 등록시킨다.
  • 코드 라인 14~21에서 idr Radix 트리를 통해 i2c 버스 id를 발급받고 adapter에 지정한다. (0부터 시작)
  • 코드 라인 23에서 adapter를 등록한다.

 

__i2c_add_numbered_adapter()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1   
 * @adap: the adapter to register (with adap->nr initialized)                   
 * Context: can sleep                                                           
 *                                                                              
 * See i2c_add_numbered_adapter() for details.                                  
 */                                                                             
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)                 
{                                                                               
        int id;                                                                 
                                                                                
        mutex_lock(&core_lock);                                                 
        id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
        mutex_unlock(&core_lock);                                               
        if (WARN(id < 0, "couldn't get idr"))                                   
                return id == -ENOSPC ? -EBUSY : id;                             
                                                                                
        return i2c_register_adapter(adap);                                      
}

지정한 i2c adapter id 번호로 id를 발급받아 i2c adapter 디바이스를 등록한다. 그런 후 해당 i2c 버스에서 검출되는 i2c 디바이스들을 등록한다.

 

i2c_register_adapter()

drivers/i2c/i2c-core-base.c

static int i2c_register_adapter(struct i2c_adapter *adap)                       
{                                                                               
        int res = -EINVAL;                                                      
                                                                                
        /* Can't register until after driver model init */                      
        if (WARN_ON(!is_registered)) {                                          
                res = -EAGAIN;                                                  
                goto out_list;                                                  
        }                                                                       
                                                                                
        /* Sanity checks */                                                     
        if (WARN(!adap->name[0], "i2c adapter has no name"))                    
                goto out_list;                                                  
                                                                                
        if (!adap->algo) {                                                      
                pr_err("adapter '%s': no algo supplied!\n", adap->name);        
                goto out_list;                                                  
        }                                                                       
                                                                                
        if (!adap->lock_ops)                                                    
                adap->lock_ops = &i2c_adapter_lock_ops;                         
                                                                                
        rt_mutex_init(&adap->bus_lock);                                         
        rt_mutex_init(&adap->mux_lock);                                         
        mutex_init(&adap->userspace_clients_lock);                              
        INIT_LIST_HEAD(&adap->userspace_clients);                               
                                                                                
        /* Set default timeout to 1 second if not already set */                
        if (adap->timeout == 0)                                                 
                adap->timeout = HZ;                                             
                                                                                
        /* register soft irqs for Host Notify */                                
        res = i2c_setup_host_notify_irq_domain(adap);                           
        if (res) {                                                              
                pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",    
                       adap->name, res);                                        
                goto out_list;                                                  
        }                                                                       
                                                                                
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);                           
        adap->dev.bus = &i2c_bus_type;                                          
        adap->dev.type = &i2c_adapter_type;                                     
        res = device_register(&adap->dev);                                      
        if (res) {                                                              
                pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
                goto out_list;                                                  
        }                                                                       
                                                                                
        dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

i2c adapter 디바이스를 등록한다. 이 후 지정된 주소에 대해 HW 디텍트를 시도하여 자동 등록한다.

  • 코드 라인 6~9에서 디바이스 드라이버 모델이 초기화되고 i2c_init() 함수가 호출되기 전까지는 adapter가 등록되지 않게 제한한다.
  • 코드 라인 12~13에서 adapter 명이 지정되지 않은 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 15~18에서 알고리즘이 지정되지 않은 경우 에러 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 20~21에서 adapter에서 지원하는 bus lock 오퍼레이션이 없는 경우 디폴트 i2c bus lock 오퍼레이션을 사용하게 한다.
  • 코드 라인 23~26에서 adapter의 lock들과 userspace_clients 리스트를 초기화한다.
  • 코드 라인 29~30에서 타임아웃이 지정되지 않은 경우 1초로 제한한다.
  • 코드 라인 33~38에서 smbus host notify를 위해 0x78개의 irq 디스크립터용 리니어 irq 도메인을 생성한다.
  • 코드 라인 40~47에서 adapter 이름을 지정하고 버스 및 i2c adapter 타입을 지정한 후 디바이스로 등록한다.

 

        pm_runtime_no_callbacks(&adap->dev);                                    
        pm_suspend_ignore_children(&adap->dev, true);                           
        pm_runtime_enable(&adap->dev);                                          
                                                                                
#ifdef CONFIG_I2C_COMPAT                                                        
        res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,    
                                       adap->dev.parent);                       
        if (res)                                                                
                dev_warn(&adap->dev,                                            
                         "Failed to create compatibility class link\n");        
#endif                                                                          
                                                                                
        i2c_init_recovery(adap);                                                
                                                                                
        /* create pre-declared device nodes */                                  
        of_i2c_register_devices(adap);                                          
        i2c_acpi_register_devices(adap);                                        
        i2c_acpi_install_space_handler(adap);                                   
                                                                                
        if (adap->nr < __i2c_first_dynamic_bus_num)                             
                i2c_scan_static_board_info(adap);                               
                                                                                
        /* Notify drivers */                                                    
        mutex_lock(&core_lock);                                                 
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);     
        mutex_unlock(&core_lock);                                               
                                                                                
        return 0;                                                               
                                                                                
out_list:                                                                       
        mutex_lock(&core_lock);                                                 
        idr_remove(&i2c_adapter_idr, adap->nr);                                 
        mutex_unlock(&core_lock);                                               
        return res;                                                             
}
  • 코드 라인 1에서 이 adapter 디바이스에 대한 런타임 PM 콜백을 무시하게 한다.
  • 코드 라인 2에서 이 adapter 디바이스의 자식 디바이스들에 대해 절전 모드 진입을 무시하게 한다.
  • 코드 라인 3에서 이 adapter 디바이스에 대한 런타임 PM을 enable한다.
  • 코드 라인 5~11에서 기존 user-space에서 클래스 디바이스를 사용하던 디바이스들과의 호환을 위해 심볼링크를 생성한다.
    • /sys/class/i2c-adapter/i2c-0 심볼링크가  /sys/devices/platform/66080000.i2c/i2c-0 adapter 클래스 디바이스를 가리키게한다.
    • /sys/devices/platform/66080000.i2c/i2c-0/device 심볼링크가 /sys/devices/platform/66080000.i2c를 가리키게한다.
  • 코드 라인 13에서 adapter가 관리하는 i2c 버스 리커버리를 초기화한다.
  • 코드 라인 16에서 adpater에 해당하는 버스 노드 뒤의 디바이스들을 등록한다.
    • 호스트 컨트롤러 노드의 서브노드에 “i2c-bus” 노드가 있으면 그 하위 노드들에 있는 디바이스들을 등록한다. 만일 “i2c-bus” 노드가 없으면 adapter 노드의 서브 노드들에 있는 디바이스들을 등록한다.
  • 코드 라인 17~18에서 adapter 버스에 매치되는 acpi 테이블의 디바이스들을 등록하고 i2c acpi address space 핸들러를 등록한다.
  • 코드 라인 20~21에서 __i2c_board_list에 등록된 디바이스들 중 버스 번호가 같은 디바이스들을 static하게 등록한다.
    • i2c_register_board_info() 함수를 사용하여 등록한다.
  • 코드 라인 25에서 마지막으로 해당 adapter의 i2c 버스에서 등록된 모든 드라이버들의 주소 리스트를 사용하여 즉, detect 지원되는 디바이스들을 대상으로 HW detect 후 발견되면 등록한다. 그리고 legacy 드라이버가 디바이스를 디텍트할 수 있도록 (*attach_adapter) 후크가 있는 경우 호출한다.

 

__process_new_adapter()

drivers/i2c/i2c-core-base.c

static int __process_new_adapter(struct device_driver *d, void *data)           
{                                                                               
        return i2c_do_add_adapter(to_i2c_driver(d), data);                      
}

아래 함수로 연결된다.

 

i2c_do_add_adapter()

drivers/i2c/i2c-core-base.c

static int i2c_do_add_adapter(struct i2c_driver *driver,                        
                              struct i2c_adapter *adap)                         
{                                                                               
        /* Detect supported devices on that bus, and instantiate them */        
        i2c_detect(adap, driver);                                               
                                                                                
        /* Let legacy drivers scan this bus for matching devices */             
        if (driver->attach_adapter) {                                           
                dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
                         driver->driver.name);                                  
                dev_warn(&adap->dev,                                            
                         "Please use another way to instantiate your i2c_client\n");
                /* We ignore the return code; if it fails, too bad */           
                driver->attach_adapter(adap);                                   
        }                                                                       
        return 0;                                                               
}

adapter에 해당하는 i2c 버스에서 detect 지원되는 디바이스들을 HW detect 후 발견되면 등록한다. 그리고 legacy 드라이버가 디바이스를 디텍트할 수 있도록 (*attach_adapter) 후크가 있는 경우 호출한다.

 

I2C 디바이스 검출

i2c_detect()

drivers/i2c/i2c-core-base.c

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)   
{                                                                               
        const unsigned short *address_list;                                     
        struct i2c_client *temp_client;                                         
        int i, err = 0;                                                         
        int adap_id = i2c_adapter_id(adapter);                                  
                                                                                
        address_list = driver->address_list;                                    
        if (!driver->detect || !address_list)                                   
                return 0;                                                       
                                                                                
        /* Warn that the adapter lost class based instantiation */              
        if (adapter->class == I2C_CLASS_DEPRECATED) {                           
                dev_dbg(&adapter->dev,                                          
                        "This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
                        "If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",
                        driver->driver.name);                                   
                return 0;                                                       
        }                                                                       
                                                                                
        /* Stop here if the classes do not match */                             
        if (!(adapter->class & driver->class))                                  
                return 0;                                                       
                                                                                
        /* Set up a temporary client to help detect callback */                 
        temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);           
        if (!temp_client)                                                       
                return -ENOMEM;                                                 
        temp_client->adapter = adapter;                                         
                                                                                
        for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {                
                dev_dbg(&adapter->dev,                                          
                        "found normal entry for adapter %d, addr 0x%02x\n",     
                        adap_id, address_list[i]);                              
                temp_client->addr = address_list[i];                            
                err = i2c_detect_address(temp_client, driver);                  
                if (unlikely(err))                                              
                        break;                                                  
        }                                                                       
                                                                                
        kfree(temp_client);                                                     
        return err;                                                             
}

adapter에 해당하는 i2c 버스에서 detect 지원되는 디바이스들을 HW detect 후 발견되면 등록한다.

  • 코드 라인 8~10에서 드라이버에 지정된 주소 리스트가 없거나 드라이버에 (*detect) 후크 함수가 없는 경우 HW 디텍트를 수행할 필요가 없으므로 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 13~18에서 adapter 클래스가 I2C_CLASS_DEPRECATED로 지정된 경우 디버그 메시지를 출력하고 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 21~22에서 adapter와 드라이버가 가리키는 클래스가 동일하지 않으면 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 25~28에서 i2c_client를 하나 임시로 만들고 adapter를 지정한다.
  • 코드 라인 30~38에서 주소 리스트 수 만큼 순회하며 주소로 HW 디텍트를 시도하여 검출되는 디바이스를 등록한다.
  • 코드 라인 40에서 임시로 할당한 i2c_client를 할당해제한다.

 

i2c_detect_address()

drivers/i2c/i2c-core-base.c

static int i2c_detect_address(struct i2c_client *temp_client,                   
                              struct i2c_driver *driver)                        
{                                                                               
        struct i2c_board_info info;                                             
        struct i2c_adapter *adapter = temp_client->adapter;                     
        int addr = temp_client->addr;                                           
        int err;                                                                
                                                                                
        /* Make sure the address is valid */                                    
        err = i2c_check_7bit_addr_validity_strict(addr);                        
        if (err) {                                                              
                dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",       
                         addr);                                                 
                return err;                                                     
        }                                                                       
                                                                                
        /* Skip if already in use (7 bit, no need to encode flags) */           
        if (i2c_check_addr_busy(adapter, addr))                                 
                return 0;                                                       
                                                                                
        /* Make sure there is something at this address */                      
        if (!i2c_default_probe(adapter, addr))                                  
                return 0;                                                       
                                                                                
        /* Finally call the custom detection function */                        
        memset(&info, 0, sizeof(struct i2c_board_info));                        
        info.addr = addr;                                                       
        err = driver->detect(temp_client, &info);                               
        if (err) {                                                              
                /* -ENODEV is returned if the detection fails. We catch it      
                   here as this isn't an error. */                              
                return err == -ENODEV ? 0 : err;                                
        }                                                                       
                                                                                
        /* Consistency check */                                                 
        if (info.type[0] == '\0') {                                             
                dev_err(&adapter->dev,                                          
                        "%s detection function provided no name for 0x%x\n",    
                        driver->driver.name, addr);                             
        } else {                                                                
                struct i2c_client *client;                                      
                                                                                
                /* Detection succeeded, instantiate the device */               
                if (adapter->class & I2C_CLASS_DEPRECATED)                      
                        dev_warn(&adapter->dev,                                 
                                "This adapter will soon drop class based instantiation of devices. "
                                "Please make sure client 0x%02x gets instantiated by other means. "
                                "Check 'Documentation/i2c/instantiating-devices' for details.\n",
                                info.addr);                                     
                                                                                
                dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",               
                        info.type, info.addr);                                  
                client = i2c_new_device(adapter, &info);                        
                if (client)                                                     
                        list_add_tail(&client->detected, &driver->clients);     
                else                                                            
                        dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
                                info.type, info.addr);                          
        }                                                                       
        return 0;                                                               
}

주소로 HW 디텍트를 시도하여 검출되는 디바이스를 등록한다.

  • 코드 라인 10~15에서 요청한 타겟 주소가 0x08~0x77 범위를 벗어나는 경우 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 18~19에서 요청한 타겟 주소가 mux 트리에 연결되어 있을 수 있으므로 요청한 타겟 주소까지 busy한 장비가 있는지 확인한다.
  • 코드 라인 22~23에서 smbus를 사용하는 특정 장치의 HW detect를 위해 smbus에서 1바이트를 전송 또는 수신하는 것으로 probe 한다.
  • 코드 라인 26~33에서 최종 custom detection을 위해 i2c_board_info 구조체에 타겟 주소를 담고  드라이버의 (*detect) 후크 함수를 호출한다.
  • 코드 라인 36~39에서 디바이스 이름을 알아오지 못한 경우 에러 메시지를 출력한다.
  • 코드 라인 40~49에서 adapter 클래스가 I2C_CLASS_DEPRECATED 인 경우 경고 메시지를 출력한다.
  • 코드 라인 53~58에서 검출된 디바이스를 등록한다. 그리고 드라이버의 clients 리스트에 detect된 디바이스를 추가한다.

 

i2c_check_7bit_addr_validity_strict()

drivers/i2c/i2c-core-base.c

/* And this is a strict address validity check, used when probing. If a
 * device uses a reserved address, then it shouldn't be probed. 7-bit
 * addressing is assumed, 10-bit address devices are rare and should be
 * explicitly enumerated. */
int i2c_check_7bit_addr_validity_strict(unsigned short addr)
{
        /*
         * Reserved addresses per I2C specification:
         *  0x00       General call address / START byte
         *  0x01       CBUS address
         *  0x02       Reserved for different bus format
         *  0x03       Reserved for future purposes
         *  0x04-0x07  Hs-mode master code
         *  0x78-0x7b  10-bit slave addressing
         *  0x7c-0x7f  Reserved for future purposes
         */
        if (addr < 0x08 || addr > 0x77)
                return -EINVAL;
        return 0;
}

요청한 타겟 주소가 0x08~0x77 범위이내인 경우 성공(0)을 반환한다.

 

i2c_default_probe()

drivers/i2c/i2c-core-base.c

/* ----------------------------------------------------
 * the i2c address scanning function
 * Will not work for 10-bit addresses!
 * ----------------------------------------------------
 */

/*
 * Legacy default probe function, mostly relevant for SMBus. The default
 * probe method is a quick write, but it is known to corrupt the 24RF08
 * EEPROMs due to a state machine bug, and could also irreversibly
 * write-protect some EEPROMs, so for address ranges 0x30-0x37 and 0x50-0x5f,
 * we use a short byte read instead. Also, some bus drivers don't implement
 * quick write, so we fallback to a byte read in that case too.
 * On x86, there is another special case for FSC hardware monitoring chips,
 * which want regular byte reads (address 0x73.) Fortunately, these are the
 * only known chips using this I2C address on PC hardware.
 * Returns 1 if probe succeeded, 0 if not.
 */
static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
{
        int err;
        union i2c_smbus_data dummy;

#ifdef CONFIG_X86
        if (addr == 0x73 && (adap->class & I2C_CLASS_HWMON)
         && i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE_DATA))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                     I2C_SMBUS_BYTE_DATA, &dummy);
        else
#endif
        if (!((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50)
         && i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_WRITE, 0,
                                     I2C_SMBUS_QUICK, NULL);
        else if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                     I2C_SMBUS_BYTE, &dummy);
        else {
                dev_warn(&adap->dev, "No suitable probing method supported for address 0x%02X\n",
                         addr);
                err = -EOPNOTSUPP;
        }

        return err >= 0;
}

레거시 default probe 펑션으로 7비트 주소를 사용하며 대부분 SMBus에서 사용된다. 제한된 범위의 주소를 사용하는 디바이스들을 대상으로 SMBus에 읽거나 쓰는 것으로 HW detect를 하기 위해 probe 준비를 한다. 성공의 경우 1을 반환한다.

  • 코드 라인 6~12에서 x86 pc만 해당하며 0x73 주소를 사용하는 센서 디바이스(lm73)에서 1바이트 데이터를 읽어온다.
  • 코드 라인 13~16에서 0x30~0x37 또는 0x50~0x5f 주소에 해당하는 디바이스들을 대상으로 smbus quick 송신한다. (1 바이트)
  • 코드 라인 17~19에서 그 외의 디바이스를 대상으로 smbus로 1바이트를 읽어온다.

 

I2C 주소 busy 체크

i2c_check_addr_busy()

drivers/i2c/i2c-core-base.c

static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
{
        struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
        int result = 0;

        if (parent)
                result = i2c_check_mux_parents(parent, addr);

        if (!result)
                result = device_for_each_child(&adapter->dev, &addr,
                                                i2c_check_mux_children);

        return result;
}

주소 busy 체크를 수행한다. i2c mux가 사용될 수 있으므로 윗 방향과 아랫 방향을 계속 조사한다. 성공의 경우 0을 반환한다.

 

i2c_check_mux_parents()

drivers/i2c/i2c-core-base.c

/* walk up mux tree */
static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr)
{
        struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
        int result;

        result = device_for_each_child(&adapter->dev, &addr,
                                        __i2c_check_addr_busy);

        if (!result && parent)
                result = i2c_check_mux_parents(parent, addr);

        return result;
}

mux 트리의 윗 방향으로 이동하면서 주소 busy 체크를 수행한다. 성공의 경우 0을 반환한다.

  • 코드 라인 4에서 i2c-mux 인 경우 부모 adapter를 알아온다. 없는 경우 null이다.
  • 코드 라인 7~8에서 현재 adapter에 연결된 i2c 디바이스들을 대상으로 주소 busy 체크를 수행한다. 성공은 0을 반환한다.
  • 코드 라인 10~11에서 결과가 성공이면서 부모 adapter가 있는 경우 부모 adapter로 이 함수를 재귀 호출한다.

 

i2c_check_mux_children()

drivers/i2c/i2c-core-base.c

/* recurse down mux tree */
static int i2c_check_mux_children(struct device *dev, void *addrp)
{
        int result;

        if (dev->type == &i2c_adapter_type)
                result = device_for_each_child(dev, addrp,
                                                i2c_check_mux_children);
        else
                result = __i2c_check_addr_busy(dev, addrp);

        return result;
}

디바이스가 adapter인 경우 자식 디바이스들을 대상으로 busy 체크하기 위해 재귀 호출읋하고, 클라이언트 디바이스인 경우 busy 체크를 수행한다. 성공의 경우 0을 반환한다.

 

__i2c_check_addr_busy()

drivers/i2c/i2c-core-base.c

static int __i2c_check_addr_busy(struct device *dev, void *addrp)
{
        struct i2c_client       *client = i2c_verify_client(dev);
        int                     addr = *(int *)addrp;

        if (client && i2c_encode_flags_to_addr(client) == addr)
                return -EBUSY;
        return 0;
}

클라이언트 디바이스의 주소가이미 엔코딩되어 있는 경우 -EBUSY를 반환한다. 그 외 성공(0)을 반환한다.

 

i2c_encode_flags_to_addr()

drivers/i2c/i2c-core-base.c

/* Return a unique address which takes the flags of the client into account */
static unsigned short i2c_encode_flags_to_addr(struct i2c_client *client)
{
        unsigned short addr = client->addr;

        /* For some client flags, add an arbitrary offset to avoid collisions */
        if (client->flags & I2C_CLIENT_TEN)
                addr |= I2C_ADDR_OFFSET_TEN_BIT;

        if (client->flags & I2C_CLIENT_SLAVE)
                addr |= I2C_ADDR_OFFSET_SLAVE;

        return addr;
}

클라이언트 디바이스에 사용하는 주소를 엔코딩한다.

  • 클라이언트 디바이스가 10 비트 주소를 사용하는 경우 주소에 0xa000을 or하고, slave 디바이스의 경우 0x20을 or 한다.

 

i2c 클라이언트 등록

i2c_client 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_client - represent an I2C slave device                            
 * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;     
 *      I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking            
 * @addr: Address used on the I2C bus connected to the parent adapter.          
 * @name: Indicates the type of the device, usually a chip name that's          
 *      generic enough to hide second-sourcing and compatible revisions.        
 * @adapter: manages the bus segment hosting this I2C device                    
 * @dev: Driver model device node for the slave.                                
 * @irq: indicates the IRQ generated by this device (if any)                    
 * @detected: member of an i2c_driver.clients list or i2c-core's                
 *      userspace_devices list                                                  
 * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter   
 *      calls it to pass on slave events to the slave driver.                   
 *                                                                              
 * An i2c_client identifies a single device (i.e. chip) connected to an         
 * i2c bus. The behaviour exposed to Linux is defined by the driver             
 * managing the device.                                                         
 */
struct i2c_client {                                                             
        unsigned short flags;           /* div., see below              */      
        unsigned short addr;            /* chip address - NOTE: 7bit    */      
                                        /* addresses are stored in the  */      
                                        /* _LOWER_ 7 bits               */      
        char name[I2C_NAME_SIZE];                                               
        struct i2c_adapter *adapter;    /* the adapter we sit on        */      
        struct device dev;              /* the device structure         */      
        int irq;                        /* irq issued by device         */      
        struct list_head detected;                                              
#if IS_ENABLED(CONFIG_I2C_SLAVE)                                                
        i2c_slave_cb_t slave_cb;        /* callback for slave mode      */      
#endif                                                                          
};
  • flags
    • I2C_CLIENT_PEC
      • smbus에서 패킷 에러 체킹
    • I2C_CLIENT_TEN
      • 10bit 주소 사용
    • I2C_CLIENT_SLAVE
      • slave 디바이스(EEPROM 등)
    • I2C_CLIENT_HOST_NOTIFY
      • smbus의 i2c host notify (인터럽트)
    • I2C_CLIENT_WAKE
      • 절전 기능을 위해 깨워야 하는경우 사용
    • I2C_CLIENT_SCCB
      • 옴니비전의 SCCB 프로토콜에서 사용
  • addr
    • 디바이스 주소
  • name
    • 디바이스 명
  • *adapter
    • adapter를 가리킨다.
  • dev
    • 디바이스
  • irq
    • irq가 사용되는 경우 irq 번호
  • detected
    • i2c_driver의 clients 리스트에 사용되는 노드
  • slave_cb
    • 슬레이브 callback

 

다음 그림은 i2c 클라이언트 디바이스 및 드라이버가 등록되어 매치되는 경우 드라이버의 probe 함수가 호출되는 과정을 보여준다.

 

i2c 디바이스 등록

i2c_new_device()

drivers/i2c/i2c-core-base.c

/**
 * i2c_new_device - instantiate an i2c device
 * @adap: the adapter managing the device
 * @info: describes one I2C device; bus_num is ignored
 * Context: can sleep
 *
 * Create an i2c device. Binding is handled through driver model
 * probe()/remove() methods.  A driver may be bound to this device when we
 * return from this function, or any later moment (e.g. maybe hotplugging will
 * load the driver module).  This call is not appropriate for use by mainboard
 * initialization logic, which usually runs during an arch_initcall() long
 * before any i2c_adapter could exist.
 *
 * This returns the new i2c client, which may be saved for later use with
 * i2c_unregister_device(); or NULL to indicate an error.
 */
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client       *client;
        int                     status;

        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (!client)
                return NULL;

        client->adapter = adap;

        client->dev.platform_data = info->platform_data;

        if (info->archdata)
                client->dev.archdata = *info->archdata;

        client->flags = info->flags;
        client->addr = info->addr;

        client->irq = info->irq;
        if (!client->irq)
                client->irq = i2c_dev_irq_from_resources(info->resources,
                                                         info->num_resources);

        strlcpy(client->name, info->type, sizeof(client->name));

        status = i2c_check_addr_validity(client->addr, client->flags);
        if (status) {
                dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                        client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
                goto out_err_silent;
        }

인자로 전달받은 adapter와 i2c 보드 정보로 i2c 디바이스를 생성한 후 등록한다. 매치되는 드라이버가 있는 경우 드라이버의 probe 후크를 호출한다.

  • 코드 라인 7~21에서 i2c_client 구조체를 할당받은 후 인자로 전달받은 adapter 포인터와 i2c 보드 정보로 설정한다.
  • 코드 라인 22~24에서 irq 정보가 없으면 리소스에서 찾아온다.
  • 코드 라인 26에서 보드 정보에 있는 이름을 i2c_client 이름으로 설정한다.
  • 코드 라인 28~33에서 디바이스 주소가 유효한지 체크한다.
    • 10비트 주소를 사용하는 경우 주소는 0x3ff를 초과하면 안된다.
    • 7비트 주소를 사용하는 경우 주소는 0x01~0x7f 범위를 벗어나면 안된다.

 

        /* Check for address business */
        status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));
        if (status)
                goto out_err;
                
        client->dev.parent = &client->adapter->dev;
        client->dev.bus = &i2c_bus_type;
        client->dev.type = &i2c_client_type;
        client->dev.of_node = info->of_node;
        client->dev.fwnode = info->fwnode;

        i2c_dev_set_name(adap, client);

        if (info->properties) {
                status = device_add_properties(&client->dev, info->properties);
                if (status) {
                        dev_err(&adap->dev,
                                "Failed to add properties to client %s: %d\n",
                                client->name, status);
                        goto out_err;
                }
        }

        status = device_register(&client->dev);
        if (status)
                goto out_free_props;
                
        dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
                client->name, dev_name(&client->dev));
                
        return client;

out_free_props:
        if (info->properties)
                device_remove_properties(&client->dev);
out_err:        
        dev_err(&adap->dev,
                "Failed to register i2c client %s at 0x%02x (%d)\n",
                client->name, client->addr, status);
out_err_silent:
        kfree(client);
        return NULL;
}
EXPORT_SYMBOL_GPL(i2c_new_device);
  • 코드 라인 2~4에서 해당 주소의 디바이스까지 접근하는데 busy하지 않은지 체크한다.
    • 해당 주소를 사용하는 디바이스까지 mux 트리를 검색한다.
  • 코드 라인 6~12에서 i2c_client 멤버 정보를 설정한다.
  • 코드 라인 14~22에서 디바이스 속성이 주어진 경우 디바이스에 속성들을 추가한다.
  • 코드 라인 24~26에서 디바이스를 등록한다.

 

i2c_driver 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_driver - represent an I2C device driver                           
 * @class: What kind of i2c device we instantiate (for detect)                  
 * @attach_adapter: Callback for bus addition (deprecated)                      
 * @probe: Callback for device binding - soon to be deprecated                  
 * @probe_new: New callback for device binding                                  
 * @remove: Callback for device unbinding                                       
 * @shutdown: Callback for device shutdown                                      
 * @alert: Alert callback, for example for the SMBus alert protocol             
 * @command: Callback for bus-wide signaling (optional)                         
 * @driver: Device driver model driver                                          
 * @id_table: List of I2C devices supported by this driver                      
 * @detect: Callback for device detection                                       
 * @address_list: The I2C addresses to probe (for detect)                       
 * @clients: List of detected clients we created (for i2c-core use only)        
 * @disable_i2c_core_irq_mapping: Tell the i2c-core to not do irq-mapping       
 *                                                                              
 * The driver.owner field should be set to the module owner of this driver.     
 * The driver.name field should be set to the name of this driver.              
 *                                                                              
 * For automatic device detection, both @detect and @address_list must          
 * be defined. @class should also be set, otherwise only devices forced         
 * with module parameters will be created. The detect function must             
 * fill at least the name field of the i2c_board_info structure it is           
 * handed upon successful detection, and possibly also the flags field.         
 *                                                                              
 * If @detect is missing, the driver will still work fine for enumerated        
 * devices. Detected devices simply won't be supported. This is expected        
 * for the many I2C/SMBus devices which can't be detected reliably, and         
 * the ones which can always be enumerated in practice.                         
 *                                                                              
 * The i2c_client structure which is handed to the @detect callback is          
 * not a real i2c_client. It is initialized just enough so that you can         
 * call i2c_smbus_read_byte_data and friends on it. Don't do anything           
 * else with it. In particular, calling dev_dbg and friends on it is            
 * not allowed.                                                                 
 */
struct i2c_driver {                                                             
        unsigned int class;                                                     
                                                                                
        /* Notifies the driver that a new bus has appeared. You should avoid    
         * using this, it will be removed in a near future.                     
         */                                                                     
        int (*attach_adapter)(struct i2c_adapter *) __deprecated;               
                                                                                
        /* Standard driver model interfaces */                                  
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);        
        int (*remove)(struct i2c_client *);                                     
                                                                                
        /* New driver model interface to aid the seamless removal of the        
         * current probe()'s, more commonly unused than used second parameter.  
         */                                                                     
        int (*probe_new)(struct i2c_client *);                                  
                                                                                
        /* driver model interfaces that don't relate to enumeration  */         
        void (*shutdown)(struct i2c_client *);                                  
                                                                                
        /* Alert callback, for example for the SMBus alert protocol.            
         * The format and meaning of the data value depends on the protocol.    
         * For the SMBus alert protocol, there is a single bit of data passed   
         * as the alert response's low bit ("event flag").                      
         * For the SMBus Host Notify protocol, the data corresponds to the      
         * 16-bit payload data reported by the slave device acting as master.   
         */                                                                     
        void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,    
                      unsigned int data);                                       
                                                                                
        /* a ioctl like command that can be used to perform specific functions  
         * with the device.                                                     
         */                                                                     
        int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 
                                                                                
        struct device_driver driver;                                            
        const struct i2c_device_id *id_table;                                   
                                                                                
        /* Device detection callback for automatic device creation */           
        int (*detect)(struct i2c_client *, struct i2c_board_info *);            
        const unsigned short *address_list;                                     
        struct list_head clients;                                               
                                                                                
        bool disable_i2c_core_irq_mapping;                                      
};
  • class
    • detect를 위한 i2c 디바이스 타입 번호이다.
    • pure smbus에서 자동 디텍션이 필요한 경우 다음과 같은 타입을 선택하여 사용한다.
      • I2C_CLASS_HWMON
        • lm 센서등에서 사용한다.
      • I2C_CLASS_DDC
        • 그래픽 어댑터에 위치한 DDC 버스에서 사용한다.
      • I2C_CLASS_SPD
        • 메모리 모듈에서 사용한다.
      • I2C_CLASS_DEPRECATED
        • detect를 위해 클래스 구분을 하지 않을 계획으로 deprecated되었다.
  • (*attach_adapter)
    • legacy device detection 방식으로 추후 deprecated
  • (*probe)
    • 표준 드라이버 probe 인터페이스
  • (*remove)
    • 표준 드라이버 remove 인터페이스
  • (*probe_new)
    • 새로운 드라이버 probe 인터페이스이다.
  • (*shutdown)
    • shutdown 전에 사용될 후크 함수가 지정된다.
  • (*alert)
    • smbus에서 alert를 위한 호스트 콜백 후크이다.
  • (*command)
    • ioctl과 유사한 명령이다.
  • driver
    • 드라이버
  • *id_table
    • 디바이스와 매치될 때 사용할 id 테이블
  • (*detect)
    • smbus에서 디바이스를 등록하기 위해 디바이스 HW 디텍트 시도를 통해 디텍션할 후크 함수를 지정한다.
  • *address_list
    • smbus에서 디텍트할 디바이스 주소
  • clients
    • i2c 클라이언트가 등록되는 리스트이다.
  • disable_i2c_core_irq_mapping
    • irq 매핑을 하지 않을 때 사용한다.

 

Extra 클라이언트 데이터의 취급

i2c_client 구조체에 임베드되어 있는 device 구조체의 driver_data 필드를 사용하여 extra 클라이언트 데이터를 사용할 수 있도록 다음 API들이 사용된다.

  • 저장하기
    • void i2c_set_clientdata(struct i2c_client *client, void *data);
  • 가져오기
    • void *i2c_get_clientdata(const struct i2c_client *client);

 

클라이언트에서 i2c 전송

간단히 1 바이트 데이터 송신 및 수신하는 방법은 다음과 같다.

int foo_read_value(struct i2c_client *client, u8 reg)                           
{                                                                               
        if (reg < 0x10) /* byte-sized register */                               
                return i2c_smbus_read_byte_data(client, reg);                   
        else            /* word-sized register */                               
                return i2c_smbus_read_word_data(client, reg);                   
}                                                                               
                                                                                
int foo_write_value(struct i2c_client *client, u8 reg, u16 value)               
{                                                                               
        if (reg == 0x10)        /* Impossible to write - driver error! */       
                return -EINVAL;                                                 
        else if (reg < 0x10)    /* byte-sized register */                       
                return i2c_smbus_write_byte_data(client, reg, value);           
        else                    /* word-sized register */                       
                return i2c_smbus_write_word_data(client, reg, value);           
}

 

i2c 드라이버 등록

i2c_register_driver()

drivers/i2c/i2c-core-base.c

/*                                                                              
 * An i2c_driver is used with one or more i2c_client (device) nodes to access   
 * i2c slave chips, on a bus instance associated with some i2c_adapter.         
 */                                                                            
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)        
{
        int res;

        /* Can't register until after driver model init */                      
        if (WARN_ON(!is_registered))                                            
                return -EAGAIN;                                                 
                                                                                
        /* add the driver to the list of i2c drivers in the driver core */      
        driver->driver.owner = owner;                                           
        driver->driver.bus = &i2c_bus_type;                                     
        INIT_LIST_HEAD(&driver->clients);                                       
                                                                                
        /* When registration returns, the driver core                           
         * will have called probe() for all matching-but-unbound devices.       
         */                                                                     
        res = driver_register(&driver->driver);                                 
        if (res)                                                                
                return res;                                                     
                                                                                
        pr_debug("driver [%s] registered\n", driver->driver.name);              
                                                                                
        /* Walk the adapters that are already present */                        
        i2c_for_each_dev(driver, __process_new_driver);                         
                                                                                
        return 0;                                                               
}                                                                               
EXPORT_SYMBOL(i2c_register_driver);

i2c 드라이버를 등록한다. 매치되는 디바이스가 있는 경우 드라이버의 probe 후크를 호출한다.

  • 코드 라인 14~16에서 드라이버의 모듈 정보와 i2c 버스 타입을 지정한다.그리고 드라이버의 클라이언트 리스트를 초기화한다.
  • 코드 라인 21~23에서 드라이버를 등록한다.
  • 코드 라인 28에서 i2c 버스에 등록된 모든 디바이스들에 대해 순회하며 만일 디바이스가 어답터인 경우 워커스레드에서 아답터에 등록된 주소들을 대상으로 디바이스를 디텍트 시도하여 등록한다.

 

I2C Functionality

모든 i2c adapter가 i2c 전송 또는 smbus를 지원하는 것은 아니다. 따라서 adapter가 지원하는 기능을 비트별로 노출하여 i2c 클라이언트가 이를 확인하여 동작할 수 있게 지원한다.

i2c_adapter 구조체의 functionality 필드의 각 비트는 다음과 같다.

  • I2C_FUNC_I2C
    • i2c 레벨 전송 방식을 사용한다.
  • I2C_FUNC_10BIT_ADDR
    • 10비트 주소를 지원한다.
  • I2C_FUNC_PROTOCOL_MANGLING
    • i2c 트랜잭션을 처리 시 다음 플래그들을 사용할 수 있다.
      • I2C_M_IGNORE_NAK
      • I2C_M_REV_DIR_ADDR
      • I2C_M_NO_RD_ACK
  • I2C_FUNC_SMBUS_PEC
    • smbus에 PEC(Packet Error Checking)를 사용한다.
  • I2C_FUNC_NOSTART
    • 반복(repeted) start를 skip할 수 있다.
    • 이 기능은 I2C_FUNC_PROTOCOL_MANGLING에서 처리되었던 기능을 kernel v3.5부터 분리하여 플래그로 제공한다.
  • I2C_FUNC_SLAVE
    • slave 디바이스가 clock streaching을 할 수 있도록 지원한다.
    • slave 디바이스가 clock을 low 상태로 유지시킨 채 정지하면 마스터가 버스를 사용하지 못한다.
      • 디바이스를 리셋하도록 하는 버스 리커버리가 필요하다.
  • I2C_FUNC_SMBUS_BLOCK_PROC_CALL
    • smbus 프로토콜 2.0을 사용하여 블럭(최대 32 바이트) 데이터를 송신 후 다시 블럭 데이터 수신 지원
  • I2C_FUNC_SMBUS_QUICK
    • smbus 프로토콜을 사용하여 주소만 지정하고 이에 응답하는 1 비트 ACK를 읽는 quick read 지원
    • R/W 필드 1비트를 데이터로 사용하여 quick write하는 특이한 경우도 있다.
  • I2C_FUNC_SMBUS_READ_BYTE
    • smbus 프로토콜을 사용하여 1 바이트 읽기 지원
    • i2c_smbus_read_byte() 함수
  • I2C_FUNC_SMBUS_WRITE_BYTE
    • smbus 프로토콜을 사용하여 1 바이트 쓰기 지원
    • i2c_smbus_write_byte() 함수
  • I2C_FUNC_SMBUS_READ_BYTE_DATA
    • smbus 프로토콜을 사용하여 smbus 메시지 구조체를 사용하여 1 바이트 command 쓰기 + 1 바이트 읽기
  • I2C_FUNC_SMBUS_WRITE_BYTE_DATA
    • smbus 프로토콜을 사용하여 smbus 메시지 구조체를 사용하여 1 바이트 command 쓰기 + 1 바이트 쓰기
  • I2C_FUNC_SMBUS_READ_WORD_DATA
    • smbus 프로토콜을 사용하여 워드(2 바이트) 데이터 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_WORD_DATA
    • smbus 프로토콜을 사용하여 워드(2 바이트) 데이터 쓰기 지원
  • I2C_FUNC_SMBUS_PROC_CALL
    • smbus 프로토콜 2.0을 사용하여 워드(2 바이트)를 송신 후 다시 워드 수신 지원
  • I2C_FUNC_SMBUS_READ_BLOCK_DATA
    • smbus 프로토콜을 사용하여 블럭(최대 32바이트) 데이터 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
    • smbus 프로토콜을 사용하여 불럭(최대 32바이트) 데이터 쓰기 지원
  • I2C_FUNC_SMBUS_READ_I2C_BLOCK
    • smbus 프로토콜을 사용하여 i2c 블럭 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
    • smbus 프로토콜을 사용하여 i2c 블럭 쓰기 지원
  • I2C_FUNC_SMBUS_HOST_NOTIFY
    • smbus로부터 host notify 기능 사용 지원

 

복합 플래그
  • I2C_FUNC_SMBUS_BYTE
    • 1 바이트 읽기 및 쓰기
  • I2C_FUNC_SMBUS_BYTE_DATA
    • 바이트 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_WORD_DATA
    • 워드 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_BLOCK_DATA
    • 블럭 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_I2C_BLOCK
    • i2c 블럭 읽기 및 쓰기
  • I2C_FUNC_SMBUS_EMUL
    • i2c 전송 방식을 사용하지만 smbus 명령도 에뮬레이션하여 처리한다.

 

다음 그림은 functionality에 사용하는 플래그들과 복합  플래그들을 보여준다.

  • rpi 및 ns2의 경우 I2C_FUNC_I2C 플래그와 I2C_FUNC_SMBUS_EMUL 복합 플래그를 사용한다.

 

Adapter 구현 (i2c vs smbus)

smbus만 지원하는 adapter의 경우 일반적으로 다음과 같은 기능을 지원한다. 예) i2c-piix4 드라이버

  • I2C_FUNC_SMBUS_QUICK
  • I2C_FUNC_SMBUS_BYTE
  • I2C_FUNC_SMBUS_BYTE_DATA
  • I2C_FUNC_SMBUS_WORD_DATA
  • I2C_FUNC_SMBUS_BLOCK_DATA

 

full-I2C 기능을 갖는 adapter의 경우 일반적으로 다음과 같은 기능을 지원한다.

  • I2C_FUNC_I2C
  • I2C_FUNC_SMBUS_EMUL
    • 모든  smbus transaction들을 포함한다.

 

Client Checking

클라이언트가 adapter에 attach를 시도할 때 adapter에서 이 디바이스가 사용하는 기능이 있는지 먼저 체크해야 한다.

static int lm75_detect(...)
{
        (...)
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
                                     I2C_FUNC_SMBUS_WORD_DATA))
                goto exit;
        (...)
}

위의 코드 체크에서 통과하는 경우 이제 i2c_smbus_read_byte_data() 함수 및 i2c_smbus_write_byte_data() 함수를 사용할 수 있다는 것을 의미한다.

 

I2C Character Device (i2c-dev)

유저 스페이스에서 i2c adapter를 control할 수 있도록 다음 파일명으로 i2c 캐릭터 디바이스가 노출된다.

  • /dev/i2c-N
    • 예) /dev/i2c-0
  • 또는 /dev/i2c/N
    • 예) /dev/i2c/0

 

i2c 버스에 i2c adapter 디바이스가 등록/해제될 때 다음 함수를 통해 i2c-dev 클래스 디바이스가 생성/해제되며 이 때 i2c 캐릭터 디바이스도 생성/삭제된다.

  • 등록
    • i2cdev_attach_adapter()
  • 등록 해제
    • i2cdev_detach_adapter()

 

참고로 i2c adapter에 대한 클래스 디바이스는 다음과 같이 adater 디바이스 바로 뒤에 생성된다.

  • /sys/devices/…./66080000.i2c/i2c-0/i2c-dev/i2c-0

 

ioctl 지원

생성된 캐릭터 디바이스 파일에서 ioctl() 함수를 통해 다음과 같은 기능이 지원된다.

  • I2C_RETRIES
    • 재시도 수
    • 파라메터: long
  • I2C_TIMEOUT
    • 타임아웃(ms)
    • 10ms 단위로 설정해야 한다.
    • 파라메터: long
  • I2C_SLAVE
    • slave 디바이스 주소를 사용
    • 파라메터: long
  • I2C_SLAVE_FORCE
    • 드라이버에서 사용된다 하더라도 slave 디바이스 주소를 사용
    • 파라메터: long
  • I2C_TENBIT
    • 0인 경우 7비트 주소, 0이 아닌 경우 10비트 주소를 사용한다.
    • 파라메터: long
  • I2C_FUNCS
    • adapter가 지원하는 functionality 값을 알아온다.
    • 파라메터: unsigned long *
  • I2C_RDWR
    • combined 전송
    • 파라메터: i2c_rdwr_ioctl_data 구조체를 사용한다.
  • I2C_PEC
    • 0이 아닌 값으로 설정 시 smbus에서 PEC를 사용한다.
    • 파라메터: long
  • I2C_SMBUS
    • smbus 전송 방식을 사용
    • 파라메터: i2c_smbus_ioctl_data 구조체를 사용한다.

 

I2C 트랜잭션 데이터

i2c_rdwr_ioctl_data 구조체

include/uapi/linux/i2c-dev.h

/* This is the structure as used in the I2C_RDWR ioctl call */                  
struct i2c_rdwr_ioctl_data {                                                    
        struct i2c_msg __user *msgs;    /* pointers to i2c_msgs */              
        __u32 nmsgs;                    /* number of i2c_msgs */                
};
  • *msgs
    • i2c 트랜잭션 메시지
  • nmsgs
    • 메시지 수

 

i2c_msg 구조체

include/uapi/linux/i2c.h

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 * @addr: Slave address, either seven or ten bits.  When this is a ten
 *      bit address, I2C_M_TEN must be set in @flags and the adapter
 *      must support I2C_FUNC_10BIT_ADDR.
 * @flags: I2C_M_RD is handled by all adapters.  No other flags may be
 *      provided unless the adapter exported the relevant I2C_FUNC_*
 *      flags through i2c_check_functionality().
 * @len: Number of data bytes in @buf being read from or written to the
 *      I2C slave address.  For read transactions where I2C_M_RECV_LEN
 *      is set, the caller guarantees that this buffer can hold up to
 *      32 bytes in addition to the initial length byte sent by the
 *      slave (plus, if used, the SMBus PEC); and this value will be
 *      incremented by the number of block data bytes received.
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they
 * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
 */
struct i2c_msg {
        __u16 addr;     /* slave address                        */
        __u16 flags;
#define I2C_M_RD                0x0001  /* read data, from slave to master */
                                        /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
#define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
        __u16 len;              /* msg length                           */
        __u8 *buf;              /* pointer to msg data                  */
};
  • addr
    • 타겟 slave 디바이스 주소
  • flags
    • I2C_M_RD
      • slave 디바이스로 부터 읽는다.
    • I2C_M_TEN
      • 10비트 주소 사용
    • I2C_M_RECV_LEN
      • 블럭 데이터 수신할 바이트의 길이
    • 다음 항목들은 Modified 트랜잭션에 사용되는 플래그들로 아래에서 알아본다.
      • I2C_M_NO_RD_ACK
      • I2C_M_IGNORE_NAK
      • I2C_M_REV_DIR_ADDR
      • I2C_M_NOSTART
      • I2C_M_STOP
  • len
    • 메시지 길이
  • *buf
    • 전송 및 수신 시 사용할 메시지 데이터 포인터

 

다음 그림은 유저 스페이스에서 i2c 프로토콜로 전송하기 위해 데이터를 준비한 모습을 보여준다.

 

Modified 트랜잭션

i2c_msg 구조체에서 사용하는 플래그 중 표준 전송과 다른 방법을 사용하기 위해 사용하는 플래그들은 다음과 같다. 특정 디바이스와의 통신에 문제가 있어 이를 해결하기 위한 워크 어라운드 형태로 사용된다.

  • I2C_M_IGNORE_NAK
    • 일반적으로 클라이언트에서 NACK를 보내면 즉시 메시지가 중단된다. 이 플래그를 설정하면 NACK를 ACK로 취급되고 모든 메시지가 전송된다.
  • I2C_M_NO_RD_ACK
    • 메시지를 읽을 때 ACK/NACK를 무시한다.
    • 블럭 데이터를 읽을 때 사용하는 경우가 있다.
  • I2C_M_NOSTART
    • 특정 디바이스에만 드물게 사용한다.
      • 같은 버스에 다른 디바이스와 같이 사용되는 경우 문제가 되므로 단독으로 사용해야 한다.
    • Start condition 또는Repeted Start condition이 시작할 때 주소와 R/W 및 [ACK]가 연달아 처리되는데 이들을 모두 무시하고 처리할 때 사용한다.
    • 보통 combined 메시지를 처리할 때 두 번째 메시지에 이 플래그를 사용한다.
      • 예)  S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P —>
        • 플래그 적용 시 -> S Addr Rd [A] [Data] NA Data [A] P
  • I2C_M_REV_DIR_ADDR
    • 주소 다음에 사용되는 R/W 플래그를 reverse한다.
      • 예) S Addr Wr [A] Data [A] Data [A] … [A] Data [A] P —>
        • 플래그 적용 시 -> S Addr Rd [A] Data [A] Data [A] … [A] Data [A] P —>
  • I2C_M_STOP
    • 메시지의 완료 시 마다 항상 P를 강제로 처리한다.
      • 그 후 다시 메시지를 처리하기 위해 Start condition을 보내기 위해 사용한다.
      • combination 메시지 사이에 항상 P를 강제로 처리한다.

 

SMBUS 트랜젝션 데이터

i2c_smbus_ioctl_data 구조체

include/uapi/linux/i2c-dev.h

/* This is the structure as used in the I2C_SMBUS ioctl call */                 
struct i2c_smbus_ioctl_data {                                                   
        __u8 read_write;                                                        
        __u8 command;                                                           
        __u32 size;                                                             
        union i2c_smbus_data __user *data;                                      
};
  • read_write
    • 읽기 = I2C_SMBUS_READ(1)
    • 쓰기 = I2C_SMBUS_WRITE(0)
  • command
    • 명령 바이트
  • size
    • 사이즈
  • *data
    • smbus 트랜젝션 메시지

 

i2c_smbus_data 구조체
/*
 * Data for SMBus Messages
 */
#define I2C_SMBUS_BLOCK_MAX     32      /* As specified in SMBus standard */
union i2c_smbus_data {
        __u8 byte;
        __u16 word;
        __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
                               /* and one more for user-space compatibility */
};
  • byte
    • 바이트 데이터
  • word
    • 워드 데이터
  • block[34]
    • 블럭 데이터
    • block[0] -> 길이
    • block[1] ~ block[33]까지 최대 32바이트 데이터

 

다음 그림은 유저 스페이스에서 smbus 프로토콜로 전송하기 위해 데이터를 준비한 모습을 보여준다.

 

Functionality 체크

아래 코드와 같이 유저 스페이스에서 캐릭터 디바이스를 통해 접근하는 경우에도 i2c functionality 체크를 수행하고 사용해야 한다.

int file;
if (file = open("/dev/i2c-0", O_RDWR) < 0) {
        /* Some kind of error handling */
        exit(1);
}
if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
        /* Some kind of error handling */
        exit(1);
}
if (!(funcs & I2C_FUNC_SMBUS_QUICK)) {
        /* Oops, the needed functionality (SMBus write_quick function) is
           not available! */
        exit(1);
}

 

관련  I2C Core API들

아답터 관련

  • i2c_verify_adapter()
  • i2c_add_adapter()
  • i2c_add_numbered_adapter()
  • i2c_del_adapter()
  • i2c_parse_fw_timings()
  • i2c_get_adapter()
  • i2c_put_adapter()
  • of_find_i2c_adapter_by_node()
  • of_get_i2c_adapter_by_node()

클라이언트 관련

  • i2c_match_id()
  • i2c_verify_client()
  • i2c_new_device()
  • i2c_unregister_device()
  • i2c_new_dummy()
  • i2c_new_secondary_device()
  • i2c_for_each_dev()
  • i2c_register_driver()
  • i2c_del_driver()
  • i2c_use_client()
  • i2c_clients_command()
  • i2c_new_probed_device()
  • i2c_acpi_match_device()
  • i2c_acpi_register_devices()
  • i2c_acpi_find_bus_speed()
  • i2c_acpi_new_device()
  • of_find_i2c_device_by_node()
  • i2c_of_match_device()
  • i2c_slave_register()
  • i2c_slave_unregister()
  • i2c_detect_slave_mode()

이벤트 관련

  • i2c_handle_smbus_host_notify()
  • i2c_setup_smbus_alert()
  • i2c_handle_smbus_alert()

전송 관련

1) i2c 전송 프로토콜
  • i2c_transfer()
  • i2c_master_send()
  • i2c_master_recv()
  • i2c_probe_func_quick_read()
  • i2c_check_7bit_addr_validity_strict()
2) smbus 전송 프로토콜
  • i2c_smbus_read_byte()
  • i2c_smbus_write_byte()
  • i2c_smbus_read_byte_data()
  • i2c_smbus_write_byte_data()
  • i2c_smbus_read_word_data()
  • i2c_smbus_write_word_data()
  • i2c_smbus_read_block_data()
  • i2c_smbus_write_block_data()
  • i2c_smbus_read_i2c_block_data()
  • i2c_smbus_write_i2c_block_data()
  • i2c_smbus_xfer()
  • i2c_smbus_read_i2c_block_data_or_emulated()
3) 버스 리커버리 관련
  • i2c_generic_recovery()
  • i2c_generic_scl_recovery()
  • i2c_generic_gpio_recovery()
  • i2c_recover_bus()

 

참고

 

 

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

 

참고