写简单驱动的准备:

硬件部分:

  • DTS
    • 原理图,知道外设用哪些引脚
    • 写设备树,基础模板,compatabile,硬件引用,从pinctl获取哪些引脚被描述为SPI 1/2/3.......
    • 编译脚本:模板
  • 驱动
    • 驱动框架  1.platform driver结构体,包括 名字和of_match_table,probe和remove函数
    • 驱动血肉:具体的probe和remove函数,file_operation中的操作函数,open read ,wrtie,ioctl
    • 驱动的细节:内核与用户空间的通信手段:mmap,copy_from_user/copy_to_user,定时器,线程等等

驱动基石:

 

设备树模板:

&是引用的意思,是对一个文件的描述的节点进行引用,进行重写:新增,然后是pinctrl的名字,定义spi5的引脚等等,状态,片选引脚,然后是子设备节点,一般是

&spi5 {
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&spi5_pins_a>;
        pinctrl-1 = <&spi5_sleep_pins_a>;
        status = "okay";
        cs-gpios = <&gpioh 5 GPIO_ACTIVE_LOW>, <&gpioz 4 GPIO_ACTIVE_LOW>;
        spidev: icm20608@0{
                compatible = "invensense,icm20608";
                interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
                interrupt-parent = <&gpioz>;
                spi-max-frequency = <8000000>;
                reg = <0>;
        };
        oled: oled@1{
                compatible = "oled_drv";
                spi-max-frequency = <10000000>;
                reg = <1>;
                dc-gpios = <&gpioa 13 GPIO_ACTIVE_HIGH>;
        };
};
在另外的文件是这也定义spi,并配置相关引脚为对应功能的。
arm/boot/dts/stm32mp157-100ask-pinctrl.dtsi这个文件里面有类似的:
spi5: spi@4400b000 { compatible = "st,stm32h7-spi"; reg = <0x4400b000 0x400>; interrupts = <...>; #address-cells = <1>; #size-cells = <0>; status = "disabled"; // 默认关闭 };
&pinctrl { spi5_pins_a: spi5-0 { pins { pinmux = <STM32_PINMUX('H', 6, AF5)>, /* SPI5_SCK */ <STM32_PINMUX('H', 7, AF5)>; /* SPI5_MISO */ bias-disable; drive-push-pull; slew-rate = <2>; }; }; };wwen

驱动骨架:

image

 案例,oled驱动(大部分是抄的)

框架是 probe,完成,主副设备号,设备节点的创建,设备能力:再给节点增加能力:file_operation(各种read ,write,read,ioctl,open,close)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/delay.h>

#define OLED_IOC_INIT      123
#define OLED_IOC_SET_POS   124

#define OLED_CMD   0
#define OLED_DATA  1

static struct spi_device *oled;
static struct gpio_desc *dc_gpio;
static int major;
static struct class *oled_class;
static struct fb_info *myfb_info;
static unsigned int pseudo_palette[16];
static struct task_struct *oled_thread;
static unsigned char *oled_buf;

/* ================= GPIO & SPI ================= */

static void dc_pin_init(void)
{
    gpiod_direction_output(dc_gpio, 1);
}

static void oled_set_dc_pin(int val)
{
    gpiod_set_value(dc_gpio, val);
}

static void spi_write_datas(const unsigned char *buf, int len)
{
    spi_write(oled, buf, len);
}

/* ================= OLED 基础函数 ================= */

static void oled_write_cmd_data(unsigned char data, unsigned char cmd)
{
    oled_set_dc_pin(cmd ? 1 : 0);
    spi_write_datas(&data, 1);
}

static void OLED_DIsp_Set_Pos(int x, int y)
{
    oled_write_cmd_data(0xb0 + y, OLED_CMD);
    oled_write_cmd_data((x & 0x0f), OLED_CMD);
    oled_write_cmd_data(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
}

static int oled_init(void)
{
    oled_write_cmd_data(0xae, OLED_CMD);
    oled_write_cmd_data(0x00, OLED_CMD);
    oled_write_cmd_data(0x10, OLED_CMD);
    oled_write_cmd_data(0x40, OLED_CMD);
    oled_write_cmd_data(0xB0, OLED_CMD);
    oled_write_cmd_data(0x81, OLED_CMD);
    oled_write_cmd_data(0x66, OLED_CMD);
    oled_write_cmd_data(0xa1, OLED_CMD);
    oled_write_cmd_data(0xa6, OLED_CMD);
    oled_write_cmd_data(0xa8, OLED_CMD);
    oled_write_cmd_data(0x3f, OLED_CMD);
    oled_write_cmd_data(0xc8, OLED_CMD);
    oled_write_cmd_data(0xd3, OLED_CMD);
    oled_write_cmd_data(0x00, OLED_CMD);
    oled_write_cmd_data(0xd5, OLED_CMD);
    oled_write_cmd_data(0x80, OLED_CMD);
    oled_write_cmd_data(0xd9, OLED_CMD);
    oled_write_cmd_data(0x1f, OLED_CMD);
    oled_write_cmd_data(0xda, OLED_CMD);
    oled_write_cmd_data(0x12, OLED_CMD);
    oled_write_cmd_data(0xdb, OLED_CMD);
    oled_write_cmd_data(0x30, OLED_CMD);
    oled_write_cmd_data(0x8d, OLED_CMD);
    oled_write_cmd_data(0x14, OLED_CMD);
    oled_write_cmd_data(0xaf, OLED_CMD);
    return 0;
}

/* ================= 字符设备 ================= */

static long oled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int x, y;

    switch (cmd) {
    case OLED_IOC_INIT:
        dc_pin_init();
        oled_init();
        break;

    case OLED_IOC_SET_POS:
        x = arg & 0xff;
        y = (arg >> 8) & 0xff;
        OLED_DIsp_Set_Pos(x, y);
        break;
    }
    return 0;
}
static ssize_t oled_write(struct file *filp,
                          const char __user *buf,
                          size_t count, loff_t *f_pos)
{
    char *kbuf;
    kbuf = kmalloc(count, GFP_KERNEL);
    if (!kbuf)
        return -ENOMEM;
    if (copy_from_user(kbuf, buf, count)) {
        kfree(kbuf);
        return -EFAULT;
    }
    oled_set_dc_pin(1);
    spi_write_datas(kbuf, count);
    kfree(kbuf);
    return count;
}
static const struct file_operations oled_ops = {
    .owner          = THIS_MODULE,
    .write          = oled_write,
    .unlocked_ioctl = oled_ioctl,
};
static inline unsigned int chan_to_field(unsigned int chan,
                                         struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}
static int mylcd_setcolreg(unsigned regno,
                           unsigned red, unsigned green,
                           unsigned blue, unsigned transp,
                           struct fb_info *info)
{
    unsigned int val;

    if (regno < 16) {
        u32 *pal = info->pseudo_palette;
        val  = chan_to_field(red,   &info->var.red);
        val |= chan_to_field(green, &info->var.green);
        val |= chan_to_field(blue,  &info->var.blue);

        pal[regno] = val;
    }
    return 0;
}
static struct fb_ops myfb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg = mylcd_setcolreg,
    .fb_fillrect  = cfb_fillrect,
    .fb_copyarea  = cfb_copyarea,
    .fb_imageblit = cfb_imageblit,
};
static int oled_thread_func(void *param)
{
    while (!kthread_should_stop()) {

        int i;
        for (i = 0; i < 8; i++) {
            OLED_DIsp_Set_Pos(0, i);
            oled_set_dc_pin(1);
            spi_write_datas(&oled_buf[i * 128], 128);
        }

        msleep(100);
    }
    return 0;
}
/* ================= SPI Driver ================= */

static const struct of_device_id oled_dt_ids[] = {
    { .compatible = "oled_drv" },
    { }
};
MODULE_DEVICE_TABLE(of, oled_dt_ids);
static int oled_probe(struct spi_device *spi)
{
    dma_addr_t phy_addr;
    oled = spi;
    major = register_chrdev(0, "spi_oled", &oled_ops);
    oled_class = class_create(THIS_MODULE, "spi_oled");
    device_create(oled_class, NULL, MKDEV(major, 0), NULL, "spi_oled");
    dc_gpio = gpiod_get(&spi->dev, "dc", GPIOD_OUT_HIGH);
    myfb_info = framebuffer_alloc(0, NULL);
    myfb_info->var.xres = 128;
    myfb_info->var.yres = 64;
    myfb_info->var.bits_per_pixel = 1;
    strcpy(myfb_info->fix.id, "spi_oled");
    myfb_info->fix.smem_len = 128 * 64 / 8;
    dma_set_coherent_mask(&spi->dev, DMA_BIT_MASK(32));
    myfb_info->screen_base =
        dma_alloc_wc(&spi->dev,
                     myfb_info->fix.smem_len,
                     &phy_addr,
                     GFP_KERNEL);

    myfb_info->fix.smem_start = phy_addr;
    myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
    myfb_info->fix.visual = FB_VISUAL_MONO10;
    myfb_info->fix.line_length = 128 / 8;
    myfb_info->fbops = &myfb_ops;
    myfb_info->pseudo_palette = pseudo_palette;
    register_framebuffer(myfb_info);
  oled_buf = kmalloc(1024, GFP_KERNEL);
  oled_init();
  oled_thread = kthread_run(oled_thread_func, NULL, "oled_thread");
return 0;
}
static int oled_remove(struct spi_device *spi)
{
    kthread_stop(oled_thread);
    kfree(oled_buf);
    unregister_framebuffer(myfb_info);
    dma_free_wc(&spi->dev,
                myfb_info->fix.smem_len,
                myfb_info->screen_base,
                myfb_info->fix.smem_start);
    framebuffer_release(myfb_info);
    gpiod_put(dc_gpio);
    device_destroy(oled_class, MKDEV(major, 0));
    class_destroy(oled_class);
    unregister_chrdev(major, "spi_oled");
    return 0;
}

static struct spi_driver oled_drv = {
    .driver = {
        .name = "oled_drv",
        .of_match_table = oled_dt_ids,
    },
    .probe  = oled_probe,
    .remove = oled_remove,
};
module_spi_driver(oled_drv);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qzc");
MODULE_DESCRIPTION("SPI OLED Driver");

 

留个坑:如何比较具体的写驱动,就是需要了解各种API接口。

如何抄别人的驱动:

image

或者收集模板,找别人要,或者买。当然要回报别人的,hh。

 如图。引发一个问题,如何的去阅读内核源码。sourceinsight 或者vscode + ssh + 

。或者移植:移植只需要改设备树,改上面的就行。

填充内容:

一:阅读模块芯片手册

通信方式:I2C UART SPI 

这种除去屏幕一般都比较容易写,屏幕也是那种初始化的,或者操作原理,英文比较多,不好写,其余都是比较简单的,都是主从机,遵守一种通信协议,像之前工作的指纹图像传感器,

就规定了工作模式(正常,低功耗),之间的切换,这些就规定了写什么寄存器,写什么值,然后是读图,就是写长宽(两种尺寸),然后就是增益,图像传感器的原理,光学,触摸,有个判决器,增益越大,值大,就能通过判决其,相关值就一或者0。

复杂传感器

像什么网络设备,wifi ,蓝牙,usb,这种一般是除了硬件操作,还有非常复杂的协议,多种状态,状态切换,流量控制,信息格式,而且有多种。

二:驱动基础

image

常见问题:

驱动没加载的原因:

1.引脚被占用    比如 pinctrl某组如SPI 1的 一个引脚在其他地方被使用了。

2.probe函数有误  牢记:只有设备树中的compatible 和 driver 的of match table 匹配才会触发 probe,最基本的要完成设备号的获取,设备节点的创建,然后就算特定框架,比如USB 鼠标,除了usb通信,需要设置URB,设置接口、端点,还有URB,input子系统

,屏幕有frame_buffer 框架

3.具体的操作函数实现有误

调试手段:

1.dmesg | tail -10

2.示波器

3.printk打印

……

当然还有。

 

 

更多的案例补充和分析、

[留个坑]

……