Собственный SPI драйвер для Linux на Raspberry Pi

В распространенных дистрибутивах для Raspberry Pi уже есть драйвер SPI. Но иногда, по тем или иным причинам, нужно сделать свой. Например если вы хотите сделать драйвер для spi дисплея и пофиг что он уже давно есть готовый. Мы не ищем легких путей.

Для того, чтобы подсунуть ядру свой драйвер SPI нужно поправить файл порта для raspberry. Для других плат этот файл будет отличаться, во всем остальном же статья подойдет для любого встраиваемого решения.

Сборка ядра это отдельная тема. В этой статье я соберу ядро с конфигом от raspbian
Сначала на работающей плате скопируйте конфиг.

zcat /proc/config.gz > .config

Сливаем исходники ядра и копируем туда полученный конфиг.

git clone --depth 1 git://github.com/raspberrypi/linux.git

Нам нужно поправить файл /arch/arm/mach-bcm2708/bcm2708.c
В нем вместо драйвера spidev будем подсовывать наш — spitest. Отредактируйте файл так:

{
                .modalias = "spitest",
                .max_speed_hz = 500000,
                .bus_num = 0,
                .chip_select = 1,
                .mode = SPI_MODE_0,
}

Будем считать, что у вас уже есть кросс-компилятор. Собираем ядро.

make ARCH=arm CROSS_COMPILE=${CCPREFIX} oldconfig

Тут в переменной ${CCPREFIX} должен быть префикс вашего компилятора.
Идем попить чайку.
Ну или можем тем временем набросать модуль, который и будет драйвером. Это конечно название слишком пафосное, ибо уметь он будет — ничего.
Воспользуемся модулем из статьи о потоках в ядре. И в созданном потоке будем отправлять устройству посылку из десяти 0x55.

Для работы с SPI нам нужен заголовочный файл spi.h. Также заведем указатель на устройство SPI и буфер для данных.

#include <linux/spi/spi.h>
struct spi_device *spi_dev;

const unsigned char buffer[] = {0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55}; 

Создание драйвера для SPI устройства идет аналогично любому драйверу Linux. Слава богу разработчики ядра придерживаются определенных правил.
Для создания минимального драйвера SPI нужно объявить структуру с описанием и функции подключения/отключения.

int spi_drv_probe(struct spi_device *spi);
int spi_drv_remove(struct spi_device *spi);

struct spi_driver spi_drv = {
        .driver = {
                .name = "spitest",
                .owner = THIS_MODULE,
        },
        .probe = spi_drv_probe,
        .remove = spi_drv_remove,
};

Тут должно быть все понятно. Важно чтобы имя совпадала с указанным в файле ядра, который мы правили.

В функции инициализации нужно зарегистрировать драйвер.

static int __init blablamod_init( void ) { 
        int ret;        
        printk(KERN_NOTICE "BlablaModule loaded!\n" ); 

        ret = spi_register_driver(&spi_drv);
        if (ret < 0)
        {
                printk(KERN_ALERT "Cant register spi dev.\n");
                return ret;
        }
        task = kthread_run(&thread_function,(void *)0xAA,"Blablathread");
        return 0; 
}

Также мы создаем поток, в котором будет циклически отправляться сообщение.
В функции выгрузки модуля соответственно выбрасываем созданный нами драйвер.

static void __exit blablamod_exit( void ) { 
        printk(KERN_NOTICE "BlablaModule unloaded!\n" );
        kthread_stop(task); 
        spi_unregister_driver(&spi_drv);
}

В функции подключения настраиваем SPI и отправляем первую посылку. Функция отключения весьма символична и не делает ничего.

int spi_drv_probe(struct spi_device *spi)
{
        int ret;

        printk(KERN_NOTICE "spi drv probe!\n" );

        spi->bits_per_word = 8;
        spi->mode = SPI_MODE_0;
        spi->max_speed_hz = 500000;
        
        ret = spi_setup(spi);
        if (ret < 0)
                return ret;

        spi_dev = spi;

        spi_write(spi, buffer, sizeof(buffer)); 

        return 0;
}

int spi_drv_remove(struct spi_device *spi)
{
        printk(KERN_NOTICE "spi drv remove!\n" );
        return 0;
}

Поток практически полность повторяет описанный в статье о потоках в ядре. За исключением строки с посылкой данный.

int thread_function(void *data)
{
    unsigned long tm = jiffies + msecs_to_jiffies(1000);
    printk(KERN_NOTICE "Blablamod: %s(): data = %d.\n", __func__, (int)data); 
    while(!kthread_should_stop()){
        if (time_after(jiffies, tm))
        {
            tm = jiffies + msecs_to_jiffies(1000);
            printk(KERN_NOTICE "Blablamod: %s(): %lu.\n", __func__, jiffies); 

            spi_write(spi_dev, buffer, sizeof(buffer)); 
        }
        schedule();
    }

    return 0;
}

Как не трудно догадаться, данный пересылаются функцией spi_write, которой передается указатель на устройство, который мы сохранили при подключении в глобальную переменную spi_dev, а также буфер с данными и его длина.
Компилируем модуль и инсмодим.
Получается вот так.

Код доступен на GitHub.

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий