1. Amaç    2

1. Device Tree-Kernel İlişkisi    2

2. Linux Altında SPI Kurulması    3

2.1 SPI Pinlerini Seçilmesi    3

2.1. Pinlerin SPI Pin Func Değerlerinin Oluşturulması    5

2.1 Device Tree SPI Eklenmesi    6

3. SPI Hattının Kurulumunun Testi    11

3. SPI Hattına IC Bağlanması    11

3.1 SPI Hattına Bağlanan IC Kontrol Edilmesi    13

4. Son    15

Bu yazının pdf halini buradan indirebilirsiniz. JP

1. Amaç

Bu belgenin amacı Linux SPI yapılandırmasını ve kullanımını anlatmaktır. SPI hattının linux ortamında nasıl kullanılacağını anlatırken device tree düzenlemesinden kullanıcı katmanına(user space) kadarki alanlarda çalışılacaktır. Bu sayede en dipten en üste kadarki değişiklikler gözlenmiş olacaktır. 

Belgenin isminden de anlaşılacağı gibi ilk olarak sistemde SPI hattının yapılandırılması yapılacak daha sonra SPI hattının hem kullanıcı katmanında hem de kernel katmanında nasıl kullanılabileceğine bakılacak.

SPI hattının yapılandırılması donanım bağlantılı olduğu için kullanılan donanım özelliklerini bilmek gerekir. Konu anlatımı için IMX8MM-Evk donanımı kullanılacaktır. Her ne kadar geliştirme IMX8MM-Evk üzerinde yapılsa da Linux doğası gereği buradaki uygulamalar başka donanımlara çok rahat uygulanabilir. 

1. Device Tree-Kernel İlişkisi

Nasıl ki en basit MCU bile bir çevre birimini(uart, i2c, can …) kullanmaya başlamadan önce o çevre birimin yapılandırılması gerekiyor ise linux ortamında da bu kural geçerlidir. Tek fark linux ortamında bu kurma işleminin farklı şekilde olması. MCU üzerinde çalışanlar bilir, çevre birimlerin kurulması için gerekli kod parçaları yazılan uygulamanın bir parçasıdır, dolayısı ile uygulamanın donanım bağımlılığı oluşmasına neden olur. (taşınabilir kodlama yada donanım sarmalları yapılsa da günün sonunda az yada çok bir noktada donanım bağımlılığı vardır.) 

Lİnux ortamında da çevre birimlerin yapılandırılması Kernel çalışma zamanında(run time) olur fakat Kernel içinde donanım yapılandırılması kodu bulunmaz. Bu fark ile MCU kodlarının donanım bağımlılığı yaşama sorunu Kernel için oluşmaz. Peki Kernel bunu nasıl yapıyor? Kernel donanım bağımsız olmak için donanım kurulumunu kendi yapmak yerine bu işleri başka bir birime yaptırır. Kernel sadece bu işlerin yapılmasını bu alt birimden ister. C dilindeki  pointer fonksiyonları bunun basit bir örneği olarak görebilirsiniz. Pointer fonksiyonun içinde ne olduğunu bilmeyiz ama çağırdığımızda görev yapacak birilerinin olduğunu biliriz. 

Linux ortamında Kernel katmanına donanımı kurmak için yardımcı olan birim Device Tree olarak adlandırılır. Device tree ile Kernel arasındaki ilişkiyi tam olarak anlamak için Bootloaderın ismini de anmak gerekir. Çünkü bootloader device tree ile  kernel arasında tıpkı bir çöpçatan gibi davranarak ikisini birbirine tanıtır. Bootloader donanımın kurulmasını sağlayan device tree adresini kernel bildirir ve sonrasında tüm iş kernel tarafından yönetilir. Aşağıdaki resimde akışın nasıl gerçekleştiğini görmekteyiz.

 Bu belgenin amacı SPI hattını kurmak olsa da device tree hakkında bilgi vermeden anlatım eksik kalacaktı. Çünkü yapılacak olan geliştirme device tree dosyası üzerinde olacak.. Kernel donanım bağımsız olduğu için device tree yapılan değişiklikler Kernel etkilemeyecek. Burada şunu da belirtmek gerekir; kernel device driver değişikliği/geliştirmesi device tree geliştirmesi gerektirebilir fakat bunu donanım bağımlılığı olarak göremeyiz. 

2. Linux Altında SPI Kurulması 

Projemizde bir SPI hattına ihtiyaç duyduğumuzda aslında bu hattın yapılandırılması yazılımdan önce donanım aşamasında başlar. Bu durumu aslında tüm çevre birimler için söylemek uygundur. Çünkü işlemcilerin pinleri farklı özellikler taşır ve bu özellikler bilinerek donanım ve yazılım tasarımı yapılır. İşlemcinin SPI çıkışını kullanabilmemiz için işlemcinin SPI çıkışı veren özelliğindeki pinleri kullanılmalıdır. İşlemcilerin datasheet bu bilgiler bulunmaktadır. Donanımsal olarak uygun bağlantıdan sonra yazılımcının görevi başlar.

Bu belgede örnek donanım olarak IMX8MM-Evk kullanılacağı için IMX8MM işlemcisinin verileri üzerinden gidilecektir.  Üretici firmaların işlemcilerin donanımlarını ayarlamak için yardımcı araçları da bulunmaktadır. IMX8MM için NXP sitesinde bu linkte Config Tools for IMX isminde bir yardımcı uygulama bulunmaktadır. Bu aracı sadece istenilen pini SPI olarak ayarlamaya yarayan register değerleri almak için kullanacağız. 

2.1 SPI Pinlerini Seçilmesi

IMX8MM-Evk üzerinden ilk olarak hangi pinlerin SPI olarak kullanılabileceğine bakalım. Bu işlem aslında dediğimiz gibi donanımcının atadığı SPI pinlerini kullanma amacıyla yapılacağından ilk olarak IMX8MM-Evk donanımına bakmak gerekir. Eğer SPI hattına bağlanmış bir pin olmasaydı bu sefer istediğimiz bir spi hattını seçip tüm çalışmalarımızı donanım testi olmadan sadece yazılım seviyesinde test edip tamamlayacaktık.

Şemaya baktığımızda j1003 nolu konnektörün 19, 21 ve 23 nolu pinlerine SPI2 hattının çıktığını görüyoruz. Tüm şema incelendiğinde SPI2 hattı üzerinde bir donanımın olmaması rahat rahat çalışılması için daha bir avantajlı oldu. Config Tool üzerinden bu pin için inceleme yaptığımızda yazılımsal olarak SPI2 hattının kurulmadığını görürüz. Bu durum aslında tam istediğimiz senaryoyu oluşturuyor. Şu an elimizde donanımsal olarak bağlı, yazılımsal olarak kurulmamış bir spi hattı olmuş oldu.

Config Tool aracından alınan yukarıdaki resimde, yazılımda SPI2 hattının kurulmadığını görüyoruz. Bu durumu teyit edecek bir diğer durum ise /dev dizini altında spi donanımının bulunmamasıdır. (sadece /dev dizini altında spi görmemek spi kurulmadığını kanıtlamaz)

2.1. Pinlerin SPI Pin Func Değerlerinin Oluşturulması

İşlemcinin ilgili pinlerinin SPI olarak kullanılması için pin func. register değerlerinin device tree girilmesi gerekir. Seçilen pinin her bir özelliğinin açılması/kapatılması durumunda bu pin func register değeri değişir. Config Tools aslında bizim sadece bu aşamada işimize yarıyor. Şimdi spi2 hattını bu araç üzerinden ayarlayalım.

SPI2 seçimi sonrasında yukarıdaki resmin sol tarafında SPI2 kutucukları yeşil renge döndü ve sağ tarafta da device tree kullanılacak değerler oluştu.
                MX8MM_IOMUXC_ECSPI2_MISO_ECSPI2_MISO       0x00000116
                MX8MM_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI       0x00000116
                MX8MM_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK       0x00001916
                MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0         0x00000116

Bu değerleri aldıktan sonra Config Tools olan ihtiyacımız bitiyor. Projemizdeki tüm pinler için bu atamaları yapıp Config Tools’ dan dtb dosyası da alabiliriz.

Akla şu soru geliyor olabilir, peki benim işlemcim için böyle bir araç yok ise ne yapacağım? Tabiki de çaresiz bir duruma düşmüş olmuyoruz. Oluşturulan bu değerlerin hepsi Kernel kaynak kodunun içinde var. Aslında bu soru akla geldiğinde devamında şunu düşünmemiz bizi çözüme götürür: Eğer device tree bir kaynak dosya(dts) ise ve onun da derlenmesi gerekiyor ise bu derleyicinin MX8MM_IOMUXC_ECSPI2_MISO_ECSPI2_MISO tanımlamasını bilmesi gerekir.  Bu düşünceden sonra tek yapmamız gereken tanımlamaların olduğu dosyayı bulmak olur. Kernel dizini içinde include/dt-bindings/pinctrl dizinine gidilir ise donanımımız için gerekli dosyayı görmüş  oluruz.

2.1 Device Tree SPI Eklenmesi

Bu başlığa kadar özetle spi hattının seçimini ve elimizdeki işlemcinin spi hatlarının register değerlerini elde etmeyi görmüş olduk. Artık Conf. Tool ile elde ettiğimiz spi pin register değerlerini device tree dosyasında işlememiz gerekiyor. 

Device Tree bir yapı biçiminde oluşturulmuştur ve düğümlerden oluşur. Yapıyı anlatan örnek resim aşağıda bulunmaktadır.
           
Dt(device tree) dosyası Kernel kaynak kodunun içinde arch/arm64/boot/dts/freescale dizininde bulunuyor. IMX8MM-Evk donanımı için fsl-imx8mm-evk.dts dosyası kullanılmakta. Dolayısı ile geliştirmeler bu dosya üzerinde yapılacaktır. 

SPI pin register değerlerinin device tree içinde iomuxc düğümünün içinde yazacağız. Bu düğüme aşağıdaki kodları ekleyelim. 

    pinctrl_ecspi2: ecspi2grp {
      fsl,pins = <
                MX8MM_IOMUXC_ECSPI2_MISO_ECSPI2_MISO       0x00000116
              MX8MM_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI       0x00000116
              MX8MM_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK       0x00001916
              MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0         0x00000116
        >;
    };

Bu nodu iomuxc içinde nereye yazılacağı konusunda kafa yormaya gerek yok. iomuxc içinde istenilen yere yazılabilir. Ben örnek olması için en üste yazdım.

İlgili pinleri SPI olarak ayarladıktan sonra SPI2 hattının yapılandırması işininin yapılması gerekir. Kullanıcı seviyesinden yönetilebilen ve /dev dizini altında bir sürücü oluşacak şekilde geliştirme yapacağız. Bunun için aşağıdaki kod parçasını iomuxc düğümünün bittikten sonraki yere ekleyelim 

&ecspi2 {    
#address-cells = <1>;    
#size-cells = <0>;    
pinctrl-names = “default”;    
pinctrl-0 = <&pinctrl_ecspi2>;    
cs-gpios = <&gpio5 13 0>;    
fsl,spi-num-chipselects = <1>;   
 status = “okay”;
        spidev@0 { 
              compatible = “spidev”; 
              spi-max-frequency = <12000000>; 
              reg = <0>;       
};
};




Yukarıdaki işlem sonrasında dt(device tree) düzenlemesi tamamlanmış olur. Bundan sonraki adım dts dosyasını derleyip dtb uzantılı derlenmiş dosyayı elde etmek. Derleme adımına geçmeden önce &ecspi2 alanının içinde girilen bazı parametreleri açıklayalım.

  • pinctrl-0 = <&pinctrl_ecspi2>;  Bu satır ile daha öncesinde tanımladığımız spi pin değerlerini içeren alanı tanıtıyoruz. Dolayısı ile sistem ecspi2 kurarken hangi pinleri kullanacağına bu değer ile öğrenmiş oluyor.
  • cs-gpios = <&gpio5 13 0>;  Bilindiği üzere SPI hattında data hatlarının yanında bir de çip seçme hattı bulunur. Bu hatta gelende CS(chip select) yada SS(slave select) denir. Bu satır ile çip seçme için hangi pinin kullanılacağı belirtilir. Bu pin aslında Pinctrl_ecspi2 kısmında tanımlanan MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0 değerdir. gpio5 13 0 değeri port 5 pin 13 ve çıkış olarak ayarlandığını bildirir. Pinctrl_ecspi2 içinde zaten tanımlama yapıldı tekrar ecspi2 içinde yapılmaya gerek var mıydı diye düşünebilirsiniz. SPI iletişimlerinde genelde birden fazla çip seçme pini olması bu durumu açıklayabilir.
  • fsl,spi-num-chipselects = <1>;    hat üzerinde kaç adet SPI çipi olduğunu belirtir.
  • status = “okay”;   ecspi2 aktif yapar. 
  • spidev@0   spidev isminde yeni düğüm oluşturur. Buradaki @0 değeri spi üzerindeki sıfırıncı cihazı adresler. Eğer sistemimizde iki adet spi çipi olsaydı onlar diğer için bu diğer @1 olacaktı. @0 dğeri ile birinci çip seçme pini eşleşir.
  • compatible = “spidev”;   hangi Kernel driver ile eşleşeceğini belirtir.
  • reg = <0>;  spidev SPI hattındaki kaçıncı cihaz olduğunu belirtir. Bu değer ile hangi çip seçme pinini atanacağı belirlenir.

DT düzenlemesinden sonra derlemek için kernel  kök dizinine gidilir. Dt iki şekilde derleyebiliriz. Birinci yöntem tüm kernel kodunun derlenmesi ile dt dosyasının da dolaylı olarak derlenmesi. Eğer kernel kodlarında değişiklik yok ise derleme işi hızlıca olacaktır. Eğer sadece dt dosyasının derlemek istiyor isek o zaman aşağıdaki kodu koşmak gerekir:      
       $ make ARCH=arm64 CROSS_COMPILE=aarch64-poky-linux- dtbs

Derleme sonrasında arch/arm64/boot/dts/freescale dizininde fsl-imx8mm-evk.dtb dosyası oluşur. Bu dosyayı cihazımının boot bölümüne kopyalamamız gerekir. 

Bendeki bordun uboot kısmında #> ums 0 mmc 1 komutu ile bordun içindeki emmc, PC tarafında bir USB bellek gib görünmesi sağlanır. Bu sayede bordun içine güncel dtb dosyası kolaylıkla kopyalanabilir.


Yukarıdaki resimlerde kopyalama işlemini ve sonucunu görüyorsunuz. cihaz içinde birden fazla dtb dosyası olması kafa karışıklığı yaratmasın. Hangi dtb dosyasının kullanılacağı uboot kısmında printenv komutu koşturularak görülebilir. 

Bu işlemlerden sonra cihazımız açılmaya hazırdır. Cihazı açmadan önce Kernel kodlarına hiç dokunmadığımızı ve tüm yapılanların dt dosyası içinde oldğunu hatırlatmakta fayda var. Kernel donanım bağımsızlığının bu şekilde görmüş oluyoruz.

3. SPI Hattının Kurulumunun Testi

SPI yapılandırmasından sonra artık cihazımızı açıp spi hattının oluşup oluşmadığını kontrol edebiliriz. Kontrol işlemi için /dev diznin altında spi cihazını görmemiz yeterlidir.

               

SPI çip seçme pininin doğru yapılandırılıp yapılandırılmadığını da kontrol etmek gerekir.  Çünkü /dev altında spi oluşması çip seçme pinindeki hatadan etkilenmeden görülür fakat uygulamada spi hattının kullanımında sorun yaşanır. Kontrol için aşağıdaki komutu koşarak spi çip seçme pinin listede olup olmadığına bakmamız gerekir.
                    $ cat /sys/kernel/debug/gpio

SPI çip seçme pini doğru gpio numarası-141-, doğru gpio yönlendirmesi-out- ve doğru lojik değerde-hi- olduğunu görüyoruz. (gpio hesaplama: (port-1)*32 + pinNum  (5-1)*32 + 13 = 141 )
SPI hattı artık kullanıcı katmanından kolaylıkla kullanılabilir. Artık bundan sonrası klasik linux geliştiricisi işleri, dosya okuma yazma.

3. SPI Hattına IC Bağlanması  

Yukarıda SPI hattına bir IC bağlı değilken bir geliştirme yaptık. Fakat genelde SPI hatlarında farklı görevlerde kullanılacak IC bulunur. I/O çoklayıcılar, bellekler, haberleşme cihazları(canbus) gibi cihazları örnek verilebilir. 

Örnek üzerinden giderek dt kodlamasını yapalım. Aynı SPI hattına iki adet mcp23s17 bağlayalım. (Eğer elinizde bu IC yok ise yine de bu örneği yapabilirsiniz, sorun yaşamazsınız.) dt kısmında ilk olarak pinctrl_ecspi2 kısmını düzenlenmeli. İki ayrı IC bağlandığı için bu IC ait çip seçme pinleri belirtilmeli. 

        pinctrl_ecspi2: ecspi2grp {          
fsl,pins = < 
          MX8MM_IOMUXC_ECSPI2_MISO_ECSPI2_MISO       0x00000116
        MX8MM_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI       0x00000116
            MX8MM_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK       0x00001916
            MX8MM_IOMUXC_ECSPI2_SS0_ECSPI2_SS0         0x00000116
            MX8MM_IOMUXC_SAI5_RXD3_GPIO3_IO24          0x00000116
            MX8MM_IOMUXC_SAI5_RXD1_GPIO3_IO22          0x00000116           >;      
};

Devre şemasına bakılınca SAI5_RXD3 ve SAI5_RXD1 hatları J1003 soketine çıktığı için bu hatlara bağlı GPIO3_IO22 ve GPIO3_IO24 çip seçme pini için uygun.

&ecspi2 {    
#address-cells = <1>;    
#size-cells = <0>;   
pinctrl-names = "default";    
pinctrl-0 = <&pinctrl_ecspi2>;  
cs-gpios = <&gpio5 13 0>, <&gpio3 22 0>, <&gpio3 24 0>;    
fsl,spi-num-chipselects = <3>;    
status = "okay";
        spidev@0 {
               compatible = "spidev";
               spi-max-frequency = <12000000>;               reg = <0>;       
};
        mcp23s17_1: gpio@1 {
           compatible = "microchip,mcp23s17"; 
            #gpio-cells = <2>;                          gpio-controller;
                          reg = <1>;                         
microchip,spi-present-mask = <0x01>;
                          spi-max-frequency = <10000000>;
                          #interrupt-cells = <2>;
         };

        mcp23s17_2: gpio@2 {
           compatible = "microchip,mcp23s17";
                          #gpio-cells = <2>;
                          gpio-controller;
                          reg = <2>;
                          microchip,spi-present-mask = <0x01>;
                          spi-max-frequency = <10000000>;
                          #interrupt-cells = <2>;
         };
};

Yukarıdaki düzenlemelerden sonra bir önceki adımdan farklı olarak MCP23S17 driver Kernel kodlarından aktif etmemiz gerekir. Bunun için ilk olarak Kernel kodlarının kök dizininde iken $make menuconfig komutu koşulur ve Device Drivers > Pin controllers sayfasına gidilir. Buradaki  <*> Microchip MCP23xxx I/O expander seçeneği aktif yapılır ve değişiklikler kaydedilerek çıkılır.

       

Yukarıdaki işlemlerden sonra artık sadece device tree kodunun derlenmesi yeterli değildir. mcp23s17 Kernel driver aktif edildiği için kernel baştan derlememiz gerekir. 
$ make ARCH=arm64 CROSS_COMPILE=aarch64-poky-linux- komutu ile derleme yapılır. Derleme sonrasında OBJCOPY arch/arm64/boot/Imag çıktısı kernel imajının yerini gösterir. Son olarak fsl-imx8mm-evk.dtb  ve Image dosyasını cihaza atalım.

3.1 SPI Hattına Bağlanan IC Kontrol Edilmesi

Yukarıdaki yapıda aynı spi hattı üzerinde bir adet kullanıcı katmanına açık spi hattı ve iki adet de mcp23s17 bulunmakta. Kullanıcı katmanına açık spi hattının kontrolü için /dev dizni altına bakmamız yeterli.
               
Görüldüğü üzere kullanıcı katmanına açık spi hattının kurulumu başarılı. Mcp23s17 kurulumlarının sınamak için $cat /sys/kernel/debug/gpio komutunu kullanmamız gerekir. 

Yukarıdaki resimde görüldüğü üzere hem mcp23s17 kurulumu hem de bu IC atadığımız spi çip seçme pinleri doğru, gpio-86, gpio-88. 

mcp23s17 entegresi I/O çoklayıcı olduğu için sisteme kattığı gpio numaralarını yukarıdaki resimden görebiliriz. Örneğin ilk mcp23s17 için aşağıdaki çıktıyı aldık:
            gpiochip6: GPIOs 480-495, parent: spi/spi1.1, mcp23s17.0, can sleep:
Gpio 480 ile 495 arası numaralara sahip giriş yada çıkış olarak kullanılabilecek 16 adet yeni gpio sisteme dahil edildiğini görüyoruz. Bu gpio numaraları üzerinden mcp23s17 istenilen pinleri kontrol edilebilir. 482 nolu gpio yani mcp23s17′ nin 3. pinini çıkış olarak set eden ve sonrasında logik 1 seviyesine çeken örnek  kullanım aşağıdaki gibidir.
        echo 482 > /sys/class/gpio/export
        echo out > /sys/class/gpio/gpio482/direction
        echo 1 > /sys/class/gpio/gpio482/value

Eğer elinizde mcp23s17 olmadan bu örneği yaptıysanız kodlamanın doğruluğunu yine de yapabilir. Fakat kernel katmanından mcp23s17 ulaşamama hatası görürüz.

root@imx8mmevk:~# dmesg | grep “mcp”
[  107.732333] mcp23s08 spi1.2: restoring reg 0x00 from 0x0000 to 0xffff (power-loss?)
[  107.742285] mcp23s08 spi1.2: restoring reg 0x05 from 0x0000 to 0x0808 (power-loss?)
[  107.750244] mcp23s08 spi1.1: restoring reg 0x00 from 0x0000 to 0xffff (power-loss?)
[  107.758220] mcp23s08 spi1.1: restoring reg 0x05 from 0x0000 to 0x0808 (power-loss?)

4. Son

Spi hattı yapılandırması yapıldıktan sonra kullanıcı katmanından istenilen cihazlar çok rahatlıkla kullanılabilir. Her ne kadar örnek olarak mcp23s17 kullanmış olsak da bu IC kullanımını önermiyorum. Aynı isp hattına birden fazla mcp23s17 bağlandığında problem çıkarıyor, tecrübe ile sabittir.

29.09.2020